苍穹外卖Day10代码解析以及深入思考

文章目录

  • 前言
  • 一、Spring Task
    • 1.定义
    • 2.cron表达式
    • 3.超时订单处理
  • 二、WebSocket
    • 1.定义
    • 2.与HTTP协议的对比
    • 3.优点
    • 4.来单提醒
      • 1.导入maven坐标
      • 2.配置config
      • 3.配置task
      • 4.配置server
      • 5.配置service
    • 5.用户催单
      • 1.配置controller
      • 2.配置serviceImpl
  • 总结


前言

最近在备考六级,今天补上。


一、Spring Task

1.定义

  • Spring Task是Spring框架提供的轻量级定时任务调度功能,可以方便的在Spring应用中实现定时任务。

2.cron表达式

  • cron表达式是一个定义任务触发时间的字符串。
  • 顺序是秒、分钟、小时、日、月、周、年
  • 2022年10月12日上午9点对应为: 0 0 9 12 10 ? 2022
  • 在线生成器

3.超时订单处理

  • 先导入springtask坐标,包含在spring-context里面,已导入了。
  • 在启动类上开启注解管理
  • 定义即可
@SpringBootApplication
@Slf4j
@EnableTransactionManagement // 开启注解方式的事务管理
@EnableScheduling   // 开启注解方式的定时管理
@EnableCaching  // 开启注解方式的redis缓存
public class SkyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SkyApplication.class, args);
        log.info("后台服务已启动...");
    }
}

task相关代码:

  • 为什么要减去40和60分钟就是为了寻找间隔时间 >= 40或60分钟的订单,意思就是寻找超时的订单,而这里的@Scheduled就是用来标记需要定时执行的方法,里面就是cron表达式,如果不会写cron表达式可以借助ai或者在线生成器,实际上idea可以智能显示设置的cron表达式的意思。
  • 这里的代码逻辑就是通过LocalDateTime和sql查询找出超时的订单,然后修改该订单的状态,最后加上定时操作就可以了,关于LocalDateTime的操作,如果刷过算法题的小伙伴就肯定用过,时间日期API,以前的SimpleDateFormat已经过时了,所以我们常用LocalDateTime了。
@Component
@Slf4j
public class OrderTask {
    @Autowired
    private OrderMapper orderMapper;

    private static final String AUTOCANCEL = "订单已取消";

    /**
     * 自动取消待支付的订单 40分钟后自动删除
     * 每分钟触发一次
     */
    @Scheduled(cron = "0 * * * * ?")
    public void processTimeoutOrder() {
        log.info("定时处理超时订单:{}", LocalDateTime.now());
        // 我这里就设置超过40分钟就自动取消
        LocalDateTime time = LocalDateTime.now().plusMinutes(-40);
        // 从数据库里获得待支付的订单 目的是寻找时间间隔 >= 40分钟的订单
        List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.PENDING_PAYMENT, time);
        if (ordersList != null && ordersList.size() > 0) {
            for(Orders orders : ordersList) {
                orders.setStatus(Orders.CANCELLED); // 修改为已取消
                orders.setCancelReason(AUTOCANCEL); // 修改取消原因 定义为常量 优雅的代码风格
                orders.setCancelTime(LocalDateTime.now());  // 修改取消时间
                orderMapper.update(orders);
            }
        }
    }

    /**
     * 自动设置派送中的订单为已完成
     * 在每天凌晨1点执行
     */
    @Scheduled(cron = "0 0 1 * * ?")
    public void processDeliveryOrder() {
        log.info("定时处理处于派送中的订单:{}", LocalDateTime.now());
        // 派送时间超过 >= 60分钟的订单
        LocalDateTime time = LocalDateTime.now().plusMinutes(-60);
        List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.DELIVERY_IN_PROGRESS, time);
        if (ordersList != null && ordersList.size() > 0) {
            for(Orders orders : ordersList) {
                orders.setStatus(Orders.COMPLETED); // 设置为已完成
                orderMapper.update(orders);
            }
        }
    }
}

二、WebSocket

1.定义

  • WebSocket 是一种在单个TCP连接上进行全双工通信协议,是传统HTTP协议的升级方案。
  • TCP连接:TCP(Transmission Control Protocol,传输控制协议)是互联网核心协议之一,位于OSI模型的传输层。
  • TCP核心特性:关于TCP,后续仔细研究。
  • 1.面向连接。三次握手后建立连接,四次挥手后释放连接。这种方式可以确保通信双方在开始传输数据之前已经准备好,减少了数据丢失的风险。
  • 2.可靠传输。TCP能够确保数据完整、准确地从发送方传输到接收方。通过确认应答机制(ACK)、超时重传机制、校验和保证数据传输的完整性。
  • 3.流量控制。TCP会根据接收方的处理能力,控制发送方的数据发送速度,防止接收方被数据“淹没”。通过滑动窗口机制来控制。接收方会告诉发送方自己的缓冲区大小,发送方根据这个大小来调整发送速度。
  • 4.拥塞控制。TCP会根据网络的拥塞情况,动态调整数据的发送速度,避免网络过载。通过慢启动、拥塞避免、快速重传和快速恢复实现。
  • 5.面向字节流。TCP把数据看作一个连续的字节流,而不是一个个独立的数据包。发送方和接收方可以根据需要对数据进行分割和重组,实现大量数据的传输。

2.与HTTP协议的对比

  • 不同:

  • HTTP是短连接,意思是客户端向服务端发送一次请求就是一次连接,服务器响应完毕后这个连接就消失了。

  • WebSocket是长连接

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

  • WebSoceket支持双向通信,意思是客户端可以向服务端及时发送消息,服务端也可以。

  • 相同:都是TCP连接。

3.优点

  • 节约带宽。 不停地轮询服务端数据这种方式,使用的是http协议,head信息很大,有效数据占比低, 而使用WebSocket方式,头信息很小有效数据占比高
  • 无浪费。 轮询方式有可能轮询10次,才碰到服务端数据更新,那么前9次都白轮询了,因为没有拿到变化的数据。 而WebSocket是由服务器主动回发,来的都是新数据
  • 实时性,考虑到服务器压力,使用轮询方式不可能很短的时间间隔,否则服务器压力太多,所以轮询时间间隔都比较长,好几秒,设置十几秒。 而WebSocket是由服务器主动推送过来,实时性是最高的
  • 轮询的定义
    轮询是一种客户端定期向服务器发送请求的方法,来检查是否有新的数据。就像你每隔一段时间就去问一次服务器:“有没有新消息呀?”服务器就会告诉你有没有新消息。一直询问服务器。
  • 常用场景:文件传输、网页浏览、在线聊天等。

4.来单提醒

1.导入maven坐标

xml:

  <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-websocketartifactId>
                <version>3.2.0version>
  dependency>

2.配置config

代码如下:

// websocket配置类
@Configuration
public class WebSocketConfiguration {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

3.配置task

通过websocket快速建立服务端与客户端的双向连接,需要把它放在springioc容器里面,方便进行依赖注入、生命周期管理、利用注解进行标记等操作。注意如果浏览器没有声音记得打开音频权限。
代码如下:

@Component
public class WebSocketTask {

    @Autowired
    private WebSocketServer webSocketServer;

    /**
     * 通过WebSocket每隔30秒向客户端发送消息
     */
    @Scheduled(cron = "0/30 * * * * ?")
    public void sendMessageToClient() {
        webSocketServer.sendToAllClient("这是来自服务端的消息:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
    }
}

4.配置server

这一个群发操作基本上就是固定的代码了,通过遍历session向客户端发送消息。
向我们的后台管理系统进行弹窗,上传json。
代码如下:

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

}

5.配置service

由于跳过了微信支付,所以我们直接写在payment这个方法里。通过用户支付成功后,服务端生成一个json,通过websocket协议向后台管理客户端发送json信息,从而实现及时通讯,及时提醒的操作。
代码如下:

/**
     * 订单支付
     * @param ordersPaymentDTO
     * @return
     */
    @Transactional
    public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
        // TODO:注释的功能就是没跳过微信支付验证
        // 1.当前登录用户id
        Long userId = BaseContext.getCurrentId();
        User user = userMapper.getById(userId);
        // 2.调用微信支付接口,生成预支付交易单
        /**       JSONObject jsonObject = weChatPayUtil.pay(
                ordersPaymentDTO.getOrderNumber(), //商户订单号
                new BigDecimal(0.01), //支付金额,单位 元
                "苍穹外卖订单", //商品描述
                user.getOpenid() //微信用户的openid
        );
        **/
        /** if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {
            throw new OrderBusinessException("该订单已支付");
        }
        **/
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", "ORDERPAID");
        OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
        vo.setPackageStr(jsonObject.getString("package"));
        // 为替代微信支付成功后的数据库订单状态更新 多定义一个方法进行修改
        Integer OrderPaidStatus = Orders.PAID;  // 已支付
        Integer OrderStatus = Orders.TO_BE_CONFIRMED; // 待接单
        // 发现没有将支付时间check_out属性赋值 所以在这里更新
        LocalDateTime check_out_time = LocalDateTime.now();
        // 获取订单号码
        String orderNumber = ordersPaymentDTO.getOrderNumber();
        // 操作
        orderMapper.updateStatus(OrderStatus,OrderPaidStatus,check_out_time,orderNumber);
        // 3.来单提醒
        //通过websocket向客户端浏览器推送消息 type orderId content
        Map map = new HashMap();
        map.put("type",1);
        map.put("orderId",this.orders.getId());
        map.put("content","订单号:"+this.orders.getNumber());
        String json = JSON.toJSONString(map);
        webSocketServer.sendToAllClient(json);
        System.out.println(json);
        return vo;
    }

5.用户催单

1.配置controller

代码如下:

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

2.配置serviceImpl

记得向接口声明该方法。
代码如下:

 /**
     * 用户催单
     * @param id
     */
    @Override
    public void reminder(Long id) {
        // 1.先查询该订单是否存在
        Orders ordersDB = orderMapper.getById(id);
        if (ordersDB == null) {
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);   // 状态错误
        }
        // 2.封装json传至服务端 再通过服务端传至后台管理客户端
        Map map = new HashMap();
        map.put("type", 2); // 1是提醒 2是催单
        map.put("orderId", id);
        map.put("content","订单号:" + ordersDB.getNumber());
        // 3.向后台管理客户端推送催单提醒
        webSocketServer.sendToAllClient(JSON.toJSONString(map));
    }

总结

SpringTask是一个定时任务的小框架,能够实现快速定时任务开发,websocket是一种基于tcp的双工通信协议,能够实现客户端与服务端的长连接以及及时通讯。

你可能感兴趣的:(后端,idea,java,spring,架构)