Browse Source

移除sse模块(该模块可能会导致服务宕机)

master
xsx 4 months ago
parent
commit
2420bdbdef
  1. 7
      im-admin-ui/.env.development
  2. 6
      im-admin-ui/.env.production
  3. 4
      im-admin-ui/src/api/login.ts
  4. 6
      im-admin-ui/src/layout/index.vue
  5. 1
      im-admin-ui/src/types/env.d.ts
  6. 45
      im-admin-ui/src/utils/sse.ts
  7. 6
      im-admin-ui/src/views/login.vue
  8. 12
      im-admin/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java
  9. 8
      im-admin/ruoyi-admin/src/main/resources/application.yml
  10. 1
      im-admin/ruoyi-common/pom.xml
  11. 7
      im-admin/ruoyi-common/ruoyi-common-bom/pom.xml
  12. 62
      im-admin/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/SseException.java
  13. 19
      im-admin/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfig.java
  14. 36
      im-admin/ruoyi-common/ruoyi-common-sse/pom.xml
  15. 36
      im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseAutoConfiguration.java
  16. 21
      im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseProperties.java
  17. 87
      im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java
  18. 145
      im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/core/SseEmitterManager.java
  19. 29
      im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/dto/SseMessageDto.java
  20. 48
      im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/listener/SseTopicListener.java
  21. 58
      im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/utils/SseMessageUtils.java
  22. 1
      im-admin/ruoyi-common/ruoyi-common-sse/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  23. 13
      im-admin/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java
  24. 5
      im-admin/ruoyi-modules/ruoyi-system/pom.xml
  25. 4
      im-admin/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysNoticeController.java

7
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

6
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

4
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'

6
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 });
};

1
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;

45
im-admin-ui/src/utils/sse.ts

@ -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;
});
};

6
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);

12
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);
}

8
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
# 设置访问源地址

1
im-admin/ruoyi-common/pom.xml

@ -31,7 +31,6 @@
<module>ruoyi-common-encrypt</module>
<module>ruoyi-common-tenant</module>
<module>ruoyi-common-websocket</module>
<module>ruoyi-common-sse</module>
</modules>
<artifactId>ruoyi-common</artifactId>

7
im-admin/ruoyi-common/ruoyi-common-bom/pom.xml

@ -165,13 +165,6 @@
<version>${revision}</version>
</dependency>
<!-- SSE模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-sse</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>

62
im-admin/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/SseException.java

@ -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;
}
}

19
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));
}
}

36
im-admin/ruoyi-common/ruoyi-common-sse/pom.xml

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common-sse</artifactId>
<description>
ruoyi-common-sse 模块
</description>
<dependencies>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-satoken</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-json</artifactId>
</dependency>
</dependencies>
</project>

36
im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseAutoConfiguration.java

@ -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);
}
}

21
im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/config/SseProperties.java

@ -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;
}

87
im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java

@ -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<Void> 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<Void> 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<Void> send(String msg) {
sseEmitterManager.publishAll(msg);
return R.ok();
}
/**
* 清理资源此方法目前不执行任何操作但避免因未实现而导致错误
*/
@Override
public void destroy() throws Exception {
// 销毁时不需要做什么 此方法避免无用操作报错
}
}

145
im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/core/SseEmitterManager.java

@ -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<Long, Map<String, SseEmitter>> 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<String, SseEmitter> 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<String, SseEmitter> 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<SseMessageDto> consumer) {
RedisUtils.subscribe(SSE_TOPIC, SseMessageDto.class, consumer);
}
/**
* 向指定的用户会话发送消息
*
* @param userId 要发送消息的用户id
* @param message 要发送的消息内容
*/
public void sendMessage(Long userId, String message) {
Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId);
if (emitters != null) {
for (Map.Entry<String, SseEmitter> 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);
});
}
}

29
im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/dto/SseMessageDto.java

@ -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<Long> userIds;
/**
* 需要发送的消息
*/
private String message;
}

48
im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/listener/SseTopicListener.java

@ -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;
}
}

58
im-admin/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/utils/SseMessageUtils.java

@ -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);
}
}

1
im-admin/ruoyi-common/ruoyi-common-sse/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@ -1 +0,0 @@
org.dromara.common.sse.config.SseAutoConfiguration

13
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异常
*/

5
im-admin/ruoyi-modules/ruoyi-system/pom.xml

@ -90,11 +90,6 @@
<artifactId>ruoyi-common-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-sse</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-minio</artifactId>

4
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();
}

Loading…
Cancel
Save