diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml
index dbd6e2f3c..bbae1a273 100644
--- a/yudao-dependencies/pom.xml
+++ b/yudao-dependencies/pom.xml
@@ -175,6 +175,12 @@
${revision}
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-websocket
+ ${revision}
+
+
com.github.xiaoymin
knife4j-openapi3-spring-boot-starter
diff --git a/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQConsumerAutoConfiguration.java
similarity index 92%
rename from yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQAutoConfiguration.java
rename to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQConsumerAutoConfiguration.java
index bbc63b719..d02e84b14 100644
--- a/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQAutoConfiguration.java
+++ b/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQConsumerAutoConfiguration.java
@@ -5,7 +5,6 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.system.SystemUtil;
import cn.iocoder.yudao.framework.common.enums.DocumentEnum;
import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
-import cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
import cn.iocoder.yudao.framework.mq.redis.core.job.RedisPendingMessageResendJob;
import cn.iocoder.yudao.framework.mq.redis.core.pubsub.AbstractRedisChannelMessageListener;
import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener;
@@ -23,7 +22,6 @@ import org.springframework.data.redis.connection.stream.ReadOffset;
import org.springframework.data.redis.connection.stream.StreamOffset;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.stream.StreamMessageListenerContainer;
@@ -33,30 +31,19 @@ import java.util.List;
import java.util.Properties;
/**
- * 消息队列配置类
+ * Redis 消息队列 Consumer 配置类
*
* @author 芋道源码
*/
@Slf4j
@EnableScheduling // 启用定时任务,用于 RedisPendingMessageResendJob 重发消息
@AutoConfiguration(after = YudaoRedisAutoConfiguration.class)
-public class YudaoRedisMQAutoConfiguration {
-
- @Bean
- public RedisMQTemplate redisMQTemplate(StringRedisTemplate redisTemplate,
- List interceptors) {
- RedisMQTemplate redisMQTemplate = new RedisMQTemplate(redisTemplate);
- // 添加拦截器
- interceptors.forEach(redisMQTemplate::addInterceptor);
- return redisMQTemplate;
- }
-
- // ========== 消费者相关 ==========
+public class YudaoRedisMQConsumerAutoConfiguration {
/**
* 创建 Redis Pub/Sub 广播消费的容器
*/
- @Bean(initMethod = "start", destroyMethod = "stop")
+ @Bean
@ConditionalOnBean(AbstractRedisChannelMessageListener.class) // 只有 AbstractChannelMessageListener 存在的时候,才需要注册 Redis pubsub 监听
public RedisMessageListenerContainer redisMessageListenerContainer(
RedisMQTemplate redisMQTemplate, List> listeners) {
diff --git a/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQProducerAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQProducerAutoConfiguration.java
new file mode 100644
index 000000000..c1950c489
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQProducerAutoConfiguration.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.framework.mq.redis.config;
+
+import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
+import cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
+import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+import java.util.List;
+
+/**
+ * Redis 消息队列 Producer 配置类
+ *
+ * @author 芋道源码
+ */
+@Slf4j
+@AutoConfiguration(after = YudaoRedisAutoConfiguration.class)
+public class YudaoRedisMQProducerAutoConfiguration {
+
+ @Bean
+ public RedisMQTemplate redisMQTemplate(StringRedisTemplate redisTemplate,
+ List interceptors) {
+ RedisMQTemplate redisMQTemplate = new RedisMQTemplate(redisTemplate);
+ // 添加拦截器
+ interceptors.forEach(redisMQTemplate::addInterceptor);
+ return redisMQTemplate;
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-mq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-framework/yudao-spring-boot-starter-mq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 660865453..213850b94 100644
--- a/yudao-framework/yudao-spring-boot-starter-mq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/yudao-framework/yudao-spring-boot-starter-mq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1,2 +1,3 @@
-cn.iocoder.yudao.framework.mq.redis.config.YudaoRedisMQAutoConfiguration
+cn.iocoder.yudao.framework.mq.redis.config.YudaoRedisMQProducerAutoConfiguration
+cn.iocoder.yudao.framework.mq.redis.config.YudaoRedisMQConsumerAutoConfiguration
cn.iocoder.yudao.framework.mq.rabbitmq.config.YudaoRabbitMQAutoConfiguration
\ No newline at end of file
diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/SecurityProperties.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/SecurityProperties.java
index d95dd5ff5..3d19f32a6 100644
--- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/SecurityProperties.java
+++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/SecurityProperties.java
@@ -19,6 +19,13 @@ public class SecurityProperties {
*/
@NotEmpty(message = "Token Header 不能为空")
private String tokenHeader = "Authorization";
+ /**
+ * HTTP 请求时,访问令牌的请求参数
+ *
+ * 初始目的:解决 WebSocket 无法通过 header 传参,只能通过 token 参数拼接
+ */
+ @NotEmpty(message = "Token Parameter 不能为空")
+ private String tokenParameter = "token";
/**
* mock 模式的开关
diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
index 78aa328ad..06a8f9be2 100644
--- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
+++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
@@ -129,8 +129,6 @@ public class YudaoWebSecurityConfigurerAdapter {
.antMatchers(buildAppApi("/**")).permitAll()
// 1.5 验证码captcha 允许匿名访问
.antMatchers("/captcha/get", "/captcha/check").permitAll()
- // 1.6 webSocket 允许匿名访问
- .antMatchers("/websocket/message").permitAll()
// ②:每个项目的自定义规则
.and().authorizeRequests(registry -> // 下面,循环设置自定义规则
authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(registry)))
diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
index e87f5bc44..601415c00 100644
--- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
+++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
@@ -41,7 +41,8 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
@SuppressWarnings("NullableProblems")
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
- String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
+ String token = SecurityFrameworkUtils.obtainAuthorization(request,
+ securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
if (StrUtil.isNotEmpty(token)) {
Integer userType = WebFrameworkUtils.getLoginUserType(request);
try {
@@ -74,7 +75,10 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
return null;
}
// 用户类型不匹配,无权限
- if (ObjectUtil.notEqual(accessToken.getUserType(), userType)) {
+ // 注意:只有 /admin-api/* 和 /app-api/* 有 userType,才需要比对用户类型
+ // TODO 芋艿:ws 要不要区分开?
+ if (userType != null
+ && ObjectUtil.notEqual(accessToken.getUserType(), userType)) {
throw new AccessDeniedException("错误的用户类型");
}
// 构建登录用户
diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java
index 5dc17b626..3caa7f98b 100644
--- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java
+++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.security.core.util;
+import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import org.springframework.lang.Nullable;
@@ -20,6 +21,9 @@ import java.util.Collections;
*/
public class SecurityFrameworkUtils {
+ /**
+ * HEADER 认证头 value 的前缀
+ */
public static final String AUTHORIZATION_BEARER = "Bearer";
private SecurityFrameworkUtils() {}
@@ -28,19 +32,23 @@ public class SecurityFrameworkUtils {
* 从请求中,获得认证 Token
*
* @param request 请求
- * @param header 认证 Token 对应的 Header 名字
+ * @param headerName 认证 Token 对应的 Header 名字
+ * @param parameterName 认证 Token 对应的 Parameter 名字
* @return 认证 Token
*/
- public static String obtainAuthorization(HttpServletRequest request, String header) {
- String authorization = request.getHeader(header);
- if (!StringUtils.hasText(authorization)) {
+ public static String obtainAuthorization(HttpServletRequest request,
+ String headerName, String parameterName) {
+ // 1. 获得 Token。优先级:Header > Parameter
+ String token = request.getHeader(headerName);
+ if (StrUtil.isEmpty(token)) {
+ token = request.getParameter(parameterName);
+ }
+ if (!StringUtils.hasText(token)) {
return null;
}
- int index = authorization.indexOf(AUTHORIZATION_BEARER + " ");
- if (index == -1) { // 未找到
- return null;
- }
- return authorization.substring(index + 7).trim();
+ // 2. 去除 Token 中带的 Bearer
+ int index = token.indexOf(AUTHORIZATION_BEARER + " ");
+ return index >= 0 ? token.substring(index + 7).trim() : token;
}
/**
diff --git a/yudao-framework/yudao-spring-boot-starter-websocket/pom.xml b/yudao-framework/yudao-spring-boot-starter-websocket/pom.xml
index 320e52c48..b18ee4783 100644
--- a/yudao-framework/yudao-spring-boot-starter-websocket/pom.xml
+++ b/yudao-framework/yudao-spring-boot-starter-websocket/pom.xml
@@ -12,26 +12,73 @@
jar
${project.artifactId}
- WebSocket
+ WebSocket 框架,支持多节点的广播
https://github.com/YunaiV/ruoyi-vue-pro
-
cn.iocoder.boot
yudao-common
+
+
cn.iocoder.boot
yudao-spring-boot-starter-security
+ provided
org.springframework.boot
spring-boot-starter-websocket
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-security
+ provided
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-mq
+
+
+ org.springframework.kafka
+ spring-kafka
+ true
+
+
+ org.springframework.amqp
+ spring-rabbit
+ true
+
+
+ org.apache.rocketmq
+ rocketmq-spring-boot-starter
+ true
+
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-biz-tenant
+ provided
+
\ No newline at end of file
diff --git a/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/config/WebSocketHandlerConfig.java b/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/config/WebSocketHandlerConfig.java
deleted file mode 100644
index 02c3415d5..000000000
--- a/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/config/WebSocketHandlerConfig.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package cn.iocoder.yudao.framework.websocket.config;
-
-import cn.iocoder.yudao.framework.websocket.core.UserHandshakeInterceptor;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.web.socket.server.HandshakeInterceptor;
-
-@EnableConfigurationProperties(WebSocketProperties.class)
-public class WebSocketHandlerConfig {
- @Bean
- public HandshakeInterceptor handshakeInterceptor() {
- return new UserHandshakeInterceptor();
- }
-}
diff --git a/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/config/WebSocketProperties.java b/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/config/WebSocketProperties.java
index 0ab1b498f..aa618fb04 100644
--- a/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/config/WebSocketProperties.java
+++ b/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/config/WebSocketProperties.java
@@ -4,6 +4,9 @@ import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
/**
* WebSocket 配置项
*
@@ -15,15 +18,17 @@ import org.springframework.validation.annotation.Validated;
public class WebSocketProperties {
/**
- * 路径
+ * WebSocket 的连接路径
*/
- private String path = "";
+ @NotEmpty(message = "WebSocket 的连接路径不能为空")
+ private String path = "/ws";
+
/**
- * 默认最多允许同时在线用户数
+ * 消息发送器的类型
+ *
+ * 可选值:local、redis、rocketmq、kafka、rabbitmq
*/
- private int maxOnlineCount = 0;
- /**
- * 是否保存session
- */
- private boolean sessionMap = true;
+ @NotNull(message = "WebSocket 的消息发送者不能为空")
+ private String senderType = "local";
+
}
diff --git a/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/config/YudaoWebSocketAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/config/YudaoWebSocketAutoConfiguration.java
index f8c50ae6a..0f08b7cf5 100644
--- a/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/config/YudaoWebSocketAutoConfiguration.java
+++ b/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/config/YudaoWebSocketAutoConfiguration.java
@@ -1,11 +1,34 @@
package cn.iocoder.yudao.framework.websocket.config;
+import cn.iocoder.yudao.framework.mq.redis.config.YudaoRedisMQConsumerAutoConfiguration;
+import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
+import cn.iocoder.yudao.framework.websocket.core.handler.JsonWebSocketMessageHandler;
+import cn.iocoder.yudao.framework.websocket.core.listener.WebSocketMessageListener;
+import cn.iocoder.yudao.framework.websocket.core.security.LoginUserHandshakeInterceptor;
+import cn.iocoder.yudao.framework.websocket.core.sender.kafka.KafkaWebSocketMessageConsumer;
+import cn.iocoder.yudao.framework.websocket.core.sender.kafka.KafkaWebSocketMessageSender;
+import cn.iocoder.yudao.framework.websocket.core.sender.local.LocalWebSocketMessageSender;
+import cn.iocoder.yudao.framework.websocket.core.sender.rabbitmq.RabbitMQWebSocketMessageConsumer;
+import cn.iocoder.yudao.framework.websocket.core.sender.rabbitmq.RabbitMQWebSocketMessageSender;
+import cn.iocoder.yudao.framework.websocket.core.sender.redis.RedisWebSocketMessageConsumer;
+import cn.iocoder.yudao.framework.websocket.core.sender.redis.RedisWebSocketMessageSender;
+import cn.iocoder.yudao.framework.websocket.core.sender.rocketmq.RocketMQWebSocketMessageConsumer;
+import cn.iocoder.yudao.framework.websocket.core.sender.rocketmq.RocketMQWebSocketMessageSender;
+import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionHandlerDecorator;
+import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManager;
+import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManagerImpl;
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
+import org.springframework.amqp.core.TopicExchange;
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.server.HandshakeInterceptor;
@@ -16,19 +39,139 @@ import java.util.List;
*
* @author xingyu4j
*/
-@AutoConfiguration
-// 允许使用 yudao.websocket.enable=false 禁用websocket
-@ConditionalOnProperty(prefix = "yudao.websocket", value = "enable", matchIfMissing = true)
+@AutoConfiguration(before = YudaoRedisMQConsumerAutoConfiguration.class) // before YudaoRedisMQConsumerAutoConfiguration 的原因是,需要保证 RedisWebSocketMessageConsumer 先创建,才能创建 RedisMessageListenerContainer
+@EnableWebSocket // 开启 websocket
+@ConditionalOnProperty(prefix = "yudao.websocket", value = "enable", matchIfMissing = true) // 允许使用 yudao.websocket.enable=false 禁用 websocket
@EnableConfigurationProperties(WebSocketProperties.class)
public class YudaoWebSocketAutoConfiguration {
+
@Bean
- @ConditionalOnMissingBean
- public WebSocketConfigurer webSocketConfigurer(List handshakeInterceptor,
+ public WebSocketConfigurer webSocketConfigurer(HandshakeInterceptor[] handshakeInterceptors,
WebSocketHandler webSocketHandler,
WebSocketProperties webSocketProperties) {
-
return registry -> registry
+ // 添加 WebSocketHandler
.addHandler(webSocketHandler, webSocketProperties.getPath())
- .addInterceptors(handshakeInterceptor.toArray(new HandshakeInterceptor[0]));
+ .addInterceptors(handshakeInterceptors)
+ // 允许跨域,否则前端连接会直接断开
+ .setAllowedOriginPatterns("*");
}
-}
+
+ @Bean
+ public HandshakeInterceptor handshakeInterceptor() {
+ return new LoginUserHandshakeInterceptor();
+ }
+
+ @Bean
+ public WebSocketHandler webSocketHandler(WebSocketSessionManager sessionManager,
+ List extends WebSocketMessageListener>> messageListeners) {
+ // 1. 创建 JsonWebSocketMessageHandler 对象,处理消息
+ JsonWebSocketMessageHandler messageHandler = new JsonWebSocketMessageHandler(messageListeners);
+ // 2. 创建 WebSocketSessionHandlerDecorator 对象,处理连接
+ return new WebSocketSessionHandlerDecorator(messageHandler, sessionManager);
+ }
+
+ @Bean
+ public WebSocketSessionManager webSocketSessionManager() {
+ return new WebSocketSessionManagerImpl();
+ }
+
+ // ==================== Sender 相关 ====================
+
+ @Configuration
+ @ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "local", matchIfMissing = true)
+ public class LocalWebSocketMessageSenderConfiguration {
+
+ @Bean
+ public LocalWebSocketMessageSender localWebSocketMessageSender(WebSocketSessionManager sessionManager) {
+ return new LocalWebSocketMessageSender(sessionManager);
+ }
+
+ }
+
+ @Configuration
+ @ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "redis", matchIfMissing = true)
+ public class RedisWebSocketMessageSenderConfiguration {
+
+ @Bean
+ public RedisWebSocketMessageSender redisWebSocketMessageSender(WebSocketSessionManager sessionManager,
+ RedisMQTemplate redisMQTemplate) {
+ return new RedisWebSocketMessageSender(sessionManager, redisMQTemplate);
+ }
+
+ @Bean
+ public RedisWebSocketMessageConsumer redisWebSocketMessageConsumer(
+ RedisWebSocketMessageSender redisWebSocketMessageSender) {
+ return new RedisWebSocketMessageConsumer(redisWebSocketMessageSender);
+ }
+
+ }
+
+ @Configuration
+ @ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "rocketmq", matchIfMissing = true)
+ public class RocketMQWebSocketMessageSenderConfiguration {
+
+ @Bean
+ public RocketMQWebSocketMessageSender rocketMQWebSocketMessageSender(
+ WebSocketSessionManager sessionManager, RocketMQTemplate rocketMQTemplate,
+ @Value("${yudao.websocket.sender-rocketmq.topic}") String topic) {
+ return new RocketMQWebSocketMessageSender(sessionManager, rocketMQTemplate, topic);
+ }
+
+ @Bean
+ public RocketMQWebSocketMessageConsumer rocketMQWebSocketMessageConsumer(
+ RocketMQWebSocketMessageSender rocketMQWebSocketMessageSender) {
+ return new RocketMQWebSocketMessageConsumer(rocketMQWebSocketMessageSender);
+ }
+
+ }
+
+ @Configuration
+ @ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "rabbitmq", matchIfMissing = true)
+ public class RabbitMQWebSocketMessageSenderConfiguration {
+
+ @Bean
+ public RabbitMQWebSocketMessageSender rabbitMQWebSocketMessageSender(
+ WebSocketSessionManager sessionManager, RabbitTemplate rabbitTemplate,
+ TopicExchange websocketTopicExchange) {
+ return new RabbitMQWebSocketMessageSender(sessionManager, rabbitTemplate, websocketTopicExchange);
+ }
+
+ @Bean
+ public RabbitMQWebSocketMessageConsumer rabbitMQWebSocketMessageConsumer(
+ RabbitMQWebSocketMessageSender rabbitMQWebSocketMessageSender) {
+ return new RabbitMQWebSocketMessageConsumer(rabbitMQWebSocketMessageSender);
+ }
+
+ /**
+ * 创建 Topic Exchange
+ */
+ @Bean
+ public TopicExchange websocketTopicExchange(@Value("${yudao.websocket.sender-rabbitmq.exchange}") String exchange) {
+ return new TopicExchange(exchange,
+ true, // durable: 是否持久化
+ false); // exclusive: 是否排它
+ }
+
+ }
+
+ @Configuration
+ @ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "kafka", matchIfMissing = true)
+ public class KafkaWebSocketMessageSenderConfiguration {
+
+ @Bean
+ public KafkaWebSocketMessageSender kafkaWebSocketMessageSender(
+ WebSocketSessionManager sessionManager, KafkaTemplate
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-websocket
+
+
cn.iocoder.boot
@@ -116,11 +121,6 @@
yudao-spring-boot-starter-file
-
-
- org.springframework.boot
- spring-boot-starter-websocket
-
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/api/websocket/WebSocketSenderApiImpl.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/api/websocket/WebSocketSenderApiImpl.java
new file mode 100644
index 000000000..046cd2fc1
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/api/websocket/WebSocketSenderApiImpl.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.infra.api.websocket;
+
+import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * WebSocket 发送器的 API 实现类
+ *
+ * @author 芋道源码
+ */
+@Component
+public class WebSocketSenderApiImpl implements WebSocketSenderApi {
+
+ @Resource
+ private WebSocketMessageSender webSocketMessageSender;
+
+ @Override
+ public void send(Integer userType, Long userId, String messageType, String messageContent) {
+ webSocketMessageSender.send(userType, userId, messageType, messageContent);
+ }
+
+ @Override
+ public void send(Integer userType, String messageType, String messageContent) {
+ webSocketMessageSender.send(userType, messageType, messageContent);
+ }
+
+ @Override
+ public void send(String sessionId, String messageType, String messageContent) {
+ webSocketMessageSender.send(sessionId, messageType, messageContent);
+ }
+
+}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/DemoWebSocketMessageListener.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/DemoWebSocketMessageListener.java
new file mode 100644
index 000000000..9ccf6070e
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/DemoWebSocketMessageListener.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.infra.websocket;
+
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.websocket.core.listener.WebSocketMessageListener;
+import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender;
+import cn.iocoder.yudao.framework.websocket.core.util.WebSocketFrameworkUtils;
+import cn.iocoder.yudao.module.infra.websocket.message.DemoReceiveMessage;
+import cn.iocoder.yudao.module.infra.websocket.message.DemoSendMessage;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.WebSocketSession;
+
+import javax.annotation.Resource;
+
+/**
+ * WebSocket 示例:单发消息
+ *
+ * @author 芋道源码
+ */
+@Component
+public class DemoWebSocketMessageListener implements WebSocketMessageListener {
+
+ @Resource
+ private WebSocketMessageSender webSocketMessageSender;
+
+ @Override
+ public void onMessage(WebSocketSession session, DemoSendMessage message) {
+ Long fromUserId = WebSocketFrameworkUtils.getLoginUserId(session);
+ // 情况一:单发
+ if (message.getToUserId() != null) {
+ DemoReceiveMessage toMessage = new DemoReceiveMessage().setFromUserId(fromUserId)
+ .setText(message.getText()).setSingle(true);
+ webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), message.getToUserId(), // 给指定用户
+ "demo-message-receive", toMessage);
+ return;
+ }
+ // 情况二:群发
+ DemoReceiveMessage toMessage = new DemoReceiveMessage().setFromUserId(fromUserId)
+ .setText(message.getText()).setSingle(false);
+ webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), // 给所有用户
+ "demo-message-receive", toMessage);
+ }
+
+ @Override
+ public String getType() {
+ return "demo-message-send";
+ }
+
+}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/SemaphoreUtils.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/SemaphoreUtils.java
deleted file mode 100644
index 67a87f169..000000000
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/SemaphoreUtils.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package cn.iocoder.yudao.module.infra.websocket;
-
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.concurrent.Semaphore;
-
-/**
- * 信号量相关处理
- *
- */
-@Slf4j
-public class SemaphoreUtils {
-
- /**
- * 获取信号量
- *
- * @param semaphore
- * @return
- */
- public static boolean tryAcquire(Semaphore semaphore) {
- boolean flag = false;
-
- try {
- flag = semaphore.tryAcquire();
- } catch (Exception e) {
- log.error("获取信号量异常", e);
- }
-
- return flag;
- }
-
- /**
- * 释放信号量
- *
- * @param semaphore
- */
- public static void release(Semaphore semaphore) {
-
- try {
- semaphore.release();
- } catch (Exception e) {
- log.error("释放信号量异常", e);
- }
- }
-}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketConfig.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketConfig.java
deleted file mode 100644
index 380bc9317..000000000
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketConfig.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package cn.iocoder.yudao.module.infra.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 {
- @Bean
- public ServerEndpointExporter serverEndpointExporter() {
- return new ServerEndpointExporter();
- }
-}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketServer.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketServer.java
deleted file mode 100644
index f0cfdd9dc..000000000
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketServer.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package cn.iocoder.yudao.module.infra.websocket;
-
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import javax.websocket.*;
-import javax.websocket.server.ServerEndpoint;
-import java.util.concurrent.Semaphore;
-
-/**
- * websocket 消息处理
- */
-@Component
-@ServerEndpoint("/websocket/message")
-@Slf4j
-public class WebSocketServer {
-
- /**
- * 默认最多允许同时在线用户数100
- */
- public static int socketMaxOnlineCount = 100;
-
- private static final Semaphore SOCKET_SEMAPHORE = new Semaphore(socketMaxOnlineCount);
-
- /**
- * 连接建立成功调用的方法
- */
- @OnOpen
- public void onOpen(Session session) throws Exception {
- // 尝试获取信号量
- boolean semaphoreFlag = SemaphoreUtils.tryAcquire(SOCKET_SEMAPHORE);
- if (!semaphoreFlag) {
- // 未获取到信号量
- log.error("当前在线人数超过限制数:{}", socketMaxOnlineCount);
- WebSocketUsers.sendMessage(session, "当前在线人数超过限制数:" + socketMaxOnlineCount);
- session.close();
- } else {
- String userId = WebSocketUsers.getParam("userId", session);
- if (userId != null) {
- // 添加用户
- WebSocketUsers.addSession(userId, session);
- log.info("用户【userId={}】建立连接,当前连接用户总数:{}", userId, WebSocketUsers.getUsers().size());
- WebSocketUsers.sendMessage(session, "接收内容:连接成功");
- } else {
- WebSocketUsers.sendMessage(session, "接收内容:连接失败");
- }
- }
- }
-
- /**
- * 连接关闭时处理
- */
- @OnClose
- public void onClose(Session session) {
- log.info("用户【sessionId={}】关闭连接!", session.getId());
- // 移除用户
- WebSocketUsers.removeSession(session);
- // 获取到信号量则需释放
- SemaphoreUtils.release(SOCKET_SEMAPHORE);
- }
-
- /**
- * 抛出异常时处理
- */
- @OnError
- public void onError(Session session, Throwable exception) throws Exception {
- if (session.isOpen()) {
- // 关闭连接
- session.close();
- }
- String sessionId = session.getId();
- log.info("用户【sessionId={}】连接异常!异常信息:{}", sessionId, exception);
- // 移出用户
- WebSocketUsers.removeSession(session);
- // 获取到信号量则需释放
- SemaphoreUtils.release(SOCKET_SEMAPHORE);
- }
-
- /**
- * 收到客户端消息时调用的方法
- */
- @OnMessage
- public void onMessage(Session session, String message) {
- WebSocketUsers.sendMessage(session, "接收内容:" + message);
- }
-}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketUsers.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketUsers.java
deleted file mode 100644
index 281a97c7d..000000000
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/WebSocketUsers.java
+++ /dev/null
@@ -1,178 +0,0 @@
-package cn.iocoder.yudao.module.infra.websocket;
-
-import cn.hutool.core.map.MapUtil;
-import cn.hutool.core.util.StrUtil;
-import lombok.extern.slf4j.Slf4j;
-import org.bouncycastle.util.Strings;
-
-import javax.validation.constraints.NotNull;
-import javax.websocket.Session;
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * websocket 客户端用户
- */
-@Slf4j
-public class WebSocketUsers {
-
- /**
- * 用户集
- * TODO 需要登录用户的session?
- */
- private static final Map SESSION_MAP = new ConcurrentHashMap<>();
-
- /**
- * 存储用户
- *
- * @param userId 唯一键
- * @param session 用户信息
- */
- public static void addSession(String userId, Session session) {
- SESSION_MAP.put(userId, session);
- }
-
- /**
- * 移除用户
- *
- * @param session 用户信息
- * @return 移除结果
- */
- public static boolean removeSession(Session session) {
- String key = null;
- boolean flag = SESSION_MAP.containsValue(session);
- if (flag) {
- Set> entries = SESSION_MAP.entrySet();
- for (Map.Entry entry : entries) {
- Session value = entry.getValue();
- if (value.equals(session)) {
- key = entry.getKey();
- break;
- }
- }
- } else {
- return true;
- }
- return removeSession(key);
- }
-
- /**
- * 移出用户
- *
- * @param userId 用户id
- */
- public static boolean removeSession(String userId) {
- log.info("用户【userId={}】退出", userId);
- Session remove = SESSION_MAP.remove(userId);
- if (remove != null) {
- boolean containsValue = SESSION_MAP.containsValue(remove);
- log.info("用户【userId={}】退出{},当前连接用户总数:{}", userId, containsValue ? "失败" : "成功", SESSION_MAP.size());
- return containsValue;
- } else {
- return true;
- }
- }
-
- /**
- * 获取在线用户列表
- *
- * @return 返回用户集合
- */
- public static Map getUsers() {
- return SESSION_MAP;
- }
-
- /**
- * 向所有在线人发送消息
- *
- * @param message 消息内容
- */
- public static void sendMessageToAll(String message) {
- SESSION_MAP.forEach((userId, session) -> {
- if (session.isOpen()) {
- sendMessage(session, message);
- }
- });
- }
-
- /**
- * 异步发送文本消息
- *
- * @param session 用户session
- * @param message 消息内容
- */
- public static void sendMessageAsync(Session session, String message) {
- if (session.isOpen()) {
- // TODO 需要加synchronized锁(synchronized(session))?单个session创建线程?
- session.getAsyncRemote().sendText(message);
- } else {
- log.warn("用户【session={}】不在线", session.getId());
- }
- }
-
- /**
- * 同步发送文本消息
- *
- * @param session 用户session
- * @param message 消息内容
- */
- public static void sendMessage(Session session, String message) {
- try {
- if (session.isOpen()) {
- // TODO 需要加synchronized锁(synchronized(session))?单个session创建线程?
- session.getBasicRemote().sendText(message);
- } else {
- log.warn("用户【session={}】不在线", session.getId());
- }
- } catch (IOException e) {
- log.error("发送消息异常", e);
- }
-
- }
-
- /**
- * 根据用户id发送消息
- *
- * @param userId 用户id
- * @param message 消息内容
- */
- public static void sendMessage(String userId, String message) {
- Session session = SESSION_MAP.get(userId);
- //判断是否存在该用户的session,并且是否在线
- if (session == null || !session.isOpen()) {
- return;
- }
- sendMessage(session, message);
- }
-
-
- /**
- * 获取session中的指定参数值
- *
- * @param key 参数key
- * @param session 用户session
- */
- public static String getParam(@NotNull String key, Session session) {
- //TODO 目前只针对获取一个key的值,后期根据情况拓展多个 或者直接在onClose onOpen上获取参数?
- String value = null;
- Map> parameters = session.getRequestParameterMap();
- if (MapUtil.isNotEmpty(parameters)) {
- value = parameters.get(key).get(0);
- } else {
- String queryString = session.getQueryString();
- if (!StrUtil.isEmpty(queryString)) {
- String[] params = Strings.split(queryString, '&');
- for (String paramPair : params) {
- String[] nameValues = Strings.split(paramPair, '=');
- if (key.equals(nameValues[0])) {
- value = nameValues[1];
- }
- }
- }
- }
- return value;
- }
-}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/message/DemoReceiveMessage.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/message/DemoReceiveMessage.java
new file mode 100644
index 000000000..03a246cf9
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/message/DemoReceiveMessage.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.infra.websocket.message;
+
+import lombok.Data;
+
+/**
+ * 示例:server -> client 同步消息
+ *
+ * @author 芋道源码
+ */
+@Data
+public class DemoReceiveMessage {
+
+ /**
+ * 接收人的编号
+ */
+ private Long fromUserId;
+ /**
+ * 内容
+ */
+ private String text;
+
+ /**
+ * 是否单聊
+ */
+ private Boolean single;
+
+}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/message/DemoSendMessage.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/message/DemoSendMessage.java
new file mode 100644
index 000000000..f0c14f5d3
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/message/DemoSendMessage.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.infra.websocket.message;
+
+import lombok.Data;
+
+/**
+ * 示例:client -> server 发送消息
+ *
+ * @author 芋道源码
+ */
+@Data
+public class DemoSendMessage {
+
+ /**
+ * 发送给谁
+ *
+ * 如果为空,说明发送给所有人
+ */
+ private Long toUserId;
+ /**
+ * 内容
+ */
+ private String text;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java
index ed3963e90..4b6ed7b26 100644
--- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java
@@ -53,7 +53,8 @@ public class AppAuthController {
@PermitAll
@Operation(summary = "登出系统")
public CommonResult logout(HttpServletRequest request) {
- String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
+ String token = SecurityFrameworkUtils.obtainAuthorization(request,
+ securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
if (StrUtil.isNotBlank(token)) {
authService.logout(token);
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
index c2eae8d38..061539494 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
@@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
@@ -38,7 +39,6 @@ import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
-import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization;
@Tag(name = "管理后台 - 认证")
@RestController
@@ -76,7 +76,8 @@ public class AuthController {
@Operation(summary = "登出系统")
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
public CommonResult logout(HttpServletRequest request) {
- String token = obtainAuthorization(request, securityProperties.getTokenHeader());
+ String token = SecurityFrameworkUtils.obtainAuthorization(request,
+ securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
if (StrUtil.isNotBlank(token)) {
authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/notice/NoticeController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/notice/NoticeController.java
index 0e1957785..5a566702f 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/notice/NoticeController.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/notice/NoticeController.java
@@ -1,12 +1,16 @@
package cn.iocoder.yudao.module.system.controller.admin.notice;
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi;
import cn.iocoder.yudao.module.system.controller.admin.notice.vo.NoticeCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.notice.vo.NoticePageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.notice.vo.NoticeRespVO;
import cn.iocoder.yudao.module.system.controller.admin.notice.vo.NoticeUpdateReqVO;
import cn.iocoder.yudao.module.system.convert.notice.NoticeConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.notice.NoticeDO;
import cn.iocoder.yudao.module.system.service.notice.NoticeService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
@@ -29,6 +33,9 @@ public class NoticeController {
@Resource
private NoticeService noticeService;
+ @Resource
+ private WebSocketSenderApi webSocketSenderApi;
+
@PostMapping("/create")
@Operation(summary = "创建通知公告")
@PreAuthorize("@ss.hasPermission('system:notice:create')")
@@ -69,4 +76,16 @@ public class NoticeController {
return success(NoticeConvert.INSTANCE.convert(noticeService.getNotice(id)));
}
+ @PostMapping("/push")
+ @Operation(summary = "推送通知公告", description = "只发送给 websocket 连接在线的用户")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('system:notice:update')")
+ public CommonResult push(@RequestParam("id") Long id) {
+ NoticeDO notice = noticeService.getNotice(id);
+ Assert.notNull(notice, "公告不能为空");
+ // 通过 websocket 推送给在线的用户
+ webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), "notice-push", notice);
+ return success(true);
+ }
+
}
diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml
index f15b0cf8d..6dfe13fff 100644
--- a/yudao-server/src/main/resources/application-local.yaml
+++ b/yudao-server/src/main/resources/application-local.yaml
@@ -123,8 +123,8 @@ spring:
rabbitmq:
host: 127.0.0.1 # RabbitMQ 服务的地址
port: 5672 # RabbitMQ 服务的端口
- username: guest # RabbitMQ 服务的账号
- password: guest # RabbitMQ 服务的密码
+ username: rabbit # RabbitMQ 服务的账号
+ password: rabbit # RabbitMQ 服务的密码
# Kafka 配置项,对应 KafkaProperties 配置类
kafka:
bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔
diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml
index a51fa9bbb..954411022 100644
--- a/yudao-server/src/main/resources/application.yaml
+++ b/yudao-server/src/main/resources/application.yaml
@@ -146,9 +146,17 @@ yudao:
- /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录
websocket:
enable: true # websocket的开关
- path: /websocket/message # 路径
- maxOnlineCount: 0 # 最大连接人数
- sessionMap: true # 保存sessionMap
+ path: /infra/ws # 路径
+ sender-type: local # 消息发送的类型,可选值为 local、redis、rocketmq、kafka、rabbitmq
+ sender-rocketmq:
+ topic: ${spring.application.name}-websocket # 消息发送的 RocketMQ Topic
+ consumer-group: ${spring.application.name}-websocket-consumer # 消息发送的 RocketMQ Consumer Group
+ sender-rabbitmq:
+ exchange: ${spring.application.name}-websocket-exchange # 消息发送的 RabbitMQ Exchange
+ queue: ${spring.application.name}-websocket-queue # 消息发送的 RabbitMQ Queue
+ sender-kafka:
+ topic: ${spring.application.name}-websocket # 消息发送的 Kafka Topic
+ consumer-group: ${spring.application.name}-websocket-consumer # 消息发送的 Kafka Consumer Group
swagger:
title: 芋道快速开发平台
description: 提供管理后台、用户 App 的所有功能