常量命名须带业务前缀,禁用裸 iota;跨包不可复用同一 iota 块;导出常量首字母大写,内部小写;权限宜用位运算(如 Read UserPerm = 1 << iota),避免连续整数。

常量命名必须带业务前缀,不能裸用 iota
Go 里用 iota 定义常量很常见,但直接写 Read, Write, Delete 会埋坑:不同模块(比如用户、订单、日志)的权限常量一旦混入同一作用域,编译不报错,运行时却可能误判。真实项目里,Read 到底是读用户还是读日志?没人能一眼看懂。
必须加业务前缀,比如 UserRead、OrderWrite,或者更明确地封装进类型:
-
type UserPerm uint16+const (UserRead UserPerm = iota; UserWrite; UserDelete) - 避免跨包复用同一
iota块——不同包应各自定义,防止依赖隐式耦合 - 如果常量要导出(供其他包用),首字母大写;内部使用的小范围常量,全小写即可
权限常量优先用位运算,别用连续整数
很多项目把权限存进数据库一个 int 字段,然后用 == 判断单个权限。这导致后期想支持“用户同时有读+写”就得改字段类型或加新列——硬编码逻辑扩散到 SQL、Go、前端三处。
真正可扩展的做法是每位代表一个权限,用 | 合并、& 判断:
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 定义时必须移位:
Read UserPerm = 1 ,不是 <code>Read UserPerm = iota - 这样
UserRead | UserWrite得到唯一组合值,perm & UserRead != 0就是标准判断方式 - 别在 SQL 层做
WHERE perm & ?—— MySQL 的位运算可读性差、难索引;应在 Go 层解出权限集后,用IN或JOIN查
配置类常量必须和环境绑定,禁止硬编码字符串
后台系统里一堆 “admin”、“super_admin”、“guest” 这类角色名,如果散落在 if role == "admin" 里,改名就得全局搜替换,漏一处就出权限越界。
所有配置类字符串必须集中定义,并与环境联动:
- 定义为
const AdminRole = "admin",而非直接写字符串 - 若需多环境差异(如测试环境开放 debug 权限),用
os.Getenv("ENV") == "dev"控制是否注册某权限常量,而不是靠注释开关 - JWT payload 里的
"role"字段值,必须和常量严格一致;校验时用role == AdminRole,别用strings.EqualFold忽略大小写——大小写本身就是契约
结构体 tag 和常量必须对齐,尤其 GORM 和 JSON
一个 User 结构体既用于数据库映射,又用于 API 返回,但 gorm tag 和 json tag 不一致时,常量看似没变,实际行为已偏移。比如:
type User struct { Name string `gorm:"column:user_name" json:"name"` } —— 这里 user_name 是 DB 字段,name 是 JSON key,但如果你在权限逻辑里写 if u.Name == AdminRole,而 DB 实际存的是 user_name,GORM 加载时字段为空,判断永远 false。
- GORM v2 默认忽略无 tag 字段,务必每个字段显式声明
gorm:"column:xxx" - JSON tag 用
json:"xxx,omitempty",避免空字符串被序列化成"name": ""干扰前端判断 - 常量参与字段赋值时(如默认角色),确保它被正确写入对应 column,而不是靠 struct 字段名自动映射
AdminRole,但接口返回 403”的静默失败。

















