websocket:重新封装 websocket 组件,支持 token 认证,并增加 WebSocketMessageListener 方便处理消息

This commit is contained in:
YunaiV
2023-11-25 20:44:42 +08:00
parent 522ab17902
commit 2d9aa7a94a
57 changed files with 1930 additions and 37 deletions

View File

@@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.infra.api.websocket;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.infra.api.websocket.dto.WebSocketSendReqDTO;
import cn.iocoder.yudao.module.infra.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import javax.validation.Valid;
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿fallbackFactory =
@Tag(name = "RPC 服务 - WebSocket 发送器的") // 对 WebSocketMessageSender 进行封装,提供给其它模块使用
public interface WebSocketSenderApi {
String PREFIX = ApiConstants.PREFIX + "/websocket";
@PostMapping(PREFIX + "/send")
@Operation(summary = "发送 WebSocket 消息")
CommonResult<Boolean> send(@Valid @RequestBody WebSocketSendReqDTO message);
/**
* 发送消息给指定用户
*
* @param userType 用户类型
* @param userId 用户编号
* @param messageType 消息类型
* @param messageContent 消息内容JSON 格式
*/
default void send(Integer userType, Long userId, String messageType, String messageContent) {
send(new WebSocketSendReqDTO().setUserType(userType).setUserId(userId)
.setMessageType(messageType).setMessageContent(messageContent)).checkError();
}
/**
* 发送消息给指定用户类型
*
* @param userType 用户类型
* @param messageType 消息类型
* @param messageContent 消息内容JSON 格式
*/
default void send(Integer userType, String messageType, String messageContent) {
send(new WebSocketSendReqDTO().setUserType(userType)
.setMessageType(messageType).setMessageContent(messageContent)).checkError();
}
/**
* 发送消息给指定 Session
*
* @param sessionId Session 编号
* @param messageType 消息类型
* @param messageContent 消息内容JSON 格式
*/
default void send(String sessionId, String messageType, String messageContent) {
send(new WebSocketSendReqDTO().setSessionId(sessionId)
.setMessageType(messageType).setMessageContent(messageContent)).checkError();
}
default void sendObject(Integer userType, Long userId, String messageType, Object messageContent) {
send(userType, userId, messageType, JsonUtils.toJsonString(messageContent));
}
default void sendObject(Integer userType, String messageType, Object messageContent) {
send(userType, messageType, JsonUtils.toJsonString(messageContent));
}
default void sendObject(String sessionId, String messageType, Object messageContent) {
send(sessionId, messageType, JsonUtils.toJsonString(messageContent));
}
}

View File

@@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.infra.api.websocket.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
@Schema(description = "RPC 服务 - WebSocket 消息发送 Request DTO")
@Data
public class WebSocketSendReqDTO {
@Schema(description = "Session 编号", example = "abc")
private String sessionId;
@Schema(description = "用户编号", example = "1024")
private Long userId;
@Schema(description = "用户类型", example = "1")
private Integer userType;
@Schema(description = "消息类型", example = "demo-message")
@NotEmpty(message = "消息类型不能为空")
private String messageType;
@Schema(description = "消息内容", example = "{\"name\":\"李四\"}}")
@NotEmpty(message = "消息内容不能为空")
private String messageContent;
}

View File

@@ -61,6 +61,11 @@
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-websocket</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
@@ -101,6 +106,10 @@
</dependency>
<!-- 消息队列相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-mq</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>

View File

@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.infra.api.websocket;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender;
import cn.iocoder.yudao.module.infra.api.websocket.dto.WebSocketSendReqDTO;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@RestController // 提供 RESTful API 接口,给 Feign 调用
@Validated
public class WebSocketSenderApiImpl implements WebSocketSenderApi {
@Resource
private WebSocketMessageSender webSocketMessageSender;
@Override
public CommonResult<Boolean> send(WebSocketSendReqDTO message) {
if (StrUtil.isNotEmpty(message.getSessionId())) {
webSocketMessageSender.send(message.getSessionId(),
message.getMessageType(), message.getMessageContent());
} else if (message.getUserType() != null && message.getUserId() != null) {
webSocketMessageSender.send(message.getUserType(), message.getUserId(),
message.getMessageType(), message.getMessageContent());
} else if (message.getUserType() != null) {
webSocketMessageSender.send(message.getUserType(),
message.getMessageType(), message.getMessageContent());
}
return success(true);
}
}

View File

@@ -64,7 +64,7 @@ public class CodegenBuilder {
*/
public static final String TENANT_ID_FIELD = "tenantId";
/**
* {@link BaseDO} 的字段
* {@link cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO} 的字段
*/
public static final Set<String> BASE_DO_FIELDS = new HashSet<>();
/**

View File

@@ -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<DemoSendMessage> {
@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";
}
}

View File

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

View File

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

View File

@@ -60,6 +60,21 @@ spring:
--- #################### MQ 消息队列相关配置 ####################
# rocketmq 配置项,对应 RocketMQProperties 配置类
rocketmq:
name-server: 127.0.0.1:9876 # RocketMQ Namesrv
spring:
# RabbitMQ 配置项,对应 RabbitProperties 配置类
rabbitmq:
host: 127.0.0.1 # RabbitMQ 服务的地址
port: 5672 # RabbitMQ 服务的端口
username: guest # RabbitMQ 服务的账号
password: guest # RabbitMQ 服务的密码
# Kafka 配置项,对应 KafkaProperties 配置类
kafka:
bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔
--- #################### 定时任务相关配置 ####################
xxl:
job:

View File

@@ -75,6 +75,21 @@ spring:
--- #################### MQ 消息队列相关配置 ####################
# rocketmq 配置项,对应 RocketMQProperties 配置类
rocketmq:
name-server: 127.0.0.1:9876 # RocketMQ Namesrv
spring:
# RabbitMQ 配置项,对应 RabbitProperties 配置类
rabbitmq:
host: 127.0.0.1 # RabbitMQ 服务的地址
port: 5672 # RabbitMQ 服务的端口
username: guest # RabbitMQ 服务的账号
password: guest # RabbitMQ 服务的密码
# Kafka 配置项,对应 KafkaProperties 配置类
kafka:
bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔
--- #################### 定时任务相关配置 ####################
xxl:
job:

View File

@@ -72,7 +72,31 @@ spring:
--- #################### RPC 远程调用相关配置 ####################
--- #################### MQ 消息队列相关配置 ####################
--- #################### 消息队列相关 ####################
# rocketmq 配置项,对应 RocketMQProperties 配置类
rocketmq:
# Producer 配置项
producer:
group: ${spring.application.name}_PRODUCER # 生产者分组
spring:
# Kafka 配置项,对应 KafkaProperties 配置类
kafka:
# Kafka Producer 配置项
producer:
acks: 1 # 0-不应答。1-leader 应答。all-所有 leader 和 follower 应答。
retries: 3 # 发送失败时,重试发送的次数
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer # 消息的 value 的序列化
# Kafka Consumer 配置项
consumer:
auto-offset-reset: earliest # 设置消费者分组最初的消费进度为 earliest 。可参考博客 https://blog.csdn.net/lishuangzhe7047/article/details/74530417 理解
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
properties:
spring.json.trusted.packages: '*'
# Kafka Consumer Listener 监听器配置
listener:
missing-topics-fatal: false # 消费监听接口监听的主题不存在时,默认会报错。所以通过设置为 false ,解决报错
--- #################### 定时任务相关配置 ####################
@@ -92,6 +116,19 @@ yudao:
web:
admin-ui:
url: http://dashboard.yudao.iocoder.cn # Admin 管理后台 UI 的地址
websocket:
enable: true # websocket的开关
path: /infra/ws # 路径
sender-type: redis # 消息发送的类型,可选值为 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: 提供管理员管理的所有功能