

🔥个人主页:北极的代码(欢迎来访)
🎬作者简介:java后端学习者
✨命运的结局尽可永在,不屈的挑战却不可须臾或缺!
前言:我们完成了前一天的作业,进入到了苍穹外卖的第十天开发,今天要学的内容主要是订单的定时处理,来单提醒,客户催单等功能,为了实现这些功能,我们要学习一些新的技术,Spring框架提供的Spring Task 自动处理定时任务的,以及websocket,可以在客户端浏览器和服务端进行双向的数据传输。
Spring Task 介绍与入门
一、什么是 Spring Task
Spring Task 是 Spring 框架内置的轻量级定时任务调度框架,它提供了简洁的声明式定时任务解决方案。开发者只需使用注解即可快速实现基于 cron 表达式、固定延迟或固定频率的任务调度。
应用场景:
信用卡每月还款,火车票售票系统处理未支付的订单,入职纪念日等等,可以看出来应用很广泛。
核心特点
-
零侵入性:基于注解,配置简单
-
轻量级:无需依赖 Quartz 等第三方框架
-
支持多种调度方式:cron、fixedDelay、fixedRate 等
-
与 Spring 生态无缝集成:天然支持依赖注入、事务管理等
二、快速入门
1. 环境准备
确保项目中已引入 Spring 相关依赖(Spring Boot 项目无需额外添加):
XML
xml
<!-- Maven -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
2. 开启定时任务支持
在配置类或启动类上添加 @EnableScheduling 注解:
java
java
@SpringBootApplication
@EnableScheduling // 开启定时任务支持
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3. 创建定时任务
编写一个简单的定时任务类:
java
java
@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());
}
}
三、核心注解与属性
@Scheduled 常用参数
| 参数 | 说明 | 示例 |
|---|---|---|
cron |
cron 表达式,灵活指定执行时间 | cron = "0 0 12 * * ?" 每天中午12点 |
fixedDelay |
上次执行结束后延迟多久执行(毫秒) | fixedDelay = 5000 |
fixedRate |
固定频率执行(毫秒) | fixedRate = 3000 |
initialDelay |
首次执行延迟时间(毫秒) | initialDelay = 10000 |
组合使用示例
java
java
// 启动后延迟 10 秒,然后每 5 秒执行一次
@Scheduled(initialDelay = 10000, fixedRate = 5000)
public void delayedTask() {
System.out.println("延迟启动的定时任务");
}
四、Cron 表达式详解
语法格式
text
秒 分 时 日 月 周 [年]
| 位置 | 字段 | 取值范围 | 特殊字符 |
|---|---|---|---|
| 1 | 秒 | 0-59 | , - * / |
| 2 | 分 | 0-59 | , - * / |
| 3 | 时 | 0-23 | , - * / |
| 4 | 日 | 1-31 | , - * ? / L W |
| 5 | 月 | 1-12 或 JAN-DEC | , - * / |
| 6 | 周 | 1-7 或 SUN-SAT | , - * ? / L # |
| 7 | 年(可选) | 空或 1970-2099 | , - * / |
常用示例
java
// 每天凌晨 2 点执行
@Scheduled(cron = "0 0 2 * * ?")
// 每 10 分钟执行一次
@Scheduled(cron = "0 */10 * * * ?")
// 工作日上午 9 点执行
@Scheduled(cron = "0 0 9 * * MON-FRI")
// 每月最后一天 23:59:59 执行
@Scheduled(cron = "59 59 23 L * ?")
// 每周一至周五 9-17 点,每隔 30 分钟执行
@Scheduled(cron = "0 0/30 9-17 * * MON-FRI")
五、配置线程池(重要)
默认情况下,Spring Task 使用单线程执行所有定时任务。如果某个任务执行时间过长,会阻塞其他任务。
配置异步线程池
java
java
@Configuration
@EnableScheduling
public class SchedulingConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
}
}
或通过配置文件(Spring Boot):
yaml
spring:
task:
scheduling:
pool:
size: 10 # 线程池大小
thread-name-prefix: my-scheduler- # 线程名称前缀
六、最佳实践
1. 避免长时间任务阻塞
java
@Scheduled(cron = "0/10 * * * * ?")
public void longRunningTask() {
// 异步执行耗时操作,避免阻塞调度线程
CompletableFuture.runAsync(() -> {
// 耗时业务逻辑
});
}
2. 异常处理
定时任务中的异常不会打印到控制台,建议统一处理:
java
@Scheduled(cron = "0/30 * * * * ?")
public void robustTask() {
try {
// 业务逻辑
} catch (Exception e) {
logger.error("定时任务执行失败", e);
// 可选:发送告警通知
}
}
3. 动态配置 cron 表达式
通过配置文件动态修改定时时间:
java
@Component
public class DynamicTask {
@Value("${task.cron:0 0 2 * * ?}")
private String cron;
@Scheduled(cron = "${task.cron:0 0 2 * * ?}")
public void dynamicTask() {
// 业务逻辑
}
}
七、Spring Task vs Quartz
| 对比项 | Spring Task | Quartz |
|---|---|---|
| 复杂度 | 简单,轻量级 | 复杂,功能强大 |
| 配置方式 | 注解 + 简单配置 | XML/Java 配置 + 数据库存储 |
| 集群支持 | ❌ 不支持集群 | ✅ 支持集群(需数据库) |
| 动态调度 | 部分支持(需自行实现) | ✅ 原生支持 |
| 适用场景 | 单体应用、简单定时任务 | 分布式、复杂任务调度 |
八、总结
Spring Task 是 Spring 生态中最便捷的定时任务解决方案,适合 80% 的常规定时任务场景。对于需要集群部署、任务持久化、动态管理等高级特性的场景,建议考虑 Quartz 或 XXL-JOB 等专业调度框架。
快速记忆:
-
启动类加
@EnableScheduling -
任务类加
@Component -
方法上加
@Scheduled并配置时间规则(可以使用在线生成器) -
记得配置线程池避免任务阻塞
WebSocket 简介
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它解决了 HTTP 协议单向通信的局限性。
1.1 为什么需要 WebSocket?
HTTP 的局限:
客户端必须主动发起请求才能获取数据
服务器无法主动推送数据给客户端
实时性差,需要使用轮询(频繁请求)才能实现"伪实时"
WebSocket 的优势:
全双工通信,服务器可以主动推送数据
一次连接,持久通信
低延迟,实时性好
适合实时应用场景
1.2 应用场景
即时通讯:聊天室、私信
实时推送:消息通知、公告
实时监控:服务器监控、股票行情
协同编辑:在线文档、白板
游戏:多人实时游戏
位置共享:实时定位
二、WebSocket 基础概念
2.1 连接生命周期
text
客户端 服务器
| |
|------握手请求--------->|
|<------握手响应---------|
| |
|<------推送数据---------|
|------发送数据--------->|
|<------推送数据---------|
| |
|------关闭连接--------->|
|<------关闭确认---------|
2.2 关键事件
-
onOpen:连接建立时触发
-
onMessage:收到消息时触发
-
onClose:连接关闭时触发
-
onError:发生错误时触发
三、Java WebSocket 服务端实现
3.1 项目依赖(pom.xml)
XML
xml
<!-- Spring Boot WebSocket 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Java WebSocket API -->
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
3.2 WebSocket 配置类
java
java
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() {
// 自动注册 @ServerEndpoint 注解的类
return new ServerEndpointExporter();
}
}
3.3 WebSocket 服务端核心类
java
java
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 {
// 存储所有在线会话,使用线程安全的 ConcurrentHashMap
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());
error.printStackTrace();
}
/**
* 发送消息给指定用户
*/
public void sendMessage(String sid, String message) {
Session session = sessionMap.get(sid);
if (session != null && session.isOpen()) {
try {
session.getBasicRemote().sendText(message);
System.out.println("【发送】给 " + sid + ":" + message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 发送消息给所有用户(群发)
*/
public void sendToAll(String message) {
for (String sid : sessionMap.keySet()) {
sendMessage(sid, message);
}
}
/**
* 获取在线人数
*/
public static int getOnlineCount() {
return sessionMap.size();
}
}
四、前端 WebSocket 客户端实现
4.1 基础 HTML 页面
html
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>WebSocket 聊天室</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.chat-container {
width: 800px;
height: 600px;
background: white;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
display: flex;
flex-direction: column;
overflow: hidden;
}
.chat-header {
background: #4a90e2;
color: white;
padding: 15px 20px;
font-size: 18px;
font-weight: bold;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 20px;
background: #f5f5f5;
}
.message {
margin-bottom: 15px;
display: flex;
align-items: flex-start;
}
.message.system {
justify-content: center;
}
.message.system .content {
background: #e0e0e0;
color: #666;
font-size: 12px;
padding: 5px 15px;
border-radius: 15px;
}
.message.other {
justify-content: flex-start;
}
.message.self {
justify-content: flex-end;
}
.message .content {
max-width: 70%;
padding: 10px 15px;
border-radius: 10px;
word-wrap: break-word;
}
.message.other .content {
background: white;
border: 1px solid #ddd;
}
.message.self .content {
background: #4a90e2;
color: white;
}
.message .info {
font-size: 12px;
color: #999;
margin-bottom: 5px;
}
.chat-input {
padding: 20px;
background: white;
border-top: 1px solid #ddd;
display: flex;
gap: 10px;
}
.chat-input input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
}
.chat-input button {
padding: 10px 20px;
background: #4a90e2;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
}
.chat-input button:hover {
background: #357abd;
}
.status {
padding: 10px 20px;
background: #f0f0f0;
font-size: 12px;
color: #666;
border-top: 1px solid #ddd;
}
</style>
</head>
<body>
<div class="chat-container">
<div class="chat-header">
🚀 WebSocket 聊天室
</div>
<div class="chat-messages" id="messages"></div>
<div class="chat-input">
<input type="text" id="messageInput" placeholder="输入消息..." onkeypress="handleKeyPress(event)">
<button onclick="sendMessage()">发送</button>
</div>
<div class="status" id="status">连接状态:未连接</div>
</div>
<script>
let ws = null;
let userId = 'user_' + Math.random().toString(36).substr(2, 6);
// 显示消息
function addMessage(message, type, sender) {
const messagesDiv = document.getElementById('messages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${type}`;
let html = '';
if (type === 'system') {
html = `<div class="content">📢 ${message}</div>`;
} else {
html = `
<div class="content">
<div class="info">${sender || (type === 'self' ? '我' : userId)}</div>
${message}
</div>
`;
}
messageDiv.innerHTML = html;
messagesDiv.appendChild(messageDiv);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
// 连接 WebSocket
function connect() {
const wsUrl = `ws://localhost:8080/ws/${userId}`;
ws = new WebSocket(wsUrl);
ws.onopen = function() {
console.log('WebSocket 连接成功');
document.getElementById('status').innerHTML = '连接状态:已连接 ✓';
addMessage('已连接到服务器', 'system');
};
ws.onmessage = function(event) {
console.log('收到消息:', event.data);
addMessage(event.data, 'other', '服务器');
};
ws.onclose = function() {
console.log('WebSocket 连接关闭');
document.getElementById('status').innerHTML = '连接状态:已断开 ✗';
addMessage('连接已断开', 'system');
};
ws.onerror = function(error) {
console.error('WebSocket 错误:', error);
addMessage('连接出错', 'system');
};
}
// 发送消息
function sendMessage() {
const input = document.getElementById('messageInput');
const message = input.value.trim();
if (!message) {
return;
}
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(message);
addMessage(message, 'self', '我');
input.value = '';
} else {
addMessage('连接未建立,无法发送消息', 'system');
}
}
// 回车发送
function handleKeyPress(event) {
if (event.key === 'Enter') {
sendMessage();
}
}
// 页面加载时连接
window.onload = function() {
connect();
};
// 页面关闭时断开连接
window.onbeforeunload = function() {
if (ws) {
ws.close();
}
};
</script>
</body>
</html>
苍穹外卖 WebSocket 使用指南(无微信支付场景)
既然你的苍穹外卖项目没有微信支付功能,WebSocket 依然可以用于很多其他实时通信场景。让我为你详细介绍如何在没有支付功能的情况下使用 WebSocket。
苍穹外卖中的 WebSocket 应用场景(无支付版)
需要手动设置的状态
1. 下单时直接设置(Serviceimpl实现类的submitOrder方法中)
java
order.setStatus(2); // 待接单(跳过状态1待付款) order.setPayStatus(1); // 已支付(跳过未支付状态)2. 数据库无需额外设置
不需要修改表结构
不需要添加特殊数据
订单状态按照正常流程流转即可:2(待接单) → 3(待配送) → 4(配送中) → 5(已完成)
3. 核心修改点
只需要在
OrderServiceImpl的submitOrder方法中:
状态直接设为
2(待接单)支付状态直接设为
1(已支付)其他所有功能(催单、WebSocket推送)都基于这个状态正常使用。
1.1 适合的场景
| 场景 | 说明 | 触发时机 |
|---|---|---|
| 来单提醒 | 用户下单后,实时提醒商家 | 用户点击下单按钮 |
| 催单提醒 | 用户催促商家尽快出餐 | 用户点击催单按钮 |
| 订单状态更新 | 订单状态变更实时推送 | 商家接单/派送/完成 |
| 公告推送 | 系统公告实时发送 | 管理员发送公告 |
| 在线客服 | 用户与商家实时沟通 | 客服对话 |
| 实时数据推送 | 订单统计数据实时更新 | 定时任务推送 |
1.2 项目当前状态
从你的代码看,你已经有了:
-
✅ WebSocket 配置类
-
✅ WebSocketServer(支持路径参数
/ws/{sid}) -
✅ WebSocketTask(定时任务类)
二、WebSocket 实现来单提醒功能
2.1 商家端连接 WebSocket
商家登录后自动连接 WebSocket:
javascript
javascript
// 商家端前端页面(如 orders.html)
let ws = null;
let shopId = 'shop_001'; // 实际从登录信息获取
// 连接 WebSocket
function connectWebSocket() {
ws = new WebSocket(`ws://localhost:8080/ws/${shopId}`);
ws.onopen = function() {
console.log('WebSocket 连接成功,商家ID:' + shopId);
showToast('连接成功,等待新订单...');
};
ws.onmessage = function(event) {
// 接收服务器推送的消息
const message = JSON.parse(event.data);
handlePushMessage(message);
};
ws.onclose = function() {
console.log('WebSocket 连接断开,5秒后重连');
setTimeout(connectWebSocket, 5000);
};
ws.onerror = function(error) {
console.error('WebSocket 错误:', error);
};
}
// 处理推送消息
function handlePushMessage(message) {
switch(message.type) {
case 'NEW_ORDER':
addNewOrder(message.data); // 添加新订单到列表
playNewOrderSound(); // 播放提示音
showNotification('有新订单啦!');
break;
case 'REMINDER':
showReminder(message.data); // 显示催单提醒
break;
case 'ORDER_STATUS':
updateOrderStatus(message.data); // 更新订单状态
break;
default:
console.log('未知消息类型:', message.type);
}
}
// 添加新订单到页面
function addNewOrder(order) {
const ordersDiv = document.getElementById('orderList');
const orderCard = `
<div class="order-card new-order" id="order_${order.id}">
<div class="order-number">订单号:${order.number}</div>
<div class="order-amount">金额:¥${order.amount}</div>
<div class="order-time">时间:${order.createTime}</div>
<button onclick="acceptOrder(${order.id})">接单</button>
<button onclick="rejectOrder(${order.id})">拒单</button>
</div>
`;
ordersDiv.insertAdjacentHTML('afterbegin', orderCard);
}
// 播放提示音
function playNewOrderSound() {
const audio = new Audio('/audio/new_order.mp3');
audio.play().catch(e => console.log('播放失败:', e));
}
// 页面加载时连接
document.addEventListener('DOMContentLoaded', function() {
connectWebSocket();
});
// 页面关闭时断开连接
window.addEventListener('beforeunload', function() {
if (ws) {
ws.close();
}
});
2.2 用户端下单时触发 WebSocket 推送
用户点击下单后,后端推送消息给商家:
java
java
// OrderServiceImpl.java
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private WebSocketServer webSocketServer; // 注入 WebSocket 服务
@Override
@Transactional
public void submitOrder(OrderSubmitDTO orderSubmitDTO) {
// 1. 保存订单信息到数据库
Orders order = new Orders();
// ... 设置订单信息
orderMapper.insert(order);
// 2. 保存订单明细
// ... 保存明细
// 3. 清空购物车
// ... 清空购物车
// 4. 通过 WebSocket 推送新订单给商家
// 构建推送消息
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);
// 发送给商家(假设商家ID固定为 "shop_001")
// 如果有多商家,需要根据订单中的 shopId 发送
webSocketServer.sendToClient("shop_001", jsonMessage);
System.out.println("【来单提醒】已推送新订单:" + order.getNumber());
}
}
2.3 改进 WebSocketServer 支持单发
java
java
// WebSocketServer.java 添加单发方法
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {
private static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
this.session = session;
sessionMap.put(sid, session);
System.out.println("客户端 " + sid + " 已连接");
}
/**
* 发送消息给指定客户端
*/
public void sendToClient(String sid, String message) {
Session session = sessionMap.get(sid);
if (session != null && session.isOpen()) {
try {
session.getBasicRemote().sendText(message);
System.out.println("发送消息给 " + sid + ":" + message);
} catch (IOException e) {
e.printStackTrace();
}
} else {
System.out.println("客户端 " + sid + " 不在线,消息发送失败");
}
}
/**
* 群发消息
*/
public void sendToAll(String message) {
for (Map.Entry<String, Session> entry : sessionMap.entrySet()) {
sendToClient(entry.getKey(), message);
}
}
@OnClose
public void onClose(@PathParam("sid") String sid) {
sessionMap.remove(sid);
System.out.println("客户端 " + sid + " 已断开");
}
}
三、实现催单提醒功能
3.1 用户端发送催单消息
用户在小程序或网页上点击催单按钮:
javascript
javascript
// 用户端前端
function remindOrder(orderId) {
// 发送催单请求到后端
fetch('/user/order/remind', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ orderId: orderId })
})
.then(response => response.json())
.then(data => {
if (data.code === 1) {
alert('催单成功,商家将尽快处理');
}
});
}
3.2 后端处理催单并推送
java
java
// OrderController.java
@RestController
@RequestMapping("/user/order")
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 用户催单
*/
@PostMapping("/remind")
@ApiOperation("用户催单")
public Result remind(@RequestBody RemindDTO remindDTO) {
orderService.remindOrder(remindDTO.getOrderId());
return Result.success();
}
}
java
java
// OrderServiceImpl.java
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private WebSocketServer webSocketServer;
@Override
public void remindOrder(Long orderId) {
// 1. 查询订单信息
Orders order = orderMapper.getById(orderId);
if (order == null) {
throw new BusinessException("订单不存在");
}
// 2. 检查订单状态(只有待接单或待配送状态可以催单)
if (order.getStatus() != Orders.PENDING_PAYMENT &&
order.getStatus() != Orders.TO_BE_CONFIRMED) {
throw new BusinessException("当前订单状态不支持催单");
}
// 3. 记录催单日志(可选)
orderMapper.updateRemindTime(orderId);
// 4. 推送催单消息给商家
Map<String, Object> remindMessage = new HashMap<>();
remindMessage.put("type", "REMINDER");
remindMessage.put("data", Map.of(
"orderId", order.getId(),
"orderNumber", order.getNumber(),
"remindTime", LocalDateTime.now()
));
String jsonMessage = JSON.toJSONString(remindMessage);
webSocketServer.sendToClient("shop_001", jsonMessage);
log.info("用户催单:订单号 {},已通知商家", order.getNumber());
}
}
四、定时推送订单统计数据
4.1 你的 WebSocketTask 定时任务
你的 WebSocketTask 已经配置好了,只需要完善 sendMessageToClient 方法:
java
java
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;
import java.util.HashMap;
import java.util.Map;
@Component
public class WebSocketTask {
@Autowired
private WebSocketServer webSocketServer;
@Autowired
private OrderMapper orderMapper; // 注入订单Mapper
/**
* 每隔5秒推送实时数据给商家
*/
@Scheduled(cron = "0/5 * * * * ?")
public void pushRealTimeData() {
try {
// 获取实时统计数据
Map<String, Object> data = new HashMap<>();
data.put("type", "REAL_TIME_DATA");
data.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
// 今日订单数
Integer todayOrders = orderMapper.getTodayOrderCount();
data.put("todayOrders", todayOrders);
// 今日营业额
Double todayAmount = orderMapper.getTodayTurnover();
data.put("todayAmount", todayAmount);
// 待处理订单数
Integer pendingOrders = orderMapper.getPendingOrderCount();
data.put("pendingOrders", pendingOrders);
// 当前在线人数(WebSocket 连接数)
data.put("onlineCount", WebSocketServer.getOnlineCount());
// 推送消息给所有商家
String jsonMessage = JSON.toJSONString(data);
webSocketServer.sendToAll(jsonMessage);
System.out.println("定时推送实时数据:" + jsonMessage);
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.2 商家端接收实时数据
javascript
javascript
// 商家端接收实时数据
function handlePushMessage(message) {
if (message.type === 'REAL_TIME_DATA') {
// 更新页面上的统计数据
document.getElementById('todayOrders').innerText = message.todayOrders;
document.getElementById('todayAmount').innerText = '¥' + message.todayAmount;
document.getElementById('pendingOrders').innerText = message.pendingOrders;
document.getElementById('onlineCount').innerText = message.onlineCount;
document.getElementById('updateTime').innerText = message.timestamp;
}
}