订单状态定时处理、来单提醒和客户催单(day10)

news2025/1/24 9:55:55

Spring Task

介绍

Spring Task 是 Spring 框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑。

定位: 定时任务框架

作用: 定时自动执行某段Java代码

为什么要在 Java 程序中使用 Spring Task?
应用场景:

  1. 信用卡每月还款提醒
  2. 银行贷款每月还款提醒
  3. 火车票售票系统处理未支付订单
  4. 入职纪念日为用户发送通知

强调: 只要是需要定时处理的场景都可以使用 Spring Task

cron表达式

cron表达式 其实就是一个字符串,通过cron表达式可以 定义任务触发的时间

构成规则: 分为6或7个域,由空格分隔开,每个域代表一个含义

  • 每个域的含义分别为:秒、分钟、小时、日、月、周、年(可选)

举例:
2022年10月12日上午9点整 对应的cron表达式为:0 0 9 12 10 ? 2022

说明: 一般的值不同时设置,其中一个设置,另一个用?表示。

比如: 描述2月份的最后一天,最后一天具体是几号呢?可能是28号,也有可能是29号,所以就不能写具体数字。

  • 为了描述这些信息,提供一些特殊的字符。这些具体的细节,我们就不用自己去手写,因为这个cron表达式,它其实有在线生成器。
  • cron表达式在线生成器:在线Cron表达式生成器

通配符:

  • * 表示所有值;
  • ? 表示未说明的值,即不关心它为何值;
  • - 表示一个指定的范围;
  • , 表示附加一个可能值;
  • / 符号前表示开始时间,符号后表示每次递增的值;

入门案例

Spring Task使用步骤

  1. 导入maven坐标 spring-context(已存在)
  2. 启动类添加注解 @EnableScheduling 开启任务调度
  3. 自定义定时任务类

package com.sky.task;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.util.Date;

/**
 * 自定义定时任务类
 */
@Slf4j
@Component
@Service
public class MyTask {

    /**
     * 定时任务,每隔5秒触发一次
     */
    @Scheduled(cron = "0/5 * * * * ?")
    public void executeTask(){
        log.info("定时任务执行:{}",new Date());
    }
}

订单状态定时处理

需求分析

用户下单后可能存在的情况:

  • 下单后未支付,订单一直处于 “待支付” 状态
  • 用户收货后管理端未点击完成按钮,订单一直处于 “派送中” 状态

对于上面两种情况需要通过定时任务来修改订单状态,具体逻辑为:

  • 通过定时任务每分钟检查一次是否存在支付超时订单(下单后超过15分钟仍未支付则判定为支付超时订单),如果存在则修改订单状态为“已取消”
  • 通过定时任务每天凌晨1点检查一次是否存在“派送中”的订单,如果存在则修改订单状态为“已完成”

代码开发

自定义定时任务类OrderTask(待完善):

OrderMapper接口中扩展方法

完善定时任务类的processTimeoutOrder方法:

完善定时任务类的processDeliveryOrder方法: 

功能测试

WebSocket

介绍

WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建 持久性 的连接, 并进行 双向 数据传输。

应用场景:

  • 视频弹幕
  • 网页聊天
  • 体育实况更新
  • 股票基金报价实时更新

    入门案例

    需求: 实现浏览器与服务器全双工通信。浏览器既可以向服务器发送消息,服务器也可主动向浏览器推送消息。

    实现步骤:

    1. 直接使用 websocket.html 页面作为 WebSocket 客户端
    2. 导入WebSocket的maven坐标
    3. 导入WebSocket服务端组件WebSocketServer,用于和客户端通信
    4. 导入配置类WebSocketConfiguration,注册WebSocket的服务端组件
    5. 导入定时任务类WebSocketTask,定时向客户端推送数据

    1). 定义websocket.html页面(资料中已提供)

    <!DOCTYPE HTML>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>WebSocket Demo</title>
    </head>
    <body>
        <input id="text" type="text" />
        <button onclick="send()">发送消息</button>
        <button onclick="closeWebSocket()">关闭连接</button>
        <div id="message">
        </div>
    </body>
    <script type="text/javascript">
        var websocket = null;
        var clientId = Math.random().toString(36).substr(2);
    
        //判断当前浏览器是否支持WebSocket
        if('WebSocket' in window){
            //连接WebSocket节点
            websocket = new WebSocket("ws://localhost:8080/ws/"+clientId);
        }
        else{
            alert('Not support websocket')
        }
    
        //连接发生错误的回调方法
        websocket.onerror = function(){
            setMessageInnerHTML("error");
        };
    
        //连接成功建立的回调方法
        websocket.onopen = function(){
            setMessageInnerHTML("连接成功");
        }
    
        //接收到消息的回调方法
        websocket.onmessage = function(event){
            setMessageInnerHTML(event.data);
        }
    
        //连接关闭的回调方法
        websocket.onclose = function(){
            setMessageInnerHTML("close");
        }
    
        //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
        window.onbeforeunload = function(){
            websocket.close();
        }
    
        //将消息显示在网页上
        function setMessageInnerHTML(innerHTML){
            document.getElementById('message').innerHTML += innerHTML + '<br/>';
        }
    
        //发送消息
        function send(){
            var message = document.getElementById('text').value;
            websocket.send(message);
        }
    	
    	//关闭连接
        function closeWebSocket() {
            websocket.close();
        }
    </script>
    </html>
    

     创建一个websocket包->WebSocketServer

    package com.sky.websocket;
    
    import org.springframework.stereotype.Component;
    import javax.websocket.OnClose;
    import javax.websocket.OnMessage;
    import javax.websocket.OnOpen;
    import javax.websocket.Session;
    import javax.websocket.server.PathParam;
    import javax.websocket.server.ServerEndpoint;
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * WebSocket服务
     */
    @Component
    @ServerEndpoint("/ws/{sid}")
    public class WebSocketServer {
    
        //存放会话对象
        private static Map<String, Session> sessionMap = new HashMap();
    
        /**
         * 连接建立成功调用的方法
         */
        @OnOpen
        public void onOpen(Session session, @PathParam("sid") String sid) {
            System.out.println("客户端:" + sid + "建立连接");
            sessionMap.put(sid, session);
        }
    
        /**
         * 收到客户端消息后调用的方法
         *
         * @param message 客户端发送过来的消息
         */
        @OnMessage
        public void onMessage(String message, @PathParam("sid") String sid) {
            System.out.println("收到来自客户端:" + sid + "的信息:" + message);
        }
    
        /**
         * 连接关闭调用的方法
         *
         * @param sid
         */
        @OnClose
        public void onClose(@PathParam("sid") String sid) {
            System.out.println("连接断开:" + sid);
            sessionMap.remove(sid);
        }
    
        /**
         * 群发
         *
         * @param message
         */
        public void sendToAllClient(String message) {
            Collection<Session> sessions = sessionMap.values();
            for (Session session : sessions) {
                try {
                    //服务器向客户端发送消息
                    session.getBasicRemote().sendText(message);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
    }
    

    config包下WebSocketConfiguration

    package com.sky.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.socket.server.standard.ServerEndpointExporter;
    
    /**
     * WebSocket配置类,用于注册WebSocket的Bean
     */
    @Configuration
    public class WebSocketConfiguration {
    
        @Bean
        public ServerEndpointExporter serverEndpointExporter() {
            return new ServerEndpointExporter();
        }
    
    }
    

    Task包下--》WebSocketTask 定时任务类

    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;
    
    @Component
    public class WebSocketTask {
        @Autowired
        private WebSocketServer webSocketServer;
    
        /**
         * 通过WebSocket每隔5秒向客户端发送消息
         */
        @Scheduled(cron = "0/5 * * * * ?")
        public void sendMessageToClient() {
            webSocketServer.sendToAllClient("这是来自服务端的消息:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
        }
    }
    

    思考?

    既然WebSocket支持双向通信,功能看似比HTTP强大,那么我们是不是可以基于WebSocket开发所有的业务功能?

    WebSocket缺点:

    • 服务器长期维护长连接需要一定的成本
    • 各个浏览器支持程度不一
    • WebSocket 是长连接,受网络限制比较大,需要处理好重连

    结论:WebSocket并不能完全取代HTTP,它只适合在特定的场景下使用

    来单提醒

    需求分析和设计

    用户下单并且支付成功后,需要第一时间通知外卖商家。通知的形式有如下两种:

    • 语音播报
    • 弹出提示框

    设计思路:

    • 通过 WebSocket 实现管理端页面和服务端保持长连接状态
    • 当客户支付后,调用 WebSocket 的相关 API 实现服务端向客户端推送消息
    • 客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报
    • 约定服务端发送给客户端浏览器的数据格式为JSON,字段包括:type,orderId,content
      • type 为消息类型,1为来单提醒 2为客户催单
      • orderId 为订单id
      • content 为消息内容

    首先运行后端

    Request URL:ws://localhost/ws/00sr43fvqg26  先去请求nginx,由nginx转发到8080端口号

    代码开发

    在OrderServiceImpl中注入WebSocketServer对象,修改paySuccess方法,加入如下代码:

    /**
         * 支付成功,修改订单状态
         * @param outTradeNo
         */
        public void paySuccess(String outTradeNo) {
    
            // 根据订单号查询订单
            Orders ordersDB = orderMapper.getByNumber(outTradeNo);
    
            // 根据订单id更新订单的状态、支付方式、支付状态、结账时间
            Orders orders = Orders.builder()
                    .id(ordersDB.getId())
                    .status(Orders.TO_BE_CONFIRMED)
                    .payStatus(Orders.PAID)
                    .checkoutTime(LocalDateTime.now())
                    .build();
    
            orderMapper.update(orders);
    
            //通过websocket向客户端浏览器推送消息 type orderId content
            Map map = new HashMap();//封装这些数据
            map.put("type", 1);//通知类型 1来单提醒 2客户催单
            map.put("orderId", orders.getId());//订单id
            map.put("content","订单号:" + outTradeNo);
    
            webSocketServer.sendToAllClient(JSON.toJSONString(map));
        }

     注意没有实现微信支付功能,所以我们自己调用一下函数。在订单支付中

     /**
         * 订单支付
         *
         * @param ordersPaymentDTO
         * @return
         */
        public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
            // 当前登录用户id
            Long userId = BaseContext.getCurrentId();
            User user = userMapper.getById(userId);
    
    //        //调用微信支付接口,生成预支付交易单
    //        JSONObject jsonObject = weChatPayUtil.pay(
    //                ordersPaymentDTO.getOrderNumber(), //商户订单号
    //                new BigDecimal(0.01), //支付金额,单位 元
    //                "苍穹外卖订单", //商品描述
    //                user.getOpenid() //微信用户的openid
    //        );
    //        if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {
    //            throw new OrderBusinessException("该订单已支付");
    //        }
    //
    //        OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
    //        vo.setPackageStr(jsonObject.getString("package"));
    
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("code", "ORDERPAID");
            OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
            vo.setPackageStr(jsonObject.getString("package"));
    
            //为替代微信支付成功后的数据库订单状态更新,多定义一个方法进行修改
            Integer OrderPaidStatus = Orders.PAID; //支付状态,已支付
            Integer OrderStatus = Orders.TO_BE_CONFIRMED;  //订单状态,待接单
    
            //发现没有将支付时间 check_out属性赋值,所以在这里更新
            LocalDateTime check_out_time = LocalDateTime.now();
    
            //获取订单号码
            String orderNumber = ordersPaymentDTO.getOrderNumber();
    
            log.info("调用updateStatus,用于替换微信支付更新数据库状态的问题");
            orderMapper.updateStatus(OrderStatus, OrderPaidStatus, check_out_time, orderNumber);
    
            //模拟支付成功
            paySuccess(orderNumber);
            return vo;
    
        }

     如果没有声音,1、建议更换浏览器(谷歌可能没有声音)2、商家退出重新登录

    客户催单

    需求分析和设计

    用户在小程序中点击催单按钮后,需要第一时间通知外卖商家。通知的形式有如下两种:

    • 语音播报
    • 弹出提示框

    设计思路:

    • 通过WebSocket实现管理端页面和服务端保持长连接状态
    • 当用户点击催单按钮后,调用WebSocket的相关API实现服务端向客户端推送消息
    • 客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报
    • 约定服务端发送给客户端浏览器的数据格式为JSON,字段包括:type,orderId,content
      • type 为消息类型,1为来单提醒 2为客户催单
      • orderId 为订单id
      • content 为消息内容

    接口设计: 

    代码开发

    根据用户催单的接口定义,在user/OrderController中创建催单方法:

    OrderService接口中声明reminder方法:

    OrderServiceImpl中实现reminder方法:

    上一节:

    校验收货地址是否超出配送范围实战3(day09)-CSDN博客

    下一节:

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2281347.html

    如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

    相关文章

    ES6 简单练习笔记--变量申明

    一、ES5 变量定义 1.在全局作用域中 this 其实就是window对象 <script>console.log(window this) </script>输出结果: true 2.在全局作用域中用var定义一个变量其实就相当于在window上定义了一个属性 例如: var name "孙悟空" 其实就相当于执行了 win…

    麒麟操作系统服务架构保姆级教程(十四)iptables防火墙四表五链和防火墙应用案例

    如果你想拥有你从未拥有过的东西&#xff0c;那么你必须去做你从未做过的事情 防火墙在运维工作中有着不可或缺的重要性。首先&#xff0c;它是保障网络安全的关键防线&#xff0c;通过设置访问控制规则&#xff0c;可精准过滤非法网络流量&#xff0c;有效阻挡外部黑客攻击、恶…

    【AI编辑器】字节跳动推出AI IDE——Trae,专为中文开发者深度定制

    目录 一、背景 二、核心特性 2.1 AI驱动的代码自动生成 2.2 智能问答与代码补全 2.3 多语言支持 2.4 插件与扩展 三、架构 四、下载使用 4.1 下载与安装 4.2 界面与配置 五、应用实践 5.1 快速生成代码 5.2 智能问答与调试 5.3 团队协作与代码审查 六、与Cursor…

    机器学习2 (笔记)(朴素贝叶斯,集成学习,KNN和matlab运用)

    朴素贝叶斯模型 贝叶斯定理&#xff1a; 常见类型 算法流程 优缺点 集成学习算法 基本原理 常见方法 KNN&#xff08;聚类模型&#xff09; 算法性质&#xff1a; 核心原理&#xff1a; 算法流程 优缺点 matlab中的运用 朴素贝叶斯模型 朴素贝叶斯模型是基于贝叶斯…

    【赵渝强老师】K8s中Pod探针的HTTPGetAction

    在K8s集群中&#xff0c;当Pod处于运行状态时&#xff0c;kubelet通过使用探针&#xff08;Probe&#xff09;对容器的健康状态执行检查和诊断。K8s支持三种不同类型的探针&#xff0c;分别是&#xff1a;livenessProbe&#xff08;存活探针&#xff09;、readinessProbe&#…

    每日十题八股-2025年1月23日

    1.快排为什么时间复杂度最差是O&#xff08;n^2&#xff09; 2.快排这么强&#xff0c;那冒泡排序还有必要吗&#xff1f; 3.如果要对一个很大的数据集&#xff0c;进行排序&#xff0c;而没办法一次性在内存排序&#xff0c;这时候怎么办&#xff1f; 4.面试官&#xff1a;你的…

    bat批处理删除此电脑左侧及另存为下文档视屏等多余项

    发现的一个解决强迫症人士痛点的小方法&#xff0c;自整理为一键批处理&#xff0c;需者自取 他可以一键去掉电脑左侧及另存为下文档视屏等多余项&#xff0c;还界面一个清爽整洁 干干净净&#xff0c;舒服 分享的是bat文件&#xff0c;可以右键编辑查看源代码&#xff0c;绝…

    计算机毕业设计hadoop+spark+hive图书推荐系统 豆瓣图书数据分析可视化大屏 豆瓣图书爬虫 知识图谱 图书大数据 大数据毕业设计 机器学习

    温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

    ThinkPhp伪静态设置后,访问静态资源也提示找不到Controller

    ThinkPhp没有配置伪静态时&#xff0c;除了默认的IndexController能访问&#xff0c;其他路由Controller都访问不到&#xff0c;提示404错误。配置了伪静态后就解决了这个问题。 但是当我的ThinkPhp后台项目中有静态资源放在public目录&#xff08;或子目录&#xff09;中需要…

    HarmonyOS:通过(SQLite)关系型数据库实现数据持久化

    一、场景介绍 关系型数据库基于SQLite组件&#xff0c;适用于存储包含复杂关系数据的场景&#xff0c;比如一个班级的学生信息&#xff0c;需要包括姓名、学号、各科成绩等&#xff0c;又或者公司的雇员信息&#xff0c;需要包括姓名、工号、职位等&#xff0c;由于数据之间有较…

    豆包MarsCode 蛇年编程大作战 | 高效开发“蛇年运势预测系统”

    &#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 豆包MarsCode 蛇年编程大作战 | &#x1f40d; 蛇年运势预测 在线体验地址&#xff1a;蛇年…

    JAVA-快速排序

    一、快速排序基本思想 快速排序是 Hoare 于 1962 年提出的一种二叉树结构的交换排序方法&#xff0c;其基本思想为&#xff1a; 任取待排序元素序列中的某元 素作为基准值&#xff0c;按照该排序码将待排序集合分割成两子序列&#xff0c;左子序列中所有元素均小于基准值&#…

    acm培训 part 1(题解)

    第一部分的培训为语法糖&#xff0c;stl容器以及复杂度。 题目分析 1.1 Long Loong AtCoder - abc336_a ​​ 这道题的重点在于多个o的输出。在保证前面‘L’ 的输出和后面‘ng’的输出下&#xff0c;输入需要输出的o的数字&#xff0c;来实现需要输出的效果。 代码如下 …

    vulfocus/fastjson-cnvd_2017_02833复现

    漏洞概述 Fastjson 是阿里巴巴开发的一个高性能的 Java 库&#xff0c;用于将 Java 对象转换成 JSON 格式&#xff08;序列化&#xff09;&#xff0c;以及将 JSON 字符串转换回 Java 对象&#xff08;反序列化&#xff09;。 fastjson在解析json的过程中,支持使用type字段来指…

    Ceisum无人机巡检直播视频投射

    接上次的视频投影&#xff0c;Leader告诉我这个视频投影要用在两个地方&#xff0c;一个是我原先写的轨迹回放那里&#xff0c;另一个在无人机起飞后的地图回显&#xff0c;要实时播放无人机拍摄的视频&#xff0c;还要能转镜头&#xff0c;让我把这个也接一下。 我的天&#x…

    一句话,我让 AI 帮我做了个 P 图网站!

    每到过节&#xff0c;不少小伙伴都会给自己的头像 P 个图&#xff0c;加点儿装饰。 比如圣诞节给自己头上 P 个圣诞帽&#xff0c;国庆节 P 个小红旗等等。这是一类比较简单、需求量却很大的 P 图场景&#xff0c;也有很多现成的网站和小程序&#xff0c;能帮你快速完成这件事…

    【深度学习】 自动微分

    自动微分 正如上节所说&#xff0c;求导是几乎所有深度学习优化算法的关键步骤。 虽然求导的计算很简单&#xff0c;只需要一些基本的微积分。 但对于复杂的模型&#xff0c;手工进行更新是一件很痛苦的事情&#xff08;而且经常容易出错&#xff09;。 深度学习框架通过自动…

    Go语言中的值类型和引用类型特点

    一、值类型 值类型的数据直接包含值&#xff0c;当它们被赋值给一个新的变量或者作为参数传递给函数时&#xff0c;实际上是创建了原值的一个副本。这意味着对新变量的修改不会影响原始变量的值。 Go中的值类型包括&#xff1a; 基础类型&#xff1a;int&#xff0c;float64…

    Vue2 项目二次封装Axios

    引言 在现代前端开发中&#xff0c;HTTP请求管理是构建健壮应用的核心能力之一。Axios作为目前最流行的HTTP客户端库&#xff0c;其灵活性和可扩展性为开发者提供了强大的基础能力。 1. 为什么要二次封装Axios&#xff1f; 1.1 统一项目管理需求 API路径标准化&#xff1a;…

    使用Layout三行布局(SemiDesign)

    tips&#xff1a;Semi Design网址 &#xff1a;Semi Design 1、安装Semi # 使用 npm npm i douyinfe/semi-ui# 使用 yarn yarn add douyinfe/semi-ui# 使用 pnpm pnpm add douyinfe/semi-ui 2、引入Layout布局 import { Layout } from douyinfe/semi-ui;3、开始实现三行布局…