From d9da985da5f68219a9f73e428a0dfe4bb8fff455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Fri, 28 Mar 2025 17:40:51 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=84=E5=88=92mesh=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E7=9A=84=E6=A1=86=E6=9E=B6=E7=BB=93=E6=9E=84,=20=E7=BB=86?= =?UTF-8?q?=E8=8A=82=E5=BE=85=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- httpclient/define/mesh.go | 76 ------------------------- httpclient/define/request.go | 3 + httpclient/mesh/define.go | 103 ++++++++++++++++++++++++++++++++++ httpclient/mesh/mesh.go | 106 +++++++++++++++++++++++++++++++++++ 4 files changed, 212 insertions(+), 76 deletions(-) delete mode 100644 httpclient/define/mesh.go create mode 100644 httpclient/mesh/define.go diff --git a/httpclient/define/mesh.go b/httpclient/define/mesh.go deleted file mode 100644 index f9c46ac..0000000 --- a/httpclient/define/mesh.go +++ /dev/null @@ -1,76 +0,0 @@ -// Package define ... -// -// Description : define ... -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 2025-03-28 14:12 -package define - -import ( - "git.zhangdeman.cn/zhangdeman/consts" - "sync" -) - -// MeshRequestConfig 聚合请求 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 14:14 2025/3/28 -type MeshRequestConfig struct { - CommonParam map[string]any `json:"common_param"` // 公共参数 - Group [][]MeshRequestConfigGroupItem `json:"group"` // 请求分组 - ResultRule []MeshRequestConfigResultRule `json:"result_rule"` // 结果提取规则, 多个接口的结果构造成一个返回结果 - Receiver any `json:"-"` // 整体请求结果数据接收的指针 -} - -// MeshRequestConfigGroupItem 分组请求每一项的结构 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 14:16 2025/3/28 -type MeshRequestConfigGroupItem struct { - Alias string `json:"alias"` // 请求别名 - ParamRuleList []MeshRequestConfigGroupItemParamRule `json:"param_rule_list"` // 参数规则列表 - RequestCfg Request `json:"request_cfg"` // 请求配置 - Condition any `json:"condition"` // TODO: 请求条件, 特定条件下不执行当前请求 -} - -// MeshRequestConfigGroupItemParamRule 参数提取规则 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 14:23 2025/3/28 -type MeshRequestConfigGroupItemParamRule struct { - Location consts.RequestDataLocation `json:"location"` // 参数位置 - Path string `json:"path"` // 参数设置的路径 - Type consts.DataType `json:"type"` // 参数类型 - SourceAlias string `json:"source_alias"` // 数据源请求别名 __COMMON__ 代表从公共参数读取数据 - SourceResultPath string `json:"source_result_path"` // 数据源数据, 从结果中获取的路径 -} - -// MeshRequestConfigResultRule 返回值构建规则 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 14:28 2025/3/28 -type MeshRequestConfigResultRule struct { - RequestAlias string `json:"request_alias"` // 请求别名 - RequestResultLocation consts.ResponseDataLocation `json:"request_result_location"` // 请求结果位置 - RequestResultPath string `json:"request_result_path"` // 请求结果路径 - Location consts.ResponseDataLocation `json:"location"` // 返回值位置 - DataPath string `json:"data_path"` // 返回值的路径 - DataType consts.DataType `json:"data_type"` // 返回值类型 -} - -// MeshResponse 聚合请求响应结果 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 14:40 2025/3/28 -type MeshResponse struct { - Raw []byte `json:"raw"` // 返回结果的raw数据(按照result_rule格式化后的结果) - DataMap map[string]any `json:"data_map"` // 返回结果的map格式(按照result_rule格式化后的结果) - AliasResultTable map[string]Response `json:"alias_result_table"` // 每一个请求的返回结果 key: 请求别名 value: 请求返回数据 - Lock *sync.RWMutex `json:"-"` // 数据锁, public, 外部拿到结果需要做一席并发读写操作, 可以直接服用这把所 -} diff --git a/httpclient/define/request.go b/httpclient/define/request.go index df43f58..cdc24e9 100644 --- a/httpclient/define/request.go +++ b/httpclient/define/request.go @@ -7,12 +7,15 @@ // Date : 2024-05-24 17:09 package define +import "context" + // Request 请求配置 // // Author : go_developer@163.com<白茶清欢> // // 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]string `json:"header"` // 请求Header diff --git a/httpclient/mesh/define.go b/httpclient/mesh/define.go new file mode 100644 index 0000000..d51c228 --- /dev/null +++ b/httpclient/mesh/define.go @@ -0,0 +1,103 @@ +// Package mesh ... +// +// Description : define ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2025-03-28 14:12 +package mesh + +import ( + "context" + "git.zhangdeman.cn/zhangdeman/consts" + "git.zhangdeman.cn/zhangdeman/network/httpclient/cache" + "git.zhangdeman.cn/zhangdeman/network/httpclient/define" + "sync" +) + +// RequestConfig 聚合请求 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 14:14 2025/3/28 +type RequestConfig struct { + Ctx context.Context `json:"-"` // 上下文 + CommonParam map[string]any `json:"common_param"` // 公共参数 + Group [][]*RequestConfigGroupItem `json:"group"` // 请求分组 + ResultRule []*RequestConfigResultRule `json:"result_rule"` // 结果提取规则, 多个接口的结果构造成一个返回结果 +} + +// RequestConfigGroupItem 分组请求每一项的结构 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 14:16 2025/3/28 +type RequestConfigGroupItem struct { + Alias string `json:"alias"` // 请求别名 + ParamRuleList []*RequestConfigGroupItemParamRule `json:"param_rule_list"` // 参数规则列表 + RequestCfg *define.Request `json:"request_cfg"` // 请求配置 + FailBehavior *RequestConfigGroupItemFailBehavior `json:"fail_behavior"` // 失败的行为, 不配置, 默认失败break + FinalFailureAllow bool `json:"final_failure_allow"` // 已经确定当前请求是最终失败了,当前请求是否允许执行 + CacheInstance cache.ICache `json:"-"` // 数据缓存实例 + Condition any `json:"condition"` // TODO: 请求条件, 特定条件下不执行当前请求 +} + +// RequestConfigGroupItemFailBehavior 失败的行为 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 15:51 2025/3/28 +type RequestConfigGroupItemFailBehavior struct { + Action string `json:"action"` // 请求失败的行为: continue: 继续执行下一个请求, break: 停止执行后续请求 + FinalFailure bool `json:"final_failure"` // 是否作为最终失败: action = break时, 无论外部是什么值, 永远为true, 当 action = continue时, 该值由外部传入, 原因: 当前接口失败, 可能还需要调用一些后续接口做些逻辑. +} + +// RequestConfigGroupItemParamRule 参数提取规则 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 14:23 2025/3/28 +type RequestConfigGroupItemParamRule struct { + Location consts.RequestDataLocation `json:"location"` // 参数位置 + Path string `json:"path"` // 参数设置的路径 + Type consts.DataType `json:"type"` // 参数类型 + SourceAlias string `json:"source_alias"` // 数据源请求别名 __COMMON__ 代表从公共参数读取数据 + SourceResultPath string `json:"source_result_path"` // 数据源数据, 从结果中获取的路径 +} + +// RequestConfigResultRule 返回值构建规则 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 14:28 2025/3/28 +type RequestConfigResultRule struct { + RequestAlias string `json:"request_alias"` // 请求别名 + RequestResultLocation consts.ResponseDataLocation `json:"request_result_location"` // 请求结果位置 + RequestResultPath string `json:"request_result_path"` // 请求结果路径 + Location consts.ResponseDataLocation `json:"location"` // 返回值位置 + DataPath string `json:"data_path"` // 返回值的路径 + DataType consts.DataType `json:"data_type"` // 返回值类型 +} + +// Response 聚合请求响应结果 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 14:40 2025/3/28 +type Response struct { + FinalFailure bool `json:"final_failure"` // 是否最终失败 + FailureApiAlias string `json:"failure_api_alias"` // 导致最终失败的接口别名 + IsSuccess bool `json:"is_success"` // 请求是否成功 + ErrorCode string `json:"error_code"` // 错误码 + ErrorMessage string `json:"error_message"` // 错误信息 + FailApiAlias []string `json:"fail_api_alias"` // 失败的接口别名 + Raw []byte `json:"raw"` // 返回结果的raw数据(按照result_rule格式化后的结果) + DataMap map[string]any `json:"data_map"` // 返回结果的map格式(按照result_rule格式化后的结果) + AliasResultTable map[string]*Response `json:"alias_result_table"` // 每一个请求的返回结果 key: 请求别名 value: 请求返回数据 + Lock *sync.RWMutex `json:"-"` // 数据锁, public, 外部拿到结果需要做一席并发读写操作, 可以直接复用这把锁 +} + +const ( + FailBehaviorContinue = "continue" // 失败后继续请求 + FailBehaviorError = "error" // 失败后终止请求 +) diff --git a/httpclient/mesh/mesh.go b/httpclient/mesh/mesh.go index b440d4d..41d6566 100644 --- a/httpclient/mesh/mesh.go +++ b/httpclient/mesh/mesh.go @@ -6,3 +6,109 @@ // // Date : 2025-03-28 14:11 package mesh + +import ( + "context" + "git.zhangdeman.cn/zhangdeman/network/httpclient" + "sync" +) + +func Request(req *RequestConfig, receiver any) *Response { + if nil == req.Ctx { + req.Ctx = context.Background() + } + c := &client{ + resp: &Response{ + IsSuccess: false, + ErrorCode: "", + ErrorMessage: "", + Raw: nil, + DataMap: nil, + AliasResultTable: make(map[string]*Response), + Lock: &sync.RWMutex{}, + }, + reqCfg: req, + receiver: receiver, + } + return c.Request() +} + +type client struct { + ctx context.Context + resp *Response + reqCfg *RequestConfig + receiver any +} + +func (c *client) Request() *Response { + for _, itemGroup := range c.reqCfg.Group { + if !c.doRequest(itemGroup) { + break + } + } + return c.resp +} + +// doRequest 返回是否继续 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 15:30 2025/3/28 +func (c *client) doRequest(apiList []*RequestConfigGroupItem) bool { + if len(apiList) == 0 { + return true + } + // 初始化请求配置, 并获取 http client 实例 + httpClientList := make([]*httpclient.HttpClient, 0) + for _, apiCfg := range apiList { + var ( + err error + httpClient *httpclient.HttpClient + ) + // 初始化一下请求 + c.initApiCfg(apiCfg) + if httpClient, err = httpclient.NewHttpClient(apiCfg.RequestCfg, apiCfg.CacheInstance); nil != err { + // 此处获取客户端实例即发生异常, 忽略一切配置, 直接作为全局失败, 后续也不请求了 + c.resp.ErrorCode = "-500" + c.resp.ErrorMessage = err.Error() + return false + } + httpClientList = append(httpClientList, httpClient) + } + isContinue := true + wg := &sync.WaitGroup{} + for idx, apiCfg := range apiList { + wg.Add(1) + go func(clientIdx int, apiCfg *RequestConfigGroupItem) { + defer func() { + if err := recover(); nil != err { + + } + wg.Done() + }() + // TODO : 判断是否已经是最终失败 + resp := httpClientList[clientIdx].Request() + c.resp.Lock.Lock() + defer c.resp.Lock.Unlock() + if !resp.IsSuccess { + } + }(idx, apiCfg) + } + wg.Wait() + return isContinue +} + +func (c *client) initApiCfg(apiCfg *RequestConfigGroupItem) { + if apiCfg.FailBehavior == nil { + apiCfg.FailBehavior = &RequestConfigGroupItemFailBehavior{ + Action: FailBehaviorError, // 默认失败终止请求 + FinalFailure: true, // 默认一旦失败,则争个整个失败 + } + } + if apiCfg.FailBehavior.Action == FailBehaviorError { + // 配置了请求失败则报错, 一定是导致结果最终失败 + apiCfg.FailBehavior.FinalFailure = true + } + // 每一个请求有独立的context + apiCfg.RequestCfg.Ctx = context.WithValue(c.reqCfg.Ctx, "alias", apiCfg.Alias) +}