RBAC 权限系统方案 — 最小改动版

现状分析

当前系统的权限控制非常简单:

角色 状态
Guest 0 占位,未使用
Common 1 普通用户
Admin 10 管理员
custom 11 已定义,未实现
Root 100 超级管理员

权限判断只有一种方式:role >= 阈值。没有细粒度控制——要么看到整个管理区,要么完全看不到。


最小改动方案:复用 options 表 + 内存缓存

核心思路

在现有 options 表中新增一个 key RolePermissions,存 JSON 格式的角色权限矩阵。不建新表、不改用户表结构

完全复用 SidebarModulesAdmin 的模式:options 表存储 → OptionMap 内存缓存 → /api/status 下发给前端。

存储格式

{
  "10": ["menu:admin.channel", "menu:admin.models", "channel:create", "channel:update", "..."],
  "11": ["menu:admin.redemption", "menu:admin.user", "redemption:create", "..."],
  "1":  ["menu:chat.playground", "menu:console.token", "token:create", "..."],
  "100": ["*"]
}
  • key 是角色值的字符串,value 是该角色拥有的权限数组
  • Root 用 ["*"] 代表全部权限
  • Guest 无条目

权限命名规则:资源:动作

菜单权限(控制页面/菜单可见性):

menu:chat.playground    menu:chat.chat
menu:console.detail     menu:console.model_usage    menu:console.token
menu:console.log        menu:console.midjourney     menu:console.task
menu:personal.topup     menu:personal.personal
menu:admin.channel      menu:admin.models           menu:admin.deployment
menu:admin.subscription menu:admin.redemption       menu:admin.user
menu:admin.setting

操作权限(控制页面内按钮/动作):

channel:create   channel:update   channel:delete   channel:test
token:create     token:update     token:delete
user:create      user:update      user:delete      user:set_role
redemption:create  redemption:update  redemption:delete
model:create     model:update     model:delete
setting:update   log:delete

各角色默认权限矩阵

权限类别 Common(1) Admin(10) Sale(11) Root(100)
menu:chat.* / console.* / personal.* 全部 全部 全部 *
menu:admin.channel/models/deployment/subscription - - *
menu:admin.redemption/user - *
menu:admin.setting - - - *
channel 增删改测 - 全部 - *
token 增删改 全部 全部 全部 *
user 增删改 - 有(无 set_role) - *
redemption 增删改 - 全部 全部 *
setting:update / log:delete - log:delete - *

改动清单

后端(4 个文件新增,3 个文件修改)

操作 文件 说明
新增 common/permissions.go 权限常量定义 + DefaultRolePermissions() 返回默认矩阵 + 从 OptionMap 解析的内存缓存 HasPermission(role, perm)
修改 model/option.go InitOptionMap 加入 RolePermissions 默认值;updateOptionMap 中加 case 解析 JSON 到内存缓存
新增 middleware/permission.go RequirePermission(perms...) 中间件,从缓存查权限,无权返回 403
新增 controller/role_permission.go Root-only API:查询/更新/重置角色权限(读写 options 表的 RolePermissions key)
修改 router/api-router.go 各路由组追加 RequirePermission;兑换码路由从 AdminAuthUserAuth + RequirePermission(激活 Sale);加权限管理路由
修改 controller/user.go GetSelf/setupLogin 返回 permissions 字段(从缓存读当前角色权限列表)
修改 controller/misc.go GetStatus 返回 RolePermissions(供前端全局使用)

前端(3 个文件新增,5+ 个文件修改)

操作 文件 说明
新增 helpers/permission.js hasPermission(perm) / hasAnyPermission(...perms) 工具函数,从 localStorage 读权限
新增 hooks/common/usePermission.js React Hook 封装
新增 pages/Setting/System/RolePermissionSettings.jsx Root 权限管理 UI:按角色 Tab + 按分类分组的权限开关
修改 helpers/auth.jsx 新增 PermissionRoute 组件(按权限控制路由访问)
修改 App.jsx Admin 路由从 <AdminRoute> 改为 <PermissionRoute permission="menu:admin.xxx">
修改 hooks/common/useSidebar.js 三层可见性加入权限层:管理员全局配置 AND 角色权限 AND 用户偏好
修改 components/layout/SiderBar.jsx isAdmin()hasAnyPermission('menu:admin.*')
修改 各 Actions 组件 按钮根据操作权限条件渲染(如 hasPermission('channel:delete') 才显示删除按钮)

i18n(7 个文件修改)

所有 web/src/i18n/locales/*.json 添加权限名称和权限管理页面的翻译。


数据流

启动 → InitOptionMap 写入默认 RolePermissions → 内存缓存就绪
                                                    ↓
用户登录 → setupLogin → 从缓存读角色权限 → 返回 { permissions: [...] }
                                                    ↓
前端存 localStorage → useSidebar/usePermission 读取 → 菜单和按钮按权限显示
                                                    ↓
API 请求 → UserAuth/AdminAuth(第一道门槛)→ RequirePermission(第二道门槛)
                                                    ↓
Root 修改权限 → UpdateOption("RolePermissions", json) → 刷新缓存
             → 用户下次刷新页面自动获取新权限

向后兼容

  • 不改 users 表role 字段不变
  • 保留 UserAuth/AdminAuth/RootAuth 作为第一道门槛
  • RequirePermission 作为叠加的第二道门槛
  • 前端检测不到 permissions 时回退到原有 isAdmin()/isRoot() 逻辑
  • 首次启动自动写入默认权限,现有用户无感迁移

总结

  • 不新增数据库表,复用 options 表存一条 JSON
  • 不改用户表结构
  • 后端新增 3 个文件、修改 4 个文件
  • 前端新增 3 个文件、修改 5-8 个文件
  • 完全兼容现有逻辑,渐进式叠加