拔高原理篇 - 飞书云文档 前三节
在若依框架的生态中,代码生成器、RBAC 权限模型、异步任务管理器是支撑企业级开发的 "三驾马车"------ 它们分别解决了 "重复编码""权限管控""异步任务调度" 的核心痛点。但你是否真正理解它们的底层设计逻辑?如何在实战中灵活运用?这篇深度解析将通过问答形式,带你吃透这三大组件的本质与实现。
一、若依代码生成器:为何能做到 "表结构驱动生成代码"?底层设计逻辑是什么?
问题 1:若依代码生成器的 "数据底座" 是什么?如何存储表结构与生成规则?
若依代码生成器的核心是 **"结构化数据 + 模板引擎"**,其 "数据底座" 依赖两张核心表(gen_table、gen_table_column)实现表结构与生成规则的持久化:
1. gen_table(业务表元信息表)------ 存储 "表级配置"
| 核心字段 | 作用 |
|---|---|
table_name |
数据库表名(如tb_node),关联物理表 |
table_comment |
表描述(如 "点位表"),用于生成实体类注释、页面标题 |
class_name |
生成的实体类名(如Node),支持自定义 |
module_name |
生成代码所属模块(如manage),决定代码存放路径(com.ruoyi.manage) |
gen_path |
代码生成根路径(默认/),结合模块名形成完整路径 |
author |
代码作者(如admin),写入类注释 |
template_id |
关联模板 ID(若依支持多模板配置,如 "Vue2 模板""Vue3 模板") |
2. gen_table_column(字段元信息表)------ 存储 "字段级配置"
| 核心字段 | 作用 |
|---|---|
column_name |
数据库字段名(如node_name) |
column_type |
字段类型(如varchar(50)),用于映射 Java 类型(String) |
column_comment |
字段描述(如 "点位名称"),生成页面标签、字段注释 |
is_pk |
是否主键(1是 /0否),主键字段会自动生成@TableId注解 |
is_required |
是否必填(1是 /0否),生成前端表单校验规则 |
html_type |
前端组件类型(如input/select/textarea),决定页面渲染组件 |
dict_type |
字典类型(如business_type),关联字典表实现下拉框选项 |
设计逻辑:通过这两张表,若依将 "物理表结构" 转换为 "可配置的元数据",后续代码生成只需读取元数据,结合模板即可输出代码 ------ 这也是 "表结构驱动" 的本质。
问题 2:若依代码生成器的 "模板引擎" 如何工作?模板与数据如何结合?
若依采用Freemarker作为模板引擎(也支持 Velocity),其核心是 "模板文件 + 数据模型 = 生成代码":
1. 模板文件的结构(以后端为例)
若依的模板存放在resources/templates/generator目录下,按功能划分:
java/domain.java.ftl:实体类模板(生成Node.java);java/mapper.java.ftl:Mapper 接口模板;java/service.java.ftl:Service 接口与实现类模板;java/controller.java.ftl:Controller 接口模板;vue/index.vue.ftl:前端列表页模板。
2. 数据模型的构建
生成代码时,若依会将gen_table和gen_table_column的数据封装为Map结构的模型,例如:
java
运行
Map<String, Object> dataModel = new HashMap<>();
dataModel.put("tableName", genTable.getTableName()); // 表名
dataModel.put("className", genTable.getClassName()); // 实体类名
dataModel.put("columns", genTableColumnList); // 字段列表(包含每个字段的配置)
dataModel.put("moduleName", genTable.getModuleName()); // 模块名
3. 模板渲染流程
java
运行
// 获取模板文件
Template template = configuration.getTemplate("java/domain.java.ftl");
// 将数据模型写入模板,生成代码字符串
StringWriter writer = new StringWriter();
template.process(dataModel, writer);
// 将字符串写入文件
FileUtils.writeStringToFile(new File(outputPath + "/Node.java"), writer.toString(), "UTF-8");
核心优势:模板与数据分离,开发者可通过修改模板(如调整 Controller 返回格式)实现自定义代码风格,无需改动生成逻辑。
二、若依 RBAC 权限模型:如何实现从 "菜单权限" 到 "按钮权限" 的细粒度管控?
问题 1:若依 RBAC 的核心表结构是什么?如何实现 "用户 - 角色 - 权限" 的关联?
若依的 RBAC 是 **"用户 - 角色 - 菜单(权限)" 三层模型 **,依赖 5 张核心表实现关联:
1. 基础表关系
| 表名 | 作用 | 核心关联字段 |
|---|---|---|
sys_user |
用户表 | user_id(主键) |
sys_role |
角色表 | role_id(主键) |
sys_menu |
菜单 / 权限表 | menu_id(主键) |
sys_user_role |
用户 - 角色关联表 | user_id + role_id |
sys_role_menu |
角色 - 菜单关联表 | role_id + menu_id |
2. sys_menu表的特殊设计(关键!)
sys_menu不仅存储菜单(如 "点位管理"),还存储按钮级权限 (如 "新增点位""删除点位"),通过type字段区分:
type=0:目录(如 "系统管理");type=1:菜单(如 "用户管理");type=2:按钮 / 权限(如 "sys:user:add")。
举例 :"删除点位" 按钮对应的sys_menu记录:
menu_name:删除点位;permission:manage:node:remove;type:2;parent_id:关联 "点位管理" 菜单的menu_id。
3. 权限关联逻辑
- 一个用户可关联多个角色(
sys_user_role); - 一个角色可关联多个菜单 / 权限(
sys_role_menu); - 最终用户的权限集合 = 所属角色的权限集合的并集。
问题 2:若依如何在代码层面实现权限校验?从 "登录鉴权" 到 "按钮控制" 的流程是什么?
1. 登录时的权限加载
用户登录成功后,若依会通过SysMenuService查询该用户的所有权限(permission字段),并存储到SecurityContextHolder中:
java
运行
// 获取用户权限列表
List<String> permissions = menuService.selectMenuPermsByUserId(userId);
// 将权限存入Authentication
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
2. 接口级权限校验(@PreAuthorize 注解)
Controller 层通过@PreAuthorize注解校验权限:
java
运行
@PreAuthorize("@ss.hasPermi('manage:node:add')")
@PostMapping("/add")
public AjaxResult add(@RequestBody Node node) {
return toAjax(nodeService.insertNode(node));
}
其中@ss.hasPermi()是若依自定义的权限校验方法,会对比当前用户的权限集合是否包含manage:node:add。
3. 前端按钮级权限控制(v-hasPermi 指令)
前端通过自定义指令v-hasPermi控制按钮显示:
html
预览
<el-button
type="primary"
icon="Plus"
@click="handleAdd"
v-hasPermi="['manage:node:add']"
>新增</el-button>
指令内部会读取 Vuex 中存储的权限集合,若不包含该权限则隐藏按钮:
javascript
运行
// v-hasPermi指令实现
export default {
mounted(el, binding) {
const { value } = binding;
const permissions = store.getters.permissions; // 用户权限集合
if (!permissions.includes(value[0])) {
el.parentNode.removeChild(el); // 移除无权限的按钮
}
}
};
4. 菜单路由的动态生成
前端路由并非硬编码,而是通过后端返回的用户菜单列表动态生成:
javascript
运行
// 获取用户菜单并生成路由
export function generateRoutes() {
return new Promise((resolve) => {
menuApi.getRouters().then(res => {
const asyncRoutes = filterAsyncRoutes(res.data); // 过滤出有权限的路由
router.addRoutes(asyncRoutes); // 动态添加路由
resolve(asyncRoutes);
});
});
}
核心价值:从 "URL 拦截" 到 "按钮显示" 的全链路权限管控,避免越权操作。
三、若依异步任务管理器:基于 Quartz 的任务调度如何保证可靠执行与监控?
问题 1:若依异步任务管理器的底层依赖是什么?如何存储任务配置与执行日志?
若依的异步任务管理器基于Quartz 框架实现,同时扩展了任务配置表和日志表,确保任务可配置、可监控:
1. 核心依赖
xml
<!-- Quartz核心依赖 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<!-- Spring整合Quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
2. 任务配置表:sys_job
| 核心字段 | 作用 |
|---|---|
job_id |
任务 ID(主键) |
job_name |
任务名称 |
job_group |
任务分组(默认DEFAULT) |
invoke_target |
执行目标(如com.ruoyi.quartz.task.NodeTask::syncNodeData) |
cron_expression |
Cron 表达式(如0 0/5 * * * ?,每 5 分钟执行) |
status |
任务状态(0正常 /1暂停) |
concurrent |
是否并发(0允许 /1禁止) |
3. 任务日志表:sys_job_log
记录任务执行结果:job_log_id(日志 ID)、job_id(关联任务)、status(执行状态)、error_msg(异常信息)、times(执行耗时)等。
问题 2:若依如何实现任务的 "动态调度"?暂停、恢复、立即执行的底层逻辑是什么?
1. 任务的初始化与启动
项目启动时,若依会读取sys_job中状态为 "正常" 的任务,通过 Quartz 的 API 创建JobDetail和Trigger:
java
运行
// 创建JobDetail(绑定任务执行类)
JobDetail jobDetail = JobBuilder.newJob(QuartzJobExecution.class)
.withIdentity(job.getJobName(), job.getJobGroup())
.build();
// 设置JobDataMap(存储执行目标)
jobDetail.getJobDataMap().put("invokeTarget", job.getInvokeTarget());
// 创建CronTrigger(绑定Cron表达式)
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(job.getJobName(), job.getJobGroup())
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression()))
.build();
// 将任务添加到调度器
scheduler.scheduleJob(jobDetail, trigger);
其中QuartzJobExecution是若依自定义的任务执行类,负责反射调用invoke_target指定的方法。
2. 任务的暂停与恢复
-
暂停任务 :通过调度器暂停 Trigger;
java
运行
scheduler.pauseTrigger(TriggerKey.triggerKey(job.getJobName(), job.getJobGroup())); -
恢复任务 :恢复 Trigger 的状态;
java
运行
scheduler.resumeTrigger(TriggerKey.triggerKey(job.getJobName(), job.getJobGroup()));
3. 任务的立即执行
无需等待 Cron 表达式触发,直接通过triggerJob方法执行:
java
运行
scheduler.triggerJob(JobKey.jobKey(job.getJobName(), job.getJobGroup()));
4. 任务执行的异常处理
若任务执行抛出异常,会捕获并记录到sys_job_log:
java
运行
try {
// 反射执行任务方法
MethodInvokeUtil.invokeMethod(invokeTarget);
// 记录成功日志
jobLog.setStatus("0");
} catch (Exception e) {
// 记录失败日志
jobLog.setStatus("1");
jobLog.setErrorMsg(e.getMessage());
} finally {
jobLogService.insertJobLog(jobLog);
}
核心优势:任务配置可视化(页面可编辑 Cron 表达式)、执行状态可追溯(日志记录)、支持动态调整(无需重启项目)。
总结:三大组件的协同价值
若依的代码生成器、RBAC、异步任务管理器并非孤立存在 ------ 代码生成器可一键生成带权限注解的业务代码,RBAC 保障接口与按钮的权限安全,异步任务管理器支撑定时业务(如点位数据同步),三者共同构建了 "高效开发 + 安全管控 + 可靠调度" 的企业级开发体系。理解它们的底层设计,不仅能灵活使用若依,更能借鉴其思想构建自己的业务框架。