Hyperf 中不能直接用 casbin.NewEnforcer 初始化,因其为同步单例,不支持协程上下文切换,易致缓存错乱或并发冲突;须用 casbin.NewSyncedEnforcer 或 casbin.NewEnforcer 配合协程安全的 Adapter(如 hyperf/database-adapter)。

Hyperf 本身不内置 RBAC,但通过 hyperf/casbin 可以低成本、高灵活性地实现基于角色的权限控制,关键是配置模型、适配数据源、拦截请求三步到位。
为什么不能直接用 casbin.NewEnforcer 初始化?
Hyperf 是协程环境,casbin.NewEnforcer 默认创建的是单例同步版,无法自动感知协程上下文切换,会导致规则缓存错乱或并发读写冲突。必须使用 Casbin 官方为 Swoole/Hyperf 优化的 casbin.NewSyncedEnforcer 或更推荐的 casbin.NewEnforcer + Adapter 组合,并确保 Adapter 支持协程安全(如 hyperf/database-adapter)。
- 优先选用
hyperf/database-adapter,它基于 Hyperf 的Db组件,天然支持协程 MySQL/PostgreSQL - 避免用
file-adapter在生产环境,文件读写不支持并发,且无法热更新策略 - 若用 Redis 缓存策略,需自行实现
Adapter并保证LoadPolicy和SavePolicy的原子性
如何定义 RBAC 模型并加载到 Enforcer?
Casbin 默认的 rbac_model.conf 已支持角色继承和资源动作匹配,但需注意 Hyperf 中模型文件路径和加载时机——不能在 __construct 里硬编码路径,而应通过 DI 容器注入配置。
- 把模型文件放在
config/autoload/casbin.php中,用model_path指向app/Config/rbac_model.conf - 模型内容保持标准:
[request_definition] r = sub, obj, act+[policy_definition] p = sub, obj, act+[role_definition] g = _, _+[policy_effect] e = some(where (p.eft == allow)) - 不要手动调用
$enforcer->loadModel(),hyperf/casbin的EnforcerFactory会按配置自动加载
# app/Config/rbac_model.conf [request_definition] r = sub, obj, act <p>[policy_definition] p = sub, obj, act</p><p>[role<em>definition] g = </em>, _</p><p>[policy_effect] e = some(where (p.eft == allow))</p><p>[matchers] m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
怎么在 Controller 或 Middleware 中做权限校验?
最常用方式是写一个 CasbinMiddleware,在 process 里提取 sub(用户 ID 或角色名)、obj(路由路径或资源标识)、act(HTTP 方法或自定义动作),再调用 $enforcer->enforce()。
-
sub建议传用户角色名(如"admin"),而非用户 ID;角色映射关系由g策略维护,便于统一授权 -
obj推荐标准化为"user:profile"、"order:list"这类资源标识,而不是原始 URL,避免路由参数干扰匹配 -
act可设为"view"、"edit"、"delete",比直接用GET/POST更语义化且易扩展 - 校验失败时抛出
AuthorizationException,配合 Hyperf 全局异常处理器返回 403
数据库策略表结构和初始化要注意什么?
hyperf/database-adapter 默认使用 casbin_rule 表,但字段类型和索引必须匹配,否则大流量下 enforce 查询会变慢甚至超时。
- 确保
p_type字段是VARCHAR(100),能容纳"p"、"g"、"g2"等策略类型 -
v0~v5全部设为TEXT或VARCHAR(255),避免截断角色名或资源路径 - 给
(p_type, v0, v1, v2)加联合索引,这是enforce最频繁的查询条件 - 首次部署后,用
php bin/hyperf.php casbin:init(需自定义命令)或手动插入几条g规则,例如:["g", "admin", "super_admin"]
复杂点在于角色动态继承和权限实时生效——Casbin 不自动监听数据库变更,如果策略表被外部系统修改,需调用 $enforcer->loadPolicy() 主动刷新,或用 Redis Pub/Sub 触发 reload。这点容易被忽略,线上权限不生效往往卡在这里。


















