diff --git a/src/main/java/com/njcn/middle/cache/autoconfig/RedisCacheProperties.java b/src/main/java/com/njcn/middle/cache/autoconfig/RedisCacheProperties.java new file mode 100644 index 0000000..012e807 --- /dev/null +++ b/src/main/java/com/njcn/middle/cache/autoconfig/RedisCacheProperties.java @@ -0,0 +1,48 @@ +package com.njcn.middle.cache.autoconfig; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Redis 缓存配置属性,前缀 {@code redis-cache}。全部可选,有合理默认。 + */ +@Data +@ConfigurationProperties("redis-cache") +public class RedisCacheProperties { + + /** 统一 key 前缀。 */ + private String keyPrefix = "cache:"; + + /** 是否按环境隔离 key。 */ + private boolean envIsolation = false; + + /** 环境标识(隔离开启且非空时拼入 key)。 */ + private String env = ""; + + /** set/getOrLoad 不传 TTL 时的默认过期(秒)。 */ + private long defaultTtlSeconds = 3600; + + /** 空值标记 TTL(秒)——穿透防护,宜短。 */ + private long nullTtlSeconds = 60; + + /** TTL 随机抖动比例——雪崩防护;0 关闭抖动。 */ + private double jitterRatio = 0.1; + + /** 读命中真实值时自动续期(滑动过期),默认关闭。 */ + private boolean slidingExpireEnabled = false; + + private Breakdown breakdown = new Breakdown(); + + /** 击穿:互斥锁。 */ + @Data + public static class Breakdown { + /** 关闭后 getOrLoad 退化为无锁直接回源。 */ + private boolean enabled = true; + /** 锁持有 TTL(ms),防持锁线程崩溃死锁。 */ + private long lockTtlMs = 10000; + /** 未抢到锁的线程最长等待(ms),超时降级直接回源。 */ + private long waitTimeoutMs = 3000; + /** 未抢到锁时自旋重读间隔(ms)。 */ + private long waitIntervalMs = 50; + } +} diff --git a/src/test/java/com/njcn/middle/cache/autoconfig/RedisCachePropertiesTest.java b/src/test/java/com/njcn/middle/cache/autoconfig/RedisCachePropertiesTest.java new file mode 100644 index 0000000..754273f --- /dev/null +++ b/src/test/java/com/njcn/middle/cache/autoconfig/RedisCachePropertiesTest.java @@ -0,0 +1,44 @@ +package com.njcn.middle.cache.autoconfig; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.context.properties.source.ConfigurationPropertySources; +import org.springframework.mock.env.MockEnvironment; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class RedisCachePropertiesTest { + + @Test + void defaults() { + RedisCacheProperties p = new RedisCacheProperties(); + assertEquals("cache:", p.getKeyPrefix()); + assertFalse(p.isEnvIsolation()); + assertEquals("", p.getEnv()); + assertEquals(3600, p.getDefaultTtlSeconds()); + assertEquals(60, p.getNullTtlSeconds()); + assertEquals(0.1, p.getJitterRatio()); + assertFalse(p.isSlidingExpireEnabled()); + assertTrue(p.getBreakdown().isEnabled()); + assertEquals(10000, p.getBreakdown().getLockTtlMs()); + assertEquals(3000, p.getBreakdown().getWaitTimeoutMs()); + assertEquals(50, p.getBreakdown().getWaitIntervalMs()); + } + + @Test + void relaxedBinding() { + MockEnvironment env = new MockEnvironment() + .withProperty("redis-cache.default-ttl-seconds", "1800") + .withProperty("redis-cache.sliding-expire-enabled", "true") + .withProperty("redis-cache.breakdown.lock-ttl-ms", "5000") + .withProperty("redis-cache.breakdown.enabled", "false"); + RedisCacheProperties p = Binder.get(env) + .bind("redis-cache", RedisCacheProperties.class).get(); + assertEquals(1800, p.getDefaultTtlSeconds()); + assertTrue(p.isSlidingExpireEnabled()); + assertEquals(5000, p.getBreakdown().getLockTtlMs()); + assertFalse(p.getBreakdown().isEnabled()); + } +}