当前位置: 首页 > news >正文

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验证

  1. 在若依框架中,如果访问页面或接口没有额外配置,系统会默认检查 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 的说明与注意事项

  1. 用户 userId 的获取
    本示例中 userId 是固定写死的,但在实际项目中,可以通过前端请求后台接口获取当前用户的 userId。这样每个用户就能拥有独立的 WebSocket 连接,实现精准消息推送。

  2. Token 验证问题
    WebSocket 连接无法像普通 HTTP 请求一样携带 token,因此直接访问该 URL 时无法通过若依默认的 token 权限校验。
    解决方法:在后端 com.ruoyi.framework.config 包下的 SecurityConfig 类中,找到 filterChain 方法,将 /websocket/** 路径配置为免 token 验证。这样 WebSocket 请求就不会被拦截。

  3. 为什么把 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 方法可在后台数据更新时调用,实现实时推送

  • handleTransportErrorafterConnectionClosed 确保断开连接及时清理


5. 工作流程

  1. 前端 Vue 页面加载时调用 initWebSocket() 建立连接

  2. 后端 MyWsHandler 保存用户 session 并返回初始未处理数量

  3. 后端业务逻辑变化(如审批通过/新订单)调用 sendMessage(),推送新数量

  4. 前端 onmessage 接收并更新页面显示

  5. 网络断开后,前端自动重连


6. 特点与注意事项

  • 支持实时消息推送,避免轮询数据库

  • 根据 userId 精准推送

  • 可以扩展支持角色、权限等业务逻辑

  • 前端自动重连机制保证稳定性

  • 开发环境可允许跨域,生产环境应限制 origin

  • session.isOpen() 用于判断 WebSocket 是否仍有效

http://www.lryc.cn/news/623778.html

相关文章:

  • 在职老D渗透日记day19:sqli-labs靶场通关(第26a关)get布尔盲注 过滤or和and基础上又过滤了空格和注释符 ‘)闭合
  • 【架构师从入门到进阶】第五章:DNSCDN网关优化思路——第十一节:网关安全-对称与非对称加密
  • 告别“测试滞后”:AI实时测试工具在敏捷开发中的落地经验
  • 【165页PPT】锂电池行业SAP解决方案(附下载方式)
  • 自动驾驶中的传感器技术34——Lidar(9)
  • 定时器中断点灯
  • 记一次安装OpenStack(Stein)-nova报错问题解决
  • 42 C++ STL模板库11-容器4-forward_list
  • 利用标准IO实现寻找文件中字符出现最多次数
  • Opencv 形态学与梯度运算
  • python的软件工程与项目管理课程组学习系统
  • 【LeetCode题解】LeetCode 33. 搜索旋转排序数组
  • Android studio gradle有关设置
  • 一周学会Matplotlib3 Python 数据可视化-多子图及布局实现
  • java之 junit4单元测试Mockito的使用
  • 魔改chromium源码——解除 iframe 的同源策略
  • 《Nursing Research》(护理SCI)LaTeX模板详细教程:从入门到投稿(一)
  • 深度解析 Spring Bean 生命周期
  • Microsoft WebView2
  • SQL详细语法教程(四)约束和多表查询
  • 网络常识-我的电脑啥时安装了证书
  • 【力扣热题100】双指针—— 接雨水
  • 【AI智能体】Dify 搭建发票识别助手操作实战详解
  • 微信小程序 小白gps工具v0.01 使用说明
  • XF 306-2025 阻燃耐火电线电缆检测
  • QUIC浅析
  • C++ 标准模板库 (^^ゞ 致敬 STL 创始人 Alexander Stepanov
  • 笔记本电脑wifi小图标不见了 或者 蓝牙功能消失、电脑开不开机解决方法
  • 基于飞算JavaAI的可视化数据分析集成系统项目实践:从需求到落地的全流程解析
  • Shell脚本-while循环语法结构