今日目标
- 实现订单定时任务(超时自动取消、自动完成)
- 集成 WebSocket 实现来单提醒和催单功能
- 开始数据统计报表模块
一、订单定时任务
1.1 为什么需要定时任务?
有些业务不能等用户主动操作,需要系统自动处理:
- 超时取消:用户下单后15分钟未支付,自动取消订单
- 自动完成:订单派送中超过一定时间,自动标记为已完成
这些场景用 Spring 的 @Scheduled 注解实现。
1.2 订单超时自动取消
每分钟执行一次,查找超过15分钟未支付的订单,自动取消:
@Component
@Slf4j
public class OrderTask {
@Autowired
private OrderMapper orderMapper;
/**
* 处理超时订单,每分钟检查一次
*/
@Scheduled(cron = "0 * * * * ?")
public void processTimeoutOrder() {
log.info("处理超时订单:{}", LocalDateTime.now());
// 查询超过15分钟未支付的订单
LocalDateTime time = LocalDateTime.now().plusMinutes(–15);
List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(
Orders.PENDING_PAYMENT, time);
if (ordersList != null && ordersList.size() > 0) {
for (Orders orders : ordersList) {
orders.setStatus(Orders.CANCELLED);
orders.setCancelReason("订单超时,自动取消");
orders.setCancelTime(LocalDateTime.now());
orderMapper.update(orders);
}
}
}
}
1.3 订单自动完成
每天凌晨1点执行,查找派送中超过60分钟的订单,自动完成:
/**
* 处理派送中的订单,每天凌晨1点执行
*/
@Scheduled(cron = "0 0 1 * * ?")
public void processDeliveryOrder() {
log.info("处理派送中订单:{}", LocalDateTime.now());
LocalDateTime time = LocalDateTime.now().plusMinutes(–60);
List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(
Orders.DELIVERY_IN_PROGRESS, time);
if (ordersList != null && ordersList.size() > 0) {
for (Orders orders : ordersList) {
orders.setStatus(Orders.COMPLETED);
orders.setDeliveryTime(LocalDateTime.now());
orderMapper.update(orders);
}
}
}
1.4 Cron 表达式速查
| 0 * * * * ? | 每分钟整点执行 |
| 0 0 1 * * ? | 每天凌晨1点执行 |
| 0/5 * * * * ? | 每5秒执行一次 |
格式:秒 分 时 日 月 周
1.5 Mapper 新增
@Select("select * from orders where status = #{status} and order_time < #{orderTime}")
List<Orders> getByStatusAndOrderTimeLT(Integer status, LocalDateTime orderTime);
二、WebSocket 实时消息推送
2.1 为什么需要 WebSocket?
传统 HTTP 是"请求-响应"模式,服务端不能主动通知客户端。但来单提醒需要服务端主动推送消息给管理端,这时候就需要 WebSocket 长连接。
2.2 配置
注册 ServerEndpointExporter Bean,让 Spring 支持 WebSocket:
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
2.3 服务端实现
@Component
@ServerEndpoint("/ws/{sid}")
@Slf4j
public class WebSocketServer {
// 存放连接的客户端
private static Map<String, Session> sessionMap = new HashMap<>();
@OnOpen
public void onOpen(Session session, @PathParam("sid") sid) {
log.info("客户端:{} 建立连接", sid);
sessionMap.put(sid, session);
}
@OnClose
public void onClose(@PathParam("sid") sid) {
log.info("客户端:{} 断开连接", sid);
sessionMap.remove(sid);
}
@OnMessage
public void onMessage(String message, @PathParam("sid") sid) {
log.info("收到来自客户端:{} 的信息:{}", sid, message);
}
/**
* 群发消息给所有客户端
*/
public void sendToAllClient(String message) {
try {
for (Session session : sessionMap.values()) {
log.info("推送消息给客户端:{}", message);
session.getBasicRemote().sendText(message);
}
} catch (IOException e) {
log.error("推送消息失败:{}", e.getMessage());
}
}
}
2.4 来单提醒
用户支付成功后,通过 WebSocket 向管理端推送新订单通知:
// PayNotifyController 中
@Autowired
private WebSocketServer webSocketServer;
@RequestMapping("/paySuccess")
public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response)
throws Exception {
// … 解密、更新订单状态 …
// 推送来单提醒
webSocketServer.sendToAllClient("来单提醒:订单号 " + outTradeNo);
responseToWeixin(response);
}
2.5 催单功能
用户对已接单的订单进行催单:
@GetMapping("/reminder/{id}")
public Result reminder(@PathVariable("id") Long id) {
orderService.reminder(id);
return Result.success();
}
public void reminder(Long id) {
Orders ordersDB = orderMapper.getById(id);
if (ordersDB == null) {
throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);
}
// 只有待接单和已确认状态才能催单
if (!ordersDB.getStatus().equals(Orders.TO_BE_CONFIRMED)
&& !ordersDB.getStatus().equals(Orders.CONFIRMED)) {
throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
}
// 通过WebSocket推送催单消息
webSocketServer.sendToAllClient("催单提醒:订单号 " + ordersDB.getNumber());
}
三、数据统计报表(进行中)
新增 ReportController,按日期范围查询营业额:
@RestController("adminReportController")
@RequestMapping("/admin/report")
public class ReportController {
@Autowired
private ReportService reportService;
@GetMapping("/turnoverStatistics")
public Result<TurnoverReportVO> turnoverStatistics(LocalDate begin, LocalDate end) {
TurnoverReportVO reportVO = reportService.getTurnoverStatistics(begin, end);
return Result.success(reportVO);
}
}
目前先实现了日期列表生成,营业额数据查询还在完善中。
四、项目整体进度
| 员工管理 | ✅ 完成 |
| 分类管理 | ✅ 完成 |
| 菜品管理 | ✅ 完成 |
| 套餐管理 | ✅ 完成 |
| 文件上传 | ✅ 完成 |
| 店铺管理 | ✅ 完成 |
| 微信登录 | ✅ 完成 |
| 购物车 | ✅ 完成 |
| 地址簿 | ✅ 完成 |
| 订单管理 | ✅ 完成 |
| 微信支付 | ✅ 完成 |
| 定时任务 | ✅ 完成 |
| WebSocket 来单提醒 | ✅ 完成 |
| 催单功能 | ✅ 完成 |
| 数据统计报表 | 🔧 进行中 |
五、今日总结
今天学了三个新技术:
WebSocket 是之前没接触过的技术,理解"长连接"和"双向通信"花了点时间。但实际写起来比想象中简单,核心就是维护一个 Session Map,需要推送时遍历所有 Session 发送消息。
苍穹外卖 Day9 完成 ✅




