微信公衆號:一個優秀的廢人。若有問題,請後臺留言,反正我也不會聽。
昨天那篇介紹了 WebSocket 實現廣播,也即服務器端有消息時,將消息發送給全部鏈接了當前 endpoint 的瀏覽器。但這沒法解決消息由誰發送,又由誰接收的問題。因此,今天寫一篇實現一對一的聊天室。javascript
今天這一篇創建在昨天那一篇的基礎之上,爲便於更好理解今天這一篇,推薦先閱讀:「SpringBoot 整合WebSocket 實現廣播消息 」html
因聊天室涉及到用戶相關,因此在上一篇基礎上引入 Spring Security 2.1.3 RELEASE 依賴前端
<!-- Spring Security 依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
雖然說涉及到 Spring Security ,但鑑於篇幅有限,這裏只對這個項目相關的部分進行介紹,具體的 Spring Security 教程,後面會出。java
這裏的 Spring Security 配置很簡單,具體就是設置登陸路徑、設置安全資源以及在內存中建立用戶和密碼,密碼須要注意加密,這裏使用 BCrypt 加密算法在用戶登陸時對密碼進行加密。 代碼註釋很詳細,很少說。jquery
package com.nasus.websocket.config; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @Configuration // 開啓Spring Security的功能 @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // 設置 SpringSecurity 對 / 和 "/login" 路徑不攔截 .mvcMatchers("/","/login").permitAll() .anyRequest().authenticated() .and() .formLogin() // 設置 Spring Security 的登陸頁面訪問路徑爲/login .loginPage("/login") // 登陸成功後轉向 /chat 路徑 .defaultSuccessUrl("/chat") .permitAll() .and() .logout() .permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() // 在內存中分配兩個用戶 nasus 和 chenzy ,用戶名和密碼一致 // BCryptPasswordEncoder() 是 Spring security 5.0 中新增的加密方式 // 登錄時用 BCrypt 加密方式對用戶密碼進行處理。 .passwordEncoder(new BCryptPasswordEncoder()) .withUser("nasus") // 保證用戶登陸時使用 bcrypt 對密碼進行處理再與內存中的密碼比對 .password(new BCryptPasswordEncoder().encode("nasus")).roles("USER") .and() // 登錄時用 BCrypt 加密方式對用戶密碼進行處理。 .passwordEncoder(new BCryptPasswordEncoder()) .withUser("chenzy") // 保證用戶登陸時使用 bcrypt 對密碼進行處理再與內存中的密碼比對 .password(new BCryptPasswordEncoder().encode("chenzy")).roles("USER"); } @Override public void configure(WebSecurity web) throws Exception { // /resource/static 目錄下的靜態資源,Spring Security 不攔截 web.ignoring().antMatchers("/resource/static**"); } }
在上一篇的基礎上另外註冊一個名爲 "/endpointChat" 的節點,以供用戶訂閱,只有訂閱了該節點的用戶才能接收到消息;而後,再增長一個名爲 "/queue" 消息代理。git
@Configuration // @EnableWebSocketMessageBroker 註解用於開啓使用 STOMP 協議來傳輸基於代理(MessageBroker)的消息,這時候控制器(controller) // 開始支持@MessageMapping,就像是使用 @requestMapping 同樣。 @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { //註冊一個名爲 /endpointNasus 的 Stomp 節點(endpoint),並指定使用 SockJS 協議。 registry.addEndpoint("/endpointNasus").withSockJS(); //註冊一個名爲 /endpointChat 的 Stomp 節點(endpoint),並指定使用 SockJS 協議。 registry.addEndpoint("/endpointChat").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { // 廣播式配置名爲 /nasus 消息代理 , 這個消息代理必須和 controller 中的 @SendTo 配置的地址前綴同樣或者全匹配 // 點對點增長一個 /queue 消息代理 registry.enableSimpleBroker("/queue","/nasus/getResponse"); } }
指定發送消息的格式以及模板。詳情見,代碼註釋。github
@Autowired //使用 SimpMessagingTemplate 向瀏覽器發送信息 private SimpMessagingTemplate messagingTemplate; @MessageMapping("/chat") public void handleChat(Principal principal,String msg){ // 在 SpringMVC 中,能夠直接在參數中得到 principal,principal 中包含當前用戶信息 if (principal.getName().equals("nasus")){ // 硬編碼,若是發送人是 nasus 則接收人是 chenzy 反之也成立。 // 經過 messageingTemplate.convertAndSendToUser 方法向用戶發送信息,參數一是接收消息用戶,參數二是瀏覽器訂閱地址,參數三是消息自己 messagingTemplate.convertAndSendToUser("chenzy", "/queue/notifications",principal.getName()+"-send:" + msg); } else { messagingTemplate.convertAndSendToUser("nasus", "/queue/notifications",principal.getName()+"-send:" + msg); } }
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <meta charset="UTF-8" /> <head> <title>登錄頁面</title> </head> <body> <div th:if="${param.error}"> 無效的帳號和密碼 </div> <div th:if="${param.logout}"> 你已註銷 </div> <form th:action="@{/login}" method="post"> <div><label> 帳號 : <input type="text" name="username"/> </label></div> <div><label> 密碼: <input type="password" name="password"/> </label></div> <div><input type="submit" value="登錄"/></div> </form> </body> </html>
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <meta charset="UTF-8" /> <head> <title>Home</title> <script th:src="@{sockjs.min.js}"></script> <script th:src="@{stomp.min.js}"></script> <script th:src="@{jquery.js}"></script> </head> <body> <p> 聊天室 </p> <form id="nasusForm"> <textarea rows="4" cols="60" name="text"></textarea> <input type="submit"/> </form> <script th:inline="javascript"> $('#nasusForm').submit(function(e){ e.preventDefault(); var text = $('#nasusForm').find('textarea[name="text"]').val(); sendSpittle(text); }); // 鏈接 SockJs 的 endpoint 名稱爲 "/endpointChat" var sock = new SockJS("/endpointChat"); var stomp = Stomp.over(sock); stomp.connect('guest', 'guest', function(frame) { // 訂閱 /user/queue/notifications 發送的消息,這裏與在控制器的 // messagingTemplate.convertAndSendToUser 中訂閱的地址保持一致 // 這裏多了 /user 前綴,是必須的,使用了 /user 纔會把消息發送到指定用戶 stomp.subscribe("/user/queue/notifications", handleNotification); }); function handleNotification(message) { $('#output').append("<b>Received: " + message.body + "</b><br/>") } function sendSpittle(text) { stomp.send("/chat", {}, text); } $('#stop').click(function() {sock.close()}); </script> <div id="output"></div> </body> </html>
@Controller public class ViewController { @GetMapping("/nasus") public String getView(){ return "nasus"; } @GetMapping("/login") public String getLoginView(){ return "login"; } @GetMapping("/chat") public String getChatView(){ return "chat"; } }
預期結果應該是:兩個用戶登陸系統,能夠互相發送消息。可是同一個瀏覽器的用戶會話的 session 是共享的,這裏須要在 Chrome 瀏覽器再添加一個用戶。web
具體操做在 Chrome 的 設置-->管理用戶-->添加用戶:算法
兩個用戶分別訪問 http://localhost:8080/login 登陸系統,跳轉至聊天界面:spring
相互發送消息:
https://github.com/turoDog/De...
若是以爲對你有幫助,請給個 Star 再走唄,很是感謝。
若是本文對你哪怕有一丁點幫助,請幫忙點好看。你的好看是我堅持寫做的動力。
另外,關注以後在發送 1024 可領取免費學習資料。
資料詳情請看這篇舊文:Python、C++、Java、Linux、Go、前端、算法資料分享