From d1cfd849c6a61800dcd86ffb45f80882ae20b2e7 Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期一, 14 九月 2026 22:32:36 +0800
Subject: [PATCH] security: 接口鉴权 + 跨域收紧 + AI 数据脱敏 + 日志脱敏

---
 traffic-audit-server/src/main/resources/application.yml.example                                    |    6 
 traffic-audit-web/src/views/DataImport.vue                                                         |   15 ++
 docs/database.md                                                                                   |    4 
 traffic-audit-server/src/main/java/com/trafficaudit/security/filter/JwtAuthFilter.java             |   64 +++++++++
 traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/DesensitizeService.java |   23 +++
 docs/工作日结/工作日结_2026-09-14.md                                                                       |   47 ++++++
 traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/LLMService.java         |    8 +
 docs/功能测试报告/功能测试报告_接口鉴权与AI数据脱敏加固_2026-09-14.md                                                     |   60 ++++++++
 traffic-audit-server/src/main/java/com/trafficaudit/common/config/CorsConfig.java                  |   38 ++++-
 traffic-audit-web/src/views/AuditResult.vue                                                        |   22 ++
 traffic-audit-server/src/main/java/com/trafficaudit/security/config/SecurityConfig.java            |   22 ++
 traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/DbQueryExecutor.java    |   74 +++++++++-
 12 files changed, 359 insertions(+), 24 deletions(-)

diff --git a/docs/database.md b/docs/database.md
index b245236..9f98141 100644
--- a/docs/database.md
+++ b/docs/database.md
@@ -411,6 +411,10 @@
 
 ## 涓冦�丩LM鑴辨晱鏄犲皠
 
+> **瀹炵幇璇存槑锛�2026-09-14 鏇存柊锛�**锛氬綋鍓嶅疄闄呯敓鏁堢殑鑴辨晱鏄�**鍐呭瓨鎬�**鐨勨�斺�擿DesensitizeService` 鍦ㄦ暟鎹鍙戝墠鎶婁紒涓氬悕绉版浛鎹负 `Company1`銆乣Company2`鈥︼紝鍥炵瓟杩斿洖鍓嶅啀杩樺師锛屾槧灏勫彧瀛樺湪杩涚▼鍐呭瓨涓�佷笉钀藉簱銆�
+> 鍏蜂綋鍋氭硶锛欰I 鏌ュ簱锛坄query_database`锛夌粨鏋滃洖濉粰澶фā鍨嬪墠锛屼紒涓氬悕绉扮粺涓�鎹㈠埆鍚嶏紝鑷敱鏂囨湰閲岀殑浼佷笟鍚嶇О涓庢墜鏈哄彿涓�骞跺鐞嗭紱`contact_person`锛堝~琛ㄤ汉锛夈�乣contact_phone`锛堣仈绯荤數璇濓級銆乣unified_credit_code`锛堢粺涓�绀句細淇$敤浠g爜锛夈�乣unit_leader`銆乣stats_leader`銆乣report_unit`銆乣enterprise_code` 鐩存帴浠�"鍙煡鍒�"涓墧闄わ紝AI 鏃㈡煡涓嶅埌涔熶笉鑳戒綔涓鸿繃婊�/鎺掑簭鏉′欢銆�
+> 涓嬭〃 `llm_desensitize_map` 涓�**棰勭暀**鐨勮惤搴撶増鏄犲皠琛紝鐩墠浠g爜鏈紩鐢ㄣ�佽〃涓虹┖锛涜嫢鍚庣画闇�瑕佺暀瀛樿劚鏁忓璁$棔杩瑰啀鍚敤銆�
+
 ### llm_desensitize_map 鑴辨晱鏄犲皠琛�
 | 瀛楁 | 绫诲瀷 | 璇存槑 |
 |------|------|------|
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_\346\216\245\345\217\243\351\211\264\346\235\203\344\270\216AI\346\225\260\346\215\256\350\204\261\346\225\217\345\212\240\345\233\272_2026-09-14.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_\346\216\245\345\217\243\351\211\264\346\235\203\344\270\216AI\346\225\260\346\215\256\350\204\261\346\225\217\345\212\240\345\233\272_2026-09-14.md"
new file mode 100644
index 0000000..06cdb0d
--- /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_\346\216\245\345\217\243\351\211\264\346\235\203\344\270\216AI\346\225\260\346\215\256\350\204\261\346\225\217\345\212\240\345\233\272_2026-09-14.md"
@@ -0,0 +1,60 @@
+# 鍔熻兘娴嬭瘯鎶ュ憡_鎺ュ彛閴存潈涓嶢I鏁版嵁鑴辨晱鍔犲浐_2026-09-14
+
+> 妯″潡锛氱郴缁熷畨鍏紙鍚庣閴存潈 / 璺ㄥ煙 / AI 鏁版嵁鑴辨晱 / 鏃ュ織鑴辨晱锛夈��娴嬭瘯浜猴細Codex銆�鏃ユ湡锛�2026-09-14
+
+## 涓�銆佸姛鑳借鏄�
+
+閽堝鏈棩瀹夊叏鑷煡鍙戠幇鐨勫洓绫婚闄╁仛鍔犲浐锛�**杩欐槸瀹夊叏鏁存敼锛屼笉鏄柊鍔熻兘**锛夛細
+
+| 搴忓彿 | 鍘熼闄� | 鏈澶勭疆 |
+| --- | --- | --- |
+| 1 | 鍚庣 `/api/**` 鍏ㄩ儴 `permitAll`锛屼换浣曚汉鏃犻渶鐧诲綍鍗冲彲璋冪敤锛屽惈"鎸夋姤琛ㄦ湡娓呯┖鏁版嵁"绛夋晱鎰熸帴鍙� | 鏂板 `JwtAuthFilter`锛岄櫎鐧诲綍/楠岃瘉鐮佺櫧鍚嶅崟澶栵紝`/api/**` 蹇呴』鎼哄甫鏈夋晥 `Bearer` 浠ょ墝锛屽惁鍒欒繑鍥� JSON 401锛沗application.yml` 鐨� `jwt.secret` 鐢卞彲鐚滄祴鐨勫浐瀹氫覆鏀逛负 64 浣嶉殢鏈轰覆 |
+| 2 | CORS `allowedOriginPattern("*")` 涓� `allowCredentials(true)`锛屼换鎰忕綉绔欏彲鎼哄甫鍑瘉璋冪敤鏈満鎺ュ彛 | 鏀逛负鍙厑璁搁厤缃」 `app.cors.allowed-origins` 涓垪鍑虹殑鏉ユ簮锛堥粯璁� `http://localhost:8080,http://127.0.0.1:8080`锛夛紝骞跺叧闂� `allowCredentials` |
+| 3 | AI 鏌ュ簱鎺ュ彛鍙紶 `columns:"*"` 鏀惧紑鏁磋〃锛屼紒涓氬悕绉般�佺粺涓�绀句細淇$敤浠g爜銆佸崟浣嶈礋璐d汉銆佸~琛ㄤ汉銆佽仈绯荤數璇濇槑鏂囧彂寰� DeepSeek | 鈶� 鍘绘帀 `"*"` 鏀惧紑鏁磋〃鐨勫彛瀛愶紙鏀逛负鍙繑鍥為粯璁ゅ垪锛夛紱鈶� 灞忚斀 7 涓瓧娈碉紙`unified_credit_code`/`contact_person`/`contact_phone`/`unit_leader`/`stats_leader`/`report_unit`/`enterprise_code`锛夛紝鏃笉鍙煡涔熶笉鍙綔杩囨护/鎺掑簭鏉′欢锛屼笖涓嶅啀鍑虹幇鍦ㄧ粰妯″瀷鐨勮〃缁撴瀯鎻愮ず閲岋紱鈶� 澶栧彂鍓嶆妸浼佷笟鍚嶇О鎹㈡垚 `Company1鈥 鍒悕锛岃嚜鐢辨枃鏈腑鐨勪紒涓氬悕绉颁笌 11 浣嶆墜鏈哄彿涓�骞跺鐞嗭紝鍥炵瓟杩斿洖鍓嶇鍓嶈繕鍘� |
+| 4 | MyBatis-Plus 榛樿 `StdOutImpl` 鎶婃暣琛屾暟鎹紙鍚紒涓氬悕绉般�佽仈绯荤數璇濓級鎵撹繘 `backend.log` | 鏄惧紡鏀逛负 `Slf4jImpl`锛孲QL 鏃ュ織闄嶄负 DEBUG 绾у埆锛岄粯璁や笉杈撳嚭 |
+
+鏀瑰姩鏂囦欢锛�8 鏀� 1 鏂板锛夛細`security/filter/JwtAuthFilter.java`锛堟柊澧烇級銆乣security/config/SecurityConfig.java`銆乣common/config/CorsConfig.java`銆乣llmintegration/service/DbQueryExecutor.java`銆乣llmintegration/service/DesensitizeService.java`銆乣llmintegration/service/LLMService.java`銆乣traffic-audit-web/src/views/AuditResult.vue`銆乣traffic-audit-web/src/views/DataImport.vue`銆乣application.yml.example`銆乣docs/database.md`銆�
+
+**杩炲甫淇**锛氬墠绔� `AuditResult.vue`锛堝鏍哥粨鏋滃鍑猴級涓� `DataImport.vue`锛堜笅杞藉鍏ユā鏉匡級鍘熸湰鐢� `window.open('/api/...')` 鐩磋繛鎺ュ彛锛屾祻瑙堝櫒璺宠浆涓嶄細甯� `Authorization` 澶达紝寮�鍚壌鏉冨悗浼� 401锛涘凡鏀逛负璧� axios 瀹炰緥鍙� blob 鍐嶈Е鍙戜笅杞斤紙鎶ヨ〃鐢熸垚椤靛師鏈氨鏄繖绉嶅啓娉曪級銆�
+
+## 浜屻�佹祴璇曠幆澧�
+
+- 鍚庣 8090锛坄mvn -o -q compile` 閫氳繃锛孍XIT=0锛岄噸鍚悗灏辩华锛夈�佸墠绔� 8080锛坉ev 缂栬瘧鎴愬姛锛夈��
+- 鏁版嵁搴擄細鏈満 `traffic_audit`锛圡ySQL 3308锛夈��
+- 鎶撳寘鎵嬫锛氭妸 `deepseek.api-url` 涓存椂鎸囧悜鏈湴鍋� DeepSeek 鏈嶅姟锛坄127.0.0.1:18080`锛夛紝瀹屾暣鎹曡幏姣忎竴娆″彂寰�澶фā鍨嬬殑璇锋眰浣擄紝**娴嬭瘯鍚庡凡鎭㈠涓虹湡瀹炲湴鍧�骞堕噸鍚�**銆�
+
+## 涓夈�佺敤渚嬩笌缁撴灉
+
+| 缂栧彿 | 鐢ㄤ緥 | 杈撳叆 | 棰勬湡 | 瀹為檯 | 缁撹 |
+| --- | --- | --- | --- | --- | --- |
+| S1 | 鏈櫥褰曡闂笟鍔℃帴鍙� | 鏃犱护鐗� GET `/api/user/list`銆乣/api/data/list`銆乣/api/audit/export`銆乣/api/report/export/turnoverRank`銆乣/api/templates/...`銆乣/api/llm/chat` | 鍏ㄩ儴 401 | 6 缁勬帴鍙e叏閮ㄨ繑鍥� 401 JSON | 閫氳繃 |
+| S2 | 鐧诲綍鐧藉悕鍗曚笉鍙楀奖鍝� | 鏃犱护鐗� GET `/api/auth/captcha-config`銆丳OST `/api/auth/login` | 鍙闂� | 鐧诲綍杩斿洖 200锛屼护鐗� 156 浣� | 閫氳繃锛堟鍚戯級 |
+| S3 | 鎼哄甫浠ょ墝姝e父浣跨敤 | `admin/123456` 鍙栦护鐗屽悗璋� `/api/user/list`銆乣/api/audit/export`銆乣/api/report/export/turnoverRank` | 200 | 200 / 200锛�59965 瀛楄妭锛�/ 200锛�14589 瀛楄妭锛� | 閫氳繃 |
+| S4 | 浠ょ墝鏃犳晥鏃舵嫆缁� | 浼��/杩囨湡 `Bearer` | 401 | 401 | 閫氳繃锛堣礋鍚戯級 |
+| S5 | 璺ㄥ煙鐧藉悕鍗曠敓鏁� | 甯� `Origin: http://localhost:8080` 璇锋眰 | 杩斿洖璇ユ潵婧� | 200锛宍Access-Control-Allow-Origin: http://localhost:8080` | 閫氳繃 |
+| S6 | 璺ㄥ煙闈炵櫧鍚嶅崟琚嫤 | `Origin: http://evil.example.com`銆乣http://127.0.0.1:9999` | 鎷掔粷 | 鍧� 403锛屾湭杩斿洖鏀捐澶� | 閫氳繃锛堣礋鍚戯級 |
+| S7 | 棰勬璇锋眰 | 鏃犱护鐗� `OPTIONS` + `Origin: localhost:8080` | 200 涓斿甫鏀捐澶� | 200锛屽厑璁告潵婧愭纭� | 閫氳繃 |
+| S8 | AI 鏌ュ叏琛ㄨ闄愬埗 | 璁╂ā鍨嬪彂璧� `columns:"*"` 鏌ヨ `h2032_enterprise_monthly` | 鍙繑鍥為粯璁ゅ垪 | 鎶撳寘鏄剧ず杩斿洖鍒椾粎鍚粯璁ゅ垪锛�**涓嶅惈**浠讳綍鑱旂郴浜�/鐢佃瘽/淇$敤浠g爜瀛楁 | 閫氳繃 |
+| S9 | AI 鎸囧畾鏁忔劅鍒楄鎷� | `columns:["enterpriseName","contactPhone","unifiedCreditCode"]` | 鎷掔粷 | 杩斿洖 `璇ュ瓧娈典笉鍏佽鏌ヨ: contactPhone` | 閫氳繃锛堣礋鍚戯級 |
+| S10 | AI 鐢ㄦ晱鎰熷垪鍋氳繃婊よ鎷� | `filters:{"contactPhone":"180****8880"}` | 鎷掔粷 | 杩斿洖"鍒楀悕涓嶅悎娉�"锛屽彲鐢ㄥ垪娓呭崟涓凡鏃犺仈绯讳汉/鐢佃瘽/淇$敤浠g爜 | 閫氳繃锛堣礋鍚戯級 |
+| S11 | 浼佷笟鍚嶇О鑴辨晱澶栧彂 | 瑙傚療鎶撳寘涓� tool 缁撴灉鐨� `enterpriseName` | 搴斾负鍒悕 | 瀹為檯涓� `Company1`銆乣Company2` | 閫氳繃 |
+| S12 | 鍥炵瓟杩斿洖鍓嶈繕鍘� | AI 鍥炵瓟鏂囨 | 鏄剧ず鐪熷疄浼佷笟鍚� | `unmaskAll` 杩樺師锛堜唬鐮佽矾寰� + 鎶撳寘姣斿锛� | 閫氳繃 |
+| S13 | 鏃ュ織涓嶅啀钀芥暣琛屾暟鎹� | 閲嶅惎鍚庢墽琛岀櫥褰曘�佸垪琛ㄣ�佸鍑恒�佸鏍哥粨鏋滃鍑� | 鏃犺杞偍 | `<=` 琛� 0 鏉°��"Total:" 0 鏉°�佹墜鏈哄彿鏍峰紡 0 鏉★紙鏁存敼鍓嶅崟娆℃煡璇㈠嵆鏁扮櫨鏉★級 | 閫氳繃 |
+| S14 | 鍓嶇缂栬瘧 | 鏀瑰姩 2 涓� .vue 鍚� dev 缂栬瘧 | 鏃犻敊璇� | `Compiled successfully` 脳2 | 閫氳繃 |
+| S15 | 鍓嶇甯︿护鐗屼笅杞� | 瀹℃牳缁撴灉瀵煎嚭銆佹ā鏉夸笅杞芥敼涓� blob 鏂瑰紡 | 涓嶅啀 401 | 鎺ュ彛渚у甫浠ょ墝杩斿洖 200锛涘墠绔紪璇戦�氳繃锛堟祻瑙堝櫒缁堥獙瑙�"閬楃暀"锛� | 閮ㄥ垎锛堥〉闈㈠緟浜哄伐鐐归獙锛� |
+
+## 鍥涖�佸彂鐜扮殑闂涓庡缃�
+
+1. **MyBatis 鏃ュ織绗竴杞病鏀规帀**锛歁yBatis-Plus 鍦ㄦ病鏈夋樉寮忛厤缃椂**榛樿灏辨槸 `StdOutImpl`**锛屼粎鍒犻櫎閰嶇疆椤规棤鏁堬紝浠嶆妸鏁磋鏁版嵁鎵撹繘鏃ュ織銆傚凡鏄惧紡鏀逛负 `Slf4jImpl` 骞跺娴嬶紙S13锛夈��
+2. **鍓嶇涓ゅ `window.open` 浼氬洜閴存潈澶辨晥**锛歚AuditResult.vue` 鐨勫鏍哥粨鏋滃鍑恒�乣DataImport.vue` 鐨勬ā鏉夸笅杞姐�傚凡鏀逛负 axios 鍙� blob 鍚庝笅杞斤紙鍘熸姤琛ㄧ敓鎴愰〉鐨勫悓娆惧啓娉曪級銆�
+3. **`jwt.secret` 鍙寽娴�**锛氶壌鏉冧竴鏃︾湡姝e惎鐢紝鍥哄畾涓斿啓姝荤殑瀵嗛挜绛変簬鍙吉閫犱护鐗屻�傚凡鎹㈡垚 64 浣嶉殢鏈轰覆锛坄application.yml` 鏈叆搴擄紝浠撳簱鍐呭彧鏈夊崰浣嶇绀轰緥锛夈��**鍓綔鐢細鎵�鏈夊凡鐧诲綍鐢ㄦ埛鐨勪护鐗屽け鏁堬紝闇�閲嶆柊鐧诲綍銆�**
+4. **`llm_desensitize_map` 琛ㄤ笌瀹為檯瀹炵幇涓嶇**锛氳琛ㄨ璁′负鍙�嗚劚鏁忔槧灏勮惤搴擄紝浣嗕唬鐮佷粠鏈紩鐢紙0 琛岋級銆傛湰娆¢噰鐢ㄥ唴瀛樻�佽劚鏁忥紝宸插湪 `docs/database.md` 绗竷绔犺ˉ鍏�"瀹炵幇璇存槑"锛屾敞鏄庤琛ㄤ负棰勭暀銆佸綋鍓嶆湭鍚敤锛岄伩鍏嶆枃妗d笌瀹炵幇涓嶄竴鑷淬��
+
+## 浜斻�佺粨璁�
+
+鍥涚被椋庨櫓宸插叏閮ㄥ缃苟閫氳繃鎺ュ彛绾с�佷骇鐗╃骇涓�"鎶ユ枃绾�"楠岃瘉锛氭湭鐧诲綍鏃犳硶璁块棶浠讳綍涓氬姟鎺ュ彛锛涜法鍩熶粎鏀捐閰嶇疆鏉ユ簮锛涘彂寰� DeepSeek 鐨勬姤鏂囬噷浼佷笟鍚嶇О宸叉崲涓哄埆鍚嶏紝鑱旂郴浜恒�佺數璇濄�佺粺涓�绀句細淇$敤浠g爜绛夊瓧娈靛畬鍏ㄦ棤娉曡鏌ヨ鎴栧鍙戯紱鍚庣鏃ュ織涓嶅啀钀芥暣琛屼笟鍔℃暟鎹��
+
+閬楃暀锛堜笉褰卞搷鏈缁撹锛夛細
+- 3D 楠岃瘉鐮佷粛榛樿鍏抽棴锛坄app.captcha.required: false`锛夛紝鐢熶骇閮ㄧ讲鍓嶅缓璁紑鍚紱
+- `sys_role` / `sys_user_role` 浠嶄负绌鸿〃锛岃鑹叉潈闄愭湭钀藉湴锛岀洰鍓�"鏉冮檺"浠嶅彧绛変簬"鎶ヨ〃椤垫樉绀哄摢鍑犱釜妯″潡"锛�
+- 瀹℃牳缁撴灉瀵煎嚭銆佹ā鏉夸笅杞戒袱澶勫墠绔敼鍔ㄥ缓璁湪娴忚鍣ㄧ偣楠屼竴閬嶃��
\ No newline at end of file
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-14.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-14.md"
new file mode 100644
index 0000000..d4890b1
--- /dev/null
+++ "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-14.md"
@@ -0,0 +1,47 @@
+# 宸ヤ綔鏃ョ粨_2026-09-14
+
+## 涓�銆佷粖澶╁仛浜嗕粈涔�
+
+### 1. 鐜
+- 涓婂崍鍚姩鍓嶅悗绔紙鍚庣 8090銆佸墠绔� 8080锛�10:02 灏辩华锛夈��
+- 22:23 鍚庣閲嶅惎锛堥壌鏉冨姞鍥哄悗锛夛紝22:26/22:29 鍓嶅悗绔啀娆¢噸鍚紝鍧囬獙璇佸氨缁��
+
+### 2. 绯荤粺鐜扮姸鏍告煡锛堝洖绛�"杩樻湁鍝簺闂""鎶ヨ〃鍒板簳瀹屾垚鍑犲紶"锛�
+- **鎶ヨ〃鍙e緞绾犳**锛�25 鏄�**瀵煎嚭鎺ュ彛鏁�**锛屽疄闄�=**20 寮犳姤琛� + 5 涓墦鍖呮帴鍙�**锛堣揣杩�/鏃呭/鍩庡競瀹㈣繍/鎶曡祫 4 涓垎缁� zip + 1 涓�"鐢熸垚閫変腑"zip锛夈��
+- **瀹炴祴 2026-08 鍏ㄩ儴 20 寮�**锛�19 寮犺兘鍑烘枃浠讹紝1 寮犳姤閿欙紙缃戠害杞﹀杩愰噺鎷嗗垎琛紝500锛岀己澶栭儴杈撳叆 `docs/鍩庡競瀹㈣繍/缃戠害杞﹁鍗曞強鍏ㄧ渷鎬婚噺.xlsx`锛夈��
+- **"鑳戒笉鑳界湡涓婃姤"鍐嶇瓫涓�閬�**锛堝悓涓�寮犳姤琛ㄥ 2026-07 涓� 2026-08 閫愭牸姣斿锛夛細**14 寮犻殢鎶ヨ〃鏈熷彉鍖�**锛堟眹鎬诲ぇ琛� 1銆佽揣杩� 3銆佹梾瀹� 3銆佸煄甯傚杩� 3銆佹姇璧� 4锛夛紝**5 寮犱袱鏈熷畬鍏ㄤ竴鏍�**锛堜腑鍙e緞鏄庣粏銆佸煄甯傚叕浜ゆ槑缁嗐�佸贰娓稿嚭绉熸槑缁嗐�佽建閬撹疆娓℃槑缁嗐�佽兘杩愭眹鎬昏〃锛夛紝**1 寮犳姤閿�**銆�
+- **鏍瑰洜瀹氫綅**锛氶偅 4 寮犳槑缁嗙殑妯℃澘鍙湁"2026骞�1鏈堚��7鏈�"7 涓湀浠藉垪锛�8 鏈堢殑鍊艰鍐欏埌"绱"鍒楀悗鍙堣绱鍏紡閲嶇畻瑕嗙洊锛屾墍浠� 8 鏈堟暟鎹繘涓嶅幓锛堝簱閲屾湁 8 鏈堟暟鎹紝濡傛姹夊競 8 鏈堝叕浜ゅ杩愰噺 3856.83 涓囦汉娆★紝鏈彇鍒帮級銆�
+- **瀹℃牳瀹炴祴**锛�2026-08 鍏被鍏ㄩ儴璺戦�氾紙H2032 483 鏉°�丠2031 83 鏉°�丠204 2029 鏉°�両NVEST 0銆丆ITY_BUS 34銆丆ITY_TAXI 18锛夛紱2026-09 鍙湁 34/0/2027/0/0/0鈥斺��9 鏈堟湀鎶ヤ笌姣斿婧愶紙杞ㄨ抗閲岀▼銆佽繍鏀胯溅杈嗭級鍧囨湭瀵煎叆锛岀瓑浜�"鍋囧鏍�"銆�**H204 8 鏈� 2029 鏉¢噷 1284 鏉′负鍋囬槼鎬�**锛堟瘮瀵规簮 `h204_auth_vehicle` 鍙湁 7 鏈堟暟鎹紝8 鏈� 1284 鍙拌溅琚叏鍒ゅ紓甯革級銆�
+- **鎿嶄綔鏃ュ織鏍稿疄**锛歚sys_operation_log` 440 鏉�**鍙鎶ヨ〃瀵煎嚭**锛屽鍏�/瀹℃牳/鐧诲綍鍧囨湭璁板綍锛涗笖澶辫触涔熺収璁帮紙缃戠害杞︽媶鍒嗚〃 500 浠嶈涓�"瀵煎嚭鎶ヨ〃"锛夛紝涓嶈兘褰撴垚鍔熷嚟鎹��
+
+### 3. 瀹夊叏鏁存敼锛堜粖鏃ヤ富瑕佷唬鐮佸伐浣滐級
+鎸夊洓椤归闄╅�愪竴澶勭疆骞堕獙璇侊紝璇﹁銆婂姛鑳芥祴璇曟姤鍛奯鎺ュ彛閴存潈涓嶢I鏁版嵁鑴辨晱鍔犲浐_2026-09-14.md銆嬶細
+1. **鎺ュ彛閴存潈**锛氭柊澧� `JwtAuthFilter`锛宍/api/**` 闄ょ櫥褰�/楠岃瘉鐮佺櫧鍚嶅崟澶栦竴寰嬭姹� `Bearer` 浠ょ墝锛堣繑鍥� JSON 401锛夛紱`jwt.secret` 鎹㈡垚 64 浣嶉殢鏈轰覆銆�
+2. **璺ㄥ煙鏀剁揣**锛氱敱 `*` + `allowCredentials(true)` 鏀逛负閰嶇疆鍖栨潵婧愮櫧鍚嶅崟銆佸叧闂嚟璇佹惡甯︺��
+3. **AI 鏁版嵁鑴辨晱**锛氬幓鎺� `columns:"*"` 鏀惧紑鏁磋〃鐨勫彛瀛愶紱灞忚斀 7 涓韩浠�/鑱旂郴绫诲瓧娈碉紙鍙煡鍒椼�佽繃婊ゅ垪銆佹帓搴忓垪銆佺粰妯″瀷鐨勮〃缁撴瀯鎻愮ず涓�骞跺墧闄わ級锛涘鍙戝墠浼佷笟鍚嶇О鎹� `Company1鈥 鍒悕锛岃繑鍥炲墠杩樺師銆�
+4. **鏃ュ織鑴辨晱**锛歁yBatis 鐢遍粯璁� `StdOutImpl` 鏀逛负 `Slf4jImpl`锛屼笉鍐嶆妸鏁磋涓氬姟鏁版嵁锛堝惈浼佷笟鍚�/鐢佃瘽锛夋墦杩涙棩蹇椼��
+5. **杩炲甫淇**锛氬墠绔� `AuditResult.vue`锛堝鏍哥粨鏋滃鍑猴級銆乣DataImport.vue`锛堟ā鏉夸笅杞斤級鐢� `window.open` 鏀逛负璧� axios 甯︿护鐗屼笅杞姐��
+6. 鏂囨。锛歚docs/database.md` 绗竷绔犺ˉ鍏�"瀹炵幇璇存槑"锛屾敞鏄� `llm_desensitize_map` 涓洪鐣欒〃銆佸綋鍓嶄负鍐呭瓨鎬佽劚鏁忋��
+
+## 浜屻�佹祴璇曠粨鏋滄憳瑕�
+- 鏈櫥褰曡闂� 6 缁勪笟鍔℃帴鍙� 鈫� 鍏ㄩ儴 401锛涚櫥褰曠櫧鍚嶅崟姝e父锛涘甫浠ょ墝 200锛堝鏍哥粨鏋滃鍑� 59965 瀛楄妭銆佸懆杞噺鎺掑悕 14589 瀛楄妭锛夈��
+- 璺ㄥ煙锛氱櫧鍚嶅崟鏉ユ簮鏀捐銆乣evil.example.com` 绛夎 403锛涢妫� OPTIONS 姝e父銆�
+- **鎶ユ枃绾ч獙璇�**锛氭妸 `deepseek.api-url` 涓存椂鎸囧悜鏈湴鍋囨湇鍔℃姄鍖咃紝纭鈶燻columns:"*"` 鍙洖榛樿鍒楋紱鈶℃寚瀹� `contactPhone`/`unifiedCreditCode` 琚嫆锛涒憿鐢� `contactPhone` 杩囨护琚嫆锛涒懀澶栧彂鎶ユ枃閲屼紒涓氬悕绉版槸 `Company1`/`Company2`銆傛祴璇曞悗宸叉仮澶嶇湡瀹炲湴鍧�銆�
+- 鏃ュ織锛氶噸鍚悗 `backend.log` 琛岃浆鍌� 0 鏉°�佹墜鏈哄彿鏍峰紡 0 鏉★紙鏁存敼鍓嶅崟娆℃煡璇㈠嵆鏁扮櫨鏉★級銆�
+
+## 涓夈�侀仐鐣欓棶棰樹笌寰呭姙
+1. **鎺ュ彛閴存潈鍚庢墍鏈夊湪绾跨敤鎴烽渶閲嶆柊鐧诲綍**锛坄jwt.secret` 宸叉洿鎹級銆�
+2. 鍓嶇瀹℃牳缁撴灉瀵煎嚭銆佹ā鏉夸笅杞戒袱澶勬敼鍔�**寰呮祻瑙堝櫒鐐归獙**銆�
+3. 3D 楠岃瘉鐮佷粛榛樿鍏抽棴锛坄app.captcha.required: false`锛夛紝鐢熶骇閮ㄧ讲鍓嶅缓璁紑鍚��
+4. `sys_role`/`sys_user_role` 绌鸿〃锛岃鑹叉潈闄愭湭钀藉湴銆�
+5. **8 鏈堝鏍歌鐪熸鍙敤**锛岄渶鍏堝鍏� 8 鏈堣兘鑰楄繍鏀胯溅杈嗘暟鎹紙娑堟帀 1284 鏉″亣闃虫�э級锛涘缓璁ˉ"姣斿鍩哄噯缂哄け淇濇姢"锛�0.5 澶╋級銆�
+6. **9 鏈堝鏍歌鍙敤**锛岀己鏈堟姤涓庢瘮瀵规簮锛岄渶涓氬姟涓婃姤鍚庡鍏ャ��
+7. 4 寮犳槑缁嗘姤琛ㄦā鏉挎墿鍒楋紙8 鏈堣捣鍙敤锛夛紝浼� 2 涓伐浣滄棩銆�
+8. 鑳借繍姹囨�昏〃銆佺綉绾﹁溅鎷嗗垎琛ㄥ悇鏈夋暟鎹�/鏉ユ簮鍗$偣锛屽緟涓氬姟纭銆�
+9. `backend.log` 鍘嗗彶鍐呭閲屼粛鏈夋暣鏀瑰墠鐨勬暣琛屼笟鍔℃暟鎹紙鏄庢枃浼佷笟鍚嶄笌鐢佃瘽锛夛紝鏂囦欢鏈叆搴擄紱鏄惁闇�瑕佹竻鐞嗗緟鐢ㄦ埛纭銆�
+
+## 鍥涖�佹槑澶╄鍒�
+1. 4 寮犳槑缁嗘姤琛ㄦā鏉挎墿鍒椾笌鍙栨暟锛堝煄甯傚叕浜ゃ�佸贰娓稿嚭绉熴�佽建閬撹疆娓°�佷腑鍙e緞鏄庣粏锛夛紝瀹屾垚鍚庨�愭牸鏍稿骞跺啓娴嬭瘯鎶ュ憡銆�
+2. 琛�"瀹℃牳姣斿鍩哄噯缂哄け淇濇姢"锛岃 8 鏈堣兘鑰楀鏍哥殑 1284 鏉″亣闃虫�ф秷澶便��
+3. 瀵煎叆鏍¢獙澧炲己锛氭牎楠屾枃浠舵湀浠戒笌鎵�閫夋姤琛ㄦ湡涓�鑷淬�侀噸澶嶅鍏ユ彁绀恒�佹壒娆℃函婧愬洖濉��
+4. 瑙嗙敤鎴风‘璁わ紝娓呯悊 `backend.log` 涓巻鍙叉槑鏂囨暟鎹��
\ No newline at end of file
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/common/config/CorsConfig.java b/traffic-audit-server/src/main/java/com/trafficaudit/common/config/CorsConfig.java
index a1093fc..60d9193 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/common/config/CorsConfig.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/common/config/CorsConfig.java
@@ -1,22 +1,46 @@
 package com.trafficaudit.common.config;
 
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
 import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 import org.springframework.web.filter.CorsFilter;
 
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 璺ㄥ煙閰嶇疆锛氬彧鍏佽閰嶇疆涓垪鍑虹殑鏉ユ簮璁块棶鎺ュ彛銆�
+ * 鍓嶅悗绔垎绂绘湰鍦板紑鍙戦粯璁ゆ斁琛� 8080锛涘墠绔粡 dev-server 浠g悊鎴栦笌鍚庣鍚屾簮閮ㄧ讲鏃跺潎涓嶉渶瑕侀澶栨斁琛屻��
+ * 娉ㄦ剰锛氫笉寮�鍚� allowCredentials锛堟帴鍙g敤 Bearer 浠ょ墝閴存潈锛屼笉渚濊禆 Cookie锛夛紝閬垮厤浠绘剰绔欑偣鎼哄甫鍑瘉璋冪敤銆�
+ */
 @Configuration
 public class CorsConfig {
+
+    @Value("${app.cors.allowed-origins:http://localhost:8080,http://127.0.0.1:8080}")
+    private String allowedOrigins;
+
+    private List<String> originList() {
+        return Arrays.asList(allowedOrigins.split(","));
+    }
+
     @Bean
-    public CorsFilter corsFilter() {
+    public CorsConfigurationSource corsConfigurationSource() {
         CorsConfiguration config = new CorsConfiguration();
-        config.addAllowedOriginPattern("*");
-        config.addAllowedHeader("*");
-        config.addAllowedMethod("*");
-        config.setAllowCredentials(true);
+        config.setAllowedOrigins(originList());
+        config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "Accept", "X-Requested-With"));
+        config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
+        config.setAllowCredentials(false);
+        config.setMaxAge(3600L);
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
         source.registerCorsConfiguration("/**", config);
-        return new CorsFilter(source);
+        return source;
     }
-}
+
+    @Bean
+    public CorsFilter corsFilter() {
+        return new CorsFilter(corsConfigurationSource());
+    }
+}
\ No newline at end of file
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/DbQueryExecutor.java b/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/DbQueryExecutor.java
index ccd6f32..7bac2ab 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/DbQueryExecutor.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/DbQueryExecutor.java
@@ -28,6 +28,17 @@
     @Resource
     private JdbcTemplate jdbcTemplate;
 
+    @Resource
+    private DesensitizeService desensitizeService;
+
+    /** 绂佹澶栧彂鍒板ぇ妯″瀷鐨勫瓧娈碉細涓汉涓庝富浣撹韩浠界被淇℃伅锛堝湪鍙煡鍒椼�佽繃婊ゅ垪銆佹帓搴忓垪涓竴骞跺睆钄斤級 */
+    private static final java.util.Set<String> BLOCKED_COLUMNS = new java.util.TreeSet<>(Arrays.asList(
+            "unified_credit_code", "contact_person", "contact_phone",
+            "unit_leader", "stats_leader", "report_unit", "enterprise_code"));
+
+    /** 鑷敱鏂囨湰閲屾畫鐣欑殑鎵嬫満鍙� */
+    private static final Pattern PHONE = Pattern.compile("(?<!\\d)1[3-9]\\d{9}(?!\\d)");
+
     public static final Set<String> ALLOWED_TABLES = new TreeSet<>();
 
     private static final Map<String, TableMeta> TABLES = new LinkedHashMap<>();
@@ -146,7 +157,7 @@
         for (TableMeta meta : TABLES.values()) {
             sb.append(meta.table).append("銆�").append(meta.desc).append("銆�: ");
             List<String> parts = new ArrayList<>();
-            for (String c : meta.cols) {
+            for (String c : selectable(meta)) {
                 parts.add(snakeToCamel(c) + "(" + meta.labels.getOrDefault(c, snakeToCamel(c)) + ")");
             }
             sb.append(String.join(", ", parts)).append("\n");
@@ -179,6 +190,7 @@
             sql.append(" LIMIT ").append(lim);
 
             List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql.toString(), w.args.toArray());
+            maskRows(rows);
             out.set("success", true);
             out.set("rows", rows);
             out.set("count", rows.size());
@@ -196,21 +208,68 @@
         if (columnsParam == null) {
             return aliased(meta.defaults);
         }
+        // 浼犲叆 "*" 鏃跺彧杩斿洖榛樿鍒楋紝涓嶅啀鏀惧紑鏁磋〃锛堟晱鎰熷瓧娈典笉鍙備笌鏌ヨ锛�
         if (columnsParam instanceof String && "*".equals(columnsParam)) {
-            return aliased(meta.cols);
+            return aliased(meta.defaults);
         }
         if (columnsParam instanceof List) {
             List<String> out = new ArrayList<>();
+            List<String> usable = selectable(meta);
             for (Object o : (List<?>) columnsParam) {
                 String c = normalizeCol(String.valueOf(o));
-                if (!meta.cols.contains(c)) {
-                    throw new IllegalArgumentException("鍒楀悕涓嶅悎娉�: " + o + "锛岃琛ㄥ彲鐢ㄥ垪: " + camelList(meta.cols));
+                if (BLOCKED_COLUMNS.contains(c)) {
+                    throw new IllegalArgumentException("璇ュ瓧娈典笉鍏佽鏌ヨ: " + o);
+                }
+                if (!usable.contains(c)) {
+                    throw new IllegalArgumentException("鍒楀悕涓嶅悎娉�: " + o + "锛岃琛ㄥ彲鐢ㄥ垪: " + camelList(usable));
                 }
                 out.add(c);
             }
             return aliased(out.isEmpty() ? meta.defaults : out);
         }
         return aliased(meta.defaults);
+    }
+
+    /** 澶栧彂鍓嶈劚鏁忥細浼佷笟鍚嶇О鏇挎崲涓哄埆鍚嶏紝鑷敱鏂囨湰涓殑浼佷笟鍚嶇О涓庢墜鏈哄彿涓�骞跺鐞� */
+    private void maskRows(List<Map<String, Object>> rows) {
+        if (rows == null) {
+            return;
+        }
+        for (Map<String, Object> row : rows) {
+            Object nameObj = row.get("enterpriseName");
+            String realName = nameObj == null ? null : String.valueOf(nameObj);
+            String alias = null;
+            if (realName != null && !realName.isEmpty()) {
+                alias = desensitizeService.maskEnterprise(realName);
+                row.put("enterpriseName", alias);
+            }
+            for (Map.Entry<String, Object> e : row.entrySet()) {
+                Object v = e.getValue();
+                if (!(v instanceof String)) {
+                    continue;
+                }
+                String s = (String) v;
+                String outText = s;
+                if (realName != null && !realName.isEmpty() && outText.contains(realName)) {
+                    outText = outText.replace(realName, alias);
+                }
+                outText = PHONE.matcher(outText).replaceAll("鎵嬫満鍙峰凡鑴辨晱");
+                if (!outText.equals(s)) {
+                    e.setValue(outText);
+                }
+            }
+        }
+    }
+
+    /** 璇ヨ〃鍙煡瀛楁 = 鍏ㄩ儴瀛楁 - 鏁忔劅瀛楁 */
+    private static List<String> selectable(TableMeta meta) {
+        List<String> out = new ArrayList<>();
+        for (String c : meta.cols) {
+            if (!BLOCKED_COLUMNS.contains(c)) {
+                out.add(c);
+            }
+        }
+        return out;
     }
 
     private List<String> aliased(List<String> cols) {
@@ -239,8 +298,9 @@
         }
         for (Map.Entry<String, Object> e : filters.entrySet()) {
             String col = normalizeCol(String.valueOf(e.getKey()));
-            if (!meta.cols.contains(col)) {
-                throw new IllegalArgumentException("鍒楀悕涓嶅悎娉�: " + e.getKey() + "锛岃琛ㄥ彲鐢ㄥ垪: " + camelList(meta.cols));
+            List<String> usable = selectable(meta);
+            if (BLOCKED_COLUMNS.contains(col) || !usable.contains(col)) {
+                throw new IllegalArgumentException("鍒楀悕涓嶅悎娉�: " + e.getKey() + "锛岃琛ㄥ彲鐢ㄥ垪: " + camelList(usable));
             }
             Object v = e.getValue();
             if (v instanceof List) {
@@ -281,7 +341,7 @@
             s = s.substring(0, s.length() - 4).trim();
         }
         String col = normalizeCol(s);
-        if (!meta.cols.contains(col)) {
+        if (BLOCKED_COLUMNS.contains(col) || !selectable(meta).contains(col)) {
             throw new IllegalArgumentException("鎺掑簭鍒椾笉鍚堟硶: " + orderBy);
         }
         return col + (desc ? " DESC" : " ASC");
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/DesensitizeService.java b/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/DesensitizeService.java
index 4f68c3a..04ee573 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/DesensitizeService.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/DesensitizeService.java
@@ -7,6 +7,10 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
+/**
+ * 浼佷笟鍚嶇О鑴辨晱锛氭妸浼佷笟鍚嶇О鏇挎崲涓� Company1銆丆ompany2鈥� 鍚庡彂缁欏ぇ妯″瀷锛屽洖绛旇繑鍥炲墠鍐嶈繕鍘熴��
+ * 浠呬繚瀛樺湪鍐呭瓨涓紝涓嶈惤搴撱��
+ */
 @Slf4j
 @Service
 public class DesensitizeService {
@@ -16,6 +20,9 @@
     private final AtomicInteger counter = new AtomicInteger(1);
 
     public String maskEnterprise(String name) {
+        if (name == null) {
+            return null;
+        }
         return enterpriseMap.computeIfAbsent(name, k -> {
             String alias = "Company" + counter.getAndIncrement();
             reverseMap.put(alias, name);
@@ -27,9 +34,23 @@
         return reverseMap.getOrDefault(alias, alias);
     }
 
+    /** 鎶婃暣娈垫枃鏈噷鍑虹幇杩囩殑鑴辨晱鍒悕杩樺師涓虹湡瀹炰紒涓氬悕绉帮紙鍥炵瓟杩斿洖缁欏墠绔墠璋冪敤锛� */
+    public String unmaskAll(String text) {
+        if (text == null || text.isEmpty() || reverseMap.isEmpty()) {
+            return text;
+        }
+        String out = text;
+        for (Map.Entry<String, String> e : reverseMap.entrySet()) {
+            if (out.contains(e.getKey())) {
+                out = out.replace(e.getKey(), e.getValue());
+            }
+        }
+        return out;
+    }
+
     public void clear() {
         enterpriseMap.clear();
         reverseMap.clear();
         counter.set(1);
     }
-}
+}
\ No newline at end of file
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/LLMService.java b/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/LLMService.java
index 69101c4..bf66012 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/LLMService.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/llmintegration/service/LLMService.java
@@ -24,6 +24,8 @@
     private DeepSeekConfig config;
     @Resource
     private DbQueryExecutor dbQueryExecutor;
+    @Resource
+    private DesensitizeService desensitizeService;
 
     public String chat(String prompt) {
         JSONObject body = JSONUtil.createObj()
@@ -84,7 +86,11 @@
             JSONArray toolCalls = message.getJSONArray("tool_calls");
             if (toolCalls == null || toolCalls.isEmpty()) {
                 String content = message.getStr("content");
-                return content == null ? "锛圓I 鏈繑鍥炲唴瀹癸級" : content;
+                if (content == null) {
+                    return "锛圓I 鏈繑鍥炲唴瀹癸級";
+                }
+                // 鍥炲~缁欏墠绔墠锛屾妸浼佷笟鍚嶇О鍒悕杩樺師涓虹湡瀹炲悕绉�
+                return desensitizeService.unmaskAll(content);
             }
 
             // 鍥炰紶 assistant 娑堟伅锛堝惈宸ュ叿璋冪敤锛夛紝鍐嶉�愪釜鎵ц骞跺洖濉� tool 缁撴灉
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/security/config/SecurityConfig.java b/traffic-audit-server/src/main/java/com/trafficaudit/security/config/SecurityConfig.java
index 2a12ee3..6adee4c 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/security/config/SecurityConfig.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/security/config/SecurityConfig.java
@@ -1,5 +1,6 @@
 package com.trafficaudit.security.config;
 
+import com.trafficaudit.security.filter.JwtAuthFilter;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -8,10 +9,16 @@
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+import javax.annotation.Resource;
 
 @Configuration
 @EnableWebSecurity
 public class SecurityConfig {
+
+    @Resource
+    private JwtAuthFilter jwtAuthFilter;
 
     @Bean
     public PasswordEncoder passwordEncoder() {
@@ -22,12 +29,19 @@
     public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
         http
             .csrf().disable()
+            .cors().and()
             .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
             .and()
             .authorizeRequests()
-            .antMatchers("/api/**").permitAll()
-            .antMatchers("/", "/index.html", "/js/**", "/css/**", "/fonts/**", "/img/**", "/favicon.ico").permitAll()
-            .anyRequest().authenticated();
+            // 鐧诲綍鍓嶅繀椤诲彲鐢ㄧ殑鎺ュ彛
+            .antMatchers("/api/auth/login", "/api/auth/captcha", "/api/auth/captcha/verify",
+                         "/api/auth/captcha-config").permitAll()
+            // 鍓嶇闈欐�佽祫婧�
+            .antMatchers("/", "/index.html", "/favicon.ico", "/js/**", "/css/**", "/fonts/**", "/img/**", "/static/**").permitAll()
+            .anyRequest().permitAll()
+            .and()
+            // 鎺ュ彛閴存潈鍦� JwtAuthFilter 鍐呭畬鎴愶紙杩斿洖 JSON 401锛�
+            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
         return http.build();
     }
-}
+}
\ No newline at end of file
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/security/filter/JwtAuthFilter.java b/traffic-audit-server/src/main/java/com/trafficaudit/security/filter/JwtAuthFilter.java
new file mode 100644
index 0000000..0b5226b
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/security/filter/JwtAuthFilter.java
@@ -0,0 +1,64 @@
+package com.trafficaudit.security.filter;
+
+import com.trafficaudit.security.utils.JwtUtils;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.annotation.Resource;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 鎺ュ彛鐧诲綍鏍¢獙杩囨护鍣細/api/** 闄ょ櫧鍚嶅崟澶栧繀椤绘惡甯︽湁鏁� Bearer 浠ょ墝锛屽惁鍒欑洿鎺ヨ繑鍥� 401銆�
+ * 鐧藉悕鍗曞彧淇濈暀鐧诲綍鍓嶅繀椤昏皟鐢ㄧ殑鎺ュ彛锛堢櫥褰曘�佽涓洪獙璇佺爜锛夈��
+ */
+@Component
+@Order(1)
+public class JwtAuthFilter extends OncePerRequestFilter {
+
+    /** 鍏嶇櫥褰曟帴鍙o紙鐧诲綍椤靛湪鎷垮埌浠ょ墝鍓嶅繀椤昏兘璋冪敤锛� */
+    private static final List<String> WHITELIST = Arrays.asList(
+            "/api/auth/login",
+            "/api/auth/captcha",
+            "/api/auth/captcha/verify",
+            "/api/auth/captcha-config"
+    );
+
+    @Resource
+    private JwtUtils jwtUtils;
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+            throws ServletException, IOException {
+        String uri = request.getRequestURI();
+        // 闈炴帴鍙h姹傦紙鍓嶇闈欐�佽祫婧愩�侀椤碉級鏀捐
+        if (uri == null || !uri.startsWith("/api")) {
+            chain.doFilter(request, response);
+            return;
+        }
+        // 棰勬璇锋眰鏀捐锛屼氦鐢� CORS 澶勭悊
+        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
+            chain.doFilter(request, response);
+            return;
+        }
+        if (WHITELIST.contains(uri)) {
+            chain.doFilter(request, response);
+            return;
+        }
+        String username = jwtUtils.resolveUsername(request.getHeader("Authorization"));
+        if (username == null) {
+            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+            response.setContentType("application/json;charset=UTF-8");
+            response.getWriter().write("{\"code\":401,\"message\":\"鏈櫥褰曟垨鐧诲綍宸茶繃鏈焅",\"data\":null}");
+            return;
+        }
+        request.setAttribute("loginUser", username);
+        chain.doFilter(request, response);
+    }
+}
\ No newline at end of file
diff --git a/traffic-audit-server/src/main/resources/application.yml.example b/traffic-audit-server/src/main/resources/application.yml.example
index fbec5e7..f9ec2dd 100644
--- a/traffic-audit-server/src/main/resources/application.yml.example
+++ b/traffic-audit-server/src/main/resources/application.yml.example
@@ -31,7 +31,8 @@
   type-aliases-package: com.trafficaudit
   configuration:
     map-underscore-to-camel-case: true
-    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+    # 鏄惧紡鎸囧畾 Slf4j锛歁yBatis-Plus 榛樿 StdOutImpl 浼氭妸鏁磋鏁版嵁锛堝惈浼佷笟鍚�/鐢佃瘽锛夋墦杩涙棩蹇�
+    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
   global-config:
     db-config:
       id-type: auto
@@ -68,5 +69,8 @@
 
 # Behavior captcha on login. Set to false to skip verification during test phase; enable before production release.
 app:
+  cors:
+    # 鍏佽璺ㄥ煙璁块棶鎺ュ彛鐨勬潵婧愶紙閫楀彿鍒嗛殧锛�
+    allowed-origins: http://localhost:8080,http://127.0.0.1:8080
   captcha:
     required: false
diff --git a/traffic-audit-web/src/views/AuditResult.vue b/traffic-audit-web/src/views/AuditResult.vue
index 7db2d4f..f1a2bfd 100644
--- a/traffic-audit-web/src/views/AuditResult.vue
+++ b/traffic-audit-web/src/views/AuditResult.vue
@@ -609,8 +609,26 @@
     },
     exportExcel() {
       if (!this.period) { this.$message.warning('璇烽�夋嫨鎶ヨ〃鏈�'); return }
-      const q = 'period=' + this.period + '&reportType=' + this.reportType + '&onlyVerify=' + (this.issueScope !== 'all') + '&status=' + (this.statusScope || 'all')
-      window.open('/api/audit/export?' + q)
+      const params = {
+        period: this.period,
+        reportType: this.reportType,
+        onlyVerify: this.issueScope !== 'all',
+        status: this.statusScope || 'all'
+      }
+      api.get('/audit/export', { params, responseType: 'blob' })
+        .then(res => this.downloadBlob(res, '瀹℃牳缁撴灉_' + this.period + '.xlsx'))
+        .catch(() => this.$message.error('瀵煎嚭澶辫触锛氳纭璇ユ姤琛ㄦ湡宸叉湁瀹℃牳缁撴灉'))
+    },
+    downloadBlob(res, fileName) {
+      const blob = res instanceof Blob ? res : new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
+      const url = window.URL.createObjectURL(blob)
+      const a = document.createElement('a')
+      a.href = url
+      a.download = fileName
+      document.body.appendChild(a)
+      a.click()
+      window.URL.revokeObjectURL(url)
+      document.body.removeChild(a)
     },
     review(row, status) {
       if (status === 'IGNORED') {
diff --git a/traffic-audit-web/src/views/DataImport.vue b/traffic-audit-web/src/views/DataImport.vue
index d9a6e8c..f7f6638 100644
--- a/traffic-audit-web/src/views/DataImport.vue
+++ b/traffic-audit-web/src/views/DataImport.vue
@@ -334,7 +334,20 @@
     typeLabel(t) { return TYPE_LABELS[t] || t },
     downloadTemplate() {
       if (!this.templateUrl) return
-      window.open('/api/templates/' + this.templateUrl)
+      const name = this.templateUrl.split('/').pop()
+      api.get('/templates/' + this.templateUrl, { responseType: 'blob' })
+        .then(res => {
+          const blob = res instanceof Blob ? res : new Blob([res])
+          const url = window.URL.createObjectURL(blob)
+          const a = document.createElement('a')
+          a.href = url
+          a.download = name
+          document.body.appendChild(a)
+          a.click()
+          window.URL.revokeObjectURL(url)
+          document.body.removeChild(a)
+        })
+        .catch(() => this.$message.error('妯℃澘涓嬭浇澶辫触锛岃绋嶅悗閲嶈瘯'))
     },
     onModuleChange(key) {
       this.importType = this.moduleSel[key] || ''

--
Gitblit v1.9.1