From a37ea9024646a2814bde69446ad5fca111a3d82e Mon Sep 17 00:00:00 2001 From: zhuxianglong <56494565@qq.com> Date: Tue, 21 Jan 2025 18:10:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=89=93=E7=82=B9=E3=80=81Ht?= =?UTF-8?q?tp=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 10 ++- go.sum | 9 +++ point/point.go | 46 ++++++++++++++ test/point_test.go | 19 ++++++ utils/ahttp/ahttp.go | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 point/point.go create mode 100644 test/point_test.go create mode 100644 utils/ahttp/ahttp.go diff --git a/go.mod b/go.mod index 531878d..ae5e9e6 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,12 @@ module gitea.weitiangame.com/sdk/wt-game go 1.20 -require github.com/go-resty/resty/v2 v2.16.3 +require ( + github.com/go-resty/resty/v2 v2.16.3 + go.uber.org/zap v1.27.0 +) -require golang.org/x/net v0.33.0 // indirect +require ( + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/net v0.33.0 // indirect +) diff --git a/go.sum b/go.sum index e1499ca..920aedb 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,14 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/go-resty/resty/v2 v2.16.3 h1:zacNT7lt4b8M/io2Ahj6yPypL7bqx9n1iprfQuodV+E= github.com/go-resty/resty/v2 v2.16.3/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/point/point.go b/point/point.go new file mode 100644 index 0000000..e9b2f20 --- /dev/null +++ b/point/point.go @@ -0,0 +1,46 @@ +package point + +import ( + "gitea.weitiangame.com/sdk/wt-game/utils/ahttp" + "github.com/go-resty/resty/v2" + "go.uber.org/zap" + "sync" +) + +var ( + instance *Point + once sync.Once +) + +type Point struct { + clientID string + logger *zap.Logger + ahttp *resty.Request +} + +// New returns the singleton instance of Point. +func New(clientID string, logger *zap.Logger) *Point { + once.Do(func() { + instance = &Point{ + clientID: clientID, + logger: logger, + } + + if instance.logger == nil { + var err error + instance.logger, err = zap.NewProduction() + if err != nil { + panic(err) + } + } + + instance.ahttp = ahttp.New(nil).SetLog(instance.logger).Client() + }) + + return instance +} + +// Event sends an event to the specified URL. +func (p *Point) Event(data map[string]interface{}) (*resty.Response, error) { + return p.ahttp.SetBody(data).Post("https://e.tapdb.net/v2/event") +} diff --git a/test/point_test.go b/test/point_test.go new file mode 100644 index 0000000..194c557 --- /dev/null +++ b/test/point_test.go @@ -0,0 +1,19 @@ +package test + +import ( + "fmt" + "gitea.weitiangame.com/sdk/wt-game/point" + "testing" +) + +var pointSdk = point.New("your-client-id", nil) + +// TestOrderSign 测试订单签名 +func TestPoint(t *testing.T) { + event, err := pointSdk.Event(map[string]interface{}{ + "test": "test", + }) + + fmt.Println(event) + fmt.Println(err) +} diff --git a/utils/ahttp/ahttp.go b/utils/ahttp/ahttp.go new file mode 100644 index 0000000..3414215 --- /dev/null +++ b/utils/ahttp/ahttp.go @@ -0,0 +1,167 @@ +package ahttp + +import ( + "crypto/tls" + "github.com/go-resty/resty/v2" + "go.uber.org/zap" + "net" + "net/http" + "runtime" + "sync" + "time" +) + +// Config 包含 HTTP 客户端的配置选项 +// Config contains the configuration options for the HTTP client +type Config struct { + MaxIdleConnections int // 连接池的最大空闲连接数 / Maximum idle connections in the connection pool + IdleConnectionTimeout time.Duration // 空闲连接超时时间 / Timeout for idle connections + DisableCompression bool // 禁用压缩 / Disable compression + DisableKeepAlives bool // 禁用 keep-alive / Disable keep-alive + InsecureSkipVerify bool // 跳过 TLS 证书验证 / Skip TLS certificate verification + Timeout time.Duration // 总超时时间 / Total timeout duration + TLSHandshakeTimeout time.Duration // TLS 握手超时时间 / Timeout for TLS handshake + ExpectContinueTimeout time.Duration // 100-continue 超时时间 / Timeout for 100-continue + MaxConnectionsPerHost int // 每主机的最大连接数 / Maximum connections per host + RetryAttempts int // 请求重试次数 / Number of retry attempts for failed requests + DialerTimeout time.Duration // Dialer 的连接超时时间 / Dialer connection timeout + DialerKeepAlive time.Duration // Dialer 的 Keep-Alive 时间 / Dialer keep-alive time +} + +// HttpClient 是对 Resty 客户端的封装,支持自定义配置和日志 / HttpClient is a wrapper for the Resty client with custom configuration and logging support +type HttpClient struct { + httpClient *resty.Client // Resty 客户端实例 / Resty client instance + httpTransport *http.Transport // HTTP 传输层配置 / HTTP transport layer configuration + logger *zap.Logger // 日志记录器 / Logger instance + config *Config +} + +var ( + singletonClient *HttpClient + once sync.Once +) + +// New 创建一个新的 HTTP 客户端实例 / NewHttpClient creates a new HTTP client instance +func New(config *Config) *HttpClient { + once.Do(func() { + if config == nil { + config = defaultConfig() + } + + singletonClient = &HttpClient{ + httpClient: newRestyClient(config), + httpTransport: newTransport(config), + config: config, + } + }) + return singletonClient +} + +// newRestyClient 配置 Resty 客户端 / Configures the Resty client +func newRestyClient(config *Config) *resty.Client { + client := resty.NewWithClient(&http.Client{ + Transport: newTransport(config), + Timeout: config.Timeout, + }) + + client.SetRetryCount(config.RetryAttempts) + client.SetRetryWaitTime(1 * time.Second) + client.SetRetryMaxWaitTime(10 * time.Second) + + return client +} + +// defaultConfig 返回默认的配置值 / Returns the default configuration values +func defaultConfig() *Config { + return &Config{ + MaxIdleConnections: runtime.GOMAXPROCS(0) * 200, + IdleConnectionTimeout: 120 * time.Second, + DisableCompression: false, + Timeout: 90 * time.Second, + TLSHandshakeTimeout: 30 * time.Second, + ExpectContinueTimeout: 2 * time.Second, + MaxConnectionsPerHost: runtime.GOMAXPROCS(0) * 100, + RetryAttempts: 3, + DialerTimeout: 15 * time.Second, // 默认 Dialer 超时时间 + DialerKeepAlive: 60 * time.Second, // 默认 Dialer Keep-Alive 时间 + } +} + +// newTransport 创建并配置 HTTP 传输层 / Creates and configures the HTTP transport +func newTransport(config *Config) *http.Transport { + tcpDialer := &net.Dialer{ + Timeout: config.DialerTimeout, + KeepAlive: config.DialerKeepAlive, + DualStack: true, + } + + return &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: tcpDialer.DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: config.MaxIdleConnections, + MaxIdleConnsPerHost: config.MaxConnectionsPerHost, + IdleConnTimeout: config.IdleConnectionTimeout, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: config.InsecureSkipVerify, + MinVersion: tls.VersionTLS12, + CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256}, + }, + TLSHandshakeTimeout: config.TLSHandshakeTimeout, + ExpectContinueTimeout: config.ExpectContinueTimeout, + DisableCompression: config.DisableCompression, + } +} + +// SetLog 设置日志记录器 / SetLog sets the logger +func (h *HttpClient) SetLog(logger *zap.Logger) *HttpClient { + h.logger = logger + h.httpClient.OnBeforeRequest(func(c *resty.Client, r *resty.Request) error { + logger.Info("Request", + zap.String("URL", r.URL), + zap.String("Method", r.Method), + zap.Any("Headers", r.Header), + zap.Any("Cookies", r.Cookies), + zap.Any("FormData", r.FormData), + zap.Any("QueryParam", r.QueryParam), + zap.Any("Body", r.Body), + zap.Duration("Timeout", h.config.Timeout)) + return nil + }) + + h.httpClient.OnAfterResponse(func(c *resty.Client, r *resty.Response) error { + if r.StatusCode() >= 400 { + logger.Error("Request failed", + zap.Int("StatusCode", r.StatusCode()), + zap.String("URL", r.Request.URL), + zap.String("Method", r.Request.Method), + zap.Any("Headers", r.Header), + zap.Any("Cookies", r.Cookies), + zap.Any("FormData", r.Request.FormData), + zap.Any("QueryParam", r.Request.QueryParam), + zap.ByteString("Body", r.Body()), + zap.Duration("Duration", time.Since(r.Request.Time)), + ) + } else { + logger.Info("Response", + zap.Int("StatusCode", r.StatusCode()), + zap.String("URL", r.Request.URL), + zap.String("Method", r.Request.Method), + zap.Any("Headers", r.Header), + zap.Any("Cookies", r.Cookies), + zap.Any("FormData", r.Request.FormData), + zap.Any("QueryParam", r.Request.QueryParam), + zap.ByteString("Body", r.Body()), + zap.Duration("Duration", time.Since(r.Request.Time)), + ) + } + return nil + }) + return h +} + +// Client 返回 Resty 客户端的请求实例 / Returns a request instance of the Resty client +func (h *HttpClient) Client() *resty.Request { + h.httpClient.SetHeader("User-Agent", "ahttp") + return h.httpClient.R() +}