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

苍穹外卖实战:Spring Task 定时任务与 WebSocket 应用

Spring Task 提供轻量级定时任务调度,支持注解配置 Cron 表达式及线程池管理,适用于订单处理等场景。WebSocket 实现全双工通信,解决 HTTP 单向限制,常用于来单提醒、催单通知等实时推送。结合苍穹外卖项目,通过集成两者可实现商家端订单状态实时更新与消息即时触达,提升系统交互体验。

奇形怪状发布于 2026/3/26更新于 2026/7/3044 浏览
苍穹外卖实战:Spring Task 定时任务与 WebSocket 应用

Spring Task 定时任务实战

在订单处理场景中,我们经常需要执行一些后台任务,比如清理超时未支付订单、发送催单通知等。Spring Task 是 Spring 框架内置的轻量级调度工具,无需引入 Quartz 等第三方依赖,通过注解即可快速实现。

一、核心配置与入门

要在项目中启用定时任务支持,只需在启动类或配置类上添加 @EnableScheduling 注解。

@SpringBootApplication
@EnableScheduling
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

接下来定义具体的任务逻辑。使用 @Scheduled 注解标记方法,并指定触发规则。常见的调度方式有三种:

  1. 固定频率 (fixedRate):每隔 N 毫秒执行一次,不考虑上次任务是否完成。
  2. 固定延迟 (fixedDelay):上次任务结束后,再等待 N 毫秒执行下一次。
  3. 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());
    }

    /**
     * Cron 表达式:每分钟的第 10 秒执行
     */
    @Scheduled(cron = "10 * * * * ?")
    public void taskWithCron() {
        logger.info("Cron Task - 当前时间:{}", System.currentTimeMillis());
    }
}

线程池配置(重要)

默认情况下,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-

Cron 表达式速查

Cron 表达式格式为:秒 分 时 日 月 周 [年]。

字段取值范围特殊字符
秒0-59, - * /
分0-59, - * /
时0-23, - * /
日1-31, - * ? / L W
月1-12, - * /
周1-7, - * ? / L #

常用示例:

  • 每天凌晨 2 点:0 0 2 * * ?
  • 每 10 分钟:0 */10 * * * ?
  • 工作日 9 点到 17 点:0 0 9-17 * * MON-FRI

注意:实际开发中建议配合在线生成器编写 Cron 表达式,避免手写出错。

二、WebSocket 实时通信

HTTP 协议是单向的,服务器无法主动推送数据给客户端。对于来单提醒、状态变更通知等场景,WebSocket 的全双工特性是最佳选择。

1. 服务端配置

首先引入 WebSocket 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

创建一个配置类,自动注册带有 @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();
    }
}

2. 核心业务类

这里以聊天室为例,展示如何管理会话和发送消息。关键点是使用 ConcurrentHashMap 存储在线 Session,保证线程安全。

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 + " 已断开");
        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();
    }
}

3. 前端对接

前端使用原生 WebSocket API 即可建立连接。以下是一个简单的 HTML 示例,展示了连接、发送和接收消息的流程。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>WebSocket 测试</title>
</head>
<body>
    <div id="status">连接状态:未连接</div>
    <input type="text" id="messageInput" placeholder="输入消息...">
    <button onclick="sendMessage()">发送</button>
    <div id="messages"></div>

    <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('messageInput');
            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 = `[${type}] ${text}`;
            document.getElementById('messages').appendChild(div);
        }

        window.onload = connect;
    </script>
</body>
</html>

三、苍穹外卖项目集成方案

在实际的苍穹外卖项目中,我们可以利用上述技术实现商家端的实时通知功能。即使没有微信支付,WebSocket 依然能解决订单流转中的信息滞后问题。

1. 订单状态调整

由于部分版本移除了待付款环节,下单后直接设置为'待接单'状态,方便后续推送。

// OrderServiceImpl.java
order.setStatus(2); // 待接单
order.setPayStatus(1); // 已支付

2. 来单提醒推送

当用户提交订单成功后,后端立即通过 WebSocket 将订单详情推送到商家的连接通道。

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

    @Override
    @Transactional
    public void submitOrder(OrderSubmitDTO orderSubmitDTO) {
        // ... 保存订单逻辑 ...
        
        // 构建推送消息
        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
        webSocketServer.sendMessage("shop_001", jsonMessage);
    }
}

3. 催单与统计推送

除了新订单,用户催单和定时统计数据更新也可以通过 WebSocket 实时触达。

催单逻辑:

@Override
public void remindOrder(Long orderId) {
    Orders order = orderMapper.getById(orderId);
    // ... 校验状态 ...
    
    Map<String, Object> remindMessage = new HashMap<>();
    remindMessage.put("type", "REMINDER");
    remindMessage.put("data", order);
    
    String jsonMessage = JSON.toJSONString(remindMessage);
    webSocketServer.sendMessage("shop_001", jsonMessage);
}

定时统计推送:

结合 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());
        
        String jsonMessage = JSON.toJSONString(data);
        webSocketServer.sendToAll(jsonMessage);
    }
}

商家端接收到消息后,解析 JSON 并更新页面 DOM,即可实现类似'心跳'般的实时效果。这种方案比轮询更高效,且用户体验更流畅。

目录

  1. Spring Task 定时任务实战
  2. 一、核心配置与入门
  3. 线程池配置(重要)
  4. Cron 表达式速查
  5. 二、WebSocket 实时通信
  6. 1. 服务端配置
  7. 2. 核心业务类
  8. 3. 前端对接
  9. 三、苍穹外卖项目集成方案
  10. 1. 订单状态调整
  11. 2. 来单提醒推送
  12. 3. 催单与统计推送
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • JDK 17 安装与配置指南
  • FPGA 基础简介与开发流程指南
  • 使用 GitHub Copilot 配合 Figma MCP 还原设计稿生成前端代码
  • 与模型对话:理解与预防 ChatGPT 中的常见误解
  • 阿里云配置 Zabbix 邮件发送
  • CentOS 7 系统安装 Docker CE 详解
  • 行空板 K10 与 Mind+ 零基础 AI 入门实战
  • 医疗大模型 LoRA 微调实战指南
  • AI Agent 新范式:FastGPT+MCP 协议实现工具增强型智能体构建
  • Node.js+Vue 宠物店管理系统设计与实现
  • FastGPT 集成 MCP 协议构建工具增强型智能体
  • Stable Diffusion 皮革服装 LoRA 模型部署与使用指南
  • Python+AI 学习方向拆解:3 个高性价比赛道与路线
  • 二叉树最近公共祖先解法:递归法与栈存路径法
  • 国产数据库新机遇:电科金仓融合技术与 AI 化演进
  • Dify 工作流发布为 MCP Server 实战指南
  • 构建与 GitHub 深度集成的自动化工作流指南
  • Dify 工作流发布为 MCP Server 实战指南
  • OpenClaw 集成飞书机器人配置指南
  • QA 一来,大家都要靠边站

相关免费在线工具

  • 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