第四章. 核心数据库表结构解析

第四章. 核心数据库表结构解析)

摘要:本章我们将深入数据库层面,彻底解析“瘦身”后 RVP 5.x 框架的核心表结构。我们将从“表菜单”映射入手,识别每个表的功能,然后重点分析“租户”、“字典”等简单关联,最后将 重中-之重 放在 sys_user (用户) 表上,详细拆解其与角色、部门、岗位、菜单之间的 9 表关联(RBAC 模型),为你后续理解“权限控制”和“数据权限”打下最坚实的地基。

本章学习路径

我们将按照“从宏观到微观、从简单到复杂”的路径,一步步解构 RVP 的核心数据库:

核心表结构解析


4.1. 数据库表与功能菜单总览

在第三章中,我们成功完成了 ruoyi-workflow 模块的“架构瘦身”,得到了一个更轻量、启动更快的后端项目,数据库也变得更加整洁。然而,一个不熟悉数据库的开发者,就像一个不熟悉地基的建筑师。我们数据库中还剩下 gen_sj_sys_ 等几十张表。它们分别对应什么功能?

为了在后续章节中真正理解框架的原理(特别是权限控制),本节我们将严格按照教学文稿的指引,把这些剩余的表和后台管理界面的功能菜单进行一次“连连看”,彻底弄清每个表的功能。

为什么要建立映射?
在深入研究复杂的关系(如 RBAC)之前,我们必须先建立一个宏观的认识。知道哪个表支撑哪个功能,是我们后续阅读源码、进行二次开发的基础蓝图。

是什么?
“瘦身”后的数据库表,根据其功能和前缀,主要分为三大类:

  1. gen_...:代码生成模块。
  2. sj_...:SnailJob 任务调度模块。
  3. sys_...:系统核心模块(权限、租户、日志等)。我们将逐一解析这些表。

4.1.1. 扩展模块表(gen_sj_

这两类表属于 RVP 框架集成的扩展功能,它们与核心的 sys_ 表关联较少。

1. 代码生成(gen_...
这两张表对应的是后台管理界面中的 “系统工具” -> “代码生成” 菜单。

  • gen_table:存储代码生成的“主表”信息,例如您要为哪个表生成代码、作者是谁、生成的模块名叫什么。
  • gen_table_column:存储 gen_table 中每张表的“字段”信息,例如字段类型、是否为查询条件、使用什么 HTML 控件(如输入框、下拉框)等。

为了解决“代码生成”的需求,RVP 提供了 gen_table 表来存储元数据:

1
2
3
4
5
6
7
8
9
10
11
-- 代码生成-业务表 (简化版)
CREATE TABLE `gen_table` (
`table_id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`table_name` varchar(200) DEFAULT '' COMMENT '表名称',
`table_comment` varchar(500) DEFAULT '' COMMENT '表描述',
`class_name` varchar(100) DEFAULT '' COMMENT '实体类名称',
`module_name` varchar(30) DEFAULT '' COMMENT '生成模块名',
`function_author` varchar(50) DEFAULT '' COMMENT '作者',
`options` varchar(1000) DEFAULT NULL COMMENT '其它生成选项',
PRIMARY KEY (`table_id`)
) COMMENT='代码生成业务表';
  • 核心要点gen_tablegen_table_column 是代码生成器的“蓝图”,我们对它们的操作(增删改查)就是配置代码生成策略的过程。

2. 任务调度(sj_...
所有以 sj_ (SnailJob) 开头的表,对应的都是 “系统监控” -> “任务调度中心” 菜单。

  • sj_...:这些表(如 sj_job_info, sj_job_batch, sj_namespace 等)是 SnailJob 任务调度框架自身运行所需的核心表,用于存储任务信息、执行批次、命名空间等。
1
2
3
4
5
6
7
8
9
10
-- 任务调度-任务信息表 (SnailJob 简化版)
CREATE TABLE `sj_job_info` (
`id` bigint NOT NULL AUTO_INCREMENT,
`job_name` varchar(64) NOT NULL COMMENT '任务名称',
`job_type` tinyint NOT NULL DEFAULT '1' COMMENT '任务类型 1、定时任务 2、延迟任务',
`trigger_type` tinyint DEFAULT '1' COMMENT '触发类型 1.CRON 2.固定延时 3.固定频率',
`trigger_interval` varchar(20) DEFAULT NULL COMMENT 'CRON/延时/频率',
`job_status` tinyint DEFAULT '0' COMMENT '状态 0、停止 1、运行',
PRIMARY KEY (`id`)
) COMMENT='任务信息';

4.1.2. 系统核心表(sys_

这批表是 RVP 框架的基石,支撑着 系统管理租户管理 等核心功能,我们的学习重点将围绕它们展开。

  • sys_config:对应 “系统管理” -> “参数设置”。用于存储系统的全局配置项,如“主框架页-默认皮肤主题”。
  • sys_dept:对应 “系统管理” -> “部门管理”。存储组织架构(如“xx 公司”、“xx 部门”)。
  • sys_dict_typesys_dict_data:对应 “系统管理” -> “字典管理”type 存储字典类型(如 sys_user_sex - 用户性别),data 存储具体的键值对(如 0 = 男, 1 = 女)。
1
2
3
4
5
6
7
8
9
-- 字典类型表 (简化版)
CREATE TABLE `sys_dict_type` (
`dict_id` bigint NOT NULL AUTO_INCREMENT COMMENT '字典主键',
`dict_name` varchar(100) DEFAULT '' COMMENT '字典名称',
`dict_type` varchar(100) DEFAULT '' COMMENT '字典类型(全局唯一)',
`status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
PRIMARY KEY (`dict_id`),
UNIQUE KEY `dict_type` (`dict_type`)
) COMMENT='字典类型表';
  • sys_logininfor:对应 “系统管理” -> “日志管理” -> “登录日志”。记录用户的登录和登出行为。
  • sys_menu:对应 “系统管理” -> “菜单管理”。存储系统的所有菜单和按钮权限(如“系统管理”、“用户新增”)。
  • sys_notice:对应 “系统管理” -> “通知公告”
  • sys_oper_log:对应 “系统管理” -> “日志管理” -> “操作日志”。记录用户对系统的所有操作(增删改查)。
  • sys_osssys_oss_config:对应 “系统工具” -> “文件管理”config 存储 OSS(对象存储,如 Minio、阿里云)的配置,oss 存储上传的文件记录。
  • sys_post:对应 “系统管理” -> “岗位管理”。存储岗位信息(如“CEO”、“Java 工程师”)。
  • sys_role:对应 “系统管理” -> “角色管理”。存储角色信息(如“管理员”、“普通用户”)。
  • sys_social:对应 “系统管理” -> “三方登录”。存储绑定的第三方账号信息(如 Gitee、GitHub)。
  • sys_tenantsys_tenant_package:对应 “租户管理” 菜单。tenant 存储租户(公司)信息,package 存储租户可用的套餐(如“免费版”、“专业版”)。
  • sys_user:对应 “系统管理” -> “用户管理”。存储系统的登录用户信息,是 RBAC 权限的核心
1
2
3
4
5
6
7
8
9
10
11
-- 用户信息表 (简化版)
CREATE TABLE `sys_user` (
`user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
`dept_id` bigint DEFAULT NULL COMMENT '部门ID',
`user_name` varchar(30) NOT NULL COMMENT '用户账号',
`nick_name` varchar(30) NOT NULL COMMENT '用户昵称',
`password` varchar(100) DEFAULT '' COMMENT '密码',
`status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
PRIMARY KEY (`user_id`)
) COMMENT='用户信息表';

4.1.3. 核心关联表(中间表)

除了上述的“实体表”,数据库中还有几张表名类似 sys_a_b 的表,它们是连接各个实体的“桥梁”(中间表),用于实现“多对多”关系:

  • sys_role_dept:角色-部门关联表。
  • sys_role_menu:角色-菜单关联表。
  • sys_user_post:用户-岗位关联表。
  • sys_user_role:用户-角色关联表。

这些表是 RVP 权限设计的精髓,我们将在 4.3 节中详细解剖它们。


4.1.4. 本节小结

我们完成了数据库表的“宏观”认知,将它们与具体的功能菜单进行了映射。

核心要点

  • 数据库表按前缀可分为三大类:gen_(代码生成)、sj_(任务调度)和 sys_(系统核心)。
  • sys_ 表是框架的基石,包含了用户、角色、菜单、部门、岗位等核心实体。
  • sys_user_role 这类中间表是实现 RBAC 权限(多对多)的关键。

瘦身后的核心表清单(速查)

表前缀表名(示例)对应菜单(后台 UI)作用
gen_gen_table, gen_table_column系统工具 -> 代码生成存储代码生成的配置信息
sj_sj_job_info, sj_job_batch系统监控 -> 任务调度中心SnailJob 框架运行数据
sys_sys_user系统管理 -> 用户管理核心用户表
sys_sys_role系统管理 -> 角色管理核心角色表
sys_sys_menu系统管理 -> 菜单管理核心菜单/权限表
sys_sys_dept系统管理 -> 部门管理核心部门表
sys_sys_post系统管理 -> 岗位管理核心岗位表
sys_sys_dict_type, sys_dict_data系统管理 -> 字典管理存储字典键值对
sys_sys_tenant, sys_tenant_package租户管理租户及套餐信息
sys_sys_logininfor, sys_oper_log系统管理 -> 日志管理存储登录和操作日志
sys_sys_oss_config, sys_oss系统工具 -> 文件管理OSS 配置和文件记录
sys_sys_social系统管理 -> 三方登录社交登录绑定关系
sys_sys_user_role (中间表)(隐藏)连接用户和角色
sys_sys_role_menu (中间表)(隐藏)连接角色和菜单

4.2. 独立与简单关联表分析

在 4.1 节中,我们对所有核心表的功能和前缀(gen_、sj_、sys_)有了宏观认识,就像拿到了一张布满“点”的地图。但要理解它们如何协同工作,光看“点”是不够的,我们必须开始分析连接这些“点”的“线”(即表关系)。本节我们将从最简单的关系入手:分析那些“独立”的表和经典的“一对多”关联表,为后续理解复杂的多对多 RBAC 模型打下坚实的基础。


4.2.1. 独立表:系统配置与日志

在 RVP 框架中,有几张表的设计相对“独立”,它们不依赖于复杂的业务逻辑,而是为系统提供基础支持。我们首先来看 sys_config(参数配置表)和 sys_oper_log(操作日志表)。

参数配置表 (sys_config)

我们首先要理解,为什么需要 sys_config 这张表?在项目开发中,我们经常遇到一些需要动态调整的变量,例如“新用户初始密码”、“文件上传大小限制”等。如果将这些值 硬编码(Hardcoding,即写死在代码里),每次修改都需要后端开发人员修改代码、重新打包并部署服务,效率极低且风险高。

为了解决这个问题,RVP 设计了 sys_config 表,它本质上是一个数据库化的键值对(Key-Value)存储。

1
2
3
4
5
6
7
8
9
10
11
create table sys_config (
config_id bigint(20) not null comment '参数主键',
config_name varchar(100) default '' comment '参数名称',
config_key varchar(100) default '' comment '参数键名', -- 核心:程序靠它来取值
config_value varchar(500) default '' comment '参数键值', -- 核心:程序要取的值
config_type char(1) default 'N' comment '系统内置(Y是 N否)',
...
) comment = '参数配置表';

-- 示例数据
insert into sys_config values(2, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', ...);

在这张表中,config_key 是程序用来获取配置的唯一键。例如,RVP 中重置密码的功能,就会在代码中读取 sys.user.initPassword 的值。config_type 字段用于标记是否为系统内置,'Y'(是)通常表示这是框架运行必需的配置,防止管理员在后台误删。

在我们的二次开发中,这张表非常有用。设想一下,我们需要增加一个“全局文件上传大小限制”。正确的做法是:

  1. sys_config 表中插入一条新纪录:
    • config_key: sys.file.maxUploadSizeMb
    • config_value: 10
    • config_name: 全局上传大小限制(MB)
  2. 在 Java 代码中,注入 RVP 封装好的服务(如通过 RedisUtils 获取缓存)来读取 sys.file.maxUploadSizeMb 的值进行判断。

如此一来,当运营人员需要将限制调整到 20MB 时,他们只需在【系统管理】->【参数设置】菜单中修改这个值,系统即可动态生效,无需我们后端开发人员介入。

操作日志表 (sys_oper_log)

看完了配置表,我们再来分析另一张同样重要的独立表:sys_oper_log(操作日志表)。这张表是系统的“黑匣子”,用于安全审计和问题排查。

当线上数据出现异常(例如,一个用户的关键信息被恶意篡改,或一个重要订单被误删),我们需要有据可查,必须能追溯到:谁 (Who)什么时间 (When)哪个 IP (Where) 做了 什么操作 (What)参数是什么 (How),以及 结果是成功还是失败 (Result)

sys_oper_log 就是为了记录这一切而设计的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
create table sys_oper_log (
oper_id bigint(20) not null comment '日志主键',
title varchar(50) default '' comment '模块标题',
business_type int(2) default 0 comment '业务类型(0其它 1新增 2修改 3删除)',
method varchar(100) default '' comment '方法名称', -- 如:com.ruoyi.web.controller.system.SysUserController.add()
oper_name varchar(50) default '' comment '操作人员',
oper_url varchar(255) default '' comment '请求URL',
oper_ip varchar(128) default '' comment '主机地址',
oper_param varchar(4000)default '' comment '请求参数', -- 核心:记录了提交的JSON数据
json_result varchar(4000)default '' comment '返回参数',
status int(1) default 0 comment '操作状态(0正常 1异常)',
error_msg varchar(4000)default '' comment '错误消息',
oper_time datetime comment '操作时间',
cost_time bigint(20) default 0 comment '消耗时间',
...
) comment = '操作日志记录';

这张表是通过 AOP(面向切面编程) 技术实现的。RVP 框架定义了一个 @Log 注解,当我们在 Controller 的方法上标记这个注解时,AOP 切面会自动拦截该方法的请求和响应,并将 oper_name(操作人)、oper_ip(IP 地址)、oper_param(请求参数)、json_result(返回结果)等信息异步存入这张表。

在二次开发中,我们不需要手动操作这张表。我们只需要在我们自己编写的、涉及重要操作(增、删、改)的 Controller 方法上,也加上 @Log 注解,就能自动享受到完整的操作审计功能。


4.2.2. 一对多:字典类型与字典数据

理解了独立表,我们开始进入表与表之间最常见的一种关系:一对多。字典管理(sys_dict_typesys_dict_data)是这种设计的经典范例。

试想一个场景:系统中有很多地方需要用户选择“性别”,下拉框里是“男”、“女”、“未知”。我们不能在每个前端页面都写死这三个选项。如果哪天需要增加一个“保密”选项,岂不是要修改所有相关页面?

字典功能就是用来解决这个问题的。它将这些“可枚举”的数据变成了动态配置。

  • sys_dict_type (字典类型表):用于定义一个“类别”。
  • sys_dict_data (字典数据表):用于存储这个“类别”下的所有可选“数据项”。

它们的关系是:一个“类型”对应多个“数据项”

1. 字典类型表 (sys_dict_type)

1
2
3
4
5
6
7
8
9
create table sys_dict_type (
dict_id bigint(20) not null comment '字典主键',
dict_name varchar(100) default '' comment '字典名称', -- 如:'用户性别'
dict_type varchar(100) default '' comment '字典类型', -- 如:'sys_user_sex' (程序用这个key来查询)
...
) comment = '字典类型表';

-- 示例数据
insert into sys_dict_type values(1, '000000', '用户性别', 'sys_user_sex', ...);

这张表很简单,dict_name 是给人看的(如“用户性别”),dict_type 是给程序用的唯一标识(如 sys_user_sex)。

2. 字典数据表 (sys_dict_data)

这张表是关键,它通过 dict_type 字段与 sys_dict_type 表建立了关联。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
create table sys_dict_data (
dict_code bigint(20) not null comment '字典编码',
dict_sort int(4) default 0 comment '字典排序',
dict_label varchar(100) default '' comment '字典标签', -- 如:'男'(显示用)
dict_value varchar(100) default '' comment '字典键值', -- 如:'0'(存储用)
dict_type varchar(100) default '' comment '字典类型', -- 核心外键:关联 sys_dict_type.dict_type
list_class varchar(100) default null comment '表格回显样式', -- 如:'primary', 'danger' (用于控制颜色)
...
) comment = '字典数据表';

-- 示例数据 (注意它们的 dict_type 都是 'sys_user_sex')
insert into sys_dict_data values(1, '000000', 1, '男', '0', 'sys_user_sex', '', 'primary', 'Y', ...);
insert into sys_dict_data values(2, '000000', 2, '女', '1', 'sys_user_sex', '', 'danger', 'N', ...);
insert into sys_dict_data values(3, '000000', 3, '未知', '2', 'sys_user_sex', '', 'info', 'N', ...);

关联分析
sys_dict_type 中的一条记录 (dict_type = 'sys_user_sex') 对应了 sys_dict_data 中的三条记录。

  • 数据字典的运作原理:当我们需要在前端页面(如用户注册页)展示一个“性别”下拉框时:
    1. 前端向后端发起请求,调用“根据字典类型查询字典数据”的接口,参数为 sys_user_sex
    2. 后端根据 dict_type = 'sys_user_sex'sys_dict_data 表中查出“男(0)”、“女(1)”、“未知(2)”三条数据。
    3. 前端动态渲染下拉框。
    4. 用户选择了“男”,前端提交给后端的 value'0'
    5. 后端在 sys_user 表的 sex 字段中存储 '0'
    6. 当在列表页显示时,再根据 '0' 反向查询 sys_dict_data,得到 dict_label 为“男”,并在表格中显示“男”字。list_class 字段则用来控制这个“男”字显示什么颜色。

4.2.3. 一对多:租户与租户套餐

RVP 框架的一大特色是支持多租户(SaaS 平台)。租户相关的表设计也使用了一对多,但关系与字典有所不同。

  • sys_tenant_package (租户套餐表):定义了 SaaS 服务的不同等级,例如“基础版”、“专业版”、“旗舰版”。
  • sys_tenant (租户表):记录了所有入驻 SaaS 平台的“企业客户”。

它们的关系是:一个“套餐”可以被多个“租户”使用

1. 租户套餐表 (sys_tenant_package)

这是“一”的一方。它定义了套餐包含哪些功能。

1
2
3
4
5
6
create table sys_tenant_package (
package_id bigint(20) not null comment '租户套餐id', -- 主键
package_name varchar(20) comment '套餐名称', -- 如:'基础版'
menu_ids varchar(3000) comment '关联菜单id', -- 核心:套餐包含的菜单ID列表
...
) comment = '租户套餐表';

核心字段是 menu_ids,它以逗号分隔的方式存储了该套餐能使用的所有菜单 ID(关联 sys_menu 表)。

2. 租户表 (sys_tenant)

这是“多”的一方。它记录了企业信息,并通过 package_id 关联到套餐表。

1
2
3
4
5
6
7
8
9
10
11
12
13
create table sys_tenant (
id bigint(20) not null comment 'id',
tenant_id varchar(20) not null comment '租户编号', -- 核心:用于数据隔离
company_name varchar(30) comment '企业名称',
package_id bigint(20) comment '租户套餐编号', -- 核心外键:关联 sys_tenant_package.package_id
expire_time datetime comment '过期时间',
account_count int default -1 comment '用户数量(-1不限制)',
status char(1) default '0' comment '租户状态(0正常 1停用)',
...
) comment = '租户表';

-- 示例数据
insert into sys_tenant values(1, '000000', '管理组', ..., null, null, -1, '0', ...); -- 默认的超级管理租户

关联分析
sys_tenant 表中的 package_id 字段,指向了 sys_tenant_package 表的 package_id

  • 二次开发场景:这个设计是 SaaS 系统的核心。当我们开发新功能时,比如开发了一个“高级报表”菜单。
    1. 我们在 sys_menu 表中添加这个“高级报表”菜单(假设 menu_id 为 2001)。
    2. 运营人员在【租户管理】->【租户套餐】中,编辑“专业版”套餐,将 menu_id 2001 添加到其 menu_ids 字段中。
    3. 此时,所有购买了“基础版”套餐的租户(他们的 package_id 指向基础版)登录系统时,权限校验会发现他们的套餐不包含 2001 菜单,因此看不到“高级报表”。
    4. 而购买了“专业版”的租户,则可以正常看到并使用该功能。
    5. 这就实现了通过套餐配置动态控制不同企业客户功能可见性的目的。

4.2.4. 一对多:代码生成主表与字段表

最后我们来看 gen_ 前缀的这两张表。这是 RVP 的代码生成器功能所依赖的核心表。

  • gen_table (代码生成业务表):存储了要生成的“主表”信息。
  • gen_table_column (代码生成业务表字段):存储了该主表下的“所有字段”信息。

它们的关系是:一个“表”对应多个“字段”

1. 代码生成业务表 (gen_table)

1
2
3
4
5
6
7
8
9
10
11
create table gen_table (
table_id bigint(20) not null comment '编号', -- 主键
table_name varchar(200) default '' comment '表名称', -- 如:'sys_user'
table_comment varchar(500) default '' comment '表描述', -- 如:'用户信息表'
class_name varchar(100) default '' comment '实体类名称', -- 如:'SysUser'
package_name varchar(100) comment '生成包路径', -- 如:'com.ruoyi.system'
module_name varchar(30) comment '生成模块名', -- 如:'system'
business_name varchar(30) comment '生成业务名', -- 如:'user'
function_name varchar(50) comment '生成功能名', -- 如:'用户'
...
) comment = '代码生成业务表';

这张表存储了我们在【代码生成】界面上配置的各种信息,例如“包路径”、“模块名”、“作者”等。

2. 代码生成业务表字段 (gen_table_column)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
create table gen_table_column (
column_id bigint(20) not null comment '编号',
table_id bigint(20) comment '归属表编号', -- 核心外键:关联 gen_table.table_id
column_name varchar(200) comment '列名称', -- 如:'user_name'
column_type varchar(100) comment '列类型', -- 如:'varchar(30)'
java_type varchar(500) comment 'JAVA类型', -- 如:'String'
java_field varchar(200) comment 'JAVA字段名', -- 如:'userName'
is_pk char(1) comment '是否主键(1是)',
is_required char(1) comment '是否必填(1是)',
is_list char(1) comment '是否列表字段(1是)',
is_query char(1) comment '是否查询字段(1是)',
query_type varchar(200) default 'EQ' comment '查询方式(等于、不等于、大于、小于、范围)',
html_type varchar(200) comment '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)',
dict_type varchar(200) default '' comment '字典类型', -- 如:'sys_user_sex'
...
) comment = '代码生成业务表字段';

关联分析
gen_table_column 表中的 table_id 字段,指向了 gen_table 表的 table_id

当我们点击“导入”按钮,从数据库中选择一张表(如 sys_user)时:

  1. RVP 会读取 sys_user 的表信息,在 gen_table 中创建一条记录。
  2. 同时,RVP 会读取 sys_user 的所有字段(如 user_id, user_name, sex…),并在 gen_table_column 中创建多条记录,这些记录的 table_id 都指向第 1 步中创建的主表 ID。

当我们点击“编辑”时,会看到一个包含两部分的界面:

  • 上半部分(基本信息、生成信息):修改的数据对应 gen_table
  • 下半部分(字段信息列表):修改的数据对应 gen_table_column

这张 gen_table_column 表非常关键。它通过 html_type 告诉生成器这个字段在前端应该渲染成“文本框”还是“下拉框”;通过 dict_type 告诉生成器如果这是一个下拉框,应该去加载哪个字典(如 sys_user_sex);通过 is_query 告诉生成器这个字段是否作为查询条件。

理解了这张表的配置,我们就掌握了 RVP 代码生成器的精髓。


4.2.5. 本节小结

我们学习了独立表和“一对多”关联表的设计思想,这是理解 RVP 框架的基石。

  • 独立表sys_config(参数配置)和 sys_oper_log(操作日志)是系统的基础支撑。
    • sys_config 用于实现“配置与代码分离”,便于动态调整系统参数。
    • sys_oper_log 基于 AOP 实现,用于安全审计和问题排查。
  • 一对多关系:是企业级系统中最常见的数据模型。
    • 字典 (Type/Data)sys_dict_type (一) 对应 sys_dict_data (多),用于管理系统中的枚举值(如下拉框选项)。
    • 租户 (Package/Tenant)sys_tenant_package (一) 对应 sys_tenant (多),用于实现 SaaS 平台的套餐功能隔离。
    • 代码生成 (Table/Column)gen_table (一) 对应 gen_table_column (多),用于存储代码生成器的元数据配置。

4.3. RBAC 权限模型:9 表关联详解

在 4.2 节中,我们掌握了系统设计中常见的“一对多”关系,例如字典类型与字典数据、租户套餐与租户。但在实际的权限管理中,“一对多”是远远不够的。

设想一个场景:一个“财务经理”角色,他需要访问“报表中心”、“工资条管理”、“审批管理”三个菜单。同时,“张三”和“李四”都是“财务经理”。

  • 一个用户(张三)可以有多个角色(例如他同时也是“数据分析师”)。
  • 一个角色(财务经理)可以被多个用户(张三、李四)拥有。
  • 一个角色(财务经理)可以访问多个菜单。
  • 一个菜单(报表中心)可以被多个角色(财务经理、数据分析师)访问。

这些都是“多对多”关系。本节我们将聚焦于 RVP 框架的精髓——RBAC(Role-Based Access Control,基于角色的访问控制)模型。我们将彻底解析支撑这套模型运行的 5 张核心实体表及其 4 张关联表,共 9 张表,这是理解整个系统权限设计的地基。


4.3.1. 什么是 RBAC?

RBAC(Role-Based Access Control)是企业级应用权限设计的行业标准。

它的核心思想是 解耦“用户”与“权限”

  • 没有 RBAC 的糟糕设计:直接将“权限”(如访问 sys_menu 表中的菜单)分配给“用户”(sys_user 表)。

    • 痛点:假设我们有 1000 个用户和 100 个菜单项。当新员工“王五”入职时,我们需要手动从 100 个菜单中为他勾选 50 个他需要的权限。当下个月“工资条管理”菜单下线,换成“薪酬系统”时,我们必须找出所有拥有该权限的 800 个用户,逐一为他们移除旧权限、添加新权限。这是“运维灾难”。
  • 采用 RBAC 的优雅设计:我们在“用户”和“权限”之间增加了一个抽象层,叫做“角色”(sys_role 表)。

    1. 我们将“权限”(菜单)分配给“角色”。例如,创建“财务经理”角色,并为其分配“报表中心”、“工资条管理”等 50 个菜单权限。
    2. 我们将“角色”分配给“用户”。

    在这种模型下,当新员工“王五”入职时,我们 只需要给他分配一个“财务经理”角色,他就立即自动获得了该角色所绑定的 50 个菜单权限。当下个月“薪酬系统”替换“工资条管理”时,我们 只需要修改“财务经理”这一个角色的权限,所有拥有该角色的 800 个用户(包括张三、李四、王五)的权限就同步更新了。

这就是 RBAC 的威力:它将对“人”的管理转变为对“角色”的管理,极大提升了系统的可维护性和扩展性。


4.3.2. 9 大核心表关系模型

RVP 的 RBAC 模型正是基于上述思想构建的,并在此基础上扩展了“部门”和“岗位”的概念,形成了 9 张表的核心体系。

这 9 张表可以分为两类:

1. 实体表(5 张):系统的“名词”,代表具体的实体。

  • sys_user(用户信息表)
  • sys_role(角色信息表)
  • sys_menu(菜单权限表)
  • sys_dept(部门表)
  • sys_post(岗位信息表)

2. 中间表/连接表(4 张):系统的“连线”,用于实现实体之间的“多对多”关系。

  • sys_user_role(用户和角色关联表)
  • sys_role_menu(角色和菜单关联表)
  • sys_user_post(用户与岗位关联表)
  • sys_role_dept(角色和部门关联表)

这 9 张表协同工作,支撑了 RVP 的两大权限体系:

  1. 菜单权限(功能权限)

    • 决定了“你能看到哪些菜单、能点击哪些按钮”。
    • 实现路径:用户 -> 角色 -> 菜单 (sys_user -> sys_user_role -> sys_role -> sys_role_menu -> sys_menu)
  2. 数据权限

    • 决定了“在同一个菜单下,你能看到哪些人的数据”。(例如,A 部门经理只能看 A 部门的订单,B 部门经理只能看 B 部门的订单)
    • 实现路径:用户 -> 角色 -> 部门 (sys_user -> sys_user_role -> sys_role -> sys_role_dept -> sys_dept)

4.3.3. 用户表 (sys_user):一切的核心

sys_user 表是整个权限模型的起点,所有关系都围绕“人”展开。

1
2
3
4
5
6
7
8
9
create table sys_user (
user_id bigint(20) not null comment '用户ID',
tenant_id varchar(20) default '000000' comment '租户编号',
dept_id bigint(20) default null comment '部门ID',
user_name varchar(30) not null comment '用户账号',
nick_name varchar(30) not null comment '用户昵称',
password varchar(100) default '' comment '密码',
...
) comment = '用户信息表';

核心字段分析

  • user_id:主键,所有中间表都将使用它来关联用户。
  • tenant_id:租户编号。这是 SaaS 架构的基础,它确保了 A 公司的 admin 和 B 公司的 admin 是两个完全不同的用户,他们的数据被 tenant_id 严格隔离。我们后续分析的所有核心表(sys_role, sys_dept 等)都有这个字段。
  • dept_id这是一个关键字段。它是一个 直接外键,指向 sys_dept.dept_id

重要理解sys_user 表中的 dept_id 代表了该用户的“归属部门”或“主部门”。这是一个“一对多”关系(一个部门可以有多个用户)。这与后续的数据权限(如“仅看本部门数据”)密切相关。


4.3.4. 部门与岗位 (sys_dept 与 sys_post)

sys_dept(部门)和 sys_post(岗位)定义了企业的组织架构。

1. 部门表 (sys_dept)

sys_dept 不仅仅是一张简单的信息表,它是一个 树形结构

1
2
3
4
5
6
7
8
9
10
11
12
13
create table sys_dept (
dept_id bigint(20) not null comment '部门id',
parent_id bigint(20) default 0 comment '父部门id',
ancestors varchar(500) default '' comment '祖级列表',
dept_name varchar(30) default '' comment '部门名称',
order_num int(4) default 0 comment '显示顺序',
...
) comment = '部门表';

-- 示例数据
insert into sys_dept values(100, '000000', 0, '0', 'XXX科技', ...);
insert into sys_dept values(101, '000000', 100, '0,100', '深圳总公司', ...);
insert into sys_dept values(103, '000000', 101, '0,100,101', '研发部门', ...);

核心字段分析(树形结构)

  • parent_id:指向父节点的 dept_id。根节点(如“XXX 科技”)的 parent_id 为 0。
  • ancestors这是一个关键的性能优化字段。它用逗号分隔,存储了从根节点到当前节点的所有父级 ID 路径。
    • 为什么需要 ancestors
    • 试想一个需求:“查询研发部门(id = 103)下的所有子部门”。如果只有 parent_id,我们需要编写一个复杂的递归 SQL 查询,性能很差。
    • 有了 ancestors 字段,查询变得极其简单高效:SELECT * FROM sys_dept WHERE ancestors LIKE '%,103,%'(查询所有祖级列表中包含 103 的部门)。
    • RVP 框架在实现“本部门及以下数据权限”时,正是严重依赖了这个字段。

2. 岗位信息表 (sys_post)

sys_post 用于定义具体的“职位”,如“董事长”、“项目经理”、“普通员工”。

1
2
3
4
5
6
7
create table sys_post (
post_id bigint(20) not null comment '岗位ID',
post_code varchar(64) not null comment '岗位编码',
post_name varchar(50) not null comment '岗位名称',
post_sort int(4) not null comment '显示顺序',
...
) comment = '岗位信息表';

在 RVP 5.x 的瘦身版 SQL 中,sys_post 表本身不直接关联 sys_dept。它是一个独立的岗位字典。

一个“用户”和一个“岗位”的关系是如何建立的呢?答案是通过我们前面提到的中间表 sys_user_post

1
2
3
4
5
create table sys_user_post (
user_id bigint(20) not null comment '用户ID',
post_id bigint(20) not null comment '岗位ID',
primary key(user_id, post_id)
) comment = '用户与岗位关联表';

这张表清晰地定义了 sys_usersys_post 之间的“多对多”关系,允许一个用户(如某个部门经理)同时兼任另一个项目的“项目经理”岗位。


4.3.5. 本节小结

我们详细拆解了 RBAC 模型的 9 张核心表,重点理解了 5 张实体表(用户、角色、菜单、部门、岗位)和 4 张中间表(用户-角色、角色-菜单、用户-岗位、角色-部门)的职责。

  • RBAC 核心:在“用户”和“权限”之间建立“角色”层,实现解耦,提升可维护性。
  • 9 表模型:5 张实体表(名词) + 4 张中间表(连线)。
  • sys_user:权限模型的中心,dept_id 是其“归属部门”的直接外键。
  • sys_dept:通过 parent_idancestors 字段实现了高性能的树形结构,是“数据权限”的基石。
  • 多对多关系:通过 sys_user_role, sys_role_menu, sys_user_post 等中间表实现。