外观
若依框架 学习笔记
若依(RuoYi)是一套基于 Spring Boot 的快速开发框架,内置用户/角色/权限、代码生成、定时任务、日志监控等开箱即用模块,主打"拿来即改",用于快速搭建企业级后台管理系统。
1. 简单了解若依系统自动的强大功能
【重点要点】
- 技术底座:Spring Boot 2.x + Spring Security(Vue 前后端分离版)+ MyBatis + Redis + Vue/ElementUI,数据库默认 MySQL,连接池 Druid
- 内置成熟功能:用户/角色/菜单/部门/岗位/字典/参数 管理、操作与登录日志、在线用户、定时任务、数据监控(Druid)、服务监控、文件上传、代码生成
- Maven 多模块划分:ruoyi-admin(启动)、ruoyi-framework(安全/框架核心)、ruoyi-system(系统管理业务)、ruoyi-quartz(定时任务)、ruoyi-generator(代码生成)、ruoyi-common(公共工具类)
- 易错点:二次开发新业务放独立业务模块,尽量不改 framework 与系统核心表结构;敏感操作记得补 @Log 与权限校验
yaml
# application.yml 关键配置
spring:
datasource:
druid:
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8
username: root
password: root
redis:
host: localhost
port: 6379
server:
port: 80802. 借助生成代码,定时任务,权限管理功能实现个性化业务的开发
【重点要点】
- 代码生成三步走:系统工具 → 代码生成 → 导入表;编辑配置(包名/模块名/功能名/上级菜单/生成类型);生成或下载代码,最后执行生成的 menu.sql 注册菜单
- 生成类型:单表;主子表(一对多,配置子表及外键关联);树表(如部门/分类,配置树编码、父编码字段)
- 定制方式:改 resources/vm 下模板(Java/Vue)可全局调整生成风格;生成代码后按业务补校验与 @Transactional 事务
- 定时任务接入业务:任务管理里"调用目标"填反射字符串
bean名.方法名('参数'),如ryTask.ryParams('ry') - 权限三件套:菜单(前端按钮显隐)+ 权限标识 + @PreAuthorize 后端校验,缺一不可
java
// 列表与编辑接口的权限注解
@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/list")
public TableDataInfo list(SysUser user) { return getDataTable(userService.selectUserList(user)); }
@PreAuthorize("@ss.hasPermi('system:user:edit')")
@Log(title = "用户管理", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody SysUser user) { ... }yaml
# 定时任务示例(任务管理中配置)
# 调用目标:ryTask.ryParams('ry')
# cron:0/10 * * * * ? # 每 10 秒执行
# 是否并发执行:否(防止上次未结束再次触发)3. 了解EasyExcel框架的使用
【重点要点】
- 定位:阿里巴巴 EasyExcel,基于 SAX 逐行解析,内存占用远低于 POI,几万~几十万行导出/导入首选
- 模型绑定:@ExcelProperty("列名") 映射字段;@ExcelIgnore 忽略;复杂表头用字符串数组;@ExcelProperty(index=0) 按列序适配动态表头
- 导出:
EasyExcel.write(输出流, 实体.class).sheet("名").doWrite(list);大数据量分页查询 + 分批 doWrite - 导入:必须实现 ReadListener(invoke 每行回调、doAfterAllAnalysed 收尾),边读边入库,不要攒全量 List
- 扩展:实现 Converter<T> 处理日期/枚举/下拉;WriteHandler 定制样式与合并单元格
- 易错点:字段与列名不匹配直接抛错;日期需指定 format;文件尾部空行/全空数据要注意容错
java
@Data
public class Student {
@ExcelProperty("姓名")
private String name;
@ExcelProperty(value = "出生日期", format = "yyyy-MM-dd")
private Date birthday;
@ExcelIgnore
private String remark;
}
// 导出
EasyExcel.write(response.getOutputStream(), Student.class)
.sheet("学生列表").doWrite(list);
// 导入(监听器方式)
EasyExcel.read(inputStream, Student.class, new AnalysisEventListener<Student>() {
@Override
public void invoke(Student data, AnalysisContext context) { save(data); }
@Override
public void doAfterAllAnalysed(AnalysisContext context) { }
}).sheet().doRead();4. 了解x-file-storage技术,快速实现统一云服务的配置
【重点要点】
- 定位:dromara 开源的文件存储统一抽象框架,一套 API 对接 本地磁盘、MinIO、阿里 OSS、腾讯 COS、华为 OBS、七牛、FTP、S3
- 配置:yaml 声明多个平台(platform 唯一标识),default-platform 指定默认平台;按需设置公开/私有桶
- 核心 API:FileStorageService 链式调用
of(文件).setPath(目录).setSaveFilename(重命名).upload(),返回 FileInfo(url、平台、objectId) - 常用操作:upload / download / delete(按 url 或 fileId)/ exists;可用 @RegisterFileStoragePlugin 扩展
- 与若依结合:替换原生 FileUploadUtils 上传逻辑,业务层只依赖 FileStorageService,切换云厂商零改动
- 易错点:文件名中文/空格需清理;私有桶要用预签名 URL;删除时须按原上传平台解析,平台不匹配会失败
yaml
dromara:
x-file-storage:
default-platform: minio-1
minio:
- platform: minio-1
bucket-name: ruoyi
endpoint: http://localhost:9000
access-key: admin
secret-key: admin123
domain: http://localhost:9000/ruoyijava
@Autowired
private FileStorageService fileStorageService;
public String upload(MultipartFile file) {
FileInfo info = fileStorageService.of(file)
.setPath("avatar/2026/09")
.upload();
return info.getUrl(); // 存库即可,后续按 url 下载/删除
}5. knife4j学习国内的接口测试框架
【重点要点】
- 定位:国产增强版接口文档框架,基于 springdoc-openapi(或 springfox),UI 更友好、支持在线调试,访问地址 /doc.html
- 版本注意:Spring Boot 2.x 用非 jakarta 包,Spring Boot 3.x 用 jakarta 包
- 注解:类 @Tag(name)、方法 @Operation(summary)、参数 @Parameter、实体 @Schema/@ApiModelProperty
- 分组:多个 GroupedOpenApi 按模块分组;生产环境 knife4j.enable=false 关闭
- 易错点:接口被 Spring Security 拦截 → 记得放行 /doc.html 与 swagger 相关路径;实体参数不加注解时文档可读性差;接口签名变更后文档需刷新
xml
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>java
@Tag(name = "用户管理")
@RestController
@RequestMapping("/system/user")
public class SysUserController {
@Operation(summary = "查询用户列表")
@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/list")
public TableDataInfo list(@Parameter(description = "查询条件") SysUser user) { ... }
}6. 尝试学习原理-代码生成器
【重点要点】
- 核心思路四步:读表结构元数据 → 封装 GenTable/GenTableColumn → 类型映射 → 模板渲染输出
- 元数据读取:按 DbType 走不同 SQL 查 information_schema(表名/注释、列名、类型、主键、必填、默认值),封装为 GenTable(表)与 GenTableColumn(列)
- 类型映射:数据库类型 → Java 类型/前端组件,如 varchar→String、datetime→Date、bigint→Long、decimal→BigDecimal、tinyint→Integer;字典/注释决定前端生成 select、radio、date 控件
- 模板渲染:VelocityUtils.prepareContext 组装上下文,加载 resources/vm/java/{domain,controller,service,serviceImpl,mapper}.java.vm 与 vm/vue/*.vue.vm,Merge 后写入包路径,并生成 menu.sql
- 关键类:GenUtils(校验与组装模板数据)、VelocityUtils(模板加载/渲染)、GenConstants(类型映射常量)
- 易错点:表无主键会生成失败;列注释缺失则表单 label 空白(导入前先补注释);改模板后需重启(Velocity 模板缓存)
java
// 核心渲染片段(GenUtils / VelocityUtils 逻辑)
VelocityContext context = VelocityUtils.prepareContext(genTable);
StringWriter writer = new StringWriter();
// 模板路径如 resources/vm/java/domain.java.vm
Velocity.mergeTemplate(templatePath, CharsetKit.UTF_8, context, writer);
// writer 内容写入目标 .java,同时拼接 mapper xml / vue 页面 / menu.sql7. 尝试学习原理-RBAC权限控制
【重点要点】
- 模型:用户-角色、角色-菜单两对多。核心表 sys_user、sys_role、sys_user_role、sys_menu、sys_role_menu、sys_dept;权限标识即 sys_menu.perms 字段(如 system:user:list)
- 登录加载:登录成功后把 用户→角色→菜单 perms 去重为 Set<String> 放入 LoginUser(Redis 缓存),
*:*:*表示超级管理员全部权限 - 接口鉴权:Spring Security 过滤器链 +
@PreAuthorize("@ss.hasPermi('xxx')"),@ss 即 PermissionService Bean,hasPermi 对权限集合做通配/前缀匹配;前端配合 v-hasPermi 指令控制按钮显隐 - 数据权限:@DataScope(deptAlias, userAlias) 注解 + 切面在 SQL 前拼部门/用户过滤条件(数据范围:全部/自定义/本部门/本部门及以下/仅本人),由角色 data_scope 字段驱动
- 易错点:新接口忘加 @PreAuthorize 会失去粒度控制;权限变更后旧 token 需重新登录才刷新;权限字符串必须与 sys_menu.perms 完全一致
java
// 接口鉴权
@PreAuthorize("@ss.hasPermi('system:user:remove')")
@DeleteMapping("/{userIds}")
public AjaxResult remove(@PathVariable Long[] userIds) { ... }
// 数据权限:切面自动拼接部门/用户条件后传给 Mapper
@DataScope(deptAlias = "d", userAlias = "u")
@GetMapping("/list")
public TableDataInfo list(SysUser user) { ... }8. 了解原理-异步任务管理器
【重点要点】
- 作用:把耗时且不关心结果的任务(记录日志、发通知、刷新缓存)丢进线程池异步执行,接口快速返回
- 两条路线:
- 注解方式:@EnableAsync + @Async;注意同类内部 this.xxx() 调用不走代理会失效,需拆到别的 Bean 或注入代理
- 若依封装:AsyncManager.me().execute(Runnable) 单例管理器,内部持有 ThreadPoolTaskExecutor,配合 AsyncFactory 提供现成任务(如 recordOper 记录操作日志)
- 线程池参数:corePoolSize=2、maxPoolSize=10、queueCapacity=200、keepAliveSeconds=300;拒绝策略用 CallerRunsPolicy(积压时退回调用线程执行,避免任务丢失)
- 易错点:异步方法异常默认静默,需实现 AsyncUncaughtExceptionHandler 统一记录日志;容器关闭时未完成任务可能被丢弃(重要操作落库或等线程池收尾);线程名加业务前缀便于排查
java
// 提交异步任务(若依)
AsyncManager.me().execute(AsyncFactory.recordOper(oper, loginUser));
// 自定义线程池
@Bean("threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}9. 尝试学习原理-定时任务
【重点要点】
- 技术底座:内嵌 Quartz —— Scheduler(调度器)+ JobDetail(任务定义)+ CronTrigger(触发器,cron 表达式驱动)
- 数据表:sys_job(任务配置)、sys_job_log(执行日志);任务管理页面即可增删改启停,无需硬编码配置
- 反射调用式任务:不写 Job 类,配置"调用目标"字符串
bean名.方法名('参数')(如 ryTask.ryParams('ry')),QuartzJobBean.executeInternal 中由 JobInvokeUtil.invokeMethod 解析并反射调用 Spring Bean 方法,参数用单引号包裹 - 防重入:sys_job.concurrent 字段 / @DisallowConcurrentExecution,上次未结束则不触发下一次
- 状态与 cron:0 暂停、1 正常;Quartz cron 用 7 位(秒 分 时 日 月 周 年可省),
0 0 2 * * ?每天凌晨 2 点,?只能用于日/周之一 - 易错点:被调方法必须 public 且参数类型匹配,否则反射失败只在 sys_job_log 报错;方法内异常需自行 catch 落日志;多节点部署需引入分布式调度(如 xxl-job)或加锁,否则任务重复执行
java
// ryTask 是注入容器的 Bean,方法签名要与调用目标匹配
@Component("ryTask")
public class RyTask {
public void ryParams(String params) {
// 业务:更新统计、推送消息等
log.info("定时任务执行,参数:{}", params);
}
}yaml
# sys_job 配置示例
job_name: 同步缓存任务
invoke_target: ryTask.ryParams('sync')
cron_expression: 0/30 * * * * ?
status: 1 # 0 暂停 / 1 正常
concurrent: 0 # 0 禁止并发 / 1 允许并发