From 46b0c13462ce787fcdef17578dd16b0a5eed9540 Mon Sep 17 00:00:00 2001 From: hongawen <83944980@qq.com> Date: Thu, 9 Jul 2026 10:49:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(mq-starter-redis-stream):=20=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E6=9C=9F=20PING=20=E6=8E=A2=E6=B5=8B=20Redis=20?= =?UTF-8?q?=E8=BF=9E=E9=80=9A=E6=80=A7=EF=BC=88=E2=91=A0=E2=91=A1=20fail-f?= =?UTF-8?q?ast=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...isStreamStartupProbeAutoConfiguration.java | 61 +++++++++++++++++++ .../main/resources/META-INF/spring.factories | 3 +- ...reamStartupProbeAutoConfigurationTest.java | 56 +++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 mq-starter-redis-stream/src/main/java/com/njcn/mq/driver/redis/RedisStreamStartupProbeAutoConfiguration.java create mode 100644 mq-starter-redis-stream/src/test/java/com/njcn/mq/driver/redis/RedisStreamStartupProbeAutoConfigurationTest.java diff --git a/mq-starter-redis-stream/src/main/java/com/njcn/mq/driver/redis/RedisStreamStartupProbeAutoConfiguration.java b/mq-starter-redis-stream/src/main/java/com/njcn/mq/driver/redis/RedisStreamStartupProbeAutoConfiguration.java new file mode 100644 index 0000000..3b36fc9 --- /dev/null +++ b/mq-starter-redis-stream/src/main/java/com/njcn/mq/driver/redis/RedisStreamStartupProbeAutoConfiguration.java @@ -0,0 +1,61 @@ +package com.njcn.mq.driver.redis; + +import com.njcn.mq.autoconfig.MqStartupChecks; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; + +/** + * ①② redis 探测:mq.type=redis-stream 时,在 bean 初始化阶段主动 PING 一次 Redis。 + * + *

lettuce 默认懒连接(启动不真正连、运行时才连);此处主动 PING,连不上就 fail-fast。 + */ +@Configuration +@ConditionalOnProperty(name = "mq.type", havingValue = "redis-stream") +public class RedisStreamStartupProbeAutoConfiguration implements InitializingBean { + + private final RedisConnectionFactory connectionFactory; + private final Environment env; + + public RedisStreamStartupProbeAutoConfiguration(RedisConnectionFactory connectionFactory, Environment env) { + this.connectionFactory = connectionFactory; + this.env = env; + } + + @Override + public void afterPropertiesSet() { + if (!MqStartupChecks.enabled(env)) { + return; + } + try { + RedisConnection conn = connectionFactory.getConnection(); + try { + conn.ping(); + } finally { + conn.close(); // 探测用连接必须释放,避免泄漏 + } + } catch (Exception e) { + throw new IllegalStateException( + "MQ 启动失败:mq.type=redis-stream,无法连接 Redis(" + target() + ")。原因:" + + e.getMessage() + + "。请检查 spring.data.redis.host/port/password,或确认 Redis 服务已启动。", e); + } + } + + /** 尽力取 host:port 用于提示;取不到(非 Lettuce / cluster / sentinel)则回退 unknown,不再抛新异常。 */ + private String target() { + try { + if (connectionFactory instanceof LettuceConnectionFactory) { + LettuceConnectionFactory lf = (LettuceConnectionFactory) connectionFactory; + return lf.getHostName() + ":" + lf.getPort(); + } + } catch (Exception ignore) { + // 取地址失败不能影响主流程 + } + return "unknown"; + } +} diff --git a/mq-starter-redis-stream/src/main/resources/META-INF/spring.factories b/mq-starter-redis-stream/src/main/resources/META-INF/spring.factories index cfc2394..85db6ea 100644 --- a/mq-starter-redis-stream/src/main/resources/META-INF/spring.factories +++ b/mq-starter-redis-stream/src/main/resources/META-INF/spring.factories @@ -1,2 +1,3 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -com.njcn.mq.driver.redis.RedisStreamMqDriverAutoConfiguration +com.njcn.mq.driver.redis.RedisStreamMqDriverAutoConfiguration,\ +com.njcn.mq.driver.redis.RedisStreamStartupProbeAutoConfiguration diff --git a/mq-starter-redis-stream/src/test/java/com/njcn/mq/driver/redis/RedisStreamStartupProbeAutoConfigurationTest.java b/mq-starter-redis-stream/src/test/java/com/njcn/mq/driver/redis/RedisStreamStartupProbeAutoConfigurationTest.java new file mode 100644 index 0000000..5f294ca --- /dev/null +++ b/mq-starter-redis-stream/src/test/java/com/njcn/mq/driver/redis/RedisStreamStartupProbeAutoConfigurationTest.java @@ -0,0 +1,56 @@ +package com.njcn.mq.driver.redis; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class RedisStreamStartupProbeAutoConfigurationTest { + + private final ApplicationContextRunner runner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(RedisStreamStartupProbeAutoConfiguration.class)); + + @Test + void pingOk_startsUp_andReleasesConnection() { + RedisConnectionFactory factory = mock(RedisConnectionFactory.class); + RedisConnection conn = mock(RedisConnection.class); + when(factory.getConnection()).thenReturn(conn); + when(conn.ping()).thenReturn("PONG"); + + runner.withPropertyValues("mq.type=redis-stream") + .withBean(RedisConnectionFactory.class, () -> factory) + .run(ctx -> assertThat(ctx).hasNotFailed()); + + verify(conn).close(); + } + + @Test + void pingFails_startupFails() { + RedisConnectionFactory factory = mock(RedisConnectionFactory.class); + when(factory.getConnection()).thenThrow(new RuntimeException("Connection refused")); + + runner.withPropertyValues("mq.type=redis-stream") + .withBean(RedisConnectionFactory.class, () -> factory) + .run(ctx -> { + assertThat(ctx).hasFailed(); + assertThat(ctx).getFailure().hasMessageContaining("Redis"); + assertThat(ctx).getFailure().hasMessageContaining("Connection refused"); + }); + } + + @Test + void disabled_skipsProbe() { + RedisConnectionFactory factory = mock(RedisConnectionFactory.class); + when(factory.getConnection()).thenThrow(new RuntimeException("should not be called")); + + runner.withPropertyValues("mq.type=redis-stream", "mq.startup-check.enabled=false") + .withBean(RedisConnectionFactory.class, () -> factory) + .run(ctx -> assertThat(ctx).hasNotFailed()); + } +}