小柒味来 - Spring Task 定时任务

一、Spring Task

1.1 介绍

Spring Task 是 Spring 框架内置的定时任务调度工具,可在约定的时间自动执行指定逻辑。
作用:定时执行 Java 代码

为什么要使用 Spring Task?
我们的项目目前还有两大问题还没有解决

  1. 支付超时的订单如何处理
  2. 派送中的订单一直没有点击完成如何处理

应用场景

  1. 信用卡每月还款提醒
  2. 银行贷款还款提醒
  3. 火车票系统处理未支付订单

只要有定时处理需求,都可以使用 Spring Task


1.2 Cron 表达式

1.2.1 基本格式

Cron表达式是一个字符串,用于定义任务触发时间。
其由7个域组成,依次为:**秒、分、时、日、月、周、年(可选)**。

字段 允许值
0-59
0-59
0-23
1-31
1-12 或 JAN-DEC
0-7 (0或7=周日) 或 SUN-SAT
可选,1970-2099

1.2.2 通配符含义

  • * :任意值(例如 * 在分钟位表示每分钟)
  • , :枚举值(例如 1,5,10 表示第1、5、10分钟)
  • - :范围(例如 1-5 表示1到5)
  • / :步长(例如 0/5 表示从0开始每5个单位)
  • ? :日和周互斥时使用,表示不指定
  • L :最后(如日字段 L 表示当月最后一天,周字段 L 表示最后一个星期几)
  • W :最近的工作日(如 15W 表示离本月15日最近的工作日)
  • # :第几个星期几(如 6#3 表示当月第三个周五)

1.2.3 常见特殊用法:

  • 每月某日最近的工作日

    例:每月28号最近的工作日 → 如果28号是周六,则触发27号;如果是周日,则触发29号。

  • 每月最后一天

    例:2 月份最后一天可能是 28 或 29 号,不需手写,直接用生成器生成即可。

Cron 在线生成器

常用案例

表达式 含义
0 0 12 * * ? 每天中午12点执行
0 0/5 * * * ? 每5分钟执行一次
0 0 9 ? * MON-FRI 周一到周五上午9点执行
0 0 0 1 * ? 每月1日0点执行
0 15 10 L * ? 每月最后一天上午10:15执行
0 15 10 ? * 6L 每月最后一个周五上午10:15执行

1.3 入门案例

1.3.1 启动类添加 @EnableScheduling 开启任务调度

1.3.2 定义定时任务类

自定义定时任务类

@Component
@Slf4j
public class MyTask {
    @Scheduled(cron = "0/5 * * * * ?")
    public void executeTask(){
        log.info("定时任务执行:{}", new Date());
    }
}

1.3.3 功能测试

启动服务 → 查看日志


二、订单状态定时处理功能

2.1 需求分析

用户下单后可能出现:

  • 未支付:订单一直停留在“待支付”状态
  • 未确认完成:收货后未点击“完成”,订单一直处于“派送中”

解决方案

  • 每分钟检查是否有超时未支付订单(>15 分钟),若有则自动取消
  • 每天凌晨 1 点检查是否有“派送中”订单,若有则自动完成

2.2 代码开发

2.2.1 自定义定时任务类

@Component
@Slf4j
public class OrderTask {
    @Autowired
    private OrderMapper orderMapper;

    @Scheduled(cron = "0 * * * * ?") // 每分钟
    public void processTimeoutOrder(){
        log.info("处理支付超时订单:{}", new Date());
    }

    @Scheduled(cron = "0 0 1 * * ?") // 每天凌晨 1 点
    public void processDeliveryOrder(){
        log.info("处理派送中订单:{}", new Date());
    }
}

2.2.2 处理支付超时订单

@Scheduled(cron = "0 * * * * ?")
public void processTimeoutOrder(){
    log.info("处理支付超时订单:{}", new Date());
    LocalDateTime time = LocalDateTime.now().plusMinutes(-15);

    List<Orders> ordersList = orderMapper.getByStatusAndOrdertimeLT(Orders.PENDING_PAYMENT, time);
    if(ordersList != null && !ordersList.isEmpty()){
        for (Orders orders : ordersList) {
            orders.setStatus(Orders.CANCELLED);
            orders.setCancelReason("支付超时,自动取消");
            orders.setCancelTime(LocalDateTime.now());
            orderMapper.update(orders);
        }
    }
}

2.2.3 处理派送中订单

@Scheduled(cron = "0 0 1 * * ?")
public void processDeliveryOrder(){
    log.info("处理派送中订单:{}", new Date());
    LocalDateTime time = LocalDateTime.now().plusMinutes(-60);

    List<Orders> ordersList = orderMapper.getByStatusAndOrdertimeLT(Orders.DELIVERY_IN_PROGRESS, time);
    if(ordersList != null && !ordersList.isEmpty()){
        for (Orders orders : ordersList) {
            orders.setStatus(Orders.CANCELLED); // 已完成
            orderMapper.update(orders);
        }
    }
}

2.2.4 Mapper 层

OrderMapper.java

@Select("select * from orders where status = #{status} and order_time < #{orderTime}")
List<Orders> getByStatusAndOrdertimeLT(Integer status, LocalDateTime orderTime);

2.3 功能测试

步骤 1:查看订单表
存在状态为 1(待支付)的订单

步骤 2:开启定时任务
启动服务,控制台每分钟输出任务执行日志

步骤 3:再次查看订单表
状态已更新为 6(已取消)

证明定时任务生效。

“派送中”订单处理方式类似,可通过修改cron表达式加快任务频率进行测试。


关于定时任务的功能就基本实现了,下一章小柒味来 - WebSocket 来单提醒


欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1701220998@qq.com
导航页 GitHub