《苍穹外卖》全栈项目:SpringBoot+MyBatis-Plus+Redis全程跟进记录DAY10
目录
Day10内容
Spring Task
一.简介
介绍:Spring Task 是Spring框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑。
作用:定时自动执行某段Java代码
应用场景:
- 信用卡每月还款提醒
- 银行贷款每月还款提醒
- 火车票售票系统
- 处理未支付订单
- 入职纪念日为用户发送通知
二.cron表达式
cron表达式其实就是一个字符串,通过cron表达式可以定义任务触发的时间 构成规则:分为6或7个域,由空格分隔开,每个域代表一个含义 每个域的含义分别为:秒、分钟、小时、日、月、周、年(可选)

cron表达式在线生成器:https://cron.qqe2.com/

三.入门案例
1.分析
Spring Task使用步骤:
- 导入maven坐标 spring-context(已存在)
- 启动类添加注解 @EnableScheduling 开启任务调度
- 自定义定时任务类
2.代码实现
创建task包和Mytask测试文件 com.sky.task.MyTask 编写每5秒执行一次
Mytask
package com.sky.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());
}
}
启动类添加注解 @EnableScheduling
package com.sky;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching//开启缓存注解功能
@EnableScheduling//开启任务调度
public class SkyApplication {
public static void main(String[] args) {
SpringApplication.run(SkyApplication.class, args);
log.info("server started");
}
}
3.测试

订单状态定时处理
一.问题分析
用户下单后可能存在的情况:
- 下单后未支付,订单一直处于“待支付”状态
- 用户收货后管理端未点击完成按钮,订单一直处于“派送中”状态
对于上面两种情况需要通过定时任务来修改订单状态,具体逻辑为:
- 通过定时任务每分钟检查一次是否存在支付超时订单(下单后超过15分钟仍未支付则判定为支付超时订单),如果存在则修改订单状态为“已取消”
- 通过定时任务每天凌晨1点检查一次是否存在“派送中”的订单,如果存在则修改订单状态为“已完成”
二.代码实现
OrderTask(新建)
package com.sky.task;
import com.sky.entity.Orders;
import com.sky.mapper.OrderMapper;
import com.sky.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.sky.entity.Orders;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
/**
* 定时任务类,定时处理订单状态
*/
@Component
@Slf4j
public class OrderTask {
@Autowired
private OrderMapper orderMapper;
/**
* 处理支付超时订单
*/
@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){
for(Orders order : ordersList) {
order.setStatus(Orders.CANCELLED);
order.setCancelReason("订单支付超时,自动取消");
order.setCancelTime(LocalDateTime.now());
orderMapper.update(order);
}
}
}
/**
* 处理处于派送中状态的订单
*/
@Scheduled(cron = "0 0 1 * * ?")//每天凌晨一点触发一次
public void processDeliveryOrder(){
log.info("定时处理处于派送中的订单{}",LocalDateTime.now());
LocalDateTime time = LocalDateTime.now().plusMinutes(-60);
orderMapper.getByStatusAndOrdertimeLT(Orders.DELIVERY_IN_PROGRESS,time);
List<Orders> ordersList = orderMapper.getByStatusAndOrdertimeLT(Orders.DELIVERY_IN_PROGRESS, time);
if(ordersList != null && ordersList.size() > 0){
for(Orders order : ordersList) {
order.setStatus(Orders.COMPLETED);
orderMapper.update(order);
}
}
}
}
在OrderMapper接口中扩展方法
/**
* 根据订单状态和下单时间查询订单
* @param status
* @param orderTime
* @return
*/
@Select("select * from orders where status = #{status} and order_time < #{orderTime}")
List<Orders> getByStatusAndOrdertimeLT(Integer status, LocalDateTime orderTime);
三.测试
为方便测试,注释掉了原先两个方法的执行时间,修改为Mytask中的执行时间
在数据库修改一个订单状态为1时,会被修改为6已取消,可以看到sql语句的执行

也可以等待一分钟,看到sql语句执行,order表中的状态更新为已取消


四.gitee提交记录
2025-08-05
- 提交说明:feat: 订单状态定时处理功能
-查看详情:[Gitee 提交记录]
feat: 订单状态定时处理功能 · 0cc79b4 · jh11/sky-take-out - Gitee.com
WebSocket
一.介绍
WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接, 并进行双向数据传输。


HTTP协议和WebSocket协议对比:
-
HTTP是短连接
-
WebSocket是长连接
-
HTTP通信是单向的,基于请求响应模式
-
WebSocket支持双向通信
-
HTTP和WebSocket底层都是TCP连接
应用场景:
- 视频弹幕
- 网页聊天
- 体育实况更新
- 股票基金报价实时更新
既然WebSocket支持双向通信,功能看似比HTTP强大,那么我们是不是可以基于WebSocket开发所有的业务功能?
WebSocket缺点:
服务器长期维护长连接需要一定的成本
各个浏览器支持程度不一
WebSocket 是长连接,受网络限制比较大,需要处理好重连
结论:WebSocket并不能完全取代HTTP,它只适合在特定的场景下使用
二.入门案例
一.实现步骤:
1.直接使用websocket.html页面作为WebSocket客户端
2.导入WebSocket的maven坐标
3.导入WebSocket服务端组件WebSocketServer,用于和客户端通信
4.导入配置类WebSocketConfiguration,注册WebSocket的服务端组件
5.导入定时任务类WebSocketTask,定时向客户端推送数据
二.代码实现
导入代码
WebSocketTask 路径 com.sky.task.WebSocketTask
package com.sky.task;
import com.sky.websocket.WebSocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Component
public class WebSocketTask {
@Autowired
private WebSocketServer webSocketServer;
/**
* 通过WebSocket每隔5秒向客户端发送消息
*/
@Scheduled(cron = "0/5 * * * * ?")
public void sendMessageToClient() {
webSocketServer.sendToAllClient("这是来自服务端的消息:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
}
}
WebSocketServer 路径 com.sky.websocket.WebSocketServer
package com.sky.websocket;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* 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();
}
}
}
}
WebSocketConfiguration 路径 com.sky.config.WebSocketConfiguration
package com.sky.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* WebSocket配置类,用于注册WebSocket的Bean
*/
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
maven坐标和HTML页面已经定义或提供
三.测试



来单提醒
一.问题分析
用户下单并且支付成功后,需要第一时间通知外卖商家。通知的形式有如下两种:
-
语音播报

-
弹出提示框
产品原型

设计思路:
- 通过WebSocket实现管理端页面和服务端保持长连接状态
- 当客户支付后,调用WebSocket的相关API实现服务端向客户端推送消息
- 客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报
- 约定服务端发送给客户端浏览器的数据格式为JSON,字段包括:type,orderId,content
type 为消息类型,1为来单提醒 2为客户催单
orderId 为订单id
content 为消息内容
二.代码实现
在OrderServiceImpl中注入WebSocketServer对象,修改paySuccess方法,加入如下代码:
/**
* 支付成功,修改订单状态
*
* @param outTradeNo
*/
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向客户端浏览器推送消息 type orderId content
Map map=new HashMap();
map.put("type",1);//消息类型,1表示来单提醒
map.put("orderId",orders.getId());
map.put("content","订单号:"+outTradeNo);
String json=JSON.toJSONString(map);
//通过WebSocket实现来单提醒,向客户端浏览器推送消息
webSocketServer.sendToAllClient(json);
--------------------------------------------------------------------------------------
}
由于之前微信支付采用了模拟支付,所以paySuccess方法不会被调用,所以修改pyment代码实现此功能,与此同时,优化一下paySuccess中的getByNumber,如果只查询订单号,就无法知道是哪个用户点的单,所以把getByNumber优化为getByNumberAndUserId,修改如下
修改后的OrderServiceImpl
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);
//通过Websocket向客户端浏览器推送消息 type orderId content
Map map=new HashMap();
map.put("type",1);//消息类型,1表示来单提醒
map.put("orderId",orders.getId());
map.put("content","订单号:"+outTradeNo);
String json=JSON.toJSONString(map);
//通过WebSocket实现来单提醒,向客户端浏览器推送消息
webSocketServer.sendToAllClient(json);
}
OrderMapper
/**
* 根据订单号和用户id查询订单
* @param orderNumber
* @param userId
*/
@Select("select * from orders where number = #{orderNumber} and user_id= #{userId}")
Orders getByNumberAndUserId(String orderNumber, Long userId);
将订单提醒功能加入OrderServiceImpl的payMent方法中(虚线划分部分)
Integer OrderPaidStatus = Orders.PAID; //支付状态,已支付
Integer OrderStatus = Orders.TO_BE_CONFIRMED; //订单状态,待接单
//发现没有将支付时间 check_out属性赋值,所以在这里更新
LocalDateTime check_out_time = LocalDateTime.now();
//获取订单号码
String orderNumber = ordersPaymentDTO.getOrderNumber();
log.info("调用updateStatus,用于替换微信支付更新数据库状态的问题");
orderMapper.updateStatus(OrderStatus, OrderPaidStatus, check_out_time, orderNumber);
--------------------------------------------------------------------------------------
Map map = new HashMap();
map.put("type", 1);// 消息类型,1表示来单提醒
//获取订单id
Orders orders=orderMapper.getByNumberAndUserId(orderNumber, userId);
map.put("orderId", orders.getId());
map.put("content", "订单号:" + orderNumber);
// 通过WebSocket实现来单提醒,向客户端浏览器推送消息
webSocketServer.sendToAllClient(JSON.toJSONString(map));
log.info("来单提醒:{}", JSON.toJSONString(map));
-----------------------------------------------------------------------------
return vo;
}
三.测试
在小程序下单后,管理端有消息播报提醒和语音播报提醒,如无语音提醒,请重新登录尝试

四.gitee提交记录
2025-08-05
- 提交说明:feat: 来单提醒功能
-查看详情:[Gitee 提交记录]
feat: 来单提醒功能 · 980de04 · jh11/sky-take-out - Gitee.com
客户催单
一.问题分析
用户在小程序中点击催单按钮后,需要第一时间通知外卖商家。通知的形式有如下两种:
-
语音播报

-
弹出提示框
产品原型

设计思路:
通过WebSocket实现管理端页面和服务端保持长连接状态
当用户点击催单按钮后,调用WebSocket的相关API实现服务端向客户端推送消息
客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报
约定服务端发送给客户端浏览器的数据格式为JSON,字段包括:type,orderId,content
type 为消息类型,1为来单提醒 2为客户催单
orderId 为订单id
content 为消息内容
当用户点击催单按钮时,向服务端发送请求。
接口设计

二.代码实现
OrderController(user)
/**
* 用户催单
* @param id
* @return
*/
@GetMapping("/reminder/{id}")
@ApiOperation("用户催单")
public Result reminder(@PathVariable("id") Long id) {
orderService.reminder(id);
return Result.success();
}
OrderService
/**
* 用户催单
* @param id
*/
void reminder(Long id);
OrderServiceImpl
/**
* 用户催单
* @param id
*/
public void reminder(Long id) {
//根据id查询订单
Orders ordersDB = orderMapper.getById(id);
//检验订单是否存在
if (ordersDB == null) {
throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
}
Map map = new HashMap();
map.put("type",2);
map.put("orderId",id);
map.put("content","订单号:"+ordersDB.getNumber());
//调用websocket向客户端浏览器推送消息
webSocketServer.sendToAllClient(JSON.toJSONString(map));
}
OrderMapper内容先前已经定义,不做赘述
三.测试
在小程序点击催单按钮,会弹出催单成功的弹窗

管理端会收取到催单的消息提醒和语音提醒

四.gitee提交记录
2025-08-05
- 提交说明:feat: 用户催单功能
-查看详情:[Gitee 提交记录]
feat: 用户催单功能 · 8771fbb · jh11/sky-take-out - Gitee.com
资料来源:B站 黑马程序员
更多推荐
所有评论(0)