权限常量必须是2的幂,推荐用1U << n或1ULL << n定义,确保每位独占、互不干扰,避免误判和溢出。

权限常量必须用 1 定义,不能直接用连续整数
直接写 const Read = 0; Write = 1; Delete = 2 看似简单,但会导致位运算失效:这些值在二进制中不是独立的位(0 是全 0,1 是 0001,2 是 0010,但 3 就是 0011 —— 它同时包含 Read | Write,却无法被单独识别)。
正确做法是让每个权限独占一位:
const Read uint64 = 1 → <code>0001-
Write→0010 -
Delete→0100
这样 Read | Write 得到的是 0011,而 userPerms & Read 能精准命中第 0 位,不会误判。
HasPerm 函数里必须用 & != 0,不能用 & == flag
常见错误是写成 userPerms & requiredPerm == requiredPerm。这在 requiredPerm 为 0 时恒成立(任何数 & 0 都是 0),导致空权限绕过校验。
立即学习“go语言免费学习笔记(深入)”;
更隐蔽的问题是:当 requiredPerm 是组合值(如 Read | Write)时,该表达式要求用户权限**完全等于**这个组合,而不是“至少包含”,语义错位。
正确且高效的做法只有一行:
func HasPerm(perms, p uint64) bool {
return perms & p != 0
}
前提是:p 必须是单个权限位(如 Read),不是掩码组合。若需检查多个权限是否**全部存在**,应另写 HasAllPerms 函数。
清除权限必须用 &^,不能用 ^ 或 & ~
想禁用「删除」和「审计」权限,常见误写:
-
userPerms ^ (Delete | Audit)→ 这是异或,会翻转位:原本没有Delete的用户,这一操作反而把它打开了 -
userPerms & (~Delete | ~Audit)→ 逻辑错误,~Delete是一个极大值,|后几乎全 1,结果接近原值
Go 提供专用的 AND-NOT 运算符 &^,语义清晰、单指令完成:
forbidden := Delete | Audit userSafePerms := userPerms &^ forbidden
注意:forbidden 必须是多位置 1 的掩码(即多个权限用 | 组合),不是单个位;且 userPerms 和 forbidden 类型必须一致(推荐统一用 uint64)。
为什么必须用 uint64 而不是 int 或 uint
int 在 32 位系统上只有 32 位,而现代权限系统常需支持 64 种原子操作(如 user:read、order:export、audit:purge 等),permission & 1 在高位截断后永远为 0,权限判断直接失效。
uint 宽度依赖平台(32 或 64 位),不可控;int64 对负数右移行为在 Go 不同版本间不一致(补符号位 or 补 0)。
uint64 提供稳定 64 位无符号空间,右移恒补 0,位运算语义确定。所有地方都要对齐:
- 权限常量声明为
const Read uint64 = 1 - 用户权限变量类型为
uint64 - 全权限初始化写
^uint64(0),而非math.MaxUint64(前者编译期常量,零开销)
类型不一致会在编译时报错,但隐性溢出(如 32 位环境下的 int)不会报错,只会静默失败——这是最危险的坑。


















