苍穹外卖 项目记录 day11 Spring Task订单定时处理-来单提醒-客户催单

文章目录


Spring Task

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

应用场景:

1). 信用卡每月还款提醒

2). 银行贷款每月还款提醒

3). 火车票售票系统处理未支付订单

4). 入职纪念日为用户发送通知

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


cron表达式

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

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

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

举例:

2022年10月12日上午9点整 对应的cron表达式为:0 0 9 12 10 ? 2022 秒 分种 小时 日 月 周 年

一般日周的值不同时设置,其中一个设置,另一个用?表示。

cron表达式在线生成器:https://cron.qqe2.com/

通配符:

*表示所有值;

? 表示未说明的值,即不关心它为何值;

-表示一个指定的范围;

, 表示附加一个可能值;

/ 符号前表示开始时间,符号后表示每次递增的值;

cron表达式案例:

*/5 * * * * ? 每隔5秒执行一次

0 */1 * * * ? 每隔1分钟执行一次

0 0 5-15 * * ? 每天5-15点整点触发

0 0/3 * * * ? 每三分钟触发一次


Spring Task使用步骤

1). 导入maven坐标 spring-context(已存在)

2). 启动类添加注解 @EnableScheduling 开启任务调度

3). 自定义定时任务类

sky.server下创建task包

java 复制代码
@Component
@Slf4j
public class MyTask {
    /**
     * 定时任务 每5s执行一次
     */
    @Scheduled
    (cron = "*/5 * * * * ?")
    public void executeTask(){
        log.info("定时任务开始执行: {}", new Date());
    }
}

配置启动类

java 复制代码
@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching
@EnableScheduling
public class SkyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SkyApplication.class, args);
        log.info("server started");
    }
}
java 复制代码
@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching
@EnableScheduling
public class SkyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SkyApplication.class, args);
        log.info("server started");
    }
}

订单状态定时处理

2.1 需求分析

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

  • 下单后未支付,订单一直处于**"待支付"**状态

  • 用户收货后管理端未点击完成按钮,订单一直处于**"派送中"**状态

  • 通过定时任务每分钟检查一次是否存在支付超时订单(下单后超过15分钟仍未支付则判定为支付超时订单),如果存在则修改订单状态为"已取消"

  • 通过定时任务每天凌晨1点检查一次是否存在"派送中"的订单,如果存在则修改订单状态为"已完成"

自定义定时任务类OrderTask

java 复制代码
package com.sky.task;

/**
 * 自定义定时任务,实现订单状态定时处理
 */
@Component
@Slf4j
public class OrderTask {

    @Autowired
    private OrderMapper orderMapper;

    /**
     * 处理支付超时订单
     */
    @Scheduled(cron = "0 * * * * ?")
    public void processTimeoutOrder(){
        log.info("处理支付超时订单:{}", new Date());
    }

    /**
     * 处理"派送中"状态的订单
     */
    @Scheduled(cron = "0 0 1 * * ?")
    public void processDeliveryOrder(){
        log.info("处理派送中订单:{}", new Date());
    }

}

在OrderMapper接口中扩展方法:

java 复制代码
/**
     * 根据状态和下单时间查询订单
     * @param status
     * @param orderTime
     */
@Select("select * from orders where status = #{status} and order_time < #{orderTime}")
List<Orders> getByStatusAndOrdertimeLT(Integer status, LocalDateTime orderTime);

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

java 复制代码
/**
     * 处理支付超时订单
     */
@Scheduled(cron = "0 * * * * ?")
public void processTimeoutOrder(){
    log.info("处理支付超时订单:{}", new Date());

    LocalDateTime time = LocalDateTime.now().plusMinutes(-15);

    // select * from orders where status = 1 and order_time < 当前时间-15分钟
    List<Orders> ordersList = orderMapper.getByStatusAndOrdertimeLT(Orders.PENDING_PAYMENT, time);
    if(ordersList != null && ordersList.size() > 0){
        ordersList.forEach(order -> {
            order.setStatus(Orders.CANCELLED);
            order.setCancelReason("支付超时,自动取消");
            order.setCancelTime(LocalDateTime.now());
            orderMapper.update(order);
        });
    }
}

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

java 复制代码
/**
     * 处理"派送中"状态的订单
     */
@Scheduled(cron = "0 0 1 * * ?")
public void processDeliveryOrder(){
    log.info("处理派送中订单:{}", new Date());
    // select * from orders where status = 4 and order_time < 当前时间-1小时
    LocalDateTime time = LocalDateTime.now().plusMinutes(-60);
    List<Orders> ordersList = orderMapper.getByStatusAndOrdertimeLT(Orders.DELIVERY_IN_PROGRESS, time);

    if(ordersList != null && ordersList.size() > 0){
        ordersList.forEach(order -> {
            order.setStatus(Orders.COMPLETED);
            orderMapper.update(order);
        });
    }
}

功能测试

有一条订单,状态为1。订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消

启动服务,观察控制台日志。处理支付超时订单任务每隔1分钟执行一次。

状态已更改为6,已取消。


WebSocket

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

HTTP协议和WebSocket协议对比:

  • HTTP是短连接
  • WebSocket是长连接
  • HTTP通信是单向的,基于请求响应模式
  • WebSocket支持双向通信
  • HTTP和WebSocket底层都是TCP连接
  • handShake -> Acknowledgement

WebSocket缺点:

服务器长期维护长连接需要一定的成本

各个浏览器支持程度不一

WebSocket 是长连接,受网络限制比较大,需要处理好重连

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

WebSocket应用场景:

1). 视频弹幕

2). 网页聊天

3). 体育实况更新

4). 股票基金报价实时更新

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


WebSockt入门示例

实现步骤:

1). 直接使用websocket.html页面作为WebSocket客户端

2). 导入WebSocket的maven坐标

3). 导入WebSocket服务端组件WebSocketServer,用于和客户端通信

4). 导入配置类WebSocketConfiguration,注册WebSocket的服务端组件

5). 导入定时任务类WebSocketTask,定时向客户端推送数据

WebSocketServer

java 复制代码
@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();
            }
        }
    }

}

WebSocketConfiguration

java 复制代码
@Configuration
public class WebSocketConfiguration {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

WebSocketTask

java 复制代码
@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实现管理端页面 和服务端保持长连接状态

用户点击催单按钮 调用WebSocket相关API实现服务端向客户端推送消息

客户端浏览器解析服务端推送消息 判断是来单提醒 还是客户催单 进行相应消息提示 和 语音播报

约定服务端 发给客户端浏览器数据格式JSON 字段 type orderId content

type 消息类型 1 来单提醒 2 客户催单 orderId 订单id content 消息内容

在OrderServiceImpl中

java 复制代码
@Autowired
private WebSocketServer webSocketServer;

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

    //支付成功时  向客户端浏览器推送消息
    Map map = new HashMap();
    map.put("type",1);
    map.put("orderId",ordersDB.getId());
    map.put("content","订单号:"+ outTradeNo);
    String json = JSON.toJSONString(map);
    webSocketServer.sendToAllClient(json);
}

客户催单

user/order/reminder/{id} GET

OrderController

java 复制代码
/**
     *  用户催单
     * @return
     */
@GetMapping("/reminder/{id}")
@ApiOperation("用户催单")
public Result reminder(@PathVariable("id") Long id){
    orderService.reminder(id);
    return Result.success();
}
java 复制代码
 void reminder(Long id);
java 复制代码
@Override
public void reminder(Long id) {
    //查询订单是否存在
    Orders orders = orderMapper.getById(id);
    if(orders == null){
        throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);
    }

    //基于WebSocket实现催单
    Map map = new HashMap();
    map.put("type",2);
    map.put("orderI",id);
    map.put("content","订单号:" + orders.getNumber());
    webSocketServer.sendToAllClient(JSON.toJSONString(map));
}
相关推荐
硬件人某某某6 分钟前
微信小程序~电器维修系统小程序
java·ajax·微信小程序·小程序
猿java8 分钟前
MySQL 如何实现主从复制?
java·后端·mysql
申尧强12 分钟前
flink JobGraph解析
大数据·数据库·flink
martian66518 分钟前
【Java基础篇】——第4篇:Java常用类库与工具类
java·开发语言
荷碧TongZJ21 分钟前
Jupyter Notebook 6/7 设置代码补全
ide·python·jupyter
励碼26 分钟前
解决 Sentinel 控制台无法显示 OpenFeign 资源的问题
spring boot·spring cloud·sentinel·bug·openfeign
violin-wang43 分钟前
Intellij IDEA调整栈内存空间大小详细教程,添加参数-Xss....
java·ide·intellij-idea·xss·栈内存·栈空间
在下陈平安1 小时前
java-LinkedList源码详解
java·开发语言
黑客老李1 小时前
一次使用十六进制溢出绕过 WAF实现XSS的经历
java·运维·服务器·前端·sql·学习·xss
圆️️1 小时前
【故障处理】ORA-19849 ORA-19612 0RA-17627 ORA-03114
运维·数据库·oracle