跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Java大前端java

苍穹外卖实战:SpringTask 定时任务与 WebSocket 实时通信

Spring Task 结合 WebSocket 可实现订单系统的定时处理与实时通知。 Spring Task 的配置、Cron 表达式用法及线程池优化方案,重点演示了 WebSocket 服务端与客户端的全双工通信实现。在苍穹外卖场景中,通过整合两者完成了来单提醒、催单通知及实时数据统计推送,解决了传统 HTTP 轮询效率低的问题,提升了用户体验与系统响应速度。

BackendPro发布于 2026/3/28更新于 2026/10/1979 浏览
苍穹外卖实战:SpringTask 定时任务与 WebSocket 实时通信

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 元素即可实现数据看板实时更新。

目录

  1. Spring Task 与 WebSocket 在订单系统中的应用
  2. 一、Spring Task 入门与配置
  3. 1. 开启定时任务支持
  4. 2. 编写定时任务
  5. 3. Cron 表达式详解
  6. 4. 线程池配置(重要)
  7. 5. 最佳实践
  8. 二、WebSocket 实时通信实现
  9. 1. 项目依赖
  10. 2. 服务端配置
  11. 3. 核心业务类
  12. 4. 前端客户端实现
  13. 三、苍穹外卖场景整合
  14. 1. 来单提醒逻辑
  15. 2. 催单功能
  16. 3. 定时推送统计数据

更多推荐文章

查看全部
  • 拒绝经验过剩:程序员的工作只能是代码?
  • SpringAI 与 Deepseek 大模型应用开发实战笔记(上)
  • Spring MVC 响应处理:页面、数据与状态配置
  • AI 与 SQL 双驱动 ER 图在线生成工具
  • 苍穹外卖实战:Spring Task 定时任务与 WebSocket 应用
  • 网络安全应急响应流程与方法论
  • TortoiseGit 安装与基本操作指南
  • RISC-V开源处理器实战:从Verilog RTL设计到FPGA原型验证
  • 自然语言处理在客户服务领域的应用与实战
  • GitNexus:纯本地代码知识图谱与可视化关系网工具
  • 大规模语言模型与智能代理:理论及实践应用
  • Java 核心语法与并发编程实战示例
  • Android 常用数据结构原理与性能分析
  • OpenClaw.ai:Agentic AI 时代的 Spring Framework 时刻
  • 挖 SRC 时的信息收集方法与工具实践
  • Python Web 框架 Django 实战指南
  • 行星减速器原理、减速比计算及 C++ 实现
  • 扩散模型原理与实战技术详解
  • n8n 深度解析:如何用可视化工作流构建 AI 自动化流水线
  • 飞书机器人对接 Claude Code 实现移动端 AI 编程助手

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online