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

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

SpringTask 提供轻量级定时任务方案,配合 WebSocket 实现全双工通信。文章详解配置流程、Cron 表达式及线程池调优,并结合苍穹外卖项目演示来单提醒、催单通知与实时数据推送的落地实践。重点涵盖服务端连接管理、前端 JS 对接以及无支付场景下的订单状态流转优化,确保系统具备实时响应能力。

邪神洛基发布于 2026/3/29更新于 2026/6/219 浏览
苍穹外卖实战:SpringTask 定时任务与 WebSocket 实时通信

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

在苍穹外卖项目的开发中,订单的定时处理、来单提醒以及客户催单是提升用户体验的关键功能。为了实现这些需求,我们需要掌握 Spring Task 自动调度机制以及 WebSocket 双向通信协议。

Spring Task 概览

Spring Task 是 Spring 框架内置的轻量级定时任务调度器,它通过注解方式提供简洁的声明式解决方案。相比 Quartz 等第三方框架,它的优势在于零侵入性和与 Spring 生态的无缝集成。

核心特点

  • 配置简单:基于注解,无需复杂 XML 配置
  • 轻量级:适合单体应用中的常规定时任务
  • 调度灵活:支持 Cron 表达式、固定延迟(fixedDelay)和固定频率(fixedRate)
  • 依赖注入:天然支持 Spring 事务管理

快速上手

1. 环境准备

Spring Boot 项目通常已包含相关依赖,无需额外引入。

<!-- Maven 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
2. 开启调度支持

在启动类或配置类上添加 @EnableScheduling 注解。

@SpringBootApplication
@EnableScheduling
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
3. 编写任务类

使用 @Component 标记 Bean,并在方法上使用 @Scheduled 定义规则。

@Component
public class ScheduledTasks {
    private      LoggerFactory.getLogger(ScheduledTasks.class);

    
    
       {
        logger.info(, System.currentTimeMillis());
    }

    
    
       {
        logger.info(, System.currentTimeMillis());
    }

    
    
       {
        logger.info(, System.currentTimeMillis());
    }
}

目录

  1. 苍穹外卖实战:SpringTask 定时任务与 WebSocket 实时通信
  2. Spring Task 概览
  3. 核心特点
  4. 快速上手
  5. 1. 环境准备
  6. 2. 开启调度支持
  7. 3. 编写任务类
  8. Cron 表达式详解
  9. 线程池配置(重要)
  10. 最佳实践
  11. WebSocket 实时通信
  12. 为什么需要 WebSocket?
  13. 服务端实现
  14. 1. 依赖引入
  15. 2. 配置类
  16. 3. 核心类
  17. 前端客户端实现
  18. 苍穹外卖场景落地
  19. 1. 订单状态流转优化
  20. 2. 来单提醒
  21. 3. 催单通知
  22. 4. 实时数据统计
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • LLM Agent 驱动 SLAM 回环检测:Python 与 LangChain 轻量化实践
  • Python 爬虫入门实战:从请求解析到反爬策略
  • AI 时代:非技术背景者的生产力变革与商业机会
  • Python 实现 GitHub 热门项目自动挖掘与分析 AI Agent
  • 分布式环境下高可靠分布式锁的实现与优化
  • LeetCode Hot 100 链表经典题目实战解析
  • Pico 4XVR 1.10.13 安装与使用指南
  • 基于 DamoFD-0.5G 的 AR 虚拟试妆系统实现
  • 汇川 RobotLab 软件常规操作指南
  • C++ 从零实现高质量随机数生成器
  • FPGA 开发从入门到精通
  • LoRA 指令微调核心原理与实战细节
  • C++ 继承进阶:多继承、菱形继承与虚继承机制
  • Arduino BLDC 基于串口指令的远程控制工业巡检机器人
  • Qwen-Image-2512 V2 模型 ComfyUI 及 WebUI 整合部署指南
  • Java 开发修改冒险岛 079 私服完整流程
  • 大模型参数高效微调(PEFT)技术综述:从原理到应用
  • 鸿蒙金融理财全栈项目——生态合作、用户运营、数据变现
  • KaiwuDB 3.1.0 在 Ubuntu 22.04 部署实战:TLS 配置与性能基线
  • 基于 SpringBoot 的影视周边推荐系统设计与实现

相关免费在线工具

  • 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

static
final
Logger
logger
=
/** * 固定延迟:上次执行完成后间隔 5 秒执行 */
@Scheduled(fixedDelay = 5000)
public
void
taskWithFixedDelay
()
"FixedDelay Task - 当前时间:{}"
/** * 固定频率:每 3 秒执行一次(不考虑上次执行是否完成) */
@Scheduled(fixedRate = 3000)
public
void
taskWithFixedRate
()
"FixedRate Task - 当前时间:{}"
/** * Cron 表达式:每分钟的第 10 秒执行 */
@Scheduled(cron = "10 * * * * ?")
public
void
taskWithCron
()
"Cron Task - 当前时间:{}"

Cron 表达式详解

Cron 表达式决定了任务的执行时机,格式为:秒 分 时 日 月 周 [年]。

位置字段取值范围特殊字符
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, - * /

常用示例:

// 每天凌晨 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 * ?")

线程池配置(重要)

默认情况下,Spring Task 使用单线程执行任务。如果某个任务耗时过长,会阻塞后续任务。生产环境中建议配置异步线程池。

@Configuration
@EnableScheduling
public class SchedulingConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        // 设置线程池大小为 10
        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
    }
}

或者在 application.yml 中配置:

spring:
  task:
    scheduling:
      pool:
        size: 10
        thread-name-prefix: my-scheduler-

最佳实践

  1. 避免阻塞:耗时操作应放入异步线程中执行。
    @Scheduled(cron = "0/10 * * * * ?")
    public void longRunningTask() {
        CompletableFuture.runAsync(() -> {
            // 耗时业务逻辑
        });
    }
    
  2. 异常处理:定时任务异常不会自动打印到控制台,需手动捕获记录日志。
  3. 动态配置:可通过配置文件动态修改 Cron 表达式,实现运行时调整。

WebSocket 实时通信

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,解决了 HTTP 单向通信的局限性,非常适合即时通讯和实时推送场景。

为什么需要 WebSocket?

  • HTTP 局限:客户端必须主动发起请求,服务器无法主动推送数据。轮询方式效率低且延迟高。
  • WebSocket 优势:全双工通信,服务器可主动推送;一次握手建立持久连接;低延迟。

服务端实现

1. 依赖引入
<!-- 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>
2. 配置类

需要注册 ServerEndpointExporter 以自动扫描 @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());
        error.printStackTrace();
    }

    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();
    }
}

前端客户端实现

前端通过 JavaScript 的 WebSocket API 建立连接。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>WebSocket 聊天室</title>
    <style>
        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; margin: 0; }
        .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; }
        .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; }
        .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="info">${sender || (type === 'self' ? '我' : userId)}</div><div class="content">${message}</div>`;
    }
    messageDiv.innerHTML = html;
    messagesDiv.appendChild(messageDiv);
    messagesDiv.scrollTop = messagesDiv.scrollHeight;
}

function connect() {
    const wsUrl = `ws://localhost:8080/ws/${userId}`;
    ws = new WebSocket(wsUrl);
    ws.onopen = function() {
        .();
        .(). = ;
        (, );
    };
    ws. = () {
        .(, event.);
        (event., , );
    };
    ws. = () {
        .();
        .(). = ;
        (, );
    };
    ws. = () {
        .(, error);
        (, );
    };
}

 () {
     input = .();
     message = input..();
     (!message) ;
     (ws && ws. === .) {
        ws.(message);
        (message, , );
        input. = ;
    }  {
        (, );
    }
}

 () {
     (event. === ) ();
}

. = () { (); };
. = () {  (ws) ws.(); };
</script>
</body>
</html>

苍穹外卖场景落地

在实际项目中,我们利用上述技术实现订单状态的实时同步。

1. 订单状态流转优化

在无微信支付场景中,下单后直接跳过待付款状态,进入待接单流程。

  • Service 层修改:在 submitOrder 方法中,将订单状态设为 2(待接单),支付状态设为 1(已支付)。
  • 数据库:无需修改表结构,保持原有状态码定义即可。

2. 来单提醒

商家端登录后自动连接 WebSocket,当有新订单时,后端推送消息触发前端弹窗或提示音。

// 商家端前端
let ws = null;
let shopId = 'shop_001';

function connectWebSocket() {
    ws = new WebSocket(`ws://localhost:8080/ws/${shopId}`);
    ws.onopen = function() {
        console.log('WebSocket 连接成功,商家 ID:' + shopId);
    };
    ws.onmessage = function(event) {
        const message = JSON.parse(event.data);
        if (message.type === 'NEW_ORDER') {
            playNewOrderSound();
            showNotification('有新订单啦!');
        }
    };
    ws.onclose = function() {
        setTimeout(connectWebSocket, 5000); // 断线重连
    };
}

3. 催单通知

用户点击催单按钮时,后端查询订单并立即通过 WebSocket 推送给对应商家。

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private WebSocketServer webSocketServer;

    @Override
    public void remindOrder(Long orderId) {
        Orders order = orderMapper.getById(orderId);
        if (order == null) throw new BusinessException("订单不存在");
        
        Map<String, Object> remindMessage = new HashMap<>();
        remindMessage.put("type", "REMINDER");
        remindMessage.put("data", Map.of(
            "orderId", order.getId(),
            "orderNumber", order.getNumber()
        ));
        
        String jsonMessage = JSON.toJSONString(remindMessage);
        // 发送给指定商家
        webSocketServer.sendToClient("shop_001", jsonMessage);
    }
}

4. 实时数据统计

通过定时任务每隔几秒获取今日订单数、营业额等数据,推送到所有在线商家端,实现数据大屏实时更新。

@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);
    }
}

通过 Spring Task 与 WebSocket 的组合,我们可以构建出响应迅速、交互流畅的后端服务,显著提升系统的实时性和用户体验。

console
log
'WebSocket 连接成功'
document
getElementById
'status'
innerHTML
'连接状态:已连接 ✓'
addMessage
'已连接到服务器'
'system'
onmessage
function
event
console
log
'收到消息:'
data
addMessage
data
'other'
'服务器'
onclose
function
console
log
'WebSocket 连接关闭'
document
getElementById
'status'
innerHTML
'连接状态:已断开 ✗'
addMessage
'连接已断开'
'system'
onerror
function
error
console
error
'WebSocket 错误:'
addMessage
'连接出错'
'system'
function
sendMessage
const
document
getElementById
'messageInput'
const
value
trim
if
return
if
readyState
WebSocket
OPEN
send
addMessage
'self'
'我'
value
''
else
addMessage
'连接未建立,无法发送消息'
'system'
function
handleKeyPress
event
if
key
'Enter'
sendMessage
window
onload
function
connect
window
onbeforeunload
function
if
close