commit
ae763f727f
@ -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<RedisMessageInterceptor> interceptors) {
|
||||
RedisMQTemplate redisMQTemplate = new RedisMQTemplate(redisTemplate);
|
||||
// 添加拦截器
|
||||
interceptors.forEach(redisMQTemplate::addInterceptor);
|
||||
return redisMQTemplate;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
package cn.iocoder.yudao.framework.websocket.core;
|
||||
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.server.HandshakeInterceptor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class UserHandshakeInterceptor implements HandshakeInterceptor {
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
|
||||
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
|
||||
attributes.put(WebSocketKeyDefine.LOGIN_USER, loginUser);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
package cn.iocoder.yudao.framework.websocket.core;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class WebSocketKeyDefine {
|
||||
public static final String LOGIN_USER ="LOGIN_USER";
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
package cn.iocoder.yudao.framework.websocket.core;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class WebSocketMessageDO {
|
||||
/**
|
||||
* 接收消息的seesion
|
||||
*/
|
||||
private List<Object> seesionKeyList;
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
private String msgText;
|
||||
|
||||
public static WebSocketMessageDO build(List<Object> seesionKeyList, String msgText) {
|
||||
return new WebSocketMessageDO().setMsgText(msgText).setSeesionKeyList(seesionKeyList);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
package cn.iocoder.yudao.framework.websocket.core;
|
||||
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class WebSocketSessionHandler {
|
||||
private WebSocketSessionHandler() {
|
||||
}
|
||||
|
||||
private static final Map<String, WebSocketSession> SESSION_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
public static void addSession(Object sessionKey, WebSocketSession session) {
|
||||
SESSION_MAP.put(sessionKey.toString(), session);
|
||||
}
|
||||
|
||||
public static void removeSession(Object sessionKey) {
|
||||
SESSION_MAP.remove(sessionKey.toString());
|
||||
}
|
||||
|
||||
public static WebSocketSession getSession(Object sessionKey) {
|
||||
return SESSION_MAP.get(sessionKey.toString());
|
||||
}
|
||||
|
||||
public static Collection<WebSocketSession> getSessions() {
|
||||
return SESSION_MAP.values();
|
||||
}
|
||||
|
||||
public static Set<String> getSessionKeys() {
|
||||
return SESSION_MAP.keySet();
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
package cn.iocoder.yudao.framework.websocket.core;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Slf4j
|
||||
public class WebSocketUtils {
|
||||
public static boolean sendMessage(WebSocketSession seesion, String message) {
|
||||
if (seesion == null) {
|
||||
log.error("seesion 不存在");
|
||||
return false;
|
||||
}
|
||||
if (seesion.isOpen()) {
|
||||
try {
|
||||
seesion.sendMessage(new TextMessage(message));
|
||||
} catch (IOException e) {
|
||||
log.error("WebSocket 消息发送异常 Session={} | msg= {} | exception={}", seesion, message, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean sendMessage(Object sessionKey, String message) {
|
||||
WebSocketSession session = WebSocketSessionHandler.getSession(sessionKey);
|
||||
return sendMessage(session, message);
|
||||
}
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
package cn.iocoder.yudao.framework.websocket.core;
|
||||
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.handler.WebSocketHandlerDecorator;
|
||||
|
||||
public class YudaoWebSocketHandlerDecorator extends WebSocketHandlerDecorator {
|
||||
public YudaoWebSocketHandlerDecorator(WebSocketHandler delegate) {
|
||||
super(delegate);
|
||||
}
|
||||
|
||||
/**
|
||||
* websocket 连接时执行的动作
|
||||
* @param session websocket session 对象
|
||||
* @throws Exception 异常对象
|
||||
*/
|
||||
@Override
|
||||
public void afterConnectionEstablished(final WebSocketSession session) throws Exception {
|
||||
Object sessionKey = sessionKeyGen(session);
|
||||
WebSocketSessionHandler.addSession(sessionKey, session);
|
||||
}
|
||||
|
||||
/**
|
||||
* websocket 关闭连接时执行的动作
|
||||
* @param session websocket session 对象
|
||||
* @param closeStatus 关闭状态对象
|
||||
* @throws Exception 异常对象
|
||||
*/
|
||||
@Override
|
||||
public void afterConnectionClosed(final WebSocketSession session, CloseStatus closeStatus) throws Exception {
|
||||
Object sessionKey = sessionKeyGen(session);
|
||||
WebSocketSessionHandler.removeSession(sessionKey);
|
||||
}
|
||||
|
||||
public Object sessionKeyGen(WebSocketSession webSocketSession) {
|
||||
|
||||
Object obj = webSocketSession.getAttributes().get(WebSocketKeyDefine.LOGIN_USER);
|
||||
|
||||
if (obj instanceof LoginUser) {
|
||||
LoginUser loginUser = (LoginUser) obj;
|
||||
// userId 作为唯一区分
|
||||
return String.valueOf(loginUser.getId());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package cn.iocoder.yudao.framework.websocket.core.security;
|
||||
|
||||
import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;
|
||||
import cn.iocoder.yudao.framework.websocket.config.WebSocketProperties;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
|
||||
|
||||
/**
|
||||
* WebSocket 的权限自定义
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class WebSocketAuthorizeRequestsCustomizer extends AuthorizeRequestsCustomizer {
|
||||
|
||||
private final WebSocketProperties webSocketProperties;
|
||||
|
||||
@Override
|
||||
public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
|
||||
registry.antMatchers(webSocketProperties.getPath()).permitAll();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package cn.iocoder.yudao.framework.websocket.core.sender.kafka;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Kafka 广播 WebSocket 的消息
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class KafkaWebSocketMessage {
|
||||
|
||||
/**
|
||||
* Session 编号
|
||||
*/
|
||||
private String sessionId;
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private Integer userType;
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 消息类型
|
||||
*/
|
||||
private String messageType;
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String messageContent;
|
||||
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
package cn.iocoder.yudao.framework.websocket.core.sender.kafka;
|
||||
|
||||
import cn.iocoder.yudao.framework.websocket.core.sender.AbstractWebSocketMessageSender;
|
||||
import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender;
|
||||
import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.kafka.core.KafkaTemplate;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
/**
|
||||
* 基于 Kafka 的 {@link WebSocketMessageSender} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class KafkaWebSocketMessageSender extends AbstractWebSocketMessageSender {
|
||||
|
||||
private final KafkaTemplate<Object, Object> kafkaTemplate;
|
||||
|
||||
private final String topic;
|
||||
|
||||
public KafkaWebSocketMessageSender(WebSocketSessionManager sessionManager,
|
||||
KafkaTemplate<Object, Object> kafkaTemplate,
|
||||
String topic) {
|
||||
super(sessionManager);
|
||||
this.kafkaTemplate = kafkaTemplate;
|
||||
this.topic = topic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Integer userType, Long userId, String messageType, String messageContent) {
|
||||
sendKafkaMessage(null, userId, userType, messageType, messageContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Integer userType, String messageType, String messageContent) {
|
||||
sendKafkaMessage(null, null, userType, messageType, messageContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(String sessionId, String messageType, String messageContent) {
|
||||
sendKafkaMessage(sessionId, null, null, messageType, messageContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 Kafka 广播消息
|
||||
*
|
||||
* @param sessionId Session 编号
|
||||
* @param userId 用户编号
|
||||
* @param userType 用户类型
|
||||
* @param messageType 消息类型
|
||||
* @param messageContent 消息内容
|
||||
*/
|
||||
private void sendKafkaMessage(String sessionId, Long userId, Integer userType,
|
||||
String messageType, String messageContent) {
|
||||
KafkaWebSocketMessage mqMessage = new KafkaWebSocketMessage()
|
||||
.setSessionId(sessionId).setUserId(userId).setUserType(userType)
|
||||
.setMessageType(messageType).setMessageContent(messageContent);
|
||||
try {
|
||||
kafkaTemplate.send(topic, mqMessage).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
log.error("[sendKafkaMessage][发送消息({}) 到 Kafka 失败]", mqMessage, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package cn.iocoder.yudao.framework.websocket.core.sender.rabbitmq;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* RabbitMQ 广播 WebSocket 的消息
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class RabbitMQWebSocketMessage implements Serializable {
|
||||
|
||||
/**
|
||||
* Session 编号
|
||||
*/
|
||||
private String sessionId;
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private Integer userType;
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 消息类型
|
||||
*/
|
||||
private String messageType;
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String messageContent;
|
||||
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
package cn.iocoder.yudao.framework.websocket.core.sender.rabbitmq;
|
||||
|
||||
import cn.iocoder.yudao.framework.websocket.core.sender.AbstractWebSocketMessageSender;
|
||||
import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender;
|
||||
import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.TopicExchange;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
|
||||
/**
|
||||
* 基于 RabbitMQ 的 {@link WebSocketMessageSender} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class RabbitMQWebSocketMessageSender extends AbstractWebSocketMessageSender {
|
||||
|
||||
private final RabbitTemplate rabbitTemplate;
|
||||
|
||||
private final TopicExchange topicExchange;
|
||||
|
||||
public RabbitMQWebSocketMessageSender(WebSocketSessionManager sessionManager,
|
||||
RabbitTemplate rabbitTemplate,
|
||||
TopicExchange topicExchange) {
|
||||
super(sessionManager);
|
||||
this.rabbitTemplate = rabbitTemplate;
|
||||
this.topicExchange = topicExchange;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Integer userType, Long userId, String messageType, String messageContent) {
|
||||
sendRabbitMQMessage(null, userId, userType, messageType, messageContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Integer userType, String messageType, String messageContent) {
|
||||
sendRabbitMQMessage(null, null, userType, messageType, messageContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(String sessionId, String messageType, String messageContent) {
|
||||
sendRabbitMQMessage(sessionId, null, null, messageType, messageContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 RabbitMQ 广播消息
|
||||
*
|
||||
* @param sessionId Session 编号
|
||||
* @param userId 用户编号
|
||||
* @param userType 用户类型
|
||||
* @param messageType 消息类型
|
||||
* @param messageContent 消息内容
|
||||
*/
|
||||
private void sendRabbitMQMessage(String sessionId, Long userId, Integer userType,
|
||||
String messageType, String messageContent) {
|
||||
RabbitMQWebSocketMessage mqMessage = new RabbitMQWebSocketMessage()
|
||||
.setSessionId(sessionId).setUserId(userId).setUserType(userType)
|
||||
.setMessageType(messageType).setMessageContent(messageContent);
|
||||
rabbitTemplate.convertAndSend(topicExchange.getName(), null, mqMessage);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package cn.iocoder.yudao.framework.websocket.core.sender.redis;
|
||||
|
||||
import cn.iocoder.yudao.framework.mq.redis.core.pubsub.AbstractRedisChannelMessage;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Redis 广播 WebSocket 的消息
|
||||
*/
|
||||
@Data
|
||||
public class RedisWebSocketMessage extends AbstractRedisChannelMessage {
|
||||
|
||||
/**
|
||||
* Session 编号
|
||||
*/
|
||||
private String sessionId;
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private Integer userType;
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 消息类型
|
||||
*/
|
||||
private String messageType;
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String messageContent;
|
||||
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package cn.iocoder.yudao.framework.websocket.core.sender.redis;
|
||||
|
||||
import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
|
||||
import cn.iocoder.yudao.framework.websocket.core.sender.AbstractWebSocketMessageSender;
|
||||
import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender;
|
||||
import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 基于 Redis 的 {@link WebSocketMessageSender} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class RedisWebSocketMessageSender extends AbstractWebSocketMessageSender {
|
||||
|
||||
private final RedisMQTemplate redisMQTemplate;
|
||||
|
||||
public RedisWebSocketMessageSender(WebSocketSessionManager sessionManager,
|
||||
RedisMQTemplate redisMQTemplate) {
|
||||
super(sessionManager);
|
||||
this.redisMQTemplate = redisMQTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Integer userType, Long userId, String messageType, String messageContent) {
|
||||
sendRedisMessage(null, userId, userType, messageType, messageContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Integer userType, String messageType, String messageContent) {
|
||||
sendRedisMessage(null, null, userType, messageType, messageContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(String sessionId, String messageType, String messageContent) {
|
||||
sendRedisMessage(sessionId, null, null, messageType, messageContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 Redis 广播消息
|
||||
*
|
||||
* @param sessionId Session 编号
|
||||
* @param userId 用户编号
|
||||
* @param userType 用户类型
|
||||
* @param messageType 消息类型
|
||||
* @param messageContent 消息内容
|
||||
*/
|
||||
private void sendRedisMessage(String sessionId, Long userId, Integer userType,
|
||||
String messageType, String messageContent) {
|
||||
RedisWebSocketMessage mqMessage = new RedisWebSocketMessage()
|
||||
.setSessionId(sessionId).setUserId(userId).setUserType(userType)
|
||||
.setMessageType(messageType).setMessageContent(messageContent);
|
||||
redisMQTemplate.send(mqMessage);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package cn.iocoder.yudao.framework.websocket.core.sender.rocketmq;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* RocketMQ 广播 WebSocket 的消息
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class RocketMQWebSocketMessage {
|
||||
|
||||
/**
|
||||
* Session 编号
|
||||
*/
|
||||
private String sessionId;
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private Integer userType;
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 消息类型
|
||||
*/
|
||||
private String messageType;
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String messageContent;
|
||||
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
package cn.iocoder.yudao.framework.websocket.core.sender.rocketmq;
|
||||
|
||||
import cn.iocoder.yudao.framework.websocket.core.sender.AbstractWebSocketMessageSender;
|
||||
import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender;
|
||||
import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||
|
||||
/**
|
||||
* 基于 RocketMQ 的 {@link WebSocketMessageSender} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class RocketMQWebSocketMessageSender extends AbstractWebSocketMessageSender {
|
||||
|
||||
private final RocketMQTemplate rocketMQTemplate;
|
||||
|
||||
private final String topic;
|
||||
|
||||
public RocketMQWebSocketMessageSender(WebSocketSessionManager sessionManager,
|
||||
RocketMQTemplate rocketMQTemplate,
|
||||
String topic) {
|
||||
super(sessionManager);
|
||||
this.rocketMQTemplate = rocketMQTemplate;
|
||||
this.topic = topic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Integer userType, Long userId, String messageType, String messageContent) {
|
||||
sendRocketMQMessage(null, userId, userType, messageType, messageContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Integer userType, String messageType, String messageContent) {
|
||||
sendRocketMQMessage(null, null, userType, messageType, messageContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(String sessionId, String messageType, String messageContent) {
|
||||
sendRocketMQMessage(sessionId, null, null, messageType, messageContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 RocketMQ 广播消息
|
||||
*
|
||||
* @param sessionId Session 编号
|
||||
* @param userId 用户编号
|
||||
* @param userType 用户类型
|
||||
* @param messageType 消息类型
|
||||
* @param messageContent 消息内容
|
||||
*/
|
||||
private void sendRocketMQMessage(String sessionId, Long userId, Integer userType,
|
||||
String messageType, String messageContent) {
|
||||
RocketMQWebSocketMessage mqMessage = new RocketMQWebSocketMessage()
|
||||
.setSessionId(sessionId).setUserId(userId).setUserType(userType)
|
||||
.setMessageType(messageType).setMessageContent(messageContent);
|
||||
rocketMQTemplate.syncSend(topic, mqMessage);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
package cn.iocoder.yudao.framework.websocket.core.session;
|
||||
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* {@link WebSocketSession} 管理器的接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface WebSocketSessionManager {
|
||||
|
||||
/**
|
||||
* 添加 Session
|
||||
*
|
||||
* @param session Session
|
||||
*/
|
||||
void addSession(WebSocketSession session);
|
||||
|
||||
/**
|
||||
* 移除 Session
|
||||
*
|
||||
* @param session Session
|
||||
*/
|
||||
void removeSession(WebSocketSession session);
|
||||
|
||||
/**
|
||||
* 获得指定编号的 Session
|
||||
*
|
||||
* @param id Session 编号
|
||||
* @return Session
|
||||
*/
|
||||
WebSocketSession getSession(String id);
|
||||
|
||||
/**
|
||||
* 获得指定用户类型的 Session 列表
|
||||
*
|
||||
* @param userType 用户类型
|
||||
* @return Session 列表
|
||||
*/
|
||||
Collection<WebSocketSession> getSessionList(Integer userType);
|
||||
|
||||
/**
|
||||
* 获得指定用户编号的 Session 列表
|
||||
*
|
||||
* @param userType 用户类型
|
||||
* @param userId 用户编号
|
||||
* @return Session 列表
|
||||
*/
|
||||
Collection<WebSocketSession> getSessionList(Integer userType, Long userId);
|
||||
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
package cn.iocoder.yudao.framework.websocket.core.util;
|
||||
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 专属于 web 包的工具类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class WebSocketFrameworkUtils {
|
||||
|
||||
public static final String ATTRIBUTE_LOGIN_USER = "LOGIN_USER";
|
||||
|
||||
/**
|
||||
* 设置当前用户
|
||||
*
|
||||
* @param loginUser 登录用户
|
||||
* @param attributes Session
|
||||
*/
|
||||
public static void setLoginUser(LoginUser loginUser, Map<String, Object> attributes) {
|
||||
attributes.put(ATTRIBUTE_LOGIN_USER, loginUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户
|
||||
*
|
||||
* @return 当前用户
|
||||
*/
|
||||
public static LoginUser getLoginUser(WebSocketSession session) {
|
||||
return (LoginUser) session.getAttributes().get(ATTRIBUTE_LOGIN_USER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前用户的编号
|
||||
*
|
||||
* @return 用户编号
|
||||
*/
|
||||
public static Long getLoginUserId(WebSocketSession session) {
|
||||
LoginUser loginUser = getLoginUser(session);
|
||||
return loginUser != null ? loginUser.getId() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前用户的类型
|
||||
*
|
||||
* @return 用户编号
|
||||
*/
|
||||
public static Integer getLoginUserType(WebSocketSession session) {
|
||||
LoginUser loginUser = getLoginUser(session);
|
||||
return loginUser != null ? loginUser.getUserType() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前用户的租户编号
|
||||
*
|
||||
* @param session Session
|
||||
* @return 租户编号
|
||||
*/
|
||||
public static Long getTenantId(WebSocketSession session) {
|
||||
LoginUser loginUser = getLoginUser(session);
|
||||
return loginUser != null ? loginUser.getTenantId() : null;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
<http://www.iocoder.cn/Spring-Boot/WebSocket/?yudao>
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue