概述
不同上文Spring Boot系列十七 Spring Boot 整合 websocket,使用RabbitMQ做為訊息代理,本文我們介紹通過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上的程式碼一直相同