diff --git a/httpclient/abstract/IResponse.go b/httpclient/abstract/IResponse.go new file mode 100644 index 0000000..5a11f7a --- /dev/null +++ b/httpclient/abstract/IResponse.go @@ -0,0 +1,30 @@ +// 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) + // BusinessSuccess 业务状态码是否成功 + BusinessSuccess(reqCfg *define.Request, response *define.Response) bool + // HttpSuccess http状态码是否成功 + HttpSuccess(reqCfg *define.Request, response *define.Response) bool +} diff --git a/httpclient/client.go b/httpclient/client.go index 1d0416b..4e02e57 100644 --- a/httpclient/client.go +++ b/httpclient/client.go @@ -9,6 +9,7 @@ package httpclient import ( "fmt" + "git.zhangdeman.cn/zhangdeman/network/httpclient/implement" "net" "strings" "time" @@ -19,7 +20,6 @@ import ( "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,6 +28,13 @@ import ( // // Date : 15:27 2024/5/31 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() // 未单独指定日志实例, 则使用全局日志实例 } @@ -420,11 +427,9 @@ func (hc *HttpClient) requestBackendApi() *define.Response { 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 @@ -450,7 +455,7 @@ func (hc *HttpClient) requestBackendApi() *define.Response { } 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", @@ -517,93 +522,6 @@ 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.reqCfg.CodeField).String() - response.Message = gjson.Get(response.Data, hc.reqCfg.MessageField).String() - businessData := gjson.Get(response.Data, hc.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() == hc.reqCfg.CodeField || - key.String() == hc.reqCfg.MessageField || - key.String() == hc.reqCfg.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.reqCfg.SuccessCodeList { - if itemSuccessCode == response.Code { - return true - } - } - return false -} - // getCacheResult 获取缓存结果 // // Author : go_developer@163.com<白茶清欢> diff --git a/httpclient/implement/response.go b/httpclient/implement/response.go new file mode 100644 index 0000000..e3168f9 --- /dev/null +++ b/httpclient/implement/response.go @@ -0,0 +1,127 @@ +// Package implement ... +// +// Description : implement ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2025-05-07 14:56 +package implement + +import ( + "fmt" + "git.zhangdeman.cn/zhangdeman/network/httpclient/define" + "git.zhangdeman.cn/zhangdeman/serialize" + "github.com/tidwall/gjson" +) + +// Response 响应结果解析 +type Response struct { +} + +func (r *Response) Parse(reqConfig *define.Request, response *define.Response) { + r.fillResponseHeader(response) + r.fillResponseCookie(response) + r.fillResponseBody(reqConfig, response) + return +} + +// 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) { + response.Data = string(response.RestyResponse.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为空指针, 归一化成空对象 + 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 + }) +} + +// 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 +} diff --git a/httpclient/implement/response_parser.go b/httpclient/implement/response_parser.go new file mode 100644 index 0000000..3e14fc7 --- /dev/null +++ b/httpclient/implement/response_parser.go @@ -0,0 +1,23 @@ +// 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 响应数据解析器 +var ResponseParserTable = map[string]abstract.IResponseParser{ + "json": serialize.JSON, + "xml": serialize.Xml, + "yml": serialize.Yml, + "toml": serialize.Toml, + "yaml": serialize.Yml, + "text": serialize.JSON, +} diff --git a/httpclient/option.go b/httpclient/option.go index daa9478..3176c56 100644 --- a/httpclient/option.go +++ b/httpclient/option.go @@ -13,5 +13,6 @@ import ( // RequestOption 请求一些选项 type RequestOption struct { - CacheInstance abstract.ICache `json:"-"` // 数据结果缓存实例 + CacheInstance abstract.ICache `json:"-"` // 数据结果缓存实例 + ResponseParser abstract.IResponse `json:"-"` // 返回结果解析, 不配置使用内置实现 }