4. 数据库设计:用户、角色、权限与多租户
这一章我们聊聊数据库设计。说白了,行级权限落地好不好,一半看表结构设计得怎么样。
我见过不少项目,一开始图省事,把权限逻辑全写在代码里。结果业务一扩张,改权限就像拆炸弹——牵一发动全身。所以,先把表结构理清楚,后面会省很多事。
4.1 核心表:用户、角色、权限
先看最基础的三张表。它们构成了权限系统的骨架。
| 表名 | 核心字段 | 说明 |
|---|---|---|
| user | id, username, password, tenant_id, status | 用户表,关联租户 |
| role | id, name, code, tenant_id, description | 角色表,code 用于硬编码判断 |
| permission | id, name, code, type, parent_id | 权限表,树形结构 |
用户和角色是多对多关系,所以需要一张关联表 user_role。角色和权限也是多对多,需要 role_permission。
关键点:权限表的 code 字段要全局唯一。我习惯用 模块:操作 的格式,比如 order:create、report:export。这样在代码里做鉴权时,直接匹配字符串就行,不用查数据库。
4.2 数据权限规则表:行级权限的核心
这才是本章的重点。用户能看哪些数据行,就靠这张表来控制。
CREATE TABLE data_permission_rule (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
role_id BIGINT NOT NULL COMMENT '角色ID',
table_name VARCHAR(100) NOT NULL COMMENT '目标表名',
field_name VARCHAR(100) NOT NULL COMMENT '过滤字段',
operator VARCHAR(20) NOT NULL COMMENT '操作符: eq, ne, gt, lt, in, like',
field_value VARCHAR(500) NOT NULL COMMENT '字段值',
logic_type TINYINT DEFAULT 0 COMMENT '0=AND, 1=OR',
tenant_id BIGINT NOT NULL,
create_time DATETIME
);
这张表的设计思路,说白了就是把 SQL 的 WHERE 条件拆成结构化数据。
举个例子:销售经理只能看自己部门的订单。那规则就是:table_name = 'order',field_name = 'dept_id',operator = 'eq',field_value = '${currentUser.deptId}'。
个人经验:field_value 里可以放变量占位符,比如 ${userId}、${deptId}。系统在解析时自动替换成当前用户的信息。这样一条规则就能适配所有用户,不用每人建一条。
4.3 多租户字段设计
多租户是行级权限的天然搭档。每个租户的数据必须物理或逻辑隔离。
我建议在每张业务表里都加上 tenant_id 字段。查询时自动带上这个条件,从源头避免数据越界。
| 方案 | 优点 | 缺点 |
|---|---|---|
| 共享表 + tenant_id | 维护简单,成本低 | 查询量大时需注意索引 |
| 独立数据库 | 隔离性最强 | 成本高,跨库查询麻烦 |
| 独立 Schema | 折中方案 | 数据库数量仍较多 |
大多数 SaaS 项目用第一种就够了。我在项目中遇到过租户数量上千的情况,共享表加 tenant_id 配合分区表,性能完全扛得住。
避坑指南:我曾经在查询时忘了加 tenant_id 条件,结果一个租户看到了另一个租户的数据。虽然只是测试环境,但冷汗都出来了。从那以后,我强制所有 DAO 层查询都通过一个公共拦截器自动拼接 tenant_id 条件。
4.4 整体关系图
下面这张图展示了用户、角色、权限、数据规则和多租户之间的关系。
4.5 建表脚本示例
最后给出一份完整的建表脚本。你可以直接拿去用,稍作调整就能适配你的项目。
-- 用户表
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(255) NOT NULL,
`tenant_id` bigint NOT NULL,
`status` tinyint DEFAULT '1',
`dept_id` bigint DEFAULT NULL,
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_tenant` (`tenant_id`)
);
-- 角色表
CREATE TABLE `role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`code` varchar(50) NOT NULL,
`tenant_id` bigint NOT NULL,
`description` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_code_tenant` (`code`, `tenant_id`)
);
-- 权限表
CREATE TABLE `permission` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`code` varchar(100) NOT NULL,
`type` tinyint DEFAULT '1' COMMENT '1=菜单 2=按钮 3=API',
`parent_id` bigint DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_code` (`code`)
);
-- 用户角色关联表
CREATE TABLE `user_role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL,
`role_id` bigint NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_role` (`user_id`, `role_id`)
);
-- 角色权限关联表
CREATE TABLE `role_permission` (
`id` bigint NOT NULL AUTO_INCREMENT,
`role_id` bigint NOT NULL,
`permission_id` bigint NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_role_perm` (`role_id`, `permission_id`)
);
-- 数据权限规则表
CREATE TABLE `data_permission_rule` (
`id` bigint NOT NULL AUTO_INCREMENT,
`role_id` bigint NOT NULL,
`table_name` varchar(100) NOT NULL,
`field_name` varchar(100) NOT NULL,
`operator` varchar(20) NOT NULL,
`field_value` varchar(500) NOT NULL,
`logic_type` tinyint DEFAULT '0',
`tenant_id` bigint NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_role_table` (`role_id`, `table_name`)
);
嗯,这套表结构我在三个项目里验证过,支撑过百万级用户和上千租户的场景。你拿过去用,基本不用大改。