Compare commits
11 Commits
1abe6c1c29
...
feature/su
Author | SHA1 | Date | |
---|---|---|---|
78810e906b | |||
0e6bd9e669 | |||
0b2546217c | |||
42b9f26a9a | |||
5ddab034f3 | |||
a56528f2ee | |||
7b1a36a7c3 | |||
21060e4913 | |||
70d044e3a3 | |||
8ddb1f72b2 | |||
1403693fda |
21
go.mod
21
go.mod
@ -4,19 +4,20 @@ go 1.24.1
|
||||
|
||||
require (
|
||||
git.zhangdeman.cn/gateway/validate v0.0.0-20250506091017-1d2dfd10f600
|
||||
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250425024726-cc17224cb995
|
||||
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250622103742-f363092a1a50
|
||||
git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20250429065800-fc340b9417cf
|
||||
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/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
|
||||
resty.dev/v3 v3.0.0-beta.3
|
||||
)
|
||||
|
||||
require (
|
||||
git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20250429065800-fc340b9417cf // indirect
|
||||
git.zhangdeman.cn/zhangdeman/json_filter v0.0.0-20250506090824-b0db389ca3d7 // indirect
|
||||
git.zhangdeman.cn/zhangdeman/exception v0.0.0-20250510123912-a0d52fc093ab // indirect
|
||||
git.zhangdeman.cn/zhangdeman/json_filter v0.0.0-20250524105628-2e1ece4c82fc // indirect
|
||||
git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 // indirect
|
||||
git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
@ -26,10 +27,10 @@ require (
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mozillazg/go-pinyin v0.20.0 // indirect
|
||||
github.com/mozillazg/go-pinyin v0.21.0 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/sbabiv/xml2map v1.2.1 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
@ -38,10 +39,10 @@ require (
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.38.0 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
20
go.sum
20
go.sum
@ -2,10 +2,16 @@ git.zhangdeman.cn/gateway/validate v0.0.0-20250506091017-1d2dfd10f600 h1:o+ggli1
|
||||
git.zhangdeman.cn/gateway/validate v0.0.0-20250506091017-1d2dfd10f600/go.mod h1:DHrM753rWJ7Dvrm7CqWq1Q05jMhiGPNMKXYcKp11BHg=
|
||||
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/consts v0.0.0-20250622103742-f363092a1a50 h1:Bx1MtVKAWvCbtM03+9Ob6Eeaaivj6A+RmkYTo2N46XY=
|
||||
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250622103742-f363092a1a50/go.mod h1:5p8CEKGBxi7qPtTXDI3HDmqKAfIm5i/aBWdrbkbdNjc=
|
||||
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/exception v0.0.0-20250510123912-a0d52fc093ab h1:O0XaAKKb8qrjcjewonmKfnRsMFoCfJF+tUv6RfhRe94=
|
||||
git.zhangdeman.cn/zhangdeman/exception v0.0.0-20250510123912-a0d52fc093ab/go.mod h1:Voc8J4ordx7nuMWpgACXXZULQy7ZIuBzcEIoS8VnDIw=
|
||||
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/json_filter v0.0.0-20250524105628-2e1ece4c82fc h1:XTbNK0TUnieu3gyXvxuzSRKWzrpVfhcNnZbUX5Kcxig=
|
||||
git.zhangdeman.cn/zhangdeman/json_filter v0.0.0-20250524105628-2e1ece4c82fc/go.mod h1:k8GNZ8C/XZ0uJCLtjht+EtBoiDEfL67OyBfzP8QWkcU=
|
||||
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-20250504055908-8d68e6106ea9 h1:/GLQaFoLb+ciHOtAS2BIyPNnf4O5ME3AC5PUaJY9kfs=
|
||||
@ -36,6 +42,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-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
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=
|
||||
@ -52,6 +60,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mozillazg/go-pinyin v0.20.0 h1:BtR3DsxpApHfKReaPO1fCqF4pThRwH9uwvXzm+GnMFQ=
|
||||
github.com/mozillazg/go-pinyin v0.20.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
|
||||
github.com/mozillazg/go-pinyin v0.21.0 h1:Wo8/NT45z7P3er/9YSLHA3/kjZzbLz5hR7i+jGeIGao=
|
||||
github.com/mozillazg/go-pinyin v0.21.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
|
||||
github.com/mssola/user_agent v0.6.0 h1:uwPR4rtWlCHRFyyP9u2KOV0u8iQXmS7Z7feTrstQwk4=
|
||||
github.com/mssola/user_agent v0.6.0/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
@ -86,12 +96,20 @@ 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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
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/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
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/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
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=
|
||||
@ -101,3 +119,5 @@ 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=
|
||||
resty.dev/v3 v3.0.0-beta.3 h1:3kEwzEgCnnS6Ob4Emlk94t+I/gClyoah7SnNi67lt+E=
|
||||
resty.dev/v3 v3.0.0-beta.3/go.mod h1:OgkqiPvTDtOuV4MGZuUDhwOpkY8enjOsjjMzeOHefy4=
|
||||
|
@ -7,7 +7,9 @@
|
||||
// Date : 2025-05-07 21:13
|
||||
package abstract
|
||||
|
||||
import "git.zhangdeman.cn/zhangdeman/network/httpclient/define"
|
||||
|
||||
// RateLimiter v2 流控口约束, v3移除了, 人工补齐
|
||||
type RateLimiter interface {
|
||||
Allow() bool
|
||||
Allow(reqCfg *define.Request) bool
|
||||
}
|
||||
|
16
httpclient/abstract/IRequestBodyWrite.go
Normal file
16
httpclient/abstract/IRequestBodyWrite.go
Normal file
@ -0,0 +1,16 @@
|
||||
// Package abstract ...
|
||||
//
|
||||
// Description : abstract ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2025-05-08 14:18
|
||||
package abstract
|
||||
|
||||
import "resty.dev/v3"
|
||||
|
||||
// IRequestBodyWrite 请求信息写入Body的接口约束
|
||||
type IRequestBodyWrite interface {
|
||||
// Write 写入请求Body
|
||||
Write(request *resty.Request, bodyData map[string]any) error
|
||||
}
|
@ -10,7 +10,6 @@ package httpclient
|
||||
import (
|
||||
"fmt"
|
||||
"git.zhangdeman.cn/zhangdeman/network/httpclient/implement"
|
||||
"git.zhangdeman.cn/zhangdeman/wrapper"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
@ -99,7 +98,13 @@ func NewHttpClient(reqConfig *define.Request, reqOption *RequestOption) (*HttpCl
|
||||
if ua, exist := reqConfig.Header[consts.HeaderKeyUserAgent.String()]; !exist || nil == ua || fmt.Sprintf("%v", ua) == "" {
|
||||
reqConfig.Header[consts.HeaderKeyUserAgent.String()] = "resty-v3@network/httpclient"
|
||||
}
|
||||
restyClient, restyRequest := NewRestyClient(reqConfig, reqOption)
|
||||
restyClient, restyRequest, err := NewRestyClient(reqConfig, reqOption)
|
||||
if nil != err {
|
||||
if nil != restyClient {
|
||||
_ = restyClient.Close()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
defer restyClient.Close()
|
||||
|
||||
hc := &HttpClient{
|
||||
@ -235,8 +240,8 @@ func (hc *HttpClient) Request() *define.Response {
|
||||
"remaining_ttl": ttl,
|
||||
}, hc.reqCfg)
|
||||
// 配置了最小剩余时间,并且key剩余有效期小于最小剩余时间
|
||||
// 预热加锁, 并发请求触发预热, 仅触发一个即可, 使用接口做key + query参数做key, 按照一般约定, 写请求不会做缓存, 只有读请求会
|
||||
lockKey := wrapper.String(hc.reqCfg.FullUrl + serialize.JSON.MarshalForStringIgnoreError(hc.reqCfg.Query)).Md5().Value
|
||||
// 预热加锁, 并发请求触发预热, 仅触发一个即可, 使用接口缓存key + LOCK做锁的key {{CACHE_KEY}}_LOCK, 按照一般约定, 写请求不会做缓存, 只有读请求会
|
||||
lockKey := hc.reqOption.CacheInstance.GetKey(hc.reqCfg) + "_LOCK"
|
||||
if err := hc.reqOption.CacheInstance.Lock(lockKey); err != nil {
|
||||
log.RecordWarn("接口请求命中缓存, 缓存结果有效期大于剩余时长小于配置阈值, 触发预热, 加锁失败, 未执行预热", map[string]any{
|
||||
"min_ttl": cachePreHeatConfig.MinTTL,
|
||||
@ -450,7 +455,7 @@ func (hc *HttpClient) newResponse() *define.Response {
|
||||
//
|
||||
// Date : 16:04 2024/6/3
|
||||
func (hc *HttpClient) getCacheResult() *define.Response {
|
||||
if nil == hc.reqOption.CacheInstance {
|
||||
if nil == hc.reqOption || nil == hc.reqOption.CacheInstance {
|
||||
log.RecordDebug("接口请求前缓存检测, 未设置缓存实例", map[string]any{}, hc.reqCfg)
|
||||
return nil
|
||||
}
|
||||
|
@ -7,34 +7,6 @@
|
||||
// Date : 2024-05-31 14:51
|
||||
package define
|
||||
|
||||
// Http4xxHandler 4xx handler
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 14:54 2024/5/31
|
||||
type Http4xxHandler func(req *Request, rep *Response)
|
||||
|
||||
// Http5xxHandler 5xx handler
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 14:55 2024/5/31
|
||||
type Http5xxHandler func(req *Request, rep *Response)
|
||||
|
||||
// HttpBusinessErrorHandler 接口请求业务错误
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 18:04 2024/6/1
|
||||
type HttpBusinessErrorHandler func(req *Request, rep *Response)
|
||||
|
||||
// RequestSendErrorHandler 请求发送失败的处理逻辑
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 18:23 2024/6/1
|
||||
type RequestSendErrorHandler func(req *Request)
|
||||
|
||||
// RequestFinishHandler 请求最终完成事件, 不区分成功 OR 失败
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
|
@ -30,6 +30,7 @@ type Request struct {
|
||||
Method string `json:"method"` // 请求方法
|
||||
DataField string `json:"data_field"` // 数据字段
|
||||
CodeField string `json:"code_field"` // 业务状态码字段
|
||||
CodeLocation string `json:"code_location"` // 业务状态码位置
|
||||
MessageField string `json:"message_field"` // code描述字段
|
||||
DataReceiver any `json:"-"` // 响应data部分数据解析
|
||||
SuccessHttpCodeList []int `json:"success_http_code_list"` // 哪些http状态码视为成功, 不配置, 默认2xx
|
||||
|
55
httpclient/implement/request/write_body.go
Normal file
55
httpclient/implement/request/write_body.go
Normal file
@ -0,0 +1,55 @@
|
||||
// Package request ...
|
||||
//
|
||||
// Description : request ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2025-05-08 14:21
|
||||
package request
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.zhangdeman.cn/zhangdeman/network/httpclient/abstract"
|
||||
"resty.dev/v3"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// WriteBodyInstanceTable 请求类型 => 请求类型写入的实现
|
||||
WriteBodyInstanceTable = map[string]abstract.IRequestBodyWrite{
|
||||
"json": &WriteJson{},
|
||||
"xml": nil,
|
||||
"x-www-form-urlencoded": &WriteForm{},
|
||||
}
|
||||
)
|
||||
|
||||
func SetWriteBodyInstance(cType string, instance abstract.IRequestBodyWrite) {
|
||||
WriteBodyInstanceTable[cType] = instance
|
||||
}
|
||||
|
||||
func DeleteWriteBodyInstance(cType string) {
|
||||
delete(WriteBodyInstanceTable, cType)
|
||||
}
|
||||
|
||||
// WriteBody 数据写入body
|
||||
func WriteBody(contentType string, request *resty.Request, bodyData map[string]any) error {
|
||||
if nil == bodyData {
|
||||
// body为nil, 无需写入, 否则即使为空map也要正常写入
|
||||
return nil
|
||||
}
|
||||
if nil == request {
|
||||
return errors.New("request is nil")
|
||||
}
|
||||
contentType = strings.TrimSpace(strings.Split(contentType, ";")[0])
|
||||
if len(contentType) == 0 {
|
||||
return errors.New("contentType is empty or invalid")
|
||||
}
|
||||
contentTypeArr := strings.Split(contentType, "/")
|
||||
realType := contentTypeArr[len(contentTypeArr)-1]
|
||||
writeInstance, ok := WriteBodyInstanceTable[realType]
|
||||
if !ok || writeInstance == nil {
|
||||
return errors.New(realType + " is not support, writeInstance is nil")
|
||||
}
|
||||
writeInstance.Write(request, bodyData)
|
||||
return nil
|
||||
}
|
33
httpclient/implement/request/write_form.go
Normal file
33
httpclient/implement/request/write_form.go
Normal file
@ -0,0 +1,33 @@
|
||||
// Package request ...
|
||||
//
|
||||
// Description : request ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2025-05-08 14:39
|
||||
package request
|
||||
|
||||
import (
|
||||
"git.zhangdeman.cn/zhangdeman/serialize"
|
||||
"github.com/tidwall/gjson"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
// WriteForm application/x-www-form-urlencoded 实现
|
||||
type WriteForm struct {
|
||||
}
|
||||
|
||||
func (w *WriteForm) Write(request *resty.Request, bodyData map[string]any) error {
|
||||
if len(bodyData) == 0 {
|
||||
return nil
|
||||
}
|
||||
bodyStr := serialize.JSON.MarshalForStringIgnoreError(bodyData)
|
||||
formatBodyData := map[string]string{}
|
||||
jsonObj := gjson.Parse(bodyStr)
|
||||
jsonObj.ForEach(func(key, value gjson.Result) bool {
|
||||
formatBodyData[key.String()] = value.String()
|
||||
return true
|
||||
})
|
||||
request.SetFormData(formatBodyData)
|
||||
return nil
|
||||
}
|
21
httpclient/implement/request/write_json.go
Normal file
21
httpclient/implement/request/write_json.go
Normal file
@ -0,0 +1,21 @@
|
||||
// Package request ...
|
||||
//
|
||||
// Description : request ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2025-05-08 14:21
|
||||
package request
|
||||
|
||||
import (
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
// WriteJson application/json写入
|
||||
type WriteJson struct {
|
||||
}
|
||||
|
||||
func (w *WriteJson) Write(request *resty.Request, bodyData map[string]any) error {
|
||||
request.SetBody(bodyData)
|
||||
return nil
|
||||
}
|
46
httpclient/implement/request/write_xml.go
Normal file
46
httpclient/implement/request/write_xml.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Package request ...
|
||||
//
|
||||
// Description : request ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2025-05-08 14:21
|
||||
package request
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"git.zhangdeman.cn/zhangdeman/serialize"
|
||||
"resty.dev/v3"
|
||||
"git.zhangdeman.cn/zhangdeman/dynamic-struct/wrapper"
|
||||
)
|
||||
|
||||
// WriteXml application/xml body写入
|
||||
type WriteXml struct{}
|
||||
|
||||
func (w *WriteXml) Write(request *resty.Request, bodyData map[string]any) error {
|
||||
if nil == bodyData {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
err error
|
||||
bodyBytes []byte
|
||||
xmlBodyByte []byte
|
||||
)
|
||||
|
||||
if bodyBytes, err = serialize.JSON.MarshalForByte(bodyData); nil != err {
|
||||
return err
|
||||
}
|
||||
instance, err := wrapper.NewJson(string(bodyBytes), &wrapper.Option{XmlName: "RequestBody"})
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
if nil == instance {
|
||||
return errors.New("xml generate instance is nil")
|
||||
}
|
||||
if xmlBodyByte, err = instance.Marshal("xml"); nil != err {
|
||||
return err
|
||||
}
|
||||
request.SetBody(bytes.NewReader(xmlBodyByte))
|
||||
return nil
|
||||
}
|
@ -14,6 +14,7 @@ import (
|
||||
"git.zhangdeman.cn/zhangdeman/network/httpclient/define"
|
||||
"git.zhangdeman.cn/zhangdeman/serialize"
|
||||
"github.com/tidwall/gjson"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -67,6 +68,9 @@ func (r *Response) fillResponseBody(reqCfg *define.Request, response *define.Res
|
||||
responseContentType := response.RestyResponse.Header().Get(consts.HeaderKeyContentType.String())
|
||||
if responseContentType == "" {
|
||||
// 返回数据未说明 Content-Type
|
||||
if response.RestyResponse.StatusCode() != http.StatusOK {
|
||||
return nil
|
||||
}
|
||||
return errors.New("response content type is empty")
|
||||
}
|
||||
typeArr := strings.Split(strings.Split(responseContentType, ";")[0], "/")
|
||||
@ -94,8 +98,20 @@ func (r *Response) fillResponseBody(reqCfg *define.Request, response *define.Res
|
||||
return errors.New("response body Marshal error :" + err.Error())
|
||||
}
|
||||
response.Data = string(jsonByte)
|
||||
if strings.ToLower(reqCfg.CodeLocation) == "header" {
|
||||
if reqCfg.CodeField == "code" {
|
||||
response.Code = fmt.Sprintf("%v", response.HttpCode)
|
||||
response.Message = response.RestyResponse.Status()
|
||||
} else {
|
||||
response.Code = response.RestyResponse.Header().Get(reqCfg.CodeField)
|
||||
response.Message = response.RestyResponse.Header().Get(reqCfg.MessageField)
|
||||
}
|
||||
} else {
|
||||
// 统一认为Body
|
||||
response.Code = gjson.Get(response.Data, reqCfg.CodeField).String()
|
||||
response.Message = gjson.Get(response.Data, reqCfg.MessageField).String()
|
||||
}
|
||||
|
||||
businessData := gjson.Get(response.Data, reqCfg.DataField)
|
||||
if businessData.Value() == nil {
|
||||
// data为空指针, 归一化成空对象
|
||||
|
@ -41,6 +41,7 @@ type RequestConfigGroupItem struct {
|
||||
CacheInstance abstract.ICache `json:"-"` // 数据缓存实例
|
||||
RateLimiter abstract.RateLimiter `json:"-"` // 流控实例
|
||||
ResponseParser abstract.IResponse `json:"-"` // 响应数据解析
|
||||
RequestBodyWrite abstract.IRequestBodyWrite `json:"-"` // 请求Body数据写入的实现
|
||||
Condition any `json:"condition"` // TODO: 请求条件, 特定条件下不执行当前请求
|
||||
}
|
||||
|
||||
|
@ -15,5 +15,6 @@ import (
|
||||
type RequestOption struct {
|
||||
CacheInstance abstract.ICache `json:"-"` // 数据结果缓存实例
|
||||
RateLimiter abstract.RateLimiter `json:"-"` // 流控实例
|
||||
RequestBodyWrite abstract.IRequestBodyWrite `json:"-"` // 将请求Body写入请求实例
|
||||
ResponseParser abstract.IResponse `json:"-"` // 返回结果解析, 不配置使用内置实现
|
||||
}
|
||||
|
@ -8,12 +8,12 @@
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.zhangdeman.cn/zhangdeman/consts"
|
||||
"git.zhangdeman.cn/zhangdeman/network/httpclient/define"
|
||||
"git.zhangdeman.cn/zhangdeman/serialize"
|
||||
requestBoodyWriter "git.zhangdeman.cn/zhangdeman/network/httpclient/implement/request"
|
||||
"git.zhangdeman.cn/zhangdeman/wrapper"
|
||||
"github.com/tidwall/gjson"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"resty.dev/v3"
|
||||
@ -75,19 +75,19 @@ func initRequestConfig(reqConfig *define.Request) {
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 15:00 2024/5/31
|
||||
func NewRestyClient(reqConfig *define.Request, reqOption *RequestOption) (*resty.Client, *resty.Request) {
|
||||
func NewRestyClient(reqConfig *define.Request, reqOption *RequestOption) (*resty.Client, *resty.Request, error) {
|
||||
client := resty.New()
|
||||
request := client.R()
|
||||
if nil == reqConfig {
|
||||
return client, request
|
||||
return nil, nil, errors.New("request config is nil")
|
||||
}
|
||||
// 限流处理, 增加限流中间件
|
||||
client.AddRequestMiddleware(func(client *resty.Client, request *resty.Request) error {
|
||||
if nil != reqOption && nil != reqOption.RateLimiter {
|
||||
if nil == reqOption || nil == reqOption.RateLimiter {
|
||||
// 未配置流控
|
||||
return nil
|
||||
}
|
||||
if !reqOption.RateLimiter.Allow() {
|
||||
if !reqOption.RateLimiter.Allow(reqConfig) {
|
||||
// 命中流控
|
||||
return define.ErrRateLimitExceeded
|
||||
}
|
||||
@ -131,34 +131,28 @@ func NewRestyClient(reqConfig *define.Request, reqOption *RequestOption) (*resty
|
||||
})
|
||||
}
|
||||
request.SetCookies(cookieList) // 设置cookie
|
||||
setRestyBody(reqConfig, request) // 设置请求Body
|
||||
return client, request
|
||||
if err := setRestyBody(reqConfig, reqOption, request); nil != err {
|
||||
return nil, nil, err
|
||||
}
|
||||
return client, request, nil
|
||||
}
|
||||
|
||||
// setRestyBody 设置请求BODY TODO: 支持xml / yml等
|
||||
// setRestyBody 设置请求BODY
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 17:18 2024/5/31
|
||||
func setRestyBody(reqConfig *define.Request, request *resty.Request) {
|
||||
if nil == reqConfig.Body || len(reqConfig.Body) == 0 {
|
||||
return
|
||||
func setRestyBody(reqConfig *define.Request, requestOption *RequestOption, request *resty.Request) error {
|
||||
if nil == reqConfig.Body {
|
||||
return nil
|
||||
}
|
||||
if strings.Contains(strings.ToLower(reqConfig.ContentType), consts.MimeTypeJson) {
|
||||
request.SetBody(reqConfig.Body)
|
||||
return
|
||||
if nil != requestOption && nil != requestOption.RequestBodyWrite {
|
||||
// 外部传入的实现
|
||||
requestOption.RequestBodyWrite.Write(request, reqConfig.Body)
|
||||
return nil
|
||||
}
|
||||
if strings.Contains(strings.ToLower(reqConfig.ContentType), consts.MimeTypeXWWWFormUrlencoded) {
|
||||
bodyStr := serialize.JSON.MarshalForStringIgnoreError(reqConfig.Body)
|
||||
bodyData := map[string]string{}
|
||||
jsonObj := gjson.Parse(bodyStr)
|
||||
jsonObj.ForEach(func(key, value gjson.Result) bool {
|
||||
bodyData[key.String()] = value.String()
|
||||
return true
|
||||
})
|
||||
request.SetFormData(bodyData)
|
||||
}
|
||||
return
|
||||
// 外部没传入, 使用内置实现, 内置默认实现支持: xml/json/form, 如需其他, 自行扩展
|
||||
return requestBoodyWriter.WriteBody(reqConfig.ContentType, request, reqConfig.Body)
|
||||
}
|
||||
|
||||
// formatHeader 格式化header
|
||||
|
Reference in New Issue
Block a user