以按照以下步驟進(jìn)行來(lái)實(shí)現(xiàn),在Spring Boot中使用Spring WebFlux結(jié)合Bootstrap模板引擎實(shí)現(xiàn)單頁(yè)面應(yīng)用(SPA)。
需要在POM文件中添加Spring Reactive Web、 Thymeleaf、Spring Data Reactive MongoDB等依賴(lài)配置,如下所示。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId> <!-- 或者使用其他模板引擎 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId> <!-- 如果需要持久化 -->
</dependency>
如果有特殊的需求,可以在application.yml或application.properties中添加WebFlux相關(guān)的配置信息,如果沒(méi)有就可以不用配置了。
在src/main/resources/templates目錄下創(chuàng)建Thymeleaf模板文件(或者你選擇的其他模板文件)。例如,創(chuàng)建index.html文件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SPA with Spring WebFlux</title>
<!-- Bootstrap CSS -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<h1 class="mt-5">Hello, Spring WebFlux with Bootstrap!</h1>
<!-- Add your SPA content here -->
<div id="app"></div>
</div>
<!-- Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.1/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>
定義一個(gè)Controller來(lái)處理前端請(qǐng)求,如下所示。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import reactor.core.publisher.Mono;
@Controller
@RequestMapping("/")
public class MainController {
@GetMapping
public Mono<String> index() {
return Mono.just("index"); // 指向模板文件名
}
}
為了使你的應(yīng)用能夠處理SPA的前端路由,你需要在Controller中添加相應(yīng)的路由處理邏輯,或者利用JavaScript前端框架(如React、Vue.js等)來(lái)管理前端路由。
啟動(dòng)Spring Boot應(yīng)用,訪問(wèn)http://localhost:8080(默認(rèn)端口),應(yīng)該能夠看到使用Bootstrap樣式的頁(yè)面。
通過(guò)這種方式,你可以利用Spring WebFlux的響應(yīng)式特性和Bootstrap的前端樣式來(lái)構(gòu)建現(xiàn)代化的單頁(yè)面應(yīng)用。如果你需要使用現(xiàn)代SPA框架(如React、Vue.js),你可以將其構(gòu)建后的靜態(tài)文件放在src/main/resources/static目錄中,并配置Spring Boot以提供這些靜態(tài)資源。
pringboot是如何路由到頁(yè)面?
現(xiàn)在我們創(chuàng)建了一個(gè)新的springboot的web工程
非常干凈。
resources目錄下面有兩個(gè)空的文件夾static和templates
static是用來(lái)放靜態(tài)資源的,包括靜態(tài)頁(yè)面,css,js,圖片等等
template用來(lái)放動(dòng)態(tài)頁(yè)面,就是要根據(jù)java后臺(tái)代碼的返回值來(lái)動(dòng)態(tài)生成的
先什么依賴(lài)都不加,當(dāng)前我的maven依賴(lài)只有
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
在static下建一個(gè)login.html
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"/> </head> <body> <h2> 這是一個(gè)靜態(tài)登錄頁(yè)面 </h2> </body> </html>
啟動(dòng)
測(cè)試
可以看到,默認(rèn)情況下springboot會(huì)直接訪問(wèn)到static下的靜態(tài)文件
必須加文件的后綴名。
測(cè)試結(jié)果:
可以看到,經(jīng)過(guò)controller處理了一下請(qǐng)求,找到了靜態(tài)資源
但是,我們后臺(tái)開(kāi)發(fā)主要還是使用動(dòng)態(tài)頁(yè)面。現(xiàn)在引入Thymeleaf
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
在templates路徑下也創(chuàng)建一個(gè)login.html
測(cè)試:
已經(jīng)找的是templates下的文件了。這說(shuō)明引入Thymeleaf后,通過(guò)java代碼處理的請(qǐng)求,默認(rèn)是找templates下的文件
當(dāng)然,你測(cè)試
依然是靜態(tài)頁(yè)面
另外,此時(shí),你的java代碼返回值帶不帶html后綴都可以
我們的最終目的是根據(jù)后臺(tái)數(shù)據(jù)動(dòng)態(tài)生成頁(yè)面
所以傳點(diǎn)數(shù)據(jù)看看
言
Hi,大家好,我是希留。
在項(xiàng)目的開(kāi)發(fā)工程中,可能會(huì)遇到實(shí)時(shí)性比較高的場(chǎng)景需求,例如說(shuō),聊天 IM 即時(shí)通訊功能、消息訂閱服務(wù)、在線客服等等。那遇到這種功能的時(shí)候應(yīng)該怎么去做呢?通常是使用WebSocket去實(shí)現(xiàn)。
那么,本篇文章就帶大家來(lái)了解一下是什么是WebSocket,以及使用SpringBoot搭建一個(gè)簡(jiǎn)易的聊天室功能。如果對(duì)你有幫助的話,還不忘點(diǎn)贊轉(zhuǎn)發(fā)支持一下,感謝!
源碼地址:
https://github.com/277769738/java-sjzl-demo/tree/master/springboot-websocket
https://gitee.com/huoqstudy/java-sjzl-demo/tree/master/springboot-websocket
目錄
一、什么是WebSocket
WebSocket 是HTML5一種新的協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信(full-duplex)。一開(kāi)始的握手需要借助HTTP請(qǐng)求完成。WebSocket是真正實(shí)現(xiàn)了全雙工通信的服務(wù)器向客戶端推的互聯(lián)網(wǎng)技術(shù)。它是一種在單個(gè)TCP連接上進(jìn)行全雙工通訊協(xié)議。Websocket通信協(xié)議于2011年被IETF定為標(biāo)準(zhǔn)RFC 6455,Websocket API被W3C定為標(biāo)準(zhǔn)。
全雙工和單工的區(qū)別?
二、Http與WebSocket的區(qū)別
http協(xié)議是短連接,因?yàn)檎?qǐng)求之后,都會(huì)關(guān)閉連接,下次重新請(qǐng)求數(shù)據(jù),需要再次打開(kāi)鏈接。
WebSocket協(xié)議是一種長(zhǎng)鏈接,只需要通過(guò)一次請(qǐng)求來(lái)初始化鏈接,然后所有的請(qǐng)求和響應(yīng)都是通過(guò)這個(gè)TCP鏈接進(jìn)行通訊。
三、代碼實(shí)現(xiàn)
Maven 依賴(lài):
<!--websocket依賴(lài)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 引入 Fastjson ,實(shí)現(xiàn)對(duì) JSON 的序列化,因?yàn)楹罄m(xù)我們會(huì)使用它解析消息 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
因?yàn)?WebSocket 協(xié)議,不像 HTTP 協(xié)議有 URI 可以區(qū)分不同的 API 請(qǐng)求操作,所以我們需要在 WebSocket 的 Message 里,增加能夠標(biāo)識(shí)消息類(lèi)型,這里我們采用 type 字段。所以在這個(gè)示例中,我們采用的 Message 采用 JSON 格式編碼,格式如下:
{
type : "", //消息類(lèi)型
boby: {} //消息體
}
創(chuàng)建 Message 接口,基礎(chǔ)消息體,所有消息體都要實(shí)現(xiàn)該接口。目前作為一個(gè)標(biāo)記接口,未定義任何操作。代碼如下:
public interface Message {
}
創(chuàng)建 AuthRequest 類(lèi),用戶認(rèn)證請(qǐng)求。代碼如下:
public class AuthRequest implements Message{
public static final String TYPE="AUTH_REQUEST";
/**
* 認(rèn)證 Token
*/
private String accessToken;
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken=accessToken;
}
}
TYPE 靜態(tài)屬性,消息類(lèi)型為 AUTH_REQUEST 。
accessToken 屬性,認(rèn)證 Token 。在 WebSocket 協(xié)議中,我們也需要認(rèn)證當(dāng)前連接,用戶身份是什么。一般情況下,我們采用用戶調(diào)用 HTTP 登錄接口,登錄成功后返回的訪問(wèn)令牌 accessToken 。
WebSocket 協(xié)議是基于 Message 模型,進(jìn)行交互。但是,這并不意味著它的操作,不需要響應(yīng)結(jié)果。例如說(shuō),用戶認(rèn)證請(qǐng)求,是需要用戶認(rèn)證響應(yīng)的。所以,我們創(chuàng)建 AuthResponse 類(lèi),作為用戶認(rèn)證響應(yīng)。代碼如下:
public class AuthResponse implements Message {
public static final String TYPE="AUTH_RESPONSE";
/**
* 響應(yīng)狀態(tài)碼
*/
private Integer code;
/**
* 響應(yīng)提示
*/
private String message;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code=code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message=message;
}
}
創(chuàng)建 SendToOneRequest 類(lèi),發(fā)送給指定人的私聊消息的 Message。代碼如下:
public class SendToOneRequest implements Message {
public static final String TYPE="SEND_TO_ONE_REQUEST";
/**
* 發(fā)送給的用戶
*/
private String toUser;
/**
* 消息編號(hào)
*/
private String msgId;
/**
* 發(fā)送的內(nèi)容
*/
private String content;
public String getToUser() {
return toUser;
}
public void setToUser(String toUser) {
this.toUser=toUser;
}
public String getMsgId() {
return msgId;
}
public void setMsgId(String msgId) {
this.msgId=msgId;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content=content;
}
}
在服務(wù)端接收到發(fā)送消息的請(qǐng)求,需要異步響應(yīng)發(fā)送是否成功。所以,創(chuàng)建 SendResponse 類(lèi),發(fā)送消息響應(yīng)結(jié)果的 Message 。代碼如下:
public class SendResponse implements Message{
public static final String TYPE="SEND_RESPONSE";
/**
* 消息編號(hào)
*/
private String msgId;
/**
* 響應(yīng)狀態(tài)碼
*/
private Integer code;
/**
* 響應(yīng)提示
*/
private String message;
}
在服務(wù)端接收到發(fā)送消息的請(qǐng)求,需要轉(zhuǎn)發(fā)消息給對(duì)應(yīng)的人。所以,創(chuàng)建 SendToUserRequest 類(lèi),發(fā)送消息給一個(gè)用戶的 Message 。代碼如下:
public class SendToUserRequest implements Message{
public static final String TYPE="SEND_TO_USER_REQUEST";
/**
* 消息編號(hào)
*/
private String msgId;
/**
* 內(nèi)容
*/
private String content;
public String getMsgId() {
return msgId;
}
public void setMsgId(String msgId) {
this.msgId=msgId;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content=content;
}
}
每個(gè)客戶端發(fā)起的 Message 消息類(lèi)型,我們會(huì)聲明對(duì)應(yīng)的 MessageHandler 消息處理器。這個(gè)就類(lèi)似在 SpringMVC 中,每個(gè) API 接口對(duì)應(yīng)一個(gè) Controller 的 Method 方法。
創(chuàng)建 MessageHandler 接口,消息處理器接口。代碼如下:
public interface MessageHandler<T extends Message> {
/**
* 執(zhí)行處理消息
* @param session 會(huì)話
* @param message 消息
*/
void execute(WebSocketSession session, T message);
/**
* 消息類(lèi)型,即每個(gè) Message 實(shí)現(xiàn)類(lèi)上的 TYPE 靜態(tài)字段
* @return
*/
String getType();
}
創(chuàng)建 AuthMessageHandler 類(lèi),處理 AuthRequest 消息。代碼如下:
@Component
public class AuthMessageHandler implements MessageHandler<AuthRequest>{
@Override
public void execute(WebSocketSession session, AuthRequest message) {
// 如果未傳遞 accessToken
if (StringUtils.isEmpty(message.getAccessToken())) {
AuthResponse authResponse=new AuthResponse();
authResponse.setCode(1);
authResponse.setMessage("認(rèn)證 accessToken 未傳入");
WebSocketUtil.send(session, AuthResponse.TYPE,authResponse);
return;
}
// 添加到 WebSocketUtil 中,考慮到代碼簡(jiǎn)化,我們先直接使用 accessToken 作為 User
WebSocketUtil.addSession(session, message.getAccessToken());
// 判斷是否認(rèn)證成功。這里,假裝直接成功
AuthResponse authResponse=new AuthResponse();
authResponse.setCode(0);
WebSocketUtil.send(session, AuthResponse.TYPE, authResponse);
}
@Override
public String getType() {
return AuthRequest.TYPE;
}
}
創(chuàng)建 SendToOneHandler 類(lèi),處理 SendToOneRequest 消息。代碼如下:
@Component
public class SendToOneHandler implements MessageHandler<SendToOneRequest>{
@Override
public void execute(WebSocketSession session, SendToOneRequest message) {
// 這里,假裝直接成功
SendResponse sendResponse=new SendResponse();
sendResponse.setMsgId(message.getMsgId());
sendResponse.setCode(0);
WebSocketUtil.send(session, SendResponse.TYPE, sendResponse);
// 創(chuàng)建轉(zhuǎn)發(fā)的消息
SendToUserRequest sendToUserRequest=new SendToUserRequest();
sendToUserRequest.setMsgId(message.getMsgId());
sendToUserRequest.setContent(message.getContent());
// 廣播發(fā)送
WebSocketUtil.send(message.getToUser(), SendToUserRequest.TYPE, sendToUserRequest);
}
@Override
public String getType() {
return SendToOneRequest.TYPE;
}
}
創(chuàng)建 SendToAllHandler 類(lèi),處理 SendToAllRequest 消息。代碼如下:
@Component
public class SendToAllHandler implements MessageHandler<SendToAllRequest> {
@Override
public void execute(WebSocketSession session, SendToAllRequest message) {
// 這里,假裝直接成功
SendResponse sendResponse=new SendResponse();
sendResponse.setMsgId(message.getMsgId());
sendResponse.setCode(0);
WebSocketUtil.send(session, SendResponse.TYPE, sendResponse);
// 創(chuàng)建轉(zhuǎn)發(fā)的消息
SendToUserRequest sendToUserRequest=new SendToUserRequest();
sendToUserRequest.setMsgId(message.getMsgId());
sendToUserRequest.setContent(message.getContent());
// 廣播發(fā)送
WebSocketUtil.broadcast(SendToUserRequest.TYPE, sendToUserRequest);
}
@Override
public String getType() {
return SendToAllRequest.TYPE;
}
}
創(chuàng)建 WebSocketUtil 工具類(lèi),代碼如下,主要提供兩方面的功能:
public class WebSocketUtil {
private static final Logger LOGGER=LoggerFactory.getLogger(WebSocketUtil.class);
/**
* Session 與用戶的映射
*/
private static final Map<WebSocketSession, String> SESSION_USER_MAP=new ConcurrentHashMap<>();
/**
* 用戶與 Session 的映射
*/
private static final Map<String, WebSocketSession> USER_SESSION_MAP=new ConcurrentHashMap<>();
/**
* 添加 Session 。在這個(gè)方法中,會(huì)添加用戶和 Session 之間的映射
* @param session Session
* @param user 用戶
*/
public static void addSession(WebSocketSession session, String user) {
// 更新 USER_SESSION_MAP
USER_SESSION_MAP.put(user, session);
// 更新 SESSION_USER_MAP
SESSION_USER_MAP.put(session, user);
}
/**
* 發(fā)送消息給單個(gè)用戶的 Session
* @param session Session
* @param type 消息類(lèi)型
* @param message 消息體
* @param <T> 消息類(lèi)型
*/
public static <T extends Message> void send(WebSocketSession session, String type, T message) {
// 創(chuàng)建消息
TextMessage messageText=buildTextMessage(type, message);
// 遍歷給單個(gè) Session ,進(jìn)行逐個(gè)發(fā)送
sendTextMessage(session, messageText);
}
/**
* 廣播發(fā)送消息給所有在線用戶
* @param type 消息類(lèi)型
* @param message 消息體
* @param <T> 消息類(lèi)型
*/
public static <T extends Message> void broadcast(String type, T message) {
// 創(chuàng)建消息
TextMessage messageText=buildTextMessage(type, message);
// 遍歷 SESSION_USER_MAP ,進(jìn)行逐個(gè)發(fā)送
for (WebSocketSession session : SESSION_USER_MAP.keySet()) {
sendTextMessage(session, messageText);
}
}
/**
* 發(fā)送消息給指定用戶
* @param user 指定用戶
* @param type 消息類(lèi)型
* @param message 消息體
* @param <T> 消息類(lèi)型
* @return 發(fā)送是否成功
*/
public static <T extends Message> boolean send(String user, String type, T message) {
// 獲得用戶對(duì)應(yīng)的 Session
WebSocketSession session=USER_SESSION_MAP.get(user);
if (session==null) {
LOGGER.error("[send][user({}) 不存在對(duì)應(yīng)的 session]", user);
return false;
}
// 發(fā)送消息
send(session, type, message);
return true;
}
/**
* 構(gòu)建完整的消息
* @param type 消息類(lèi)型
* @param message 消息體
* @param <T> 消息類(lèi)型
* @return 消息
*/
private static <T extends Message> TextMessage buildTextMessage(String type, T message) {
JSONObject messageObject=new JSONObject();
messageObject.put("type", type);
messageObject.put("body", message);
return new TextMessage(messageObject.toString());
}
/**
* 真正發(fā)送消息
*
* @param session Session
* @param textMessage 消息
*/
private static void sendTextMessage(WebSocketSession session, TextMessage textMessage) {
if (session==null) {
LOGGER.error("[sendTextMessage][session 為 null]");
return;
}
try {
session.sendMessage(textMessage);
} catch (IOException e) {
LOGGER.error("[sendTextMessage][session({}) 發(fā)送消息{}) 發(fā)生異常",
session, textMessage, e);
}
}
}
處理類(lèi),在Spring中,處理消息的具體業(yè)務(wù)邏輯,進(jìn)行開(kāi)啟、關(guān)閉連接等操作。
public class MyHandler extends TextWebSocketHandler implements InitializingBean {
private Logger logger=LoggerFactory.getLogger(getClass());
/**
* 消息類(lèi)型與 MessageHandler 的映射
* 無(wú)需設(shè)置成靜態(tài)變量
*/
private final Map<String, MessageHandler> HANDLERS=new HashMap<>();
@Autowired
private ApplicationContext applicationContext;
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
System.out.println("獲取到消息 >> " + message.getPayload());
logger.info("[handleMessage][session({}) 接收到一條消息({})]", session, message);
// 獲得消息類(lèi)型
JSONObject jsonMessage=JSON.parseObject(message.getPayload());
String messageType=jsonMessage.getString("type");
// 獲得消息處理器
MessageHandler messageHandler=HANDLERS.get(messageType);
if (messageHandler==null) {
logger.error("[onMessage][消息類(lèi)型({}) 不存在消息處理器]", messageType);
return;
}
// 解析消息
Class<? extends Message> messageClass=this.getMessageClass(messageHandler);
// 處理消息
Message messageObj=JSON.parseObject(jsonMessage.getString("body"), messageClass);
messageHandler.execute(session, messageObj);
}
/**
* 連接建立時(shí)觸發(fā)
**/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
logger.info("[afterConnectionEstablished][session({}) 接入]", session);
// 解析 accessToken
String accessToken=(String) session.getAttributes().get("accessToken");
// 創(chuàng)建 AuthRequest 消息類(lèi)型
AuthRequest authRequest=new AuthRequest();
authRequest.setAccessToken(accessToken);
// 獲得消息處理器
MessageHandler<AuthRequest> messageHandler=HANDLERS.get(AuthRequest.TYPE);
if (messageHandler==null) {
logger.error("[onOpen][認(rèn)證消息類(lèi)型,不存在消息處理器]");
return;
}
messageHandler.execute(session, authRequest);
}
/**
* 關(guān)閉連接時(shí)觸發(fā)
**/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
System.out.println("斷開(kāi)連接!");
}
@Override
public void afterPropertiesSet() throws Exception {
// 通過(guò) ApplicationContext 獲得所有 MessageHandler Bean
applicationContext.getBeansOfType(MessageHandler.class).values()
// 添加到 handlers 中
.forEach(messageHandler -> HANDLERS.put(messageHandler.getType(), messageHandler));
logger.info("[afterPropertiesSet][消息處理器數(shù)量:{}]", HANDLERS.size());
}
private Class<? extends Message> getMessageClass(MessageHandler handler) {
// 獲得 Bean 對(duì)應(yīng)的 Class 類(lèi)名。因?yàn)橛锌赡鼙?AOP 代理過(guò)。
Class<?> targetClass=AopProxyUtils.ultimateTargetClass(handler);
// 獲得接口的 Type 數(shù)組
Type[] interfaces=targetClass.getGenericInterfaces();
Class<?> superclass=targetClass.getSuperclass();
// 此處,是以父類(lèi)的接口為準(zhǔn)
while ((Objects.isNull(interfaces) || 0==interfaces.length) && Objects.nonNull(superclass)) {
interfaces=superclass.getGenericInterfaces();
superclass=targetClass.getSuperclass();
}
if (Objects.nonNull(interfaces)) {
// 遍歷 interfaces 數(shù)組
for (Type type : interfaces) {
// 要求 type 是泛型參數(shù)
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType=(ParameterizedType) type;
// 要求是 MessageHandler 接口
if (Objects.equals(parameterizedType.getRawType(), MessageHandler.class)) {
Type[] actualTypeArguments=parameterizedType.getActualTypeArguments();
// 取首個(gè)元素
if (Objects.nonNull(actualTypeArguments) && actualTypeArguments.length > 0) {
return (Class<Message>) actualTypeArguments[0];
} else {
throw new IllegalStateException(String.format("類(lèi)型(%s) 獲得不到消息類(lèi)型", handler));
}
}
}
}
}
throw new IllegalStateException(String.format("類(lèi)型(%s) 獲得不到消息類(lèi)型", handler));
}
}
在Spring中提供了websocket攔截器,可以在建立連接之前寫(xiě)些業(yè)務(wù)邏輯,比如校驗(yàn)登錄等。
public class MyHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
/**
* @Description 握手之前,若返回false,則不建立鏈接
* @Date 21:59 2021/5/16
* @return boolean
**/
@Override
public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> attributes) throws Exception {
//獲得 accessToken ,將用戶id放入socket處理器的會(huì)話(WebSocketSession)中
if (serverHttpRequest instanceof ServletServerHttpRequest) {
ServletServerHttpRequest serverRequest=(ServletServerHttpRequest) serverHttpRequest;
attributes.put("accessToken", serverRequest.getServletRequest().getParameter("accessToken"));
}
// 調(diào)用父方法,繼續(xù)執(zhí)行邏輯
return super.beforeHandshake(serverHttpRequest, serverHttpResponse, webSocketHandler, attributes);
}
@Configuration
@EnableWebSocket //開(kāi)啟spring websocket功能
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
//配置處理器
registry.addHandler(this.myHandler(), "/")
//配置攔截器
.addInterceptors(new MyHandshakeInterceptor())
.setAllowedOrigins("*");
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
@Bean
public MyHandshakeInterceptor webSocketShakeInterceptor() {
return new MyHandshakeInterceptor();
}
}
@SpringBootApplication
public class MyWebsocketApplication {
public static void main(String[] args) {
SpringApplication.run(MyWebsocketApplication.class,args);
}
}
7.實(shí)現(xiàn)效果
打開(kāi)三個(gè)瀏覽器,輸入在線測(cè)試websocket地址:
http://www.easyswoole.com/wstool.html
創(chuàng)建三個(gè)連接。分別設(shè)置服務(wù)地址如下:
發(fā)送單人消息
{
tpye: "SEND_TO_ONE_REQUEST",
boby: {
toUser: "1002",
msgId: "qwwerqrsfd123",
centent: "這是1001發(fā)送給1002的單聊消息"
}
}
可以看到1002收到了1001發(fā)的單聊信息,1003未收到。效果圖如下:
發(fā)送多人消息
{
tpye: "SEND_TO_ALL_REQUEST",
boby: {
msgId: "qwerqcfwwerqrsfd123",
centent: "我是一條群聊消息"
}
}
可以看到1001,1002,1003都收到了消息,效果圖如下:
結(jié)語(yǔ)
好了,以上就是今天要講的內(nèi)容,本文介紹了WebSocket協(xié)議以及使用它簡(jiǎn)單的實(shí)現(xiàn)及時(shí)聊天的場(chǎng)景。
感謝大家的閱讀,喜歡的朋友,歡迎點(diǎn)贊支持一下。
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。