Day | 10【苍穹外卖:SpringTask 和WebSocket 案例】

🔥个人主页:北极的代码(欢迎来访)

🎬作者简介:java后端学习者

❄️个人专栏:苍穹外卖日记SSM框架深入JavaWeb

命运的结局尽可永在,不屈的挑战却不可须臾或缺!

前言:我们完成了前一天的作业,进入到了苍穹外卖的第十天开发,今天要学的内容主要是订单的定时处理,来单提醒,客户催单等功能,为了实现这些功能,我们要学习一些新的技术,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 等专业调度框架。

快速记忆

  1. 启动类加 @EnableScheduling

  2. 任务类加 @Component

  3. 方法上加 @Scheduled 并配置时间规则(可以使用在线生成器)

  4. 记得配置线程池避免任务阻塞

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. 核心修改点

只需要在 OrderServiceImplsubmitOrder 方法中:

  • 状态直接设为 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;
    }
}
相关推荐
wangchunting1 小时前
Jvm-垃圾回收算法
java·jvm·算法
Java面试题总结1 小时前
新人笔记之模板方法模式
java·笔记·模板方法模式
2401_831824961 小时前
RESTful API设计最佳实践(Python版)
jvm·数据库·python
NGC_66111 小时前
深入理解 Java 线程池:从原理到实战
java·开发语言·python
zjeweler1 小时前
redis_tools_gui_v1.2 —Redis图形化漏洞利用工具
数据库·redis·web安全·缓存·安全性测试
暮冬-  Gentle°1 小时前
更优雅的测试:Pytest框架入门
jvm·数据库·python
专利观察员1 小时前
专利检索万字报告分享:《专利数据库3.0时代:2021-2025专利数据库的AI浪潮与选型逻辑重构》
数据库·人工智能·科技·专利检索·专利数据库
白宇横流学长1 小时前
基于SpringMVC 的景区智能客流预测系统设计与实现
java
凸头1 小时前
后过滤召回塌陷:Redis 先召回 → ES 再过滤,如果全部被过滤掉怎么办?
数据库·redis·elasticsearch