二刷 苍穹外卖day10(含bug修改)

发布于:2025-07-02 ⋅ 阅读:(15) ⋅ 点赞:(0)

Spring Task

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

cron表达式

一个字符串,通过cron表达式可以定义任务触发的时间
**构成规则:**分为6或7个域,由空格分隔开,每个域代表一个含义

每个域的含义分别为:秒、分钟、小时、日、月、周、年(可选)
cron表达式在线生成器:https://cron.qqe2.com/
可以直接在这个网站上面,只要根据自己的要求去生成corn表达式即可。所以一般就不用自己去编写这个表达式。
通配符:

* 表示所有值;

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

- 表示一个指定的范围;

, 表示附加一个可能值;

/ 符号前表示开始时间,符号后表示每次递增的值;
**cron表达式案例:**

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

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

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

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

0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发

0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发

0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发

0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时

0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
	/**
     * 处理支付超时订单
     */
    @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);
            });
        }
    }

每分钟触发,每次触发会查询状态为1,即非付款且未付款时间大于15分钟的订单,拿到这些订单后将状态修改,更新到数据库中

	/**
     * 处理“派送中”状态的订单
     */
    @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);
            });
        }
    }

每天早上一点触发,将时间超过一个小时的订单进行修改

WebSocker

基于TCP的网络协议,实现了浏览器与服务器全双工通信,浏览器和服务器只需要一次握手,就可以创建持久性的连接,并进行双向数据传输。
与HTTP协议的对比:
HTTP是短连接,WebSocket是长连接
HTTP通信是单向,基于请求响应模式
WebSocket支持双向通信
HTTP和WebSocket底层都是TCP连接
![[Pasted image 20250701094005.png]]

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

WebSocket应用场景
1.视频弹幕
2.网页聊天
3.体育实况更新
4.股票基金报价实时更新

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

}

连接建立成功后存放会话对象:
sessionMap.put(sid,session);

连接关闭后调用:
sessionMap.remove(sid);

服务端给客户端发送信息:
session.getBasicRemote().sendText(messge);

“@ServerEndpoint” 表明该注解所标注的类是一个 WebSocket 服务器端点,它定义了 WebSocket 服务端与客户端进行通信的端点。“/ws/{sid}” 是这个 WebSocket 端点的路径,其中 “{sid}” 是一个占位符,代表会话标识符之类的动态参数,在实际使用中,这个参数值会被具体的内容替换,比如在客户端连接时传递一个具体的会话 ID,服务端就能根据这个路径和参数来处理不同的 WebSocket 连接请求。 例如,客户端可能通过 “ws://[localhost:8080/ws/12345](https://localhost:8080/ws/12345)” 这样的地址连接到这个 WebSocket 端点,其中 “12345” 就是替换 “{sid}” 的具体值。

和@RequestMapping的区别:

应用场景有别

  • WebSocket 服务端端点注解(@ServerEndpoint:主要用于创建基于 WebSocket 协议的通信通道。这种通信是全双工的,意味着客户端和服务端能够同时进行数据传输,比较适合需要实时通信的场景,像在线聊天、实时数据推送等。
  • Spring MVC 中处理 HTTP 请求的注解(@RequestMapping:用于构建 RESTful API,遵循的是 HTTP 请求 - 响应模式。客户端发送请求后,服务端进行处理并返回响应,之后连接就会关闭,主要适用于传统的 Web 应用场景。

通信模式不同

  • WebSocket 服务端端点注解(@ServerEndpoint:建立的是持久连接,在连接建立之后,客户端和服务端可以随时发送消息,无需重新建立连接。
  • Spring MVC 中处理 HTTP 请求的注解(@RequestMapping:采用的是无状态的请求 - 响应模式,每次请求都需要重新建立连接。

注解参数不一样

  • WebSocket 服务端端点注解(@ServerEndpoint:可以设置路径参数(如 /{sid})、子协议以及编码器 / 解码器等。
  • Spring MVC 中处理 HTTP 请求的注解(@RequestMapping:能够指定 HTTP 方法(GET、POST 等)、请求头、请求参数以及 consumes/produces 等内容。

方法签名有差异

  • WebSocket 服务端端点注解(@ServerEndpoint:端点方法要处理生命周期事件,例如 onOpenonMessageonClose 等。
  • Spring MVC 中处理 HTTP 请求的注解(@RequestMapping:方法的返回值会直接转换为 HTTP 响应,像 JSON、XML 等格式。

来单提醒

用户下单并支付成功后,需要第一时间同时外卖商家
通过WebSocket实现管理端页面和服务端保持长连接
客户支付后,调用WebSocket实现服务端向客户端推送消息
客户端浏览器解析服务器推送的消息,判断是来单提醒还是催单,进行对应的消息提示和语音播报
由于我并没有实现支付功能,所以原代码中的来单提醒在paySuccess中是无法调用的,我将其移动到了payment函数之中,并在Mapper中定义了一个新的查询函数

@Select("select * from orders where number=#{id} ")  
Orders getByOrderId(Long id);

service

  
@Override  
public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {  
    Long userId = BaseContext.getCurrentId();  
    userMapper.getById(userId);  
  
    JSONObject jsonObject = new JSONObject();  
    jsonObject.put("code", "ORDERPAID");  
    OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);  
    vo.setPackageStr(jsonObject.getString("package"));  
    Map map = new HashMap();  
    Long orderNumber= Long.valueOf(ordersPaymentDTO.getOrderNumber());  
    Orders byOrderId = orderMapper.getByOrderId(orderNumber);  
      //#########
    map.put("type", 1);//消息类型,1表示来单提醒  
    map.put("orderId", byOrderId.getId());  
    map.put("content", "订单号:" + orderNumber.toString());  
  
    //通过WebSocket实现来单提醒,向客户端浏览器推送消息  
    webSocketServer.sendToAllClient(JSON.toJSONString(map));  
    //#########
    return vo;  
  
}

收到推送
![[Pasted image 20250701143111.png]]

bug修改

起因是在写代码的时候发现一直报服务器错误
![[Pasted image 20250701141324.png]]

我以为是我代码的问题,死活改不对。。。。
后面一看浏览器提示404,我就知道应该是前端发送的请求地址有问题,
找到这个文件,在里面搜索ws://localhost
![[Pasted image 20250701142638.png]]![[Pasted image 20250701142721.png]]

这里我的后端端口是8080,我就填的8080,具体看个人的后端端口
修改好之后重启nginx和java后端,刷新之后显示连接上了
![[Pasted image 20250701142823.png]]

总结

1. Spring Task

Spring框架提供的任务调度工具,可以按约定的时间自动执行某个代码逻辑
cron表达式 分为6或7个域,由空格分隔开
每个域含义分别为秒、分、时、日、月、周
通配符

2.WebSocker

基于TCP的网络协议,实现了全双工通信,更消耗资源,对网络要求高,适合频繁的资源传输
使用场景:
弹幕、实时聊天、实况更新、股票更新

3.@ServerEndPoint与@RequestMapping的区别

应用场景不同:@ServerEndPoint主要创建基于WebSocke协议,@RequestMapping遵循HTTP请求,客户端发送,服务器响应后关闭连接
通信模式不同:webSocket服务端端点注解建立的是持久连接,而HTTP请求采用的是无状态的请求-响应模式,每次请求都需要重新建立
注解参数不同:Websocket设置参数路径(/{sid})- `{sid}`是路径参数占位符,用于标识不同的客户端会话(如用户 ID)。
			在服务端方法中,通过`@PathParam("sid")`注解获取该参数
方法签名不同:WebSocket端点方法要处理生命周期事件,例如 `onOpen`、`onMessage`、`onClose` 等。@RequestMapping的返回值会转换为HTTP响应

4.WebSocket 服务端如何实现向所有客户端群发消息?

回答:
通过维护一个会话集合(如Map<String, Session>),遍历所有会话并调用sendText()方法:

public void sendToAllClient(String message) {
    for (Session session : sessionMap.values()) {
        session.getBasicRemote().sendText(message);
    }
}

其中sessionMap存储客户端 ID 与会话的映射,确保群发时能访问所有活跃连接。

5.WebSocket 连接时出现 404 错误,可能的原因有哪些?

  1. 服务端路径配置错误:如@ServerEndpoint的路径与客户端请求的 URL 不匹配(例:服务端为/ws/{sid},客户端请求/websocket/123)。
  2. 端口不匹配:客户端连接的端口(如ws://localhost:8080)与服务端实际端口不一致。
  3. 未正确部署 WebSocket 服务:如未添加@Component注解或未配置 WebSocket 容器。

WebSocket 的onOpenonMessageonClose方法的触发时机是什么?

  • onOpen:当客户端与服务端成功建立 WebSocket 连接时触发。
  • onMessage:当服务端接收到客户端发送的消息时触发。
  • onClose:当连接关闭(客户端断开、服务端主动关闭或异常断开)时触发。

网站公告

今日签到

点亮在社区的每一天
去签到