架构设计
数据模型与权限边界
理解多租户数据设计、RBAC、permission_key 与数据权限应该如何协同工作。
数据设计的基本原则
- 统一基础字段
- 租户隔离优先
- 查询路径优先于“表看起来漂亮”
- 审计、回滚、演进必须可支持
这几条原则看起来很基础,但它们直接决定了后面权限、审计、导出、分页、归档、跨租户隔离是不是会一路返工。
常见基础字段
核心表通常具备:
idtenant_idstatusdeleted或is_deletedcreated_atupdated_atcreated_byupdated_by
很多核心表还会补:
versionremarksort_order
主键建议走 bigint 的雪花 ID 或类似方案,避免长期依赖数据库自增。
多租户分层
典型分层包括:
- 平台级公共数据
- 租户级数据
- 租户内组织级数据
这意味着“一个用户属于多个租户”是正常模型,不应把用户和租户绑定成一对一。
从开发角度看,这个分层会直接影响三件事:
- 表是否必须带
tenant_id - 唯一键是全局唯一还是租户内唯一
- 查询时需要按哪个边界做过滤
权限模型的核心目标
这套系统的权限模型,不只是为了“菜单能不能显示”,而是要同时解决:
- 多租户用户关系
- 平台态与租户态切换
- 菜单、按钮、接口、数据范围联动
- 审计与权限变更追踪
所以它不能退化成“用户-角色-菜单”三张表的简化版理解。
RBAC 的真实关系
推荐理解为:
用户 -> 用户租户关系 -> 角色 -> permission_key -> 页面/接口/数据范围不要把“用户-角色-菜单”当成完整权限模型,它在多租户场景里不够。
更准确地说,用户进入某个租户之后,角色、菜单、按钮、数据范围才开始被解释。
permission_key 规范
统一格式:
<domain>:<resource>:<action>例如:
system:user:viewsystem:file:uploadai:knowledge:document:uploadplugin:management:install
这套 key 应被菜单、按钮、后端接口、审计和种子数据共同复用。
一个很重要的约束是:不要前端一套字符串、后端再写一套近似字符串。 一旦偏离,页面显隐、接口鉴权、角色授权、审计都很容易对不上。
权限四层
- 登录态:用户是否已认证
- 路由权限:是否能访问页面或 API 类别
- 操作权限:是否拥有某个
permission_key - 数据权限:能看到哪些组织、资源和记录
这四层最容易混的是“按钮隐藏”和“真实权限校验”。前端隐藏按钮只是交互控制,真正安全边界仍然在后端接口和查询层。
前后端职责
前端负责:
- 页面守卫
- 菜单显隐
- 按钮显隐
后端负责:
- 真实接口鉴权
- 数据范围过滤
- 高风险操作审计
更细一点说:
auth-service负责登录协议、token、刷新、二次认证system-serviceIAM 负责角色、菜单、权限快照、数据范围基础- 业务 owner service 负责资源级权限和真正的数据边界
数据范围建议
第一阶段至少支持:
- 仅本人
- 本部门
- 本部门及下级
- 指定部门
- 全租户
更复杂的场景可以继续扩展,但建议先把这五类做稳定。重点不是枚举写得多花,而是要把过滤真正落到后端查询层。
这些规则应该在后端 owner service 的查询层落地,而不是交给前端决定。
什么时候必须刷新权限快照
以下场景都应该触发权限快照失效或重建:
- 用户角色变更
- 角色菜单变更
- 角色数据范围变更
- 菜单状态变更
- 用户加入或退出租户
- 当前租户切换
开发红线
- 不允许把前端按钮隐藏当成安全控制
- 不允许让前端决定租户边界
- 不允许不同模块各自定义一套权限编码
- 不允许平台态无审计地做跨租户操作
- 不允许用“是否管理员”粗暴替代细粒度权限