Commit 0233b092b81220b38007ff6be592f204fac91b10
1 parent
1941a7b3
websocket:重新封装 websocket 组件,支持 sender 广播
Showing
31 changed files
with
1107 additions
and
12 deletions
yudao-framework/yudao-spring-boot-starter-websocket/pom.xml
| ... | ... | @@ -30,12 +30,55 @@ |
| 30 | 30 | --> |
| 31 | 31 | <groupId>cn.iocoder.boot</groupId> |
| 32 | 32 | <artifactId>yudao-spring-boot-starter-security</artifactId> |
| 33 | + <scope>provided</scope> | |
| 33 | 34 | </dependency> |
| 34 | 35 | |
| 35 | 36 | <dependency> |
| 36 | 37 | <groupId>org.springframework.boot</groupId> |
| 37 | 38 | <artifactId>spring-boot-starter-websocket</artifactId> |
| 38 | 39 | </dependency> |
| 40 | + | |
| 41 | + <!-- Web 相关 --> | |
| 42 | + <dependency> | |
| 43 | + <!-- 为什么是 websocket 依赖 security 呢?而不是 security 拓展 websocket 呢? | |
| 44 | + 因为 websocket 和 LoginUser 当前登录的用户有一定的相关性,具体可见 WebSocketSessionManagerImpl 逻辑。 | |
| 45 | + 如果让 security 拓展 websocket 的话,会导致 websocket 组件的封装很散,进而增大理解成本。 | |
| 46 | + --> | |
| 47 | + <groupId>cn.iocoder.boot</groupId> | |
| 48 | + <artifactId>yudao-spring-boot-starter-security</artifactId> | |
| 49 | + <scope>provided</scope> | |
| 50 | + </dependency> | |
| 51 | + | |
| 52 | + <!-- 消息队列相关 --> | |
| 53 | + <dependency> | |
| 54 | + <groupId>cn.iocoder.boot</groupId> | |
| 55 | + <artifactId>yudao-spring-boot-starter-mq</artifactId> | |
| 56 | + </dependency> | |
| 57 | + <dependency> | |
| 58 | + <groupId>org.springframework.kafka</groupId> | |
| 59 | + <artifactId>spring-kafka</artifactId> | |
| 60 | + <optional>true</optional> | |
| 61 | + </dependency> | |
| 62 | + <dependency> | |
| 63 | + <groupId>org.springframework.amqp</groupId> | |
| 64 | + <artifactId>spring-rabbit</artifactId> | |
| 65 | + <optional>true</optional> | |
| 66 | + </dependency> | |
| 67 | + <dependency> | |
| 68 | + <groupId>org.apache.rocketmq</groupId> | |
| 69 | + <artifactId>rocketmq-spring-boot-starter</artifactId> | |
| 70 | + <optional>true</optional> | |
| 71 | + </dependency> | |
| 72 | + | |
| 73 | + <!-- 业务组件 --> | |
| 74 | + <dependency> | |
| 75 | + <!-- 为什么要依赖 tenant 组件? | |
| 76 | + 因为广播某个类型的用户时候,需要根据租户过滤下,避免广播到别的租户! | |
| 77 | + --> | |
| 78 | + <groupId>cn.iocoder.boot</groupId> | |
| 79 | + <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId> | |
| 80 | + <scope>provided</scope> | |
| 81 | + </dependency> | |
| 39 | 82 | </dependencies> |
| 40 | 83 | |
| 41 | 84 | </project> |
| 42 | 85 | \ No newline at end of file | ... | ... |
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; |
| 4 | 4 | import org.springframework.boot.context.properties.ConfigurationProperties; |
| 5 | 5 | import org.springframework.validation.annotation.Validated; |
| 6 | 6 | |
| 7 | +import javax.validation.constraints.NotEmpty; | |
| 8 | +import javax.validation.constraints.NotNull; | |
| 9 | + | |
| 7 | 10 | /** |
| 8 | 11 | * WebSocket 配置项 |
| 9 | 12 | * |
| ... | ... | @@ -17,6 +20,15 @@ public class WebSocketProperties { |
| 17 | 20 | /** |
| 18 | 21 | * WebSocket 的连接路径 |
| 19 | 22 | */ |
| 23 | + @NotEmpty(message = "WebSocket 的连接路径不能为空") | |
| 20 | 24 | private String path = "/ws"; |
| 21 | 25 | |
| 26 | + /** | |
| 27 | + * 消息发送器的类型 | |
| 28 | + * | |
| 29 | + * 可选值:local、redis、rocketmq、kafka、rabbitmq | |
| 30 | + */ | |
| 31 | + @NotNull(message = "WebSocket 的消息发送者不能为空") | |
| 32 | + private String senderType = "local"; | |
| 33 | + | |
| 22 | 34 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/config/YudaoWebSocketAutoConfiguration.java
| 1 | 1 | package cn.iocoder.yudao.framework.websocket.config; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate; | |
| 3 | 4 | import cn.iocoder.yudao.framework.websocket.core.handler.JsonWebSocketMessageHandler; |
| 4 | 5 | import cn.iocoder.yudao.framework.websocket.core.listener.WebSocketMessageListener; |
| 5 | 6 | import cn.iocoder.yudao.framework.websocket.core.security.LoginUserHandshakeInterceptor; |
| 7 | +import cn.iocoder.yudao.framework.websocket.core.sender.kafka.KafkaWebSocketMessageConsumer; | |
| 8 | +import cn.iocoder.yudao.framework.websocket.core.sender.kafka.KafkaWebSocketMessageSender; | |
| 9 | +import cn.iocoder.yudao.framework.websocket.core.sender.local.LocalWebSocketMessageSender; | |
| 10 | +import cn.iocoder.yudao.framework.websocket.core.sender.rabbitmq.RabbitMQWebSocketMessageConsumer; | |
| 11 | +import cn.iocoder.yudao.framework.websocket.core.sender.rabbitmq.RabbitMQWebSocketMessageSender; | |
| 12 | +import cn.iocoder.yudao.framework.websocket.core.sender.redis.RedisWebSocketMessageConsumer; | |
| 13 | +import cn.iocoder.yudao.framework.websocket.core.sender.redis.RedisWebSocketMessageSender; | |
| 14 | +import cn.iocoder.yudao.framework.websocket.core.sender.rocketmq.RocketMQWebSocketMessageConsumer; | |
| 15 | +import cn.iocoder.yudao.framework.websocket.core.sender.rocketmq.RocketMQWebSocketMessageSender; | |
| 6 | 16 | import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionHandlerDecorator; |
| 7 | 17 | import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManager; |
| 8 | 18 | import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManagerImpl; |
| 19 | +import org.apache.rocketmq.spring.core.RocketMQTemplate; | |
| 20 | +import org.springframework.amqp.core.TopicExchange; | |
| 21 | +import org.springframework.amqp.rabbit.core.RabbitTemplate; | |
| 22 | +import org.springframework.beans.factory.annotation.Value; | |
| 9 | 23 | import org.springframework.boot.autoconfigure.AutoConfiguration; |
| 10 | 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
| 11 | 25 | import org.springframework.boot.context.properties.EnableConfigurationProperties; |
| 12 | 26 | import org.springframework.context.annotation.Bean; |
| 27 | +import org.springframework.context.annotation.Configuration; | |
| 28 | +import org.springframework.kafka.core.KafkaTemplate; | |
| 13 | 29 | import org.springframework.web.socket.WebSocketHandler; |
| 14 | 30 | import org.springframework.web.socket.config.annotation.EnableWebSocket; |
| 15 | 31 | import org.springframework.web.socket.config.annotation.WebSocketConfigurer; |
| ... | ... | @@ -25,7 +41,6 @@ import java.util.List; |
| 25 | 41 | @AutoConfiguration |
| 26 | 42 | @EnableWebSocket // 开启 websocket |
| 27 | 43 | @ConditionalOnProperty(prefix = "yudao.websocket", value = "enable", matchIfMissing = true) // 允许使用 yudao.websocket.enable=false 禁用 websocket |
| 28 | - | |
| 29 | 44 | @EnableConfigurationProperties(WebSocketProperties.class) |
| 30 | 45 | public class YudaoWebSocketAutoConfiguration { |
| 31 | 46 | |
| ... | ... | @@ -60,4 +75,103 @@ public class YudaoWebSocketAutoConfiguration { |
| 60 | 75 | return new WebSocketSessionManagerImpl(); |
| 61 | 76 | } |
| 62 | 77 | |
| 78 | + // ==================== Sender 相关 ==================== | |
| 79 | + | |
| 80 | + @Configuration | |
| 81 | + @ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "local", matchIfMissing = true) | |
| 82 | + public class LocalWebSocketMessageSenderConfiguration { | |
| 83 | + | |
| 84 | + @Bean | |
| 85 | + public LocalWebSocketMessageSender localWebSocketMessageSender(WebSocketSessionManager sessionManager) { | |
| 86 | + return new LocalWebSocketMessageSender(sessionManager); | |
| 87 | + } | |
| 88 | + | |
| 89 | + } | |
| 90 | + | |
| 91 | + @Configuration | |
| 92 | + @ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "redis", matchIfMissing = true) | |
| 93 | + public class RedisWebSocketMessageSenderConfiguration { | |
| 94 | + | |
| 95 | + @Bean | |
| 96 | + public RedisWebSocketMessageSender redisWebSocketMessageSender(WebSocketSessionManager sessionManager, | |
| 97 | + RedisMQTemplate redisMQTemplate) { | |
| 98 | + return new RedisWebSocketMessageSender(sessionManager, redisMQTemplate); | |
| 99 | + } | |
| 100 | + | |
| 101 | + // TODO 芋艿:需要额外删除 YudaoRedisMQAutoConfiguration 的 RedisMessageListenerContainer Bean 上的 @ConditionalOnBean 注解。可能是 spring boot 的 bug! | |
| 102 | + @Bean | |
| 103 | + public RedisWebSocketMessageConsumer redisWebSocketMessageConsumer( | |
| 104 | + RedisWebSocketMessageSender redisWebSocketMessageSender) { | |
| 105 | + return new RedisWebSocketMessageConsumer(redisWebSocketMessageSender); | |
| 106 | + } | |
| 107 | + | |
| 108 | + } | |
| 109 | + | |
| 110 | + @Configuration | |
| 111 | + @ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "rocketmq", matchIfMissing = true) | |
| 112 | + public class RocketMQWebSocketMessageSenderConfiguration { | |
| 113 | + | |
| 114 | + @Bean | |
| 115 | + public RocketMQWebSocketMessageSender rocketMQWebSocketMessageSender( | |
| 116 | + WebSocketSessionManager sessionManager, RocketMQTemplate rocketMQTemplate, | |
| 117 | + @Value("${yudao.websocket.sender-rocketmq.topic}") String topic) { | |
| 118 | + return new RocketMQWebSocketMessageSender(sessionManager, rocketMQTemplate, topic); | |
| 119 | + } | |
| 120 | + | |
| 121 | + @Bean | |
| 122 | + public RocketMQWebSocketMessageConsumer rocketMQWebSocketMessageConsumer( | |
| 123 | + RocketMQWebSocketMessageSender rocketMQWebSocketMessageSender) { | |
| 124 | + return new RocketMQWebSocketMessageConsumer(rocketMQWebSocketMessageSender); | |
| 125 | + } | |
| 126 | + | |
| 127 | + } | |
| 128 | + | |
| 129 | + @Configuration | |
| 130 | + @ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "rabbitmq", matchIfMissing = true) | |
| 131 | + public class RabbitMQWebSocketMessageSenderConfiguration { | |
| 132 | + | |
| 133 | + @Bean | |
| 134 | + public RabbitMQWebSocketMessageSender rabbitMQWebSocketMessageSender( | |
| 135 | + WebSocketSessionManager sessionManager, RabbitTemplate rabbitTemplate, | |
| 136 | + TopicExchange websocketTopicExchange) { | |
| 137 | + return new RabbitMQWebSocketMessageSender(sessionManager, rabbitTemplate, websocketTopicExchange); | |
| 138 | + } | |
| 139 | + | |
| 140 | + @Bean | |
| 141 | + public RabbitMQWebSocketMessageConsumer rabbitMQWebSocketMessageConsumer( | |
| 142 | + RabbitMQWebSocketMessageSender rabbitMQWebSocketMessageSender) { | |
| 143 | + return new RabbitMQWebSocketMessageConsumer(rabbitMQWebSocketMessageSender); | |
| 144 | + } | |
| 145 | + | |
| 146 | + /** | |
| 147 | + * 创建 Topic Exchange | |
| 148 | + */ | |
| 149 | + @Bean | |
| 150 | + public TopicExchange websocketTopicExchange(@Value("${yudao.websocket.sender-rabbitmq.exchange}") String exchange) { | |
| 151 | + return new TopicExchange(exchange, | |
| 152 | + true, // durable: 是否持久化 | |
| 153 | + false); // exclusive: 是否排它 | |
| 154 | + } | |
| 155 | + | |
| 156 | + } | |
| 157 | + | |
| 158 | + @Configuration | |
| 159 | + @ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "kafka", matchIfMissing = true) | |
| 160 | + public class KafkaWebSocketMessageSenderConfiguration { | |
| 161 | + | |
| 162 | + @Bean | |
| 163 | + public KafkaWebSocketMessageSender kafkaWebSocketMessageSender( | |
| 164 | + WebSocketSessionManager sessionManager, KafkaTemplate<Object, Object> kafkaTemplate, | |
| 165 | + @Value("${yudao.websocket.sender-kafka.topic}") String topic) { | |
| 166 | + return new KafkaWebSocketMessageSender(sessionManager, kafkaTemplate, topic); | |
| 167 | + } | |
| 168 | + | |
| 169 | + @Bean | |
| 170 | + public KafkaWebSocketMessageConsumer kafkaWebSocketMessageConsumer( | |
| 171 | + KafkaWebSocketMessageSender kafkaWebSocketMessageSender) { | |
| 172 | + return new KafkaWebSocketMessageConsumer(kafkaWebSocketMessageSender); | |
| 173 | + } | |
| 174 | + | |
| 175 | + } | |
| 176 | + | |
| 63 | 177 | } |
| 64 | 178 | \ No newline at end of file | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/handler/JsonWebSocketMessageHandler.java
| ... | ... | @@ -3,8 +3,10 @@ package cn.iocoder.yudao.framework.websocket.core.handler; |
| 3 | 3 | import cn.hutool.core.util.StrUtil; |
| 4 | 4 | import cn.hutool.core.util.TypeUtil; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 6 | +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | |
| 6 | 7 | import cn.iocoder.yudao.framework.websocket.core.listener.WebSocketMessageListener; |
| 7 | 8 | import cn.iocoder.yudao.framework.websocket.core.message.JsonWebSocketMessage; |
| 9 | +import cn.iocoder.yudao.framework.websocket.core.util.WebSocketFrameworkUtils; | |
| 8 | 10 | import lombok.extern.slf4j.Slf4j; |
| 9 | 11 | import org.springframework.web.socket.TextMessage; |
| 10 | 12 | import org.springframework.web.socket.WebSocketHandler; |
| ... | ... | @@ -70,8 +72,9 @@ public class JsonWebSocketMessageHandler extends TextWebSocketHandler { |
| 70 | 72 | } |
| 71 | 73 | // 2.3 处理消息 |
| 72 | 74 | Type type = TypeUtil.getTypeArgument(messageListener.getClass(), 0); |
| 73 | - Object messageObj = JsonUtils.parseObject(jsonMessage.getMessage(), type); | |
| 74 | - messageListener.onMessage(session, messageObj); | |
| 75 | + Object messageObj = JsonUtils.parseObject(jsonMessage.getContent(), type); | |
| 76 | + Long tenantId = WebSocketFrameworkUtils.getTenantId(session); | |
| 77 | + TenantUtils.execute(tenantId, () -> messageListener.onMessage(session, messageObj)); | |
| 75 | 78 | } catch (Throwable ex) { |
| 76 | 79 | log.error("[handleTextMessage][session({}) message({}) 处理异常]", session.getId(), message.getPayload()); |
| 77 | 80 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/message/JsonWebSocketMessage.java
| ... | ... | @@ -3,13 +3,15 @@ package cn.iocoder.yudao.framework.websocket.core.message; |
| 3 | 3 | import cn.iocoder.yudao.framework.websocket.core.listener.WebSocketMessageListener; |
| 4 | 4 | import lombok.Data; |
| 5 | 5 | |
| 6 | +import java.io.Serializable; | |
| 7 | + | |
| 6 | 8 | /** |
| 7 | 9 | * JSON 格式的 WebSocket 消息帧 |
| 8 | 10 | * |
| 9 | 11 | * @author 芋道源码 |
| 10 | 12 | */ |
| 11 | 13 | @Data |
| 12 | -public class JsonWebSocketMessage { | |
| 14 | +public class JsonWebSocketMessage implements Serializable { | |
| 13 | 15 | |
| 14 | 16 | /** |
| 15 | 17 | * 消息类型 |
| ... | ... | @@ -22,6 +24,6 @@ public class JsonWebSocketMessage { |
| 22 | 24 | * |
| 23 | 25 | * 要求 JSON 对象 |
| 24 | 26 | */ |
| 25 | - private String message; | |
| 27 | + private String content; | |
| 26 | 28 | |
| 27 | 29 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/sender/AbstractWebSocketMessageSender.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.websocket.core.sender; | |
| 2 | + | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.hutool.core.util.StrUtil; | |
| 5 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 6 | +import cn.iocoder.yudao.framework.websocket.core.message.JsonWebSocketMessage; | |
| 7 | +import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManager; | |
| 8 | +import lombok.RequiredArgsConstructor; | |
| 9 | +import lombok.extern.slf4j.Slf4j; | |
| 10 | +import org.springframework.web.socket.TextMessage; | |
| 11 | +import org.springframework.web.socket.WebSocketSession; | |
| 12 | + | |
| 13 | +import java.io.IOException; | |
| 14 | +import java.util.Collection; | |
| 15 | +import java.util.Collections; | |
| 16 | +import java.util.List; | |
| 17 | + | |
| 18 | +/** | |
| 19 | + * WebSocketMessageSender 实现类 | |
| 20 | + * | |
| 21 | + * @author 芋道源码 | |
| 22 | + */ | |
| 23 | +@Slf4j | |
| 24 | +@RequiredArgsConstructor | |
| 25 | +public abstract class AbstractWebSocketMessageSender implements WebSocketMessageSender { | |
| 26 | + | |
| 27 | + private final WebSocketSessionManager sessionManager; | |
| 28 | + | |
| 29 | + @Override | |
| 30 | + public void send(Integer userType, Long userId, String messageType, String messageContent) { | |
| 31 | + send(null, userType, userId, messageType, messageContent); | |
| 32 | + } | |
| 33 | + | |
| 34 | + @Override | |
| 35 | + public void send(Integer userType, String messageType, String messageContent) { | |
| 36 | + send(null, userType, null, messageType, messageContent); | |
| 37 | + } | |
| 38 | + | |
| 39 | + @Override | |
| 40 | + public void send(String sessionId, String messageType, String messageContent) { | |
| 41 | + send(sessionId, null, null, messageType, messageContent); | |
| 42 | + } | |
| 43 | + | |
| 44 | + /** | |
| 45 | + * 发送消息 | |
| 46 | + * | |
| 47 | + * @param sessionId Session 编号 | |
| 48 | + * @param userType 用户类型 | |
| 49 | + * @param userId 用户编号 | |
| 50 | + * @param messageType 消息类型 | |
| 51 | + * @param messageContent 消息内容 | |
| 52 | + */ | |
| 53 | + public void send(String sessionId, Integer userType, Long userId, String messageType, String messageContent) { | |
| 54 | + // 1. 获得 Session 列表 | |
| 55 | + List<WebSocketSession> sessions = Collections.emptyList(); | |
| 56 | + if (StrUtil.isNotEmpty(sessionId)) { | |
| 57 | + WebSocketSession session = sessionManager.getSession(sessionId); | |
| 58 | + if (session != null) { | |
| 59 | + sessions = Collections.singletonList(session); | |
| 60 | + } | |
| 61 | + } else if (userType != null && userId != null) { | |
| 62 | + sessions = (List<WebSocketSession>) sessionManager.getSessionList(userType, userId); | |
| 63 | + } else if (userType != null) { | |
| 64 | + sessions = (List<WebSocketSession>) sessionManager.getSessionList(userType); | |
| 65 | + } | |
| 66 | + if (CollUtil.isEmpty(sessions)) { | |
| 67 | + log.info("[send][sessionId({}) userType({}) userId({}) messageType({}) messageContent({}) 未匹配到会话]", | |
| 68 | + sessionId, userType, userId, messageType, messageContent); | |
| 69 | + } | |
| 70 | + // 2. 执行发送 | |
| 71 | + doSend(sessions, messageType, messageContent); | |
| 72 | + } | |
| 73 | + | |
| 74 | + /** | |
| 75 | + * 发送消息的具体实现 | |
| 76 | + * | |
| 77 | + * @param sessions Session 列表 | |
| 78 | + * @param messageType 消息类型 | |
| 79 | + * @param messageContent 消息内容 | |
| 80 | + */ | |
| 81 | + public void doSend(Collection<WebSocketSession> sessions, String messageType, String messageContent) { | |
| 82 | + JsonWebSocketMessage message = new JsonWebSocketMessage().setType(messageType).setContent(messageContent); | |
| 83 | + String payload = JsonUtils.toJsonString(message); // 关键,使用 JSON 序列化 | |
| 84 | + sessions.forEach(session -> { | |
| 85 | + // 1. 各种校验,保证 Session 可以被发送 | |
| 86 | + if (session == null) { | |
| 87 | + log.error("[doSend][session 为空, message({})]", message); | |
| 88 | + return; | |
| 89 | + } | |
| 90 | + if (!session.isOpen()) { | |
| 91 | + log.error("[doSend][session({}) 已关闭, message({})]", session.getId(), message); | |
| 92 | + return; | |
| 93 | + } | |
| 94 | + // 2. 执行发送 | |
| 95 | + try { | |
| 96 | + session.sendMessage(new TextMessage(payload)); | |
| 97 | + log.info("[doSend][session({}) 发送消息成功,message({})]", session.getId(), message); | |
| 98 | + } catch (IOException ex) { | |
| 99 | + log.error("[doSend][session({}) 发送消息失败,message({})]", session.getId(), message, ex); | |
| 100 | + } | |
| 101 | + }); | |
| 102 | + } | |
| 103 | + | |
| 104 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/sender/WebSocketMessageSender.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.websocket.core.sender; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * WebSocket 消息的发送器接口 | |
| 7 | + * | |
| 8 | + * @author 芋道源码 | |
| 9 | + */ | |
| 10 | +public interface WebSocketMessageSender { | |
| 11 | + | |
| 12 | + /** | |
| 13 | + * 发送消息给指定用户 | |
| 14 | + * | |
| 15 | + * @param userType 用户类型 | |
| 16 | + * @param userId 用户编号 | |
| 17 | + * @param messageType 消息类型 | |
| 18 | + * @param messageContent 消息内容,JSON 格式 | |
| 19 | + */ | |
| 20 | + void send(Integer userType, Long userId, String messageType, String messageContent); | |
| 21 | + | |
| 22 | + /** | |
| 23 | + * 发送消息给指定用户类型 | |
| 24 | + * | |
| 25 | + * @param userType 用户类型 | |
| 26 | + * @param messageType 消息类型 | |
| 27 | + * @param messageContent 消息内容,JSON 格式 | |
| 28 | + */ | |
| 29 | + void send(Integer userType, String messageType, String messageContent); | |
| 30 | + | |
| 31 | + /** | |
| 32 | + * 发送消息给指定 Session | |
| 33 | + * | |
| 34 | + * @param sessionId Session 编号 | |
| 35 | + * @param messageType 消息类型 | |
| 36 | + * @param messageContent 消息内容,JSON 格式 | |
| 37 | + */ | |
| 38 | + void send(String sessionId, String messageType, String messageContent); | |
| 39 | + | |
| 40 | + default void sendObject(Integer userType, Long userId, String messageType, Object messageContent) { | |
| 41 | + send(userType, userId, messageType, JsonUtils.toJsonString(messageContent)); | |
| 42 | + } | |
| 43 | + | |
| 44 | + default void sendObject(Integer userType, String messageType, Object messageContent) { | |
| 45 | + send(userType, messageType, JsonUtils.toJsonString(messageContent)); | |
| 46 | + } | |
| 47 | + | |
| 48 | + default void sendObject(String sessionId, String messageType, Object messageContent) { | |
| 49 | + send(sessionId, messageType, JsonUtils.toJsonString(messageContent)); | |
| 50 | + } | |
| 51 | + | |
| 52 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/sender/kafka/KafkaWebSocketMessage.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.websocket.core.sender.kafka; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * Kafka 广播 WebSocket 的消息 | |
| 7 | + * | |
| 8 | + * @author 芋道源码 | |
| 9 | + */ | |
| 10 | +@Data | |
| 11 | +public class KafkaWebSocketMessage { | |
| 12 | + | |
| 13 | + /** | |
| 14 | + * Session 编号 | |
| 15 | + */ | |
| 16 | + private String sessionId; | |
| 17 | + /** | |
| 18 | + * 用户类型 | |
| 19 | + */ | |
| 20 | + private Integer userType; | |
| 21 | + /** | |
| 22 | + * 用户编号 | |
| 23 | + */ | |
| 24 | + private Long userId; | |
| 25 | + | |
| 26 | + /** | |
| 27 | + * 消息类型 | |
| 28 | + */ | |
| 29 | + private String messageType; | |
| 30 | + /** | |
| 31 | + * 消息内容 | |
| 32 | + */ | |
| 33 | + private String messageContent; | |
| 34 | + | |
| 35 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/sender/kafka/KafkaWebSocketMessageConsumer.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.websocket.core.sender.kafka; | |
| 2 | + | |
| 3 | +import lombok.RequiredArgsConstructor; | |
| 4 | +import org.springframework.amqp.rabbit.annotation.RabbitHandler; | |
| 5 | +import org.springframework.kafka.annotation.KafkaListener; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * {@link KafkaWebSocketMessage} 广播消息的消费者,真正把消息发送出去 | |
| 9 | + * | |
| 10 | + * @author 芋道源码 | |
| 11 | + */ | |
| 12 | +@RequiredArgsConstructor | |
| 13 | +public class KafkaWebSocketMessageConsumer { | |
| 14 | + | |
| 15 | + private final KafkaWebSocketMessageSender rabbitMQWebSocketMessageSender; | |
| 16 | + | |
| 17 | + @RabbitHandler | |
| 18 | + @KafkaListener( | |
| 19 | + topics = "${yudao.websocket.sender-kafka.topic}", | |
| 20 | + // 在 Group 上,使用 UUID 生成其后缀。这样,启动的 Consumer 的 Group 不同,以达到广播消费的目的 | |
| 21 | + groupId = "${yudao.websocket.sender-kafka.consumer-group}" + "-" + "#{T(java.util.UUID).randomUUID()}") | |
| 22 | + public void onMessage(KafkaWebSocketMessage message) { | |
| 23 | + rabbitMQWebSocketMessageSender.send(message.getSessionId(), | |
| 24 | + message.getUserType(), message.getUserId(), | |
| 25 | + message.getMessageType(), message.getMessageContent()); | |
| 26 | + } | |
| 27 | + | |
| 28 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/sender/kafka/KafkaWebSocketMessageSender.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.websocket.core.sender.kafka; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.websocket.core.sender.AbstractWebSocketMessageSender; | |
| 4 | +import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender; | |
| 5 | +import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManager; | |
| 6 | +import lombok.extern.slf4j.Slf4j; | |
| 7 | +import org.springframework.kafka.core.KafkaTemplate; | |
| 8 | + | |
| 9 | +import java.util.concurrent.ExecutionException; | |
| 10 | + | |
| 11 | +/** | |
| 12 | + * 基于 Kafka 的 {@link WebSocketMessageSender} 实现类 | |
| 13 | + * | |
| 14 | + * @author 芋道源码 | |
| 15 | + */ | |
| 16 | +@Slf4j | |
| 17 | +public class KafkaWebSocketMessageSender extends AbstractWebSocketMessageSender { | |
| 18 | + | |
| 19 | + private final KafkaTemplate<Object, Object> kafkaTemplate; | |
| 20 | + | |
| 21 | + private final String topic; | |
| 22 | + | |
| 23 | + public KafkaWebSocketMessageSender(WebSocketSessionManager sessionManager, | |
| 24 | + KafkaTemplate<Object, Object> kafkaTemplate, | |
| 25 | + String topic) { | |
| 26 | + super(sessionManager); | |
| 27 | + this.kafkaTemplate = kafkaTemplate; | |
| 28 | + this.topic = topic; | |
| 29 | + } | |
| 30 | + | |
| 31 | + @Override | |
| 32 | + public void send(Integer userType, Long userId, String messageType, String messageContent) { | |
| 33 | + sendKafkaMessage(null, userId, userType, messageType, messageContent); | |
| 34 | + } | |
| 35 | + | |
| 36 | + @Override | |
| 37 | + public void send(Integer userType, String messageType, String messageContent) { | |
| 38 | + sendKafkaMessage(null, null, userType, messageType, messageContent); | |
| 39 | + } | |
| 40 | + | |
| 41 | + @Override | |
| 42 | + public void send(String sessionId, String messageType, String messageContent) { | |
| 43 | + sendKafkaMessage(sessionId, null, null, messageType, messageContent); | |
| 44 | + } | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * 通过 Kafka 广播消息 | |
| 48 | + * | |
| 49 | + * @param sessionId Session 编号 | |
| 50 | + * @param userId 用户编号 | |
| 51 | + * @param userType 用户类型 | |
| 52 | + * @param messageType 消息类型 | |
| 53 | + * @param messageContent 消息内容 | |
| 54 | + */ | |
| 55 | + private void sendKafkaMessage(String sessionId, Long userId, Integer userType, | |
| 56 | + String messageType, String messageContent) { | |
| 57 | + KafkaWebSocketMessage mqMessage = new KafkaWebSocketMessage() | |
| 58 | + .setSessionId(sessionId).setUserId(userId).setUserType(userType) | |
| 59 | + .setMessageType(messageType).setMessageContent(messageContent); | |
| 60 | + try { | |
| 61 | + kafkaTemplate.send(topic, mqMessage).get(); | |
| 62 | + } catch (InterruptedException | ExecutionException e) { | |
| 63 | + log.error("[sendKafkaMessage][发送消息({}) 到 Kafka 失败]", mqMessage, e); | |
| 64 | + } | |
| 65 | + } | |
| 66 | + | |
| 67 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/sender/local/LocalWebSocketMessageSender.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.websocket.core.sender.local; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.websocket.core.sender.AbstractWebSocketMessageSender; | |
| 4 | +import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender; | |
| 5 | +import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManager; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * 本地的 {@link WebSocketMessageSender} 实现类 | |
| 9 | + * | |
| 10 | + * 注意:仅仅适合单机场景!!! | |
| 11 | + * | |
| 12 | + * @author 芋道源码 | |
| 13 | + */ | |
| 14 | +public class LocalWebSocketMessageSender extends AbstractWebSocketMessageSender { | |
| 15 | + | |
| 16 | + public LocalWebSocketMessageSender(WebSocketSessionManager sessionManager) { | |
| 17 | + super(sessionManager); | |
| 18 | + } | |
| 19 | + | |
| 20 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/sender/rabbitmq/RabbitMQWebSocketMessage.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.websocket.core.sender.rabbitmq; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +import java.io.Serializable; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * RabbitMQ 广播 WebSocket 的消息 | |
| 9 | + * | |
| 10 | + * @author 芋道源码 | |
| 11 | + */ | |
| 12 | +@Data | |
| 13 | +public class RabbitMQWebSocketMessage implements Serializable { | |
| 14 | + | |
| 15 | + /** | |
| 16 | + * Session 编号 | |
| 17 | + */ | |
| 18 | + private String sessionId; | |
| 19 | + /** | |
| 20 | + * 用户类型 | |
| 21 | + */ | |
| 22 | + private Integer userType; | |
| 23 | + /** | |
| 24 | + * 用户编号 | |
| 25 | + */ | |
| 26 | + private Long userId; | |
| 27 | + | |
| 28 | + /** | |
| 29 | + * 消息类型 | |
| 30 | + */ | |
| 31 | + private String messageType; | |
| 32 | + /** | |
| 33 | + * 消息内容 | |
| 34 | + */ | |
| 35 | + private String messageContent; | |
| 36 | + | |
| 37 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/sender/rabbitmq/RabbitMQWebSocketMessageConsumer.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.websocket.core.sender.rabbitmq; | |
| 2 | + | |
| 3 | +import lombok.RequiredArgsConstructor; | |
| 4 | +import org.springframework.amqp.core.ExchangeTypes; | |
| 5 | +import org.springframework.amqp.rabbit.annotation.*; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * {@link RabbitMQWebSocketMessage} 广播消息的消费者,真正把消息发送出去 | |
| 9 | + * | |
| 10 | + * @author 芋道源码 | |
| 11 | + */ | |
| 12 | +@RabbitListener( | |
| 13 | + bindings = @QueueBinding( | |
| 14 | + value = @Queue( | |
| 15 | + // 在 Queue 的名字上,使用 UUID 生成其后缀。这样,启动的 Consumer 的 Queue 不同,以达到广播消费的目的 | |
| 16 | + name = "${yudao.websocket.sender-rabbitmq.queue}" + "-" + "#{T(java.util.UUID).randomUUID()}", | |
| 17 | + // Consumer 关闭时,该队列就可以被自动删除了 | |
| 18 | + autoDelete = "true" | |
| 19 | + ), | |
| 20 | + exchange = @Exchange( | |
| 21 | + name = "${yudao.websocket.sender-rabbitmq.exchange}", | |
| 22 | + type = ExchangeTypes.TOPIC, | |
| 23 | + declare = "false" | |
| 24 | + ) | |
| 25 | + ) | |
| 26 | +) | |
| 27 | +@RequiredArgsConstructor | |
| 28 | +public class RabbitMQWebSocketMessageConsumer { | |
| 29 | + | |
| 30 | + private final RabbitMQWebSocketMessageSender rabbitMQWebSocketMessageSender; | |
| 31 | + | |
| 32 | + @RabbitHandler | |
| 33 | + public void onMessage(RabbitMQWebSocketMessage message) { | |
| 34 | + rabbitMQWebSocketMessageSender.send(message.getSessionId(), | |
| 35 | + message.getUserType(), message.getUserId(), | |
| 36 | + message.getMessageType(), message.getMessageContent()); | |
| 37 | + } | |
| 38 | + | |
| 39 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/sender/rabbitmq/RabbitMQWebSocketMessageSender.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.websocket.core.sender.rabbitmq; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.websocket.core.sender.AbstractWebSocketMessageSender; | |
| 4 | +import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender; | |
| 5 | +import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManager; | |
| 6 | +import lombok.extern.slf4j.Slf4j; | |
| 7 | +import org.springframework.amqp.core.TopicExchange; | |
| 8 | +import org.springframework.amqp.rabbit.core.RabbitTemplate; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * 基于 RabbitMQ 的 {@link WebSocketMessageSender} 实现类 | |
| 12 | + * | |
| 13 | + * @author 芋道源码 | |
| 14 | + */ | |
| 15 | +@Slf4j | |
| 16 | +public class RabbitMQWebSocketMessageSender extends AbstractWebSocketMessageSender { | |
| 17 | + | |
| 18 | + private final RabbitTemplate rabbitTemplate; | |
| 19 | + | |
| 20 | + private final TopicExchange topicExchange; | |
| 21 | + | |
| 22 | + public RabbitMQWebSocketMessageSender(WebSocketSessionManager sessionManager, | |
| 23 | + RabbitTemplate rabbitTemplate, | |
| 24 | + TopicExchange topicExchange) { | |
| 25 | + super(sessionManager); | |
| 26 | + this.rabbitTemplate = rabbitTemplate; | |
| 27 | + this.topicExchange = topicExchange; | |
| 28 | + } | |
| 29 | + | |
| 30 | + @Override | |
| 31 | + public void send(Integer userType, Long userId, String messageType, String messageContent) { | |
| 32 | + sendRabbitMQMessage(null, userId, userType, messageType, messageContent); | |
| 33 | + } | |
| 34 | + | |
| 35 | + @Override | |
| 36 | + public void send(Integer userType, String messageType, String messageContent) { | |
| 37 | + sendRabbitMQMessage(null, null, userType, messageType, messageContent); | |
| 38 | + } | |
| 39 | + | |
| 40 | + @Override | |
| 41 | + public void send(String sessionId, String messageType, String messageContent) { | |
| 42 | + sendRabbitMQMessage(sessionId, null, null, messageType, messageContent); | |
| 43 | + } | |
| 44 | + | |
| 45 | + /** | |
| 46 | + * 通过 RabbitMQ 广播消息 | |
| 47 | + * | |
| 48 | + * @param sessionId Session 编号 | |
| 49 | + * @param userId 用户编号 | |
| 50 | + * @param userType 用户类型 | |
| 51 | + * @param messageType 消息类型 | |
| 52 | + * @param messageContent 消息内容 | |
| 53 | + */ | |
| 54 | + private void sendRabbitMQMessage(String sessionId, Long userId, Integer userType, | |
| 55 | + String messageType, String messageContent) { | |
| 56 | + RabbitMQWebSocketMessage mqMessage = new RabbitMQWebSocketMessage() | |
| 57 | + .setSessionId(sessionId).setUserId(userId).setUserType(userType) | |
| 58 | + .setMessageType(messageType).setMessageContent(messageContent); | |
| 59 | + rabbitTemplate.convertAndSend(topicExchange.getName(), null, mqMessage); | |
| 60 | + } | |
| 61 | + | |
| 62 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/sender/redis/RedisWebSocketMessage.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.websocket.core.sender.redis; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.mq.redis.core.pubsub.AbstractRedisChannelMessage; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * Redis 广播 WebSocket 的消息 | |
| 8 | + */ | |
| 9 | +@Data | |
| 10 | +public class RedisWebSocketMessage extends AbstractRedisChannelMessage { | |
| 11 | + | |
| 12 | + /** | |
| 13 | + * Session 编号 | |
| 14 | + */ | |
| 15 | + private String sessionId; | |
| 16 | + /** | |
| 17 | + * 用户类型 | |
| 18 | + */ | |
| 19 | + private Integer userType; | |
| 20 | + /** | |
| 21 | + * 用户编号 | |
| 22 | + */ | |
| 23 | + private Long userId; | |
| 24 | + | |
| 25 | + /** | |
| 26 | + * 消息类型 | |
| 27 | + */ | |
| 28 | + private String messageType; | |
| 29 | + /** | |
| 30 | + * 消息内容 | |
| 31 | + */ | |
| 32 | + private String messageContent; | |
| 33 | + | |
| 34 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/sender/redis/RedisWebSocketMessageConsumer.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.websocket.core.sender.redis; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.mq.redis.core.pubsub.AbstractRedisChannelMessageListener; | |
| 4 | +import lombok.RequiredArgsConstructor; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * {@link RedisWebSocketMessage} 广播消息的消费者,真正把消息发送出去 | |
| 8 | + * | |
| 9 | + * @author 芋道源码 | |
| 10 | + */ | |
| 11 | +@RequiredArgsConstructor | |
| 12 | +public class RedisWebSocketMessageConsumer extends AbstractRedisChannelMessageListener<RedisWebSocketMessage> { | |
| 13 | + | |
| 14 | + private final RedisWebSocketMessageSender redisWebSocketMessageSender; | |
| 15 | + | |
| 16 | + @Override | |
| 17 | + public void onMessage(RedisWebSocketMessage message) { | |
| 18 | + redisWebSocketMessageSender.send(message.getSessionId(), | |
| 19 | + message.getUserType(), message.getUserId(), | |
| 20 | + message.getMessageType(), message.getMessageContent()); | |
| 21 | + } | |
| 22 | + | |
| 23 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/sender/redis/RedisWebSocketMessageSender.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.websocket.core.sender.redis; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate; | |
| 4 | +import cn.iocoder.yudao.framework.websocket.core.sender.AbstractWebSocketMessageSender; | |
| 5 | +import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender; | |
| 6 | +import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManager; | |
| 7 | +import lombok.extern.slf4j.Slf4j; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * 基于 Redis 的 {@link WebSocketMessageSender} 实现类 | |
| 11 | + * | |
| 12 | + * @author 芋道源码 | |
| 13 | + */ | |
| 14 | +@Slf4j | |
| 15 | +public class RedisWebSocketMessageSender extends AbstractWebSocketMessageSender { | |
| 16 | + | |
| 17 | + private final RedisMQTemplate redisMQTemplate; | |
| 18 | + | |
| 19 | + public RedisWebSocketMessageSender(WebSocketSessionManager sessionManager, | |
| 20 | + RedisMQTemplate redisMQTemplate) { | |
| 21 | + super(sessionManager); | |
| 22 | + this.redisMQTemplate = redisMQTemplate; | |
| 23 | + } | |
| 24 | + | |
| 25 | + @Override | |
| 26 | + public void send(Integer userType, Long userId, String messageType, String messageContent) { | |
| 27 | + sendRedisMessage(null, userId, userType, messageType, messageContent); | |
| 28 | + } | |
| 29 | + | |
| 30 | + @Override | |
| 31 | + public void send(Integer userType, String messageType, String messageContent) { | |
| 32 | + sendRedisMessage(null, null, userType, messageType, messageContent); | |
| 33 | + } | |
| 34 | + | |
| 35 | + @Override | |
| 36 | + public void send(String sessionId, String messageType, String messageContent) { | |
| 37 | + sendRedisMessage(sessionId, null, null, messageType, messageContent); | |
| 38 | + } | |
| 39 | + | |
| 40 | + /** | |
| 41 | + * 通过 Redis 广播消息 | |
| 42 | + * | |
| 43 | + * @param sessionId Session 编号 | |
| 44 | + * @param userId 用户编号 | |
| 45 | + * @param userType 用户类型 | |
| 46 | + * @param messageType 消息类型 | |
| 47 | + * @param messageContent 消息内容 | |
| 48 | + */ | |
| 49 | + private void sendRedisMessage(String sessionId, Long userId, Integer userType, | |
| 50 | + String messageType, String messageContent) { | |
| 51 | + RedisWebSocketMessage mqMessage = new RedisWebSocketMessage() | |
| 52 | + .setSessionId(sessionId).setUserId(userId).setUserType(userType) | |
| 53 | + .setMessageType(messageType).setMessageContent(messageContent); | |
| 54 | + redisMQTemplate.send(mqMessage); | |
| 55 | + } | |
| 56 | + | |
| 57 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/sender/rocketmq/RocketMQWebSocketMessage.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.websocket.core.sender.rocketmq; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * RocketMQ 广播 WebSocket 的消息 | |
| 7 | + * | |
| 8 | + * @author 芋道源码 | |
| 9 | + */ | |
| 10 | +@Data | |
| 11 | +public class RocketMQWebSocketMessage { | |
| 12 | + | |
| 13 | + /** | |
| 14 | + * Session 编号 | |
| 15 | + */ | |
| 16 | + private String sessionId; | |
| 17 | + /** | |
| 18 | + * 用户类型 | |
| 19 | + */ | |
| 20 | + private Integer userType; | |
| 21 | + /** | |
| 22 | + * 用户编号 | |
| 23 | + */ | |
| 24 | + private Long userId; | |
| 25 | + | |
| 26 | + /** | |
| 27 | + * 消息类型 | |
| 28 | + */ | |
| 29 | + private String messageType; | |
| 30 | + /** | |
| 31 | + * 消息内容 | |
| 32 | + */ | |
| 33 | + private String messageContent; | |
| 34 | + | |
| 35 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/sender/rocketmq/RocketMQWebSocketMessageConsumer.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.websocket.core.sender.rocketmq; | |
| 2 | + | |
| 3 | +import lombok.RequiredArgsConstructor; | |
| 4 | +import org.apache.rocketmq.spring.annotation.MessageModel; | |
| 5 | +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; | |
| 6 | +import org.apache.rocketmq.spring.core.RocketMQListener; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * {@link RocketMQWebSocketMessage} 广播消息的消费者,真正把消息发送出去 | |
| 10 | + * | |
| 11 | + * @author 芋道源码 | |
| 12 | + */ | |
| 13 | +@RocketMQMessageListener( // 重点:添加 @RocketMQMessageListener 注解,声明消费的 topic | |
| 14 | + topic = "${yudao.websocket.sender-rocketmq.topic}", | |
| 15 | + consumerGroup = "${yudao.websocket.sender-rocketmq.consumer-group}", | |
| 16 | + messageModel = MessageModel.BROADCASTING // 设置为广播模式,保证每个实例都能收到消息 | |
| 17 | +) | |
| 18 | +@RequiredArgsConstructor | |
| 19 | +public class RocketMQWebSocketMessageConsumer implements RocketMQListener<RocketMQWebSocketMessage> { | |
| 20 | + | |
| 21 | + private final RocketMQWebSocketMessageSender rocketMQWebSocketMessageSender; | |
| 22 | + | |
| 23 | + @Override | |
| 24 | + public void onMessage(RocketMQWebSocketMessage message) { | |
| 25 | + rocketMQWebSocketMessageSender.send(message.getSessionId(), | |
| 26 | + message.getUserType(), message.getUserId(), | |
| 27 | + message.getMessageType(), message.getMessageContent()); | |
| 28 | + } | |
| 29 | + | |
| 30 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/sender/rocketmq/RocketMQWebSocketMessageSender.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.websocket.core.sender.rocketmq; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.websocket.core.sender.AbstractWebSocketMessageSender; | |
| 4 | +import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender; | |
| 5 | +import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManager; | |
| 6 | +import lombok.extern.slf4j.Slf4j; | |
| 7 | +import org.apache.rocketmq.spring.core.RocketMQTemplate; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * 基于 RocketMQ 的 {@link WebSocketMessageSender} 实现类 | |
| 11 | + * | |
| 12 | + * @author 芋道源码 | |
| 13 | + */ | |
| 14 | +@Slf4j | |
| 15 | +public class RocketMQWebSocketMessageSender extends AbstractWebSocketMessageSender { | |
| 16 | + | |
| 17 | + private final RocketMQTemplate rocketMQTemplate; | |
| 18 | + | |
| 19 | + private final String topic; | |
| 20 | + | |
| 21 | + public RocketMQWebSocketMessageSender(WebSocketSessionManager sessionManager, | |
| 22 | + RocketMQTemplate rocketMQTemplate, | |
| 23 | + String topic) { | |
| 24 | + super(sessionManager); | |
| 25 | + this.rocketMQTemplate = rocketMQTemplate; | |
| 26 | + this.topic = topic; | |
| 27 | + } | |
| 28 | + | |
| 29 | + @Override | |
| 30 | + public void send(Integer userType, Long userId, String messageType, String messageContent) { | |
| 31 | + sendRocketMQMessage(null, userId, userType, messageType, messageContent); | |
| 32 | + } | |
| 33 | + | |
| 34 | + @Override | |
| 35 | + public void send(Integer userType, String messageType, String messageContent) { | |
| 36 | + sendRocketMQMessage(null, null, userType, messageType, messageContent); | |
| 37 | + } | |
| 38 | + | |
| 39 | + @Override | |
| 40 | + public void send(String sessionId, String messageType, String messageContent) { | |
| 41 | + sendRocketMQMessage(sessionId, null, null, messageType, messageContent); | |
| 42 | + } | |
| 43 | + | |
| 44 | + /** | |
| 45 | + * 通过 RocketMQ 广播消息 | |
| 46 | + * | |
| 47 | + * @param sessionId Session 编号 | |
| 48 | + * @param userId 用户编号 | |
| 49 | + * @param userType 用户类型 | |
| 50 | + * @param messageType 消息类型 | |
| 51 | + * @param messageContent 消息内容 | |
| 52 | + */ | |
| 53 | + private void sendRocketMQMessage(String sessionId, Long userId, Integer userType, | |
| 54 | + String messageType, String messageContent) { | |
| 55 | + RocketMQWebSocketMessage mqMessage = new RocketMQWebSocketMessage() | |
| 56 | + .setSessionId(sessionId).setUserId(userId).setUserType(userType) | |
| 57 | + .setMessageType(messageType).setMessageContent(messageContent); | |
| 58 | + rocketMQTemplate.syncSend(topic, mqMessage); | |
| 59 | + } | |
| 60 | + | |
| 61 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/session/WebSocketSessionManagerImpl.java
| ... | ... | @@ -2,10 +2,14 @@ package cn.iocoder.yudao.framework.websocket.core.session; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.iocoder.yudao.framework.security.core.LoginUser; |
| 5 | +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | |
| 5 | 6 | import cn.iocoder.yudao.framework.websocket.core.util.WebSocketFrameworkUtils; |
| 6 | 7 | import org.springframework.web.socket.WebSocketSession; |
| 7 | 8 | |
| 8 | -import java.util.*; | |
| 9 | +import java.util.ArrayList; | |
| 10 | +import java.util.Collection; | |
| 11 | +import java.util.LinkedList; | |
| 12 | +import java.util.List; | |
| 9 | 13 | import java.util.concurrent.ConcurrentHashMap; |
| 10 | 14 | import java.util.concurrent.ConcurrentMap; |
| 11 | 15 | import java.util.concurrent.CopyOnWriteArrayList; |
| ... | ... | @@ -91,10 +95,18 @@ public class WebSocketSessionManagerImpl implements WebSocketSessionManager { |
| 91 | 95 | return new ArrayList<>(); |
| 92 | 96 | } |
| 93 | 97 | LinkedList<WebSocketSession> result = new LinkedList<>(); // 避免扩容 |
| 98 | + Long contextTenantId = TenantContextHolder.getTenantId(); | |
| 94 | 99 | for (List<WebSocketSession> sessions : userSessionsMap.values()) { |
| 95 | - if (CollUtil.isNotEmpty(sessions)) { | |
| 100 | + if (CollUtil.isEmpty(sessions)) { | |
| 96 | 101 | continue; |
| 97 | 102 | } |
| 103 | + // 特殊:如果租户不匹配,则直接排除 | |
| 104 | + if (contextTenantId != null) { | |
| 105 | + Long userTenantId = WebSocketFrameworkUtils.getTenantId(sessions.get(0)); | |
| 106 | + if (!contextTenantId.equals(userTenantId)) { | |
| 107 | + continue; | |
| 108 | + } | |
| 109 | + } | |
| 98 | 110 | result.addAll(sessions); |
| 99 | 111 | } |
| 100 | 112 | return result; | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/util/WebSocketFrameworkUtils.java
| 1 | 1 | package cn.iocoder.yudao.framework.websocket.core.util; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.security.core.LoginUser; |
| 4 | -import org.springframework.lang.Nullable; | |
| 5 | 4 | import org.springframework.web.socket.WebSocketSession; |
| 6 | 5 | |
| 7 | 6 | import java.util.Map; |
| ... | ... | @@ -39,7 +38,6 @@ public class WebSocketFrameworkUtils { |
| 39 | 38 | * |
| 40 | 39 | * @return 用户编号 |
| 41 | 40 | */ |
| 42 | - @Nullable | |
| 43 | 41 | public static Long getLoginUserId(WebSocketSession session) { |
| 44 | 42 | LoginUser loginUser = getLoginUser(session); |
| 45 | 43 | return loginUser != null ? loginUser.getId() : null; |
| ... | ... | @@ -50,10 +48,20 @@ public class WebSocketFrameworkUtils { |
| 50 | 48 | * |
| 51 | 49 | * @return 用户编号 |
| 52 | 50 | */ |
| 53 | - @Nullable | |
| 54 | 51 | public static Integer getLoginUserType(WebSocketSession session) { |
| 55 | 52 | LoginUser loginUser = getLoginUser(session); |
| 56 | 53 | return loginUser != null ? loginUser.getUserType() : null; |
| 57 | 54 | } |
| 58 | 55 | |
| 56 | + /** | |
| 57 | + * 获得当前用户的租户编号 | |
| 58 | + * | |
| 59 | + * @param session Session | |
| 60 | + * @return 租户编号 | |
| 61 | + */ | |
| 62 | + public static Long getTenantId(WebSocketSession session) { | |
| 63 | + LoginUser loginUser = getLoginUser(session); | |
| 64 | + return loginUser != null ? loginUser.getTenantId() : null; | |
| 65 | + } | |
| 66 | + | |
| 59 | 67 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/《芋道 Spring Boot WebSocket 入门》.md
0 → 100644
| 1 | +<http://www.iocoder.cn/Spring-Boot/WebSocket/?yudao> | ... | ... |
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/websocket/WebSocketSenderApi.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.api.websocket; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * WebSocket 发送器的 API 接口 | |
| 7 | + * | |
| 8 | + * 对 WebSocketMessageSender 进行封装,提供给其它模块使用 | |
| 9 | + * | |
| 10 | + * @author 芋道源码 | |
| 11 | + */ | |
| 12 | +public interface WebSocketSenderApi { | |
| 13 | + | |
| 14 | + /** | |
| 15 | + * 发送消息给指定用户 | |
| 16 | + * | |
| 17 | + * @param userType 用户类型 | |
| 18 | + * @param userId 用户编号 | |
| 19 | + * @param messageType 消息类型 | |
| 20 | + * @param messageContent 消息内容,JSON 格式 | |
| 21 | + */ | |
| 22 | + void send(Integer userType, Long userId, String messageType, String messageContent); | |
| 23 | + | |
| 24 | + /** | |
| 25 | + * 发送消息给指定用户类型 | |
| 26 | + * | |
| 27 | + * @param userType 用户类型 | |
| 28 | + * @param messageType 消息类型 | |
| 29 | + * @param messageContent 消息内容,JSON 格式 | |
| 30 | + */ | |
| 31 | + void send(Integer userType, String messageType, String messageContent); | |
| 32 | + | |
| 33 | + /** | |
| 34 | + * 发送消息给指定 Session | |
| 35 | + * | |
| 36 | + * @param sessionId Session 编号 | |
| 37 | + * @param messageType 消息类型 | |
| 38 | + * @param messageContent 消息内容,JSON 格式 | |
| 39 | + */ | |
| 40 | + void send(String sessionId, String messageType, String messageContent); | |
| 41 | + | |
| 42 | + default void sendObject(Integer userType, Long userId, String messageType, Object messageContent) { | |
| 43 | + send(userType, userId, messageType, JsonUtils.toJsonString(messageContent)); | |
| 44 | + } | |
| 45 | + | |
| 46 | + default void sendObject(Integer userType, String messageType, Object messageContent) { | |
| 47 | + send(userType, messageType, JsonUtils.toJsonString(messageContent)); | |
| 48 | + } | |
| 49 | + | |
| 50 | + default void sendObject(String sessionId, String messageType, Object messageContent) { | |
| 51 | + send(sessionId, messageType, JsonUtils.toJsonString(messageContent)); | |
| 52 | + } | |
| 53 | + | |
| 54 | +} | ... | ... |
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/api/websocket/WebSocketSenderApiImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.api.websocket; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender; | |
| 4 | +import org.springframework.stereotype.Component; | |
| 5 | + | |
| 6 | +import javax.annotation.Resource; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * WebSocket 发送器的 API 实现类 | |
| 10 | + * | |
| 11 | + * @author 芋道源码 | |
| 12 | + */ | |
| 13 | +@Component | |
| 14 | +public class WebSocketSenderApiImpl implements WebSocketSenderApi { | |
| 15 | + | |
| 16 | + @Resource | |
| 17 | + private WebSocketMessageSender webSocketMessageSender; | |
| 18 | + | |
| 19 | + @Override | |
| 20 | + public void send(Integer userType, Long userId, String messageType, String messageContent) { | |
| 21 | + webSocketMessageSender.send(userType, userId, messageType, messageContent); | |
| 22 | + } | |
| 23 | + | |
| 24 | + @Override | |
| 25 | + public void send(Integer userType, String messageType, String messageContent) { | |
| 26 | + webSocketMessageSender.send(userType, messageType, messageContent); | |
| 27 | + } | |
| 28 | + | |
| 29 | + @Override | |
| 30 | + public void send(String sessionId, String messageType, String messageContent) { | |
| 31 | + webSocketMessageSender.send(sessionId, messageType, messageContent); | |
| 32 | + } | |
| 33 | + | |
| 34 | +} | ... | ... |
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/DemoWebSocketMessageListener.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.websocket; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | |
| 4 | +import cn.iocoder.yudao.framework.websocket.core.listener.WebSocketMessageListener; | |
| 5 | +import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender; | |
| 6 | +import cn.iocoder.yudao.framework.websocket.core.util.WebSocketFrameworkUtils; | |
| 7 | +import cn.iocoder.yudao.module.infra.websocket.message.DemoReceiveMessage; | |
| 8 | +import cn.iocoder.yudao.module.infra.websocket.message.DemoSendMessage; | |
| 9 | +import org.springframework.stereotype.Component; | |
| 10 | +import org.springframework.web.socket.WebSocketSession; | |
| 11 | + | |
| 12 | +import javax.annotation.Resource; | |
| 13 | + | |
| 14 | +/** | |
| 15 | + * WebSocket 示例:单发消息 | |
| 16 | + * | |
| 17 | + * @author 芋道源码 | |
| 18 | + */ | |
| 19 | +@Component | |
| 20 | +public class DemoWebSocketMessageListener implements WebSocketMessageListener<DemoSendMessage> { | |
| 21 | + | |
| 22 | + @Resource | |
| 23 | + private WebSocketMessageSender webSocketMessageSender; | |
| 24 | + | |
| 25 | + @Override | |
| 26 | + public void onMessage(WebSocketSession session, DemoSendMessage message) { | |
| 27 | + Long fromUserId = WebSocketFrameworkUtils.getLoginUserId(session); | |
| 28 | + // 情况一:单发 | |
| 29 | + if (message.getToUserId() != null) { | |
| 30 | + DemoReceiveMessage toMessage = new DemoReceiveMessage().setFromUserId(fromUserId) | |
| 31 | + .setText(message.getText()).setSingle(true); | |
| 32 | + webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), message.getToUserId(), // 给指定用户 | |
| 33 | + "demo-message-receive", toMessage); | |
| 34 | + return; | |
| 35 | + } | |
| 36 | + // 情况二:群发 | |
| 37 | + DemoReceiveMessage toMessage = new DemoReceiveMessage().setFromUserId(fromUserId) | |
| 38 | + .setText(message.getText()).setSingle(false); | |
| 39 | + webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), // 给所有用户 | |
| 40 | + "demo-message-receive", toMessage); | |
| 41 | + } | |
| 42 | + | |
| 43 | + @Override | |
| 44 | + public String getType() { | |
| 45 | + return "demo-message-send"; | |
| 46 | + } | |
| 47 | + | |
| 48 | +} | ... | ... |
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/message/DemoReceiveMessage.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.websocket.message; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * 示例:server -> client 同步消息 | |
| 7 | + * | |
| 8 | + * @author 芋道源码 | |
| 9 | + */ | |
| 10 | +@Data | |
| 11 | +public class DemoReceiveMessage { | |
| 12 | + | |
| 13 | + /** | |
| 14 | + * 接收人的编号 | |
| 15 | + */ | |
| 16 | + private Long fromUserId; | |
| 17 | + /** | |
| 18 | + * 内容 | |
| 19 | + */ | |
| 20 | + private String text; | |
| 21 | + | |
| 22 | + /** | |
| 23 | + * 是否单聊 | |
| 24 | + */ | |
| 25 | + private Boolean single; | |
| 26 | + | |
| 27 | +} | ... | ... |
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/websocket/message/DemoSendMessage.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.websocket.message; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * 示例:client -> server 发送消息 | |
| 7 | + * | |
| 8 | + * @author 芋道源码 | |
| 9 | + */ | |
| 10 | +@Data | |
| 11 | +public class DemoSendMessage { | |
| 12 | + | |
| 13 | + /** | |
| 14 | + * 发送给谁 | |
| 15 | + * | |
| 16 | + * 如果为空,说明发送给所有人 | |
| 17 | + */ | |
| 18 | + private Long toUserId; | |
| 19 | + /** | |
| 20 | + * 内容 | |
| 21 | + */ | |
| 22 | + private String text; | |
| 23 | + | |
| 24 | +} | ... | ... |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/notice/NoticeController.java
| 1 | 1 | package cn.iocoder.yudao.module.system.controller.admin.notice; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.lang.Assert; | |
| 4 | +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | |
| 3 | 5 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 7 | +import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi; | |
| 5 | 8 | import cn.iocoder.yudao.module.system.controller.admin.notice.vo.NoticeCreateReqVO; |
| 6 | 9 | import cn.iocoder.yudao.module.system.controller.admin.notice.vo.NoticePageReqVO; |
| 7 | 10 | import cn.iocoder.yudao.module.system.controller.admin.notice.vo.NoticeRespVO; |
| 8 | 11 | import cn.iocoder.yudao.module.system.controller.admin.notice.vo.NoticeUpdateReqVO; |
| 9 | 12 | import cn.iocoder.yudao.module.system.convert.notice.NoticeConvert; |
| 13 | +import cn.iocoder.yudao.module.system.dal.dataobject.notice.NoticeDO; | |
| 10 | 14 | import cn.iocoder.yudao.module.system.service.notice.NoticeService; |
| 11 | 15 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 12 | 16 | import io.swagger.v3.oas.annotations.Parameter; |
| ... | ... | @@ -29,6 +33,9 @@ public class NoticeController { |
| 29 | 33 | @Resource |
| 30 | 34 | private NoticeService noticeService; |
| 31 | 35 | |
| 36 | + @Resource | |
| 37 | + private WebSocketSenderApi webSocketSenderApi; | |
| 38 | + | |
| 32 | 39 | @PostMapping("/create") |
| 33 | 40 | @Operation(summary = "创建通知公告") |
| 34 | 41 | @PreAuthorize("@ss.hasPermission('system:notice:create')") |
| ... | ... | @@ -69,4 +76,16 @@ public class NoticeController { |
| 69 | 76 | return success(NoticeConvert.INSTANCE.convert(noticeService.getNotice(id))); |
| 70 | 77 | } |
| 71 | 78 | |
| 79 | + @PostMapping("/push") | |
| 80 | + @Operation(summary = "推送通知公告", description = "只发送给 websocket 连接在线的用户") | |
| 81 | + @Parameter(name = "id", description = "编号", required = true, example = "1024") | |
| 82 | + @PreAuthorize("@ss.hasPermission('system:notice:update')") | |
| 83 | + public CommonResult<Boolean> push(@RequestParam("id") Long id) { | |
| 84 | + NoticeDO notice = noticeService.getNotice(id); | |
| 85 | + Assert.notNull(notice, "公告不能为空"); | |
| 86 | + // 通过 websocket 推送给在线的用户 | |
| 87 | + webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), "notice-push", notice); | |
| 88 | + return success(true); | |
| 89 | + } | |
| 90 | + | |
| 72 | 91 | } | ... | ... |
yudao-server/src/main/resources/application-local.yaml
| ... | ... | @@ -123,8 +123,8 @@ spring: |
| 123 | 123 | rabbitmq: |
| 124 | 124 | host: 127.0.0.1 # RabbitMQ 服务的地址 |
| 125 | 125 | port: 5672 # RabbitMQ 服务的端口 |
| 126 | - username: guest # RabbitMQ 服务的账号 | |
| 127 | - password: guest # RabbitMQ 服务的密码 | |
| 126 | + username: rabbit # RabbitMQ 服务的账号 | |
| 127 | + password: rabbit # RabbitMQ 服务的密码 | |
| 128 | 128 | # Kafka 配置项,对应 KafkaProperties 配置类 |
| 129 | 129 | kafka: |
| 130 | 130 | bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔 | ... | ... |
yudao-server/src/main/resources/application.yaml
| ... | ... | @@ -147,6 +147,16 @@ yudao: |
| 147 | 147 | websocket: |
| 148 | 148 | enable: true # websocket的开关 |
| 149 | 149 | path: /infra/ws # 路径 |
| 150 | + sender-type: local # 消息发送的类型,可选值为 local、redis、rocketmq、kafka、rabbitmq | |
| 151 | + sender-rocketmq: | |
| 152 | + topic: ${spring.application.name}-websocket # 消息发送的 RocketMQ Topic | |
| 153 | + consumer-group: ${spring.application.name}-websocket-consumer # 消息发送的 RocketMQ Consumer Group | |
| 154 | + sender-rabbitmq: | |
| 155 | + exchange: ${spring.application.name}-websocket-exchange # 消息发送的 RabbitMQ Exchange | |
| 156 | + queue: ${spring.application.name}-websocket-queue # 消息发送的 RabbitMQ Queue | |
| 157 | + sender-kafka: | |
| 158 | + topic: ${spring.application.name}-websocket # 消息发送的 Kafka Topic | |
| 159 | + consumer-group: ${spring.application.name}-websocket-consumer # 消息发送的 Kafka Consumer Group | |
| 150 | 160 | swagger: |
| 151 | 161 | title: 芋道快速开发平台 |
| 152 | 162 | description: 提供管理后台、用户 App 的所有功能 | ... | ... |