订单状态定时处理
分析

在支付订单界面,如果超时不支付就会取消订单,取消是通过程序而不是人工。
还有派送订单,在派送成功以及用户收到后我们就会点击完成,而如果没点的话我们也需要通过程序来处理。
用户下单后可能存在的情况:

处理逻辑:

代码
逻辑:
先搭好框架:
@Scheduled(cron ="0 * * * * ? ")
public void processTimeoutOrder(){
log.info("定时处理超时订单:{}", LocalDateTime.now());
引入ordersmapper以此来进行操作:
根据逻辑,如何判断每分钟触发一次的前提是超时的订单,一共有两点要求:
1.状态处于待付款
2.处于这个状态超过15分钟
sql语句大概这样写:
selecet*from orders where status=?(待付款状态) and order_time<?(小于当前时间-15min)
放到ordesmapper中:查询出来的很有可能是一个集合:
/*
判断超时订单
*/
@Select("select * from orders where status=#{status} and order_time<#{orderTime}")
List<Orders> getByStatusAndOrderTimeLT(Integer status, LocalDateTime orderTime);
}
再回到代码,如何实现超过15分钟这个逻辑,放在代码里大概是这样:现在的时间减去15分钟得到的时间,比得到的时间小的就是超时,使用代码:
LocalDateTime time = LocalDateTime.now().plusMinutes(-15);
List<Orders> orders= orderMapper.getByStatusAndOrderTimeLT(Orders.PENDING_PAYMENT, time);
接下来就要判断是否查询到数据:
if (orders!=null && orders.size()>0){
for (Orders orders1:orders){
orders1.setStatus(Orders.CANCELLED);
orders1.setCancelReason("订单超时");
orders1.setCancelTime(LocalDateTime.now());
orderMapper.update(orders1);
}
完整代码:
@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);
//查询超时的订单:
// selecet*from orders where status=?(待付款状态) and order_time<?(小于当前时间-15min)
List<Orders> orders= orderMapper.getByStatusAndOrderTimeLT(Orders.PENDING_PAYMENT, time);
if (orders!=null && orders.size()>0){
for (Orders orders1:orders){
orders1.setStatus(Orders.CANCELLED);
orders1.setCancelReason("订单超时");
orders1.setCancelTime(LocalDateTime.now());
orderMapper.update(orders1);
}
}
}
}
以上是处理超时订单,以下是处理派送中订单:
/*
处理一直派送中订单
*/
@Scheduled(cron = "0 0 1 * * ?")//每天凌晨1点触发
public void processDeliveryOrder(){
log.info("定时处理处于派送中的订单{}",LocalDateTime.now());
LocalDateTime localDateTime = LocalDateTime.now().plusMinutes(-60);
List<Orders> orders = orderMapper.getByStatusAndOrderTimeLT(Orders.DELIVERY_IN_PROGRESS, localDateTime);
if (orders!=null && orders.size()>0){
for (Orders orders1:orders){
orders1.setStatus(Orders.COMPLETED);
orderMapper.update(orders1);
}
}
Spring Task
介绍

是一个定时任务框架。作用就是定时自动执行某段java代码。
应用场景如:

cron表达式
本质是一个字符串,用于定义任务触发的时间。
构成规则:

其中这个周代表的是星期几。
一般来说,日和周是不能同时出现的,因为并不能确定这个日就是正确的周
有时候会用特殊字符,比如2月的最后一天究竟是28还是29,表达式通过在线cron表达式生成器网址生成即可:在线Cron表达式生成器
案例
使用:

添加启动:

入门:

测试
只需要运行项目,查看控制台输出是否有输出。
来单提醒

用户支付完订单后商家会跳出语言提醒。
分析

设计:

type是用来标注订单的类型,不然不知道究竟是来单提醒还是客户催单。
orderid是订单id,因为无论是什么提醒都是订单。
content是指具体的提醒内容,指的是弹出来的提醒小框。
代码
通过websocket实现长连接是通过webSocketSever实现的,至于如何和前端绑定,是由前端代码写好了代码。

请求的地址没有写8080,为什么也能请求到呢?
这是因为我们是用了nignx反向代理,是进行了一次转发。但是这样做的前提是在nignx进行了路径配置。

如图所示
在微信的支付订单代码(controller/notify)这个方法中调用的这个方法。
所以在这个方法通过ws给客户端推送消息。

WebSocket

即浏览器可以向服务器传输数据,服务器也可以主动向浏览器传输数据。
区别:

常见的应用场景:
视频弹幕
网页聊天
实况更新
入门案例
实现步骤:
使用websocket.html页面作为socket客户端。
导入maven坐标:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
导入服务端组件WebSocketServer,用于和客户端通信。
导入定时任务类定时推送数据。

客户端服务端建立连接,本质上就是一个会话,这个map是用来存储会话对象的:

握手成功后服务端会自动掉用下面这个方法。路径参数加sid对应的是上文中的{sid}

配置:创建一个websocket包:
/**
* 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();
}
}
}
}
在配置类加入一个类:
/**
* WebSocket配置类,用于注册WebSocket的Bean
*/
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
方便测试效果,加入一个定时测试类:
@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()));
}
}
启动程序之后查看效果:
先打开html文件,显示建立连接:

每五秒出现一个消息。
此外客户端也可以给服务端发消息:

客户催单
分析


是由客户催发的,用户点击催单按钮之后它会发起一次请求来请求服务端,所以需要设计一个新接口。

代码
controller:
/*
用户催单提醒
*/
@GetMapping("/reminder/{id}")
public Result reminder(@PathVariable("id") Long id){
orderService.reminder(id);
return Result.success();
}
serviceimpl:
/*
用户催单提醒
*/
@Override
public void reminder(Long id) {
//确认有没有订单
// 根据id查询订单
Orders ordersDB = orderMapper.getById(id);
// 校验订单是否存在,并且状态为4
if (ordersDB == null) {
throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
}
//由于方法要的是json,先转换
//通过ws向客户端浏览器推送消息 type orderid content
Map map = new HashMap<>();
//1为来单提醒,2为客户催单
map.put("type",2);
map.put("orderId",ordersDB.getId());
map.put("content","订单号:"+ordersDB.getNumber());
//map转json
String json = JSON.toJSONString(map);
webSocketServer.sendToAllClient(json);
//存在就推消息
webSocketServer.sendToAllClient(json);
}