Spring - Vue+Spring Boot實現WebSocket定時訊息推送
阿新 • • 發佈:2021-06-11
Vue+Spring Boot實現WebSocket定時訊息推送
要實現本篇訊息推送功能,首先要準備好:一個vue專案,一個已經整合Quartz框架的Spring Boot專案。
後端配置
首先在pom中新增webSocket依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
然後建立一個 MySpringConfigurator。這個類的作用是端點配置類,用在接下來我們的Web Socket配置中。
從下面程式碼可以看出,如果不建立這個類,Web Socket註冊的Bean預設是由ServerEndpointConfig自己管理的,這個類的作用就是把Web Socket相關Bean也交給Spring去管理:
public class MySpringConfigurator extends ServerEndpointConfig.Configurator implements ApplicationContextAware {private static volatile BeanFactory context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { MySpringConfigurator.context = applicationContext; } @Override public <T> T getEndpointInstance(Class<T> clazz) throwsInstantiationException { return context.getBean(clazz); } }
建立真正的Web Socket配置類:
@Configuration public class WebSocketConfig { /** * 注入ServerEndpointExporter, * 這個bean會自動註冊使用了@ServerEndpoint註解宣告的Websocket endpoint * * @return ServerEndpointExporter */ @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } /** * 註冊自定義的配置類 * * @return MySpringConfigurator */ @Bean public MySpringConfigurator mySpringConfigurator() { return new MySpringConfigurator(); } }
然後是Web Socket的真正實現類。因為我沒找到ws傳輸header的解決方案,所以只能在連線的時候,用url param去鑑權,如果鑑權失敗就關閉連線:
@Slf4j @Component //此註解相當於設定訪問URL @ServerEndpoint(value = "/websocket/alarm/{userId}/{token}", configurator = MySpringConfigurator.class) public class AlarmWebSocket { /** * 鑑權業務邏輯類 */ @Autowired private ShiroService shiroService; // 連線會話,通過它來和客戶端互動 private Session session; // 儲存websocket連線 public static final CopyOnWriteArraySet<AlarmWebSocket> ALARM_WEB_SOCKETS = new CopyOnWriteArraySet<>(); // 儲存使用者和session的對應關係 private static final Map<String, Session> sessionPool = new HashMap<>(); /** * 連線成功回撥 * 因為web socket沒有請求頭,所以需要在連線成功的時候做一次鑑權 * 如果鑑權失敗,斷開連線 * * @param session session * @param userId 使用者id */ @OnOpen public void onOpen(Session session, @PathParam(value = "userId") String userId, @PathParam(value = "token") String token) throws IOException { // 根據accessToken,查詢使用者資訊 SysUserTokenEntity tokenEntity = shiroService.queryByToken(token); // token失效 if (tokenEntity == null || tokenEntity.getExpireTime().getTime() < System.currentTimeMillis() || !userId.equalsIgnoreCase(tokenEntity.getUserId())) { // 自定義websocket關閉原因 CloseReason closeReason = new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "鑑權失敗!"); session.close(closeReason); throw new IncorrectCredentialsException("token失效,請重新登入"); } this.session = session; ALARM_WEB_SOCKETS.add(this); sessionPool.put(userId, session); log.info("【websocket訊息】有新的連線,總數為:{}", ALARM_WEB_SOCKETS.size()); } /** * 連線關閉回撥 */ @OnClose public void onClose() { ALARM_WEB_SOCKETS.remove(this); log.info("【websocket訊息】連線斷開,總數為:{}", ALARM_WEB_SOCKETS.size()); } /** * 收到資訊的回撥 * * @param message 收到的資訊 */ @OnMessage public void onMessage(String message) { log.info("【websocket訊息】收到客戶端訊息:{}", message); } /** * 廣播訊息 * * @param message 訊息內容 */ public void sendAllMessage(String message) { for (AlarmWebSocket alarmWebSocket : ALARM_WEB_SOCKETS) { log.info("【websocket訊息】廣播訊息:{}", message); try { Session session1 = alarmWebSocket.session; session1.getAsyncRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } } /** * 傳送一對一訊息 * * @param userId 對端userId * @param message 訊息內容 */ public void sendTextMessage(String userId, String message) { Session session = sessionPool.get(userId); if (session != null) { try { session.getBasicRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } } }
最後就是定時推送任務的編寫了,因為集成了 Quartz 框架,所以後臺只需要寫一個定時任務Bean就好了。沒有整合的朋友們可以用@Scheduled 註解定義定時任務來代替:
@Slf4j @Component("alarmTask") public class AlarmTask implements ITask { @Autowired private AlarmWebSocket alarmWebSocket; @Override public void run(String params) { // 如果有連線則查詢報警訊息並推送 if (AlarmWebSocket.ALARM_WEB_SOCKETS.size() > 0) { alarmWebSocket.sendAllMessage("需要推送的資料"); } log.info("alarmTask定時任務正在執行"); } }
注:我在開發的過程中遇到了一個問題,AlarmWebSocket裡的session.getBasicRemote().sendText(message); 這句程式碼,如果我使用 sendText 方法就可以傳送訊息,用sendObject 就不能。期待以後有時間了研究一下。
前端配置
前端配置倒也簡單了,都是制式的東西,程式碼如下:
// 初始化websocket initWebSocket: function () { // 建立websocket連線,傳入鑑權引數 let userId = Vue.cookie.get('userId'); let token = Vue.cookie.get('token');
// 這裡的url要換成你自己的url this.websock = new WebSocket(window.SITE_CONFIG.wsUrl + "/websocket/alarm/" + userId + "/" + token); // 配置回撥方法 this.websock.onopen = this.websocketOnOpen; this.websock.onerror = this.websocketOnError; this.websock.onmessage = this.websocketOnMessage; this.websock.onclose = this.websocketClose; }, // 連線成功回撥 websocketOnOpen: function () { console.log("WebSocket連線成功"); }, // 錯誤回撥 websocketOnError: function (e) { console.log("WebSocket連線發生錯誤"); console.log(e); }, // 收到訊息回撥 websocketOnMessage: function (e) { let obj = JSON.parse(e.data); ...業務邏輯 }, // 連線關閉回撥 websocketClose: function (e) { console.log("WebSocket連線成功"); }
然後在頁面生命週期函式裡面呼叫方法:
created() { // 連線websocket this.initWebSocket(); }, destroyed() { this.websocketClose(); },
到這裡一個定時訊息推送功能就全部完成了。