发放明细缓存改造 — 执行与验证报告
日期:2026-08-24
环境:本地 spring-boot:run(8088)+ 生产 DMS 121.43.55.7:2101
数据规模:发放明细约 32,933 条(集中在 202608 单月);叶榭镇 T_YEXIE 约 9,881 条
1. 改造目标与结论
| 目标 |
结论 |
| 取消启动时全表发放明细热缓存 |
✅ 已达成 — 启动约 4.3s,无 payment 预热日志 |
按人/批/镇/月走 DMS search 条件查询 |
✅ 已达成 — states=0,3,支持 c_user_id / c_batch_id / c_pay_month + 镇村 |
| 作用域短缓存(32 键、TTL 15min) |
✅ 已达成 — 二次请求显著加速 |
| 工作台图表改 batch_stat 聚合 |
✅ 已达成 — 不再扫全表明细 |
| 写路径 invalidate 不再触发全表重载 |
✅ 已达成 — invalidatePaymentCache() 仅清 scope map |
总体结论:改造按改动清单完成,编译通过,核心读路径与业务流程可用;按批首开仍约 30~40s(DMS 翻页物理上限),命中 scope 缓存后 <300ms。
2. 改动清单执行状态
Phase 1 — DmsClient 条件查询 ✅
- 新增
STATES_DEFAULT_PAYMENT = "0,3"
- 新增
selectContentPage(..., states, searchJson)、selectContentAllWithSearch(...)
- 新增
buildEqSearchJson(...) 静态构造器
Phase 2 — pagePayment / getPayment 按需 DMS ✅
pagePayment:按 personnelId → c_user_id;按 batchId → c_batch_id;按镇/月 → scope 查询
getPayment(id):单条 DMS 查
fillTownBatchFromDetails / fillDistrictFromDetails / fillMissingBatchTotalsFromDetails 改为按批 DMS 拉取
Phase 3 — 作用域短缓存 + workbench 走 stat ✅
paymentScopeCache:最多 32 键,TTL 15 分钟,LRU 式淘汰
listPaymentsByBatch / listPaymentsByScope / listPaymentsByMonthScope 走 scope cache
buildRegionDashboard / warmVillageRegionDashCache 月度 series 改 aggregateMonthFromLists + batch_stat
Phase 4 — 取消启动预热 + invalidate 轻量化 ✅
warmPersonnelOnReady 保留 region / personnel / batch / alert,移除 payment
warmPaymentCacheAsync() → no-op
invalidatePaymentCache() → 仅 paymentScopeCache.clear()(不再异步全表重载)
- 可选优化
invalidatePaymentScopeForBatch(batchId) 已提供;写路径仍调全量 clear(32 条成本低,未逐一替换)
Phase 5 — 编译、探针、业务流程、报告 ✅
mvn compile -DskipTests 通过
- 热路径探针、定向探针、DMS 基准、E2E 脚本均已跑(见下文)
3. 涉及文件
| 文件 |
变更要点 |
DmsClient.java |
search 条件分页、全量翻页、states 0,3 |
BizDataService.java |
移除全表 paymentListCache;新增 paymentScopeCache 及按需加载 |
BizFlowService.java |
看板/workbench 改 stat 聚合;applyPayrollHeadcount 用 listPaymentsByMonthScope |
前端 无需改动,仍调用相同 API。
4. 性能验证
4.1 启动
| 指标 |
改造前(参考) |
改造后(实测) |
| Spring Boot 就绪 |
~50s+(含 payment 全表预热) |
4.3s |
| 人员缓存(异步) |
同步阻塞或同量级 |
~39s 后台 [personnel-cache] loaded 32939 |
| 告警缓存 |
— |
158ms(11 条) |
4.2 DMS 直连基准(bench-dms-payment-query-v2.js)
| 场景 |
耗时 |
条数 |
| 按人单页(5 次 p50) |
117ms |
1 |
| 镇+月单页 p50 |
225ms |
9,882 |
| 镇+月全量翻页 |
17.3s |
9,882 / 99 页 |
| 全表全量翻页 |
59.0s |
32,933 / 330 页 |
4.3 后端 API 探针
热路径脚本 perf-hot-paths.js(第二次请求为 warm,阈值 300ms)
| 接口 |
cold |
warm |
结果 |
GET /api/regions |
148ms |
4ms |
OK |
GET /api/dashboard/region-stats |
189ms |
4ms |
OK |
GET /api/dashboard/workbench |
21.7s |
211ms |
OK |
POST /api/payments/details/page(镇) |
92ms* |
62ms |
OK |
POST /api/batches/page |
48ms |
7ms |
OK |
POST /api/personnel/page |
59ms |
30ms |
OK |
* 探针顺序在 workbench 之后,镇级明细已部分预热。
定向探针(独立 cold / warm)
| 场景 |
cold |
warm |
total |
按批 TB202608-T_YEXIE + 镇 |
35.9s |
186ms |
9,881 |
按人 P33040 |
159ms |
4ms |
0 |
| workbench 叶榭 6 月 |
20.8s |
58ms |
— |
Scope 缓存日志(服务端)
[payment-scope] month|202608|T_YEXIE| loaded 9881 in 9767ms
[payment-scope] scope|202608|T_YEXIE| loaded 9881 in 10718ms
5. 业务流程测试
5.1 e2e-api-flow-test.js(59 项)
与本次改造相关的接口均通过:
POST /api/payments/details/page(待审批、已回盘)→ PASS,code=200
GET /api/dashboard/summary → PASS
POST /api/batch-stats/page、recalculate → PASS
失败项原因(与改造无关):
- 脚本硬编码批次/人员 ID(
VB202604001、MB202604002、P001 等)在生产库不存在
- 业务模型已改为「仅全区月批 + 镇统计切片」,脚本仍尝试「创建村级批次 / 镇审村批」
- Excel 导入探针上传空文件导致解析失败
5.2 e2e-biz-gates-full.js(43 项)
失败主因:脚本依赖已废弃的村级批次闭环(ensure-current-month 返回 400「不再创建村级批次」),与发放明细缓存改造无关。门禁类断言(未过节点不可出盘/领导批等)在缺批次时仍正确返回 400/404。
5.3 业务场景 UX 预期(改造后)
| 场景 |
首开 |
缓存命中 |
说明 |
| 批次明细弹窗(batchId+town) |
~30–40s |
<200ms |
需 DMS 按批翻页拉全量再内存分页 |
| 人员历月(personnelId) |
~110–160ms |
<10ms |
最优路径 |
| 工作台图表 |
~20s(含村级 dash 预热) |
<300ms |
改走 batch_stat,不再扫明细 |
| 按镇查当月明细(无 batchId) |
~10s |
<100ms |
镇+月 scope |
6. 内存与运维
| 项 |
建议 |
| JVM 堆 |
当前 1 个月明细规模:-Xmx1~1.5g 足够;不再常驻 3 万+ PaymentDetailVO |
| 10 年预估 |
若每月 ~1 万条、仅热缓存 32 个 scope,内存与改造前全表热缓存相比可控 |
| 索引 |
PG 已建 idx_ffmx_pay_month、idx_ffmx_batch_id、idx_ffmx_user_id、idx_ffmx_month_town;单页有提升,全量翻页仍受 DMS 分页次数限制 |
| 写后一致性 |
写操作仍调 invalidatePaymentCache() 清 scope;15min TTL 内极端情况下可能读到旧 scope(可后续改按 batchId 精准失效) |
7. 风险与后续建议
- 按批首开慢(~36s):根因是 DMS 按
c_batch_id 翻页 9,881 条;若前端常开批次明细,可考虑:
- 后端
pagePayment 在仅有 batchId 时改为 DMS 端分页(不先 selectContentAllWithSearch);
- 或批次 summary 单独缓存合计字段,明细弹窗纯分页不拉全量。
- workbench 首开 ~20s:村级 dash 预热 + 当月/上月
listPaymentsByMonthScope;可进一步减少村级并发预热范围。
- E2E 脚本过期:建议更新
e2e-api-flow-test.js / e2e-biz-gates-full.js 为「人员资格审批 + 全区月批」新流程。
- 写路径精准失效:高并发写场景可将
invalidatePaymentCache() 替换为 invalidatePaymentScopeForBatch(batchId),减少无关 scope 被清。
8. 验证命令速查
# 编译
mvn -q compile -DskipTests
# 启动
mvn -DskipTests spring-boot:run
# 热路径(warm < 300ms)
node scripts/perf-hot-paths.js
# 全量 API 冒烟
node scripts/e2e-api-flow-test.js
# DMS 直连基准
node scripts/bench-dms-payment-query-v2.js
9. 签收清单
报告人:Cursor Agent(自动执行改动清单 + 验证)
关联脚本:scripts/perf-hot-paths.js、scripts/e2e-api-flow-test.js、scripts/bench-dms-payment-query-v2.js