fix(mq-starter): 修复启动 fail-fast 校验 review 发现的缺陷(relaxed binding / 逃生开关 / redis 假地址)

workflow 多智能体 review 后修复:

- rocketmq 探测改用 Binder 读 rocketmq.name-server,与 driver(RocketMQProperties)同源、
  享受 relaxed binding,修复 camelCase(rocketmq.nameServer) 被误判为「未配置」而错误 fail-fast
- MqStartupChecks 逃生开关只认严格 false(忽略大小写/空白),缺省或非法值一律继续校验,
  不再因非法布尔值(如 enabled=disabled)抛类型转换异常炸掉启动
- redis 探测 target() 对 cluster/sentinel 回退到指向真实配置项的文案,
  不再假报使用者从未配过的 localhost:6379
- 补齐回归测试:camelCase 键 / 非法开关值继续校验 / host:port 提示分支 / cluster 回退
  (core 5、redis-stream 5、rocketmq 5,全绿)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-09 14:06:05 +08:00
parent 0c6d37842e
commit 886c02ff96
6 changed files with 91 additions and 5 deletions

View File

@@ -46,11 +46,19 @@ public class RedisStreamStartupProbeAutoConfiguration implements InitializingBea
}
}
/** 尽力取 host:port 用于提示;取不到(非 Lettuce / cluster / sentinel则回退 unknown,不再抛新异常。 */
/** 尽力取 host:port 用于提示;取不到(非 Lettuce / cluster / sentinel则回退到有意义的文案,不再抛新异常。 */
private String target() {
try {
if (connectionFactory instanceof LettuceConnectionFactory) {
LettuceConnectionFactory lf = (LettuceConnectionFactory) connectionFactory;
// cluster/sentinel 模式下 getHostName()/getPort() 返回的是 standalone 默认值 localhost:6379
//(使用者从未配置过的假地址),会把排障引偏,故回退到指向真实配置项的文案。
if (lf.isClusterAware()) {
return "集群模式,见 spring.data.redis.cluster.nodes";
}
if (lf.isRedisSentinelAware()) {
return "哨兵模式,见 spring.data.redis.sentinel";
}
return lf.getHostName() + ":" + lf.getPort();
}
} catch (Exception ignore) {

View File

@@ -3,8 +3,11 @@ 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.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -44,6 +47,47 @@ class RedisStreamStartupProbeAutoConfigurationTest {
});
}
@Test
void pingFails_messageContainsHostPort() {
// 用真实 LettuceConnectionFactory(standalone) 覆盖 target() 的 host:port 提示分支;
// override getConnection() 让探测立即失败,无需真实 Redis / 网络。
LettuceConnectionFactory factory =
new LettuceConnectionFactory(new RedisStandaloneConfiguration("myhost", 1234)) {
@Override
public RedisConnection getConnection() {
throw new RuntimeException("Connection refused");
}
};
runner.withPropertyValues("mq.type=redis-stream")
.withBean(RedisConnectionFactory.class, () -> factory)
.run(ctx -> {
assertThat(ctx).hasFailed();
assertThat(ctx).getFailure().hasMessageContaining("myhost:1234");
});
}
@Test
void clusterMode_avoidsFakeLocalhostInMessage() {
// cluster 模式下 getHostName()/getPort() 会返回 standalone 默认值 localhost:6379(假地址)
// 修复后应回退到指向 cluster 配置项的文案,而不是打印使用者从未配过的 localhost:6379。
LettuceConnectionFactory factory =
new LettuceConnectionFactory(
new RedisClusterConfiguration(java.util.Collections.singletonList("127.0.0.1:7000"))) {
@Override
public RedisConnection getConnection() {
throw new RuntimeException("Connection refused");
}
};
runner.withPropertyValues("mq.type=redis-stream")
.withBean(RedisConnectionFactory.class, () -> factory)
.run(ctx -> {
assertThat(ctx).hasFailed();
assertThat(ctx).getFailure().hasMessageContaining("集群模式");
});
}
@Test
void disabled_skipsProbe() {
RedisConnectionFactory factory = mock(RedisConnectionFactory.class);