diff --git a/go.mod b/go.mod index 0df0bba..d7bc24b 100644 --- a/go.mod +++ b/go.mod @@ -8,10 +8,10 @@ require ( git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20250504055908-8d68e6106ea9 git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250321102712-1cbfbe959740 github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 - github.com/go-resty/resty/v2 v2.16.5 github.com/mssola/user_agent v0.6.0 github.com/tidwall/gjson v1.18.0 go.uber.org/zap v1.27.0 + resty.dev/v3 v3.0.0-beta.2 ) require ( diff --git a/go.sum b/go.sum index d70fefa..b182199 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,13 @@ -git.zhangdeman.cn/gateway/validate v0.0.0-20250331140536-3477e2ec106d h1:rEnbN/j9/xqDAqdjAwIxE1UxoQoZVBl5Qdj+R/zEdCQ= -git.zhangdeman.cn/gateway/validate v0.0.0-20250331140536-3477e2ec106d/go.mod h1:Bwb9ks9pUp0Em+7CDTCUfnI+o6jRxMLifSHICsWmWXU= -git.zhangdeman.cn/gateway/validate v0.0.0-20250428044112-b3b4cc8e571e h1:5zb/LEL24dk6/yEU+5RQ3PO1jbHpszDumDw2buVV9Rk= -git.zhangdeman.cn/gateway/validate v0.0.0-20250428044112-b3b4cc8e571e/go.mod h1:UuNLo2MniMXCJPr5AT+31H/3VwxyzmpZBQnr1rffMos= git.zhangdeman.cn/gateway/validate v0.0.0-20250506091017-1d2dfd10f600 h1:o+ggli1YCXiiB/SwlPqb34DePkP6mh+KqhQ/lYhBISE= git.zhangdeman.cn/gateway/validate v0.0.0-20250506091017-1d2dfd10f600/go.mod h1:DHrM753rWJ7Dvrm7CqWq1Q05jMhiGPNMKXYcKp11BHg= -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250328040304-7e4a6f9f148c h1:cl3gQGXQpJ8ugDs0C/hQLfcvF4lGBm5BeABLvROFDoM= -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250328040304-7e4a6f9f148c/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250425024726-cc17224cb995 h1:LmPRAf0AsxRVFPibdpZR89ajlsz8hof2IvMMyTqiEq4= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250425024726-cc17224cb995/go.mod h1:5p8CEKGBxi7qPtTXDI3HDmqKAfIm5i/aBWdrbkbdNjc= -git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20250401073534-5d4788e0fd86 h1:RvEOgfkutynvRjIbSWoB7tHCjJ8wl62O3rqUb9OuUd4= -git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20250401073534-5d4788e0fd86/go.mod h1:XR0ElBVxzFGVh3aE9KOSEbjZLO/VRw2vxxFJVKWCuNk= -git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20250428024209-cd85d80da6e7 h1:mDt8CqHHymPaDsaXJBl13fh+cqTjf4qgUhwAHMUFI1M= -git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20250428024209-cd85d80da6e7/go.mod h1:24/T5zwlvo9Cgx5tzHbBZlSpcKDZYbEtKtXB0oX4p7E= git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20250429065800-fc340b9417cf h1:xCPM3U6i62UvLo9VNvDP45Ue3dPl7ratHu1rSEJRE2k= git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20250429065800-fc340b9417cf/go.mod h1:onY+qrB+Uwfuv75JlgHlGdkirAfYcINrvCashtVoBX0= -git.zhangdeman.cn/zhangdeman/json_filter v0.0.0-20250329080213-587a9bbe9dbc h1:jqaoktpx+gLiBLGpW/X03ZZ+oIsrQIlvimLSaHQo0iA= -git.zhangdeman.cn/zhangdeman/json_filter v0.0.0-20250329080213-587a9bbe9dbc/go.mod h1:KuVC2+wQ4dXMgqy4RF+wrsDxa+FWYuc7x17Y+/2b1YE= git.zhangdeman.cn/zhangdeman/json_filter v0.0.0-20250506090824-b0db389ca3d7 h1:fBf+lN63axb8zYwqCVbexzPm8x9W4aWxzzIJIXIxS2U= git.zhangdeman.cn/zhangdeman/json_filter v0.0.0-20250506090824-b0db389ca3d7/go.mod h1:KuVC2+wQ4dXMgqy4RF+wrsDxa+FWYuc7x17Y+/2b1YE= git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 h1:gUDlQMuJ4xNfP2Abl1Msmpa3fASLWYkNlqDFF/6GN0Y= git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0/go.mod h1:VHb9qmhaPDAQDcS6vUiDCamYjZ4R5lD1XtVsh55KsMI= -git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd h1:q7GG14qgXKB4MEXQFOe7/UYebsqMfPaSX80TcPdOosI= -git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd/go.mod h1:+D6uPSljwHywjVY5WSBY4TRVMj26TN5f5cFGEYMldjs= -git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20250428041157-135850ee8a58 h1:fTkmucGaUoKocoX+ASM4AnwsAVJOtOOLUFSqA+uwVzg= -git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20250428041157-135850ee8a58/go.mod h1:Ig3GZC2hJDkQp7F8Tm53GvMWLh9bdbbauow/vxGO4YA= git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20250504055908-8d68e6106ea9 h1:/GLQaFoLb+ciHOtAS2BIyPNnf4O5ME3AC5PUaJY9kfs= git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20250504055908-8d68e6106ea9/go.mod h1:ABJ655C5QenQNOzf7LjCe4sSB52CXvaWLX2Zg4uwDJY= git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e h1:Q973S6CcWr1ICZhFI1STFOJ+KUImCl2BaIXm6YppBqI= @@ -40,8 +24,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= -github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= -github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= @@ -54,8 +36,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= -github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= -github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -102,24 +84,14 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.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/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -127,3 +99,5 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8X gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +resty.dev/v3 v3.0.0-beta.2 h1:xu4mGAdbCLuc3kbk7eddWfWm4JfhwDtdapwss5nCjnQ= +resty.dev/v3 v3.0.0-beta.2/go.mod h1:OgkqiPvTDtOuV4MGZuUDhwOpkY8enjOsjjMzeOHefy4= diff --git a/httpclient/abstract/IRateLimiter.go b/httpclient/abstract/IRateLimiter.go new file mode 100644 index 0000000..cce0457 --- /dev/null +++ b/httpclient/abstract/IRateLimiter.go @@ -0,0 +1,13 @@ +// Package abstract ... +// +// Description : abstract ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2025-05-07 21:13 +package abstract + +// RateLimiter v2 流控口约束, v3移除了, 人工补齐 +type RateLimiter interface { + Allow() bool +} diff --git a/httpclient/client.go b/httpclient/client.go index 097b7cb..273e969 100644 --- a/httpclient/client.go +++ b/httpclient/client.go @@ -20,7 +20,7 @@ import ( "git.zhangdeman.cn/zhangdeman/network/httpclient/log" "git.zhangdeman.cn/zhangdeman/network/httpclient/validate" "git.zhangdeman.cn/zhangdeman/serialize" - "github.com/go-resty/resty/v2" + "resty.dev/v3" ) // NewHttpClient 获取http client @@ -97,18 +97,17 @@ func NewHttpClient(reqConfig *define.Request, reqOption *RequestOption) (*HttpCl reqConfig.Header = make(map[string]any) } if ua, exist := reqConfig.Header[consts.HeaderKeyUserAgent.String()]; !exist || nil == ua || fmt.Sprintf("%v", ua) == "" { - reqConfig.Header[consts.HeaderKeyUserAgent.String()] = "resty-v2@network/httpclient" + reqConfig.Header[consts.HeaderKeyUserAgent.String()] = "resty-v3@network/httpclient" } - restyClient, restyRequest := NewRestyClient(reqConfig) + restyClient, restyRequest := NewRestyClient(reqConfig, reqOption) + defer restyClient.Close() + hc := &HttpClient{ - Client: restyClient, - request: restyRequest, - reqOption: reqOption, - reqCfg: reqConfig, - http4xxHandler: make([]define.Http4xxHandler, 0), - http5xxHandler: make([]define.Http5xxHandler, 0), - httpBusinessErrorHandler: make([]define.HttpBusinessErrorHandler, 0), - requestFinishHandler: make([]define.RequestFinishHandler, 0), + Client: restyClient, + request: restyRequest, + reqOption: reqOption, + reqCfg: reqConfig, + requestFinishHandler: make([]define.RequestFinishHandler, 0), } hc.OnRequestFinish(func(req *define.Request, rep *define.Response) { if rep.IsSuccess { @@ -129,41 +128,10 @@ func NewHttpClient(reqConfig *define.Request, reqOption *RequestOption) (*HttpCl // Date : 15:27 2024/5/31 type HttpClient struct { *resty.Client - request *resty.Request - reqOption *RequestOption - reqCfg *define.Request - http4xxHandler []define.Http4xxHandler - http5xxHandler []define.Http5xxHandler - httpBusinessErrorHandler []define.HttpBusinessErrorHandler - requestSendErrorHandler []define.RequestSendErrorHandler - requestFinishHandler []define.RequestFinishHandler -} - -// OnResponse4xx 4xx处理逻辑 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 18:20 2024/6/1 -func (hc *HttpClient) OnResponse4xx(handlerList ...define.Http4xxHandler) { - hc.http4xxHandler = append(hc.http4xxHandler, handlerList...) -} - -// OnResponse5xx 5xx处理逻辑 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 18:21 2024/6/1 -func (hc *HttpClient) OnResponse5xx(handlerList ...define.Http5xxHandler) { - hc.http5xxHandler = append(hc.http5xxHandler, handlerList...) -} - -// OnResponseBusinessError 业务错误出路逻辑 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 18:22 2024/6/1 -func (hc *HttpClient) OnResponseBusinessError(handlerList ...define.HttpBusinessErrorHandler) { - hc.httpBusinessErrorHandler = append(hc.httpBusinessErrorHandler, handlerList...) + request *resty.Request + reqOption *RequestOption + reqCfg *define.Request + requestFinishHandler []define.RequestFinishHandler } // OnRequestFinish 请求完成事件 @@ -175,37 +143,6 @@ func (hc *HttpClient) OnRequestFinish(handlerList ...define.RequestFinishHandler hc.requestFinishHandler = append(hc.requestFinishHandler, handlerList...) } -// getRequestValidateMiddleware 请求验证的Middleware -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 15:40 2024/5/31 -func (hc *HttpClient) getRequestValidateMiddleware() resty.RequestMiddleware { - return func(client *resty.Client, request *resty.Request) error { - return nil - } -} - -// getResponseValidateMiddleware 获取响应数据验证的middleware -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 15:42 2024/5/31 -func (hc *HttpClient) getResponseValidateMiddleware() resty.ResponseMiddleware { - return func(client *resty.Client, response *resty.Response) error { - return nil - } -} - -// SetRestyClient 设置client -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 15:54 2024/5/31 -func (hc *HttpClient) SetRestyClient(restyClient *resty.Client) { - hc.Client = restyClient -} - // GetRestyClient 获取 resty client // // Author : go_developer@163.com<白茶清欢> @@ -341,8 +278,6 @@ func (hc *HttpClient) Request() *define.Response { // // Date : 18:47 2024/10/9 func (hc *HttpClient) requestBackendApi() *define.Response { - hc.Client.OnBeforeRequest(hc.getRequestValidateMiddleware()) // 请求参数验证中间件必注册 - hc.Client.OnAfterResponse(hc.getResponseValidateMiddleware()) // 响应验证中间件必注册 var ( err error ) @@ -365,7 +300,7 @@ func (hc *HttpClient) requestBackendApi() *define.Response { response.RequestCount = i + 1 if response.RestyResponse, err = hc.request.Send(); nil != err { errType := define.RequestFailTypeSend - if err.Error() == resty.ErrRateLimitExceeded.Error() { + if err.Error() == define.ErrRateLimitExceeded.Error() { // 命中限流 errType = define.RequestFailTypeRateLimit } else if netErr, ok := err.(net.Error); ok { @@ -385,11 +320,6 @@ func (hc *HttpClient) requestBackendApi() *define.Response { "err_type": errType, "err_msg": err.Error(), }, hc.reqCfg) - if errType == define.RequestFailTypeRateLimit { - // 命中限流就不重试了 - log.RecordDebug("请求命中限流, 忽略重试策略, 不进行重试", nil, hc.reqCfg) - break - } if errType == define.RequestFailTypeTimeoutError && !hc.reqOption.ResponseParser.NeedRetry(hc.reqCfg, response) { // 未配置超时重试 diff --git a/httpclient/define/error.go b/httpclient/define/error.go index bee6f77..f9b327b 100644 --- a/httpclient/define/error.go +++ b/httpclient/define/error.go @@ -16,3 +16,8 @@ var ( ErrMethodIsEmpty = errors.New("METHOD_IS_EMPTY") // 请求 方法为空 ErrMethodIsNotSupport = errors.New("METHOD_IS_NOT_SUPPORT") // 请求 method不支持 ) + +var ( + // ErrRateLimitExceeded v2版本中命中流控的错误信息 + ErrRateLimitExceeded = errors.New("rate limit exceeded") +) diff --git a/httpclient/define/request.go b/httpclient/define/request.go index e675e2e..4fbdb3f 100644 --- a/httpclient/define/request.go +++ b/httpclient/define/request.go @@ -9,7 +9,6 @@ package define import ( "context" - "github.com/go-resty/resty/v2" "go.uber.org/zap" ) @@ -39,7 +38,6 @@ type Request struct { ReadTimeout int64 `json:"read_timeout"` // 读取超时时间 RetryRule *RequestRetryRule `json:"retry_rule"` // 重试规则 Logger *zap.Logger `json:"-"` // 日志记录器 - RateLimiter resty.RateLimiter `json:"-"` // 流控实例 } // RequestRetryRule 重试规则 diff --git a/httpclient/define/response.go b/httpclient/define/response.go index 40fedee..6a08a41 100644 --- a/httpclient/define/response.go +++ b/httpclient/define/response.go @@ -8,7 +8,7 @@ package define import ( - "github.com/go-resty/resty/v2" + "resty.dev/v3" ) // Response 响应的数据结构定义 diff --git a/httpclient/implement/response.go b/httpclient/implement/response.go index 89ff91e..1c8e26f 100644 --- a/httpclient/implement/response.go +++ b/httpclient/implement/response.go @@ -87,7 +87,7 @@ func (r *Response) fillResponseBody(reqCfg *define.Request, response *define.Res jsonByte []byte ) - if err = parser.Unmarshal(response.RestyResponse.Body(), &res); nil != err { + if err = parser.Unmarshal(response.RestyResponse.Bytes(), &res); nil != err { return errors.New("response parse body error :" + err.Error()) } if jsonByte, err = parser.MarshalForByte(res); nil != err { diff --git a/httpclient/mesh/define.go b/httpclient/mesh/define.go index 67ce458..3878dce 100644 --- a/httpclient/mesh/define.go +++ b/httpclient/mesh/define.go @@ -39,6 +39,7 @@ type RequestConfigGroupItem struct { FailBehavior *RequestConfigGroupItemFailBehavior `json:"fail_behavior"` // 失败的行为, 不配置, 默认失败break FinalFailureAllow bool `json:"final_failure_allow"` // 已经确定当前请求是最终失败了,当前请求是否允许执行 CacheInstance abstract.ICache `json:"-"` // 数据缓存实例 + RateLimiter abstract.RateLimiter `json:"-"` // 流控实例 ResponseParser abstract.IResponse `json:"-"` // 响应数据解析 Condition any `json:"condition"` // TODO: 请求条件, 特定条件下不执行当前请求 } diff --git a/httpclient/mesh/mesh.go b/httpclient/mesh/mesh.go index c9e7199..c3bcdf2 100644 --- a/httpclient/mesh/mesh.go +++ b/httpclient/mesh/mesh.go @@ -149,6 +149,7 @@ func (c *client) doRequest(apiList []*RequestConfigGroupItem) bool { apiCfg.RequestCfg.Query = param[strings.ToLower(consts.RequestDataLocationQuery.String())] // query if httpClient, err = httpclient.NewHttpClient(apiCfg.RequestCfg, &httpclient.RequestOption{ CacheInstance: apiCfg.CacheInstance, + RateLimiter: apiCfg.RateLimiter, ResponseParser: apiCfg.ResponseParser, }); nil != err { // 此处获取客户端实例即发生异常, 忽略一切配置, 直接作为全局失败, 后续也不请求了 diff --git a/httpclient/option.go b/httpclient/option.go index 3176c56..3c4e960 100644 --- a/httpclient/option.go +++ b/httpclient/option.go @@ -13,6 +13,7 @@ import ( // RequestOption 请求一些选项 type RequestOption struct { - CacheInstance abstract.ICache `json:"-"` // 数据结果缓存实例 - ResponseParser abstract.IResponse `json:"-"` // 返回结果解析, 不配置使用内置实现 + CacheInstance abstract.ICache `json:"-"` // 数据结果缓存实例 + RateLimiter abstract.RateLimiter `json:"-"` // 流控实例 + ResponseParser abstract.IResponse `json:"-"` // 返回结果解析, 不配置使用内置实现 } diff --git a/httpclient/resty.go b/httpclient/resty.go index bc375ee..1cd9757 100644 --- a/httpclient/resty.go +++ b/httpclient/resty.go @@ -8,38 +8,96 @@ package httpclient import ( - "encoding/json" + "fmt" "git.zhangdeman.cn/zhangdeman/consts" "git.zhangdeman.cn/zhangdeman/network/httpclient/define" "git.zhangdeman.cn/zhangdeman/serialize" "git.zhangdeman.cn/zhangdeman/wrapper" - "github.com/go-resty/resty/v2" "github.com/tidwall/gjson" "net/http" "net/textproto" + "resty.dev/v3" "strings" ) +// 初始化请求配置, 包括: +// 1. 初始化空指针 +// 2. 合并static参数 +// 3. 使用 textproto.CanonicalMIMEHeaderKey 统一格式化header key +func initRequestConfig(reqConfig *define.Request) { + if nil == reqConfig.Header { + reqConfig.Header = map[string]any{} + } + if nil == reqConfig.Body { + reqConfig.Body = map[string]any{} + } + if nil == reqConfig.Cookie { + reqConfig.Cookie = map[string]any{} + } + if reqConfig.Query == nil { + reqConfig.Query = map[string]any{} + } + if reqConfig.PathParam == nil { + reqConfig.PathParam = map[string]string{} + } + + // 合并静态参数, 传入参数与静态参数存在相同参数, 以静态参数为准 + for loc, paramTable := range reqConfig.Static { + switch strings.ToUpper(loc) { + case consts.RequestDataLocationBody.String(): + for name, val := range paramTable { + reqConfig.Body[name] = val + } + case consts.RequestDataLocationQuery.String(): + for name, val := range paramTable { + reqConfig.Query[name] = val + } + case consts.RequestDataLocationCookie.String(): + for name, val := range paramTable { + reqConfig.Cookie[name] = val + } + case consts.RequestDataLocationHeader.String(): + for name, val := range paramTable { + reqConfig.Header[name] = val + } + case consts.RequestDataLocationUriPath.String(): + for name, val := range paramTable { + reqConfig.PathParam[name] = fmt.Sprintf("%v", val) + } + } + } + + formatHeader(reqConfig) +} + // NewRestyClient 获取resty client // // Author : go_developer@163.com<白茶清欢> // // Date : 15:00 2024/5/31 -func NewRestyClient(reqConfig *define.Request) (*resty.Client, *resty.Request) { +func NewRestyClient(reqConfig *define.Request, reqOption *RequestOption) (*resty.Client, *resty.Request) { client := resty.New() request := client.R() if nil == reqConfig { return client, request } - if nil != reqConfig.RateLimiter { - // 设置流控实例 - client.SetRateLimiter(reqConfig.RateLimiter) - } - formatHeader(reqConfig) - client.SetAllowGetMethodPayload(true) // 配置 GET 请求允许带 Body - client.SetJSONMarshaler(json.Marshal) // 序列化方法 - client.SetJSONEscapeHTML(true) // 处理html实体字符 - client.SetJSONUnmarshaler(serialize.JSON.UnmarshalWithNumber) // 反序列化方法 + // 限流处理, 增加限流中间件 + client.AddRequestMiddleware(func(client *resty.Client, request *resty.Request) error { + if nil != reqOption && nil != reqOption.RateLimiter { + // 未配置流控 + return nil + } + if !reqOption.RateLimiter.Allow() { + // 命中流控 + return define.ErrRateLimitExceeded + } + return nil + }) + + initRequestConfig(reqConfig) // 初始化 + 格式化配置 + client.SetAllowMethodGetPayload(true) // 配置 GET 请求允许带 Body + client.SetAllowMethodDeletePayload(true) // 配置 DELETE 请求允许带 Body + client.SetJSONEscapeHTML(true) // 处理html实体字符 request.SetPathParams(reqConfig.PathParam) // 设置path中的参数 query := map[string]string{} @@ -59,12 +117,8 @@ func NewRestyClient(reqConfig *define.Request) (*resty.Client, *resty.Request) { } request.SetHeaders(header) // 设置header request.URL = reqConfig.FullUrl // 请求接口 - for pathParamName, pathParamValue := range reqConfig.PathParam { - if len(pathParamValue) == 0 { - continue - } - reqConfig.FullUrl = strings.ReplaceAll(reqConfig.FullUrl, "{"+pathParamName+"}", pathParamValue) - } + // 设置url参数, url path格式需要是 /user/{user_id}/detail格式, {user_id} 会自动替换 + request.SetPathParams(reqConfig.PathParam) request.Method = reqConfig.Method // 请求方法 cookieList := make([]*http.Cookie, 0) for cookieName, cookieValue := range reqConfig.Cookie { @@ -81,7 +135,7 @@ func NewRestyClient(reqConfig *define.Request) (*resty.Client, *resty.Request) { return client, request } -// setRestyBody 设置请求BODY +// setRestyBody 设置请求BODY TODO: 支持xml / yml等 // // Author : go_developer@163.com<白茶清欢> //