苍穹外卖-day10
Spring Task
Spring Task 是Spring框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑。
cron表达式
cron表达式其实就是一个字符串,通过cron表达式可以定义任务触发的时间
**构成规则:**分为6或7个域,由空格分隔开,每个域代表一个含义
每个域的含义分别为:秒、分钟、小时、日、月、周、年(可选)
举例:
2022年10月12日上午9点整 对应的cron表达式为:0 0 9 12 10 ? 2022
说明:一般日和周的值不同时设置,其中一个设置,另一个用?表示。
**比如:**描述2月份的最后一天,最后一天具体是几号呢?可能是28号,也有可能是29号,所以就不能写具体数字。
为了描述这些信息,提供一些特殊的字符。这些具体的细节,我们就不用自己去手写,因为这个cron表达式,它其实有在线生成器。
cron表达式在线生成器:https://cron.qqe2.com/
可以直接在这个网站上面,只要根据自己的要求去生成corn表达式即可。所以一般就不用自己去编写这个表达式。
使用步骤
1). 导入maven坐标 spring-context(已存在)
2). 启动类添加注解 @EnableScheduling 开启任务调度
3). 自定义定时任务类
订单状态定时处理
-
下单后未支付,订单一直处于**“待支付”**状态
-
用户收货后管理端未点击完成按钮,订单一直处于**“派送中”**状态
-
通过定时任务每分钟检查一次是否存在支付超时订单(下单后超过15分钟仍未支付则判定为支付超时订单),如果存在则修改订单状态为“已取消”
-
通过定时任务每天凌晨1点检查一次是否存在“派送中”的订单,如果存在则修改订单状态为“已完成”
1. 自定义定时任务类OrderTask
处理支付超时订单
1.在方法上面加上注解@Scheduled来定义cron表达式设置为每分钟执行一次
2.调用localDateTime的方法算出当前时间减去十五分钟
3.调用mapper方法查询超时订单,返回订单列表
4.if判断集合是否为空,遍历订单集合,完善每个订单的状态 取消原因和的时间 然后更新数据库
处理“派送中”状态的订单
1.在方法上面加上注解@Scheduled来定义cron表达式设置为每天凌晨一点执行一次
2…调用localDateTime的方法算出当前时间减去一个小时来设置超时一个小时阈值
3.查询带派送且超时一小时的订单集合
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);});}}
/*** 处理“派送中”状态的订单*/@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);});}}
WebSocket
WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接, 并进行双向数据传输。
HTTP协议和WebSocket协议对比:
- HTTP是短连接
- WebSocket是长连接
- HTTP通信是单向的,基于请求响应模式
- WebSocket支持双向通信
- HTTP和WebSocket底层都是TCP连接
**思考:**既然WebSocket支持双向通信,功能看似比HTTP强大,那么我们是不是可以基于WebSocket开发所有的业务功能?
WebSocket缺点:
服务器长期维护长连接需要一定的成本
各个浏览器支持程度不一
WebSocket 是长连接,受网络限制比较大,需要处理好重连
**结论:**WebSocket并不能完全取代HTTP,它只适合在特定的场景下使用
WebSocket应用场景:
1). 视频弹
2). 网页聊天
3). 体育实况更新
4). 股票基金报价实时更新
实现步骤:
导入WebSocket的maven坐标
导入WebSocket服务端组件WebSocketServer,用于和客户端通信
导入配置类WebSocketConfiguration,注册WebSocket的服务端组件
来单提醒
用户下单并且支付成功后,需要第一时间通知外卖商家。通知的形式有如下两种:
- 语音播报
- 弹出提示框
设计思路:
- 通过WebSocket实现管理端页面和服务端保持长连接状态
- 当客户支付后,调用WebSocket的相关API实现服务端向客户端推送消息
- 客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报
- 约定服务端发送给客户端浏览器的数据格式为JSON,字段包括:type,orderId,content
- type 为消息类型,1为来单提醒 2为客户催单
- orderId 为订单id
- content 为消息内容
在OrderServiceImpl中注入WebSocketServer对象,修改paySuccess方法
由于我们是跳过支付功能的,所以没有调用paysucess方法,因此我们就在payment方法执行完更新数据库后面执行逻辑
1.new一个map集合,往集合里面put需要发送给前端的格式数据
2.调用webSocketServer的sendToAllClient方法,把map集合转换为json数据推送出去.
@Overridepublic OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {// 当前登录用户idLong userId = BaseContext.getCurrentId();User user = userMapper.getById(userId);// //调用微信支付接口,生成预支付交易单
// 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 OrderStatus = Orders.TO_BE_CONFIRMED;Integer OrderPaidStatus = Orders.PAID;LocalDateTime check_out_time = LocalDateTime.now();orderMapper.updateStatus(OrderStatus,OrderPaidStatus,check_out_time,this.orders.getId());Map map = new HashMap();map.put("type",1);//消息类型,1表示菜单提醒map.put("orderId", orders.getId());map.put("content", "订单号:" + this.orders.getNumber());webSocketServer.sendToAllClient(JSONObject.toJSONString(map));return vo;}
客户催单
用户在小程序中点击催单按钮后,需要第一时间通知外卖商家。通知的形式有如下两种:
语音播报
弹出提示框
设计思路:
- 通过WebSocket实现管理端页面和服务端保持长连接状态
- 当用户点击催单按钮后,调用WebSocket的相关API实现服务端向客户端推送消息
- 客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报
约定服务端发送给客户端浏览器的数据格式为JSON,字段包括:type,orderId,content- type 为消息类型,1为来单提醒 2为客户催单
- orderId 为订单id
- content 为消息内容
当用户点击催单按钮时,向服务端发送请求。
Controller层
1.根据接口文档定义方法,参数为路径参数id
2.调用service层的方法
3.return回去result
/*** 用户催单** @param id* @return*/@GetMapping("/reminder/{id}")@ApiOperation("用户催单")public Result reminder(@PathVariable("id") Long id) {orderService.reminder(id);return Result.success();}
Service层实现类
和之前来单提醒的逻辑一样
1.new一个map集合,往集合里面put需要发送给前端的格式数据
2.调用webSocketServer的sendToAllClient方法,把map集合转换为json数据推送出去.
/*** 用户催单** @param id*/public void reminder(Long id) {// 查询订单是否存在Orders orders = orderMapper.getById(id);if (orders == null) {throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);}//基于WebSocket实现催单Map map = new HashMap();map.put("type", 2);//2代表用户催单map.put("orderId", id);map.put("content", "订单号:" + orders.getNumber());webSocketServer.sendToAllClient(JSON.toJSONString(map));}