docs/database.md
@@ -411,6 +411,10 @@ ## ä¸ãLLMè±ææ å° > **å®ç°è¯´æï¼2026-09-14 æ´æ°ï¼**ï¼å½åå®é çæçè±ææ¯**å åæ**çââ`DesensitizeService` 卿°æ®å¤ååæä¼ä¸åç§°æ¿æ¢ä¸º `Company1`ã`Company2`â¦ï¼åçè¿åååè¿åï¼æ å°åªåå¨è¿ç¨å åä¸ãä¸è½åºã > å ·ä½åæ³ï¼AI æ¥åºï¼`query_database`ï¼ç»æåå¡«ç»å¤§æ¨¡ååï¼ä¼ä¸åç§°ç»ä¸æ¢å«åï¼èªç±ææ¬éçä¼ä¸åç§°ä¸ææºå·ä¸å¹¶å¤çï¼`contact_person`ï¼å¡«è¡¨äººï¼ã`contact_phone`ï¼èç³»çµè¯ï¼ã`unified_credit_code`ï¼ç»ä¸ç¤¾ä¼ä¿¡ç¨ä»£ç ï¼ã`unit_leader`ã`stats_leader`ã`report_unit`ã`enterprise_code` ç´æ¥ä»"坿¥å"ä¸åé¤ï¼AI æ¢æ¥ä¸å°ä¹ä¸è½ä½ä¸ºè¿æ»¤/æåºæ¡ä»¶ã > ä¸è¡¨ `llm_desensitize_map` 为**é¢ç**çè½åºçæ å°è¡¨ï¼ç®åä»£ç æªå¼ç¨ã表为空ï¼è¥åç»éè¦çåè±æå®¡è®¡ç迹åå¯ç¨ã ### llm_desensitize_map è±ææ å°è¡¨ | åæ®µ | ç±»å | 说æ | |------|------|------| docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_½Ó¿Ú¼øÈ¨ÓëAIÊý¾ÝÍÑÃô¼Ó¹Ì_2026-09-14.md
New file @@ -0,0 +1,60 @@ # åè½æµè¯æ¥å_æ¥å£é´æä¸AIæ°æ®è±æå åº_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:"*"` æ¾å¼æ´è¡¨ï¼ä¼ä¸åç§°ãç»ä¸ç¤¾ä¼ä¿¡ç¨ä»£ç ãåä½è´è´£äººã填表人ãèç³»çµè¯ææåå¾ 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`ï¼SQL æ¥å¿é为 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` éè¿ï¼EXIT=0ï¼éå¯å就绪ï¼ãå端 8080ï¼dev ç¼è¯æåï¼ã - æ°æ®åºï¼æ¬æº `traffic_audit`ï¼MySQL 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 ç»æ¥å£å ¨é¨è¿å 401 JSON | éè¿ | | S2 | ç»å½ç½ååä¸åå½±å | æ 令ç GET `/api/auth/captcha-config`ãPOST `/api/auth/login` | å¯è®¿é® | ç»å½è¿å 200ï¼ä»¤ç 156 ä½ | éè¿ï¼æ£åï¼ | | S3 | æºå¸¦ä»¤çæ£å¸¸ä½¿ç¨ | `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` | åªè¿åé»è®¤å | æå æ¾ç¤ºè¿ååä» å«é»è®¤åï¼**ä¸å«**ä»»ä½è系人/çµè¯/ä¿¡ç¨ä»£ç åæ®µ | éè¿ | | S9 | AI æå®ææå被æ | `columns:["enterpriseName","contactPhone","unifiedCreditCode"]` | æç» | è¿å `è¯¥åæ®µä¸å 许æ¥è¯¢: contactPhone` | éè¿ï¼è´åï¼ | | S10 | AI ç¨ææååè¿æ»¤è¢«æ | `filters:{"contactPhone":"180****8880"}` | æç» | è¿å"ååä¸åæ³"ï¼å¯ç¨åæ¸ åä¸å·²æ è系人/çµè¯/ä¿¡ç¨ä»£ç | éè¿ï¼è´åï¼ | | 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 æ¥å¿ç¬¬ä¸è½®æ²¡æ¹æ**ï¼MyBatis-Plus 卿²¡ææ¾å¼é ç½®æ¶**é»è®¤å°±æ¯ `StdOutImpl`**ï¼ä» å é¤é ç½®é¡¹æ æï¼ä»ææ´è¡æ°æ®æè¿æ¥å¿ãå·²æ¾å¼æ¹ä¸º `Slf4jImpl` 并夿µï¼S13ï¼ã 2. **åç«¯ä¸¤å¤ `window.open` ä¼å 鴿失æ**ï¼`AuditResult.vue` çå®¡æ ¸ç»æå¯¼åºã`DataImport.vue` çæ¨¡æ¿ä¸è½½ãå·²æ¹ä¸º axios å blob åä¸è½½ï¼åæ¥è¡¨çæé¡µç忬¾åæ³ï¼ã 3. **`jwt.secret` å¯çæµ**ï¼é´æä¸æ¦çæ£å¯ç¨ï¼åºå®ä¸åæ»çå¯é¥çäºå¯ä¼ªé 令çãå·²æ¢æ 64 ä½éæºä¸²ï¼`application.yml` æªå ¥åºï¼ä»åºå åªæå ä½ç¬¦ç¤ºä¾ï¼ã**å¯ä½ç¨ï¼ææå·²ç»å½ç¨æ·ç令ç失æï¼ééæ°ç»å½ã** 4. **`llm_desensitize_map` 表ä¸å®é å®ç°ä¸ç¬¦**ï¼è¯¥è¡¨è®¾è®¡ä¸ºå¯éè±ææ å°è½åºï¼ä½ä»£ç 仿ªå¼ç¨ï¼0 è¡ï¼ãæ¬æ¬¡éç¨å åæè±æï¼å·²å¨ `docs/database.md` 第ä¸ç« è¡¥å "å®ç°è¯´æ"ï¼æ³¨æè¯¥è¡¨ä¸ºé¢çãå½åæªå¯ç¨ï¼é¿å ææ¡£ä¸å®ç°ä¸ä¸è´ã ## äºãç»è®º åç±»é£é©å·²å ¨é¨å¤ç½®å¹¶éè¿æ¥å£çº§ã产ç©çº§ä¸"æ¥æçº§"éªè¯ï¼æªç»å½æ æ³è®¿é®ä»»ä½ä¸å¡æ¥å£ï¼è·¨åä» æ¾è¡é ç½®æ¥æºï¼åå¾ DeepSeek çæ¥æéä¼ä¸åç§°å·²æ¢ä¸ºå«åï¼è系人ãçµè¯ãç»ä¸ç¤¾ä¼ä¿¡ç¨ä»£ç çåæ®µå®å ¨æ æ³è¢«æ¥è¯¢æå¤åï¼å端æ¥å¿ä¸åè½æ´è¡ä¸å¡æ°æ®ã éçï¼ä¸å½±åæ¬æ¬¡ç»è®ºï¼ï¼ - 3D éªè¯ç ä»é»è®¤å ³éï¼`app.captcha.required: false`ï¼ï¼ç产é¨ç½²å建议å¼å¯ï¼ - `sys_role` / `sys_user_role` ä»ä¸ºç©ºè¡¨ï¼è§è²æéæªè½å°ï¼ç®å"æé"ä»åªçäº"æ¥è¡¨é¡µæ¾ç¤ºåªå 个模å"ï¼ - å®¡æ ¸ç»æå¯¼åºã模æ¿ä¸è½½ä¸¤å¤å端æ¹å¨å»ºè®®å¨æµè§å¨ç¹éªä¸éã docs/¹¤×÷ÈÕ½á/¹¤×÷ÈÕ½á_2026-09-14.md
New file @@ -0,0 +1,47 @@ # 工使¥ç»_2026-09-14 ## ä¸ãä»å¤©åäºä»ä¹ ### 1. ç¯å¢ - ä¸åå¯å¨åå端ï¼å端 8090ãå端 8080ï¼10:02 就绪ï¼ã - 22:23 å端éå¯ï¼é´æå åºåï¼ï¼22:26/22:29 ååç«¯åæ¬¡éå¯ï¼åéªè¯å°±ç»ªã ### 2. ç³»ç»ç°ç¶æ ¸æ¥ï¼åç"è¿æåªäºé®é¢""æ¥è¡¨å°åºå®æå å¼ "ï¼ - **æ¥è¡¨å£å¾çº æ£**ï¼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 å¼ ä¸¤æå®å ¨ä¸æ ·**ï¼ä¸å£å¾æç»ãåå¸å ¬äº¤æç»ã巡游åºç§æç»ã轨é轮渡æç»ãè½è¿æ±æ»è¡¨ï¼ï¼**1 å¼ æ¥é**ã - **æ ¹å å®ä½**ï¼é£ 4 å¼ æç»ç模æ¿åªæ"2026å¹´1æâ¦7æ"7 个æä»½åï¼8 æçå¼è¢«åå°"累计"åååè¢«ç´¯è®¡å ¬å¼éç®è¦çï¼æä»¥ 8 ææ°æ®è¿ä¸å»ï¼åºéæ 8 ææ°æ®ï¼å¦æ¦æ±å¸ 8 æå ¬äº¤å®¢è¿é 3856.83 ä¸äººæ¬¡ï¼æªåå°ï¼ã - **å®¡æ ¸å®æµ**ï¼2026-08 å ç±»å ¨é¨è·éï¼H2032 483 æ¡ãH2031 83 æ¡ãH204 2029 æ¡ãINVEST 0ãCITY_BUS 34ãCITY_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. å®å ¨æ´æ¹ï¼ä»æ¥ä¸»è¦ä»£ç å·¥ä½ï¼ æå项é£é©éä¸å¤ç½®å¹¶éªè¯ï¼è¯¦è§ãåè½æµè¯æ¥å_æ¥å£é´æä¸AIæ°æ®è±æå åº_2026-09-14.mdãï¼ 1. **æ¥å£é´æ**ï¼æ°å¢ `JwtAuthFilter`ï¼`/api/**` é¤ç»å½/éªè¯ç ç½ååå¤ä¸å¾è¦æ± `Bearer` 令çï¼è¿å JSON 401ï¼ï¼`jwt.secret` æ¢æ 64 ä½éæºä¸²ã 2. **è·¨åæ¶ç´§**ï¼ç± `*` + `allowCredentials(true)` æ¹ä¸ºé ç½®åæ¥æºç½ååãå ³éåè¯æºå¸¦ã 3. **AI æ°æ®è±æ**ï¼å»æ `columns:"*"` æ¾å¼æ´è¡¨çå£åï¼å±è½ 7 个身份/èç³»ç±»åæ®µï¼å¯æ¥åãè¿æ»¤åãæåºåãç»æ¨¡åçè¡¨ç»ææç¤ºä¸å¹¶åé¤ï¼ï¼å¤ååä¼ä¸åç§°æ¢ `Company1â¦` å«åï¼è¿ååè¿åã 4. **æ¥å¿è±æ**ï¼MyBatis ç±é»è®¤ `StdOutImpl` æ¹ä¸º `Slf4jImpl`ï¼ä¸åææ´è¡ä¸å¡æ°æ®ï¼å«ä¼ä¸å/çµè¯ï¼æè¿æ¥å¿ã 5. **è¿å¸¦ä¿®å¤**ï¼å端 `AuditResult.vue`ï¼å®¡æ ¸ç»æå¯¼åºï¼ã`DataImport.vue`ï¼æ¨¡æ¿ä¸è½½ï¼ç± `window.open` æ¹ä¸ºèµ° axios 带令çä¸è½½ã 6. ææ¡£ï¼`docs/database.md` 第ä¸ç« è¡¥å "å®ç°è¯´æ"ï¼æ³¨æ `llm_desensitize_map` 为é¢ç表ãå½å为å åæè±æã ## äºãæµè¯ç»ææè¦ - æªç»å½è®¿é® 6 ç»ä¸å¡æ¥å£ â å ¨é¨ 401ï¼ç»å½ç½å忣叏ï¼å¸¦ä»¤ç 200ï¼å®¡æ ¸ç»æå¯¼åº 59965 åèãå¨è½¬éæå 14589 åèï¼ã - è·¨åï¼ç½ååæ¥æºæ¾è¡ã`evil.example.com` ç被 403ï¼é¢æ£ OPTIONS æ£å¸¸ã - **æ¥æçº§éªè¯**ï¼æ `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 å¼ æç»æ¥è¡¨æ¨¡æ¿æ©åä¸åæ°ï¼åå¸å ¬äº¤ã巡游åºç§ã轨é轮渡ãä¸å£å¾æç»ï¼ï¼å®æåéæ ¼æ ¸å¯¹å¹¶åæµè¯æ¥åã 2. è¡¥"å®¡æ ¸æ¯å¯¹åºåç¼ºå¤±ä¿æ¤"ï¼è®© 8 æè½èå®¡æ ¸ç 1284 æ¡å鳿§æ¶å¤±ã 3. å¯¼å ¥æ ¡éªå¢å¼ºï¼æ ¡éªæä»¶æä»½ä¸æéæ¥è¡¨æä¸è´ãéå¤å¯¼å ¥æç¤ºãæ¹æ¬¡æº¯æºåå¡«ã 4. è§ç¨æ·ç¡®è®¤ï¼æ¸ ç `backend.log` ä¸åå²æææ°æ®ã 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 代çæä¸åç«¯åæºé¨ç½²æ¶åä¸éè¦é¢å¤æ¾è¡ã * 注æï¼ä¸å¼å¯ allowCredentialsï¼æ¥å£ç¨ 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()); } } 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"); 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ãCompany2⦠ååç»å¤§æ¨¡åï¼åçè¿åååè¿åã * ä» ä¿åå¨å åä¸ï¼ä¸è½åºã */ @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); } } } 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 ? "ï¼AI æªè¿åå 容ï¼" : content; if (content == null) { return "ï¼AI æªè¿åå 容ï¼"; } // åå¡«ç»å端åï¼æä¼ä¸åç§°å«åè¿å为çå®åç§° return desensitizeService.unmaskAll(content); } // åä¼ assistant æ¶æ¯ï¼å«å·¥å ·è°ç¨ï¼ï¼åé个æ§è¡å¹¶åå¡« tool ç»æ 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(); } } } traffic-audit-server/src/main/java/com/trafficaudit/security/filter/JwtAuthFilter.java
New file @@ -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 { /** å ç»å½æ¥å£ï¼ç»å½é¡µå¨æ¿å°ä»¤çåå¿ é¡»è½è°ç¨ï¼ */ 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(); // 鿥å£è¯·æ±ï¼å端éæèµæºãé¦é¡µï¼æ¾è¡ 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); } } 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ï¼MyBatis-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 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') { 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] || ''