From 2420bdbdef883f3b9e2c7666f8f6523e5ef927a1 Mon Sep 17 00:00:00 2001 From: xsx <825657193@qq.com> Date: Tue, 25 Nov 2025 17:24:30 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4sse=E6=A8=A1=E5=9D=97(?= =?UTF-8?q?=E8=AF=A5=E6=A8=A1=E5=9D=97=E5=8F=AF=E8=83=BD=E4=BC=9A=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E6=9C=8D=E5=8A=A1=E5=AE=95=E6=9C=BA)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- im-admin-ui/.env.development | 7 +- im-admin-ui/.env.production | 6 +- im-admin-ui/src/api/login.ts | 4 - im-admin-ui/src/layout/index.vue | 6 +- im-admin-ui/src/types/env.d.ts | 1 - im-admin-ui/src/utils/sse.ts | 45 ------ im-admin-ui/src/views/login.vue | 6 +- .../web/controller/AuthController.java | 12 +- .../src/main/resources/application.yml | 8 +- im-admin/ruoyi-common/pom.xml | 1 - .../ruoyi-common/ruoyi-common-bom/pom.xml | 7 - .../common/core/exception/SseException.java | 62 -------- .../security/config/SecurityConfig.java | 19 +-- .../ruoyi-common/ruoyi-common-sse/pom.xml | 36 ----- .../sse/config/SseAutoConfiguration.java | 36 ----- .../common/sse/config/SseProperties.java | 21 --- .../common/sse/controller/SseController.java | 87 ----------- .../common/sse/core/SseEmitterManager.java | 145 ------------------ .../dromara/common/sse/dto/SseMessageDto.java | 29 ---- .../common/sse/listener/SseTopicListener.java | 48 ------ .../common/sse/utils/SseMessageUtils.java | 58 ------- ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../web/handler/GlobalExceptionHandler.java | 13 -- im-admin/ruoyi-modules/ruoyi-system/pom.xml | 5 - .../system/SysNoticeController.java | 4 +- 25 files changed, 22 insertions(+), 645 deletions(-) delete mode 100644 im-admin-ui/src/utils/sse.ts delete mode 100644 im-admin/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/SseException.java delete mode 100644 im-admin/ruoyi-common/ruoyi-common-sse/pom.xml delete mode 100644 im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseAutoConfiguration.java delete mode 100644 im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseProperties.java delete mode 100644 im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java delete mode 100644 im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/core/SseEmitterManager.java delete mode 100644 im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/dto/SseMessageDto.java delete mode 100644 im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/listener/SseTopicListener.java delete mode 100644 im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/utils/SseMessageUtils.java delete mode 100644 im-admin/ruoyi-common/ruoyi-common-sse/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/im-admin-ui/.env.development b/im-admin-ui/.env.development index 59875df..2591860 100644 --- a/im-admin-ui/.env.development +++ b/im-admin-ui/.env.development @@ -15,8 +15,5 @@ VITE_APP_PORT = 3000 # 客户端id VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e' -# websocket 开关 默认使用sse推送 -VITE_APP_WEBSOCKET = false - -# sse 开关 -VITE_APP_SSE = true +# websocket 开关 +VITE_APP_WEBSOCKET = true diff --git a/im-admin-ui/.env.production b/im-admin-ui/.env.production index b02e18a..1b3e46e 100644 --- a/im-admin-ui/.env.production +++ b/im-admin-ui/.env.production @@ -13,8 +13,6 @@ VITE_APP_BASE_API = 'https://www.boxim.online/adm/api' # 客户端id VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e' -# websocket 开关 默认使用sse推送 -VITE_APP_WEBSOCKET = false +# websocket 开关 +VITE_APP_WEBSOCKET = true -# sse 开关 -VITE_APP_SSE = true diff --git a/im-admin-ui/src/api/login.ts b/im-admin-ui/src/api/login.ts index c7c291e..b6955de 100644 --- a/im-admin-ui/src/api/login.ts +++ b/im-admin-ui/src/api/login.ts @@ -51,10 +51,6 @@ export function register(data: any) { * 注销 */ export function logout() { - request({ - url: '/resource/sse/close', - method: 'get' - }); return request({ url: '/auth/logout', method: 'post' diff --git a/im-admin-ui/src/layout/index.vue b/im-admin-ui/src/layout/index.vue index ce47a30..8ef0af9 100644 --- a/im-admin-ui/src/layout/index.vue +++ b/im-admin-ui/src/layout/index.vue @@ -27,7 +27,7 @@ import { AppMain, Navbar, Settings, TagsView } from './components'; import useAppStore from '@/store/modules/app'; import useSettingsStore from '@/store/modules/settings'; import { initWebSocket } from '@/utils/websocket'; -import { initSSE } from "@/utils/sse"; + const settingsStore = useSettingsStore(); const theme = computed(() => settingsStore.theme); @@ -72,10 +72,6 @@ onMounted(() => { initWebSocket(protocol + window.location.host + import.meta.env.VITE_APP_BASE_API + '/resource/websocket'); }); -onMounted(() => { - initSSE(import.meta.env.VITE_APP_BASE_API + '/resource/sse'); -}); - const handleClickOutside = () => { useAppStore().closeSideBar({ withoutAnimation: false }); }; diff --git a/im-admin-ui/src/types/env.d.ts b/im-admin-ui/src/types/env.d.ts index 1fb9f62..777c858 100644 --- a/im-admin-ui/src/types/env.d.ts +++ b/im-admin-ui/src/types/env.d.ts @@ -19,7 +19,6 @@ interface ImportMetaEnv { VITE_APP_RSA_PRIVATE_KEY: string; VITE_APP_CLIENT_ID: string; VITE_APP_WEBSOCKET: string; - VITE_APP_SSE: string; } interface ImportMeta { readonly env: ImportMetaEnv; diff --git a/im-admin-ui/src/utils/sse.ts b/im-admin-ui/src/utils/sse.ts deleted file mode 100644 index 9174f0d..0000000 --- a/im-admin-ui/src/utils/sse.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { getToken } from '@/utils/auth'; -import { ElNotification } from 'element-plus'; -import useNoticeStore from '@/store/modules/notice'; - -// 初始化 -export const initSSE = (url: any) => { - if (import.meta.env.VITE_APP_SSE === 'false') { - return; - } - - url = url + '?Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID - const { - data, - error - } = useEventSource(url, [], { - autoReconnect: { - retries: 10, - delay: 3000, - onFailed() { - console.log('Failed to connect after 10 retries'); - } - } - }); - - watch(error, () => { - console.log('SSE connection error:', error.value); - error.value = null; - }); - - watch(data, () => { - if (!data.value) return; - useNoticeStore().addNotice({ - message: data.value, - read: false, - time: new Date().toLocaleString() - }); - ElNotification({ - title: '消息', - message: data.value, - type: 'success', - duration: 3000 - }); - data.value = null; - }); -}; diff --git a/im-admin-ui/src/views/login.vue b/im-admin-ui/src/views/login.vue index a04cbc2..e3fc8fd 100644 --- a/im-admin-ui/src/views/login.vue +++ b/im-admin-ui/src/views/login.vue @@ -49,7 +49,7 @@ import { getCodeImg, getTenantList } from '@/api/login'; import { useUserStore } from '@/store/modules/user'; import { LoginData, TenantVO } from '@/api/types'; import { to } from 'await-to-js'; -import logo from '@/assets/logo/logo.png'; + const userStore = useUserStore(); const router = useRouter(); @@ -73,9 +73,9 @@ const loginRules: ElFormRules = { const codeUrl = ref(''); const loading = ref(false); // 验证码开关 -const captchaEnabled = ref(true); +const captchaEnabled = ref(false); // 租户开关 -const tenantEnabled = ref(true); +const tenantEnabled = ref(false); // 注册开关 const register = ref(false); diff --git a/im-admin/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java b/im-admin/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java index 1fe8eae..870cee0 100644 --- a/im-admin/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java +++ b/im-admin/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java @@ -23,10 +23,10 @@ import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.social.config.properties.SocialLoginConfigProperties; import org.dromara.common.social.config.properties.SocialProperties; -import org.dromara.common.social.utils.SocialUtils; -import org.dromara.common.sse.dto.SseMessageDto; -import org.dromara.common.sse.utils.SseMessageUtils; +import org.dromara.common.social.utils.SocialUtils;; import org.dromara.common.tenant.helper.TenantHelper; +import org.dromara.common.websocket.dto.WebSocketMessageDto; +import org.dromara.common.websocket.utils.WebSocketUtils; import org.dromara.system.domain.bo.SysTenantBo; import org.dromara.system.domain.vo.SysClientVo; import org.dromara.system.domain.vo.SysTenantVo; @@ -102,10 +102,10 @@ public class AuthController { Long userId = LoginHelper.getUserId(); scheduledExecutorService.schedule(() -> { - SseMessageDto dto = new SseMessageDto(); + WebSocketMessageDto dto = new WebSocketMessageDto(); dto.setMessage("欢迎登录盒子IM后台管理系统"); - dto.setUserIds(List.of(userId)); - SseMessageUtils.publishMessage(dto); + dto.setSessionKeys(List.of(userId)); + WebSocketUtils.publishMessage(dto); }, 5, TimeUnit.SECONDS); return R.ok(loginVo); } diff --git a/im-admin/ruoyi-admin/src/main/resources/application.yml b/im-admin/ruoyi-admin/src/main/resources/application.yml index e851c3b..ded7f00 100644 --- a/im-admin/ruoyi-admin/src/main/resources/application.yml +++ b/im-admin/ruoyi-admin/src/main/resources/application.yml @@ -213,16 +213,10 @@ lock4j: # 分布式锁的超时时间,默认为 30 秒 expire: 30000 - ---- # 默认/推荐使用sse推送 -sse: - enabled: true - path: /resource/sse - --- # websocket websocket: # 如果关闭 需要和前端开关一起关闭 - enabled: false + enabled: true # 路径 path: /resource/websocket # 设置访问源地址 diff --git a/im-admin/ruoyi-common/pom.xml b/im-admin/ruoyi-common/pom.xml index a46c7c9..2b6f7e0 100644 --- a/im-admin/ruoyi-common/pom.xml +++ b/im-admin/ruoyi-common/pom.xml @@ -31,7 +31,6 @@ ruoyi-common-encrypt ruoyi-common-tenant ruoyi-common-websocket - ruoyi-common-sse ruoyi-common diff --git a/im-admin/ruoyi-common/ruoyi-common-bom/pom.xml b/im-admin/ruoyi-common/ruoyi-common-bom/pom.xml index 36d00b8..783fe3e 100644 --- a/im-admin/ruoyi-common/ruoyi-common-bom/pom.xml +++ b/im-admin/ruoyi-common/ruoyi-common-bom/pom.xml @@ -165,13 +165,6 @@ ${revision} - - - org.dromara - ruoyi-common-sse - ${revision} - - diff --git a/im-admin/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/SseException.java b/im-admin/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/SseException.java deleted file mode 100644 index a76e16d..0000000 --- a/im-admin/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/SseException.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.dromara.common.core.exception; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - -import java.io.Serial; - -/** - * sse 特制异常 - * - * @author LionLi - */ -@Data -@EqualsAndHashCode(callSuper = true) -@NoArgsConstructor -@AllArgsConstructor -public final class SseException extends RuntimeException { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 错误码 - */ - private Integer code; - - /** - * 错误提示 - */ - private String message; - - /** - * 错误明细,内部调试错误 - */ - private String detailMessage; - - public SseException(String message) { - this.message = message; - } - - public SseException(String message, Integer code) { - this.message = message; - this.code = code; - } - - @Override - public String getMessage() { - return message; - } - - public SseException setMessage(String message) { - this.message = message; - return this; - } - - public SseException setDetailMessage(String detailMessage) { - this.detailMessage = detailMessage; - return this; - } -} diff --git a/im-admin/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfig.java b/im-admin/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfig.java index a4e921f..33175fa 100644 --- a/im-admin/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfig.java +++ b/im-admin/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfig.java @@ -11,7 +11,6 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.constant.HttpStatus; -import org.dromara.common.core.exception.SseException; import org.dromara.common.core.utils.ServletUtils; import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.StringUtils; @@ -57,11 +56,7 @@ public class SecurityConfig implements WebMvcConfigurer { try { StpUtil.checkLogin(); } catch (NotLoginException e) { - if (request.getRequestURI().contains("sse")) { - throw new SseException(e.getMessage(), e.getCode()); - } else { - throw e; - } + throw e; } // 检查 header 与 param 里的 clientid 与 token 里的是否一致 @@ -70,8 +65,7 @@ public class SecurityConfig implements WebMvcConfigurer { String clientId = StpUtil.getExtra(LoginHelper.CLIENT_KEY).toString(); if (!StringUtils.equalsAny(clientId, headerCid, paramCid)) { // token 无效 - throw NotLoginException.newInstance(StpUtil.getLoginType(), - "-100", "客户端ID与Token不匹配", + throw NotLoginException.newInstance(StpUtil.getLoginType(), "-100", "客户端ID与Token不匹配", StpUtil.getTokenValue()); } @@ -94,12 +88,9 @@ public class SecurityConfig implements WebMvcConfigurer { public SaServletFilter getSaServletFilter() { String username = SpringUtils.getProperty("spring.boot.admin.client.username"); String password = SpringUtils.getProperty("spring.boot.admin.client.password"); - return new SaServletFilter() - .addInclude("/actuator", "/actuator/**") - .setAuth(obj -> { - SaHttpBasicUtil.check(username + ":" + password); - }) - .setError(e -> SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED)); + return new SaServletFilter().addInclude("/actuator", "/actuator/**").setAuth(obj -> { + SaHttpBasicUtil.check(username + ":" + password); + }).setError(e -> SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED)); } } diff --git a/im-admin/ruoyi-common/ruoyi-common-sse/pom.xml b/im-admin/ruoyi-common/ruoyi-common-sse/pom.xml deleted file mode 100644 index ae44c98..0000000 --- a/im-admin/ruoyi-common/ruoyi-common-sse/pom.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - org.dromara - ruoyi-common - ${revision} - - 4.0.0 - - ruoyi-common-sse - - - ruoyi-common-sse 模块 - - - - - org.dromara - ruoyi-common-core - - - org.dromara - ruoyi-common-redis - - - org.dromara - ruoyi-common-satoken - - - org.dromara - ruoyi-common-json - - - diff --git a/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseAutoConfiguration.java b/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseAutoConfiguration.java deleted file mode 100644 index 0cf8054..0000000 --- a/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseAutoConfiguration.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.dromara.common.sse.config; - -import org.dromara.common.sse.controller.SseController; -import org.dromara.common.sse.core.SseEmitterManager; -import org.dromara.common.sse.listener.SseTopicListener; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -/** - * SSE 自动装配 - * - * @author Lion Li - */ -@AutoConfiguration -@ConditionalOnProperty(value = "sse.enabled", havingValue = "true") -@EnableConfigurationProperties(SseProperties.class) -public class SseAutoConfiguration { - - @Bean - public SseEmitterManager sseEmitterManager() { - return new SseEmitterManager(); - } - - @Bean - public SseTopicListener sseTopicListener() { - return new SseTopicListener(); - } - - @Bean - public SseController sseController(SseEmitterManager sseEmitterManager) { - return new SseController(sseEmitterManager); - } - -} diff --git a/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseProperties.java b/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseProperties.java deleted file mode 100644 index ce4e173..0000000 --- a/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseProperties.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.dromara.common.sse.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * SSE 配置项 - * - * @author Lion Li - */ -@Data -@ConfigurationProperties("sse") -public class SseProperties { - - private Boolean enabled; - - /** - * 路径 - */ - private String path; -} diff --git a/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java b/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java deleted file mode 100644 index e5331e4..0000000 --- a/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.dromara.common.sse.controller; - -import cn.dev33.satoken.annotation.SaIgnore; -import cn.dev33.satoken.stp.StpUtil; -import lombok.RequiredArgsConstructor; -import org.dromara.common.core.domain.R; -import org.dromara.common.satoken.utils.LoginHelper; -import org.dromara.common.sse.core.SseEmitterManager; -import org.dromara.common.sse.dto.SseMessageDto; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -import java.util.List; - -/** - * SSE 控制器 - * - * @author Lion Li - */ -@RestController -@ConditionalOnProperty(value = "sse.enabled", havingValue = "true") -@RequiredArgsConstructor -public class SseController implements DisposableBean { - - private final SseEmitterManager sseEmitterManager; - - /** - * 建立 SSE 连接 - */ - @GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) - public SseEmitter connect() { - String tokenValue = StpUtil.getTokenValue(); - Long userId = LoginHelper.getUserId(); - return sseEmitterManager.connect(userId, tokenValue); - } - - /** - * 关闭 SSE 连接 - */ - @SaIgnore - @GetMapping(value = "${sse.path}/close") - public R close() { - String tokenValue = StpUtil.getTokenValue(); - Long userId = LoginHelper.getUserId(); - sseEmitterManager.disconnect(userId, tokenValue); - return R.ok(); - } - - /** - * 向特定用户发送消息 - * - * @param userId 目标用户的 ID - * @param msg 要发送的消息内容 - */ - @GetMapping(value = "${sse.path}/send") - public R send(Long userId, String msg) { - SseMessageDto dto = new SseMessageDto(); - dto.setUserIds(List.of(userId)); - dto.setMessage(msg); - sseEmitterManager.publishMessage(dto); - return R.ok(); - } - - /** - * 向所有用户发送消息 - * - * @param msg 要发送的消息内容 - */ - @GetMapping(value = "${sse.path}/sendAll") - public R send(String msg) { - sseEmitterManager.publishAll(msg); - return R.ok(); - } - - /** - * 清理资源。此方法目前不执行任何操作,但避免因未实现而导致错误 - */ - @Override - public void destroy() throws Exception { - // 销毁时不需要做什么 此方法避免无用操作报错 - } - -} diff --git a/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/core/SseEmitterManager.java b/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/core/SseEmitterManager.java deleted file mode 100644 index 0bfe0ca..0000000 --- a/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/core/SseEmitterManager.java +++ /dev/null @@ -1,145 +0,0 @@ -package org.dromara.common.sse.core; - -import lombok.extern.slf4j.Slf4j; -import org.dromara.common.redis.utils.RedisUtils; -import org.dromara.common.sse.dto.SseMessageDto; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -import java.io.IOException; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; - -/** - * 管理 Server-Sent Events (SSE) 连接 - * - * @author Lion Li - */ -@Slf4j -public class SseEmitterManager { - - /** - * 订阅的频道 - */ - private final static String SSE_TOPIC = "global:sse"; - - private final static Map> USER_TOKEN_EMITTERS = new ConcurrentHashMap<>(); - - /** - * 建立与指定用户的 SSE 连接 - * - * @param userId 用户的唯一标识符,用于区分不同用户的连接 - * @param token 用户的唯一令牌,用于识别具体的连接 - * @return 返回一个 SseEmitter 实例,客户端可以通过该实例接收 SSE 事件 - */ - public SseEmitter connect(Long userId, String token) { - // 从 USER_TOKEN_EMITTERS 中获取或创建当前用户的 SseEmitter 映射表(ConcurrentHashMap) - // 每个用户可以有多个 SSE 连接,通过 token 进行区分 - Map emitters = USER_TOKEN_EMITTERS.computeIfAbsent(userId, k -> new ConcurrentHashMap<>()); - - // 创建一个新的 SseEmitter 实例,超时时间设置为 0 表示无限制 - SseEmitter emitter = new SseEmitter(0L); - - emitters.put(token, emitter); - - // 当 emitter 完成、超时或发生错误时,从映射表中移除对应的 token - emitter.onCompletion(() -> emitters.remove(token)); - emitter.onTimeout(() -> emitters.remove(token)); - emitter.onError((e) -> emitters.remove(token)); - - try { - // 向客户端发送一条连接成功的事件 - emitter.send(SseEmitter.event().comment("connected")); - } catch (IOException e) { - // 如果发送消息失败,则从映射表中移除 emitter - emitters.remove(token); - } - return emitter; - } - - /** - * 断开指定用户的 SSE 连接 - * - * @param userId 用户的唯一标识符,用于区分不同用户的连接 - * @param token 用户的唯一令牌,用于识别具体的连接 - */ - public void disconnect(Long userId, String token) { - Map emitters = USER_TOKEN_EMITTERS.get(userId); - if (emitters != null) { - try { - emitters.get(token).send(SseEmitter.event().comment("disconnected")); - } catch (Exception ignore) { - } - emitters.remove(token); - } - } - - /** - * 订阅SSE消息主题,并提供一个消费者函数来处理接收到的消息 - * - * @param consumer 处理SSE消息的消费者函数 - */ - public void subscribeMessage(Consumer consumer) { - RedisUtils.subscribe(SSE_TOPIC, SseMessageDto.class, consumer); - } - - /** - * 向指定的用户会话发送消息 - * - * @param userId 要发送消息的用户id - * @param message 要发送的消息内容 - */ - public void sendMessage(Long userId, String message) { - Map emitters = USER_TOKEN_EMITTERS.get(userId); - if (emitters != null) { - for (Map.Entry entry : emitters.entrySet()) { - try { - entry.getValue().send(SseEmitter.event() - .name("message") - .data(message)); - } catch (Exception e) { - emitters.remove(entry.getKey()); - } - } - } - } - - /** - * 本机全用户会话发送消息 - * - * @param message 要发送的消息内容 - */ - public void sendMessage(String message) { - for (Long userId : USER_TOKEN_EMITTERS.keySet()) { - sendMessage(userId, message); - } - } - - /** - * 发布SSE订阅消息 - * - * @param sseMessageDto 要发布的SSE消息对象 - */ - public void publishMessage(SseMessageDto sseMessageDto) { - SseMessageDto broadcastMessage = new SseMessageDto(); - broadcastMessage.setMessage(sseMessageDto.getMessage()); - broadcastMessage.setUserIds(sseMessageDto.getUserIds()); - RedisUtils.publish(SSE_TOPIC, broadcastMessage, consumer -> { - log.info("SSE发送主题订阅消息topic:{} session keys:{} message:{}", - SSE_TOPIC, sseMessageDto.getUserIds(), sseMessageDto.getMessage()); - }); - } - - /** - * 向所有的用户发布订阅的消息(群发) - * - * @param message 要发布的消息内容 - */ - public void publishAll(String message) { - SseMessageDto broadcastMessage = new SseMessageDto(); - broadcastMessage.setMessage(message); - RedisUtils.publish(SSE_TOPIC, broadcastMessage, consumer -> { - log.info("SSE发送主题订阅消息topic:{} message:{}", SSE_TOPIC, message); - }); - } -} diff --git a/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/dto/SseMessageDto.java b/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/dto/SseMessageDto.java deleted file mode 100644 index a2e1210..0000000 --- a/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/dto/SseMessageDto.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.dromara.common.sse.dto; - -import lombok.Data; - -import java.io.Serial; -import java.io.Serializable; -import java.util.List; - -/** - * 消息的dto - * - * @author zendwang - */ -@Data -public class SseMessageDto implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 需要推送到的session key 列表 - */ - private List userIds; - - /** - * 需要发送的消息 - */ - private String message; -} diff --git a/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/listener/SseTopicListener.java b/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/listener/SseTopicListener.java deleted file mode 100644 index 7a4dff1..0000000 --- a/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/listener/SseTopicListener.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.dromara.common.sse.listener; - -import cn.hutool.core.collection.CollUtil; -import lombok.extern.slf4j.Slf4j; -import org.dromara.common.sse.core.SseEmitterManager; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; -import org.springframework.core.Ordered; - -/** - * SSE 主题订阅监听器 - * - * @author Lion Li - */ -@Slf4j -public class SseTopicListener implements ApplicationRunner, Ordered { - - @Autowired - private SseEmitterManager sseEmitterManager; - - /** - * 在Spring Boot应用程序启动时初始化SSE主题订阅监听器 - * - * @param args 应用程序参数 - * @throws Exception 初始化过程中可能抛出的异常 - */ - @Override - public void run(ApplicationArguments args) throws Exception { - sseEmitterManager.subscribeMessage((message) -> { - log.info("SSE主题订阅收到消息session keys={} message={}", message.getUserIds(), message.getMessage()); - // 如果key不为空就按照key发消息 如果为空就群发 - if (CollUtil.isNotEmpty(message.getUserIds())) { - message.getUserIds().forEach(key -> { - sseEmitterManager.sendMessage(key, message.getMessage()); - }); - } else { - sseEmitterManager.sendMessage(message.getMessage()); - } - }); - log.info("初始化SSE主题订阅监听器成功"); - } - - @Override - public int getOrder() { - return -1; - } -} diff --git a/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/utils/SseMessageUtils.java b/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/utils/SseMessageUtils.java deleted file mode 100644 index c6abdc8..0000000 --- a/im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/utils/SseMessageUtils.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.dromara.common.sse.utils; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.dromara.common.core.utils.SpringUtils; -import org.dromara.common.sse.core.SseEmitterManager; -import org.dromara.common.sse.dto.SseMessageDto; - -/** - * SSE工具类 - * - * @author Lion Li - */ -@Slf4j -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class SseMessageUtils { - - private final static SseEmitterManager MANAGER = SpringUtils.getBean(SseEmitterManager.class); - - /** - * 向指定的WebSocket会话发送消息 - * - * @param userId 要发送消息的用户id - * @param message 要发送的消息内容 - */ - public static void sendMessage(Long userId, String message) { - MANAGER.sendMessage(userId, message); - } - - /** - * 本机全用户会话发送消息 - * - * @param message 要发送的消息内容 - */ - public static void sendMessage(String message) { - MANAGER.sendMessage(message); - } - - /** - * 发布SSE订阅消息 - * - * @param sseMessageDto 要发布的SSE消息对象 - */ - public static void publishMessage(SseMessageDto sseMessageDto) { - MANAGER.publishMessage(sseMessageDto); - } - - /** - * 向所有的用户发布订阅的消息(群发) - * - * @param message 要发布的消息内容 - */ - public static void publishAll(String message) { - MANAGER.publishAll(message); - } - -} diff --git a/im-admin/ruoyi-common/ruoyi-common-sse/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/im-admin/ruoyi-common/ruoyi-common-sse/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index b809713..0000000 --- a/im-admin/ruoyi-common/ruoyi-common-sse/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -org.dromara.common.sse.config.SseAutoConfiguration diff --git a/im-admin/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java b/im-admin/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java index 321c226..41031a0 100644 --- a/im-admin/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java +++ b/im-admin/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java @@ -9,10 +9,8 @@ import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.domain.R; import org.dromara.common.core.exception.ServiceException; -import org.dromara.common.core.exception.SseException; import org.dromara.common.core.exception.base.BaseException; import org.dromara.common.core.utils.StreamUtils; -import org.dromara.common.json.utils.JsonUtils; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.validation.BindException; import org.springframework.web.HttpRequestMethodNotSupportedException; @@ -56,17 +54,6 @@ public class GlobalExceptionHandler { return ObjectUtil.isNotNull(code) ? R.fail(code, e.getMessage()) : R.fail(e.getMessage()); } - /** - * 认证失败 - */ - @ResponseStatus(org.springframework.http.HttpStatus.UNAUTHORIZED) - @ExceptionHandler(SseException.class) - public String handleNotLoginException(SseException e, HttpServletRequest request) { - String requestURI = request.getRequestURI(); - log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage()); - return JsonUtils.toJsonString(R.fail(HttpStatus.HTTP_UNAUTHORIZED, "认证失败,无法访问系统资源")); - } - /** * servlet异常 */ diff --git a/im-admin/ruoyi-modules/ruoyi-system/pom.xml b/im-admin/ruoyi-modules/ruoyi-system/pom.xml index bd391ab..127c892 100644 --- a/im-admin/ruoyi-modules/ruoyi-system/pom.xml +++ b/im-admin/ruoyi-modules/ruoyi-system/pom.xml @@ -90,11 +90,6 @@ ruoyi-common-websocket - - org.dromara - ruoyi-common-sse - - org.dromara ruoyi-common-minio diff --git a/im-admin/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysNoticeController.java b/im-admin/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysNoticeController.java index 5d65137..a0aa26e 100644 --- a/im-admin/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysNoticeController.java +++ b/im-admin/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysNoticeController.java @@ -8,8 +8,8 @@ import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; -import org.dromara.common.sse.utils.SseMessageUtils; import org.dromara.common.web.core.BaseController; +import org.dromara.common.websocket.utils.WebSocketUtils; import org.dromara.system.domain.bo.SysNoticeBo; import org.dromara.system.domain.vo.SysNoticeVo; import org.dromara.system.service.ISysNoticeService; @@ -62,7 +62,7 @@ public class SysNoticeController extends BaseController { return R.fail(); } String type = dictService.getDictLabel("sys_notice_type", notice.getNoticeType()); - SseMessageUtils.publishAll("[" + type + "] " + notice.getNoticeTitle()); + WebSocketUtils.publishAll("[" + type + "] " + notice.getNoticeTitle()); return R.ok(); }