12 Commits

12 changed files with 74 additions and 66 deletions

6
go.mod
View File

@@ -5,7 +5,7 @@ go 1.24.1
toolchain go1.24.2
require (
git.zhangdeman.cn/zhangdeman/api-doc v1.0.3-0.20260107152122-a877079712ee
git.zhangdeman.cn/zhangdeman/api-doc v1.0.3-0.20260109145855-7244d2bc19c9
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260108043513-c4b7f3ce16e8
git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20251207071238-9aa24be3d708
git.zhangdeman.cn/zhangdeman/exception v0.0.0-20250510123912-a0d52fc093ab
@@ -61,7 +61,7 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-redis/redis_rate/v10 v10.0.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.1 // indirect
github.com/goccy/go-yaml v1.19.2 // 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
@@ -103,7 +103,7 @@ require (
golang.org/x/mod v0.31.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.40.0 // indirect

12
go.sum
View File

@@ -1,9 +1,7 @@
git.zhangdeman.cn/zhangdeman/api-doc v1.0.3-0.20260107152122-a877079712ee h1:cUrlM+87fQW8StLbGBA8EB+Ke3ZR1QslWyZqbXyD5C0=
git.zhangdeman.cn/zhangdeman/api-doc v1.0.3-0.20260107152122-a877079712ee/go.mod h1:EO06OJ2rIS+s3CaXoGiX6yTuyhKFeSrgHukOY7FofHw=
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260108031051-bf46e2083c84 h1:1+erCrsFvz5QuHKdraVuH7KmokUoqJULaKpWXMT6m3Y=
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260108031051-bf46e2083c84/go.mod h1:5p8CEKGBxi7qPtTXDI3HDmqKAfIm5i/aBWdrbkbdNjc=
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260108040723-8d5895324da5 h1:v02Z5/rLXxRUGMZLBPgOnL0hHN36bbH1tGTC+40pF1E=
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260108040723-8d5895324da5/go.mod h1:5p8CEKGBxi7qPtTXDI3HDmqKAfIm5i/aBWdrbkbdNjc=
git.zhangdeman.cn/zhangdeman/api-doc v1.0.3-0.20260109145855-7244d2bc19c9 h1:CWkIVo2z9SHWAq8lWRrjiNADff+SEaOLAsRqWFov8yk=
git.zhangdeman.cn/zhangdeman/api-doc v1.0.3-0.20260109145855-7244d2bc19c9/go.mod h1:O7ztYxBFP+hPZgOXk0pxMVdAewBEk/PmWuhgCN21Bns=
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260108043513-c4b7f3ce16e8 h1:mXZD40M+EDWb4HiDQ5plqj28BjNwc7MCNvLUl1fPAQo=
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260108043513-c4b7f3ce16e8/go.mod h1:5p8CEKGBxi7qPtTXDI3HDmqKAfIm5i/aBWdrbkbdNjc=
git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20251207071238-9aa24be3d708 h1:yVr38JAgPwS/6JeYdHLDa8PXU3fTa2dnENL1JGdu3ns=
@@ -120,8 +118,8 @@ github.com/go-webtools/knife4go v1.0.4 h1:p32SApmM0sx2/Y5p0QfeaGv5KD96R1mj2CaHdy
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/goccy/go-yaml v1.19.1 h1:3rG3+v8pkhRqoQ/88NYNMHYVGYztCOCIZ7UQhu7H+NE=
github.com/goccy/go-yaml v1.19.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
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=
@@ -280,6 +278,8 @@ 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.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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=

View File

@@ -114,10 +114,12 @@ func LogRequest(cfg *AccessConfig) gin.HandlerFunc {
// AccessConfig 访问记录的配置
type AccessConfig struct {
Logger *zap.Logger // 日志实例
RequestHeaderList []string // 要记录哪些header , 不传全部记录
ResponseHeaderList []string // 要记录哪些响应header, 不传全部记录
IsRecordLog func(ctx *gin.Context) bool // 验证当前请求是否记录日志
Logger *zap.Logger // 日志实例
RequestHeaderList []string // 要记录哪些header , 不传全部记录
IgnoreRequestHeaderList []string // 忽略不记录的 header
ResponseHeaderList []string // 要记录哪些响应header, 不传全部记录
IgnoreResponseHeaderList []string // 忽略不记录的响应 header
IsRecordLog func(ctx *gin.Context) bool // 验证当前请求是否记录日志
}
// defaultIsRecordLog 默认仅记录 json api 日志

View File

@@ -21,19 +21,19 @@ import (
type GetCommonParam func(ctx *gin.Context) (any, error)
// AddCommonParamRule 添加公共参数注入规则
func (s *server) AddCommonParamRule(fieldName string, getParamFunc GetCommonParam) {
func (s *Server) AddCommonParamRule(fieldName string, getParamFunc GetCommonParam) {
s.commonParam[fieldName] = getParamFunc
}
// AddCommonParamRules 批量添加公共参数注入规则
func (s *server) AddCommonParamRules(rules map[string]GetCommonParam) {
func (s *Server) AddCommonParamRules(rules map[string]GetCommonParam) {
for fieldName, rule := range rules {
s.AddCommonParamRule(fieldName, rule)
}
}
// injectCommonParam 注入公共参数
func (s *server) injectCommonParam(ctx *gin.Context, formValue any) error {
func (s *Server) injectCommonParam(ctx *gin.Context, formValue any) error {
innerCtx := util.GinCtxToContext(ctx)
var (
val any

View File

@@ -141,7 +141,10 @@ func (c controllerParser) setUriMeta(metaField reflect.StructField, cfg *define.
cfg.Path = metaField.Tag.Get(TagNamePath) // 接口路由
cfg.RequestMethod = strings.Split(strings.ToUpper(metaField.Tag.Get(TagNameMethod)), ",") // 请求方法
cfg.Desc = metaField.Tag.Get(TagNameDesc) // 接口描述
cfg.TagList = strings.Split(metaField.Tag.Get(TagNameUriTag), ",") // 接口标签
if len(cfg.Desc) == 0 {
cfg.Desc = metaField.Tag.Get(TagNameSummary)
}
cfg.TagList = strings.Split(metaField.Tag.Get(TagNameUriTag), ",") // 接口标签
// 以下是bool类型的配置解析
var boolMetaParse = func(tagName string) bool {
val := strings.ToLower(metaField.Tag.Get(tagName))

View File

@@ -29,6 +29,7 @@ const (
TagNameNoLogin = "no-login" // 接口是否需要登录(无需登录, 则有token就验证, 无token不验证)
TagNameMaxExecTime = "max-exec-time" // 接口最大执行时间, 单位: s, 配置为0则不验证
TagNameRateLimit = "rate-limit" // 接口限流
TagNameSummary = "summary" // 接口摘要
TagNameBinding = "binding" // gin 内置的验证规则tag
TagNameValidate = "validate" // validator v10 默认的验证规则tag
TagNameErrMsg = "err" // 验证失败错误信息 tag

View File

@@ -1,17 +0,0 @@
// Package router ...
//
// Description : router ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-02-14 21:48
package router
import (
"git.zhangdeman.cn/zhangdeman/api-doc/openapi"
"github.com/getkin/kin-openapi/openapi3"
)
func NewOpenApiDoc(docFlag string, docOption ...openapi.OptionFunc) *openapi3.T {
return openapi.DocManager.NewOpenApiDoc(docFlag, docOption...)
}

View File

@@ -29,7 +29,7 @@ import (
"github.com/mcuadros/go-defaults"
)
func (s *server) getFormInitValue(ctx *gin.Context, uriCfg define.UriConfig) (any, error) {
func (s *Server) getFormInitValue(ctx *gin.Context, uriCfg define.UriConfig) (any, error) {
var (
formParam reflect.Value
formValue any
@@ -58,7 +58,7 @@ func (s *server) getFormInitValue(ctx *gin.Context, uriCfg define.UriConfig) (an
return formValue, nil
}
func (s *server) initRequest(ctx *gin.Context, uriCfg define.UriConfig) (any, reflect.Value, exception.IException) {
func (s *Server) initRequest(ctx *gin.Context, uriCfg define.UriConfig) (any, reflect.Value, exception.IException) {
var (
err error
formValue any
@@ -92,7 +92,7 @@ func (s *server) initRequest(ctx *gin.Context, uriCfg define.UriConfig) (any, re
}
// callFunc 调用方法
func (s *server) callFunc(ctx *gin.Context, uriCfg define.UriConfig, inputValue reflect.Value) (any, exception.IException) {
func (s *Server) callFunc(ctx *gin.Context, uriCfg define.UriConfig, inputValue reflect.Value) (any, exception.IException) {
var (
firstParam = reflect.ValueOf(ctx)
@@ -108,7 +108,7 @@ func (s *server) callFunc(ctx *gin.Context, uriCfg define.UriConfig, inputValue
}
// formatError 格式化错误
func (s *server) formatError(ctx *gin.Context, err any) exception.IException {
func (s *Server) formatError(ctx *gin.Context, err any) exception.IException {
if nil == err {
return nil
}
@@ -135,7 +135,7 @@ func (s *server) formatError(ctx *gin.Context, err any) exception.IException {
}
// RequestHandler 获取请求处理方法
func (s *server) RequestHandler(uriCfg define.UriConfig) gin.HandlerFunc {
func (s *Server) RequestHandler(uriCfg define.UriConfig) gin.HandlerFunc {
return func(ctx *gin.Context) {
defer s.hook(ctx, uriCfg)
var (
@@ -164,7 +164,7 @@ func (s *server) RequestHandler(uriCfg define.UriConfig) gin.HandlerFunc {
}
// SseHandler sse连接请求
func (s *server) SseHandler(uriCfg define.UriConfig) gin.HandlerFunc {
func (s *Server) SseHandler(uriCfg define.UriConfig) gin.HandlerFunc {
return func(ctx *gin.Context) {
var (
err error

View File

@@ -17,7 +17,7 @@ import (
)
// hook 执行hook逻辑
func (s *server) hook(ctx *gin.Context, uriCfg define.UriConfig) {
func (s *Server) hook(ctx *gin.Context, uriCfg define.UriConfig) {
var (
exists bool
isSuccess any
@@ -47,7 +47,7 @@ func (s *server) hook(ctx *gin.Context, uriCfg define.UriConfig) {
}
}
func (s *server) hookAfter(ctx *gin.Context, uriCfg define.UriConfig, hookInstance *define.LogicAfterResponse, success bool) {
func (s *Server) hookAfter(ctx *gin.Context, uriCfg define.UriConfig, hookInstance *define.LogicAfterResponse, success bool) {
innerContext := util.GinCtxToContext(ctx)
defer func() {
if err := recover(); err != nil {

View File

@@ -25,6 +25,17 @@ type serverOption struct {
loggerCfg *middleware.AccessConfig // 日志配置
initContextData gin.HandlerFunc // 初始化一些请求数据
rateLimitInstance abstract.IRateLimit // 服务流控实例
demoCheck gin.HandlerFunc // demo检查
}
// WithDemoCheck 设置demo检查
func WithDemoCheck(f gin.HandlerFunc) SetServerOptionFunc {
return func(so *serverOption) {
if nil == f {
return
}
so.demoCheck = f
}
}
// WithRateLimitInstance 设置流控实例, 配置为 nil, 代表禁用

View File

@@ -51,7 +51,7 @@ func newServerOption(port int, optionList ...SetServerOptionFunc) *serverOption
}
// NewServer server实例
func NewServer(port int, optionList ...SetServerOptionFunc) *server {
func NewServer(port int, optionList ...SetServerOptionFunc) *Server {
if port < 80 {
panic("port should be greater than 80")
}
@@ -78,7 +78,7 @@ func NewServer(port int, optionList ...SetServerOptionFunc) *server {
}
openapi.DocManager.NewOpenApiDoc(option.docConfig.Flag, optionFuncList...)
}
s := &server{
s := &Server{
router: gin.Default(),
port: port,
option: option,
@@ -99,7 +99,7 @@ func NewServer(port int, optionList ...SetServerOptionFunc) *server {
return s
}
type server struct {
type Server struct {
router *gin.Engine
port int
option *serverOption
@@ -111,7 +111,7 @@ type server struct {
uriTable map[string]define.UriConfig // uri配置表
}
func (s *server) getGlobalMiddlewareList(option *serverOption) {
func (s *Server) getGlobalMiddlewareList(option *serverOption) {
// 全局 panic 捕获
s.globalMiddlewareList = append(s.globalMiddlewareList, middleware.CustomRecover())
// 全局流控中间件
@@ -143,6 +143,10 @@ func (s *server) getGlobalMiddlewareList(option *serverOption) {
AllowFiles: true,
}))
}
// demo 检查
if nil != option.demoCheck {
s.globalMiddlewareList = append(s.globalMiddlewareList, option.demoCheck)
}
if len(option.globalMiddlewareList) > 0 {
// 自定义全局中间件追加
s.globalMiddlewareList = append(s.globalMiddlewareList, option.globalMiddlewareList...)
@@ -151,7 +155,7 @@ func (s *server) getGlobalMiddlewareList(option *serverOption) {
}
// RegisterDocHandler 注册文档路由
func (s *server) RegisterDocHandler() {
func (s *Server) RegisterDocHandler() {
if !s.option.docConfig.Enable {
return
}
@@ -188,7 +192,7 @@ func (s *server) RegisterDocHandler() {
}
// Start 启动服务
func (s *server) Start() {
func (s *Server) Start() {
// 注册文档
s.RegisterDocHandler()
// s.uiInstance.RegisterHandler(s.router, s.option.swaggerBaseUri)
@@ -207,17 +211,17 @@ func (s *server) Start() {
}
// Router 对外访问路由实例, 不建议直接用
func (s *server) Router() *gin.Engine {
func (s *Server) Router() *gin.Engine {
return s.router
}
// Handler404 注册404的处理方法
func (s *server) Handler404(f gin.HandlerFunc) {
func (s *Server) Handler404(f gin.HandlerFunc) {
s.router.NoRoute(f)
}
// SetCustomRouter 自定义路由处理
func (s *server) SetCustomRouter(f func(r *gin.Engine)) {
func (s *Server) SetCustomRouter(f func(r *gin.Engine)) {
if nil == f {
return
}
@@ -225,7 +229,7 @@ func (s *server) SetCustomRouter(f func(r *gin.Engine)) {
}
// Group 注册接口路由
func (s *server) Group(routerPrefix string, middlewareList []gin.HandlerFunc, controllerList ...any) {
func (s *Server) Group(routerPrefix string, middlewareList []gin.HandlerFunc, controllerList ...any) {
routerGroup := s.router.Group(routerPrefix)
routerGroup.Use(middlewareList...)
parser := controllerParser{}
@@ -259,13 +263,15 @@ func (s *server) Group(routerPrefix string, middlewareList []gin.HandlerFunc, co
routerPrefix = "/" + strings.TrimSuffix(strings.TrimPrefix(routerPrefix, "/"), "/")
}
fullUriPath := routerPrefix + "/" + strings.TrimPrefix(itemUriCfg.Path, "/")
// 注册接口文档
_ = openapi.DocManager.AddApiDoc(s.option.docConfig.Flag, apiDocDefine.UriConfig{
Path: fullUriPath,
RequestMethod: method,
TagList: itemUriCfg.TagList,
Desc: itemUriCfg.Desc,
}, itemUriCfg.FormDataType, itemUriCfg.ResultDataType)
if s.option.docConfig.Enable {
// 注册接口文档
_ = openapi.DocManager.AddApiDoc(s.option.docConfig.Flag, apiDocDefine.UriConfig{
Path: fullUriPath,
RequestMethod: method,
TagList: itemUriCfg.TagList,
Desc: itemUriCfg.Desc,
}, itemUriCfg.FormDataType, itemUriCfg.ResultDataType)
}
s.uriTable[fullUriPath] = itemUriCfg // 注册路由时存储 接口路径 => 接口配置的信息
s.consoleOutput = append(s.consoleOutput, []any{
fullUriPath,
@@ -279,7 +285,7 @@ func (s *server) Group(routerPrefix string, middlewareList []gin.HandlerFunc, co
}
// registerRouter 注册路由
func (s *server) registerRouter(routerGroup *gin.RouterGroup, method string, itemUriCfg define.UriConfig, handleFunc gin.HandlerFunc) {
func (s *Server) registerRouter(routerGroup *gin.RouterGroup, method string, itemUriCfg define.UriConfig, handleFunc gin.HandlerFunc) {
funcList := []gin.HandlerFunc{
middleware.Timeout(itemUriCfg.MaxExecTime), // 超时处理
handleFunc,
@@ -307,7 +313,7 @@ func (s *server) registerRouter(routerGroup *gin.RouterGroup, method string, ite
}
// getMiddlewareDescList 获取中间件路径的描述列表
func (s *server) getMiddlewareDescList(middlewareList []gin.HandlerFunc) []string {
func (s *Server) getMiddlewareDescList(middlewareList []gin.HandlerFunc) []string {
middlewareDescList := []string{}
for _, itemMiddleware := range middlewareList {
middlewareValue := reflect.ValueOf(itemMiddleware)
@@ -323,7 +329,7 @@ func (s *server) getMiddlewareDescList(middlewareList []gin.HandlerFunc) []strin
}
// TableOutput 表格输出
func (s *server) TableOutput(header []any, footer []any) {
func (s *Server) TableOutput(header []any, footer []any) {
// 帮助函数
anySlice2TableRow := func(input []any) table.Row {
tableRowData := make(table.Row, 0, len(input))

View File

@@ -9,8 +9,8 @@
<style>
.theme-selector {
position: fixed;
top: 20px;
right: 20px;
top: 70px;
right: 0px;
z-index: 100;
background: white;
padding: 10px;
@@ -25,6 +25,7 @@
</style>
</head>
<body>
<!--
<div class="theme-selector">
<label for="theme-select">选择主题: </label>
<select id="theme-select" onchange="changeTheme(this.value)">
@@ -44,6 +45,7 @@
<option value="2.x-theme-outline.css">2.x-theme-outline.css</option>
</select>
</div>
-->
<div id="swagger-ui"></div>
@@ -53,7 +55,7 @@
<script>
// 主题映射表
const themes = {
'theme-default': '',
'theme-default': '3.x/theme-material.css',
'3.x-theme-material': '3.x/theme-material.css',
'3.x-theme-monokai': '3.x/theme-monokai.css',
'3.x-theme-feeling-blue': '3.x/theme-feeling-blue.css',
@@ -87,7 +89,7 @@
window.onload = function() {
// 从本地存储恢复主题
const savedTheme = localStorage.getItem('swagger-theme') || 'theme-material';
document.getElementById('theme-select').value = savedTheme;
// document.getElementById('theme-select').value = savedTheme;
if (savedTheme !== 'theme-default') {
document.getElementById('swagger-theme').href = themes[savedTheme];