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;
    }
}
相关推荐
無限進步D1 小时前
Java 运行原理
java·开发语言·入门
難釋懷1 小时前
安装Canal
java
是苏浙1 小时前
JDK17新增特性
java·开发语言
不光头强1 小时前
spring cloud知识总结
后端·spring·spring cloud
Mike117.1 小时前
GBase 8a 日期边界写法和时间窗口取数偏差
数据库
SPC的存折3 小时前
1、Redis数据库基础
linux·运维·服务器·数据库·redis·缓存
GetcharZp4 小时前
告别 Python 依赖!用 LangChainGo 打造高性能大模型应用,Go 程序员必看!
后端
阿里加多4 小时前
第 4 章:Go 线程模型——GMP 深度解析
java·开发语言·后端·golang
likerhood5 小时前
java中`==`和`.equals()`区别
java·开发语言·python
小小李程序员5 小时前
Langchain4j工具调用获取不到ThreadLocal
java·后端·ai