# 后端移除 Redis 改造为内存缓存实施计划 > **For agentic workers:** REQUIRED: Use superpowers:executing-plans to implement this plan in this repository. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 移除后端 Redis 依赖,并在单实例 JVM 内存中承接登录态、验证码、限流、防重提交、参数/字典缓存、在线用户和缓存监控能力。 **Architecture:** 保留现有 `RedisCache` 业务入口,底层替换为基于 `ConcurrentHashMap` 的本地缓存存储,减少对现有业务代码的侵入。限流、在线用户、缓存监控等能力继续沿用现有 key 规则,仅替换存储实现与统计来源。 **Tech Stack:** Java 8, Spring Boot 2.x, Spring Security, Maven, JUnit 5 --- ## 文件结构 **Create:** - `ruoyi-common/src/main/java/com/ruoyi/common/core/cache/InMemoryCacheEntry.java` - `ruoyi-common/src/main/java/com/ruoyi/common/core/cache/InMemoryCacheStats.java` - `ruoyi-common/src/main/java/com/ruoyi/common/core/cache/InMemoryCacheStore.java` - `ruoyi-common/src/test/java/com/ruoyi/common/core/cache/InMemoryCacheStoreTest.java` - `ruoyi-common/src/test/java/com/ruoyi/common/core/redis/RedisCacheTest.java` - `ruoyi-framework/src/test/java/com/ruoyi/framework/aspectj/RateLimiterAspectTest.java` - `ruoyi-framework/src/test/java/com/ruoyi/framework/web/service/TokenServiceLocalCacheTest.java` - `ruoyi-admin/src/test/java/com/ruoyi/web/controller/monitor/CacheControllerTest.java` **Modify:** - `pom.xml` - `ruoyi-common/pom.xml` - `ruoyi-framework/pom.xml` - `ruoyi-admin/pom.xml` - `ruoyi-admin/src/main/resources/application.yml` - `ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java` - `ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java` - `ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java` **Delete:** - `ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java` - `ruoyi-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java` **Verify existing behavior against:** - `ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java` - `ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java` - `ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java` - `ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPasswordService.java` - `ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java` - `ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java` - `ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java` - `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java` - `ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java` ### Task 1: 搭建内存缓存底座 **Files:** - Create: `ruoyi-common/src/main/java/com/ruoyi/common/core/cache/InMemoryCacheEntry.java` - Create: `ruoyi-common/src/main/java/com/ruoyi/common/core/cache/InMemoryCacheStats.java` - Create: `ruoyi-common/src/main/java/com/ruoyi/common/core/cache/InMemoryCacheStore.java` - Test: `ruoyi-common/src/test/java/com/ruoyi/common/core/cache/InMemoryCacheStoreTest.java` - [ ] **Step 1: 先写 `InMemoryCacheStore` 的失败测试** ```java @Test void shouldExpireEntryAfterTimeout() throws Exception { InMemoryCacheStore store = new InMemoryCacheStore(); store.set("captcha:1", "1234", 50L, TimeUnit.MILLISECONDS); Thread.sleep(80L); assertNull(store.get("captcha:1")); } ``` - [ ] **Step 2: 运行测试确认失败** Run: `mvn -pl ruoyi-common -Dtest=InMemoryCacheStoreTest test` Expected: FAIL,提示 `InMemoryCacheStore` 或相关方法不存在 - [ ] **Step 3: 实现最小缓存条目与存储类** ```java public final class InMemoryCacheEntry { private final Object value; private final Long expireAtMillis; } ``` ```java @Component public class InMemoryCacheStore { private final ConcurrentHashMap entries = new ConcurrentHashMap<>(); } ``` - [ ] **Step 4: 为底座补齐核心能力** 实现并覆盖测试: - `set/get` - `set(key, value, timeout, unit)` - `hasKey` - `delete` - `delete(Collection)` - `keys(pattern)` - `expire/getExpire` - `increment` - `clear` - `snapshot` - [ ] **Step 5: 运行测试确认通过** Run: `mvn -pl ruoyi-common -Dtest=InMemoryCacheStoreTest test` Expected: PASS - [ ] **Step 6: 提交这一小步** ```bash git add ruoyi-common/src/main/java/com/ruoyi/common/core/cache ruoyi-common/src/test/java/com/ruoyi/common/core/cache/InMemoryCacheStoreTest.java git commit -m "新增内存缓存底座" ``` ### Task 2: 将 `RedisCache` 改为内存缓存门面 **Files:** - Modify: `ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java` - Test: `ruoyi-common/src/test/java/com/ruoyi/common/core/redis/RedisCacheTest.java` - [ ] **Step 1: 先写 `RedisCache` 门面测试** ```java @Test void shouldReadAndDeleteCachedObject() { RedisCache cache = new RedisCache(new InMemoryCacheStore()); cache.setCacheObject("login_tokens:abc", "value"); assertEquals("value", cache.getCacheObject("login_tokens:abc")); assertTrue(cache.deleteObject("login_tokens:abc")); } ``` - [ ] **Step 2: 运行测试确认失败** Run: `mvn -pl ruoyi-common -Dtest=RedisCacheTest test` Expected: FAIL,提示当前 `RedisCache` 仍依赖 `RedisTemplate` - [ ] **Step 3: 将 `RedisCache` 改成构造注入 `InMemoryCacheStore`** ```java @Component public class RedisCache { private final InMemoryCacheStore cacheStore; public RedisCache(InMemoryCacheStore cacheStore) { this.cacheStore = cacheStore; } } ``` - [ ] **Step 4: 保留原有业务方法并补充监控/限流能力** 补齐: - `setCacheObject/getCacheObject` - `setCacheMap/getCacheMap` - `setCacheList/getCacheList` - `setCacheSet/getCacheSet` - `deleteObject` - `keys` - `increment` - `getCacheStats` - `clear` - [ ] **Step 5: 运行测试确认通过** Run: `mvn -pl ruoyi-common -Dtest=RedisCacheTest,InMemoryCacheStoreTest test` Expected: PASS - [ ] **Step 6: 提交这一小步** ```bash git add ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java ruoyi-common/src/test/java/com/ruoyi/common/core/redis/RedisCacheTest.java git commit -m "改造缓存门面为内存实现" ``` ### Task 3: 用内存计数替换限流实现 **Files:** - Modify: `ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java` - Test: `ruoyi-framework/src/test/java/com/ruoyi/framework/aspectj/RateLimiterAspectTest.java` - [ ] **Step 1: 为限流切换先写失败测试** ```java @Test void shouldRejectRequestWhenCountExceeded() throws Throwable { RedisCache cache = new RedisCache(new InMemoryCacheStore()); RateLimiterAspect aspect = new RateLimiterAspect(cache); // 连续触发三次,阈值为2,第三次应抛 ServiceException } ``` - [ ] **Step 2: 运行测试确认失败** Run: `mvn -pl ruoyi-framework -am -Dtest=RateLimiterAspectTest test` Expected: FAIL,提示当前 `RateLimiterAspect` 仍依赖 `RedisTemplate/RedisScript` - [ ] **Step 3: 删除 `RedisTemplate` 和 Lua 脚本依赖** 将核心调用改为: ```java long number = redisCache.increment(combineKey, time, TimeUnit.SECONDS); if (number > count) { throw new ServiceException("访问过于频繁,请稍候再试"); } ``` - [ ] **Step 4: 运行测试确认通过** Run: `mvn -pl ruoyi-framework -am -Dtest=RateLimiterAspectTest test` Expected: PASS - [ ] **Step 5: 提交这一小步** ```bash git add ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java ruoyi-framework/src/test/java/com/ruoyi/framework/aspectj/RateLimiterAspectTest.java git commit -m "改造限流为内存计数" ``` ### Task 4: 调整缓存监控接口到内存统计 **Files:** - Modify: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java` - Test: `ruoyi-admin/src/test/java/com/ruoyi/web/controller/monitor/CacheControllerTest.java` - [ ] **Step 1: 为监控接口写失败测试** ```java @Test void shouldReturnInMemoryCacheStats() { // 断言返回 info.cache_type == "IN_MEMORY" // 断言返回 info.cache_mode == "single-instance" } ``` - [ ] **Step 2: 运行测试确认失败** Run: `mvn -pl ruoyi-admin -am -Dtest=CacheControllerTest test` Expected: FAIL,提示当前接口仍返回 Redis 信息结构 - [ ] **Step 3: 将 `CacheController` 改为依赖 `RedisCache`** 实现行为: - `getInfo()` 返回内存缓存统计 - `getKeys()` 通过 `redisCache.keys(pattern)` 获取 - `getCacheValue()` 将对象序列化为字符串 - `clearCacheName()`、`clearCacheKey()`、`clearCacheAll()` 走内存门面 - [ ] **Step 4: 运行测试确认通过** Run: `mvn -pl ruoyi-admin -am -Dtest=CacheControllerTest test` Expected: PASS - [ ] **Step 5: 手工冒烟现有依赖方** 检查以下类不需要额外逻辑改造: - `TokenService` - `SysLoginService` - `SysRegisterService` - `SysPasswordService` - `SameUrlDataInterceptor` - `SysUserOnlineController` - `SysConfigServiceImpl` - `DictUtils` Expected: 仅继续依赖 `RedisCache` 即可,无需改 key 规则 - [ ] **Step 6: 提交这一小步** ```bash git add ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java ruoyi-admin/src/test/java/com/ruoyi/web/controller/monitor/CacheControllerTest.java git commit -m "改造缓存监控为内存统计" ``` ### Task 5: 删除 Redis 配置与依赖 **Files:** - Modify: `pom.xml` - Modify: `ruoyi-common/pom.xml` - Modify: `ruoyi-framework/pom.xml` - Modify: `ruoyi-admin/pom.xml` - Modify: `ruoyi-admin/src/main/resources/application.yml` - Delete: `ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java` - Delete: `ruoyi-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java` - [ ] **Step 1: 删除 Maven 中 Redis 相关依赖** 删除: - `spring-boot-starter-data-redis` - `commons-pool2` - [ ] **Step 2: 删除应用配置中的 `spring.redis` 配置块** 保留其它配置项不变,只移除 Redis 相关字段 - [ ] **Step 3: 删除 Redis 基础设施类** 删除: - `RedisConfig` - `FastJson2JsonRedisSerializer` - [ ] **Step 4: 全量编译验证** Run: `mvn clean package -DskipTests` Expected: BUILD SUCCESS - [ ] **Step 5: 运行后端相关测试** Run: `mvn -pl ruoyi-common,ruoyi-framework,ruoyi-admin -am test` Expected: BUILD SUCCESS - [ ] **Step 6: 启动后端做接口冒烟** Run: `mvn -pl ruoyi-admin -am spring-boot:run` Expected: - 应用可正常启动 - 不再尝试连接 Redis - 登录、验证码、缓存监控接口正常可用 - [ ] **Step 7: 停止测试进程** 结束 `spring-boot:run` 启动的 Java 进程,避免遗留后台服务 - [ ] **Step 8: 提交这一小步** ```bash git add pom.xml ruoyi-common/pom.xml ruoyi-framework/pom.xml ruoyi-admin/pom.xml ruoyi-admin/src/main/resources/application.yml ruoyi-framework/src/main/java/com/ruoyi/framework/config git commit -m "移除Redis依赖与配置" ``` ### Task 6: 补充实施记录 **Files:** - Modify: `doc/2026-04-15-后端移除Redis改造为内存缓存实施计划.md` - Create or Modify: `doc/2026-04-15-移除Redis依赖改造为内存缓存后端实施记录.md` - [ ] **Step 1: 记录最终实际修改文件和偏差** 记录内容至少包括: - 实际变更文件 - 测试命令 - 冒烟结论 - 与计划有无偏差 - [ ] **Step 2: 提交文档** ```bash git add doc/2026-04-15-后端移除Redis改造为内存缓存实施计划.md doc/2026-04-15-移除Redis依赖改造为内存缓存后端实施记录.md git commit -m "补充后端实施记录" ```