websocket:重新封装 websocket 组件,支持 token 认证,并增加 WebSocketMessageListener 方便处理消息
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<>();
|
||||
/**
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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: 提供管理员管理的所有功能
|
||||
|
||||
Reference in New Issue
Block a user