From ecf2bd6b553630c817728b4118c81953cfa98948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Thu, 17 Apr 2025 18:31:04 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E9=87=8D=E8=AF=95=E8=A7=84=E5=88=99?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=85=8D=E7=BD=AE=E6=8C=87=E5=AE=9A=20http?= =?UTF-8?q?=20code=20/=20business=20code=20=E9=87=8D=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- httpclient/client.go | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/httpclient/client.go b/httpclient/client.go index 6abab1b..7f930ee 100644 --- a/httpclient/client.go +++ b/httpclient/client.go @@ -37,6 +37,14 @@ func NewHttpClient(reqConfig *define.Request, cacheInstance cache.ICache) (*Http if err := validate.RequestConfig(reqConfig); nil != err { return nil, err } + if reqConfig.RetryRule == nil { + reqConfig.RetryRule = &define.RequestRetryRule{ + RetryCount: 0, + RetryTimeInterval: 0, + RetryHttpCodeList: []int64{}, + RetryBusinessCodeList: []string{}, + } // 未指定重试规则, 则使用默认重试规则 + } if len(reqConfig.Static) > 0 { for loc, valMap := range reqConfig.Static { if len(valMap) == 0 { @@ -276,6 +284,16 @@ func (hc *HttpClient) requestBackendApi() *define.Response { 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 + } + } if response.RestyResponse, err = hc.request.Send(); nil != err { errType := define.RequestFailTypeSend if err.Error() == resty.ErrRateLimitExceeded.Error() { @@ -293,6 +311,10 @@ func (hc *HttpClient) requestBackendApi() *define.Response { // 命中限流就不重试了 break } + if len(retryHttpCodeTable) > 0 && !retryHttpCodeTable[499] { + // 未配置超时重试 + break + } time.Sleep(time.Duration(hc.reqConfig.RetryRule.RetryTimeInterval) * time.Millisecond) continue } @@ -309,7 +331,8 @@ func (hc *HttpClient) requestBackendApi() *define.Response { hc.fillResponseHeader(response) hc.fillResponseCookie(response) hc.fillResponseBody(response) - if response.HttpCode != http.StatusOK { + if response.HttpCode < http.StatusOK || response.HttpCode >= http.StatusMultipleChoices { + // 非 2xx errType := define.RequestFailTypeServerError if response.HttpCode/100 == 4 { // 客户端错误 @@ -319,6 +342,10 @@ func (hc *HttpClient) requestBackendApi() *define.Response { Type: errType, Message: "http code is " + response.HttpCodeStatus + ", not success", } + if len(retryHttpCodeTable) > 0 && !retryHttpCodeTable[int64(response.HttpCode)] { + // 未配置http code重试 + break + } time.Sleep(time.Duration(hc.reqConfig.RetryRule.RetryTimeInterval) * time.Millisecond) continue } @@ -327,6 +354,10 @@ func (hc *HttpClient) requestBackendApi() *define.Response { Type: define.RequestFailTypeBusinessError, Message: "business code is " + response.Code + ", not success", } + if len(retryBusinessCodeTable) > 0 && !retryBusinessCodeTable[response.Code] { + // 未配置业务code重试 + break + } time.Sleep(time.Duration(hc.reqConfig.RetryRule.RetryTimeInterval) * time.Millisecond) continue } -- 2.36.6 From e8e02d9848e14acadbc0540d59b848bd47a574a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Thu, 17 Apr 2025 18:38:22 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=93=AA=E4=BA=9Bhttp=20code=E8=A7=86=E4=BD=9C=E6=88=90?= =?UTF-8?q?=E5=8A=9F,=20=E6=9C=AA=E6=8C=87=E5=AE=9A,=20=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=202xx=20=E8=A7=86=E4=B8=BA=E6=88=90=E5=8A=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- httpclient/client.go | 14 ++++++++++-- httpclient/define/request.go | 41 ++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/httpclient/client.go b/httpclient/client.go index 7f930ee..3b7a374 100644 --- a/httpclient/client.go +++ b/httpclient/client.go @@ -45,6 +45,10 @@ func NewHttpClient(reqConfig *define.Request, cacheInstance cache.ICache) (*Http RetryBusinessCodeList: []string{}, } // 未指定重试规则, 则使用默认重试规则 } + // 初始化成功的 http code list + if len(reqConfig.SuccessHttpCodeList) == 0 { + reqConfig.SuccessHttpCodeList = []int64{} + } if len(reqConfig.Static) > 0 { for loc, valMap := range reqConfig.Static { if len(valMap) == 0 { @@ -294,6 +298,10 @@ func (hc *HttpClient) requestBackendApi() *define.Response { retryBusinessCodeTable[businessCode] = true } } + successHttpCodeTable := make(map[int]bool) + for _, itemHttpCode := range hc.reqConfig.SuccessHttpCodeList { + successHttpCodeTable[itemHttpCode] = true + } if response.RestyResponse, err = hc.request.Send(); nil != err { errType := define.RequestFailTypeSend if err.Error() == resty.ErrRateLimitExceeded.Error() { @@ -331,8 +339,10 @@ func (hc *HttpClient) requestBackendApi() *define.Response { hc.fillResponseHeader(response) hc.fillResponseCookie(response) hc.fillResponseBody(response) - if response.HttpCode < http.StatusOK || response.HttpCode >= http.StatusMultipleChoices { - // 非 2xx + // 配置了当前code为成功, 或者未配置任何code, 当前code为2xx, 则认为请求成功 + isHttpSuccess := successHttpCodeTable[response.HttpCode] || (len(successHttpCodeTable) == 0 && response.HttpCode/100 == 2) + if !isHttpSuccess { + // 非 成功 errType := define.RequestFailTypeServerError if response.HttpCode/100 == 4 { // 客户端错误 diff --git a/httpclient/define/request.go b/httpclient/define/request.go index 0d8db94..e675e2e 100644 --- a/httpclient/define/request.go +++ b/httpclient/define/request.go @@ -19,26 +19,27 @@ import ( // // Date : 17:10 2024/5/24 type Request struct { - Ctx context.Context `json:"-"` // 请求上下文 - PathParam map[string]string `json:"path_param"` // 替换url中的占位符 - Body map[string]any `json:"body"` // 请求Body - Header map[string]any `json:"header"` // 请求Header - Cookie map[string]any `json:"cookie"` // 请求Cookie - Query map[string]any `json:"query"` // 请求query - Static map[string]map[string]any `json:"static"` // 静态参数: location => valName => val - FullUrl string `json:"full_url"` // 完整的请求URL - ContentType string `json:"content_type"` // 请求类型 - Method string `json:"method"` // 请求方法 - DataField string `json:"data_field"` // 数据字段 - CodeField string `json:"code_field"` // 业务状态码字段 - MessageField string `json:"message_field"` // code描述字段 - DataReceiver any `json:"-"` // 响应data部分数据解析 - SuccessCodeList []string `json:"success_code_list"` // 哪些业务状态码视为成功 - ConnectTimeout int64 `json:"connect_timeout"` // 连接超时时间: ms - ReadTimeout int64 `json:"read_timeout"` // 读取超时时间 - RetryRule *RequestRetryRule `json:"retry_rule"` // 重试规则 - Logger *zap.Logger `json:"-"` // 日志记录器 - RateLimiter resty.RateLimiter `json:"-"` // 流控实例 + Ctx context.Context `json:"-"` // 请求上下文 + PathParam map[string]string `json:"path_param"` // 替换url中的占位符 + Body map[string]any `json:"body"` // 请求Body + Header map[string]any `json:"header"` // 请求Header + Cookie map[string]any `json:"cookie"` // 请求Cookie + Query map[string]any `json:"query"` // 请求query + Static map[string]map[string]any `json:"static"` // 静态参数: location => valName => val + FullUrl string `json:"full_url"` // 完整的请求URL + ContentType string `json:"content_type"` // 请求类型 + Method string `json:"method"` // 请求方法 + DataField string `json:"data_field"` // 数据字段 + CodeField string `json:"code_field"` // 业务状态码字段 + MessageField string `json:"message_field"` // code描述字段 + DataReceiver any `json:"-"` // 响应data部分数据解析 + SuccessHttpCodeList []int `json:"success_http_code_list"` // 哪些http状态码视为成功, 不配置, 默认2xx + SuccessCodeList []string `json:"success_code_list"` // 哪些业务状态码视为成功 + ConnectTimeout int64 `json:"connect_timeout"` // 连接超时时间: ms + ReadTimeout int64 `json:"read_timeout"` // 读取超时时间 + RetryRule *RequestRetryRule `json:"retry_rule"` // 重试规则 + Logger *zap.Logger `json:"-"` // 日志记录器 + RateLimiter resty.RateLimiter `json:"-"` // 流控实例 } // RequestRetryRule 重试规则 -- 2.36.6 From 84cf228b5d314c3d4f41ccef297dc8364a9c21c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Thu, 17 Apr 2025 18:39:42 +0800 Subject: [PATCH 3/4] fix --- httpclient/client.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/httpclient/client.go b/httpclient/client.go index 3b7a374..76c396e 100644 --- a/httpclient/client.go +++ b/httpclient/client.go @@ -10,7 +10,6 @@ package httpclient import ( "fmt" "net" - "net/http" "strings" "time" @@ -47,7 +46,7 @@ func NewHttpClient(reqConfig *define.Request, cacheInstance cache.ICache) (*Http } // 初始化成功的 http code list if len(reqConfig.SuccessHttpCodeList) == 0 { - reqConfig.SuccessHttpCodeList = []int64{} + reqConfig.SuccessHttpCodeList = []int{} } if len(reqConfig.Static) > 0 { for loc, valMap := range reqConfig.Static { -- 2.36.6 From f780551ba697b1ab2732f91f2942e7ad89894c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Thu, 17 Apr 2025 18:42:45 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9url=20path=20param=20?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E5=8D=A0=E4=BD=8D=E6=A0=BC=E5=BC=8F=20{#para?= =?UTF-8?q?m=5Fname#}=20=3D>=20{param=5Fname}?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- httpclient/resty.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpclient/resty.go b/httpclient/resty.go index 8e2f9fc..bc375ee 100644 --- a/httpclient/resty.go +++ b/httpclient/resty.go @@ -63,7 +63,7 @@ func NewRestyClient(reqConfig *define.Request) (*resty.Client, *resty.Request) { if len(pathParamValue) == 0 { continue } - reqConfig.FullUrl = strings.ReplaceAll(reqConfig.FullUrl, "{#"+pathParamName+"#}", pathParamValue) + reqConfig.FullUrl = strings.ReplaceAll(reqConfig.FullUrl, "{"+pathParamName+"}", pathParamValue) } request.Method = reqConfig.Method // 请求方法 cookieList := make([]*http.Cookie, 0) -- 2.36.6