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();/*** 連接建立成功調用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam("sid") String sid) {System.out.println("客戶端:" + sid + "建立連接");sessionMap.put(sid, session);}/*** 收到客戶端消息后調用的方法** @param message 客戶端發送過來的消息*/@OnMessagepublic void onMessage(String message, @PathParam("sid") String sid) {System.out.println("收到來自客戶端:" + sid + "的信息:" + message);}/*** 連接關閉調用的方法** @param sid*/@OnClosepublic 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
:當連接關閉(客戶端斷開、服務端主動關閉或異常斷開)時觸發。