Springboot3.0 响应式WebSocket代码分享

工程目录结构

springboot3-reactive
  ┣ src/main/java
      ┣ com.es.springboot3
          ┣ cache
              ┣ WebSocketClientCache.java
          ┣ config
              ┣ ApplicationConfiguration.java
              ┣ RedissonAutoConfiguration.java
              ┣ WebConfiguration.java
              ┣ WebSocketConfiguration.java
          ┣ context
              ┣ SpringContextHolder.java
          ┣ entity
              ┣ websocket
                  ┣ WebSocketClient.java
                  ┣ WebSocketMessageRecord.java
          ┣ event
              ┣ MessageEvent.java
          ┣ handler
              ┣ BroadcastWebSocketHandler.java
              ┣ P2MPWebSocketHandler.java
              ┣ P2PWebSocketHandler.java
          ┣ listener
              ┣ MessageListener.java
          ┣ properties
              ┣ RedissonProperty.java
              ┣ RedissonSentinel.java
              ┣ RedissonSingle.java
          ┣ SpringBoot3ReactiveApplication.java
  ┣ src/main/resources
      ┣  application.yml
      ┣  logback-spring.xml
  ┣ pom.xml

代码及配置

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.es</groupId>
	<artifactId>springboot3-reactive</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<description>响应式</description>

	<properties>

		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>17</maven.compiler.source>
		<maven.compiler.target>17</maven.compiler.target>
		<spring-boot.version>3.0.0</spring-boot.version>
		<hutool.version>5.8.10</hutool.version>
		<fastjson.version>2.0.20</fastjson.version>
		<redisson.version>3.18.0</redisson.version>
	</properties>

	<dependencies>
	    <!--配置文件处理器-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>
		<!--Lombok-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>jakarta.websocket</groupId>
			<artifactId>jakarta.websocket-api</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>
		<!-- hutool -->
		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
		</dependency>
		<!--fastjson -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
		</dependency>

		<dependency>
			<groupId>org.redisson</groupId>
			<artifactId>redisson</artifactId>
		</dependency>
	</dependencies>


	<dependencyManagement>
		<dependencies>
			<!-- spring boot 依赖 -->
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-dependencies</artifactId>
				<version>${spring-boot.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<dependency>
				<groupId>cn.hutool</groupId>
				<artifactId>hutool-all</artifactId>
				<version>${hutool.version}</version>
			</dependency>
			<!--fastjson 版本-->
			<dependency>
				<groupId>com.alibaba</groupId>
				<artifactId>fastjson</artifactId>
				<version>${fastjson.version}</version>
			</dependency>
			<!-- redisson -->
			<dependency>
				<groupId>org.redisson</groupId>
				<artifactId>redisson</artifactId>
				<version>${redisson.version}</version>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<finalName>${project.name}</finalName>
		<resources>
			<resource>
				<directory>src/main/resources</directory>
				<!--开启过滤,用指定的参数替换directory下的文件中的参数-->
				<filtering>true</filtering>
			</resource>
		</resources>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<encoding>utf-8</encoding>
					<source>17</source>
					<target>17</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-jar-plugin</artifactId>
				<configuration>
					<archive>
						<manifest>
							<!-- 添加依赖jar路径 -->
							<addClasspath>true</addClasspath>
							<!-- 入口程序 -->
							<mainClass>com.es.springboot3.SpringBoot3ReactiveApplication</mainClass>
							<classpathPrefix>lib/</classpathPrefix>
						</manifest>
					</archive>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-dependency-plugin</artifactId>
				<executions>
					<execution>
						<id>copy</id>
						<phase>package</phase>
						<goals>
							<goal>copy-dependencies</goal>
						</goals>
						<configuration>
							<outputDirectory>
								${project.build.directory}/lib
							</outputDirectory>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

application.yml

server:
  port: 8009

application:
  name: @artifactId@
  version: @version@
spring:
  application:
    name: @artifactId@
  redisson:
    enabled: true
    server: single
    single:
      address: redis://192.168.31.167:6379
      connectionMinimumIdleSize: 5
      connectionPoolSize: 50
    password: 
    database: 14
    timeout: 3000

SpringBoot3ReactiveApplication.java

package com.es.springboot3;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBoot3ReactiveApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBoot3ReactiveApplication.class, args);
    }

}

WebSocketClientCache.java

package com.es.springboot3.cache;

import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;

import org.redisson.api.RedissonClient;
import org.springframework.web.reactive.socket.WebSocketSession;

import com.es.springboot3.entity.websocket.WebSocketClient;

import lombok.experimental.UtilityClass;

@UtilityClass
public class WebSocketClientCache {
	
	private static final ConcurrentMap<String, HashMap<String, WebSocketClient>> WEB_SOCKET_CLIENT_MAP = new ConcurrentHashMap<>();

	public void setWebSocketClient(String source, WebSocketClient webSocketClient) {
		HashMap<String, WebSocketClient> webSocketClientCache = WEB_SOCKET_CLIENT_MAP.get(source);

		if (Objects.isNull(webSocketClientCache)) {
			webSocketClientCache = new HashMap<>();
		}
		WebSocketSession session = webSocketClient.getSession();
		if (Objects.nonNull(session)) {
			webSocketClientCache.put(session.getId(), webSocketClient);
			WEB_SOCKET_CLIENT_MAP.put(source, webSocketClientCache);
		}
		
	}
	
	public HashMap<String,WebSocketClient> getWebSocketClient(String source){
        return WEB_SOCKET_CLIENT_MAP.get(source);
    }
	
	
	public void delWebSocketClient(String source,String sessionId){
        WEB_SOCKET_CLIENT_MAP.get(source).remove(sessionId);
    }
}

ApplicationConfiguration.java

package com.es.springboot3.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;

@Configuration(proxyBeanMethods = false)
@EnableAsync
public class ApplicationConfiguration {
}

RedissonAutoConfiguration.java

package com.es.springboot3.config;


import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.es.springboot3.properties.RedissonProperty;
import com.es.springboot3.properties.RedissonSentinel;
import com.es.springboot3.properties.RedissonSingle;

import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;

/**
 * redisson自动配置
 */
@RequiredArgsConstructor
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(RedissonProperty.class)
public class RedissonAutoConfiguration {
	
	private final RedissonProperty conf;
	
	@Bean(name = "redission", destroyMethod = "shutdown")
	public RedissonClient redission() {
		Config config = new Config();
		config.setCodec(new org.redisson.client.codec.StringCodec());
		if ("sentinel".equals(conf.getServer())) {
			RedissonSentinel sentinel = conf.getSentinel();
			config.useSentinelServers().setMasterName(sentinel.getMasterName())
					.addSentinelAddress(sentinel.getSentinelAddresses()).setPassword(conf.getPassword())
					.setDatabase(conf.getDatabase());
		} else {
			RedissonSingle single = conf.getSingle();
			SingleServerConfig serverConfig = config.useSingleServer().setAddress(single.getAddress())
					.setTimeout(conf.getTimeout()).setConnectionPoolSize(single.getConnectionPoolSize())
					.setConnectionMinimumIdleSize(single.getConnectionMinimumIdleSize())
					.setDatabase(conf.getDatabase());
			if (StrUtil.isNotBlank(conf.getPassword())) {
				serverConfig.setPassword(conf.getPassword());
			}
		}
		return Redisson.create(config);
	}
}

WebConfiguration.java

package com.es.springboot3.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.EnableWebFlux;

@Configuration(proxyBeanMethods = false)
@EnableWebFlux
public class WebConfiguration {

}

WebSocketConfiguration.java

package com.es.springboot3.config;

import java.util.HashMap;
import java.util.Map;

import org.redisson.api.RedissonClient;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;

import com.es.springboot3.handler.BroadcastWebSocketHandler;
import com.es.springboot3.handler.P2MPWebSocketHandler;
import com.es.springboot3.handler.P2PWebSocketHandler;

import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Sinks;

@Configuration
@AutoConfigureAfter(RedissonAutoConfiguration.class)
@RequiredArgsConstructor
public class WebSocketConfiguration {
	
	private final RedissonClient redissonClient;
	
	@Bean
	public SimpleUrlHandlerMapping webSocketHandlerMapping() {
		Map<String,WebSocketHandler> webSocketHandlerMap = new HashMap<>();
		// 上线、单聊、群聊、聊天室发消息
		webSocketHandlerMap.put("/ws/p2p", new P2PWebSocketHandler(redissonClient));
		// 聊天室加入、退出
		webSocketHandlerMap.put("/ws/p2mp", new P2MPWebSocketHandler(redissonClient));
		// 广播
		webSocketHandlerMap.put("/ws/broadcast", new BroadcastWebSocketHandler(manySink()));
		
		return new SimpleUrlHandlerMapping(webSocketHandlerMap, Ordered.HIGHEST_PRECEDENCE);
	}
	
	@Bean
	public WebSocketHandlerAdapter webSocketHandlerAdapter() {
		return new WebSocketHandlerAdapter();
	}
	
	@Bean
	public Sinks.Many<String> manySink(){
		return Sinks.many().multicast().directBestEffort();
	}
	
}

SpringContextHolder.java

package com.es.springboot3.context;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/**
 * Spring上下文工具类
 */
@Slf4j
@Service
@Lazy(false)
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {

	private static ApplicationContext applicationContext = null;

	/**
	 * 取得存储在静态变量中的ApplicationContext.
	 */
	public static ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	/**
	 * 实现ApplicationContextAware接口, 注入Context到静态变量中.
	 */
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) {
		SpringContextHolder.applicationContext = applicationContext;
	}

	/**
	 * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getBean(String name) {
		return (T) applicationContext.getBean(name);
	}

	/**
	 * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
	 */
	public static <T> T getBean(Class<T> requiredType) {
		return applicationContext.getBean(requiredType);
	}

	/**
	 * 清除SpringContextHolder中的ApplicationContext为Null.
	 */
	public static void clearHolder() {
		if (log.isDebugEnabled()) {
			log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
		}
		applicationContext = null;
	}

	/**
	 * 发布事件
	 * 
	 * @param event
	 */
	public static void publishEvent(ApplicationEvent event) {
		if (applicationContext == null) {
			return;
		}
		applicationContext.publishEvent(event);
	}

	/**
	 * 实现DisposableBean接口, 在Context关闭时清理静态变量.
	 */
	@Override
	@SneakyThrows
	public void destroy() {
		SpringContextHolder.clearHolder();
	}

	/**
	 * 环境变量中获取配置信息
	 * 
	 * @param key
	 * @return
	 */
	public static Object getProperty(String key) {
		return getApplicationContext().getEnvironment().getProperty(key);
	}

}

WebSocketClient.java

package com.es.springboot3.entity.websocket;

import java.io.Serializable;

import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.WebSocketSession;

import lombok.AllArgsConstructor;
import lombok.Data;
import reactor.core.publisher.FluxSink;

@Data
@AllArgsConstructor
public class WebSocketClient implements Serializable {
	/**
	* 
	*/
	private static final long serialVersionUID = 1L;

	private FluxSink<WebSocketMessage> sink;

	private WebSocketSession session;

	public void sendMsg(String msg) {
		sink.next(session.textMessage(msg));
	}

}

WebSocketMessageRecord.java

package com.es.springboot3.entity.websocket;

import java.time.LocalDateTime;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

/**
 * @param msg 消息内容
 * @param source 消息来源 消息发送者
 * @param target 消息目标 消息接收者
 * @param targetType 消息目标类型 SINGLE, GROUP, CHATROOM
 * @param createTime 消息创建时间
 */
public record WebSocketMessageRecord(String msg, String source, String target, String targetType, LocalDateTime createTime) {

	public static WebSocketMessageRecord trans(String message) {
		JSONObject msgJSON = JSON.parseObject(message);
		msgJSON.put("createTime", LocalDateTime.now());
		return JSON.toJavaObject(msgJSON, WebSocketMessageRecord.class);
	}
}

MessageEvent.java

package com.es.springboot3.event;

import org.springframework.context.ApplicationEvent;

public class MessageEvent extends ApplicationEvent{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public MessageEvent(Object source) {
		super(source);
	}

}

BroadcastWebSocketHandler.java

package com.es.springboot3.handler;

import java.time.Duration;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.stereotype.Component;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.WebSocketSession;

import com.es.springboot3.context.SpringContextHolder;
import com.es.springboot3.event.MessageEvent;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;

@Slf4j
@Component
@RequiredArgsConstructor
public class BroadcastWebSocketHandler implements WebSocketHandler {

	public static ConcurrentHashMap<String, WebSocketSession> sessionBucket = new ConcurrentHashMap<>();

	private final Sinks.Many<String> manySink;
	

	@Override
	public Mono<Void> handle(WebSocketSession session) {
		sessionBucket.putIfAbsent(session.getId(), session);
		
		Mono<Void> receiveMono = session.receive().map(WebSocketMessage::getPayloadAsText)
				.doOnNext(data -> {
					MessageEvent messageEvent = new MessageEvent(data);
					SpringContextHolder.publishEvent(messageEvent);
					manySink.emitNext(data, Sinks.EmitFailureHandler.FAIL_FAST);
				}).then();

		Mono<Void> sendMono = session
				.send(Mono.delay(Duration.ofMillis(500)).thenMany(manySink.asFlux().map(msg -> session.textMessage(msg))))
				.then();

		return Mono.zip(receiveMono, sendMono).then();

	}

}

P2MPWebSocketHandler.java

package com.es.springboot3.handler;

import java.net.InetSocketAddress;
import java.util.Map;

import org.redisson.api.RSet;
import org.redisson.api.RedissonClient;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.socket.HandshakeInfo;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.HttpUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Slf4j
@Component
@RequiredArgsConstructor
public class P2MPWebSocketHandler implements WebSocketHandler {

	private final RedissonClient redissonClient;

	@Override
	public Mono<Void> handle(WebSocketSession session) {

		HandshakeInfo handshakeInfo = session.getHandshakeInfo();
		InetSocketAddress remoteAddress = handshakeInfo.getRemoteAddress();
		HttpHeaders headers = handshakeInfo.getHeaders();
		String query = handshakeInfo.getUri().getQuery();
		Map<String, String> paramMap = HttpUtil.decodeParamMap(query, CharsetUtil.CHARSET_UTF_8);

		String userId = paramMap.get("userId");
		String roomId = paramMap.get("roomId"); // 聊天室

		Mono<Void> receiveMono = session.receive().doOnSubscribe(conn -> {
			log.info("建立连接:{},用户ip:{},房间id:{},用户:{}", session.getId(), remoteAddress.getHostName(), roomId, userId);
		}).doOnComplete(() -> {
			exitFromChatroom(roomId, userId);
			log.info("doOnComplete关闭连接:{}", session.getId());
		}).doOnCancel(() -> {
			exitFromChatroom(roomId, userId);
			log.info("doOnCancel关闭连接:{}", session.getId());
		}).then();

		Mono<Void> sendMono = session.send(Flux.create(sink -> entryToChatroom(roomId, userId)));

		return Mono.zip(receiveMono, sendMono).then();

	}

	void entryToChatroom(String roomId, String userId) {
		RSet<String> ids = redissonClient.getSet("chatroom:"+roomId);
		ids.add(userId);
	}

	void exitFromChatroom(String roomId, String userId) {
		RSet<String> ids = redissonClient.getSet("chatroom:"+roomId);
		ids.remove(userId);
	}

}

P2PWebSocketHandler.java

package com.es.springboot3.handler;

import java.net.InetSocketAddress;
import java.util.Map;

import org.redisson.api.RedissonClient;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.socket.HandshakeInfo;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;

import com.es.springboot3.cache.WebSocketClientCache;
import com.es.springboot3.context.SpringContextHolder;
import com.es.springboot3.entity.websocket.WebSocketClient;
import com.es.springboot3.entity.websocket.WebSocketMessageRecord;
import com.es.springboot3.event.MessageEvent;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.HttpUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Slf4j
@Component
@RequiredArgsConstructor
public class P2PWebSocketHandler implements WebSocketHandler {


	private final RedissonClient redissonClient;

	@Override
	public Mono<Void> handle(WebSocketSession session) {

		HandshakeInfo handshakeInfo = session.getHandshakeInfo();
		InetSocketAddress remoteAddress = handshakeInfo.getRemoteAddress();
		HttpHeaders headers = handshakeInfo.getHeaders();
		String query = handshakeInfo.getUri().getQuery();
		Map<String, String> paramMap = HttpUtil.decodeParamMap(query, CharsetUtil.CHARSET_UTF_8);

		String userId = paramMap.get("userId");

		Mono<Void> receiveMono = session.receive().doOnSubscribe(conn -> {
			log.info("建立连接:{},用户ip:{},用户:{}", session.getId(), remoteAddress.getHostName(), userId);
		}).doOnNext(msg -> {
			String data = msg.getPayloadAsText();
			WebSocketMessageRecord messageRecord = WebSocketMessageRecord.trans(data);
			sendP2P(messageRecord);
		}).doOnComplete(() -> {
			WebSocketClientCache.delWebSocketClient(userId, session.getId());
			log.info("doOnComplete关闭连接:{},{}", userId, session.getId());
		}).doOnCancel(() -> {
			WebSocketClientCache.delWebSocketClient(userId, session.getId());
			log.info("doOnCancel关闭连接:{},{}", userId, session.getId());
		}).then();

		Mono<Void> sendMono = session
				.send(Flux.create(sink -> handleClient(userId, new WebSocketClient(sink, session))));

		return Mono.zip(receiveMono, sendMono).then();

	}

	void handleClient(String source, WebSocketClient webSocketClient) {
		WebSocketClientCache.setWebSocketClient(source, webSocketClient);
	}

	void sendP2P(WebSocketMessageRecord messageRecord) {
		MessageEvent messageEvent = new MessageEvent(messageRecord);
		SpringContextHolder.publishEvent(messageEvent);

	}
}

MessageListener.java

package com.es.springboot3.listener;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;

import org.redisson.api.RReliableTopic;
import org.redisson.api.RSet;
import org.redisson.api.RedissonClient;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.es.springboot3.cache.WebSocketClientCache;
import com.es.springboot3.entity.websocket.WebSocketClient;
import com.es.springboot3.entity.websocket.WebSocketMessageRecord;
import com.es.springboot3.event.MessageEvent;

import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
@RequiredArgsConstructor
public class MessageListener {

	private final RedissonClient redissonClient;
	
	@PostConstruct
	public void messageTopicListen() {

		RReliableTopic messageTopic = redissonClient.getReliableTopic("messageTopic");

		messageTopic.addListener(String.class, (channel, messageRecordObj) -> {
			log.info("channel:{}", channel);
			log.info("msg:{}", messageRecordObj);
			
			WebSocketMessageRecord messageRecord = JSON.toJavaObject(JSON.parseObject(messageRecordObj), WebSocketMessageRecord.class);

			String msg = JSON.toJSONString(messageRecord);
			String source = messageRecord.source();
			String target = messageRecord.target();

			String targetType = messageRecord.targetType();
			switch (targetType) {
			case "SINGLE":
				HashMap<String, WebSocketClient> sourceWebSocketClientMap = WebSocketClientCache.getWebSocketClient(source);

				if (Objects.nonNull(sourceWebSocketClientMap)) {
					sourceWebSocketClientMap.forEach((k, sourceWebSocketClient) -> {
						sourceWebSocketClient.sendMsg(msg);
					});
				}

				HashMap<String, WebSocketClient> targetWebSocketClientMap = WebSocketClientCache.getWebSocketClient(target);
				if (Objects.nonNull(targetWebSocketClientMap)) {
					targetWebSocketClientMap.forEach((k, targetWebSocketClient) -> {
						targetWebSocketClient.sendMsg(msg);
					});
				}
				break;
			case "GROUP":
				// 根据target 获取组成员
				List<String> members = Arrays.asList("1", "2", "3");

				members.parallelStream().forEach((id) -> {
					HashMap<String, WebSocketClient> webSocketClientMap = WebSocketClientCache.getWebSocketClient(id);
					if (Objects.nonNull(webSocketClientMap)) {
						webSocketClientMap.forEach((k, webSocketClient) -> {
							webSocketClient.sendMsg(msg);
						});
					}
				});

				break;
			case "CHATROOM":

				RSet<String> ids = redissonClient.getSet("chatroom:" + target);
				ids.parallelStream().forEach((id) -> {
					HashMap<String, WebSocketClient> webSocketClientMap = WebSocketClientCache.getWebSocketClient(id);
					if (Objects.nonNull(webSocketClientMap)) {
						webSocketClientMap.forEach((k, webSocketClient) -> {
							webSocketClient.sendMsg(msg);
						});
					}
				});

				break;
			}
		});
	}

	@Async
	@EventListener
	public void messageListen(MessageEvent messageEvent) {
		log.info("{}", JSON.toJSONString(messageEvent.getSource()));
		WebSocketMessageRecord messageRecord = (WebSocketMessageRecord) messageEvent.getSource();
		RReliableTopic messageTopic = redissonClient.getReliableTopic("messageTopic");
		messageTopic.publish(JSON.toJSONString(messageRecord));
		
	}
}

RedissonProperty.java

package com.es.springboot3.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;

import lombok.Data;

@Data
@ConfigurationProperties(prefix = "spring.redisson")
public class RedissonProperty {

	private boolean enabled;

	/**
	 * 服务类型 single sentinel cluster
	 */
	private String server;

	/**
	 * 单点配置
	 */
	private RedissonSingle single;

	/**
	 * 哨兵配置
	 */
	private RedissonSentinel sentinel;

	/**
	 * 密码
	 */
	private String password;

	/**
	 * 设置的数据库0-15
	 */
	private int database;

	/**
	 * 超时时间
	 */
	private int timeout;

}

RedissonSentinel.java

package com.es.springboot3.properties;

import lombok.Data;

@Data
public class RedissonSentinel {

	/*
	 * 以下参数暂未设置
	 */
	private int slaveConnectionPoolSize;

	private int masterConnectionPoolSize;

	private String[] sentinelAddresses;

	private String masterName;
}

RedissonSingle.java

package com.es.springboot3.properties;

import lombok.Data;

@Data
public class RedissonSingle {

	/**
	 * 链接地址
	 */
	private String address;

	/**
	 * 连接最小闲置进程数
	 */
	private int connectionMinimumIdleSize;
	/**
	 * 连接池大小
	 */
	private int connectionPoolSize;
}

客户端连接

postman

在这里插入图片描述

创建连接

在这里插入图片描述

发送消息

在这里插入图片描述

接收消息

在这里插入图片描述

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值