3. 权限数据模型设计:五张核心表的设计与SQL实现
好,咱们直接进入正题。权限系统说到底,就是解决「谁」能「做什么」的问题。而数据模型,就是把这个逻辑落地到数据库里。
我见过不少团队,一上来就搞复杂的ABAC(基于属性的权限控制),结果连最基本的RBAC都没跑通。我个人习惯是:先做好RBAC,再考虑扩展。今天咱们就聊聊最经典的5张表设计。
3.1 用户表(User)—— 权限的起点
用户表没什么花头,但有几个坑我得提醒你。
CREATE TABLE `sys_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(128) NOT NULL COMMENT '密码(加密后)',
`real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`status` tinyint(4) DEFAULT '1' COMMENT '状态:0禁用 1启用',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
另外,status字段我建议用数字而不是字符串。0和1的判断在代码里更干净,索引效率也更高。
3.2 角色表(Role)—— 权限的载体
角色,说白了就是一组权限的集合。在CRM里,常见的角色有:销售主管、客户经理、数据管理员等。
CREATE TABLE `sys_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_name` varchar(50) NOT NULL COMMENT '角色名称',
`role_code` varchar(50) NOT NULL COMMENT '角色编码(唯一)',
`description` varchar(200) DEFAULT NULL COMMENT '角色描述',
`sort_order` int(11) DEFAULT '0' COMMENT '排序号',
`status` tinyint(4) DEFAULT '1' COMMENT '状态:0禁用 1启用',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_role_code` (`role_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
这里有个设计细节:role_code 我用了唯一索引。为什么?因为代码里经常要通过编码来查找角色,比如 ROLE_SALES_MANAGER。用编码比用ID更直观,也方便做国际化。
ROLE_ADMIN、ROLE_SALES。这样在代码里一眼就能看出是角色常量。
3.3 权限表(Permission)—— 权限的定义
权限表定义的是「能做什么」。在CRM里,权限通常对应一个URL或一个按钮。
CREATE TABLE `sys_permission` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '权限ID',
`perm_name` varchar(50) NOT NULL COMMENT '权限名称',
`perm_code` varchar(100) NOT NULL COMMENT '权限编码(如 customer:create)',
`perm_type` tinyint(4) DEFAULT '1' COMMENT '类型:1菜单 2按钮 3API',
`parent_id` bigint(20) DEFAULT '0' COMMENT '父权限ID',
`path` varchar(200) DEFAULT NULL COMMENT '路由路径',
`component` varchar(200) DEFAULT NULL COMMENT '组件路径',
`icon` varchar(50) DEFAULT NULL COMMENT '图标',
`sort_order` int(11) DEFAULT '0' COMMENT '排序号',
`status` tinyint(4) DEFAULT '1',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_perm_code` (`perm_code`),
KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';
嗯,这里要注意 perm_code 的设计。我建议用「冒号分隔」的层级结构,比如:
customer:list—— 查看客户列表customer:create—— 创建客户customer:edit—— 编辑客户customer:delete—— 删除客户
这种命名方式,在代码里做权限校验时特别方便。比如判断用户是否有「客户管理」的权限,直接检查 customer:* 通配符就行。
3.4 用户-角色关联表(User-Role)—— 多对多的桥梁
一个用户可以有多个角色,一个角色也可以属于多个用户。这就是典型的多对多关系。
CREATE TABLE `sys_user_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '关联ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_role` (`user_id`, `role_id`),
KEY `idx_role_id` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户-角色关联表';
这里有个关键点:联合唯一索引。为什么要加?防止同一个用户被重复分配同一个角色。我曾经在项目里见过,因为没加唯一索引,用户被分配了3次「销售主管」角色,结果权限校验时查出3条记录,代码逻辑直接崩了。
3.5 角色-权限关联表(Role-Permission)—— 权限的分配
这张表决定了「角色能做什么」。和用户-角色表类似,也是多对多关系。
CREATE TABLE `sys_role_permission` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '关联ID',
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
`permission_id` bigint(20) NOT NULL COMMENT '权限ID',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_role_perm` (`role_id`, `permission_id`),
KEY `idx_permission_id` (`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色-权限关联表';
你想想看,这张表其实就决定了整个权限系统的灵活性。比如:
- 给「销售主管」角色分配
customer:list、customer:create、customer:edit - 给「普通销售」角色只分配
customer:list、customer:create
这样,不同角色的权限就区分开了。而且修改权限时,只需要改这张表的数据,不需要改代码。
3.6 五张表的关系总览
咱们用一张图来总结这五张表的关系(嗯,虽然不能画图,但用文字描述一下):
| 表名 | 核心字段 | 作用 |
|---|---|---|
| sys_user | id, username, password, status | 存储用户基本信息 |
| sys_role | id, role_name, role_code, status | 定义角色(权限组) |
| sys_permission | id, perm_name, perm_code, parent_id | 定义具体的权限点 |
| sys_user_role | user_id, role_id | 用户与角色的关联 |
| sys_role_permission | role_id, permission_id | 角色与权限的关联 |
说白了,权限校验的流程就是:
- 通过用户ID,在
sys_user_role里找到所有角色ID - 通过角色ID,在
sys_role_permission里找到所有权限ID - 通过权限ID,在
sys_permission里拿到具体的权限编码 - 判断当前请求的权限编码是否在用户的权限列表中
3.7 扩展思考:数据权限怎么处理?
咱们今天聊的是「功能权限」——能不能访问某个页面或按钮。但CRM里还有个更头疼的问题:数据权限。
比如:销售A只能看自己的客户,销售主管能看整个团队的客户,管理员能看所有客户。这怎么设计?
嗯,这个咱们后面章节会详细讲。但简单提一句:数据权限通常不放在这5张表里,而是通过「数据权限规则表」来实现。比如定义一条规则:「用户只能查看 owner_id = 当前用户ID 的客户」。
好了,这一章就到这里。五张表的设计,说白了就是「用户-角色-权限」三层模型,加上两张关联表。把这个基础打牢了,后面再复杂的权限需求都能往上加。
下一章,咱们聊聊权限校验的代码实现——怎么在Spring Boot里用拦截器做权限校验。