目录

Day10内容

Spring Task

一.简介

二.cron表达式

三.入门案例

1.分析

2.代码实现

3.测试

订单状态定时处理

一.问题分析

二.代码实现

三.测试

四.gitee提交记录

WebSocket 

一.介绍

二.入门案例

一.实现步骤:

二.代码实现

三.测试

来单提醒

一.问题分析

二.代码实现

三.测试

四.gitee提交记录

客户催单

一.问题分析

二.代码实现

三.测试

四.gitee提交记录


Day10内容

Spring Task

一.简介

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

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

应用场景:

- 信用卡每月还款提醒

- 银行贷款每月还款提醒

- 火车票售票系统

- 处理未支付订单

- 入职纪念日为用户发送通知

二.cron表达式

cron表达式其实就是一个字符串,通过cron表达式可以定义任务触发的时间 构成规则:分为6或7个域,由空格分隔开,每个域代表一个含义 每个域的含义分别为:秒、分钟、小时、日、月、周、年(可选)

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

三.入门案例

1.分析

Spring Task使用步骤:

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

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

- 自定义定时任务类

2.代码实现

创建task包和Mytask测试文件  com.sky.task.MyTask 编写每5秒执行一次

Mytask

package com.sky.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());
    }
}

启动类添加注解 @EnableScheduling

package com.sky;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching//开启缓存注解功能
@EnableScheduling//开启任务调度
public class SkyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SkyApplication.class, args);
        log.info("server started");
    }
}
3.测试

订单状态定时处理

一.问题分析

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

-  下单后未支付,订单一直处于“待支付”状态

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

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

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

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

二.代码实现

OrderTask(新建)

package com.sky.task;

import com.sky.entity.Orders;
import com.sky.mapper.OrderMapper;
import com.sky.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.sky.entity.Orders;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;

/**
 * 定时任务类,定时处理订单状态
 */
@Component
@Slf4j
public class OrderTask {
    @Autowired
    private OrderMapper orderMapper;
    /**
     * 处理支付超时订单
     */
    @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){
            for(Orders order : ordersList) {
                order.setStatus(Orders.CANCELLED);
                order.setCancelReason("订单支付超时,自动取消");
                order.setCancelTime(LocalDateTime.now());
                orderMapper.update(order);
            }
        }
    }

    /**
     * 处理处于派送中状态的订单
     */
    @Scheduled(cron = "0 0 1 * * ?")//每天凌晨一点触发一次
    public void processDeliveryOrder(){
        log.info("定时处理处于派送中的订单{}",LocalDateTime.now());
        LocalDateTime time = LocalDateTime.now().plusMinutes(-60);
        orderMapper.getByStatusAndOrdertimeLT(Orders.DELIVERY_IN_PROGRESS,time);
        List<Orders> ordersList = orderMapper.getByStatusAndOrdertimeLT(Orders.DELIVERY_IN_PROGRESS, time);
        if(ordersList != null && ordersList.size() > 0){
            for(Orders order : ordersList) {
                order.setStatus(Orders.COMPLETED);
                orderMapper.update(order);
            }
        }




    }

}

在OrderMapper接口中扩展方法

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

为方便测试,注释掉了原先两个方法的执行时间,修改为Mytask中的执行时间

在数据库修改一个订单状态为1时,会被修改为6已取消,可以看到sql语句的执行

也可以等待一分钟,看到sql语句执行,order表中的状态更新为已取消

四.gitee提交记录

 2025-08-05
- 提交说明:feat: 订单状态定时处理功能
-查看详情:[Gitee 提交记录] 

feat: 订单状态定时处理功能 · 0cc79b4 · jh11/sky-take-out - Gitee.com

WebSocket 

一.介绍

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

HTTP协议和WebSocket协议对比:

  • HTTP是短连接

  • WebSocket是长连接

  • HTTP通信是单向的,基于请求响应模式

  • WebSocket支持双向通信

  • HTTP和WebSocket底层都是TCP连接

应用场景:

-  视频弹幕

-  网页聊天

-  体育实况更新

-  股票基金报价实时更新

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

WebSocket缺点:

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

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

二.入门案例

一.实现步骤:

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

2.导入WebSocket的maven坐标

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

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

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

二.代码实现

导入代码

WebSocketTask  路径  com.sky.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()));
    }
}

WebSocketServer 路径  com.sky.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();
            }
        }
    }

}

WebSocketConfiguration 路径 com.sky.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();
    }

}

maven坐标和HTML页面已经定义或提供

三.测试

来单提醒

一.问题分析

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

  • 语音播报 在这里插入图片描述

  • 弹出提示框

产品原型

设计思路:

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

二.代码实现

在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表示来单提醒
        map.put("orderId",orders.getId());
        map.put("content","订单号:"+outTradeNo);
        String json=JSON.toJSONString(map);
        //通过WebSocket实现来单提醒,向客户端浏览器推送消息
        webSocketServer.sendToAllClient(json);
--------------------------------------------------------------------------------------

    }

  由于之前微信支付采用了模拟支付,所以paySuccess方法不会被调用,所以修改pyment代码实现此功能,与此同时,优化一下paySuccess中的getByNumber,如果只查询订单号,就无法知道是哪个用户点的单,所以把getByNumber优化为getByNumberAndUserId,修改如下

修改后的OrderServiceImpl

   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);
        //通过Websocket向客户端浏览器推送消息 type orderId content
        Map map=new HashMap();
        map.put("type",1);//消息类型,1表示来单提醒
        map.put("orderId",orders.getId());
        map.put("content","订单号:"+outTradeNo);
        String json=JSON.toJSONString(map);
        //通过WebSocket实现来单提醒,向客户端浏览器推送消息
        webSocketServer.sendToAllClient(json);


    }

OrderMapper

   /**
     * 根据订单号和用户id查询订单
     * @param orderNumber
     * @param userId
     */
    @Select("select * from orders where number = #{orderNumber} and user_id= #{userId}")
    Orders getByNumberAndUserId(String orderNumber, Long userId);

将订单提醒功能加入OrderServiceImpl的payMent方法中(虚线划分部分)

        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);
--------------------------------------------------------------------------------------
        Map map = new HashMap();
        map.put("type", 1);// 消息类型,1表示来单提醒
        //获取订单id
        Orders orders=orderMapper.getByNumberAndUserId(orderNumber, userId);
        map.put("orderId", orders.getId());
        map.put("content", "订单号:" + orderNumber);

        // 通过WebSocket实现来单提醒,向客户端浏览器推送消息
        webSocketServer.sendToAllClient(JSON.toJSONString(map));
        log.info("来单提醒:{}", JSON.toJSONString(map));


-----------------------------------------------------------------------------
        return vo;
    }

三.测试

在小程序下单后,管理端有消息播报提醒和语音播报提醒,如无语音提醒,请重新登录尝试

四.gitee提交记录

 2025-08-05
- 提交说明:feat: 来单提醒功能
-查看详情:[Gitee 提交记录] 

feat: 来单提醒功能 · 980de04 · jh11/sky-take-out - Gitee.com

客户催单

一.问题分析

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

  • 语音播报 在这里插入图片描述

  • 弹出提示框

产品原型

设计思路:

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

接口设计

二.代码实现

OrderController(user)

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

OrderService

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

OrderServiceImpl

   /**
     * 用户催单
     * @param id
     */
    public void reminder(Long id) {
        //根据id查询订单
        Orders ordersDB = orderMapper.getById(id);
        //检验订单是否存在
        if (ordersDB == null) {
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }
        Map map = new HashMap();
        map.put("type",2);
        map.put("orderId",id);
        map.put("content","订单号:"+ordersDB.getNumber());
        //调用websocket向客户端浏览器推送消息
        webSocketServer.sendToAllClient(JSON.toJSONString(map));
    }

OrderMapper内容先前已经定义,不做赘述

三.测试

在小程序点击催单按钮,会弹出催单成功的弹窗

管理端会收取到催单的消息提醒和语音提醒

四.gitee提交记录

 2025-08-05
- 提交说明:feat: 用户催单功能
-查看详情:[Gitee 提交记录] 

feat: 用户催单功能 · 8771fbb · jh11/sky-take-out - Gitee.com


资料来源:B站 黑马程序员

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐