fix(cache): set/expire/getOrLoad 对 ttl<=0 fail-fast,杜绝 expire 误删 key
expire(key, ttl<=0) 经 applyJitter 透传 0/负值,Redis EXPIRE 非正值会立即删 key 并返回 1,使"续期"静默删数据且报 true;set(ttl<=0) 则下发非法过期抛 RedisSystemException。统一在 set/expire/doGetOrLoad 入口 requirePositiveTtl 校验, 非正 TTL 直接抛 IllegalArgumentException。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -51,6 +51,7 @@ public class RedisCacheEnhanceTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void set(String key, Object value, long ttlSeconds) {
|
public void set(String key, Object value, long ttlSeconds) {
|
||||||
|
requirePositiveTtl(ttlSeconds);
|
||||||
redis.opsForValue().set(keyBuilder.build(key), codec.encode(value),
|
redis.opsForValue().set(keyBuilder.build(key), codec.encode(value),
|
||||||
applyJitter(ttlSeconds), TimeUnit.SECONDS);
|
applyJitter(ttlSeconds), TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
@@ -94,6 +95,7 @@ public class RedisCacheEnhanceTemplate {
|
|||||||
// ---- 续期 ----
|
// ---- 续期 ----
|
||||||
|
|
||||||
public boolean expire(String key, long ttlSeconds) {
|
public boolean expire(String key, long ttlSeconds) {
|
||||||
|
requirePositiveTtl(ttlSeconds);
|
||||||
return Boolean.TRUE.equals(
|
return Boolean.TRUE.equals(
|
||||||
redis.expire(keyBuilder.build(key), applyJitter(ttlSeconds), TimeUnit.SECONDS));
|
redis.expire(keyBuilder.build(key), applyJitter(ttlSeconds), TimeUnit.SECONDS));
|
||||||
}
|
}
|
||||||
@@ -125,6 +127,13 @@ public class RedisCacheEnhanceTemplate {
|
|||||||
return ttlSeconds + ThreadLocalRandom.current().nextLong(bound + 1);
|
return ttlSeconds + ThreadLocalRandom.current().nextLong(bound + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** TTL 必须为正:0/负会被 Redis 当作"立即删除"(EXPIRE)或非法过期时间(SET),显式 fail-fast。 */
|
||||||
|
private static void requirePositiveTtl(long ttlSeconds) {
|
||||||
|
if (ttlSeconds <= 0) {
|
||||||
|
throw new IllegalArgumentException("ttlSeconds must be positive, but was " + ttlSeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- 读取或回源(三防核心)----
|
// ---- 读取或回源(三防核心)----
|
||||||
|
|
||||||
public <T> T getOrLoad(String key, Class<T> type, Supplier<T> loader) {
|
public <T> T getOrLoad(String key, Class<T> type, Supplier<T> loader) {
|
||||||
@@ -140,6 +149,7 @@ public class RedisCacheEnhanceTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private <T> T doGetOrLoad(String key, Function<String, T> decoder, long ttlSeconds, Supplier<T> loader) {
|
private <T> T doGetOrLoad(String key, Function<String, T> decoder, long ttlSeconds, Supplier<T> loader) {
|
||||||
|
requirePositiveTtl(ttlSeconds);
|
||||||
String dataKey = keyBuilder.build(key);
|
String dataKey = keyBuilder.build(key);
|
||||||
|
|
||||||
Lookup<T> r = tryGet(dataKey, decoder);
|
Lookup<T> r = tryGet(dataKey, decoder);
|
||||||
|
|||||||
@@ -240,4 +240,28 @@ class RedisCacheEnhanceTemplateTest {
|
|||||||
User got = template.getOrLoad("user:1", User.class, 60, () -> new User(1L, "fresh"));
|
User got = template.getOrLoad("user:1", User.class, 60, () -> new User(1L, "fresh"));
|
||||||
assertEquals("fresh", got.getName());
|
assertEquals("fresh", got.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- #1+#2: ttl<=0 入口校验(fail-fast,不下发非法过期) ----
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void setRejectsNonPositiveTtl() {
|
||||||
|
User u = new User(1L, "a");
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> template.set("user:1", u, 0));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> template.set("user:1", u, -1));
|
||||||
|
verify(valueOps, never()).set(anyString(), anyString(), anyLong(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void expireRejectsNonPositiveTtl() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> template.expire("user:1", 0));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> template.expire("user:1", -5));
|
||||||
|
verify(redis, never()).expire(anyString(), anyLong(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getOrLoadRejectsNonPositiveTtl() {
|
||||||
|
props.getBreakdown().setEnabled(false); // 避免跑红时走入等待自旋
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> template.getOrLoad("user:1", User.class, 0, () -> new User(1L, "a")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user