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() { // 页面关闭时断开 WebSocket if (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 { @Resource MyWsHandler myWsHandler; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { // 添加 WebSocket 处理器,并拦截 userId registry.addHandler(myWsHandler,"/websocket/message") .addInterceptors(new HttpSessionHandshakeInterceptor() { @Override public 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<>(); /** * 当客户端成功建立连接时调用 */ @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { // 从握手阶段存入的属性中获取 userId String userId = (String) session.getAttributes().get("userId"); // 将 userId 与该用户的会话绑定到 sessionMap sessionMap.put(userId, session); // ======== 业务逻辑示例 ======== // 模拟查询该用户未处理的订单数量(比如从数据库 select count(*)) int count = 20; // 连接建立成功后立即返回该用户的待办数量 session.sendMessage(new TextMessage(count + "")); } /** * 处理前端主动发送过来的消息 * 例如:心跳检测、客户端指令 * 本例暂时不需要,保留空实现 */ @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { // 这里可以打印或处理客户端发来的消息 // System.out.println("收到客户端消息:" + message.getPayload()); } /** * 当传输出现异常时调用 * 例如:网络断开、消息发送异常 */ @Override public 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()); } /** * 当连接关闭时调用 * 比如用户刷新页面 / 浏览器关闭 */ @Override public 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 是否仍有效