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 整体关系图

下面这张图展示了用户、角色、权限、数据规则和多租户之间的关系。

行级权限核心表关系图 用户表 (user) 角色表 (role) 权限表 (permission) 用户角色关联 角色权限关联 数据权限规则表 多租户 (tenant_id) 所有业务表都通过 tenant_id 与多租户关联,数据权限规则表控制行级可见性

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`)
);

嗯,这套表结构我在三个项目里验证过,支撑过百万级用户和上千租户的场景。你拿过去用,基本不用大改。