本篇文章介绍了定时任务的快速上手方式(@EnableScheduling、@Scheduled 与常用 cron 表达式)、WebSocket 服务端组件的注册与群发消息的实现思路,以及 HTTP 与 WebSocket 在连接形态和通信模式上的差异,为项目加入“准实时”能力打下基础。

完成任务清单
- 订单状态定时处理
- 来单提醒
- 客户催单
主要功能展示
1. 订单状态定时处理
在这一个功能下,使用到了Spring Task的知识点,简单讲解,作为记录。
Spring Task 快速入门
Spring Task是Spring框架提供的任务调度工具,可以按照约定时间自动执行代码逻辑。
业务场景
常用于每月话费短信、自动取消超时支付的订单等地方。
实现流程
1.导入Maven坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
2.添加注解@EnableScheduling至启动类,开启任务调度
3.自定义任务类:每五秒自动触发一次
我们使用@Scheduled设置定时,括号内是
cron表达式
cron表达式实则就是字符串,可以定义任务触发的时间,从左到右分别为:秒、分钟、小时、日、月、周、年(可选)
可以使用在线生成器生成:cron表达式在线生成器
@Component
@Slf4j
public class MyTask {
@Scheduled(cron = "0/5 * * * * ?")
public void executeTask(){
log.info("定时任务开始执行:{}",new Date());
}
}
订单状态定时处理功能
业务分析
用户下单之后可能存在以下情况
1.下单后未支付,订单一直处于待支付状态
2.用户收货后管理端未点击完成按钮,订单一直处于派送中状态
解决思路
通过Spring Task去进行定时任务来修改订单状态即可,如以下情况
下单后未支付,订单一直处于待支付状态
用户收货后管理端未点击完成按钮,订单一直处于派送中状态
实现流程
1.先自定义定时任务类,用于备用(别忘记在启动类添加@EnableScheduling)
@Component
@Slf4j
public class OrderTask {
@Autowired
private OrderMapper orderMapper;
/**
* 处理支付超时订单
*/
@Scheduled(cron = "0 * * * * ?")
public void processTimeoutOrder(){
log.info("处理支付超时订单:{}", new Date());
}
/**
* 处理“派送中”状态的订单
*/
@Scheduled(cron = "0 0 1 * * ?")
public void processDeliveryOrder(){
log.info("处理派送中订单:{}", new Date());
}
}
2.由于涉及了订单的时间,因此需要在对应Mapper接口拓展方法。
/**
* 根据状态和下单时间查询订单
* @param status
* @param orderTime
*/
@Select("select * from orders where status = #{status} and order_time < #{orderTime}")
List<Orders> getByStatusAndOrdertimeLT(Integer status, LocalDateTime orderTime);
3.完善自定义任务类
处理支付超时订单
@Scheduled(cron = "0 * * * * ?")
public void processTimeoutOrder(){
log.info("处理支付超时订单:{}", new Date());
LocalDateTime time = LocalDateTime.now().plusMinutes(-15);
// select * from orders where status = 1 and order_time < 当前时间-15分钟
List<Orders> ordersList = orderMapper.getByStatusAndOrdertimeLT(Orders.PENDING_PAYMENT, time);
if(ordersList != null && ordersList.size() > 0){
ordersList.forEach(order -> {
order.setStatus(Orders.CANCELLED);
order.setCancelReason("支付超时,自动取消");
order.setCancelTime(LocalDateTime.now());
orderMapper.update(order);
});
}
}
处理派送中状态的订单:
@Scheduled(cron = "0 0 1 * * ?")
public void processDeliveryOrder(){
log.info("处理派送中订单:{}", new Date());
// select * from orders where status = 4 and order_time < 当前时间-1小时
LocalDateTime time = LocalDateTime.now().plusMinutes(-60);
List<Orders> ordersList = orderMapper.getByStatusAndOrdertimeLT(Orders.DELIVERY_IN_PROGRESS, time);
if(ordersList != null && ordersList.size() > 0){
ordersList.forEach(order -> {
order.setStatus(Orders.COMPLETED);
orderMapper.update(order);
});
}
}
2. 来单提醒与客户催单功能
在这一个功能下,使用到了WebSocket的知识点。
WebSocket 简单介绍
WebSocket是一种在单个TCP连接上进行全双工通信的网络协议,允许服务器和客户端之间进行实时双向数据传输。
HTTP协议与WebSocket协议对比:
| 对比项目 | HTTP协议 | WebSocket协议 |
|---|---|---|
| 连接类型 | 短连接 | 长连接 |
| 通信方向 | 单向通信,基于请求响应模式 | 双向通信 |
| 底层协议 | 基于TCP连接 | 基于TCP连接 |
业务场景
WebSocket功能看似比HTTP强大,但由于WebSocket是长连接,受网络限制比较大,加之服务器长期维护长连接需要成本,故只能在特定场景使用。
常适用于视频弹幕、网络聊天、实况更新等场景
解决思路
1.通过WebSocket实现管理端页面和服务端保持长连接状态
2.当客户支付后,调用WebSocket的相关API实现服务端向客户端推送消息
3.客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报
4.约定服务端发送给客户端浏览器的数据格式为JSON,字段包括:type(消息类型),orderId(订单id),content(消息内容)
来单提醒功能
业务场景
用户下单并且支付成功后,需要第一时间通知外卖商家。通知形式一般有语音播报和弹出提示框
实现流程图
来单提醒:
实现流程
1.定义WebSocket服务端组件,放置于服务器模块
@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();
}
}
}
}
2.定义配置类,注册WebSocket的服务端组件
/**
* WebSocket配置类,用于注册WebSocket的Bean
*/
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
3.在实现类中注入WebSocketServer对象,修改paySuccess方法(来单提醒功能)
@Autowired
private WebSocketServer webSocketServer;
public void paySuccess(String outTradeNo) {
// 当前登录用户id
Long userId = BaseContext.getCurrentId();
// 根据订单号查询当前用户的订单
Orders ordersDB = orderMapper.getByNumberAndUserId(outTradeNo, userId);
// 根据订单id更新订单的状态、支付方式、支付状态、结账时间
Orders orders = Orders.builder()
.id(ordersDB.getId())
.status(Orders.TO_BE_CONFIRMED)
.payStatus(Orders.PAID)
.checkoutTime(LocalDateTime.now())
.build();
orderMapper.update(orders);
Map map = new HashMap();
map.put("type", 1);//消息类型,1表示来单提醒
map.put("orderId", orders.getId());
map.put("content", "订单号:" + outTradeNo);
//通过WebSocket实现来单提醒,向客户端浏览器推送消息
webSocketServer.sendToAllClient(JSON.toJSONString(map));
}
用户催单功能
业务场景
用户催单后,也需要第一时间通知外卖商家,通知形式一般有语音播报和弹出提示框
实现流程图
实现流程
1.由于已经定义WebSocket服务端组件与配置类,故跳过
2.根据用户催单的接口定义,在Controller层创建方法
@GetMapping("/reminder/{id}")
@ApiOperation("用户催单")
public Result reminder(@PathVariable("id") Long id) {
orderService.reminder(id);
return Result.success();
}
3.在实现类中实现
public void reminder(Long id) {
// 查询订单是否存在
Orders orders = orderMapper.getById(id);
if (orders == null) {
throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);
}
//基于WebSocket实现催单
Map map = new HashMap();
map.put("type", 2);//2代表用户催单
map.put("orderId", id);
map.put("content", "订单号:" + orders.getNumber());
webSocketServer.sendToAllClient(JSON.toJSONString(map));
}
本文为苍穹外卖项目学习笔记,持续更新中…
如果我的内容对你有帮助,希望可以收获你的点赞、评论、收藏。

1725

被折叠的 条评论
为什么被折叠?



