后台权限设计方案
2025-12-25
blog
blog
字数统计: 2k(字)
阅读时长: 8(分)
本文是一套 可落地、可扩展、符合银行/金融系统实践 的后台权限设计方案,基于 RBAC(基于角色的访问控制) ,并完整覆盖:
API 权限
菜单 / 按钮权限
数据权限(含 CUSTOM 自定义)
一、设计背景与目标
1.1 业务背景
后台管理系统(银行 / 贷款 / 金融 / 审批类系统)通常具备以下共性:
多角色、多岗位(管理员、信贷员、风控、负责人等)
接口一致,但数据可见范围不同
菜单、按钮、接口权限需要统一管理
存在跨部门、临时授权、区域授权等复杂场景
如果没有一套清晰的权限模型,系统将很快出现:
权限规则混乱
SQL 到处写 if/else
数据越权风险
新需求难以扩展
1.2 设计目标
本方案旨在实现:
统一、标准的权限模型
结构清晰、可维护的表设计
API 作为安全兜底
灵活但可控的数据权限
支持复杂业务的自定义授权(CUSTOM)
二、整体权限模型概览
2.1 权限模型选型
采用业内事实标准:
RBAC(Role-Based Access Control)
并在 RBAC 基础上拆分权限层次:
API 权限(是否能访问接口)
菜单权限(页面是否可见)
按钮权限(操作是否可点)
数据权限(能看到哪些数据行)
2.2 权限分层思想(非常重要)
层级
控制内容
是否真正安全
说明
API 权限
接口是否可访问
✅
后端兜底,必须校验
菜单权限
页面是否显示
❌
仅用于前端展示
按钮权限
按钮是否显示
❌
仅用于前端控制
数据权限
能看到哪些数据
✅
决定 SQL WHERE
真正的安全 = API 权限 + 数据权限
菜单 / 按钮只负责“看不看得到”,不负责“能不能做”。
三、数据表 ER 图设计
3.1 表结构与关系
erDiagram
sys_admin ||--o{ sys_admin_role : "一对多"
sys_role ||--o{ sys_admin_role : "一对多"
sys_role ||--o{ sys_role_permission : "一对多"
sys_permission ||--o{ sys_role_permission : "一对多"
sys_dept ||--o{ sys_admin : "一对多"
sys_admin {
int id PK "用户ID"
varchar name "姓名"
char password "密码(MD5/加密)"
char mobile "手机号"
int dept_id FK "所属部门ID"
datetime register_time "注册时间"
datetime last_time "最后登录时间"
varchar last_ip "最后登录IP"
tinyint is_delete "是否删除(0否1是)"
datetime created_at "创建时间"
datetime updated_at "更新时间"
}
sys_dept {
int id PK "部门ID"
int parent_id FK "父部门ID"
varchar name "部门名称"
datetime created_at "创建时间"
datetime updated_at "更新时间"
}
sys_role {
int id PK "角色ID"
varchar name "角色名称"
varchar scope "数据权限范围(ALL/DEPT/DEPT_AND_SUB/SELF/CUSTOM)"
datetime created_at "创建时间"
datetime updated_at "更新时间"
}
sys_permission {
int id PK "权限ID"
int parent_id FK "父级权限ID(菜单用)"
tinyint type "权限类型(1接口 2菜单 3按钮)"
varchar name "权限名称"
varchar method "HTTP方法(GET/POST等)"
varchar path "接口或路由路径"
int sort "排序"
datetime created_at "创建时间"
datetime updated_at "更新时间"
}
sys_admin_role {
int id PK "主键ID"
int admin_id FK "后台用户ID"
int role_id FK "角色ID"
datetime created_at "创建时间"
datetime updated_at "更新时间"
}
sys_role_permission {
int id PK "主键ID"
int role_id FK "角色ID"
int permission_id FK "权限ID"
datetime created_at "创建时间"
datetime updated_at "更新时间"
}
3.2 表说明速览
表名
作用
sys_admin
后台用户
sys_dept
部门组织
sys_role
角色(承载权限 + 数据范围)
sys_permission
权限(API / 菜单 / 按钮)
sys_admin_role
用户-角色关联
sys_role_permission
角色-权限关联
四、RBAC 核心设计说明
4.1 为什么“用户不直接绑定权限”
一个用户可能有多个角色
角色才是业务语义的载体
权限集中在角色上,便于维护和审计
用户 → 角色 → 权限
永远不要:用户 → 权限
4.2 sys_permission 的设计思想
权限类型
1 2 3 1 = API 权限 2 = 菜单权限 3 = 按钮权限
权限 code 设计原则
示例:
1 2 3 4 loan:create loan:approve loan:grant loan:post:list
五、系统中如何使用这套权限
5.1 API 权限(中间件,安全兜底)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func PermissionMiddleware (r *ghttp.Request) { user := GetLoginUser(r.Context()) ok := permissionSvc.HasApiPermission( user.Id, r.URL.Path, r.Method, ) if !ok { r.Response.WriteStatusExit(403 , "无权限访问" ) } r.Middleware.Next() }
核心校验逻辑
1 2 3 4 5 6 7 8 SELECT 1 FROM sys_admin_role urJOIN sys_role_permission rp ON ur.role_id = rp.role_idJOIN sys_permission p ON rp.permission_id = p.idWHERE ur.admin_id = ? AND p.type = 'api' AND p.path = ? AND p.method = ?
5.2 菜单权限(登录后返回)
1 2 3 4 5 6 7 SELECT p.* FROM sys_admin_role urJOIN sys_role_permission rp ON ur.role_id = rp.role_idJOIN sys_permission p ON rp.permission_id = p.idWHERE ur.admin_id = ? AND p.type = 'menu' ORDER BY p.sort;
后端返回菜单树,前端负责渲染。
5.3 按钮权限(前端控制)
1 2 3 4 5 6 SELECT p.nameFROM sys_admin_role urJOIN sys_role_permission rp ON ur.role_id = rp.role_idJOIN sys_permission p ON rp.permission_id = p.idWHERE ur.admin_id = ? AND p.type = 'button' ;
前端示例:
1 <v-button v-if="hasPermission('loan:grant')" />
六、数据权限(Data Scope)设计
6.1 data_scope 的本质
data_scope 决定 SQL 查询时 WHERE 条件如何拼
它只控制:
它不控制:
6.2 标准 data_scope 枚举
1 2 3 4 5 ALL 全部数据 DEPT 本部门 DEPT_AND_SUB 本部门及子部门 SELF 仅本人 CUSTOM 自定义
6.3 数据权限只能放在 Service 层
1 2 3 4 5 6 7 8 9 10 11 func (s *LoanService) List(ctx context.Context, user *LoginUser) { switch user.DataScope { case "ALL" : case "DEPT" : case "DEPT_AND_SUB" : case "SELF" : } }
数据权限禁止放在中间件或 Controller
七、多角色 data_scope 冲突解决策略
7.1 核心原则
一个用户,在一个业务场景下,只能有一个最终 data_scope
7.2 推荐策略(银行系统首选)
最宽权限优先
1 ALL > DEPT_AND_SUB > DEPT > CUSTOM > SELF
示例
角色组合
最终 scope
DEPT + SELF
DEPT
CUSTOM + DEPT
DEPT
SELF + CUSTOM
CUSTOM
7.3 登录时计算最终 data_scope
1 登录 → 查询角色 → 计算 scope → 写入 Session / JWT
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func ResolveDataScope (roles []Role) string { priority := map [string ]int { "ALL" : 4 , "DEPT_AND_SUB" : 3 , "DEPT" : 2 , "CUSTOM" : 1 , "SELF" : 0 , } max := -1 result := "SELF" for _, r := range roles { if p := priority[r.DataScope]; p > max { max = p result = r.DataScope } } return result }
八、CUSTOM 自定义数据权限(高级能力)
8.1 什么时候需要 CUSTOM
跨部门授权
区域/片区管理
临时审计授权
精确到具体业务对象
8.2 自定义数据权限表设计
1 2 3 4 5 6 7 CREATE TABLE sys_role_data_scope ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID' , role_id BIGINT NOT NULL COMMENT '角色ID' , scope_type VARCHAR (32 ) NOT NULL COMMENT '授权类型' , scope_id BIGINT NOT NULL COMMENT '授权对象ID' , created_at DATETIME ) COMMENT= '角色自定义数据权限表' ;
scope_type 规范
1 2 3 4 DEPT 部门 USER 用户 LOAN 贷款 CUSTOMER 客户
8.3 CUSTOM 示例
1 2 风控专员角色(CUSTOM) → 授权部门:10、12
1 2 3 4 INSERT INTO sys_role_data_scope (role_id, scope_type, scope_id)VALUES (5 , 'DEPT' , 10 ), (5 , 'DEPT' , 12 );
8.4 CUSTOM 在代码中的生效方式
登录态结构
1 2 3 4 5 6 type LoginUser struct { Id int64 DeptId int64 DataScope string CustomScopes map [string ][]int64 }
加载 CUSTOM 数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func LoadCustomScopes (roleIds []int64 ) map [string ][]int64 { rows := []struct { ScopeType string ScopeId int64 }{} g.DB().Model("sys_role_data_scope" ). WhereIn("role_id" , roleIds). Scan(&rows) result := make (map [string ][]int64 ) for _, r := range rows { result[r.ScopeType] = append (result[r.ScopeType], r.ScopeId) } return result }
8.5 Service 层统一拼接 SQL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func (s *LoanService) applyDataScope(db *gdb.Model, user *LoginUser) *gdb.Model { switch user.DataScope { case DataScopeAll: return db case DataScopeDept: return db.Where("dept_id" , user.DeptId) case DataScopeDeptAndSub: return db.WhereIn("dept_id" , s.deptSvc.GetDeptAndSubIds(user.DeptId)) case DataScopeSelf: return db.Where("creator_id" , user.Id) case DataScopeCustom: deptIds := user.CustomScopes["DEPT" ] if len (deptIds) == 0 { return db.Where("1=0" ) } return db.WhereIn("dept_id" , deptIds) } return db }
九、总结
这套权限方案的核心思想是:
用 RBAC 控制“能不能做”
用 API 权限 作为安全兜底
用 data_scope 控制“能看多少”
用 CUSTOM 解决现实业务中的例外情况
这是一套 真正能在银行 / 金融系统中长期演进的后台权限设计方案 。