Merge pull request '响应自适应解析 + 逻辑优化' (#8) from feature/auto_support_response_type into master
Reviewed-on: #8
This commit is contained in:
commit
6dcd072570
17
go.mod
17
go.mod
@ -3,9 +3,9 @@ module git.zhangdeman.cn/zhangdeman/network
|
||||
go 1.24.1
|
||||
|
||||
require (
|
||||
git.zhangdeman.cn/gateway/validate v0.0.0-20250428044112-b3b4cc8e571e
|
||||
git.zhangdeman.cn/gateway/validate v0.0.0-20250506091017-1d2dfd10f600
|
||||
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250425024726-cc17224cb995
|
||||
git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20250428041157-135850ee8a58
|
||||
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
|
||||
@ -15,8 +15,8 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20250428024209-cd85d80da6e7 // indirect
|
||||
git.zhangdeman.cn/zhangdeman/json_filter v0.0.0-20250329080213-587a9bbe9dbc // indirect
|
||||
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/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
|
||||
@ -31,16 +31,17 @@ require (
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mozillazg/go-pinyin v0.20.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
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
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.37.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/text v0.24.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
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
18
go.sum
18
go.sum
@ -2,6 +2,8 @@ git.zhangdeman.cn/gateway/validate v0.0.0-20250331140536-3477e2ec106d h1:rEnbN/j
|
||||
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=
|
||||
@ -10,14 +12,20 @@ git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20250401073534-5d4788e0fd86 h
|
||||
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=
|
||||
git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e/go.mod h1:VpPjBlwz8U+OxZuxzHQBv1aEEZ3pStH6bZvT21ADEbI=
|
||||
git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250321102712-1cbfbe959740 h1:zPUoylfJTbc0EcxW+NEzOTBmoeFZ2I/rLFBnEzxb4Wk=
|
||||
@ -68,6 +76,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sbabiv/xml2map v1.2.1 h1:1lT7t0hhUvXZCkdxqtq4n8/ZCnwLWGq4rDuDv5XOoFE=
|
||||
github.com/sbabiv/xml2map v1.2.1/go.mod h1:2TPoAfcaM7+Sd4iriPvzyntb2mx7GY+kkQpB/GQa/eo=
|
||||
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
|
||||
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
|
||||
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
||||
@ -94,12 +104,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.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=
|
||||
|
@ -1,68 +1,36 @@
|
||||
// Package cache ...
|
||||
// Package abstract ...
|
||||
//
|
||||
// Description : cache ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2024-06-03 15:49
|
||||
package cache
|
||||
package abstract
|
||||
|
||||
import (
|
||||
"git.zhangdeman.cn/zhangdeman/network/httpclient/define"
|
||||
)
|
||||
|
||||
// ICache 缓存定义
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 15:49 2024/6/3
|
||||
type ICache interface {
|
||||
// Enable 是否启用缓存(总开关)
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 15:50 2024/6/3
|
||||
Enable() bool
|
||||
// CacheTime 缓存时长, 单位 : s , 默认 1800, 最小值 90, 设置失效时间, 会上下波动60s, 避免缓存集中失效
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 15:50 2024/6/3
|
||||
CacheTime() int64
|
||||
// IsAllow 针对当前请求数据和状态, 是否允许缓存
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 15:52 2024/6/3
|
||||
IsAllow(reqCfg *define.Request, response *define.Response) bool
|
||||
// GetKey 获取缓存key
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 15:51 2024/6/3
|
||||
GetKey(reqCfg *define.Request) string
|
||||
// GetValue 获取缓存值
|
||||
//
|
||||
// Author : zhangdeman001@ke.com<张德满>
|
||||
//
|
||||
// Date : 16:01 2024/6/3
|
||||
GetValue(cacheKey string) string
|
||||
// SetValue 设置缓存
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 16:46 2024/6/3
|
||||
SetValue(cacheKey string, cacheValue string) error
|
||||
// TTL 缓存剩余生命周期(单位: s)
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 18:39 2024/10/9
|
||||
TTL(cacheKey string) int64
|
||||
// PreHeatConfig 缓存预热配置
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 18:42 2024/10/9
|
||||
PreHeatConfig() *define.CachePreHeatConfig
|
||||
// Lock 设置缓存时加锁
|
||||
Lock(lockKey string) error
|
||||
// Unlock 完成缓存设置时, 释放锁
|
||||
Unlock(lockKey string) error
|
||||
}
|
32
httpclient/abstract/IResponse.go
Normal file
32
httpclient/abstract/IResponse.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Package abstract ...
|
||||
//
|
||||
// Description : abstract ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2025-05-07 14:28
|
||||
package abstract
|
||||
|
||||
import (
|
||||
"git.zhangdeman.cn/zhangdeman/network/httpclient/define"
|
||||
)
|
||||
|
||||
// IResponseParser 响应结果的序列化解析器
|
||||
type IResponseParser interface {
|
||||
// Unmarshal 反序列化解析
|
||||
Unmarshal(byteData []byte, receiver any) error
|
||||
// MarshalForByte 序列化
|
||||
MarshalForByte(input any) ([]byte, error)
|
||||
}
|
||||
|
||||
// IResponse 响应解析
|
||||
type IResponse interface {
|
||||
// Parse 解析响应
|
||||
Parse(reqConfig *define.Request, response *define.Response) error
|
||||
// BusinessSuccess 业务状态码是否成功
|
||||
BusinessSuccess(reqCfg *define.Request, response *define.Response) bool
|
||||
// HttpSuccess http状态码是否成功
|
||||
HttpSuccess(reqCfg *define.Request, response *define.Response) bool
|
||||
// NeedRetry 是否需要重试
|
||||
NeedRetry(reqCfg *define.Request, response *define.Response) bool
|
||||
}
|
@ -9,18 +9,18 @@ package httpclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.zhangdeman.cn/zhangdeman/network/httpclient/implement"
|
||||
"git.zhangdeman.cn/zhangdeman/wrapper"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.zhangdeman.cn/zhangdeman/consts"
|
||||
"git.zhangdeman.cn/zhangdeman/network/httpclient/cache"
|
||||
"git.zhangdeman.cn/zhangdeman/network/httpclient/define"
|
||||
"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"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// NewHttpClient 获取http client
|
||||
@ -28,7 +28,14 @@ import (
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 15:27 2024/5/31
|
||||
func NewHttpClient(reqConfig *define.Request, cacheInstance cache.ICache) (*HttpClient, error) {
|
||||
func NewHttpClient(reqConfig *define.Request, reqOption *RequestOption) (*HttpClient, error) {
|
||||
if nil == reqOption {
|
||||
reqOption = &RequestOption{}
|
||||
}
|
||||
if nil == reqOption.ResponseParser {
|
||||
// 没有自定义响应解析实现, 使用内置实现
|
||||
reqOption.ResponseParser = &implement.Response{}
|
||||
}
|
||||
if nil == reqConfig.Logger {
|
||||
reqConfig.Logger = log.Get() // 未单独指定日志实例, 则使用全局日志实例
|
||||
}
|
||||
@ -96,12 +103,12 @@ func NewHttpClient(reqConfig *define.Request, cacheInstance cache.ICache) (*Http
|
||||
hc := &HttpClient{
|
||||
Client: restyClient,
|
||||
request: restyRequest,
|
||||
reqConfig: reqConfig,
|
||||
reqOption: reqOption,
|
||||
reqCfg: reqConfig,
|
||||
http4xxHandler: make([]define.Http4xxHandler, 0),
|
||||
http5xxHandler: make([]define.Http5xxHandler, 0),
|
||||
httpBusinessErrorHandler: make([]define.HttpBusinessErrorHandler, 0),
|
||||
requestFinishHandler: make([]define.RequestFinishHandler, 0),
|
||||
cacheInstance: cacheInstance,
|
||||
}
|
||||
hc.OnRequestFinish(func(req *define.Request, rep *define.Response) {
|
||||
if rep.IsSuccess {
|
||||
@ -123,13 +130,13 @@ func NewHttpClient(reqConfig *define.Request, cacheInstance cache.ICache) (*Http
|
||||
type HttpClient struct {
|
||||
*resty.Client
|
||||
request *resty.Request
|
||||
reqConfig *define.Request
|
||||
reqOption *RequestOption
|
||||
reqCfg *define.Request
|
||||
http4xxHandler []define.Http4xxHandler
|
||||
http5xxHandler []define.Http5xxHandler
|
||||
httpBusinessErrorHandler []define.HttpBusinessErrorHandler
|
||||
requestSendErrorHandler []define.RequestSendErrorHandler
|
||||
requestFinishHandler []define.RequestFinishHandler
|
||||
cacheInstance cache.ICache
|
||||
}
|
||||
|
||||
// OnResponse4xx 4xx处理逻辑
|
||||
@ -219,17 +226,19 @@ func (hc *HttpClient) Request() *define.Response {
|
||||
)
|
||||
|
||||
if cacheResult = hc.getCacheResult(); nil == cacheResult {
|
||||
// 未命中缓存, 直接请求后端接口, 无需
|
||||
// 未命中缓存, 直接请求后端接口, 无需检测预热等逻辑
|
||||
return hc.requestBackendApi()
|
||||
}
|
||||
// TODO : 预热加锁, 并发请求触发预热, 仅触发一个即可
|
||||
// 上面若命中缓存, 则后续缓存实例不可能为nil, 无需判断
|
||||
// 判断是否开启预热
|
||||
inputCachePreHeatConfig := hc.cacheInstance.PreHeatConfig()
|
||||
var cachePreHeatConfig define.CachePreHeatConfig
|
||||
serialize.JSON.TransitionIgnoreError(inputCachePreHeatConfig, &cachePreHeatConfig)
|
||||
log.RecordDebug("接口请求命中缓存", map[string]any{
|
||||
cachePreHeatConfig := hc.reqOption.CacheInstance.PreHeatConfig()
|
||||
if nil == cachePreHeatConfig {
|
||||
log.RecordDebug("接口请求命中缓存, PreHeatConfig未返回预热配置, 不做预热处理", map[string]any{}, hc.reqCfg)
|
||||
return nil
|
||||
}
|
||||
log.RecordDebug("接口请求命中缓存, 进行预热策略处理", map[string]any{
|
||||
"cache_info": cacheResult.CacheInfo,
|
||||
}, hc.reqConfig)
|
||||
}, hc.reqCfg)
|
||||
defer func() {
|
||||
// 命中缓存的情况下, 检测缓存预热策略, 判断是否进行缓存预热
|
||||
go func() {
|
||||
@ -237,25 +246,25 @@ func (hc *HttpClient) Request() *define.Response {
|
||||
// 无预热配置或未启用预热或者未设置预热规则
|
||||
log.RecordDebug("接口请求命中缓存, 未配置缓存预热策略", map[string]any{
|
||||
"cache_pre_heat_config": cachePreHeatConfig,
|
||||
}, hc.reqConfig)
|
||||
}, hc.reqCfg)
|
||||
return
|
||||
}
|
||||
// 判断是否触发预热
|
||||
if cachePreHeatConfig.Force {
|
||||
log.RecordDebug("接口请求命中缓存, 强制执行缓存预热, 忽略其他策略配置", map[string]any{
|
||||
"cache_pre_heat_config": cachePreHeatConfig,
|
||||
}, hc.reqConfig)
|
||||
}, hc.reqCfg)
|
||||
_ = hc.requestBackendApi()
|
||||
return
|
||||
}
|
||||
// 将百分比的配置归一化成最小剩余时间的配置
|
||||
if cachePreHeatConfig.MinPercent > 0 {
|
||||
expectMinTTL := hc.cacheInstance.CacheTime() * cachePreHeatConfig.MinPercent / 100
|
||||
expectMinTTL := hc.reqOption.CacheInstance.CacheTime() * cachePreHeatConfig.MinPercent / 100
|
||||
log.RecordDebug("接口请求命中缓存, 配置预热策略:有效时长剩余百分比", map[string]any{
|
||||
"cache_pre_heat_config": cachePreHeatConfig,
|
||||
"percent_min_ttl": expectMinTTL,
|
||||
"min_ttl": cachePreHeatConfig.MinTTL,
|
||||
}, hc.reqConfig)
|
||||
}, hc.reqCfg)
|
||||
if cachePreHeatConfig.MinTTL == 0 || cachePreHeatConfig.MinTTL > expectMinTTL {
|
||||
cachePreHeatConfig.MinTTL = expectMinTTL
|
||||
}
|
||||
@ -264,16 +273,16 @@ func (hc *HttpClient) Request() *define.Response {
|
||||
// 未配置最小剩余时间
|
||||
log.RecordDebug("接口请求命中缓存, 未配置预热市场策略, 不执行预热", map[string]any{
|
||||
"min_ttl": cachePreHeatConfig.MinTTL,
|
||||
}, hc.reqConfig)
|
||||
}, hc.reqCfg)
|
||||
return
|
||||
}
|
||||
ttl := hc.cacheInstance.TTL(cacheResult.CacheInfo.CacheKey)
|
||||
ttl := hc.reqOption.CacheInstance.TTL(cacheResult.CacheInfo.CacheKey)
|
||||
if ttl < 0 {
|
||||
// 不存在或者未设置有效期
|
||||
log.RecordDebug("接口请求命中缓存, 当前缓存结果不存在或未设置有效期, 不执行预热", map[string]any{
|
||||
"min_ttl": cachePreHeatConfig.MinTTL,
|
||||
"note": "预热时间至少在缓存过期前10s触发预热, 以保证足够时间进行预热, 以及不会因为预热尚未完成, 但是大量流量涌入, 进而导致流量穿透",
|
||||
}, hc.reqConfig)
|
||||
}, hc.reqCfg)
|
||||
return
|
||||
}
|
||||
|
||||
@ -281,19 +290,48 @@ func (hc *HttpClient) Request() *define.Response {
|
||||
log.RecordDebug("接口请求命中缓存, 缓存结果有效期剩余时长大于配置阈值, 不执行预热", map[string]any{
|
||||
"min_ttl": cachePreHeatConfig.MinTTL,
|
||||
"remaining_ttl": ttl,
|
||||
}, hc.reqConfig)
|
||||
}, hc.reqCfg)
|
||||
return
|
||||
}
|
||||
log.RecordDebug("接口请求命中缓存, 缓存结果有效期大于剩余时长小于配置阈值, 触发预热", map[string]any{
|
||||
"min_ttl": cachePreHeatConfig.MinTTL,
|
||||
"remaining_ttl": ttl,
|
||||
}, hc.reqConfig)
|
||||
}, hc.reqCfg)
|
||||
// 配置了最小剩余时间,并且key剩余有效期小于最小剩余时间
|
||||
// 预热加锁, 并发请求触发预热, 仅触发一个即可, 使用接口做key + query参数做key, 按照一般约定, 写请求不会做缓存, 只有读请求会
|
||||
lockKey := wrapper.String(hc.reqCfg.FullUrl + serialize.JSON.MarshalForStringIgnoreError(hc.reqCfg.Query)).Md5().Value
|
||||
if err := hc.reqOption.CacheInstance.Lock(lockKey); err != nil {
|
||||
log.RecordWarn("接口请求命中缓存, 缓存结果有效期大于剩余时长小于配置阈值, 触发预热, 加锁失败, 未执行预热", map[string]any{
|
||||
"min_ttl": cachePreHeatConfig.MinTTL,
|
||||
"remaining_ttl": ttl,
|
||||
"err_msg": err.Error(),
|
||||
}, hc.reqCfg)
|
||||
return
|
||||
}
|
||||
log.RecordDebug("接口请求命中缓存, 缓存结果有效期大于剩余时长小于配置阈值, 触发预热, 加锁成功, 执行预热", map[string]any{
|
||||
"min_ttl": cachePreHeatConfig.MinTTL,
|
||||
"remaining_ttl": ttl,
|
||||
"lock_key": hc.reqCfg.FullUrl,
|
||||
}, hc.reqCfg)
|
||||
_ = hc.requestBackendApi()
|
||||
if err := hc.reqOption.CacheInstance.Unlock(lockKey); nil != err {
|
||||
log.RecordError("接口请求命中缓存, 缓存结果有效期大于剩余时长小于配置阈值, 触发预热, 执行预热后, 释放锁失败", map[string]any{
|
||||
"min_ttl": cachePreHeatConfig.MinTTL,
|
||||
"remaining_ttl": ttl,
|
||||
"lock_key": hc.reqCfg.FullUrl,
|
||||
"err_msg": err.Error(),
|
||||
}, hc.reqCfg)
|
||||
return
|
||||
}
|
||||
log.RecordDebug("接口请求命中缓存, 缓存结果有效期大于剩余时长小于配置阈值, 触发预热, 执行预热后, 释放锁成功", map[string]any{
|
||||
"min_ttl": cachePreHeatConfig.MinTTL,
|
||||
"remaining_ttl": ttl,
|
||||
"lock_key": hc.reqCfg.FullUrl,
|
||||
}, hc.reqCfg)
|
||||
}()
|
||||
}()
|
||||
// 命中缓存必然请求成功, 直接记录成功日志即可
|
||||
log.Record(consts.LogLevelInfo, "接口请求成功:命中缓存", hc.reqConfig, cacheResult)
|
||||
log.Record(consts.LogLevelInfo, "接口请求成功:命中缓存", hc.reqCfg, cacheResult)
|
||||
return cacheResult
|
||||
}
|
||||
|
||||
@ -309,38 +347,22 @@ func (hc *HttpClient) requestBackendApi() *define.Response {
|
||||
err error
|
||||
)
|
||||
|
||||
response := hc.newResponse()
|
||||
for i := 0; i < hc.reqConfig.RetryRule.RetryCount+1; i++ {
|
||||
if i > 0 {
|
||||
// 非首次请求, 说明是重试, 暂停指定时间间隔
|
||||
time.Sleep(time.Duration(hc.reqConfig.RetryRule.RetryTimeInterval) * time.Millisecond)
|
||||
}
|
||||
if response.Seq > 0 {
|
||||
var response *define.Response
|
||||
// +1 是因为正常便会请求一次, 正常请求1次 + 重试次数 = 请求总次数
|
||||
for i := 0; i < hc.reqCfg.RetryRule.RetryCount+1; i++ {
|
||||
if i > 0 && nil != response {
|
||||
// 说明是重试, 记录上一次的请求信息
|
||||
response.RequestFinishTime = time.Now().UnixMilli()
|
||||
response.UsedTime = response.RequestFinishTime - response.RequestStartTime
|
||||
for _, itemAfterResponse := range hc.requestFinishHandler {
|
||||
itemAfterResponse(hc.reqConfig, response)
|
||||
itemAfterResponse(hc.reqCfg, response)
|
||||
}
|
||||
// 非首次请求, 说明是重试, 暂停指定时间间隔
|
||||
time.Sleep(time.Duration(hc.reqCfg.RetryRule.RetryTimeInterval) * time.Millisecond)
|
||||
}
|
||||
response.RequestStartTime = time.Now().UnixMilli() // 每次重置请求时间
|
||||
response.RequestFinishTime = 0 // 清空完成时间
|
||||
response.Seq++
|
||||
response.RequestCount++
|
||||
retryHttpCodeTable := make(map[int64]bool)
|
||||
retryBusinessCodeTable := make(map[string]bool)
|
||||
if nil != hc.reqConfig.RetryRule {
|
||||
for _, httpCode := range hc.reqConfig.RetryRule.RetryHttpCodeList {
|
||||
retryHttpCodeTable[httpCode] = true
|
||||
}
|
||||
for _, businessCode := range hc.reqConfig.RetryRule.RetryBusinessCodeList {
|
||||
retryBusinessCodeTable[businessCode] = true
|
||||
}
|
||||
}
|
||||
successHttpCodeTable := make(map[int]bool)
|
||||
for _, itemHttpCode := range hc.reqConfig.SuccessHttpCodeList {
|
||||
successHttpCodeTable[itemHttpCode] = true
|
||||
}
|
||||
response = hc.newResponse()
|
||||
response.Seq = i
|
||||
response.RequestCount = i + 1
|
||||
if response.RestyResponse, err = hc.request.Send(); nil != err {
|
||||
errType := define.RequestFailTypeSend
|
||||
if err.Error() == resty.ErrRateLimitExceeded.Error() {
|
||||
@ -350,6 +372,9 @@ func (hc *HttpClient) requestBackendApi() *define.Response {
|
||||
if netErr.Timeout() {
|
||||
// 请求超时
|
||||
errType = define.RequestFailTypeTimeoutError
|
||||
// 重置响应状态码
|
||||
response.HttpCode = 499
|
||||
response.HttpCodeStatus = "request timeout"
|
||||
}
|
||||
}
|
||||
response.FailInfo = &define.ResponseFailInfo{
|
||||
@ -359,23 +384,23 @@ func (hc *HttpClient) requestBackendApi() *define.Response {
|
||||
log.RecordDebug("请求发送出现异常", map[string]any{
|
||||
"err_type": errType,
|
||||
"err_msg": err.Error(),
|
||||
}, hc.reqConfig)
|
||||
if errType == define.RequestFailTypeSend {
|
||||
}, hc.reqCfg)
|
||||
if errType == define.RequestFailTypeRateLimit {
|
||||
// 命中限流就不重试了
|
||||
log.RecordDebug("请求命中限流, 忽略重试策略, 不进行重试", nil, hc.reqConfig)
|
||||
log.RecordDebug("请求命中限流, 忽略重试策略, 不进行重试", nil, hc.reqCfg)
|
||||
break
|
||||
}
|
||||
|
||||
if errType == define.RequestFailTypeTimeoutError && !retryHttpCodeTable[499] {
|
||||
if errType == define.RequestFailTypeTimeoutError && !hc.reqOption.ResponseParser.NeedRetry(hc.reqCfg, response) {
|
||||
// 未配置超时重试
|
||||
log.RecordDebug("请求超时, 未配置超时重试, 不进行重试", nil, hc.reqConfig)
|
||||
log.RecordDebug("请求超时, 未配置超时重试, 不进行重试", nil, hc.reqCfg)
|
||||
break
|
||||
}
|
||||
log.RecordDebug("请求发送出现异常, 进行重试", map[string]any{
|
||||
"err_type": errType,
|
||||
"err_msg": err.Error(),
|
||||
"time_interval": time.Duration(hc.reqConfig.RetryRule.RetryTimeInterval) * time.Millisecond,
|
||||
}, hc.reqConfig)
|
||||
"time_interval": time.Duration(hc.reqCfg.RetryRule.RetryTimeInterval) * time.Millisecond,
|
||||
}, hc.reqCfg)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -386,16 +411,14 @@ func (hc *HttpClient) requestBackendApi() *define.Response {
|
||||
}
|
||||
log.RecordDebug("RestyResponse为nil, 准备重试", map[string]any{
|
||||
"err_type": response.FailInfo.Type,
|
||||
"time_interval": time.Duration(hc.reqConfig.RetryRule.RetryTimeInterval) * time.Millisecond,
|
||||
}, hc.reqConfig)
|
||||
"time_interval": time.Duration(hc.reqCfg.RetryRule.RetryTimeInterval) * time.Millisecond,
|
||||
}, hc.reqCfg)
|
||||
continue
|
||||
}
|
||||
// 解析返回信息
|
||||
hc.fillResponseHeader(response)
|
||||
hc.fillResponseCookie(response)
|
||||
hc.fillResponseBody(response)
|
||||
hc.reqOption.ResponseParser.Parse(hc.reqCfg, response)
|
||||
// 配置了当前code为成功, 或者未配置任何code, 当前code为2xx, 则认为请求成功
|
||||
isHttpSuccess := successHttpCodeTable[response.HttpCode] || (len(successHttpCodeTable) == 0 && response.HttpCode/100 == 2)
|
||||
isHttpSuccess := hc.reqOption.ResponseParser.HttpSuccess(hc.reqCfg, response)
|
||||
if !isHttpSuccess {
|
||||
// 非 成功
|
||||
errType := define.RequestFailTypeServerError
|
||||
@ -407,34 +430,37 @@ func (hc *HttpClient) requestBackendApi() *define.Response {
|
||||
Type: errType,
|
||||
Message: "http code is " + response.HttpCodeStatus + ", not success",
|
||||
}
|
||||
needRetry := hc.reqOption.ResponseParser.NeedRetry(hc.reqCfg, response)
|
||||
log.RecordWarn("请求响应的http状态码非成功", map[string]any{
|
||||
"err_type": errType,
|
||||
"err_msg": response.RestyResponse.Status(),
|
||||
"response_http_code": response.HttpCode,
|
||||
"success_http_code": hc.reqConfig.SuccessHttpCodeList,
|
||||
"allow_retry": len(retryHttpCodeTable) == 0 || retryHttpCodeTable[int64(response.HttpCode)],
|
||||
"time_interval": time.Duration(hc.reqConfig.RetryRule.RetryTimeInterval) * time.Millisecond,
|
||||
}, hc.reqConfig)
|
||||
if len(retryHttpCodeTable) > 0 && !retryHttpCodeTable[int64(response.HttpCode)] {
|
||||
"success_http_code": hc.reqCfg.SuccessHttpCodeList,
|
||||
"allow_retry": needRetry,
|
||||
"time_interval": time.Duration(hc.reqCfg.RetryRule.RetryTimeInterval) * time.Millisecond,
|
||||
}, hc.reqCfg)
|
||||
if !needRetry {
|
||||
// 未配置http code重试
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !hc.isCodeSuccess(response) {
|
||||
if !hc.reqOption.ResponseParser.BusinessSuccess(hc.reqCfg, response) {
|
||||
response.FailInfo = &define.ResponseFailInfo{
|
||||
Type: define.RequestFailTypeBusinessError,
|
||||
Message: "business code is " + response.Code + ", not success",
|
||||
}
|
||||
needRetry := hc.reqOption.ResponseParser.NeedRetry(hc.reqCfg, response)
|
||||
|
||||
log.RecordWarn("请求响应状态码成功, 业务状态码非成功", map[string]any{
|
||||
"err_type": response.FailInfo.Type,
|
||||
"err_msg": response.Message,
|
||||
"response_code": response.Code,
|
||||
"success_code": hc.reqConfig.SuccessCodeList,
|
||||
"allow_retry": len(retryBusinessCodeTable) == 0 || retryBusinessCodeTable[response.Code],
|
||||
"time_interval": time.Duration(hc.reqConfig.RetryRule.RetryTimeInterval) * time.Millisecond,
|
||||
}, hc.reqConfig)
|
||||
if len(retryBusinessCodeTable) > 0 && !retryBusinessCodeTable[response.Code] {
|
||||
"success_code": hc.reqCfg.SuccessCodeList,
|
||||
"allow_retry": needRetry,
|
||||
"time_interval": time.Duration(hc.reqCfg.RetryRule.RetryTimeInterval) * time.Millisecond,
|
||||
}, hc.reqCfg)
|
||||
if needRetry {
|
||||
// 未配置业务code重试
|
||||
break
|
||||
}
|
||||
@ -448,7 +474,7 @@ func (hc *HttpClient) requestBackendApi() *define.Response {
|
||||
response.UsedTime = response.RequestFinishTime - response.RequestStartTime
|
||||
// 请求完成hook
|
||||
for _, itemAfterResponse := range hc.requestFinishHandler {
|
||||
itemAfterResponse(hc.reqConfig, response)
|
||||
itemAfterResponse(hc.reqCfg, response)
|
||||
}
|
||||
return response
|
||||
}
|
||||
@ -470,7 +496,7 @@ func (hc *HttpClient) newResponse() *define.Response {
|
||||
HttpCodeStatus: "",
|
||||
ResponseDataRule: nil,
|
||||
Seq: 0,
|
||||
RequestStartTime: 0,
|
||||
RequestStartTime: time.Now().UnixMilli(),
|
||||
RequestFinishTime: 0,
|
||||
UsedTime: 0,
|
||||
RestyResponse: nil,
|
||||
@ -480,7 +506,7 @@ func (hc *HttpClient) newResponse() *define.Response {
|
||||
SetCache: false,
|
||||
CacheKey: "",
|
||||
CacheValue: "",
|
||||
CacheEnable: nil != hc.cacheInstance && hc.cacheInstance.Enable(),
|
||||
CacheEnable: nil != hc.reqOption.CacheInstance && hc.reqOption.CacheInstance.Enable(),
|
||||
CacheError: nil,
|
||||
},
|
||||
RequestCount: 0,
|
||||
@ -488,119 +514,32 @@ func (hc *HttpClient) newResponse() *define.Response {
|
||||
}
|
||||
}
|
||||
|
||||
// fillResponseHeader 填充响应header
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 21:30 2024/6/5
|
||||
func (hc *HttpClient) fillResponseHeader(response *define.Response) {
|
||||
response.Header = map[string]any{} // 清空已有数据
|
||||
response.HttpCode = response.RestyResponse.StatusCode() // http状态码
|
||||
response.HttpCodeStatus = response.RestyResponse.Status() // http状态码描述
|
||||
for headerName, headerValue := range response.RestyResponse.Header() {
|
||||
if len(headerValue) > 0 {
|
||||
response.Header[headerName] = headerValue[0]
|
||||
} else {
|
||||
response.Header[headerName] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fillResponseCookie 填充cookie
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 21:32 2024/6/5
|
||||
func (hc *HttpClient) fillResponseCookie(response *define.Response) {
|
||||
response.Cookie = map[string]any{} // 清空已有数据
|
||||
for _, cookieValue := range response.RestyResponse.Cookies() {
|
||||
response.Cookie[cookieValue.Name] = cookieValue.Value
|
||||
}
|
||||
}
|
||||
|
||||
// fillResponseBody 填充响应body
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 21:38 2024/6/5
|
||||
func (hc *HttpClient) fillResponseBody(response *define.Response) {
|
||||
response.Data = string(response.RestyResponse.Body())
|
||||
response.Code = gjson.Get(response.Data, hc.reqConfig.CodeField).String()
|
||||
response.Message = gjson.Get(response.Data, hc.reqConfig.MessageField).String()
|
||||
businessData := gjson.Get(response.Data, hc.reqConfig.DataField)
|
||||
if businessData.Value() == nil {
|
||||
// data为空指针, 归一化成空对象
|
||||
response.Body = map[string]any{}
|
||||
} else {
|
||||
if businessData.IsArray() {
|
||||
// 数组类型的转换
|
||||
response.Data = fmt.Sprintf(`{"list":` + businessData.String() + "}")
|
||||
} else {
|
||||
if businessData.IsObject() {
|
||||
// 返回的就是对象
|
||||
response.Data = businessData.String()
|
||||
} else {
|
||||
// 返回是普通类型
|
||||
response.Data = serialize.JSON.MarshalForStringIgnoreError(map[string]any{
|
||||
"value": businessData.Value(),
|
||||
})
|
||||
}
|
||||
}
|
||||
_ = serialize.JSON.UnmarshalWithNumber([]byte(response.Data), &response.Body)
|
||||
}
|
||||
|
||||
response.ExtendData = map[string]string{}
|
||||
gjson.Parse(response.Data).ForEach(func(key, value gjson.Result) bool {
|
||||
if key.String() == hc.reqConfig.CodeField ||
|
||||
key.String() == hc.reqConfig.MessageField ||
|
||||
key.String() == hc.reqConfig.DataField {
|
||||
return true
|
||||
}
|
||||
response.ExtendData[key.String()] = value.String()
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// isHttpCodeSuccess ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 22:48 2024/6/6
|
||||
func (hc *HttpClient) isCodeSuccess(response *define.Response) bool {
|
||||
for _, itemSuccessCode := range hc.reqConfig.SuccessCodeList {
|
||||
if itemSuccessCode == response.Code {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getCacheResult 获取缓存结果
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 16:04 2024/6/3
|
||||
func (hc *HttpClient) getCacheResult() *define.Response {
|
||||
if nil == hc.cacheInstance {
|
||||
log.RecordDebug("接口请求前缓存检测, 未设置缓存实例", map[string]any{}, hc.reqConfig)
|
||||
if nil == hc.reqOption.CacheInstance {
|
||||
log.RecordDebug("接口请求前缓存检测, 未设置缓存实例", map[string]any{}, hc.reqCfg)
|
||||
return nil
|
||||
}
|
||||
if !hc.cacheInstance.Enable() {
|
||||
log.RecordDebug("接口请求前缓存检测, 设置缓存实例, 但未启用缓存功能", map[string]any{}, hc.reqConfig)
|
||||
if !hc.reqOption.CacheInstance.Enable() {
|
||||
log.RecordDebug("接口请求前缓存检测, 设置缓存实例, 但未启用缓存功能", map[string]any{}, hc.reqCfg)
|
||||
return nil
|
||||
}
|
||||
startTime := time.Now().UnixMilli()
|
||||
cacheKey := hc.cacheInstance.GetKey(hc.reqConfig)
|
||||
cacheValue := strings.TrimSpace(hc.cacheInstance.GetValue(cacheKey))
|
||||
cacheKey := hc.reqOption.CacheInstance.GetKey(hc.reqCfg)
|
||||
cacheValue := strings.TrimSpace(hc.reqOption.CacheInstance.GetValue(cacheKey))
|
||||
if len(cacheValue) == 0 {
|
||||
log.RecordDebug("接口请求前缓存检测, 未读取到缓存数据", map[string]any{}, hc.reqConfig)
|
||||
log.RecordDebug("接口请求前缓存检测, 未读取到缓存数据", map[string]any{}, hc.reqCfg)
|
||||
return nil
|
||||
}
|
||||
response := hc.newResponse()
|
||||
if err := serialize.JSON.UnmarshalWithNumber([]byte(cacheValue), response); nil != err {
|
||||
log.RecordWarn("接口请求前缓存检测, 读取到缓存数据, 数据解析失败, 将跳过缓存, 请求对应接口", map[string]any{
|
||||
"err_msg": err.Error(),
|
||||
}, hc.reqConfig)
|
||||
}, hc.reqCfg)
|
||||
return nil
|
||||
}
|
||||
response.CacheInfo.IsCache = true // 设置缓存标记
|
||||
@ -609,7 +548,7 @@ func (hc *HttpClient) getCacheResult() *define.Response {
|
||||
response.UsedTime = response.RequestFinishTime - response.RequestStartTime // 耗时
|
||||
response.CacheInfo.CacheKey = cacheKey // 缓存key
|
||||
response.CacheInfo.CacheValue = cacheValue // 缓存值
|
||||
log.RecordDebug("接口请求前缓存检测, 命中缓存, 直接返回缓存数据", map[string]any{}, hc.reqConfig)
|
||||
log.RecordDebug("接口请求前缓存检测, 命中缓存, 直接返回缓存数据", map[string]any{}, hc.reqCfg)
|
||||
return response
|
||||
}
|
||||
|
||||
@ -619,37 +558,37 @@ func (hc *HttpClient) getCacheResult() *define.Response {
|
||||
//
|
||||
// Date : 16:24 2024/6/3
|
||||
func (hc *HttpClient) setCacheResult(response *define.Response) (bool, error) {
|
||||
if nil == response || nil == hc.cacheInstance {
|
||||
if nil == response || nil == hc.reqOption.CacheInstance {
|
||||
log.RecordDebug("接口请求成功后, 缓存设置失败", map[string]any{
|
||||
"response_is_nil": response == nil,
|
||||
"cache_instance_is_nil": hc.cacheInstance == nil,
|
||||
}, hc.reqConfig)
|
||||
"cache_instance_is_nil": hc.reqOption.CacheInstance == nil,
|
||||
}, hc.reqCfg)
|
||||
return false, nil
|
||||
}
|
||||
// 全局未开启或者当前请求不支持缓存
|
||||
globalCacheEnable := hc.cacheInstance.Enable()
|
||||
currentRequestAllowCache := hc.cacheInstance.IsAllow(hc.reqConfig, response)
|
||||
globalCacheEnable := hc.reqOption.CacheInstance.Enable()
|
||||
currentRequestAllowCache := hc.reqOption.CacheInstance.IsAllow(hc.reqCfg, response)
|
||||
log.RecordDebug("检测缓存是否允许执行", map[string]any{
|
||||
"current_cache_enable": currentRequestAllowCache,
|
||||
"global_cache_enable": globalCacheEnable,
|
||||
}, hc.reqConfig)
|
||||
}, hc.reqCfg)
|
||||
if !globalCacheEnable || !currentRequestAllowCache {
|
||||
return false, nil
|
||||
}
|
||||
cacheKey := hc.cacheInstance.GetKey(hc.reqConfig)
|
||||
cacheKey := hc.reqOption.CacheInstance.GetKey(hc.reqCfg)
|
||||
cacheValue := serialize.JSON.MarshalForStringIgnoreError(response)
|
||||
if err := hc.cacheInstance.SetValue(cacheKey, cacheValue); nil != err {
|
||||
if err := hc.reqOption.CacheInstance.SetValue(cacheKey, cacheValue); nil != err {
|
||||
log.RecordWarn("开启结果缓存, 缓存设置失败", map[string]any{
|
||||
"current_cache_enable": currentRequestAllowCache,
|
||||
"global_cache_enable": globalCacheEnable,
|
||||
"err_msg": err.Error(),
|
||||
}, hc.reqConfig)
|
||||
}, hc.reqCfg)
|
||||
return false, err
|
||||
}
|
||||
log.RecordDebug("开启结果缓存, 缓存设置成功", map[string]any{
|
||||
"current_cache_enable": currentRequestAllowCache,
|
||||
"global_cache_enable": globalCacheEnable,
|
||||
}, hc.reqConfig)
|
||||
}, hc.reqCfg)
|
||||
response.CacheInfo.CacheKey = cacheKey
|
||||
response.CacheInfo.CacheValue = cacheValue
|
||||
return true, nil
|
||||
|
183
httpclient/implement/response.go
Normal file
183
httpclient/implement/response.go
Normal file
@ -0,0 +1,183 @@
|
||||
// Package implement ...
|
||||
//
|
||||
// Description : implement ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2025-05-07 14:56
|
||||
package implement
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.zhangdeman.cn/zhangdeman/consts"
|
||||
"git.zhangdeman.cn/zhangdeman/network/httpclient/define"
|
||||
"git.zhangdeman.cn/zhangdeman/serialize"
|
||||
"github.com/tidwall/gjson"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Response 响应结果解析
|
||||
type Response struct {
|
||||
}
|
||||
|
||||
func (r *Response) Parse(reqConfig *define.Request, response *define.Response) error {
|
||||
r.fillResponseHeader(response)
|
||||
r.fillResponseCookie(response)
|
||||
return r.fillResponseBody(reqConfig, response)
|
||||
}
|
||||
|
||||
// fillResponseHeader 填充响应header
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 21:30 2024/6/5
|
||||
func (r *Response) fillResponseHeader(response *define.Response) {
|
||||
response.Header = map[string]any{} // 清空已有数据
|
||||
response.HttpCode = response.RestyResponse.StatusCode() // http状态码
|
||||
response.HttpCodeStatus = response.RestyResponse.Status() // http状态码描述
|
||||
for headerName, headerValue := range response.RestyResponse.Header() {
|
||||
if len(headerValue) > 0 {
|
||||
response.Header[headerName] = headerValue[0]
|
||||
} else {
|
||||
response.Header[headerName] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fillResponseCookie 填充cookie
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 21:32 2024/6/5
|
||||
func (r *Response) fillResponseCookie(response *define.Response) {
|
||||
response.Cookie = map[string]any{} // 清空已有数据
|
||||
for _, cookieValue := range response.RestyResponse.Cookies() {
|
||||
response.Cookie[cookieValue.Name] = cookieValue.Value
|
||||
}
|
||||
}
|
||||
|
||||
// fillResponseBody 填充响应body
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 21:38 2024/6/5
|
||||
func (r *Response) fillResponseBody(reqCfg *define.Request, response *define.Response) error {
|
||||
// 解析响应数据类型
|
||||
responseContentType := response.RestyResponse.Header().Get(consts.HeaderKeyContentType.String())
|
||||
if responseContentType == "" {
|
||||
// 返回数据未说明 Content-Type
|
||||
return errors.New("response content type is empty")
|
||||
}
|
||||
typeArr := strings.Split(strings.Split(responseContentType, ";")[0], "/")
|
||||
responseType := "json"
|
||||
if len(typeArr) > 0 {
|
||||
responseType = typeArr[len(typeArr)-1]
|
||||
}
|
||||
parser := ResponseParserTable[responseType]
|
||||
if parser == nil {
|
||||
// 未读取到的, 不支持解析对应的响应数据
|
||||
return errors.New("response type " + responseType + " is not support")
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
res map[string]any
|
||||
jsonByte []byte
|
||||
)
|
||||
|
||||
if err = parser.Unmarshal(response.RestyResponse.Body(), &res); nil != err {
|
||||
return errors.New("response parse body error :" + err.Error())
|
||||
}
|
||||
if jsonByte, err = parser.MarshalForByte(res); nil != err {
|
||||
return errors.New("response body Marshal error :" + err.Error())
|
||||
}
|
||||
response.Data = string(jsonByte)
|
||||
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为空指针, 归一化成空对象
|
||||
response.Body = map[string]any{}
|
||||
} else {
|
||||
if businessData.IsArray() {
|
||||
// 数组类型的转换
|
||||
response.Data = fmt.Sprintf(`{"list":` + businessData.String() + "}")
|
||||
} else {
|
||||
if businessData.IsObject() {
|
||||
// 返回的就是对象
|
||||
response.Data = businessData.String()
|
||||
} else {
|
||||
// 返回是普通类型
|
||||
response.Data = serialize.JSON.MarshalForStringIgnoreError(map[string]any{
|
||||
"value": businessData.Value(),
|
||||
})
|
||||
}
|
||||
}
|
||||
_ = serialize.JSON.UnmarshalWithNumber([]byte(response.Data), &response.Body)
|
||||
}
|
||||
|
||||
response.ExtendData = map[string]string{}
|
||||
gjson.Parse(response.Data).ForEach(func(key, value gjson.Result) bool {
|
||||
if key.String() == reqCfg.CodeField ||
|
||||
key.String() == reqCfg.MessageField ||
|
||||
key.String() == reqCfg.DataField {
|
||||
return true
|
||||
}
|
||||
response.ExtendData[key.String()] = value.String()
|
||||
return true
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// BusinessSuccess ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 22:48 2024/6/6
|
||||
func (r *Response) BusinessSuccess(reqCfg *define.Request, response *define.Response) bool {
|
||||
for _, itemSuccessCode := range reqCfg.SuccessCodeList {
|
||||
if itemSuccessCode == response.Code {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HttpSuccess ...
|
||||
func (r *Response) HttpSuccess(reqCfg *define.Request, response *define.Response) bool {
|
||||
if len(reqCfg.SuccessHttpCodeList) == 0 {
|
||||
// 没配置, 则 2xx 均视为成功
|
||||
return response.HttpCode/100 == 2
|
||||
}
|
||||
for _, itemHttpCode := range reqCfg.SuccessHttpCodeList {
|
||||
if itemHttpCode == response.HttpCode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NeedRetry 是否需要重试
|
||||
func (r *Response) NeedRetry(reqCfg *define.Request, response *define.Response) bool {
|
||||
if nil == reqCfg.RetryRule {
|
||||
// 未配置重试规则
|
||||
return false
|
||||
}
|
||||
if reqCfg.RetryRule.RetryCount <= 0 {
|
||||
// 未配置重试次数
|
||||
return false
|
||||
}
|
||||
for _, httpCode := range reqCfg.RetryRule.RetryHttpCodeList {
|
||||
if response.HttpCode == int(httpCode) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, businessCode := range reqCfg.RetryRule.RetryBusinessCodeList {
|
||||
if businessCode == response.Code {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
37
httpclient/implement/response_parser.go
Normal file
37
httpclient/implement/response_parser.go
Normal file
@ -0,0 +1,37 @@
|
||||
// Package implement ...
|
||||
//
|
||||
// Description : implement ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2025-05-07 14:53
|
||||
package implement
|
||||
|
||||
import (
|
||||
"git.zhangdeman.cn/zhangdeman/network/httpclient/abstract"
|
||||
"git.zhangdeman.cn/zhangdeman/serialize"
|
||||
)
|
||||
|
||||
// ResponseParserTable 响应数据解析器
|
||||
// 此处为内置解析实现, 未加锁, 未做并发安全处理
|
||||
// 原因: 无论 Add 还是 Delete, 均应在服务初始化阶段调用, 而在服务完成初始化, 完全启动之后, 应该只有读操作
|
||||
// 如果强要运行时, 动态操作此配置表, 需在外部调用处自行加锁
|
||||
// 也可完全自行实现相关解析实例, 在请求时通过 RequestOption 传入, 则控制权完全贵开发者所有
|
||||
var ResponseParserTable = map[string]abstract.IResponseParser{
|
||||
"json": serialize.JSON,
|
||||
"xml": serialize.Xml,
|
||||
"yml": serialize.Yml,
|
||||
"toml": serialize.Toml,
|
||||
"yaml": serialize.Yml,
|
||||
"text": serialize.JSON,
|
||||
}
|
||||
|
||||
// AddResponseParser 新增一个解析实现
|
||||
func AddResponseParser(t string, parser abstract.IResponseParser) {
|
||||
ResponseParserTable[t] = parser
|
||||
}
|
||||
|
||||
// RemoveResponseParser 移除一个解析实现
|
||||
func RemoveResponseParser(t string) {
|
||||
delete(ResponseParserTable, t)
|
||||
}
|
@ -10,7 +10,7 @@ package mesh
|
||||
import (
|
||||
"context"
|
||||
"git.zhangdeman.cn/zhangdeman/consts"
|
||||
"git.zhangdeman.cn/zhangdeman/network/httpclient/cache"
|
||||
"git.zhangdeman.cn/zhangdeman/network/httpclient/abstract"
|
||||
"git.zhangdeman.cn/zhangdeman/network/httpclient/define"
|
||||
"sync"
|
||||
)
|
||||
@ -38,7 +38,8 @@ type RequestConfigGroupItem struct {
|
||||
RequestCfg *define.Request `json:"request_cfg"` // 请求配置
|
||||
FailBehavior *RequestConfigGroupItemFailBehavior `json:"fail_behavior"` // 失败的行为, 不配置, 默认失败break
|
||||
FinalFailureAllow bool `json:"final_failure_allow"` // 已经确定当前请求是最终失败了,当前请求是否允许执行
|
||||
CacheInstance cache.ICache `json:"-"` // 数据缓存实例
|
||||
CacheInstance abstract.ICache `json:"-"` // 数据缓存实例
|
||||
ResponseParser abstract.IResponse `json:"-"` // 响应数据解析
|
||||
Condition any `json:"condition"` // TODO: 请求条件, 特定条件下不执行当前请求
|
||||
}
|
||||
|
||||
|
@ -147,7 +147,10 @@ func (c *client) doRequest(apiList []*RequestConfigGroupItem) bool {
|
||||
apiCfg.RequestCfg.Header = param[strings.ToLower(consts.RequestDataLocationHeader.String())] // header
|
||||
apiCfg.RequestCfg.Cookie = param[strings.ToLower(consts.RequestDataLocationCookie.String())] // cookie
|
||||
apiCfg.RequestCfg.Query = param[strings.ToLower(consts.RequestDataLocationQuery.String())] // query
|
||||
if httpClient, err = httpclient.NewHttpClient(apiCfg.RequestCfg, apiCfg.CacheInstance); nil != err {
|
||||
if httpClient, err = httpclient.NewHttpClient(apiCfg.RequestCfg, &httpclient.RequestOption{
|
||||
CacheInstance: apiCfg.CacheInstance,
|
||||
ResponseParser: apiCfg.ResponseParser,
|
||||
}); nil != err {
|
||||
// 此处获取客户端实例即发生异常, 忽略一切配置, 直接作为全局失败, 后续也不请求了
|
||||
c.resp.ErrorCode = "-500"
|
||||
c.resp.ErrorMessage = err.Error()
|
||||
|
18
httpclient/option.go
Normal file
18
httpclient/option.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Package httpclient ...
|
||||
//
|
||||
// Description : httpclient ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2025-05-07 12:19
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"git.zhangdeman.cn/zhangdeman/network/httpclient/abstract"
|
||||
)
|
||||
|
||||
// RequestOption 请求一些选项
|
||||
type RequestOption struct {
|
||||
CacheInstance abstract.ICache `json:"-"` // 数据结果缓存实例
|
||||
ResponseParser abstract.IResponse `json:"-"` // 返回结果解析, 不配置使用内置实现
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user