【苍穹外卖 day10|Spring Task、WebSocket 与来单提醒、催单铃声实现】
前言订单系统里有两类事情很容易被忽略一类是到了时间必须处理的超时订单另一类是商家页面需要立刻收到的来单和催单消息。前者适合交给 Spring Task后者适合通过 WebSocket 推送到管理端。这一篇结合苍穹外卖的订单代码整理 Spring Task 的定时处理、WebSocket 的连接与群发以及管理端根据消息类型播放来单/催单铃声的实现。一、Spring Task让订单状态按时间自动推进Spring Task 是 Spring 提供的任务调度能力。给方法加上 Scheduled并在应用中开启调度后Spring 会按设定的时间规则调用该方法。当前 Spring 文档中周期任务可以使用 cron、fixedRate 或 fixedDelay 三种方式之一被调度的方法不能声明参数通常返回 void。项目中处理订单超时的场景需要按日历时间触发使用 cron 更直观。1. 开启任务调度项目启动类已经开启了定时任务能力SpringBootApplicationEnableTransactionManagementEnableCachingEnableSchedulingpublicclassSkyApplication{publicstaticvoidmain(String[]args){SpringApplication.run(SkyApplication.class,args);}}EnableScheduling 会注册对 Scheduled 的处理。没有这项配置即使任务类和 cron 表达式都写好了方法也不会被调度执行。2. 看懂 cron 表达式Spring Task 常用 cron 由秒、分、时、日、月、周六个字段组成字段之间以空格分隔。项目里的两个表达式如下表达式含义使用位置0 * * * * ?每分钟的第 0 秒执行一次取消超时未支付订单0 1 * * * ?每天凌晨 1:00 执行一次处理长时间派送中的订单常见通配符可以这样记* 表示任意值? 表示该字段不指定值- 表示范围, 表示多个值/ 表示步长。例如 0/5 放在秒字段表示从第 0 秒开始每 5 秒触发一次。日和周两个字段通常不要同时写具体值。当前代码中的 ? 就是为了避免两个字段同时限定。3. 超时订单的定时处理订单任务类被 Spring 扫描为组件后定时器会调用其中的方法ComponentSlf4jpublicclassOrderTask{AutowiredprivateOrderMapperorderMapper;Scheduled(cron0 * * * * ?)publicvoidprocessOutOrder(){LocalDateTimetimeLocalDateTime.now().plusMinutes(-15);ListOrdersordersorderMapper.getBystatusAndOrderTimeLT(Orders.PENDING_PAYMENT,time);if(orders!null!orders.isEmpty()){for(Ordersorder:orders){order.setStatus(Orders.CANCELLED);order.setCancelReason(订单超时,自动取消);order.setCancelTime(LocalDateTime.now());orderMapper.update(order);}}}}这里的查询条件由两个部分组成订单状态必须是待支付订单创建时间必须早于当前时间减 15 分钟。查到后再更新状态、取消原因和取消时间。这样即使用户关闭了页面订单数据也能被后台任务兜底处理。另一段 processOutOrder3 使用每天凌晨一次的规则查询派送中且超过 60 分钟的订单并取消。具体的状态是否应取消、还是应完成需要以产品规则为准定时任务只负责在设定时间执行既定业务规则。4. 定时任务怎么验证开发阶段可以暂时把 cron 改成每 5 秒执行一次或者构造一条符合查询条件的测试订单。观察日志、订单状态和取消时间是否一同发生变化即可。验证完成后要恢复正式 cron避免高频扫描订单表。如果任务没有执行优先检查三处启动类是否存在 EnableScheduling、任务类是否被 Spring 扫描、cron 是否有 6 个字段且合法。二、WebSocket保持连接让服务端主动发消息HTTP 很适合“客户端发请求服务端返回响应”的业务例如提交订单、查询订单详情。但商家正在打开管理端页面时服务端不能等到下一次 HTTP 请求才告诉它有新订单。WebSocket 建立握手后会保留一条连接客户端和服务端都可以主动发送消息。因此它适合来单提醒、催单提醒、聊天和实时状态变化这类场景。它同样基于 TCP但需要处理断线、重连和在线连接管理不能替代普通的 HTTP 接口。1. 注册服务端端点项目已经引入 spring-boot-starter-websocket并通过配置类注册 ServerEndpointExporterConfigurationpublicclassWebSocketConfiguration{BeanpublicServerEndpointExporterserverEndpointExporter(){returnnewServerEndpointExporter();}}该 Bean 会注册使用 ServerEndpoint 标记的端点。当前项目的连接地址为 /ws/{sid}sid 用于区分浏览器会话。2. 管理在线会话并群发ComponentServerEndpoint(/ws/{sid})Slf4jpublicclassWebSocketServer{privatestaticMapString,SessionsessionMapnewConcurrentHashMap();OnOpenpublicvoidonOpen(Sessionsession,PathParam(sid)Stringsid){sessionMap.put(sid,session);}OnClosepublicvoidonClose(PathParam(sid)Stringsid){sessionMap.remove(sid);}publicvoidsendToAllClient(Stringmessage){for(Sessionsession:sessionMap.values()){try{session.getBasicRemote().sendText(message);}catch(Exceptione){log.error(WebSocket发送消息失败: {},e.getMessage());}}}}onOpen 在浏览器连接成功时保存 SessiononClose 在连接断开时删除 Session。sessionMap 使用 ConcurrentHashMap适合多个连接同时建立、关闭或推送的场景。sendToAllClient 遍历在线 Session 后调用 sendText把同一条文本消息推给当前在线的管理端。这里采用的是群发策略适合单门店、单后台账号或所有管理端都需要获知订单的练习场景。多门店或多角色系统应按门店 ID、员工 ID 等维度维护会话集合避免无关人员收到消息。三、支付成功后的来单提醒用户提交订单后真正应提醒商家的时机是支付成功、订单进入待接单状态。项目在 paySuccess 中完成状态更新后构造了一条 type1 的 WebSocket 消息publicvoidpaySuccess(StringoutTradeNo){OrdersordersDBorderMapper.getByNumber(outTradeNo);if(ordersDBnull){thrownewOrderBusinessException(MessageConstant.ORDER_NOT_FOUND);}OrdersordersOrders.builder().id(ordersDB.getId()).status(Orders.TO_BE_CONFIRMED).payStatus(Orders.PAID).checkoutTime(LocalDateTime.now()).build();orderMapper.update(orders);MapString,ObjectwebsocketMapnewHashMap();websocketMap.put(type,1);websocketMap.put(orderId,ordersDB.getId());websocketMap.put(content,订单号outTradeNo);webSocketServer.sendToAllClient(JSON.toJSONString(websocketMap));}消息字段的约定很简单type1 表示来单提醒orderId 方便管理端跳转订单详情content 用于弹窗或通知栏展示订单号。先更新订单状态再推送消息能避免管理端收到提醒后查询到旧状态。四、用户催单用 type2 区分提醒类别用户端催单接口为 GET /user/order/reminder/{id}。项目先根据订单 ID 查询订单再组装 type2 的消息并通过同一个 WebSocket 服务群发GetMapping(/reminder/{id})publicResultreminder(PathVariableLongid){OrdersorderorderMapper.getById(id);if(orderService.orderDetail(id)null){thrownewOrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);}MapString,ObjectmapnewHashMap();map.put(type,2);map.put(orderId,id);map.put(content,订单号order.getNumber());webSocketServer.sendToAllClient(JSON.toJSONString(map));returnResult.success();}来单和催单复用一条长连接前端只需要根据 type 决定提示文案和铃声。这样比为每一种提醒再建一个 WebSocket 地址更容易维护。这段代码还应注意业务校验当前实现通过 orderDetail 判断订单是否存在。正式项目建议同时校验订单归属和可催单状态避免用户对不属于自己的订单或已完成订单发起提醒。五、管理端接收消息并播放不同铃声当前仓库只包含后端模块下面这段是管理端页面可以直接接入的客户端代码。将 order.mp3 和 reminder.mp3 放到管理端静态资源目录并按实际构建工具调整资源地址。constorderAudionewAudio(/audio/order.mp3);constreminderAudionewAudio(/audio/reminder.mp3);letsocket;functionconnectWebSocket(){constclientIdcrypto.randomUUID();socketnewWebSocket(ws://localhost:8080/ws/${clientId});socket.onopen(){console.log(WebSocket 已连接);};socket.onmessage(event){constmessageJSON.parse(event.data);if(message.type1){orderAudio.currentTime0;orderAudio.play().catch((){console.warn(浏览器尚未允许自动播放请先点击页面一次);});showNotice(您有新的订单${message.content});return;}if(message.type2){reminderAudio.currentTime0;reminderAudio.play().catch((){console.warn(浏览器尚未允许自动播放请先点击页面一次);});showNotice(用户正在催单${message.content});}};socket.onclose(){setTimeout(connectWebSocket,3000);};}connectWebSocket();functionshowNotice(text){// 替换为管理端实际的 Message、Notification 或弹窗组件console.log(text);}message.type 是服务端约定的消息类型type1 播放来单铃声type2 播放催单铃声。每次播放前把 currentTime 设为 0连续来单时铃声会从头开始。浏览器通常会拦截没有用户交互时的音频自动播放。因此管理端登录成功或首次进入订单页后应让用户主动点击一次“开启提醒”按钮在点击事件中调用一次 orderAudio.play() 后马上 pause() 并归零浏览器便会把后续音频播放视为已授权。生产环境还应将 ws:// 根据 HTTPS 部署切换为 wss://否则浏览器会拦截混合内容。六、从下单到铃声响起的调用链整个来单提醒链路可以按下面顺序排查用户支付成功 - OrderServiceImpl.paySuccess 更新订单为待接单、已支付 - 组装 type1 的 JSON 消息 - WebSocketServer.sendToAllClient 群发 - 管理端 socket.onmessage 接收 - 播放 order.mp3 并展示来单通知 用户点击催单 - GET /user/order/reminder/{id} - 组装 type2 的 JSON 消息 - WebSocketServer.sendToAllClient 群发 - 管理端播放 reminder.mp3 并展示催单通知排查时先打开管理端浏览器控制台确认 WebSocket 是否连接成功再确认服务端日志中的在线客户端数是否大于 0最后检查推送的 JSON 是否包含正确的 type 和 orderId。若消息已到达但没有声音优先检查音频文件地址和浏览器自动播放权限。总结这一部分把订单的两种后台能力接起来了Spring Task 负责按时间扫描并修正订单状态WebSocket 负责把支付成功和用户催单实时送到管理端。通过 type1 与 type2 区分消息类型管理端就能播放不同铃声并给出对应的订单提示。

相关新闻

FPGA实战系列总纲:基于 Xilinx ZUDR RFSoC 的多通道数字收发系统全链路解析

FPGA实战系列总纲:基于 Xilinx ZUDR RFSoC 的多通道数字收发系统全链路解析

本文是「FPGA实战」系列的总纲篇,用于把前面 30 多篇单模块实战文章串成一张完整的系统图。 如果你是一路跟着「FPGA实战(N)」读下来的,这篇会告诉你每个单模块在整个 RFSoC 数字收发系统里站在什么位置、为什么这么设计;如果你是先看到这篇,建议对照文中的【系列关联】跳转…

2026/7/19 17:55:40阅读更多 →
FPGA实战(56):基于AXI EMC接口的DMA控制器寄存器解码模块设计

FPGA实战(56):基于AXI EMC接口的DMA控制器寄存器解码模块设计

摘要 本文介绍一个寄存器解码模块(axi_emc0_decode),该模块作为AXI EMC总线与内部DMA控制器之间的桥接单元,将CPU发起的读写操作转换为DMA配置信号。模块采用单周期脉冲复位机制、双通道独立控制、位域可配置测试模式等设计,经过完整的功能仿真验证,适用于FPGA中的高速数…

2026/7/19 17:55:40阅读更多 →
HTTPotion替代方案对比:Tesla vs HTTPotion vs HTTPoison – 选择最适合你的Elixir HTTP客户端

HTTPotion替代方案对比:Tesla vs HTTPotion vs HTTPoison – 选择最适合你的Elixir HTTP客户端

HTTPotion替代方案对比:Tesla vs HTTPotion vs HTTPoison – 选择最适合你的Elixir HTTP客户端 【免费下载链接】httpotion [Deprecated because ibrowse is not maintained] HTTP client for Elixir (use Tesla please) 项目地址: https://gitcode.com/gh_mirror…

2026/7/19 17:55:40阅读更多 →
ngx_output_chain_get_buf

ngx_output_chain_get_buf

1 定义 ngx_output_chain_get_buf 函数 定义在 src/core/ngx_output_chain.cstatic ngx_int_t ngx_output_chain_get_buf(ngx_output_chain_ctx_t *ctx, off_t bsize) {size_t size;ngx_buf_t *b, *in;ngx_uint_t recycled;in ctx->in->buf;size ctx->buf…

2026/7/20 0:15:05阅读更多 →
互联网大厂常见Java面试题及答案汇总(2026持续更新)

互联网大厂常见Java面试题及答案汇总(2026持续更新)

金九银十即将来袭,又是一个跳槽的好季节,准备跳槽的同学都摩拳擦掌准备大面好几场,今天为大家准备了互联网面试必备的 1 到 5 年 Java 面试者都需要掌握的面试题,分别 JVM,并发编程,MySQL,Tomca…

2026/7/20 0:15:05阅读更多 →
python数据可视化技巧的100个练习 -- 31. 类别数据的点图

python数据可视化技巧的100个练习 -- 31. 类别数据的点图

重要性★★★☆☆ 难度★★☆☆☆ 你是一家零售公司的数据分析师。你的经理要求你可视化最近产品发布的客户满意度评级分布。评级是分类的,范围从“非常不满意”到“非常满意”。创建一个点图以显示每个评级类别的频率。使用 Python 进行数据处理和可视化。在代码中生成输入…

2026/7/20 0:13:05阅读更多 →
智能体走进物理世界,千里科技携舱驾协同成果亮相WAIC 2026

智能体走进物理世界,千里科技携舱驾协同成果亮相WAIC 2026

在2026世界人工智能大会(WAIC 2026)举办期间,千里科技董事长、阶跃星辰董事长印奇作为特邀嘉宾出席大会开幕式并在大会主论坛(上午场)发表主题演讲《当智能体进入物理世界》。在印奇看来,"智能体"…

2026/7/20 0:13:05阅读更多 →
商汤大装置发布“技术-生态-商业”闭环布局,共启“国产AI基础设施规模化商用元年”

商汤大装置发布“技术-生态-商业”闭环布局,共启“国产AI基础设施规模化商用元年”

7月18日,在WAIC 2026商汤科技 “基座大模型架构创新与生态合作论坛”上,商汤科技联合创始人、大装置事业群总裁杨帆发表《智变共生——加速AI基础设施持续升级》主题演讲,系统呈现了商汤大装置国产AI基础设施“技术-生态-商业”闭环布局&…

2026/7/20 0:13:05阅读更多 →
2026郑州美发学校避坑指南:拆解5种教学方式,谁在“流水线”谁在“真传技”?

2026郑州美发学校避坑指南:拆解5种教学方式,谁在“流水线”谁在“真传技”?

2026年想在郑州学美发,很多零基础学员最先搜索的问题就是:郑州美发学校哪家好?这个问题没有一个只看学校名字就能得出的答案。因为不同学校的课程方向、学习周期、教学方式和适合人群并不一样。有的更适合零基础,有的偏向发型师进修,还有的只做某一项短期技术培训。对于完全没…

2026/7/20 0:11:05阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/20 0:50:54阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/20 0:50:54阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/20 0:50:54阅读更多 →
2026 WAIC:努比亚二代“豆包手机”NaviX Ultra亮相,智能体验全面升级!

2026 WAIC:努比亚二代“豆包手机”NaviX Ultra亮相,智能体验全面升级!

7月18日智东西消息,在2026 WAIC期间,努比亚联合字节豆包打造的二代“豆包手机”努比亚NaviX Ultra首次亮相,相比一代有诸多升级。智能体手机理念中兴通讯终端事业部总裁、努比亚总裁倪飞表示,智能体手机要从人操作手机变为手机帮人…

2026/7/20 0:01:04阅读更多 →
努比亚NaviX Ultra亮相WAIC,智能体手机能否让用户生活更简单?

努比亚NaviX Ultra亮相WAIC,智能体手机能否让用户生活更简单?

努比亚NaviX Ultra:外观与功能双升级在2026 WAIC期间,首次亮相的努比亚NaviX Ultra吸引了众多目光。它是努比亚联合字节豆包打造的二代“豆包手机”,与一代努比亚M153相比,外观设计变化较大。其机身背部搭载横向排布的大尺寸影像模…

2026/7/20 0:01:04阅读更多 →
C# 将逗号分割的字符串转换为long,并添加到List<long>

C# 将逗号分割的字符串转换为long,并添加到List<long>

目录 方法1:使用Split和Convert.ToInt64 方法2:使用LINQ的Select和ToList 方法3:使用TryParse进行异常安全转换(推荐) 如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天…

2026/7/20 0:01:04阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/19 22:50:49阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/19 14:50:26阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/19 18:50:36阅读更多 →