From 39ea9a6eaeaeeb10207908b093e89fc305484ade Mon Sep 17 00:00:00 2001 From: PIGCLOUD Date: Tue, 15 Sep 2026 14:52:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(idempotent):=20=E5=A2=9E=E5=BC=BA=E5=B9=82?= =?UTF-8?q?=E7=AD=89=E9=85=8D=E7=BD=AE=EF=BC=8C=E6=94=AF=E6=8C=81=20Redis?= =?UTF-8?q?=20=E9=9B=86=E7=BE=A4=E5=92=8C=E5=93=A8=E5=85=B5=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增对 Redis 集群和哨兵模式的支持 - 更新 Redisson 客户端配置逻辑 - 添加 IdempotentAutoConfigurationTests 测试类,验证新配置的正确性 (cherry picked from commit b836af6dc0dfbe17b20740cfda9e417deae1967d) --- .../IdempotentAutoConfiguration.java | 51 ++++++++--- .../IdempotentAutoConfigurationTests.java | 91 +++++++++++++++++++ 2 files changed, 130 insertions(+), 12 deletions(-) create mode 100644 pigx-common/pigx-common-idempotent/src/test/java/com/pig4cloud/pigx/common/idempotent/IdempotentAutoConfigurationTests.java diff --git a/pigx-common/pigx-common-idempotent/src/main/java/com/pig4cloud/pigx/common/idempotent/IdempotentAutoConfiguration.java b/pigx-common/pigx-common-idempotent/src/main/java/com/pig4cloud/pigx/common/idempotent/IdempotentAutoConfiguration.java index 7ab8e7a4d..fa91e39ac 100644 --- a/pigx-common/pigx-common-idempotent/src/main/java/com/pig4cloud/pigx/common/idempotent/IdempotentAutoConfiguration.java +++ b/pigx-common/pigx-common-idempotent/src/main/java/com/pig4cloud/pigx/common/idempotent/IdempotentAutoConfiguration.java @@ -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); diff --git a/pigx-common/pigx-common-idempotent/src/test/java/com/pig4cloud/pigx/common/idempotent/IdempotentAutoConfigurationTests.java b/pigx-common/pigx-common-idempotent/src/test/java/com/pig4cloud/pigx/common/idempotent/IdempotentAutoConfigurationTests.java new file mode 100644 index 000000000..859af6a96 --- /dev/null +++ b/pigx-common/pigx-common-idempotent/src/test/java/com/pig4cloud/pigx/common/idempotent/IdempotentAutoConfigurationTests.java @@ -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 = 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 = 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); + } + } + +}