162 Commits

Author SHA1 Message Date
f4c58d473e Merge pull request '响应数据序列化出现异常, 触发panic' (#14) from feature/upgrade_route_handler into master
Reviewed-on: #14
2025-06-21 20:41:24 +08:00
a677a8312b 响应数据序列化出现异常, 触发panic 2025-06-21 20:41:01 +08:00
c5fa764807 update go mod 2025-06-21 20:22:27 +08:00
7a3da3614e Merge pull request 'feature/graceful' (#13) from feature/graceful into master
Reviewed-on: #13
2025-05-29 15:13:16 +08:00
a1372933c0 升级graceful 2025-05-29 15:12:44 +08:00
1da666d6fc 引入并集成服务平滑关闭 2025-05-29 10:45:06 +08:00
558587ec68 引入平滑重启,有BUG待调试 2025-05-28 21:08:33 +08:00
66c2ddad1c fix handler exception 2025-05-10 20:46:37 +08:00
fcfba9c50b 升级exception 2025-05-10 20:44:31 +08:00
18dd156718 Merge pull request '升级body解析, 适配各个类型请求Body' (#12) from feature/upgrade_request_parse into master
Reviewed-on: #12
2025-05-04 15:34:01 +08:00
d725815ce7 修复content_type计算BUG 2025-05-04 15:30:31 +08:00
ecaf448100 读取结果增加缓存 2025-05-04 15:16:55 +08:00
927870b11a 修复parse_body相关BUG 2025-05-04 14:52:20 +08:00
2aa8e86917 升级body解析 2025-05-04 14:36:26 +08:00
146fbaf878 修复相应类型设置错误的BUG 2025-04-29 14:40:04 +08:00
0bd02dbd08 相应类型增加编码utf-8 2025-04-29 14:22:27 +08:00
03963ce8e3 Merge pull request '响应数据支持根据不同的content_type使用不同的发送方式' (#11) from feature/response_support_data into master
Reviewed-on: #11
2025-04-28 21:49:23 +08:00
8a21c7eb52 响应数据支持根据不同的content_type使用不同的发送方式 2025-04-28 21:48:52 +08:00
24f33309b9 Merge pull request '支持custom context' (#10) from feature/upgrade_context into master
Reviewed-on: #10
2025-04-13 15:46:21 +08:00
54a8fa91c7 修复 custom context 判断的BUG 2025-04-12 22:10:48 +08:00
e0d5e9c94b 接口入口函数首个参数支持custom context 2025-04-12 21:52:07 +08:00
a7fed88714 增加获取trace实例方法 2025-04-12 21:31:47 +08:00
5df4fbab16 修复循环引用问题 2025-04-12 21:24:02 +08:00
91a1d34474 增加CustomContext 2025-04-12 21:01:59 +08:00
53ecd0c267 upfstr go mod 2025-04-12 18:41:46 +08:00
2fd9195b77 Merge pull request '修复logic after response实际是同步逻辑问题' (#9) from feature/fix_logic_after_response into master
Reviewed-on: #9
2025-02-28 18:43:11 +08:00
c826166256 修复logic after response实际是同步逻辑问题 2025-02-28 18:18:03 +08:00
84f0e1a4f6 Merge pull request '引入对响应后处理逻辑的支持' (#8) from feature/logic_after_response into master
Reviewed-on: #8
2025-02-28 17:05:12 +08:00
9ae310782b fix 2025-02-28 17:04:36 +08:00
0d981b32c6 增加获取logic after response实例的方法 2025-02-28 16:57:02 +08:00
9decd12ee8 优化代码组织 2025-02-28 16:53:02 +08:00
b95d464c89 包装handler支持响应后触发相关逻辑 2025-02-28 16:48:22 +08:00
3432087fbd 修复拼写错误 2025-02-25 17:22:14 +08:00
2c201ae226 优化跨域处理 2025-02-22 17:05:38 +08:00
12d2735663 Merge pull request '升级路由注册' (#7) from feature/upgrade_route_register into master
Reviewed-on: #7
2025-02-22 16:15:37 +08:00
01b62b6eee 支持在router实力上自行注册路由 2025-02-22 16:14:55 +08:00
1de0715637 支持注册404Handler 2025-02-22 16:05:56 +08:00
93cef0cf7f 支持请求日志记录中间件 2025-02-22 15:45:30 +08:00
b9a7f97342 优化注册全局中间件逻辑 2025-02-22 15:00:37 +08:00
75cc21c494 Merge pull request '支持注册pprof相关路由' (#6) from feature/support_pprof into master
Reviewed-on: #6
2025-02-21 15:17:09 +08:00
4909e75928 支持注册pprof相关路由 2025-02-21 15:16:43 +08:00
bc94242d12 update apiDocPkg 2025-02-20 18:52:49 +08:00
11d90566ae update apiDocPkg 2025-02-20 18:01:26 +08:00
113b1e5a05 升级apiDocPkg 2025-02-20 16:24:08 +08:00
7530625baa update go sum 2025-02-20 10:38:00 +08:00
8cb98e4817 update apiDocPkg 2025-02-19 22:26:45 +08:00
cfcb05f4da update go mod 2025-02-19 21:49:49 +08:00
f11540fa6e update apiDocPkg 2025-02-19 18:17:15 +08:00
c3e42b944c update go mod 2025-02-19 15:37:59 +08:00
bb54978dc5 update go mod 2025-02-18 23:04:44 +08:00
de34f57a5f update go mod 2025-02-18 22:31:06 +08:00
793fefbcd2 update go mod 2025-02-18 21:29:01 +08:00
26476ee23b Merge pull request 'server初始化支持设置各种option' (#5) from feature/support_server_option into master
Reviewed-on: #5
2025-02-18 18:14:04 +08:00
1c2c5aa4ce server初始化支持设置各种option 2025-02-18 18:12:44 +08:00
74dde8b41e update go mod 2025-02-18 17:27:03 +08:00
1e53d899dc update go mod 2025-02-18 14:57:16 +08:00
a80c559a7c 修复文档路由重复注册的BUG 2025-02-18 14:39:29 +08:00
02675d6c40 update go mod 2025-02-18 14:09:24 +08:00
681bd3fe95 update api-doc 2025-02-17 18:12:52 +08:00
7ef8bf2676 修复参数解析BUG 2025-02-17 17:13:57 +08:00
293f829945 反射支持 controller 指针与普通实例; 函数调用支持指针参数与非指针参数 2025-02-17 16:31:21 +08:00
74f0ae2069 update go mod 2025-02-16 21:12:28 +08:00
d237b3b651 update go mod 2025-02-16 20:08:17 +08:00
fca399fdc3 Merge pull request '升级server自动管理' (#4) from feature/upgrade_server into master
Reviewed-on: #4
2025-02-16 17:45:07 +08:00
593df47884 引入日志初始化中间件 2025-02-16 17:41:33 +08:00
3774bda0ff 升级api-doc版本 2025-02-16 17:28:22 +08:00
4ddf55d371 优化接口文档注册 2025-02-16 16:44:57 +08:00
d832eef5a0 调试文档集成 2025-02-16 13:06:58 +08:00
c3df76e94d 修复URL注册BUG 2025-02-15 22:34:21 +08:00
99df73e50e 整合默认swagger文档 2025-02-15 21:53:21 +08:00
ff91efd1c8 完成一版文档json生成 2025-02-15 21:19:35 +08:00
e1f597ae50 引入文档解析 2025-02-14 22:30:56 +08:00
2f7d1438b2 update go mod 2025-02-14 21:16:19 +08:00
a50883b734 优化server初始化的方式 2025-02-07 18:33:07 +08:00
5d19efa8cf Merge pull request '格式化验证失败的错误信息, 默认tag=err, 支持重置默认tag' (#3) from feature/format_validate_err into master
Reviewed-on: #3
2025-02-07 18:03:21 +08:00
7c67160c65 格式化验证失败的错误信息, 默认tag=err, 支持重置默认tag 2025-02-07 18:01:05 +08:00
a338713c77 Merge pull request '支持通过controller自动解析注册接口路由' (#2) from feature/router into master
Reviewed-on: #2
2025-02-07 17:32:34 +08:00
cb4718a269 接口逻辑函数返回值严格校验时, 校验不通过, 由忽略接口注册修改为panic 2025-02-07 17:25:25 +08:00
1b1964881f 返回数据支持是否严格模式的校验, 严格模式下, 必须返回结构体或者map 2025-02-07 17:19:25 +08:00
e95061a1a8 完成基础反射调用接口 + 数据响应 2025-02-07 16:57:15 +08:00
77ea723e86 完善 RequestHandler 逻辑 2025-02-07 16:10:33 +08:00
196c437cc5 update go mod 2025-02-07 10:33:39 +08:00
b408076fa7 保存路由统一注册方法 2025-01-27 19:46:34 +08:00
851de1b3ef save code 2025-01-27 15:07:58 +08:00
e40475cdb1 update go mod 2025-01-26 14:34:24 +08:00
ec04a023cd merge master && fix both modify 2025-01-26 14:32:59 +08:00
18988c4d46 Merge pull request '升级请求体解析' (#1) from feature/parse_body into master
Reviewed-on: #1
2024-11-05 17:32:44 +08:00
196c420fcc update go mod 2024-11-05 17:31:44 +08:00
6a917d338a 支持xml body解析 2024-11-04 17:06:51 +08:00
f6db9e8edb 升级 wrapperHandle -> ParseBody 2024-11-04 16:12:09 +08:00
a0b4cdb414 update go mod 2024-11-04 15:57:39 +08:00
cffb0c8779 增加form url encode解析 2024-10-22 17:20:30 +08:00
56441151cf 增加json的请求Body解析 2024-10-22 17:14:53 +08:00
2c99eb9656 涉及请求体解析适配器接口约束 2024-10-22 16:49:45 +08:00
a72733d81a update go mod 2024-10-22 16:37:13 +08:00
0636dd1b11 优化异常堆栈打印 2024-09-30 16:17:14 +08:00
e52d67cfe3 update 2024-09-30 15:54:20 +08:00
02cdc3c792 fix 2024-09-30 15:25:58 +08:00
36d4ca844a update debug strack 2024-09-30 15:25:17 +08:00
99ea9ba111 接口非成功打印异常堆栈
- 接口非成功打印异常堆栈
- 支持开关控制是否打印
2024-09-30 11:47:04 +08:00
8ceb818a24 fix 2024-09-26 17:40:09 +08:00
22bf77019a update method name 2024-09-26 17:33:27 +08:00
01c2fdf5a2 修复对外输出扩展数据 2024-09-24 15:10:37 +08:00
29b0eaa6b3 fix middleware 2024-09-24 15:05:02 +08:00
5ae2e3fae1 数据相应支持扩展数据 2024-09-24 14:56:03 +08:00
9d0f74b19a 增加解析header + 解析 cookie 2024-09-23 17:06:49 +08:00
c4d41a9d4e fix return value 2024-09-20 17:37:03 +08:00
5abd91f947 set defult支持get 2024-09-20 17:34:53 +08:00
4f1e0e2649 表单验证支持设置默认值 2024-09-20 17:31:23 +08:00
df5a20ea87 增加GetDomain方法 2024-09-19 15:13:25 +08:00
195e391235 修复响应完成时间未设置的问题 2024-08-17 20:21:34 +08:00
ce8feaa98b 修复响应完成时间未设置的问题 2024-08-17 20:07:48 +08:00
29bcf44ec9 相应日志区区成功和失败 2024-08-17 17:31:11 +08:00
c420abfed9 update go mod 2024-08-14 14:12:37 +08:00
fce6043dca update go mod 2024-08-11 17:39:03 +08:00
df16e63e85 fix both modify 2024-07-26 14:47:00 +08:00
9b7e714b29 fix 2024-07-26 14:10:47 +08:00
b9c0389e7b 升级请求日志记录 + 升级 request wrapperHandle + 拆分after hook 2024-07-26 12:25:22 +08:00
b2fe05111c fix 2024-07-26 11:52:53 +08:00
3c29aa847c 增加获取query和body的方法 2024-07-26 11:50:42 +08:00
3368418b3b update go mod 2024-07-26 11:07:14 +08:00
86cecc4eb7 init 2024-07-23 21:53:00 +08:00
a1418c290c fix 2024-07-23 21:40:53 +08:00
8f533f5b04 fix 2024-07-23 21:34:40 +08:00
20d655973a 修复initRequest middleware 2024-07-23 18:29:31 +08:00
ca0cb31235 优化响应配置 2024-07-23 17:09:29 +08:00
9eb3b92c41 增加wrapper方法 2024-07-23 16:40:13 +08:00
60ef099fdf update readme 2024-07-22 14:33:21 +08:00
8997577181 add readme 2024-07-22 14:31:55 +08:00
cc98043aff update Test 2024-07-22 11:19:41 +08:00
0b7df330f7 切换弃用的函数 2024-07-22 10:30:18 +08:00
8a6182d0de 统一支持设置请求开始时间 2024-07-21 22:03:59 +08:00
deead87688 优化路由注册 2024-07-21 19:16:50 +08:00
f8f63691b7 完成基础路由注册, 遗留 : form表单, any数据类型解析失败 2024-07-21 18:49:44 +08:00
455f74ad89 接口meta信息解析 2024-07-21 16:51:02 +08:00
f55dee577d 完成路由中间件解析 2024-07-21 12:19:23 +08:00
b14b1ef345 支持解析路由前缀 2024-07-20 23:39:25 +08:00
cbfe92597e 优化函数定义 2024-07-19 22:31:18 +08:00
0a4a8a3c67 update go mod 2024-07-19 22:12:45 +08:00
567f8794de update go mod 2024-07-19 22:11:57 +08:00
0ffe074179 update go mod + update send with exception 2024-03-15 10:50:01 +08:00
c4edba172b 增加scheme获取 2024-01-02 16:16:42 +08:00
a6c94d55b1 包装get query 2024-01-02 16:04:17 +08:00
3298adb55a 增加请求方法的获取 2024-01-02 15:20:08 +08:00
d94d4b0e9f update func name 2023-12-29 17:58:22 +08:00
cc6975ec98 修复完成时间错误问题 2023-12-29 17:49:24 +08:00
f787899725 优化cfg初始化 2023-12-29 16:12:33 +08:00
80ce64f8e1 优化日志记录中间件 2023-12-29 16:02:39 +08:00
4dc70ee96b 优化response + wrapper 2023-12-29 15:35:46 +08:00
b7abf88670 包装gin上下文数据获取 2023-12-29 15:29:37 +08:00
782c3afffe update go mod 2023-12-29 15:04:12 +08:00
d176a23c53 update go mod 2023-09-10 15:29:35 +08:00
e7c13b6089 修复trace_id生成错误 2023-08-26 13:24:25 +08:00
817a4a8310 update go mod 2023-08-11 15:21:13 +08:00
c93cf01fe0 切换util工具函数 2023-08-10 14:47:20 +08:00
5e1aaaf64e 优化发送响应关于时间的处理 2023-08-10 12:13:49 +08:00
ec91bb7a44 优化请求时间的处理 2023-08-10 12:09:30 +08:00
f68f691dd1 迁移部分已有代码 2023-03-03 16:52:18 +08:00
25aab2e8db 增加ctx.JSON的平替方法 2023-02-15 14:52:49 +08:00
77d2310b27 修复表单解析panic问题 2023-02-12 22:26:03 +08:00
7678ee10ba 表单解析增加表单验证,使用 => github.com/go-playground/validator/v10 2022-10-16 16:45:47 +08:00
6d2ea0ce59 修复get请求参数解析 2022-07-17 22:35:24 +08:00
28 changed files with 2215 additions and 331 deletions

12
define/consts.go Normal file
View File

@ -0,0 +1,12 @@
// Package define ...
//
// Description : define ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-04-12 20:18
package define
const (
CustomContextKey = "_CUSTOM_CONTEXT" // 自定义context
)

68
define/context.go Normal file
View File

@ -0,0 +1,68 @@
// Package define ...
//
// Description : define ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-04-12 20:57
package define
import (
"fmt"
"strings"
"time"
networkUtil "git.zhangdeman.cn/zhangdeman/network/util"
"git.zhangdeman.cn/zhangdeman/wrapper"
"git.zhangdeman.cn/zhangdeman/trace"
"github.com/gin-gonic/gin"
)
type Context struct {
Context *gin.Context // 继承 gin context
Trace *trace.Runtime // trace 实例
TraceID string
RequestID string
RequestTime time.Time
}
// NewContext 创建context
func NewContext(ginCtx *gin.Context) *Context {
existCtx, exist := ginCtx.Get(CustomContextKey)
if exist && existCtx != nil {
if c, ok := existCtx.(*Context); ok {
return c
}
}
traceID := fmt.Sprintf(
"%v-%v-%v-%v",
time.Now().UnixNano()/1e6,
strings.ReplaceAll(networkUtil.IP.GetHostIP(), ".", ""),
strings.ReplaceAll(networkUtil.IP.GetRemoteIP(ginCtx.Request), ".", ""),
wrapper.StringFromRandom(32, "").Md5().Value,
)
getRequestID := func(ctx *gin.Context, traceID string) string {
requestID := ctx.GetHeader("X-Forward-Request-Id")
if len(requestID) > 0 {
return requestID
}
if len(traceID) > 0 {
return traceID
}
return traceID
}
ctx := &Context{
Context: ginCtx,
Trace: trace.NewRuntime(traceID, 1),
TraceID: traceID,
RequestID: getRequestID(ginCtx, traceID),
RequestTime: time.Now(),
}
httpHandleConfig := GetHttpHandleConfig()
ginCtx.Set(CustomContextKey, ctx)
ginCtx.Set(httpHandleConfig.TraceIDField, traceID)
ginCtx.Set(httpHandleConfig.RequestIDField, ctx.RequestID)
ginCtx.Set(httpHandleConfig.StartRequestTimeField, ctx.RequestTime.UnixMilli())
return ctx
}

View File

@ -7,31 +7,14 @@
// Date : 2022-06-25 20:33
package define
import (
"git.zhangdeman.cn/zhangdeman/consts"
"git.zhangdeman.cn/zhangdeman/wrapper"
"sync"
)
var (
// RequestIDField 请求ID
RequestIDField = "request_id"
// TraceIDField 追踪ID
TraceIDField = "trace_id"
// StartRequestTimeField 开始请求时间字段
StartRequestTimeField = "start_request_time"
// FinishRequestTimeField 完成请求时间
FinishRequestTimeField = "finish_request_time"
// ResponseCodeField 响应状态码字段
ResponseCodeField = "code"
// ResponseMessageField 响应信息字段
ResponseMessageField = "message"
// HandleRequestCostField 处理请求耗时
HandleRequestCostField = "cost"
// ResponseDataField 响应数据字段
ResponseDataField = "data"
// ResponseTraceIDField 响应TraceID字段
ResponseTraceIDField = "trace_id"
// ResponseRequestIDField 响应的请求ID字段
ResponseRequestIDField = "request_id"
// RecordResponseDataField 记录响应数据到上下文
RecordResponseDataField = "pkg_gin_record_response_data"
// RecordRequestDataField 记录请求数据到上下文
RecordRequestDataField = "pkg_gin_record_request_data"
inputHttpHandleConfig = &HttpHandleConfig{}
)
// HttpHandleConfig 请求处理配置
@ -40,15 +23,21 @@ var (
//
// Date : 20:41 2022/6/25
type HttpHandleConfig struct {
RequestIDField string
TraceIDField string
ResponseCodeField string
ResponseMessageField string
HandleRequestCostField string
ResponseDataField string
ResponseTraceIDField string
ResponseRequestIDField string
StartRequestTimeField string
RecordRequestDataField string
RecordResponseDataField string
RequestIDField string
TraceIDField string
ResponseCodeField string
ResponseMessageField string
HandleRequestCostField string
ResponseDataField string
ResponseTraceIDField string
StartRequestTimeField string
FinishRequestTimeField string
RequestIsSuccessField string // 请求处理是否成功的标识
ExtensionOutputField string // 扩展信息对外输出字段
EnableExtensionOutput bool
DisableDebugStackOutput bool // 禁用异常堆栈打印
}
// ConvertDefaultConfig 覆盖默认配置
@ -57,39 +46,110 @@ type HttpHandleConfig struct {
//
// Date : 20:41 2022/6/25
func ConvertDefaultConfig(cfg *HttpHandleConfig) {
if len(cfg.RequestIDField) > 0 {
RequestIDField = cfg.ResponseRequestIDField
}
inputHttpHandleConfig = cfg
}
if len(cfg.TraceIDField) > 0 {
TraceIDField = cfg.TraceIDField
}
if len(cfg.ResponseCodeField) > 0 {
ResponseCodeField = cfg.ResponseCodeField
}
if len(cfg.ResponseMessageField) > 0 {
ResponseMessageField = cfg.ResponseMessageField
}
if len(cfg.ResponseDataField) > 0 {
ResponseDataField = cfg.ResponseDataField
}
if len(cfg.ResponseRequestIDField) > 0 {
ResponseRequestIDField = cfg.ResponseRequestIDField
}
if len(cfg.ResponseTraceIDField) > 0 {
ResponseTraceIDField = cfg.ResponseTraceIDField
}
if len(cfg.HandleRequestCostField) > 0 {
HandleRequestCostField = cfg.HandleRequestCostField
}
if len(cfg.StartRequestTimeField) > 0 {
StartRequestTimeField = cfg.StartRequestTimeField
// GetHttpHandleConfig 获取http配置
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 16:55 2024/7/23
func GetHttpHandleConfig() *HttpHandleConfig {
return &HttpHandleConfig{
EnableExtensionOutput: inputHttpHandleConfig.EnableExtensionOutput,
DisableDebugStackOutput: inputHttpHandleConfig.DisableDebugStackOutput,
RequestIDField: wrapper.TernaryOperator.String(
nil == inputHttpHandleConfig || inputHttpHandleConfig.RequestIDField == "",
consts.GinRequestIDField,
wrapper.String(inputHttpHandleConfig.RequestIDField),
).Value(),
TraceIDField: wrapper.TernaryOperator.String(
nil == inputHttpHandleConfig || inputHttpHandleConfig.TraceIDField == "",
consts.GinTraceIDField,
wrapper.String(inputHttpHandleConfig.TraceIDField),
).Value(),
ResponseCodeField: wrapper.TernaryOperator.String(
nil == inputHttpHandleConfig || inputHttpHandleConfig.ResponseCodeField == "",
consts.GinResponseCodeField,
wrapper.String(inputHttpHandleConfig.ResponseCodeField),
).Value(),
ResponseMessageField: wrapper.TernaryOperator.String(
nil == inputHttpHandleConfig || inputHttpHandleConfig.ResponseMessageField == "",
consts.GinResponseMessageField,
wrapper.String(inputHttpHandleConfig.ResponseMessageField),
).Value(),
HandleRequestCostField: wrapper.TernaryOperator.String(
nil == inputHttpHandleConfig || inputHttpHandleConfig.HandleRequestCostField == "",
consts.GinHandleRequestCostField,
wrapper.String(inputHttpHandleConfig.HandleRequestCostField),
).Value(),
ResponseDataField: wrapper.TernaryOperator.String(
nil == inputHttpHandleConfig || inputHttpHandleConfig.ResponseDataField == "",
consts.GinResponseDataField,
wrapper.String(inputHttpHandleConfig.ResponseDataField),
).Value(),
ResponseTraceIDField: wrapper.TernaryOperator.String(
nil == inputHttpHandleConfig || inputHttpHandleConfig.ResponseTraceIDField == "",
consts.GinResponseTraceIDField,
wrapper.String(inputHttpHandleConfig.ResponseTraceIDField),
).Value(),
StartRequestTimeField: wrapper.TernaryOperator.String(
nil == inputHttpHandleConfig || inputHttpHandleConfig.StartRequestTimeField == "",
consts.GinStartRequestTimeField,
wrapper.String(inputHttpHandleConfig.StartRequestTimeField),
).Value(),
FinishRequestTimeField: wrapper.TernaryOperator.String(
nil == inputHttpHandleConfig || inputHttpHandleConfig.FinishRequestTimeField == "",
consts.GinFinishRequestTimeField,
wrapper.String(inputHttpHandleConfig.FinishRequestTimeField),
).Value(),
RecordRequestDataField: wrapper.TernaryOperator.String(
nil == inputHttpHandleConfig || inputHttpHandleConfig.RecordRequestDataField == "",
consts.GinRecordRequestDataField,
wrapper.String(inputHttpHandleConfig.RecordRequestDataField),
).Value(),
RecordResponseDataField: wrapper.TernaryOperator.String(
nil == inputHttpHandleConfig || inputHttpHandleConfig.RecordResponseDataField == "",
consts.GinRecordResponseDataField,
wrapper.String(inputHttpHandleConfig.RecordResponseDataField),
).Value(),
RequestIsSuccessField: wrapper.TernaryOperator.String(
nil == inputHttpHandleConfig || inputHttpHandleConfig.RequestIsSuccessField == "",
consts.GinRequestIsSuccessField,
wrapper.String(inputHttpHandleConfig.RecordResponseDataField),
).Value(),
ExtensionOutputField: wrapper.TernaryOperator.String(
nil == inputHttpHandleConfig || inputHttpHandleConfig.ExtensionOutputField == "",
consts.GinResponseExtensionField,
wrapper.String(inputHttpHandleConfig.RecordResponseDataField),
).Value(),
}
}
const (
LogicAfterResponseKey = "__logic_after_response__"
)
type LogicAfterResponse struct {
SuccessHookFuncList []func() `json:"-"` // 请求最后需要执行的成功hook函数
FailureHookFuncList []func() `json:"-"` // 请求最后需要执行的失败hook函数
Lock *sync.RWMutex `json:"-"` // 逻辑锁
}
func (logic *LogicAfterResponse) AddSuccessHook(f func()) {
logic.Lock.Lock()
defer logic.Lock.Unlock()
logic.SuccessHookFuncList = append(logic.SuccessHookFuncList, f)
}
func (logic *LogicAfterResponse) AddFailureHook(f func()) {
logic.Lock.Lock()
defer logic.Lock.Unlock()
logic.FailureHookFuncList = append(logic.FailureHookFuncList, f)
}
// ResponseOption 响应的可用选项
type ResponseOption struct {
ContentType string `json:"content_type"` // 响应的contentType
Extension map[string]any `json:"extension"` // 扩展数据
XmlName string `json:"xml_name"` // 以xml文件格式响应数据时, Xml文件名(根节点)
}

98
go.mod
View File

@ -1,43 +1,85 @@
module git.zhangdeman.cn/zhangdeman/gin
go 1.17
go 1.24.1
toolchain go1.24.2
require (
git.zhangdeman.cn/zhangdeman/exception v0.0.0-20220703050824-0679ce509241
git.zhangdeman.cn/zhangdeman/util v0.0.0-20220714054940-3cfcad0c6547
github.com/gin-gonic/gin v1.8.1
git.zhangdeman.cn/gateway/api-doc v0.0.0-20250528130517-e02098e64392
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250425024726-cc17224cb995
git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20250429065800-fc340b9417cf
git.zhangdeman.cn/zhangdeman/exception v0.0.0-20250510123912-a0d52fc093ab
git.zhangdeman.cn/zhangdeman/graceful v0.0.0-20250529070945-92833db6f3a4
git.zhangdeman.cn/zhangdeman/logger v0.0.0-20250427065227-163236205af5
git.zhangdeman.cn/zhangdeman/network v0.0.0-20250509030820-7b1a36a7c38c
git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20250504055908-8d68e6106ea9
git.zhangdeman.cn/zhangdeman/trace v0.0.0-20250412104923-c1ecb1bfe8d5
git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250321102712-1cbfbe959740
github.com/gin-contrib/pprof v1.5.3
github.com/gin-gonic/gin v1.10.1
github.com/go-playground/validator/v10 v10.26.0
github.com/mcuadros/go-defaults v1.2.0
go.uber.org/zap v1.27.0
)
require (
github.com/Jeffail/gabs v1.4.0 // indirect
git.zhangdeman.cn/zhangdeman/easylock v0.0.0-20230731062340-983985c12eda // indirect
git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20241101082529-28a6c68e38a4 // indirect
git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 // indirect
git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e // indirect
git.zhangdeman.cn/zhangdeman/websocket v0.0.0-20241125101541-c5ea194c9c1e // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ini/ini v1.66.6 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.10.0 // indirect
github.com/goccy/go-json v0.9.7 // indirect
github.com/bytedance/sonic v1.13.3 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-webtools/knife4go v1.0.4 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect
github.com/lestrrat-go/strftime v1.1.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/mozillazg/go-pinyin v0.20.0 // indirect
github.com/mssola/user_agent v0.6.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sbabiv/xml2map v1.2.1 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/stretchr/testify v1.8.0 // indirect
github.com/tidwall/gjson v1.14.1 // indirect
github.com/swaggo/files v1.0.1 // indirect
github.com/swaggo/gin-swagger v1.6.0 // indirect
github.com/swaggo/swag v1.16.4 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.18.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/tools v0.33.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

284
go.sum
View File

@ -1,151 +1,241 @@
git.zhangdeman.cn/zhangdeman/exception v0.0.0-20220703050824-0679ce509241 h1:tBaET7SL1ElDQ8mI3A0sHNonmycB0fFp3n8vWo410YM=
git.zhangdeman.cn/zhangdeman/exception v0.0.0-20220703050824-0679ce509241/go.mod h1:mIMM/t9BkrKHAcDCmarLCHQhHfWf0/ZjtcqJPboqmSA=
git.zhangdeman.cn/zhangdeman/util v0.0.0-20220625151616-cfe1f4c04db4 h1:uHYTRztH/XEVtr3FLykCf/3LhFQ7zQHnzVAeDyC1huQ=
git.zhangdeman.cn/zhangdeman/util v0.0.0-20220625151616-cfe1f4c04db4/go.mod h1:YI/XeTmrr9+8dxa4ThPkmNcEE8WHG5pZkKujpSWwIxM=
git.zhangdeman.cn/zhangdeman/util v0.0.0-20220714054940-3cfcad0c6547 h1:ieuPrfZDDgwz9ANQdXQrGNWlKWN6bK3qOUC+XUAITrk=
git.zhangdeman.cn/zhangdeman/util v0.0.0-20220714054940-3cfcad0c6547/go.mod h1:zTir/0IWdK3E7n0GiaogyWHADAQnBtTdl2I6Z2/OPqw=
github.com/Jeffail/gabs v1.4.0 h1://5fYRRTq1edjfIrQGvdkcd22pkYUrHZ5YC/H2GJVAo=
github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc=
git.zhangdeman.cn/gateway/api-doc v0.0.0-20250528130517-e02098e64392 h1:1HALMZC54/krtdGKnRlhATMzCT5sZdvgVIxt4zZIrTk=
git.zhangdeman.cn/gateway/api-doc v0.0.0-20250528130517-e02098e64392/go.mod h1:ypwNuZTQHxynZwm+l5Z8ZYFd5YrLrwnSWy+FxtXLqJw=
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250425024726-cc17224cb995 h1:LmPRAf0AsxRVFPibdpZR89ajlsz8hof2IvMMyTqiEq4=
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250425024726-cc17224cb995/go.mod h1:5p8CEKGBxi7qPtTXDI3HDmqKAfIm5i/aBWdrbkbdNjc=
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/easylock v0.0.0-20230731062340-983985c12eda h1:bMD6r9gjRy7cO+T4zRQVYAesgIblBdTnhzT1vN5wjvI=
git.zhangdeman.cn/zhangdeman/easylock v0.0.0-20230731062340-983985c12eda/go.mod h1:dT0rmHcJ9Z9IqWeMIt7YzR88nKkNV2V3dfG0j9Q6lK0=
git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20241101082529-28a6c68e38a4 h1:s6d4b6yY+NaK1AzoBD1pxqsuygEHQz0Oie86c45geDw=
git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20241101082529-28a6c68e38a4/go.mod h1:V4Dfg1v/JVIZGEKCm6/aehs8hK+Xow1dkL1yiQymXlQ=
git.zhangdeman.cn/zhangdeman/exception v0.0.0-20250510123912-a0d52fc093ab h1:O0XaAKKb8qrjcjewonmKfnRsMFoCfJF+tUv6RfhRe94=
git.zhangdeman.cn/zhangdeman/exception v0.0.0-20250510123912-a0d52fc093ab/go.mod h1:Voc8J4ordx7nuMWpgACXXZULQy7ZIuBzcEIoS8VnDIw=
git.zhangdeman.cn/zhangdeman/graceful v0.0.0-20250529070945-92833db6f3a4 h1:d1B3IXRitiMlY5ssEWRa//RQD24SqRPk/bCpL4/WyOA=
git.zhangdeman.cn/zhangdeman/graceful v0.0.0-20250529070945-92833db6f3a4/go.mod h1:faaKb5d5tz3NmQQ+NrTnBDa1G/tN9/CVuKun1V4WBIE=
git.zhangdeman.cn/zhangdeman/logger v0.0.0-20250427065227-163236205af5 h1:e3N+ODeTBwBOQ4iIzXm9gO3X6Htgc/YmPBImSk7o2mw=
git.zhangdeman.cn/zhangdeman/logger v0.0.0-20250427065227-163236205af5/go.mod h1:lTxYyi7IodBefXSdSZwPFs3Izggg5Wxa2RkHlFVwKLg=
git.zhangdeman.cn/zhangdeman/network v0.0.0-20250509030820-7b1a36a7c38c h1:4Yn7U1TkurpNizrjbIyJDYTRSlD4zp1ypkREyCSBKIM=
git.zhangdeman.cn/zhangdeman/network v0.0.0-20250509030820-7b1a36a7c38c/go.mod h1:uRg3BnUimmrJiv8N61P6Ir4Im2EqxXZe62m4WBw3sec=
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-20250504055908-8d68e6106ea9 h1:/GLQaFoLb+ciHOtAS2BIyPNnf4O5ME3AC5PUaJY9kfs=
git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20250504055908-8d68e6106ea9/go.mod h1:ABJ655C5QenQNOzf7LjCe4sSB52CXvaWLX2Zg4uwDJY=
git.zhangdeman.cn/zhangdeman/trace v0.0.0-20250412104923-c1ecb1bfe8d5 h1:dD1Q/MIrRmIhKqfYPH+y167ca9CKwTPuQt3c1hXWGJ8=
git.zhangdeman.cn/zhangdeman/trace v0.0.0-20250412104923-c1ecb1bfe8d5/go.mod h1:PB486NC82nuvn5yi+U2i48ogX/9EAETWAHd8O9TwY9k=
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/websocket v0.0.0-20241125101541-c5ea194c9c1e h1:YE2Gi+M03UDImIpWa3I7jzSesyfu2RL8x/4ONs5v0oE=
git.zhangdeman.cn/zhangdeman/websocket v0.0.0-20241125101541-c5ea194c9c1e/go.mod h1:L/7JugxKZL3JP9JP/XDvPAPz0FQXG1u181Su1+u/d1c=
git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250321102712-1cbfbe959740 h1:zPUoylfJTbc0EcxW+NEzOTBmoeFZ2I/rLFBnEzxb4Wk=
git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250321102712-1cbfbe959740/go.mod h1:1ct92dbVc49pmXusA/iGfcQUJzcYmJ+cjAhgc3sDv1I=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ=
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/go-ini/ini v1.66.6 h1:h6k2Bb0HWS/BXXHCXj4QHjxPmlIU4NK+7MuLp9SD+4k=
github.com/go-ini/ini v1.66.6/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/pprof v1.5.3 h1:Bj5SxJ3kQDVez/s/+f9+meedJIqLS+xlkIVDe/lcvgM=
github.com/gin-contrib/pprof v1.5.3/go.mod h1:0+LQSZ4SLO0B6+2n6JBzaEygpTBxe/nI+YEYpfQQ6xY=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-webtools/knife4go v1.0.4 h1:p32SApmM0sx2/Y5p0QfeaGv5KD96R1mj2CaHdyH8jy8=
github.com/go-webtools/knife4go v1.0.4/go.mod h1:trOlXN1tqBJ7R44sHON3exGvzCwjbsVriIHEenry3d8=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg=
github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4=
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA=
github.com/lestrrat-go/strftime v1.1.0 h1:gMESpZy44/4pXLO/m+sL0yBd1W6LjgjrrD4a68Gapyg=
github.com/lestrrat-go/strftime v1.1.0/go.mod h1:uzeIB52CeUJenCo1syghlugshMysrqUT51HlxphXVeI=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mcuadros/go-defaults v1.2.0 h1:FODb8WSf0uGaY8elWJAkoLL0Ri6AlZ1bFlenk56oZtc=
github.com/mcuadros/go-defaults v1.2.0/go.mod h1:WEZtHEVIGYVDqkKSWBdWKUVdRyKlMfulPaGDWIVeCWY=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/mozillazg/go-pinyin v0.20.0 h1:BtR3DsxpApHfKReaPO1fCqF4pThRwH9uwvXzm+GnMFQ=
github.com/mozillazg/go-pinyin v0.20.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
github.com/mssola/user_agent v0.6.0 h1:uwPR4rtWlCHRFyyP9u2KOV0u8iQXmS7Z7feTrstQwk4=
github.com/mssola/user_agent v0.6.0/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
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=
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q=
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
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/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 h1:sY2a+y0j4iDrajJcorb+a0hJIQ6uakU5gybjfLWHlXo=
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376/go.mod h1:BHKOc1m5wm8WwQkMqYBoo4vNxhmF7xg8+xhG8L+Cy3M=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

12
init.go Normal file
View File

@ -0,0 +1,12 @@
// Package gin ...
//
// Description : gin ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2024-10-22 17:01
package gin
import (
_ "git.zhangdeman.cn/zhangdeman/gin/request/parse_body"
)

View File

@ -8,90 +8,120 @@
package middleware
import (
"git.zhangdeman.cn/zhangdeman/gin/request/parse_body"
"strings"
"git.zhangdeman.cn/zhangdeman/util"
"git.zhangdeman.cn/zhangdeman/consts"
"git.zhangdeman.cn/zhangdeman/gin/request"
"go.uber.org/zap"
"git.zhangdeman.cn/zhangdeman/gin/define"
"git.zhangdeman.cn/zhangdeman/logger"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// Access 记录请求日志
// fillCfg 填充默认配置
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 10:55 2022/7/14
func Access(cfg *AccessConfig) gin.HandlerFunc {
// 未传入配置或者未传入日志实例
if nil == cfg || nil == cfg.Logger {
return func(ctx *gin.Context) {
ctx.Next()
}
// Date : 15:37 2023/12/29
func fillCfg(cfg *AccessConfig) {
if nil == cfg {
return
}
if nil == cfg.IsRecordLog {
cfg.IsRecordLog = defaultIsRecordLog
}
if nil == cfg.ExtraFieldList {
cfg.ExtraFieldList = make([]string, 0)
if nil == cfg.RequestHeaderList {
cfg.RequestHeaderList = make([]string, 0)
}
cfg.ExtraFieldList = append(cfg.ExtraFieldList, define.RecordRequestDataField, define.RecordResponseDataField)
if nil == cfg.ResponseHeaderList {
cfg.ResponseHeaderList = make([]string, 0)
}
}
// getLogRequestHeader 获取记录的请求header
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 15:59 2023/12/29
func getLogRequestHeader(ctx *gin.Context, cfg *AccessConfig) map[string][]string {
// 请求header
headerTable := make(map[string][]string)
if len(cfg.RequestHeaderList) == 0 {
// 全部记录
for headerKey := range ctx.Request.Header {
cfg.RequestHeaderList = append(cfg.RequestHeaderList, headerKey)
}
}
for _, key := range cfg.RequestHeaderList {
headerTable[key] = ctx.Request.Header.Values(key)
}
return headerTable
}
// getLogResponseHeader 记录相应header
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 16:01 2023/12/29
func getLogResponseHeader(ctx *gin.Context, cfg *AccessConfig) map[string][]string {
// 响应header
responseHeaderTable := make(map[string][]string)
if len(cfg.ResponseHeaderList) == 0 {
// 全部记录
for headerKey := range ctx.Writer.Header() {
cfg.ResponseHeaderList = append(cfg.ResponseHeaderList, headerKey)
}
zap.Any("pkg_gin_response_header", ctx.Writer.Header())
}
for _, key := range cfg.ResponseHeaderList {
responseHeaderTable[key] = ctx.Writer.Header().Values(key)
}
return responseHeaderTable
}
// LogRequest 记录请求日志
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 10:55 2022/7/14
func LogRequest(cfg *AccessConfig) gin.HandlerFunc {
fillCfg(cfg)
handleConfig := define.GetHttpHandleConfig()
return func(ctx *gin.Context) {
ctx.Next()
if !cfg.IsRecordLog(ctx) {
// 不记录日志
// 未传入配置或者未传入日志实例
if nil == cfg || nil == cfg.Logger || (nil != cfg.IsRecordLog && !cfg.IsRecordLog(ctx)) {
ctx.Next()
return
}
startRequestTime := ctx.GetInt64(define.StartRequestTimeField)
logDataList := []zap.Field{
// 开始请求时间
zap.Any(define.StartRequestTimeField, util.Time.FormatUnixNano(startRequestTime)),
startRequestTime := request.WrapperHandle.GetCtxIntData(ctx, handleConfig.StartRequestTimeField, 0)
// 记录请求日志
data := map[string]any{
handleConfig.StartRequestTimeField: startRequestTime, // 开始请求时间
"request_header": getLogRequestHeader(ctx, cfg), // 请求header
"request_query": request.WrapperHandle.ParseQuery(ctx), // 获取请求query
"request_body": map[string]any{}, // 请求body
}
data["request_body"], _ = parse_body.ExecuteForMap(ctx)
logData := logger.NewLogData(ctx, consts.LogTypeRequest, "", data)
cfg.Logger.Info("接口请求日志记录", logger.ZapLogDataList(logData)...)
ctx.Next()
// 结束时间
finishRequestTime := ctx.GetInt64(define.FinishRequestTimeField)
logDataList = append(
logDataList,
zap.Any(define.FinishRequestTimeField, util.Time.FormatUnixNano(startRequestTime)),
zap.Int64("pkg_gin_request_cost", finishRequestTime-startRequestTime),
)
// 请求header
if len(cfg.RequestHeaderList) == 0 {
// 全部记录
zap.Any("pkg_gin_request_header", ctx.Request.Header)
finishRequestTime := request.WrapperHandle.GetCtxIntData(ctx, handleConfig.FinishRequestTimeField, 0)
ctx.Set(handleConfig.FinishRequestTimeField, finishRequestTime)
// 记录相应日志
logResponseData := logger.NewLogData(ctx, consts.LogTypeOutput, "", map[string]any{
handleConfig.FinishRequestTimeField: finishRequestTime, // 完成请求时间
"request_cost": finishRequestTime - startRequestTime, // 请求耗时
"response_body": request.WrapperHandle.GetResponseBody(ctx, handleConfig.ResponseDataField, map[string]any{}), // 响应body
"response_header": getLogResponseHeader(ctx, cfg), // 响应header
})
if ctx.GetBool(define.GetHttpHandleConfig().RequestIsSuccessField) {
cfg.Logger.Info("接口响应日志记录", logger.ZapLogDataList(logResponseData)...)
} else {
headerTable := make(map[string][]string, 0)
for _, key := range cfg.RequestHeaderList {
headerTable[key] = ctx.Request.Header.Values(key)
}
zap.Any("pkg_gin_request_header", headerTable)
}
// 响应header
if len(cfg.ResponseHeaderList) == 0 {
// 全部记录
zap.Any("pkg_gin_response_header", ctx.Writer.Header())
} else {
headerTable := make(map[string][]string, 0)
for _, key := range cfg.ResponseHeaderList {
headerTable[key] = ctx.Writer.Header().Values(key)
}
zap.Any("pkg_gin_response_header", headerTable)
}
// 扩展数据
for _, field := range cfg.ExtraFieldList {
val, _ := ctx.Get(field)
logDataList = append(logDataList, zap.Any(field, val))
}
cfg.Logger.Info("请求日志记录", logDataList...)
if nil != cfg.FinishHook {
// hook 不为nil, 自动触发
cfg.FinishHook(ctx,
[]byte(ctx.GetString(define.RecordRequestDataField)),
[]byte(ctx.GetString(define.RecordResponseDataField)),
finishRequestTime-startRequestTime)
cfg.Logger.Error("接口响应日志记录", logger.ZapLogDataList(logResponseData)...)
}
}
}
@ -102,12 +132,10 @@ func Access(cfg *AccessConfig) gin.HandlerFunc {
//
// Date : 11:26 2022/7/14
type AccessConfig struct {
Logger *zap.Logger // 日志实例
RequestHeaderList []string // 要记录哪些header , 不传全部记录
ResponseHeaderList []string // 要记录哪些响应header, 不传全部记录
IsRecordLog func(ctx *gin.Context) bool // 验证当前请求是否记录日志
ExtraFieldList []string // 记录的扩展字段列表,请将相关数据使用 ctx.Set 写入上下文中, 日志会自动记录
FinishHook func(ctx *gin.Context, requestData []byte, responseData []byte, cost int64) // 请求处理完成之后, 触发的hook函数
Logger *zap.Logger // 日志实例
RequestHeaderList []string // 要记录哪些header , 不传全部记录
ResponseHeaderList []string // 要记录哪些响应header, 不传全部记录
IsRecordLog func(ctx *gin.Context) bool // 验证当前请求是否记录日志
}
// defaultIsRecordLog 默认仅记录 json api 日志

View File

@ -0,0 +1,38 @@
// Package middleware ...
//
// Description : middleware ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2024-07-26 11:54
package middleware
import (
"git.zhangdeman.cn/zhangdeman/gin/define"
"git.zhangdeman.cn/zhangdeman/gin/request"
"github.com/gin-gonic/gin"
)
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) {
ctx.Next()
if nil == hookFunc {
return
}
// hook 不为nil, 自动触发
hookFunc(
ctx,
[]byte(ctx.GetString(handleConfig.RecordRequestDataField)),
ctx.GetStringMap(handleConfig.RecordResponseDataField),
request.WrapperHandle.GetCtxIntData(ctx, handleConfig.StartRequestTimeField, 0)-request.WrapperHandle.GetCtxIntData(ctx, handleConfig.StartRequestTimeField, 0),
)
}
}

View File

@ -12,9 +12,11 @@ import (
"strings"
"time"
"git.zhangdeman.cn/zhangdeman/wrapper"
"git.zhangdeman.cn/zhangdeman/gin/define"
"git.zhangdeman.cn/zhangdeman/util"
networkUtil "git.zhangdeman.cn/zhangdeman/network/util"
"github.com/gin-gonic/gin"
)
@ -25,11 +27,12 @@ import (
// Date : 23:08 2022/6/25
func InitRequest() gin.HandlerFunc {
return func(ctx *gin.Context) {
httpHandleConfig := define.GetHttpHandleConfig()
traceID := getTraceID(ctx)
requestID := getRequestID(ctx, traceID)
ctx.Set(define.TraceIDField, traceID)
ctx.Set(define.RequestIDField, requestID)
ctx.Set(define.StartRequestTimeField, time.Now().UnixNano())
ctx.Set(httpHandleConfig.TraceIDField, traceID)
ctx.Set(httpHandleConfig.RequestIDField, requestID)
ctx.Set(httpHandleConfig.StartRequestTimeField, time.Now().UnixMilli())
ctx.Next()
}
}
@ -43,9 +46,9 @@ func getTraceID(ctx *gin.Context) string {
return fmt.Sprintf(
"%v-%v-%v-%v",
time.Now().UnixNano()/1e6,
strings.ReplaceAll(util.IP.GetHostIP(), ".", ""),
strings.ReplaceAll(util.IP.GetRemoteIP(ctx.Request), ".", ""),
util.String.Md5(util.String.GenRandom("", 32)),
strings.ReplaceAll(networkUtil.IP.GetHostIP(), ".", ""),
strings.ReplaceAll(networkUtil.IP.GetRemoteIP(ctx.Request), ".", ""),
wrapper.StringFromRandom(32, "").Md5().Value,
)
}
@ -55,7 +58,7 @@ func getTraceID(ctx *gin.Context) string {
//
// Date : 23:12 2022/6/25
func getRequestID(ctx *gin.Context, traceID string) string {
requestID := ctx.GetHeader("X-Forward-Request-ID")
requestID := ctx.GetHeader("X-Forward-Request-Id")
if len(requestID) > 0 {
return requestID
}

View File

@ -10,7 +10,7 @@ package middleware
import (
"git.zhangdeman.cn/zhangdeman/gin/define"
"git.zhangdeman.cn/zhangdeman/gin/response"
"git.zhangdeman.cn/zhangdeman/util"
networkUtil "git.zhangdeman.cn/zhangdeman/network/util"
"github.com/gin-gonic/gin"
)
@ -26,9 +26,12 @@ func ValidateBlackIPMiddleware(code interface{}, httpCode int, validateFunc defi
ctx.Next()
return
}
if validateFunc(ctx, util.IP.GetRemoteIP(ctx.Request)) {
remoteIp := networkUtil.IP.GetRemoteIP(ctx.Request)
if validateFunc(ctx, remoteIp) {
// 命中黑名单
response.Send(ctx, code, httpCode, nil)
response.Send(ctx, code, httpCode, map[string]any{
"remote_ip": remoteIp,
}, nil)
ctx.Abort()
return
}
@ -48,9 +51,12 @@ func ValidateWhiteIPMiddleware(code interface{}, httpCode int, validateFunc defi
ctx.Next()
return
}
if !validateFunc(ctx, util.IP.GetRemoteIP(ctx.Request)) {
remoteIp := networkUtil.IP.GetRemoteIP(ctx.Request)
if !validateFunc(ctx, remoteIp) {
// 非名单
response.Send(ctx, code, httpCode, nil)
response.Send(ctx, code, httpCode, map[string]any{
"remote_ip": remoteIp,
}, nil)
ctx.Abort()
return
}

View File

@ -0,0 +1,18 @@
// Package abstract ...
//
// Description : abstract ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2024-10-22 16:38
package abstract
// RequestBodyParseAdaptor 解析请求body的接口适配器约束
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 16:39 2024/10/22
type RequestBodyParseAdaptor interface {
// Unmarshal 自定义反序列化的方法, 为 nil 则使用内置的序列化方式
Unmarshal(sourceData []byte, receiver any) error
}

View File

@ -27,7 +27,7 @@ type contentType struct {
//
// Date : 00:35 2022/7/3
func (ct *contentType) IsJson(ctx *gin.Context) bool {
return strings.Contains(ct.Get(ctx), "json")
return strings.Contains(WrapperHandle.GetContentType(ctx, "application/x-www-form-urlencoded"), "json")
}
// IsFormURLEncoded 请求方式是否为 x-www-form-urlencoded
@ -36,19 +36,5 @@ func (ct *contentType) IsJson(ctx *gin.Context) bool {
//
// Date : 00:36 2022/7/3
func (ct *contentType) IsFormURLEncoded(ctx *gin.Context) bool {
return strings.Contains(ct.Get(ctx), "x-www-form-urlencoded")
}
// Get 获取请求类型
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 00:39 2022/7/3
func (ct *contentType) Get(ctx *gin.Context) string {
contentType := strings.ToLower(ctx.ContentType())
if len(contentType) == 0 {
// 请求不传type默认为 x-www-form-urlencoded
contentType = "application/x-www-form-urlencoded"
}
return contentType
return strings.Contains(WrapperHandle.GetContentType(ctx, "application/x-www-form-urlencoded"), "x-www-form-urlencoded")
}

View File

@ -10,10 +10,13 @@ package request
import (
"bytes"
"errors"
"io/ioutil"
"github.com/mcuadros/go-defaults"
"io"
"net/http"
"strings"
"github.com/go-playground/validator/v10"
"git.zhangdeman.cn/zhangdeman/gin/define"
"github.com/gin-gonic/gin"
@ -25,6 +28,7 @@ import (
//
// Date : 00:51 2022/7/3
type form struct {
checkInstance *validator.Validate
}
// Parse 解析请求表单
@ -33,32 +37,39 @@ type form struct {
//
// Date : 00:34 2022/7/3
func (f *form) Parse(ctx *gin.Context, receiver interface{}) error {
requestBody, _ := ioutil.ReadAll(ctx.Request.Body)
handleConfig := define.GetHttpHandleConfig()
requestBody, _ := io.ReadAll(ctx.Request.Body)
// 请求信息写入上下文
ctx.Set(define.RecordRequestDataField, string(requestBody))
ctx.Set(handleConfig.RecordRequestDataField, string(requestBody))
// 因为请求体被读一遍之后就没了,重新赋值 requestBody
ctx.Request.Body = ioutil.NopCloser(bytes.NewReader(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 {
return ctx.ShouldBindQuery(receiver)
}
if method == http.MethodPost ||
if err := ctx.ShouldBindQuery(receiver); nil != err {
return err
}
} else if method == http.MethodPost ||
method == http.MethodPut ||
method == http.MethodDelete {
if ContentType.IsJson(ctx) {
return ctx.ShouldBindJSON(receiver)
if err := ctx.ShouldBindJSON(receiver); nil != err {
return err
}
} else if ContentType.IsFormURLEncoded(ctx) {
if err := ctx.ShouldBind(receiver); nil != err {
return err
}
} else {
return errors.New(ctx.ContentType() + " is not support")
}
if ContentType.IsFormURLEncoded(ctx) {
return ctx.ShouldBind(receiver)
}
return errors.New(ctx.ContentType() + " is not support")
} else {
return errors.New(method + " : request method is not support")
}
return errors.New(method + " : request method is not support")
// 设置默认值
defaults.SetDefaults(receiver)
return nil
}

View File

@ -7,6 +7,8 @@
// Date : 2022-07-03 00:52
package request
import "github.com/go-playground/validator/v10"
var (
// Form 表单相关操作
Form *form
@ -15,6 +17,8 @@ var (
)
func init() {
Form = &form{}
Form = &form{
checkInstance: validator.New(),
}
ContentType = &contentType{}
}

View File

@ -0,0 +1,128 @@
// Package parse_body ...
//
// Description : parse_body ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2024-10-22 16:43
package parse_body
import (
"bytes"
"errors"
"git.zhangdeman.cn/zhangdeman/gin/request/abstract"
"git.zhangdeman.cn/zhangdeman/serialize"
"github.com/gin-gonic/gin"
"io"
"strings"
)
var (
requestBodyParseAdaptorTable = map[string]abstract.RequestBodyParseAdaptor{}
)
func init() {
requestBodyParseAdaptorTable["xml"] = serialize.Xml
requestBodyParseAdaptorTable["ini"] = serialize.Ini
requestBodyParseAdaptorTable["yml"] = serialize.Yml
requestBodyParseAdaptorTable["yaml"] = serialize.Yml
requestBodyParseAdaptorTable["json"] = serialize.JSON
requestBodyParseAdaptorTable["x-www-form-urlencoded"] = serialize.JSON
}
// Register 注册适配器实例
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 16:45 2024/10/22
func Register(requestType string, adaptor abstract.RequestBodyParseAdaptor) {
if nil == adaptor {
return
}
requestBodyParseAdaptorTable[requestType] = adaptor
}
// Execute 解析请求BODY数据
func Execute(ctx *gin.Context, receiver any) ([]byte, error) {
contentType := strings.ToLower(strings.ReplaceAll(ctx.ContentType(), " ", ""))
if len(contentType) == 0 {
return []byte("{}"), nil
}
// 裁剪出真实的类型,之所以截取,是因为 content_type 中可能还包含编码信息, 如 : application/json;chaset=utf8
contentTypeArr := strings.Split(contentType, ";")
contentTypeFormatArr := strings.Split(contentTypeArr[0], "/")
if len(contentTypeFormatArr) != 2 {
return nil, errors.New(contentType + " : content_type format error")
}
// 裁剪出真实的类型,之所以截取,是因为 content_type 中可能还包含编码信息, 如 : application/json;charset=utf8
contentType = contentTypeFormatArr[1]
if _, exist := requestBodyParseAdaptorTable[contentType]; !exist {
return nil, errors.New(contentType + " : adaptor not found")
}
var (
bodyData []byte
err error
exist bool
body any
)
bodyKey := "_body_read_result"
if body, exist = ctx.Get(bodyKey); exist && nil != body {
bodyData = body.([]byte)
} else {
if bodyData, err = ReadBody(ctx); nil != err {
return nil, err
}
// 设置读取结果
ctx.Set(bodyKey, bodyData)
}
if err = requestBodyParseAdaptorTable[contentType].Unmarshal(bodyData, receiver); nil != err {
return nil, err
}
byteData := serialize.JSON.MarshalForByteIgnoreError(receiver)
return byteData, nil
}
// ExecuteForMap 高层级包装表单解析为map
func ExecuteForMap(ctx *gin.Context) (map[string]any, error) {
var (
err error
result map[string]any
)
if _, err = Execute(ctx, &result); nil != err {
return nil, err
}
return result, nil
}
// ReadBody 读取请求Body
func ReadBody(ctx *gin.Context) ([]byte, error) {
var (
data []byte
err error
)
// 判断form url encode
if strings.Contains(ctx.ContentType(), "x-www-form-urlencoded") {
if err = ctx.Request.ParseForm(); nil != err {
return nil, err
}
body := map[string]any{}
for paramName, itemParam := range ctx.Request.PostForm {
if len(itemParam) > 0 {
body[paramName] = itemParam[0]
} else {
body[paramName] = ""
}
}
return serialize.JSON.MarshalForByteIgnoreError(body), nil
}
// 读取Body信息
if data, err = io.ReadAll(ctx.Request.Body); nil != err {
return nil, err
}
// 重置Body信息
ctx.Request.Body = io.NopCloser(bytes.NewBuffer(data))
return data, nil
}

302
request/wrapper.go Normal file
View File

@ -0,0 +1,302 @@
// Package request ...
//
// Description : gin ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2023-12-29 15:10
package request
import (
"strings"
"sync"
"git.zhangdeman.cn/zhangdeman/trace"
"git.zhangdeman.cn/zhangdeman/gin/define"
"git.zhangdeman.cn/zhangdeman/gin/request/parse_body"
"git.zhangdeman.cn/zhangdeman/wrapper"
"github.com/gin-gonic/gin"
)
var (
WrapperHandle = &wrapperHandle{}
)
type wrapperHandle struct {
}
// GetHeader 读取header
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 21:22 2023/12/25
func (wh *wrapperHandle) GetHeader(ctx *gin.Context, headerKey string, defaultVal string) string {
if nil == ctx {
return defaultVal
}
val := ctx.GetHeader(headerKey)
return wrapper.TernaryOperator.String(len(val) > 0, wrapper.String(val), wrapper.String(defaultVal)).Value()
}
// GetCookie 读取cookie
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 21:23 2023/12/25
func (wh *wrapperHandle) GetCookie(ctx *gin.Context, cookieName string, defaultVal string) string {
if nil == ctx {
return ""
}
val, err := ctx.Cookie(cookieName)
if nil != err {
return defaultVal
}
return wrapper.TernaryOperator.String(len(val) > 0, wrapper.String(val), wrapper.String(defaultVal)).Value()
}
// GetUri 获取请求URI
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 15:13 2023/12/29
func (wh *wrapperHandle) GetUri(ctx *gin.Context, defaultVal string) string {
if nil != ctx && nil != ctx.Request && nil != ctx.Request.URL {
return ctx.Request.URL.Path
}
return defaultVal
}
// GetScheme 获取scheme
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 16:16 2024/1/2
func (wh *wrapperHandle) GetScheme(ctx *gin.Context, defaultVal string) string {
if nil != ctx && nil != ctx.Request && nil != ctx.Request.URL {
return ctx.Request.URL.Scheme
}
return defaultVal
}
// ParseQuery 获取query参数
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 16:03 2024/1/2
func (wh *wrapperHandle) ParseQuery(ctx *gin.Context) map[string]string {
query := make(map[string]string)
if nil != ctx && nil != ctx.Request && nil != ctx.Request.URL {
for paramName, valueList := range ctx.Request.URL.Query() {
if len(valueList) == 0 {
query[paramName] = ""
} else {
query[paramName] = valueList[0]
}
}
return query
}
return query
}
// GetMethod 获取请求方法
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 15:19 2024/1/2
func (wh *wrapperHandle) GetMethod(ctx *gin.Context) string {
if nil != ctx && nil != ctx.Request {
return ctx.Request.Method
}
return ""
}
// GetContentType 获取请求方式
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 15:24 2023/12/29
func (wh *wrapperHandle) GetContentType(ctx *gin.Context, defaultVal string) string {
if nil == ctx {
return defaultVal
}
contentType := strings.ToLower(ctx.ContentType())
return wrapper.TernaryOperator.String(len(contentType) > 0, wrapper.String(contentType), wrapper.String(defaultVal)).Value()
}
// GetDomain 获取请求Domain
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 15:12 2024/9/19
func (wh *wrapperHandle) GetDomain(ctx *gin.Context) string {
if nil == ctx {
return ""
}
return ctx.Request.Host
}
// ParseBody 获取请求body
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 11:25 2024/7/26
func (wh *wrapperHandle) ParseBody(ctx *gin.Context) (map[string]any, error) {
return parse_body.ExecuteForMap(ctx)
}
// GetResponseBody 获取响应body
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 15:18 2024/1/2
func (wh *wrapperHandle) GetResponseBody(ctx *gin.Context, key string, defaultVal any) any {
if nil == ctx {
return defaultVal
}
if val, exist := ctx.Get(key); !exist || nil == val {
return defaultVal
} else {
return val
}
}
// GetClientIp 获取请求客户端IP
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 16:31 2024/7/23
func (wh *wrapperHandle) GetClientIp(ctx *gin.Context, defaultVal string) string {
if nil == ctx {
return defaultVal
}
return ctx.ClientIP()
}
// GetUserAgent 获取user_agent
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 16:32 2024/7/23
func (wh *wrapperHandle) GetUserAgent(ctx *gin.Context, defaultVal string) string {
if nil == ctx {
return defaultVal
}
return ctx.Request.UserAgent()
}
// GetCtxData 获取请求上下文数据
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 16:35 2024/7/23
func (wh *wrapperHandle) GetCtxData(ctx *gin.Context, key string, defaultVal any) any {
if nil == ctx {
return defaultVal
}
if val, exist := ctx.Get(key); !exist || nil == val {
return defaultVal
} else {
return val
}
}
// GetCtxStringData 获取字符串数据
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 16:37 2024/7/23
func (wh *wrapperHandle) GetCtxStringData(ctx *gin.Context, key string, defaultVal string) string {
if nil == ctx {
return defaultVal
}
val := ctx.GetString(key)
if len(val) == 0 {
return defaultVal
}
return val
}
// GetCtxIntData 获取int数据
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 16:39 2024/7/23
func (wh *wrapperHandle) GetCtxIntData(ctx *gin.Context, key string, defaultVal int64) int64 {
if nil == ctx {
return defaultVal
}
val := wh.GetCtxData(ctx, key, nil)
if nil == val {
return defaultVal
}
return ctx.GetInt64(key)
}
// ParseHeader 解析header数据
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 17:04 2024/9/23
func (wh *wrapperHandle) ParseHeader(ctx *gin.Context) map[string]string {
headerData := map[string]string{}
if nil == ctx || nil == ctx.Request {
return headerData
}
for headerName, headerVal := range ctx.Request.Header {
if len(headerVal) > 0 {
headerData[headerName] = headerVal[0]
} else {
headerData[headerName] = ""
}
}
return headerData
}
// ParseCookie 解析cookie数据
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 17:06 2024/9/23
func (wh *wrapperHandle) ParseCookie(ctx *gin.Context) map[string]string {
cookieData := map[string]string{}
if nil == ctx || nil == ctx.Request {
return cookieData
}
for _, itemCookie := range ctx.Request.Cookies() {
cookieData[itemCookie.Name] = itemCookie.Value
}
return cookieData
}
// GetLogicAfterResponse ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 16:54 2025/2/28
func (wh *wrapperHandle) GetLogicAfterResponse(ctx *gin.Context) *define.LogicAfterResponse {
if nil == ctx || nil == ctx.Request {
return nil
}
l, exist := ctx.Get(define.LogicAfterResponseKey)
if !exist || nil == l {
l = &define.LogicAfterResponse{
SuccessHookFuncList: make([]func(), 0),
FailureHookFuncList: make([]func(), 0),
Lock: &sync.RWMutex{},
}
ctx.Set(define.LogicAfterResponseKey, l)
}
// 就这么写, key值如果被其他人覆盖成非法值, 此处会直接panic
return l.(*define.LogicAfterResponse)
}
// GetCustomContext 获取自定义context
func (wh *wrapperHandle) GetCustomContext(ctx *gin.Context) *define.Context {
return define.NewContext(ctx)
}
// GetTraceInstance 获取trace实例
func (wh *wrapperHandle) GetTraceInstance(ctx *gin.Context) *trace.Runtime {
return define.NewContext(ctx).Trace
}

View File

@ -8,8 +8,11 @@
package response
import (
"encoding/json"
"fmt"
"git.zhangdeman.cn/zhangdeman/dynamic-struct/wrapper"
"git.zhangdeman.cn/zhangdeman/serialize"
"net/http"
"strings"
"time"
"git.zhangdeman.cn/zhangdeman/exception"
@ -17,14 +20,43 @@ import (
"github.com/gin-gonic/gin"
)
const (
// hasSendResponseFlag 已经发送响应数据的标识, 一旦识别到, 重复调用不会执行
hasSendResponseFlag = "GIN_PKG_HAS_SEND_RESPONSE"
)
var (
// 成功的业务状态码
successBusinessCode any = "200"
)
// SetBusinessSuccessCode 设置成状态码
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 17:01 2024/8/17
func SetBusinessSuccessCode(code any) {
successBusinessCode = code
}
// Success 成功的响应
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 22:37 2022/6/25
func Success(ctx *gin.Context, data interface{}) {
func Success(ctx *gin.Context, data any) {
successException := exception.NewSuccess(data)
Send(ctx, successException.GetCode(), successException.GetHttpCode(), successException.GetData())
Send(ctx, successException.Code(), http.StatusOK, successException.Data(), nil)
}
// SuccessWithExtension 返回扩展数据
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 14:52 2024/9/24
func SuccessWithExtension(ctx *gin.Context, data any, responseOption *define.ResponseOption) {
successException := exception.NewSuccess(data)
Send(ctx, successException.Code(), http.StatusOK, successException.Data(), responseOption)
}
// Send 基础的发送数据
@ -32,23 +64,69 @@ func Success(ctx *gin.Context, data interface{}) {
// Author : go_developer@163.com<白茶清欢>
//
// Date : 22:40 2022/6/25
func Send(ctx *gin.Context, code interface{}, httpCode int, data interface{}) {
finishRequestTime := time.Now().UnixNano()
responseData := map[string]interface{}{
define.ResponseCodeField: code,
define.ResponseMessageField: exception.GetMessage(code),
define.ResponseTraceIDField: ctx.GetString(define.TraceIDField),
define.ResponseRequestIDField: ctx.GetString(define.RequestIDField),
define.ResponseDataField: data,
define.HandleRequestCostField: (finishRequestTime - ctx.GetInt64(define.StartRequestTimeField)) / 1e6,
func Send(ctx *gin.Context, code any, httpCode int, data any, responseOption *define.ResponseOption) {
if nil == responseOption {
responseOption = &define.ResponseOption{
ContentType: "application/json; charset=utf-8",
Extension: make(map[string]any),
}
}
// 记录响应数据
recordData, _ := json.Marshal(responseData)
if len(responseOption.ContentType) == 0 {
responseOption.ContentType = "application/json; charset=utf-8"
}
if len(responseOption.XmlName) == 0 {
responseOption.XmlName = "ResponseData"
}
// 设置请求是否成功的标识
ctx.Set(define.GetHttpHandleConfig().RequestIsSuccessField, fmt.Sprintf("%v", code) == fmt.Sprintf("%v", successBusinessCode))
if ctx.GetBool(hasSendResponseFlag) {
// 已经发送过数据, 后面在发送数据, 不执行
return
}
// 设置数据已发送的标识
defer ctx.Set(hasSendResponseFlag, true)
finishRequestTime := time.Now()
responseData := BuildResponseData(ctx, finishRequestTime, code, data, responseOption.Extension)
// 记录完成时间
ctx.Set(define.FinishRequestTimeField, finishRequestTime)
ctx.Set(define.RecordResponseDataField, string(recordData))
responseException := exception.New(code, httpCode, responseData)
ctx.JSON(responseException.GetHttpCode(), responseException.GetData())
responseConfig := define.GetHttpHandleConfig()
ctx.Set(responseConfig.FinishRequestTimeField, finishRequestTime.UnixMilli())
ctx.Set(responseConfig.ResponseDataField, responseData)
responseException := exception.New(code, responseData)
responseContentType := getResponseDataType(responseOption.ContentType)
responseStr := serialize.JSON.MarshalForStringIgnoreError(responseException.Data())
if responseInstance, err := wrapper.NewJson(responseStr, &wrapper.Option{XmlName: responseOption.XmlName}); nil != err {
// 接口处理成功, 数据序列化出现异常
panic(err.Error()) // 直接panic, 交给gin处理
} else {
finalResponseData, _ := responseInstance.Marshal(responseContentType)
ctx.Data(http.StatusOK, responseOption.ContentType, finalResponseData)
}
}
// getResponseDataType 获取相应数据类型
func getResponseDataType(contentType string) string {
if contentType == "" {
return "json"
}
applicationInfo := strings.Split(contentType, ";")[0]
applicationInfoArr := strings.Split(applicationInfo, "/")
return applicationInfoArr[len(applicationInfoArr)-1]
}
// BuildResponseData 构建响应数据
func BuildResponseData(ctx *gin.Context, finishTime time.Time, code any, data any, extension map[string]any) map[string]any {
responseConfig := define.GetHttpHandleConfig()
responseData := map[string]any{
responseConfig.ResponseCodeField: code,
responseConfig.ResponseMessageField: exception.GetMessage(code),
responseConfig.ResponseTraceIDField: ctx.GetString(responseConfig.ResponseTraceIDField),
responseConfig.ResponseDataField: data,
responseConfig.HandleRequestCostField: finishTime.UnixMilli() - ctx.GetInt64(responseConfig.StartRequestTimeField),
}
if responseConfig.EnableExtensionOutput && nil != extension {
responseData[responseConfig.ExtensionOutputField] = extension
}
return responseData
}
// SendWithStatusOK ...
@ -56,8 +134,8 @@ func Send(ctx *gin.Context, code interface{}, httpCode int, data interface{}) {
// Author : go_developer@163.com<白茶清欢>
//
// Date : 22:41 2022/6/25
func SendWithStatusOK(ctx *gin.Context, code interface{}, data map[string]interface{}) {
Send(ctx, code, http.StatusOK, data)
func SendWithStatusOK(ctx *gin.Context, code any, data any) {
Send(ctx, code, http.StatusOK, data, nil)
}
// SendWithException 使用exception发送数据
@ -65,13 +143,30 @@ func SendWithStatusOK(ctx *gin.Context, code interface{}, data map[string]interf
// Author : go_developer@163.com<白茶清欢>
//
// Date : 13:08 2022/6/26
func SendWithException(ctx *gin.Context, e exception.IException, data interface{}) {
func SendWithException(ctx *gin.Context, e exception.IException, responseOption *define.ResponseOption) {
if nil == e {
e = exception.NewSuccess(data)
e = exception.NewSuccess(map[string]any{})
}
if nil != e.GetData() {
Send(ctx, e.GetCode(), e.GetHttpCode(), e.GetData())
} else {
Send(ctx, e.GetCode(), e.GetHttpCode(), data)
if !define.GetHttpHandleConfig().DisableDebugStackOutput && nil != e {
stack := e.GetStack()
if len(stack) > 0 {
fmt.Println(stack)
}
}
Send(ctx, e.Code(), http.StatusOK, e.Data(), responseOption)
}
// JSON ctx.JSON 的平替, 增加了数据是否已相应的标识
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 14:51 2023/2/15
func JSON(ctx *gin.Context, httpCode int, data any) {
if ctx.GetBool(hasSendResponseFlag) {
// 已经发送过数据, 后面在发送数据, 不执行
return
}
// 设置数据已发送的标识
defer ctx.Set(hasSendResponseFlag, true)
ctx.JSON(httpCode, data)
}

110
router/README.md Normal file
View File

@ -0,0 +1,110 @@
# 路由包使用说明
## 基础说明
- 基于 [web框架GIN](https://github.com/gin-gonic/gin) 的二次封装
## 为什么要二次封装
二次封装的想法来源于 [web框架GoFrame](https://github.com/gogf/gf)。gf框架本身内置集成了大量组件, 对于轻量应用开发, 相比较而言比较笨重, 但是启根据请求数据结构、响应数据结构自动生成表单的能力十分实用。
在采用gin开发时一般接口文档有两种选择 :
- 在内部文档平台手搓文档, 并人工维护, 此方案最终演变方式大概率为文档主键滞后,与接口实现并不一致
- 利用golang的文档生成工具[swag]() 自动生成swagger文档, 此种方案需要在代码中引入大量swag工具解析时需要的注释, 且当输入/输出发生变化时需要维护相关注释
基于以上问题, 对gin的路由注册进行二次封装
## 二次封装解决哪些问题
- 文档维护的繁琐性 : 会通过反射自动基于 **`请求的数据结构 + 返回的数据结构`** , 生成接口文档, 专注于数据结构设计即可, 无需关注文档相关内容, 程序接管, 自动生成
- 做接口设计的统一性规范约束, 如 :
- 入参数据类型不能为 any
- 入参类型不能为 map , 但是特定字段特定场景, 可以强行禁用此规则等
- 返回值必须为结构体等
## 设计方案
### 【可选】路由组定义
路由组可以通过函数进行定义, 函数名必须需为 : **`RouterPrefix`** , 函数无任何参数, 返回路由组名称, 示例 :
```go
type TestController struct{}
func (t *TestController) RouterPrefix() string {
return "/uri/prefix"
}
```
### 【可选】路由组中间件定义
路由组中间件可以通过函数进行定义, 函数名必须需为 : **`RouterMiddleware`** , 函数无任何参数, 返回路由组中间件列表 **`[]gin.HandlerFunc`**, 示例 :
```go
type TestController struct{}
func (t *TestController) RouterPrefix() string {
return "/uri/prefix"
}
func (t *TestController) RouterMiddleware() []gin.HandlerFunc {
return []gin.HandlerFunc{
func(ctx *gin.Context) {
},
}
}
```
### 接口逻辑处理定义
接口逻辑处理函数, 函数名称自定义, 必须是个 **`可导出函数`** , 函数接收两个参数, 分别是 :
- *gin.Context : gin框架的上下文信息
- any: 表单参数 **结构体指针** , 注意 : 类型必须为结构体指针
函数返回值有两个 :
- any : 返回的业务数据, 必须是个 **`结构体指针`**
- error : 可以是内置的 **error** , 也可以是 **exception.IException** , 建议使用 exception.IException , 可承载更多的异常信息
## 使用方式
```go
type TestController struct{}
func (t *TestController) RouterPrefix() string {
return "/uri/prefix"
}
func (t *TestController) RouterMiddleware() []gin.HandlerFunc {
return []gin.HandlerFunc{
func(ctx *gin.Context) {
},
}
}
func (t *TestController) Uri(ctx *gin.Context, formData *TestForm) (any, error) {
return formData, nil
}
type TestForm struct {
Meta `tag:"测试表单" path:"/a/b/c/d" desc:"测试接口" method:"get" strict:"true"`
Age int `json:"age" form:"age"`
Name string `json:"name" form:"name"`
Test *Test `json:"test" form:"test"`
Num *int64 `json:"num" form:"num"`
}
type Test struct {
L string `json:"l"`
}
func Test_parseController(t *testing.T) {
type args struct {
controller any
}
Register(8080, &TestController{})
}
```
注意事项:
- Register 方法第一个入参是监听的端口
- 后面若干个controller实例, 朱一必须以指针的方式传入

25
router/config.go Normal file
View File

@ -0,0 +1,25 @@
// Package router ...
//
// Description : router ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-02-07 17:41
package router
import "strings"
var defaultValidateErrTag = "err"
// SetValidateErrTag 设置验证失败时, 获取错误信息的tag字段
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 17:42 2025/2/7
func SetValidateErrTag(tagName string) {
tagName = strings.TrimSpace(tagName)
if tagName == "" {
return
}
defaultValidateErrTag = tagName
}

152
router/controller.go Normal file
View File

@ -0,0 +1,152 @@
// Package router ...
//
// Description : router ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-01-27 15:29
package router
import (
"reflect"
"strings"
)
// controller 解析controller有哪些方法要注册为接口
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 15:30 2025/1/27
type controller struct {
}
// Parse 执行解析
//
// 符合注册为借口的方法具有如下特征:
//
// 1. 函数接受两个入参, 第一个参数为 gin.Context , 第二个参数为 任意结构体指针, 但是必须声明 Meta 相关信息, 否则会报错
//
// 2. 函数有两个返回值, 第一个返回值为任意结构体/结构体指针(限制死不能为map/slice, 方便后续统一标准化) , 第二个返回值为 error , 代表处理的异常, 会自动适配 exception.IException 类型
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 15:31 2025/1/27
func (c controller) Parse(inputController any) map[string]UriConfig {
parseRes := make(map[string]UriConfig)
if nil == inputController {
return parseRes
}
controllerType := reflect.TypeOf(inputController)
inputController = reflect.New(controllerType).Interface()
controllerType = reflect.TypeOf(inputController)
controllerValue := reflect.ValueOf(inputController)
for methodIdx := 0; methodIdx < controllerType.NumMethod(); methodIdx++ {
uriCfg, needRegister := c.methodConfig(controllerType.Method(methodIdx))
if !needRegister {
continue
}
uriCfg.ApiStructValue = controllerValue
parseRes[uriCfg.Path] = uriCfg
}
return parseRes
}
// methodConfig 解析方法配置, 要求函数格式, 两个参数, 两个返回值, 格式 : func(ctx *gin.Context, formData anyStruct[组合Meta]) (anyStruct|map[response], error)
//
// 参数 : 方法反射结果
//
// 返回值 : 第一个 -> 解析出的接口配置 第二个 -> 是否要注册为接口
//
// 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: 函数声明
// num1: 第一个参数
// num2: 第二个参数
if methodType.NumIn() != 3 {
needRegister = false
return
}
// 第一个参数必须是 *gin.Context 或者 *define.Context
paramOne := methodType.In(1).String()
if paramOne != GinContextType && paramOne != CustomContextType {
needRegister = false
return
}
// 解析第二个参数是组合Meta的form表单
formType := methodType.In(2)
cfg.FormDataType = formType
if formType.Kind() == reflect.Ptr {
formType = methodType.In(2).Elem()
}
metaField, metaFieldExist := formType.FieldByName(FieldNameMeta)
if !metaFieldExist {
needRegister = false
return
}
cfg.ResultDataType = methodType.Out(0)
if cfg.ResultDataType == nil {
}
if methodType.Out(1).Kind().String() != ErrorType {
// 判断是否是实现 error接口的方法
outputErrParse := false
for j := 0; j < methodType.Out(1).NumMethod(); j++ {
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() {
outputErrParse = true
break
}
}
if !outputErrParse {
needRegister = false
return
}
}
// 解析meta信息
cfg.CtxType = paramOne
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)
cfg.OutputStrict = outputStrictModel == "1" || outputStrictModel == "true"
if cfg.OutputStrict {
// 开启输出严格模式校验
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
}
}
// 解析参数配置
//cfg.ParamList = c.parseParamConfig(formType)
cfg.ApiLogicFunc = reflectMethod
needRegister = true
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
}
}
return res
}

69
router/define.go Normal file
View File

@ -0,0 +1,69 @@
// Package router ...
//
// Description : router ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2024-07-20 22:57
package router
import (
"reflect"
)
const (
PrefixFuncName = "RouterPrefix" // 路由前缀函数名称
MiddlewareFuncName = "RouterMiddleware" // 路由中间件函数名称
GinContextType = "*gin.Context" // gin context 类型名称
CustomContextType = "*define.Context" // custom context 类型名称
ErrorType = "error" // error类型
ErrorInterfaceFuncName = "Error" // error接口需要实现的方法名称
)
const (
TagNamePath = "path" // 接口的请求路径
TagNameMethod = "method" // 接口的请求方法
TagNameUriTag = "tag" // 接口的tag
TagNameDesc = "desc" // 接口的描述
TagNameOutputStrict = "output_strict" // 接口数据是否为严格模式 : 严格模式, 响应数据必须是结构体/map非严格模式返回任意值
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"` // 接口请求方法, 必须配置
TagList []string `json:"tag_list"` // 接口分组
Desc string `json:"desc"` // 接口描述
OutputStrict bool `json:"output_strict"` // 接口是否为严格模式 : 不配置,可返回任意类型, 配置, 必须返回结构体或者map
CtxType string `json:"ctx_type"` // ctx参数类型
FormDataType reflect.Type `json:"-"` // 表单数据类型
ResultDataType reflect.Type `json:"-"` // 返回值数据类型
ApiStructValue reflect.Value `json:"-"` // 逻辑函数所属结构体取值
ApiLogicFunc reflect.Method `json:"-"` // 自定义的接口逻辑
}
// UriParam 接口参数配置
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 15:40 2025/1/27
type UriParam struct {
Field string `json:"field"` // 结构体字段
Name string `json:"name"` // 参数名称
Type string `json:"type"` // 参数类型
Validate string `json:"validate"` // 验证规则: validator/v10 库
ErrorMsg string `json:"error_msg"` // 验证失败的错误信息
DisableAutoType bool `json:"disable_auto_type"` // 禁用自动类型转换
Sort string `json:"sort"` // 参数读取顺序: 默认 POST : body > query > path GET : query > path > body
}
const (
FieldNameMeta = "Meta" // 元信息字段
)

43
router/doc.go Normal file
View File

@ -0,0 +1,43 @@
// Package router ...
//
// Description : router ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-02-14 21:48
package router
import (
"reflect"
apiDoc "git.zhangdeman.cn/gateway/api-doc"
"git.zhangdeman.cn/gateway/api-doc/define"
)
func NewDoc(info *define.Info, servers []*define.ServerItem) *Doc {
return &Doc{
instance: apiDoc.NewOpenapiDoc(info, servers),
}
}
type Doc struct {
instance *apiDoc.Generate
}
// Add 增加接口文档测试
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 21:55 2025/2/14
func (d *Doc) Add(routePrefix string, paramType reflect.Type, resultType reflect.Type) {
_ = d.instance.AddApiFromInAndOut(routePrefix, paramType, resultType)
}
// Data 文档数据
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 21:59 2025/2/14
func (d *Doc) Data() *define.OpenapiDoc {
return d.instance.Doc()
}

120
router/handler.go Normal file
View File

@ -0,0 +1,120 @@
// Package router ...
//
// Description : router ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-01-27 19:42
package router
import (
"errors"
"reflect"
"sync"
"git.zhangdeman.cn/zhangdeman/exception"
"git.zhangdeman.cn/zhangdeman/gin/define"
"git.zhangdeman.cn/zhangdeman/gin/request"
"git.zhangdeman.cn/zhangdeman/gin/response"
"github.com/gin-gonic/gin"
)
// RequestHandler 获取请求处理方法
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 19:44 2025/1/27
func RequestHandler(uriCfg UriConfig) gin.HandlerFunc {
return func(ctx *gin.Context) {
var (
err error
ok bool
e exception.IException
)
var formParam reflect.Value
if uriCfg.FormDataType.Kind() == reflect.Ptr {
formParam = reflect.New(uriCfg.FormDataType.Elem())
} else {
// 表单解析
formParam = reflect.New(uriCfg.FormDataType)
}
formValue := formParam.Interface()
if err = request.Form.Parse(ctx, formValue); nil != err {
// 格式化验证错误的信息
err = GetValidateErr(formValue, err)
e = exception.NewFromError(400, err)
response.SendWithException(ctx, e, &define.ResponseOption{
ContentType: "application/json;charset=utf-8",
})
ctx.Abort()
return
}
isSuccess := false
// 初始化响应之后logic
logicAfterResponse := &define.LogicAfterResponse{
SuccessHookFuncList: make([]func(), 0),
FailureHookFuncList: make([]func(), 0),
Lock: &sync.RWMutex{},
}
// 此处暴露出去,是为了使用方可以获取到对应数据
ctx.Set(define.LogicAfterResponseKey, logicAfterResponse)
defer func() {
go func() {
// 执行响应之后的相关逻辑
defer func() {
if r := recover(); r != nil {
}
}()
if isSuccess {
for _, itemFunc := range logicAfterResponse.SuccessHookFuncList {
if nil != itemFunc {
itemFunc()
}
}
} else {
for _, itemFunc := range logicAfterResponse.FailureHookFuncList {
if nil != itemFunc {
itemFunc()
}
}
}
}()
}()
// 执行逻辑
inputValue := reflect.ValueOf(formValue)
if uriCfg.FormDataType.Kind() != reflect.Ptr {
inputValue = inputValue.Elem()
}
var firstParam reflect.Value
if uriCfg.CtxType == CustomContextType {
customCtx := ctx.MustGet(define.CustomContextKey)
firstParam = reflect.ValueOf(customCtx)
} else {
firstParam = reflect.ValueOf(ctx)
}
resList := uriCfg.ApiLogicFunc.Func.Call([]reflect.Value{uriCfg.ApiStructValue, firstParam, inputValue})
if resList[1].IsNil() {
// 请求成功
isSuccess = true
response.SuccessWithExtension(ctx, resList[0].Interface(), &define.ResponseOption{ContentType: "application/json;charset=utf-8"})
return
}
// 请求失败
if ok = errors.As(resList[1].Interface().(error), &e); ok {
// 本身就是exception.IException
} else if err, ok = resList[1].Interface().(error); ok {
e = exception.NewFromError(-1, err)
} else {
e = exception.NewWithCodeAndData(-1, map[string]any{
"err": resList[1].Interface(),
})
}
response.SendWithException(ctx, e, &define.ResponseOption{
ContentType: "application/json;charset=utf-8",
})
return
}
}

19
router/meta.go Normal file
View File

@ -0,0 +1,19 @@
// Package router ...
//
// Description : router ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2024-07-20 21:40
package router
// Meta 接口的元信息, 主要包含如下信息:
//
// uri: 接口路由(不包含group前缀)
//
// method: 请求方法: get/post 等
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 21:40 2024/7/20
type Meta struct{}

151
router/option.go Normal file
View File

@ -0,0 +1,151 @@
// Package router ...
//
// Description : router ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-02-18 17:26
package router
import (
"strings"
apiDocDefine "git.zhangdeman.cn/gateway/api-doc/define"
"git.zhangdeman.cn/zhangdeman/gin/middleware"
"github.com/gin-gonic/gin"
)
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
globalMiddlewareList []gin.HandlerFunc // 全局中间件列表
disableSwaggerDoc bool // 禁用swagger文档, 特定环境不想展示文档, 可通过次方式禁用
serverInfo *apiDocDefine.Info // 服务器信息
serverList []*apiDocDefine.ServerItem // 服务器环境列表
enablePprof bool // 启用pprof
enableCors bool // 启动跨域支持
loggerCfg *middleware.AccessConfig // 日志配置
}
// 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)
if len(uiTheme) == 0 {
return
}
so.swaggerUiTheme = uiTheme
}
}
// 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
}
}
// 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)
if len(baseUri) == 0 {
return
}
baseUri = "/" + strings.TrimLeft(baseUri, "/")
so.swaggerBaseUri = baseUri
}
}
// WithDisableSwaggerDoc 禁用swagger文档
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 17:35 2025/2/18
func WithDisableSwaggerDoc() SetServerOptionFunc {
return func(so *serverOption) {
so.disableSwaggerDoc = true
}
}
// 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 {
return
}
so.serverInfo = serverInfo
}
}
// 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 {
return
}
so.serverList = serverList
}
}
// WithPprofEnable 启用pprof
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 15:10 2025/2/21
func WithPprofEnable() SetServerOptionFunc {
return func(so *serverOption) {
so.enablePprof = true
}
}
// WithEnableCors 启用全局跨域
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 14:56 2025/2/22
func WithEnableCors() SetServerOptionFunc {
return func(so *serverOption) {
so.enableCors = true
}
}
// 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 {
so.loggerCfg = loggerCfg
}
}
}

218
router/server.go Normal file
View File

@ -0,0 +1,218 @@
// Package router ...
//
// Description : router ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-02-07 18:19
package router
import (
"fmt"
"git.zhangdeman.cn/zhangdeman/graceful"
"net/http"
"strings"
"git.zhangdeman.cn/zhangdeman/gin/define"
apiDoc "git.zhangdeman.cn/gateway/api-doc"
"git.zhangdeman.cn/zhangdeman/consts"
"git.zhangdeman.cn/zhangdeman/gin/middleware"
"git.zhangdeman.cn/zhangdeman/gin/middleware/request_cors"
"github.com/gin-contrib/pprof"
apiDocDefine "git.zhangdeman.cn/gateway/api-doc/define"
"github.com/gin-gonic/gin"
)
func newServerOption(port int, optionList ...SetServerOptionFunc) *serverOption {
option := &serverOption{
swaggerUiTheme: apiDocDefine.SwaggerUIThemeRedocFree,
swaggerBaseUri: "/doc/swagger",
globalMiddlewareList: nil,
disableSwaggerDoc: false,
serverInfo: &apiDocDefine.Info{
Description: "这是一个微服务,提供一些必要的的数据接口功能",
Title: "微服务接口文档",
TermsOfService: "",
Contact: &apiDocDefine.Contact{
Name: "开发人员",
Url: "",
Email: "developer@example.com",
},
License: &apiDocDefine.License{
Name: consts.LicenseApache20,
Url: consts.LicenseUrlTable[consts.LicenseApache20],
},
Version: "0.0.1",
},
serverList: []*apiDocDefine.ServerItem{
{
Url: fmt.Sprintf("http://127.0.0.1:%d", port),
Description: "测试服务器",
Variables: nil,
},
},
}
for _, opt := range optionList {
if nil == opt {
continue
}
opt(option)
}
return option
}
// NewServer server实例
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 18:20 2025/2/7
func NewServer(port int, optionList ...SetServerOptionFunc) *server {
if port < 80 {
panic("port should be greater than 80")
}
option := newServerOption(port, optionList...)
globalMiddlewareList := make([]gin.HandlerFunc, 0)
// CustomContext 必须在第一个, 并且进行初始化
globalMiddlewareList = append(
globalMiddlewareList,
func(ctx *gin.Context) {
// 初始化上下文以及基础信息
_ = define.NewContext(ctx)
},
)
if nil != option.loggerCfg {
// 请求日志记录中间件
globalMiddlewareList = append(globalMiddlewareList, middleware.LogRequest(option.loggerCfg))
}
if option.enableCors {
// 跨域中间件
globalMiddlewareList = append(globalMiddlewareList, request_cors.New(request_cors.Config{
AllowAllOrigins: true,
AllowOrigins: nil,
AllowOriginFunc: nil,
AllowMethods: []string{"*"},
AllowHeaders: []string{"*"},
AllowCredentials: true,
ExposeHeaders: nil,
MaxAge: 0,
AllowWildcard: true,
AllowBrowserExtensions: true,
AllowWebSockets: true,
AllowFiles: true,
}))
}
if len(option.globalMiddlewareList) > 0 {
// 自定义全局中间件追加
globalMiddlewareList = append(globalMiddlewareList, option.globalMiddlewareList...)
}
r := gin.Default()
// 注册全局中间件
r.Use(globalMiddlewareList...)
// 启用pprof, 注册相关路由
if option.enablePprof {
pprof.Register(r)
}
return &server{
router: r,
uiInstance: apiDoc.NewSwaggerUI(option.serverInfo, option.serverList, option.swaggerUiTheme),
port: port,
option: option,
}
}
type server struct {
router *gin.Engine
port int
uiInstance *apiDoc.SwaggerUI
option *serverOption
}
// Start 启动服务
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 18:31 2025/2/7
func (s *server) Start() {
// 注册文档
s.uiInstance.RegisterHandler(s.router, s.option.swaggerBaseUri)
gracefulServer := graceful.NewServer(fmt.Sprintf(":%d", s.port), s.Router())
if err := gracefulServer.ListenAndServe(); err != nil {
if strings.Contains(err.Error(), "use of closed network connection") {
fmt.Println("接收到退出指令, 服务平滑关闭")
return
}
panic("服务启动监听失败" + err.Error())
}
}
// 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
}
f(s.router)
}
// 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...)
cParser := controller{}
for _, c := range cList {
urlTable := cParser.Parse(c)
for _, itemUriCfg := range urlTable {
_ = s.uiInstance.DocInstance().AddApiFromInAndOut(routerPrefix, itemUriCfg.FormDataType, itemUriCfg.ResultDataType)
method := strings.ToUpper(itemUriCfg.RequestMethod)
switch method {
case http.MethodGet:
g.GET(itemUriCfg.Path, RequestHandler(itemUriCfg))
case http.MethodHead:
g.HEAD(itemUriCfg.Path, RequestHandler(itemUriCfg))
case http.MethodPost:
g.POST(itemUriCfg.Path, RequestHandler(itemUriCfg))
case http.MethodPut:
g.PUT(itemUriCfg.Path, RequestHandler(itemUriCfg))
case http.MethodPatch:
g.PATCH(itemUriCfg.Path, RequestHandler(itemUriCfg))
case http.MethodDelete:
g.DELETE(itemUriCfg.Path, RequestHandler(itemUriCfg))
case http.MethodOptions:
g.OPTIONS(itemUriCfg.Path, RequestHandler(itemUriCfg))
case http.MethodTrace:
panic(`method Trace is not supported`)
default:
panic("method " + itemUriCfg.RequestMethod + " is not support")
}
}
}
}

19
router/server_test.go Normal file
View File

@ -0,0 +1,19 @@
// Package router ...
//
// Description : router ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-05-28 20:58
package router
import (
"github.com/gin-gonic/gin"
"testing"
)
func TestNewServer(t *testing.T) {
s := NewServer(9087)
s.Router().GET("/ping", func(c *gin.Context) {})
s.Start()
}

55
router/validator.go Normal file
View File

@ -0,0 +1,55 @@
// Package router ...
//
// Description : router ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-02-07 17:36
package router
import (
"errors"
"fmt"
"github.com/go-playground/validator/v10"
"reflect"
"strings"
)
// GetValidateErr 格式化错误信息
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 22:19 2025/1/15
func GetValidateErr(obj any, rawErr error) error {
if nil == rawErr {
return nil
}
if nil == obj {
return rawErr
}
var (
ok bool
validationErrs validator.ValidationErrors
errString []string
field reflect.StructField
)
if ok = errors.As(rawErr, &validationErrs); !ok {
return rawErr
}
objType := reflect.TypeOf(obj)
if objType.Kind() == reflect.Ptr {
objType = objType.Elem()
}
for _, validationErr := range validationErrs {
if field, ok = objType.FieldByName(validationErr.Field()); ok {
if e := field.Tag.Get(defaultValidateErrTag); e != "" {
errString = append(errString, fmt.Sprintf("%s: %s", field.Tag.Get("json"), e))
continue
} else {
errString = append(errString, fmt.Sprintf("%s: %v", field.Tag.Get("json"), validationErr.Value()))
}
}
errString = append(errString, validationErr.Error())
}
return errors.New(strings.Join(errString, "\n"))
}