From 797d76ac40b997deca78b74aa1264e0827ae3f26 Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期二, 22 九月 2026 13:07:35 +0800
Subject: [PATCH] feat(报表生成): 导出缓存守卫改用数据指纹(无关导入不再失效 / 换母版必定失效)+ 失效提示

---
 traffic-audit-web/src/views/ReportExport.vue                                                          |    1 
 docs/功能测试报告/功能测试报告_导出缓存改用数据指纹_2026-09-22.md                                                           |   61 ++++++++++++
 traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportTaskService.java |  177 ++++++++++++++++++++++++++++-------
 docs/交接文档_2026-09-21.md                                                                               |    3 
 docs/工作日结/工作日结_2026-09-22.md                                                                          |   10 ++
 5 files changed, 215 insertions(+), 37 deletions(-)

diff --git "a/docs/\344\272\244\346\216\245\346\226\207\346\241\243_2026-09-21.md" "b/docs/\344\272\244\346\216\245\346\226\207\346\241\243_2026-09-21.md"
index 4dedf8b..90d6a56 100644
--- "a/docs/\344\272\244\346\216\245\346\226\207\346\241\243_2026-09-21.md"
+++ "b/docs/\344\272\244\346\216\245\346\226\207\346\241\243_2026-09-21.md"
@@ -217,7 +217,7 @@
 | P2-3 | `applySummaryReferenceLayout()` 鎸�**鍒楀簭鍙�**濂楀垪瀹�/闅愯棌 | 鍙傜収浠朵竴鍔犲噺鍒楀氨鏁翠綋閿欎綅锛屽簲鏀逛负鎸夎〃澶存枃瀛楀尮閰� | 鉂� |
 | P2-4 | 缂烘瘝鐗堟彁绀猴紙T08锛夋湭瀹炴祴 | 鏈夋祴璇曞壇鏈椂琛ユ祴 | 鉂� |
 | P2-5 | 閮ㄧ讲鍖呭唴鏄�**甯� AJ 鍒楃殑鏃ф瘝鐗�** | `deploy/traffic-audit-deploy-20260913/backend/docs/鐢熸垚姹囨�诲ぇ琛�/`銆乣_deploytest/`锛涚敤鎴� 09-20 鏄庣‘銆岄儴缃插寘鍏堜笉绠°�嶏紝鍙戝寘鍓嶅繀椤诲悓姝� | 鈴� |
-| P2-6 | ~~瀵煎嚭缂撳瓨鏄唴瀛樻�亊~ | **宸茶В鍐筹紙2026-09-22锛�**锛氱粨鏋滆惤鐩樺埌 `docs/鐢熸垚姹囨�诲ぇ琛�/_cache/`锛堟枃浠跺悕甯︽暟鎹増鏈� `MAX(import_batch.id)`锛夛紝鍐呭瓨 鈫� 钀界洏 鈫� 鐪熺畻閫愮骇鍛戒腑锛岄噸鍚悗绔悗浠嶅彲绉掑洖澶嶇敤锛涙湁鏂板鍏ュ嵆澶辨晥锛�7 澶╀笂闄愬苟鑷姩娓呯悊銆傞仐鐣欙細鏁版嵁鐗堟湰鏄叏灞�鐨勶紙涓庢眹鎬绘棤鍏崇殑瀵煎叆涔熶細璁╃紦瀛樺け鏁堬級锛屽闇�鎸夊鍏ョ被鍨嬬粏鍒嗗緟鎺掓湡 | 鉁咃紙鍙紭鍖栭」瑙佸彸锛墊
+| P2-6 | ~~瀵煎嚭缂撳瓨鏄唴瀛樻�亊~ | **宸茶В鍐筹紙2026-09-22锛�**锛氱粨鏋滆惤鐩樺埌 `docs/鐢熸垚姹囨�诲ぇ琛�/_cache/`锛堟枃浠跺悕甯︽暟鎹増鏈� `MAX(import_batch.id)`锛夛紝鍐呭瓨 鈫� 钀界洏 鈫� 鐪熺畻閫愮骇鍛戒腑锛岄噸鍚悗绔悗浠嶅彲绉掑洖澶嶇敤锛涙湁鏂板鍏ュ嵆澶辨晥锛�7 澶╀笂闄愬苟鑷姩娓呯悊銆傞仐鐣欓」宸查棴鐜紙2026-09-22 涓嬪崍锛夛細鐗堟湰鍙峰崌绾т负**鏁版嵁鎸囩汗**锛堜緷璧栨簮琛� `CHECKSUM TABLE` + 姣嶇増绛夋枃浠惰韩浠斤紝鎺掗櫎鎶曡祫/鑳借��/瀹℃牳/绯荤粺/瀵煎叆鏃ュ織锛夛紝鏃犲叧瀵煎叆涓嶅啀澶辨晥銆佹崲姣嶇増蹇呭畾澶辨晥锛涘け鏁堟椂杩涘害寮圭獥浼氳鏄庡師鍥� | 鉁� |
 | P2-7 | 甯傚窞鍗曡〃銆屽甯傚窞鍧椼�嶅厹搴曟彁绀� | 鏄惁鍦ㄨ瘑鍒埌涓�浠芥枃浠跺惈澶氫釜甯傚窞鍧楁椂寮圭獥鎻愮ず/璺宠繃锛屽緟纭锛堜笌 P0-1 鍚屼竴鏍瑰洜锛� | 鈴� |
 | P2-8 | 浠撳簱鏍圭洰褰� `_*.*` 涓存椂鏂囦欢澧炲 | `_chk_*`锛堟湰娆℃眹鎬诲ぇ琛ㄦ帓鏌ヤ骇鐢燂紝瑙佹眹鎬诲ぇ琛ㄤ氦鎺ョ 10 鑺傦級銆乣_hyb_*`銆乣_loo_*` 绛夛紝**缁忕敤鎴峰悓鎰忓悗鍐嶆竻** | 鈴� |
 | P2-9 | `2026骞�8鏈堥亾璺繍杈撻噺姹囨�昏〃_鐗堝紡鍘熷瀷.xlsx` 鍘荤暀 | 杩愯鏃舵湭琚紩鐢� | 鈴� |
@@ -321,4 +321,5 @@
 | --- | --- |
 | 2026-09-21 | 棣栨鎴愭枃锛氭暣鍚堟眹鎬诲ぇ琛ㄤ氦鎺ワ紙`docs/闂姹囨��/姹囨�诲ぇ琛ㄩ棶棰樹氦鎺2026-09-21.md`锛変笌鎶曡祫/璐ц繍/瀹㈣繍鍚勭嚎閬楃暀 |
 | 2026-09-21锛堜笅鍗堬級 | P0-1 闂幆锛堝崄鍫版簮鏂囦欢宸插垹娈� + 閲嶅楠岃瘉锛夛紱鏂板 P1-13锛堟姇璧勭郴缁熸柊妯℃澘鏀寔锛夛紱鏁版嵁蹇収琛� `investment_system` 琛� |
+| 2026-09-22锛堜笅鍗堬級 | 瀵煎嚭缂撳瓨锛氱粨鏋滆惤鐩� + 鏁版嵁鎸囩汗瀹堝崼 + 澶辨晥鎻愮ず锛圥2-6 闂幆锛� |
 | 2026-09-21锛堜笅鍗堬級 | P1-1 闂幆锛氱敤鎴烽噰鐢ㄦ柟妗� A锛屼腑鍙e緞鎺掑悕鐨� 2025 绱鍩烘暟鏀硅姣嶇増銆婁腑鍙e緞鏄庣粏銆嬮潤鎬佸垪锛涢粍鍐堢疮璁″悓姣旂敱 7.74% 淇涓� 7.641733%锛屽弻椤典竴鑷存�у洖褰掗�氳繃 |
diff --git "a/docs/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212_\345\257\274\345\207\272\347\274\223\345\255\230\346\224\271\347\224\250\346\225\260\346\215\256\346\214\207\347\272\271_2026-09-22.md" "b/docs/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212_\345\257\274\345\207\272\347\274\223\345\255\230\346\224\271\347\224\250\346\225\260\346\215\256\346\214\207\347\272\271_2026-09-22.md"
new file mode 100644
index 0000000..6cc4bc0
--- /dev/null
+++ "b/docs/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212_\345\257\274\345\207\272\347\274\223\345\255\230\346\224\271\347\224\250\346\225\260\346\215\256\346\214\207\347\272\271_2026-09-22.md"
@@ -0,0 +1,61 @@
+# 鍔熻兘娴嬭瘯鎶ュ憡_瀵煎嚭缂撳瓨鏀圭敤鏁版嵁鎸囩汗_2026-09-22
+
+## 涓�銆佸姛鑳借鏄�
+
+鐢ㄦ埛璇夋眰锛氫笅杞借繃涓�娆°�佹湡闂存病鏈夋柊瀵煎叆鏁版嵁灏卞簲鐩存帴娌跨敤涓婃缁撴灉锛堝凡瀹炵幇钀界洏缂撳瓨锛夛紱闅忓悗鐢ㄦ埛璁ゅ彲銆屾寜鎴戣鐨勫仛銆嶏紝瑕佹眰缁х画瑙e喅缂撳瓨瀹堝崼鐨勪袱涓棶棰橈細
+
+1. **璇け鏁�**锛氬師鏉ョ敤鍏ㄥ眬 `MAX(import_batch.id)` 褰撶増鏈彿锛屽鍏ユ姇璧�/鑳借�楄繖绫讳笌姹囨�诲ぇ琛ㄦ棤鍏崇殑鏁版嵁涔熶細璁╃紦瀛樺け鏁堬紝鐧界瓑鍗佸嚑绉掞紱
+2. **婕忓け鏁堬紙姝g‘鎬ф紡娲烇級**锛氱増鏈彿鍙弽鏄犳暟鎹簱瀵煎叆璁板綍锛�**涓嶅惈姣嶇増鏂囦欢**鈥斺�旀崲姣嶇増/鏀瑰弬鐓т欢鏃剁増鏈彿涓嶅彉锛屼細鎶婃棫缁撴灉褰撴柊琛ㄥ彂鍑恒��
+
+**鏀瑰姩**锛�
+
+| # | 鏀瑰姩 | 璇存槑 |
+| --- | --- | --- |
+| 1 | 鐢�**鏁版嵁鎸囩汗**鏇夸唬鍏ㄥ眬鐗堟湰 | 鎸囩汗 = 銆屼緷璧栨簮琛ㄧ殑鍐呭鏍¢獙鍜屻��+銆屾瘝鐗堢瓑鏂囦欢韬唤銆嶏紝SHA-256 鍙栧墠 8 瀛楄妭锛�16 浣嶅崄鍏繘鍒讹級锛屽啓杩涚紦瀛樻枃浠跺悕 |
+| 2 | 婧愯〃鑼冨洿锛�**鎺掗櫎娉�** | 搴撳唴鎵�鏈夊熀琛紝**鎺掗櫎**鎶曡祫(3)/鑳借��(2)/瀹℃牳(4)/绯荤粺(6)/`import_batch`(1) 鍏� 16 寮狅紱鍏朵綑涓�寰嬬撼鍏ワ紙瀹佸彲澶氱畻涓嶅彲鍑洪敊锛� |
+| 3 | 鐢� `CHECKSUM TABLE` 鍙栨牎楠屽拰 | 鑳芥崟鎹�**鍘熷湴 UPDATE**锛堣鏁般�乵ax(id) 閮戒笉鍙樼殑鎯呭喌锛夛紝姣� `count+max(id)` 鏇翠弗鏍� |
+| 4 | 鏂囦欢韬唤 | 姣嶇増鐩綍涓嬪叏閮ㄦ枃浠讹紙姣嶇増 / 鍙傜収浠� / `_澶囦唤_` 鍥炲~浠讹級+銆婄綉绾﹁溅璁㈠崟鍙婂叏鐪佹�婚噺.xlsx銆嬶紝鍙栥�屽悕绉�+澶у皬+淇敼鏃堕棿銆� |
+| 5 | 鏂板**澶辨晥鎻愮ず**锛堢 3 姝ョ殑鎻愮ず鐗堬級 | 娌″懡涓笖瀛樺湪鏃х粨鏋滄椂锛岃繘搴﹀脊绐楁樉绀恒�屼笂娆$敓鎴愮殑缁撴灉宸插洜鏁版嵁鎴栨瘝鐗堝彉鍖栧け鏁堬紝鏈鎸夋渶鏂版暟鎹噸绠椼�傘�� |
+
+> 鎻愮ず鏀惧湪**瀵煎嚭鏃剁殑杩涘害寮圭獥**锛堣�屼笉鏄鍏ョ粨鏋滃脊绐楋級锛氣憼 姝eソ鍑虹幇鍦ㄧ敤鎴风瓑寰呯殑鏃跺�欙紱鈶� 鍏嶅幓銆屾瘡娆″鍏ラ兘璺戜竴閬嶅叏琛ㄦ牎楠屽拰銆嶇殑寮�閿�銆�
+
+## 浜屻�佹祴璇曠幆澧�
+
+- 鍚庣 Spring Boot 8090锛堥噸鍚姞杞芥柊浠g爜锛夛紱鍓嶇 8080锛汳ySQL 3308 / `traffic_audit`
+- 鎶ヨ〃鏈� 2026-08锛堟湀鎶ワ級锛涚紦瀛樼洰褰� `docs/鐢熸垚姹囨�诲ぇ琛�/_cache/`
+- 楠岃瘉鏈熼棿鍙︿竴涓細璇濋噸鍚繃鍚庣涓ゆ锛屼釜鍒楠や腑鏂悗閲嶈窇锛堜笉褰卞搷缁撹锛�
+
+## 涓夈�佺敤渚嬭〃
+
+| # | 鐢ㄤ緥 | 鎿嶄綔 | 棰勬湡 | 瀹為檯 | 缁撹 |
+| --- | --- | --- | --- | --- | --- |
+| T01 | 棣栨鐢熸垚锛堟柊鎸囩汗锛� | 鎻愪氦瀵煎嚭 | 鐪熺畻骞惰惤鐩橈紝鏃х増鏈枃浠惰娓呯悊 | `DONE` 21 s锛涚敓鎴� `鈥v227060ce99767ced.xlsx`锛屽師 `鈥v541.xlsx` 宸插垹 | 閫氳繃 |
+| T02 | 鍐嶆鎻愪氦 | 鍚屼笂鎶� | 鍐呭瓨鍛戒腑 | **241 ms**锛宍fromCache=true` | 閫氳繃 |
+| T03 | **鏃犲叧瀵煎叆涓嶅奖鍝嶇紦瀛橈紙鏍稿績锛�** | 寰� `investment_project`銆乣import_batch` 鍚勬彃 1 琛屽悗鎻愪氦 | 浠嶅懡涓� | `fromCache=true`锛堟敼閫犲墠蹇呯劧澶辨晥锛� | 閫氳繃 |
+| T04 | 鍒犻櫎涓婅堪涓よ鍚庢彁浜� | 鍚屼笂 | 浠嶅懡涓� | `fromCache=true` | 閫氳繃 |
+| T05 | **鐩稿叧婧愯〃鍘熷湴 UPDATE** | `wyc_order_monthly.pin_k +0.001`锛堣鏁�/`max(id)` 涓嶅彉锛夊悗鎻愪氦 | 蹇呴』澶辨晥 | `fromCache=false`銆乣RUNNING`锛涚紦瀛樻枃浠跺悕鎹㈡垚鏂版寚绾� `鈥v68b6bc0c鈥 | 閫氳繃 |
+| T06 | 杩樺師璇ュ�煎悗鎻愪氦 | 鍚屼笂 | 浠嶄笉鍛戒腑锛堢紦瀛樻槸鏀瑰悗鎸囩汗锛� | `fromCache=false` 鈫� 閲嶇畻锛涙寚绾瑰洖鍒� `227060ce99767ced` | 閫氳繃 |
+| T07 | **鎹㈡瘝鐗堝繀椤诲け鏁堬紙鍘熸紡娲烇級** | 鏀规瘝鐗堟枃浠朵慨鏀规椂闂达紙鍐呭涓嶅彉锛夊悗鎻愪氦 | 蹇呴』澶辨晥 | `fromCache=false`銆乣RUNNING`锛涙寚绾瑰彉 `鈥v7d65cbb6鈥 | 閫氳繃 |
+| T08 | 鎭㈠姣嶇増鏃堕棿鍚庢彁浜� | 鍚屼笂 | 澶辨晥鈫掗噸绠椻啋鍐嶆彁浜ゅ懡涓� | 閲嶇畻鍚� `fromCache=true` | 閫氳繃 |
+| T09 | 澶辨晥鎻愮ず锛堟帴鍙o級 | 瀛樺湪鏃х粨鏋滀笖鎸囩汗涓嶇鏃舵彁浜� | `staleCache=true` | `status=RUNNING fromCache=false staleCache=true` | 閫氳繃 |
+| T10 | 鍛戒腑缂撳瓨鏃犳彁绀猴紙鍥炲綊淇锛� | 鍛戒腑鏃舵彁浜� | `staleCache=false` | 淇鍓嶅懡涓粛甯� `staleCache=true` 鈫� 宸蹭慨涓哄懡涓嵆娓呴浂 | 閫氳繃 |
+| T11 | **娴忚鍣ㄥ疄娴嬫彁绀鸿** | admin/123456 鈫� 鎶ヨ〃鐢熸垚 鈫� 姹囨�诲ぇ琛� 鈫� 2026-08 鈫� 瀵煎嚭锛堝厛鏀规瘝鐗� mtime 鍒堕�犲け鏁堬級 | 寮圭獥鍑虹幇澶辨晥鎻愮ず | 寮圭獥鏄剧ず銆屼笂娆$敓鎴愮殑缁撴灉宸插洜鏁版嵁鎴栨瘝鐗堝彉鍖栧け鏁堬紝鏈鎸夋渶鏂版暟鎹噸绠椼�傘�嶏紱鐢熸垚 13.2 s銆佽嚜鍔ㄤ笅杞� 1.15 MB銆佹寜閽�岄噸鏂扮敓鎴�/鍏抽棴銆嶃�佹帶鍒跺彴 0 鎶ラ敊 | 閫氳繃 |
+| T12 | 鏁版嵁杩樺師鏍稿 | 鏌ュ簱 | 鏃犳祴璇曟畫鐣� | `import_batch` 419銆乣investment_project` 152銆乣wyc_order_monthly.2025-12 pin_k=3492.63`銆佹棤銆宊鎸囩汗娴嬭瘯椤圭洰銆� | 閫氳繃 |
+
+## 鍥涖�佸彂鐜扮殑闂涓庡缃�
+
+| # | 闂 | 澶勭疆 |
+| --- | --- | --- |
+| P1 | 鍛戒腑缂撳瓨鏃舵畫鐣欎笂涓�娆$殑銆屽凡澶辨晥銆嶆彁绀烘爣璁� | 宸蹭慨锛氬懡涓垎鏀竻闆� `staleCache` |
+| P2 | 鏂囦欢韬唤鐢ㄦ绉掔骇 mtime锛氭妸姣嶇増 mtime銆屾仮澶嶆垚鍚屼竴绉掋�嶆椂锛屼簹绉掑樊寮備粛浼氫骇鐢熸柊鎸囩汗锛堥獙璇佷腑瀹為檯閬囧埌锛� | 灞�**棰勬湡琛屼负**锛堜繚瀹堜紭鍏堬細瀹佸彲澶氱畻涓�娆★級銆傝嫢灏嗘潵瑙夊緱澶晱鎰燂紝鍙敼涓恒�岀绾� mtime銆嶆垨瀵规瘝鐗堝仛鍐呭鍝堝笇 |
+| P3 | 娴嬭瘯鐢ㄧ殑涓存椂鏁版嵁锛�1 涓姇璧勯」鐩��1 鏉″鍏ヨ褰曘�乣wyc pin 卤0.001`锛� | 宸插叏閮ㄨ繕鍘�/鍒犻櫎锛孴12 宸叉牳瀵� |
+| P4 | 鏁版嵁鎸囩汗璁$畻鎴愭湰 | 绾� 11 寮犺〃 CHECKSUM + 13 涓枃浠� stat锛屽疄娴嬪彲涓烘绉掔骇锛�3 寮犺〃 48 ms锛夛紱浠呭湪鎻愪氦瀵煎嚭鏃剁畻涓�娆� |
+
+## 浜斻�佺粨璁�
+
+- 缂撳瓨瀹堝崼浠庛�屽叏灞�瀵煎叆鐗堟湰銆嶅崌绾т负銆�**鏁版嵁鎸囩汗**銆嶅悗锛�**鏃犲叧瀵煎叆涓嶅啀璁╃紦瀛樺け鏁�**锛圱03/T04锛夛紝**鎹㈡瘝鐗堛�佹敼婧愯〃锛堝惈鍘熷湴 UPDATE锛夊繀瀹氬け鏁�**锛圱05/T07锛夛紝涓や釜鏂瑰悜閮藉疄娴嬮�氳繃锛�
+- 鐢熸垚涓�娆$殑鎴愭湰鍙姳鍦ㄧ湡姝i渶瑕侀噸绠楃殑鏃跺�欙紱姝e父銆屼笅杞借繃鍐嶇湅涓�娆°�嶄粛鏄绉掔骇鍛戒腑锛�
+- 澶辨晥鏃朵細鍦ㄨ繘搴﹀脊绐楁槑纭鏄庡師鍥狅紝鐢ㄦ埛涓嶄細璇互涓虹紦瀛樺潖浜嗭紱
+- 娴嬭瘯鏁版嵁宸插叏閮ㄨ繕鍘燂紝搴撳唴鐘舵�佷笌楠岃瘉鍓嶄竴鑷淬��
+
+娴嬭瘯浜猴細Codex锛圓I锛夈��娴嬭瘯鏃ユ湡锛�2026-09-22
diff --git "a/docs/\345\267\245\344\275\234\346\227\245\347\273\223/\345\267\245\344\275\234\346\227\245\347\273\223_2026-09-22.md" "b/docs/\345\267\245\344\275\234\346\227\245\347\273\223/\345\267\245\344\275\234\346\227\245\347\273\223_2026-09-22.md"
index c6f7a44..e4ef071 100644
--- "a/docs/\345\267\245\344\275\234\346\227\245\347\273\223/\345\267\245\344\275\234\346\227\245\347\273\223_2026-09-22.md"
+++ "b/docs/\345\267\245\344\275\234\346\227\245\347\273\223/\345\267\245\344\275\234\346\227\245\347\273\223_2026-09-22.md"
@@ -80,6 +80,15 @@
 - **椤哄甫淇� bug**锛氳惤鐩樺懡涓椂 `elapsedMs` 涓鸿礋鏁帮紙浠诲姟璧风偣鐢ㄧ幇鍦ㄣ�佺粨鏉熸椂闂存槸鍘嗗彶鐢熸垚鏃堕棿锛夆啋 宸叉敼涓轰互鐢熸垚鏃堕棿浣滆捣鐐广��
 - **瀹炴祴**锛氶娆� 12.4 s 鍐欑紦瀛橈紱鍐嶆鎻愪氦 **164 ms** 鍛戒腑鍐呭瓨锛�**閲嶅惎鍚庣鍚庢彁浜� 1.19 s 鍛戒腑钀界洏**锛涗复鏃舵彃涓�鏉″鍏ヨ褰曞悗 `fromCache=false` 閲嶆柊鐢熸垚锛堥獙璇併�屾湁鏂板鍏ュ嵆澶辨晥銆嶏級锛涙祻瑙堝櫒鐐瑰鍑虹鍑恒�佹枃妗堜笌鎸夐挳姝g‘銆佹帶鍒跺彴 0 鎶ラ敊銆�
 
+### 6. 瀵煎嚭缂撳瓨瀹堝崼鍗囩骇涓恒�屾暟鎹寚绾广��+ 澶辨晥鎻愮ず锛堢敤鎴凤細鎸変綘璇寸殑鍋氾級
+
+- **鑳屾櫙**锛氫笂涓�鐗堢紦瀛樺畧鍗敤鍏ㄥ眬 `MAX(import_batch.id)`锛屸憼瀵煎叆鎶曡祫/鑳借�楃瓑鏃犲叧鏁版嵁浼氱櫧澶辨晥锛涒憽**涓嶅惈姣嶇増鏂囦欢**锛屾崲姣嶇増鏃朵細閿欒鍛戒腑鏃х粨鏋滐紙姝g‘鎬ф紡娲烇級銆�
+- **鏀瑰姩**锛坄ReportExportTaskService.java`锛夛細
+  1. 鎸囩汗 =銆屼緷璧栨簮琛� `CHECKSUM TABLE` 鏍¢獙鍜岋紙鑳芥崟鎹夊師鍦� UPDATE锛夈��+銆屾瘝鐗堢洰褰曞叏閮ㄦ枃浠� + 缃戠害杞﹁鍗� xlsx 鐨勫悕绉�/澶у皬/淇敼鏃堕棿銆嶏紝SHA-256 鍓� 8 瀛楄妭鍐欒繘缂撳瓨鏂囦欢鍚嶏紱
+  2. 婧愯〃鑼冨洿鐢�**鎺掗櫎娉�**锛氭帓闄ゆ姇璧� 3 / 鑳借�� 2 / 瀹℃牳 4 / 绯荤粺 6 / `import_batch`锛屽叾浣欏叏绾冲叆锛堝畞鍙绠椾笉鍙嚭閿欙級锛�
+  3. 澶辨晥鎻愮ず锛堢 3 姝ユ彁绀虹増锛夛細娌″懡涓笖鏈夋棫缁撴灉鏃讹紝杩涘害寮圭獥鏄剧ず銆屼笂娆$敓鎴愮殑缁撴灉宸插洜鏁版嵁鎴栨瘝鐗堝彉鍖栧け鏁堬紝鏈鎸夋渶鏂版暟鎹噸绠椼�傘��
+- **瀹炴祴**锛氭棤鍏冲鍏ワ紙鎶曡祫椤圭洰 + 瀵煎叆璁板綍鍚勬彃涓�琛岋級**浠嶅懡涓�**缂撳瓨锛涚浉鍏虫簮琛ㄥ師鍦� UPDATE銆佹敼姣嶇増 mtime **蹇呭畾澶辨晥**骞堕噸绠楋紱鍛戒腑缂撳瓨鏃朵笉鍐嶆畫鐣欐彁绀猴紱娴忚鍣ㄥ疄娴嬫彁绀鸿鏄剧ず姝e父銆佹帶鍒跺彴 0 鎶ラ敊锛涙祴璇曟暟鎹凡鍏ㄩ儴杩樺師锛�419 鎵规 / 152 椤圭洰锛夈��
+
 ## 浜屻�佹祴璇曠粨鏋滄憳瑕�
 
 - 鎶ュ憡锛歚docs/鍔熻兘娴嬭瘯鎶ュ憡/鍔熻兘娴嬭瘯鎶ュ憡_鏁版嵁鏌ョ湅琛ラ綈缃戠害杞︿笌涓綋瀹㈣繍绫诲瀷_2026-09-22.md`
@@ -89,6 +98,7 @@
 - 鎶ュ憡锛歚docs/鍔熻兘娴嬭瘯鎶ュ憡/鍔熻兘娴嬭瘯鎶ュ憡_鏁版嵁鏌ョ湅鎸夊競宸炴眹鎬讳笌瀵煎嚭_2026-09-22.md`锛堟帴鍙� 10 鏉� + 鐪熸満 4 鏉″叏閫氳繃锛�
 - 鎶ュ憡锛歚docs/鍔熻兘娴嬭瘯鎶ュ憡/鍔熻兘娴嬭瘯鎶ュ憡_姹囨�诲ぇ琛ㄨ繘搴﹀脊绐椾紭鍖朹2026-09-22.md`锛�6 鏉$敤渚嬮�氳繃锛氱敓鎴愪腑鐘舵�� / 瀹屾垚鎻愮ず / 鍐嶆涓嬭浇璧扮紦瀛� / 鍏抽棴 / 鏃犳帶鍒跺彴鎶ラ敊 / 鍏跺畠瀵煎嚭鏈彈褰卞搷锛�
 - 鎶ュ憡锛歚docs/鍔熻兘娴嬭瘯鎶ュ憡/鍔熻兘娴嬭瘯鎶ュ憡_姹囨�诲ぇ琛ㄥ鍑虹粨鏋滆惤鐩樼紦瀛樺鐢╛2026-09-22.md`锛�10 鏉$敤渚嬮�氳繃锛氶娆$敓鎴愬啓缂撳瓨 / 鍐呭瓨鍛戒腑 164ms / **閲嶅惎鍚庤惤鐩樺懡涓� 1.19s** / 鏈夋柊瀵煎叆鍗冲け鏁� / 鍚� key 鍙暀鏈�鏂扮増鏈� / 鏃堕暱鏄剧ず淇 / 娴忚鍣ㄧ鍒扮锛�
+- 鎶ュ憡锛歚docs/鍔熻兘娴嬭瘯鎶ュ憡/鍔熻兘娴嬭瘯鎶ュ憡_瀵煎嚭缂撳瓨鏀圭敤鏁版嵁鎸囩汗_2026-09-22.md`锛�12 鏉$敤渚嬮�氳繃锛氭棤鍏冲鍏ヤ粛鍛戒腑 / 鐩稿叧婧愯〃鍘熷湴 UPDATE 蹇呭け鏁� / **鎹㈡瘝鐗堝繀澶辨晥** / 澶辨晥鎻愮ず鏄剧ず / 鍛戒腑鏃犳彁绀� / 鏁版嵁杩樺師鏍稿锛�
 - 鐜锛氬悗绔� `mvn spring-boot:run`锛�8090锛宍Started TrafficAuditApplication`锛夛紱鍓嶇 `npm run serve`锛�8080锛宍Compiled successfully in 4769ms`锛夛紱MySQL `localhost:3308/traffic_audit`锛涚櫥褰� admin/123456 鍙� JWT銆�
 - 缁撴灉锛�3 涓柊绫诲瀷姝e悜锛堝惈鏈熼棿/鍏抽敭瀛楃瓫閫夛級銆佽礋鍚戯紙鏈煡绫诲瀷銆佹棤鏁版嵁鏈熴�佹棤 token锛夊叏閮ㄩ�氳繃锛涘叆搴撴娊鏌ユ潯鏁颁笌搴撳唴涓�鑷达紙20 / 20 / 139锛夛紱鍘熸湁 15 绫诲洖褰掓甯搞��
 
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportTaskService.java b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportTaskService.java
index 6c430eb..b3a9ff5 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportTaskService.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportTaskService.java
@@ -1,19 +1,27 @@
 package com.trafficaudit.reportexport.service;
 
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.trafficaudit.dataimport.entity.ImportBatch;
-import com.trafficaudit.dataimport.mapper.ImportBatchMapper;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.PreDestroy;
 import javax.annotation.Resource;
+import javax.sql.DataSource;
 import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
 import java.util.Date;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
@@ -29,9 +37,11 @@
  * 2) 鐢熸垚杩囩▼涓嶅崰鐢� HTTP 璇锋眰绾跨▼锛岄伩鍏嶅浜哄悓鏃剁偣鎶婂鍣ㄦ嫋姝伙紙宸ヤ綔绾跨▼鍥哄畾 1 涓紝澶氫綑璇锋眰鏄剧ず銆屾帓闃熶腑銆嶏級锛�
  * 3) 鍚屼竴鎶ヨ〃鏈熺殑缁撴灉鍙鐢紙**钀界洏淇濆瓨锛岃繘绋嬮噸鍚悗渚濈劧鏈夋晥**锛夛紝閬垮厤姣忔閲嶇畻鏁存湰绾� 4 涓囨牸鍏紡銆�
  *
- * 缂撳瓨姝g‘鎬у畧鍗細鍙湁褰撱�屾湡闂存病鏈変换浣曟柊鐨勬暟鎹鍏ワ紙import_batch 鏈�澶� id 鏈彉锛夈�嶄笖銆岀粨鏋滀笉瓒呰繃 7 澶┿�嶆椂鎵嶅鐢紱
- * 瀵煎叆鍗冲け鏁堬紝閬垮厤鎶婅繃鏈熸姤琛ㄧ粰鐢ㄦ埛銆傚墠绔粛鎻愪緵銆岄噸鏂扮敓鎴愩�嶅己鍒堕噸绠楋紙璺宠繃缂撳瓨锛夈��
- * 钀界洏浣嶇疆锛歿summary.template-dir}/_cache/<key>_v<鏁版嵁鐗堟湰>.xlsx
+ * 缂撳瓨姝g‘鎬у畧鍗紳**鏁版嵁鎸囩汗**锛氭寚绾圭敱銆屾眹鎬诲ぇ琛ㄤ緷璧栫殑婧愯〃鍐呭鏍¢獙鍜屻��+銆屾瘝鐗堢瓑鏂囦欢韬唤锛堝ぇ灏�/淇敼鏃堕棿锛夈�嶇畻鍑猴紝
+ * 鍙湁鎸囩汗涓�鑷达紙涓旂粨鏋滀笉瓒呰繃 7 澶╋級鎵嶅鐢ㄣ�傚洜姝わ細
+ *   - 鎶曡祫/鑳借��/瀹℃牳绛�**涓庢眹鎬诲ぇ琛ㄦ棤鍏�**鐨勫鍏ヤ笉鍐嶈缂撳瓨澶辨晥锛�
+ *   - 鎹㈡瘝鐗堛�佹敼鍙傜収浠躲�佸姩鐩稿叧婧愯〃锛堝惈鍘熷湴 UPDATE锛夐兘浼氳缂撳瓨澶辨晥锛屼笉浼氭妸鏃ц〃褰撴柊琛ㄥ彂鍑恒��
+ * 鍓嶇浠嶆彁渚涖�岄噸鏂扮敓鎴愩�嶅己鍒堕噸绠楋紙璺宠繃缂撳瓨锛夈�傝惤鐩樹綅缃細{summary.template-dir}/_cache/<key>_v<鎸囩汗>.xlsx
  */
 @Slf4j
 @Service
@@ -50,11 +60,24 @@
     @Resource
     private ReportExportService reportService;
     @Resource
-    private ImportBatchMapper importBatchMapper;
+    private DataSource dataSource;
 
     /** 姹囨�诲伐浣滅翱鐩綍锛堜笌 ReportExportService 鍚岄厤缃級锛岃惤鐩樼紦瀛樻斁鍦ㄥ叾 _cache 瀛愮洰褰� */
     @org.springframework.beans.factory.annotation.Value("${summary.template-dir:docs/鐢熸垚姹囨�诲ぇ琛▆")
     private String summaryTemplateDir;
+
+    /** 鍩庡競瀹㈣繍鐩綍锛氱綉绾﹁溅璁㈠崟鍙婂叏鐪佹�婚噺.xlsx锛堝簱閲岀己璇ユ湀鏃剁殑鍏滃簳杈撳叆锛� */
+    @org.springframework.beans.factory.annotation.Value("${city-passenger.template-dir:docs/鍩庡競瀹㈣繍}")
+    private String cityPassengerTemplateDir;
+
+    /** 璁$畻鎸囩汗鏃�**鎺掗櫎**鐨勮〃锛氭姇璧勭嚎銆佽兘鑰楃嚎銆佸鏍哥嚎銆佺郴缁熻〃銆佸鍏ユ棩蹇椼��
+     *  杩欎簺琛ㄥ彉鍖栦笉褰卞搷姹囨�诲ぇ琛ㄥ唴瀹癸紝娌″繀瑕佽缂撳瓨澶辨晥锛涘叾浣欒〃涓�寰嬬撼鍏ワ紙瀹佸彲澶氱畻涓嶅彲鍑洪敊锛夈�� */
+    private static final Set<String> FINGERPRINT_EXCLUDE = new HashSet<>(Arrays.asList(
+        "investment_project", "investment_monthly", "investment_system",
+        "h204_vehicle_quarterly", "h204_auth_vehicle",
+        "audit_result", "audit_run", "audit_rule", "audit_explanation",
+        "sys_user", "sys_role", "sys_user_role", "sys_dept", "sys_operation_log",
+        "llm_desensitize_map", "import_batch"));
 
     /** 钀界洏缂撳瓨鐩綍锛涘畾浣嶇瓥鐣ヤ笌妯℃澘涓�鑷达紙閰嶇疆鐩綍 / user.dir 鐩稿 / 涓婄骇鐩綍锛� */
     private File cacheDir() {
@@ -95,8 +118,8 @@
         private final String mode;
         private final Integer toMonth;
         private volatile long createdAt = System.currentTimeMillis();
-        /** 鐢熸垚鏃剁殑鏁版嵁鐗堟湰锛坕mport_batch 鏈�澶� id锛夛紝鐢ㄤ簬鍒ゆ柇缂撳瓨鏄惁杩囨湡 */
-        private final long importVersion;
+        /** 鐢熸垚鏃剁殑鏁版嵁鎸囩汗锛堟簮琛ㄦ牎楠屽拰 + 姣嶇増鏂囦欢韬唤锛夛紝鐢ㄤ簬鍒ゆ柇缂撳瓨鏄惁杩囨湡 */
+        private final String fingerprint;
         private volatile String status = "PENDING"; // PENDING/RUNNING/DONE/FAILED
         private volatile int percent = 0;
         private volatile String step = "鎺掗槦涓�";
@@ -106,14 +129,16 @@
         private volatile File cacheFile;
         private volatile String fileName;
         private volatile boolean fromCache;
+        /** 鏈銆屾病鍛戒腑缂撳瓨銆嶆槸鍥犱负瀛樺湪鏃х粨鏋滀絾鏁版嵁/姣嶇増宸插彉锛堢敤浜庣粰鐢ㄦ埛涓�鍙ヨВ閲婏級 */
+        private volatile boolean staleCache;
         private volatile long finishedAt;
 
-        Task(String type, String period, String mode, Integer toMonth, long importVersion) {
+        Task(String type, String period, String mode, Integer toMonth, String fingerprint) {
             this.type = type;
             this.period = period;
             this.mode = mode;
             this.toMonth = toMonth;
-            this.importVersion = importVersion;
+            this.fingerprint = fingerprint;
         }
 
         void progress(int pct, String stepText) {
@@ -159,6 +184,7 @@
             m.put("percent", "DONE".equals(status) ? 100 : percent);
             m.put("step", step);
             m.put("fromCache", fromCache);
+            m.put("staleCache", staleCache);
             m.put("fileName", fileName);
             m.put("error", error);
             long end = finishedAt > 0 ? finishedAt : System.currentTimeMillis();
@@ -170,20 +196,22 @@
 
     /**
      * 鎻愪氦瀵煎嚭浠诲姟銆俧orceRefresh=true 鏃跺拷鐣ョ紦瀛橀噸鏂扮敓鎴愶紱
-     * 鍚﹀垯鍦ㄣ�屽悓涓�鎶ヨ〃鏈熴��10 鍒嗛挓鍐呫�佹湡闂存棤鏂板鍏ャ�嶇殑鍓嶆彁涓嬬洿鎺ュ鐢ㄤ笂娆$粨鏋溿��
+     * 鍚﹀垯鍦ㄣ�屽悓涓�鎶ヨ〃鏈� + 鏁版嵁鎸囩汗涓�鑷� + 7 澶╁唴銆嶇殑鍓嶆彁涓嬬洿鎺ュ鐢ㄤ笂娆$粨鏋溿��
      * 鍚屼竴鎶ヨ〃鏈熷凡鏈変换鍔″湪璺戞椂鐩存帴杩斿洖璇ヤ换鍔★紝閬垮厤閲嶅鎺掗槦銆�
      */
     public Task submit(String type, String period, String mode, Integer toMonth, boolean forceRefresh) {
         cleanupExpired();
         String key = cacheKey(type, period, mode, toMonth);
+        String fingerprint = dataFingerprint();
         if (!forceRefresh) {
             Task hit = cache.get(key);
-            if (cacheUsable(hit)) {
+            if (cacheUsable(hit, fingerprint)) {
                 hit.fromCache = true;
+                hit.staleCache = false; // 鍛戒腑缂撳瓨鏃朵笉鑳芥畫鐣欎笂涓�娆°�岀粨鏋滃凡澶辨晥銆嶇殑鎻愮ず
                 log.info("鎶ヨ〃瀵煎嚭浠诲姟鍛戒腑鍐呭瓨缂撳瓨锛歿} {}锛堢敓鎴愪簬 {}锛�", type, period, new Date(hit.finishedAt));
                 return hit;
             }
-            Task disk = loadFromDisk(key, type, period, mode, toMonth);
+            Task disk = loadFromDisk(key, type, period, mode, toMonth, fingerprint);
             if (disk != null) {
                 tasks.put(disk.id, disk);
                 cache.put(key, disk);
@@ -197,7 +225,9 @@
             log.info("鎶ヨ〃瀵煎嚭浠诲姟宸插湪鎵ц锛屽鐢ㄨ繘琛屼腑鐨勪换鍔★細{} {}锛坽}锛�", type, period, running.id);
             return running;
         }
-        Task task = new Task(type, period, mode, toMonth, importVersion());
+        Task task = new Task(type, period, mode, toMonth, fingerprint);
+        // 鏈夋棫缁撴灉浣嗘病鍛戒腑锛堟暟鎹垨姣嶇増鍙樹簡锛夆啋 鍓嶇鎻愮ず涓�鍙ワ紝閬垮厤鐢ㄦ埛浠ヤ负"缂撳瓨鍧忎簡"
+        task.staleCache = !forceRefresh && hasStaleCache(type, period, mode, toMonth, fingerprint);
         tasks.put(task.id, task);
         pool.submit(() -> run(task));
         return task;
@@ -240,23 +270,99 @@
         return null;
     }
 
-    private boolean cacheUsable(Task t) {
-        if (t == null || !"DONE".equals(t.status) || t.data == null) return false;
+    private boolean cacheUsable(Task t, String fingerprint) {
+        if (t == null || !"DONE".equals(t.status)) return false;
+        if (t.data == null && (t.cacheFile == null || !t.cacheFile.isFile())) return false;
         if (System.currentTimeMillis() - t.finishedAt > CACHE_MAX_AGE_MS) return false;
-        long now = importVersion();
-        if (now < 0 || t.importVersion < 0) return false; // 鍙栦笉鍒版暟鎹増鏈椂涓嶅啋椋庨櫓澶嶇敤
-        return now == t.importVersion;
+        if (fingerprint == null || t.fingerprint == null) return false; // 绠椾笉鍑烘寚绾规椂涓嶅啋椋庨櫓澶嶇敤
+        return fingerprint.equals(t.fingerprint);
     }
 
-    /** 鏁版嵁鐗堟湰锛漣mport_batch 鏈�澶� id锛氫换浣曚竴娆″鍏ラ兘浼氳瀹冨彉鍖栵紝鍙栦笉鍒拌繑鍥� -1锛堟鏃剁鐢ㄧ紦瀛橈級 */
-    private long importVersion() {
+    /**
+     * 姹囨�诲ぇ琛ㄦ暟鎹寚绾癸紳銆屼緷璧栫殑婧愯〃鍐呭鏍¢獙鍜屻��+銆屾瘝鐗堢瓑鏂囦欢韬唤銆嶃��
+     * - 婧愯〃锛氬簱鍐呴櫎 FINGERPRINT_EXCLUDE锛堟姇璧�/鑳借��/瀹℃牳/绯荤粺/瀵煎叆鏃ュ織锛変互澶栫殑鍏ㄩ儴琛紝鐢� CHECKSUM TABLE 鍙栨牎楠屽拰锛�
+     *   鑳芥崟鎹夎鏁板彉鍖栥�佹柊澧炲垹闄ゃ��**鍘熷湴 UPDATE**锛�
+     * - 鏂囦欢锛氭瘝鐗堢洰褰曚笅鍏ㄩ儴鏂囦欢锛堟瘝鐗� / 鍙傜収浠� / _澶囦唤_ 鍥炲~浠讹級+銆婄綉绾﹁溅璁㈠崟鍙婂叏鐪佹�婚噺.xlsx銆嬶紝鍙栥�屽悕绉�+澶у皬+淇敼鏃堕棿銆嶃��
+     * 浠讳竴椤瑰彉鍖栧嵆鎸囩汗鍙樺寲 鈫� 缂撳瓨澶辨晥銆傝绠楀け璐ヨ繑鍥� null锛堟鏃剁鐢ㄧ紦瀛橈紝鍙噸绠椾笉澶嶇敤锛夈��
+     */
+    private String dataFingerprint() {
         try {
-            List<Object> v = importBatchMapper.selectObjs(new QueryWrapper<ImportBatch>().select("IFNULL(MAX(id),0)"));
-            if (v == null || v.isEmpty() || v.get(0) == null) return 0L;
-            return ((Number) v.get(0)).longValue();
+            List<String> parts = new ArrayList<>();
+            try (Connection conn = dataSource.getConnection()) {
+                List<String> tables = new ArrayList<>();
+                try (Statement st = conn.createStatement();
+                     ResultSet rs = st.executeQuery("SELECT table_name FROM information_schema.tables "
+                             + "WHERE table_schema = DATABASE() AND table_type = 'BASE TABLE' ORDER BY table_name")) {
+                    while (rs.next()) tables.add(rs.getString(1));
+                }
+                for (String t : tables) {
+                    if (FINGERPRINT_EXCLUDE.contains(t)) continue;
+                    try (Statement st = conn.createStatement();
+                         ResultSet rs = st.executeQuery("CHECKSUM TABLE `" + t + "`")) {
+                        if (rs.next()) parts.add("T:" + t + ":" + rs.getString(2));
+                    }
+                }
+            }
+            for (File f : listIdentityFiles(resolveBaseDir(summaryTemplateDir))) {
+                parts.add("F:" + f.getName() + ":" + f.length() + ":" + f.lastModified());
+            }
+            File wyc = new File(resolveBaseDir(cityPassengerTemplateDir), "缃戠害杞﹁鍗曞強鍏ㄧ渷鎬婚噺.xlsx");
+            if (wyc.isFile()) parts.add("F:" + wyc.getName() + ":" + wyc.length() + ":" + wyc.lastModified());
+            return sha256Hex(String.join("|", parts)).substring(0, 16);
         } catch (Exception e) {
-            log.warn("璇诲彇瀵煎叆鏁版嵁鐗堟湰澶辫触锛屾湰娆$鐢ㄥ鍑虹紦瀛橈細{}", e.getMessage());
-            return -1L;
+            log.warn("璁$畻鏁版嵁鎸囩汗澶辫触锛屾湰娆$鐢ㄥ鍑虹紦瀛橈細{}", e.getMessage());
+            return null;
+        }
+    }
+
+    /** 姣嶇増鐩綍绛夊熀纭�鐩綍瀹氫綅锛堜笌妯℃澘瑙f瀽鍚岀瓥鐣ワ細閰嶇疆鐩綍 / user.dir 鐩稿 / 涓婄骇鐩綍锛� */
+    private File resolveBaseDir(String dir) {
+        if (dir == null) return null;
+        String rel = dir;
+        while (rel.startsWith("./")) rel = rel.substring(2);
+        String[] roots = {rel, System.getProperty("user.dir") + "/" + rel, System.getProperty("user.dir") + "/../" + rel};
+        for (String root : roots) {
+            if (root == null || root.trim().isEmpty()) continue;
+            File f = new File(root);
+            if (f.isDirectory()) return f;
+        }
+        return null;
+    }
+
+    /** 鐩綍涓嬬殑鏂囦欢锛堟寜鍚嶅瓧鎺掑簭锛屾帓闄ゅ瓙鐩綍锛屼繚璇佹寚绾圭ǔ瀹氾級 */
+    private List<File> listIdentityFiles(File dir) {
+        List<File> out = new ArrayList<>();
+        if (dir == null) return out;
+        File[] fs = dir.listFiles(File::isFile);
+        if (fs == null) return out;
+        Arrays.sort(fs, Comparator.comparing(File::getName));
+        out.addAll(Arrays.asList(fs));
+        return out;
+    }
+
+    private String sha256Hex(String text) throws Exception {
+        MessageDigest md = MessageDigest.getInstance("SHA-256");
+        byte[] d = md.digest(text.getBytes(StandardCharsets.UTF_8));
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < 8; i++) sb.append(String.format("%02x", d[i])); // 鍙栧墠 8 瀛楄妭锛�16 浣嶅崄鍏繘鍒讹紝澶熺敤涓旀枃浠跺悕鐭�
+        return sb.toString();
+    }
+
+    /** 璇ユ姤琛ㄦ湡鏄惁瀛樺湪銆屼笌褰撳墠鎸囩汗涓嶄竴鑷淬�嶇殑鏃х粨鏋滐紙鐢ㄤ簬鎻愮ず銆屼笂娆$粨鏋滃凡澶辨晥锛屾鍦ㄦ寜鏈�鏂版暟鎹噸绠椼�嶏級 */
+    private boolean hasStaleCache(String type, String period, String mode, Integer toMonth, String fingerprint) {
+        try {
+            File dir = cacheDir();
+            if (dir == null) return false;
+            String prefix = cacheFilePrefix(type, period, mode, toMonth);
+            File[] fs = dir.listFiles((d, n) -> n.startsWith(prefix + "_v") && n.endsWith(".xlsx"));
+            if (fs == null || fs.length == 0) return false;
+            String current = prefix + "_v" + fingerprint + ".xlsx";
+            for (File f : fs) {
+                if (!f.getName().equals(current)) return true;
+            }
+            return false;
+        } catch (Exception e) {
+            return false;
         }
     }
 
@@ -290,10 +396,10 @@
         try {
             File dir = cacheDir();
             if (dir == null) return;
-            long version = t.importVersion;
-            if (version < 0) return;
+            String fingerprint = t.fingerprint;
+            if (fingerprint == null) return;
             String prefix = cacheFilePrefix(t.type, t.period, t.mode, t.toMonth);
-            File target = new File(dir, prefix + "_v" + version + ".xlsx");
+            File target = new File(dir, prefix + "_v" + fingerprint + ".xlsx");
             java.nio.file.Files.write(target.toPath(), data);
             File[] siblings = dir.listFiles((d, n) -> n.startsWith(prefix + "_v") && n.endsWith(".xlsx"));
             if (siblings != null) {
@@ -308,18 +414,17 @@
         }
     }
 
-    /** 浠庤惤鐩樼紦瀛橀噷鎵俱�屽悓涓�鎶ヨ〃鏈� + 鍚屼竴鏁版嵁鐗堟湰 + 鏈秴鏈熴�嶇殑缁撴灉锛涙病鏈夎繑鍥� null */
-    private Task loadFromDisk(String key, String type, String period, String mode, Integer toMonth) {
+    /** 浠庤惤鐩樼紦瀛橀噷鎵俱�屽悓涓�鎶ヨ〃鏈� + 鍚屼竴鏁版嵁鎸囩汗 + 鏈秴鏈熴�嶇殑缁撴灉锛涙病鏈夎繑鍥� null */
+    private Task loadFromDisk(String key, String type, String period, String mode, Integer toMonth, String fingerprint) {
         try {
+            if (fingerprint == null) return null; // 绠椾笉鍑烘寚绾规椂涓嶅啋椋庨櫓澶嶇敤
             File dir = cacheDir();
             if (dir == null) return null;
-            long version = importVersion();
-            if (version < 0) return null; // 鍙栦笉鍒版暟鎹増鏈椂涓嶅啋椋庨櫓澶嶇敤
-            File f = new File(dir, cacheFilePrefix(type, period, mode, toMonth) + "_v" + version + ".xlsx");
+            File f = new File(dir, cacheFilePrefix(type, period, mode, toMonth) + "_v" + fingerprint + ".xlsx");
             if (!f.isFile()) return null;
             long finished = f.lastModified();
             if (finished <= 0 || System.currentTimeMillis() - finished > CACHE_MAX_AGE_MS) return null;
-            Task t = new Task(type, period, mode, toMonth, version);
+            Task t = new Task(type, period, mode, toMonth, fingerprint);
             t.cacheFile = f;
             t.createdAt = finished; // 澶嶇敤鍘嗗彶缁撴灉锛氫互鐢熸垚鏃堕棿浣滀负璧风偣锛岄伩鍏� elapsedMs 鍑虹幇璐熸暟
             t.fileName = "鐢熸垚_閬撹矾杩愯緭閲忔眹鎬昏〃_" + period + ".xlsx";
diff --git a/traffic-audit-web/src/views/ReportExport.vue b/traffic-audit-web/src/views/ReportExport.vue
index 20c86d5..d9e76d3 100644
--- a/traffic-audit-web/src/views/ReportExport.vue
+++ b/traffic-audit-web/src/views/ReportExport.vue
@@ -170,6 +170,7 @@
             <p class="task-step">{{ taskInfo.step || '鎺掗槦涓�' }}</p>
             <p class="task-meta" v-if="taskInfo.period && !taskInfo.fromCache">鎶ヨ〃鏈� {{ taskInfo.period }}銆�宸茬敤 {{ ((taskInfo.elapsedMs || 0) / 1000).toFixed(1) }} 绉�</p>
             <p class="task-meta" v-else-if="taskInfo.period">鎶ヨ〃鏈� {{ taskInfo.period }}銆�涓婃鐢熸垚浜� {{ taskCacheTime }}</p>
+            <p class="task-meta" v-if="taskInfo.staleCache">涓婃鐢熸垚鐨勭粨鏋滃凡鍥犳暟鎹垨姣嶇増鍙樺寲澶辨晥锛屾湰娆℃寜鏈�鏂版暟鎹噸绠椼��</p>
             <p class="task-meta" v-if="taskInfo.fromCache">鏈鐩存帴娌跨敤 {{ taskCacheTime }} 鐢熸垚鐨勭粨鏋滐紙鏈熼棿娌℃湁鏂扮殑鏁版嵁瀵煎叆锛夈�傚闇�鎸夋渶鏂版暟鎹噸绠楋紝璇风偣銆岄噸鏂扮敓鎴愩�嶃��</p>
             <p class="task-done" v-if="taskDownloaded">
               宸茶嚜鍔ㄤ笅杞斤細<b>{{ taskFileName }}</b><span v-if="taskFileSize">锛坽{ taskFileSizeText }}锛�</span>锛屾枃浠跺湪娴忚鍣ㄩ粯璁や笅杞界洰褰曘��

--
Gitblit v1.9.1