package request import ( "crypto/tls" "encoding/json" "net/http" "net/url" "time" "ckwk/pkg/log" "go.uber.org/zap/zapcore" "resty.dev/v3" ) var ( NoRedirectClient *resty.Client RestyClient *resty.Client ) const ( DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0" DefaultTimeout = 30 * time.Second DefaultDebugBody = 4 * 1024 ) type Config struct { Timeout time.Duration Proxy string Debug bool UserAgent string VerifySSL bool } func DefaultConfg() *Config { return &Config{ Timeout: DefaultTimeout, UserAgent: DefaultUserAgent, VerifySSL: true, Debug: false, } } func normalizeConfig(cfg *Config) *Config { defaults := DefaultConfg() if cfg == nil { return defaults } if cfg.Timeout <= 0 { cfg.Timeout = defaults.Timeout } if cfg.UserAgent == "" { cfg.UserAgent = defaults.UserAgent } return cfg } func buildTransport(cfg *Config) *http.Transport { baseTransport, ok := http.DefaultTransport.(*http.Transport) if !ok { baseTransport = &http.Transport{} } transport := baseTransport.Clone() transport.TLSClientConfig = &tls.Config{ InsecureSkipVerify: !cfg.VerifySSL, } transport.Proxy = http.ProxyFromEnvironment if cfg.Proxy != "" { proxyURL, err := url.Parse(cfg.Proxy) if err == nil { transport.Proxy = http.ProxyURL(proxyURL) } else { log.Warn("代理地址解析失败,已忽略") } } return transport } func ApplyConfig(client *resty.Client, cfg *Config) { cfg = normalizeConfig(cfg) client.SetHeader("User-Agent", cfg.UserAgent) client.SetTimeout(cfg.Timeout) client.SetRetryCount(3) client.SetTransport(buildTransport(cfg)) client.SetDebug(cfg.Debug) client.SetDebugBodyLimit(DefaultDebugBody) } // NewClient 创建一个标准的 Resty 客户端 func NewClient(cfg *Config) *resty.Client { client := resty.New() client.OnDebugLog(func(debugLog *resty.DebugLog) { fields := map[string]any{ "request": map[string]any{ "host": debugLog.Request.Host, "uri": debugLog.Request.URI, "method": debugLog.Request.Method, "proto": debugLog.Request.Proto, "header": log.SanitizeHeaders(debugLog.Request.Header), "attempt": debugLog.Request.Attempt, "body": log.SanitizeBody(debugLog.Request.Header.Get("Content-Type"), debugLog.Request.Body), }, "response": map[string]any{ "statusCode": debugLog.Response.StatusCode, "status": debugLog.Response.Status, "proto": debugLog.Response.Proto, "receivedAt": debugLog.Response.ReceivedAt.Format(time.RFC3339Nano), "durationMs": debugLog.Response.Duration.Milliseconds(), "size": debugLog.Response.Size, "header": log.SanitizeHeaders(debugLog.Response.Header), "body": log.SanitizeBody(debugLog.Response.Header.Get("Content-Type"), debugLog.Response.Body), }, } if debugLog.TraceInfo != nil { if traceJSON, err := json.Marshal(debugLog.TraceInfo); err == nil { fields["trace"] = json.RawMessage(traceJSON) } } log.Capture(zapcore.DebugLevel, "resty", "outbound exchange", fields) }) client.SetDebugLogFormatter(nil) ApplyConfig(client, cfg) return client } // NewNoRedirectClient 创建一个禁止重定向的客户端 func NewNoRedirectClient(cfg *Config) *resty.Client { client := NewClient(cfg) client.SetRedirectPolicy( resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }), ) return client }