Router
go
const xxxLogTitle = "参数管理" // 操作日志模块名
xxx := protected.Group("/xxx")
xxx.GET("/list", satoken.CheckPermission("system:xxx:list"), handler.XxxApiApp.List)
xxx.GET("/configKey/:configKey", sagin.CheckLogin(), handler.XxxApiApp.GetByKey)
xxx.GET("/:xxxId", satoken.CheckPermission("system:xxx:query"), handler.XxxApiApp.GetInfo)
xxx.POST("/export", satoken.CheckPermission("system:xxx:export"),
oplog.Log(xxxLogTitle, enum.BusinessTypeExport), handler.XxxApiApp.Export)
xxx.POST("", satoken.CheckPermission("system:xxx:add"),
oplog.Log(xxxLogTitle, enum.BusinessTypeInsert),
repeatsubmit.RepeatSubmit(0, ""), handler.XxxApiApp.Add)
xxx.PUT("", satoken.CheckPermission("system:xxx:edit"),
oplog.Log(xxxLogTitle, enum.BusinessTypeUpdate),
repeatsubmit.RepeatSubmit(0, ""), handler.XxxApiApp.Edit)
xxx.DELETE("/refreshCache", satoken.CheckPermission("system:xxx:remove"),
oplog.Log(xxxLogTitle, enum.BusinessTypeClean), handler.XxxApiApp.RefreshCache)
xxx.DELETE("/:xxxIds", satoken.CheckPermission("system:xxx:remove"),
oplog.Log(xxxLogTitle, enum.BusinessTypeDelete), handler.XxxApiApp.Remove)中间件顺序:鉴权 → 日志 → 防重 → handler
顺序不是风格问题,改了行为就变:
- 鉴权最前:未授权请求不该白占一个防重锁。
- 日志在防重之前:被防重挡掉的请求 handler 没执行,但仍记一条失败日志。
repeatsubmit须在encrypt.ApiEncrypt()之后:指纹要用解密后的明文,否则密文每次随机密钥、同样入参算出不同指纹,防重直接失效。
详见 一次请求的流转。
路径细节
- 根路径用
""而非"/",后者会注册成/xxx/。 - 静态段(
/export、/refreshCache、/updateByKey)与同层通配段(/:id) 可以共存,gin 静态段优先,无需刻意调整注册顺序。但 值得写测试钉住——这条规则一旦变化,DELETE /config/refreshCache会被当成「删除主键为 refreshCache 的配置」而 静默走错分支。
注解速查
| Java | Go | 说明 |
|---|---|---|
@SaCheckPermission("system:xxx:list") | satoken.CheckPermission("system:xxx:list") | 多个权限码是 OR,语义是「任一命中放行」 |
@SaCheckRole("admin") | satoken.CheckRole("admin") | 同上 |
| 仅需登录(无权限注解) | sagin.CheckLogin() | 没挂 @SaCheckPermission 的接口用这个 |
| 完全公开 | sagin.Ignore() | 仅在路由位于带 plugin.TokenInterceptor() 的组内时才需要;注册在 protected 组之外(如 /ping)的公开路由什么标记都不用加 |
@Log(title="x", businessType=INSERT) | oplog.Log("x", enum.BusinessTypeInsert) | — |
@RepeatSubmit() | repeatsubmit.RepeatSubmit(0, "") | 0 = 默认 5s;< 1s 注册期 panic |
@RepeatSubmit(interval=10000) | repeatsubmit.RepeatSubmit(10*time.Second, "") | — |
@RateLimiter(time=60,count=10,limitType=IP) | ratelimiter.RateLimiter(time.Minute, 10, ratelimiter.LimitTypeIP, 0, "") | — |
@ApiEncrypt | encrypt.ApiEncrypt() | — |
BusinessType 取值即 Java ordinal()(库里存数字): Other=0, Insert=1, Update=2, Delete=3, Grant=4, Export=5, Import=6, Force=7, GenCode=8, Clean=9。 顺序不可调整、不可插入新值,只能末尾追加。
oplog.Log 可选项:WithoutRequestData()、WithoutResponseData()、WithExcludeParams("field")、 WithOperatorType(enum.OperatorTypeMobile)。密码类字段已由 constant.ExcludeProperties 全局排除,无需重复声明。
新增进程入口的必备初始化
顺序有依赖,照抄 cmd/standalone/main.go:
go
config.Load(...)
jsonx.Init() // 必须在首个 c.JSON / 参数绑定之前接管 gin codec
database.Init(); redis.Init(); satoken.Init(); encrypt.Init()
snowflake.Init() // 主键发号器,插入前必须就绪
captcha.Init(); ratelimiter.Init(); repeatsubmit.Init() // 三者都依赖 redis
oplog.Init(systemservice.OperLogSvcApp.RecordOper) // 依赖 database + snowflake全局中间件:Recover → CORS → TraceID → RepeatableBody → AccessLog → XSS → I18n。