在技术派平台中,实现了文章被点赞或评论后,在右上角实时弹出消息提醒的功能。相较于之前仅有的消息通知红色标识,这种实时通知在交互体验上有显著提升。本文将详细介绍如何借助WebSocket实现消息的实时通知。
1 基础知识点
1.1 相关概念
- WebSocket:WebSocket是一种在单个TCP连接上进行全双工通信的协议,能使客户端和服务器实时地双向传输数据,无需频繁建立和关闭连接,可提高数据传输的效率和性能。
- STOMP:
Simple Text Oriented Messaging Protocol
,即简单文本定向消息协议,是在HTTP之上实现的简单灵活的消息传递协议,定义了一套简单的命令和帧格式用于客户端和服务器间的消息传递,可实现双向通讯。需注意,STOMP协议属于WebSocket的子协议。
1.2 WebSocket整合STOMP
WebSocket整合STOMP协议实现双向通讯的主要步骤如下:
- 建立WebSocket连接:客户端通过JavaScript的WebSocket API与服务器建立连接。
- 发送STOMP帧:连接建立后,客户端和服务器通过发送STOMP帧进行通信,STOMP帧是STOMP协议的基本单位,定义了订阅、发布等消息操作。
- 处理STOMP帧:服务器收到STOMP帧后,根据帧类型进行相应处理,如收到SUBSCRIBE帧为客户端创建订阅,收到SEND帧将消息发送到指定目的地。
- 关闭WebSocket连接:通信完成后,通过调用WebSocket API的close方法关闭连接。
1.3 SpringBoot整合STOMP流程
SpringBoot对WebSocket提供了友好封装,便于搭建基于STOMP协议的WebSocket应用工程。其基本工作流程如下:
-
步骤1:初始化
- 服务端:定义接收WebSocket连接的端点EndPoint;配置消息代理Broker,用于前端订阅,后端向Broker写入消息后,订阅该Broker的前端会收到相应消息;配置路由转发规则,将用户信息转发给相应处理器(类似
RequestMappingHandlerMapping
和@RequestMapping
注解,WebSocket中使用Destination + @MessageMapping
)。 - 客户端:与EndPoint建立连接;订阅Broker并注册消息回调。
- 服务端:定义接收WebSocket连接的端点EndPoint;配置消息代理Broker,用于前端订阅,后端向Broker写入消息后,订阅该Broker的前端会收到相应消息;配置路由转发规则,将用户信息转发给相应处理器(类似
-
步骤2:通讯
- 服务端:主动向Broker写入消息,使用
simpMessagingTemplate
;消息应答使用@SendTo
注解。 - 客户端:发送消息调用
send(xxxx)
方法;消息应答触发订阅的回调函数。
- 服务端:主动向Broker写入消息,使用
2 WebSocket集成
相关示例demo可在https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/203-websocket获取。
2.1 依赖配置
在Spring Boot应用的pom.xml配置文件中,引入WebSocket的核心依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.2 WebSocket配置
后端通过实现配置类StompConfiguration
来定义相关信息:
@Configuration
@EnableWebSocketMessageBroker
public class StompConfiguration implements WebSocketMessageBrokerConfigurer {
// 配置消息代理
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic"); // 客户端订阅地址前缀
registry.setApplicationDestinationPrefixes("/app"); // 服务端接收地址前缀
}
// 注册STOMP端点
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws/hello").withSockJS(); // 客户端连接端点
}
}
- 定义端点:使用
registerStompEndpoints()
方法。 - 定义客户端与服务端通讯信息:使用
configureMessageBroker()
方法,配置消息代理registry.enableSimpleBroker
,配置消息转发registry.setApplicationDestinationPrefixes
(转发前缀可为多个)。
配置完成后,客户端的相关路径为:建立连接路径是/ws/hello
;订阅消息路径是/topic/xxx
(由@SendTo
的路径确定);接收前端消息路径是/app/xxx
(由@MessageMapping
中的路径补齐)。
2.3 消息接收应答
实现简单消息应答,接收客户端向/app/hello发送的消息,将结果写回到/topic/hello对应的Broker,订阅该Broker的客户端会收到应答消息。
@Controller
public class HelloController {
// 处理/app/hello路径的STOMP消息
@MessageMapping("/hello")
@SendTo("/topic/hello") // 将返回结果广播到指定主题
public String greeting(String message) {
return "[" + LocalDateTime.now() + "]: " + message;
}
}
同时,编写定时器,由后端定时向/topic/hello这个Broker中写入消息,模拟后台主动下发消息场景。
@Autowired
private SimpMessagingTemplate simpMessagingTemplate;
// 定时推送示例(当前被注释)
@Scheduled(cron = "0/10 * * * * ?")
public void autoPush() {
simpMessagingTemplate.convertAndSend("/topic/hello", "系统自动消息");
}
2.4 执行流程解析
var stompClient = null;
function setConnected(connected) {
$("#connect").prop("disabled", connected);
$("#disconnect").prop("disabled", !connected);
if (connected) {
$("#conversation").show();
}
else {
$("#conversation").hide();
}
$("#greetings").html("");
}
function connect() {
var socket = new SockJS('/ws/hello');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/hello', function (greeting) {
console.log("resp: ", greeting.body)
showGreeting(greeting.body);
});
});
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendName() {
stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()}));
}
function showGreeting(message) {
$("#greetings").append("<tr><td>" + message + "</td></tr>");
}
$(function () {
$("form").on('submit', function (e) {
e.preventDefault();
});
$("#connect").click(function () {
connect();
});
$("#disconnect").click(function () {
disconnect();
});
$("#send").click(function () {
sendName();
});
});
核心js逻辑实现WebSocket连接建立与通讯:
- 建立连接connect():通过
new SockJS('/ws/hello')
与后端端点建立连接;连接成功后,使用stompClient.subscribe('/topic/hello', 消息应答回调)
订阅Broker并接收消息回传。 - 发送消息:调用
stompClient.send("/app/hello", xxx)
方法。 - 断开连接:调用
stompClient.disconnect()
方法。
2.5 效果演示
完成基于Spring Boot整合WebSocket与STOMP协议的示例应用搭建后,启动应用,可看到前端建立连接后向后端发送信息并接收后端广播消息的过程,多个客户端订阅同一Broker时,能收到后端发送的消息。
3 技术派基于WebSocket的消息通知
3.1 消息推送配置
WsChatConfig
是一个 Spring 配置类,其主要功能是配置基于 STOMP(Simple Text Oriented Messaging Protocol)协议的 WebSocket 通信。下面按照代码结构详细说明其逻辑:
- 类定义与注解
@Slf4j
@Configuration
@EnableWebSocketMessageBroker
public class WsChatConfig implements WebSocketMessageBrokerConfigurer {
@Slf4j
:Lombok 提供的注解,会自动为该类生成一个日志记录器 log,方便日志输出。@Configuration
:Spring 注解,表明该类是一个配置类,Spring 容器会扫描并加载其中定义的 Bean。@EnableWebSocketMessageBroker
:启用基于消息代理的 WebSocket 支持,允许应用使用 STOMP 协议进行消息传递。implements WebSocketMessageBrokerConfigurer
:实现该接口,需要重写其中的方法来定制 WebSocket 消息代理的配置。
- 配置消息代理
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/chat");
config.setApplicationDestinationPrefixes("/app");
}
config.enableSimpleBroker("/chat")
:启用一个简单的基于内存的消息代理,客户端订阅以/chat
开头的目的地后,就可以接收从服务端广播的消息。config.setApplicationDestinationPrefixes("/app")
:设置应用程序的目标前缀,以/app
开头的消息会被路由到带有@MessageMapping
注解的控制器方法进行处理。
- 注册 STOMP 端点
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gpt/{id}/{aiType}")
.setHandshakeHandler(new AuthHandshakeHandler())
.addInterceptors(new AuthHandshakeInterceptor())
.setAllowedOriginPatterns("*");
}
registry.addEndpoint("/gpt/{id}/{aiType}")
:注册一个 WebSocket 端点,客户端可以通过这个端点连接到服务端。{id}
和{aiType}
是路径变量,可用于标识不同的用户和 AI 类型。.setHandshakeHandler(new AuthHandshakeHandler())
:设置自定义的握手处理器,用于在 WebSocket 握手阶段进行身份验证等操作。.addInterceptors(new AuthHandshakeInterceptor())
:添加自定义的握手拦截器,进一步处理握手过程中的逻辑。.setAllowedOriginPatterns("*")
:允许所有来源的跨域请求,确保不同域名的客户端都能连接到该端点。
- 配置消息通道拦截器
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(channelInInterceptor());
}
@Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
registration.interceptors(channelOutInterceptor());
}
configureClientInboundChannel
:配置客户端发送到服务端的消息通道,添加入站拦截器channelInInterceptor()
,用于处理客户端发来的消息。configureClientOutboundChannel
:配置服务端发送到客户端的消息通道,添加出站拦截器channelOutInterceptor()
,用于处理服务端返回给客户端的消息。
- 定义 Bean
@Bean
public HandshakeHandler handshakeHandler() {
return new AuthHandshakeHandler();
}
@Bean
public HttpSessionHandshakeInterceptor handshakeInterceptor() {
return new AuthHandshakeInterceptor();
}
@Bean
public ChannelInterceptor channelInInterceptor() {
return new AuthInChannelInterceptor();
}
@Bean
public ChannelInterceptor channelOutInterceptor() {
return new AuthOutChannelInterceptor();
}
- 使用
@Bean
注解将自定义的握手处理器、握手拦截器、入站拦截器和出站拦截器注册为 Spring Bean,方便 Spring 容器进行管理和使用。
综上所述,WsChatConfig
类通过实现 WebSocketMessageBrokerConfigurer
接口,完成了 WebSocket 端点的注册、消息代理的配置以及消息通道拦截器的设置,从而实现了基于 STOMP 协议的 WebSocket 通信功能,同时加入了身份验证等安全机制。
3.2 身份鉴权拦截器
AuthHandshakeInterceptor
类继承自 HttpSessionHandshakeInterceptor
,主要作用是在 WebSocket 握手阶段进行用户身份验证和识别。下面详细解释其代码逻辑:
- 类定义和继承
public class AuthHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
这行代码定义了 AuthHandshakeInterceptor
类,并继承自 HttpSessionHandshakeInterceptor
。HttpSessionHandshakeInterceptor
是 Spring 提供的用于在 WebSocket 握手过程中处理 HTTP 会话的拦截器,继承它可以利用其已有的功能,同时自定义握手逻辑。
- 握手前的身份验证 (
beforeHandshake
方法)
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
log.info("准备开始握手了!");
String session = SessionUtil.findCookieByName(request, LoginService.SESSION_KEY);
ReqInfoContext.ReqInfo reqInfo = new ReqInfoContext.ReqInfo();
SpringUtil.getBean(GlobalInitService.class).initLoginUser(session, reqInfo);
if (reqInfo.getUser() == null) {
log.info("websocket 握手失败,请登录之后再试");
return false;
}
// 将用户信息写入到属性中
attributes.put(MdcUtil.TRACE_ID_KEY, SelfTraceIdGenerator.generate());
attributes.put(LoginService.SESSION_KEY, reqInfo);
attributes.put(WsAnswerHelper.AI_SOURCE_PARAM, initAiSource(request.getURI().getPath()));
return true;
}
- 日志记录:使用
log.info
记录开始握手的信息。 - 获取会话信息:通过
SessionUtil.findCookieByName
方法从请求中获取名为LoginService.SESSION_KEY
的Cookie
值。 - 初始化用户信息:创建
ReqInfoContext.ReqInfo
对象,并调用GlobalInitService
的initLoginUser
方法,根据会话信息初始化用户信息。 - 身份验证:检查
reqInfo
中的用户信息是否为空,如果为空则记录握手失败的日志并返回false
,表示握手失败。 - 设置属性:如果身份验证通过,将生成的跟踪 ID、用户请求信息和 AI 来源信息添加到
attributes
中,这些信息将在后续的 WebSocket 通信中使用。最后返回true
,表示握手成功。
- 初始化 AI 来源 (
initAiSource
方法)
private String initAiSource(String path) {
int index = path.lastIndexOf("/");
return path.substring(index + 1);
}
该方法从请求的 URI 路径中提取 AI 来源信息。通过查找最后一个 / 的位置,然后截取该位置之后的字符串作为 AI 来源。
- 握手后的处理 (
afterHandshake
方法)
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
log.info("握手成功了!!!");
super.afterHandshake(request, response, wsHandler, ex);
}
在握手成功后,使用 log.info
记录握手成功的日志,并调用父类的 afterHandshake
方法完成后续处理。
综上所述,AuthHandshakeInterceptor
类在 WebSocket 握手过程中进行用户身份验证,确保只有登录用户可以建立 WebSocket 连接,并在握手前后记录日志和处理相关信息。
3.3 握手处理器
AuthHandshakeHandler
类继承自 DefaultHandshakeHandler
,主要用于在 WebSocket 握手过程中确定连接的用户身份。下面详细解释其代码逻辑:
- 类定义和注解
@Slf4j
public class AuthHandshakeHandler extends DefaultHandshakeHandler {
@Slf4j
:Lombok 注解,用于自动生成日志记录器 log,方便在类中记录日志。extends DefaultHandshakeHandler
:继承DefaultHandshakeHandler
类,该类是 Spring 提供的默认握手处理器,AuthHandshakeHandler
可以重写其方法来实现自定义的握手逻辑。
- 重写
determineUser
方法
@Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
// case1: 根据cookie来识别用户,即可以实现所有用户连相同的ws地址,然后再 AuthHandshakeChannelInterceptor 中进行destination的转发
ReqInfoContext.ReqInfo reqInfo = (ReqInfoContext.ReqInfo) attributes.get(LoginService.SESSION_KEY);
if (reqInfo != null) {
return reqInfo;
}
// case2: 根据路径来区分用户
// 获取例如 ws://localhost/gpt/id 订阅地址中的最后一个用户 id 参数作为用户的标识, 为实现发送信息给指定用户做准备
String uri = request.getURI().toString();
String uid = uri.substring(uri.lastIndexOf("/") + 1);
log.info("{} -> {}", uri, uid);
return () -> uid;
}
determineUser
方法是DefaultHandshakeHandler
中的一个受保护方法,用于在握手过程中确定连接的用户身份。重写该方法可以实现自定义的用户身份识别逻辑。- 身份识别方式一:通过
Cookie
识别- 从
attributes
中获取LoginService.SESSION_KEY
对应的值,将其转换为ReqInfoContext.ReqInfo
对象。 - 如果
reqInfo
不为空,则将其作为Principal
返回,表示用户身份已通过Cookie
识别。
- 从
- 身份识别方式二:通过路径识别
- 如果通过
Cookie
未能识别用户身份,则从请求的 URI 中提取最后一个/
之后的部分作为用户 ID。 - 使用 Lambda 表达式创建一个
Principal
对象,其getName()
方法返回提取的用户 ID。 - 记录请求 URI 和提取的用户 ID 的日志信息。
- 如果通过
综上所述,AuthHandshakeHandler
类在 WebSocket 握手过程中,通过两种方式确定连接的用户身份,确保后续的消息可以准确地发送给指定用户。
3.4 消息发送与处理
WsAnswerHelper
是一个 Spring 组件,主要职责是借助 WebSocket 向用户实时推送聊天相关消息,在技术派项目里实现消息实时推送功能。下面对其代码逻辑展开详细说明:
3.4.1 消息发送方法
sendMsgToUser(String session, String question)
public void sendMsgToUser(String session, String question) {
ChatRecordsVo res = chatFacade.autoChat(question, vo -> response(session, vo));
log.info("AI直接返回:{}", res);
}
- 调用
chatFacade.autoChat
方法发起自动聊天,传入用户提问question
以及一个回调函数。 - 回调函数
vo -> response(session, vo)
会在获取到聊天结果后,调用response
方法把结果推送给指定用户。 - 记录 AI 返回结果的日志。
sendMsgToUser(AISourceEnum ai, String session, String question)
public void sendMsgToUser(AISourceEnum ai, String session, String question) {
if (ai == null) {
// 自动选择AI类型
sendMsgToUser(session, question);
} else {
ChatRecordsVo res = chatFacade.autoChat(ai, question, vo -> response(session, vo));
log.info("AI直接返回:{}", res);
}
}
- 若传入的 ai 为
null
,则调用sendMsgToUser(session, question)
方法自动选择 AI 类型进行聊天。 - 若 ai 不为
null
,则调用chatFacade.autoChat
方法,指定 AI 类型进行聊天,并将结果推送给用户,同时记录日志。
sendMsgHistoryToUser(String session, AISourceEnum ai)
public void sendMsgHistoryToUser(String session, AISourceEnum ai) {
ChatRecordsVo vo = chatFacade.history(ai);
response(session, vo);
}
- 调用
chatFacade.history
方法获取指定 AI 类型的聊天历史记录。 - 调用
response
方法将聊天历史记录推送给指定用户。
3.4.2 消息推送方法
/**
* 将返回结果推送给用户
*
* @param session
* @param response
*/
public void response(String session, ChatRecordsVo response) {
// convertAndSendToUser 方法可以发送信给给指定用户,
// 底层会自动将第二个参数目的地址 /chat/rsp 拼接为
// /user/username/chat/rsp,其中第二个参数 username 即为这里的第一个参数 session
// username 也是AuthHandshakeHandler中配置的 Principal 用户识别标志
simpMessagingTemplate.convertAndSendToUser(session, "/chat/rsp", response);
}
- 借助 SimpMessagingTemplate 的
convertAndSendToUser
方法,把聊天结果response
推送给指定用户 session。 - 目标地址
/chat/rsp
会自动拼接成/user/username/chat/rsp
,这里的username
就是 session。
3.4.3 业务逻辑执行方法
public void execute(Map<String, Object> attributes, Runnable func) {
try {
ReqInfoContext.ReqInfo reqInfo = (ReqInfoContext.ReqInfo) attributes.get(LoginService.SESSION_KEY);
ReqInfoContext.addReqInfo(reqInfo);
String traceId = (String) attributes.get(MdcUtil.TRACE_ID_KEY);
MdcUtil.add(MdcUtil.TRACE_ID_KEY, traceId);
// 执行具体的业务逻辑
func.run();
} finally {
ReqInfoContext.clear();
MdcUtil.clear();
}
}
- 从
attributes
里获取用户请求信息reqInfo
和跟踪 IDtraceId
,并分别添加到ReqInfoContext
和MdcUtil
中。 - 执行传入的业务逻辑
func.run()
。 - 无论业务逻辑是否执行成功,最后都要清除
ReqInfoContext
和MdcUtil
中的信息。
综上所述,WsAnswerHelper
类整合了聊天服务和 WebSocket 消息推送功能,实现了消息的实时发送和推送,同时对请求信息和跟踪 ID 进行管理。
3.5 前端页面集成
在前端页面 paicoding-ui/src/main/resources/templates/views/chat-home/index.html
中,可以使用 JavaScript 代码来连接 WebSocket 并接收消息。
3.5.1 连接 WebSocket
<script th:inline="javascript">
// ... 其他代码 ...
const isLogin = [[${global.isLogin}]], user = [[${global.user}]];
let stompClient = null;
$(function () {
if (isLogin) {
initWs();
} else {
console.log("请先登录");
}
});
function initWs() {
let protocol = window.location.protocol.replace("http", "ws");
let host = window.location.host;
let aiType = $('#chat-type').val();
const session = getCookie("f-session");
let socket = new WebSocket(`${protocol}//${host}/gpt/${session}/${aiType}`);
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
console.log('ws连接成功: ' + frame);
// 其他操作...
});
// 关闭链接
socket.onclose = disconnect;
}
// ... 其他代码 ...
</script>
- 登录状态判断:借助 Thymeleaf 模板引擎获取后端传递的登录状态
isLogin
和用户信息user
。页面加载完成后,若用户已登录,则调用initWs
方法来初始化 WebSocket 连接;反之,在控制台输出提示信息。 - 构建 WebSocket 地址:
initWs
方法先把当前页面的协议从http
替换成ws
,接着获取当前页面的主机名,再获取下拉框选中的 AI 类型aiType
以及用户的会话session
,最后构建出完整的 WebSocket 连接地址。 - 创建 WebSocket 连接:使用
new WebSocket
创建 WebSocket 连接,再用Stomp.over
方法将其包装成基于 STOMP 协议的客户端。 - 连接服务器:调用
stompClient.connect
方法连接服务器,连接成功后会执行回调函数,在控制台输出连接成功信息。 - 处理关闭事件:为 WebSocket 的
onclose
事件绑定disconnect
方法,当连接关闭时会触发该方法。
3.5.2 订阅消息
<script th:inline="javascript">
// ... 其他代码 ...
function initWs() {
// ... 其他代码 ...
stompClient.connect({}, function (frame) {
console.log('ws连接成功: ' + frame);
// 订阅消息
stompClient.subscribe(`/user/chat/rsp`, function (message) {
// 解析 JSON 字符串
console.log("rsp:", message);
let res = JSON.parse(message.body);
console.log("res:", res);
// 记录聊天次数
$("#chatCnt").html(` ${res.usedCnt}/${res.maxCnt} `);
const data = res.records;
// 移除 loading 元素
$(".home_chat-message-actions__loading").remove();
if (data.length > 1) {
// 返回历史全部信息
chatContent.html('');
for (let i = data.length - 1; i >= 0; i--) {
if (data[i].question) {
addClientMsg(data[i].question, false);
}
if (i == 0) {
addSplit();
}
appendServerMessage(data[i]);
}
} else {
appendServerMessage(data[0]);
}
// 除流式持续返回场景,恢复按钮状态
if(data[data.length - 1].answerType != 'STREAM') {
sendBtn.removeAttr("disabled");
}
});
});
// ... 其他代码 ...
}
// ... 其他代码 ...
</script>
- 订阅目的地:在
stompClient.connect
的回调函数里,调用stompClient.subscribe
方法订阅/user/chat/rsp
目的地,该目的地是服务器推送消息的目标地址。 - 处理接收到的消息:当服务器向该目的地推送消息时,会触发回调函数。在回调函数中,先将接收到的消息体解析成 JSON 对象,然后更新页面上的聊天次数,移除加载中的提示元素,根据消息数据的长度不同,采用不同的方式将消息添加到聊天界面,最后根据消息类型恢复发送按钮的状态。
3.5.3 发送消息
<script th:inline="javascript">
// ... 其他代码 ...
function doSend() {
const qa = inputField.val();
if (qa.length > 512) {
toastr.info("提问长度请不要超过512字符哦~");
return;
}
stompClient.send("/app/chat/" + session, {'s-uid': session}, qa);
inputField.val("");
addClientMsg(qa.replace(/\n/g, "<br/>"), true);
sendBtn.attr("disabled", true);
}
sendBtn.click(function () {
if (stompClient == null) {
initWs();
} else {
if (inputField.val() === "") {
inputField.focus();
} else {
doSend();
}
}
});
inputField.keydown(function (e) {
if (e.keyCode === 13) {
sendBtn.click();
e.preventDefault();
}
});
// ... 其他代码 ...
</script>
doSend
方法:获取输入框中的内容,若内容长度超过 512 字符,则给出提示并返回;否则,调用stompClient.send
方法将消息发送到/app/chat/
加上会话 ID 的地址,同时传递自定义头信息{'s-uid': session}
。发送消息后,清空输入框,在聊天界面添加用户消息,并禁用发送按钮。- 按钮点击事件:为发送按钮绑定点击事件,若
stompClient
为空,则调用initWs
方法初始化连接;否则,若输入框内容为空,则将焦点聚焦到输入框,反之调用doSend
方法发送消息。 - 回车键事件:为输入框绑定按键事件,当按下回车键时,触发发送按钮的点击事件,并阻止回车键的默认行为。
综上所述,前端代码通过上述步骤实现了 WebSocket 的连接、消息的订阅和发送功能,从而与服务器进行实时通信。
4 小结
本文聚焦WebSocket的应用,涵盖其基础概念、Spring整合STOMP的流程图、Spring Boot应用集成WebSocket实现双向通信,以及技术派中基于WebSocket实现消息推送的全过程。