【若依 | day36 2-底层原理】

拔高原理篇 - 飞书云文档 前三节


在若依框架的生态中,代码生成器、RBAC 权限模型、异步任务管理器是支撑企业级开发的 "三驾马车"------ 它们分别解决了 "重复编码""权限管控""异步任务调度" 的核心痛点。但你是否真正理解它们的底层设计逻辑?如何在实战中灵活运用?这篇深度解析将通过问答形式,带你吃透这三大组件的本质与实现。

一、若依代码生成器:为何能做到 "表结构驱动生成代码"?底层设计逻辑是什么?

问题 1:若依代码生成器的 "数据底座" 是什么?如何存储表结构与生成规则?

若依代码生成器的核心是 **"结构化数据 + 模板引擎"**,其 "数据底座" 依赖两张核心表(gen_tablegen_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_tablegen_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:删除点位;
  • permissionmanage: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 创建JobDetailTrigger

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 保障接口与按钮的权限安全,异步任务管理器支撑定时业务(如点位数据同步),三者共同构建了 "高效开发 + 安全管控 + 可靠调度" 的企业级开发体系。理解它们的底层设计,不仅能灵活使用若依,更能借鉴其思想构建自己的业务框架。

相关推荐
三角叶蕨7 天前
二次开发(部分)
若依
q_191328469510 天前
基于SpringBoot+Vue+若依的医院管理系统挂号系统
java·vue.js·spring boot·后端·毕业设计·若依
humors22115 天前
前端开发案例(不定期更新)
前端·vue.js·elementui·ruoyi·若依
q_19132846951 个月前
基于RuoYi框架+Mysql的汽车进销存后台管理系统
数据库·vue.js·spring boot·mysql·汽车·个人开发·若依
潇I洒3 个月前
若依4.8.1打包war后在Tomcat无法运行,404报错的一个解决方法
java·tomcat·ruoyi·若依·404
牛马程序员‍4 个月前
Day116 若依融合mqtt
java·mqtt·若依·mqttx
JosieBook4 个月前
【web应用】若依框架:基础篇18-二次开发-菜品管理
web·若依
弗锐土豆5 个月前
一个基于若依(ruoyi-vue3)的小项目部署记录
前端·vue.js·部署·springcloud·ruoyi·若依
山人在山上6 个月前
基于若依前后分离版-用户密码错误锁定
若依·用户登陆失败锁定