spring cloud 使用 webSocket

news2025/2/13 9:13:20

1.引入依赖,(在微服务模块中)

<!-- Spring WebSocket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

2.新建文件

package com.ruoyi.foundation.webSocket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * 开启WebSocket支持
 */

@Configuration
public class WebSocketConfig {
    // 使用boot内置tomcat时需要注入此bean
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
package com.ruoyi.foundation.webSocket;

import lombok.extern.slf4j.Slf4j;

import javax.websocket.Session;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class WebsocketUtil {
    private static final Map<String, Session> ONLINE_SESSION = new ConcurrentHashMap<>();

    /**
     * 添加session
     */
    public static void addSession(String userId, Session session){
        // 一个用户只允许一个session链接
        ONLINE_SESSION.putIfAbsent(userId, session);
        log.info("User [{}] connected. Total online users: {}", userId, ONLINE_SESSION.size());
    }

    /**
     * 移除session
     */
    public static void removeSession(String userId){
        ONLINE_SESSION.remove(userId);
        log.info("User [{}] disconnected. Total online users: {}", userId, ONLINE_SESSION.size());
    }

    /**
     * 给单个用户推送消息
     */
    public static void sendMessage(String userId, String message){
        Session session = ONLINE_SESSION.get(userId);
        if(session == null){
            log.warn("Session for user [{}] not found", userId);
            return;
        }
        sendMessage(session, message);
    }

    public static void sendMessage(Session session, String message) {
        if (session != null) {
            session.getAsyncRemote().sendText(message);
        }
    }

    /**
     * 给所有用户发消息
     */
    public static void sendMessageForAll(String message) {
        ONLINE_SESSION.forEach((userId, session) -> {
            CompletableFuture.runAsync(() -> sendMessage(session, message))
                    .exceptionally(ex -> {
                        log.error("Failed to send message to user [{}]: {}", userId, ex.getMessage());
                        return null;
                    });
        });
    }

    /**
     * 给指定的多个用户推送消息
     */
    public static void sendMessageForUsers(Set<String> userIds, String message) {
        userIds.forEach(userId -> {
            Session session = ONLINE_SESSION.get(userId);
            if (session == null) {
                log.warn("Session for user [{}] not found", userId);
                return;
            }
            CompletableFuture.runAsync(() -> sendMessage(session, message))
                    .exceptionally(ex -> {
                        log.error("Failed to send message to user [{}]: {}", userId, ex.getMessage());
                        return null;
                    });
        });
    }
}
package com.ruoyi.foundation.apicontroller;

import com.google.gson.Gson;
import com.ruoyi.foundation.apicontroller.req.MemorialHallWebsocketReq;
import com.ruoyi.foundation.webSocket.WebsocketUtil;
import io.seata.common.util.StringUtils;
import org.apache.poi.util.StringUtil;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Component
@ServerEndpoint(value = "/api/memorialHallWebsocket/{dailyMissId}/{userId}")
public class MmMemorialHallWebsocketController {
    /**
     * 保存每日一念纪念馆中当前在线的用户ID
     */
    private static final Map<String, List<String>> memorialHallUsers = new ConcurrentHashMap<>();

    private Gson gson=new Gson();

    @OnOpen
    public void onOpen(@PathParam(value = "dailyMissId") String dailyMissId,@PathParam(value = "userId") String userId, Session session) {
        WebsocketUtil.addSession(userId, session);

        List<String> strings = memorialHallUsers.get(dailyMissId);
        if (strings == null){
            List<String> list=new ArrayList<>();
            list.add(userId);
            memorialHallUsers.put(dailyMissId,list);
        }else{
            strings.add(userId);
        }
    }

    @OnClose
    public void onClose(@PathParam(value = "dailyMissId") String dailyMissId,@PathParam(value = "userId") String userId, Session session) {
        WebsocketUtil.removeSession(userId);

        List<String> strings = memorialHallUsers.get(dailyMissId);
        if(strings != null){
            strings.remove(userId);
        }
    }

    @OnMessage
    public void onMessage(@PathParam(value = "dailyMissId") String dailyMissId,@PathParam(value = "userId") String userId, Session session, String message) {
        /*System.out.println(dailyMissId);
        System.out.println(userId);
        System.out.println(session);
        System.out.println(message);*/

        //MemorialHallWebsocketReq memorialHallWebsocketReq = gson.fromJson(message, MemorialHallWebsocketReq.class);

        List<String> strings = memorialHallUsers.get(dailyMissId);
        if(strings == null || strings.isEmpty()){
            return;
        }

        Set<String> collect = strings.stream().filter(userId1 -> !StringUtils.equals(userId1, userId)).collect(Collectors.toSet());

        //对同纪念馆的在线用户进行广播
        WebsocketUtil.sendMessageForUsers(collect,message);
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        try {
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        throwable.printStackTrace();
    }
}

3.网关允许WebScoket

- id: ruoyi-foundationWebSocket
    uri: lb:ws://ruoyi-foundation
    predicates:
        - Path=/foundationWebSocket/**
    filters:
        - StripPrefix=1

4.测试

5.线上nginx配置


    location /mmwzGateWay/ {
        if ($request_method = OPTIONS) {
            add_header Access-Control-Allow-Origin $http_origin;
            add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type, Accept";
            add_header Access-Control-Allow-Methods GET,POST,OPTIONS,HEAD,PUT,DELETE;
            add_header Access-Control-Allow-Credentials true;
            return 200;
        }
 
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

	   # WebSocket 相关的头部配置
	   proxy_http_version 1.1;
	   proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header X-Forwarded-Proto $scheme;
        
        proxy_pass http://mmwz-gateway:8080/;
        #proxy_pass http://www.baidu.com/;
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2297267.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

安科瑞 Acrel-2000ES:解锁储能管理新高度,引领能源未来!

安科瑞 崔丽洁 在能源转型的关键时期&#xff0c;高效的储能管理成为众多企业和项目的核心需求。今天&#xff0c;就给大家介绍一款储能管理的 “神器”—— 安科瑞 Acrel-2000ES 储能能量管理系统。 安科瑞电气可是行业内的 “明星企业”&#xff0c;2003 年成立&#xff0c;2…

基于Django以及vue的电子商城系统设计与实现

基于Django以及vue的电子商城系统设计与实现 引言 随着电子商务的快速发展&#xff0c;越来越多的企业和个人选择搭建线上商城&#xff0c;以提供更加便捷的购物体验。本文基于Python开发了一套电子商城系统&#xff0c;后端采用Django框架&#xff0c;前端使用Vue.js&#x…

电脑变慢、游戏卡顿,你的SSD固态可能快坏了!

电脑用久了&#xff0c;很多人都会感觉速度变慢&#xff0c;开机变慢、文件复制时间变长&#xff0c;甚至莫名其妙的卡顿。你可能怀疑是系统问题&#xff0c;或者内存不够&#xff0c;但往往被忽略的一个关键因素——你的硬盘&#xff0c;可能正在悄悄老化。 硬盘寿命不是永久的…

AI使用场景简单测试

前言 今天来分享下AI的2个实用场景&#xff0c;我这里是使用的博主&#xff1a;小虚竹&#xff0c;搭建的AI服务&#xff0c;用的ChatGPT 4O模型&#xff0c;主要是试了3个场景&#xff0c;服装设计、直播带货话术、检验报告分析。 一、服装设计 对于最后需要的裁片设计上的尺寸…

【并发控制、更新、版本控制】.NET开源ORM框架 SqlSugar 系列

系列文章目录 &#x1f380;&#x1f380;&#x1f380; .NET开源 ORM 框架 SqlSugar 系列 &#x1f380;&#x1f380;&#x1f380; 文章目录 系列文章目录一、并发累计&#xff08;累加&#xff09;1.1 单条批量累计1.2 批量更新并且字段11.3 批量更新并且字段list中对应的…

DeepSeek-R1本地搭建

1. 前言 现在deepseek火上天了&#xff0c;因为各种应用场景,加上DeepSeek一直网络异常&#xff0c;所以本地部署Deepseek成为大家的另一种选择。 目前网络上面关于DeepSeek的部署方式有很多&#xff0c;但是太麻烦了&#xff0c;本文是一篇极为简单的DeepSeek本地部署方式&…

查出 product 表中所有 detail 字段包含 xxx 的完整记录

您可以使用以下 SQL 查询语句来查出 product 表中所有 detail 字段包含 oss.kxlist.com 的完整记录&#xff1a; SELECT * FROM product WHERE INSTR(detail, oss.kxlist.com) > 0;下面是detail字段包含的完整内容 <p><img style"max-width:100%;" src…

Redis存储⑥Redis五大数据类型之 Zset

目录 1. Zset 有序集合 1.1 Zset 有序集合常见命令 zadd zcard zcount zrange zrevrange zrangebyscore&#xff08;弃用&#xff09; zpopmax bzpopmax zpopmin bzpopmin zrank zrevrank zscore zrem zremrangebyrank zremrangebyscore zincrby 1.2 Zset有…

将Excel中的图片保存下载并导出

目录 效果演示 注意事项 核心代码 有需要将excel中的图片解析出来保存到本地的小伙子们看过来&#xff01;&#xff01;&#xff01; 效果演示 注意事项 仅支持xlsx格式&#xff1a;此方法适用于Office 2007及以上版本的.xlsx文件&#xff0c;旧版.xls格式无法使用。 图片名…

SQL注入之布尔和时间盲注,sqli-labs

实验环境&#xff1a; sqli-labs&#xff0c;小皮面板搭建&#xff0c;edge浏览器 apache&#xff1a;2.4.39&#xff0c;MySQL&#xff1a;5.7 PHP&#xff1a;5.39 Python&#xff08;pycharm2023&#xff09;:3 less-8 布尔盲注&#xff1a; 1.我这里是采用最简单的直接采…

基于云计算、大数据与YOLO设计的火灾/火焰目标检测

摘要&#xff1a;本研究针对火灾早期预警检测需求&#xff0c;采用在Kaggle平台获取数据、采用云计算部署的方式&#xff0c;以YOLO11构建模型&#xff0c;使用云计算服务器训练模型。经训练&#xff0c;box loss从约3.5降至1.0&#xff0c;cls loss从约4.0降至1.0&#xff0c;…

YOLO自定义数据集实现K折交叉验证——K-Fold Cross Validation

实现K折交叉验证&#xff08;K-Fold Cross Validation&#xff09;对于YOLO&#xff08;You Only Look Once&#xff09;自定义数据集的目标检测任务可以显著提升模型的可靠性和泛化能力。 1. 数据集准备 首先&#xff0c;你需要确保你的数据集符合YOLO的格式&#xff0c;具体…

go语言简单快速的按顺序遍历kv结构(map)

文章目录 需求描述用map实现按照map的key排序用二维切片实现用结构体实现 需求描述 在go语言中&#xff0c;如果需要对map遍历&#xff0c;每次输出的顺序是不固定的&#xff0c;可以考虑存储为二维切片或结构体。 假设现在需要在页面的下拉菜单中展示一些基础的选项&#xff…

【竞技宝】LOL-LPL:EDG3-0零封LNG

北京时间2月12日,英雄联盟LPL2025正在如火如荼的进行之中,昨日迎来LNG对阵EDG,以下是本场比赛的详细战报。 第一局: EDG:杰斯、赵信、维克托、女枪、芮尔 LNG:猴子、猪妹、飞机、韦鲁斯、布隆 首局比赛,EDG在蓝色方,LNG在红色方。阵容方面,EDG点出了杰斯、赵信、维克托、女枪…

在fedora41中安装钉钉dingtalk_7.6.25.4122001_amd64

在Fedora-Workstation-Live-x86_64-41-1.4中安装钉钉dingtalk_7.6.25.4122001_amd64.deb 到官网下载钉钉Linux客户端com.alibabainc.dingtalk_7.6.25.4122001_amd64.deb https://page.dingtalk.com/wow/z/dingtalk/simple/ddhomedownload#/ 一、直接使用dpkg命令安装deb包报错…

看期货用的指标,可以提示买卖点和K线转折变颜色的主图指标源码下载

A:MA(CLOSE,17)ABS(MA(CLOSE,17)-REF(MA(CLOSE,17),1)); B:MA(CLOSE,17)MA(CLOSE,17)-REF(MA(CLOSE,17),1); 分界线:IF(MA(CLOSE,17)<B,B,MA(CLOSE,17)),COLORFF00FF,LINETHICK2; 操作线:分界线-(EMA(C,3)-分界线),COLOR00FFFF,LINETHICK2; GUP:MA(C,5),COLORWHITE,LINE…

【PS 2022】Adobe Genuine Service Alert 弹出

电脑总是弹出Adobe Genuine Service Alert弹窗 1. 不关掉弹窗并打开任务管理器&#xff0c;找到Adobe Genuine Service Alert&#xff0c;并右键进入文件所在位置 2 在任务管理器中结束进程并将文件夹中的 .exe 文件都使用空文档替换掉 3. 打开PS不弹出弹窗&#xff0c;解决&a…

30天开发操作系统 第 20 天 -- API

前言 大家早上好&#xff0c;今天我们继续努力哦。 昨天我们已经实现了应用程序的运行, 今天我们来实现由应用程序对操作系统功能的调用(即API, 也叫系统调用)。 为什么这样的功能称为“系统调用”(system call)呢&#xff1f;因为它是由应用程序来调用(操作)系统中的功能来完…

蓝桥杯(B组)-每日一题(求最大公约数最小公倍数)

题目&#xff1a; 代码展现&#xff1a; #include<iostream> using namespace std; int main() {int m,n,x,y;cin>>m>>n;//输入两个整数int b;bm%n;//取余数xm;//赋值yn;while(b)//当余数不为0的时候{xy;//辗转相除求最小公约数yb;bx%y;}cout<<y<&…

arduino扩展:Arduino Mega 控制 32 个舵机(参考表情机器人)

参考&#xff1a;表情机器人中使用22个舵机的案例 引言 在电子制作与自动化控制领域&#xff0c;Arduino 凭借其易用性和强大的扩展性备受青睐。Arduino Mega 作为其中功能较为强大的一款开发板&#xff0c;具备丰富的引脚资源&#xff0c;能够实现复杂的控制任务。舵机作为常…