苍穹外卖学习笔记——第十天
订单状态定时处理、来单提醒和客户催单
Spring Task
介绍
- Spring Task 是Spring框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑。
应用场景
- 信用卡每月还款提醒。
- 银行贷款每月还款提醒。
- 火车票售票系统处理未支付订单。
- 入职纪念日为用户发送通知。
- ……
只要是需要定时处理的场景都可以使用Spring Task。
cron表达式
- cron表达式其实就是一个字符串,通过cron表达式可以定义任务触发的时间。
- 构成规则:分为6或7个域,由空格分隔开,每个域代表一个含义。
- 每个域的含义分别为:秒、分钟、小时、日、月、周、年(可选)。
- 一般日和周不用同时指定,没指定的那个域可以使用?占位。
- cron表达式在线生成器:https://cron.qqe2.com/。
Spring Task使用步骤
- 导入maven坐标 spring-context(Spring Task包含在spring-context中)。
- 启动类添加注解 @EnableScheduling 开启任务调度。
- 自定义定时任务类。
订单状态定时处理
需求分析
用户下单后可能存在的情况:
- 下单后未支付,订单一直处于“待支付”状态。
- 用户收货后管理端未点击完成按钮,订单一直处于“派送中”状态。
对于上面两种情况需要通过定时任务来修改订单状态,具体逻辑为:
- 通过定时任务每分钟检查一次是否存在支付超时订单(下单后超过15分钟仍未支付则判定为支付超时订单),如果存在则修改订单状态为“已取消”。
- 通过定时任务每天凌晨1点检查一次是否存在“派送中”的订单,如果存在则修改订单状态为“已完成”。
代码开发
- 在com.sky包下创建task包,然后在task包下创建OrderTask类,再在OrderTask类中创建processTimeoutOrder方法和processDeliveryOrder方法:
@Component
@Slf4j
public class OrderTask {
@Autowired
private OrderMapper orderMapper;
@Scheduled(cron = "0 * * * * ?") //每分钟触发一次
public void processTimeoutOrder() {
LocalDateTime now = LocalDateTime.now();
log.info("定时处理超时订单:{}", now);
//处理15分钟前创建的未付款订单
LocalDateTime time = now.minusMinutes(15);
List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.PENDING_PAYMENT, time);
//修改订单状态、取消时间、取消原因并批量更新
if (ordersList != null && !ordersList.isEmpty()) {
for (Orders orders : ordersList) {
orders.setStatus(Orders.CANCELLED);
orders.setCancelTime(now);
orders.setCancelReason("订单超时,自动取消");
}
orderMapper.updateBatch(ordersList);
}
}
@Scheduled(cron = "0 0 1 * * ?") //每天凌晨1点触发一次
public void processDeliveryOrder() {
LocalDateTime now = LocalDateTime.now();
log.info("定时处理处于派送中的订单:{}", now);
//处理前一天创建的处于派送中的订单
LocalDateTime time = now.minusHours(1);
List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.DELIVERY_IN_PROGRESS, time);
//修改订单状态并批量更新
if (ordersList != null && !ordersList.isEmpty()) {
for (Orders orders : ordersList) {
orders.setStatus(Orders.COMPLETED);
}
orderMapper.updateBatch(ordersList);
}
}
}
- 在OrderMapper接口中创建getByStatusAndOrderTimeLT方法并声明updateBatch方法:
@Select("select * from orders where status = #{status} and order_time < #{orderTime}")
List<Orders> getByStatusAndOrderTimeLT(Integer status, LocalDateTime orderTime);
void updateBatch(List<Orders> ordersList);
- 在OrderMapper.xml中编写updateBatch方法的SQL语句:
<update id="updateBatch">
update orders
<foreach collection="ordersList" item="ol" separator=", ">
<set>
<if test="ol.cancelReason != null and ol.cancelReason!=''">
cancel_reason = #{ol.cancelReason},
</if>
<if test="ol.rejectionReason != null and ol.rejectionReason!=''">
rejection_reason = #{ol.rejectionReason},
</if>
<if test="ol.cancelTime != null">
cancel_time = #{ol.cancelTime},
</if>
<if test="ol.payStatus != null">
pay_status = #{ol.payStatus},
</if>
<if test="ol.payMethod != null">
pay_method = #{ol.payMethod},
</if>
<if test="ol.checkoutTime != null">
checkout_time = #{ol.checkoutTime},
</if>
<if test="ol.status != null">
status = #{ol.status},
</if>
<if test="ol.deliveryTime != null">
delivery_time = #{ol.deliveryTime}
</if>
</set>
</foreach>
where
<foreach collection="ordersList" item="ol">
id = #{ol.id}
</foreach>
</update>
功能测试
可以通过如下方式进行测试:查看控制台日志和sql,查看数据库中数据变化。
WebSocket
介绍
WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接, 并进行双向数据传输。
HTTP协议和WebSocket协议的对比
HTTP | WebSocket | ||
---|---|---|---|
不同点 | 连接时长 | 短连接:客户端发送请求时建立,服务端回复响应时结束 | 长连接:客户端发送握手请求建立后,连接一直存在,直到发出连接结束的请求 |
通信方向 | 单向:基于请求响应模式,只能由客户端发起、服务端响应 | 双向:客户端和服务端都能发送消息 | |
相同点 | HTTP和WebSocket底层都是TCP连接 |
应用场景
- 视频弹幕。
- 网页聊天。
- 体育实况更新。
- 股票基金报价实时更新。
- ……
WebSocket的缺点
- 服务器长期维护长连接需要一定的成本。
- 各个浏览器的支持程度不一样。
- WebSocket 是长连接,受网络限制比较大,需要处理好重连。
结论:WebSocket并不能完全取代HTTP,它只适合在特定的场景下使用。
使用步骤
- 编写html页面作为WebSocket客户端。
- 导入WebSocket的maven坐标:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
- 编写WebSocket服务端用于和客户端通信的类。
- 编写WebSocket的配置类,注册WebSocket的服务端组件。
- 自定义与客户端通信的方法。
来单提醒
需求分析和设计
需求分析
用户下单并且支付成功后,需要第一时间通知外卖商家。通知的形式有如下两种:语音播报、弹出提示框。
设计
-
通过WebSocket实现管理端页面和服务端保持长连接状态。
-
当客户支付后,调用WebSocket的相关API实现服务端向客户端推送消息。
-
客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报。
-
约定服务端发送给客户端浏览器的数据格式为JSON,字段包括:type,orderId,content。
-
type为消息类型,1为来单提醒,2为客户催单。
-
orderId为订单id。
-
content为消息内容。
-
代码开发
- 在com.sky包下创建websocket包,再导入WebSocket服务端组件WebSocketServer:
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {
//存放会话对象
private static Map<String, Session> sessionMap = new HashMap();
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
System.out.println("客户端:" + sid + "建立连接");
sessionMap.put(sid, session);
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, @PathParam("sid") String sid) {
System.out.println("收到来自客户端:" + sid + "的信息:" + message);
}
/**
* 连接关闭调用的方法
*
* @param sid
*/
@OnClose
public void onClose(@PathParam("sid") String sid) {
System.out.println("连接断开:" + sid);
sessionMap.remove(sid);
}
/**
* 群发
*
* @param message
*/
public void sendToAllClient(String message) {
Collection<Session> sessions = sessionMap.values();
for (Session session : sessions) {
try {
//服务器向客户端发送消息
session.getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
- 在com.sky.config包下导入配置类WebSocketConfiguration:
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
- 在OrderServiceImpl中注入WebSocketServer对象,并修改paySuccess方法:
@Autowired
private WebSocketServer webSocketServer;
public void paySuccess(String outTradeNo) {
...
//通过WebSocket向客户端浏览器推送消息
Map map = new HashMap();
map.put("type", 1); //1表示来单提醒,2表示客户催单
map.put("orderId", ordersDB.getId());
map.put("content", "订单号:" + outTradeNo);
String json = JSON.toJSONString(map);
webSocketServer.sendToAllClient(json);
}
功能测试
可以通过如下方式进行测试:查看浏览器调试工具数据交互过程,前后端联调。
客户催单
需求分析和设计
需求分析
用户在小程序中点击催单按钮后,需要第一时间通知外卖商家。通知的形式有如下两种:语音播报、弹出提示框。
设计
- 通过WebSocket实现管理端页面和服务端保持长连接状态。
- 当用户点击催单按钮后,调用WebSocket的相关API实现服务端向客户端推送消息。
- 客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报。
- 约定服务端发送给客户端浏览器的数据格式为JSON,字段包括:type,orderId,content。
- type 为消息类型,1为来单提醒,2为客户催单。
- orderId为订单id。
- content为消息内容。
接口设计

代码开发
- 在user/OrderController中创建reminder方法:
@GetMapping("/reminder/{id}")
@ApiOperation("客户催单")
public Result reminder(@PathVariable Long id) {
log.info("客户催单,订单id为:{}", id);
orderService.reminder(id);
return Result.success();
}
- 在OrderService接口中声明reminder方法:
void reminder(Long orderId);
- 在OrderServiceImpl中实现reminder方法:
@Override
public void reminder(Long orderId) {
//根据订单号查询订单
Orders orders = orderMapper.getById(orderId);
//通过WebSocket向客户端浏览器推送消息
Map map = new HashMap();
map.put("type", 2); //1表示来单提醒,2表示客户催单
map.put("orderId", orders.getId());
map.put("content", orders.getNumber());
String json = JSON.toJSONString(map);
webSocketServer.sendToAllClient(json);
}
功能测试
可以通过如下方式进行测试:查看浏览器调试工具数据交互过程,前后端联调。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了