WebSocket--精准推送方案(二):实时消息推送-若依项目示例
1. 功能概述
在后台管理系统中,需要实时通知用户未处理订单或审批数量的变化。本例通过前端 Vue + 后端 Spring Boot WebSocket 实现:
前端实时显示未处理订单数量
后端实时推送消息给指定用户或所有用户
支持用户角色区分(如会计、老板、库管员)推送不同数量
使用下面的方式解决了
1.跨域问题:
registry.addHandler(handler, "/ws").setAllowedOrigins("*")
或指定前端地址2.注入问题:下面代码中在 WebSocket 处理器类中(MyWsHandler),可以使用@Autowired注入想要注入的类
2. 前端实现
2.1 HTML / Vue 模板
在若依中找到src\layout\components\Navbar.vue后可以把下面代码添加到<div class="right-menu">中
<!-- 消息图标和未处理数量 --> <div class="message-container"><i class="iconfont icon-xiaoxi"></i><span v-if="unreadCount > 0" class="unread-count">有未处理订单数量:{{ unreadCount }}</span> </div>
2.2 Vue 数据与生命周期
把下面代码加src\layout\components\Navbar.vue的到export default {}中,注意,分隔符
data() {return {unreadCount: 0, // 未处理数量websocket: null}; }, created() {this.initWebSocket(); }, beforeDestroy() {// 页面关闭时断开 WebSocketif (this.websocket) {this.websocket.close();} }
2.3 WebSocket 初始化
把下面代码加src\layout\components\Navbar.vue的到methods: {}中,注意,分隔符
initWebSocket() {const wsUrl = `ws://localhost:8080/websocket/message?userId=1`; // userId 可动态传this.websocket = new WebSocket(wsUrl);this.websocket.onopen = () => {console.log('WebSocket 连接成功');};this.websocket.onmessage = (event) => {console.log('收到消息:', event.data);this.unreadCount = parseInt(event.data) || 0; // 更新未处理数量};this.websocket.onclose = () => {console.log('WebSocket 连接关闭,3秒后重连');setTimeout(this.initWebSocket, 3000); // 自动重连};this.websocket.onerror = () => {console.error('WebSocket 出错');}; }
说明:
WebSocket
是浏览器原生 API,可实现前端与后端长连接通过 URL 查询参数传递
userId
自动重连机制保证网络中断时能恢复连接
接收到消息后更新
unreadCount
显示数量3.前端配置完成后,若依框架后端关闭token验证
在若依框架中,如果访问页面或接口没有额外配置,系统会默认检查 token 权限。因此直接访问
ws://localhost:8080/websocket/message?userId=1
可能会被拦截,导致 WebSocket 无法建立连接。
解决方法:在后端
com.ruoyi.framework.config
包下的SecurityConfig
类中,找到filterChain
方法,为该路径配置免 token 验证,即将/websocket/**
设置为不进行 token 权限检查。
注重点
关于
ws://localhost:8080/websocket/message?userId=1
的说明与注意事项
用户 userId 的获取
本示例中 userId 是固定写死的,但在实际项目中,可以通过前端请求后台接口获取当前用户的 userId。这样每个用户就能拥有独立的 WebSocket 连接,实现精准消息推送。Token 验证问题
WebSocket 连接无法像普通 HTTP 请求一样携带 token,因此直接访问该 URL 时无法通过若依默认的 token 权限校验。
解决方法:在后端com.ruoyi.framework.config
包下的SecurityConfig
类中,找到filterChain
方法,将/websocket/**
路径配置为免 token 验证。这样 WebSocket 请求就不会被拦截。为什么把 userId 拼接在 URL 中
由于 WebSocket 连接无法直接使用 token 验证,后端在获取当前登录用户信息时(获取登录方法需要验证权限也就是Tocken)无法直接识别用户身份。因此,将 userId 作为 URL 参数传递,后台在握手阶段读取该参数,就可以确定用户身份,实现对应的消息推送。
4. 后端实现
4.1 需要的xml
spring-boot-starter-websocket
确实是 Spring Boot 自带管理的 starter,所以不用写版本号,它会跟随 Spring Boot 的版本自动选择合适的依赖版本。<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dependency>
4.2 WebSocket 配置类
@Configuration @EnableWebSocket public class MyWsConfig implements WebSocketConfigurer {@ResourceMyWsHandler myWsHandler;@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {// 添加 WebSocket 处理器,并拦截 userIdregistry.addHandler(myWsHandler,"/websocket/message").addInterceptors(new HttpSessionHandshakeInterceptor() {@Overridepublic boolean beforeHandshake(ServerHttpRequest request,ServerHttpResponse response,WebSocketHandler wsHandler,Map<String, Object> attributes) throws Exception {String query = request.getURI().getQuery();if (query != null) {for (String param : query.split("&")) {String[] kv = param.split("=");if (kv.length == 2 && "userId".equals(kv[0])) {attributes.put("userId", kv[1]); // 保存 userId}}}return true;}}).setAllowedOrigins("*"); // 允许跨域} }
说明:
WebSocketConfigurer
用于注册 WebSocket 处理器
HttpSessionHandshakeInterceptor
可以在握手阶段获取 URL 参数(如 userId)
setAllowedOrigins("*")
允许跨域连接(开发环境用,生产可改为指定域名)
4.3 WebSocket 处理器
/*** WebSocket 核心处理类* 继承 AbstractWebSocketHandler 来处理前端与后端之间的消息交互* 功能点:* 1. 建立连接时记录用户连接(sessionMap 管理)* 2. 支持后端主动推送消息* 3. 处理异常与连接关闭,保证 sessionMap 干净*/ @Component public class MyWsHandler extends AbstractWebSocketHandler {/*** 保存所有在线用户的 WebSocketSession* key -> userId(用户唯一标识)* value -> 与该用户对应的 WebSocketSession* 使用 ConcurrentHashMap 保证线程安全*/private static Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<>();/*** 当客户端成功建立连接时调用*/@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {// 从握手阶段存入的属性中获取 userIdString userId = (String) session.getAttributes().get("userId");// 将 userId 与该用户的会话绑定到 sessionMapsessionMap.put(userId, session);// ======== 业务逻辑示例 ========// 模拟查询该用户未处理的订单数量(比如从数据库 select count(*))int count = 20;// 连接建立成功后立即返回该用户的待办数量session.sendMessage(new TextMessage(count + ""));}/*** 处理前端主动发送过来的消息* 例如:心跳检测、客户端指令* 本例暂时不需要,保留空实现*/@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {// 这里可以打印或处理客户端发来的消息// System.out.println("收到客户端消息:" + message.getPayload());}/*** 当传输出现异常时调用* 例如:网络断开、消息发送异常*/@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {// 如果 session 仍然是开启状态,先关闭if (session.isOpen()) {session.close();}// 注意:这里移除时用 session.getId(),而不是 userId// 说明:前面存的时候是 userId,这里用 session.getId() 可能导致删除不一致// 建议改成根据 userId 移除sessionMap.remove(session.getId());}/*** 当连接关闭时调用* 比如用户刷新页面 / 浏览器关闭*/@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {// 同样需要移除该用户的 session// 建议和 afterConnectionEstablished 的存储逻辑保持一致sessionMap.remove(session.getId());}/*** 后端主动推送消息给所有在线用户* 场景:当有新订单 / 审批状态变化时,需要实时通知所有人*///这里如果不想推送给所有人,可以给sendMse()添加参数作为推送条件public void sendMse() {// 遍历所有在线用户for (String userId : sessionMap.keySet()) {try {// ======== 业务逻辑示例 ========// 模拟动态查询该用户对应的未处理数量// 实际开发中应根据 userId 查询不同角色的待办数量int count = 30;// 拿到该用户的 WebSocket 会话WebSocketSession session = sessionMap.get(userId);// 判断 session 是否有效(防止空指针或连接已关闭)if (session != null && session.isOpen()) {// 主动推送消息给前端session.sendMessage(new TextMessage(count + ""));}} catch (IOException e) {e.printStackTrace();}}} }
本样例推送时推送所有人,如果不想推送给所有人,可以给sendMse()添加参数作为推送条件
说明:
sessionMap
保存所有在线用户的WebSocketSession
,便于推送消息
afterConnectionEstablished
建立连接时发送初始未处理数量
sendMessage
方法可在后台数据更新时调用,实现实时推送
handleTransportError
与afterConnectionClosed
确保断开连接及时清理
5. 工作流程
前端 Vue 页面加载时调用
initWebSocket()
建立连接后端
MyWsHandler
保存用户 session 并返回初始未处理数量后端业务逻辑变化(如审批通过/新订单)调用
sendMessage()
,推送新数量前端
onmessage
接收并更新页面显示网络断开后,前端自动重连
6. 特点与注意事项
支持实时消息推送,避免轮询数据库
根据
userId
精准推送可以扩展支持角色、权限等业务逻辑
前端自动重连机制保证稳定性
开发环境可允许跨域,生产环境应限制 origin
session.isOpen()
用于判断 WebSocket 是否仍有效