毕设项目<<基于微信小程序的餐馆外卖系统的设计后端>>(开发记录(九)校验收货地址是否超出配送范围和订单状态定时处理)
视频传送带
校验收货地址是否超出配送范围
1. 环境准备
注册账号:https://passport.baidu.com/v2/?reg&tt=1671699340600&overseas=&gid=CF954C2-A3D2-417F-9FE6-B0F249ED7E33&tpl=pp&u=https%3A%2F%2Flbsyun.baidu.com%2Findex.php%3Ftitle%3D%E9%A6%96%E9%A1%B5
登录百度地图开放平台:https://lbsyun.baidu.com/
进入控制台,创建应用,获取AK:
相关接口:
https://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-geocoding
https://lbsyun.baidu.com/index.php?title=webapi/directionlite-v1
2. 代码开发
2.1 application.yml
2.2 OrderServiceImpl
改造OrderServiceImpl,注入上面的配置项:
@Value("${sky.shop.address}")
private String shopAddress;
@Value("${sky.baidu.ak}")
private String ak;
在OrderServiceImpl中提供校验方法:
/**
* 检查客户的收货地址是否超出配送范围
* @param address
*/
private void checkOutOfRange(String address) {
Map map = new HashMap();
map.put("address",shopAddress);
map.put("output","json");
map.put("ak",ak);
//获取店铺的经纬度坐标
String shopCoordinate = HttpClientUtil.doGet("https://api.map.baidu.com/geocoding/v3", map);
JSONObject jsonObject = JSON.parseObject(shopCoordinate);
if(!jsonObject.getString("status").equals("0")){
throw new OrderBusinessException("店铺地址解析失败");
}
//数据解析
JSONObject location = jsonObject.getJSONObject("result").getJSONObject("location");
String lat = location.getString("lat");
String lng = location.getString("lng");
//店铺经纬度坐标
String shopLngLat = lat + "," + lng;
map.put("address",address);
//获取用户收货地址的经纬度坐标
String userCoordinate = HttpClientUtil.doGet("https://api.map.baidu.com/geocoding/v3", map);
jsonObject = JSON.parseObject(userCoordinate);
if(!jsonObject.getString("status").equals("0")){
throw new OrderBusinessException("收货地址解析失败");
}
//数据解析
location = jsonObject.getJSONObject("result").getJSONObject("location");
lat = location.getString("lat");
lng = location.getString("lng");
//用户收货地址经纬度坐标
String userLngLat = lat + "," + lng;
map.put("origin",shopLngLat);
map.put("destination",userLngLat);
map.put("steps_info","0");
//路线规划
String json = HttpClientUtil.doGet("https://api.map.baidu.com/directionlite/v1/driving", map);
jsonObject = JSON.parseObject(json);
if(!jsonObject.getString("status").equals("0")){
throw new OrderBusinessException("配送路线规划失败");
}
//数据解析
JSONObject result = jsonObject.getJSONObject("result");
JSONArray jsonArray = (JSONArray) result.get("routes");
Integer distance = (Integer) ((JSONObject) jsonArray.get(0)).get("distance");
if(distance > 5000){
//配送距离超过5000米
throw new OrderBusinessException("超出配送范围");
}
}
在OrderServiceImpl的submitOrder方法中调用上面的校验方法:
订单状态定时处理
1. SpringTask
技术
1.1介绍
Spring Task
是 Spring
框架提供的一种任务调度和异步处理的解决方案。可以按照约定的时间自动执行某个代码逻辑.它可以帮助开发者在 Spring
应用中轻松地实现定时任务、异步任务等功能,提高应用的效率和可维护性
1.2 主要特点
定位:定时任务框架
- 简单易用:
Spring Task
提供了简洁的注解和配置方式,使得任务调度和异步处理变得非常容易上手 - 内置支持:
Spring Task
内置于Spring
框架中,无需额外的依赖,开发者可以直接在Spring
应用中使用 - 灵活的任务调度:
Spring Task
支持基于cron
表达式的定时任务调度,能够满足各种复杂的调度需求 - 异步任务支持:除了定时任务,
Spring Task
也支持异步任务的处理,能够在后台线程中执行耗时操作,提高系统的响应速度 - 集成注解:
Spring Task
提供了@Scheduled
注解用于标识定时任务的方法,以及@Async
注解用于标识异步任务的方法,使用起来非常方便 - 监控和管理:
Spring Task
支持任务的监控和管理,可以通过JMX
或者Spring Boot Actuator
进行任务的查看和控制。
1.3 应用场景
- 信用卡每月还款提醒
- 银行贷款每月还款提醒
- 自动续费短信提醒
- 火车票售票系统处理未支付订单
- 入职纪念日为用户发送通知
1.4 使用案例
1.4.1 使用步骤
- 导入maven坐标,集成在spring-context中
- 启动类添加注解
@EnableScheduling
开启任务调度 - 自定义定时任务类
附在线Cron表达式生成器 (qqe2.com)
2. 订单状态定时处理
2.1 需求分析
用户下单后可能存在的情况:
- 下单后未支付,订单一直处于“待支付”状态
- 用户收货后管理端未点击完成按钮,订单一直处于“派送中”状态
实现逻辑
- 通过定时任务每分钟检查一次是否存在支付超时的订单(下单后超过15分钟未支付的判定为支付超时订单),如果存在则修改订单状态为“已取消”
- 通过定时任务每天凌晨1点检查一次是否存在“派送中”的订单,如果存在则修改订单状态为已完成
2.2 代码开发
@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);
// select * from orders where status = ? and order_time < (当前时间 - 15分钟)
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("订单超时,自动取消");
orders.setCancelTime(LocalDateTime.now());
orderMapper.update(orders);
}
}
}
/**
* 处理一直处于派送中的订单
*/
@Scheduled(cron = "0 0 1 * * ?")
public void processDeliveryOrder(){
log.info("定时处理一直处于派送中的订单:{}",LocalDateTime.now());
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);
}
}
}
}
2.3功能测试
3.WebSocket
3.1 介绍
WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大不同是:
WebScoket
是基于TCP
的一种新的网络协议,它实现了浏览器与服务器全双工通信——浏览器只需要完成一次握手,两者就可以创建持久性的连接,并进行双向数据传输。
WebSocket
是一种双向通信协议,在建立连接后,后续数据都以帧序列的形式传输。在客户端断开 WebSocket 连接或 Server 端断掉连接前,不需要客户端和服务端重新发起连接请求。在海量并发及客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。WebSocket
服务器和Browser/Client Agent
都能主动的向对方发送或接收数据,就像Socket
一样;WebSocket
需要类似TCP
的客户端和服务器端通过握手连接,连接成功后才能相互通信。
3.1.1 应用场景
- 视频弹幕
- 网页聊天
- 体育实况更新
- 股票基金报价实时更新
3.1.2 实现步骤
- 直接使用websocket.html页面作为WebSocket的客户端
- 导入WebSocket的Maven坐标
- 导入WebSocket服务端组件WebSocketServer,用于和客户端通信
- 导入配置类WebSocketConfiguration,注册WebSocket的服务端组件
- 导入定时任务WebSocketTask,定时向客户端发送数据
3.1.3 测试
4. 来单提醒
4.1 需求分析和设计
用户下单并且支付成功后,需要第一时间通知外卖商家,通知的形式有两种:语音播报和弹窗提示
4.2 设计
- 通过web Socket实现管理端页面和服务端保持长连接状态
- 当客户支付后,调用WebSocket的相关API实现服务端向客户端推送消息
- 当客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报
- 约定服务端发送给客户端浏览器的数据格式为JSON,包括字段:type(标识消息类型),orderId(订单ID),content(消息内容)
4.3 代码开发
//通过websocket向客户端浏览器推送消息 type orderId contentMap map = new HashMap();
map.put("type",1); // 1表示来单提醒 2表示客户催单
map.put("orderId",ordersDB.getId());
map.put("content","订单号:" + outTradeNo);
String json = JSON.toJSONString(map);
webSocketServer.sendToAllClient(json);
4.4 功能测试
5.客户催单
5.1 需求分析和设计
用户在小程序中点击催单按钮后,需要第一时间通知外卖商家,通知的形式有两种:语音播报、弹窗提醒
设计
- 通过WebSocket实现管理端页面和服务器保持长连接状态
- 当用户点击催单按钮后,调用web Socket和相关API实现服务端向客户端推送消息
- 当客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报
- 约定服务端发送给客户端浏览器的数据格式为JSON,包括字段:type(标识消息类型),orderId(订单ID),content(消息内容)
5.2 代码开发
/**
* 客户催单
* @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); //1表示来单提醒 2表示客户催单
map.put("orderId",id);
map.put("content","订单号:" + ordersDB.getNumber());
//通过websocket向客户端浏览器推送消息
webSocketServer.sendToAllClient(JSON.toJSONString(map));
}
5.3 功能测试