把 CaseDataCache 中的 BUCKETS(caseId → CaseBucket)JVM 内存缓存整体迁移到 Redis:删除 CaseBucket 内部类、ReentrantReadWriteLock 全套锁、快照重建逻辑。公开静态 API 保持不变,20+ 调用方零改动。
前缀 qing_jian:case:{caseId}:(复用 StrConsts.CACHE_TAG_QING_JIAN,管理端 SystemService.cleanCache() 的 KEYS qing_jian* 全量清理自动覆盖)。{caseId} 花括号天然是 Redis Cluster hash tag,将来上集群时同案 key 同 slot。
| Key | 类型 | 内容 | 服务的查询 |
|---|---|---|---|
qing_jian:case:{id}:pno:{libType}:{personName} |
SET | personNo 集合 | getPerPhones/getPerCards(SMEMBERS)、putPerLib(SADD) |
qing_jian:case:{id}:val2name |
HASH | personNo → personName | getPersonPhone/getPersonCard/findPersonVal(HGET)、containsPersonValue(HEXISTS)——O(1) 替代现在 findPersonVal 的全表线性扫描 |
qing_jian:case:{id}:pno-idx |
SET | 全部正向 key 后缀 | cleanCache/initCache 精确删除,避免 SCAN |
说明:现在 personPhone/personCard 按 libType 分两个 Map,统一为 val2name 后理论上同一字符串既是手机号又是卡号会互串——实际号码位数不同不可能冲突,接受(代码注释注明)。
GOVERN_CONFIG 及 cleanGovernConfig 保持不变(本地 ConcurrentHashMap,key 已含 caseId;GovernConfService/SystemService 不动)。StringRedisTemplate(SpringUtil.getBean,Boot 自动配置的 bean,纯字符串数据无需 JSON 序列化器)。initCache(caseId):requireCaseId → 查库(CaseContextHolder.callWith 包裹,不变)→ 按 pno-idx 删旧 key → 每批 ~1000 行 pipeline 写入 SADD 正向 + HSET 反向 + SADD idx。失败抛异常(CaseInfoService.open 已有 try/catch 兜底)。putPerLib 两个重载:解析 key(沿用现有 split 逻辑)→ pipeline 一次往返执行 SADD + HSET + SADD idx → CleanCache.addPersonLibNo(...) 不变。Redis 异常 log.error 后继续,不杀长时清洗任务(数据仍经 CleanCache 落库,下次 initCache 修复)。cleanCache(caseId):按 idx 删除三组 key;异常吞掉记日志(调用点在 listener/reaper)。cleanAll()(无调用方);保留 findPersonVal(HGET 一行实现,虽生产已无调用但保持 API 完整)。重写 CaseDataCacheConcurrencyTest:
Assumptions.assumeTrue(ping 通),不可达时跳过(离线 mvn test 保持绿)。CaseDataCacheConcurrencyTest(Redis 可达全跑,不可达自动跳过)。redis-cli --scan --pattern 'qing_jian:case:*' 确认 key 存在 → 清洗小文件后 val2name 出现新号码 → 退案后 key 全部删除。ai-server/.../common/cache/CaseDataCache.java(重写,约 310 行 → 约 200 行,无锁无内部类)ai-server/src/test/.../CaseDataCacheConcurrencyTest.java(重写匹配新语义)qing_jian* 清理;进程崩溃可能留孤儿 key(上限=案件数),后续可按需补 TTL。