From 1e673cdf838a8542efb70a0b9828e0b4dc7af2f2 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 26 Jun 2026 09:54:55 +0800 Subject: [PATCH] =?UTF-8?q?fix(cache):=20=E6=9E=84=E9=80=A0=E6=9C=9F?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E9=85=8D=E7=BD=AE=E9=A1=B9,=E8=AF=AF?= =?UTF-8?q?=E9=85=8D=E5=9C=A8=E8=A3=85=E9=85=8D=E9=98=B6=E6=AE=B5=E5=8D=B3?= =?UTF-8?q?=20fail-fast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit default/null-ttl-seconds、jitter-ratio、breakdown 的 lock-ttl-ms/wait-timeout-ms/ wait-interval-ms 原无校验,负值/越界要到运行期(写入非法 TTL、抖动放大数倍)才暴露。 在 RedisCacheEnhanceTemplate 构造器手工校验,非法即抛 IllegalArgumentException; 不引 hibernate-validator,守住零新依赖。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../template/RedisCacheEnhanceTemplate.java | 34 +++++++++++++++++++ .../RedisCacheEnhanceTemplateTest.java | 24 +++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/main/java/com/njcn/middle/cache/template/RedisCacheEnhanceTemplate.java b/src/main/java/com/njcn/middle/cache/template/RedisCacheEnhanceTemplate.java index 1857aaf..2e6347d 100644 --- a/src/main/java/com/njcn/middle/cache/template/RedisCacheEnhanceTemplate.java +++ b/src/main/java/com/njcn/middle/cache/template/RedisCacheEnhanceTemplate.java @@ -38,6 +38,7 @@ public class RedisCacheEnhanceTemplate { public RedisCacheEnhanceTemplate(StringRedisTemplate redis, CacheKeyBuilder keyBuilder, CacheValueCodec codec, RedisCacheProperties props) { + validateProps(props); this.redis = redis; this.keyBuilder = keyBuilder; this.codec = codec; @@ -134,6 +135,39 @@ public class RedisCacheEnhanceTemplate { } } + /** 构造期校验配置项,误配在装配阶段即 fail-fast,而非运行期才暴露。 */ + private static void validateProps(RedisCacheProperties props) { + if (props.getDefaultTtlSeconds() <= 0) { + throw new IllegalArgumentException( + "redis-cache.default-ttl-seconds must be positive, but was " + props.getDefaultTtlSeconds()); + } + if (props.getNullTtlSeconds() <= 0) { + throw new IllegalArgumentException( + "redis-cache.null-ttl-seconds must be positive, but was " + props.getNullTtlSeconds()); + } + double ratio = props.getJitterRatio(); + if (ratio < 0 || ratio >= 1) { + throw new IllegalArgumentException( + "redis-cache.jitter-ratio must be in [0,1), but was " + ratio); + } + RedisCacheProperties.Breakdown bd = props.getBreakdown(); + if (bd == null) { + throw new IllegalArgumentException("redis-cache.breakdown must not be null"); + } + if (bd.getLockTtlMs() <= 0) { + throw new IllegalArgumentException( + "redis-cache.breakdown.lock-ttl-ms must be positive, but was " + bd.getLockTtlMs()); + } + if (bd.getWaitTimeoutMs() < 0) { + throw new IllegalArgumentException( + "redis-cache.breakdown.wait-timeout-ms must be >= 0, but was " + bd.getWaitTimeoutMs()); + } + if (bd.getWaitIntervalMs() <= 0) { + throw new IllegalArgumentException( + "redis-cache.breakdown.wait-interval-ms must be positive, but was " + bd.getWaitIntervalMs()); + } + } + // ---- 读取或回源(三防核心)---- public T getOrLoad(String key, Class type, Supplier loader) { diff --git a/src/test/java/com/njcn/middle/cache/template/RedisCacheEnhanceTemplateTest.java b/src/test/java/com/njcn/middle/cache/template/RedisCacheEnhanceTemplateTest.java index 7e451b2..311f59d 100644 --- a/src/test/java/com/njcn/middle/cache/template/RedisCacheEnhanceTemplateTest.java +++ b/src/test/java/com/njcn/middle/cache/template/RedisCacheEnhanceTemplateTest.java @@ -19,6 +19,7 @@ import org.springframework.data.redis.core.script.RedisScript; import java.time.Duration; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.function.Supplier; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -294,4 +295,27 @@ class RedisCacheEnhanceTemplateTest { // 中断后立即停止等待:仅首次 tryGet 读一次,而非空转数百次 verify(valueOps, atMost(2)).get("cache:user:1"); } + + // ---- #5: 构造期配置 fail-fast 校验 ---- + + private RedisCacheEnhanceTemplate newTemplateWith(Consumer mut) { + RedisCacheProperties p = new RedisCacheProperties(); + mut.accept(p); + return new RedisCacheEnhanceTemplate(redis, keyBuilder, codec, p); + } + + @Test + void constructorRejectsInvalidProps() { + assertThrows(IllegalArgumentException.class, () -> newTemplateWith(p -> p.setDefaultTtlSeconds(0))); + assertThrows(IllegalArgumentException.class, () -> newTemplateWith(p -> p.setNullTtlSeconds(-1))); + assertThrows(IllegalArgumentException.class, () -> newTemplateWith(p -> p.setJitterRatio(1.0))); + assertThrows(IllegalArgumentException.class, () -> newTemplateWith(p -> p.setJitterRatio(-0.1))); + assertThrows(IllegalArgumentException.class, () -> newTemplateWith(p -> p.getBreakdown().setLockTtlMs(0))); + assertThrows(IllegalArgumentException.class, () -> newTemplateWith(p -> p.getBreakdown().setWaitIntervalMs(0))); + } + + @Test + void constructorAcceptsDefaults() { + new RedisCacheEnhanceTemplate(redis, keyBuilder, codec, new RedisCacheProperties()); + } }