ponytail设计范式:轻量、确定、可维护的工程实践
2026/10/31 5:06:10
<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"> <properties> <java.version>21</java.version> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <maven.compiler.release>21</maven.compiler.release> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-restclient</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webmvc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-docker-compose</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-restclient-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webmvc-test</artifactId> <scope>test</scope> </dependency> <!-- MySQL驱动 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.33</version> <scope>runtime</scope> </dependency> <!-- Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- Kafka --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-kafka</artifactId> </dependency> <!-- TDengine 驱动 --> <dependency> <groupId>com.taosdata.jdbc</groupId> <artifactId>taos-jdbcdriver</artifactId> <version>3.9.2</version> <type>pom</type> </dependency> <!-- MyBatis Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- 用来做sql脚本版本管理的--> <dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> </dependency> <dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-mysql</artifactId> </dependency> <!-- dotenv 支持自动提取env中的密码 --> <dependency> <groupId>me.paulschwarz</groupId> <artifactId>spring-dotenv</artifactId> <version>4.0.0</version> </dependency> </dependencies> </project>遇到问题如图:
配置项目如下
证据链(都是实跑出来的) 1. 服务端没问题:docker inspect iec-redis 的 Cmd 是 redis-server /etc/redis.conf --requirepass Redis@123456;用 redis-cli 和裸 socket 测 AUTH <pwd>、AUTH default <pwd>、HELLO 3 AUTH default <pwd> 全部 +OK;ACL 里 user default on ... #1dfc1cc...。 2. 客户端复现:跑起应用后打 /actuator/health,返回 "redis":{"status":"DOWN","error":"...Unable to connect to Redis"},最内层是 io.lettuce.core.RedisCommandExecutionException: NOAUTH Authentication required.;而同一进程里你的 RedisConfigCheck 打印的正是 Redis password configured: Redis@123456 —— 说明属性对,但压根没被用在连接上。 3. 单变量验证:只加 -Dspring.docker.compose.enabled=false 重启,同一个 /actuator/health 变成 "redis":{"status":"UP","details":{"version":"7.2.17"}}。因果确定。 4. 源码确认(spring-boot-data-redis-4.1.2-sources.jar): - RedisDockerComposeConnectionDetailsFactory$RedisDockerComposeConnectionDetails 只做 Standalone.of(service.host(), service.ports().get(6379)),完全不解析 compose 里的 --requirepass,也没有 username/password 字段(恒为 null)。 - DataRedisConnectionConfiguration.getStandaloneConfig() 用 connectionDetails.getUsername()/getPassword() 建 RedisStandaloneConfiguration,password 为 null 就不发 AUTH。 - DataRedisAutoConfiguration 里的 PropertiesDataRedisConnectionDetails 标了 @ConditionalOnMissingBean(DataRedisConnectionDetails.class),compose 那个 bean(dataRedisConnectionDetailsForRedis)存在时,按属性的那份根本不创建 → host/port/username/password/database 全部失效。 两个容易误判的点 - 为什么看起来是“启动时”失败:Lettuce 连接是懒加载,启动阶段不连 Redis。这条 WARN 是访问 /actuator/health(或前端/浏览器轮询 actuator)时才触发的,我这次是启动后 32 秒 curl 才出现。 - .env 的 CRLF 不是元凶:实测 dotenv-java 已剥掉 \r(REDIS_PWD 长度 12 = Redis@123456)。conf/redis/redis.conf 的 CRLF 也没影响。 解决方案(三选一) 方案1(推荐,零代码改动):application.yaml 里关掉 compose 支持,中间件自己 docker compose up -d 起: spring: docker: compose: enabled: false 注意副作用:关掉后 MySQL 走你自己的 URL,而你写的是 iec-biz,容器实际库名是 iec_biz(compose 的 MYSQL_DATABASE),会报 Unknown database 'iec-biz'。要么把 URL 改成 iec_biz,要么统一两边命名。也就是说“开着 compose 时 db 是 UP”正是靠这份连接信息顺带修正的,关掉要自己补。 方案2(保留 compose 自动装配,只把 Redis 接管回来):在 RedisConfig 里自己提供一个 RedisStandaloneConfiguration bean(用 @Value 取 spring.data.redis.* 组好 host/port/username/password/database)。因为 getStandaloneConfig() 开头就 if (this.standaloneConfiguration != null) return ...,用户 bean 优先级最高,能绕开 compose 那份 details。注意别去自定义 DataRedisConnectionDetails 类型的 bean —— 会和 compose 注册的那个同类型冲突。 方案3:让 Boot 别读这份 compose(spring.docker.compose.file 指向一个不含 redis 的 compose),或直接去掉 spring-boot-docker-compose 依赖。补充一句:我查了 4.1.2 的源码,这版没有 spring.docker.compose.skip.included-services 这种“只跳过某个服务”的开关,所以没法用一个属性单独放过 redis。 另外:RedisConfig 里强制 RESP2 的 customizer 不是必需的(RESP3 的 HELLO 3 AUTH default <pwd> 我也测通了),留着当防御性配置无妨;password: "Redis@123456" 也可以改成 ${REDIS_PWD} 交给 dotenv,省得两处维护。applicon.yaml中增加配置能够读取。env的配置文件
config:
import: optional:file:.env[.properties]
docker:
compose:
enabled: false
修改配置项目
spring: application: name: IECDemo config: import: optional:file:.env[.properties] flyway: enabled: true locations: classpath:db/migration baseline-on-migrate: true #如果数据库中已经有酒标,自动baseline datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/iec_biz?unicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai username: root password: ${MYSQL_ROOT_PWD} data: redis: host: 127.0.0.1 port: 6379 username: default password: ${REDIS_PWD} database: 0 Kafka: bootstrap-servers: 127.0.0.1:9092 docker: compose: enabled: false management: endpoints: web: exposure: include: health,metrics,threaddump endpoint: health: show-details: always #显示健康状态http://127.0.0.1:8080/actuator/health