【苍穹外卖】项目实战Day11

🔥 本文由 程序喵正在路上 原创,CSDN首发!
💖 系列专栏:苍穹外卖项目实战
🌠 首发时间:2024年5月21日
🦋 欢迎关注🖱点赞👍收藏🌟留言🐾

Spring Task

介绍

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

  • 定位:定时任务框架
  • 作用:定时自动执行某段 Java 代码

应用场景:

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

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

cron表达式

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

  • 构成规则:分为 67 个域,由空格分隔开,每个域代表一个含义
  • 每个域的含义分别为:秒、分钟、小时、日、月、周、年(可选)

下面是一个简单的 cron 表达式:
在这里插入图片描述
虽然 cron 表达式不是很复杂,但是有时候自己写起来还是有点麻烦的,所以我们这里介绍一个 cron 表达式在线生成器:https://cron.qqe2.com/,我们只要选择自己的需求,它就会自动帮我们生成 cron 表达式。

在这里插入图片描述

入门案例

Spring Task 使用步骤:

  • 导入 maven 坐标,其包含在 spring-context 这个依赖中(已存在)

    在这里插入图片描述

  • 启动类添加注解 @EnableScheduling 开启任务调度

  • 自定义定时任务类

    新建一个包 task 用来存放我们的定时任务类:

    在这里插入图片描述

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    import java.util.Date;
    
    @Component
    @Slf4j
    public class MyTask {
    
        /**
         * 定时任务, 每隔5秒触发一次
         */
        @Scheduled(cron = "0/5 * * * * ?")
        public void executeTask() {
            log.info("定时任务开始执行:{}", new Date());
        }
    }
    

    当我们启动服务后,控制台每隔 5 秒就会输出一次:

    在这里插入图片描述

    测试完,可以将这个类注释掉

订单状态定时处理

需求分析

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

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

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

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

代码开发

task 下创建订单定时任务类 OrderTask

import com.sky.constant.MessageConstant;
import com.sky.entity.Orders;
import com.sky.mapper.OrderMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.List;

@Component
@Slf4j
public class OrderTask {

    @Autowired
    private OrderMapper orderMapper;

    /**
     * 支付超时订单处理
     */
    @Scheduled(cron = "0 * * * * ?")    //每分钟执行一次
    public void processTimeoutOrder() {
        log.info("开始进行支付超时订单处理:{}", LocalDateTime.now());

        LocalDateTime time = LocalDateTime.now().plusMinutes(-15);  //在该时间前未支付的订单都已超时
        List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.PENDING_PAYMENT, time);

        if (ordersList != null && ordersList.size() > 0) {
            ordersList.forEach(orders -> {
                orders.setStatus(Orders.CANCELLED); //已取消
                orders.setCancelReason(MessageConstant.PAYMENT_TIMEOUT_AUTO_CANCEL);    //取消原因
                orders.setCancelTime(LocalDateTime.now());  //取消时间

                //更新订单
                orderMapper.update(orders);
            });
        }
    }

    /**
     * 派送中状态的订单处理
     */
    @Scheduled(cron = "0 0 1 * * ?")   //每天凌晨1点执行一次
    public void processDeliveryOrder() {
        log.info("开始进行未完成订单状态处理:{}", LocalDateTime.now());

        LocalDateTime time = LocalDateTime.now().plusMinutes(-60);  //处理前一天所有派送中状态的订单
        List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.DELIVERY_IN_PROGRESS, time);

        if (ordersList != null && ordersList.size() > 0) {
            ordersList.forEach(orders -> {
                orders.setStatus(Orders.COMPLETED); //已完成

                //更新订单
                orderMapper.update(orders);
            });
        }
    }
}

OrderMapper 中创建 getByStatusAndOrderTimeLT 方法并配置 SQL

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

功能测试

可以通过如下方式进行测试:

  • 查看控制台sql
  • 查看数据库中数据变化

可以将时间改一下,这样比较好测试

在这里插入图片描述

WebSocket

介绍

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

在这里插入图片描述

HTTP 协议和 WebSocket 协议对比:

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

应用场景:

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

入门案例

接一下,我们实现一个简单的 websocket 入门案例,效果如下图所示

在这里插入图片描述

实现步骤:

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

相关代码在资料中已全部准备好,我们只需要导入代码,看懂代码逻辑并测试一下即可。

在这里插入图片描述
在这里插入图片描述

记得重新编译一下整个项目。

启动服务,打开客户端:

在这里插入图片描述

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

WebSocket 缺点:

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

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

来单提醒

需求分析和设计

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

  • 语音播报

  • 弹出提示框

    在这里插入图片描述

设计:

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

代码开发

前面,我们已经导入 WebSocket 服务端组件,我们就是通过它来和客户端进行通信的。

在这里插入图片描述

OrderServiceImpl 中注入 WebSocketServer 对象,然后修改 paySuccess 方法,加入一些代码:

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向客户端浏览器推送消息
    Map map = new HashMap();
    map.put("type", 1); //来单提醒
    map.put("orderId", orders.getId());
    map.put("content", "订单号:" + outTradeNo);

    webSocketServer.sendToAllClient(JSON.toJSONString(map));
}

功能测试

启动服务,可以看到 websocket 已经连接:

在这里插入图片描述

在这里插入图片描述

来到小程序端,随便添加菜品,用户完成支付后,管理端就会自动弹出待接单提醒,同时会有语音播报:

如果没有语音的话,可以尝试管理端重新登录,或者小程序端重新编译。

在这里插入图片描述

用户催单

需求分析和设计

当订单长时间没被接单时,用户可以点击催单按钮进行催单,用户点击按钮后,系统需要第一时间通知外卖商家。通知的形式有如下两种:

  • 语音播报
  • 弹出提示框

在这里插入图片描述

设计:

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

接口设计:
在这里插入图片描述

代码开发

OrderController 中创建 reminder 方法:

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

OrderService 接口中声明 reminder 方法:

/**
 * 用户催单
 *
 * @param id
 */
void reminder(Long id);

OrderServiceImpl 中实现 reminder 方法:

/**
 * 用户催单
 *
 * @param id
 */
public void reminder(Long id) {
    //查询订单是否存在
    Orders orders = orderMapper.getById(id);
    if (orders == null) {
        throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);
    }

    //基于websocket实现催单
    HashMap map = new HashMap();
    map.put("type", 2); //用户催单
    map.put("orderId", id);
    map.put("content", "订单号:" + orders.getNumber());

    webSocketServer.sendToAllClient(JSON.toJSONString(map));
}

功能测试

启动服务,来到小程序端,随便打开一个待接单的订单,点击催单按钮后,可以发现管理端自动弹出催单提醒,同时会有语音播报:

在这里插入图片描述

在这里插入图片描述

  • 43
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序喵正在路上

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值