Spring Boot 中实现到期提醒任务的定时Job详解
在金融或借贷系统中,到期提醒是常见的功能需求。通过定时任务,可以定期扫描即将到期的借款记录,并生成或更新提醒信息。本文基于提供的三个JobHandler类(FarExpireRemindJob、MidExpireRemindJob 和 RecentExpireRemindJob),详细分析其实现逻辑。这些类使用Spring Boot的组件注解(@Component),并实现了JobHandler接口(假设这是自定义的定时任务接口),结合MyBatis-Plus的QueryWrapper进行数据库操作。代码涉及借款入库(LoanInDO)、借款出库(LoanOutDO)和提醒记录(RemindExpireDO)的处理。
本文将逐一拆解每个类的代码结构、核心逻辑、潜在优化点和注意事项,确保覆盖所有提供的代码片段。假设这些Job是集成在XXL-JOB或类似调度框架中的定时任务。
1. 整体功能概述
- FarExpireRemindJob:负责远期到期提醒(默认30天)。扫描即将到期的借款记录,生成提醒记录,并更新借款记录的提醒标志。
- MidExpireRemindJob:负责中期到期提醒(默认15天)。更新已存在的提醒记录,调整提醒状态和时间。
- RecentExpireRemindJob:负责近期到期提醒(默认7天)。类似于中期提醒,更新提醒记录的状态和时间。 这些Job通过参数(param)支持自定义天数,支持事务回滚(仅FarExpireRemindJob有@Transactional注解),并返回执行结果字符串,便于日志记录或监控。
依赖的Mapper(如LoanInMapper、RemindExpireMapper等)使用MyBatis-Plus进行CRUD操作。DO类(如LoanInDO)是数据库实体,包含字段如id、custId、expireTime等。
2. FarExpireRemindJob 详细分析
这个类是三个Job中最复杂的,涉及多表操作和批量更新。它的目的是在借款到期前一定天数(默认30天)生成提醒记录,并标记借款记录已提醒。
代码结构:
-
类注解和依赖注入:
@Component
public class FarExpireRemindJob implements JobHandler {
@Autowired
private LoanInMapper loanInMapper;
@Autowired
private RemindExpireMapper remindExpireMapper;
@Autowired
private LoanOutMapper loanOutMapper;
-
- @Component:将类注册为Spring Bean,便于调度框架注入和调用。
- 注入三个Mapper:用于查询和更新借款入库、出库和提醒表。
-
execute方法(核心执行逻辑):
@Override
@Transactional(rollbackFor = Exception.class)
public String execute(String param) throws Exception {
// 到期提醒默认值
int day = (StringUtils.isEmpty(param)) ? 30 : Integer.parseInt(param);
List<LoanInDO> loanInDOList = loanInMapper.selectList(
new QueryWrapperX<LoanInDO>()
.eq("overdue_flag", 0)
.eq("expire_remind_flag", 0)
.le("expire_time", LocalDate.now().plusDays(day))
);
List<LoanOutDO> loanOutDOList = loanOutMapper.selectList(
new QueryWrapper<LoanOutDO>()
.eq("overdue_flag", 0)
.eq("expire_remind_flag", 0)
.le("expire_time", LocalDate.now().plusDays(day))
); -
解析参数:如果param为空,默认day=30,否则转换为整数。
-
查询借款入库记录:使用QueryWrapperX(MyBatis-Plus扩展)过滤未逾期(overdue_flag=0)、未提醒(expire_remind_flag=0)、到期时间 <= 当前日期 + day 的记录。
-
类似查询借款出库记录。注意:QueryWrapperX可能是自定义的Wrapper,支持更灵活的查询。
List<RemindExpireDO> remindExpireDOList = new ArrayList<>(); loanInDOList.forEach(loanInDO -> { loanInDO.setExpireRemindFlag(1); RemindExpireDO remindExpireDO = RemindExpireDO.builder() .loanId(loanInDO.getId()) .custId(loanInDO.getCustId()) .custCode(loanInDO.getCustCode()) .custName(loanInDO.getCustName()) .contractCode(loanInDO.getContractCode()) .amount(loanInDO.getAmount()) .balance(loanInDO.getBalance()) .beginTime(loanInDO.getBeginTime()) .expireTime(loanInDO.getExpireTime()) .remindTime(LocalDateTime.now()) .showFlag(1) .remindStatus(day) .remindFlag(1) .userName(loanInDO.getUserName()) .userId(loanInDO.getUserId()) .deptName(loanInDO.getDeptName()) .deptId(loanInDO.getDeptId()) .build(); loanInDO.setExpireRemindFlag(1); // 重复设置,可能是笔误 remindExpireDOList.add(remindExpireDO); });
-
处理借款入库列表:遍历每个LoanInDO,设置expireRemindFlag=1(标记已提醒)。
-
使用Lombok的builder模式创建RemindExpireDO,复制借款相关字段,并设置提醒时间、状态等。
-
注意:loanInDO.setExpireRemindFlag(1); 出现了两次,第二次是多余的,可能为代码笔误,会导致无谓操作但不影响功能。
-
添加到remindExpireDOList中。
loanOutDOList.forEach(loanOutDO -> { loanOutDO.setExpireRemindFlag(1); RemindExpireDO remindExpireDO = RemindExpireDO.builder() // ... 与入库类似,复制字段 ... .build(); loanOutDO.setExpireRemindFlag(1); // 同样重复设置 remindExpireDOList.add(remindExpireDO); });
处理借款出库列表:逻辑与入库完全相同,包括重复的setExpireRemindFlag。
if (!loanInDOList.isEmpty()) {
loanInMapper.updateBatch(loanInDOList);
}
if (!loanOutDOList.isEmpty()) {
loanOutMapper.updateBatch(loanOutDOList);
}
if (!remindExpireDOList.isEmpty()) {
remindExpireMapper.insertBatch(remindExpireDOList);
}
return String.format("已产生 %s 条到期提醒", remindExpireDOList.size());
}
-
- 批量更新借款记录(如果列表非空)。
- 批量插入提醒记录。
- 返回结果字符串,报告生成的提醒条数。
- @Transactional:确保所有操作在事务中,如果异常则回滚,防止数据不一致。
分析与优化点:
- 优点:批量操作高效,避免了循环中的单条更新;事务保障数据一致性;参数化day支持灵活配置。
- 潜在问题:如果借款记录量大,查询和遍历可能耗时,需要监控性能。重复的setExpireRemindFlag是小bug,可移除第二次调用。未处理param解析异常(e.g., 非数字),建议添加try-catch。
- 改进建议:添加日志记录(e.g., SLF4J);如果day很大,查询条件可优化索引(expire_time字段应有索引);考虑分页查询以防内存溢出。
3. MidExpireRemindJob 详细分析
这个Job专注于更新中期提醒(默认15天),不生成新记录,只更新现有提醒的status和time。
代码结构:
-
类注解和依赖注入:
@Component
public class MidExpireRemindJob implements JobHandler {@Autowired private RemindExpireMapper remindExpireMapper;
-
- 类似前者,只注入提醒Mapper。
-
execute方法:
@Override
public String execute(String param) throws Exception {
int day = (StringUtils.isEmpty(param)) ? 15 : Integer.parseInt(param);
List<RemindExpireDO> remindExpireDOS = remindExpireMapper.selectList(
new QueryWrapperX<RemindExpireDO>()
.between("expire_time",
LocalDate.now().plusDays(day),
LocalDate.now().plusDays(day + 1))
);
remindExpireDOS.forEach(remindExpireDO -> {
remindExpireDO.setRemindTime(LocalDateTime.now());
remindExpireDO.setRemindStatus(day);
});
if (!remindExpireDOS.isEmpty()) {
remindExpireMapper.updateBatch(remindExpireDOS);
}
return String.format("更新了 %d 条提醒", remindExpireDOS.size());
} -
- 解析day,默认15。
- 查询提醒记录:到期时间在 [当前+day, 当前+day+1) 之间,使用between条件。
- 遍历更新:设置当前提醒时间和status=day。
- 批量更新,如果非空。
- 返回更新条数。
分析与优化点:
- 优点:简单高效,只更新必要字段;between条件精确匹配特定天窗。
- 潜在问题:未加@Transactional,如果更新失败无回滚(与其他Job不一致)。查询未过滤已逾期或已处理记录,可能重复更新。
- 改进建议:添加事务注解;添加过滤条件如.eq("remindStatus", > day) 以避免重复;考虑day的边界(如day=0时)。
4. RecentExpireRemindJob 详细分析
与MidExpireRemindJob几乎相同,仅默认day=7和返回字符串略不同,用于近期提醒。
代码结构:
-
类注解和依赖注入: 同Mid,仅注入RemindExpireMapper。
-
execute方法:
@Override
public String execute(String param) throws Exception {
int day = (StringUtils.isEmpty(param)) ? 7 : Integer.parseInt(param);
List<RemindExpireDO> remindExpireDOS = remindExpireMapper.selectList(
new QueryWrapperX<RemindExpireDO>()
.between("expire_time",
LocalDate.now().plusDays(day),
LocalDate.now().plusDays(day + 1))
);
remindExpireDOS.forEach(remindExpireDO -> {
remindExpireDO.setRemindTime(LocalDateTime.now());
remindExpireDO.setRemindStatus(day);
});
if (!remindExpireDOS.isEmpty()) {
remindExpireMapper.updateBatch(remindExpireDOS);
}
return String.format("更新了 %d 条提醒记录", remindExpireDOS.size());
} -
- 逻辑完全一致,仅day默认7,返回消息为"提醒记录"。
分析与优化点:
- 优点:代码复用性高,与Mid类似,便于维护。
- 潜在问题:同Mid,无事务;可能与Mid重叠更新(如果day重合)。
- 改进建议:合并Mid和Recent为一个Job,通过param区分;添加唯一性检查避免多Job冲突。
5. 整体系统设计与注意事项
- 定时调度:这些Job适合每天运行一次(e.g., 凌晨)。FarJob生成初始提醒,Mid/Recent逐步更新status,形成多级提醒(如30→15→7天)。
- 数据一致性:Far有事务,其他无,建议统一添加。所有Job使用LocalDate.now(),注意时区。
- 性能考虑:批量操作好,但大表需索引(expire_time、flags)。错误处理:param非数字时抛异常,可加校验。
- 扩展性:可添加短信/邮件通知在更新后;DO类字段丰富,支持报表。
- 测试建议:单元测试查询条件;集成测试事务回滚。
通过这些Job,可以实现自动化到期提醒系统。如果需要完整项目代码或进一步优化,欢迎评论交流!