【项目日志|苍穹外卖】Day10:订单状态定时处理与 WebSocket 实战(来单提醒 / 客户催单)

本篇文章介绍了定时任务的快速上手方式(@EnableScheduling、@Scheduled 与常用 cron 表达式)、WebSocket 服务端组件的注册与群发消息的实现思路,以及 HTTP 与 WebSocket 在连接形态和通信模式上的差异,为项目加入“准实时”能力打下基础。

请添加图片描述



完成任务清单

  • 订单状态定时处理
  • 来单提醒
  • 客户催单

主要功能展示


1. 订单状态定时处理

在这一个功能下,使用到了Spring Task的知识点,简单讲解,作为记录。

Spring Task 快速入门

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

业务场景

常用于每月话费短信自动取消超时支付的订单等地方。

实现流程

1.导入Maven坐标

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
</dependency>

2.添加注解@EnableScheduling至启动类,开启任务调度
3.自定义任务类:每五秒自动触发一次

我们使用@Scheduled设置定时,括号内是cron表达式
cron表达式实则就是字符串,可以定义任务触发的时间,从左到右分别为:秒、分钟、小时、日、月、周、年(可选)
可以使用在线生成器生成:cron表达式在线生成器

@Component
@Slf4j
public class MyTask {
    @Scheduled(cron = "0/5 * * * * ?")
    public void executeTask(){
        log.info("定时任务开始执行:{}",new Date());
    }
}
订单状态定时处理功能

业务分析

用户下单之后可能存在以下情况

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

解决思路
通过Spring Task去进行定时任务来修改订单状态即可,如以下情况

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

实现流程
1.先自定义定时任务类,用于备用(别忘记在启动类添加@EnableScheduling)

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

2.由于涉及了订单的时间,因此需要在对应Mapper接口拓展方法。

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

3.完善自定义任务类

处理支付超时订单

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

处理派送中状态的订单

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

2. 来单提醒与客户催单功能

在这一个功能下,使用到了WebSocket的知识点。

WebSocket 简单介绍

WebSocket是一种在单个TCP连接上进行全双工通信的网络协议,允许服务器和客户端之间进行实时双向数据传输。

HTTP协议与WebSocket协议对比:

对比项目HTTP协议WebSocket协议
连接类型短连接长连接
通信方向单向通信,基于请求响应模式双向通信
底层协议基于TCP连接基于TCP连接

业务场景

WebSocket功能看似比HTTP强大,但由于WebSocket是长连接,受网络限制比较大,加之服务器长期维护长连接需要成本,故只能在特定场景使用。
常适用于视频弹幕网络聊天实况更新等场景

解决思路

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

来单提醒功能

业务场景

用户下单并且支付成功后,需要第一时间通知外卖商家。通知形式一般有语音播报弹出提示框

实现流程图
来单提醒:

成功支付
触发'paySuccess'方法
WebSocket推送
用户下单
微信支付回调
更新订单状态为待确认
商家端获得提醒

实现流程

1.定义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();
            }
        }
    }

}

2.定义配置类,注册WebSocket的服务端组件

/**
 * WebSocket配置类,用于注册WebSocket的Bean
 */
@Configuration
public class WebSocketConfiguration {

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

}

3.在实现类中注入WebSocketServer对象,修改paySuccess方法(来单提醒功能)

	@Autowired
    private WebSocketServer webSocketServer;

    public void paySuccess(String outTradeNo) {
        // 当前登录用户id
        Long userId = BaseContext.getCurrentId();

        // 根据订单号查询当前用户的订单
        Orders ordersDB = orderMapper.getByNumberAndUserId(outTradeNo, userId);

        // 根据订单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);//消息类型,1表示来单提醒
        map.put("orderId", orders.getId());
        map.put("content", "订单号:" + outTradeNo);

        //通过WebSocket实现来单提醒,向客户端浏览器推送消息
        webSocketServer.sendToAllClient(JSON.toJSONString(map));
    }
用户催单功能

业务场景

用户催单后,也需要第一时间通知外卖商家,通知形式一般有语音播报弹出提示框

实现流程图

WebSocket推送
用户催单
商家端获得提醒

实现流程

1.由于已经定义WebSocket服务端组件与配置类,故跳过

2.根据用户催单的接口定义,在Controller层创建方法

    @GetMapping("/reminder/{id}")
    @ApiOperation("用户催单")
    public Result reminder(@PathVariable("id") Long id) {
        orderService.reminder(id);
        return Result.success();
    }

3.在实现类中实现

    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);//2代表用户催单
        map.put("orderId", id);
        map.put("content", "订单号:" + orders.getNumber());
        webSocketServer.sendToAllClient(JSON.toJSONString(map));
    }

本文为苍穹外卖项目学习笔记,持续更新中…

如果我的内容对你有帮助,希望可以收获你的点赞、评论、收藏。

请添加图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值