springboot2.0+websocket整合【群發訊息+單對單】(二)
阿新 • • 發佈:2019-01-02
繼續上次的專案。如果對下面的程式碼有部分看不明白的,請到上一篇看看流程,或者到文末貼出專案的git地址。
1. 先從配置開始,WebStompConfig
程式碼中的註釋基本能夠解釋清楚每行的意思了,這裡就不再細說
完整程式碼
package com.example.websocketdemo1.stomp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework. messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework. web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
/**
* EnableWebSocketMessageBroker 註解表明: 這個配置類不僅配置了 WebSocket,還配置了基於代理的 STOMP 訊息;
* registerStompEndpoints() 方法:新增一個服務端點,來接收客戶端的連線。將 “/chat” 路徑註冊為 STOMP 端點。這個路徑與之前傳送和接收訊息的目的路徑有所不同, 這是一個端點,客戶端在訂閱或釋出訊息到目的地址前,要連線該端點,即使用者傳送請求 :url=’/127.0.0.1:8080/chat’ 與 STOMP server 進行連線,之後再轉發到訂閱url;
* configureMessageBroker() 方法:配置了一個 簡單的訊息代理,通俗一點講就是設定訊息連線請求的各種規範資訊。
*
* @author linyun
* @date 2018/9/13 下午5:15
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebStompConfig implements WebSocketMessageBrokerConfigurer {
@Autowired
private WebSocketHandleInterceptor interceptor;
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//新增一個/chat端點,客戶端就可以通過這個端點來進行連線;withSockJS作用是新增SockJS支援
registry.addEndpoint("/chat").setAllowedOrigins("*").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
//定義了兩個客戶端訂閱地址的字首資訊,也就是客戶端接收服務端傳送訊息的字首資訊
registry.enableSimpleBroker("/message", "/notice");
//定義了服務端接收地址的字首,也即客戶端給服務端發訊息的地址字首
registry.setApplicationDestinationPrefixes("/app");
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
//註冊了一個接受客戶端訊息通道攔截器
registration.interceptors(interceptor);
}
}
2. 使用者資訊註冊,WebSocketHandleInterceptor
上一篇裡面,使用者資訊我們是直接儲存到session中,然後再通過握手的時候,將使用者資訊存入WebSocketSession。
這次使用stomp的模式也存在一個單對單的傳送訊息,就需要知道對方是誰,所以也要註冊一下使用者資訊。
完整的程式碼
package com.example.websocketdemo1.stomp;
import com.sun.security.auth.UserPrincipal;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.security.Principal;
/**
* @author linyun
* @date 2018/9/13 下午5:57
*/
@Component
public class WebSocketHandleInterceptor implements ChannelInterceptor {
/**
* 繫結user到websocket conn上
* @param message
* @param channel
* @return
*/
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
String username = accessor.getFirstNativeHeader("username");
if (StringUtils.isEmpty(username)) {
return null;
}
// 繫結user
Principal principal = new UserPrincipal(username);
accessor.setUser(principal);
}
return message;
}
}
注意這裡的username資訊
String username = accessor.getFirstNativeHeader("username");
username是在頁面中傳遞來的,具體的傳遞方式在後面的頁面中,具體的引數名稱可以隨意自定義。
另外一種獲取使用者資訊的方式:
Object raw = message.getHeaders().get(SimpMessageHeaderAccessor.NATIVE_HEADERS);
if (raw instanceof Map) {
System.out.println(raw);
// 列印raw之後,可以檢視頭部的引數,包含了username。
}
3.處理訊息的類,GreetingController
用來接收和傳送訊息。
先來一個訊息的model,用來包裝訊息,使用lombok外掛,省去了getset了。
程式碼:
package com.example.websocketdemo1.stomp;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author linyun
* @date 2018/9/13 下午5:44
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Message {
private String to;
private Long date;
private String from;
private String content;
}
controller的完整程式碼
package com.example.websocketdemo1.stomp;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.handler.annotation.*;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.security.Principal;
import java.util.Map;
/**
* @author linyun
* @date 2018/9/13 下午5:42
*/
@Slf4j
@Controller
public class GreetingController {
@Autowired
private SimpMessagingTemplate simpMessagingTemplate;
/**
* 測試頁面
* @return
*/
@RequestMapping("/chat4")
public String chat4() {
return "chat4";
}
/**
* 測試頁面2
* @return
*/
@RequestMapping("/chat5")
public String chat5() {
return "chat5";
}
/**
* 測試訂閱
* @param message
* @param messageHeaders
* @param destination
* @param headers
* @param id
* @param body
*/
@MessageMapping("/hello/{id}")
public void hello(Message message,
MessageHeaders messageHeaders,
@Header("destination") String destination,
@Headers Map<String, Object> headers,
@DestinationVariable long id,
@Payload String body) {
log.info("message:{}", message);
log.info("messageHeaders:{}", messageHeaders);
log.info("destination:{}", destination);
log.info("headers:{}", headers);
log.info("id:{}", id);
log.info("body:{}", body);
}
/*** 群訊息 ***/
/**
* 主動返回訊息。
* @param message
*/
@MessageMapping("/hello")
public void hello(@Payload com.example.websocketdemo1.stomp.Message message) {
System.out.println(message);
com.example.websocketdemo1.stomp.Message returnMessage = new com.example.websocketdemo1.stomp.Message();
returnMessage.setContent("轉發," + message.getContent());
simpMessagingTemplate.convertAndSend("/message/public", returnMessage);
}
/**
* 使用註解的方式返回訊息
* @param message
* @return
*/
@MessageMapping("/hello1")
@SendTo("/message/public")
public com.example.websocketdemo1.stomp.Message hello1(@Payload com.example.websocketdemo1.stomp.Message message) {
System.out.println(message);
com.example.websocketdemo1.stomp.Message returnMessage = new com.example.websocketdemo1.stomp.Message();
returnMessage.setContent("轉發2," + message.getContent());
return returnMessage;
}
/*** 點對點 ***/
/**
* 點對點發送訊息。接收訊息的人是從訊息中獲取的。
* @param message
* @param principal
*/
@MessageMapping("/hello2")
public void hello2(@Payload com.example.websocketdemo1.stomp.Message message, Principal principal) {
System.out.println(message);
System.out.println(principal);
com.example.websocketdemo1.stomp.Message returnMessage = new com.example.websocketdemo1.stomp.Message();
returnMessage.setContent("轉發3," + message.getContent());
simpMessagingTemplate.convertAndSendToUser(message.getTo(), "/notice/msg", returnMessage);
}
}
稍微解釋一下程式碼中的幾個方法
第一個方法,/hello/{id},主要是用來測試在一次傳送訊息的請求中能夠獲取到那些引數,合理的利用這些引數於自己的業務中。
@MessageMapping("/hello/{id}")
public void hello(Message message,
MessageHeaders messageHeaders,
@Header("destination") String destination,
@Headers Map<String, Object> headers,
@DestinationVariable long id,
@Payload String body) {
log.info("message:{}", message);
log.info("messageHeaders:{}", messageHeaders);
log.info("destination:{}", destination);
log.info("headers:{}", headers);
log.info("id:{}", id);
log.info("body:{}", body);
}
4.結合頁面測試
新建2個頁面,頁面中設定使用者的資訊
http://127.0.0.1:8080/chat4
username='tom';
http://127.0.0.1:8080/chat5
username='jerry';
主要是引入 stomp.js和socketjs這2個js。
頁面完整程式碼:
<!DOCTYPE html>
<html lang="en">
<head>
<title>測試websocket</title>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-toast-plugin/1.3.2/jquery.toast.min.css">
</head>
<body>
<div class="container">
<button type="button" class="btn btn-primary" onclick="connect()">連結</button>
<button type="button" class="btn btn-primary" onclick="disconnect()">斷開</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.2/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-toast-plugin/1.3.2/jquery.toast.min.js"></script>
<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
<script language=javascript>
var username = 'tom';
var sendMessage = null;
var disConnect = null;
function connect() {
var socket = new SockJS("http://127.0.0.1:8080/chat");
var client = Stomp.over(socket);
client.connect({
username: username
}, function (succ) {
console.log('client connect success:', succ);
client.subscribe("/message/public", function (res) {
console.log('收到訊息---/message/public:',res);
});
client.subscribe("/user/notice/msg", function (res) {
console.log('個人訊息:',res)
});
}, function (error) {
console.log('client connect error:', error);
});
sendMessage = function (destination, headers, body) {
client.send(destination, headers, body)
};
disConnect = function () {
client.disconnect();
console.log('client connect break')
}
}
function disconnect() {
disConnect();
}
//傳送聊天資訊
function send(roomId, ct) {
var messageModel = {};
messageModel.type = 1;
messageModel.content = ct;
messageModel.from = username;
sendMessage("/app/hello/" + roomId, {}, JSON.stringify(messageModel));
}
/**
* 測試傳送一個訊息,如果訂閱了/sub/public的使用者都會收到訊息。
*/
function send1() {
var messageModel = {};
messageModel.type = 1;
messageModel.content = '你好,' + new Date().getTime();
messageModel.from = username;
sendMessage("/app/hello", {}, JSON.stringify(messageModel));
}
function send2() {
var messageModel = {};
messageModel.type = 1;
messageModel.content = 'hello1,' + new Date().getTime();
messageModel.from = username;
sendMessage("/app/hello1", {}, JSON.stringify(messageModel));
}
/** 傳送訊息給個人,接收者 to **/
function send3() {
var messageModel = {};
messageModel.to = 'jerry';
messageModel.type = 1;
messageModel.content = 'hello1,' + new Date().getTime();
messageModel.from = username;
sendMessage("/app/hello2", {}, JSON.stringify(messageModel));
}
}
</script>
</body>