流程拆解
图清单
1. 接口权限校验流程
flowchart TD A[请求带 JWT 到达业务服务] B[取 TenantContext] C{未登录} D{tenant_id 为 0} E{是平台超管} F[查平台角色权限点] G{是超管或租户管理员} H[5 表 JOIN 校验权限点] P[放行] N[权限不足拒绝] A --> B --> C C -->|是| N C -->|否| D D -->|是 平台视角| E E -->|是| P E -->|否| F F --> P D -->|否 租户视角| G G -->|是| H G -->|否| H H --> P
说明:核心判定在 HasEffectivePermission。租户视角下普通用户走完整 5 表 JOIN;超管/租户管理员跳过角色判定但仍查租户菜单。全程无缓存,每请求实时查库。
代码锚点
- 中间件入口:
middleware/permission.go:13RequirePermission - 核心判定:
utils/rbac/permission.go:266HasEffectivePermission - 平台角色权限点:
utils/rbac/permission.go:73hasPlatformRolePermission - 菜单交集:
utils/rbac/permission.go:194EffectiveMenuIDs
2. 数据权限过滤流程
flowchart TD Q[service 发起列表查询] S{是否挂 DataScopeFilter} SC[取用户最大 data_scope] D1[1 全部数据 不限制] D2[2 自定义部门 限部门列表] D3[3 本部门 限本部门] D4[4 本部门及以下 限部门子树] D5[5 仅本人 限创建人] NOW[现状 未挂 仅租户隔离] Q --> S S -->|已挂 理想态| SC SC --> D1 SC --> D2 SC --> D3 SC --> D4 SC --> D5 S -->|未挂 实际态| NOW
说明:data_scope 数值越小权限越大(1 最大);多角色取最大范围。当前实际态是「未挂」分支,故数据权限未生效。
代码锚点
- 过滤器:
middleware/data_scope.go:18DataScopeFilter - 最大范围:
middleware/data_scope.go:55getMaxDataScope - 子部门 BFS:
middleware/data_scope.go:87getChildDeptIDs - 未接入证据:
pkg/下DataScopeFilter调用数为 0(仅data_scope_test.go使用)
3. 前端按钮级控制
v-permission 指令(directive/auth/permission.ts):检查 userStore.userInfo.permissions 是否含 *:*:* 或精确权限标识,不满足则 el.remove() 移除元素。hasPermission/hasAnyPermission 在 utils/permission.ts。
注意:前端控制仅为 UX,真正安全边界在后端(后端已做)。
优化建议
P0 — 立即处理
- 接入数据权限(P0-2):在各域列表/详情查询的 GORM 链补
.Scopes(DataScopeFilter(tc, ownerField)),统一收口到 BaseService/查询 helper 避免逐个漏挂(漏挂即越权)。 - 权限缓存落地(P0-1):登录/切租户时一次性算出用户「有效权限集合」缓存到 Redis(按
userID+tenantID,TTL + 角色/菜单变更主动失效),中间件改为内存集合 O(1) 判断。复用已定义的CacheKeyRoleMenus。
P1 — 架构层
- 鉴权上移网关:粗粒度权限在网关统一做,业务服务只做细粒度数据权限,减少 322 次重复。
- DataScopeFilter N+1 治理:
getMaxDataScope/getCustomDeptIDs/getChildDeptIDs每次过滤都查库,部门树、角色 data_scope 应随权限缓存一起设计。 - 前端
el.remove()改造:用display:none或占位注释节点替代不可逆的 DOM 移除,避免updated重渲染失效。
P2 — 增强
- RBAC → ABAC/字段级:外贸场景常有「金额字段对销售脱敏」「合同价格仅财务可见」需求,可在 DTO 序列化层加字段级权限。
- 权限变更主动失效:引入缓存后配套「角色/菜单变更 → 清缓存」,
sys_permission_audit_log已记录变更事件,可作失效触发点。