feat(idempotent): 增强幂等配置,支持 Redis 集群和哨兵模式

- 新增对 Redis 集群和哨兵模式的支持
- 更新 Redisson 客户端配置逻辑
- 添加 IdempotentAutoConfigurationTests 测试类,验证新配置的正确性

(cherry picked from commit b836af6dc0dfbe17b20740cfda9e417deae1967d)
This commit is contained in:
PIGCLOUD committed 2026-09-22 21:43:41 +08:00
1 parent c80d5bc44a
commit 39ea9a6eae
2 files changed
+130 -12

No files matched your search

@@ -5,8 +5,10 @@ import com.pig4cloud.pigx.common.idempotent.expression.ExpressionResolver;
import com.pig4cloud.pigx.common.idempotent.expression.KeyResolver;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.BaseConfig;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.redisson.config.SentinelServersConfig;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -40,31 +42,56 @@ public class IdempotentAutoConfiguration {
}
/**
* 幂等方面
* 幂等 Redis 客户端
*
* @param redisson redisson 客户端
* @param keyResolver KEY解析器
* @return {@link IdempotentAspect }
* @param redisProperties Redis 连接配置
* @return Redisson 客户端
*/
@Bean
@ConditionalOnMissingBean(RedissonClient.class)
public RedissonClient redissonClient(DataRedisProperties redisProperties) {
Config config = new Config();
SingleServerConfig singleServerConfig = config.useSingleServer()
.setAddress("redis://" + redisProperties.getHost() + ":" + redisProperties.getPort())
.setDatabase(redisProperties.getDatabase());
BaseConfig<?> serverConfig;
if (redisProperties.getSentinel() != null) {
DataRedisProperties.Sentinel sentinel = redisProperties.getSentinel();
SentinelServersConfig sentinelConfig = config.useSentinelServers()
.setMasterName(sentinel.getMaster())
.setDatabase(redisProperties.getDatabase());
for (String node : sentinel.getNodes()) {
sentinelConfig.addSentinelAddress("redis://" + node);
}
if (StringUtils.hasText(sentinel.getUsername())) {
sentinelConfig.setSentinelUsername(sentinel.getUsername());
}
if (StringUtils.hasText(sentinel.getPassword())) {
sentinelConfig.setSentinelPassword(sentinel.getPassword());
}
serverConfig = sentinelConfig;
}
else if (redisProperties.getCluster() != null) {
ClusterServersConfig clusterConfig = config.useClusterServers();
for (String node : redisProperties.getCluster().getNodes()) {
clusterConfig.addNodeAddress("redis://" + node);
}
serverConfig = clusterConfig;
}
else {
serverConfig = config.useSingleServer()
.setAddress("redis://" + redisProperties.getHost() + ":" + redisProperties.getPort())
.setDatabase(redisProperties.getDatabase());
}
if (StringUtils.hasText(redisProperties.getUsername())) {
singleServerConfig.setUsername(redisProperties.getUsername());
serverConfig.setUsername(redisProperties.getUsername());
}
if (StringUtils.hasText(redisProperties.getPassword())) {
singleServerConfig.setPassword(redisProperties.getPassword());
serverConfig.setPassword(redisProperties.getPassword());
}
if (redisProperties.getConnectTimeout() != null) {
singleServerConfig.setConnectTimeout((int) redisProperties.getConnectTimeout().toMillis());
serverConfig.setConnectTimeout((int) redisProperties.getConnectTimeout().toMillis());
}
if (redisProperties.getTimeout() != null) {
singleServerConfig.setTimeout((int) redisProperties.getTimeout().toMillis());
serverConfig.setTimeout((int) redisProperties.getTimeout().toMillis());
}
return Redisson.create(config);
@@ -0,0 +1,91 @@
package com.pig4cloud.pigx.common.idempotent;
import java.time.Duration;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.SentinelServersConfig;
import org.springframework.boot.data.redis.autoconfigure.DataRedisProperties;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.withSettings;
class IdempotentAutoConfigurationTests {
@Test
void usesClusterNodesInsteadOfDefaultSingleServer() {
DataRedisProperties properties = new DataRedisProperties();
properties.setUsername("cluster-user");
properties.setPassword("cluster-password");
properties.setConnectTimeout(Duration.ofSeconds(2));
properties.setTimeout(Duration.ofSeconds(3));
DataRedisProperties.Cluster cluster = new DataRedisProperties.Cluster();
cluster.setNodes(List.of("redis-1:7000", "redis-2:7001", "redis-3:7002"));
properties.setCluster(cluster);
RedissonClient client = mock(RedissonClient.class);
try (MockedStatic<Redisson> redisson = mockStatic(Redisson.class,
withSettings().mockMaker("mock-maker-inline"))) {
redisson.when(() -> Redisson.create(any(Config.class))).thenAnswer(invocation -> {
Config config = invocation.getArgument(0);
assertThat(config.isClusterConfig()).as("必须使用集群模式连接").isTrue();
ClusterServersConfig servers = config.useClusterServers();
assertThat(servers.getNodeAddresses()).containsExactly(
"redis://redis-1:7000", "redis://redis-2:7001", "redis://redis-3:7002");
assertThat(servers.getUsername()).isEqualTo("cluster-user");
assertThat(servers.getPassword()).isEqualTo("cluster-password");
assertThat(servers.getConnectTimeout()).isEqualTo(2000);
assertThat(servers.getTimeout()).isEqualTo(3000);
return client;
});
assertThat(new IdempotentAutoConfiguration().redissonClient(properties)).isSameAs(client);
}
}
@Test
void usesSentinelConfigurationInsteadOfDefaultSingleServer() {
DataRedisProperties properties = new DataRedisProperties();
properties.setDatabase(2);
properties.setUsername("data-user");
properties.setPassword("data-password");
properties.setConnectTimeout(Duration.ofSeconds(2));
properties.setTimeout(Duration.ofSeconds(3));
DataRedisProperties.Sentinel sentinel = new DataRedisProperties.Sentinel();
sentinel.setMaster("mymaster");
sentinel.setNodes(List.of("sentinel-1:26379", "sentinel-2:26379"));
sentinel.setUsername("sentinel-user");
sentinel.setPassword("sentinel-password");
properties.setSentinel(sentinel);
RedissonClient client = mock(RedissonClient.class);
try (MockedStatic<Redisson> redisson = mockStatic(Redisson.class,
withSettings().mockMaker("mock-maker-inline"))) {
redisson.when(() -> Redisson.create(any(Config.class))).thenAnswer(invocation -> {
Config config = invocation.getArgument(0);
assertThat(config.isSentinelConfig()).as("必须使用哨兵模式连接").isTrue();
SentinelServersConfig servers = config.useSentinelServers();
assertThat(servers.getMasterName()).isEqualTo("mymaster");
assertThat(servers.getSentinelAddresses())
.containsExactly("redis://sentinel-1:26379", "redis://sentinel-2:26379");
assertThat(servers.getSentinelUsername()).isEqualTo("sentinel-user");
assertThat(servers.getSentinelPassword()).isEqualTo("sentinel-password");
assertThat(servers.getUsername()).isEqualTo("data-user");
assertThat(servers.getPassword()).isEqualTo("data-password");
assertThat(servers.getDatabase()).isEqualTo(2);
assertThat(servers.getConnectTimeout()).isEqualTo(2000);
assertThat(servers.getTimeout()).isEqualTo(3000);
return client;
});
assertThat(new IdempotentAutoConfiguration().redissonClient(properties)).isSameAs(client);
}
}
}