diff --git a/src/main/java/com/njcn/middle/cache/autoconfig/RedisCacheAutoConfiguration.java b/src/main/java/com/njcn/middle/cache/autoconfig/RedisCacheAutoConfiguration.java index 58a957f..52c24d1 100644 --- a/src/main/java/com/njcn/middle/cache/autoconfig/RedisCacheAutoConfiguration.java +++ b/src/main/java/com/njcn/middle/cache/autoconfig/RedisCacheAutoConfiguration.java @@ -17,7 +17,7 @@ import org.springframework.data.redis.core.StringRedisTemplate; * Redis 缓存自动装配。装配在 {@link RedisAutoConfiguration} 之后,确保 {@link StringRedisTemplate} 就绪。 * 与 stream 装配互不引用;纯模板、无后台线程。 */ -@Configuration +@Configuration(proxyBeanMethods = false) @ConditionalOnClass(StringRedisTemplate.class) @ConditionalOnBean(StringRedisTemplate.class) @AutoConfigureAfter(RedisAutoConfiguration.class) diff --git a/src/main/java/com/njcn/middle/cache/support/CacheKeyBuilder.java b/src/main/java/com/njcn/middle/cache/support/CacheKeyBuilder.java index a562ce3..c7c4c64 100644 --- a/src/main/java/com/njcn/middle/cache/support/CacheKeyBuilder.java +++ b/src/main/java/com/njcn/middle/cache/support/CacheKeyBuilder.java @@ -23,6 +23,9 @@ public class CacheKeyBuilder { /** 业务 key → 实际数据 key。 */ public String build(String bizKey) { + if (StrUtil.isBlank(bizKey)) { + throw new IllegalArgumentException("bizKey must not be blank"); + } if (envIsolation && StrUtil.isNotBlank(env)) { return keyPrefix + env + ":" + bizKey; } diff --git a/src/main/java/com/njcn/middle/cache/support/CacheValueCodec.java b/src/main/java/com/njcn/middle/cache/support/CacheValueCodec.java index 5807c6f..d077069 100644 --- a/src/main/java/com/njcn/middle/cache/support/CacheValueCodec.java +++ b/src/main/java/com/njcn/middle/cache/support/CacheValueCodec.java @@ -2,6 +2,7 @@ package com.njcn.middle.cache.support; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; +import com.alibaba.fastjson.parser.Feature; import com.njcn.middle.cache.constant.CacheConstant; /** @@ -25,13 +26,13 @@ public class CacheValueCodec { return CacheConstant.NULL_SENTINEL.equals(raw); } - /** 字符串 → 对象(Class)。 */ + /** 字符串 → 对象(Class)。关闭 @type 自动类型(纵深防御:禁止脏值据 @type 实例化任意类)。 */ public T decode(String raw, Class type) { - return JSON.parseObject(raw, type); + return JSON.parseObject(raw, type, Feature.IgnoreAutoType); } - /** 字符串 → 对象(TypeReference,支持泛型)。 */ + /** 字符串 → 对象(TypeReference,支持泛型)。同样关闭 @type 自动类型。 */ public T decode(String raw, TypeReference type) { - return JSON.parseObject(raw, type); + return JSON.parseObject(raw, type, Feature.IgnoreAutoType); } } 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 07ba6a5..512a8c8 100644 --- a/src/main/java/com/njcn/middle/cache/template/RedisCacheEnhanceTemplate.java +++ b/src/main/java/com/njcn/middle/cache/template/RedisCacheEnhanceTemplate.java @@ -118,7 +118,7 @@ public class RedisCacheEnhanceTemplate { } } - /** 在原 TTL 上叠加 [0, ttl*ratio] 的随机抖动(只增不减);ratio<=0 或 ttl<=0 时原样返回。 */ + /** 在原 TTL 上叠加 [0, ttl*ratio) 的随机抖动(只增不减,半开区间);ratio<=0 或 ttl<=0 时原样返回。 */ long applyJitter(long ttlSeconds) { double ratio = props.getJitterRatio(); if (ratio <= 0 || ttlSeconds <= 0) { @@ -128,7 +128,7 @@ public class RedisCacheEnhanceTemplate { if (bound <= 0) { return ttlSeconds; } - return ttlSeconds + ThreadLocalRandom.current().nextLong(bound + 1); + return ttlSeconds + ThreadLocalRandom.current().nextLong(bound); } /** TTL 必须为正:0/负会被 Redis 当作"立即删除"(EXPIRE)或非法过期时间(SET),显式 fail-fast。 */ @@ -202,7 +202,7 @@ public class RedisCacheEnhanceTemplate { return loadAndCache(dataKey, ttlSeconds, loader); } - String lockKey = dataKey + CacheConstant.LOCK_SUFFIX; + String lockKey = keyBuilder.buildLock(key); // 复用 keyBuilder,锁 key 规则单一来源 String token = IdUtil.fastSimpleUUID(); boolean locked = Boolean.TRUE.equals(redis.opsForValue() .setIfAbsent(lockKey, token, Duration.ofMillis(bd.getLockTtlMs()))); diff --git a/src/test/java/com/njcn/middle/cache/support/CacheKeyBuilderTest.java b/src/test/java/com/njcn/middle/cache/support/CacheKeyBuilderTest.java index 32503cc..ea9a6e9 100644 --- a/src/test/java/com/njcn/middle/cache/support/CacheKeyBuilderTest.java +++ b/src/test/java/com/njcn/middle/cache/support/CacheKeyBuilderTest.java @@ -3,6 +3,7 @@ package com.njcn.middle.cache.support; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; class CacheKeyBuilderTest { @@ -31,4 +32,12 @@ class CacheKeyBuilderTest { assertEquals("cache:prod:user:1:__lock__", new CacheKeyBuilder("cache:", true, "prod").buildLock("user:1")); } + + @Test + void buildRejectsBlankBizKey() { + CacheKeyBuilder kb = new CacheKeyBuilder("cache:", false, ""); + assertThrows(IllegalArgumentException.class, () -> kb.build(null)); + assertThrows(IllegalArgumentException.class, () -> kb.build("")); + assertThrows(IllegalArgumentException.class, () -> kb.build(" ")); + } } diff --git a/src/test/java/com/njcn/middle/cache/support/CacheValueCodecTest.java b/src/test/java/com/njcn/middle/cache/support/CacheValueCodecTest.java index e37df46..ae6d127 100644 --- a/src/test/java/com/njcn/middle/cache/support/CacheValueCodecTest.java +++ b/src/test/java/com/njcn/middle/cache/support/CacheValueCodecTest.java @@ -61,4 +61,13 @@ class CacheValueCodecTest { void decodeDirtyStringThrows() { assertThrows(RuntimeException.class, () -> codec.decode("{not-json", Foo.class)); } + + @Test + void decodeIgnoresAtTypeHint() { + // 含 @type 的脏值(如 Redis 值被外部篡改注入)应被忽略,安全解析为目标类型 + String raw = "{\"@type\":\"java.util.HashMap\",\"id\":1,\"name\":\"a\"}"; + Foo foo = codec.decode(raw, Foo.class); + assertEquals(1L, foo.getId()); + assertEquals("a", foo.getName()); + } } 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 093293e..88215b4 100644 --- a/src/test/java/com/njcn/middle/cache/template/RedisCacheEnhanceTemplateTest.java +++ b/src/test/java/com/njcn/middle/cache/template/RedisCacheEnhanceTemplateTest.java @@ -111,9 +111,9 @@ class RedisCacheEnhanceTemplateTest { void applyJitterWithinBounds() { props.setJitterRatio(0.1); long bound = (long) (100 * 0.1); - for (int i = 0; i < 50; i++) { + for (int i = 0; i < 500; i++) { long ttl = template.applyJitter(100); - assertTrue(ttl >= 100 && ttl <= 100 + bound, "ttl=" + ttl); + assertTrue(ttl >= 100 && ttl < 100 + bound, "ttl=" + ttl); // 半开区间 [100,110) } props.setJitterRatio(0); assertEquals(100, template.applyJitter(100));