必须用 uint64 存权限位,因 int 在 32 位系统仅 32 位易溢出,且 int64 右移行为不一致;uint64 无符号、右移恒补 0,语义确定,权限常量须显式声明为 uint64。

为什么必须用 uint64 而不是 int 存权限位
在 32 位系统上,int 只有 32 位,而现代微服务常需支持 64 种以上原子权限(如 user:read、order:write、audit:export),int 直接溢出。更危险的是:int64 对负数做右移(>>)时,Go 各版本行为不一致;uint64 无符号,右移恒补 0,语义确定。
常见错误:permission & 1 在 32 位环境可能永远为 0——因为高位被截断。实操建议:
- 所有权限常量声明为
uint64:例如const PermRead uint64 = 1 - 用户权限变量类型固定为
uint64,不写uint(宽度依赖平台) - 初始化全权限用
^uint64(0),而非math.MaxUint64(前者编译期常量,零开销)
HasPerm 函数里为什么用 & 而不是 ==
权限检查本质是「是否包含某权限位」,不是「是否完全相等」。userPerms & requiredPerm == requiredPerm 看似严谨,但当 requiredPerm 是 0(空权限)时,该表达式恒为 true,导致绕过校验。
正确且高效的做法是:userPerms & requiredPerm != 0。它只做一次与运算加非零判断,性能最优,语义清晰:只要对应位被置 1 就返回 true。
注意前提:requiredPerm 必须是单个权限位(如 PermRead),不能是组合值(如 PermRead | PermWrite)——否则逻辑失效,应改用 HasAllPerms 单独实现。
批量禁用权限时别用 ^,要用 &^
想从用户权限中剔除「删除」和「审计」两个权限,常见误写:userPerms ^ (PermDelete | PermAudit)。这是异或,会翻转对应位,而不是清除——若用户原本没这些权限,反而被意外打开。
正确方式是 Go 提供的 AND-NOT 运算符 &^:
forbidden := PermDelete | PermAudit userSafePerms := userPerms &^ forbidden // 单指令清除,无分支、无循环
关键点:forbidden 必须是多位置 1 的掩码,不是单个位。不要手写循环遍历每个权限位去清零——O(n) 且易漏。
权限位与租户上下文必须解耦
权限位本身只管「能做什么」,但「对谁做」由租户上下文决定。把 tenant_id 塞进 JWT 的 permissions 数组是典型反模式——它不是权限,而是作用域边界。
正确分层:
- 鉴权中间件先从请求提取
tenant_id(子域名 / header / JWT claim) - 权限检查函数只接收
uint64类型的权限集合和单个uint64权限位 - 租户过滤逻辑放在上层(如 gRPC 拦截器或 HTTP 中间件),统一调用
userPerms &^ forbidden剔除跨租户敏感权限
最容易被忽略的一点:权限常量定义时若漏写 uint64 类型,比如只写 const PermRead = 1 ,默认是 <code>int,在 32 位环境或跨平台部署时会悄无声息地失效。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











