苍穹外卖资源点整理+个人错误解析-Day10-订单状态定时处理(Spring Task)、来单提醒和客户催单

订单状态定时处理

分析

在支付订单界面,如果超时不支付就会取消订单,取消是通过程序而不是人工。

还有派送订单,在派送成功以及用户收到后我们就会点击完成,而如果没点的话我们也需要通过程序来处理。

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

处理逻辑:

代码

逻辑:

先搭好框架:

复制代码
@Scheduled(cron ="0 * * * * ?  ")
public void processTimeoutOrder(){
log.info("定时处理超时订单:{}", LocalDateTime.now());

引入ordersmapper以此来进行操作:

根据逻辑,如何判断每分钟触发一次的前提是超时的订单,一共有两点要求:

1.状态处于待付款

2.处于这个状态超过15分钟

sql语句大概这样写:

复制代码
 selecet*from orders where status=?(待付款状态) and order_time<?(小于当前时间-15min)

放到ordesmapper中:查询出来的很有可能是一个集合:

复制代码
 /*
 判断超时订单
  */
    @Select("select * from orders where status=#{status} and order_time<#{orderTime}")
    List<Orders> getByStatusAndOrderTimeLT(Integer status, LocalDateTime orderTime);
}

再回到代码,如何实现超过15分钟这个逻辑,放在代码里大概是这样:现在的时间减去15分钟得到的时间,比得到的时间小的就是超时,使用代码:

复制代码
LocalDateTime time = LocalDateTime.now().plusMinutes(-15);
复制代码
List<Orders> orders= orderMapper.getByStatusAndOrderTimeLT(Orders.PENDING_PAYMENT, time);

接下来就要判断是否查询到数据:

复制代码
if (orders!=null && orders.size()>0){
    for (Orders orders1:orders){
        orders1.setStatus(Orders.CANCELLED);
        orders1.setCancelReason("订单超时");
        orders1.setCancelTime(LocalDateTime.now());
        orderMapper.update(orders1);
    }

完整代码:

复制代码
@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);
        //查询超时的订单:
        // selecet*from orders where status=?(待付款状态) and order_time<?(小于当前时间-15min)
        List<Orders> orders= orderMapper.getByStatusAndOrderTimeLT(Orders.PENDING_PAYMENT, time);
    if (orders!=null && orders.size()>0){
        for (Orders orders1:orders){
            orders1.setStatus(Orders.CANCELLED);
            orders1.setCancelReason("订单超时");
            orders1.setCancelTime(LocalDateTime.now());
            orderMapper.update(orders1);
        }
    }
    }
}

以上是处理超时订单,以下是处理派送中订单:

复制代码
/*
处理一直派送中订单
 */
@Scheduled(cron = "0 0 1 * * ?")//每天凌晨1点触发
public void processDeliveryOrder(){
    log.info("定时处理处于派送中的订单{}",LocalDateTime.now());
    LocalDateTime localDateTime = LocalDateTime.now().plusMinutes(-60);
    List<Orders> orders = orderMapper.getByStatusAndOrderTimeLT(Orders.DELIVERY_IN_PROGRESS, localDateTime);
    if (orders!=null && orders.size()>0){
        for (Orders orders1:orders){
            orders1.setStatus(Orders.COMPLETED);
            orderMapper.update(orders1);
        }
}

Spring Task

介绍

是一个定时任务框架。作用就是定时自动执行某段java代码。

应用场景如:

cron表达式

本质是一个字符串,用于定义任务触发的时间。

构成规则:

其中这个周代表的是星期几。

一般来说,日和周是不能同时出现的,因为并不能确定这个日就是正确的周

有时候会用特殊字符,比如2月的最后一天究竟是28还是29,表达式通过在线cron表达式生成器网址生成即可:在线Cron表达式生成器

案例

使用:

添加启动:

入门:

测试

只需要运行项目,查看控制台输出是否有输出。

来单提醒

用户支付完订单后商家会跳出语言提醒。

分析

设计:

type是用来标注订单的类型,不然不知道究竟是来单提醒还是客户催单。

orderid是订单id,因为无论是什么提醒都是订单。

content是指具体的提醒内容,指的是弹出来的提醒小框。

代码

通过websocket实现长连接是通过webSocketSever实现的,至于如何和前端绑定,是由前端代码写好了代码。

请求的地址没有写8080,为什么也能请求到呢?

这是因为我们是用了nignx反向代理,是进行了一次转发。但是这样做的前提是在nignx进行了路径配置。

如图所示

在微信的支付订单代码(controller/notify)这个方法中调用的这个方法。

所以在这个方法通过ws给客户端推送消息。

WebSocket

即浏览器可以向服务器传输数据,服务器也可以主动向浏览器传输数据。

区别:

常见的应用场景:

视频弹幕

网页聊天

实况更新

入门案例

实现步骤:

使用websocket.html页面作为socket客户端。

导入maven坐标:

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-websocket</artifactId>

</dependency>

导入服务端组件WebSocketServer,用于和客户端通信。

导入定时任务类定时推送数据。

客户端服务端建立连接,本质上就是一个会话,这个map是用来存储会话对象的:

握手成功后服务端会自动掉用下面这个方法。路径参数加sid对应的是上文中的{sid}

配置:创建一个websocket包:

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

}

在配置类加入一个类:

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

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

}

方便测试效果,加入一个定时测试类:

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

启动程序之后查看效果:

先打开html文件,显示建立连接:

每五秒出现一个消息。

此外客户端也可以给服务端发消息:

客户催单

分析

是由客户催发的,用户点击催单按钮之后它会发起一次请求来请求服务端,所以需要设计一个新接口。

代码

controller:

复制代码
/*
用户催单提醒
 */
@GetMapping("/reminder/{id}")
public Result reminder(@PathVariable("id") Long id){
    orderService.reminder(id);
    return Result.success();
}

serviceimpl:

复制代码
/*
用户催单提醒
 */
    @Override
    public void reminder(Long id) {
//确认有没有订单
        // 根据id查询订单
        Orders ordersDB = orderMapper.getById(id);
        // 校验订单是否存在,并且状态为4
        if (ordersDB == null) {
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }
        //由于方法要的是json,先转换
        //通过ws向客户端浏览器推送消息 type orderid content
        Map map = new HashMap<>();
       //1为来单提醒,2为客户催单
        map.put("type",2);
        map.put("orderId",ordersDB.getId());
        map.put("content","订单号:"+ordersDB.getNumber());
        //map转json
        String json = JSON.toJSONString(map);
        webSocketServer.sendToAllClient(json);
        //存在就推消息
        webSocketServer.sendToAllClient(json);
    }
相关推荐
leobertlan2 小时前
2025年终总结
前端·后端·程序员
面向Google编程3 小时前
从零学习Kafka:数据存储
后端·kafka
易安说AI4 小时前
Claude Opus 4.6 凌晨发布,我体验了一整晚,说说真实感受。
后端
易安说AI4 小时前
Ralph Loop 让Claude无止尽干活的牛马...
前端·后端
易安说AI4 小时前
用 Claude Code 远程分析生产日志,追踪 Claude Max 账户被封原因
后端
JH30734 小时前
SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案
java·spring boot·spring
颜酱5 小时前
图结构完全解析:从基础概念到遍历实现
javascript·后端·算法
Coder_Boy_5 小时前
技术让开发更轻松的底层矛盾
java·大数据·数据库·人工智能·深度学习
invicinble6 小时前
对tomcat的提供的功能与底层拓扑结构与实现机制的理解
java·tomcat
较真的菜鸟6 小时前
使用ASM和agent监控属性变化
java