1. 引言
权限管理是几乎所有多用户系统的核心难题。很多团队在初期只做“登录 + 角色”,上线后才发现:用户 A 能看用户 B 的私有数据、两个部门共用了同一份资源却互相覆盖、离职员工的权限迟迟没有回收……这些问题的根源,往往在于没有把“用户资格”和“资源空间”这两个维度分开设计。
本文从你提出的两个关键诉求出发——每个用户具有自己使用权限资源的资格、多个用户共同占有同一个空间——梳理一套可落地的权限模型,并给出数据库设计与代码示例。
2. 先厘清两个核心概念:资格(Entitlement)与空间(Space)
在动手设计之前,先把需求拆成两个正交的维度:
- 资格(Entitlement):描述“某个用户能不能做某件事”。例如“张三可以编辑文档”“李四可以管理成员”。它回答的是Who can do What。
- 空间(Space):描述“资源归属于哪个容器”。例如“项目 A 的文档库”“团队 B 的共享文件夹”。它回答的是Where the resource lives。
你提到的“每个用户具有自己使用权限资源的资格”,本质是资格要落到用户个体;“共同占有同样的空间”,本质是多个用户共享同一个资源容器。
把这两个维度分开,就能避免最常见的错误:把“谁能访问”和“资源在哪”混在一张表里,导致共享空间里每个人的权限无法独立控制。
3. 推荐模型:RBAC + 资源空间隔离
业界最稳妥的组合是RBAC(基于角色的访问控制)+ 资源空间(Space)隔离:
- 用RBAC管理“资格”:用户 → 角色 → 权限点。
- 用Space管理“空间”:资源挂在某个 Space 下,Space 再与用户/角色建立授权关系。
这样既保留了角色的复用性,又能做到“同一个空间里,不同用户拥有不同资格”。
3.1 核心实体
| 实体 | 说明 | 示例 |
|---|---|---|
| User | 用户 | 张三 |
| Role | 角色(一组权限的集合) | 管理员、编辑者、访客 |
| Permission | 权限点(最小操作单元) | doc:edit、doc:view |
| Space | 资源空间容器 | 项目 A、团队 B |
| SpaceMember | 空间成员(用户在某空间内的角色) | 张三在项目 A 中是编辑者 |
3.2 关键设计:空间成员表
“共同占有同一个空间”的核心落点,就是SpaceMember这张表。它把“用户”和“空间”关联起来,并记录该用户在这个空间内的角色:
CREATETABLEspace_member(idBIGINTPRIMARYKEYAUTO_INCREMENT,space_idBIGINTNOTNULLCOMMENT'空间ID',user_idBIGINTNOTNULLCOMMENT'用户ID',role_idBIGINTNOTNULLCOMMENT'角色ID(在该空间内生效)',created_atDATETIMEDEFAULTCURRENT_TIMESTAMP,UNIQUEKEYuk_space_user(space_id,user_id))COMMENT'空间成员表:用户在某空间内的资格';通过这张表,你可以回答两个关键问题:
- “张三在项目 A 里能做什么?” → 查
space_member得到角色,再映射到权限点。 - “项目 A 里有哪些成员?” → 按
space_id查space_member。
4. 权限判断的核心流程
当用户发起一次操作(比如“编辑项目 A 的文档”)时,判断流程如下:
- 找到资源所属的Space。
- 查
space_member,得到该用户在这个 Space 内的Role。 - 由 Role 映射出Permission集合。
- 判断所需权限点是否在集合中。
publicclassPermissionService{privatefinalSpaceMemberMapperspaceMemberMapper;privatefinalRolePermissionMapperrolePermissionMapper;publicbooleancheckPermission(LonguserId,LongspaceId,StringpermissionCode){// 1. 用户在空间内的角色SpaceMembermember=spaceMemberMapper.findBySpaceIdAndUserId(spaceId,userId);if(member==null){returnfalse;// 不是空间成员,默认无权限}// 2. 角色对应的权限点List<String>codes=rolePermissionMapper.findPermissionCodesByRoleId(member.getRoleId());returncodes.contains(permissionCode);}}5. 如何兼顾“个人资格”与“共享空间”
你提到的两个诉求,在模型里分别对应两种授权方式:
5.1 个人专属资格
当某个用户拥有独立于任何空间的资格时(例如“全局管理员”“仅查看自己的数据”),可以:
- 在
user_role表中记录用户的全局角色; - 或在
space_member中为每个用户创建一个私有空间(Personal Space),把个人资源挂进去。
CREATETABLEuser_role(idBIGINTPRIMARYKEYAUTO_INCREMENT,user_idBIGINTNOTNULL,role_idBIGINTNOTNULL,UNIQUEKEYuk_user_role(user_id,role_id))COMMENT'用户全局角色表:与空间无关的资格';5.2 共享空间内的差异化资格
多个用户共同占有同一个 Space,但各自资格不同:
| 用户 | 空间 | 角色 | 可执行操作 |
|---|---|---|---|
| 张三 | 项目 A | 编辑者 | 编辑、评论 |
| 李四 | 项目 A | 访客 | 仅查看 |
| 王五 | 项目 A | 管理员 | 编辑、删除、管理成员 |
这正是space_member表的价值:同一个 space_id,不同 user_id,不同 role_id。
6. 权限判断的完整时序
7. 常见坑与建议
- 不要把权限写死在业务代码里:用
if (user.isAdmin())这种散落判断,后期会失控。统一走权限点校验。 - 空间删除前先清理成员关系:避免产生孤儿数据。
- 权限点要足够细:至少拆到“查看 / 编辑 / 删除 / 管理成员”级别,方便后续做细粒度控制。
- 缓存角色-权限映射:权限判断是高频操作,建议把
role_permission缓存到 Redis,减少数据库压力。 - 超管与空间管理员分开:全局超管走
user_role,空间管理员走space_member,两者互不干扰。
8. 总结
权限管理的核心,是把“资格”和“空间”两个维度解耦:
- 资格用 RBAC 管理,落到用户个体;
- 空间用 Space 容器管理,多个用户通过
space_member共享同一空间,并在空间内拥有各自独立的角色。
这样既满足了“每个用户具有自己使用权限资源的资格”,也实现了“共同占有同样的空间”。按这个模型落地,后续无论是加新空间、调成员角色,还是做细粒度权限,都能平滑扩展。