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连接
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
):端点方法要处理生命周期事件,例如onOpen
、onMessage
、onClose
等。 - 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;
}
收到推送
bug修改
起因是在写代码的时候发现一直报服务器错误
我以为是我代码的问题,死活改不对。。。。
后面一看浏览器提示404,我就知道应该是前端发送的请求地址有问题,
找到这个文件,在里面搜索ws://localhost
这里我的后端端口是8080,我就填的8080,具体看个人的后端端口
修改好之后重启nginx和java后端,刷新之后显示连接上了
总结
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 错误,可能的原因有哪些?
- 服务端路径配置错误:如
@ServerEndpoint
的路径与客户端请求的 URL 不匹配(例:服务端为/ws/{sid}
,客户端请求/websocket/123
)。 - 端口不匹配:客户端连接的端口(如
ws://localhost:8080
)与服务端实际端口不一致。 - 未正确部署 WebSocket 服务:如未添加
@Component
注解或未配置 WebSocket 容器。
WebSocket 的onOpen
、onMessage
、onClose
方法的触发时机是什么?
onOpen
:当客户端与服务端成功建立 WebSocket 连接时触发。onMessage
:当服务端接收到客户端发送的消息时触发。onClose
:当连接关闭(客户端断开、服务端主动关闭或异常断开)时触发。