欢迎光临
我们一直在努力

♪苍穹外卖♪Day9 | 项目日记

今日目标

  • 实现订单定时任务(超时自动取消、自动完成)
  • 集成 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 来单提醒 ✅ 完成
催单功能 ✅ 完成
数据统计报表 🔧 进行中

五、今日总结

今天学了三个新技术:

  • Spring 定时任务:@Scheduled + Cron 表达式,实现订单超时自动取消和自动完成
  • WebSocket:@ServerEndpoint、@OnOpen、@OnClose、@OnMessage,实现服务端主动推送消息
  • 数据报表:日期范围查询,营业额统计接口(进行中)
  • WebSocket 是之前没接触过的技术,理解"长连接"和"双向通信"花了点时间。但实际写起来比想象中简单,核心就是维护一个 Session Map,需要推送时遍历所有 Session 发送消息。


    苍穹外卖 Day9 完成 ✅

    赞(0)
    未经允许不得转载:171主机测评 » ♪苍穹外卖♪Day9 | 项目日记
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址