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

在这里插入图片描述

创建连接

在这里插入图片描述

发送消息

在这里插入图片描述

接收消息

在这里插入图片描述

<think>好的,我现在需要帮助用户解决在Spring Boot 3.0中整合Redis的问题。首先,我应该回想一下Spring Boot 3.0的新特性和与Redis整合的基本步骤。记得Spring Boot 3.0需要Java 17或更高版本,这可能是一个前提条件,用户需要确认他们的环境是否符合要求。 接下来,整合Redis通常需要添加相关的依赖。在Spring Boot 2.x中,常用的依赖是spring-boot-starter-data-redis,但到了3.0,可能依赖的版本号有所变化。需要检查最新的Spring Data Redis文档,确保使用正确的依赖配置。例如,可能需要使用Spring Boot 3.0对应的Spring Data 2022.0.0或更高版本。 然后,配置Redis连接信息。用户需要在application.properties或application.yml文件中设置Redis服务器的主机地址、端口、密码等信息。这里可能会涉及到是否使用lettuce作为默认的连接池,因为Spring Boot从2.x开始默认使用Lettuce而不是Jedis,这一点在3.0中可能继续保持。 接下来是创建配置类,可能涉及到RedisTemplate和StringRedisTemplate的配置。需要确保这些Bean被正确创建,并且序列化方式设置正确,避免出现序列化问题。通常会建议使用Jackson2JsonRedisSerializer来替代默认的JdkSerializationRedisSerializer,以便更友好地处理JSON数据。 然后,用户可能需要编写Repository或者使用RedisTemplate直接操作数据。这里需要给出示例代码,比如如何保存、获取、删除键值对,或者如何使用Hash操作。同时,要提醒用户注意事务管理和管道操作的使用,如果有相关需求的话。 另外,异常处理也是需要考虑的部分。比如连接失败时的处理,或者Redis服务器不可用时的回退机制。可能需要提到Spring的异常处理机制,或者使用@Cacheable等注解时的缓存异常处理。 还有可能涉及到高级功能,比如发布/订阅模式、分布式锁的实现,或者与Spring Cache集成,使用@Cacheable注解来简化缓存逻辑。需要根据用户的具体需求来决定是否展开这些内容。 测试部分也很重要,建议用户编写测试用例,使用@SpringBootTest来验证Redis配置是否正确,数据操作是否如预期。可能需要使用嵌入式Redis服务器进行测试,但注意Spring Boot 3.0是否支持常见的嵌入式Redis库,或者是否需要使用测试容器(Testcontainers)。 最后,需要生成相关问题,可能包括性能优化、集群配置、SSL连接、与其他数据库的整合等,以帮助用户进一步探索。 现在需要确保所有步骤都符合Spring Boot 3.0的最新要求,比如Jakarta EE的包名变更(javax到jakarta),这可能影响到某些配置类或依赖项。同时,检查是否有过时的API或注解,确保示例代码的准确性和兼容性。 可能还需要参考官方文档或可靠的教程,确保提供的信息是最新和正确的。例如,Spring Data Redis的官方文档或Spring Boot的指南,确保依赖项版本和配置示例正确无误。 总结一下,步骤大致是:环境准备、添加依赖、配置连接信息、配置RedisTemplate、编写数据操作代码、异常处理、测试,以及可能的进阶功能。每个步骤都需要详细说明,并给出代码示例,同时注意3.0版本的新变化。</think>在Spring Boot 3.0中整合Redis可通过以下步骤实现,注意新版本对JDK 17和Jakarta EE 9的依赖升级[^1]: ### 步骤1:添加依赖 ```xml <!-- pom.xml --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </dependency> ``` ### 步骤2:配置连接参数 ```properties # application.properties spring.data.redis.host=localhost spring.data.redis.port=6379 spring.data.redis.password=yourpassword spring.data.redis.lettuce.pool.max-active=8 ``` ### 步骤3:配置序列化规则 ```java @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; } } ``` ### 步骤4:操作示例 ```java @Service public class UserService { @Autowired private RedisTemplate<String, Object> redisTemplate; public void saveUser(User user) { redisTemplate.opsForValue().set("user:" + user.getId(), user); } public User getUser(String userId) { return (User) redisTemplate.opsForValue().get("user:" + userId); } } ``` ### 高级整合:缓存注解 ```java @SpringBootApplication @EnableCaching public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } @Service public class ProductService { @Cacheable(value = "products", key = "#id") public Product getProductById(String id) { // 数据库查询逻辑 } } ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值