🔥 本文由 程序喵正在路上 原创,CSDN首发!
💖 系列专栏:苍穹外卖项目实战
🌠 首发时间:2024年5月21日
🦋 欢迎关注🖱点赞👍收藏🌟留言🐾
目录
Spring Task
介绍
Spring Task 是 Spring 框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑。
- 定位:定时任务框架
- 作用:定时自动执行某段 Java 代码
应用场景:
- 信用卡每月还款提醒
- 银行贷款每月还款提醒
- 火车票售票系统处理未支付订单
- 入职纪念日为用户发送通知
只要是需要定时处理的场景都可以使用 Spring Task
cron表达式
cron 表达式其实就是一个字符串,通过 cron 表达式可以定义任务触发的时间
- 构成规则:分为 6 或 7 个域,由空格分隔开,每个域代表一个含义
- 每个域的含义分别为:秒、分钟、小时、日、月、周、年(可选)
下面是一个简单的 cron 表达式:
虽然 cron 表达式不是很复杂,但是有时候自己写起来还是有点麻烦的,所以我们这里介绍一个 cron 表达式在线生成器:https://cron.qqe2.com/,我们只要选择自己的需求,它就会自动帮我们生成 cron 表达式。
入门案例
Spring Task 使用步骤:
-
导入 maven 坐标,其包含在 spring-context 这个依赖中(已存在)
-
启动类添加注解
@EnableScheduling
开启任务调度 -
自定义定时任务类
新建一个包 task 用来存放我们的定时任务类:
import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.Date; @Component @Slf4j public class MyTask { /** * 定时任务, 每隔5秒触发一次 */ @Scheduled(cron = "0/5 * * * * ?") public void executeTask() { log.info("定时任务开始执行:{}", new Date()); } }
当我们启动服务后,控制台每隔 5 秒就会输出一次:
测试完,可以将这个类注释掉
订单状态定时处理
需求分析
用户下单后可能存在的情况:
- 下单后未支付,订单一直处于 “待支付” 状态
- 用户收货后管理端未点击完成按钮,订单一直处于 “派送中” 状态
对于上面两种情况需要通过定时任务来修改订单状态,具体逻辑为:
- 通过定时任务每分钟检查一次是否存在支付超时订单(下单后超过15分钟仍未支付则判定为支付超时订单),如果存在则修改订单状态为 “已取消”
- 通过定时任务每天凌晨1点检查一次是否存在 “派送中” 的订单,如果存在则修改订单状态为 “已完成”
代码开发
在 task 下创建订单定时任务类 OrderTask:
import com.sky.constant.MessageConstant;
import com.sky.entity.Orders;
import com.sky.mapper.OrderMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.List;
@Component
@Slf4j
public class OrderTask {
@Autowired
private OrderMapper orderMapper;
/**
* 支付超时订单处理
*/
@Scheduled(cron = "0 * * * * ?") //每分钟执行一次
public void processTimeoutOrder() {
log.info("开始进行支付超时订单处理:{}", LocalDateTime.now());
LocalDateTime time = LocalDateTime.now().plusMinutes(-15); //在该时间前未支付的订单都已超时
List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.PENDING_PAYMENT, time);
if (ordersList != null && ordersList.size() > 0) {
ordersList.forEach(orders -> {
orders.setStatus(Orders.CANCELLED); //已取消
orders.setCancelReason(MessageConstant.PAYMENT_TIMEOUT_AUTO_CANCEL); //取消原因
orders.setCancelTime(LocalDateTime.now()); //取消时间
//更新订单
orderMapper.update(orders);
});
}
}
/**
* 派送中状态的订单处理
*/
@Scheduled(cron = "0 0 1 * * ?") //每天凌晨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) {
ordersList.forEach(orders -> {
orders.setStatus(Orders.COMPLETED); //已完成
//更新订单
orderMapper.update(orders);
});
}
}
}
在 OrderMapper 中创建 getByStatusAndOrderTimeLT 方法并配置 SQL:
/**
* 根据订单状态和下单时间查询订单
*
* @param status
* @param orderTime
* @return
*/
@Select("select * from orders where status = #{status} and order_time < #{orderTime}")
List<Orders> getByStatusAndOrderTimeLT(Integer status, LocalDateTime orderTime);
功能测试
可以通过如下方式进行测试:
- 查看控制台sql
- 查看数据库中数据变化
可以将时间改一下,这样比较好测试
WebSocket
介绍
WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接, 并进行双向数据传输。
HTTP 协议和 WebSocket 协议对比:
- HTTP 是短连接
- WebSocket 是长连接
- HTTP 通信是单向的,基于请求响应模式
- WebSocket 支持双向通信
- HTTP 和 WebSocket 底层都是 TCP 连接
应用场景:
- 视频弹幕
- 网页聊天
- 体育实况更新
- 股票基金报价实时更新
入门案例
接一下,我们实现一个简单的 websocket 入门案例,效果如下图所示
实现步骤:
- 直接使用 websocket.html 页面作为 WebSocket 客户端
- 导入 WebSocket 的 maven 坐标(项目中已存在)
- 导入 WebSocket 服务端组件 WebSocketServer,用于和客户端通信
- 导入配置类 WebSocketConfiguration,注册 WebSocket 的服务端组件
- 导入定时任务类 WebSocketTask,定时向客户端推送数据
相关代码在资料中已全部准备好,我们只需要导入代码,看懂代码逻辑并测试一下即可。
记得重新编译一下整个项目。
启动服务,打开客户端:
思考:既然 WebSocket 支持双向通信,功能看似比 HTTP 强大,那么我们是不是可以基于 WebSocket 开发所有的业务功能?
WebSocket 缺点:
- 服务器长期维护长连接需要一定的成本
- 各个浏览器支持程度不一
- WebSocket 是长连接,受网络限制比较大,需要处理好重连
结论:WebSocket 并不能完全取代 HTTP,它只适合在特定的场景下使用
来单提醒
需求分析和设计
用户下单并且支付成功后,需要第一时间通知外卖商家。通知的形式有如下两种:
-
语音播报
-
弹出提示框
设计:
- 通过 WebSocket 实现管理端页面和服务端保持长连接状态
- 当客户支付后,调用 WebSocket 的相关 API 实现服务端向客户端推送消息
- 客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报
- 约定服务端发送给客户端浏览器的数据格式为 JSON,字段包括:type,orderId,content
- type 为消息类型,1 为来单提醒,2 为客户催单
- orderId 为订单 id
- content 为消息内容
代码开发
前面,我们已经导入 WebSocket 服务端组件,我们就是通过它来和客户端进行通信的。
在 OrderServiceImpl 中注入 WebSocketServer 对象,然后修改 paySuccess 方法,加入一些代码:
public void paySuccess(String outTradeNo) {
// 根据订单号查询订单
Orders ordersDB = orderMapper.getByNumber(outTradeNo);
// 根据订单id更新订单的状态、支付方式、支付状态、结账时间
Orders orders = Orders.builder()
.id(ordersDB.getId())
.status(Orders.TO_BE_CONFIRMED)
.payStatus(Orders.PAID)
.checkoutTime(LocalDateTime.now())
.build();
orderMapper.update(orders);
//通过websocket向客户端浏览器推送消息
Map map = new HashMap();
map.put("type", 1); //来单提醒
map.put("orderId", orders.getId());
map.put("content", "订单号:" + outTradeNo);
webSocketServer.sendToAllClient(JSON.toJSONString(map));
}
功能测试
启动服务,可以看到 websocket 已经连接:
来到小程序端,随便添加菜品,用户完成支付后,管理端就会自动弹出待接单提醒,同时会有语音播报:
如果没有语音的话,可以尝试管理端重新登录,或者小程序端重新编译。
用户催单
需求分析和设计
当订单长时间没被接单时,用户可以点击催单按钮进行催单,用户点击按钮后,系统需要第一时间通知外卖商家。通知的形式有如下两种:
- 语音播报
- 弹出提示框
设计:
- 通过 WebSocket 实现管理端页面和服务端保持长连接状态
- 当用户点击催单按钮后,调用 WebSocket 的相关 API 实现服务端向客户端推送消息
- 客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报
- 约定服务端发送给客户端浏览器的数据格式为 JSON,字段包括:type,orderId,content
- type 为消息类型,1 为来单提醒,2 为客户催单
- orderId 为订单 id
- content 为消息内容
接口设计:
代码开发
在 OrderController 中创建 reminder 方法:
/**
* 用户催单
*
* @param id
* @return
*/
@GetMapping("/reminder/{id}")
@ApiOperation("用户催单")
public Result reminder(@PathVariable Long id) {
orderService.reminder(id);
return Result.success();
}
在 OrderService 接口中声明 reminder 方法:
/**
* 用户催单
*
* @param id
*/
void reminder(Long id);
在 OrderServiceImpl 中实现 reminder 方法:
/**
* 用户催单
*
* @param id
*/
public void reminder(Long id) {
//查询订单是否存在
Orders orders = orderMapper.getById(id);
if (orders == null) {
throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);
}
//基于websocket实现催单
HashMap map = new HashMap();
map.put("type", 2); //用户催单
map.put("orderId", id);
map.put("content", "订单号:" + orders.getNumber());
webSocketServer.sendToAllClient(JSON.toJSONString(map));
}
功能测试
启动服务,来到小程序端,随便打开一个待接单的订单,点击催单按钮后,可以发现管理端自动弹出催单提醒,同时会有语音播报: