Spring Websocket實現向指定的使用者傳送訊息
概述
本文我們介紹通過Spring websocket實現向特定的使用者傳送訊息。
本文的內容如下:
1. 首先實現簡單的登入功能,這裡向特定使用者傳送訊息的必要條件
2. 使用者登入系統後,才可以登入websocket,並重寫MyPrincipal
3. 實現向特定使用者傳送訊息的功能
4. 測試
首先實現簡單的登入功能,這是向特定使用者傳送訊息的必要條件
TestMQCtl:控制類
提供模擬登入,登入成功後轉到websocket頁面
/** * 模擬登入*/ @RequestMapping(value = "loginIn", method = RequestMethod.POST) public String login(HttpServletRequest request, @RequestParam(required=true) String name, String pwd){ HttpSession httpSession = request.getSession(); // 如果登入成功,則儲存到會話中 httpSession.setAttribute("loginName", name); return "websocket/sendtouser/ws-sendtouser-rabbitmq"; } /** * 轉到登入頁面 */ @RequestMapping(value = "login", method = RequestMethod.GET) public String loginPage(){ // 轉到登入頁面 return "websocket/sendtouser/login"; } /** * websocket頁面 * @return */ @RequestMapping(value="/broadcast-rabbitmq/index") public String broadcastIndex(){ return "websocket/sendtouser/ws-sendtouser-rabbitmq"; }
login.jsp
簡單的form表單,將請求提到loginIn,並轉到ws-sendtouser-rabbitmq.jsp頁面
<form action="loginIn" method="post"> 使用者名稱:<input type="text" name="name" /> <p> 密碼:<input type="password" name="password" /> <p> <input type="submit" value="submit" /> </form>
ws-sendtouser-rabbitmq.jsp
連線websocket並訂閱訊息,這個jsp之前的文章已經介紹過了這裡不詳細描述。頁面通過向/ws/icc/websocket啟動websocket,然後訂閱/user/topic/demo訊息
<script type="text/javascript"> var stompClient = null; function setConnected(connected) { document.getElementById('connect').disabled = connected; document.getElementById('disconnect').disabled = !connected; document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden'; $('#response').html(); } function connect() { // websocket的連線地址,此值等於WebSocketMessageBrokerConfigurer中registry.addEndpoint("/ws/icc/websocket").withSockJS()配置的地址 var socket = new SockJS('/ws/icc/websocket'); //1 stompClient = Stomp.over(socket); stompClient.connect({}, function(frame) { setConnected(true); console.log('Connected: ' + frame); // 客戶端訂閱訊息的目的地址:此值等於BroadcastCtl中@SendTo註解的裡配置的值。 stompClient.subscribe( '/user/topic/demo', function(respnose){ showResponse(JSON.parse(respnose.body)); } ); }); } function disconnect() { if (stompClient != null) { stompClient.disconnect(); } setConnected(false); console.log("Disconnected"); } function showResponse(message) { var response = $("#response"); response.html(message.name + "<br>" + response.html()); } </script>
使用者登入系統後,才可以登入websocket,並重寫MyPrincipal
AuthHandshakeInterceptor
AuthHandshakeInterceptor是HandshakeInterceptor 的子類。在websocket握手前判斷,判斷當前使用者是否已經登入。如果未登入,則不允許登入websocket
@Component public class AuthHandshakeInterceptor implements HandshakeInterceptor { private static final Logger log = LoggerFactory.getLogger(AuthHandshakeInterceptor.class); @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { HttpSession httpSession = getSession(request); String user = (String)httpSession.getAttribute("loginName"); if(StringUtils.isEmpty(user)){ log.error("未登入系統,禁止登入websocket!"); return false; } log.info("login = " + user); return true; } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { } // 參考 HttpSessionHandshakeInterceptor private HttpSession getSession(ServerHttpRequest request) { if (request instanceof ServletServerHttpRequest) { ServletServerHttpRequest serverRequest = (ServletServerHttpRequest) request; return serverRequest.getServletRequest().getSession(false); } return null; } }
MyPrincipalHandshakeHandler
MyPrincipalHandshakeHandler是DefaultHandshakeHandler 的子類,處理websocket請求,這裡我們只重寫determineUser方法,生成我們自己的Principal ,這裡我們使用loginName標記登入使用者,而不是預設值
@Component public class MyPrincipalHandshakeHandler extends DefaultHandshakeHandler { private static final Logger log = LoggerFactory.getLogger(MyPrincipalHandshakeHandler.class); @Override protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) { HttpSession httpSession = getSession(request); String user = (String)httpSession.getAttribute("loginName"); if(StringUtils.isEmpty(user)){ log.error("未登入系統,禁止登入websocket!"); return null; } log.info(" MyDefaultHandshakeHandler login = " + user); return new MyPrincipal(user); } private HttpSession getSession(ServerHttpRequest request) { if (request instanceof ServletServerHttpRequest) { ServletServerHttpRequest serverRequest = (ServletServerHttpRequest) request; return serverRequest.getServletRequest().getSession(false); } return null; } }
MyPrincipal
定義自己的Principal
public class MyPrincipal implements Principal { private String loginName; public MyPrincipal(String loginName){ this.loginName = loginName; } @Override public String getName() { return loginName; } }
配置websocket
在registerStompEndpoints中將我們MyPrincipalHandshakeHandler 和AuthHandshakeInterceptor 配置到服務中
configureMessageBroker方法配置rabbitmq資訊,這裡略
@Configuration // 此註解開使用STOMP協議來傳輸基於訊息代理的訊息,此時可以在@Controller類中使用@MessageMapping @EnableWebSocketMessageBroker public class WebSocketRabbitMQMessageBrokerConfigurer extends AbstractWebSocketMessageBrokerConfigurer { @Autowired private MyPrincipalHandshakeHandler myDefaultHandshakeHandler; @Autowired private AuthHandshakeInterceptor sessionAuthHandshakeInterceptor; @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws/icc/websocket") .addInterceptors(sessionAuthHandshakeInterceptor) .setHandshakeHandler(myDefaultHandshakeHandler) .withSockJS(); } …. }
實現向特定使用者傳送訊息的功能
TestMQCtl:
登入到模擬傳送頁面:send.jsp
我們使用SimpMessagingTemplate 物件的convertAndSendToUser向指定使用者的/topic/demo傳送訊息
@Autowired private SimpMessagingTemplate template; /** * 傳送頁面 */ @RequestMapping(value = "send") public String sendMq2UserPage(String msg, String userName){ return "websocket/sendtouser/send"; } /** * 向執行使用者傳送請求 */ @RequestMapping(value = "send2user") @ResponseBody public int sendMq2User(String msg, String name){ System.out.println("===========" + msg + "=======" + name); RequestMessage demoMQ = new RequestMessage(); demoMQ.setName(msg); template.convertAndSendToUser(name, "/topic/demo", JSON.toJSONString(demoMQ)); return 0; }
send.jsp
模擬傳送頁面
<form action="login" method="post"> 接收者使用者:<input type="text" id="name" name="name" value="<%=session.getAttribute("loginName") %>" /> <p> 訊息內容:<input type="text" id="msg" name="msg" /> <p> <input type="button" id="send" value="傳送" /> </form> <script src="/websocket/jquery.js"></script> <script type=text/javascript> $("#send").click(function(){ $.post("send2user", { name: $('#name').val(), msg: $('#msg').val() }, function(data, status){ alert("Data: " + data + "nStatus: " + status); }); }); </script>
測試
測試一:
登入 http://127.0.0.1:8080/ws/login,使用xiaoming登入,並提交
點選連線,如果連線變灰色,則登入websocket成功
登入模擬傳送頁面http://127.0.0.1:8080/ws/send,向xiaoming傳送test-msg
此時頁面收到資訊:
在模擬介面,如果我們向其它使用者傳送資訊,則此介面不會收到資訊
測試二:
開啟兩個不同的瀏覽器,分別使用xiaoming1,xiaoming2登入系統,
使用模擬介面向xiaoming1傳送訊息,則只有xiaoming1收到
使用模擬介面向xiaoming2傳送訊息,則只有xiaoming2收到
結論:
我們已經實現向特定的使用者傳送訊息的功能
程式碼
所有的詳細程式碼見github程式碼,請儘量使用tag v0.23,不要使用master,因為master一直在變,不能保證文章中程式碼和github上的程式碼一直相同
原文地址:https://blog.csdn.net/hry2015/article/details/81123549