☰
RuoYi二次开发实战:Java档案管理系统设计与权限核心解析
2026/10/27 14:50:31 网站建设 项目流程

简介:基于RuoYi框架的Java档案管理系统设计源码,面向后端与全栈开发者,适合用来学习企业级权限管理、前后端分离及自动化部署等实践。压缩包共812个文件,打包体积约99.27MB,内容以Java源文件、Vue组件、JavaScript脚本、XML配置、SVG图标资源及Shell脚本为主,涵盖业务代码、界面元素、环境配置与构建工具等层面。目前已有970人浏览学习,具有较高的参考热度。包内不仅提供完整的档案管理业务模块,还包含数据库初始化脚本、多环境配置文件、启动/打包批处理命令及环境使用手册,便于读者从开发到部署完整走通流程;通过研读代码与配置,可以快速掌握RuoYi框架的菜单权限、数据字典、日志监控等核心机制,并对Vue前端与Spring Boot后端的交互方式形成清晰认识。

1. 基于RuoYi的Java档案管理系统设计源码:先分清它能解决什么问题

市面上打着“档案管理系统”旗号的项目不少,但大部分要么闭源、要么把业务逻辑焊死在一个大方法里,想改个字段都要翻半天。这份基于RuoYi的Java档案管理系统设计源码走的是另一条路:骨架是RuoYi现成的用户、角色、菜单、部门、日志体系,业务层单独实现了档案登记、借阅、审批、归还、销毁这一整条链路。拿到手你能直接跑起来,也能顺着RuoYi的代码生成器把新表接进去,属于典型的“框架管通用能力,业务管领域逻辑”的结构。

它的落地场景很明确:档案室数字化、公司内部资料柜电子化、以及高校里Java课程设计或毕业设计。适合两类人,一类是刚接触RuoYi、想找个完整业务练二次开发的新手,另一类是手里有多个若依工程、想快速比对数据权限和事务边界写法的熟手。这个项目最值得看的地方不在页面多好看,而在借阅审批的状态机怎么设计、部门数据权限怎么落到SQL上、定时任务怎么扫超期档案——这几块才是档案类系统的真正难点。

2. 从RuoYi源码到档案模块:先把三个黑匣子拆开

2.1 工程目录与三层架构:哪些目录别乱动,哪些目录随便写

用IDEA打开工程后,先别急着找档案代码。RuoYi把框架代码和业务代码分得很开,目录结构大概是这样的:

ruoyi-admin 启动入口、Controller、静态资源,端口配置在 application.yml ruoyi-common 通用工具类、常量、异常、分页封装 ruoyi-framework Shiro安全框架、拦截器、切面、数据源配置 ruoyi-system 用户、角色、菜单、部门、字典等核心表对应的Service和Mapper ruoyi-generator 代码生成器的后台实现 ruoyi-quartz 内置定时任务模块 ruoyi-ui 前端页面工程(经典版是Thymeleaf,分离版是Vue)

我的习惯是:ruoyi-framework里的东西一律不碰,ruoyi-common只加常量类,ruoyi-system保持原样给框架用。档案模块的Controller放在ruoyi-admin下的com.ruoyi.web.controller.arch包,Service和Mapper放在ruoyi-system下的com.ruoyi.system.service、com.ruoyi.system.mapper。这样跟代码生成器的默认输出路径保持一致,以后重新生成代码不会出现“代码跑到别人家目录”的问题。

连接数据库的配置在ruoyi-admin/src/main/resources/application-druid.yml,默认连本地3306的ry库。第一次跑之前先确认JDK环境变量没配错,命令行敲一句java -version,能正常输出1.8或11版本再继续,否则启动报错你都分不清是环境问题还是代码问题。

2.2 登录到鉴权的链路:菜单、按钮权限是怎么生效的

RuoYi经典版用的是Shiro,登录走的是“认证→授权→会话校验”这条路。用户提交账号密码后,UserRealm的doGetAuthenticationInfo去查用户表并校验密码,登录成功后访问任何接口都要经过Shiro过滤器,带权限标识的接口再用注解拦截。核心逻辑在UserRealm.java里,它会从sys_menu表把当前用户的权限标识汇总出来:

@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SysUser user = SecurityUtils.getLoginUser().getUser(); Set<String> roles = new HashSet<String>(); Set<String> perms = new HashSet<String>(); // 根据用户ID查询角色标识和权限标识 List<SysRole> roleList = userService.selectRolesByUserId(user.getUserId()); for (SysRole role : roleList) { roles.add(role.getRoleKey()); // 从 sys_menu 表的 perms 字段取出权限标识,例如 arch:record:list perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId())); } SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setRoles(roles); info.setStringPermissions(perms); return info; }

这里最容易被新手忽略的是:sys_menu表里每一行菜单或按钮的perms字段,决定了Controller上能不能用@RequiresPermissions("arch:record:list")拦住请求。如果你的资源是RuoYi-Vue分离版,这套链路换成了Spring Security,注解也要换成@PreAuthorize("@ss.hasPermi('arch:record:list')")。区别不大,但面试被问到权限设计时,能说清Shiro和Spring Security两套注解各自的拦截时机,是加分项。

工程类型权限注解底层框架特点
RuoYi经典版@RequiresPermissions("xxx:yyy:zzz")Apache ShirofilterChain按URL拦截,注解在方法上,权限集合从sys_menu聚合
RuoYi-Vue分离版@PreAuthorize("@ss.hasPermi('xxx:yyy:zzz')")Spring SecuritySecurityFilter链+方法级校验,适合前后端完全分离

2.3 用代码生成器跑通第一张档案表:从导入表到生成菜单

RuoYi最提效率的功能就是代码生成器。你先在数据库里建好arch_record表,然后登录后台,打开“系统工具→代码生成”,点“导入”把表加进去,再点“编辑”配置生成参数。

一套典型配置是这样的:生成包路径填com.ruoyi,生成模块名填system,生成业务名填archRecord,功能名填“档案登记”,作者填自己的名字。模板默认会生成实体类、Mapper、Service、Controller和一套管理页面,点击“生成”会得到一个zip压缩包,里面带着一段菜单SQL:

insert into sys_menu(menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) values('档案登记', 3, 1, 'record', 'arch/record/index', 1, 0, 'C', '0', '0', 'arch:record:list', 'documentation', 'admin', sysdate(), '');

这段SQL里有几个字段必须理解清楚,否则菜单会挂在错误的位置。parent_id写的是父菜单ID,3通常对应“系统管理”这一级的其他菜单,你要按sys_menu表实际情况查一下父ID;menu_type里 C代表目录、M代表菜单、F代表按钮;perms写的是权限标识,必须和Controller注解里的字符串完全一致。按钮行的parent_id要指向它所属的菜单ID,权限标识按“列表/新增/修改/删除”分别配置成arch:record:list、arch:record:add、arch:record:edit、arch:record:remove。

提示:代码生成器不是万能的。生成的列表查询页里,前端表单控件的name属性经常跟实体字段对不齐,接到真实业务时一定要逐个字段核对,尤其是有下拉框和时间日期控件的地方。

3. 档案管理核心表设计:八张表的字段与状态流转

3.1 档案分类与登记主表:门类、保管期限、密级这三个栏位藏着业务规则

档案系统的第一张基础表是档案分类表,它解决“档案怎么组织”的问题。很多单位档案是按“门类→年度→案卷→文件”这种层级组织的,所以分类表直接用父子结构:

CREATE TABLE arch_category ( category_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '档案分类ID', category_name VARCHAR(64) NOT NULL COMMENT '分类名称', parent_id BIGINT DEFAULT 0 COMMENT '父分类ID,0表示顶级', status CHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用)', create_by VARCHAR(64) COMMENT '创建者', create_time DATETIME COMMENT '创建时间', update_by VARCHAR(64) COMMENT '更新者', update_time DATETIME COMMENT '更新时间', remark VARCHAR(500) COMMENT '备注' ) ENGINE=InnoDB COMMENT='档案分类表';

核心的档案登记主表是arch_record,它把一份档案的静态属性都记录下来:档案编号、名称、分类、存放位置、保管人、保管期限、密级、当前状态。密级不建议用字符串乱写,用CHAR(1)加数字字典更规范:

CREATE TABLE arch_record ( record_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '档案ID', archive_code VARCHAR(32) NOT NULL COMMENT '档案编号', archive_name VARCHAR(128) NOT NULL COMMENT '档案名称', category_id BIGINT COMMENT '档案分类ID', dept_id BIGINT COMMENT '归属部门ID,数据权限过滤用', storage_location VARCHAR(64) COMMENT '存放位置', keeper VARCHAR(32) COMMENT '保管人', retention_period VARCHAR(16) COMMENT '保管期限分类:永久/30年/10年', secrecy_level CHAR(1) DEFAULT '0' COMMENT '密级(0普通 1内部 2秘密)', status CHAR(1) DEFAULT '0' COMMENT '状态(0在库 1借出 2待销毁 3已销毁)', create_by VARCHAR(64) COMMENT '创建者', create_time DATETIME COMMENT '创建时间', update_by VARCHAR(64) COMMENT '更新者', update_time DATETIME COMMENT '更新时间', remark VARCHAR(500) COMMENT '备注', UNIQUE KEY uk_archive_code (archive_code) ) ENGINE=InnoDB COMMENT='档案登记主表';

archive_code必须唯一,这是档案实体在系统里的身份证。dept_id这个字段特别重要,后面第4章讲数据权限时会拿它做部门过滤,如果没有这个字段,行级权限的SQL就无从下手。

3.2 借阅、审批、归还与销毁:状态机设计比想象中复杂

档案的状态不是“能借/不能借”这么简单,它牵扯到一条完整的状态流转:在库 → 借出 → 归还 → 待销毁 → 已销毁。我用借阅表承载这条链路的中间状态,审批表单独记录每一次审批意见:

CREATE TABLE arch_borrow ( borrow_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '借阅ID', borrow_no VARCHAR(32) NOT NULL COMMENT '借阅单号', record_id BIGINT NOT NULL COMMENT '档案ID', borrower VARCHAR(32) COMMENT '借阅人', borrower_dept VARCHAR(64) COMMENT '借阅人部门', borrow_date DATETIME COMMENT '借出时间', due_date DATETIME COMMENT '应还日期', return_date DATETIME COMMENT '实际归还日期', approve_status CHAR(1) DEFAULT '0' COMMENT '审批状态(0待审 1已通过 2已驳回 3借出中 4已归还 5已超期)', approver VARCHAR(64) COMMENT '审批人', approve_time DATETIME COMMENT '审批时间', remark VARCHAR(500) COMMENT '备注' ) ENGINE=InnoDB COMMENT='档案借阅表'; CREATE TABLE arch_approval ( approval_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '审批ID', borrow_id BIGINT NOT NULL COMMENT '借阅ID', result CHAR(1) COMMENT '审批结果(1通过 2驳回)', opinion VARCHAR(255) COMMENT '审批意见', approver VARCHAR(64) COMMENT '审批人', approve_time DATETIME COMMENT '审批时间' ) ENGINE=InnoDB COMMENT='档案借阅审批记录表';

我采用的流转逻辑是:借阅人提交借阅单时approve_status=0,档案本身还是status=0;审批通过后,借阅单变为approve_status=1,同时把档案状态置为status=1(借出);归还时借阅单变为approve_status=4,档案状态再回到status=0。销毁则是独立的流程,档案状态从0改为2(待销毁),确认销毁后改为3(已销毁)。中间还涉及一个超期状态5,由定时任务扫描due_date触发,这个在第6章展开。

这八张表在实际项目里还可以继续扩展:arch_borrow_item做一张借阅单借多份档案的明细、arch_destroy记录销毁原因和销毁人信息。但核心主链路先保持上面的四张表加归档关联表,字段越少,二次开发越不容易翻车。

3.3 关于MyBatis-Plus实体类反推建表:这个工程我为什么不用

现在很多Java课程设计喜欢用MyBatis-Plus,有人会问能不能直接靠实体类自动生成建表SQL。MyBatis-Plus确实支持用实体类拼CREATE TABLE,也能根据字段注解反推列名、类型、长度,甚至有现成的插件可以在启动时自动建表。但我在这个项目里坚持“DDL先行”,原因很现实:档案表的字段注释、索引设计、状态枚举不是实体类上一行注解能完全表达的。比如approve_status的每个数字代表什么状态、archive_code要不要唯一索引,这些语义写在COMMENT里比写在Java注解里更直观,DBA和运维也能直接看懂。

提示:如果你手里拿到的是基于MyBatis-Plus改造的RuoYi分支,用实体类反推建表只是兜底手段。核心表比如arch_record、arch_borrow还是建议把SQL写死,再让代码生成器反向生成实体,这样注释能同步到Java类上,维护成本最低。

表名用途关键状态字段
arch_category档案分类树status(正常/停用)
arch_record档案登记主表status(在库/借出/待销毁/已销毁)
arch_borrow借阅申请与归还approve_status(待审/通过/驳回/归还/超期)
arch_approval审批意见流水result(通过/驳回)
arch_destroy销毁登记status(待销毁/已销毁)
sys_dept部门表数据权限的join对象
sys_user用户表借阅人、审批人
sys_menu菜单权限表perms权限标识

4. 把档案业务落进代码:Controller、Service 与数据权限实战

4.1 档案分页查询:RuoYi的startPage和TableDataInfo到底怎么用

RuoYi后台管理最常见的接口就是分页列表查询。它的封装逻辑是:Controller继承BaseController,调用startPage()会从请求参数里读取pageNum、pageSize、orderByColumn、isAsc,然后通过PageHelper拦截器在SQL执行前自动拼接分页。列表查询代码长这样:

@RequiresPermissions("system:arch:list") @GetMapping("/list") public TableDataInfo list(ArchRecord archRecord) { startPage(); List<ArchRecord> list = archRecordService.selectArchRecordList(archRecord); return getDataTable(list); }

selectArchRecordList的实现要传给Mapper一个实体对象作为查询条件,MyBatis的XML里用<where>加<if>动态拼接SQL。这里有两个高频坑:第一,startPage()必须在调用Mapper接口之前执行,一旦中间有任何其它查询先碰了数据库,分页就会串页;第二,Controller方法的入参对象必须和查询对象是同一个,否则RuoYi的params属性传不到SQL里。前端用的表格组件一般会传pageNum=1&pageSize=10和排序字段,这些参数名在RuoYi里是固定的,不要自己改成page、limit,否则分页直接失效。

4.2 借阅审批的状态更新:一张借阅单改三张表的事务边界

借阅审批是整个系统里事务边界最有代表性的场景。审批通过时,要同时更新借阅单状态、写一条审批意见、把对应档案状态改为借出。三个动作必须在一个事务里,中间任何一个失败都要整体回滚:

@Transactional(rollbackFor = Exception.class) public AjaxResult approveBorrow(ArchBorrow borrow, String opinion, String approveResult) { ArchBorrow dbBorrow = archBorrowMapper.selectArchBorrowById(borrow.getBorrowId()); if (dbBorrow == null) { throw new ServiceException("借阅单不存在"); } if (!"0".equals(dbBorrow.getApproveStatus())) { throw new ServiceException("该借阅单已审批,请勿重复操作"); } // 1. 更新借阅单状态:通过置1,驳回置2 ArchBorrow update = new ArchBorrow(); update.setBorrowId(borrow.getBorrowId()); update.setApproveStatus("1".equals(approveResult) ? "1" : "2"); update.setApprover(SecurityUtils.getUsername()); update.setApproveTime(DateUtils.getNowDate()); archBorrowMapper.updateArchBorrow(update); // 2. 写入审批意见流水 ArchApproval approval = new ArchApproval(); approval.setBorrowId(borrow.getBorrowId()); approval.setResult(approveResult); approval.setOpinion(opinion); approval.setApprover(SecurityUtils.getUsername()); approval.setApproveTime(DateUtils.getNowDate()); archApprovalMapper.insertArchApproval(approval); // 3. 审批通过时,档案状态改为借出 if ("1".equals(approveResult)) { ArchRecord record = new ArchRecord(); record.setRecordId(dbBorrow.getRecordId()); record.setStatus("1"); archRecordMapper.updateArchRecord(record); } return AjaxResult.success("审批完成"); }

这段代码的rollbackFor = Exception.class很关键,默认的Spring事务只在RuntimeException时回滚,如果业务方法抛的是受检异常,不加这个参数事务不会回滚。另外注意approveResult的字符串比较一定要用"1".equals(...),不要用approveResult.equals("1"),空指针问题大多出在这种细节上。

4.3 行级数据权限:让不同部门只能看到本部门的档案

档案管理系统的敏感度高,不能所有登录用户都看到全部档案。RuoYi内置了数据权限注解@DataScope,它本质上是往SQL尾部拼接一段部门过滤条件。用法是给Service实现类的方法加上注解,声明SQL别名:

@DataScope(deptAlias = "r") @Override public List<ArchRecord> selectArchRecordList(ArchRecord archRecord) { return archRecordMapper.selectArchRecordList(archRecord); }

对应的Mapper XML里,查询语句要joinsys_dept表,并在<where>最终位置放上${params.dataScope}:

<select id="selectArchRecordList" parameterType="ArchRecord" resultMap="ArchRecordResult"> select r.*, d.dept_name from arch_record r left join sys_dept d on r.dept_id = d.dept_id <where> <if test="archiveName != null and archiveName != ''"> AND r.archive_name LIKE CONCAT('%', #{archiveName}, '%') </if> <if test="categoryId != null"> AND r.category_id = #{categoryId} </if> ${params.dataScope} </where> </select>

deptAlias = "r"必须和SQL里档案表的别名完全一致,${params.dataScope}拼接出来的内容类似AND r.dept_id IN (100, 101)。这里有个特别隐蔽的坑:如果Service方法上有多个参数对象,或者XML里忘了写${params.dataScope},注解会静默失效,系统不会报错,但所有部门都能查到全量数据。我排查这类问题时,习惯先把日志级别调到DEBUG,看打印出来的SQL尾部有没有多出AND开头的过滤条件。这个知识点在Java面试里经常被问到“如何设计行级权限”,直接把这段结构讲清楚就是一份标准答案。

4.4 代码生成后的四步收尾:菜单、权限标识、字典、打包

使用代码生成器落到业务代码后,还要做四件事:第一,执行菜单SQL,把菜单挂到正确父级;第二,逐个核对Controller上的@RequiresPermissions和菜单SQL里的perms是否一致;第三,如果页面有下拉框,去“系统管理→字典管理”维护数据字典;第四,本地打包验证:

mvn clean package -DskipTests java -jar ruoyi-admin/target/ruoyi-admin.jar

打包时必须跳过测试,因为RuoYi自带的测试类一般不需要参与打包。生成出来的jar包在ruoyi-admin/target目录下,用IDE启动则直接运行RuoYiApplication的main方法。启动日志里看到“启动成功”字样后,先别登录业务页面,用admin账号确认菜单有没有出现,再点一次列表和新增按钮,基本就能把生成代码的90%问题暴露出来。

5. RuoYi二次开发避坑:五个真实翻车现场与排查手段

5.1 菜单树乱掉:重复执行菜单SQL的连锁反应

现象:admin登录后,侧边栏菜单出现多个“档案登记”入口,或者点了菜单后页面空白。

原因:为了调试,开发人员反复执行了代码生成器导出的菜单SQL。sys_menu表里已经存在同名的菜单记录,再次插入根本没有做幂等处理,主键重复或数据冗余就产生了。

解决:执行菜单SQL之前先清理同名菜单:

delete from sys_menu where menu_name in ('档案登记', '档案借阅', '档案审批');

然后再执行新的插入SQL。如果菜单已经乱到影响使用,最简单粗暴的办法是重新导入若依初始化的ry_*.sql菜单部分,再执行自己的菜单脚本。从那以后我每次拿到新的菜单SQL,都会先把脚本里的menu_name和现有表比对一遍再执行。

5.2 列表页时间少了8小时:JSON序列化时区问题

现象:数据库里create_time是2025-03-01 12:00:00,页面上显示成2025-03-01 04:00:00,而且时间控件回显也不对。

原因:Spring Boot默认的Jackson序列化时区是UTC,跟中国时区相差8小时。RuoYi没有在每个实体类上统一配置时区,导致部分字段按UTC输出。

解决:在涉及时间展示的字段上显式声明格式化:

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date createTime;

如果你是RuoYi-Vue分离版,可能用的是LocalDateTime,此时除了上面的注解,还要检查application.yml里spring.jackson.time-zone: GMT+8。这个配置加上后,前后端时间都不会再差8小时。

5.3 @DataScope数据权限不生效:注解摆错了位置

现象:用两个不同部门的测试账号登录,查列表返回的是同一份全量数据,部门过滤完全没起作用。

原因:最常见的是@DataScope写在了Controller方法上,而RuoYi的切面实际拦截的是Service层方法;或者是Mapper XML里没有拼接${params.dataScope};还有一种情况是查询方法除了ArchRecord还传了别的参数,切面找不到匹配的数据源注入位置。

解决:把注解从Controller移到Service实现类方法上,确保XML里有${params.dataScope},并保证查询对象是Mapper方法的唯一业务参数。改完之后,打开日志看打印的SQL末尾有没有AND r.dept_id IN (...),有就是生效了。

5.4 事务整体没生效:Service内部this调用的死穴

现象:审批过了,审批意见也写了,但档案状态没变成借出,数据库里出现部分更新。日志里明明看到抛了异常,前面的写操作却还在。

原因:经典的事务失效场景。我在一个Service里写了两个方法,approveBorrow调用了内部的this.updateRecordStatus(),后者标了@Transactional。Spring的事务代理只在外部调用时生效,内部this调用绕过了代理,事务注解形同虚设。

解决:把@Transactional注解放到外部入口方法上,一个事务方法只做一件事;如果确实需要拆分方法,就注入Service自身再调用,或者把写操作拆到另一个Spring Bean里去。排查时看日志里有没有TransactionInterceptor的痕迹,没有就说明代理没进来。

5.5 上传档案附件后无法预览:profile路径与实际映射不一致

现象:附件上传成功,返回了文件路径,但浏览器访问/profile/upload/2025/03/xxx.pdf显示404。

原因:RuoYi的上传文件默认写到配置的ruoyi.profile目录,访问路径/profile/**通过Spring的ResourceHandler映射到硬盘上的实际目录。如果改了上传目录但没有同步映射配置,或者把profile配置成了相对路径,就会出现“上传成功、访问失败”的诡异现象。

解决:在application.yml里显式配置绝对路径:

ruoyi: profile: D:/ruoyi/uploadPath

然后在WebMvcConfig里确认资源映射指向同一个目录。用IDE启动时相对路径经常变化,所以我一律写绝对路径,尽量避免./upload这种写法带来的不确定性。

6. 借阅超期提醒定时任务:设计、验证与一次回归演练

6.1 用@Scheduled扫出超期借阅单并自动变更状态

档案借出去长期不还,光靠人工盯不现实。我加一个定时任务,每小时扫一次arch_borrow表,把approve_status=1(已通过但未归还)且due_date早于当前时间的数据置为超期状态5:

@Component("archBorrowTask") public class ArchBorrowTask { private static final Logger log = LoggerFactory.getLogger(ArchBorrowTask.class); @Resource private ArchBorrowMapper archBorrowMapper; @Scheduled(cron = "0 0 0/1 * * ?") public void checkOverdue() { List<ArchBorrow> overdueList = archBorrowMapper.selectOverdueBorrowList(); for (ArchBorrow borrow : overdueList) { ArchBorrow update = new ArchBorrow(); update.setBorrowId(borrow.getBorrowId()); update.setApproveStatus("5"); archBorrowMapper.updateArchBorrow(update); log.info("借阅单 {} 已超期,状态置为5", borrow.getBorrowNo()); } } }

selectOverdueBorrowList的SQL条件主要是approve_status = '1' AND due_date < now()。如果你不想写@Scheduled注解,RuoYi内置的Quartz定时任务模块能做得更规范:往sys_job表插一条任务记录,指定调用目标和cron表达式,后台就可以动态启停:

insert into sys_job(job_name, job_group, invoke_target, cron_expression, status, create_by, create_time) values('借阅超期扫描', 'DEFAULT', 'archBorrowTask.checkOverdue()', '0 0 0/1 * * ?', '0', 'admin', sysdate());

区别在于@Scheduled改时间要改代码重新打包,而sys_job方式可以在后台管理界面上直接改cron。项目刚起步用@Scheduled最快,等稳定了再切到Quartz也不迟。

6.2 验证方法与回归清单:改一条数据是最快的测试手段

定时任务写完不能只靠肉眼等整点。我一般手动改一条数据来模拟超期场景,SQL类似这样:

update arch_borrow set approve_status = '1', due_date = date_sub(now(), interval 1 day) where borrow_id = 88;

把borrow_id=88这条借阅单改成“昨天就该还但还没还”的状态,然后手动调用定时任务方法,或者等任务执行后再查一次:

select borrow_id, borrow_no, approve_status, due_date from arch_borrow where borrow_id = 88;

如果approve_status从1变成了5,说明扫描逻辑正常。做完这一项后,我会把整条业务主链路从头到尾走一遍:新增档案登记→提交借阅申请→审批通过→确认归还→发起销毁→检查列表页数据权限。整个过程配合DEBUG日志,重点看每条SQL是否带了dataScope过滤条件。这套回归动作虽然繁琐,但能挡住绝大多数“改完一个功能、崩了另一个流程”的情况。

从那以后,我每次拿到RuoYi二次开发包,都强制自己先跑一遍初始化脚本、菜单SQL、演示数据SQL,再手动走通业务主链路,最后才去改新功能。这套流程帮我挡掉了不少次线上翻车,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询