From 25cc9564a21d6a7492c010581df50a8492763257 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, 26 Dec 2025 10:47:40 +0800 Subject: [PATCH 01/12] feat: update comment --- router/define.go | 10 +++++----- router/option.go | 40 ---------------------------------------- 2 files changed, 5 insertions(+), 45 deletions(-) diff --git a/router/define.go b/router/define.go index f629ce7..0d54038 100644 --- a/router/define.go +++ b/router/define.go @@ -15,20 +15,20 @@ const ( PrefixFuncName = "RouterPrefix" // 路由前缀函数名称 MiddlewareFuncName = "RouterMiddleware" // 路由中间件函数名称 GinContextType = "*gin.Context" // gin context 类型名称 - ErrorType = "error" // error类型 - ErrorInterfaceFuncName = "Error" // error接口需要实现的方法名称 + ErrorType = "error" // error 类型 + ErrorInterfaceFuncName = "Error" // error 接口需要实现的方法名称 ) const ( TagNamePath = "path" // 接口的请求路径 TagNameMethod = "method" // 接口的请求方法 - TagNameUriTag = "tag" // 接口的tag + TagNameUriTag = "tag" // 接口的 tag TagNameDesc = "desc" // 接口的描述 TagNameOutputStrict = "output_strict" // 接口数据是否为严格模式 : 严格模式, 响应数据必须是结构体/map,非严格模式返回任意值 - TagNameHookSync = "hook-sync" // hook同步执行 + TagNameHookSync = "hook-sync" // hook 同步执行 TagNameBinding = "binding" // gin 内置的验证规则tag TagNameValidate = "validate" // validator v10 默认的验证规则tag - TagNameErrMsg = "err" // 验证失败错误信息tag + TagNameErrMsg = "err" // 验证失败错误信息 tag ) // UriConfig 接口配置 diff --git a/router/option.go b/router/option.go index e79bc3f..b551c21 100644 --- a/router/option.go +++ b/router/option.go @@ -18,10 +18,6 @@ import ( type SetServerOptionFunc func(so *serverOption) // serverOption 获取server实例的选项 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 17:28 2025/2/18 type serverOption struct { swaggerUiTheme string // swagger 主题 swaggerBaseUri string // swagger基础path @@ -60,10 +56,6 @@ func WithInitContextData(formatFunc func(ctx *gin.Context) map[string]any) SetSe } // WithSwaggerUITheme 设置swaggerUI主题 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 17:29 2025/2/18 func WithSwaggerUITheme(uiTheme string) SetServerOptionFunc { return func(so *serverOption) { uiTheme = strings.TrimSpace(uiTheme) @@ -75,10 +67,6 @@ func WithSwaggerUITheme(uiTheme string) SetServerOptionFunc { } // WithGlobalMiddlewareList 设置全局中间件 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 17:33 2025/2/18 func WithGlobalMiddlewareList(middlewareList ...gin.HandlerFunc) SetServerOptionFunc { return func(so *serverOption) { so.globalMiddlewareList = middlewareList @@ -86,10 +74,6 @@ func WithGlobalMiddlewareList(middlewareList ...gin.HandlerFunc) SetServerOption } // WithSwaggerBaseUri ... -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 18:05 2025/2/18 func WithSwaggerBaseUri(baseUri string) SetServerOptionFunc { return func(so *serverOption) { baseUri = strings.TrimSpace(baseUri) @@ -102,10 +86,6 @@ func WithSwaggerBaseUri(baseUri string) SetServerOptionFunc { } // WithDisableSwaggerDoc 禁用swagger文档 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 17:35 2025/2/18 func WithDisableSwaggerDoc() SetServerOptionFunc { return func(so *serverOption) { so.disableSwaggerDoc = true @@ -113,10 +93,6 @@ func WithDisableSwaggerDoc() SetServerOptionFunc { } // WithServerInfo 设置serverInfo -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 17:51 2025/2/18 func WithServerInfo(serverInfo *apiDocDefine.Info) SetServerOptionFunc { return func(so *serverOption) { if nil == serverInfo { @@ -127,10 +103,6 @@ func WithServerInfo(serverInfo *apiDocDefine.Info) SetServerOptionFunc { } // WithServerList 设置服务器列表 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 17:52 2025/2/18 func WithServerList(serverList []*apiDocDefine.ServerItem) SetServerOptionFunc { return func(so *serverOption) { if len(serverList) == 0 { @@ -141,10 +113,6 @@ func WithServerList(serverList []*apiDocDefine.ServerItem) SetServerOptionFunc { } // WithPprofEnable 启用pprof -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 15:10 2025/2/21 func WithPprofEnable() SetServerOptionFunc { return func(so *serverOption) { so.enablePprof = true @@ -152,10 +120,6 @@ func WithPprofEnable() SetServerOptionFunc { } // WithEnableCors 启用全局跨域 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 14:56 2025/2/22 func WithEnableCors() SetServerOptionFunc { return func(so *serverOption) { so.enableCors = true @@ -163,10 +127,6 @@ func WithEnableCors() SetServerOptionFunc { } // WithLoggerCfg ... -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 15:25 2025/2/22 func WithLoggerCfg(loggerCfg *middleware.AccessConfig) SetServerOptionFunc { return func(so *serverOption) { if nil != loggerCfg || nil != loggerCfg.Logger { From 5c04a736c7bb39e495573cf3fedb629210571fc2 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, 26 Dec 2025 10:53:09 +0800 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E9=AA=8C=E8=AF=81=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- router/config.go | 21 --------------------- router/handler.go | 2 +- router/validator.go | 18 +++++++++++++----- 3 files changed, 14 insertions(+), 27 deletions(-) delete mode 100644 router/config.go diff --git a/router/config.go b/router/config.go deleted file mode 100644 index 859dc7b..0000000 --- a/router/config.go +++ /dev/null @@ -1,21 +0,0 @@ -// Package router ... -// -// Description : router ... -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 2025-02-07 17:41 -package router - -import "strings" - -var defaultValidateErrTag = "err" - -// SetValidateErrTag 设置验证失败时, 获取错误信息的tag字段 -func SetValidateErrTag(tagName string) { - tagName = strings.TrimSpace(tagName) - if tagName == "" { - return - } - defaultValidateErrTag = tagName -} diff --git a/router/handler.go b/router/handler.go index 32e2284..19df456 100644 --- a/router/handler.go +++ b/router/handler.go @@ -80,7 +80,7 @@ func (s *server) RequestHandler(uriCfg UriConfig) gin.HandlerFunc { // 默认请求失败 ctx.Set(consts.GinRequestSuccess, false) - // 初始化响应之后logic + // 初始化响应之后 logic logicAfterResponse := &define.LogicAfterResponse{ SuccessHookFuncList: make([]func(ctx *gin.Context), 0), FailureHookFuncList: make([]func(ctx *gin.Context), 0), diff --git a/router/validator.go b/router/validator.go index 92602f0..1fbd5fc 100644 --- a/router/validator.go +++ b/router/validator.go @@ -10,16 +10,24 @@ package router import ( "errors" "fmt" - "github.com/go-playground/validator/v10" "reflect" "strings" + + "github.com/go-playground/validator/v10" ) +var defaultValidateErrTag = "err" + +// SetValidateErrTag 设置验证失败时, 获取错误信息的tag字段 +func SetValidateErrTag(tagName string) { + tagName = strings.TrimSpace(tagName) + if tagName == "" { + return + } + defaultValidateErrTag = tagName +} + // GetValidateErr 格式化错误信息 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 22:19 2025/1/15 func GetValidateErr(obj any, rawErr error) error { if nil == rawErr { return nil From 03c1eadd603e60680965cf3d9303d2cf61667546 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, 26 Dec 2025 11:25:13 +0800 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=AF=B9?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=85=83=E6=95=B0=E6=8D=AE=20Meta=20?= =?UTF-8?q?=E7=9A=84=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- router/controller.go | 56 +++++++++++++++------------------------ router/define.go | 12 +++++---- router/server.go | 62 ++++++++++++++++---------------------------- 3 files changed, 50 insertions(+), 80 deletions(-) diff --git a/router/controller.go b/router/controller.go index 97e1da1..fae87fe 100644 --- a/router/controller.go +++ b/router/controller.go @@ -52,10 +52,6 @@ func (c controller) Parse(inputController any) map[string]UriConfig { // 参数 : 方法反射结果 // // 返回值 : 第一个 -> 解析出的接口配置 第二个 -> 是否要注册为接口 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 16:05 2025/1/27 func (c controller) methodConfig(reflectMethod reflect.Method) (cfg UriConfig, needRegister bool) { methodType := reflectMethod.Type // num0: 函数声明 @@ -71,7 +67,7 @@ func (c controller) methodConfig(reflectMethod reflect.Method) (cfg UriConfig, n needRegister = false return } - // 解析第二个参数是组合Meta的form表单 + // 解析第二个参数是组合 Meta 的form表单 formType := methodType.In(2) cfg.FormDataType = formType if formType.Kind() == reflect.Ptr { @@ -89,7 +85,7 @@ func (c controller) methodConfig(reflectMethod reflect.Method) (cfg UriConfig, n // 判断是否是实现 error接口的方法 outputErrParse := false for j := 0; j < methodType.Out(1).NumMethod(); j++ { - if methodType.Out(1).Method(j).Name == ErrorInterfaceFuncName && // 实现Error方法 + if methodType.Out(1).Method(j).Name == ErrorInterfaceFuncName && // 实现 Error 方法 methodType.Out(1).Method(j).Type.NumIn() == 0 && // 没有任何参数 methodType.Out(1).Method(j).Type.NumOut() == 1 && // 一个返回值 methodType.Out(1).Method(j).Type.Out(0).Kind().String() == reflect.String.String() { @@ -102,16 +98,8 @@ func (c controller) methodConfig(reflectMethod reflect.Method) (cfg UriConfig, n return } } - // 解析meta信息 - cfg.Path = metaField.Tag.Get(TagNamePath) - cfg.RequestMethod = metaField.Tag.Get(TagNameMethod) - cfg.Desc = metaField.Tag.Get(TagNameDesc) - cfg.TagList = strings.Split(metaField.Tag.Get(TagNameUriTag), ",") - // 解析第一个返回值, 要求必须是结构体或者是map - outputStrictModel := metaField.Tag.Get(TagNameOutputStrict) - hookSync := strings.ToLower(metaField.Tag.Get(TagNameHookSync)) - cfg.HookSync = hookSync == "1" || hookSync == "true" // 同步执行判断 - cfg.OutputStrict = outputStrictModel == "1" || outputStrictModel == "true" + c.setUriMeta(metaField, &cfg) + if cfg.OutputStrict { // 开启输出严格模式校验 if methodType.Out(0).Kind() != reflect.Struct && methodType.Out(0).Kind() != reflect.Map { @@ -119,6 +107,7 @@ func (c controller) methodConfig(reflectMethod reflect.Method) (cfg UriConfig, n return } } + // 解析参数配置 //cfg.ParamList = c.parseParamConfig(formType) cfg.ApiLogicFunc = reflectMethod @@ -126,24 +115,21 @@ func (c controller) methodConfig(reflectMethod reflect.Method) (cfg UriConfig, n return } -// parseParamConfig 解析参数配置 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 14:35 2025/2/7 -func (c controller) parseParamConfig(formDataType reflect.Type) []UriParam { - res := make([]UriParam, 0) - for i := 0; i < formDataType.NumField(); i++ { - structField := formDataType.Field(i) - if structField.Name == FieldNameMeta { - // Meta 字段, 忽略 - continue - } - jsonTag := structField.Tag.Get("json") - if jsonTag == "" { - jsonTag = structField.Name - } - +// setUriMeta 设置接口的 meta 信息 +func (c controller) setUriMeta(metaField reflect.StructField, cfg *UriConfig) { + // 解析 meta 信息 + cfg.Path = metaField.Tag.Get(TagNamePath) // 接口路由 + cfg.RequestMethod = strings.Split(strings.ToUpper(metaField.Tag.Get(TagNameMethod)), ",") // 请求方法 + cfg.Desc = metaField.Tag.Get(TagNameDesc) // 接口描述 + cfg.TagList = strings.Split(metaField.Tag.Get(TagNameUriTag), ",") // 接口标签 + // 以下是bool类型的配置解析 + var boolMetaParse = func(tagName string) bool { + val := strings.ToLower(metaField.Tag.Get(tagName)) + return val == "1" || val == "true" } - return res + cfg.OutputStrict = boolMetaParse(TagNameOutputStrict) // 配置接口数据是否严格要求是对象 + cfg.HookSync = boolMetaParse(TagNameHookSync) // 同步执行判断 + cfg.IsWebsocket = boolMetaParse(TagNameIsWebsocket) // 是否是 websocket 接口 + cfg.IsSse = boolMetaParse(TagNameIsSse) // 是否是 sse 接口 + cfg.NoLogin = boolMetaParse(TagNameNoLogin) // 是否需要登录 } diff --git a/router/define.go b/router/define.go index 0d54038..d9ccbdc 100644 --- a/router/define.go +++ b/router/define.go @@ -24,25 +24,27 @@ const ( TagNameMethod = "method" // 接口的请求方法 TagNameUriTag = "tag" // 接口的 tag TagNameDesc = "desc" // 接口的描述 + TagNameIsSse = "is-sse" // 是否 SSE 连接 + TagNameIsWebsocket = "is-ws" // 是否 websocket 连接 TagNameOutputStrict = "output_strict" // 接口数据是否为严格模式 : 严格模式, 响应数据必须是结构体/map,非严格模式返回任意值 TagNameHookSync = "hook-sync" // hook 同步执行 + TagNameNoLogin = "no-login" // 接口是否需要登录(无需登录, 则有token就验证, 无token不验证) TagNameBinding = "binding" // gin 内置的验证规则tag TagNameValidate = "validate" // validator v10 默认的验证规则tag TagNameErrMsg = "err" // 验证失败错误信息 tag ) // UriConfig 接口配置 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 15:41 2024/7/21 type UriConfig struct { Path string `json:"path"` // 接口路由, 必须配置 - RequestMethod string `json:"request_method"` // 接口请求方法, 必须配置 + RequestMethod []string `json:"request_method"` // 接口请求方法, 必须配置 TagList []string `json:"tag_list"` // 接口分组 Desc string `json:"desc"` // 接口描述 + IsSse bool `json:"is_sse"` // 是否 SSE 连接 + IsWebsocket bool `json:"is_ws"` // 是否 websocket 连接 OutputStrict bool `json:"output_strict"` // 接口是否为严格模式 : 不配置,可返回任意类型, 配置, 必须返回结构体或者map HookSync bool `json:"hook_sync"` // 接口主逻辑执行完成之后,hook是否同步执行, 默认异步执行 + NoLogin bool `json:"no_login"` // 接口是否需要登录(无需登录, 则有token就验证, 无token不验证) FormDataType reflect.Type `json:"-"` // 表单数据类型 ResultDataType reflect.Type `json:"-"` // 返回值数据类型 ApiStructValue reflect.Value `json:"-"` // 逻辑函数所属结构体取值 diff --git a/router/server.go b/router/server.go index ca39410..3ceecfc 100644 --- a/router/server.go +++ b/router/server.go @@ -135,10 +135,6 @@ type server struct { } // Start 启动服务 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 18:31 2025/2/7 func (s *server) Start() { // 注册文档 s.uiInstance.RegisterHandler(s.router, s.option.swaggerBaseUri) @@ -153,28 +149,16 @@ func (s *server) Start() { } // Router 对外访问路由实例, 不建议直接用 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 17:35 2025/2/18 func (s *server) Router() *gin.Engine { return s.router } // Handler404 注册404的处理方法 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 16:01 2025/2/22 func (s *server) Handler404(f gin.HandlerFunc) { s.router.NoRoute(f) } // SetCustomRouter 自定义路由处理 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 16:09 2025/2/22 func (s *server) SetCustomRouter(f func(r *gin.Engine)) { if nil == f { return @@ -183,10 +167,6 @@ func (s *server) SetCustomRouter(f func(r *gin.Engine)) { } // Group 注册接口路由 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 19:35 2025/1/27 func (s *server) Group(routerPrefix string, middlewareList []gin.HandlerFunc, cList ...any) { g := s.router.Group(routerPrefix) g.Use(middlewareList...) @@ -195,27 +175,29 @@ func (s *server) Group(routerPrefix string, middlewareList []gin.HandlerFunc, cL urlTable := cParser.Parse(c) for _, itemUriCfg := range urlTable { _ = s.uiInstance.DocInstance().AddApiFromInAndOut(routerPrefix, itemUriCfg.FormDataType, itemUriCfg.ResultDataType) - method := strings.ToUpper(itemUriCfg.RequestMethod) handleFunc := s.RequestHandler(itemUriCfg) - switch method { - case http.MethodGet: - g.GET(itemUriCfg.Path, handleFunc) - case http.MethodHead: - g.HEAD(itemUriCfg.Path, handleFunc) - case http.MethodPost: - g.POST(itemUriCfg.Path, handleFunc) - case http.MethodPut: - g.PUT(itemUriCfg.Path, handleFunc) - case http.MethodPatch: - g.PATCH(itemUriCfg.Path, handleFunc) - case http.MethodDelete: - g.DELETE(itemUriCfg.Path, handleFunc) - case http.MethodOptions: - g.OPTIONS(itemUriCfg.Path, handleFunc) - case http.MethodTrace: - panic(`method Trace is not supported`) - default: - panic("method " + itemUriCfg.RequestMethod + " is not support") + // 一个接口支持注册多种请求方法 + for _, method := range itemUriCfg.RequestMethod { + switch method { + case http.MethodGet: + g.GET(itemUriCfg.Path, handleFunc) + case http.MethodHead: + g.HEAD(itemUriCfg.Path, handleFunc) + case http.MethodPost: + g.POST(itemUriCfg.Path, handleFunc) + case http.MethodPut: + g.PUT(itemUriCfg.Path, handleFunc) + case http.MethodPatch: + g.PATCH(itemUriCfg.Path, handleFunc) + case http.MethodDelete: + g.DELETE(itemUriCfg.Path, handleFunc) + case http.MethodOptions: + g.OPTIONS(itemUriCfg.Path, handleFunc) + case http.MethodTrace: + panic(`method Trace is not supported`) + default: + panic("method " + method + " is not support") + } } } } From ac6c707e29c08246d737c06bd35f3c1cad126814 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, 26 Dec 2025 11:35:41 +0800 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20meta=E5=85=83=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=85=8D=E7=BD=AE=E6=9C=80=E5=A4=A7=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E6=89=A7=E8=A1=8C=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- router/controller.go | 10 ++++++++++ router/define.go | 2 ++ 2 files changed, 12 insertions(+) diff --git a/router/controller.go b/router/controller.go index fae87fe..3ef4d1d 100644 --- a/router/controller.go +++ b/router/controller.go @@ -10,6 +10,8 @@ package router import ( "reflect" "strings" + + "git.zhangdeman.cn/zhangdeman/util" ) // controller 解析controller有哪些方法要注册为接口 @@ -132,4 +134,12 @@ func (c controller) setUriMeta(metaField reflect.StructField, cfg *UriConfig) { cfg.IsWebsocket = boolMetaParse(TagNameIsWebsocket) // 是否是 websocket 接口 cfg.IsSse = boolMetaParse(TagNameIsSse) // 是否是 sse 接口 cfg.NoLogin = boolMetaParse(TagNameNoLogin) // 是否需要登录 + + // 最大执行时间 + cfg.MaxExecTime = uint(0) + if tagVal := strings.ToLower(metaField.Tag.Get(TagNameMaxExecTime)); tagVal != "" { + if err := util.ConvertAssign(&cfg.MaxExecTime, tagVal); nil != err { + panic(cfg.Path + " : 最大执行时间配置错误(配置的值必须是无符号整型), 请检查配置 : " + err.Error()) + } + } } diff --git a/router/define.go b/router/define.go index d9ccbdc..42a7b9d 100644 --- a/router/define.go +++ b/router/define.go @@ -29,6 +29,7 @@ const ( TagNameOutputStrict = "output_strict" // 接口数据是否为严格模式 : 严格模式, 响应数据必须是结构体/map,非严格模式返回任意值 TagNameHookSync = "hook-sync" // hook 同步执行 TagNameNoLogin = "no-login" // 接口是否需要登录(无需登录, 则有token就验证, 无token不验证) + TagNameMaxExecTime = "max-exec-time" // 接口最大执行时间, 单位: s, 配置为0则不验证 TagNameBinding = "binding" // gin 内置的验证规则tag TagNameValidate = "validate" // validator v10 默认的验证规则tag TagNameErrMsg = "err" // 验证失败错误信息 tag @@ -43,6 +44,7 @@ type UriConfig struct { IsSse bool `json:"is_sse"` // 是否 SSE 连接 IsWebsocket bool `json:"is_ws"` // 是否 websocket 连接 OutputStrict bool `json:"output_strict"` // 接口是否为严格模式 : 不配置,可返回任意类型, 配置, 必须返回结构体或者map + MaxExecTime uint `json:"max_exec_time"` // 接口最大执行时间, 单位: ms, 配置为0则不验证, 注意, 超时后不会报错, 会打印一条warn日志, 如果配置了报警策略, 也会发送报警信息 HookSync bool `json:"hook_sync"` // 接口主逻辑执行完成之后,hook是否同步执行, 默认异步执行 NoLogin bool `json:"no_login"` // 接口是否需要登录(无需登录, 则有token就验证, 无token不验证) FormDataType reflect.Type `json:"-"` // 表单数据类型 From c23d93809261653ec1e04dd5be0265c272180609 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, 26 Dec 2025 11:41:26 +0800 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20=E6=97=A5=E5=BF=97=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96,=20=E9=BB=98=E8=AE=A4=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E5=88=B0=E6=A0=87=E5=87=86=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- logger/instance.go | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/logger/instance.go b/logger/instance.go index 6afb4a8..cfbd258 100644 --- a/logger/instance.go +++ b/logger/instance.go @@ -7,12 +7,33 @@ // Date : 2025-10-30 16:54 package logger -import "go.uber.org/zap" +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) var ( - Instance = zap.NewNop() + Instance *zap.Logger ) +// 日志实例初始化默认输出到标准输出 +func init() { + cfg := zap.NewProductionConfig() + cfg.OutputPaths = []string{"stdout"} + cfg.ErrorOutputPaths = []string{"stderr"} + cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + logger, err := cfg.Build() + if err != nil { + panic("init logger error") + } + Instance = logger.Sugar().Desugar() +} + +// Disable 禁用日志 +func Disable() { + Instance = zap.NewNop() +} + // SetInstance 设置日志实例 func SetInstance(l *zap.Logger) { if nil == l { From 1636a2652fc7a8b53dbfa548437bd1684c0ae8fd 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, 26 Dec 2025 12:01:52 +0800 Subject: [PATCH 06/12] =?UTF-8?q?feat:=20=E4=BF=AE=E5=A4=8Dgolint=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E8=AD=A6=E5=91=8A=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 2 +- go.sum | 2 ++ logger/instance.go | 2 +- middleware/hook_after_response.go | 2 +- request/form.go | 12 ++++++------ router/controller.go | 3 --- router/handler.go | 1 - router/hook.go | 18 ++++++++++-------- router/server.go | 4 ---- 9 files changed, 21 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index 06a9796..6c89395 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.2 require ( git.zhangdeman.cn/zhangdeman/api-doc v1.0.3-0.20251013152001-868ee8955623 - git.zhangdeman.cn/zhangdeman/consts v0.0.0-20251217031322-9e8b61fd5156 + git.zhangdeman.cn/zhangdeman/consts v0.0.0-20251226040044-6bc8da22219a git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20251013092857-dcf591d4e8a8 git.zhangdeman.cn/zhangdeman/exception v0.0.0-20250510123912-a0d52fc093ab git.zhangdeman.cn/zhangdeman/graceful v0.0.0-20250529070945-92833db6f3a4 diff --git a/go.sum b/go.sum index 5f50290..bcec335 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ git.zhangdeman.cn/zhangdeman/consts v0.0.0-20251101092813-3dd9fb807f1c h1:H0Tlh7 git.zhangdeman.cn/zhangdeman/consts v0.0.0-20251101092813-3dd9fb807f1c/go.mod h1:5p8CEKGBxi7qPtTXDI3HDmqKAfIm5i/aBWdrbkbdNjc= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20251217031322-9e8b61fd5156 h1:VaaejWXKShnuySe0o2twIBMkWc8bJ+Fw654KLKjoXW8= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20251217031322-9e8b61fd5156/go.mod h1:5p8CEKGBxi7qPtTXDI3HDmqKAfIm5i/aBWdrbkbdNjc= +git.zhangdeman.cn/zhangdeman/consts v0.0.0-20251226040044-6bc8da22219a h1:p85je5ExTyUsd2jR+Lq2rSJneAPM3MkuUhFHH+TfhEM= +git.zhangdeman.cn/zhangdeman/consts v0.0.0-20251226040044-6bc8da22219a/go.mod h1:5p8CEKGBxi7qPtTXDI3HDmqKAfIm5i/aBWdrbkbdNjc= git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20251013092857-dcf591d4e8a8 h1:Pw981jG3hH9ZHrB3s1xPpsZafgX3USuMAjnGi2GEP9Y= git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20251013092857-dcf591d4e8a8/go.mod h1:xtCw3om5DRrG30EfQd/lfQPXgptfK7l9oBSt4Kdhjok= git.zhangdeman.cn/zhangdeman/easylock v0.0.0-20230731062340-983985c12eda h1:bMD6r9gjRy7cO+T4zRQVYAesgIblBdTnhzT1vN5wjvI= diff --git a/logger/instance.go b/logger/instance.go index cfbd258..5a97bd7 100644 --- a/logger/instance.go +++ b/logger/instance.go @@ -24,7 +24,7 @@ func init() { cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder logger, err := cfg.Build() if err != nil { - panic("init logger error") + panic("init logger error: " + err.Error()) } Instance = logger.Sugar().Desugar() } diff --git a/middleware/hook_after_response.go b/middleware/hook_after_response.go index f1487a4..6b88cf3 100644 --- a/middleware/hook_after_response.go +++ b/middleware/hook_after_response.go @@ -32,7 +32,7 @@ func HookAfterResponseMiddleware(hookFunc HookFunc) gin.HandlerFunc { ctx, []byte(ctx.GetString(handleConfig.RecordRequestDataField)), ctx.GetStringMap(handleConfig.RecordResponseDataField), - request.WrapperHandle.GetCtxIntData(ctx, handleConfig.StartRequestTimeField, 0)-request.WrapperHandle.GetCtxIntData(ctx, handleConfig.StartRequestTimeField, 0), + request.WrapperHandle.GetCtxIntData(ctx, handleConfig.FinishRequestTimeField, 0)-request.WrapperHandle.GetCtxIntData(ctx, handleConfig.StartRequestTimeField, 0), ) } } diff --git a/request/form.go b/request/form.go index 89158ba..0a401e1 100644 --- a/request/form.go +++ b/request/form.go @@ -10,11 +10,13 @@ package request import ( "bytes" "errors" - "github.com/mcuadros/go-defaults" "io" "net/http" "strings" + "git.zhangdeman.cn/zhangdeman/wrapper/op_array" + "github.com/mcuadros/go-defaults" + "github.com/go-playground/validator/v10" "git.zhangdeman.cn/zhangdeman/gin/define" @@ -44,11 +46,9 @@ func (f *form) Parse(ctx *gin.Context, receiver interface{}) error { // 因为请求体被读一遍之后就没了,重新赋值 requestBody ctx.Request.Body = io.NopCloser(bytes.NewReader(requestBody)) method := strings.ToUpper(ctx.Request.Method) - if method == http.MethodGet || - method == http.MethodPatch || - method == http.MethodTrace || - method == http.MethodConnect || - method == http.MethodOptions { + if op_array.ArrayType([]string{ + http.MethodGet, http.MethodPatch, http.MethodTrace, http.MethodConnect, http.MethodOptions, + }).Has(method) >= 0 { if err := ctx.ShouldBindQuery(receiver); nil != err { return err } diff --git a/router/controller.go b/router/controller.go index 3ef4d1d..b6c1adb 100644 --- a/router/controller.go +++ b/router/controller.go @@ -81,8 +81,6 @@ func (c controller) methodConfig(reflectMethod reflect.Method) (cfg UriConfig, n return } cfg.ResultDataType = methodType.Out(0) - if cfg.ResultDataType == nil { - } if methodType.Out(1).Kind().String() != ErrorType { // 判断是否是实现 error接口的方法 outputErrParse := false @@ -106,7 +104,6 @@ func (c controller) methodConfig(reflectMethod reflect.Method) (cfg UriConfig, n // 开启输出严格模式校验 if methodType.Out(0).Kind() != reflect.Struct && methodType.Out(0).Kind() != reflect.Map { panic(cfg.Path + " : 接口配置输出严格校验, 输出数据类型必须为 struct 或 *struct 或 map, 实际返回数据类型 : " + methodType.Out(0).Kind().String()) - return } } diff --git a/router/handler.go b/router/handler.go index 19df456..47473bb 100644 --- a/router/handler.go +++ b/router/handler.go @@ -115,6 +115,5 @@ func (s *server) RequestHandler(uriCfg UriConfig) gin.HandlerFunc { response.SendWithException(ctx, e, &define.ResponseOption{ ContentType: consts.MimeTypeJson, }) - return } } diff --git a/router/hook.go b/router/hook.go index 470999b..c1fe960 100644 --- a/router/hook.go +++ b/router/hook.go @@ -18,19 +18,23 @@ import ( // hook 执行hook逻辑 func (s *server) hook(ctx *gin.Context, uriCfg UriConfig) { - responseAfter, exist := ctx.Get(consts.GinLogicAfterResponseKey) + var ( + exists bool + isSuccess any + responseAfter any + ) + innerContext := util.GinCtxToContext(ctx) - if !exist || nil != responseAfter { + if responseAfter, exists = ctx.Get(consts.GinLogicAfterResponseKey); !exists || nil != responseAfter { // 未配置 - logger.Instance.Debug("未配置Logic执行后的hook逻辑", pkgLogger.NewLogData(innerContext, logger.RecordType, logger.CodeLogicHook, map[string]any{ + logger.Instance.Debug("未配置 Logic 执行后的 hook 逻辑", pkgLogger.NewLogData(innerContext, logger.RecordType, logger.CodeLogicHook, map[string]any{ "uri": uriCfg.Path, "logic_after_response_key": consts.GinLogicAfterResponseKey, }).ToFieldList()...) return } - isSuccess, exist := ctx.Get(consts.GinRequestSuccess) success := false - if nil != isSuccess && (isSuccess == "1" || isSuccess == "true" || isSuccess.(bool)) { + if isSuccess, exists = ctx.Get(consts.GinRequestSuccess); exists && nil != isSuccess && (isSuccess == "1" || isSuccess == "true" || isSuccess.(bool)) { success = true } hookInstance := responseAfter.(*define.LogicAfterResponse) @@ -47,14 +51,12 @@ func (s *server) hookAfter(ctx *gin.Context, uriCfg UriConfig, hookInstance *def innerContext := util.GinCtxToContext(ctx) defer func() { if err := recover(); err != nil { - logger.Instance.Error("hook执行异常", pkgLogger.NewLogData(innerContext, logger.RecordType, logger.CodeLogicHook, map[string]any{ + logger.Instance.Error("hook 执行异常", pkgLogger.NewLogData(innerContext, logger.RecordType, logger.CodeLogicHook, map[string]any{ "uri": uriCfg.Path, "logic_after_response_key": consts.GinLogicAfterResponseKey, "error": err.(error).Error(), "logic_success": success, }).ToFieldList()...) - } else { - } }() if success { diff --git a/router/server.go b/router/server.go index 3ceecfc..65d19f7 100644 --- a/router/server.go +++ b/router/server.go @@ -77,10 +77,6 @@ func NewServer(port int, optionList ...SetServerOptionFunc) *server { if !option.disableInitRequest { // 启用了初始化请求 globalMiddlewareList = append(globalMiddlewareList, middleware.InitRequest()) // 初始化请求 } - // CustomContext 必须在第一个, 并且进行初始化 - globalMiddlewareList = append( - globalMiddlewareList, - ) if nil != option.loggerCfg { // 请求日志记录中间件 globalMiddlewareList = append(globalMiddlewareList, middleware.LogRequest(option.loggerCfg)) From 29201ac796966c924f55aabd0c157e6535f8834f 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, 26 Dec 2025 15:15:54 +0800 Subject: [PATCH 07/12] feat: remove comment --- middleware/hook_after_response.go | 4 ---- middleware/init_request.go | 12 ------------ middleware/ip_validate.go | 8 -------- 3 files changed, 24 deletions(-) diff --git a/middleware/hook_after_response.go b/middleware/hook_after_response.go index 6b88cf3..0c3c897 100644 --- a/middleware/hook_after_response.go +++ b/middleware/hook_after_response.go @@ -16,10 +16,6 @@ import ( type HookFunc func(ctx *gin.Context, requestData []byte, responseData map[string]interface{}, cost int64) // HookAfterResponseMiddleware 请求最终处理完成之后执行的中间件 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 11:55 2024/7/26 func HookAfterResponseMiddleware(hookFunc HookFunc) gin.HandlerFunc { handleConfig := define.GetHttpHandleConfig() return func(ctx *gin.Context) { diff --git a/middleware/init_request.go b/middleware/init_request.go index 286c16b..d3771cc 100644 --- a/middleware/init_request.go +++ b/middleware/init_request.go @@ -21,10 +21,6 @@ import ( ) // InitRequest 初始化请求 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 23:08 2022/6/25 func InitRequest() gin.HandlerFunc { return func(ctx *gin.Context) { httpHandleConfig := define.GetHttpHandleConfig() @@ -38,10 +34,6 @@ func InitRequest() gin.HandlerFunc { } // getRequestID 生成 request id -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 23:12 2022/6/25 func getRequestID(ctx *gin.Context) string { return fmt.Sprintf( "%v-%v-%v-%v", @@ -53,10 +45,6 @@ func getRequestID(ctx *gin.Context) string { } // getTraceID 生成 trace id -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 23:12 2022/6/25 func getTraceID(ctx *gin.Context, requestID string) string { traceID := ctx.GetHeader("X-Forward-Trace-Id") if len(traceID) > 0 { diff --git a/middleware/ip_validate.go b/middleware/ip_validate.go index 9052095..9c5ea1c 100644 --- a/middleware/ip_validate.go +++ b/middleware/ip_validate.go @@ -15,10 +15,6 @@ import ( ) // ValidateBlackIPMiddleware 验证黑名单IP的中间件 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 23:24 2022/6/25 func ValidateBlackIPMiddleware(code interface{}, httpCode int, validateFunc define.IsBlackIP) gin.HandlerFunc { return func(ctx *gin.Context) { if nil == validateFunc { @@ -40,10 +36,6 @@ func ValidateBlackIPMiddleware(code interface{}, httpCode int, validateFunc defi } // ValidateWhiteIPMiddleware 是否白名单IP -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 23:33 2022/6/25 func ValidateWhiteIPMiddleware(code interface{}, httpCode int, validateFunc define.IsWhiteIP) gin.HandlerFunc { return func(ctx *gin.Context) { if nil == validateFunc { From e415bad86906e79cc6069a04cf4dbbace9b5ceb6 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, 26 Dec 2025 17:22:37 +0800 Subject: [PATCH 08/12] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E6=96=B9=E6=B3=95=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- define/middleare.go | 8 ------ define/response.go | 16 ++---------- logger/instance.go | 5 ++-- router/common_param.go | 15 ++++++----- router/controller.go | 59 +++++++++++++++++++++++++++++------------- router/handler.go | 9 ++++++- router/option.go | 4 +-- 7 files changed, 65 insertions(+), 51 deletions(-) diff --git a/define/middleare.go b/define/middleare.go index bd042a2..0e05c83 100644 --- a/define/middleare.go +++ b/define/middleare.go @@ -10,15 +10,7 @@ package define import "github.com/gin-gonic/gin" // IsBlackIP 是否是黑名单IP -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 23:25 2022/6/25 type IsBlackIP func(ctx *gin.Context, clientIP string) bool // IsWhiteIP 是否白名单IP -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 23:27 2022/6/25 type IsWhiteIP func(ctx *gin.Context, clientIP string) bool diff --git a/define/response.go b/define/response.go index a995dad..43e824e 100644 --- a/define/response.go +++ b/define/response.go @@ -20,10 +20,6 @@ var ( ) // HttpHandleConfig 请求处理配置 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 20:41 2022/6/25 type HttpHandleConfig struct { RecordRequestDataField string RecordResponseDataField string @@ -43,19 +39,11 @@ type HttpHandleConfig struct { } // ConvertDefaultConfig 覆盖默认配置 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 20:41 2022/6/25 func ConvertDefaultConfig(cfg *HttpHandleConfig) { inputHttpHandleConfig = cfg } // GetHttpHandleConfig 获取http配置 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 16:55 2024/7/23 func GetHttpHandleConfig() *HttpHandleConfig { return &HttpHandleConfig{ EnableExtensionOutput: inputHttpHandleConfig.EnableExtensionOutput, @@ -129,8 +117,8 @@ func GetHttpHandleConfig() *HttpHandleConfig { } type LogicAfterResponse struct { - SuccessHookFuncList []func(ctx *gin.Context) `json:"-"` // 请求最后需要执行的成功hook函数 - FailureHookFuncList []func(ctx *gin.Context) `json:"-"` // 请求最后需要执行的失败hook函数 + SuccessHookFuncList []func(ctx *gin.Context) `json:"-"` // 请求最后需要执行的成功 hook函数 + FailureHookFuncList []func(ctx *gin.Context) `json:"-"` // 请求最后需要执行的失败 hook函数 Lock *sync.RWMutex `json:"-"` // 逻辑锁 } diff --git a/logger/instance.go b/logger/instance.go index 5a97bd7..82991de 100644 --- a/logger/instance.go +++ b/logger/instance.go @@ -47,6 +47,7 @@ const ( ) const ( - CodeInjectCommonParam = "inject-common-param" - CodeLogicHook = "logic-hook" + CodeInjectCommonParam = "inject-common-param" + CodeLogicHook = "logic-hook" + CodeParamValidateFailure = "param-validate-failure" ) diff --git a/router/common_param.go b/router/common_param.go index eb576ae..b43d28f 100644 --- a/router/common_param.go +++ b/router/common_param.go @@ -49,14 +49,17 @@ func (s *server) injectCommonParam(ctx *gin.Context, formValue any) error { } else { reflectType = reflectFormValue.Type() } + if reflectType.Kind() == reflect.Ptr { + reflectType = reflectType.Elem() + } fieldTable := map[string]bool{} - fieldNum := reflectType.Elem().NumField() + fieldNum := reflectType.NumField() for i := 0; i < fieldNum; i++ { - if reflectType.Elem().Field(i).Anonymous && // 是匿名字段, 再做一次解析 - ((reflectType.Elem().Field(i).Type.Kind() == reflect.Ptr && reflectType.Elem().Field(i).Type.Kind() == reflect.Struct) || // 结构体指针 - reflectType.Elem().Field(i).Type.Kind() == reflect.Struct) { // 结构体 - anonymousFieldType := reflectType.Elem().Field(i).Type + if reflectType.Field(i).Anonymous && // 是匿名字段, 再做一次解析 + ((reflectType.Field(i).Type.Kind() == reflect.Ptr && reflectType.Field(i).Type.Kind() == reflect.Struct) || // 结构体指针 + reflectType.Field(i).Type.Kind() == reflect.Struct) { // 结构体 + anonymousFieldType := reflectType.Field(i).Type if anonymousFieldType.Kind() == reflect.Ptr { anonymousFieldType = anonymousFieldType.Elem() } @@ -65,7 +68,7 @@ func (s *server) injectCommonParam(ctx *gin.Context, formValue any) error { } } else { // 提取全部结构体字段 - fieldTable[reflectType.Elem().Field(i).Name] = true + fieldTable[reflectType.Field(i).Name] = true } } for fieldName, getParamFunc := range s.commonParam { diff --git a/router/controller.go b/router/controller.go index b6c1adb..38f9156 100644 --- a/router/controller.go +++ b/router/controller.go @@ -11,6 +11,7 @@ import ( "reflect" "strings" + "git.zhangdeman.cn/zhangdeman/gin/logger" "git.zhangdeman.cn/zhangdeman/util" ) @@ -49,37 +50,59 @@ func (c controller) Parse(inputController any) map[string]UriConfig { return parseRes } -// methodConfig 解析方法配置, 要求函数格式, 两个参数, 两个返回值, 格式 : func(ctx *gin.Context, formData anyStruct[组合Meta]) (anyStruct|map[response], error) -// -// 参数 : 方法反射结果 -// -// 返回值 : 第一个 -> 解析出的接口配置 第二个 -> 是否要注册为接口 -func (c controller) methodConfig(reflectMethod reflect.Method) (cfg UriConfig, needRegister bool) { +// preCheckMethod 预检查方法是否可以注册为接口 +func (c controller) preCheckMethod(reflectMethod reflect.Method) (bool, reflect.Type, reflect.StructField) { + var ( + metaField reflect.StructField + metaFieldExist bool + ) + methodType := reflectMethod.Type // num0: 函数声明 // num1: 第一个参数 // num2: 第二个参数 if methodType.NumIn() != 3 { - needRegister = false - return + return false, nil, metaField } // 第一个参数必须是 *gin.Context 或者 *define.Context paramOne := methodType.In(1).String() if paramOne != GinContextType { - needRegister = false - return + return false, nil, metaField } + // 解析第二个参数是组合 Meta 的form表单 formType := methodType.In(2) - cfg.FormDataType = formType if formType.Kind() == reflect.Ptr { - formType = methodType.In(2).Elem() + formType = formType.Elem() } - metaField, metaFieldExist := formType.FieldByName(FieldNameMeta) - if !metaFieldExist { - needRegister = false - return + + if metaField, metaFieldExist = formType.FieldByName(FieldNameMeta); !metaFieldExist { + return false, nil, metaField } + return true, formType, metaField +} + +// methodConfig 解析方法配置, 要求函数格式, 两个参数, 两个返回值, 格式 : func(ctx *gin.Context, formData anyStruct[组合Meta]) (anyStruct|map[response], error) +// +// 参数 : 方法反射结果 +// +// 返回值 : 第一个 -> 解析出的接口配置 第二个 -> 是否要注册为接口 +func (c controller) methodConfig(reflectMethod reflect.Method) (UriConfig, bool) { + var ( + needRegister bool + metaField reflect.StructField + cfg UriConfig + ) + + methodType := reflectMethod.Type + // num0: 函数声明 + // num1: 第一个参数 + // num2: 第二个参数 + if needRegister, cfg.FormDataType, metaField = c.preCheckMethod(reflectMethod); !needRegister { + logger.Instance.Info("接口方法不符合要求, 不注册为接口, 方法名: " + reflectMethod.Name + ", 方法签名: " + methodType.String()) + return UriConfig{}, false + } + cfg.ResultDataType = methodType.Out(0) if methodType.Out(1).Kind().String() != ErrorType { // 判断是否是实现 error接口的方法 @@ -95,7 +118,7 @@ func (c controller) methodConfig(reflectMethod reflect.Method) (cfg UriConfig, n } if !outputErrParse { needRegister = false - return + return cfg, needRegister } } c.setUriMeta(metaField, &cfg) @@ -111,7 +134,7 @@ func (c controller) methodConfig(reflectMethod reflect.Method) (cfg UriConfig, n //cfg.ParamList = c.parseParamConfig(formType) cfg.ApiLogicFunc = reflectMethod needRegister = true - return + return cfg, needRegister } // setUriMeta 设置接口的 meta 信息 diff --git a/router/handler.go b/router/handler.go index 47473bb..566cc80 100644 --- a/router/handler.go +++ b/router/handler.go @@ -9,14 +9,18 @@ package router import ( "errors" + "net/http" "reflect" "sync" "git.zhangdeman.cn/zhangdeman/consts" "git.zhangdeman.cn/zhangdeman/exception" "git.zhangdeman.cn/zhangdeman/gin/define" + "git.zhangdeman.cn/zhangdeman/gin/logger" "git.zhangdeman.cn/zhangdeman/gin/request" "git.zhangdeman.cn/zhangdeman/gin/response" + "git.zhangdeman.cn/zhangdeman/gin/util" + loggerPkg "git.zhangdeman.cn/zhangdeman/logger" "github.com/gin-gonic/gin" "github.com/mcuadros/go-defaults" ) @@ -38,6 +42,9 @@ func (s *server) getFormInitValue(ctx *gin.Context, uriCfg UriConfig) (any, erro if err = request.Form.Parse(ctx, formValue); nil != err { // 格式化验证错误的信息 err = GetValidateErr(formValue, err) + logger.Instance.Error("参数解析出现异常", loggerPkg.NewLogData(util.GinCtxToContext(ctx), logger.RecordType, logger.CodeParamValidateFailure, map[string]any{ + "err_msg": err.Error(), + }).ToFieldList()...) return nil, err } return formValue, nil @@ -55,7 +62,7 @@ func (s *server) RequestHandler(uriCfg UriConfig) gin.HandlerFunc { ) if formValue, err = s.getFormInitValue(ctx, uriCfg); nil != err { - e = exception.NewFromError(400, err) + e = exception.NewFromError(http.StatusBadRequest, err) response.SendWithException(ctx, e, &define.ResponseOption{ ContentType: consts.MimeTypeJson, }) diff --git a/router/option.go b/router/option.go index b551c21..c80cbcb 100644 --- a/router/option.go +++ b/router/option.go @@ -20,12 +20,12 @@ type SetServerOptionFunc func(so *serverOption) // serverOption 获取server实例的选项 type serverOption struct { swaggerUiTheme string // swagger 主题 - swaggerBaseUri string // swagger基础path + swaggerBaseUri string // swagger 基础path globalMiddlewareList []gin.HandlerFunc // 全局中间件列表 disableSwaggerDoc bool // 禁用swagger文档, 特定环境不想展示文档, 可通过次方式禁用 serverInfo *apiDocDefine.Info // 服务器信息 serverList []*apiDocDefine.ServerItem // 服务器环境列表 - enablePprof bool // 启用pprof + enablePprof bool // 启用 pprof enableCors bool // 启动跨域支持 disableInitRequest bool // 禁用初始化请求 loggerCfg *middleware.AccessConfig // 日志配置 From 166ce9a52a6357c104acce6a67f2fa91c6ddf094 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, 26 Dec 2025 17:32:33 +0800 Subject: [PATCH 09/12] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96RequestHandler?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- logger/instance.go | 1 + router/handler.go | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/logger/instance.go b/logger/instance.go index 82991de..8491cb5 100644 --- a/logger/instance.go +++ b/logger/instance.go @@ -50,4 +50,5 @@ const ( CodeInjectCommonParam = "inject-common-param" CodeLogicHook = "logic-hook" CodeParamValidateFailure = "param-validate-failure" + CodeLogicErrorWrapper = "logic-error-wrapper" ) diff --git a/router/handler.go b/router/handler.go index 566cc80..58bacf8 100644 --- a/router/handler.go +++ b/router/handler.go @@ -70,7 +70,7 @@ func (s *server) RequestHandler(uriCfg UriConfig) gin.HandlerFunc { return } // 表单数据 - inputValue := reflect.ValueOf(formValue) + inputValue := reflect.ValueOf(formValue).Elem() // 注入公共参数 if err = s.injectCommonParam(ctx, inputValue); nil != err { @@ -95,11 +95,7 @@ func (s *server) RequestHandler(uriCfg UriConfig) gin.HandlerFunc { } // 此处暴露出去,是为了使用方可以获取到对应数据 ctx.Set(consts.GinLogicAfterResponseKey, logicAfterResponse) - defer s.hook(ctx, uriCfg) // 执行Logic之后的相关逻辑 - // 执行逻辑 - if uriCfg.FormDataType.Kind() != reflect.Ptr { - inputValue = inputValue.Elem() - } + defer s.hook(ctx, uriCfg) // 执行 Logic 之后的相关逻辑 firstParam = reflect.ValueOf(ctx) resList := uriCfg.ApiLogicFunc.Func.Call([]reflect.Value{uriCfg.ApiStructValue, firstParam, inputValue}) @@ -112,9 +108,12 @@ func (s *server) RequestHandler(uriCfg UriConfig) gin.HandlerFunc { // 请求失败 if ok = errors.As(resList[1].Interface().(error), &e); ok { // 本身就是exception.IException + logger.Instance.Debug("请求结果err类型为 exception.IException, 无需特殊处理", loggerPkg.NewLogData(util.GinCtxToContext(ctx), logger.RecordType, logger.CodeLogicErrorWrapper, map[string]any{}).ToFieldList()...) } else if err, ok = resList[1].Interface().(error); ok { + logger.Instance.Debug("请求结果err类型为 error, 包装为 exception.IException", loggerPkg.NewLogData(util.GinCtxToContext(ctx), logger.RecordType, logger.CodeLogicErrorWrapper, map[string]any{}).ToFieldList()...) e = exception.NewFromError(-1, err) } else { + logger.Instance.Debug("请求结果err类型 既不是 error 也不是 exception.IException, 包装为 exception.IException", loggerPkg.NewLogData(util.GinCtxToContext(ctx), logger.RecordType, logger.CodeLogicErrorWrapper, map[string]any{}).ToFieldList()...) e = exception.NewWithCodeAndData(-1, map[string]any{ "err": resList[1].Interface(), }) From 94aaccaa9cfce829464cd4e9dc9e1e5e0b94fb65 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, 26 Dec 2025 17:48:31 +0800 Subject: [PATCH 10/12] =?UTF-8?q?=E5=87=86=E5=A4=87=E6=94=AF=E6=8C=81=20ss?= =?UTF-8?q?e=20=E8=BF=9E=E6=8E=A5=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 2 +- go.sum | 2 ++ router/handler.go | 81 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 6c89395..c9c34ea 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.2 require ( git.zhangdeman.cn/zhangdeman/api-doc v1.0.3-0.20251013152001-868ee8955623 - git.zhangdeman.cn/zhangdeman/consts v0.0.0-20251226040044-6bc8da22219a + git.zhangdeman.cn/zhangdeman/consts v0.0.0-20251226093912-de8914503463 git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20251013092857-dcf591d4e8a8 git.zhangdeman.cn/zhangdeman/exception v0.0.0-20250510123912-a0d52fc093ab git.zhangdeman.cn/zhangdeman/graceful v0.0.0-20250529070945-92833db6f3a4 diff --git a/go.sum b/go.sum index bcec335..015c2ab 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ git.zhangdeman.cn/zhangdeman/consts v0.0.0-20251217031322-9e8b61fd5156 h1:VaaejW git.zhangdeman.cn/zhangdeman/consts v0.0.0-20251217031322-9e8b61fd5156/go.mod h1:5p8CEKGBxi7qPtTXDI3HDmqKAfIm5i/aBWdrbkbdNjc= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20251226040044-6bc8da22219a h1:p85je5ExTyUsd2jR+Lq2rSJneAPM3MkuUhFHH+TfhEM= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20251226040044-6bc8da22219a/go.mod h1:5p8CEKGBxi7qPtTXDI3HDmqKAfIm5i/aBWdrbkbdNjc= +git.zhangdeman.cn/zhangdeman/consts v0.0.0-20251226093912-de8914503463 h1:IEUkYzFIWoxQwoJ1c16mDrBuYUgQzYCQ7YHkWXsb5uo= +git.zhangdeman.cn/zhangdeman/consts v0.0.0-20251226093912-de8914503463/go.mod h1:5p8CEKGBxi7qPtTXDI3HDmqKAfIm5i/aBWdrbkbdNjc= git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20251013092857-dcf591d4e8a8 h1:Pw981jG3hH9ZHrB3s1xPpsZafgX3USuMAjnGi2GEP9Y= git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20251013092857-dcf591d4e8a8/go.mod h1:xtCw3om5DRrG30EfQd/lfQPXgptfK7l9oBSt4Kdhjok= git.zhangdeman.cn/zhangdeman/easylock v0.0.0-20230731062340-983985c12eda h1:bMD6r9gjRy7cO+T4zRQVYAesgIblBdTnhzT1vN5wjvI= diff --git a/router/handler.go b/router/handler.go index 58bacf8..d8e21ca 100644 --- a/router/handler.go +++ b/router/handler.go @@ -123,3 +123,84 @@ func (s *server) RequestHandler(uriCfg UriConfig) gin.HandlerFunc { }) } } + +// SseHandler sse连接请求 +func (s *server) SseHandler(uriCfg UriConfig) gin.HandlerFunc { + return func(ctx *gin.Context) { + var ( + err error + ok bool + e exception.IException + formValue any + firstParam reflect.Value + ) + + if formValue, err = s.getFormInitValue(ctx, uriCfg); nil != err { + e = exception.NewFromError(http.StatusBadRequest, err) + response.SendWithException(ctx, e, &define.ResponseOption{ + ContentType: consts.MimeTypeJson, + }) + ctx.Abort() + return + } + // 表单数据 + inputValue := reflect.ValueOf(formValue).Elem() + + // 注入公共参数 + if err = s.injectCommonParam(ctx, inputValue); nil != err { + e = exception.NewFromError(500, err) + response.SendWithException(ctx, e, &define.ResponseOption{ + ContentType: consts.MimeTypeJson, + }) + ctx.Abort() + return + } + + // 非必传参数设置默认值 + defaults.SetDefaults(formValue) + + // 默认请求失败 + ctx.Set(consts.GinRequestSuccess, false) + // 初始化响应之后 logic + logicAfterResponse := &define.LogicAfterResponse{ + SuccessHookFuncList: make([]func(ctx *gin.Context), 0), + FailureHookFuncList: make([]func(ctx *gin.Context), 0), + Lock: &sync.RWMutex{}, + } + // 此处暴露出去,是为了使用方可以获取到对应数据 + ctx.Set(consts.GinLogicAfterResponseKey, logicAfterResponse) + defer s.hook(ctx, uriCfg) // 执行 Logic 之后的相关逻辑 + + ctx.Writer.Header().Set(consts.HeaderKeyContentType.String(), "text/event-stream") + ctx.Writer.Header().Set(consts.HeaderKeyCacheControl.String(), "no-cache") + ctx.Writer.Header().Set(consts.HeaderKeyConnection.String(), "keep-alive") + ctx.Writer.Header().Set(consts.HeaderKeyXAccelBuffering.String(), "no") + flusher, _ := ctx.Writer.(http.Flusher) + // TODO: 发送连接就绪消息 + flusher.Flush() + firstParam = reflect.ValueOf(ctx) + resList := uriCfg.ApiLogicFunc.Func.Call([]reflect.Value{uriCfg.ApiStructValue, firstParam, inputValue}) + if resList[1].IsNil() { + // 请求成功, 更新标识 + ctx.Set(consts.GinRequestSuccess, true) + response.SuccessWithExtension(ctx, resList[0].Interface(), &define.ResponseOption{ContentType: consts.MimeTypeJson}) + return + } + // 请求失败 + if ok = errors.As(resList[1].Interface().(error), &e); ok { + // 本身就是exception.IException + logger.Instance.Debug("请求结果err类型为 exception.IException, 无需特殊处理", loggerPkg.NewLogData(util.GinCtxToContext(ctx), logger.RecordType, logger.CodeLogicErrorWrapper, map[string]any{}).ToFieldList()...) + } else if err, ok = resList[1].Interface().(error); ok { + logger.Instance.Debug("请求结果err类型为 error, 包装为 exception.IException", loggerPkg.NewLogData(util.GinCtxToContext(ctx), logger.RecordType, logger.CodeLogicErrorWrapper, map[string]any{}).ToFieldList()...) + e = exception.NewFromError(-1, err) + } else { + logger.Instance.Debug("请求结果err类型 既不是 error 也不是 exception.IException, 包装为 exception.IException", loggerPkg.NewLogData(util.GinCtxToContext(ctx), logger.RecordType, logger.CodeLogicErrorWrapper, map[string]any{}).ToFieldList()...) + e = exception.NewWithCodeAndData(-1, map[string]any{ + "err": resList[1].Interface(), + }) + } + response.SendWithException(ctx, e, &define.ResponseOption{ + ContentType: consts.MimeTypeJson, + }) + } +} From 8511f311f8d5c0952ef25a1e36e15cb0e63493e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Sun, 28 Dec 2025 18:21:44 +0800 Subject: [PATCH 11/12] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20form=20value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- router/handler.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/router/handler.go b/router/handler.go index d8e21ca..7ba3b97 100644 --- a/router/handler.go +++ b/router/handler.go @@ -47,6 +47,10 @@ func (s *server) getFormInitValue(ctx *gin.Context, uriCfg UriConfig) (any, erro }).ToFieldList()...) return nil, err } + + // 非必传参数设置默认值 + defaults.SetDefaults(formValue) + return formValue, nil } @@ -82,9 +86,6 @@ func (s *server) RequestHandler(uriCfg UriConfig) gin.HandlerFunc { return } - // 非必传参数设置默认值 - defaults.SetDefaults(formValue) - // 默认请求失败 ctx.Set(consts.GinRequestSuccess, false) // 初始化响应之后 logic @@ -156,10 +157,6 @@ func (s *server) SseHandler(uriCfg UriConfig) gin.HandlerFunc { return } - // 非必传参数设置默认值 - defaults.SetDefaults(formValue) - - // 默认请求失败 ctx.Set(consts.GinRequestSuccess, false) // 初始化响应之后 logic logicAfterResponse := &define.LogicAfterResponse{ From f424813a08f823b3d7bf9f704a0349b38a203db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Mon, 29 Dec 2025 10:27:17 +0800 Subject: [PATCH 12/12] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0sse=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- define/response.go | 10 ++++++ logger/instance.go | 2 ++ router/handler.go | 76 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/define/response.go b/define/response.go index 43e824e..eea05b8 100644 --- a/define/response.go +++ b/define/response.go @@ -139,3 +139,13 @@ type ResponseOption struct { Extension map[string]any `json:"extension"` // 扩展数据 XmlName string `json:"xml_name"` // 以xml文件格式响应数据时, Xml文件名(根节点) } + +type SseData struct { + ID string `json:"id"` // 响应数据 ID + Object string `json:"object"` // 响应数据对象类型 + Created int64 `json:"created"` // 创建时间, 秒级时间戳 + Choices []map[string]any `json:"choices"` // choice 列表 + EventType string `json:"event_type"` // 事件类型 +} + +const SseMsgFormat = "id:%v\nevent:%v\ndata:%v\n\n" diff --git a/logger/instance.go b/logger/instance.go index 8491cb5..352a938 100644 --- a/logger/instance.go +++ b/logger/instance.go @@ -51,4 +51,6 @@ const ( CodeLogicHook = "logic-hook" CodeParamValidateFailure = "param-validate-failure" CodeLogicErrorWrapper = "logic-error-wrapper" + CodeLogicSseInitError = "sse-init-error" + CodeLogicSseClosedError = "sse-closed-error" ) diff --git a/router/handler.go b/router/handler.go index 7ba3b97..c120892 100644 --- a/router/handler.go +++ b/router/handler.go @@ -9,9 +9,11 @@ package router import ( "errors" + "fmt" "net/http" "reflect" "sync" + "time" "git.zhangdeman.cn/zhangdeman/consts" "git.zhangdeman.cn/zhangdeman/exception" @@ -21,6 +23,8 @@ import ( "git.zhangdeman.cn/zhangdeman/gin/response" "git.zhangdeman.cn/zhangdeman/gin/util" loggerPkg "git.zhangdeman.cn/zhangdeman/logger" + "git.zhangdeman.cn/zhangdeman/serialize" + "git.zhangdeman.cn/zhangdeman/wrapper/op_string" "github.com/gin-gonic/gin" "github.com/mcuadros/go-defaults" ) @@ -173,8 +177,38 @@ func (s *server) SseHandler(uriCfg UriConfig) gin.HandlerFunc { ctx.Writer.Header().Set(consts.HeaderKeyConnection.String(), "keep-alive") ctx.Writer.Header().Set(consts.HeaderKeyXAccelBuffering.String(), "no") flusher, _ := ctx.Writer.(http.Flusher) - // TODO: 发送连接就绪消息 + // 发送连接就绪消息 + if _, err = fmt.Fprintf(ctx.Writer, define.SseMsgFormat, -1, "system", serialize.JSON.MarshalForStringIgnoreError(define.SseData{ + ID: op_string.Random(8, ""), + Object: "system", + Created: time.Now().Unix(), + Choices: []map[string]any{}, + EventType: "connected", + })); nil != err { + // 无法推送数据, 等价于结束, 如有必要, 让客户端发起重连 + logger.Instance.Error("SSE 连接建立成功后, 发送链接成功消息出现异常", loggerPkg.NewLogData(util.GinCtxToContext(ctx), logger.RecordType, logger.CodeLogicSseInitError, map[string]any{ + "err_msg": err.Error(), + }).ToFieldList()...) + return + } flusher.Flush() + defer func() { + // 发送连接关闭消息 + if _, err = fmt.Fprintf(ctx.Writer, define.SseMsgFormat, -3, "system", serialize.JSON.MarshalForStringIgnoreError(define.SseData{ + ID: op_string.Random(8, ""), + Object: "system", + Created: time.Now().Unix(), + Choices: []map[string]any{}, + EventType: "closed", + })); nil != err { + // 无法推送数据, 等价于结束, 如有必要, 让客户端发起重连 + logger.Instance.Error("SSE 连接断开前, 发送链接断开消息出现异常", loggerPkg.NewLogData(util.GinCtxToContext(ctx), logger.RecordType, logger.CodeLogicSseClosedError, map[string]any{ + "err_msg": err.Error(), + }).ToFieldList()...) + return + } + flusher.Flush() + }() firstParam = reflect.ValueOf(ctx) resList := uriCfg.ApiLogicFunc.Func.Call([]reflect.Value{uriCfg.ApiStructValue, firstParam, inputValue}) if resList[1].IsNil() { @@ -196,8 +230,42 @@ func (s *server) SseHandler(uriCfg UriConfig) gin.HandlerFunc { "err": resList[1].Interface(), }) } - response.SendWithException(ctx, e, &define.ResponseOption{ - ContentType: consts.MimeTypeJson, - }) + if nil != e { + // 异常终止 + if _, err = fmt.Fprintf(ctx.Writer, define.SseMsgFormat, -3, "system", serialize.JSON.MarshalForStringIgnoreError(define.SseData{ + ID: op_string.Random(8, ""), + Object: "system", + Created: time.Now().Unix(), + Choices: []map[string]any{ + { + "err_msg": e.Message(), + "err_code": e.Code(), + "err_data": e.Data(), + }, + }, + EventType: "failure", + })); nil != err { + // 无法推送数据, 等价于结束, 如有必要, 让客户端发起重连 + logger.Instance.Error("SSE 业务处理完成, 发送业务处理失败消息出现异常", loggerPkg.NewLogData(util.GinCtxToContext(ctx), logger.RecordType, logger.CodeLogicSseClosedError, map[string]any{ + "err_msg": err.Error(), + }).ToFieldList()...) + return + } + } else { + // 正常终止 + if _, err = fmt.Fprintf(ctx.Writer, define.SseMsgFormat, -3, "system", serialize.JSON.MarshalForStringIgnoreError(define.SseData{ + ID: op_string.Random(8, ""), + Object: "system", + Created: time.Now().Unix(), + Choices: []map[string]any{}, + EventType: "success", + })); nil != err { + // 无法推送数据, 等价于结束, 如有必要, 让客户端发起重连 + logger.Instance.Error("SSE 业务处理完成, 发送业务处理成功消息出现异常", loggerPkg.NewLogData(util.GinCtxToContext(ctx), logger.RecordType, logger.CodeLogicSseClosedError, map[string]any{ + "err_msg": err.Error(), + }).ToFieldList()...) + return + } + } } }