Spring Task 与 WebSocket 在订单系统中的应用
在订单处理场景中,定时任务常用于处理超时未支付订单、每日报表统计等;而 WebSocket 则能解决 HTTP 单向通信的局限,实现服务端主动推送消息(如新订单提醒、催单通知)。本文将结合实战案例,讲解如何在 Spring Boot 项目中集成这两项技术。
一、Spring Task 入门与配置
Spring Task 是 Spring 框架内置的轻量级定时任务调度器,无需引入 Quartz 等第三方依赖,通过注解即可快速实现。它支持 Cron 表达式、固定延迟和固定频率等多种调度方式。
1. 开启定时任务支持
在启动类或配置类上添加 @EnableScheduling 注解:
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2. 编写定时任务
使用 @Scheduled 注解标记需要定时执行的方法。常见的三种模式如下:
- 固定延迟:上次任务结束后等待指定时间再执行。
- 固定频率:每隔指定时间执行一次,不关心上次是否完成。
- Cron 表达式:灵活定义执行时间点。
@Component
public class ScheduledTasks {
private static final Logger logger = LoggerFactory.getLogger(ScheduledTasks.class);
/**
* 固定延迟:上次执行完成后间隔 5 秒执行
*/
@Scheduled(fixedDelay = 5000)
public void taskWithFixedDelay() {
logger.info("FixedDelay Task - 当前时间:{}", System.currentTimeMillis());
}
/**
* 固定频率:每 3 秒执行一次
*/
@Scheduled(fixedRate = 3000)
public void taskWithFixedRate() {
logger.info("FixedRate Task - 当前时间:{}", System.currentTimeMillis());
}
/**
* Cron 表达式:每分钟的第 10 秒执行
*/
@Scheduled(cron = "10 * * * * ?")
public void taskWithCron() {
logger.info("Cron Task - 当前时间:{}", System.currentTimeMillis());
}
}
3. Cron 表达式详解
Cron 表达式格式为:秒 分 时 日 月 周 [年]。常用示例包括:
- 每天凌晨 2 点:
0 0 2 * * ? - 每 10 分钟执行一次:
0 */10 * * * ? - 工作日上午 9 点:
0 0 9 * * MON-FRI
4. 线程池配置(重要)
默认情况下,Spring Task 使用单线程执行所有任务。如果某个任务耗时较长,会阻塞后续任务。生产环境建议配置异步线程池。
配置文件方式(application.yml):
spring:
task:
scheduling:
pool:
size: 10
thread-name-prefix: my-scheduler-
代码配置方式:
@Configuration
@EnableScheduling
public class SchedulingConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
}
}
5. 最佳实践
- 避免阻塞:耗时操作应放入异步线程中执行,例如使用
CompletableFuture。 - 异常处理:定时任务中的异常不会自动打印到控制台,建议在方法内捕获并记录日志。
- 动态配置:可通过
@Value注入配置文件中的 Cron 表达式,实现运行时调整。
二、WebSocket 实时通信实现
HTTP 协议是请求响应模式,服务器无法主动推送数据。WebSocket 基于 TCP 连接,实现了全双工通信,适合即时通讯、状态推送等场景。
1. 项目依赖
确保引入 Spring Boot WebSocket 支持及 Java WebSocket API:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
2. 服务端配置
需要注册一个 Bean 来自动扫描 @ServerEndpoint 注解的类。
package com.sky.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
3. 核心业务类
服务端需维护在线会话列表,通常使用 ConcurrentHashMap 保证线程安全。
package com.sky.websocket;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {
// 存储所有在线会话
private static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();
private Session session;
private String sid;
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
this.session = session;
this.sid = sid;
sessionMap.put(sid, session);
System.out.println("【连接】用户 " + sid + " 已连接,当前在线人数:" + sessionMap.size());
sendMessage(sid, "欢迎 " + sid + " 连接成功!");
}
@OnMessage
public void onMessage(String message, @PathParam("sid") String sid) {
System.out.println("【消息】用户 " + sid + " 发送:" + message);
if ("ping".equals(message)) {
sendMessage(sid, "pong");
} else {
sendToAll(sid + " 说:" + message);
}
}
@OnClose
public void onClose(@PathParam("sid") String sid) {
sessionMap.remove(sid);
System.out.println("【关闭】用户 " + sid + " 已断开,当前在线人数:" + sessionMap.size());
sendToAll("用户 " + sid + " 离开了聊天室");
}
@OnError
public void onError(Session session, Throwable error) {
System.out.println("【错误】" + error.getMessage());
}
/**
* 发送消息给指定用户
*/
public void sendMessage(String sid, String message) {
Session s = sessionMap.get(sid);
if (s != null && s.isOpen()) {
try {
s.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 群发消息
*/
public void sendToAll(String message) {
for (String key : sessionMap.keySet()) {
sendMessage(key, message);
}
}
public static int getOnlineCount() {
return sessionMap.size();
}
}
4. 前端客户端实现
浏览器端使用原生 WebSocket API 即可建立连接。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>WebSocket 测试</title>
</head>
<body>
<div id="status">连接状态:未连接</div>
<div id="messages"></div>
<input type="text" id="input" placeholder="输入消息">
<button onclick="sendMessage()">发送</button>
<script>
let ws = null;
let userId = 'user_' + Math.random().toString(36).substr(2, 6);
function connect() {
const wsUrl = `ws://localhost:8080/ws/${userId}`;
ws = new WebSocket(wsUrl);
ws.onopen = function() {
document.getElementById('status').innerHTML = '连接状态:已连接 ✓';
};
ws.onmessage = function(event) {
console.log('收到消息:', event.data);
addMessage(event.data, 'other');
};
ws.onclose = function() {
document.getElementById('status').innerHTML = '连接状态:已断开 ✗';
};
}
function sendMessage() {
const input = document.getElementById('input');
const message = input.value.trim();
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(message);
addMessage(message, 'self');
input.value = '';
}
}
function addMessage(text, type) {
const div = document.createElement('div');
div.innerText = text;
div.style.marginBottom = '10px';
document.getElementById('messages').appendChild(div);
}
window.onload = connect;
</script>
</body>
</html>
三、苍穹外卖场景整合
在订单系统中,我们可以利用上述技术实现来单提醒、催单通知及实时数据统计。
1. 来单提醒逻辑
当用户下单成功后,后端通过 WebSocket 将订单信息推送到商家端。
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private WebSocketServer webSocketServer;
@Override
@Transactional
public void submitOrder(OrderSubmitDTO orderSubmitDTO) {
// 1. 保存订单信息
Orders order = new Orders();
// ... 设置订单信息 ...
orderMapper.insert(order);
// 2. 构建推送消息
Map<String, Object> pushMessage = new HashMap<>();
pushMessage.put("type", "NEW_ORDER");
pushMessage.put("data", order);
pushMessage.put("timestamp", System.currentTimeMillis());
String jsonMessage = JSON.toJSONString(pushMessage);
// 3. 发送给商家(假设商家 ID 为 shop_001)
webSocketServer.sendMessage("shop_001", jsonMessage);
}
}
2. 催单功能
用户点击催单按钮时,后端查询订单状态并推送提醒消息。
@Override
public void remindOrder(Long orderId) {
Orders order = orderMapper.getById(orderId);
if (order == null || !isRemindable(order.getStatus())) {
throw new BusinessException("当前订单不支持催单");
}
Map<String, Object> remindMessage = new HashMap<>();
remindMessage.put("type", "REMINDER");
remindMessage.put("orderId", order.getId());
remindMessage.put("orderNumber", order.getNumber());
String jsonMessage = JSON.toJSONString(remindMessage);
webSocketServer.sendMessage("shop_001", jsonMessage);
}
3. 定时推送统计数据
结合 Spring Task,每隔一段时间向商家端推送今日订单数和营业额。
@Component
public class WebSocketTask {
@Autowired
private WebSocketServer webSocketServer;
@Autowired
private OrderMapper orderMapper;
@Scheduled(cron = "0/5 * * * * ?")
public void pushRealTimeData() {
Map<String, Object> data = new HashMap<>();
data.put("type", "REAL_TIME_DATA");
data.put("todayOrders", orderMapper.getTodayOrderCount());
data.put("todayAmount", orderMapper.getTodayTurnover());
data.put("onlineCount", WebSocketServer.getOnlineCount());
String jsonMessage = JSON.toJSONString(data);
webSocketServer.sendToAll(jsonMessage);
}
}
商家端接收到 REAL_TIME_DATA 类型消息后,更新页面 DOM 元素即可实现数据看板实时更新。

