1 文件已重命名
43个文件已添加
20个文件已修改
106个文件已删除
| | |
| | | _tmp_codexhome/ |
| | | # Codex åºç¨å¨æ¬ä»åºæ ¹ç®å½äº§çç产ç©ç®å½ï¼ä¿ç卿¬å°ï¼ä¸å
¥åºï¼ |
| | | outputs/ |
| | | traffic-audit-server/config/application.yml |
| | | |
| | | # ==== è¿ç¨æ¥å¿ï¼æ¬å°èªæ¥ç¨ï¼ä¸å
¥åºï¼2026-09-27 èµ·æåäºæäº¤æ¹å¼è°æ´ï¼==== |
| | | docs/åè½æµè¯æ¥å/ |
| | | docs/工使¥ç»/ |
| | | # ==== æ¬å°å¤ä»½ï¼ä¸å
¥åºï¼2026-09-27ï¼==== |
| | | _backups/ |
| | | |
| | | # æ±æ»å¤§è¡¨å·®å¼æ¯å¯¹æ¸
åï¼è¿ç¨äº§ç©ï¼åªçæ¬å°ï¼ |
| | | docs/é®é¢æ±æ»/_*.md |
| | | docs/é®é¢æ±æ»/_*.csv |
| | |
| | | - æ²ç®±å
`Get-NetTCPConnection` ä¼å æéâæç»è®¿é®âèéé»å¤±è´¥ï¼**ä¸è¦**æ®æ¤å¤å®ç«¯å£æªçå¬ï¼å¤ææå¡ç¶æä»¥ backend.log / frontend.log 为åã |
| | | - ç¨æ·è¦æ±ï¼ä¸´æ¶èæ¬/æä»¶æªç»åæä¸å ï¼å¯¼å
¥å¤±è´¥è¦å¨é¡µé¢æ¾ç¤ºè¡å·ï¼AI åæç»æè¦ç»ç»æåæç + éåè¾åºå¨ç»ã |
| | | |
| | | ## å·¥ä½è§èï¼2026-09-02 èµ·ï¼å¿
é¡»éµå®ï¼ |
| | | 1. **æ¯åå®ä¸ä¸ªåè½ï¼ç«å³æµè¯å¹¶åãåè½æµè¯æ¥åã** |
| | | ## å·¥ä½è§èï¼2026-09-02 èµ·ï¼2026-09-27 æåäºæäº¤æ¹å¼è°æ´ï¼ |
| | | |
| | | 1. **æ¯åå®ä¸ä¸ªåè½ï¼ç«å³æµè¯**ï¼æµè¯è¦æ±ä¸åï¼ |
| | | - æµè¯è³å°è¦çï¼æ£åç¨ä¾ï¼æ£å¸¸æ°æ®ï¼ãè´åç¨ä¾ï¼éç±»å/éåä½/缺åçï¼ãå
¥åºæ½æ¥ï¼ä¸æºæä»¶é项对é½ï¼ã |
| | | - æ¥ååå
¥ `docs/åè½æµè¯æ¥å/`ï¼å½åï¼`åè½æµè¯æ¥å_<åè½å>_<YYYY-MM-DD>.md`ã |
| | | - ãåè½æµè¯æ¥åãå卿¬å° `docs/åè½æµè¯æ¥å/`ï¼å½åï¼`åè½æµè¯æ¥å_<åè½å>_<YYYY-MM-DD>.md`ï¼**该ç®å½å·² gitignoreï¼ä¸å
¥åº**ï¼èªå·±çæ¡£/夿¥ç¨ï¼ã |
| | | - æ¥åé¡»å
å«ï¼åè½è¯´æãæµè¯ç¯å¢ãç¨ä¾è¡¨ï¼è¾å
¥/颿/å®é
/ç»è®ºï¼ãåç°çé®é¢ä¸å¤ç½®ãç»è®ºã |
| | | 2. **æ¯å¤©ä¸çååã工使¥ç»ãï¼å½å鿥æ** |
| | | - åå
¥ `docs/工使¥ç»/`ï¼å½åï¼`工使¥ç»_<YYYY-MM-DD>.md`ã |
| | | - å
容è³å°å
å«ï¼ä»å¤©åäºä»ä¹ï¼åè½/ä¿®å¤ï¼ãæµè¯ç»ææè¦ãéçé®é¢ä¸å¾
åãæå¤©è®¡åã |
| | | - å½å¤©æ²¡æä»£ç /æµè¯å·¥ä½æ¶ï¼ä¹è¦åï¼ä¸å¥è¯è¯´æâ仿¥æ ä»£ç æ¹å¨âå³å¯ï¼ã |
| | | 3. **git 工使µï¼2026-09-03 ç¨æ·ææï¼æ éåè¯¢é®æ¯å¦æ¨éï¼** |
| | | - æ¯æ¬¡ä¿®æ¹ä»£ç /ææ¡£åï¼å
æµè¯å¹¶åãåè½æµè¯æ¥åã/æ´æ°ã工使¥ç»ãï¼å `git fetch origin` ä¸è¿ç«¯ `main` 对æ¯åå¹¶ï¼å¿
è¦æ¶è§£å³å²çªï¼ï¼ç¡®è®¤æ 误å commit å¹¶ push å°å
¬å¸ giteaï¼`http://61.183.254.94:3000/r/trafficAudit.git`ï¼è´¦å· zyjï¼ã |
| | | - **å
¥åºçæ¯ç»è®º**ï¼æå
³é®ç»è®º/å£å¾/é£é©/å¾
ååè¿ `HANDOFF.md`ï¼å¿
è¦æ¶å¦å `docs/äº¤æ¥ææ¡£_<æ¥æ>.md`ã`docs/é®é¢æ±æ»/`ï¼ï¼ä¸è¯¥æ¬¡ä»£ç æ¹å¨**æ¾å¨å䏿¡æäº¤**éã |
| | | 2. **ã工使¥ç»ãæ¬å°åãä¸å
¥åº** |
| | | - åå
¥æ¬å° `docs/工使¥ç»/`ï¼å½åï¼`工使¥ç»_<YYYY-MM-DD>.md`ï¼**该ç®å½å·² gitignoreï¼ä¸å
¥åº**ï¼ç»èªå·±çï¼ã |
| | | - å
容è³å°å
å«ï¼ä»å¤©åäºä»ä¹ãæµè¯ç»ææè¦ãéçé®é¢ä¸å¾
åãæå¤©è®¡åï¼å½å¤©æ 代ç /æµè¯å·¥ä½ä¹è¦åä¸å¥è¯è¯´æã |
| | | - éè¦äº¤æ¥çé¨åï¼æ"å½åç¶æ + å¾
å"追å å° `HANDOFF.md`ï¼åäºç忳就æ¯è¿æ ·ï¼ã |
| | | 3. **git 工使µï¼2026-09-03 ç¨æ·ææï¼æ éåè¯¢é®æ¯å¦æ¨éï¼æäº¤ç²åº¦åèåäº xycï¼** |
| | | - **䏿¡æäº¤ = ä¸ä¸ªæ¹å¨**ï¼ä»£ç /èæ¬ + 该æ¹å¨çè¯´æææ¡£ï¼`HANDOFF.md` / æ¹æ¡ / å£å¾æ¸
åï¼æ¾å¨å䏿¡æäº¤éï¼ä¸è¦ä¸ºæ¯ä¸ªå°æ¹å¨åç¬å»ºæ¥åååç¬æäº¤ã |
| | | - message åæ³ï¼`feat(模å): â¦` / `fix(模å): â¦` / `docs: â¦` / `docs(交æ¥): â¦`ï¼ä¸å¥è¯è¯´æ¸
"æ¹äºä»ä¹"ã |
| | | - **ç¦æ¢å
¥åº**ï¼ä¸´æ¶èæ¬ä¸è°è¯äº§ç©ï¼`_tmp_*`ã`_shots/`ã`outputs/`ï¼ãæå»ºäº§ç©ï¼jar / dist / targetï¼ãå¯é¥ä¸å£ä»¤ã大ä½ç§¯å¤ä»½ï¼`*.sql` å
¨åºå¯¼åºï¼ï¼è¿ç¨æ¥å¿ä¸æ¥ç»åªçæ¬å°ã |
| | | - æ¨éåï¼å
æä¸é¢å®ææµè¯ï¼å `git fetch origin` ä¸è¿ç«¯ `main` 对æ¯åå¹¶ï¼å¿
è¦æ¶è§£å³å²çªï¼ï¼ç¡®è®¤æ 误å commit å¹¶ push å°å
¬å¸ giteaï¼`http://61.183.254.94:3000/r/trafficAudit.git`ï¼è´¦å· zyjï¼ã |
| | | - è¥è¿ç«¯å·²æåäºæ°æäº¤ï¼å¿
é¡»å
åå¹¶/åºäºææ°åæ¨ï¼ç¦æ¢å¼ºæ¨è¦çåäºä»£ç ã |
| | | - å
¬å¸ git è´¦å·å¯ç åªéè¿æ¬æº git åæ®/ä¸´æ¶ askpass 使ç¨ï¼ç¦æ¢åè¿ä»åºå
ä»»ä½ææ¡£ææäº¤å
容ã |
| | | |
| | |
| | | > **æ¯å¤©ç»ææ¶**ï¼æå½å¤©è¿å±ã踩è¿çåãæ°éæ±ãæªå®æäºé¡¹æ´æ°å°æ¬æä»¶ï¼ç¶åå¯ä»¥æ¾å¿å¼æ°å¯¹è¯ã |
| | | > **åä½çº¦å®ï¼2026-08-31ï¼**ï¼è¯´ãæäº¤ã= æ¬å° commit + æ¨éå°è¿ç«¯ gitï¼origin mainï¼Gitblitï¼ï¼ä¸ååªåæ¬å° commitã |
| | | |
| | | ## ææ°ï¼2026-10-02ï¼ |
| | | |
| | | - **æ°æ¨¡åï¼ç¨æ·å·²éªæ¶ï¼æ¬æ¬¡æäº¤/æ¨éï¼**ï¼æ°å¢âä¸é¡¹ç»è®¡-éè¦èåæ¥âæ¨¡åï¼å«è忥é
ç½®ãè¥ä¸æ§å®¢æµãéè¥ä¸æ§è½¦æµã客è¿ç³»æ° 4 å¼ è¡¨ï¼ç³»ç»é
ç½®èåä»
admin å¯ç»´æ¤ï¼ä¸é¡¹ç»è®¡-èåæ¥æ¯æå¯¼å
¥ã导åºã模æ¿ä¸è½½å Word å¿«æ¥ä¸è½½ã |
| | | - **æ°æ®åå§å**ï¼ä» `docs/ä¸é¡¹ç»è®¡/éè¦è忥/2026å¹´å䏿°æ®.xlsx` åå§å 2024/2025 å¹´åä¸å®¢æµã车æµå 2024-2026 年客è¿ç³»æ°ï¼æºè¡¨ 90 ä¸ªæ°æ®åå
æ ¼å
¥åºé项æ¯å¯¹å·®å¼ 0ã |
| | | - **æ¥å¸¸å¯¼å
¥å£å¾**ï¼åªè¯»åæé年份çå½å¹´åï¼2026 年为å客æµå·¥ä½è¡¨ C åãæ®éå½çé D åï¼ï¼åå²å¹´ä»½ä¸è¦æ±æ¥å¸¸é导ã |
| | | - **å岿°æ®ä¿æ¤**ï¼å¯¼å
¥æ¶å
æ£æ¥æ´æ©å岿¥ææ¯å¦è¢«ä¿®æ¹ãæåååå
ä¸ååºï¼è¿å差弿¸
åï¼å端弹çªè®©ç¨æ·éæ©âå
许导å
¥/ä¸å
许âï¼ç¡®è®¤åæè¦çãæ¥å£åæ° `allowHistoricalOverwrite=true`ã |
| | | - **å¿«æ¥**ï¼é»è®¤ä¸è½½ç³»ç»æ¥æåä¸å¤©çãæ¹åçå½åºåæç»¼åè¿è¾å®¢æµå¿«æ¥ï¼10æXæ¥ï¼.docxãï¼ç¨æ·å¯èªéæ¥æï¼ç¯æ¯/忝æå¢é¿ãä¸éãæå¹³è¾åºã |
| | | - **Excel æµ®ç¹ç²¾åº¦ï¼2026-10-02 追å ï¼**ï¼åè `docs/Excelæµ®ç¹æ°åå¨.txt`ï¼æ°å¢ `HolidayNumberUtils`ï¼`NumberToTextConverter.toText` + `BigDecimal`ï¼`MathContext(15, HALF_UP)`ï¼ï¼èåæ¥æ¨¡åç± `Double` å
¨é¢æ¹ `BigDecimal`ï¼ä¸å¼ 表æ°å¼åæ¹ `DECIMAL(30,15)`ï¼åºå
6 æ¡å®¢è¿ç³»æ°ç± 2.3999999999999995 æ ¡æ£ä¸º 2.4ãè¿ç§»èæ¬ `docs/sql_holiday_decimal_migration.sql`ã |
| | | - **èå顺åº**ï¼âä¸é¡¹ç»è®¡â已移å°âæ¥è¡¨çæâä¹åï¼å½å为 æ¥è¡¨çæ -> ä¸é¡¹ç»è®¡ -> è§å管ç -> ç¨æ·ç®¡ç -> ç³»ç»é
ç½®ã |
| | | - **è忥å é¤å¤æ´»ä¿®å¤ï¼2026-10-02 追å ï¼**ï¼ååå§åé»è¾æ¯æ¬¡å¯å¨è¡¥é½ç¼ºå¤±ç第 N æ¥ï¼å¯¼è´ admin å é¤ 2026-10-08 åè¢«éæ°æå
¥ï¼ç°æ¹ä¸ºä»
`holiday_calendar` æ´è¡¨ä¸ºç©ºæ¶é¦æ¬¡åå§åãéå¯å 2026 å¹´åä¸ä¿æ 10-01 è³ 10-07 å
± 7 天ã |
| | | - **æ®éå½çéç¯æ¯åºæï¼2026-10-02 追å ï¼**ï¼æ®éå½çé C å为å¯éç¯æ¯åºæï¼ä»
2026 å¹´å¯ç¨ï¼å¡«åååªå½±åæ®éå½çé车æµéèªèº«ç¯æ¯ `彿¥ / åºæ - 1`ï¼æªå¡«åæå䏿¥ã`holiday_vehicle_flow.comparison_base` å·²å
¥åºï¼å¯¼å
¥å¯¼åºä¸åå²åæ´ç¡®è®¤åå·²æ¥å
¥ï¼æç®äººåæµå¨éãæ»äººåæµå¨éå Word å¿«æ¥ç»§ç»æ Excel åå
¬å¼ï¼ä¸ååºæå½±åã2024/2025 ä¸ä½¿ç¨ã |
| | | - **å¿«æ¥é¦æ¥å£å¾ï¼2026-10-02 追å ï¼**ï¼åæç¬¬1天没æå䏿¥ç¯æ¯åºæ°ï¼æ£æä¸è¾åºâç¯æ¯å¢é¿/ä¸é/æå¹³âçè¯ï¼è¡¨æ ¼å½æ¥ç¯æ¯å为âââï¼ç¬¬2å¤©èµ·ä»æå®é
å䏿¥è¾åºç¯æ¯ãå·²ç¨ 2025-10-01/02 å¿«æ¥åå½éªè¯ã |
| | | - **å¿«æ¥ç¾åæ¯ç²¾åº¦ï¼2026-10-02 追å ï¼**ï¼Word å¿«æ¥ä¸åæ¯/ç¯æ¯ç¾åæ¯ç»ä¸ä¿ç1ä½å°æ°ï¼å¦6.79%æ¾ç¤º6.8%ã11.46%æ¾ç¤º11.5%ï¼ï¼äººæ¬¡ã车æµçä¸å¡æ°å¼ä»ä¿ç2ä½å°æ°ã |
| | | - **éªè¯ç»æ**ï¼å端ç¼è¯ãå端æå»ºãæé/CRUD/è´å导å
¥ãåå²åæ´ä¸¤é¶æ®µå¯¼å
¥ãExcel 导åºãWord å¿«æ¥ãå端 admin/é admin çæµåéè¿ï¼èå顺åºã客è¿ç³»æ° 2.4 å±ç¤ºï¼ç³»ç»é
置页 + è忥ç»è®¡é¡µï¼ã`decimal(30,15)` ååãåå²åæ´ä¸¤é¶æ®µå¯¼å
¥åå½å已宿µï¼åè½æµè¯æ¥åä¸å·¥ä½æ¥ç»æ 2026-09-27 æäº¤è§èä»
æ¬å°çåï¼ä¸å
¥åºï¼ï¼`docs/åè½æµè¯æ¥å/åè½æµè¯æ¥å_éè¦è忥ä¸é¡¹ç»è®¡_2026-10-02.md`ã`docs/工使¥ç»/工使¥ç»_2026-10-02.md`ã |
| | | - **è¿è¡ç¶æ**ï¼å端 8090ãå端 8080 å·²å¯å¨ï¼æ°æ®åº `127.0.0.1:3305/traffic_audit`ãæµè¯äº§çç 2026 ä¸´æ¶æ°æ®å导å
¥æ¹æ¬¡å·²æ¸
çã |
| | | - **å
³æºæ¶å°¾ï¼2026-10-02ï¼**ï¼å·²åææ¬æ¬¡å¼åç¨å端 8090ãå端 8080 ååèªè¿ç¨æ ï¼ç«¯å£å已鿾ï¼MySQL æå¡ä¿çãå½åå¯å®å
¨å
³æºã |
| | | - **Git**ï¼ç¨æ·å·²äº 2026-10-02 æç¡®éªæ¶éè¿ï¼æ¬æ¬¡æè¦æ±åå¹¶è¿ç«¯ `origin/main` åæäº¤å¹¶æ¨éï¼ä¸è¦çè¿ç«¯åäºæäº¤ã |
| | | - **é¨ç½²æ¨¡æ¿åæ¥**ï¼è忥 4 å¼ è¡¨å·²åæ¥å° `deploy-templates/db/init.sql`ï¼`sql_holiday.sql` å两个æ§åºè¿ç§»èæ¬éé¨ç½²å
åå¸ï¼é¿å
æ°å
缺表ã |
| | | |
| | | ## ææ°ï¼2026-09-28ï¼ |
| | | |
| | | - **ç¨æ·ç¨ WPS ç¼è¾æ¯ç**ï¼æ¹ãç线å
车ãè¾¹æ¡ããéè·¯è¿è¾å¨è½¬éãåå·ï¼å¹¶å é¤ãä¸å£å¾æç»ã`AW` åï¼æå·¥å®ç¨¿åå ï¼ãå·²éªè¯çæç«¯æ£å¸¸ï¼ä¸å£å¾æç» 2024 åæ´æ©åæ´ä½å·¦ç§»ï¼è·¨é¡µ/å页å
¬å¼èªå¨æ¹å¼ç¨ï¼`#REF!` 2â0ï¼ãä¸å£å¾æåå J..R â A..I åæ£ç¡®å¸¦å
¥äº§ç©ã |
| | | - **â ï¸ WPS ä¿åçè¿å¸¦æåï¼å¾
åè¡¥ï¼**ï¼ãç线å
车ã2019/2020 å约 **590 æ ¼**å
容丢失ï¼`FN/FO/FM`ã`FP:FT`ã`GE` å空ï¼`EO/EQ/ES/EU/EW/EY/DT/DW...EM` å
± **144 æ ¼**å `#DIV/0!`ï¼ãçæä»¶å·²å¸¦åºï¼è¯¥é¡µ `#DIV/0!` 228â372ï¼ã**å¾
ç¨æ·ç¡®è®¤åæå¤ä»½/å®ç¨¿åè¡¥ï¼åªè¡¥å¼ä¸å
¬å¼ï¼ä¿çå
¶è¾¹æ¡ä¸åå·ï¼ã** |
| | | - **å£å¾è½å°ã缺æ°ç空ã**ï¼æ¯çã è´§è¿ãè¡ 90/93ï¼ä»æ¡ è§ä¸ä¸¤è¡ï¼ã108/111ï¼æåºï¼å¨ 2021ã2022 å¹´åç 78 个 `0` å¼å·²æ¸
空ï¼ä¸æå·¥å®ç¨¿æè¡¨å¤´å¯¹é½éæ ¼æ ¸å¯¹ï¼æ¯ç=0 ä¸å®ç¨¿=空 78 æ ¼ãå²çª 0 æ ¼ï¼ï¼ä»£ç `FreightCalc.buildMetrics` 彿忝æ¹ä¸ºãè¯¥ææ è§ä¸è®°å½ â ç空ãï¼ä¸åå 0ï¼ç´¯è®¡åæ¯ä¸åï¼ã |
| | | - **çº æ£ä¸æ¡éå¤**ï¼æ¤åãç¨åºå°ä¸å 11 æç¯æ¯ã䏿ç«ââçæä»¶ä¸æå·¥å®ç¨¿çç¯æ¯æé´éåå®å
¨ä¸è´ï¼12æâ¦1æ å
± 12 åï¼ï¼å·®å¼æ ¹å æ¯æå·¥å®ç¨¿ã è´§è¿ãå¨ 2025 å¹´åå¤ä¸å**æ 表头è¾
å©å**ï¼`U`ï¼74 æ ¼ï¼ï¼èªè¯¥åèµ·æå·¥æ´ä½å³ç§» 1 åã**æ¯å¯¹å¿
é¡»æè¡¨å¤´æé´å¯¹é½ï¼ä¸è½æåä½ç½®æ¯ã** |
| | | - **éªè¯**ï¼çæä¸¤æ¬¡ï¼çº¦ 15 ç§/次ï¼ï¼ç¬¬äºæ¬¡ç¸å¯¹ç¬¬ä¸æ¬¡**ä»
78 æ ¼ 0â空**ï¼éè¯¯å¼æ°éä¸åï¼`å
¬äº¤!S5=98460.97` çç¼å弿½æ¥ä¿çã |
| | | - **è¿è¡ç¶æ**ï¼å端已æ `start-dev.ps1` éå¯ï¼æ° PIDï¼8090/8080 å¨è·ï¼ï¼æ¬æºåºä»ä¸º `localhost:3308/traffic_audit`ã |
| | | - **â
ç线å
车åè¡¥å®æï¼09-28 ä¸åï¼**ï¼ç¨æ·ç¡®è®¤åæ 09-27 æ¯çå¤ä»½ï¼`_bak_202608æ¯çææ¬æ°å转æ°å¼å_20260927.xlsx`ï¼XML ç²¾ååè¡¥ãç线å
车ã2019/2020 å ââ **è¯¥é¡µä¸ 09-27 æ¯çéæ ¼ä¸è´ï¼0 å·®å¼ï¼**ï¼é误å¼åå° `#REF!` 8 / `#DIV/0!` 228ï¼æ¯çå
¶å®é¡µæªåå½±åãéªæ¶çæï¼ä¸ 09-27 çæä»¶ (6) æ¯å¯¹é误å¼é页ä¸è´ï¼å·®å¼ä»
婿¢å®æ¹å¨ï¼` è´§è¿` 78 æ ¼æ¸
0ãä¸å£å¾æç» 4915 æ ¼å åä½ç§»ãä¸å£å¾æå 672 æ ¼åä½ç§»ãç线å
车 82 æ ¼æµ®ç¹å°¾å·®/0-空ï¼ã |
| | | - **差弿¸
åå£å¾æ´æ°ï¼09-28ï¼ç¨æ·ç¡®è®¤ï¼**ï¼â ` è´§è¿` æ°å¢çãè§ä¸+è§ä¸è´§è¿éããè§ä¸è´§è¿éãå 17 è¡ï¼æ¯ç设计ï¼å
± 34 è¡ï¼ä¸è®¡å·®å¼ï¼â¡ ` è´§è¿!U5:U112` æ 表头è稿åä¸è®¡ï¼â¢ æå表ã彿åã为æ¯ç设计ä¸è®¡ï¼â£ å¸å·çå/å
¨åä¸å½±åï¼å·¥å
·å½ä¸åï¼ï¼â¤ å°å·®å¼ï¼åæ´å°¾å·®/ææ¬åæ°åï¼åªè®¡æ°ï¼åªåå·®å¼å¤§çå¹¶éåå ãå·¥å
· `docs/å·¥å
·/æ±æ»å¤§è¡¨å·®å¼æ¯å¯¹.py` å·²ææ¤æ´æ°ï¼ä¸¤ä»½æ¸
åå·²éçæã |
| | | - **æ°åç°ï¼å¾
æ ¸å¯¹ï¼**ï¼`ç线å
车!EO36/FN36/FO36`ï¼èé¨ å®¢è¿é 2020 累计/ç´¯è®¡åæ¯ï¼**ç¨åºçææ¶éç®ä¸º -2731.41 / -1.0008 / -1.0002**ï¼èæ¯ç䏿工å®ç¨¿å为 0.181777 / 2312.2199 / -0.586419 â çæ¯ç 2020 å¹´ååæ¯ä¸º 0ï¼éæ ¸å¯¹å
¬å¼ä¸æ°æ®ã |
| | | - **æ±æ»å¤§è¡¨å·®å¼æ¯å¯¹ï¼09-28ï¼å·¥å
·å·²åºåå£å¾ï¼**ï¼`docs/å·¥å
·/æ±æ»å¤§è¡¨å·®å¼æ¯å¯¹.py`ï¼é页æè¡/åæ ç¾åºå对é½ï¼å¯å¤çå¤å/å¤è¡/éå¤è¡¨å¤´ï¼ã**æ¸
åå±è¿ç¨äº§ç©åªçæ¬å°**ï¼`docs/é®é¢æ±æ»/_æ¸
å_*.md/.csv`ï¼å·² gitignore ä¸å
¥åºï¼ã |
| | | - 已确认å£å¾ï¼ä¸åéå¤ç¡®è®¤/䏿¥ï¼ï¼â è´§è¿æ¯å¸å· 6 è¡ vs æå·¥ 4 è¡ï¼æ°å¢ãè§ä¸+è§ä¸è´§è¿éããè§ä¸è´§è¿éãå 17 è¡ï¼å±æ¯ç设计ï¼è¡å·åç§»æ"å°åº+ææ "对é½åä¸è´ï¼â¡ ` è´§è¿` U åï¼U2:U118ï¼ä¸ºäººå·¥è稿ï¼â¢ ä»
æå·¥æä¸**æ 表头**çå = è稿ï¼â£ æµ®ç¹å°¾å·®ãææ¬åæ°å忽ç¥ï¼â¤ æå表ã彿åã为æ¯ç设计ï¼â¥ å¸å·çå/å
¨åä¸å½±åã |
| | | - **éå¤çï¼æ°å¼ï¼**ï¼`ç线å
车!EO36/FN36/FO36`ï¼èé¨ å®¢è¿é 2020 累计/ç´¯è®¡åæ¯ï¼ââç¨åºçææ¶éç®ä¸º -2731.41 / -1.0008 / -1.0002ï¼æ¯ç䏿工å®ç¨¿å为 0.181777 / 2312.2199 / -0.586419ï¼çæ¯ç 2020 å¹´ååæ¯ä¸º 0ï¼ï¼å¾
æ ¸å¯¹ï¼` è´§è¿` 2022 å¹´åå¸å·ãç´¯è®¡åæ¯ã10 æ¡ï¼ä¸å¡å·²ç¡®è®¤**软件对**ï¼ä¸æ¹ï¼ã |
| | | - å
¶ä½å¾
åï¼æå·¥ä¾§ 4 å¤èæ°æ®ï¼`ç线å
车!IL4/IL5` æé¾ã`IL50` åå¼å·ã` è´§è¿!EA93/EA111` ç `#DIV/0!`ï¼ã286 æ ¼ææ¬åæ°åã304 æ ¼è稿åï¼æ¯ç 11 个 `#REF!` + 356 个 `#DIV/0!`ï¼`åå¸å®¢è¿` 第 2 è¡ç¬¬ 3 个æèµ·è¡¨å¤´ä¸ºæ¥æåºåå·ï¼ç¼º 2026-09 æ¯çï¼å¯¼åºååºæ°ä½æ£ï¼P0-3ï¼ã |
| | | - **09-28 æ¶å°¾ï¼`ç线å
车` 3 æ ¼éç®é®é¢å·²ä¿®**ï¼`EO36`/`FN36`/`FO36` 卿¯çéæ¹ä¸º**éæå¼**ï¼0.181777001400256 / 2312.2199 / -0.586418964965219ï¼ä¸æå·¥å®ç¨¿ä¸è´ï¼ï¼å¹¶æåå½±åçå
±äº«å
¬å¼ç»å¤å¿åæ ¼ï¼35 æ ¼ï¼ä¸å¹¶éæåï¼å¼é¶ååï¼ãéæ°çæéªè¯ï¼çæä»¶/æ¯ç/å®ç¨¿ä¸è¾¹ä¸è´ï¼ä¸ä¸ä¸ççæä»¶ä»
34 æ ¼ä¸ºç²¾åº¦è¡¨ç¤ºå·®å¼ãæ æ°å¼ååã |
| | | - **å½å"éå¤ç"ä»
å© 16 æ¡**ï¼å屿工侧/ä¸å¡å·²ç¡®è®¤ï¼éç¨åºé®é¢ï¼ï¼` è´§è¿` EA92~EA112 å
± 12 æ¡ï¼2022 åå¸å·ç´¯è®¡åæ¯ï¼2021 å¹´åºæ°å£å¾ä¸åï¼ä¸å¡å·²ç¡®è®¤è½¯ä»¶å¯¹ï¼å«æå·¥ `#DIV/0!` 2 æ¡ï¼ã`ç线å
车` IL4/IL5ï¼æå·¥ `#REF!`ï¼/IL50ï¼æå·¥åå¼å·ï¼3 æ¡ã`å叿±æ»!G20` 表头æªè¾ 1 æ¡ãå
¶ä½å
¨é¨æå£å¾å¿½ç¥ï¼è稿å/è¡å·åç§»/彿å/æµ®ç¹å°¾å·®/ææ¬åæ°åï¼ã |
| | | - **æªæ¸
é¡¹ï¼ææä¿çï¼**ï¼æ¯ç 11 个 `#REF!` + 356 个 `#DIV/0!` åå²éè¯¯å¼æªæ¸
ââæå·¥å®ç¨¿å类约 15 + 358ï¼åæ¸
ä¸è¾¹ä¼å空å¤åºå边差å¼ï¼è¦æ¸
é两边ä¸èµ·ã |
| | | - **ç¡®è®¤å·®å¼æ¸
åï¼æ¬å°ï¼**ï¼`docs/é®é¢æ±æ»/_å·²ç¡®è®¤å·®å¼æ¸
å_2026-08_20260928.md`ï¼ä¸å
¥åºï¼ã |
| | | - **09-28 å£å¾å
¨é¨è½å°ï¼å·®å¼æ¸
å"éå¤ç"å½é¶**ï¼ |
| | | - â æè½¯ä»¶ï¼æå·¥å®ç¨¿ã è´§è¿ãEB64~EB78 ç 12 æ ¼ 2022 ç´¯è®¡åæ¯ + ãç线å
车ãIL4/IL5/IL50 å·²æè½¯ä»¶å¼åæ¥ï¼å¤ä»½ `2026å¹´8æå®¢è¿éãè´§è¿éå®ç¨¿_åæ¥è½¯ä»¶å_20260928.xlsx`ï¼ã |
| | | - â¡ ææå·¥ç»ä¸æªè¾ï¼æ¯çãå叿±æ»!G20ãæ¹ä¸ºæå·¥åæ³"2025å¹´1-12æå
¨çç´¯è®¡å®æåå¸å®¢è¿éæ
åµ"ï¼å¤ä»½ `_bak_202608æ¯ç_0928æªè¾å.xlsx`ï¼ã |
| | | - ⢠åå²é误å¼çç½ï¼æ¯ç 363 æ ¼ï¼`_bak_202608æ¯ç_0928çç½å.xlsx`ï¼ãæå·¥å®ç¨¿ 379 + å°¾å 6 æ ¼ï¼`..._çç½å°¾åå_20260928.xlsx`ï¼â 两边ç°å为 0 个 `#REF!`/`#DIV/0!`ã |
| | | - ç»æï¼æå½åæ¯çéæ°çæ 2026-08 åï¼ä¸æå·¥å®ç¨¿ç**éå¤çå·®å¼ = 0**ï¼å
¶ä½å
¨é¨è½å¨"已忽ç¥ï¼çº¦å®å·®å¼ï¼"ãæ¸
åï¼`docs/é®é¢æ±æ»/_æ¸
å_æ¯çææ°çæä»¶_20260928.md/.csv`ï¼æ¬å°ï¼ä¸å
¥åºï¼ã |
| | | - 注æï¼æ¡é¢é£ä»½ `çæ_éè·¯è¿è¾éæ±æ»è¡¨_2026-08.xlsx` æ¯æ§å¯¼åºï¼å«æ§é误å¼/æ§åºæ°ï¼ï¼ä¸æ´æ°åçå®ç¨¿æ¯å¯¹ä¼æ¾ç¤ºè¾å¤å·®å¼ï¼ä»¥"æ¯çéæ°çæä»¶"为åã |
| | | - **æ 表头èç¨¿åæ¸
åï¼9 å¤ï¼å
± 539 æ ¼ï¼**ï¼`å
¬äº¤/åºç§/ç½çº¦è½¦!U`ï¼å 72ï¼ã`åå¸å®¢è¿!U`ï¼36ï¼ã`ç线å
车!DU`ï¼55ï¼è¡¨å¤´"1-3æç´¯è®¡"ï¼ã`ä¸å£å¾æç»!U`ï¼90ï¼ã`ä¸å£å¾å®¢è¿é!AB`ï¼180ï¼ã` è´§è¿!V` ä¸ ` è´§è¿!AX`ï¼å 74ï¼ç¨æ·ææ U å卿工é£ä»½å³ç§»ä¸åï¼ãæ¯ç/çæä»¶**å·²å
¨é¨æ¸
空**ï¼æå·¥å®ç¨¿ä»ä¿ç â å¯¹æ¯æ¶å½å
¥ãå·²ç¥è®¾è®¡(æ 表头è稿å)ããå
¶ä½"æ 表头å"æ¯åå¹¶è¡¨å¤´çæ£å¸¸åï¼` è´§è¿!Câ¦Q` æä»½å¼åãåå¹´åå°¾é¨ç´¯è®¡/ç´¯è®¡åæ¯åãæå页 R åçï¼ï¼ä¸¤è¾¹é½æãéè稿ãæç»è§ `docs/é®é¢æ±æ»/_å·²ç¡®è®¤å·®å¼æ¸
å_2026-08_20260928.md`ã |
| | | - **å¾
å**ï¼â æåé¡µç»æï¼å顺åºãæ 颿ªè¾ãå¸å·åçå/å
¨åãå¨è½¬éæåå¤ 7 è¡ï¼ï¼â¡ æåé¡µç»æï¼å顺åºãæ 颿ªè¾ãå¸å·åçå/å
¨åãå¨è½¬éæåå¤ 7 è¡ï¼ï¼â¢ 缺 2026-09 æ¯çã导åºååºæ°ä½æ£ï¼P0-3ï¼ã |
| | | |
| | | ## ææ°ï¼2026-09-27ï¼ |
| | | |
| | | - **æäº¤è§èæ¹ä¸ºåäºæ¹å¼ï¼ç¨æ· 09-27 å³å®ï¼å·²åå
¥ AGENTS.mdï¼**ï¼ |
| | | 1. ãåè½æµè¯æ¥åãã工使¥ç»ãæ¹ä¸º**æ¬å°åãä¸å
¥åº**ï¼`docs/åè½æµè¯æ¥å/`ã`docs/工使¥ç»/` å·² gitignoreï¼æ¬æ¬¡ 109 个æä»¶ç§»åºä»åºãæ¬å°æä»¶å
¨é¨ä¿çï¼å
¶ä¸ã模ååè½éªè¯ç¶æç©éµ_2026-09-02ãå±å¢éå
±äº«ä¿¡æ¯ï¼å·²ç§»å° `docs/` æ ¹ç®å½ç»§ç»å
¥åºï¼ï¼ |
| | | 2. **å
¥åºçæ¯ç»è®º**ï¼å
³é®ç»è®º/å£å¾/é£é©/å¾
ååè¿æ¬æä»¶ï¼`HANDOFF.md`ï¼æ `docs/äº¤æ¥ææ¡£_*.md`ã`docs/é®é¢æ±æ»/`ï¼ä¸è¯¥æ¬¡ä»£ç æ¹å¨æ¾å¨å䏿¡æäº¤ï¼ |
| | | 3. æäº¤ç²åº¦ä¸ messageï¼ä¸æ¡æäº¤ä¸ä¸ªæ¹å¨ï¼ä»£ç +ç¸å
³ææ¡£ï¼ï¼åç¼ `feat(模å):` / `fix(模å):` / `docs:` / `docs(交æ¥):`ï¼ç¦æ¢å
¥åºä¸´æ¶èæ¬/è°è¯äº§ç©/æå»ºäº§ç©/å¯é¥/大ä½ç§¯ sqlã |
| | | > åèæ°æ®ï¼åäº xyc 55 次æäº¤ä¸ 37 æ¬¡æ´æ°æ¬æä»¶ãåè½æµè¯æ¥åä»
4 æ¬¡ãæ¥ç» 7 æ¬¡ï¼æ¤åææ¹ 144 次æäº¤éå« 102 份æ¥å + 106 份æ¥ç»ã |
| | | - **æå
å»å¯ï¼æ ¹å 已修ï¼**ï¼æ§é¨ç½²å
ç jar å
ç½® `BOOT-INF/classes/application.yml` 带ç**çå®å¯é¥ä¸åºå£ä»¤**ï¼å¤å± application.yml å¹²åãè¿è¡æ¶ä»¥å¤é¨ä¸ºåï¼ä½ jar å¯è§£åææå¯è§ï¼ä¸ zip å缩导è´ç´æ¥ grep æä¸å°ï¼ã |
| | | ç°å·²æ¹ä¸ºï¼**è¿ jar çé
ç½®åªæå ä½ç¬¦**ï¼`${TRAFFIC_DB_*}`ã`${DEEPSEEK_API_KEY:}`ã`${TRAFFIC_JWT_SECRET:...}`ï¼ï¼æ¬æºçå®å¼èµ° **Windows ç¨æ·ç¯å¢åé**ï¼`TRAFFIC_DB_PASSWORD` / `DEEPSEEK_API_KEY`ï¼ï¼`start-dev.ps1` å¢å äºä»æ³¨å表补é½çå
åºã |
| | | - **æ°å¢èæ¬ï¼å·²å
¥åºï¼**ï¼`pack-deploy.ps1`ï¼ä¸é®æå
ï¼æå»º â **è§£å jar å»å¯èªæ£ï¼å½ä¸çå®å¯é¥/ææå£ä»¤å³ä¸æ¢** â é建å端 â ç»è£
é¨ç½²å
å° `E:\è¯éª\traffic-audit-deploy-<yyyyMMdd>`ï¼ã`set-secrets.ps1`ï¼è½®æ¢å交äºå¼æ´æ°æ¬æºç¯å¢åéï¼ã`deploy-templates/`ï¼æå
模æ¿å
¥åºï¼å¤é¨ application.ymlãå¯å¨èæ¬ãnginx 示ä¾ãdb 建表 sqlãé¨ç½² READMEââåå
åªå¨æªè·è¸ªçæ§å
éï¼å å
å³ä¸¢ï¼ã |
| | | - **æ¸
çæ§é¨ç½²ä»¶ï¼ç¨æ·ç¡®è®¤åæ§è¡ï¼**ï¼å é¤ `E:\è¯éª\traffic-audit-deploy-20260913`ï¼ç®å½ + **69.9 MB ç zip**ï¼ã`deploy\traffic-audit-deploy-20260913`ã`_deploytest`ã空 `out`/`deploy`ï¼æ§ `target` jar ç± `mvn clean` è¦çãå
¨ç夿¥ï¼ä»åº + `E:\è¯éª`ï¼ï¼**å·²æ ä»»ä½å«ææ key ç jar / å缩å
/ ææ¬æä»¶**ã |
| | | - **干忰å
已产åº**ï¼`E:\è¯éª\traffic-audit-deploy-20260927`ï¼336 æä»¶ / 96 MBï¼åç«¯å«ææ°ãæå¸å·æ±æ»ãï¼jar å
ç½®é
ç½®å
¨å ä½ç¬¦ï¼ã |
| | | - **è¿è¡ç¶æï¼09-27 宿µï¼**ï¼dev å端 `8090`ãå端 `8080` å¨è¿è¡ï¼**æ¬æºæ°æ®åºä¸º MySQL80 `localhost:3308/traffic_audit`ï¼root/å£ä»¤èµ°ç¯å¢åéï¼**ââæ³¨æ `docs/äº¤æ¥ææ¡£_2026-09-23.md` åç `3305` æ¯**åäºæºå¨**çé
ç½®ï¼æ¬æºæ²¡æ 3305ï¼å«åç
§æã |
| | | - **å¾
åï¼æ¥æä¼å
ï¼**ï¼ |
| | | 1. **ãéç¨æ·ãè½®æ¢å·²æ³é²ç DeepSeek key**ï¼æ§å¶å°æ°å»º + å æ§ â è· `set-secrets.ps1`ï¼ï¼ |
| | | 2. **ãéç¨æ·ãæå¡å¨æ¢å
**ï¼å æ§å
/zip â æ· `traffic-audit-deploy-20260927` â æ¹ `backend\application.yml` æ°æ®åºä¸å¤ï¼éè¦ AI åæåå¡« `deepseek.api-key`ï¼â `start-backend.cmd`ï¼ |
| | | 3. P0 ä¸é¡¹ä¸åï¼P0-2 亿å
æèµé¡¹ç®æ Q7 ææè¿½åãP0-3 导åºååºæ°ä½æ£ +ãæ°æ®ç¼ºå¤±æ¸
åã页ãP0-4 缺 2026-06/09 æ¯çï¼ |
| | | 3.1 **æ±æ»å¤§è¡¨ç°ç¶å¤æ ¸ï¼2026-09-27 宿µï¼æ¯ç `docs/çææ±æ»å¤§è¡¨/2026å¹´8æéè·¯è¿è¾éæ±æ»è¡¨.xlsx` + æè¿çæä»¶ï¼**ï¼ |
| | | - **累计åå
¬å¼æ²¡è¦çå°å½æï¼å®æµ 306 æ ¼ï¼æªè½å®**ï¼ç¡®åä½ç½®ï¼æ¯ç `docs/çææ±æ»å¤§è¡¨/2026å¹´8æéè·¯è¿è¾éæ±æ»è¡¨.xlsx`ï¼ï¼ |
| | | · `å
¬äº¤!U5:U76`ã`åºç§!U5:U76`ã`ç½çº¦è½¦!U5:U76` å 72 æ ¼ = 216 æ ¼ï¼å
¬å¼ `=V5+X5+Z5+AB5+AD5+AF5`ï¼åªç´¯å 2025 å¹´ 1~6 æï¼U å表头为空ï¼ä½ç½®å¨"2025å¹´1æ"å左侧ï¼å¯¹åº 2026 åç `S` å"2026年累计"ï¼è¯ä¹å³"2025 年累计"ï¼ï¼ |
| | | · `åå¸å®¢è¿!U4:U39` 36 æ ¼ï¼å模å¼ï¼åª 6 个æï¼ï¼`ç线å
车!DU4:DU57` 54 æ ¼ï¼`DU2` è¡¨å¤´ç´æ¥å"1-3æç´¯è®¡"ï¼åª 3 个æââå±ç弿ªéæä»½æ©å±ï¼ã |
| | | â ä¸å¡å£å¾ä¸º"坿¸
空ç空"ï¼**çæä»¶ä¸æ¯çéæ ¼ç¸å**ï¼è¯å®ç¨åºåªè¡¥å½æåãä¸ç¢°åå²åï¼ã09-22 差弿¥åæå¦ä¸å页å£å¾ç»è®¡ä¸º"304 æ ¼ï¼å«ä¸å£å¾æç» 88 æ ¼ï¼"ï¼æ¬æ¬¡æå
¬å¼æ¨¡å¼å®ä½å°çæ¯ä¸è¿° 306 æ ¼ï¼ä¸¤è
为åä¸ç±»é®é¢ãå页å½ç±»ä¸åã |
| | | - **2026-09-27 å·²å¤ç½®ï¼æ¯çéç"äººå·¥æ ¸ç®è稿累计å"æ´ä½æ¸
空**ï¼ç¨æ·ç¡®è®¤"è¿ä¸åé½å¯ä»¥çç©ºï¼æ¯äººå·¥æ ¸ç®æ¶å¼çè稿"ï¼ãæ¸
空èå´å
± **577 æ ¼**ï¼`å
¬äº¤/åºç§/ç½çº¦è½¦!U5:U76`ã`åå¸å®¢è¿!U4:U39`ã`ç线å
车!DU4:DU57`ï¼å«è¡¨å¤´ `DU2`"1-3æç´¯è®¡"ï¼ã`ä¸å£å¾æç»!U99:U188`ã`ä¸å£å¾å®¢è¿é!AB5:AB188`ã |
| | | · 便®ï¼è¿äºå卿´æ¬å·¥ä½ç°¿é**没æä»»ä½å
¬å¼å¼ç¨**ï¼å
¨ç°¿æ«æ 0 å½ä¸ï¼ï¼ä¸ç¨åº**ä¸åå®ä»¬**ï¼çæä»¶ä¸æ¯çéæ ¼ç¸åï¼â **åªæ¹æ¯çå³å¯ï¼æ 鿹代ç **ã |
| | | · åæ³ï¼ç´æ¥æ¹ xlsx å
é¨ XMLï¼åªå»å¯¹åºåå
æ ¼ç `<f>/<v>`ãä¿çæ ·å¼ï¼ï¼**é¿å
openpyxl ä¿å导è´å
¨ç°¿å
¬å¼ç¼åå¼ä¸¢å¤±**ï¼è¯¥å已宿µå¤ç°ï¼ç¨ openpyxl å䏿¬¡å `å
¬äº¤!S5` ç± 98460.97 å Noneï¼ä¼ç ´å"ä¸å£å¾æåç¨æ¯ç 2025 éæå"ï¼ã |
| | | · éªè¯ï¼éæ°çæ 2026-08ï¼æ¯çåæ´ä½¿ç¼åèªå¨å¤±æï¼`staleCache=true`ï¼â æ°æ§çæä»¶éæ ¼æ¯å¯¹ï¼**ä»
è¿ 577 æ ¼å为空ï¼19 页ç¾ä¸å°ºå¯¸ä¸åï¼å
¶å®ä¸å¾ä¸å¨**ï¼ä¾èµæ¯çç¼åå¼çå£å¾ï¼ä¸å£å¾æå M3ãéè·¯è¿è¾å¨è½¬é G4ãè´§è¿éæå B4ãå叿±æ» B3ãå
¬äº¤ S5/Q5ãä¸å£å¾æç» C99ãç线å
车 Q4ï¼**å
¨é¨ä¸è´**ã |
| | | · å¤ä»½ï¼æ¬å°ä¿çãä¸å
¥åºï¼ï¼`docs/çææ±æ»å¤§è¡¨/_bak_202608æ¯çæ¸
空è稿累计åå_20260927.xlsx`ï¼æ°çæä»¶å·²å `D:\æ¡é¢\çææ°æ®\çæ_éè·¯è¿è¾éæ±æ»è¡¨_2026-08 (4).xlsx`ã |
| | | · **表头æªè¾ä¿®å¤ï¼æäººå·¥å®ç¨¿ä¸ºåï¼**ï¼`轨éã轮渡!R2`ã`R11` ç±æ¯çèªå¸¦ç"8æä¸å»å¹´åæ¯"æ¹ä¸º"忝"ï¼ä¸è¯¥é¡µ `T2/T11`"ç´¯è®¡åæ¯"ä½ä¾ä¸è´ã䏿工å®ç¨¿ä¸è´ï¼ç¨åºä¸åè¿ä¸¤æ ¼ï¼æ
åªæ¹æ¯çï¼ã |
| | | éªè¯ï¼éæ°çæ 2026-08 â æ°æ§çæä»¶éæ ¼æ¯å¯¹ **ä»
è¿ 2 æ ¼æåååï¼19 页ç¾ä¸å°ºå¯¸ä¸åãå
¶å®ä¸å¾ä¸å¨**ã |
| | | · **å¤å®ä¸º"ç¨åºæ£ç¡®ã䏿¹"ç两å¤**ï¼â `éè·¯è¿è¾å¨è½¬é!A23` ç¨åº"2026å¹´8æå
¨çå®æäº¤éå¨è½¬éæ
åµ"ï¼è¯¥åæ¯**彿å¼**ï¼å
¨ç 2,301,720 vs 累计å 17,749,657 ä¸å¨å
¬éï¼â æå·¥å®ç¨¿å¤åäº"累计"ï¼â¡ ` è´§è¿!EA92:EA112` 2022 å¹´ç´¯è®¡åæ¯ 10 æ¡ï¼ä¸å¡å·²ç¡®è®¤è½¯ä»¶å£å¾æ£ç¡®ï¼ã |
| | | · **ææ¬åæ°åç»ä¸ä¸ºæ°å¼ï¼ç¨æ·å£å¾"æç¨åºå¯¹çæ¹"ï¼**ï¼æ¯ç 157 æ ¼ææ¬åæ°å â æ°å¼ï¼`å
¬äº¤` 153 = AF 68 + M 68 + G 17ï¼`轨éã轮渡` 4 = M 2 + E 2ï¼ï¼ä»ç¨ XML ç²¾åæ¹ç±»åï¼ç¼åå¼å
¨é¨ä¿çï¼ãéæ°çæ 2026-08 â ä¸ä¸ä¸çæ¯å¯¹ï¼**ä»
68 æ ¼ç±»åååï¼strâæ°å¼ãæ°å¼ç¸åï¼ï¼æ ä»»ä½å¼/尺寸åå**ï¼å
¶ä½ 89 æ ¼ç¨åºæ¬å°±ä¼åæ°å¼ï¼ãæ³¨ï¼æ¯å¯¹é妿 286 æ¡"ææ¬åæ°å"å·®å¼å¨**æå·¥å®ç¨¿ä¾§**ï¼æå·¥åææ¬ï¼ï¼éæå·¥ä¾§ç»ä¸æå¿½ç¥ã |
| | | · **æå页"两个å顺åºå¯¹è°"ï¼å®ä¸ºé¡ºåºé®é¢ï¼ä¸æ¯å¤äºä¸å¼ 表ï¼**ï¼ç¨åº ä»¥æ¨¡æ¿ `docs/è´§è¿/çæ_è´§è¿éæå.xlsx`ï¼æ¨¡æ¿æ¬èº«=累计åå¨è¡1ã彿åå¨è¡23ï¼å¡«å
â `è´§è¿éæå` ç¨åº=è¡1累计/è¡22å½æï¼æå·¥=è¡1彿ï¼"2026å¹´8æåå¸å·è´§è¿éæå"ï¼/è¡22累计ï¼`è´§è¿å¨è½¬éæå` ç¨åº 44Ã18ï¼ç´¯è®¡å¨åï¼vs æå·¥ 51Ã18ï¼å½æå¨åãä¸å¤ 7 è¡å¾
ç»çï¼ã**ç¨æ·å£å¾ï¼æå·¥ä¸ºå** â éæ¹æ¨¡æ¿ï¼ä¸¤å对è°ï¼+ ç¸åºä»£ç ï¼åå®ä½/æ é¢åæ³ `ReportExportService:961-966/1315/1575-1583`ï¼+ å¸å·åç± `RegionUtil.shortName()` æ¹å
¨åï¼ä¼° 0.5~1 天å«åå½ï¼å¾
å¼å·¥ã |
| | | · **æå·¥å¤åºç"è¾
å©å"æ¸
å**ï¼` è´§è¿` å¤ 1 åï¼æ 表头ï¼ä½äº"忝"ä¹åã2025 å¹´ 1 æä¹å â å®ä¸º `U` å"2025 å¹´ 1-8 æç´¯è®¡"è¾
å©åï¼74 æ ¼ï¼ä» V åèµ·æå·¥æ´ä½å³ç§»ï¼ï¼`åå¸å®¢è¿` å¤ `AJ`="2025å¹´1-7æç´¯è®¡"ï¼æ¯ç 09-20 å·²å ãç¨åºå·²ä¸è¾åºï¼ï¼`ç线å
车` å¤ `DU`="1-3æç´¯è®¡"ï¼å³æ¬æ¬¡æ¸
空çè稿åï¼ï¼`éè·¯è¿è¾å¨è½¬é` æ«å°¾å¤ 2 åï¼Z/AAï¼**表头ä¸å
容å为空**ï¼ã |
| | | · 忴尾差 194 æ¡ï¼ç¨æ·æç¡®"ä¸ç¨ç®¡"ã |
| | | |
| | | · **ä»å¾
ä¸å¡å®ççå¼/æªè¾é¡¹**ï¼`è´§è¿éæå`/`è´§è¿å¨è½¬éæå` çæ é¢ã表头ï¼ç¨åº"ç´¯è®¡å®æ(ä¸å¨)" vs æå·¥"è§ä¸è´§è¿é(ä¸å¨ï¼"ï¼ä¸å¸å·åï¼ç¨åºæè®¾è®¡ç¨çå"æ¦æ±/æ©æ½/æåº"ãæå·¥ç¨"æ¦æ±å¸"çå
¨åï¼ââéä¸å¡ç¡®è®¤ä»¥åªç为åã |
| | | |
| | | · â **å¾
åäºç¡®è®¤**ï¼æå·¥å®ç¨¿ä¾§ä¹æè¿äºè稿åç空ï¼å¦åæ¯å¯¹ä¼ä»"ç¨åºæå¼/æå·¥æå¼"åæ"ç¨åºç©º/æå·¥æå¼"ï¼å类差å¼ãæ¹åç¸åï¼ã |
| | | |
| | | · **æ¸
空ç 7 å对ç
§è¡¨ï¼ä¾åäºå¤æ ¸ï¼å¼ç¨æ°ä¸ºæ¸
空åçå
¨ç°¿æ«æï¼**ï¼ |
| | | |
| | | | é¡µç¾ | å / èå´ | æ¸
ç©ºæ ¼æ° | å表头 | å
¬å¼è¯ä¹ | æ¸
空åç¼åå¼ï¼é¦è¡æ ·ä¾ï¼ | å页å¼ç¨ | 跨页å¼ç¨ | |
| | | | --- | --- | ---: | --- | --- | --- | ---: | ---: | |
| | | | å
¬äº¤ | `U`ï¼U5:U76ï¼ | 72 | ï¼æ è¡¨å¤´ï¼ | `=V5+X5+Z5+AB5+AD5+AF5` â 2025 å¹´ 1~6 æç´¯è®¡ | 88908.91 | 0 | 0 | |
| | | | åºç§ | `U`ï¼U5:U76ï¼ | 72 | ï¼æ è¡¨å¤´ï¼ | `=V5+X5+Z5+AB5+AD5+AF5` â 2025 å¹´ 1~6 æç´¯è®¡ | 39797.94 | 0 | 0 | |
| | | | ç½çº¦è½¦ | `U`ï¼U5:U76ï¼ | 72 | ï¼æ è¡¨å¤´ï¼ | `=V5+X5+Z5+AB5+AD5+AF5` â 2025 å¹´ 1~6 æç´¯è®¡ | 16384.07 | 0 | 0 | |
| | | | åå¸å®¢è¿ | `U`ï¼U4:U39ï¼ | 36 | ï¼æ è¡¨å¤´ï¼ | `=V4+X4+Z4+AB4+AD4+AF4` â 2025 å¹´ 1~6 æç´¯è®¡ | 206639.67 | 0 | 0 | |
| | | | ç线å
车 | `DU`ï¼DU4:DU57ï¼ | 54 | 1-3æç´¯è®¡ | `=DO4+DQ4+DS4` â 1~3 æç´¯è®¡ | 5566.8922 | 0 | 0 | |
| | | | ä¸å£å¾æç» | `U`ï¼U99:U188ï¼ | 90 | ï¼æ è¡¨å¤´ï¼ | `=V99+X99+Z99+AB99+AD99+AF99+AH99` â 2025 å¹´ 1~7 æç´¯è®¡ | 1102278.385724 | 0 | 0 | |
| | | | ä¸å£å¾å®¢è¿é | `AB`ï¼AB5:AB188ï¼ | 184 | ï¼æ è¡¨å¤´ï¼ | `=AC5+AE5` â 2024 å¹´ 1~2 æç´¯è®¡ | 6088.1871 | 0 | 0 | |
| | | |
| | | · **â ååå嫿¸
éï¼åä¾å¯¹ç
§ï¼**ï¼`ä¸å£å¾æç»!AB3 = "2025å¹´4æ"` æ¯**æ£å¸¸æä»½å**ï¼è¢« **700+ å¤å页 + 34 å¤è·¨é¡µå
¬å¼å¼ç¨**ï¼ä¾å¦ `ä¸å£å¾æç»!J5 = I5/AB5-1` 2026 å¹´ 4 æåæ¯ã`T5 = S5/SUM(V5,X5,Z5,AB5,â¦)-1` ç´¯è®¡åæ¯ã`AT5 = X5+V5+Z5+AB5+â¦` 2025 累计ã`ä¸å£å¾å®¢è¿é!I11 = ä¸å£å¾æç»!AB11`ï¼ï¼**æ¬æ¬¡æªå¨ãä¹ä¸è½æ¸
**ã夿®ï¼è¯¥å**æè¡¨å¤´**ã**被å¼ç¨æ°è¿å¤§äº 0**ãå
¬å¼è¯ä¹æ¯"被å«çæ ¼å¼ç¨"è䏿¯å¼ç¨å
¶å®æä»½åã |
| | | |
| | | - **çæä»¶ééè¯¯å¼ 356 个 `#DIV/0!` + 13 个 `#REF!`**ï¼ç线å
车 228+8ãä¸å£å¾æç» 64+2ãè´§è¿ 38+3ãä¸å£å¾å®¢è¿é 26ï¼ä¸å¡å£å¾æªå®ï¼æ¯å¦æ¸
æç©ºç½ï¼ã |
| | | - **åå¸å®¢è¿é¡µè¡¨å¤´ä»æ¯æ¥æåºåå·ï¼ç¡®è¯æªä¿®ï¼**ï¼æ¯çä¸çæä»¶ç¬¬ 2 è¡ç¬¬ 3 个æèµ·å为åºåå·ï¼G2=46082ãI2=46113â¦ï¼ï¼1ã2 ææ¯æåï¼`fixCityMonthHeaderText()` æªè¦ç该页 â "æè¡¨å¤´æåå¹é
å"æ¹æ¡å¨è¯¥é¡µå¤±æãï¼æ¤åææ¡£æ "å¾
æ ¸å®"ï¼ç°å·²ç¡®è¯ãï¼ |
| | | - 缺 2026-09 æ¯çï¼ç®å½ä»
7 æ/8 æ/çå¼åå + 2 个 `_bak_*`ï¼â 9 æåºè¡¨ä¼ `ready=false`ï¼éææ¿æ¹æ¡ Aï¼ç¨åºæ©åï¼/ Bï¼Excel å°å·¥å
·ï¼ã |
| | | 4. æ±æ»å¤§è¡¨ 304 æ ¼ã2025 累计åå°å 7ã8 æãæ¸
空ï¼ä¸å¡å·²ç¡®è®¤å£å¾ï¼å¾
æ§è¡ï¼ï¼ |
| | | 5. æ¬æºåå²ä¸´æ¶ç®å½å¾
æ¸
ï¼`outputs/` 41MBã`_shots/`ã`_test_check*`ã`_test_passenger`ã`.tmp_*`ã`_tmp_codexhome`ï¼ï¼`_ç»åäº_2026-09-21/`ï¼52MB å
¨åºå¤ä»½ï¼å±äº¤ä»ä»¶ï¼æªå¨ã |
| | | |
| | | ## ææ°ï¼2026-09-23ï¼ |
| | | |
| | | - **已忥**ï¼æ¬å° `main` å¿«è¿åå¹¶å
¬å¸ `origin/main` ææ° `8b739ce`ï¼åäº 09-22 å
³æºå¿«ç
§ç»ä¸çï¼ï¼åå¹¶å `HEAD = 8b739ce = origin/main`ï¼å·¥ä½åºå¹²åã |
| New file |
| | |
| | | TrafficAudit 交éè´§è¿ç»è®¡å®¡æ ¸ç³»ç» ââ é¨ç½²å
ï¼{stamp}ï¼ |
| | | ======================================================== |
| | | |
| | | ãç®å½ç»æã |
| | | backend/ |
| | | traffic-audit-server-1.0.0-SNAPSHOT.jar å端ç¨åºï¼å«å
¨é¨ä¾èµï¼**å·²å
åµå端**ï¼ |
| | | docs/ æ¥è¡¨æ¨¡æ¿ä¸è¿è¡æ°æ®ç®å½ï¼å¿
éï¼å¿å ï¼ |
| | | application.yml å¤é¨é
ç½®ï¼æ¹è¿éå³å¯ï¼æ ééæ°æå
ï¼ |
| | | start-backend.cmd / start-backend.sh å¯å¨èæ¬ |
| | | frontend/ |
| | | dist/ å端æå»ºäº§ç©ï¼å¤ç¨ï¼æ³ç¨ nginx æç®¡æ¶ç¨ï¼ |
| | | nginx.conf.example nginx é
ç½®ç¤ºä¾ |
| | | db/ |
| | | init.sql 建åºå»ºè¡¨ + é¢ç½®è´¦å·ï¼å¿
é¡»æ§è¡ï¼ |
| | | sql_city_bus.sql åå¸å®¢è¿-å
¬äº¤æåº¦è¡¨ï¼å¿
é¡»æ§è¡ï¼ |
| | | sql_city_taxi.sql åå¸å®¢è¿-åºç§æåº¦è¡¨/åºç§ææè¡¨ï¼å¿
é¡»æ§è¡ï¼ |
| | | sql_wyc.sql ç½çº¦è½¦å
¨çæ»é表/订ååå¸å·è¡¨ï¼å¿
é¡»æ§è¡ï¼ |
| | | sql_holiday.sql ä¸é¡¹ç»è®¡-éè¦èåæ¥è¡¨ï¼æ§åºç¼ºè¡¨æ¶æ§è¡ï¼ |
| | | sql_holiday_decimal_migration.sql è忥æ°å¼å精度è¿ç§»ï¼æ§åºåçº§ï¼ |
| | | sql_holiday_comparison_base_migration.sql èåæ¥ç¯æ¯åºæå段è¿ç§»ï¼æ§åºåçº§ï¼ |
| | | README-é¨ç½²è¯´æ.txt |
| | | |
| | | ãé¨ç½²æºç¯å¢è¦æ±ã |
| | | JDK 8ï¼å¿
éï¼ |
| | | MySQL 5.7 / 8.0ï¼å¿
éï¼ |
| | | **ä¸éè¦ Redis**ï¼ä»£ç æªä½¿ç¨ï¼æ§ç说æéç Redis æ¯éçé
ç½®ï¼å¯å¿½ç¥ï¼ |
| | | **ä¸éè¦ nginx**ï¼å端已å
åµè¿ jarï¼è®¿é® 8090 å³å¯ï¼nginx ä»
为å¤éæ¹æ¡ï¼ |
| | | |
| | | ãé¨ç½²æ¥éª¤ã |
| | | 1. åå§åæ°æ®åºï¼4 ä¸ªèæ¬é½è¦æ§è¡ï¼é¡ºåºå¦ä¸ï¼ |
| | | mysql --default-character-set=utf8mb4 -u root -p < db/init.sql |
| | | mysql --default-character-set=utf8mb4 -u root -p traffic_audit < db/sql_city_bus.sql |
| | | mysql --default-character-set=utf8mb4 -u root -p traffic_audit < db/sql_city_taxi.sql |
| | | mysql --default-character-set=utf8mb4 -u root -p traffic_audit < db/sql_wyc.sql |
| | | 注ï¼å
¨æ°åºç init.sql å·²å«è忥 4 å¼ è¡¨ï¼è¥æ¯å¨æ©æçæ¬åºä¸è¡¥èåæ¥æ¨¡åï¼åæ§è¡ db/sql_holiday.sqlï¼å¹¶æéæ§è¡ä¸¤ä¸ªè¿ç§»èæ¬ã |
| | | é¢ç½®è´¦å·ï¼admin / admin123ï¼é¦æ¬¡ç»å½å请æ¹å¯ç ï¼ |
| | | |
| | | 2. æ¹é
ç½® backend/application.yml |
| | | - spring.datasource.url / username / password ï¼æ¹æé¨ç½²æºç MySQLï¼é»è®¤ç«¯å£ 3306ï¼ |
| | | - jwt.secret ï¼æ¹æä¸ä¸²éæºå符串ï¼**å¿
é¡»æ¯ Base64 åæ³å符ä¸é¿åº¦æ¯ 4 çåæ°**ï¼å¦ 32 ä½åå
è¿å¶ï¼å¡«ä¸æä¼ç»å½å¤±è´¥ï¼ |
| | | - deepseek.api-key ï¼éè¦ AI åææ¶å¡«ï¼ä¸éè¦å¯ç空 |
| | | 注ï¼application.yml ä¸ jar å级å³çæï¼ä¸ç¨éæ°æå
ã |
| | | |
| | | 3. å¯å¨å端ï¼å¨ backend ç®å½ä¸ï¼ |
| | | Windowsï¼åå» start-backend.cmd |
| | | Linuxï¼ sh start-backend.sh |
| | | æåæ å¿ï¼åºç° "Started TrafficAuditApplication" ä¸ "Tomcat started on port(s): 8090" |
| | | |
| | | 4. 访é®ç³»ç» |
| | | æµè§å¨æå¼ http://<é¨ç½²æºIP>:8090 ï¼å端已å
åµï¼é¡µé¢ä¸æ¥å£åæºï¼ |
| | | è´¦å·ï¼admin / admin123 |
| | | |
| | | ãæ°æ®è¿ç§»ï¼å¯éï¼ã |
| | | æ³ææ¬æºæ°æ®ä¸å¹¶æ¬å°æ°æºå¨ï¼ç¨ mysqldumpï¼æ¬æº MySQL è¥ä¸å¨ 3306ï¼æå®é
ç«¯å£æ¹ï¼ï¼ |
| | | mysqldump -h 127.0.0.1 -P 3306 -u root -p --databases traffic_audit > traffic_audit_full.sql |
| | | å卿°æºå¨æ§è¡ï¼ |
| | | mysql -u root -p < traffic_audit_full.sql |
| | | 注æï¼å
¨é dump ä¼å¸¦ä¸æ¬æºçè´¦å·å¯ç ï¼è¦ç init.sql é¢ç½®ç admin è´¦å·ã |
| | | |
| | | ã常è§é®é¢ã |
| | | 1. å¯å¨æ¥ãæªæ¾å°æ°æ®ç®å½ docsã/ å¯¼åºæ¥ãæªæ¾å°æ¨¡æ¿æä»¶ãï¼ |
| | | å¿
é¡»å¨ backend ç®å½ä¸å¯å¨ï¼jar ä¸ docs å级ï¼ï¼ä¸è¦åªæ· jarã |
| | | 2. æ¥è¡¨å¯¼åºæ¥ãæªæ¾å°æ±æ»è¡¨æ¯çãYYYYå¹´Mæéè·¯è¿è¾éæ±æ»è¡¨.xlsxããï¼ |
| | | docs/çææ±æ»å¤§è¡¨ ä¸ç¼ºå°è¯¥æä»½æ¯çï¼ææºæºå¨ç该æä»¶æ·è¿å»ã |
| | | 3. 8090 端å£è¢«å ç¨ï¼æ¹ application.yml ç server.port åéå¯ã |
| | | 4. 页颿弿¯ç©ºç½ï¼ç¡®è®¤è®¿é®çæ¯ http://<IP>:8090 æ ¹è·¯å¾ï¼ä¸æµè§å¨å¼ºå¶å·æ°ï¼Ctrl+F5ï¼ã |
| | | 5. ä¸ä¼ 大æä»¶å¤±è´¥ï¼å端éå¶ 100MBï¼å·²é
ç½®ï¼å¦åé¢è¿æäº nginxï¼é忥æ¾å¤§ client_max_body_sizeã |
| | | |
| | | ã忢æå¡ã |
| | | Windowsï¼å
³é start-backend.cmd çªå£ |
| | | Linuxï¼ Ctrl+Cï¼æ kill å¯¹åº java è¿ç¨ï¼ps -ef | grep javaï¼ |
| New file |
| | |
| | | # ============================================================ |
| | | # TrafficAudit å端å¤é¨é
ç½®ï¼ä¸ jar å级å³å¯çæï¼ä¼å
级é«äº jar å
ç½®é
ç½®ï¼ |
| | | # é¨ç½²å°æ°æºå¨åï¼è¯·**è³å°**ä¿®æ¹ä¸é¢æ 注 TODO çä¸å¤æ°æ®åºé
ç½®ï¼ |
| | | # ============================================================ |
| | | server: |
| | | port: 8080 |
| | | app: |
| | | captcha: |
| | | required: false # ç产ç¯å¢å¦éå¾å½¢éªè¯ç ï¼æ¹ä¸º true |
| | | spring: |
| | | datasource: |
| | | url: jdbc:mysql://127.0.0.1:3306/traffic_audit?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true |
| | | username: root # TODO æ¹æä½ ç MySQL è´¦å· |
| | | password: è¯·æ¹æä½ çæ°æ®åºå¯ç # TODO æ¹æä½ ç MySQL å¯ç |
| | | driver-class-name: com.mysql.cj.jdbc.Driver |
| | | servlet: |
| | | multipart: |
| | | max-file-size: 100MB |
| | | max-request-size: 100MB |
| | | jackson: |
| | | date-format: yyyy-MM-dd HH:mm:ss |
| | | time-zone: Asia/Shanghai |
| | | mybatis-plus: |
| | | mapper-locations: classpath*:/mapper/**/*.xml |
| | | type-aliases-package: com.trafficaudit |
| | | configuration: |
| | | map-underscore-to-camel-case: true |
| | | global-config: |
| | | db-config: |
| | | id-type: auto |
| | | logic-delete-field: deleted |
| | | logic-delete-value: 1 |
| | | logic-not-delete-value: 0 |
| | | jwt: |
| | | # å¿
é¡»æ¯ãBase64 åæ³å符 + é¿åº¦æ¯ 4 çåæ°ãï¼ä¸é¢è¿ä¸² 32 ä½åå
è¿å¶å¯ç´æ¥ç¨ï¼ |
| | | # ç产ç¯å¢å»ºè®®æ¢æèªå·±çéæºä¸²ï¼å¡«ä¸ææä¸åæ³å符ä¼å¯¼è´ç»å½ 500ï¼ |
| | | secret: 8f2c1d64a7b34e09c5d81f6a2b0e7c93 |
| | | expiration: 86400000 |
| | | docs: |
| | | template-root: docs |
| | | city-passenger: |
| | | template-dir: docs/åå¸å®¢è¿ |
| | | passenger: |
| | | template-dir: docs/å
¬è·¯æ
客 |
| | | freight: |
| | | template-dir: docs/è´§è¿ |
| | | energy: |
| | | template-dir: docs/è½è |
| | | investment: |
| | | template-dir: docs/æèµ/æ¨¡æ¿ |
| | | summary: |
| | | template-dir: docs/çææ±æ»å¤§è¡¨ # æ±æ»å¤§è¡¨æ¯çä¸çå¼åç
§ä»¶æå¨ç®å½ï¼å¿
éï¼ |
| | | deepseek: |
| | | api-key: "" # AI åæç¨ï¼ç空å AI åæä¸å¯ç¨ |
| | | api-url: https://api.deepseek.com/v1/chat/completions |
| | | model: deepseek-chat |
| New file |
| | |
| | | @echo off |
| | | chcp 65001 >nul |
| | | cd /d %~dp0 |
| | | where java >nul 2>nul |
| | | if errorlevel 1 ( |
| | | echo [ERROR] æªæ¾å° javaï¼è¯·å
å®è£
JDK 8 å¹¶å å
¥ PATH |
| | | pause |
| | | exit /b 1 |
| | | ) |
| | | echo ============================================ |
| | | echo TrafficAudit å¯å¨ä¸ï¼http://localhost:8090 |
| | | echo å端已å
åµï¼ç´æ¥è®¿é® 8090 å³å¯ï¼ä¸éè¦ nginxï¼ |
| | | echo æ¥å¿æ¾ç¤ºå¨æ¬çªå£ï¼å
³éçªå£å³åæ¢æå¡ |
| | | echo ============================================ |
| | | java -jar traffic-audit-server-1.0.0-SNAPSHOT.jar |
| | | pause |
| New file |
| | |
| | | #!/bin/sh |
| | | cd "$(dirname "$0")" || exit 1 |
| | | echo "TrafficAudit å¯å¨ä¸ï¼http://localhost:8090 ï¼Ctrl+C 忢ï¼" |
| | | java -jar traffic-audit-server-1.0.0-SNAPSHOT.jar 2>&1 | tee app.log |
| New file |
| | |
| | | -- 交éç»è®¡æ¥è¡¨å®¡æ ¸ç³»ç» - æ°æ®åºåå§åèæ¬ v3 |
| | | -- Demoèå´ï¼äº¤ä¼ç»H203-2表_é路货ç©è¿è¾æåº¦ç产æ
åµ |
| | | |
| | | CREATE DATABASE IF NOT EXISTS traffic_audit DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; |
| | | USE traffic_audit; |
| | | |
| | | -- ============================================ |
| | | -- ä¸ãç¨æ·æé模åï¼å«ä¸åï¼ |
| | | -- ============================================ |
| | | |
| | | -- é¨é¨è¡¨ |
| | | DROP TABLE IF EXISTS sys_dept; |
| | | CREATE TABLE sys_dept ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | dept_name VARCHAR(100) NOT NULL COMMENT 'é¨é¨åç§°', |
| | | dept_code VARCHAR(20) NOT NULL COMMENT 'è¡æ¿åºå代ç ', |
| | | parent_id BIGINT DEFAULT 0 COMMENT 'ä¸çº§é¨é¨', |
| | | dept_level TINYINT COMMENT '1ç 2å¸ 3å¿', |
| | | sort_order INT DEFAULT 0 |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | -- è§è²è¡¨ |
| | | DROP TABLE IF EXISTS sys_role; |
| | | CREATE TABLE sys_role ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | role_name VARCHAR(50) NOT NULL COMMENT 'è§è²å', |
| | | role_code VARCHAR(50) NOT NULL COMMENT 'SYS_ADMIN/SEC_OFFICER/AUDITOR/PROVINCE/CITY/COUNTY', |
| | | role_type VARCHAR(20) DEFAULT 'BUSINESS' COMMENT 'SYSTEM(ä¸å)/BUSINESS(ä¸å¡)', |
| | | role_level TINYINT DEFAULT 1 COMMENT '0ä¸å 1çå
2å¸å± 3å¿åº', |
| | | is_mutex TINYINT DEFAULT 0 COMMENT '1ä¸åäºæ¥ 0æ®é' |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | -- ç¨æ·è¡¨ |
| | | DROP TABLE IF EXISTS sys_user; |
| | | CREATE TABLE sys_user ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | username VARCHAR(50) NOT NULL UNIQUE, |
| | | password VARCHAR(200) NOT NULL COMMENT 'BCrypt', |
| | | real_name VARCHAR(50), |
| | | modules VARCHAR(255) DEFAULT NULL COMMENT 'è´è´£æ¥è¡¨æ¨¡åï¼éå·åéï¼', |
| | | phone VARCHAR(20), |
| | | dept_id BIGINT, |
| | | status TINYINT DEFAULT 1 COMMENT '0ç¦ç¨ 1å¯ç¨', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | -- ç¨æ·è§è²å
³è |
| | | DROP TABLE IF EXISTS sys_user_role; |
| | | CREATE TABLE sys_user_role ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | user_id BIGINT NOT NULL, |
| | | role_id BIGINT NOT NULL |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | -- ============================================ |
| | | -- äºãä¸å¡æ°æ®æ¨¡å |
| | | -- ============================================ |
| | | |
| | | -- æ¸
çæ§çéç表ï¼v2 schemaï¼ |
| | | DROP TABLE IF EXISTS report_enterprise_monthly; |
| | | |
| | | -- 交ä¼ç»H203-2表ï¼é路货ç©è¿è¾æåº¦ç产æ
åµï¼Demoæ ¸å¿è¡¨ï¼ |
| | | DROP TABLE IF EXISTS h2032_enterprise_monthly; |
| | | CREATE TABLE h2032_enterprise_monthly ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ 2026-06', |
| | | region_code VARCHAR(20) COMMENT 'æå±å°åº', |
| | | enterprise_code VARCHAR(50) COMMENT 'ä¼ä¸ä»£ç ', |
| | | enterprise_name VARCHAR(200) COMMENT 'ä¼ä¸åç§°', |
| | | unified_credit_code VARCHAR(50) COMMENT 'ç»ä¸ç¤¾ä¼ä¿¡ç¨ä»£ç ', |
| | | report_unit VARCHAR(200) COMMENT 'å¡«æ¥åä½', |
| | | |
| | | -- è½¦è¾æ°ä¸å¨ä½æ° |
| | | vehicle_total INT COMMENT 'è½¦è¾æ°_æ»è®¡', |
| | | tons_total DOUBLE COMMENT 'æ è®°å¨ä½æ°_æ»è®¡', |
| | | vehicle_tractor INT COMMENT 'è½¦è¾æ°_çµå¼è½¦', |
| | | vehicle_trailer INT COMMENT 'è½¦è¾æ°_æè½¦', |
| | | tons_trailer DOUBLE COMMENT 'æ è®°å¨ä½æ°_æè½¦', |
| | | vehicle_container INT COMMENT 'è½¦è¾æ°_éè£
箱车', |
| | | tons_container DOUBLE COMMENT 'æ è®°å¨ä½æ°_éè£
箱车', |
| | | vehicle_whole INT COMMENT 'è½¦è¾æ°_æ´è½¦', |
| | | tons_whole DOUBLE COMMENT 'æ è®°å¨ä½æ°_æ´è½¦', |
| | | |
| | | -- è´§è¿éä¸å¨è½¬é |
| | | freight_total DOUBLE COMMENT 'è´§è¿é_æ»è®¡ï¼å¨ï¼', |
| | | turnover_total DOUBLE COMMENT 'è´§ç©å¨è½¬é_æ»è®¡ï¼å¨å
¬éï¼', |
| | | |
| | | -- å货类货è¿é |
| | | freight_container DOUBLE COMMENT 'è´§è¿é_éè£
ç®±ï¼å¨ï¼', |
| | | freight_coal DOUBLE COMMENT 'è´§è¿é_ç
¤çåå¶åï¼å¨ï¼', |
| | | freight_oil_gas DOUBLE COMMENT 'è´§è¿é_ç³æ²¹å¤©ç¶æ°åå¶åï¼å¨ï¼', |
| | | freight_crude_oil DOUBLE COMMENT 'è´§è¿é_å
¶ä¸åæ²¹ï¼å¨ï¼', |
| | | freight_metal_ore DOUBLE COMMENT 'è´§è¿é_éå±ç¿ç³ï¼å¨ï¼', |
| | | freight_iron_ore DOUBLE COMMENT 'è´§è¿é_å
¶ä¸éç¿ç³ï¼å¨ï¼', |
| | | freight_building DOUBLE COMMENT 'è´§è¿é_ç¿ç©æ§å»ºçææï¼å¨ï¼', |
| | | freight_grain DOUBLE COMMENT 'è´§è¿é_ç²®é£ï¼å¨ï¼', |
| | | |
| | | -- è¿è¥ææ |
| | | avg_tonnage DOUBLE COMMENT '货车平åå¨ä½', |
| | | avg_tonnage_trailer DOUBLE COMMENT 'å¹³åå¨ä½_æè½¦', |
| | | avg_tonnage_whole DOUBLE COMMENT 'å¹³åå¨ä½_æ´è½¦', |
| | | avg_distance DOUBLE COMMENT '货车平åè¿è·ï¼å
¬éï¼', |
| | | daily_trips_per_ton DOUBLE COMMENT 'å卿¥åè½½è¿æ¬¡æ°', |
| | | daily_mileage_per_vehicle DOUBLE COMMENT 'å车å天è¡é©¶éç¨ï¼å
¬éï¼', |
| | | daily_volume_per_vehicle DOUBLE COMMENT 'å车æ¥åè¿éï¼å¨ï¼', |
| | | daily_trips_with_tractor DOUBLE COMMENT 'å卿¥åè½½è¿æ¬¡æ°ï¼ç»åçµå¼è½¦ï¼', |
| | | |
| | | -- ç¯æ¯å¢é |
| | | vehicle_mom_growth DOUBLE COMMENT 'è½¦è¾æ°-æ»è®¡ç¯æ¯å¢é', |
| | | tons_mom_growth DOUBLE COMMENT 'æ è®°å¨ä½-æ»è®¡ç¯æ¯å¢é', |
| | | freight_mom_growth DOUBLE COMMENT 'è´§è¿é-æ»è®¡ç¯æ¯å¢é', |
| | | turnover_mom_growth DOUBLE COMMENT 'è´§ç©å¨è½¬é-æ»è®¡ç¯æ¯å¢é', |
| | | avg_distance_mom_growth DOUBLE COMMENT '货车平åè¿è·-ç¯æ¯å¢é', |
| | | |
| | | -- 忝å¢é |
| | | vehicle_yoy_growth DOUBLE COMMENT 'è½¦è¾æ°-æ»è®¡åæ¯å¢é', |
| | | tons_yoy_growth DOUBLE COMMENT 'æ è®°å¨ä½-æ»è®¡åæ¯å¢é', |
| | | tractor_trailer_ratio DOUBLE COMMENT 'çµå¼è½¦æè½¦æ¯', |
| | | freight_yoy_growth DOUBLE COMMENT 'è´§è¿é-æ»è®¡åæ¯å¢é', |
| | | turnover_yoy_growth DOUBLE COMMENT 'è´§ç©å¨è½¬é-æ»è®¡åæ¯å¢é', |
| | | avg_distance_yoy_growth DOUBLE COMMENT '货车平åè¿è·-忝å¢é', |
| | | |
| | | -- å¡«æ¥ä¿¡æ¯ |
| | | unit_leader VARCHAR(50) COMMENT 'åä½è´è´£äºº', |
| | | stats_leader VARCHAR(50) COMMENT 'ç»è®¡è´è´£äºº', |
| | | contact_person VARCHAR(50) COMMENT '填表人', |
| | | contact_phone VARCHAR(20) COMMENT 'èç³»çµè¯', |
| | | report_date DATE COMMENT 'æ¥åºæ¥æ', |
| | | verify_explanation TEXT COMMENT 'æ ¸å®æ§åå è§£é', |
| | | report_notes TEXT COMMENT '䏿¥è¯´æ', |
| | | modify_record TEXT COMMENT 'ä¿®æ¹è®°å½', |
| | | |
| | | import_batch_id BIGINT COMMENT '导å
¥æ¹æ¬¡', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='交ä¼ç»H203-2表_é路货ç©è¿è¾æåº¦ç产æ
åµ'; |
| | | |
| | | -- è¿æ¿è½¦è¾æ°æ® |
| | | DROP TABLE IF EXISTS transport_auth_vehicle; |
| | | CREATE TABLE transport_auth_vehicle ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) COMMENT 'æ¥è¡¨æ', |
| | | enterprise_name VARCHAR(200) COMMENT 'ä¼ä¸åç§°', |
| | | tractor_count INT DEFAULT 0 COMMENT 'æ»çµå¼è½¦æ°', |
| | | trailer_count INT DEFAULT 0 COMMENT 'æ»æè½¦æ°', |
| | | other_count INT DEFAULT 0 COMMENT 'å
¶å®è½¦è¾æ°', |
| | | trailer_tons DOUBLE COMMENT 'æè½¦æ»å¨ä½(å¨)', |
| | | other_tons DOUBLE COMMENT 'å
¶å®è½¦è¾æ»å¨ä½(å¨)', |
| | | import_batch_id BIGINT COMMENT '导å
¥æ¹æ¬¡', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='è¿æ¿è½¦è¾æ°æ®'; |
| | | |
| | | -- 车è¾è½¨è¿¹éç¨æ°æ® |
| | | DROP TABLE IF EXISTS vehicle_track_mileage; |
| | | CREATE TABLE vehicle_track_mileage ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | enterprise_name VARCHAR(200) COMMENT 'ä¼ä¸åç§°', |
| | | monthly_mileage DOUBLE COMMENT 'æåº¦æ»éç¨(å
Ž)', |
| | | tracked_vehicles INT DEFAULT 0 COMMENT 'æéç¨çè½¦è¾æ°', |
| | | report_period VARCHAR(20) COMMENT 'æ¥è¡¨æ', |
| | | import_batch_id BIGINT COMMENT '导å
¥æ¹æ¬¡', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车è¾è½¨è¿¹éç¨æ°æ®'; |
| | | |
| | | -- è§ä¸è§ä¸æåè¿è¾éï¼ç¨æ·æå¨ä¸ä¼ ï¼ |
| | | DROP TABLE IF EXISTS scale_split_transport; |
| | | CREATE TABLE scale_split_transport ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ 2026-06', |
| | | period_type VARCHAR(10) NOT NULL COMMENT 'MONTH彿/CUMULATIVE累计', |
| | | region_name VARCHAR(50) COMMENT 'å¸å·åç§°', |
| | | |
| | | -- è§ä¸ |
| | | above_scale_freight DOUBLE COMMENT 'è§ä¸è´§è¿é', |
| | | above_scale_turnover DOUBLE COMMENT 'è§ä¸å¨è½¬é', |
| | | above_scale_rank INT COMMENT 'è§ä¸æå', |
| | | above_scale_yoy DOUBLE COMMENT 'è§ä¸åæ¯', |
| | | above_scale_yoy_rank INT COMMENT 'è§ä¸å¢éæå', |
| | | |
| | | -- è§ä¸ |
| | | below_scale_freight DOUBLE COMMENT 'è§ä¸è´§è¿é', |
| | | below_scale_turnover DOUBLE COMMENT 'è§ä¸å¨è½¬é', |
| | | below_scale_rank INT COMMENT 'è§ä¸æå', |
| | | below_scale_yoy DOUBLE COMMENT 'è§ä¸åæ¯', |
| | | below_scale_yoy_rank INT COMMENT 'è§ä¸å¢éæå', |
| | | |
| | | -- å计 |
| | | total_freight DOUBLE COMMENT 'è¿è¾éå计-è´§è¿é', |
| | | total_turnover DOUBLE COMMENT 'è¿è¾éå计-å¨è½¬é', |
| | | total_rank INT COMMENT 'å计æå', |
| | | total_yoy DOUBLE COMMENT 'åè®¡åæ¯', |
| | | total_yoy_rank INT COMMENT 'å计å¢éæå', |
| | | |
| | | import_batch_id BIGINT COMMENT '导å
¥æ¹æ¬¡', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='è§ä¸è§ä¸æåè¿è¾é'; |
| | | |
| | | -- 模æ¿_è´§è¿éå¨è½¬éï¼æ¯æå¯¼å
¥ï¼å·¦åä»å¹´1-Næï¼å³åå»å¹´1-12æï¼N=æ¥è¡¨ææä»½ï¼ |
| | | DROP TABLE IF EXISTS freight_turnover_import; |
| | | CREATE TABLE freight_turnover_import ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ 2026-06', |
| | | region_name VARCHAR(50) NOT NULL COMMENT 'å¸å·ææ¹åç', |
| | | |
| | | -- ä»å¹´1-12æè´§è¿éï¼å·¦åï¼ä»
1-Nææå¼ï¼ |
| | | freight_m01 DOUBLE COMMENT '1æè´§è¿é(ä¸å¨)', |
| | | freight_m02 DOUBLE COMMENT '2æè´§è¿é(ä¸å¨)', |
| | | freight_m03 DOUBLE COMMENT '3æè´§è¿é(ä¸å¨)', |
| | | freight_m04 DOUBLE COMMENT '4æè´§è¿é(ä¸å¨)', |
| | | freight_m05 DOUBLE COMMENT '5æè´§è¿é(ä¸å¨)', |
| | | freight_m06 DOUBLE COMMENT '6æè´§è¿é(ä¸å¨)', |
| | | freight_m07 DOUBLE COMMENT '7æè´§è¿é(ä¸å¨)', |
| | | freight_m08 DOUBLE COMMENT '8æè´§è¿é(ä¸å¨)', |
| | | freight_m09 DOUBLE COMMENT '9æè´§è¿é(ä¸å¨)', |
| | | freight_m10 DOUBLE COMMENT '10æè´§è¿é(ä¸å¨)', |
| | | freight_m11 DOUBLE COMMENT '11æè´§è¿é(ä¸å¨)', |
| | | freight_m12 DOUBLE COMMENT '12æè´§è¿é(ä¸å¨)', |
| | | |
| | | -- å»å¹´1-12æè´§è¿éï¼å³åï¼åºå®12个æï¼ç®åæ¯ç¨ï¼ |
| | | last_freight_m01 DOUBLE COMMENT 'å»å¹´1æè´§è¿é(ä¸å¨)', |
| | | last_freight_m02 DOUBLE COMMENT 'å»å¹´2æè´§è¿é(ä¸å¨)', |
| | | last_freight_m03 DOUBLE COMMENT 'å»å¹´3æè´§è¿é(ä¸å¨)', |
| | | last_freight_m04 DOUBLE COMMENT 'å»å¹´4æè´§è¿é(ä¸å¨)', |
| | | last_freight_m05 DOUBLE COMMENT 'å»å¹´5æè´§è¿é(ä¸å¨)', |
| | | last_freight_m06 DOUBLE COMMENT 'å»å¹´6æè´§è¿é(ä¸å¨)', |
| | | last_freight_m07 DOUBLE COMMENT 'å»å¹´7æè´§è¿é(ä¸å¨)', |
| | | last_freight_m08 DOUBLE COMMENT 'å»å¹´8æè´§è¿é(ä¸å¨)', |
| | | last_freight_m09 DOUBLE COMMENT 'å»å¹´9æè´§è¿é(ä¸å¨)', |
| | | last_freight_m10 DOUBLE COMMENT 'å»å¹´10æè´§è¿é(ä¸å¨)', |
| | | last_freight_m11 DOUBLE COMMENT 'å»å¹´11æè´§è¿é(ä¸å¨)', |
| | | last_freight_m12 DOUBLE COMMENT 'å»å¹´12æè´§è¿é(ä¸å¨)', |
| | | |
| | | -- ä»å¹´1-12æå¨è½¬éï¼å·¦åï¼ä»
1-Nææå¼ï¼å¯¼å
¥åæ¡£ï¼æ¥è¡¨ç¨æåè¡¨ï¼ |
| | | turnover_m01 DOUBLE COMMENT '1æå¨è½¬é(ä¸å¨å
Ž)', |
| | | turnover_m02 DOUBLE COMMENT '2æå¨è½¬é(ä¸å¨å
Ž)', |
| | | turnover_m03 DOUBLE COMMENT '3æå¨è½¬é(ä¸å¨å
Ž)', |
| | | turnover_m04 DOUBLE COMMENT '4æå¨è½¬é(ä¸å¨å
Ž)', |
| | | turnover_m05 DOUBLE COMMENT '5æå¨è½¬é(ä¸å¨å
Ž)', |
| | | turnover_m06 DOUBLE COMMENT '6æå¨è½¬é(ä¸å¨å
Ž)', |
| | | turnover_m07 DOUBLE COMMENT '7æå¨è½¬é(ä¸å¨å
Ž)', |
| | | turnover_m08 DOUBLE COMMENT '8æå¨è½¬é(ä¸å¨å
Ž)', |
| | | turnover_m09 DOUBLE COMMENT '9æå¨è½¬é(ä¸å¨å
Ž)', |
| | | turnover_m10 DOUBLE COMMENT '10æå¨è½¬é(ä¸å¨å
Ž)', |
| | | turnover_m11 DOUBLE COMMENT '11æå¨è½¬é(ä¸å¨å
Ž)', |
| | | turnover_m12 DOUBLE COMMENT '12æå¨è½¬é(ä¸å¨å
Ž)', |
| | | |
| | | -- å»å¹´1-12æå¨è½¬éï¼å³åï¼åºå®12个æï¼ |
| | | last_turnover_m01 DOUBLE COMMENT 'å»å¹´1æå¨è½¬é(ä¸å¨å
Ž)', |
| | | last_turnover_m02 DOUBLE COMMENT 'å»å¹´2æå¨è½¬é(ä¸å¨å
Ž)', |
| | | last_turnover_m03 DOUBLE COMMENT 'å»å¹´3æå¨è½¬é(ä¸å¨å
Ž)', |
| | | last_turnover_m04 DOUBLE COMMENT 'å»å¹´4æå¨è½¬é(ä¸å¨å
Ž)', |
| | | last_turnover_m05 DOUBLE COMMENT 'å»å¹´5æå¨è½¬é(ä¸å¨å
Ž)', |
| | | last_turnover_m06 DOUBLE COMMENT 'å»å¹´6æå¨è½¬é(ä¸å¨å
Ž)', |
| | | last_turnover_m07 DOUBLE COMMENT 'å»å¹´7æå¨è½¬é(ä¸å¨å
Ž)', |
| | | last_turnover_m08 DOUBLE COMMENT 'å»å¹´8æå¨è½¬é(ä¸å¨å
Ž)', |
| | | last_turnover_m09 DOUBLE COMMENT 'å»å¹´9æå¨è½¬é(ä¸å¨å
Ž)', |
| | | last_turnover_m10 DOUBLE COMMENT 'å»å¹´10æå¨è½¬é(ä¸å¨å
Ž)', |
| | | last_turnover_m11 DOUBLE COMMENT 'å»å¹´11æå¨è½¬é(ä¸å¨å
Ž)', |
| | | last_turnover_m12 DOUBLE COMMENT 'å»å¹´12æå¨è½¬é(ä¸å¨å
Ž)', |
| | | |
| | | import_batch_id BIGINT COMMENT '导å
¥æ¹æ¬¡', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='模æ¿_è´§è¿éå¨è½¬é导å
¥'; |
| | | |
| | | -- ============================================ |
| | | -- ä¸ãå®¡æ ¸è§å模å |
| | | -- ============================================ |
| | | |
| | | DROP TABLE IF EXISTS audit_rule; |
| | | CREATE TABLE audit_rule ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | rule_name VARCHAR(100) NOT NULL COMMENT 'è§ååç§°', |
| | | rule_code VARCHAR(50) NOT NULL COMMENT 'è§åç¼ç ', |
| | | description TEXT COMMENT 'è§åæè¿°', |
| | | check_field VARCHAR(200) COMMENT 'æ£æ¥å段', |
| | | compare_type VARCHAR(20) COMMENT 'DIFF/RATIO/MOM/YOY/NULL_CHECK/RANGE', |
| | | threshold VARCHAR(50) COMMENT 'éå¼', |
| | | alert_level VARCHAR(10) DEFAULT 'YELLOW' COMMENT 'RED/YELLOW', |
| | | report_type VARCHAR(50) DEFAULT 'H2032' COMMENT 'éç¨æ¥è¡¨è¡¨å·', |
| | | is_enabled TINYINT DEFAULT 1, |
| | | llm_raw_input TEXT COMMENT 'LLMç¿»è¯åå§è¾å
¥', |
| | | sort_order INT DEFAULT 0, |
| | | created_by BIGINT, |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | -- é¢ç½®6æ¡å®¡æ ¸è§åï¼Demoï¼ |
| | | INSERT INTO audit_rule (rule_name, rule_code, description, check_field, compare_type, threshold, alert_level, report_type, is_enabled, sort_order) VALUES |
| | | ('è½¦è¾æ°è¿æ¿å¯¹æ¯', 'RULE_001', 'ä¼ä¸ä¸æ¥è½¦è¾æ°ä¸è¿æ¿æ°æ®å¯¹æ¯ï¼å·®è·>5å°éæ ¸å®', 'vehicleTotal', 'DIFF', '5', 'RED', 'H2032', 1, 1), |
| | | ('å¨ä½è¿æ¿å¯¹æ¯', 'RULE_002', 'ä¼ä¸ä¸æ¥å¨ä½ä¸è¿æ¿æ°æ®å¯¹æ¯ï¼å·®è·>50éæ ¸å®', 'tonsTotal', 'DIFF', '50', 'RED', 'H2032', 1, 2), |
| | | ('å¨è½¬é轨迹对æ¯', 'RULE_003', '䏿¥å¨è½¬éä¸è½¨è¿¹æµç®å¨è½¬é对æ¯ï¼é«åº30%éæ ¸å®', 'turnoverTotal,trackMileage', 'RATIO', '1.3', 'RED', 'H2032', 1, 3), |
| | | ('è´§è¿ç±»åç¯æ¯', 'RULE_004', '䏿æ ï¼æ¬ææ°å¢ï¼æéï¼ä¸ææï¼æ¬ææ ï¼æéï¼è´§è¿ç±»åç¯æ¯ï¼', 'freightContainer,freightCoal,freightOilGas,freightCrudeOil,freightMetalOre,freightIronOre,freightBuilding,freightGrain', 'MOM', '0', 'YELLOW', 'H2032', 1, 4), |
| | | ('éè£
ç®±å¼å¸¸', 'RULE_005', 'æ éè£
ç®±è½¦ä½æéè£
箱货è¿ééæ ¸å®', 'freightContainer,vehicleContainer', 'NULL_CHECK', '0', 'RED', 'H2032', 1, 5), |
| | | ('æ´è½¦å¨ä½èå´', 'RULE_006', 'æ´è½¦å¨ä½ä¸å¨4-40å¨ä¹é´', 'tonsWhole', 'OUT_RANGE', '4,40', 'YELLOW', 'H2032', 1, 6); |
| | | |
| | | -- ============================================ |
| | | -- åãå®¡æ ¸ç»ææ¨¡å |
| | | -- ============================================ |
| | | |
| | | DROP TABLE IF EXISTS audit_result; |
| | | CREATE TABLE audit_result ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | rule_id BIGINT NOT NULL COMMENT 'FK -> audit_rule', |
| | | report_id BIGINT NOT NULL COMMENT 'FK -> h2032_enterprise_monthly', |
| | | enterprise_code VARCHAR(50) COMMENT 'ä¼ä¸ä»£ç ', |
| | | report_period VARCHAR(20) COMMENT 'æ¥è¡¨æ', |
| | | actual_value VARCHAR(200) COMMENT 'å®é
å¼', |
| | | threshold_value VARCHAR(200) COMMENT 'éå¼', |
| | | deviation VARCHAR(200) COMMENT 'åå·®', |
| | | status VARCHAR(20) DEFAULT 'PENDING' COMMENT 'PENDING/CONFIRMED/IGNORED', |
| | | reviewer_id BIGINT COMMENT 'å®¡æ ¸äºº', |
| | | review_comment TEXT COMMENT 'å®¡æ ¸æè§', |
| | | reviewed_at DATETIME, |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | DROP TABLE IF EXISTS audit_explanation; |
| | | CREATE TABLE audit_explanation ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | audit_result_id BIGINT NOT NULL COMMENT 'FK -> audit_result', |
| | | explanation_text TEXT COMMENT '䏿¥äººè§£é', |
| | | llm_suggestion TEXT COMMENT 'LLM建议', |
| | | llm_confidence DECIMAL(5,2) COMMENT '置信度', |
| | | llm_status VARCHAR(20) DEFAULT 'PENDING' COMMENT 'PENDING/PROCESSED/FAILED' |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | -- ============================================ |
| | | -- äºãç³»ç»æ¥å¿ä¸å¯¼å
¥æ¹æ¬¡ |
| | | -- ============================================ |
| | | |
| | | DROP TABLE IF EXISTS sys_operation_log; |
| | | CREATE TABLE sys_operation_log ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | user_id BIGINT COMMENT 'æä½äºº', |
| | | module VARCHAR(50) COMMENT '模å', |
| | | action VARCHAR(50) COMMENT 'æä½', |
| | | detail TEXT COMMENT '详æ
JSON', |
| | | ip_address VARCHAR(50) COMMENT 'IPå°å', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | DROP TABLE IF EXISTS import_batch; |
| | | CREATE TABLE import_batch ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | file_name VARCHAR(200) COMMENT 'æä»¶å', |
| | | import_type VARCHAR(50) COMMENT 'H2032/TRANSPORT_AUTH/TRACK_MILEAGE/SCALE_SPLIT', |
| | | report_period VARCHAR(20) COMMENT 'æ¥è¡¨æ', |
| | | total_rows INT DEFAULT 0, |
| | | success_rows INT DEFAULT 0, |
| | | fail_rows INT DEFAULT 0, |
| | | error_detail TEXT COMMENT 'é误详æ
', |
| | | created_by BIGINT COMMENT '导å
¥äºº', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | -- ============================================ |
| | | -- å
ãLLMè±ææ å° |
| | | -- ============================================ |
| | | |
| | | DROP TABLE IF EXISTS llm_desensitize_map; |
| | | CREATE TABLE llm_desensitize_map ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | original_value VARCHAR(200) COMMENT 'åå§å¼', |
| | | masked_value VARCHAR(200) COMMENT 'è±æå¼', |
| | | mask_type VARCHAR(20) COMMENT 'ENTERPRISE/REGION', |
| | | call_id VARCHAR(50) COMMENT 'è°ç¨ID' |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | -- ============================================ |
| | | -- ä¸ãé¢ç½®æ°æ® |
| | | -- ============================================ |
| | | |
| | | -- é¢ç½®é¨é¨ï¼æ¹åçï¼ |
| | | INSERT INTO sys_dept (dept_name, dept_code, dept_level) VALUES ('æ¹åç交éè¿è¾å
', '420000', 1); |
| | | |
| | | -- é¢ç½®è§è² |
| | | INSERT INTO sys_role (role_name, role_code, role_type, role_level, is_mutex) VALUES |
| | | ('ç³»ç»ç®¡çå', 'SYS_ADMIN', 'SYSTEM', 0, 1), |
| | | ('å®å
¨ä¿å¯å', 'SEC_OFFICER', 'SYSTEM', 0, 1), |
| | | ('å®å
¨å®¡è®¡å', 'AUDITOR', 'SYSTEM', 0, 1), |
| | | ('çå
ä¸å¡å', 'PROVINCE', 'BUSINESS', 1, 0); |
| | | |
| | | -- é¢ç½®ç®¡çåè´¦å· admin / admin123 |
| | | INSERT INTO sys_user (username, password, real_name, dept_id) VALUES |
| | | ('admin', '$2b$10$aqN02Pwvzzuta/66V9MpGerfUH6favtq8av3cc/gueg6lQ9E4CH8u', 'ç³»ç»ç®¡çå', 1); |
| | | |
| | | INSERT INTO sys_user_role (user_id, role_id) VALUES (1, 1); |
| | | |
| | | |
| | | -- ============================================ |
| | | -- å
«ãå
¬è·¯æ
客è¿è¾ï¼äº¤ä¼ç»H203-1ï¼æ©å±æ¨¡å |
| | | -- ============================================ |
| | | |
| | | DROP TABLE IF EXISTS h2031_enterprise_monthly; |
| | | CREATE TABLE h2031_enterprise_monthly ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ 2026-06', |
| | | region_code VARCHAR(20) COMMENT 'æå±å°åº', |
| | | enterprise_code VARCHAR(50) COMMENT 'ä¼ä¸ä»£ç ', |
| | | enterprise_name VARCHAR(200) COMMENT 'ä¼ä¸åç§°', |
| | | unified_credit_code VARCHAR(50) COMMENT 'ç»ä¸ç¤¾ä¼ä¿¡ç¨ä»£ç ', |
| | | report_unit VARCHAR(200) COMMENT 'å¡«æ¥åä½', |
| | | vehicle_total INT COMMENT '客è¿è½¦è¾_æ»è®¡', |
| | | seat_total INT COMMENT 'æ ¸å®è½½å®¢ä½_æ»è®¡', |
| | | vehicle_schedule INT COMMENT 'è½¦è¾æ°_客è¿ç车ï¼å«å®çº¿æ
游客è¿å®¢è½¦ï¼', |
| | | seat_schedule INT COMMENT 'æ ¸å®è½½å®¢ä½_客è¿ç车ï¼å«å®çº¿æ
游客è¿å®¢è½¦ï¼', |
| | | vehicle_charter INT COMMENT 'è½¦è¾æ°_客è¿å
车ï¼å«éå®çº¿æ
游客è¿å®¢è½¦ï¼', |
| | | seat_charter INT COMMENT 'æ ¸å®è½½å®¢ä½_客è¿å
车ï¼å«éå®çº¿æ
游客è¿å®¢è½¦ï¼', |
| | | passenger_total DOUBLE COMMENT '客è¿é_æ»è®¡ï¼äººï¼', |
| | | turnover_total DOUBLE COMMENT 'æ
客å¨è½¬é_æ»è®¡ï¼äººå
¬éï¼', |
| | | passenger_class1 DOUBLE COMMENT '客è¿é_ä¸ç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼', |
| | | turnover_class1 DOUBLE COMMENT 'æ
客å¨è½¬é_ä¸ç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼', |
| | | passenger_class2 DOUBLE COMMENT '客è¿é_äºç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼', |
| | | turnover_class2 DOUBLE COMMENT 'æ
客å¨è½¬é_äºç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼', |
| | | passenger_class3 DOUBLE COMMENT '客è¿é_ä¸ç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼', |
| | | turnover_class3 DOUBLE COMMENT 'æ
客å¨è½¬é_ä¸ç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼', |
| | | passenger_class4 DOUBLE COMMENT '客è¿é_å类客è¿ç线ï¼å«å®çº¿æ
游ï¼', |
| | | turnover_class4 DOUBLE COMMENT 'æ
客å¨è½¬é_å类客è¿ç线ï¼å«å®çº¿æ
游ï¼', |
| | | passenger_charter DOUBLE COMMENT '客è¿é_客è¿å
车ï¼å«éå®çº¿æ
游ï¼', |
| | | turnover_charter DOUBLE COMMENT 'æ
客å¨è½¬é_客è¿å
车ï¼å«éå®çº¿æ
游ï¼', |
| | | avg_distance_total DOUBLE COMMENT '客è¿å¹³åè¿è·ï¼å
¬éï¼', |
| | | avg_distance_class1 DOUBLE COMMENT 'ä¸ç±»å®¢è¿ç线平åè¿è·ï¼å
¬éï¼', |
| | | avg_distance_class2 DOUBLE COMMENT 'äºç±»å®¢è¿ç线平åè¿è·ï¼å
¬éï¼', |
| | | avg_distance_class3 DOUBLE COMMENT 'ä¸ç±»å®¢è¿ç线平åè¿è·ï¼å
¬éï¼', |
| | | avg_distance_class4 DOUBLE COMMENT 'å类客è¿ç线平åè¿è·ï¼å
¬éï¼', |
| | | avg_distance_charter DOUBLE COMMENT 'æ
游ãå
车平åè¿è·ï¼å
¬éï¼', |
| | | verify_explanation TEXT COMMENT 'æ ¸å®æ§åå è§£é', |
| | | import_batch_id BIGINT COMMENT '导å
¥æ¹æ¬¡', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='交ä¼ç»H203-1表_å
¬è·¯æ
客è¿è¾æåº¦ç产æ
åµ'; |
| | | |
| | | DROP TABLE IF EXISTS passenger_auth_vehicle; |
| | | CREATE TABLE passenger_auth_vehicle ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) COMMENT 'æ¥è¡¨æ', |
| | | enterprise_code VARCHAR(50) COMMENT 'ä¼ä¸ä»£ç ', |
| | | enterprise_name VARCHAR(200) COMMENT 'ä¼ä¸åç§°', |
| | | vehicle_count INT DEFAULT 0 COMMENT 'è½¦è¾æ°', |
| | | seat_count INT DEFAULT 0 COMMENT 'è½½å®¢ä½æ°', |
| | | import_batch_id BIGINT COMMENT '导å
¥æ¹æ¬¡', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°æ®'; |
| | | |
| | | -- H203-1 é¢ç½®å®¡æ ¸è§åï¼4ç»8æ¡ï¼report_type=H2031ï¼ |
| | | INSERT INTO audit_rule (rule_name, rule_code, description, check_field, compare_type, threshold, alert_level, report_type, is_enabled, sort_order) VALUES |
| | | ('è½¦è¾æ°è¿æ¿å¯¹æ¯(æ
客)', 'RULE_P001', '客è¿è½¦è¾æ°ä¸è¿æ¿è½¦è¾æ°å¯¹æ¯ï¼å·®è·>3å°éæ ¸å®', 'vehicleTotal', 'DIFF', '3', 'RED', 'H2031', 1, 21), |
| | | ('è½½å®¢ä½æ°è¿æ¿å¯¹æ¯(æ
客)', 'RULE_P002', 'æ ¸å®è½½å®¢ä½æ°ä¸è¿æ¿è½½å®¢ä½æ°å¯¹æ¯ï¼å·®è·>50éæ ¸å®', 'seatTotal', 'DIFF', '50', 'RED', 'H2031', 1, 22), |
| | | ('客è¿éç¯æ¯=0æ ¸å®', 'RULE_P003', 'ä¸ç±»/äºç±»/ä¸ç±»/åç±»/å
车客è¿éç¯æ¯ä¸ä¸ªæçäº0éæ ¸å®', 'passengerClass1,passengerClass2,passengerClass3,passengerClass4,passengerCharter', 'MOM_EQ_ZERO', '0', 'YELLOW', 'H2031', 1, 23), |
| | | ('æ
客å¨è½¬éç¯æ¯=0æ ¸å®', 'RULE_P004', 'ä¸ç±»/äºç±»/ä¸ç±»/åç±»/å
车æ
客å¨è½¬éç¯æ¯ä¸ä¸ªæçäº0éæ ¸å®', 'turnoverClass1,turnoverClass2,turnoverClass3,turnoverClass4,turnoverCharter', 'MOM_EQ_ZERO', '0', 'YELLOW', 'H2031', 1, 24), |
| | | ('å¹³åè¿è·ç¯æ¯è¶
±20%æ ¸å®', 'RULE_P005', 'ä¸ç±»/äºç±»/ä¸ç±»/å类平åè¿è·ç¯æ¯ä¸ä¸ªæè¶
è¿Â±20%éæ ¸å®', 'avgDistanceClass1,avgDistanceClass2,avgDistanceClass3,avgDistanceClass4', 'MOM_PCT', '20', 'YELLOW', 'H2031', 1, 25), |
| | | ('å
车平åè¿è·çäºä¸ææ ¸å®', 'RULE_P006', 'æ
游å
车平åè¿è·çäºä¸ä¸ªæéæ ¸å®', 'avgDistanceCharter', 'MOM_EQ', '0', 'YELLOW', 'H2031', 1, 26), |
| | | ('æ ç线æç线客è¿éæ ¸å®', 'RULE_P007', 'æ 客è¿ç车ï¼ççº¿è½¦è¾æ°ä¸º0ï¼ä½æç线客è¿ééæ ¸å®', 'vehicleSchedule,passengerClass1,passengerClass2,passengerClass3,passengerClass4', 'EXISTENCE', '0', 'RED', 'H2031', 1, 27), |
| | | ('æ å
车æå
车客è¿éæ ¸å®', 'RULE_P008', 'æ 客è¿å
车ï¼å
è½¦è½¦è¾æ°ä¸º0ï¼ä½æå
车客è¿ééæ ¸å®', 'vehicleCharter,passengerCharter', 'EXISTENCE', '0', 'RED', 'H2031', 1, 28); |
| | | |
| | | |
| | | -- ============================================ |
| | | -- ä¹ãé路货è¿è½æºæ¶èï¼äº¤ä¼ç»H204ï¼æ©å±æ¨¡å |
| | | -- ============================================ |
| | | |
| | | -- é路货è¿è½¦è¾è½æºæ¶èæ
åµï¼è½¦è¾ç²åº¦å£æ¥ï¼ |
| | | DROP TABLE IF EXISTS h204_vehicle_quarterly; |
| | | CREATE TABLE h204_vehicle_quarterly ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ 2026-06ï¼å£åº¦æ«æï¼', |
| | | region_code VARCHAR(20) COMMENT 'æå±å°åº', |
| | | enterprise_code VARCHAR(50) COMMENT 'ä¼ä¸ä»£ç ', |
| | | enterprise_name VARCHAR(200) COMMENT 'ä¼ä¸åç§°', |
| | | plate_no VARCHAR(30) COMMENT '车çå·', |
| | | vehicle_type_code VARCHAR(10) COMMENT '车è¾ç±»å代ç ', |
| | | vehicle_type VARCHAR(50) COMMENT '车è¾ç±»åï¼ä¸æï¼', |
| | | fuel_type_code VARCHAR(10) COMMENT 'çæç±»å代ç ', |
| | | fuel_type VARCHAR(50) COMMENT 'çæç±»åï¼ä¸æï¼', |
| | | manufacture_year INT COMMENT 'åºå年份', |
| | | marked_tonnage DOUBLE COMMENT 'æ è®°å¨ä½ï¼å¨ï¼', |
| | | total_mileage DOUBLE COMMENT 'æ»è¡ç¨ï¼å
¬éï¼', |
| | | loaded_mileage DOUBLE COMMENT '载货éç¨ï¼å
¬éï¼', |
| | | empty_mileage DOUBLE COMMENT '空驶éç¨ï¼å
¬éï¼', |
| | | freight DOUBLE COMMENT 'è´§è¿éï¼å¨ï¼', |
| | | turnover DOUBLE COMMENT 'è´§ç©å¨è½¬éï¼å¨å
¬éï¼', |
| | | trip_count INT COMMENT 'è¶æ¬¡æ°ï¼ä¸ªï¼', |
| | | fuel_unit VARCHAR(20) COMMENT 'çæ1计éåä½', |
| | | fuel_consumption DOUBLE COMMENT 'çæ1æ¶èé', |
| | | avg_distance DOUBLE COMMENT 'å¹³åè¿è·ï¼å
¬éï¼', |
| | | avg_loaded_tonnage DOUBLE COMMENT 'å¹³å载货å¨ä½ï¼å¨ï¼', |
| | | avg_trip_mileage DOUBLE COMMENT 'åè¶å¹³åéç¨ï¼å
¬éï¼', |
| | | fuel_per_100km DOUBLE COMMENT 'ç¾å
¬éåè', |
| | | turnover_fuel DOUBLE COMMENT 'å¨è½¬éåè', |
| | | import_batch_id BIGINT COMMENT '导å
¥æ¹æ¬¡', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | KEY idx_period_plate (report_period, plate_no), |
| | | KEY idx_period_ent (report_period, enterprise_code) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='交ä¼ç»H204表_é路货è¿è½¦è¾è½æºæ¶èæ
åµï¼è½¦è¾ç²åº¦ï¼'; |
| | | |
| | | -- é路货è¿è½è_è¿æ¿è½¦è¾ä¿¡æ¯ï¼è½¦çç²åº¦ï¼ |
| | | DROP TABLE IF EXISTS h204_auth_vehicle; |
| | | CREATE TABLE h204_auth_vehicle ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ', |
| | | plate_no VARCHAR(30) NOT NULL COMMENT '车çå·', |
| | | vehicle_type VARCHAR(50) COMMENT '车è¾ç±»å', |
| | | fuel_type VARCHAR(50) COMMENT 'çæç±»å', |
| | | marked_tonnage DOUBLE COMMENT 'æ è®°å¨ä½ï¼å¨ï¼', |
| | | traction_quality DOUBLE COMMENT 'åçµå¼è´¨éï¼çµå¼è½¦æ è®°å¨ä½ï¼', |
| | | import_batch_id BIGINT COMMENT '导å
¥æ¹æ¬¡', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | KEY idx_period_plate (report_period, plate_no) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='é路货è¿è½è_è¿æ¿è½¦è¾ä¿¡æ¯ï¼è½¦çç²åº¦ï¼'; |
| | | |
| | | -- H204 é¢ç½®å®¡æ ¸è§åï¼4æ¡ï¼report_type=H204ï¼ |
| | | INSERT INTO audit_rule (rule_name, rule_code, description, check_field, compare_type, threshold, alert_level, report_type, is_enabled, sort_order) VALUES |
| | | ('车è¾ç±»åä¸è¿æ¿å¯¹æ¯', 'RULE_E001', 'è½èæç»è½¦è¾ç±»åä¸è¿æ¿è½¦è¾ä¿¡æ¯ä¸ä¸è´éæ ¸å®', 'vehicleType', 'CROSS_DIFF', '0', 'RED', 'H204', 1, 31), |
| | | ('çæç±»åä¸è¿æ¿å¯¹æ¯', 'RULE_E002', 'è½èæç»çæç±»åä¸è¿æ¿è½¦è¾ä¿¡æ¯ä¸ä¸è´éæ ¸å®', 'fuelType', 'CROSS_DIFF', '0', 'RED', 'H204', 1, 32), |
| | | ('æ è®°å¨ä½ä¸è¿æ¿å¯¹æ¯', 'RULE_E003', 'è½èæç»æ è®°å¨ä½ä¸è¿æ¿å¯¹æ¯ï¼å·®è·>0.05å¨éæ ¸å®ï¼çµå¼è½¦å¯¹æ¯åçµå¼è´¨éï¼', 'markedTonnage', 'CROSS_DIFF', '0.05', 'YELLOW', 'H204', 1, 33), |
| | | ('è¿æ¿è½¦è¾ä¿¡æ¯ç¼ºå¤±', 'RULE_E004', 'è½èæç»è½¦çå·å¨è¿æ¿è½¦è¾ä¿¡æ¯ä¸ä¸åå¨éæ ¸å®', 'plateNo', 'EXISTENCE_AUTH', '0', 'RED', 'H204', 1, 34), |
| | | ('载货éç¨å æ¯ä¸åè¶éç¨æ¯æ ¸å®', 'RULE_E005', '载货éç¨/æ»è¡ç¨ ä¸ å¹³åè¿è·/åè¶å¹³åéç¨ ç¸å·®Â±10%éæ ¸å®', 'loadedMileage,totalMileage,avgDistance,avgTripMileage', 'LOADED_RATIO_DIFF', '0.1', 'YELLOW', 'H204', 1, 35), |
| | | ('ç¾å
¬éåèè¶
èå´æ ¸å®', 'RULE_E006', 'ç¾å
¬éåèå离åçæç±»åä¸ä½æ°è¶
è¿50%éæ ¸å®', 'fuelPer100km,fuelTypeCode', 'FUEL_RANGE', '0.5', 'YELLOW', 'H204', 1, 36), |
| | | ('ç¾å
¬éåèç¯æ¯ååæ ¸å®', 'RULE_E007', 'åä¸è¾è½¦ç¾å
¬éåèç¯æ¯ååè¶
è¿Â±50%éæ ¸å®', 'fuelPer100km,plateNo', 'FUEL_MOM', '0.5', 'YELLOW', 'H204', 1, 37), |
| | | ('è½èéä¸è´§è¿ææ¥å¯¹æ¯æ ¸å®', 'RULE_E008', 'ä¼ä¸è½èè´§è¿é/å¨è½¬éä¹å大äºè´§è¿ææ¥50%éæ ¸å®', 'freight,turnover,enterpriseName', 'FREIGHT_VS_H2032', '0.5', 'RED', 'H204', 1, 38), |
| | | ('å¹³åè¿è·ä¸è´§è¿ææ¥å¯¹æ¯æ ¸å®', 'RULE_E009', '车è¾å¹³åè¿è·ä¸è´§è¿ææ¥å¹³åè¿è·ç¸å·®Â±60%éæ ¸å®', 'avgDistance,enterpriseName', 'AVG_DIST_VS_H2032', '0.6', 'YELLOW', 'H204', 1, 39), |
| | | ('å¹³å载货å¨ä½ä¸æ è®°å¨ä½æ¯å¼æ ¸å®', 'RULE_E010', 'å¹³å载货å¨ä½/æ è®°å¨ä½ å°äº0.5æå¤§äº1éæ ¸å®', 'avgLoadedTonnage,markedTonnage', 'LOADED_TON_RATIO', '0.5,1', 'YELLOW', 'H204', 1, 40); |
| | | |
| | | -- ============================================ |
| | | -- åãæèµæ¨¡åï¼å®¢è¿ç«åº/ç©æµååºï¼æ©å± |
| | | -- ============================================ |
| | | |
| | | -- æèµé¡¹ç®ä¸»æ¡£ï¼æ 项ç®å+ç±»å« å¯ä¸ï¼ |
| | | DROP TABLE IF EXISTS investment_project; |
| | | CREATE TABLE investment_project ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | project_name VARCHAR(200) NOT NULL COMMENT '项ç®åç§°', |
| | | category VARCHAR(20) NOT NULL COMMENT 'ç±»å«ï¼å®¢è¿ç«åº/ç©æµååº', |
| | | city VARCHAR(50) COMMENT 'å¸å·', |
| | | county VARCHAR(100) COMMENT 'å¿ï¼å¸ãåºï¼', |
| | | builder_name VARCHAR(200) COMMENT 'æèµå»ºè®¾åä½', |
| | | construct_nature VARCHAR(50) COMMENT '建设æ§è´¨', |
| | | start_time VARCHAR(20) COMMENT 'å¼å·¥æ¶é´', |
| | | end_time VARCHAR(20) COMMENT '建æ/竣工æ¶é´', |
| | | total_investment DOUBLE COMMENT 'è®¡åæ»æèµï¼ä¸å
ï¼', |
| | | approval_gk VARCHAR(500) COMMENT '工坿¹å¤', |
| | | approval_cs VARCHAR(500) COMMENT 'å设æ¹å¤', |
| | | building_area DOUBLE COMMENT '建çé¢ç§¯ï¼å¹³æ¹ç±³ï¼', |
| | | is_billion TINYINT DEFAULT 0 COMMENT '亿å
æèµé¡¹ç®æ è®°', |
| | | is_county TINYINT DEFAULT 0 COMMENT 'ç»æµå¼ºå¿é¡¹ç®æ è®°', |
| | | is_five_year TINYINT DEFAULT 0 COMMENT 'åäºäºéç¹ç©æµé¡¹ç®æ è®°', |
| | | source VARCHAR(50) COMMENT 'æ°æ®æ¥æº', |
| | | import_batch_id BIGINT COMMENT '导å
¥æ¹æ¬¡', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | UNIQUE KEY uk_name_category (project_name, category) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='æèµé¡¹ç®ä¸»æ¡£'; |
| | | |
| | | -- æèµé¡¹ç®æåº¦å®ææ
åµ |
| | | DROP TABLE IF EXISTS investment_monthly; |
| | | CREATE TABLE investment_monthly ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ 2026-07', |
| | | project_id BIGINT NOT NULL COMMENT '项ç®ä¸»æ¡£ID', |
| | | year_plan DOUBLE COMMENT 'æ¬å¹´è®¡åæèµï¼ä¸å
ï¼', |
| | | year_cum DOUBLE COMMENT 'èªå¹´åç´¯è®¡å®ææèµï¼ä¸å
ï¼', |
| | | month_done DOUBLE COMMENT 'å
¶ä¸ï¼æ¬æå®ææèµï¼ä¸å
ï¼', |
| | | start_cum DOUBLE COMMENT 'èªå¼å§å»ºè®¾ç´¯è®¡å®ææèµï¼ä¸å
ï¼', |
| | | progress_stage VARCHAR(100) COMMENT 'å»ºè®¾é¶æ®µ', |
| | | progress_desc VARCHAR(2000) COMMENT '形象è¿åº¦', |
| | | building_area DOUBLE COMMENT '建çé¢ç§¯ï¼å¹³æ¹ç±³ï¼', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | KEY idx_period (report_period), |
| | | KEY idx_project (project_id) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='æèµé¡¹ç®æåº¦å®ææ
åµ'; |
| | | |
| | | -- æèµç³»ç»æ¥è¯¢å¯¼åºï¼å®¡æ ¸æ¯å¯¹åºåï¼ |
| | | DROP TABLE IF EXISTS investment_system; |
| | | CREATE TABLE investment_system ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ 2026-07', |
| | | project_name VARCHAR(200) NOT NULL COMMENT '项ç®åç§°ï¼åä½ï¼', |
| | | total_investment DOUBLE COMMENT 'è®¡åæ»æèµï¼ä¸å
ï¼', |
| | | start_cum DOUBLE COMMENT 'èªå¼å§å»ºè®¾è³æ¬æç´¯è®¡å®ææèµï¼ä¸å
ï¼', |
| | | year_plan DOUBLE COMMENT 'æ¬å¹´è®¡åæèµï¼ä¸å
ï¼', |
| | | year_cum DOUBLE COMMENT 'èªå¹´åç´¯è®¡å®ææèµï¼ä¸å
ï¼', |
| | | month_done DOUBLE COMMENT '彿宿æèµï¼ä¸å
ï¼', |
| | | import_batch_id BIGINT COMMENT '导å
¥æ¹æ¬¡', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | KEY idx_period (report_period) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='æèµç³»ç»æ¥è¯¢å¯¼åºï¼å®¡æ ¸æ¯å¯¹åºåï¼'; |
| | | |
| | | -- INVEST é¢ç½®å®¡æ ¸è§åï¼4æ¡ï¼report_type=INVESTï¼ |
| | | INSERT INTO audit_rule (rule_name, rule_code, description, check_field, compare_type, threshold, alert_level, report_type, is_enabled, sort_order) VALUES |
| | | ('èªå¼å§ç´¯è®¡ä¸æèµç³»ç»å¯¹æ¯', 'RULE_I001', 'èªå¼å§å»ºè®¾ç´¯è®¡å®ææèµä¸æèµç³»ç»å¯¼åºå·®å¼è¶
éå¼éæ ¸å®', 'startCum', 'CROSS_DIFF', '100|5', 'YELLOW', 'INVEST', 1, 41), |
| | | ('èªå¹´åç´¯è®¡ä¸æèµç³»ç»å¯¹æ¯', 'RULE_I002', 'èªå¹´åç´¯è®¡å®ææèµä¸æèµç³»ç»å¯¼åºå·®å¼è¶
éå¼éæ ¸å®', 'yearCum', 'CROSS_DIFF', '50|5', 'YELLOW', 'INVEST', 1, 42), |
| | | ('æ¬æå®æä¸æèµç³»ç»å¯¹æ¯', 'RULE_I003', 'æ¬æå®ææèµä¸æèµç³»ç»å¯¼åºå·®å¼è¶
éå¼éæ ¸å®', 'monthDone', 'CROSS_DIFF', '30|30', 'YELLOW', 'INVEST', 1, 43), |
| | | ('æèµç³»ç»é¡¹ç®ç¼ºå¤±æ ¸å®', 'RULE_I004', 'æ±æ»å¤§è¡¨é¡¹ç®å¨æèµç³»ç»å¯¼åºä¸ä¸åå¨éæ ¸å®', 'projectName', 'EXISTENCE_SYS', '0', 'YELLOW', 'INVEST', 1, 44); |
| | | |
| | | |
| | | -- ============================================ |
| | | -- ä¹ãå
¬è·¯æ
客个ä½å®¢è¿é/å¨è½¬éï¼ä¼ä¸çº¿ä¸æ±æ¥ï¼ |
| | | -- ============================================ |
| | | DROP TABLE IF EXISTS passenger_individual_monthly; |
| | | CREATE TABLE passenger_individual_monthly ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) COMMENT 'æ¥è¡¨æ 2026-07', |
| | | region_code VARCHAR(20) COMMENT 'æå±å°åºï¼è¡æ¿åºå代ç ï¼', |
| | | enterprise_code VARCHAR(50) COMMENT 'ä¼ä¸ä»£ç ', |
| | | enterprise_name VARCHAR(200) COMMENT 'ä¼ä¸åç§°', |
| | | passenger_count DOUBLE COMMENT 'å
¶ä¸ä¸ªä½å®¢è¿éï¼äººï¼', |
| | | turnover DOUBLE COMMENT 'å
¶ä¸ä¸ªä½æ
客å¨è½¬éï¼äººå
¬éï¼', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | KEY idx_period (report_period) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='å
¬è·¯æ
客个ä½å®¢è¿é/å¨è½¬é'; |
| | | |
| | | -- ============================================ |
| | | -- åãæ¥è¡¨æå®¡æ ¸éè¿æ è®°ï¼audit_runï¼ |
| | | -- ============================================ |
| | | DROP TABLE IF EXISTS audit_run; |
| | | CREATE TABLE audit_run ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_type VARCHAR(50) COMMENT 'æ¥è¡¨ç±»å', |
| | | report_period VARCHAR(20) COMMENT 'æ¥è¡¨æ', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | KEY idx_type_period (report_type, report_period) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='æ¥è¡¨æå®¡æ ¸éè¿æ è®°'; |
| | | |
| | | |
| | | -- ============================================ |
| | | -- åä¸ãä¸é¡¹ç»è®¡-éè¦è忥 |
| | | -- ============================================ |
| | | |
| | | -- èåæ¥æ¥æé
ç½® |
| | | DROP TABLE IF EXISTS holiday_calendar; |
| | | CREATE TABLE holiday_calendar ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL COMMENT '年份', |
| | | holiday_type VARCHAR(32) NOT NULL COMMENT 'è忥åç±»ï¼SPRING_FESTIVAL/LABOR_DAY/NATIONAL_DAY', |
| | | holiday_date DATE NOT NULL COMMENT 'è忥å
·ä½æ¥æ', |
| | | day_index INT NOT NULL COMMENT 'ç¬¬å æ¥ï¼ä» 1 å¼å§', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_calendar_seq (year, holiday_type, day_index), |
| | | UNIQUE KEY uk_holiday_calendar_date (year, holiday_type, holiday_date), |
| | | KEY idx_holiday_calendar_date (holiday_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='éè¦èåæ¥æ¥æé
ç½®'; |
| | | |
| | | -- èåæ¥ç»¼åè¿è¾è¥ä¸æ§å®¢æµ |
| | | DROP TABLE IF EXISTS holiday_passenger_flow; |
| | | CREATE TABLE holiday_passenger_flow ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL COMMENT '年份', |
| | | flow_date DATE NOT NULL COMMENT 'æ¥æ', |
| | | transport_mode VARCHAR(32) NOT NULL COMMENT 'è¿è¾æ¹å¼ï¼RAIL/CIVIL_AVIATION/ROAD/WATER', |
| | | passenger_count DECIMAL(30,15) NOT NULL COMMENT 'åéæ
客æ°ï¼ä¸äººæ¬¡ï¼', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_passenger (year, flow_date, transport_mode), |
| | | KEY idx_holiday_passenger_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='èåæ¥ç»¼åè¿è¾è¥ä¸æ§å®¢æµæ
åµ'; |
| | | |
| | | -- èåæ¥ç»¼åè¿è¾éè¥ä¸æ§è½¦æµ |
| | | DROP TABLE IF EXISTS holiday_vehicle_flow; |
| | | CREATE TABLE holiday_vehicle_flow ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL COMMENT '年份', |
| | | flow_date DATE NOT NULL COMMENT 'æ¥æ', |
| | | road_category VARCHAR(32) NOT NULL COMMENT 'éè·¯åç±»ï¼EXPRESSWAY/NATIONAL_PROVINCIAL', |
| | | vehicle_type VARCHAR(32) NOT NULL COMMENT '车ååç±»ï¼SMALL_PASSENGER/MEDIUM_SMALL_PASSENGER', |
| | | vehicle_count DECIMAL(30,15) NOT NULL COMMENT '车æµéï¼ä¸è¾æ¬¡ï¼', |
| | | comparison_base DECIMAL(30,15) NULL COMMENT 'æ®éå½çéç¯æ¯åºæï¼ä¸è¾æ¬¡ï¼ä»
å½å¹´ä½¿ç¨ï¼', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_vehicle (year, flow_date, road_category, vehicle_type), |
| | | KEY idx_holiday_vehicle_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='èåæ¥ç»¼åè¿è¾éè¥ä¸æ§è½¦æµæ
åµ'; |
| | | |
| | | -- 客è¿ç³»æ°é
ç½® |
| | | DROP TABLE IF EXISTS holiday_passenger_coefficient; |
| | | CREATE TABLE holiday_passenger_coefficient ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL COMMENT '年份', |
| | | date_category VARCHAR(32) NOT NULL COMMENT 'æ¥æåç±»ï¼HOLIDAY/NON_HOLIDAY', |
| | | coefficient DECIMAL(30,15) NOT NULL COMMENT '客è¿ç³»æ°', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_coefficient (year, date_category) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客è¿ç³»æ°é
ç½®'; |
| New file |
| | |
| | | -- åå¸å®¢è¿ç¬¬1æ¥ï¼å
¬äº¤ï¼å»ºè¡¨ + å®¡æ ¸è§å |
| | | -- æ°æ®æºï¼å¯¼å
¥_åå¸å
Œ
±äº¤éæåº¦è¿è¥æ
åµï¼2026å¹´7æï¼.xlsï¼ä¼ä¸çº§æç»ï¼å«å
Œ
±æ±½çµè½¦/轨é/轮渡ä¸ç±»ï¼æ¬è¡¨åªåå
Œ
±æ±½çµè½¦åæ®µï¼ |
| | | |
| | | CREATE TABLE IF NOT EXISTS city_bus_monthly ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ 2026-07', |
| | | region_code VARCHAR(20) DEFAULT NULL COMMENT 'æå±å°åºä»£ç ', |
| | | city VARCHAR(50) DEFAULT NULL COMMENT 'å¸å·ï¼ç±å°åºä»£ç æ å°ï¼', |
| | | enterprise_code VARCHAR(50) DEFAULT NULL COMMENT 'ä¼ä¸ä»£ç ', |
| | | enterprise_name VARCHAR(200) DEFAULT NULL COMMENT 'ä¼ä¸åç§°', |
| | | op_vehicles DOUBLE DEFAULT NULL COMMENT 'å
Œ
±æ±½çµè½¦è¿è¥è½¦æ°', |
| | | stop_total DOUBLE DEFAULT NULL COMMENT 'è¿è¥ç«ç¹æ»ä¸ªæ°', |
| | | stop_city DOUBLE DEFAULT NULL COMMENT 'å
¶ä¸ï¼åå¸å
è¿è¥ç«ç¹', |
| | | passenger_volume DOUBLE DEFAULT NULL COMMENT 'å
Œ
±æ±½çµè½¦å®¢è¿éï¼ä¸äººæ¬¡ï¼', |
| | | passenger_city DOUBLE DEFAULT NULL COMMENT 'å
¶ä¸ï¼åå¸å
客è¿éï¼ä¸äººæ¬¡ï¼', |
| | | turnover DOUBLE DEFAULT NULL COMMENT 'å
Œ
±æ±½çµè½¦æ
客å¨è½¬éï¼ä¸äººå
¬éï¼', |
| | | turnover_city DOUBLE DEFAULT NULL COMMENT 'å
¶ä¸ï¼åå¸å
æ
客å¨è½¬éï¼ä¸äººå
¬éï¼', |
| | | avg_distance DOUBLE DEFAULT NULL COMMENT 'å
Œ
±æ±½çµè½¦å¹³åè¿è·ï¼å
¬éï¼', |
| | | avg_distance_city DOUBLE DEFAULT NULL COMMENT 'åå¸å
å¹³åè¿è·ï¼å
¬éï¼', |
| | | passenger_chengxiang DOUBLE DEFAULT NULL COMMENT 'åé
å乡客è¿éï¼ä¸äººæ¬¡ï¼', |
| | | turnover_chengxiang DOUBLE DEFAULT NULL COMMENT 'åé
å乡æ
客å¨è½¬éï¼ä¸äººå
¬éï¼', |
| | | avg_distance_chengxiang DOUBLE DEFAULT NULL COMMENT 'åé
å乡平åè¿è·ï¼å
¬éï¼', |
| | | rate_op_vehicles_mom DOUBLE DEFAULT NULL COMMENT 'è¿è¥è½¦æ°ç¯æ¯å¢é', |
| | | rate_passenger_mom DOUBLE DEFAULT NULL COMMENT '客è¿éç¯æ¯å¢é', |
| | | rate_passenger_city_mom DOUBLE DEFAULT NULL COMMENT 'åå¸å
客è¿éç¯æ¯', |
| | | rate_chengxiang_mom DOUBLE DEFAULT NULL COMMENT 'åé
å乡客è¿éç¯æ¯å¢é', |
| | | rate_chengxiang_turnover_mom DOUBLE DEFAULT NULL COMMENT 'åé
å乡æ
客å¨è½¬éç¯æ¯å¢é', |
| | | rate_turnover_mom DOUBLE DEFAULT NULL COMMENT 'æ
客å¨è½¬éç¯æ¯å¢é', |
| | | avg_distance_mom_change DOUBLE DEFAULT NULL COMMENT 'å¹³åè¿è·ç¯æ¯å¢åï¼å
¬éï¼', |
| | | avg_distance_city_mom_change DOUBLE DEFAULT NULL COMMENT 'åå¸å
å¹³åè¿è·ç¯æ¯å¢åï¼å
¬éï¼', |
| | | daily_passenger_per_vehicle DOUBLE DEFAULT NULL COMMENT 'å车æ¥å客è¿é', |
| | | rate_op_vehicles_yoy DOUBLE DEFAULT NULL COMMENT 'è¿è¥è½¦æ°åæ¯å¢é', |
| | | rate_passenger_yoy DOUBLE DEFAULT NULL COMMENT '客è¿é忝å¢é', |
| | | rate_passenger_city_yoy DOUBLE DEFAULT NULL COMMENT 'åå¸å
客è¿é忝å¢é', |
| | | rate_chengxiang_yoy DOUBLE DEFAULT NULL COMMENT 'åé
å乡客è¿é忝å¢é', |
| | | rate_chengxiang_turnover_yoy DOUBLE DEFAULT NULL COMMENT 'åé
å乡æ
客å¨è½¬é忝å¢é', |
| | | rate_turnover_yoy DOUBLE DEFAULT NULL COMMENT 'æ
客å¨è½¬é忝å¢é', |
| | | stop_city_ratio DOUBLE DEFAULT NULL COMMENT 'åå¸å
è¿è¥ç«ç¹å æ¯', |
| | | passenger_city_ratio DOUBLE DEFAULT NULL COMMENT 'åå¸å
客è¿éå æ¯', |
| | | chengxiang_ratio DOUBLE DEFAULT NULL COMMENT 'åé
å乡客è¿éå æ¯', |
| | | contact_person VARCHAR(100) DEFAULT NULL COMMENT '填表人', |
| | | contact_phone VARCHAR(50) DEFAULT NULL COMMENT 'èç³»çµè¯', |
| | | report_date VARCHAR(20) DEFAULT NULL COMMENT 'æ¥åºæ¥æ', |
| | | verify_explanation TEXT COMMENT 'æ ¸å®æ§åå è§£é', |
| | | report_note TEXT COMMENT '䏿¥è¯´æ', |
| | | modify_record TEXT COMMENT 'ä¿®æ¹è®°å½', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | KEY idx_period (report_period), |
| | | KEY idx_city (city), |
| | | KEY idx_enterprise (enterprise_name) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='åå¸å
¬äº¤ä¼ä¸æåº¦æ°æ®ï¼åå¸å
Œ
±äº¤éæåº¦è¿è¥æ
åµï¼'; |
| | | |
| | | -- åå¸å
¬äº¤ 4 ç»å®¡æ ¸è§åï¼report_type=CITY_BUSï¼ |
| | | INSERT IGNORE INTO audit_rule (rule_name, rule_code, description, check_field, compare_type, threshold, alert_level, report_type, is_enabled, sort_order) VALUES |
| | | ('åå¸å
ä¸åé
å乡客è¿éè¶å¿ä¸è´æ ¸å®', 'RULE_B001', 'åå¸å
客è¿éç¯æ¯/忝ä¸åé
å乡客è¿éç¯æ¯/忝è¶å¿ä¸ä¸è´ä¸å¢é>2%éæ ¸å®', 'passengerCityMom,passengerChengxiangMom,passengerCityYoy,passengerChengxiangYoy', 'TREND_CONSISTENCY', '0.02', 'YELLOW', 'CITY_BUS', 1, 51), |
| | | ('客è¿é䏿
客å¨è½¬éè¶å¿ä¸è´æ ¸å®', 'RULE_B002', '客è¿é忝/ç¯æ¯ä¸æ
客å¨è½¬é忝/ç¯æ¯è¶å¿ä¸ä¸è´éæ ¸å®', 'passengerMom,turnoverMom,passengerYoy,turnoverYoy', 'TREND_CONSISTENCY', '0.02', 'YELLOW', 'CITY_BUS', 1, 52), |
| | | ('åå¸å
/åé
å乡客è¿éå æ¯ååæ ¸å®', 'RULE_B003', 'åå¸å
客è¿éå æ¯ååé
å乡客è¿éå æ¯ä¸ä¸æç¸æ¯åå>1%éæ ¸å®', 'passengerCityRatio,chengxiangRatio', 'RATIO_CHANGE', '0.01', 'YELLOW', 'CITY_BUS', 1, 53), |
| | | ('å¹³åè¿è·ååå¹
åº¦æ ¸å®', 'RULE_B004', 'å¹³åè¿è·ä¸ä¸æç¸æ¯ååå¹
度è¶
è¿3å
¬ééæ ¸å®ï¼ç¹å«æ¯åé
å乡ï¼', 'avgDistanceMomChange,avgDistanceCityMomChange,avgDistanceChengxiang', 'AVG_DIST_CHANGE', '3', 'YELLOW', 'CITY_BUS', 1, 54); |
| New file |
| | |
| | | -- åå¸å®¢è¿ç¬¬2æ¥ï¼å·¡æ¸¸åºç§ï¼å»ºè¡¨ + å®¡æ ¸è§å |
| | | -- æ°æ®æº1ï¼å¯¼å
¥_巡游åºç§æ±½è½¦è¿è¥æå¡æ
嵿æ¥ï¼2026å¹´7æï¼.xlsï¼å¸å·çº§ï¼17è¡ï¼ |
| | | -- æ°æ®æº2ï¼è¾å
¥_åºç§è½¦ä¿¡æ¯(è¿æ¿è½¦è¾ä¿¡æ¯).xlsï¼è½¦è¾çº§æç» 42636 è¡ï¼ |
| | | |
| | | CREATE TABLE IF NOT EXISTS city_taxi_monthly ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ 2026-07', |
| | | region_code VARCHAR(20) DEFAULT NULL COMMENT 'æå±å°åºä»£ç ', |
| | | city VARCHAR(50) DEFAULT NULL COMMENT 'å¸å·', |
| | | trip_total DOUBLE DEFAULT NULL COMMENT 'è½½å®¢è½¦æ¬¡æ»æ°', |
| | | trip_city DOUBLE DEFAULT NULL COMMENT 'è½½å®¢è½¦æ¬¡æ»æ°_åå¸å
', |
| | | passenger_volume DOUBLE DEFAULT NULL COMMENT '客è¿éï¼ä¸äººæ¬¡ï¼', |
| | | passenger_city DOUBLE DEFAULT NULL COMMENT '客è¿é_å
¶ä¸åå¸å
', |
| | | turnover DOUBLE DEFAULT NULL COMMENT 'æ
客å¨è½¬éï¼ä¸äººå
¬éï¼', |
| | | turnover_city DOUBLE DEFAULT NULL COMMENT 'æ
客å¨è½¬é_åå¸å
', |
| | | op_vehicles DOUBLE DEFAULT NULL COMMENT 'è¿è¥è½¦è¾æ°', |
| | | avg_distance DOUBLE DEFAULT NULL COMMENT '巡游åºç§æ±½è½¦å¹³åè¿è·ï¼å
¬éï¼', |
| | | avg_distance_city DOUBLE DEFAULT NULL COMMENT 'åå¸å
å¹³åè¿è·ï¼å
¬éï¼', |
| | | daily_trips_per_vehicle DOUBLE DEFAULT NULL COMMENT 'å车æ¥å载客车次', |
| | | daily_passenger_per_vehicle DOUBLE DEFAULT NULL COMMENT 'å车æ¥å客è¿é', |
| | | passengers_per_trip DOUBLE DEFAULT NULL COMMENT 'å车次载客人æ°', |
| | | passengers_per_trip_city DOUBLE DEFAULT NULL COMMENT 'å车次载客人æ°_åå¸å
', |
| | | passengers_per_trip_chengxiang DOUBLE DEFAULT NULL COMMENT 'å车次载客人æ°_åé
å乡', |
| | | rate_op_vehicles_mom DOUBLE DEFAULT NULL COMMENT 'è¿è¥è½¦æ°ç¯æ¯å¢é', |
| | | rate_op_vehicles_yoy DOUBLE DEFAULT NULL COMMENT 'è¿è¥è½¦æ°åæ¯å¢é', |
| | | rate_trip_mom DOUBLE DEFAULT NULL COMMENT 'è½½å®¢è½¦æ¬¡æ»æ°ç¯æ¯å¢é', |
| | | rate_trip_yoy DOUBLE DEFAULT NULL COMMENT 'è½½å®¢è½¦æ¬¡åæ¯å¢é', |
| | | rate_passenger_mom DOUBLE DEFAULT NULL COMMENT '客è¿éç¯æ¯å¢é', |
| | | rate_passenger_yoy DOUBLE DEFAULT NULL COMMENT '客è¿é忝å¢é', |
| | | avg_distance_mom_change DOUBLE DEFAULT NULL COMMENT 'å¹³åè¿è·ç¯æ¯å¢åï¼å
¬éï¼', |
| | | avg_distance_city_mom_change DOUBLE DEFAULT NULL COMMENT 'å¹³åè¿è·_åå¸å
ç¯æ¯å¢åï¼å
¬éï¼', |
| | | rate_trip_city_mom DOUBLE DEFAULT NULL COMMENT 'è½½å®¢è½¦æ¬¡æ»æ°ç¯æ¯å¢é_åå¸å
', |
| | | rate_passenger_city_mom DOUBLE DEFAULT NULL COMMENT '客è¿éç¯æ¯å¢é_åå¸å
', |
| | | rate_trip_chengxiang_mom DOUBLE DEFAULT NULL COMMENT 'è½½å®¢è½¦æ¬¡æ»æ°_åé
åä¹¡ç¯æ¯å¢é', |
| | | rate_passenger_chengxiang_mom DOUBLE DEFAULT NULL COMMENT '客è¿é_åé
åä¹¡ç¯æ¯å¢é', |
| | | avg_distance_chengxiang DOUBLE DEFAULT NULL COMMENT 'å¹³åè¿è·_åé
å乡ï¼å
¬éï¼', |
| | | trip_city_ratio DOUBLE DEFAULT NULL COMMENT 'è½½å®¢è½¦æ¬¡æ»æ°_åå¸å
å æ¯', |
| | | passenger_city_ratio DOUBLE DEFAULT NULL COMMENT '客è¿é_åå¸å
å æ¯', |
| | | contact_person VARCHAR(100) DEFAULT NULL COMMENT '填表人', |
| | | contact_phone VARCHAR(50) DEFAULT NULL COMMENT 'èç³»çµè¯', |
| | | report_date VARCHAR(20) DEFAULT NULL COMMENT 'æ¥åºæ¥æ', |
| | | verify_explanation TEXT COMMENT 'æ ¸å®æ§åå è§£é', |
| | | report_note TEXT COMMENT '䏿¥è¯´æ', |
| | | modify_record TEXT COMMENT 'ä¿®æ¹è®°å½', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | KEY idx_period (report_period), |
| | | KEY idx_city (city) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='巡游åºç§æ±½è½¦è¿è¥æå¡æ
嵿æ¥ï¼å¸å·çº§ï¼'; |
| | | |
| | | CREATE TABLE IF NOT EXISTS city_taxi_auth ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ 2026-07', |
| | | city VARCHAR(50) DEFAULT NULL COMMENT 'å¸å·ï¼ç±è¯å·/æ¡£æ¡å·/å°åæåï¼', |
| | | plate_no VARCHAR(30) DEFAULT NULL COMMENT '车çå·ç ', |
| | | vehicle_type VARCHAR(50) DEFAULT NULL COMMENT '车è¾ç±»å', |
| | | biz_type VARCHAR(50) DEFAULT NULL COMMENT 'ç»è¥ç±»å', |
| | | owner_name VARCHAR(100) DEFAULT NULL COMMENT '车主åç§°', |
| | | license_no VARCHAR(100) DEFAULT NULL COMMENT 'ç»è¥æå·', |
| | | archive_no VARCHAR(100) DEFAULT NULL COMMENT 'æ¡£æ¡å·', |
| | | road_cert_no VARCHAR(100) DEFAULT NULL COMMENT 'éè·¯è¿è¾è¯åå·', |
| | | fuel_type VARCHAR(50) DEFAULT NULL COMMENT 'çæç±»å', |
| | | operator_name VARCHAR(200) DEFAULT NULL COMMENT '䏿·åç§°', |
| | | operator_addr VARCHAR(300) DEFAULT NULL COMMENT '䏿·å°å', |
| | | op_status VARCHAR(20) DEFAULT NULL COMMENT 'è¥è¿ç¶æ', |
| | | cert_status VARCHAR(20) DEFAULT NULL COMMENT 'è¯ä»¶ç¶æ', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | KEY idx_period (report_period), |
| | | KEY idx_city (city), |
| | | KEY idx_period_city (report_period, city) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='åºç§è½¦è¿æ¿è½¦è¾ä¿¡æ¯ï¼è½¦è¾çº§æç»ï¼'; |
| | | |
| | | -- åå¸å®¢è¿ç¬¬2æ¥ï¼å·¡æ¸¸åºç§ 7 ç»å®¡æ ¸è§åï¼report_type=CITY_TAXIï¼ |
| | | INSERT IGNORE INTO audit_rule (rule_name, rule_code, description, check_field, compare_type, threshold, alert_level, report_type, is_enabled, sort_order) VALUES |
| | | ('è¿è¥è½¦æ°ä¸è¿æ¿ç³»ç»å¯¹æ¯æ ¸å®', 'RULE_T001', 'ææ¥è¿è¥è½¦è¾æ°ä¸è¿æ¿ç³»ç»è½¦è¾æ°å·®è·>3%éæ ¸å®', 'opVehicles', 'CROSS_DIFF_PCT', '3', 'RED', 'CITY_TAXI', 1, 61), |
| | | ('åå¸å
ä¸åé
å乡客è¿éè¶å¿ä¸è´æ ¸å®', 'RULE_T002', 'åå¸å
客è¿éç¯æ¯ä¸åé
å乡客è¿éç¯æ¯è¶å¿ä¸ä¸è´ä¸å¢é>2%éæ ¸å®', 'passengerCityMom,passengerChengxiangMom', 'TREND_CONSISTENCY', '0.02', 'YELLOW', 'CITY_TAXI', 1, 62), |
| | | ('客è¿é䏿
客å¨è½¬éè¶å¿ä¸è´æ ¸å®', 'RULE_T003', '客è¿éç¯æ¯ä¸æ
客å¨è½¬éç¯æ¯ï¼ç±å¹³åè¿è·æ¨å¯¼ï¼è¶å¿ä¸ä¸è´éæ ¸å®', 'passengerMom,turnoverMomDerived', 'TREND_CONSISTENCY', '0.02', 'YELLOW', 'CITY_TAXI', 1, 63), |
| | | ('åå¸å
客è¿éå æ¯ååæ ¸å®', 'RULE_T004', 'åå¸å
客è¿éå æ¯ä¸ä¸æç¸æ¯åå>1%éæ ¸å®', 'passengerCityRatio', 'RATIO_CHANGE', '0.01', 'YELLOW', 'CITY_TAXI', 1, 64), |
| | | ('å¹³åè¿è·ååå¹
åº¦æ ¸å®', 'RULE_T005', 'å¹³åè¿è·ä¸ä¸æç¸æ¯ååè¶
è¿3å
¬ééæ ¸å®ï¼ç¹å«æ¯åé
å乡ï¼', 'avgDistanceMomChange,avgDistanceCityMomChange,avgDistanceChengxiang', 'AVG_DIST_CHANGE', '3', 'YELLOW', 'CITY_TAXI', 1, 65), |
| | | ('å车次载客人æ°ç³»æ°ååæ ¸å®', 'RULE_T006', 'å车次载客人æ°=客è¿é/è½½å®¢è½¦æ¬¡æ»æ°ï¼ä¸ä¸æç¸æ¯åå>0.2éæ ¸å®', 'passengersPerTrip', 'PAX_PER_TRIP_CHANGE', '0.2', 'YELLOW', 'CITY_TAXI', 1, 66), |
| | | ('è½½å®¢è½¦æ¬¡æ»æ°ä¸å®¢è¿éè¶å¿ä¸è´æ ¸å®', 'RULE_T007', 'è½½å®¢è½¦æ¬¡æ»æ°ç¯æ¯/忝ä¸å®¢è¿éç¯æ¯/忝è¶å¿ä¸ä¸è´éæ ¸å®', 'tripMom,passengerMom,tripYoy,passengerYoy', 'TREND_CONSISTENCY', '0.01', 'YELLOW', 'CITY_TAXI', 1, 67); |
| New file |
| | |
| | | -- Holiday statistics module. Run against the traffic_audit database. |
| | | CREATE TABLE IF NOT EXISTS holiday_calendar ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL, |
| | | holiday_type VARCHAR(32) NOT NULL, |
| | | holiday_date DATE NOT NULL, |
| | | day_index INT NOT NULL, |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_calendar_seq (year, holiday_type, day_index), |
| | | UNIQUE KEY uk_holiday_calendar_date (year, holiday_type, holiday_date), |
| | | KEY idx_holiday_calendar_date (holiday_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | CREATE TABLE IF NOT EXISTS holiday_passenger_flow ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL, |
| | | flow_date DATE NOT NULL, |
| | | transport_mode VARCHAR(32) NOT NULL, |
| | | passenger_count DECIMAL(30,15) NOT NULL, |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_passenger (year, flow_date, transport_mode), |
| | | KEY idx_holiday_passenger_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | CREATE TABLE IF NOT EXISTS holiday_vehicle_flow ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL, |
| | | flow_date DATE NOT NULL, |
| | | road_category VARCHAR(32) NOT NULL, |
| | | vehicle_type VARCHAR(32) NOT NULL, |
| | | vehicle_count DECIMAL(30,15) NOT NULL, |
| | | comparison_base DECIMAL(30,15) NULL COMMENT 'æ®éå½çéç¯æ¯åºæï¼ä¸è¾æ¬¡ï¼ä»
å½å¹´ä½¿ç¨ï¼', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_vehicle (year, flow_date, road_category, vehicle_type), |
| | | KEY idx_holiday_vehicle_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | CREATE TABLE IF NOT EXISTS holiday_passenger_coefficient ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL, |
| | | date_category VARCHAR(32) NOT NULL, |
| | | coefficient DECIMAL(30,15) NOT NULL, |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_coefficient (year, date_category) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| New file |
| | |
| | | -- Add the optional ordinary national/provincial road comparison base. |
| | | -- The field is used only for the 2026 holiday data. |
| | | ALTER TABLE holiday_vehicle_flow |
| | | ADD COLUMN comparison_base DECIMAL(30,15) NULL |
| | | COMMENT 'æ®éå½çéç¯æ¯åºæï¼ä¸è¾æ¬¡ï¼ä»
å½å¹´ä½¿ç¨ï¼' |
| | | AFTER vehicle_count; |
| New file |
| | |
| | | -- Convert holiday statistics numeric columns from binary floating point to exact decimal storage. |
| | | -- Run once against the traffic_audit database. |
| | | ALTER TABLE holiday_passenger_flow |
| | | MODIFY COLUMN passenger_count DECIMAL(30,15) NOT NULL COMMENT 'åéæ
客æ°ï¼ä¸äººæ¬¡ï¼'; |
| | | |
| | | ALTER TABLE holiday_vehicle_flow |
| | | MODIFY COLUMN vehicle_count DECIMAL(30,15) NOT NULL COMMENT '车æµéï¼ä¸è¾æ¬¡ï¼'; |
| | | |
| | | ALTER TABLE holiday_passenger_coefficient |
| | | MODIFY COLUMN coefficient DECIMAL(30,15) NOT NULL COMMENT '客è¿ç³»æ°'; |
| | | |
| | | -- The seeded coefficient was averaged from repeated 2.4 cells through a Java double. |
| | | -- Normalize the migrated value to the exact Excel decimal. |
| | | UPDATE holiday_passenger_coefficient |
| | | SET coefficient = 2.4 |
| | | WHERE year IN (2024, 2025, 2026); |
| New file |
| | |
| | | -- åå¸å®¢è¿ç½çº¦è½¦æåº¦è¾å
¥å»ºè¡¨ï¼2026-09-04ï¼ |
| | | -- æ°æ®æº1ï¼æ¨¡æ¿_ç½çº¦è½¦æ»æ°.xlsxï¼æåº¦å
¨ç pinï¼å®¢è¿é/å
¶ä¸åå¸å
/å¨è½¬é/å
¶ä¸åå¸å
å¨è½¬éï¼ |
| | | -- æ°æ®æº2ï¼æ¨¡æ¿_ç½çº¦è½¦è®¢ååå
¨çæ»é.xlsxï¼æåº¦è¾å
¥é¡µï¼B-E å
¨ç pin + F 订åå计 + G-W 17å¸å·è®¢åï¼ |
| | | |
| | | CREATE TABLE IF NOT EXISTS wyc_total_monthly ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ yyyy-MM', |
| | | pin_k DOUBLE DEFAULT NULL COMMENT 'å
¨çç½çº¦è½¦å®¢è¿é(ä¸äººæ¬¡)', |
| | | pin_ck DOUBLE DEFAULT NULL COMMENT 'å
¶ä¸åå¸å
客è¿é', |
| | | pin_z DOUBLE DEFAULT NULL COMMENT 'å
¨ç客è¿å¨è½¬é(ä¸äººå
Ž)', |
| | | pin_cz DOUBLE DEFAULT NULL COMMENT 'å
¶ä¸åå¸å
å¨è½¬é', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_wyc_total_period (report_period) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ç½çº¦è½¦æåº¦å
¨ç pin è¾å
¥'; |
| | | |
| | | CREATE TABLE IF NOT EXISTS wyc_order_monthly ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ yyyy-MM', |
| | | pin_k DOUBLE DEFAULT NULL, |
| | | pin_ck DOUBLE DEFAULT NULL, |
| | | pin_z DOUBLE DEFAULT NULL, |
| | | pin_cz DOUBLE DEFAULT NULL, |
| | | order_sum DOUBLE DEFAULT NULL COMMENT '订åå计ï¼åï¼', |
| | | order_wuhan DOUBLE DEFAULT NULL, |
| | | order_huangshi DOUBLE DEFAULT NULL, |
| | | order_shiyan DOUBLE DEFAULT NULL, |
| | | order_yichang DOUBLE DEFAULT NULL, |
| | | order_xiangyang DOUBLE DEFAULT NULL, |
| | | order_ezhou DOUBLE DEFAULT NULL, |
| | | order_jingmen DOUBLE DEFAULT NULL, |
| | | order_xiaogan DOUBLE DEFAULT NULL, |
| | | order_jingzhou DOUBLE DEFAULT NULL, |
| | | order_huanggang DOUBLE DEFAULT NULL, |
| | | order_xianning DOUBLE DEFAULT NULL, |
| | | order_suizhou DOUBLE DEFAULT NULL, |
| | | order_enshi DOUBLE DEFAULT NULL, |
| | | order_xiantao DOUBLE DEFAULT NULL, |
| | | order_qianjiang DOUBLE DEFAULT NULL, |
| | | order_tianmen DOUBLE DEFAULT NULL, |
| | | order_shennong DOUBLE DEFAULT NULL, |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_wyc_order_period (report_period) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ç½çº¦è½¦è®¢ååå
¨çæ»éæåº¦è¾å
¥'; |
| New file |
| | |
| | | # å端 nginx é
置示ä¾ï¼åªæå¨ä¸æ³ç¨ãjar å
åµåç«¯ãæ¶æéè¦ï¼ |
| | | server { |
| | | listen 8080; |
| | | server_name localhost; |
| | | root /opt/traffic-audit/frontend/dist; # æ¹æå®é
è·¯å¾ |
| | | index index.html; |
| | | location /api/ { |
| | | proxy_pass http://127.0.0.1:8090; |
| | | proxy_set_header Host $host; |
| | | proxy_set_header X-Real-IP $remote_addr; |
| | | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| | | proxy_read_timeout 300s; |
| | | } |
| | | location / { try_files $uri $uri/ /index.html; } |
| | | } |
| New file |
| | |
| | | excelçæ°å¼è®¡ç®é常精确ï¼å
¶å
鍿¯æä¹å卿°å¼çå¢ï¼æ¯ç¨æµ®ç¹æ°å |
| | | ç¨æ·æ³ç¥éExcelå
é¨å¦ä½å卿°å¼ï¼æ¯å¦ä½¿ç¨æµ®ç¹æ°ãæéè¦æä¾åç¡®çè§£éã为äºå
¨é¢è·åä¿¡æ¯ï¼æå°åæ¶è¿è¡å¤é¡¹æç´¢ï¼è¦çExcelæ°å¼å卿ºå¶ãæµ®ç¹æ°ç²¾åº¦é®é¢ãä¸IEEE 754æ åçå
³ç³»ä»¥åå¯è½çä¼åææ¯ã |
| | | |
| | | æç´¢å° 16 个ç½é¡µ |
| | | æç´¢ç»ææ¾ç¤ºäºä¸äºç¸å
³é¡µé¢ã为äºå
¨é¢è·åä¿¡æ¯ï¼æä»¬éè¦åæ¶æå¼è¿äºé¡µé¢ã |
| | | |
| | | æµè§ 1 ä¸ªé¡µé¢ |
| | | Floating Point Errors in Excel |
| | | è¿äºèµææä¾äºå
³äºExcelæ°å¼åå¨ç详ç»ä¿¡æ¯ãå¾®è½¯çææ¡£æç¡®æåºExceléµå¾ªIEEE 754æ åï¼ä½¿ç¨åç²¾åº¦æµ®ç¹æ°ï¼æææ°å为15ä½ãStack Overflowç讨论ä¹ç¡®è®¤äºè¿ä¸ç¹ãExcelJetçæç« è§£éäºæµ®ç¹æ°è¯¯å·®çåå ãæçåçå°æ¶µç以ä¸å 个æ¹é¢ï¼Excel使ç¨IEEE 754åç²¾åº¦æµ®ç¹æ°ï¼64ä½ï¼ï¼ææç²¾åº¦ä¸º15ä½ï¼åå¨èå´ï¼ä»¥åæµ®ç¹æ°è¯¯å·®åå
¶åå ãæè¿ä¼æåä¸äºç¹æ®æ
åµï¼æ¯å¦æ´æ°åå¨åæ¥ææ¶é´åå¨ãä¸ºäºæ´å
¨é¢å°è¦çè¿äºæ¹é¢ï¼æéè¦åæ¶è¿è¡å¤é¡¹æç´¢ã |
| | | |
| | | æç´¢å° 12 个ç½é¡µ |
| | | ç°å¨å¯ä»¥ç»ç»çæ¡äºãçæ¡å°æ¶µçï¼Excel使ç¨IEEE 754åç²¾åº¦æµ®ç¹æ°ï¼64ä½ï¼ï¼ææç²¾åº¦ä¸º15ä½ï¼åå¨èå´ï¼ä»¥åæµ®ç¹æ°è¯¯å·®åå
¶åå ãè¿ä¼æåæ´æ°åæ¥ææ¶é´çå卿¹å¼ãåçæ¶ä¼å¼ç¨å¾®è½¯ææ¡£ãStack Overflowçæ¥æºã |
| | | |
| | | æ¯çï¼Excel çæ°å¼è®¡ç®ç¡®å®åºäºæµ®ç¹æ°ï¼å
·ä½æ¥è¯´ï¼å®éµå¾ª IEEE 754 æ åç 64 ä½åç²¾åº¦æµ®ç¹æ°æ ¼å¼æ¥åå¨åè®¡ç®æ°å¼ã |
| | | |
| | | æ ¸å¿æºå¶ï¼64ä½åç²¾åº¦æµ®ç¹æ° |
| | | Excel å
é¨å°ææå·¥ä½è¡¨åå
æ ¼ä¸çæ°åï¼å
æ¬æ´æ°ãæ¥æåæ¶é´ï¼é½ç»ä¸åå¨ä¸º 8åèï¼64ä½ï¼çåç²¾åº¦æµ®ç¹æ°ã |
| | | |
| | | è¿ç§å卿 ¼å¼ç 64 个æ¯ç¹ä½è¢«åå为ä¸ä¸ªé¨åï¼ |
| | | |
| | | 符å·ä½ (1ä½)ï¼è¡¨ç¤ºæ£è´ã |
| | | |
| | | ææ°ä½ (11ä½)ï¼å³å®æ°å¼çèå´ã |
| | | |
| | | å°¾æ°ä½ (52ä½)ï¼å³å®æ°å¼ç精度ã |
| | | |
| | | 精度éå¶ï¼15ä½æææ°å |
| | | 尽管åç²¾åº¦æµ®ç¹æ°è½æä¾çº¦ 15 å° 16 ä½çåè¿å¶æææ°åï¼ä½ Excel 为äºç¡®ä¿è®¡ç®ç»æçå¯é æ§ï¼å°ææç²¾åº¦éå¶å¨ 15 ä½ã |
| | | |
| | | è¿æå³çï¼å¦æä¸ä¸ªæ°åçææä½æ°è¶
è¿ 15 ä½ï¼ä»ç¬¬ 16 ä½å¼å§å°è¢«æ¿æ¢ä¸º 0ãä¾å¦ï¼è¾å
¥ 123456789012345678ï¼Excel å®é
åå¨çå°æ¯ 123456789012345000ã |
| | | |
| | | æ°å¼èå´ |
| | | åºäºæ¤æ åï¼Excel å¯ä»¥å¤ççæ°å¼èå´å¦ä¸ï¼ |
| | | |
| | | æå¤§æ£æ°ï¼1.79769313486232E+308 |
| | | |
| | | æå°æ£æ°ï¼2.2250738585072E-308 |
| | | |
| | | è¶
åºæ¤èå´çæ°å¼ä¼è¢«è§ä¸ºâæ 穷大âå¹¶æ¾ç¤ºä¸ºé误å¼ã |
| | | |
| | | 为ä»ä¹ä¼ææµ®ç¹æ°è¯¯å·®ï¼ |
| | | ä½ å¯è½ä¼éå° Excel è®¡ç® 0.1 + 0.2 å¾å° 0.30000000000000004 çæ
åµï¼è¿å¹¶é Bugï¼èæ¯äºè¿å¶æµ®ç¹æ°çåºæç¹æ§ã |
| | | |
| | | å
¶æ ¹æ¬åå å¨äºï¼å 0.1 è¿æ ·çåè¿å¶å°æ°æ æ³ç¨äºè¿å¶æµ®ç¹æ°ç²¾ç¡®è¡¨ç¤ºï¼å°±ååè¿å¶æ æ³ç²¾ç¡®è¡¨ç¤º 1/3 䏿 ·ãExcel åå¨çæ¯ä¸ä¸ªæå
¶æ¥è¿ 0.1 çè¿ä¼¼å¼ï¼å½å¤ä¸ªè¿æ ·çè¿ä¼¼å¼è¿è¡è¿ç®æ¶ï¼å¾®å°ç误差ä¼ç´¯ç§¯ï¼æç»å¯¼è´ç»æåºç°æå°çåå·®ã |
| | | |
| | | å
¶ä»æ°æ®ç±»åçåå¨ |
| | | æ´æ°ï¼ä¹æåç²¾åº¦æµ®ç¹æ°åå¨ï¼å æ¤ Excel è½ç²¾ç¡®è¡¨ç¤ºçæ´æ°ä¸éæ¯ 2^53ï¼çº¦ 9,007,199,254,740,992ï¼ãè¶
è¿æ¤èå´çæ´æ°å¯è½ä¼ä¸¢å¤±ç²¾åº¦ã |
| | | |
| | | æ¥æåæ¶é´ï¼åæ ·æ¯æµ®ç¹æ°ãæ´æ°é¨åä»£è¡¨æ¥æï¼èª 1900 å¹´ 1 æ 1 æ¥ä»¥æ¥ç天æ°ï¼ï¼å°æ°é¨å代表æ¶é´ï¼ä¸å¤©ä¸çæ¯ä¾ï¼ã |
| | | |
| | | æ»ç»æ¥è¯´ï¼Excel çæ°å¼è®¡ç®å¨åºå±å°±æ¯æ åç IEEE 754 åç²¾åº¦æµ®ç¹æ°ï¼å
¶âç²¾ç¡®âæ¯ç¸å¯¹çï¼åéäº 15 ä½æææ°åç精度åäºè¿å¶æµ®ç¹æ°æ¬èº«ç表示éå¶ã |
| | | |
| | | é£ä¹ç¨Javaå¤çexcelçæ°æ®æ¶ï¼ä¸ºäºä¿æä¸excelç计ç®ç»æä¸è´ï¼æ°æ®åºåJavaä¸å¦ä½åå¨å计ç®å¢ |
| | | ç¨æ·æ³ç¥éå¦ä½å¨Javaä¸å¤çExcelæ°æ®ï¼ä»¥ä¿æä¸Excel计ç®ç»æä¸è´ï¼æ¶åæ°æ®åºåJavaä¸çåå¨ä¸è®¡ç®ãæéè¦æä¾å
³äºæ°æ®ç±»åéæ©ã计ç®çç¥ä»¥åå¯è½éå°ç精度é®é¢æ¹é¢ç建议ã为äºå
¨é¢è·åä¿¡æ¯ï¼æå°åæ¶è¿è¡å¤é¡¹æç´¢ï¼è¦çJavaå¤çExcelç²¾åº¦ãæ°æ®åºåå¨çç¥ãBigDecimal使ç¨ãApache POIå¤çæ¹å¼ä»¥åIEEE 754å
¼å®¹æ§çå
³é®æ¹é¢ã |
| | | |
| | | æç´¢å° 36 个ç½é¡µ |
| | | æç´¢ç»ææ¾ç¤ºäºä¸äºç¸å
³èµæºã为äºå
¨é¢è·åä¿¡æ¯ï¼æä»¬éè¦åæ¶æå¼è¿äºç»æã |
| | | |
| | | æµè§ 4 ä¸ªé¡µé¢ |
| | | NumberComparer (POI API Documentation) |
| | | EasyExcel读å
¥æ°åç±»åæ°æ®æ¶åºç°å°æ°ä½å¢é¿ç°è±¡ - EasyExcel读å
¥æ°åç±»åæ°æ®æ¶åºç°å°æ°ä½å¢é¿ç°è±¡ |
| | | ãå®ç¾è§£å³POI读åExcelææ¡£æ°å¼ç±»å缺失精度é®é¢ã |
| | | MySQL :: MySQL 9.0 åèæå :: 13.1.3 å®é»é¡åï¼ç²¾ç¢ºå¼ï¼- DECIMALãNUMERIC |
| | | è¿äºèµææä¾äºå
³äºç²¾åº¦é®é¢ãApache POIçNumberToTextConverterãExcelç15ä½ç²¾åº¦éå¶ä»¥åæ°æ®åºDECIMALç±»åçä¿¡æ¯ãæçåçå°æ¶µçJavaä¸çåå¨ä¸è®¡ç®ãæ°æ®åºä¸çåå¨ã以åå¦ä½ä¿æç»æä¸è´ãåçä¼å¼ç¨è¿äºèµæãç°å¨å¼å§ç»ç»åçã |
| | | |
| | | è¦å¨Javaä¸å¤çExcelæ°æ®å¹¶ä¿æç»æä¸è´ï¼æ ¸å¿æè·¯æ¯å°Excelç15ä½æææ°å精度ä½ä¸ºåºåï¼å¨Javaåæ°æ®åºä¸éç¨æ´é«ç²¾åº¦æç²¾ç¡®ç±»åè¿è¡åå¨ï¼å¹¶ç»ä¸è®¡ç®ä¸èå
¥è§åã |
| | | |
| | | ð¾ ç¬¬ä¸æ¥ï¼å¨Javaä¸å¦ä½åå¨ä¸è¯»å |
| | | Javaç double ç±»ååæ ·éµå¾ª IEEE 754 æ åï¼ä½å
¶æææ°åå¯è¾¾15-17ä½ï¼èExcelä¸¥æ ¼éå¶ä¸º15ä½ï¼è¿æ¯å·®å¼çæ ¹æºã |
| | | |
| | | è¯»åæ¶ï¼æ¨èç¨ BigDecimal æ String |
| | | |
| | | ç´æ¥ç¨ double æ¥æ¶ Excel æ°å¼ä¼å¼å
¥é¢å¤çäºè¿å¶æµ®ç¹è¯¯å·®ãä¾å¦ï¼Excel ä¸ç 9699.1 读æ double å¯è½åæ 9699.0999999999999996ã |
| | | |
| | | æ¨èæ¹æ¡ï¼ |
| | | |
| | | 读å为 BigDecimalï¼å¨å®ä½ç±»ä¸ï¼å°éé¢ãæ°éçæ°å¼å段声æä¸º BigDecimal ç±»åï¼èé double æ floatã |
| | | |
| | | ä½¿ç¨ POI ç NumberToTextConverterï¼Apache POI æä¾äº NumberToTextConverter.toText(double) å·¥å
·ç±»ï¼å¯ä»¥å° Excel çåå§æ°å¼ç²¾ç¡®å°è½¬æ¢ä¸ºå符串ï¼åç±å符串æé BigDecimalï¼é¿å
ä¸é´ç²¾åº¦æå¤±ã |
| | | |
| | | java |
| | | import org.apache.poi.ss.util.NumberToTextConverter; |
| | | import java.math.BigDecimal; |
| | | |
| | | if (cell.getCellType() == CellType.NUMERIC) { |
| | | String exactText = NumberToTextConverter.toText(cell.getNumericCellValue()); |
| | | BigDecimal value = new BigDecimal(exactText); |
| | | // æ¤æ¶ value æ¯ Excel ä¸åå¨ç精确å¼ï¼15ä½æææ°åï¼ |
| | | } |
| | | ðï¸ ç¬¬äºæ¥ï¼å¨æ°æ®åºä¸å¦ä½åå¨ |
| | | æ°æ®åºå段åå¿ä½¿ç¨ FLOAT æ DOUBLEï¼å 为å®ä»¬åæ ·åå¨äºè¿å¶æµ®ç¹è¯¯å·®ï¼ä¼å¯¼è´ä¸Excelç»æçåå·®ã |
| | | |
| | | æ¨èæ¹æ¡ï¼ä½¿ç¨ DECIMAL ç±»å |
| | | |
| | | DECIMALï¼æ NUMERICï¼æ¯å®ç¹ç²¾ç¡®ç±»åï¼å®ææ´æ°ä½åå°æ°ä½åå¨ï¼è½ç²¾ç¡®è¡¨ç¤ºåè¿å¶å°æ°ã |
| | | |
| | | æ°æ®åº æ¨èç±»å 说æ |
| | | MySQL DECIMAL(p, s) p 为æ»ç²¾åº¦ï¼s ä¸ºå°æ°ä½ãExcel ææä½15ï¼å»ºè®® p è³å°ä¸º 15ï¼åæ ¹æ®å°æ°ä½éæ±è®¾å®ãä¾å¦ DECIMAL(18, 6)ã |
| | | PostgreSQL NUMERIC(p, s) åä¸ï¼æ åSQLç±»åã |
| | | SQL Server DECIMAL(p, s) åä¸ã |
| | | Oracle NUMBER(p, s) NUMBER ç±»åå¯åå¨å®ç¹æ°ï¼ææçåã |
| | | JDBC æ å°ï¼ä½¿ç¨ JDBC è¯»åæ¶ï¼DECIMAL åæ®µä¼ç´æ¥æ å°ä¸º Java ç BigDecimalï¼ä¿è¯äºç²¾åº¦å¨ä¼ éè¿ç¨ä¸ä¸ä¸¢å¤±ã |
| | | |
| | | ð§® ç¬¬ä¸æ¥ï¼Javaä¸ç计ç®ä¸èå
¥è§å |
| | | å³ä½¿åå¨ç²¾ç¡®ï¼è®¡ç®é»è¾ä¹å¿
须模æ Excel çè¡ä¸ºã |
| | | |
| | | 1. å§ç»ä½¿ç¨ BigDecimal è¿è¡è®¡ç® |
| | | |
| | | BigDecimal çå åä¹é¤ï¼add, subtract, multiply, divideï¼é½æ¯ç²¾ç¡®è®¡ç®ï¼ä¸ä¼å¼å
¥äºè¿å¶è¯¯å·®ãåå¿å° BigDecimal 转å double åå计ç®ã |
| | | |
| | | 2. 模æ Excel ç15ä½æææ°åéå¶ |
| | | |
| | | Excel 卿¯æ¬¡å°ä¸é´ç»æåå
¥åå
æ ¼æ¶ï¼é½ä¼å°ç»æèå
¥å°15ä½æææ°åãå¨ Java ä¸ï¼éè¦æ¨¡æè¿ä¸è¡ä¸ºã |
| | | |
| | | java |
| | | import java.math.BigDecimal; |
| | | import java.math.MathContext; |
| | | import java.math.RoundingMode; |
| | | |
| | | // å建ä¸ä¸ªç²¾åº¦ä¸º15ä½æææ°åï¼èå
¥æ¨¡å¼ä¸ºâåèäºå
¥âçä¸ä¸æ |
| | | MathContext EXCEL_MC = new MathContext(15, RoundingMode.HALF_UP); |
| | | |
| | | public BigDecimal excelRound(BigDecimal value) { |
| | | // åºç¨Excelçèå
¥è§å |
| | | return value.round(EXCEL_MC); |
| | | } |
| | | |
| | | // 示ä¾ï¼å¨æ¯ä¸æ¥å
³é®è®¡ç®åè°ç¨ |
| | | BigDecimal intermediate = a.add(b); |
| | | intermediate = excelRound(intermediate); // 模æåå
¥åå
æ ¼ |
| | | BigDecimal result = intermediate.multiply(c); |
| | | result = excelRound(result); |
| | | 注æï¼Excel çèå
¥æ¨¡å¼è¿ä¼¼äºâåèäºå
¥âï¼ä½åå¨å¾®å¦çâé¶è¡å®¶èå
¥âè¡ä¸ºãRoundingMode.HALF_UP æ¯ææ¥è¿çè¿ä¼¼ï¼ä½å¨æå°æ°è¾¹ç¼æ
åµä¸ä»æå·®å¼ã对äºè´¢å¡ç³»ç»ï¼å»ºè®®å¨æç»ç»æä¸æä¸å¡è§åï¼å¦ RoundingMode.HALF_UPï¼æ¾å¼ä¿çåºå®å°æ°ä½ã |
| | | |
| | | 3. æ¯è¾è¿ç®éç¹æ®å¤ç |
| | | |
| | | Excel 卿¯è¾æ°åæ¶æé¢å¤ç容差é»è¾ãä¾å¦ï¼(0.06-0.01)=0.05 å¨ Excel ä¸ä¸º TRUEï¼ä½å¨ Java ç double æ¯è¾ä¸ä¸º falseãApache POI æä¾äº NumberComparer.compare(double a, double b) æ¥æ¨¡æè¿ç§æ¯è¾è¡ä¸ºãå¯¹äº BigDecimalï¼å»ºè®®å
æ15ä½æææ°åèå
¥ååç¨ compareTo() æ¯è¾ã |
| | | |
| | | ð æ»ç» |
| | | è¦å¨ Java çæä¸å¤ç° Excel ç计ç®ç»æï¼å
³é®å¨äºï¼ |
| | | |
| | | å
¥å£ç²¾ç¡®ï¼ç¨ NumberToTextConverter + BigDecimal 读åï¼é¿å
double 污æã |
| | | |
| | | åå¨ç²¾ç¡®ï¼æ°æ®åºä½¿ç¨ DECIMAL ç±»åï¼Java åæ®µä½¿ç¨ BigDecimalã |
| | | |
| | | 计ç®ç²¾ç¡®ï¼å
¨ç¨ä½¿ç¨ BigDecimal è¿ç®ã |
| | | |
| | | èå
¥å¯¹é½ï¼å¨æ¯æ¬¡å¯è½äº§çä¸é´ç»æçå°æ¹ï¼ç¨ MathContext(15, RoundingMode.HALF_UP) 模æ Excel ç15ä½æææ°åæªæã |
| | | |
| | | æ¯è¾å®¹å·®ï¼ä½¿ç¨æ¨¡æ Excel è¡ä¸ºçæ¯è¾é»è¾ï¼èéç´æ¥æ¯è¾ã |
| | | |
| | | è¿æ ·å°±è½æå¤§ç¨åº¦å°ä¿è¯ Java ä¸ Excel ç计ç®ç»æä¸è´ã |
| | |
| | | | /api/report/export/investFiveYearLogistics | "åäºäº"ç©æµé¡¹ç®è¿å± | åºå®ååè¿æ»¤ï¼å¸å·å°è+项ç®è¡ | |
| | | | /api/report/export/investBillion | 亿å
æèµé¡¹ç® | æèµæ»è¡¨ + è§ä¸é¡¹ç®è¡¨ä¸¤sheetï¼äº¿å
ååè¿æ»¤ | |
| | | | /api/report/export/investCounty | ç»æµå¼ºå¿æèµç»è®¡ | ç»æµå¼ºå¿ååè¿æ»¤ | |
| | | |
| | | |
| | | ## ä¹ãä¸é¡¹ç»è®¡-éè¦èåæ¥æ¨¡å |
| | | |
| | | > æ°æ®æ¥æºä¸æ¨¡æ¿ï¼`docs/ä¸é¡¹ç»è®¡/éè¦è忥/2026å¹´å䏿°æ®.xlsx`ï¼å¿«æ¥æ¨¡æ¿ï¼`docs/ä¸é¡¹ç»è®¡/éè¦è忥/æ¹åçå½åºåæç»¼åè¿è¾å®¢æµå¿«æ¥æ¨¡æ¿.docx`ã馿¬¡å¯å¨ä¼ä»å·¥ä½ç°¿åå§å 2024/2025 å¹´åä¸å®¢æµãè½¦æµæ°æ®ï¼å¹¶åå§å 2024-2026 年客è¿ç³»æ°ã |
| | | |
| | | ### holiday_calendar èåæ¥æ¥æé
ç½® |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | id | bigint | PK | |
| | | | year | int | 年份 | |
| | | | holiday_type | varchar(32) | æ¥è SPRING_FESTIVAL / äºä¸ LABOR_DAY / åä¸ NATIONAL_DAY | |
| | | | holiday_date | date | è忥å
·ä½æ¥æ | |
| | | | day_index | int | ç¬¬å æ¥ï¼ä» 1 å¼å§ | |
| | | | created_at / updated_at | datetime | å建/æ´æ°æ¶é´ | |
| | | |
| | | å¯ä¸é®ï¼`(year, holiday_type, day_index)`ã`(year, holiday_type, holiday_date)`ãä»
admin å¯éè¿ç³»ç»é
ç½®èåç»´æ¤ã |
| | | |
| | | ### holiday_passenger_flow èåæ¥ç»¼åè¿è¾è¥ä¸æ§å®¢æµ |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | id | bigint | PK | |
| | | | year | int | 年份 | |
| | | | flow_date | date | æ¥æ | |
| | | | transport_mode | varchar(32) | éè·¯ RAIL / æ°èª CIVIL_AVIATION / éè·¯ ROAD / æ°´è·¯ WATER | |
| | | | passenger_count | decimal(30,15) | åéæ
客æ°ï¼ä¸äººæ¬¡ï¼ | |
| | | | created_at / updated_at | datetime | å建/æ´æ°æ¶é´ | |
| | | |
| | | å¯ä¸é®ï¼`(year, flow_date, transport_mode)`ã |
| | | |
| | | ### holiday_vehicle_flow èåæ¥ç»¼åè¿è¾éè¥ä¸æ§è½¦æµ |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | id | bigint | PK | |
| | | | year | int | 年份 | |
| | | | flow_date | date | æ¥æ | |
| | | | road_category | varchar(32) | é«éå
¬è·¯ EXPRESSWAY / æ®éå½çé NATIONAL_PROVINCIAL | |
| | | | vehicle_type | varchar(32) | å°å®¢è½¦ SMALL_PASSENGER / ä¸å°å®¢è½¦ MEDIUM_SMALL_PASSENGER | |
| | | | vehicle_count | decimal(30,15) | 车æµéï¼ä¸è¾æ¬¡ï¼ | |
| | | | comparison_base | decimal(30,15) | æ®éå½çéç¯æ¯åºæï¼ä¸è¾æ¬¡ï¼ä»
å½å¹´ä½¿ç¨ï¼ | |
| | | | created_at / updated_at | datetime | å建/æ´æ°æ¶é´ | |
| | | |
| | | å¯ä¸é®ï¼`(year, flow_date, road_category, vehicle_type)`ã |
| | | |
| | | ### holiday_passenger_coefficient 客è¿ç³»æ°é
ç½® |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | id | bigint | PK | |
| | | | year | int | 年份 | |
| | | | date_category | varchar(32) | è忥 HOLIDAY / éè忥 NON_HOLIDAY | |
| | | | coefficient | decimal(30,15) | 客è¿ç³»æ° | |
| | | | created_at / updated_at | datetime | å建/æ´æ°æ¶é´ | |
| | | |
| | | å¯ä¸é®ï¼`(year, date_category)`ãä»
admin å¯éè¿ç³»ç»é
ç½®èåç»´æ¤ã |
| | | |
| | | ### 计ç®ä¸å¯¼å
¥å¯¼åºå£å¾ |
| | | - éè¥ä¸æ§äººåæµå¨é =ï¼é«éè½¦æµ + æ®éå½çé车æµï¼Ã å½å¹´è忥客è¿ç³»æ°ã |
| | | - 2026 å¹´æ®éå½çéå¦å¡«åç¯æ¯åºæï¼ååªå½±åæ®éå½çé车æµéèªèº«ç¯æ¯ï¼å¡«åæ¶æâ彿¥ / ç¯æ¯åºæ - 1âï¼æªå¡«åæ¶æâ彿¥ / å䏿¥ - 1âï¼æç®äººåæµå¨éãæ»äººåæµå¨éçç»§ç»æ Excel åå
¬å¼è®¡ç®ã2024ã2025 å岿°æ®ä¸ä½¿ç¨è¯¥å段ã |
| | | - å
¬è·¯äººåæµå¨é = éè·¯è¥ä¸æ§å®¢è¿é + éè¥ä¸æ§äººåæµå¨éã |
| | | - æ»äººåæµå¨é = å
¬è·¯ + éè·¯ + æ°èª + æ°´è·¯ã |
| | | - 导å
¥æâ年份 + è忥åç±»â读å `holiday_calendar` çæ¥æï¼å°å·¥ä½ç°¿â第Næ¥âè¡è¦çåå
¥å½å¹´å®¢æµ/车æµï¼æ¥å¸¸åªè¯»åæé年份çå½å¹´åï¼åå²å¹´ä»½ä»
馿¬¡åå§åã |
| | | - 导å
¥åæ£æ¥æ¯æ¬æ¬¡ææ°å¯¼å
¥æ¥æ´æ©çå岿¥ææ¯å¦åçååï¼å¦æååå
ä¸ååºï¼è¿å差弿¸
åï¼ç¨æ·ç¡®è®¤åæå
许è¦çã |
| | | - 导åºä»¥æºå·¥ä½ç°¿ä¸ºåºç¨¿ï¼åå¡«åºå
æ°æ®ä¸å®¢è¿ç³»æ°ï¼å¿«æ¥ææ¥æçæ Word å¹¶æ¿æ¢æ¨¡æ¿ä¸ç彿¥ã累计ãç¯æ¯å忝å
容ã |
| | |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | KEY idx_type_period (report_type, report_period) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='æ¥è¡¨æå®¡æ ¸éè¿æ è®°'; |
| | | |
| | | |
| | | -- ============================================ |
| | | -- åä¸ãä¸é¡¹ç»è®¡-éè¦è忥 |
| | | -- ============================================ |
| | | |
| | | -- èåæ¥æ¥æé
ç½® |
| | | DROP TABLE IF EXISTS holiday_calendar; |
| | | CREATE TABLE holiday_calendar ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL COMMENT '年份', |
| | | holiday_type VARCHAR(32) NOT NULL COMMENT 'è忥åç±»ï¼SPRING_FESTIVAL/LABOR_DAY/NATIONAL_DAY', |
| | | holiday_date DATE NOT NULL COMMENT 'è忥å
·ä½æ¥æ', |
| | | day_index INT NOT NULL COMMENT 'ç¬¬å æ¥ï¼ä» 1 å¼å§', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_calendar_seq (year, holiday_type, day_index), |
| | | UNIQUE KEY uk_holiday_calendar_date (year, holiday_type, holiday_date), |
| | | KEY idx_holiday_calendar_date (holiday_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='éè¦èåæ¥æ¥æé
ç½®'; |
| | | |
| | | -- èåæ¥ç»¼åè¿è¾è¥ä¸æ§å®¢æµ |
| | | DROP TABLE IF EXISTS holiday_passenger_flow; |
| | | CREATE TABLE holiday_passenger_flow ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL COMMENT '年份', |
| | | flow_date DATE NOT NULL COMMENT 'æ¥æ', |
| | | transport_mode VARCHAR(32) NOT NULL COMMENT 'è¿è¾æ¹å¼ï¼RAIL/CIVIL_AVIATION/ROAD/WATER', |
| | | passenger_count DECIMAL(30,15) NOT NULL COMMENT 'åéæ
客æ°ï¼ä¸äººæ¬¡ï¼', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_passenger (year, flow_date, transport_mode), |
| | | KEY idx_holiday_passenger_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='èåæ¥ç»¼åè¿è¾è¥ä¸æ§å®¢æµæ
åµ'; |
| | | |
| | | -- èåæ¥ç»¼åè¿è¾éè¥ä¸æ§è½¦æµ |
| | | DROP TABLE IF EXISTS holiday_vehicle_flow; |
| | | CREATE TABLE holiday_vehicle_flow ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL COMMENT '年份', |
| | | flow_date DATE NOT NULL COMMENT 'æ¥æ', |
| | | road_category VARCHAR(32) NOT NULL COMMENT 'éè·¯åç±»ï¼EXPRESSWAY/NATIONAL_PROVINCIAL', |
| | | vehicle_type VARCHAR(32) NOT NULL COMMENT '车ååç±»ï¼SMALL_PASSENGER/MEDIUM_SMALL_PASSENGER', |
| | | vehicle_count DECIMAL(30,15) NOT NULL COMMENT '车æµéï¼ä¸è¾æ¬¡ï¼', |
| | | comparison_base DECIMAL(30,15) NULL COMMENT 'æ®éå½çéç¯æ¯åºæï¼ä¸è¾æ¬¡ï¼ä»
å½å¹´ä½¿ç¨ï¼', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_vehicle (year, flow_date, road_category, vehicle_type), |
| | | KEY idx_holiday_vehicle_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='èåæ¥ç»¼åè¿è¾éè¥ä¸æ§è½¦æµæ
åµ'; |
| | | |
| | | -- 客è¿ç³»æ°é
ç½® |
| | | DROP TABLE IF EXISTS holiday_passenger_coefficient; |
| | | CREATE TABLE holiday_passenger_coefficient ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL COMMENT '年份', |
| | | date_category VARCHAR(32) NOT NULL COMMENT 'æ¥æåç±»ï¼HOLIDAY/NON_HOLIDAY', |
| | | coefficient DECIMAL(30,15) NOT NULL COMMENT '客è¿ç³»æ°', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_coefficient (year, date_category) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客è¿ç³»æ°é
ç½®'; |
| New file |
| | |
| | | -- Holiday statistics module. Run against the traffic_audit database. |
| | | CREATE TABLE IF NOT EXISTS holiday_calendar ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL, |
| | | holiday_type VARCHAR(32) NOT NULL, |
| | | holiday_date DATE NOT NULL, |
| | | day_index INT NOT NULL, |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_calendar_seq (year, holiday_type, day_index), |
| | | UNIQUE KEY uk_holiday_calendar_date (year, holiday_type, holiday_date), |
| | | KEY idx_holiday_calendar_date (holiday_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | CREATE TABLE IF NOT EXISTS holiday_passenger_flow ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL, |
| | | flow_date DATE NOT NULL, |
| | | transport_mode VARCHAR(32) NOT NULL, |
| | | passenger_count DECIMAL(30,15) NOT NULL, |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_passenger (year, flow_date, transport_mode), |
| | | KEY idx_holiday_passenger_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | CREATE TABLE IF NOT EXISTS holiday_vehicle_flow ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL, |
| | | flow_date DATE NOT NULL, |
| | | road_category VARCHAR(32) NOT NULL, |
| | | vehicle_type VARCHAR(32) NOT NULL, |
| | | vehicle_count DECIMAL(30,15) NOT NULL, |
| | | comparison_base DECIMAL(30,15) NULL COMMENT 'æ®éå½çéç¯æ¯åºæï¼ä¸è¾æ¬¡ï¼ä»
å½å¹´ä½¿ç¨ï¼', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_vehicle (year, flow_date, road_category, vehicle_type), |
| | | KEY idx_holiday_vehicle_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | CREATE TABLE IF NOT EXISTS holiday_passenger_coefficient ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL, |
| | | date_category VARCHAR(32) NOT NULL, |
| | | coefficient DECIMAL(30,15) NOT NULL, |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_coefficient (year, date_category) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| New file |
| | |
| | | -- Add the optional ordinary national/provincial road comparison base. |
| | | -- The field is used only for the 2026 holiday data. |
| | | ALTER TABLE holiday_vehicle_flow |
| | | ADD COLUMN comparison_base DECIMAL(30,15) NULL |
| | | COMMENT 'æ®éå½çéç¯æ¯åºæï¼ä¸è¾æ¬¡ï¼ä»
å½å¹´ä½¿ç¨ï¼' |
| | | AFTER vehicle_count; |
| New file |
| | |
| | | -- Convert holiday statistics numeric columns from binary floating point to exact decimal storage. |
| | | -- Run once against the traffic_audit database. |
| | | ALTER TABLE holiday_passenger_flow |
| | | MODIFY COLUMN passenger_count DECIMAL(30,15) NOT NULL COMMENT 'åéæ
客æ°ï¼ä¸äººæ¬¡ï¼'; |
| | | |
| | | ALTER TABLE holiday_vehicle_flow |
| | | MODIFY COLUMN vehicle_count DECIMAL(30,15) NOT NULL COMMENT '车æµéï¼ä¸è¾æ¬¡ï¼'; |
| | | |
| | | ALTER TABLE holiday_passenger_coefficient |
| | | MODIFY COLUMN coefficient DECIMAL(30,15) NOT NULL COMMENT '客è¿ç³»æ°'; |
| | | |
| | | -- The seeded coefficient was averaged from repeated 2.4 cells through a Java double. |
| | | -- Normalize the migrated value to the exact Excel decimal. |
| | | UPDATE holiday_passenger_coefficient |
| | | SET coefficient = 2.4 |
| | | WHERE year IN (2024, 2025, 2026); |
| New file |
| | |
| | | # -*- coding: utf-8 -*- |
| | | """æ±æ»å¤§è¡¨å·®å¼æ¯å¯¹ï¼çæä»¶ vs æå·¥å®ç¨¿ |
| | | ç¨æ³: python æ±æ»å¤§è¡¨å·®å¼æ¯å¯¹.py <çæä»¶.xlsx> <æå·¥å®ç¨¿.xlsx> <è¾åºåç¼> |
| | | 说æ: é页æ"è¡æ ç¾/å表头"ååºå对é½ï¼difflibï¼ï¼å¯æ£ç¡®å¤çå¤å/å¤è¡/éå¤è¡¨å¤´ï¼ |
| | | æ¯ä¸ªä¸ä¸è´æ ¼å½å
¥ ç»æä½ç§»|忴尾差|ææ¬åæ°å|è稿/è¾
å©å|ä»
ç¨åºæ|ä»
æå·¥æ|ææ¬ä¸å|é误å¼|å®è´¨æ°å¼å·®å¼ã |
| | | """ |
| | | import datetime, io, os, re, sys |
| | | from difflib import SequenceMatcher |
| | | from openpyxl import load_workbook |
| | | from openpyxl.utils import get_column_letter |
| | | |
| | | def col_label(ws, c, hdr_rows=6): |
| | | for r in range(1, hdr_rows+1): |
| | | v = ws.cell(row=r, column=c).value |
| | | if isinstance(v, (int, float)) and not isinstance(v, bool) and 20000 < v < 60000: |
| | | d = datetime.date(1899,12,30) + datetime.timedelta(days=int(v)) |
| | | return "%d-%02d" % (d.year, d.month) |
| | | if isinstance(v, str) and v.strip() and not v.startswith("="): |
| | | return v.strip()[:20] |
| | | return "(æ 表头å)" |
| | | |
| | | def row_label(ws): |
| | | out = []; cur = ""; seen = {} |
| | | for r in range(1, ws.max_row+1): |
| | | a = ws.cell(row=r, column=1).value; b = ws.cell(row=r, column=2).value |
| | | if isinstance(a, str) and a.strip(): cur = a.strip() |
| | | if isinstance(b, str) and b.strip(): |
| | | lab = "%s|%s|%s" % (block_of(ws, r), norm_region(cur), norm_indicator(b)) |
| | | else: |
| | | lab = "%s|%s|" % (block_of(ws, r), norm_region(cur)) |
| | | seen[lab] = seen.get(lab, 0) + 1 |
| | | out.append("%s#%d" % (lab, seen[lab])) |
| | | return out |
| | | |
| | | def norm(s): |
| | | """æ ç¾å½ä¸åï¼å»ç©ºç½ãå»å
¨è§ç©ºæ ¼ãç»ä¸å¼å·ã廿âå¸/ç/å·âåç¼å·®å¼""" |
| | | if not isinstance(s, str): return "" |
| | | t = s.replace("\u3000", " ").replace("ï¼", "(").replace("ï¼", ")").strip() |
| | | t = re.sub(r"[\s]+", "", t) |
| | | return t |
| | | |
| | | def norm_region(s): |
| | | t = norm(s) |
| | | for suf in ("å¸", "ç", "èªæ²»å·", "å·"): |
| | | if t.endswith(suf) and len(t) > len(suf): t = t[:-len(suf)] |
| | | return t |
| | | |
| | | def norm_indicator(s): |
| | | t = norm(s).replace("(ä¸å¨å
¬é)", "").replace("(ä¸å¨)", "").replace("(ä¸äººå
¬é)", "").replace("(ä¸äºº)", "") |
| | | t = t.replace("å
¶ä¸è§ä¸", "è§ä¸").replace("å
¶ä¸è§ä¸", "è§ä¸") |
| | | return t |
| | | |
| | | def block_of(ws, r): |
| | | """å䏿¾æè¿ä¸ä¸ª"æ é¢è¡"ï¼B åä¸ºç©ºä¸ A/æåè¾é¿ï¼å¤ææå±åï¼ç´¯è®¡ / 彿 / å
¶å®""" |
| | | for rr in range(r, 0, -1): |
| | | a = ws.cell(row=rr, column=1).value; b = ws.cell(row=rr, column=2).value |
| | | txt = " ".join(str(x) for x in (a, b) if isinstance(x, str)) |
| | | if not isinstance(b, str) or not b.strip(): |
| | | if len(norm(txt)) >= 6: |
| | | if "累计" in txt: return "累计å" |
| | | if "åå¸å·" in txt or "æå" in txt or "æç»è¡¨" in txt: return "彿å" |
| | | return "å
¶å®" |
| | | |
| | | # å·²ç¥è®¾è®¡å·®å¼ï¼ä¸è®¡å
¥é®é¢ï¼æç¨æ· 2026-09-28 å£å¾ï¼ |
| | | KNOWN_ROWS = {" è´§è¿": ("è§ä¸+è§ä¸è´§è¿é", "è§ä¸è´§è¿é")} |
| | | KNOWN_SHEETS_ANY_BLOCK = () # æå表ç彿åç± block_of å¤ç |
| | | |
| | | def is_known_extra_row(sheet, indicator, side): |
| | | if sheet in KNOWN_ROWS and side == "ç¨åº": |
| | | for pat in KNOWN_ROWS[sheet]: |
| | | if norm_indicator(indicator) == norm_indicator(pat): return True |
| | | return False |
| | | |
| | | def num(v): |
| | | return isinstance(v, (int, float)) and not isinstance(v, bool) |
| | | |
| | | def text_num(v): |
| | | if isinstance(v, str): |
| | | s = v.strip().replace(",", "") |
| | | try: |
| | | return float(s) |
| | | except Exception: |
| | | return None |
| | | return None |
| | | |
| | | def match_labels(a, b): |
| | | sm = SequenceMatcher(None, a, b, autojunk=False) |
| | | pairs = {} |
| | | for op, i1, i2, j1, j2 in sm.get_opcodes(): |
| | | if op == "equal": |
| | | for k in range(i2-i1): pairs[i1+k] = j1+k |
| | | return pairs |
| | | |
| | | NOTE = """ |
| | | > **已确认å£å¾ï¼2026-09-28ï¼ç¨æ·ï¼ââ以ä¸ä¸è®¡å
¥é®é¢** |
| | | > 1. ` è´§è¿` æ¯å¸å· 6 è¡ vs æå·¥ 4 è¡ï¼ç¨åºæ°å¢ãè§ä¸+è§ä¸è´§è¿éï¼ä¸å¨ï¼ããè§ä¸è´§è¿éï¼ä¸å¨ï¼ãå 17 è¡ï¼å
± 34 è¡ï¼ï¼å±æ¯ç设计 â å½å
¥ãå·²ç¥è®¾è®¡(æ¯çæ°å¢è¡)ãã |
| | | > å è¡æ°ä¸åï¼ç¬¬ k 个å¸å·å¨ç¨åºéæ¯æå·¥ä½ 2Ã(k-1) è¡ï¼æ¦æ±é½ä»ç¬¬ 11 è¡èµ·ï¼é»ç³ ç¨åº 17 / æå·¥ 15ï¼ç¥åæ¶ ç¨åº 107 / æå·¥ 75ï¼ââ**纯ä½ç½®åç§»ï¼æ"å°åº+ææ "对é½åæ°å¼ä¸è´**ã |
| | | > 2. ` è´§è¿!U5:U112`ï¼æ 表头è¾
å©åï¼æ¯äººå·¥è稿 â å½å
¥ãå·²ç¥è®¾è®¡(æ 表头è稿å)ãã |
| | | > 3. åæå表ã彿åãæ¯å½ååæ¯çæ¶å°±å ä¸ç设计 â å½å
¥ãå·²ç¥è®¾è®¡(æåè¡¨å½æ/累计å)ãã |
| | | > 4. å¸å·åçå/å
¨åï¼æ¦æ± vs æ¦æ±å¸ï¼ä¸å½±å â å·²åå½ä¸åã |
| | | > 5. åæ´å°¾å·®ãææ¬åæ°ååªå¨æ±æ»é计æ°ï¼ä¸è¿ãå®è´¨æ°å¼å·®å¼ãæ¸
åã |
| | | > |
| | | > **ä»éå¤ç/æ ¸å¯¹** |
| | | > - `ç线å
车` 3 æ ¼ï¼`EO36`/`FN36`/`FO36`ï¼èé¨ å®¢è¿é 2020 累计/ç´¯è®¡åæ¯ï¼ââ**ç¨åºçææ¶éç®ä¸º -2731.41 / -1.0008 / -1.0002**ï¼æ¯ç䏿工å®ç¨¿å为 0.181777 / 2312.2199 / -0.586419ï¼çæ¯ç 2020 å¹´ååæ¯ä¸º 0ï¼éæ ¸å¯¹å
¬å¼ä¸æ°æ®ã |
| | | > - ` è´§è¿` 10 æ¡ 2022 å¹´åå¸å·ãç´¯è®¡åæ¯ãï¼ä¸å¡å·²ç¡®è®¤**软件å£å¾æ£ç¡®**ï¼æå·¥é£åéï¼ã |
| | | > - æå·¥ä¾§å¾
æ¹ï¼304 æ ¼è稿åã286 æ ¼ææ¬åæ°åã4 å¤èæ°æ®ï¼`ç线å
车!IL4/IL5` æé¾ã`IL50` åå¼å·ã` è´§è¿!EA93/EA111` ç `#DIV/0!`ï¼ã |
| | | > - æ¯ç/ç¨åºèªèº«ï¼11 个 `#REF!` + 356 个 `#DIV/0!`ï¼åå²éçï¼ã`åå¸å®¢è¿` 第 2 è¡ç¬¬ 3 个æèµ·è¡¨å¤´ä¸ºæ¥æåºåå·ã缺 2026-09 æ¯çã导åºååºæ°ä½æ£æªåã |
| | | |
| | | """ |
| | | |
| | | def main(gen, dgf, prefix): |
| | | ACTION = ("å®è´¨æ°å¼å·®å¼", "é误å¼(ç¨åº)", "é误å¼(æå·¥)", "éå¤ç(鿰弿æ¬)", "ææ¬ä¸å") |
| | | IGNORE = ("已忽ç¥(åè¾¹æå¼)", "已忽ç¥(ç»æ/设计)", "忴尾差(å¯å¿½ç¥)", "ææ¬åæ°å(å¯å¿½ç¥)", |
| | | "å·²ç¥è®¾è®¡(æ¯çæ°å¢è¡)", "å·²ç¥è®¾è®¡(æ 表头è稿å)", "å·²ç¥è®¾è®¡(æåè¡¨å½æ/累计å)", "å
¶å®") |
| | | cats = list(ACTION) + list(IGNORE) |
| | | REASON = {(" è´§è¿", "EA"): "2022å¹´åå¸å·ç´¯è®¡åæ¯ï¼ä¸å¡å·²ç¡®è®¤è½¯ä»¶å£å¾æ£ç¡®ï¼æå·¥é£åéï¼", |
| | | ("ç线å
车", "EO"): "ç¨åºçææ¶éç®ï¼çæ¯ç2020å¹´ååæ¯ä¸º0ï¼ï¼æ¯ç/æå·¥ä¸º 0.1818ï¼å¾
æ ¸å¯¹", |
| | | ("ç线å
车", "FN"): "åä¸ï¼æ¯ç/æå·¥ä¸º 2312.2199", |
| | | ("ç线å
车", "FO"): "åä¸ï¼æ¯ç/æå·¥ä¸º -0.5864"} |
| | | def reason_of(x): |
| | | ref = x[1] |
| | | col = "".join(ch for ch in ref if ch.isalpha()) |
| | | return REASON.get((x[0], col), "") |
| | | |
| | | g = load_workbook(gen, data_only=True); d = load_workbook(dgf, data_only=True) |
| | | rows_out = [] |
| | | per_sheet = [] |
| | | for sh in g.sheetnames: |
| | | if sh not in d.sheetnames: continue |
| | | wg, wd = g[sh], d[sh] |
| | | rl_g, rl_d = row_label(wg), row_label(wd) |
| | | cl_g = [col_label(wg, c) for c in range(1, wg.max_column+1)] |
| | | cl_d = [col_label(wd, c) for c in range(1, wd.max_column+1)] |
| | | colmap = match_labels(cl_g, cl_d); rowmap = match_labels(rl_g, rl_d) |
| | | cnt = {} |
| | | def bump(k): |
| | | cnt[k] = cnt.get(k, 0) + 1 |
| | | # å/è¡çç»ææ§ç¼ºå£ |
| | | unmatched_cols_g = [c-1 for c in range(1, len(cl_g)+1) if c-1 not in colmap] |
| | | unmatched_cols_d = [c-1 for c in range(1, len(cl_d)+1) if c-1 not in set(colmap.values())] |
| | | unmatched_rows_g = [r-1 for r in range(1, len(rl_g)+1) if r-1 not in rowmap] |
| | | unmatched_rows_d = [r-1 for r in range(1, len(rl_d)+1) if r-1 not in set(rowmap.values())] |
| | | for c in unmatched_cols_g: |
| | | if c < 2: continue |
| | | if cl_g[c] == "(æ 表头å)": |
| | | bump("å·²ç¥è®¾è®¡(æ 表头è稿å)"); continue |
| | | bump("已忽ç¥(ç»æ/设计)") |
| | | for c in unmatched_cols_d: |
| | | if c < 2: continue |
| | | if cl_d[c] == "(æ 表头å)": |
| | | bump("å·²ç¥è®¾è®¡(æ 表头è稿å)"); continue |
| | | bump("已忽ç¥(ç»æ/设计)") |
| | | for r in unmatched_rows_g: |
| | | if r < 4: continue |
| | | rowb = "%s" % wg.cell(row=r+1, column=2).value |
| | | if is_known_extra_row(sh, rowb, "ç¨åº"): |
| | | bump("å·²ç¥è®¾è®¡(æ¯çæ°å¢è¡)"); continue |
| | | bump("已忽ç¥(ç»æ/设计)") |
| | | for r in unmatched_rows_d: |
| | | if r < 4: continue |
| | | if "彿å" in rl_d[r] or "累计å" in rl_d[r]: |
| | | bump("å·²ç¥è®¾è®¡(æåè¡¨å½æ/累计å)"); continue |
| | | bump("已忽ç¥(ç»æ/设计)") |
| | | for i, j in sorted(colmap.items()): |
| | | if cl_g[i] == "(æ 表头å)" or cl_d[j] == "(æ 表头å)": |
| | | bump("å·²ç¥è®¾è®¡(æ 表头è稿å)"); continue |
| | | for p, q in sorted(rowmap.items()): |
| | | vg = wg.cell(row=p+1, column=i+1).value; vd = wd.cell(row=q+1, column=j+1).value |
| | | if vg == vd: continue |
| | | if vg is None and isinstance(vd, str) and vd.startswith("="): vd = None |
| | | if vd is None and isinstance(vg, str) and vg.startswith("="): vg = None |
| | | if (vg is None) and (vd is None): continue |
| | | cat = None; note = "" |
| | | tg, td = text_num(vg), text_num(vd) |
| | | if num(vg) and num(vd): |
| | | diff = abs(vg - vd) |
| | | if diff <= 1e-6: continue |
| | | rel = diff / max(abs(vd), 1e-9) |
| | | if diff <= 0.01 or rel <= 1e-4: cat = "忴尾差(å¯å¿½ç¥)" |
| | | else: cat = "å®è´¨æ°å¼å·®å¼" |
| | | note = "å·®=%.6g" % diff |
| | | elif num(vg) and (vd is None): |
| | | cat = "已忽ç¥(åè¾¹æå¼)" |
| | | elif num(vd) and (vg is None): |
| | | cat = "已忽ç¥(åè¾¹æå¼)" |
| | | elif tg is not None and num(vd) and abs(tg - vd) <= 1e-9: |
| | | cat = "ææ¬åæ°å(å¯å¿½ç¥)"; note = "æå·¥ä¸ºææ¬'%s'" % vg |
| | | elif td is not None and num(vg) and abs(td - vg) <= 1e-9: |
| | | cat = "ææ¬åæ°å(å¯å¿½ç¥)"; note = "ç¨åºä¸ºææ¬'%s'" % vd |
| | | elif isinstance(vg, str) and isinstance(vd, str): |
| | | cat = "ææ¬ä¸å"; note = "ç¨åº'%s' / æå·¥'%s'" % (vg[:18], vd[:18]) |
| | | elif isinstance(vg, str) and vg.startswith("#") : |
| | | cat = "é误å¼(ç¨åº)"; note = vg |
| | | elif isinstance(vd, str) and vd.startswith("#"): |
| | | cat = "é误å¼(æå·¥)"; note = vd |
| | | elif (num(vg) and isinstance(vd, str)) or (num(vd) and isinstance(vg, str)): |
| | | cat = "éå¤ç(鿰弿æ¬)"; note = "ç¨åº=%s æå·¥=%s" % (str(vg)[:14], str(vd)[:14]) |
| | | else: |
| | | cat = "å
¶å®"; note = "ç¨åº=%s æå·¥=%s" % (str(vg)[:14], str(vd)[:14]) |
| | | if cat: |
| | | bump(cat) |
| | | rows_out.append([sh, "%s%d" % (get_column_letter(i+1), p+1), rl_g[p][:38], cl_g[i][:18], cat, |
| | | ("" if vg is None else str(vg)[:20]), ("" if vd is None else str(vd)[:20]), note, |
| | | "%s%d" % (get_column_letter(j+1), q+1)]) |
| | | per_sheet.append((sh, cnt)) |
| | | # è¾åº CSV |
| | | csv = prefix + ".csv" |
| | | with io.open(csv, "w", encoding="utf-8-sig", newline="") as f: |
| | | f.write("页ç¾,çæä»¶åå
æ ¼,è¡æ ç¾,å表头,ç±»å«,çæä»¶å¼,æå·¥å¼,夿³¨,æå·¥åå
æ ¼\n") |
| | | for x in rows_out: |
| | | if x[4] not in ACTION: continue |
| | | f.write(",".join('"%s"' % str(v).replace('"', "'") for v in x) + "\n") |
| | | # è¾åº MD |
| | | md = prefix + ".md" |
| | | with io.open(md, "w", encoding="utf-8", newline="\n") as f: |
| | | f.write("# æ±æ»å¤§è¡¨å·®å¼æç»ï¼çæä»¶ vs æå·¥å®ç¨¿ï¼\n\n") |
| | | f.write("- çæä»¶ï¼`%s`\n- æå·¥å®ç¨¿ï¼`%s`\n- æ¯å¯¹æ¶é´ï¼%s\n- æç»æ°æ®ï¼`%s`\n\n" % (gen, dgf, datetime.datetime.now().strftime("%Y-%m-%d %H:%M"), os.path.basename(csv))) |
| | | f.write("## éé¡µæ±æ»\n\n| é¡µç¾ | " + " | ".join(cats[:7]) + " |\n|---|" + "---|"*7 + "\n") |
| | | for sh, cnt in per_sheet: |
| | | f.write("| %s | %s |\n" % (sh, " | ".join(str(cnt.get(c, 0)) for c in cats[:7]))) |
| | | f.write("\n## éå¤çå·®å¼ï¼å
¨é¨ï¼å«åå ï¼\n\n") |
| | | subs = [x for x in rows_out if x[4] in ACTION and x[4] != "ææ¬ä¸å"] |
| | | if not subs: f.write("ï¼æ ï¼\n") |
| | | for x in subs[:300]: |
| | | f.write("- **%s** %s [%s] çæä»¶=%s æå·¥=%s%s\n" % (x[0], x[1], x[2], x[5], x[6], |
| | | ("ï¼åå ï¼%sï¼" % reason_of(x)) if reason_of(x) else "")) |
| | | f.write("\n## æ é¢/表头æªè¾å·®å¼\n\n") |
| | | tt = [x for x in rows_out if x[4] == "ææ¬ä¸å"] |
| | | if not tt: f.write("ï¼æ ï¼\n") |
| | | for x in tt[:50]: |
| | | f.write("- %s %s [%s] çæä»¶=%s æå·¥=%s\n" % (x[0], x[1], x[2], x[5], x[6])) |
| | | f.write("\n## å·²æå£å¾å¿½ç¥ï¼ä»
计æ°ï¼æç»è§ CSV ä¹å¤ä¸ååºï¼\n\n| é¡µç¾ | " + " | ".join(IGNORE) + " |\n|---|" + "---|"*len(IGNORE) + "\n") |
| | | for sh, cnt in per_sheet: |
| | | f.write("| %s | %s |\n" % (sh, " | ".join(str(cnt.get(c, 0)) for c in IGNORE))) |
| | | print("å·²è¾åº:", md, "|", csv, "| æç»æ¡æ°:", len(rows_out)) |
| | | for sh, cnt in per_sheet: |
| | | if cnt: print(" %-12s %s" % (sh, dict(cnt))) |
| | | |
| | | if __name__ == "__main__": |
| | | main(sys.argv[1], sys.argv[2], sys.argv[3]) |
| | |
| | | â ââ init.sql 建åºå»ºè¡¨ + é¢ç½®è´¦å·ï¼å¿
é¡»æ§è¡ï¼ |
| | | â ââ sql_city_bus.sql åå¸å®¢è¿-å
¬äº¤æåº¦è¡¨ï¼å¿
é¡»æ§è¡ï¼ |
| | | â ââ sql_city_taxi.sql åå¸å®¢è¿-åºç§æåº¦è¡¨/ææè¡¨ï¼å¿
é¡»æ§è¡ï¼ |
| | | â ââ sql_wyc.sql ç½çº¦è½¦å
¨çæ»é/订ååå¸å·è¡¨ï¼å¿
é¡»æ§è¡ï¼ |
| | | â ââ sql_wyc.sql ç½çº¦è½¦å
¨çæ»é/订ååå¸å·è¡¨ï¼å¿
é¡»æ§è¡ï¼ |
| | | â ââ sql_holiday.sql ä¸é¡¹ç»è®¡-éè¦èåæ¥è¡¨ï¼æ§åºè¡¥å»ºæ¶æ§è¡ï¼ |
| | | â ââ sql_holiday_decimal_migration.sql è忥æ°å¼å精度è¿ç§»ï¼æ§åºåçº§ï¼ |
| | | â ââ sql_holiday_comparison_base_migration.sql èåæ¥ç¯æ¯åºæå段è¿ç§»ï¼æ§åºåçº§ï¼ |
| | | ââ README-é¨ç½²è¯´æ.txt |
| | | ``` |
| | | |
| | |
| | | |
| | | ```bash |
| | | cd db |
| | | mysql --default-character-set=utf8mb4 -u root -p < init.sql # å»ºåº + 24 å¼ åºç¡è¡¨ + é¢ç½®è´¦å· |
| | | mysql --default-character-set=utf8mb4 -u root -p < init.sql # å»ºåº + 28 å¼ åºç¡è¡¨ + é¢ç½®è´¦å· |
| | | mysql --default-character-set=utf8mb4 -u root -p -D traffic_audit < sql_city_bus.sql |
| | | mysql --default-character-set=utf8mb4 -u root -p -D traffic_audit < sql_city_taxi.sql |
| | | mysql --default-character-set=utf8mb4 -u root -p -D traffic_audit < sql_wyc.sql |
| | | ``` |
| | | |
| | | > â ï¸ **`init.sql` æ¯ç ´åæ§ç**ï¼éé¢çåºç¡è¡¨é½å¸¦ `DROP TABLE IF EXISTS`ã**å·²ææ°æ®çåºä¸åä¸ä¸è¦åè· init.sql**ï¼å¦åè¡¨æ°æ®ä¼è¢«æ¸
空é建ã |
| | | > 4 ä¸ªèæ¬å
±è¦ç 29 å¼ è¡¨ï¼init.sql 24 å¼ + åå¸å®¢è¿ 3 å¼ + ç½çº¦è½¦ 2 å¼ ï¼ã |
| | | > æ©æçæ¬åºè¥æ²¡æè忥 4 å¼ è¡¨ï¼æ§è¡ `sql_holiday.sql`ï¼è¥å·²æèåæ¥è¡¨ä½ç¼ºç²¾åº¦æç¯æ¯åºæåæ®µï¼æéæ§è¡ä¸¤ä¸ªè¿ç§»èæ¬ã |
| | | > 4 ä¸ªèæ¬å
±è¦ç 33 å¼ è¡¨ï¼init.sql 28 å¼ ï¼å
¶ä¸å«è忥 4 å¼ + åå¸å®¢è¿ 3 å¼ + ç½çº¦è½¦ 2 å¼ ï¼ã |
| | | |
| | | #### 路线 Bï¼æç°ç½/å¼åæºçæ°æ®æ´å¥æ¬è¿å»ï¼æ¨èï¼ä¸ç¨è·èæ¬ï¼ |
| | | |
| | |
| | | |
| | | - `--databases` 伿 `CREATE DATABASE` + `USE` ä¸èµ·åè¿å¤ä»½ï¼æä»¥ç®æ æºå¨**ä¸ç¨å
建åºãä¹ä¸ç¨è·é£ 4 ä¸ªèæ¬**ã |
| | | - å¤ä»½éå
å« `sys_user`ï¼è´¦å·å¯ç 䏿ºæºå¨å®å
¨ä¸è´ï¼æ¬æºæ¯ `admin / 123456`ï¼ï¼ä¸ä¼è¢«éç½®æ `admin/admin123`ã |
| | | - â ï¸ å¤ä»½éæ¯å¼ 表é½å¸¦ `DROP TABLE IF EXISTS`ï¼å
± 29 å¼ ï¼ï¼**ç®æ åºè¦æ¯å·²ææ°æ®ï¼ä¼è¢«ç´æ¥è¦ç**ã请è¿åå°ç©ºåºï¼æå
èªå·±å¤ä»½ç®æ åºã |
| | | - â ï¸ å¤ä»½éæ¯å¼ 表é½å¸¦ `DROP TABLE IF EXISTS`ï¼ä»¥å®é
å¤ä»½ä¸ºåï¼ææ°ä¸º 33 å¼ ï¼ï¼**ç®æ åºè¦æ¯å·²ææ°æ®ï¼ä¼è¢«ç´æ¥è¦ç**ã请è¿åå°ç©ºåºï¼æå
èªå·±å¤ä»½ç®æ åºã |
| | | - æ¬æ¬¡å·²çæå¹¶**éªè¯è¿**å¤ä»½ï¼`traffic_audit_full_20260913.sql`ï¼45.5 MBï¼8.0.29 客æ·ç«¯å¯¼åºï¼ã |
| | | éªè¯æ¹å¼ï¼æå¤ä»½åºåæ¹æä¸´æ¶åºå¯¼å
¥ï¼é表æ¯å¯¹ `SHOW CREATE TABLE`ï¼è¡¨ç»æï¼ä¸ `COUNT(*)`ï¼è¡æ°ï¼ââ**29 å¼ è¡¨ç»æä¸è¡æ°å 0 å·®å¼**ï¼æ½æ¥ `wyc_order_monthly` 2026-08 æ°æ®ä¸è´ï¼éªè¯åå·²å é¤ä¸´æ¶åºã |
| | | éªè¯æ¹å¼ï¼æå¤ä»½åºåæ¹æä¸´æ¶åºå¯¼å
¥ï¼é表æ¯å¯¹ `SHOW CREATE TABLE`ï¼è¡¨ç»æï¼ä¸ `COUNT(*)`ï¼è¡æ°ï¼ââ**29 å¼ è¡¨ç»æä¸è¡æ°å 0 å·®å¼ï¼æ°å¢èåæ¥æ¨¡åå为 33 å¼ ï¼**ï¼æ½æ¥ `wyc_order_monthly` 2026-08 æ°æ®ä¸è´ï¼éªè¯åå·²å é¤ä¸´æ¶åºã |
| | | |
| | | #### 路线 B2ï¼ç¨ Navicat çãå¤ä»½ / è¿åå¤ä»½ãï¼.nb3ï¼æè´´è¿æ¥å¸¸ä¹ æ¯ï¼ |
| | | |
| | |
| | | | å¯¼åºæ¥ãæªæ¾å°æ±æ»è¡¨æ¯çãYYYYå¹´Mæâ¦ãã | `docs/çææ±æ»å¤§è¡¨` ç¼ºè¯¥ææ¯ç | |
| | | | ç»å½æ¥å£ 500 | `jwt.secret` å¡«äºéæ³å符ï¼å¦ä¸æï¼ï¼æ¹æ 32 ä½åå
è¿å¶ä¸² | |
| | | | 页é¢è½å¼ä½æ¥å£ 404 | èµ° nginx æ¹æ¡æ¶æ²¡é
`/api` åä»£ï¼æ¹ç¨ jar å
åµè®¿é® 8090 | |
| | | | æ°æ®åºè¿æ¥å¤±è´¥ | æ£æ¥ `application.yml`ï¼ç¡®è®¤ 4 个 SQL èæ¬å·²æ§è¡ | |
| | | | æ°æ®åºè¿æ¥å¤±è´¥ | æ£æ¥ `application.yml`ï¼ç¡®è®¤ 5 个 SQL èæ¬å·²æ§è¡ | |
| | | | 端å£å ç¨ | æ¹ `server.port`ï¼å端å¼åç«¯å£ 8080 | |
| | | |
| | | ## åãå¼åç¯å¢æ¥å¸¸å¯å¨ |
| | |
| | | |
| | | - **2026-09-13**ï¼æ°å¢ä¸é®æå
èæ¬ `package-deploy.py`ï¼jar æ¹ä¸ºå
åµå端ï¼é¨ç½²æºä¸åéè¦ nginxï¼ï¼å é¤ Redis ç¸å
³è¯´æï¼ä»£ç æªä½¿ç¨ï¼ï¼è¡¥ `sql_wyc.sql` ä¸ `summary.template-dir`ï¼è¡¥ `jwt.secret` åå¼è§åï¼è¡¥é¨ç½²åéªè¯æ¸
åã |
| | | - 2026-08-27ï¼é¦çï¼zip é¨ç½²å
+ nginx æ¹æ¡ï¼ã |
| | | |
| | | --- |
| | | |
| | | ## å¯é¥ä¸é
ç½®ï¼2026-09-26 èµ·ï¼æå
å¿
è¯»ï¼ |
| | | |
| | | ### åå±çº¦å® |
| | | |
| | | | ä½ç½® | ç¨é | æ¯å¦è¿ git | æ¯å¦è¿ jar | |
| | | | --- | --- | --- | --- | |
| | | | `traffic-audit-server/src/main/resources/application.yml` | è¿ jar çé»è®¤é
ç½®ï¼**åªæå ä½ç¬¦**ï¼`${TRAFFIC_DB_PASSWORD:}`ã`${DEEPSEEK_API_KEY:}` çï¼ | å¦ï¼å·² gitignoreï¼ç¨ `application.yml.example` 忍¡æ¿ï¼ | **æ¯**ï¼æ çå®å¼ï¼ | |
| | | | `traffic-audit-server/config/application.yml` | æ¬æºå¼åçå®å¼ï¼åºå£ä»¤ / JWT / DeepSeek keyï¼ | å¦ï¼å·² gitignoreï¼ | å¦ | |
| | | | jar å级ç `backend/application.yml` | é¨ç½²æºçå®å¼ï¼ä¼å
级é«äº jar å
ç½®ï¼ | å¦ | å¦ï¼å¨å
å¤ï¼ç°åºå¡«ï¼ | |
| | | | ç¯å¢åé `TRAFFIC_DB_PASSWORD` / `TRAFFIC_DB_USER` / `TRAFFIC_DB_URL` / `TRAFFIC_JWT_SECRET` / `DEEPSEEK_API_KEY` | è¦ç以ä¸ä»»æä¸é¡¹ | å¦ | å¦ | |
| | | |
| | | ### æ¬æºå¼åï¼å¼æ¾ç¯å¢åéï¼ä¸è½æä»¶ï¼ |
| | | |
| | | ```powershell |
| | | setx TRAFFIC_DB_PASSWORD "æ¬æº MySQL å£ä»¤" # è®¾ä¸æ¬¡å³å¯ï¼ä¹åæ°å¼çç»ç«¯èªå¨å¸¦ä¸ |
| | | setx DEEPSEEK_API_KEY "DeepSeek key" |
| | | # éè¦æ¶åè¦çï¼setx TRAFFIC_DB_URL / TRAFFIC_DB_USER / TRAFFIC_JWT_SECRET |
| | | |
| | | powershell -NoProfile -ExecutionPolicy Bypass -File start-dev.ps1 # ä¼èªå¨ä»æ³¨å表补é½å¹¶æ³¨å
¥å端è¿ç¨ |
| | | ``` |
| | | |
| | | - `traffic-audit-server/config/application.yml` åªç注é说æï¼æ ææï¼ï¼å æå®ä¹ä¸å½±åï¼å¼å
¨å¨ç¯å¢åééï¼ã |
| | | - è¥ç¨ IDE ç´æ¥è·ä¸»ç±»ï¼è¯·å¨**æ°å¼ç**ç»ç«¯/IDE éå¯å¨ï¼è®©æ°ç»ç«¯ç»§æ¿ç¨æ·ç¯å¢åéï¼ï¼ææå¨ `setx` åéå¯ IDEã |
| | | |
| | | ### æå
åèªæ¥ï¼30 ç§ï¼ |
| | | |
| | | ```powershell |
| | | # 1) ç¡®è®¤è¿ jar çé
置没æçå®å¯é¥ï¼åºåªçå° ${...}ï¼ |
| | | Select-String -Path traffic-audit-server\src\main\resources\application.yml -Pattern "password:|api-key:|secret:" |
| | | # 2) æå®å
ååéªè¯ä¸æ¬¡ jar å
é¨ |
| | | python -c "import zipfile;print(zipfile.ZipFile('traffic-audit-server/target/traffic-audit-server-1.0.0-SNAPSHOT.jar').read('BOOT-INF/classes/application.yml').decode('utf-8'))" | Select-String "password:|api-key:" |
| | | ``` |
| | | |
| | | |
| | | ### ä¸é®æå
ï¼æ¨èï¼2026-09-27 èµ·ï¼ |
| | | |
| | | ```powershell |
| | | powershell -NoProfile -ExecutionPolicy Bypass -File pack-deploy.ps1 |
| | | # å¯éåæ°ï¼ -OutRoot "E:\è¯éª" -SkipFrontend |
| | | ``` |
| | | |
| | | èæ¬ååä»¶äºï¼ |
| | | 1. `mvn clean package -DskipTests` æå»ºå端 jarï¼ |
| | | 2. **è§£å jar èªæ£**ï¼æ£æ¥ `BOOT-INF/classes/application.yml` æ¯å¦å
¨ä¸ºå ä½ç¬¦ââ䏿¦åç° `sk-` æææå£ä»¤**ç´æ¥ä¸æ¢æå
**ï¼ |
| | | 3. `npm run build` é建å端 `dist`ï¼ |
| | | 4. æ `deploy-templates/` ç»è£
宿´é¨ç½²å
ï¼backend / frontend / db / README-é¨ç½²è¯´æ.txtï¼ï¼è¾åºå° `<OutRoot>\traffic-audit-deploy-<yyyyMMdd>`ã |
| | | |
| | | 模æ¿ä»¶ç»ä¸æ¾å¨ **`deploy-templates/`**ï¼å·²å
¥åºï¼ï¼å¤é¨ `application.yml`ã`start-backend.cmd/.sh`ã`nginx.conf.example`ã`db\{init.sql,sql_city_bus,sql_city_taxi,sql_wyc}.sql`ã`README-é¨ç½²è¯´æ.txt`ãæ¹æ¨¡æ¿å³æ¹æå
产ç©ã |
| | | |
| | | ### è½®æ¢å¯é¥ååæ¥æ¬æº |
| | | |
| | | ```powershell |
| | | powershell -NoProfile -ExecutionPolicy Bypass -File set-secrets.ps1 |
| | | ``` |
| | | |
| | | 交äºå¼è¾å
¥ï¼åè½¦è·³è¿æä¸é¡¹ï¼ï¼èªå¨ `setx` å°ç¨æ·ç¯å¢åéå¹¶æç¤ºéå¯å端ï¼å¼ä¸ä¼åå
¥ä»»ä½æä»¶ã |
| | | |
| | | ### é¨ç½²æºä¸å /æ¢ DeepSeek key |
| | | |
| | | 1. æå
ä¸ä¼ åï¼ç¼è¾ **jar å级**ç `backend/application.yml`ï¼`deepseek.api-key: "ä½ çæ°key"`ï¼ç空åé¡µé¢ AI åæä¸å¯ç¨ï¼å
¶ä½åè½æ£å¸¸ï¼ï¼ |
| | | 2. éå¯å端ï¼`start-backend.cmd`ï¼æ `java -jar traffic-audit-server-1.0.0-SNAPSHOT.jar`ï¼ï¼ |
| | | 3. 页é¢éç¹ä¸æ¬¡ AI åæç¡®è®¤å¯ç¨ï¼ |
| | | 4. **ä¸è¦**廿¹ jar å
é¨çé
ç½®ï¼æ¹äºä¸æ¬¡åå
就丢ï¼ï¼ä¹ä¸è¦æ key æäº¤è¿ä»»ä½ä»åºæä»¶ã |
| | | |
| | | ### è½®æ¢ï¼ä½åºï¼å·²æ³é²ç key |
| | | |
| | | 1. ç»å½ DeepSeek æ§å¶å° â API Keys â æ°å»ºä¸ä¸ª keyï¼ |
| | | 2. å é¤/ç¦ç¨æ§ keyï¼æ§ key ç«å»å¤±æï¼æ¤åæå
带åºå»ç jar éé£ä»½å³ä½åºï¼ï¼ |
| | | 3. æä¸é¢ç¬¬ 1~3 æ¥ææ° key å¡«å°é¨ç½²æºçå¤é¨é
置并éå¯éªè¯ã |
| New file |
| | |
| | | # æ°æ®æ¥çãæå¸å·æ±æ»ãä¸ãæ±æ»å¤§è¡¨ãå£å¾å¯¹åºå
³ç³»ä¸ä¸è´æ§æ ¸å¯¹ï¼2026-09-23ï¼ |
| | | |
| | | > æ ¸å¯¹æ¥æï¼2026-09-23ãæé®ï¼æ°æ®æ¥çéçæ±æ»åè½ä¸å¤§è¡¨çæçæ°æ®æ¯å¦ä¸è´ |
| | | > æ ¸å¯¹æ¹å¼ï¼å¤§è¡¨åèªç¨åºè¾åº `D:\æ¡é¢\çææ°æ®\çæ_éè·¯è¿è¾éæ±æ»è¡¨_2026-08 (3).xlsx`ï¼å«ç¼åç´åºï¼2026-09-22 14:10 çæï¼ï¼æ°æ®æ¥çåèª `/api/data/summary?from=2026-01&to=2026-08` |
| | | > æ ¸å¯¹æ¹å¼ï¼å¤§è¡¨éæåå æ» vs æ±æ»æ¥å£é弿¯å¯¹ï¼å½æ¶ç¨ç临æ¶èæ¬ä¸ä¸é´æä»¶å·²éåå²ä¸´æ¶ä»¶æ¸
çï¼éè¦å¤ç°ææ¬æç¬¬åèæ¹æ³éè·ï¼ |
| | | |
| | | ## ä¸ãç»è®º |
| | | |
| | | **ä¸è´ã** ä¸ä¸ªå¯ç´æ¥å¯¹ä½çå£å¾ï¼åå¸å
¬äº¤ã巡游åºç§ãç½çº¦è½¦ï¼**éä½ç¸å**ï¼ |
| | | |
| | | | é¡¹ç® | æ¯å¯¹å¼ä¸ªæ° | è¶
åºå®¹å·®(>0.01) | æå¤§ç»å¯¹å·® | |
| | | | --- | --- | --- | --- | |
| | | | åå¸å
¬äº¤ï¼å®¢è¿é/æ
客å¨è½¬é/åå¸å
两项 à å
¨ç+17å¸å·ï¼ | 72 | 0 | 0.000000 | |
| | | | 巡游åºç§ï¼åä¸ 4 ææ ï¼ | 72 | 0 | 0.000000 | |
| | | | ç½çº¦è½¦ï¼æåå£å¾ 4 ææ ï¼ | 72 | 0 | 0.000000 | |
| | | |
| | | è´§è¿ä¾§å 为大表åãè§ä¸ / è§ä¸ / å计ãä¸åï¼éæå对åºï¼è§ç¬¬äºèï¼ï¼**对åºåä¹å®å
¨ä¸è´**ã |
| | | |
| | | ## äºãå£å¾å¯¹åºå
³ç³» |
| | | |
| | | | æ°æ®æ¥çç±»å | 对åºå¤§è¡¨ä½ç½® | å£å¾ | æ ¸å¯¹æ ·ä¾ï¼2026 å¹´ 1-8 æï¼ | |
| | | | --- | --- | --- | --- | |
| | | | `cityBus` åå¸å
Œ
±äº¤é | ãå
¬äº¤ã页åå¸å· 4 ææ | å°è´¦å¼éæç¸å | å
¨ç客è¿é 110776.34 ä¸äººæ¬¡ãæ
客å¨è½¬é 738254.64 ä¸äººå
¬é ââ éä½ä¸è´ | |
| | | | `cityTaxi` 巡游åºç§ | ãåºç§ã页åå¸å· 4 ææ | åä¸ | å
¨ç客è¿é 48069.02 ä¸äººæ¬¡ãæ
客å¨è½¬é 280664.05 ä¸äººå
¬é ââ éä½ä¸è´ | |
| | | | `wycOrder` ç½çº¦è½¦ | ãç½çº¦è½¦ã页åå¸å· 4 ææ | æåå£å¾ï¼è®¢åå æ¯ à å
¨ç pinï¼åå¸å
æå·¡æ¸¸åºç§å æ¯ä»£çï¼ï¼ä¸ `WycSplitCalc` åæº | å
¨ç客è¿é 33864.94 ä¸äººæ¬¡ç 4 项 ââ éä½ä¸è´ | |
| | | | `h2032` è´§è¿ä¼ä¸ | ãè´§è¿éæå / è´§è¿å¨è½¬éæåãç**è§ä¸**å | è§ä¸ä¼ä¸ææ¥ï¼h2032ï¼ | å
¨çè§ä¸è´§è¿é 31399.8248 ä¸å¨ = 313,998,248 å¨ï¼ä¸è´ï¼ï¼æ¦æ± 10072.9811 ä¸å¨ = 100,729,811 å¨ï¼ä¸è´ï¼ | |
| | | | `freightTurnover` è´§è¿éå¨è½¬é | ãéè·¯è¿è¾å¨è½¬éãçé路货è¿å¨è½¬éåï¼è´§è¿é = 大表ãè§ä¸ + è§ä¸ãå计 | è§ä¸+è§ä¸ï¼å
¨å£å¾ï¼ | å
¨çå¨è½¬é 17,492,129.0479 ä¸å¨å
¬éï¼ä¸è´ï¼ï¼å
¨çè´§è¿é 130,311.89 ä¸å¨ = è§ä¸ 31399.8248 + è§ä¸ 98912.0679ï¼ä¸è´ï¼ | |
| | | |
| | | ## ä¸ãå£å¾å·®å¼ï¼ä¸æ¯æ°æ®å·®å¼ï¼æ¯è®¾è®¡å·®å¼ï¼ |
| | | |
| | | 1. **æ¶ç¹å**ï¼æ°æ®æ¥çæ±æ»æç¨æ·è¦æ±ä¸æ¾ç¤ºè½¦è¾æ°/ä¼ä¸æ°/ç«ç¹æ°ï¼ä¸åç´¯å ï¼ï¼å¤§è¡¨çè½¦è¾æ°çææåèªå°è´¦ãæ ¸å¯¹è§æ¨¡ææ 请åæç»æç´æ¥ç大表对åºé¡µã |
| | | 2. **æ¯çå**ï¼æ°æ®æ¥çæ±æ»æç´¯è®¡å£å¾éç®ï¼ç´¯è®¡åæ¯ = æ¬æç´¯è®¡ ÷ ä¸å¹´åæç´¯è®¡ â 1ï¼å¹³åè¿è· = 累计å¨è½¬é ÷ 累计客è¿éï¼ï¼å¤§è¡¨æ¯éæåæ¯å + çå¼åºå®ã两è
"æåº¦åæ¯"å"ç´¯è®¡åæ¯"ä¸è½ç´æ¥äºæ¯ï¼éæåä¸å£å¾åå¼ã |
| | | 3. **è¦çèå´**ï¼æ°æ®æ¥çåªè¦ç**å¯å¯¼å
¥çæ°æ®è¡¨**ï¼7 ç±»æ¯ææ±æ»ï¼ï¼å¤§è¡¨è¿å«ç线å
车ãå
¬è·¯æ»å®¢è¿ãä¸å£å¾æç»/æå/åæã轨é轮渡ãå叿±æ»/åå¸å·ãæèµä¸è½èæ¥è¡¨çï¼è¿äºä¸å¨æ°æ®æ¥çèå´å
ã |
| | | 4. **åä½**ï¼`h2032` ä¸ºå¨ / å¨å
¬éï¼`freightTurnover` 䏿å表为ä¸å¨ / ä¸å¨å
¬éï¼å¤§è¡¨ç»ä¸ç¨ä¸å¨ / ä¸å¨å
¬éï¼æ°æ®æ¥çååå·²æ æ³¨åä½ï¼ã |
| | | |
| | | ## åã夿 ¸æ¹æ³ï¼å¯å¤ç°ï¼ |
| | | |
| | | 1. `GET /api/report/exportTask/summaryWorkbook?period=2026-08` çæ/åç¼å â å `çæ_éè·¯è¿è¾éæ±æ»è¡¨_2026-08.xlsx`ï¼ |
| | | 2. `GET /api/data/summary?type=<ç±»å>&from=2026-01&to=2026-08` åæ±æ»ï¼ |
| | | 3. è¿è¡ `_tmp_cmp_dabiao2.py`ï¼æå¤§è¡¨é¡µç¾ç第 3 è¡è¡¨å¤´è¯å« 2026 å¹´åæåï¼éå¸å·éææ å æ»å䏿±æ»æ¥å£é弿¯å¯¹ï¼å®¹å·® 0.01ï¼ã |
| | |
| | | | `_chk_all_pref.txt` | 17 å¸å·ä¸¤é¡µåæ¯å·®å¼æ¸
å | |
| | | | `_chk_code*.txt`ã`_chk_wyccalc.txt`ã`_chk_wycdb.txt`ã`_chk_filler.txt`ã`_chk_freight.txt` | ç¸å
³ä»£ç 段æå½ | |
| | | |
| | | > 说æï¼2026-09-27ï¼ï¼ä¸è¡¨ä¸ç `_chk_*` è¯æ®ä»¶ä¸ºå½æ¶çä¸´æ¶ææ¬ dumpï¼å·²éåå²ä¸´æ¶æä»¶ä¸å¹¶æ¸
çï¼æ¬å°ä¸åä¿çï¼ï¼å¦éå¤ç°ï¼ææ¬ç« æ¹æ³éè·ï¼æ°æ®æºä¸æ¥éª¤è§ä¸ï¼ã |
| | | |
| | | ## 11. å¾
ç¨æ·/ä¸å¡ç¡®è®¤æ¸
åï¼æ¥æäººå
å«èªå·±æï¼ |
| | | |
| | | 1. ~~P0-1 èµ°æ¹æ¡ A è¿æ¯ Bï¼ï¼å»ºè®® Aï¼~~ ç¨æ·å·²éæ©æ¹æ¡ Aï¼2026-09-21 已宿½å¹¶åå½éè¿ã |
| New file |
| | |
| | | <# |
| | | ä¸é®æå
é¨ç½²å
ï¼å«"å»å¯èªæ£"ï¼ââ 2026-09-27 æ°å¢ |
| | | ç¨æ³ï¼ |
| | | powershell -NoProfile -ExecutionPolicy Bypass -File pack-deploy.ps1 |
| | | powershell -NoProfile -ExecutionPolicy Bypass -File pack-deploy.ps1 -OutRoot "E:\è¯éª" -SkipFrontend |
| | | ååä»¶äºï¼ |
| | | 1) mvn clean package æå»ºå端 jar |
| | | 2) è§£å jar æ£æ¥å
ç½® application.ymlï¼å¿
é¡»å
¨æ¯å ä½ç¬¦ï¼ç¦æ¢ sk- å¯é¥ / ææå£ä»¤ï¼ï¼ä¸åæ ¼ç´æ¥ä¸æ¢ |
| | | 3) npm run build æå»ºå端 distï¼å¯ -SkipFrontendï¼ |
| | | 4) ç»è£
deploy ç®å½ç»æï¼backend / frontend / db / README-é¨ç½²è¯´æ.txtï¼ï¼è¾åºå° <OutRoot>\traffic-audit-deploy-<yyyyMMdd> |
| | | #> |
| | | param( |
| | | [string]$OutRoot = 'E:\è¯éª', |
| | | [switch]$SkipFrontend |
| | | ) |
| | | |
| | | $ErrorActionPreference = 'Stop' |
| | | $root = Split-Path -Parent $MyInvocation.MyCommand.Path |
| | | $tplDir = Join-Path $root 'deploy-templates' # æå
模æ¿ä»¶ï¼å·²å
¥åºï¼2026-09-27 èµ·ï¼ |
| | | $jarRel = 'traffic-audit-server\target\traffic-audit-server-1.0.0-SNAPSHOT.jar' |
| | | $jar = Join-Path $root $jarRel |
| | | |
| | | Write-Host '[1/5] æå»ºå端ï¼mvn clean package -DskipTestsï¼...' |
| | | Push-Location $root |
| | | cmd /c "mvn.cmd -q clean package -DskipTests -f traffic-audit-server\pom.xml > pack-build.log 2>&1" |
| | | $rc = $LASTEXITCODE |
| | | Pop-Location |
| | | if ($rc -ne 0) { throw 'å端æå»ºå¤±è´¥ï¼è¯¦è§ pack-build.log' } |
| | | if (-not (Test-Path $jar)) { throw "æªæ¾å°æå»ºäº§ç©ï¼$jar" } |
| | | Write-Host (" 产ç©ï¼{0:N1} MB" -f ((Get-Item $jar).Length / 1MB)) |
| | | |
| | | Write-Host '[2/5] å»å¯èªæ£ï¼è§£å jar æ£æ¥ BOOT-INF/classes/application.yml ...' |
| | | Add-Type -AssemblyName System.IO.Compression.FileSystem |
| | | $zip = [System.IO.Compression.ZipFile]::OpenRead($jar) |
| | | try { |
| | | $entry = $zip.Entries | Where-Object { $_.FullName -eq 'BOOT-INF/classes/application.yml' } |
| | | if (-not $entry) { throw 'jar å
ç¼ºå° BOOT-INF/classes/application.yml' } |
| | | $sr = New-Object System.IO.StreamReader($entry.Open()) |
| | | $text = $sr.ReadToEnd() |
| | | $sr.Close() |
| | | } finally { $zip.Dispose() } |
| | | |
| | | $bad = @() |
| | | if ($text -match 'sk-[A-Za-z0-9]{16,}') { $bad += 'çä¼¼ææ API keyï¼sk-â¦ï¼' } |
| | | foreach ($line in ($text -split "`n")) { |
| | | if ($line -match '^\s*(password|api-key|secret)\s*:\s*(.+)$') { |
| | | $v = $matches[2].Trim().Trim('"') |
| | | if ($v -and -not $v.StartsWith('${')) { $bad += ("$($matches[1]) 䏿¯å ä½ç¬¦") } |
| | | } |
| | | } |
| | | if ($bad.Count -gt 0) { |
| | | throw ('å»å¯èªæ£æªéè¿ï¼' + ($bad -join 'ï¼') + 'ãè¯·æ£æ¥ traffic-audit-server\src\main\resources\application.yml') |
| | | } |
| | | Write-Host ' éè¿ï¼jar å
ç½®é
ç½®å
¨é¨ä¸ºå ä½ç¬¦ï¼æ ææå¯é¥/å£ä»¤ã' |
| | | |
| | | Write-Host '[3/5] æå»ºå端ï¼npm run buildï¼å¯ -SkipFrontend è·³è¿ï¼...' |
| | | if (-not $SkipFrontend) { |
| | | Push-Location (Join-Path $root 'traffic-audit-web') |
| | | cmd /c "npm.cmd run build > ..\pack-front.log 2>&1" |
| | | $rc2 = $LASTEXITCODE |
| | | Pop-Location |
| | | if ($rc2 -ne 0) { throw 'å端æå»ºå¤±è´¥ï¼è¯¦è§ pack-front.log' } |
| | | Write-Host ' å端 dist å·²é建' |
| | | } |
| | | |
| | | Write-Host '[4/5] ç»è£
é¨ç½²ç®å½...' |
| | | $stamp = Get-Date -Format 'yyyyMMdd' |
| | | $dest = Join-Path $OutRoot ("traffic-audit-deploy-" + $stamp) |
| | | $n = 2 |
| | | while (Test-Path $dest) { $dest = Join-Path $OutRoot ("traffic-audit-deploy-" + $stamp + "-" + $n); $n++ } |
| | | New-Item -ItemType Directory -Force -Path (Join-Path $dest 'backend'), (Join-Path $dest 'frontend'), (Join-Path $dest 'db') | Out-Null |
| | | |
| | | Copy-Item $jar (Join-Path $dest 'backend') -Force |
| | | Copy-Item (Join-Path $tplDir 'backend\application.yml') (Join-Path $dest 'backend') -Force |
| | | Copy-Item (Join-Path $tplDir 'backend\start-backend.cmd') (Join-Path $dest 'backend') -Force |
| | | Copy-Item (Join-Path $tplDir 'backend\start-backend.sh') (Join-Path $dest 'backend') -Force |
| | | Copy-Item (Join-Path $root 'traffic-audit-web\dist') (Join-Path $dest 'frontend') -Recurse -Force |
| | | Copy-Item (Join-Path $tplDir 'frontend\nginx.conf.example') (Join-Path $dest 'frontend') -Force |
| | | Copy-Item (Join-Path $tplDir 'db\*.sql') (Join-Path $dest 'db') -Force |
| | | |
| | | # docsï¼è¿è¡æ¶æ¨¡æ¿ + ç¨æ·ææ¡£ï¼æé¤å¼åè¿ç¨ææ¡£ä¸ç¼å/å¤ä»½ï¼åå°ä½ç§¯ãé¿å
æå
鍿¸
å带åºå»ï¼ |
| | | $docsDest = Join-Path $dest 'backend\docs' |
| | | New-Item -ItemType Directory -Force -Path $docsDest | Out-Null |
| | | cmd /c "robocopy `"$root\docs`" `"$docsDest`" /E /NFL /NDL /NJH /NJS /NP /XD _cache 工使¥ç» åè½æµè¯æ¥å é®é¢æ±æ» /XF çæ_*.xlsx _bak_* >nul" |
| | | if ($LASTEXITCODE -ge 8) { throw "docs å¤å¶å¤±è´¥ï¼robocopy è¿å $LASTEXITCODEï¼" } |
| | | # db èæ¬ä¹æ¾ä¸ä»½å° docsï¼ä¸æ§å
ä¸è´ï¼docs/init.sql çï¼ |
| | | Copy-Item (Join-Path $tplDir 'db\init.sql'), (Join-Path $tplDir 'db\sql_city_bus.sql'), (Join-Path $tplDir 'db\sql_city_taxi.sql'), (Join-Path $tplDir 'db\sql_wyc.sql'), (Join-Path $tplDir 'db\sql_holiday.sql'), (Join-Path $tplDir 'db\sql_holiday_decimal_migration.sql'), (Join-Path $tplDir 'db\sql_holiday_comparison_base_migration.sql') $docsDest -Force -ErrorAction SilentlyContinue |
| | | |
| | | # READMEï¼ç¨æ§å
模æ¿å¹¶æ¿æ¢æ¥æå ä½ |
| | | $readme = Get-Content (Join-Path $tplDir 'README-é¨ç½²è¯´æ.txt') -Raw -Encoding UTF8 |
| | | $readme = $readme.Replace('{stamp}', $stamp) |
| | | Set-Content -LiteralPath (Join-Path $dest 'README-é¨ç½²è¯´æ.txt') -Value $readme -Encoding UTF8 |
| | | |
| | | Write-Host '[5/5] æ±æ»...' |
| | | $files = Get-ChildItem $dest -Recurse -File |
| | | Write-Host (" è¾åºç®å½ï¼{0}" -f $dest) |
| | | Write-Host (" æä»¶æ°ï¼{0}ãå计ï¼{1:N2} MB" -f $files.Count, (($files | Measure-Object Length -Sum).Sum / 1MB)) |
| | | Write-Host ' æéï¼æå
æ·å°é¨ç½²æºåï¼åªéæ¹ backend\application.yml éçæ°æ®åºä¸å¤ + éè¦æ¶å¡« deepseek.api-keyã' |
| New file |
| | |
| | | <# |
| | | 设置/æ´æ°æ¬æºå¯é¥ç¯å¢åéï¼å¼ä¸è½æä»¶ï¼ââ è½®æ¢ DeepSeek key åè·è¿ä¸ª |
| | | ç¨æ³ï¼powershell -NoProfile -ExecutionPolicy Bypass -File set-secrets.ps1 |
| | | ï¼åè½¦è·³è¿æä¸é¡¹ï¼è¡¨ç¤ºä¸æ¹ï¼ |
| | | #> |
| | | $ErrorActionPreference = 'Stop' |
| | | |
| | | function Read-Secret([string]$prompt) { |
| | | $sec = Read-Host -Prompt $prompt -AsSecureString |
| | | if ($null -eq $sec) { return '' } |
| | | $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec) |
| | | try { return [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) } |
| | | finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) } |
| | | } |
| | | |
| | | foreach ($name in @('DEEPSEEK_API_KEY', 'TRAFFIC_DB_PASSWORD')) { |
| | | $cur = [Environment]::GetEnvironmentVariable($name, 'User') |
| | | $hint = if ($cur) { 'ï¼å·²è®¾ç½®ï¼é¿åº¦ ' + $cur.Length + 'ï¼' } else { 'ï¼å½åæªè®¾ç½®ï¼' } |
| | | $val = Read-Secret ("è¾å
¥ " + $name + " " + $hint + "ï¼ç´æ¥å车跳è¿") |
| | | if ([string]::IsNullOrWhiteSpace($val)) { Write-Host (" è·³è¿ " + $name); continue } |
| | | & setx.exe $name $val | Out-Null |
| | | if ($LASTEXITCODE -eq 0) { |
| | | [Environment]::SetEnvironmentVariable($name, $val, 'Process') |
| | | Write-Host (" å·²æ´æ° " + $name) |
| | | } else { |
| | | Write-Host (" æ´æ°å¤±è´¥ï¼" + $name) |
| | | } |
| | | } |
| | | Write-Host '' |
| | | Write-Host '宿ãçææ¹å¼ï¼æ°å¼çç»ç«¯èªå¨å¸¦ä¸ï¼ç¨ start-dev.ps1 å¯å¨ä¹ä¼èªå¨ä»æ³¨å表载å
¥ã' |
| | | Write-Host 'ï¼è¥å端æ£å¨è¿è¡ï¼è¯·éå¯å端ï¼åæ 8090 è¿ç¨åéæ°æ§è¡ start-dev.ps1ï¼' |
| | |
| | | $root = Split-Path -Parent $MyInvocation.MyCommand.Path |
| | | $webDir = Join-Path $root 'traffic-audit-web' |
| | | |
| | | # ---------- å¯é¥ï¼ä» Windows ç¨æ·ç¯å¢åéè¡¥é½ï¼çå®å¼ä¸è½æä»¶ï¼---------- |
| | | # è¥å½å shell æ¯æ§è¿ç¨ãæ¿ä¸å°æ°è®¾çåéï¼å仿³¨å表 HKCU\Environment è¯»ä¸æ¬¡ï¼ |
| | | # ä¾å端åè¿ç¨ç»§æ¿ï¼é¿å
"å¯é¥ä¸å¨ä»»ä½æä»¶éãå读ä¸å°ç¯å¢åé"ç空档ï¼ã |
| | | foreach ($name in @('TRAFFIC_DB_PASSWORD', 'DEEPSEEK_API_KEY', 'TRAFFIC_DB_URL', 'TRAFFIC_DB_USER', 'TRAFFIC_JWT_SECRET')) { |
| | | if (-not [Environment]::GetEnvironmentVariable($name, 'Process')) { |
| | | $val = [Environment]::GetEnvironmentVariable($name, 'User') |
| | | if ($val) { |
| | | [Environment]::SetEnvironmentVariable($name, $val, 'Process') |
| | | Write-Host "å·²ä»ç¨æ·ç¯å¢åéè½½å
¥ $name" |
| | | } |
| | | } |
| | | } |
| | | |
| | | function Test-PortListening($port) { |
| | | try { |
| | | $c = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction Stop |
| New file |
| | |
| | | package com.trafficaudit.holiday; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | public final class HolidayConstants { |
| | | public static final int COMPARISON_BASE_YEAR = 2026; |
| | | |
| | | public static final String TYPE_SPRING_FESTIVAL = "SPRING_FESTIVAL"; |
| | | |
| | | public static final String TYPE_LABOR_DAY = "LABOR_DAY"; |
| | | public static final String TYPE_NATIONAL_DAY = "NATIONAL_DAY"; |
| | | |
| | | public static final String CATEGORY_HOLIDAY = "HOLIDAY"; |
| | | public static final String CATEGORY_NON_HOLIDAY = "NON_HOLIDAY"; |
| | | |
| | | public static final String MODE_RAIL = "RAIL"; |
| | | public static final String MODE_CIVIL_AVIATION = "CIVIL_AVIATION"; |
| | | public static final String MODE_ROAD = "ROAD"; |
| | | public static final String MODE_WATER = "WATER"; |
| | | |
| | | public static final String ROAD_EXPRESSWAY = "EXPRESSWAY"; |
| | | public static final String ROAD_NATIONAL_PROVINCIAL = "NATIONAL_PROVINCIAL"; |
| | | |
| | | public static final String VEHICLE_SMALL_PASSENGER = "SMALL_PASSENGER"; |
| | | public static final String VEHICLE_MEDIUM_SMALL_PASSENGER = "MEDIUM_SMALL_PASSENGER"; |
| | | |
| | | public static final Map<String, String> HOLIDAY_TYPE_LABELS; |
| | | public static final Map<String, String> DATE_CATEGORY_LABELS; |
| | | public static final Map<String, String> TRANSPORT_MODE_LABELS; |
| | | public static final Map<String, String> ROAD_CATEGORY_LABELS; |
| | | public static final Map<String, String> VEHICLE_TYPE_LABELS; |
| | | |
| | | private static final Set<String> HOLIDAY_TYPES; |
| | | private static final Set<String> DATE_CATEGORIES; |
| | | private static final Set<String> TRANSPORT_MODES; |
| | | private static final Set<String> ROAD_CATEGORIES; |
| | | private static final Set<String> VEHICLE_TYPES; |
| | | |
| | | static { |
| | | Map<String, String> holidayTypes = new LinkedHashMap<>(); |
| | | holidayTypes.put(TYPE_SPRING_FESTIVAL, "æ¥è"); |
| | | holidayTypes.put(TYPE_LABOR_DAY, "äºä¸"); |
| | | holidayTypes.put(TYPE_NATIONAL_DAY, "åä¸"); |
| | | HOLIDAY_TYPE_LABELS = Collections.unmodifiableMap(holidayTypes); |
| | | HOLIDAY_TYPES = new LinkedHashSet<>(holidayTypes.keySet()); |
| | | |
| | | Map<String, String> dateCategories = new LinkedHashMap<>(); |
| | | dateCategories.put(CATEGORY_HOLIDAY, "è忥"); |
| | | dateCategories.put(CATEGORY_NON_HOLIDAY, "éè忥"); |
| | | DATE_CATEGORY_LABELS = Collections.unmodifiableMap(dateCategories); |
| | | DATE_CATEGORIES = new LinkedHashSet<>(dateCategories.keySet()); |
| | | |
| | | Map<String, String> transportModes = new LinkedHashMap<>(); |
| | | transportModes.put(MODE_RAIL, "éè·¯"); |
| | | transportModes.put(MODE_CIVIL_AVIATION, "æ°èª"); |
| | | transportModes.put(MODE_ROAD, "éè·¯"); |
| | | transportModes.put(MODE_WATER, "æ°´è·¯"); |
| | | TRANSPORT_MODE_LABELS = Collections.unmodifiableMap(transportModes); |
| | | TRANSPORT_MODES = new LinkedHashSet<>(transportModes.keySet()); |
| | | |
| | | Map<String, String> roadCategories = new LinkedHashMap<>(); |
| | | roadCategories.put(ROAD_EXPRESSWAY, "é«éå
¬è·¯"); |
| | | roadCategories.put(ROAD_NATIONAL_PROVINCIAL, "æ®éå½çé"); |
| | | ROAD_CATEGORY_LABELS = Collections.unmodifiableMap(roadCategories); |
| | | ROAD_CATEGORIES = new LinkedHashSet<>(roadCategories.keySet()); |
| | | |
| | | Map<String, String> vehicleTypes = new LinkedHashMap<>(); |
| | | vehicleTypes.put(VEHICLE_SMALL_PASSENGER, "å°å®¢è½¦"); |
| | | vehicleTypes.put(VEHICLE_MEDIUM_SMALL_PASSENGER, "ä¸å°å®¢è½¦"); |
| | | VEHICLE_TYPE_LABELS = Collections.unmodifiableMap(vehicleTypes); |
| | | VEHICLE_TYPES = new LinkedHashSet<>(vehicleTypes.keySet()); |
| | | } |
| | | |
| | | private HolidayConstants() { |
| | | } |
| | | |
| | | public static boolean supportsComparisonBase(int year) { |
| | | return year == COMPARISON_BASE_YEAR; |
| | | } |
| | | |
| | | public static boolean isValidHolidayType(String value) { |
| | | return HOLIDAY_TYPES.contains(value); |
| | | } |
| | | |
| | | public static boolean isValidDateCategory(String value) { |
| | | return DATE_CATEGORIES.contains(value); |
| | | } |
| | | |
| | | public static boolean isValidTransportMode(String value) { |
| | | return TRANSPORT_MODES.contains(value); |
| | | } |
| | | |
| | | public static boolean isValidRoadCategory(String value) { |
| | | return ROAD_CATEGORIES.contains(value); |
| | | } |
| | | |
| | | public static boolean isValidVehicleType(String value) { |
| | | return VEHICLE_TYPES.contains(value); |
| | | } |
| | | |
| | | public static Map<String, Object> optionMap() { |
| | | Map<String, Object> options = new LinkedHashMap<>(); |
| | | options.put("holidayTypes", HOLIDAY_TYPE_LABELS); |
| | | options.put("dateCategories", DATE_CATEGORY_LABELS); |
| | | options.put("transportModes", TRANSPORT_MODE_LABELS); |
| | | options.put("roadCategories", ROAD_CATEGORY_LABELS); |
| | | options.put("vehicleTypes", VEHICLE_TYPE_LABELS); |
| | | options.put("years", Arrays.asList(2024, 2025, 2026)); |
| | | return options; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday; |
| | | |
| | | import org.apache.poi.ss.util.NumberToTextConverter; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.MathContext; |
| | | import java.math.RoundingMode; |
| | | |
| | | public final class HolidayNumberUtils { |
| | | |
| | | public static final MathContext EXCEL_MC = new MathContext(15, RoundingMode.HALF_UP); |
| | | public static final BigDecimal ZERO = BigDecimal.ZERO; |
| | | |
| | | private HolidayNumberUtils() { |
| | | } |
| | | |
| | | public static BigDecimal excelValue(double value) { |
| | | return normalize(new BigDecimal(NumberToTextConverter.toText(value))); |
| | | } |
| | | |
| | | public static BigDecimal decimalValue(Object value) { |
| | | if (value == null || value.toString().trim().isEmpty()) return null; |
| | | return normalize(new BigDecimal(value.toString().trim())); |
| | | } |
| | | |
| | | public static BigDecimal normalize(BigDecimal value) { |
| | | if (value == null) return null; |
| | | BigDecimal rounded = value.round(EXCEL_MC); |
| | | return rounded.signum() == 0 ? ZERO : rounded.stripTrailingZeros(); |
| | | } |
| | | |
| | | public static BigDecimal valueOrZero(BigDecimal value) { |
| | | return value == null ? ZERO : value; |
| | | } |
| | | |
| | | public static BigDecimal sumNullable(BigDecimal left, BigDecimal right) { |
| | | if (left == null && right == null) return null; |
| | | return normalize(valueOrZero(left).add(valueOrZero(right))); |
| | | } |
| | | |
| | | public static BigDecimal multiply(BigDecimal left, BigDecimal right) { |
| | | if (left == null || right == null) return null; |
| | | return normalize(left.multiply(right)); |
| | | } |
| | | |
| | | public static BigDecimal divide(BigDecimal dividend, BigDecimal divisor) { |
| | | if (dividend == null || divisor == null || divisor.signum() == 0) return null; |
| | | return normalize(dividend.divide(divisor, EXCEL_MC)); |
| | | } |
| | | |
| | | public static boolean sameValue(BigDecimal left, BigDecimal right) { |
| | | if (left == null || right == null) return false; |
| | | return normalize(left).compareTo(normalize(right)) == 0; |
| | | } |
| | | |
| | | public static String changeText(BigDecimal value) { |
| | | if (value == null) return ""; |
| | | return normalize(value).toPlainString(); |
| | | } |
| | | |
| | | public static String format1(BigDecimal value) { |
| | | if (value == null) return ""; |
| | | return value.setScale(1, RoundingMode.HALF_UP).toPlainString(); |
| | | } |
| | | |
| | | public static String format2(BigDecimal value) { |
| | | if (value == null) return ""; |
| | | return value.setScale(2, RoundingMode.HALF_UP).toPlainString(); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday.controller; |
| | | |
| | | import com.trafficaudit.common.Result; |
| | | import com.trafficaudit.holiday.HolidayConstants; |
| | | import com.trafficaudit.holiday.entity.HolidayCalendar; |
| | | import com.trafficaudit.holiday.entity.HolidayPassengerCoefficient; |
| | | import com.trafficaudit.holiday.service.HolidayConfigService; |
| | | import com.trafficaudit.security.utils.JwtUtils; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestHeader; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @RestController |
| | | @RequestMapping("/api/holiday/config") |
| | | public class HolidayConfigController { |
| | | |
| | | @Resource |
| | | private HolidayConfigService configService; |
| | | @Resource |
| | | private JwtUtils jwtUtils; |
| | | |
| | | @GetMapping("/options") |
| | | public Result<Map<String, Object>> options() { |
| | | return Result.ok(HolidayConstants.optionMap()); |
| | | } |
| | | |
| | | @GetMapping("/holidays") |
| | | public Result<List<HolidayCalendar>> holidays(@RequestParam(value = "year", required = false) Integer year, |
| | | @RequestParam(value = "holidayType", required = false) String holidayType) { |
| | | return Result.ok(configService.listHolidays(year, holidayType)); |
| | | } |
| | | |
| | | @PostMapping("/holidays/save") |
| | | public Result<HolidayCalendar> saveHoliday(@RequestHeader(value = "Authorization", required = false) String auth, |
| | | @RequestBody Map<String, Object> params) { |
| | | if (!isAdmin(auth)) return Result.error(403, "ä»
admin å¯ä»¥ç®¡çè忥é
ç½®"); |
| | | try { |
| | | return Result.ok(configService.saveHoliday(params)); |
| | | } catch (Exception e) { |
| | | return Result.error(400, e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @PostMapping("/holidays/delete") |
| | | public Result<Void> deleteHoliday(@RequestHeader(value = "Authorization", required = false) String auth, |
| | | @RequestParam("id") Long id) { |
| | | if (!isAdmin(auth)) return Result.error(403, "ä»
admin å¯ä»¥ç®¡çè忥é
ç½®"); |
| | | try { |
| | | configService.deleteHoliday(id); |
| | | return Result.ok(); |
| | | } catch (Exception e) { |
| | | return Result.error(400, e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @GetMapping("/coefficients") |
| | | public Result<List<HolidayPassengerCoefficient>> coefficients(@RequestParam(value = "year", required = false) Integer year) { |
| | | return Result.ok(configService.listCoefficients(year)); |
| | | } |
| | | |
| | | @PostMapping("/coefficients/save") |
| | | public Result<HolidayPassengerCoefficient> saveCoefficient(@RequestHeader(value = "Authorization", required = false) String auth, |
| | | @RequestBody Map<String, Object> params) { |
| | | if (!isAdmin(auth)) return Result.error(403, "ä»
admin å¯ä»¥ç®¡ç客è¿ç³»æ°"); |
| | | try { |
| | | return Result.ok(configService.saveCoefficient(params)); |
| | | } catch (Exception e) { |
| | | return Result.error(400, e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @PostMapping("/coefficients/delete") |
| | | public Result<Void> deleteCoefficient(@RequestHeader(value = "Authorization", required = false) String auth, |
| | | @RequestParam("id") Long id) { |
| | | if (!isAdmin(auth)) return Result.error(403, "ä»
admin å¯ä»¥ç®¡ç客è¿ç³»æ°"); |
| | | try { |
| | | configService.deleteCoefficient(id); |
| | | return Result.ok(); |
| | | } catch (Exception e) { |
| | | return Result.error(400, e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private boolean isAdmin(String auth) { |
| | | return "admin".equals(jwtUtils.resolveUsername(auth)); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday.controller; |
| | | |
| | | import com.trafficaudit.common.Result; |
| | | import com.trafficaudit.holiday.service.HolidayBulletinService; |
| | | import com.trafficaudit.holiday.service.HolidayStatService; |
| | | import org.springframework.http.HttpHeaders; |
| | | import org.springframework.http.MediaType; |
| | | import org.springframework.http.ResponseEntity; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.net.URLEncoder; |
| | | import java.time.LocalDate; |
| | | import java.util.Map; |
| | | |
| | | @RestController |
| | | @RequestMapping("/api/holiday") |
| | | public class HolidayStatController { |
| | | |
| | | @Resource |
| | | private HolidayStatService statService; |
| | | @Resource |
| | | private HolidayBulletinService bulletinService; |
| | | |
| | | @GetMapping("/overview") |
| | | public Result<Map<String, Object>> overview(@RequestParam("year") int year, |
| | | @RequestParam("holidayType") String holidayType) { |
| | | try { |
| | | return Result.ok(statService.overview(year, holidayType)); |
| | | } catch (Exception e) { |
| | | return Result.error(400, e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @PostMapping("/import") |
| | | public Result<Map<String, Object>> importWorkbook(@RequestParam(value = "file", required = false) MultipartFile file, |
| | | @RequestParam("year") int year, |
| | | @RequestParam("holidayType") String holidayType, |
| | | @RequestParam(value = "allowHistoricalOverwrite", defaultValue = "false") boolean allowHistoricalOverwrite) { |
| | | try { |
| | | return Result.ok(statService.importWorkbook(file, year, holidayType, allowHistoricalOverwrite)); |
| | | } catch (Exception e) { |
| | | return Result.error(400, e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @GetMapping("/export") |
| | | public ResponseEntity<byte[]> exportWorkbook(@RequestParam("year") int year, |
| | | @RequestParam("holidayType") String holidayType) throws Exception { |
| | | byte[] data = statService.exportWorkbook(year, holidayType); |
| | | String fileName = year + "å¹´" + holidayTypeFileLabel(holidayType) + "æ°æ®.xlsx"; |
| | | String encoded = URLEncoder.encode(fileName, "UTF-8").replace("+", "%20"); |
| | | return ResponseEntity.ok() |
| | | .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded) |
| | | .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")) |
| | | .body(data); |
| | | } |
| | | |
| | | @GetMapping("/bulletin") |
| | | public ResponseEntity<byte[]> bulletin(@RequestParam(value = "date", required = false) String date) throws Exception { |
| | | LocalDate targetDate = (date == null || date.trim().isEmpty()) |
| | | ? LocalDate.now().minusDays(1) : LocalDate.parse(date); |
| | | byte[] data = bulletinService.generate(targetDate); |
| | | String fileName = String.format("æ¹åçå½åºåæç»¼åè¿è¾å®¢æµå¿«æ¥ï¼10æ%dæ¥ï¼.docx", targetDate.getDayOfMonth()); |
| | | String encoded = URLEncoder.encode(fileName, "UTF-8").replace("+", "%20"); |
| | | return ResponseEntity.ok() |
| | | .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded) |
| | | .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.wordprocessingml.document")) |
| | | .body(data); |
| | | } |
| | | |
| | | private String holidayTypeFileLabel(String holidayType) { |
| | | if ("SPRING_FESTIVAL".equals(holidayType)) return "æ¥è"; |
| | | if ("LABOR_DAY".equals(holidayType)) return "äºä¸"; |
| | | return "åä¸"; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Data |
| | | @TableName("holiday_calendar") |
| | | public class HolidayCalendar { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | private Integer year; |
| | | private String holidayType; |
| | | private LocalDate holidayDate; |
| | | private Integer dayIndex; |
| | | private LocalDateTime createdAt; |
| | | private LocalDateTime updatedAt; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Data |
| | | @TableName("holiday_passenger_coefficient") |
| | | public class HolidayPassengerCoefficient { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | private Integer year; |
| | | private String dateCategory; |
| | | private BigDecimal coefficient; |
| | | private LocalDateTime createdAt; |
| | | private LocalDateTime updatedAt; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Data |
| | | @TableName("holiday_passenger_flow") |
| | | public class HolidayPassengerFlow { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | private Integer year; |
| | | private LocalDate flowDate; |
| | | private String transportMode; |
| | | private BigDecimal passengerCount; |
| | | private LocalDateTime createdAt; |
| | | private LocalDateTime updatedAt; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.FieldStrategy; |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Data |
| | | @TableName("holiday_vehicle_flow") |
| | | public class HolidayVehicleFlow { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | private Integer year; |
| | | private LocalDate flowDate; |
| | | private String roadCategory; |
| | | private String vehicleType; |
| | | private BigDecimal vehicleCount; |
| | | @TableField(updateStrategy = FieldStrategy.IGNORED) |
| | | private BigDecimal comparisonBase; |
| | | private LocalDateTime createdAt; |
| | | private LocalDateTime updatedAt; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.holiday.entity.HolidayCalendar; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface HolidayCalendarMapper extends BaseMapper<HolidayCalendar> { |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.holiday.entity.HolidayPassengerCoefficient; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface HolidayPassengerCoefficientMapper extends BaseMapper<HolidayPassengerCoefficient> { |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.holiday.entity.HolidayPassengerFlow; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface HolidayPassengerFlowMapper extends BaseMapper<HolidayPassengerFlow> { |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.holiday.entity.HolidayVehicleFlow; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface HolidayVehicleFlowMapper extends BaseMapper<HolidayVehicleFlow> { |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday.service; |
| | | |
| | | import com.trafficaudit.holiday.HolidayNumberUtils; |
| | | import com.trafficaudit.holiday.service.HolidayStatService.DailyMetrics; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Service; |
| | | import org.w3c.dom.Document; |
| | | import org.w3c.dom.Element; |
| | | import org.w3c.dom.Node; |
| | | import org.w3c.dom.NodeList; |
| | | |
| | | import javax.xml.parsers.DocumentBuilderFactory; |
| | | import javax.xml.transform.OutputKeys; |
| | | import javax.xml.transform.Transformer; |
| | | import javax.xml.transform.TransformerFactory; |
| | | import javax.xml.transform.dom.DOMSource; |
| | | import javax.xml.transform.stream.StreamResult; |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.ByteArrayOutputStream; |
| | | import java.io.File; |
| | | import java.io.FileInputStream; |
| | | import java.io.InputStream; |
| | | import java.math.BigDecimal; |
| | | import java.nio.file.Files; |
| | | import java.time.LocalDate; |
| | | import java.util.ArrayList; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.zip.ZipEntry; |
| | | import java.util.zip.ZipInputStream; |
| | | import java.util.zip.ZipOutputStream; |
| | | |
| | | @Slf4j |
| | | @Service |
| | | public class HolidayBulletinService { |
| | | |
| | | private static final String WORD_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; |
| | | private static final BigDecimal EPSILON = new BigDecimal("0.0000001"); |
| | | |
| | | @Value("${holiday.bulletin-template:docs/ä¸é¡¹ç»è®¡/éè¦è忥/æ¹åçå½åºåæç»¼åè¿è¾å®¢æµå¿«æ¥æ¨¡æ¿.docx}") |
| | | private String bulletinTemplatePath; |
| | | |
| | | private final HolidayStatService statService; |
| | | private static final ThreadLocal<Document> CURRENT_DOCUMENT = new ThreadLocal<>(); |
| | | |
| | | public HolidayBulletinService(HolidayStatService statService) { |
| | | this.statService = statService; |
| | | } |
| | | |
| | | public byte[] generate(LocalDate date) { |
| | | DailyMetrics current = statService.findMetricsByDate(date); |
| | | if (current == null) { |
| | | throw new IllegalArgumentException("æéæ¥æä¸å¨å·²é
ç½®çè忥èå´å
"); |
| | | } |
| | | DailyMetrics previousDay = current.getDayIndex() > 1 |
| | | ? statService.dailyMetrics(current.getYear(), current.getHolidayType(), current.getDayIndex() - 1) : null; |
| | | DailyMetrics previousYear = statService.dailyMetrics(current.getYear() - 1, current.getHolidayType(), current.getDayIndex()); |
| | | DailyMetrics currentCumulative = statService.cumulativeMetrics(current.getYear(), current.getHolidayType(), current.getDayIndex()); |
| | | DailyMetrics previousYearCumulative = statService.cumulativeMetrics(current.getYear() - 1, current.getHolidayType(), current.getDayIndex()); |
| | | |
| | | if (current.getTotalPersonFlow() == null) { |
| | | throw new IllegalArgumentException("æéæ¥æçå
¨ç¤¾ä¼è·¨åºåäººåæµå¨é没æè¶³å¤æ°æ®ï¼æ æ³çæå¿«æ¥"); |
| | | } |
| | | |
| | | String currentTotal = formatNumber(current.getTotalPersonFlow()); |
| | | String cumulativeTotalYi = formatNumber(currentCumulative.getTotalPersonFlow() == null |
| | | ? null : HolidayNumberUtils.divide(currentCumulative.getTotalPersonFlow(), BigDecimal.valueOf(10000))); |
| | | String ringRatioClause = previousDay == null ? "" : "ï¼ç¯æ¯" + ratioPhrase( |
| | | current.getTotalPersonFlow(), previousDay.getTotalPersonFlow()); |
| | | String paragraph1 = String.format("%dæ%dæ¥ï¼åæç¬¬%d天ï¼ï¼å
¨çå
¨ç¤¾ä¼è·¨åºåäººåæµå¨é%sä¸äººæ¬¡%sï¼æ¯2025å¹´åæ%sã", |
| | | date.getMonthValue(), date.getDayOfMonth(), current.getDayIndex(), currentTotal, |
| | | ringRatioClause, |
| | | ratioPhrase(current.getTotalPersonFlow(), previousYear == null ? null : previousYear.getTotalPersonFlow())); |
| | | String paragraph2 = String.format("åæä»¥æ¥ï¼å
¨çå
¨ç¤¾ä¼è·¨åºåäººåæµå¨é累计%säº¿äººæ¬¡ï¼æ¯2025å¹´åæ%sã", |
| | | cumulativeTotalYi, |
| | | ratioPhrase(currentCumulative.getTotalPersonFlow(), |
| | | previousYearCumulative == null ? null : previousYearCumulative.getTotalPersonFlow())); |
| | | |
| | | File template = resolveTemplateFile(); |
| | | if (!template.exists() || !template.isFile()) { |
| | | throw new IllegalArgumentException("å¿«æ¥æ¨¡æ¿æä»¶ä¸åå¨: " + template.getAbsolutePath()); |
| | | } |
| | | |
| | | try { |
| | | byte[] templateBytes; |
| | | try (InputStream in = new FileInputStream(template)) { |
| | | templateBytes = readAll(in); |
| | | } |
| | | byte[] documentXml = readZipEntry(templateBytes, "word/document.xml"); |
| | | Document document = parseDocument(documentXml); |
| | | CURRENT_DOCUMENT.set(document); |
| | | try { |
| | | replaceParagraph(document, text -> text.startsWith("10æ") && text.contains("åæç¬¬"), paragraph1); |
| | | replaceParagraph(document, text -> text.startsWith("åæä»¥æ¥"), paragraph2); |
| | | fillTable(document, current, previousDay, previousYear, currentCumulative, previousYearCumulative); |
| | | byte[] replacedXml = serializeDocument(document); |
| | | return replaceZipEntry(templateBytes, "word/document.xml", replacedXml); |
| | | } finally { |
| | | CURRENT_DOCUMENT.remove(); |
| | | } |
| | | } catch (Exception e) { |
| | | throw new IllegalArgumentException("å¿«æ¥çæå¤±è´¥: " + e.getMessage(), e); |
| | | } |
| | | } |
| | | |
| | | private void fillTable(Document document, DailyMetrics current, DailyMetrics previousDay, |
| | | DailyMetrics previousYear, DailyMetrics currentCumulative, |
| | | DailyMetrics previousYearCumulative) { |
| | | Element table = firstElement(document, "tbl"); |
| | | if (table == null) { |
| | | throw new IllegalArgumentException("å¿«æ¥æ¨¡æ¿ä¸æ¾ä¸å°æ°æ®è¡¨"); |
| | | } |
| | | writeTableRow(table, 2, new String[]{ |
| | | number(current.getNonBusinessPersonFlow()), |
| | | ratio(current.getNonBusinessPersonFlow(), previousDay == null ? null : previousDay.getNonBusinessPersonFlow()), |
| | | ratio(current.getNonBusinessPersonFlow(), previousYear == null ? null : previousYear.getNonBusinessPersonFlow()), |
| | | "â", |
| | | number(currentCumulative.getNonBusinessPersonFlow()), |
| | | ratio(currentCumulative.getNonBusinessPersonFlow(), |
| | | previousYearCumulative == null ? null : previousYearCumulative.getNonBusinessPersonFlow()), |
| | | "â" |
| | | }); |
| | | writeTableRow(table, 3, new String[]{ |
| | | number(current.getRoad()), |
| | | ratio(current.getRoad(), previousDay == null ? null : previousDay.getRoad()), |
| | | ratio(current.getRoad(), previousYear == null ? null : previousYear.getRoad()), |
| | | "â", |
| | | number(currentCumulative.getRoad()), |
| | | ratio(currentCumulative.getRoad(), previousYearCumulative == null ? null : previousYearCumulative.getRoad()), |
| | | "â" |
| | | }); |
| | | writeTableRow(table, 4, new String[]{ |
| | | number(current.getRoadPersonFlow()), |
| | | ratio(current.getRoadPersonFlow(), previousDay == null ? null : previousDay.getRoadPersonFlow()), |
| | | ratio(current.getRoadPersonFlow(), previousYear == null ? null : previousYear.getRoadPersonFlow()), |
| | | "â", |
| | | number(currentCumulative.getRoadPersonFlow()), |
| | | ratio(currentCumulative.getRoadPersonFlow(), previousYearCumulative == null ? null : previousYearCumulative.getRoadPersonFlow()), |
| | | "â" |
| | | }); |
| | | writeTableRow(table, 5, transportRow(current.getRail(), previousDay == null ? null : previousDay.getRail(), |
| | | previousYear == null ? null : previousYear.getRail(), currentCumulative.getRail(), |
| | | previousYearCumulative == null ? null : previousYearCumulative.getRail())); |
| | | writeTableRow(table, 6, transportRow(current.getCivilAviation(), previousDay == null ? null : previousDay.getCivilAviation(), |
| | | previousYear == null ? null : previousYear.getCivilAviation(), currentCumulative.getCivilAviation(), |
| | | previousYearCumulative == null ? null : previousYearCumulative.getCivilAviation())); |
| | | writeTableRow(table, 7, transportRow(current.getWater(), previousDay == null ? null : previousDay.getWater(), |
| | | previousYear == null ? null : previousYear.getWater(), currentCumulative.getWater(), |
| | | previousYearCumulative == null ? null : previousYearCumulative.getWater())); |
| | | writeTableRow(table, 8, transportRow(current.getTotalPersonFlow(), previousDay == null ? null : previousDay.getTotalPersonFlow(), |
| | | previousYear == null ? null : previousYear.getTotalPersonFlow(), currentCumulative.getTotalPersonFlow(), |
| | | previousYearCumulative == null ? null : previousYearCumulative.getTotalPersonFlow())); |
| | | } |
| | | |
| | | private String[] transportRow(BigDecimal current, BigDecimal previousDay, BigDecimal previousYear, |
| | | BigDecimal currentCumulative, BigDecimal previousYearCumulative) { |
| | | return new String[]{ |
| | | number(current), |
| | | ratio(current, previousDay), |
| | | ratio(current, previousYear), |
| | | "â", |
| | | number(currentCumulative), |
| | | ratio(currentCumulative, previousYearCumulative), |
| | | "â" |
| | | }; |
| | | } |
| | | |
| | | private void writeTableRow(Element table, int rowIndex, String[] values) { |
| | | List<Element> rows = childElements(table, "tr"); |
| | | if (rowIndex >= rows.size()) return; |
| | | Element row = rows.get(rowIndex); |
| | | List<Element> cells = cellRefs(row); |
| | | int logicalColumn = 0; |
| | | for (Element cell : cells) { |
| | | int span = gridSpan(cell); |
| | | if (logicalColumn >= 2 && logicalColumn < 9) { |
| | | int valueIndex = logicalColumn - 2; |
| | | if (valueIndex < values.length) { |
| | | setCellText(cell, values[valueIndex]); |
| | | } |
| | | } |
| | | logicalColumn += span; |
| | | } |
| | | } |
| | | |
| | | private List<Element> cellRefs(Element row) { |
| | | List<Element> cells = new ArrayList<>(); |
| | | for (Element child : childElements(row, "tc")) cells.add(child); |
| | | return cells; |
| | | } |
| | | |
| | | private int gridSpan(Element cell) { |
| | | Element tcPr = firstChildElement(cell, "tcPr"); |
| | | if (tcPr == null) return 1; |
| | | Element span = firstChildElement(tcPr, "gridSpan"); |
| | | if (span == null) return 1; |
| | | String value = span.getAttributeNS(WORD_NS, "val"); |
| | | try { |
| | | return Integer.parseInt(value); |
| | | } catch (Exception e) { |
| | | return 1; |
| | | } |
| | | } |
| | | |
| | | private void setCellText(Element cell, String text) { |
| | | Element tcPr = firstChildElement(cell, "tcPr"); |
| | | Element templateParagraph = firstChildElement(cell, "p"); |
| | | Element paragraph = createElement("p"); |
| | | if (templateParagraph != null) { |
| | | Element pPr = firstChildElement(templateParagraph, "pPr"); |
| | | if (pPr != null) paragraph.appendChild(pPr.cloneNode(true)); |
| | | } |
| | | Element run = createElement("r"); |
| | | Element t = createElement("t"); |
| | | t.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:space", "preserve"); |
| | | t.setTextContent(text == null ? "" : text); |
| | | run.appendChild(t); |
| | | paragraph.appendChild(run); |
| | | |
| | | List<Node> removals = new ArrayList<>(); |
| | | Node child = cell.getFirstChild(); |
| | | while (child != null) { |
| | | Node next = child.getNextSibling(); |
| | | if (child instanceof Element && "p".equals(localName(child))) { |
| | | removals.add(child); |
| | | } |
| | | child = next; |
| | | } |
| | | for (Node node : removals) cell.removeChild(node); |
| | | if (tcPr != null && tcPr.getNextSibling() != null) { |
| | | cell.insertBefore(paragraph, tcPr.getNextSibling()); |
| | | } else if (tcPr != null) { |
| | | cell.appendChild(paragraph); |
| | | } else { |
| | | cell.insertBefore(paragraph, cell.getFirstChild()); |
| | | } |
| | | } |
| | | |
| | | private void replaceParagraph(Document document, ParagraphMatcher matcher, String replacement) { |
| | | NodeList paragraphs = document.getElementsByTagNameNS(WORD_NS, "p"); |
| | | for (int i = 0; i < paragraphs.getLength(); i++) { |
| | | Element paragraph = (Element) paragraphs.item(i); |
| | | String text = paragraphText(paragraph); |
| | | if (matcher.matches(text)) { |
| | | setParagraphText(paragraph, replacement); |
| | | return; |
| | | } |
| | | } |
| | | throw new IllegalArgumentException("å¿«æ¥æ¨¡æ¿ç¼ºå°å¯æ¿æ¢æ®µè½: " + replacement); |
| | | } |
| | | |
| | | private void setParagraphText(Element paragraph, String text) { |
| | | Element pPr = firstChildElement(paragraph, "pPr"); |
| | | Element firstRun = firstChildElement(paragraph, "r"); |
| | | Element rPr = firstRun == null ? null : firstChildElement(firstRun, "rPr"); |
| | | List<Node> removals = new ArrayList<>(); |
| | | Node child = paragraph.getFirstChild(); |
| | | while (child != null) { |
| | | Node next = child.getNextSibling(); |
| | | if (child != pPr) removals.add(child); |
| | | child = next; |
| | | } |
| | | for (Node node : removals) paragraph.removeChild(node); |
| | | |
| | | Element run = createElement("r"); |
| | | if (rPr != null) run.appendChild(rPr.cloneNode(true)); |
| | | Element t = createElement("t"); |
| | | t.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:space", "preserve"); |
| | | t.setTextContent(text); |
| | | run.appendChild(t); |
| | | paragraph.appendChild(run); |
| | | } |
| | | |
| | | private String paragraphText(Element paragraph) { |
| | | StringBuilder builder = new StringBuilder(); |
| | | NodeList texts = paragraph.getElementsByTagNameNS(WORD_NS, "t"); |
| | | for (int i = 0; i < texts.getLength(); i++) { |
| | | builder.append(texts.item(i).getTextContent()); |
| | | } |
| | | return builder.toString(); |
| | | } |
| | | |
| | | private String ratioPhrase(BigDecimal current, BigDecimal base) { |
| | | if (current == null || base == null || base.abs().compareTo(EPSILON) < 0) return "æ 坿¯æ°æ®"; |
| | | BigDecimal ratio = HolidayNumberUtils.divide(current, base); |
| | | BigDecimal percent = HolidayNumberUtils.multiply(ratio.subtract(BigDecimal.ONE), BigDecimal.valueOf(100)); |
| | | if (percent == null || percent.abs().compareTo(EPSILON) < 0) return "æå¹³"; |
| | | return (percent.signum() > 0 ? "å¢é¿" : "ä¸é") + HolidayNumberUtils.format1(percent.abs()) + "%"; |
| | | } |
| | | |
| | | private String ratio(BigDecimal current, BigDecimal base) { |
| | | if (current == null || base == null || base.abs().compareTo(EPSILON) < 0) return "â"; |
| | | return ratioPhrase(current, base); |
| | | } |
| | | |
| | | private String number(BigDecimal value) { |
| | | return value == null ? "â" : formatNumber(value); |
| | | } |
| | | |
| | | private String formatNumber(BigDecimal value) { |
| | | if (value == null) return ""; |
| | | return HolidayNumberUtils.format2(value); |
| | | } |
| | | |
| | | private File resolveTemplateFile() { |
| | | List<File> candidates = new ArrayList<>(); |
| | | File configured = new File(bulletinTemplatePath); |
| | | candidates.add(configured); |
| | | if (!configured.isAbsolute()) { |
| | | candidates.add(new File(System.getProperty("user.dir"), bulletinTemplatePath)); |
| | | candidates.add(new File(System.getProperty("user.dir"), "../" + bulletinTemplatePath)); |
| | | } |
| | | for (File candidate : candidates) { |
| | | try { |
| | | if (candidate.exists() && candidate.isFile()) { |
| | | return candidate.getCanonicalFile(); |
| | | } |
| | | } catch (Exception ignored) { |
| | | // Keep checking. |
| | | } |
| | | } |
| | | return configured; |
| | | } |
| | | |
| | | private byte[] readZipEntry(byte[] zipBytes, String entryName) throws Exception { |
| | | try (ZipInputStream zin = new ZipInputStream(new ByteArrayInputStream(zipBytes))) { |
| | | ZipEntry entry; |
| | | while ((entry = zin.getNextEntry()) != null) { |
| | | if (entryName.equals(entry.getName())) { |
| | | return readAll(zin); |
| | | } |
| | | } |
| | | } |
| | | throw new IllegalArgumentException("模æ¿ä¸ç¼ºå° " + entryName); |
| | | } |
| | | |
| | | private byte[] replaceZipEntry(byte[] zipBytes, String entryName, byte[] replacement) throws Exception { |
| | | ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| | | try (ZipInputStream zin = new ZipInputStream(new ByteArrayInputStream(zipBytes)); |
| | | ZipOutputStream zout = new ZipOutputStream(out)) { |
| | | ZipEntry entry; |
| | | byte[] buffer = new byte[8192]; |
| | | while ((entry = zin.getNextEntry()) != null) { |
| | | ZipEntry target = new ZipEntry(entry.getName()); |
| | | zout.putNextEntry(target); |
| | | if (entryName.equals(entry.getName())) { |
| | | zout.write(replacement); |
| | | } else { |
| | | int length; |
| | | while ((length = zin.read(buffer)) > 0) { |
| | | zout.write(buffer, 0, length); |
| | | } |
| | | } |
| | | zout.closeEntry(); |
| | | } |
| | | } |
| | | return out.toByteArray(); |
| | | } |
| | | |
| | | private Document parseDocument(byte[] xml) throws Exception { |
| | | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
| | | factory.setNamespaceAware(true); |
| | | return factory.newDocumentBuilder().parse(new ByteArrayInputStream(xml)); |
| | | } |
| | | |
| | | private byte[] serializeDocument(Document document) throws Exception { |
| | | Transformer transformer = TransformerFactory.newInstance().newTransformer(); |
| | | transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); |
| | | transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); |
| | | ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| | | transformer.transform(new DOMSource(document), new StreamResult(out)); |
| | | return out.toByteArray(); |
| | | } |
| | | |
| | | private Element firstElement(Document document, String localName) { |
| | | NodeList list = document.getElementsByTagNameNS(WORD_NS, localName); |
| | | return list.getLength() == 0 ? null : (Element) list.item(0); |
| | | } |
| | | |
| | | private Element createElement(String localName) { |
| | | Document document = CURRENT_DOCUMENT.get(); |
| | | if (document == null) { |
| | | throw new IllegalStateException("Word document is not initialized"); |
| | | } |
| | | return document.createElementNS(WORD_NS, "w:" + localName); |
| | | } |
| | | |
| | | private List<Element> childElements(Element parent, String localName) { |
| | | List<Element> result = new ArrayList<>(); |
| | | Node child = parent.getFirstChild(); |
| | | while (child != null) { |
| | | if (child instanceof Element && localName.equals(localName(child))) { |
| | | result.add((Element) child); |
| | | } |
| | | child = child.getNextSibling(); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private Element firstChildElement(Element parent, String localName) { |
| | | Node child = parent.getFirstChild(); |
| | | while (child != null) { |
| | | if (child instanceof Element && localName.equals(localName(child))) { |
| | | return (Element) child; |
| | | } |
| | | child = child.getNextSibling(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private String localName(Node node) { |
| | | return node.getLocalName() == null ? node.getNodeName().replaceFirst("^.*:", "") : node.getLocalName(); |
| | | } |
| | | |
| | | private byte[] readAll(InputStream in) throws Exception { |
| | | ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| | | byte[] buffer = new byte[8192]; |
| | | int length; |
| | | while ((length = in.read(buffer)) > 0) { |
| | | out.write(buffer, 0, length); |
| | | } |
| | | return out.toByteArray(); |
| | | } |
| | | |
| | | private interface ParagraphMatcher { |
| | | boolean matches(String text); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.trafficaudit.holiday.HolidayConstants; |
| | | import com.trafficaudit.holiday.entity.HolidayCalendar; |
| | | import com.trafficaudit.holiday.entity.HolidayPassengerCoefficient; |
| | | import com.trafficaudit.holiday.mapper.HolidayCalendarMapper; |
| | | import com.trafficaudit.holiday.mapper.HolidayPassengerCoefficientMapper; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @Service |
| | | public class HolidayConfigService { |
| | | |
| | | @Resource |
| | | private HolidayCalendarMapper holidayCalendarMapper; |
| | | |
| | | @Resource |
| | | private HolidayPassengerCoefficientMapper coefficientMapper; |
| | | |
| | | public List<HolidayCalendar> listHolidays(Integer year, String holidayType) { |
| | | LambdaQueryWrapper<HolidayCalendar> wrapper = new LambdaQueryWrapper<HolidayCalendar>() |
| | | .eq(year != null, HolidayCalendar::getYear, year) |
| | | .eq(holidayType != null && !holidayType.trim().isEmpty(), HolidayCalendar::getHolidayType, holidayType) |
| | | .orderByAsc(HolidayCalendar::getYear) |
| | | .orderByAsc(HolidayCalendar::getHolidayType) |
| | | .orderByAsc(HolidayCalendar::getDayIndex); |
| | | return holidayCalendarMapper.selectList(wrapper); |
| | | } |
| | | |
| | | public List<HolidayCalendar> findHolidayDays(int year, String holidayType) { |
| | | return holidayCalendarMapper.selectList(new LambdaQueryWrapper<HolidayCalendar>() |
| | | .eq(HolidayCalendar::getYear, year) |
| | | .eq(HolidayCalendar::getHolidayType, holidayType) |
| | | .orderByAsc(HolidayCalendar::getDayIndex)); |
| | | } |
| | | |
| | | public HolidayCalendar findHolidayByDate(LocalDate date) { |
| | | return holidayCalendarMapper.selectOne(new LambdaQueryWrapper<HolidayCalendar>() |
| | | .eq(HolidayCalendar::getHolidayDate, date) |
| | | .orderByAsc(HolidayCalendar::getDayIndex) |
| | | .last("limit 1")); |
| | | } |
| | | |
| | | @Transactional |
| | | public HolidayCalendar saveHoliday(Map<String, Object> params) { |
| | | Long id = longValue(params.get("id")); |
| | | Integer year = intValue(params.get("year")); |
| | | String holidayType = stringValue(params.get("holidayType")); |
| | | LocalDate holidayDate = dateValue(params.get("holidayDate")); |
| | | Integer dayIndex = intValue(params.get("dayIndex")); |
| | | if (year == null || year < 2000 || year > 2100) { |
| | | throw new IllegalArgumentException("年份䏿£ç¡®"); |
| | | } |
| | | if (!HolidayConstants.isValidHolidayType(holidayType)) { |
| | | throw new IllegalArgumentException("è忥åç±»ä¸æ£ç¡®"); |
| | | } |
| | | if (holidayDate == null || holidayDate.getYear() != year) { |
| | | throw new IllegalArgumentException("æ¥æå¿
é¡»å±äºæé年份"); |
| | | } |
| | | if (dayIndex == null || dayIndex < 1) { |
| | | throw new IllegalArgumentException("ç¬¬å æ¥å¿
é¡»ä» 1 å¼å§"); |
| | | } |
| | | LambdaQueryWrapper<HolidayCalendar> duplicate = new LambdaQueryWrapper<HolidayCalendar>() |
| | | .eq(HolidayCalendar::getYear, year) |
| | | .eq(HolidayCalendar::getHolidayType, holidayType) |
| | | .and(w -> w.eq(HolidayCalendar::getHolidayDate, holidayDate) |
| | | .or().eq(HolidayCalendar::getDayIndex, dayIndex)); |
| | | if (id != null) { |
| | | duplicate.ne(HolidayCalendar::getId, id); |
| | | } |
| | | if (holidayCalendarMapper.selectCount(duplicate) > 0) { |
| | | throw new IllegalArgumentException("åä¸è忥ä¸çæ¥ææç¬¬å æ¥å·²åå¨"); |
| | | } |
| | | |
| | | HolidayCalendar entity = id == null ? new HolidayCalendar() : holidayCalendarMapper.selectById(id); |
| | | if (entity == null) { |
| | | throw new IllegalArgumentException("èåæ¥è®°å½ä¸åå¨"); |
| | | } |
| | | if (id == null) { |
| | | entity.setCreatedAt(LocalDateTime.now()); |
| | | } |
| | | entity.setYear(year); |
| | | entity.setHolidayType(holidayType); |
| | | entity.setHolidayDate(holidayDate); |
| | | entity.setDayIndex(dayIndex); |
| | | entity.setUpdatedAt(LocalDateTime.now()); |
| | | if (id == null) { |
| | | holidayCalendarMapper.insert(entity); |
| | | } else { |
| | | holidayCalendarMapper.updateById(entity); |
| | | } |
| | | return entity; |
| | | } |
| | | |
| | | @Transactional |
| | | public void deleteHoliday(Long id) { |
| | | if (id == null) { |
| | | throw new IllegalArgumentException("缺å°è®°å½ id"); |
| | | } |
| | | holidayCalendarMapper.deleteById(id); |
| | | } |
| | | |
| | | public List<HolidayPassengerCoefficient> listCoefficients(Integer year) { |
| | | return coefficientMapper.selectList(new LambdaQueryWrapper<HolidayPassengerCoefficient>() |
| | | .eq(year != null, HolidayPassengerCoefficient::getYear, year) |
| | | .orderByAsc(HolidayPassengerCoefficient::getYear) |
| | | .orderByAsc(HolidayPassengerCoefficient::getDateCategory)); |
| | | } |
| | | |
| | | public BigDecimal coefficientOf(int year, String dateCategory) { |
| | | HolidayPassengerCoefficient row = coefficientMapper.selectOne( |
| | | new LambdaQueryWrapper<HolidayPassengerCoefficient>() |
| | | .eq(HolidayPassengerCoefficient::getYear, year) |
| | | .eq(HolidayPassengerCoefficient::getDateCategory, dateCategory) |
| | | .last("limit 1")); |
| | | return row == null ? null : row.getCoefficient(); |
| | | } |
| | | |
| | | @Transactional |
| | | public HolidayPassengerCoefficient saveCoefficient(Map<String, Object> params) { |
| | | Long id = longValue(params.get("id")); |
| | | Integer year = intValue(params.get("year")); |
| | | String dateCategory = stringValue(params.get("dateCategory")); |
| | | BigDecimal coefficient = decimalValue(params.get("coefficient")); |
| | | if (year == null || year < 2000 || year > 2100) { |
| | | throw new IllegalArgumentException("年份䏿£ç¡®"); |
| | | } |
| | | if (!HolidayConstants.isValidDateCategory(dateCategory)) { |
| | | throw new IllegalArgumentException("æ¥æåç±»ä¸æ£ç¡®"); |
| | | } |
| | | if (coefficient == null || coefficient.compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw new IllegalArgumentException("客è¿ç³»æ°å¿
é¡»å¤§äº 0"); |
| | | } |
| | | LambdaQueryWrapper<HolidayPassengerCoefficient> duplicate = new LambdaQueryWrapper<HolidayPassengerCoefficient>() |
| | | .eq(HolidayPassengerCoefficient::getYear, year) |
| | | .eq(HolidayPassengerCoefficient::getDateCategory, dateCategory); |
| | | if (id != null) { |
| | | duplicate.ne(HolidayPassengerCoefficient::getId, id); |
| | | } |
| | | if (coefficientMapper.selectCount(duplicate) > 0) { |
| | | throw new IllegalArgumentException("è¯¥å¹´ä»½åæ¥æåç±»ç客è¿ç³»æ°å·²åå¨"); |
| | | } |
| | | |
| | | HolidayPassengerCoefficient entity = id == null ? new HolidayPassengerCoefficient() : coefficientMapper.selectById(id); |
| | | if (entity == null) { |
| | | throw new IllegalArgumentException("客è¿ç³»æ°è®°å½ä¸åå¨"); |
| | | } |
| | | if (id == null) { |
| | | entity.setCreatedAt(LocalDateTime.now()); |
| | | } |
| | | entity.setYear(year); |
| | | entity.setDateCategory(dateCategory); |
| | | entity.setCoefficient(coefficient); |
| | | entity.setUpdatedAt(LocalDateTime.now()); |
| | | if (id == null) { |
| | | coefficientMapper.insert(entity); |
| | | } else { |
| | | coefficientMapper.updateById(entity); |
| | | } |
| | | return entity; |
| | | } |
| | | |
| | | @Transactional |
| | | public void deleteCoefficient(Long id) { |
| | | if (id == null) { |
| | | throw new IllegalArgumentException("缺å°è®°å½ id"); |
| | | } |
| | | coefficientMapper.deleteById(id); |
| | | } |
| | | |
| | | private Long longValue(Object value) { |
| | | if (value == null || value.toString().trim().isEmpty()) return null; |
| | | return Long.valueOf(value.toString()); |
| | | } |
| | | |
| | | private Integer intValue(Object value) { |
| | | if (value == null || value.toString().trim().isEmpty()) return null; |
| | | return Integer.valueOf(value.toString()); |
| | | } |
| | | |
| | | private BigDecimal decimalValue(Object value) { |
| | | if (value == null || value.toString().trim().isEmpty()) return null; |
| | | return com.trafficaudit.holiday.HolidayNumberUtils.decimalValue(value); |
| | | } |
| | | |
| | | private String stringValue(Object value) { |
| | | return value == null ? null : value.toString().trim(); |
| | | } |
| | | |
| | | private LocalDate dateValue(Object value) { |
| | | if (value == null || value.toString().trim().isEmpty()) return null; |
| | | return LocalDate.parse(value.toString()); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday.service; |
| | | |
| | | import org.springframework.boot.ApplicationArguments; |
| | | import org.springframework.boot.ApplicationRunner; |
| | | import org.springframework.core.annotation.Order; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.Resource; |
| | | |
| | | @Component |
| | | @Order(100) |
| | | public class HolidayReferenceDataInitializer implements ApplicationRunner { |
| | | |
| | | @Resource |
| | | private HolidayWorkbookService workbookService; |
| | | |
| | | @Override |
| | | public void run(ApplicationArguments args) { |
| | | workbookService.seedReferenceDataIfNeeded(); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.trafficaudit.dataimport.entity.ImportBatch; |
| | | import com.trafficaudit.dataimport.mapper.ImportBatchMapper; |
| | | import com.trafficaudit.holiday.HolidayConstants; |
| | | import com.trafficaudit.holiday.HolidayNumberUtils; |
| | | import com.trafficaudit.holiday.entity.HolidayCalendar; |
| | | import com.trafficaudit.holiday.entity.HolidayPassengerFlow; |
| | | import com.trafficaudit.holiday.entity.HolidayVehicleFlow; |
| | | import com.trafficaudit.holiday.mapper.HolidayPassengerFlowMapper; |
| | | import com.trafficaudit.holiday.mapper.HolidayVehicleFlowMapper; |
| | | import lombok.Data; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @Service |
| | | public class HolidayStatService { |
| | | |
| | | @Resource |
| | | private HolidayConfigService configService; |
| | | @Resource |
| | | private HolidayWorkbookService workbookService; |
| | | @Resource |
| | | private HolidayPassengerFlowMapper passengerFlowMapper; |
| | | @Resource |
| | | private HolidayVehicleFlowMapper vehicleFlowMapper; |
| | | @Resource |
| | | private ImportBatchMapper importBatchMapper; |
| | | |
| | | public Map<String, Object> overview(int year, String holidayType) { |
| | | List<HolidayCalendar> calendar = configService.findHolidayDays(year, holidayType); |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("year", year); |
| | | result.put("holidayType", holidayType); |
| | | result.put("holidayTypeLabel", HolidayConstants.HOLIDAY_TYPE_LABELS.get(holidayType)); |
| | | result.put("days", calendar); |
| | | result.put("rows", buildOverviewRows(year, holidayType, calendar)); |
| | | result.put("templateFileName", workbookService.referenceFileName()); |
| | | return result; |
| | | } |
| | | |
| | | @Transactional |
| | | public Map<String, Object> importWorkbook(MultipartFile file, int year, String holidayType, boolean allowHistoricalOverwrite) { |
| | | HolidayWorkbookService.ImportSummary summary = workbookService.importWorkbook(file, year, holidayType, allowHistoricalOverwrite); |
| | | if (summary.isConfirmationRequired()) { |
| | | return importResult(summary); |
| | | } |
| | | ImportBatch batch = new ImportBatch(); |
| | | batch.setFileName(file == null ? null : file.getOriginalFilename()); |
| | | batch.setImportType("HOLIDAY_FLOW"); |
| | | batch.setReportPeriod(year + "-" + holidayType); |
| | | batch.setTotalRows(summary.getSuccessRows() + summary.getFailRows()); |
| | | batch.setSuccessRows(summary.getSuccessRows()); |
| | | batch.setFailRows(summary.getFailRows()); |
| | | batch.setErrorDetail(joinErrors(summary.getErrors())); |
| | | batch.setCreatedAt(LocalDateTime.now()); |
| | | importBatchMapper.insert(batch); |
| | | |
| | | return importResult(summary); |
| | | } |
| | | |
| | | private Map<String, Object> importResult(HolidayWorkbookService.ImportSummary summary) { |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("passengerRows", summary.getPassengerRows()); |
| | | result.put("vehicleRows", summary.getVehicleRows()); |
| | | result.put("skippedRows", summary.getSkippedRows()); |
| | | result.put("successRows", summary.getSuccessRows()); |
| | | result.put("failRows", summary.getFailRows()); |
| | | result.put("errors", summary.getErrors()); |
| | | result.put("confirmationRequired", summary.isConfirmationRequired()); |
| | | result.put("historicalChanges", summary.getHistoricalChanges()); |
| | | return result; |
| | | } |
| | | |
| | | public byte[] exportWorkbook(int year, String holidayType) { |
| | | return workbookService.exportWorkbook(year, holidayType); |
| | | } |
| | | |
| | | public List<Map<String, Object>> buildOverviewRows(int year, String holidayType, List<HolidayCalendar> calendar) { |
| | | Map<LocalDate, Map<String, BigDecimal>> passengerByDate = loadPassengerByDate(year); |
| | | Map<LocalDate, Map<String, BigDecimal>> vehicleByDate = loadVehicleByDate(year); |
| | | Map<LocalDate, BigDecimal> comparisonBaseByDate = loadComparisonBaseByDate(year); |
| | | BigDecimal coefficient = configService.coefficientOf(year, HolidayConstants.CATEGORY_HOLIDAY); |
| | | List<Map<String, Object>> rows = new ArrayList<>(); |
| | | BigDecimal previousNationalProvincial = null; |
| | | for (HolidayCalendar day : calendar) { |
| | | Map<String, BigDecimal> passenger = passengerByDate.getOrDefault(day.getHolidayDate(), Collections.emptyMap()); |
| | | Map<String, BigDecimal> vehicle = vehicleByDate.getOrDefault(day.getHolidayDate(), Collections.emptyMap()); |
| | | BigDecimal expressway = vehicle.get(HolidayConstants.ROAD_EXPRESSWAY); |
| | | BigDecimal nationalProvincial = vehicle.get(HolidayConstants.ROAD_NATIONAL_PROVINCIAL); |
| | | BigDecimal comparisonBase = comparisonBaseByDate.get(day.getHolidayDate()); |
| | | BigDecimal nationalProvincialRingRatio = nationalProvincialRingRatio( |
| | | year, nationalProvincial, previousNationalProvincial, comparisonBase); |
| | | BigDecimal nonBusinessPersonFlow = null; |
| | | if (coefficient != null && (expressway != null || nationalProvincial != null)) { |
| | | nonBusinessPersonFlow = HolidayNumberUtils.multiply( |
| | | HolidayNumberUtils.sumNullable(expressway, nationalProvincial), coefficient); |
| | | } |
| | | Map<String, Object> row = new LinkedHashMap<>(); |
| | | row.put("dayIndex", day.getDayIndex()); |
| | | row.put("date", day.getHolidayDate()); |
| | | row.put("rail", passenger.get(HolidayConstants.MODE_RAIL)); |
| | | row.put("civilAviation", passenger.get(HolidayConstants.MODE_CIVIL_AVIATION)); |
| | | row.put("road", passenger.get(HolidayConstants.MODE_ROAD)); |
| | | row.put("water", passenger.get(HolidayConstants.MODE_WATER)); |
| | | row.put("expressway", expressway); |
| | | row.put("nationalProvincial", nationalProvincial); |
| | | row.put("comparisonBase", comparisonBase); |
| | | row.put("nationalProvincialRingRatio", nationalProvincialRingRatio); |
| | | row.put("coefficient", coefficient); |
| | | row.put("nonBusinessPersonFlow", nonBusinessPersonFlow); |
| | | row.put("totalPersonFlow", totalPersonFlow(passenger, nonBusinessPersonFlow)); |
| | | rows.add(row); |
| | | previousNationalProvincial = nationalProvincial; |
| | | } |
| | | return rows; |
| | | } |
| | | |
| | | public DailyMetrics dailyMetrics(int year, String holidayType, int dayIndex) { |
| | | List<HolidayCalendar> calendar = configService.findHolidayDays(year, holidayType); |
| | | HolidayCalendar target = null; |
| | | for (HolidayCalendar day : calendar) { |
| | | if (day.getDayIndex() != null && day.getDayIndex() == dayIndex) { |
| | | target = day; |
| | | break; |
| | | } |
| | | } |
| | | if (target == null) return null; |
| | | |
| | | Map<LocalDate, Map<String, BigDecimal>> passengerByDate = loadPassengerByDate(year); |
| | | Map<LocalDate, Map<String, BigDecimal>> vehicleByDate = loadVehicleByDate(year); |
| | | Map<LocalDate, BigDecimal> comparisonBaseByDate = loadComparisonBaseByDate(year); |
| | | Map<String, BigDecimal> passenger = passengerByDate.getOrDefault(target.getHolidayDate(), Collections.emptyMap()); |
| | | Map<String, BigDecimal> vehicle = vehicleByDate.getOrDefault(target.getHolidayDate(), Collections.emptyMap()); |
| | | BigDecimal coefficient = configService.coefficientOf(year, HolidayConstants.CATEGORY_HOLIDAY); |
| | | BigDecimal expressway = vehicle.get(HolidayConstants.ROAD_EXPRESSWAY); |
| | | BigDecimal nationalProvincial = vehicle.get(HolidayConstants.ROAD_NATIONAL_PROVINCIAL); |
| | | BigDecimal comparisonBase = comparisonBaseByDate.get(target.getHolidayDate()); |
| | | BigDecimal nonBusiness = null; |
| | | if (coefficient != null && (expressway != null || nationalProvincial != null)) { |
| | | nonBusiness = HolidayNumberUtils.multiply( |
| | | HolidayNumberUtils.sumNullable(expressway, nationalProvincial), coefficient); |
| | | } |
| | | |
| | | DailyMetrics metrics = new DailyMetrics(); |
| | | metrics.setYear(year); |
| | | metrics.setHolidayType(holidayType); |
| | | metrics.setDayIndex(dayIndex); |
| | | metrics.setDate(target.getHolidayDate()); |
| | | metrics.setRail(passenger.get(HolidayConstants.MODE_RAIL)); |
| | | metrics.setCivilAviation(passenger.get(HolidayConstants.MODE_CIVIL_AVIATION)); |
| | | metrics.setRoad(passenger.get(HolidayConstants.MODE_ROAD)); |
| | | metrics.setWater(passenger.get(HolidayConstants.MODE_WATER)); |
| | | metrics.setExpressway(expressway); |
| | | metrics.setNationalProvincial(nationalProvincial); |
| | | metrics.setComparisonBase(comparisonBase); |
| | | metrics.setCoefficient(coefficient); |
| | | metrics.setNonBusinessPersonFlow(nonBusiness); |
| | | metrics.setRoadPersonFlow(sumNullable(metrics.getRoad(), nonBusiness)); |
| | | metrics.setTotalPersonFlow(totalPersonFlow(passenger, nonBusiness)); |
| | | return metrics; |
| | | } |
| | | |
| | | public DailyMetrics cumulativeMetrics(int year, String holidayType, int dayIndex) { |
| | | DailyMetrics cumulative = new DailyMetrics(); |
| | | cumulative.setYear(year); |
| | | cumulative.setHolidayType(holidayType); |
| | | cumulative.setDayIndex(dayIndex); |
| | | for (int day = 1; day <= dayIndex; day++) { |
| | | DailyMetrics item = dailyMetrics(year, holidayType, day); |
| | | if (item == null) continue; |
| | | cumulative.setRail(sumNullable(cumulative.getRail(), item.getRail())); |
| | | cumulative.setCivilAviation(sumNullable(cumulative.getCivilAviation(), item.getCivilAviation())); |
| | | cumulative.setRoad(sumNullable(cumulative.getRoad(), item.getRoad())); |
| | | cumulative.setWater(sumNullable(cumulative.getWater(), item.getWater())); |
| | | cumulative.setExpressway(sumNullable(cumulative.getExpressway(), item.getExpressway())); |
| | | cumulative.setNationalProvincial(sumNullable(cumulative.getNationalProvincial(), item.getNationalProvincial())); |
| | | cumulative.setNonBusinessPersonFlow(sumNullable(cumulative.getNonBusinessPersonFlow(), item.getNonBusinessPersonFlow())); |
| | | cumulative.setRoadPersonFlow(sumNullable(cumulative.getRoadPersonFlow(), item.getRoadPersonFlow())); |
| | | cumulative.setTotalPersonFlow(sumNullable(cumulative.getTotalPersonFlow(), item.getTotalPersonFlow())); |
| | | } |
| | | return cumulative; |
| | | } |
| | | |
| | | |
| | | public DailyMetrics findMetricsByDate(LocalDate date) { |
| | | HolidayCalendar calendar = configService.findHolidayByDate(date); |
| | | if (calendar == null) return null; |
| | | return dailyMetrics(calendar.getYear(), calendar.getHolidayType(), calendar.getDayIndex()); |
| | | } |
| | | |
| | | private Map<LocalDate, Map<String, BigDecimal>> loadPassengerByDate(int year) { |
| | | Map<LocalDate, Map<String, BigDecimal>> result = new LinkedHashMap<>(); |
| | | List<HolidayPassengerFlow> rows = passengerFlowMapper.selectList( |
| | | new LambdaQueryWrapper<HolidayPassengerFlow>().eq(HolidayPassengerFlow::getYear, year)); |
| | | for (HolidayPassengerFlow row : rows) { |
| | | result.computeIfAbsent(row.getFlowDate(), key -> new LinkedHashMap<>()) |
| | | .put(row.getTransportMode(), row.getPassengerCount()); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private Map<LocalDate, Map<String, BigDecimal>> loadVehicleByDate(int year) { |
| | | Map<LocalDate, Map<String, BigDecimal>> result = new LinkedHashMap<>(); |
| | | List<HolidayVehicleFlow> rows = vehicleFlowMapper.selectList( |
| | | new LambdaQueryWrapper<HolidayVehicleFlow>().eq(HolidayVehicleFlow::getYear, year)); |
| | | for (HolidayVehicleFlow row : rows) { |
| | | result.computeIfAbsent(row.getFlowDate(), key -> new LinkedHashMap<>()) |
| | | .put(row.getRoadCategory(), row.getVehicleCount()); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private Map<LocalDate, BigDecimal> loadComparisonBaseByDate(int year) { |
| | | Map<LocalDate, BigDecimal> result = new LinkedHashMap<>(); |
| | | List<HolidayVehicleFlow> rows = vehicleFlowMapper.selectList( |
| | | new LambdaQueryWrapper<HolidayVehicleFlow>() |
| | | .eq(HolidayVehicleFlow::getYear, year) |
| | | .eq(HolidayVehicleFlow::getRoadCategory, HolidayConstants.ROAD_NATIONAL_PROVINCIAL)); |
| | | for (HolidayVehicleFlow row : rows) { |
| | | if (row.getComparisonBase() != null) result.put(row.getFlowDate(), row.getComparisonBase()); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private BigDecimal totalPersonFlow(Map<String, BigDecimal> passenger, BigDecimal nonBusinessPersonFlow) { |
| | | BigDecimal total = nonBusinessPersonFlow; |
| | | total = sumNullable(total, passenger.get(HolidayConstants.MODE_ROAD)); |
| | | total = sumNullable(total, passenger.get(HolidayConstants.MODE_RAIL)); |
| | | total = sumNullable(total, passenger.get(HolidayConstants.MODE_CIVIL_AVIATION)); |
| | | total = sumNullable(total, passenger.get(HolidayConstants.MODE_WATER)); |
| | | return total; |
| | | } |
| | | |
| | | private BigDecimal nationalProvincialRingRatio(int year, BigDecimal current, |
| | | BigDecimal previous, BigDecimal comparisonBase) { |
| | | if (current == null) return null; |
| | | if (HolidayConstants.supportsComparisonBase(year) && comparisonBase != null) { |
| | | return ratio(current, comparisonBase); |
| | | } |
| | | return previous == null ? null : ratio(current, previous); |
| | | } |
| | | |
| | | private BigDecimal ratio(BigDecimal current, BigDecimal base) { |
| | | BigDecimal quotient = HolidayNumberUtils.divide(current, base); |
| | | return quotient == null ? null : HolidayNumberUtils.normalize(quotient.subtract(BigDecimal.ONE)); |
| | | } |
| | | |
| | | private BigDecimal sumNullable(BigDecimal left, BigDecimal right) { |
| | | return HolidayNumberUtils.sumNullable(left, right); |
| | | } |
| | | |
| | | private String joinErrors(List<String> errors) { |
| | | if (errors == null || errors.isEmpty()) return null; |
| | | StringBuilder builder = new StringBuilder(); |
| | | int limit = Math.min(errors.size(), 200); |
| | | for (int i = 0; i < limit; i++) { |
| | | if (i > 0) builder.append('\n'); |
| | | builder.append(errors.get(i)); |
| | | } |
| | | if (errors.size() > limit) { |
| | | builder.append("\n... ").append(errors.size() - limit).append(" more"); |
| | | } |
| | | return builder.toString(); |
| | | } |
| | | |
| | | @Data |
| | | public static class DailyMetrics { |
| | | private int year; |
| | | private String holidayType; |
| | | private int dayIndex; |
| | | private LocalDate date; |
| | | private BigDecimal rail; |
| | | private BigDecimal civilAviation; |
| | | private BigDecimal road; |
| | | private BigDecimal water; |
| | | private BigDecimal expressway; |
| | | private BigDecimal nationalProvincial; |
| | | private BigDecimal comparisonBase; |
| | | private BigDecimal coefficient; |
| | | private BigDecimal nonBusinessPersonFlow; |
| | | private BigDecimal roadPersonFlow; |
| | | private BigDecimal totalPersonFlow; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.trafficaudit.holiday.HolidayConstants; |
| | | import com.trafficaudit.holiday.HolidayNumberUtils; |
| | | import com.trafficaudit.holiday.entity.HolidayCalendar; |
| | | import com.trafficaudit.holiday.entity.HolidayPassengerCoefficient; |
| | | import com.trafficaudit.holiday.entity.HolidayPassengerFlow; |
| | | import com.trafficaudit.holiday.entity.HolidayVehicleFlow; |
| | | import com.trafficaudit.holiday.mapper.HolidayPassengerCoefficientMapper; |
| | | import com.trafficaudit.holiday.mapper.HolidayPassengerFlowMapper; |
| | | import com.trafficaudit.holiday.mapper.HolidayVehicleFlowMapper; |
| | | import lombok.Data; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.poi.ss.usermodel.Cell; |
| | | import org.apache.poi.ss.usermodel.CellType; |
| | | import org.apache.poi.ss.usermodel.DataFormatter; |
| | | import org.apache.poi.ss.usermodel.FormulaEvaluator; |
| | | import org.apache.poi.ss.usermodel.Row; |
| | | import org.apache.poi.ss.usermodel.Sheet; |
| | | import org.apache.poi.ss.usermodel.Workbook; |
| | | import org.apache.poi.ss.usermodel.WorkbookFactory; |
| | | import org.apache.poi.xssf.usermodel.XSSFWorkbook; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.io.ByteArrayOutputStream; |
| | | import java.io.File; |
| | | import java.io.FileInputStream; |
| | | import java.io.InputStream; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeParseException; |
| | | import java.util.ArrayList; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @Slf4j |
| | | @Service |
| | | public class HolidayWorkbookService { |
| | | |
| | | private static final Map<String, String> PASSENGER_SHEET_MODE = new LinkedHashMap<>(); |
| | | private static final int FIRST_DATA_ROW_INDEX = 2; |
| | | private static final int[] REFERENCE_YEARS = {2024, 2025, 2026}; |
| | | |
| | | static { |
| | | PASSENGER_SHEET_MODE.put("éè·¯", HolidayConstants.MODE_RAIL); |
| | | PASSENGER_SHEET_MODE.put("æ°èª", HolidayConstants.MODE_CIVIL_AVIATION); |
| | | PASSENGER_SHEET_MODE.put("éè·¯", HolidayConstants.MODE_ROAD); |
| | | PASSENGER_SHEET_MODE.put("æ°´è·¯", HolidayConstants.MODE_WATER); |
| | | } |
| | | |
| | | @Resource |
| | | private HolidayConfigService configService; |
| | | @Resource |
| | | private HolidayPassengerFlowMapper passengerFlowMapper; |
| | | @Resource |
| | | private HolidayVehicleFlowMapper vehicleFlowMapper; |
| | | @Resource |
| | | private HolidayPassengerCoefficientMapper coefficientMapper; |
| | | |
| | | @Value("${holiday.reference-path:docs/ä¸é¡¹ç»è®¡/éè¦è忥/2026å¹´å䏿°æ®.xlsx}") |
| | | private String referencePath; |
| | | |
| | | public File resolveReferenceFile() { |
| | | List<File> candidates = new ArrayList<>(); |
| | | File configured = new File(referencePath); |
| | | candidates.add(configured); |
| | | if (!configured.isAbsolute()) { |
| | | candidates.add(new File(System.getProperty("user.dir"), referencePath)); |
| | | candidates.add(new File(System.getProperty("user.dir"), "../" + referencePath)); |
| | | } |
| | | for (File candidate : candidates) { |
| | | try { |
| | | if (candidate.exists() && candidate.isFile()) { |
| | | return candidate.getCanonicalFile(); |
| | | } |
| | | } catch (Exception ignored) { |
| | | // Keep checking the remaining candidate paths. |
| | | } |
| | | } |
| | | return configured; |
| | | } |
| | | |
| | | public String referenceFileName() { |
| | | return resolveReferenceFile().getName(); |
| | | } |
| | | |
| | | @Transactional |
| | | public ImportSummary importWorkbook(MultipartFile file, int year, String holidayType, boolean allowHistoricalOverwrite) { |
| | | if (file == null || file.isEmpty()) { |
| | | throw new IllegalArgumentException("è¯·éæ©è¦å¯¼å
¥ç Excel æä»¶"); |
| | | } |
| | | List<HolidayCalendar> holidayDays = configService.findHolidayDays(year, holidayType); |
| | | if (holidayDays.isEmpty()) { |
| | | throw new IllegalArgumentException("æé年份åè忥尿ªé
ç½®æ¥æï¼è¯·å
å¨ç³»ç»é
ç½®ä¸ç»´æ¤è忥"); |
| | | } |
| | | Map<Integer, LocalDate> dateByDay = new LinkedHashMap<>(); |
| | | for (HolidayCalendar day : holidayDays) { |
| | | dateByDay.put(day.getDayIndex(), day.getHolidayDate()); |
| | | } |
| | | |
| | | if (!allowHistoricalOverwrite) { |
| | | ImportSummary historicalSummary = detectHistoricalChanges(file, year, dateByDay); |
| | | if (historicalSummary.isConfirmationRequired()) { |
| | | return historicalSummary; |
| | | } |
| | | } |
| | | |
| | | ImportSummary summary = new ImportSummary(); |
| | | try (InputStream in = file.getInputStream(); Workbook workbook = WorkbookFactory.create(in)) { |
| | | FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator(); |
| | | importPassengerSheets(workbook, evaluator, year, dateByDay, summary); |
| | | importVehicleSheets(workbook, evaluator, year, dateByDay, summary); |
| | | } catch (Exception e) { |
| | | throw new IllegalArgumentException("å·¥ä½ç°¿è§£æå¤±è´¥: " + e.getMessage(), e); |
| | | } |
| | | return summary; |
| | | } |
| | | |
| | | private ImportSummary detectHistoricalChanges(MultipartFile file, int year, Map<Integer, LocalDate> dateByDay) { |
| | | ImportSummary summary = new ImportSummary(); |
| | | try (InputStream in = file.getInputStream(); Workbook workbook = WorkbookFactory.create(in)) { |
| | | FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator(); |
| | | int latestDay = findLatestIncomingDay(workbook, evaluator, year, dateByDay); |
| | | if (latestDay <= 1) { |
| | | return summary; |
| | | } |
| | | Map<LocalDate, Map<String, BigDecimal>> existingPassenger = loadPassengerByDate(year); |
| | | Map<LocalDate, Map<String, BigDecimal>> existingVehicle = loadVehicleByDate(year); |
| | | detectPassengerHistory(workbook, evaluator, year, dateByDay, latestDay, existingPassenger, summary); |
| | | detectVehicleHistory(workbook, evaluator, year, dateByDay, latestDay, existingVehicle, summary); |
| | | if (!summary.getHistoricalChanges().isEmpty()) { |
| | | summary.setConfirmationRequired(true); |
| | | } |
| | | } catch (Exception e) { |
| | | throw new IllegalArgumentException("å·¥ä½ç°¿è§£æå¤±è´¥: " + e.getMessage(), e); |
| | | } |
| | | return summary; |
| | | } |
| | | |
| | | private int findLatestIncomingDay(Workbook workbook, FormulaEvaluator evaluator, int year, Map<Integer, LocalDate> dateByDay) { |
| | | int latest = 0; |
| | | for (Integer dayIndex : dateByDay.keySet()) { |
| | | if (hasPassengerValue(workbook, evaluator, year, dayIndex) |
| | | || hasVehicleValue(workbook, evaluator, year, dayIndex)) { |
| | | latest = Math.max(latest, dayIndex); |
| | | } |
| | | } |
| | | return latest; |
| | | } |
| | | |
| | | private boolean hasPassengerValue(Workbook workbook, FormulaEvaluator evaluator, int year, int dayIndex) { |
| | | for (Map.Entry<String, String> entry : PASSENGER_SHEET_MODE.entrySet()) { |
| | | Sheet sheet = workbook.getSheet(entry.getKey()); |
| | | if (sheet == null) continue; |
| | | Integer yearColumn = findYearColumn(sheet, evaluator, year, 2, 7); |
| | | if (yearColumn == null) continue; |
| | | Row row = sheet.getRow(FIRST_DATA_ROW_INDEX + dayIndex - 1); |
| | | if (row == null) continue; |
| | | BigDecimal value = numberValue(row.getCell(yearColumn), evaluator); |
| | | if (value != null) return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private boolean hasVehicleValue(Workbook workbook, FormulaEvaluator evaluator, int year, int dayIndex) { |
| | | Sheet expressway = workbook.getSheet("é«éå
¬è·¯"); |
| | | if (expressway != null) { |
| | | Integer yearColumn = findYearColumn(expressway, evaluator, year, 2, 7); |
| | | if (yearColumn != null) { |
| | | Row row = expressway.getRow(FIRST_DATA_ROW_INDEX + dayIndex - 1); |
| | | if (row != null && numberValue(row.getCell(yearColumn), evaluator) != null) return true; |
| | | } |
| | | } |
| | | Sheet nationalProvincial = workbook.getSheet("æ®éå½çé"); |
| | | if (nationalProvincial != null) { |
| | | Integer yearColumn = findYearColumn(nationalProvincial, evaluator, year, 3, 8); |
| | | if (yearColumn != null) { |
| | | Row row = nationalProvincial.getRow(FIRST_DATA_ROW_INDEX + dayIndex - 1); |
| | | if (row != null && numberValue(row.getCell(yearColumn), evaluator) != null) return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private void detectPassengerHistory(Workbook workbook, FormulaEvaluator evaluator, int year, |
| | | Map<Integer, LocalDate> dateByDay, int latestDay, |
| | | Map<LocalDate, Map<String, BigDecimal>> existing, |
| | | ImportSummary summary) { |
| | | for (Map.Entry<String, String> entry : PASSENGER_SHEET_MODE.entrySet()) { |
| | | Sheet sheet = workbook.getSheet(entry.getKey()); |
| | | if (sheet == null) continue; |
| | | Integer yearColumn = findYearColumn(sheet, evaluator, year, 2, 7); |
| | | if (yearColumn == null) continue; |
| | | for (Map.Entry<Integer, LocalDate> day : dateByDay.entrySet()) { |
| | | if (day.getKey() >= latestDay) continue; |
| | | Row row = sheet.getRow(FIRST_DATA_ROW_INDEX + day.getKey() - 1); |
| | | if (row == null) continue; |
| | | BigDecimal incoming = numberValue(row.getCell(yearColumn), evaluator); |
| | | if (incoming == null) continue; |
| | | Map<String, BigDecimal> existingDay = existing.get(day.getValue()); |
| | | BigDecimal oldValue = existingDay == null ? null : existingDay.get(entry.getValue()); |
| | | if (oldValue != null && !sameValue(oldValue, incoming)) { |
| | | summary.addHistoricalChange("第" + day.getKey() + "æ¥ " + day.getValue() + " " |
| | | + entry.getKey() + "ï¼åå¼ " + formatChangeValue(oldValue) + "ï¼å¯¼å
¥å¼ " + formatChangeValue(incoming)); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void detectVehicleHistory(Workbook workbook, FormulaEvaluator evaluator, int year, |
| | | Map<Integer, LocalDate> dateByDay, int latestDay, |
| | | Map<LocalDate, Map<String, BigDecimal>> existing, |
| | | ImportSummary summary) { |
| | | detectVehicleSheetHistory(workbook, evaluator, year, dateByDay, latestDay, existing, summary, |
| | | "é«éå
¬è·¯", HolidayConstants.ROAD_EXPRESSWAY, 2, 7); |
| | | detectVehicleSheetHistory(workbook, evaluator, year, dateByDay, latestDay, existing, summary, |
| | | "æ®éå½çé", HolidayConstants.ROAD_NATIONAL_PROVINCIAL, 3, 8); |
| | | detectComparisonBaseHistory(workbook, evaluator, year, dateByDay, latestDay, existing, summary); |
| | | } |
| | | |
| | | private void detectVehicleSheetHistory(Workbook workbook, FormulaEvaluator evaluator, int year, |
| | | Map<Integer, LocalDate> dateByDay, int latestDay, |
| | | Map<LocalDate, Map<String, BigDecimal>> existing, |
| | | ImportSummary summary, String sheetName, String roadCategory, |
| | | int startColumn, int endColumn) { |
| | | Sheet sheet = workbook.getSheet(sheetName); |
| | | if (sheet == null) return; |
| | | Integer yearColumn = findYearColumn(sheet, evaluator, year, startColumn, endColumn); |
| | | if (yearColumn == null) return; |
| | | for (Map.Entry<Integer, LocalDate> day : dateByDay.entrySet()) { |
| | | if (day.getKey() >= latestDay) continue; |
| | | Row row = sheet.getRow(FIRST_DATA_ROW_INDEX + day.getKey() - 1); |
| | | if (row == null) continue; |
| | | BigDecimal incoming = numberValue(row.getCell(yearColumn), evaluator); |
| | | if (incoming == null) continue; |
| | | Map<String, BigDecimal> existingDay = existing.get(day.getValue()); |
| | | BigDecimal oldValue = existingDay == null ? null : existingDay.get(roadCategory); |
| | | if (oldValue != null && !sameValue(oldValue, incoming)) { |
| | | summary.addHistoricalChange("第" + day.getKey() + "æ¥ " + day.getValue() + " " |
| | | + sheetName + "ï¼åå¼ " + formatChangeValue(oldValue) + "ï¼å¯¼å
¥å¼ " + formatChangeValue(incoming)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void detectComparisonBaseHistory(Workbook workbook, FormulaEvaluator evaluator, int year, |
| | | Map<Integer, LocalDate> dateByDay, int latestDay, |
| | | Map<LocalDate, Map<String, BigDecimal>> existingVehicle, |
| | | ImportSummary summary) { |
| | | if (!HolidayConstants.supportsComparisonBase(year)) return; |
| | | Sheet sheet = workbook.getSheet("æ®éå½çé"); |
| | | if (sheet == null) return; |
| | | Map<LocalDate, BigDecimal> existingBase = loadComparisonBaseByDate(year); |
| | | for (Map.Entry<Integer, LocalDate> day : dateByDay.entrySet()) { |
| | | if (day.getKey() >= latestDay) continue; |
| | | Map<String, BigDecimal> existingDay = existingVehicle.get(day.getValue()); |
| | | if (existingDay == null || existingDay.get(HolidayConstants.ROAD_NATIONAL_PROVINCIAL) == null) continue; |
| | | Row row = sheet.getRow(FIRST_DATA_ROW_INDEX + day.getKey() - 1); |
| | | if (row == null) continue; |
| | | BigDecimal incoming; |
| | | try { |
| | | incoming = numberValue(row.getCell(2), evaluator); |
| | | } catch (Exception e) { |
| | | summary.addError("æ®éå½çé 第" + (row.getRowNum() + 1) + "è¡ç¯æ¯åºæï¼" + e.getMessage()); |
| | | continue; |
| | | } |
| | | BigDecimal oldValue = existingBase.get(day.getValue()); |
| | | if (!sameNullableValue(oldValue, incoming)) { |
| | | summary.addHistoricalChange("第" + day.getKey() + "æ¥ " + day.getValue() + " æ®éå½çéç¯æ¯åºæï¼åå¼ " |
| | | + nullableChangeValue(oldValue) + "ï¼å¯¼å
¥å¼ " + nullableChangeValue(incoming)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | Map<LocalDate, Map<String, BigDecimal>> result = new LinkedHashMap<>(); |
| | | private Map<LocalDate, Map<String, BigDecimal>> loadPassengerByDate(int year) { |
| | | List<HolidayPassengerFlow> rows = passengerFlowMapper.selectList( |
| | | new LambdaQueryWrapper<HolidayPassengerFlow>().eq(HolidayPassengerFlow::getYear, year)); |
| | | for (HolidayPassengerFlow row : rows) { |
| | | result.computeIfAbsent(row.getFlowDate(), key -> new LinkedHashMap<>()) |
| | | .put(row.getTransportMode(), row.getPassengerCount()); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private Map<LocalDate, Map<String, BigDecimal>> loadVehicleByDate(int year) { |
| | | Map<LocalDate, Map<String, BigDecimal>> result = new LinkedHashMap<>(); |
| | | List<HolidayVehicleFlow> rows = vehicleFlowMapper.selectList( |
| | | new LambdaQueryWrapper<HolidayVehicleFlow>().eq(HolidayVehicleFlow::getYear, year)); |
| | | for (HolidayVehicleFlow row : rows) { |
| | | result.computeIfAbsent(row.getFlowDate(), key -> new LinkedHashMap<>()) |
| | | .put(row.getRoadCategory(), row.getVehicleCount()); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private Map<LocalDate, BigDecimal> loadComparisonBaseByDate(int year) { |
| | | Map<LocalDate, BigDecimal> result = new LinkedHashMap<>(); |
| | | List<HolidayVehicleFlow> rows = vehicleFlowMapper.selectList( |
| | | new LambdaQueryWrapper<HolidayVehicleFlow>() |
| | | .eq(HolidayVehicleFlow::getYear, year) |
| | | .eq(HolidayVehicleFlow::getRoadCategory, HolidayConstants.ROAD_NATIONAL_PROVINCIAL)); |
| | | for (HolidayVehicleFlow row : rows) result.put(row.getFlowDate(), row.getComparisonBase()); |
| | | return result; |
| | | } |
| | | |
| | | private boolean sameValue(BigDecimal left, BigDecimal right) { |
| | | return HolidayNumberUtils.sameValue(left, right); |
| | | } |
| | | |
| | | private boolean sameNullableValue(BigDecimal left, BigDecimal right) { |
| | | if (left == null || right == null) return left == right; |
| | | return sameValue(left, right); |
| | | } |
| | | |
| | | private String nullableChangeValue(BigDecimal value) { |
| | | return value == null ? "空" : formatChangeValue(value); |
| | | } |
| | | |
| | | private String formatChangeValue(BigDecimal value) { |
| | | return HolidayNumberUtils.changeText(value); |
| | | } |
| | | |
| | | @Transactional |
| | | public void seedReferenceDataIfNeeded() { |
| | | File reference = resolveReferenceFile(); |
| | | if (!reference.exists() || !reference.isFile()) { |
| | | log.warn("Holiday reference workbook not found: {}", reference.getAbsolutePath()); |
| | | return; |
| | | } |
| | | try (InputStream in = new FileInputStream(reference); Workbook workbook = WorkbookFactory.create(in)) { |
| | | FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator(); |
| | | seedNationalDayCalendar(); |
| | | for (int year : REFERENCE_YEARS) { |
| | | if (passengerFlowMapper.selectCount(new LambdaQueryWrapper<HolidayPassengerFlow>() |
| | | .eq(HolidayPassengerFlow::getYear, year)) == 0) { |
| | | seedPassengerYear(workbook, evaluator, year); |
| | | } |
| | | if (vehicleFlowMapper.selectCount(new LambdaQueryWrapper<HolidayVehicleFlow>() |
| | | .eq(HolidayVehicleFlow::getYear, year)) == 0) { |
| | | seedVehicleYear(workbook, evaluator, year); |
| | | } |
| | | if (coefficientMapper.selectCount(new LambdaQueryWrapper<HolidayPassengerCoefficient>() |
| | | .eq(HolidayPassengerCoefficient::getYear, year)) == 0) { |
| | | seedCoefficientYear(workbook, evaluator, year); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("Failed to seed holiday reference data", e); |
| | | } |
| | | } |
| | | |
| | | public byte[] exportWorkbook(int year, String holidayType) { |
| | | File reference = resolveReferenceFile(); |
| | | if (!reference.exists() || !reference.isFile()) { |
| | | throw new IllegalArgumentException("èåæ¥æ¨¡æ¿æä»¶ä¸åå¨: " + reference.getAbsolutePath()); |
| | | } |
| | | try (InputStream in = new FileInputStream(reference); XSSFWorkbook workbook = new XSSFWorkbook(in)) { |
| | | FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator(); |
| | | fillPassengerSheets(workbook, evaluator, year, holidayType); |
| | | fillVehicleSheets(workbook, evaluator, year, holidayType); |
| | | fillCoefficients(workbook, evaluator, year, holidayType); |
| | | workbook.setForceFormulaRecalculation(true); |
| | | try { |
| | | evaluator.evaluateAll(); |
| | | } catch (Exception e) { |
| | | log.warn("Workbook formula recalculation did not complete; Excel will recalculate on open", e); |
| | | } |
| | | ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| | | workbook.write(out); |
| | | return out.toByteArray(); |
| | | } catch (Exception e) { |
| | | throw new IllegalArgumentException("èåæ¥æ°æ®å¯¼åºå¤±è´¥: " + e.getMessage(), e); |
| | | } |
| | | } |
| | | |
| | | private void importPassengerSheets(Workbook workbook, FormulaEvaluator evaluator, int year, |
| | | Map<Integer, LocalDate> dateByDay, ImportSummary summary) { |
| | | for (Map.Entry<String, String> entry : PASSENGER_SHEET_MODE.entrySet()) { |
| | | Sheet sheet = workbook.getSheet(entry.getKey()); |
| | | if (sheet == null) { |
| | | summary.addError(entry.getKey() + "ï¼æ¾ä¸å°å·¥ä½è¡¨"); |
| | | continue; |
| | | } |
| | | Integer yearColumn = findYearColumn(sheet, evaluator, year, 2, 7); |
| | | if (yearColumn == null) { |
| | | summary.addError(entry.getKey() + "ï¼æ¾ä¸å° " + year + " å¹´æ°æ®å"); |
| | | continue; |
| | | } |
| | | for (Map.Entry<Integer, LocalDate> day : dateByDay.entrySet()) { |
| | | Row row = sheet.getRow(FIRST_DATA_ROW_INDEX + day.getKey() - 1); |
| | | if (row == null) { |
| | | summary.addError(entry.getKey() + " 第" + day.getKey() + "æ¥ï¼å·¥ä½è¡¨ç¼ºå°è¯¥è¡"); |
| | | continue; |
| | | } |
| | | Integer rowDay = parseDayIndex(row.getCell(0), evaluator); |
| | | if (rowDay == null || !rowDay.equals(day.getKey())) { |
| | | summary.addError(entry.getKey() + " 第" + (row.getRowNum() + 1) + "è¡ï¼ç¬¬å æ¥ä¸è忥é
ç½®ä¸ä¸è´"); |
| | | continue; |
| | | } |
| | | BigDecimal value; |
| | | try { |
| | | value = numberValue(row.getCell(yearColumn), evaluator); |
| | | } catch (Exception e) { |
| | | summary.addError(entry.getKey() + " 第" + (row.getRowNum() + 1) + "è¡ï¼" + e.getMessage()); |
| | | continue; |
| | | } |
| | | if (value == null) { |
| | | summary.incrementSkipped(); |
| | | continue; |
| | | } |
| | | upsertPassenger(year, day.getValue(), entry.getValue(), value); |
| | | summary.incrementPassenger(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void importVehicleSheets(Workbook workbook, FormulaEvaluator evaluator, int year, |
| | | Map<Integer, LocalDate> dateByDay, ImportSummary summary) { |
| | | importVehicleSheet(workbook, evaluator, year, dateByDay, summary, |
| | | "é«éå
¬è·¯", HolidayConstants.ROAD_EXPRESSWAY, HolidayConstants.VEHICLE_SMALL_PASSENGER, 2, 7, null); |
| | | importVehicleSheet(workbook, evaluator, year, dateByDay, summary, |
| | | "æ®éå½çé", HolidayConstants.ROAD_NATIONAL_PROVINCIAL, HolidayConstants.VEHICLE_MEDIUM_SMALL_PASSENGER, 3, 8, |
| | | HolidayConstants.supportsComparisonBase(year) ? 2 : null); |
| | | } |
| | | |
| | | private void importVehicleSheet(Workbook workbook, FormulaEvaluator evaluator, int year, |
| | | Map<Integer, LocalDate> dateByDay, ImportSummary summary, |
| | | String sheetName, String roadCategory, String vehicleType, |
| | | int startColumn, int endColumn, Integer comparisonBaseColumn) { |
| | | Sheet sheet = workbook.getSheet(sheetName); |
| | | if (sheet == null) { |
| | | summary.addError(sheetName + "ï¼æ¾ä¸å°å·¥ä½è¡¨"); |
| | | return; |
| | | } |
| | | Integer yearColumn = findYearColumn(sheet, evaluator, year, startColumn, endColumn); |
| | | if (yearColumn == null) { |
| | | summary.addError(sheetName + "ï¼æ¾ä¸å° " + year + " å¹´æ°æ®å"); |
| | | return; |
| | | } |
| | | for (Map.Entry<Integer, LocalDate> day : dateByDay.entrySet()) { |
| | | Row row = sheet.getRow(FIRST_DATA_ROW_INDEX + day.getKey() - 1); |
| | | if (row == null) { |
| | | summary.addError(sheetName + " 第" + day.getKey() + "æ¥ï¼å·¥ä½è¡¨ç¼ºå°è¯¥è¡"); |
| | | continue; |
| | | } |
| | | Integer rowDay = parseDayIndex(row.getCell(0), evaluator); |
| | | if (rowDay == null || !rowDay.equals(day.getKey())) { |
| | | summary.addError(sheetName + " 第" + (row.getRowNum() + 1) + "è¡ï¼ç¬¬å æ¥ä¸è忥é
ç½®ä¸ä¸è´"); |
| | | continue; |
| | | } |
| | | BigDecimal value; |
| | | try { |
| | | value = numberValue(row.getCell(yearColumn), evaluator); |
| | | } catch (Exception e) { |
| | | summary.addError(sheetName + " 第" + (row.getRowNum() + 1) + "è¡ï¼" + e.getMessage()); |
| | | continue; |
| | | } |
| | | if (value == null) { |
| | | summary.incrementSkipped(); |
| | | continue; |
| | | } |
| | | BigDecimal comparisonBase = null; |
| | | if (comparisonBaseColumn != null) { |
| | | try { |
| | | comparisonBase = numberValue(row.getCell(comparisonBaseColumn), evaluator); |
| | | } catch (Exception e) { |
| | | summary.addError(sheetName + " 第" + (row.getRowNum() + 1) + "è¡ç¯æ¯åºæï¼" + e.getMessage()); |
| | | continue; |
| | | } |
| | | } |
| | | upsertVehicle(year, day.getValue(), roadCategory, vehicleType, value, comparisonBase); |
| | | summary.incrementVehicle(); |
| | | } |
| | | } |
| | | |
| | | private void fillPassengerSheets(XSSFWorkbook workbook, FormulaEvaluator evaluator, int selectedYear, String holidayType) { |
| | | for (Map.Entry<String, String> entry : PASSENGER_SHEET_MODE.entrySet()) { |
| | | Sheet sheet = workbook.getSheet(entry.getKey()); |
| | | if (sheet == null) continue; |
| | | for (int year : REFERENCE_YEARS) { |
| | | Integer yearColumn = findYearColumn(sheet, evaluator, year, 2, 7); |
| | | if (yearColumn == null) continue; |
| | | Map<Integer, LocalDate> dayDates = dayDates(year, holidayType); |
| | | Map<LocalDate, BigDecimal> values = loadPassengerValues(year, entry.getValue()); |
| | | for (Map.Entry<Integer, LocalDate> day : dayDates.entrySet()) { |
| | | if (!values.containsKey(day.getValue())) continue; |
| | | setNumeric(sheet, FIRST_DATA_ROW_INDEX + day.getKey() - 1, yearColumn, values.get(day.getValue())); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void fillVehicleSheets(XSSFWorkbook workbook, FormulaEvaluator evaluator, int selectedYear, String holidayType) { |
| | | fillVehicleSheet(workbook, evaluator, "é«éå
¬è·¯", HolidayConstants.ROAD_EXPRESSWAY, 2, 7, holidayType, null); |
| | | fillVehicleSheet(workbook, evaluator, "æ®éå½çé", HolidayConstants.ROAD_NATIONAL_PROVINCIAL, 3, 8, holidayType, |
| | | HolidayConstants.supportsComparisonBase(selectedYear) ? 2 : null); |
| | | } |
| | | |
| | | private void fillVehicleSheet(XSSFWorkbook workbook, FormulaEvaluator evaluator, String sheetName, |
| | | String roadCategory, int startColumn, int endColumn, String holidayType, |
| | | Integer comparisonBaseColumn) { |
| | | Sheet sheet = workbook.getSheet(sheetName); |
| | | if (sheet == null) return; |
| | | for (int year : REFERENCE_YEARS) { |
| | | Integer yearColumn = findYearColumn(sheet, evaluator, year, startColumn, endColumn); |
| | | if (yearColumn == null) continue; |
| | | Map<Integer, LocalDate> dayDates = dayDates(year, holidayType); |
| | | Map<LocalDate, BigDecimal> values = loadVehicleValues(year, roadCategory); |
| | | for (Map.Entry<Integer, LocalDate> day : dayDates.entrySet()) { |
| | | if (!values.containsKey(day.getValue())) continue; |
| | | setNumeric(sheet, FIRST_DATA_ROW_INDEX + day.getKey() - 1, yearColumn, values.get(day.getValue())); |
| | | } |
| | | if (comparisonBaseColumn != null && HolidayConstants.supportsComparisonBase(year)) { |
| | | Map<LocalDate, BigDecimal> bases = loadComparisonBaseValues(year); |
| | | for (Map.Entry<Integer, LocalDate> day : dayDates.entrySet()) { |
| | | if (!bases.containsKey(day.getValue())) continue; |
| | | setNumeric(sheet, FIRST_DATA_ROW_INDEX + day.getKey() - 1, comparisonBaseColumn, bases.get(day.getValue())); |
| | | } |
| | | for (Map.Entry<Integer, LocalDate> day : dayDates.entrySet()) { |
| | | if (!bases.containsKey(day.getValue())) continue; |
| | | int rowIndex = FIRST_DATA_ROW_INDEX + day.getKey() - 1; |
| | | int excelRow = rowIndex + 1; |
| | | setFormula(sheet, rowIndex, 4, "=(D" + excelRow + "-C" + excelRow + ")/C" + excelRow); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void fillCoefficients(XSSFWorkbook workbook, FormulaEvaluator evaluator, int selectedYear, String holidayType) { |
| | | fillCoefficientSheet(workbook, evaluator, "é«éå
¬è·¯", 8, 10, holidayType); |
| | | fillCoefficientSheet(workbook, evaluator, "æ®éå½çé", 9, 11, holidayType); |
| | | } |
| | | |
| | | private void fillCoefficientSheet(XSSFWorkbook workbook, FormulaEvaluator evaluator, String sheetName, |
| | | int startColumn, int endColumn, String holidayType) { |
| | | Sheet sheet = workbook.getSheet(sheetName); |
| | | if (sheet == null) return; |
| | | for (int year : REFERENCE_YEARS) { |
| | | Integer yearColumn = findYearColumn(sheet, evaluator, year, startColumn, endColumn); |
| | | if (yearColumn == null) continue; |
| | | BigDecimal coefficient = configService.coefficientOf(year, HolidayConstants.CATEGORY_HOLIDAY); |
| | | if (coefficient == null) continue; |
| | | Map<Integer, LocalDate> dayDates = dayDates(year, holidayType); |
| | | for (Integer dayIndex : dayDates.keySet()) { |
| | | setNumeric(sheet, FIRST_DATA_ROW_INDEX + dayIndex - 1, yearColumn, coefficient); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void seedNationalDayCalendar() { |
| | | // Calendar is admin-managed after the first bootstrap. Re-adding missing day indexes on |
| | | // every start would resurrect days the admin deleted, so only seed a fresh table. |
| | | if (!configService.listHolidays(null, null).isEmpty()) { |
| | | return; |
| | | } |
| | | int[] lengths = {7, 8, 8}; |
| | | for (int i = 0; i < REFERENCE_YEARS.length; i++) { |
| | | int year = REFERENCE_YEARS[i]; |
| | | List<HolidayCalendar> existing = configService.findHolidayDays(year, HolidayConstants.TYPE_NATIONAL_DAY); |
| | | Map<Integer, HolidayCalendar> byDay = new LinkedHashMap<>(); |
| | | for (HolidayCalendar row : existing) { |
| | | byDay.put(row.getDayIndex(), row); |
| | | } |
| | | for (int day = 1; day <= lengths[i]; day++) { |
| | | if (byDay.containsKey(day)) continue; |
| | | HolidayCalendar calendar = new HolidayCalendar(); |
| | | calendar.setYear(year); |
| | | calendar.setHolidayType(HolidayConstants.TYPE_NATIONAL_DAY); |
| | | calendar.setHolidayDate(LocalDate.of(year, 10, day)); |
| | | calendar.setDayIndex(day); |
| | | calendar.setCreatedAt(LocalDateTime.now()); |
| | | calendar.setUpdatedAt(LocalDateTime.now()); |
| | | configService.saveHoliday(toMap(calendar)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void seedPassengerYear(Workbook workbook, FormulaEvaluator evaluator, int year) { |
| | | Map<Integer, LocalDate> dayDates = dayDates(year, HolidayConstants.TYPE_NATIONAL_DAY); |
| | | if (dayDates.isEmpty()) return; |
| | | for (Map.Entry<String, String> entry : PASSENGER_SHEET_MODE.entrySet()) { |
| | | Sheet sheet = workbook.getSheet(entry.getKey()); |
| | | if (sheet == null) continue; |
| | | Integer yearColumn = findYearColumn(sheet, evaluator, year, 2, 7); |
| | | if (yearColumn == null) continue; |
| | | for (Map.Entry<Integer, LocalDate> day : dayDates.entrySet()) { |
| | | Row row = sheet.getRow(FIRST_DATA_ROW_INDEX + day.getKey() - 1); |
| | | if (row == null) continue; |
| | | BigDecimal value = numberValue(row.getCell(yearColumn), evaluator); |
| | | if (value != null) { |
| | | upsertPassenger(year, day.getValue(), entry.getValue(), value); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void seedVehicleYear(Workbook workbook, FormulaEvaluator evaluator, int year) { |
| | | Map<Integer, LocalDate> dayDates = dayDates(year, HolidayConstants.TYPE_NATIONAL_DAY); |
| | | if (dayDates.isEmpty()) return; |
| | | seedVehicleSheet(workbook, evaluator, year, dayDates, "é«éå
¬è·¯", |
| | | HolidayConstants.ROAD_EXPRESSWAY, HolidayConstants.VEHICLE_SMALL_PASSENGER, 2, 7); |
| | | seedVehicleSheet(workbook, evaluator, year, dayDates, "æ®éå½çé", |
| | | HolidayConstants.ROAD_NATIONAL_PROVINCIAL, HolidayConstants.VEHICLE_MEDIUM_SMALL_PASSENGER, 3, 8); |
| | | } |
| | | |
| | | private void seedVehicleSheet(Workbook workbook, FormulaEvaluator evaluator, int year, |
| | | Map<Integer, LocalDate> dayDates, String sheetName, |
| | | String roadCategory, String vehicleType, int startColumn, int endColumn) { |
| | | Sheet sheet = workbook.getSheet(sheetName); |
| | | if (sheet == null) return; |
| | | Integer yearColumn = findYearColumn(sheet, evaluator, year, startColumn, endColumn); |
| | | if (yearColumn == null) return; |
| | | for (Map.Entry<Integer, LocalDate> day : dayDates.entrySet()) { |
| | | Row row = sheet.getRow(FIRST_DATA_ROW_INDEX + day.getKey() - 1); |
| | | if (row == null) continue; |
| | | BigDecimal value = numberValue(row.getCell(yearColumn), evaluator); |
| | | if (value != null) { |
| | | upsertVehicle(year, day.getValue(), roadCategory, vehicleType, value, null); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void seedCoefficientYear(Workbook workbook, FormulaEvaluator evaluator, int year) { |
| | | List<BigDecimal> values = new ArrayList<>(); |
| | | collectCoefficientValues(workbook, evaluator, "é«éå
¬è·¯", year, 8, 10, values); |
| | | collectCoefficientValues(workbook, evaluator, "æ®éå½çé", year, 9, 11, values); |
| | | if (values.isEmpty()) return; |
| | | BigDecimal sum = BigDecimal.ZERO; |
| | | for (BigDecimal value : values) { |
| | | sum = HolidayNumberUtils.normalize(sum.add(value)); |
| | | } |
| | | BigDecimal coefficient = HolidayNumberUtils.divide(sum, BigDecimal.valueOf(values.size())); |
| | | upsertCoefficient(year, HolidayConstants.CATEGORY_HOLIDAY, coefficient); |
| | | upsertCoefficient(year, HolidayConstants.CATEGORY_NON_HOLIDAY, coefficient); |
| | | } |
| | | |
| | | private void collectCoefficientValues(Workbook workbook, FormulaEvaluator evaluator, String sheetName, |
| | | int year, int startColumn, int endColumn, List<BigDecimal> values) { |
| | | Sheet sheet = workbook.getSheet(sheetName); |
| | | if (sheet == null) return; |
| | | Integer yearColumn = findYearColumn(sheet, evaluator, year, startColumn, endColumn); |
| | | if (yearColumn == null) return; |
| | | for (int rowIndex = FIRST_DATA_ROW_INDEX; rowIndex < FIRST_DATA_ROW_INDEX + 10; rowIndex++) { |
| | | Row row = sheet.getRow(rowIndex); |
| | | if (row == null) continue; |
| | | try { |
| | | BigDecimal value = numberValue(row.getCell(yearColumn), evaluator); |
| | | if (value != null && value.compareTo(BigDecimal.ZERO) > 0) values.add(value); |
| | | } catch (Exception ignored) { |
| | | // Skip cells that are not numeric coefficients. |
| | | } |
| | | } |
| | | } |
| | | |
| | | private Map<Integer, LocalDate> dayDates(int year, String holidayType) { |
| | | Map<Integer, LocalDate> result = new LinkedHashMap<>(); |
| | | for (HolidayCalendar row : configService.findHolidayDays(year, holidayType)) { |
| | | result.put(row.getDayIndex(), row.getHolidayDate()); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private Map<LocalDate, BigDecimal> loadPassengerValues(int year, String transportMode) { |
| | | Map<LocalDate, BigDecimal> result = new LinkedHashMap<>(); |
| | | List<HolidayPassengerFlow> rows = passengerFlowMapper.selectList( |
| | | new LambdaQueryWrapper<HolidayPassengerFlow>() |
| | | .eq(HolidayPassengerFlow::getYear, year) |
| | | .eq(HolidayPassengerFlow::getTransportMode, transportMode)); |
| | | for (HolidayPassengerFlow row : rows) result.put(row.getFlowDate(), row.getPassengerCount()); |
| | | return result; |
| | | } |
| | | |
| | | private Map<LocalDate, BigDecimal> loadVehicleValues(int year, String roadCategory) { |
| | | Map<LocalDate, BigDecimal> result = new LinkedHashMap<>(); |
| | | List<HolidayVehicleFlow> rows = vehicleFlowMapper.selectList( |
| | | new LambdaQueryWrapper<HolidayVehicleFlow>() |
| | | .eq(HolidayVehicleFlow::getYear, year) |
| | | .eq(HolidayVehicleFlow::getRoadCategory, roadCategory)); |
| | | for (HolidayVehicleFlow row : rows) result.put(row.getFlowDate(), row.getVehicleCount()); |
| | | return result; |
| | | } |
| | | |
| | | private Map<LocalDate, BigDecimal> loadComparisonBaseValues(int year) { |
| | | Map<LocalDate, BigDecimal> result = new LinkedHashMap<>(); |
| | | List<HolidayVehicleFlow> rows = vehicleFlowMapper.selectList( |
| | | new LambdaQueryWrapper<HolidayVehicleFlow>() |
| | | .eq(HolidayVehicleFlow::getYear, year) |
| | | .eq(HolidayVehicleFlow::getRoadCategory, HolidayConstants.ROAD_NATIONAL_PROVINCIAL)); |
| | | for (HolidayVehicleFlow row : rows) { |
| | | if (row.getComparisonBase() != null) result.put(row.getFlowDate(), row.getComparisonBase()); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private void upsertPassenger(int year, LocalDate date, String mode, BigDecimal value) { |
| | | HolidayPassengerFlow existing = passengerFlowMapper.selectOne(new LambdaQueryWrapper<HolidayPassengerFlow>() |
| | | .eq(HolidayPassengerFlow::getYear, year) |
| | | .eq(HolidayPassengerFlow::getFlowDate, date) |
| | | .eq(HolidayPassengerFlow::getTransportMode, mode) |
| | | .last("limit 1")); |
| | | if (existing == null) { |
| | | existing = new HolidayPassengerFlow(); |
| | | existing.setYear(year); |
| | | existing.setFlowDate(date); |
| | | existing.setTransportMode(mode); |
| | | existing.setPassengerCount(value); |
| | | existing.setCreatedAt(LocalDateTime.now()); |
| | | existing.setUpdatedAt(LocalDateTime.now()); |
| | | passengerFlowMapper.insert(existing); |
| | | } else { |
| | | existing.setPassengerCount(value); |
| | | existing.setUpdatedAt(LocalDateTime.now()); |
| | | passengerFlowMapper.updateById(existing); |
| | | } |
| | | } |
| | | |
| | | private void upsertVehicle(int year, LocalDate date, String roadCategory, String vehicleType, |
| | | BigDecimal value, BigDecimal comparisonBase) { |
| | | HolidayVehicleFlow existing = vehicleFlowMapper.selectOne(new LambdaQueryWrapper<HolidayVehicleFlow>() |
| | | .eq(HolidayVehicleFlow::getYear, year) |
| | | .eq(HolidayVehicleFlow::getFlowDate, date) |
| | | .eq(HolidayVehicleFlow::getRoadCategory, roadCategory) |
| | | .eq(HolidayVehicleFlow::getVehicleType, vehicleType) |
| | | .last("limit 1")); |
| | | if (existing == null) { |
| | | existing = new HolidayVehicleFlow(); |
| | | existing.setYear(year); |
| | | existing.setFlowDate(date); |
| | | existing.setRoadCategory(roadCategory); |
| | | existing.setVehicleType(vehicleType); |
| | | existing.setVehicleCount(value); |
| | | existing.setComparisonBase(comparisonBase); |
| | | existing.setCreatedAt(LocalDateTime.now()); |
| | | existing.setUpdatedAt(LocalDateTime.now()); |
| | | vehicleFlowMapper.insert(existing); |
| | | } else { |
| | | existing.setVehicleCount(value); |
| | | existing.setComparisonBase(comparisonBase); |
| | | existing.setUpdatedAt(LocalDateTime.now()); |
| | | vehicleFlowMapper.updateById(existing); |
| | | } |
| | | } |
| | | |
| | | private void upsertCoefficient(int year, String dateCategory, BigDecimal value) { |
| | | HolidayPassengerCoefficient existing = coefficientMapper.selectOne( |
| | | new LambdaQueryWrapper<HolidayPassengerCoefficient>() |
| | | .eq(HolidayPassengerCoefficient::getYear, year) |
| | | .eq(HolidayPassengerCoefficient::getDateCategory, dateCategory) |
| | | .last("limit 1")); |
| | | if (existing == null) { |
| | | existing = new HolidayPassengerCoefficient(); |
| | | existing.setYear(year); |
| | | existing.setDateCategory(dateCategory); |
| | | existing.setCoefficient(value); |
| | | existing.setCreatedAt(LocalDateTime.now()); |
| | | existing.setUpdatedAt(LocalDateTime.now()); |
| | | coefficientMapper.insert(existing); |
| | | } else { |
| | | existing.setCoefficient(value); |
| | | existing.setUpdatedAt(LocalDateTime.now()); |
| | | coefficientMapper.updateById(existing); |
| | | } |
| | | } |
| | | |
| | | private Integer findYearColumn(Sheet sheet, FormulaEvaluator evaluator, int year, int startColumn, int endColumn) { |
| | | Row header = sheet.getRow(1); |
| | | if (header == null) return null; |
| | | String wanted = year + "å¹´"; |
| | | DataFormatter formatter = new DataFormatter(); |
| | | for (int column = startColumn; column <= endColumn; column++) { |
| | | Cell cell = header.getCell(column); |
| | | if (cell == null) continue; |
| | | String text = formatter.formatCellValue(cell, evaluator).replace("\n", "").trim(); |
| | | if (wanted.equals(text) || text.startsWith(wanted + "累计")) return column; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private Integer parseDayIndex(Cell cell, FormulaEvaluator evaluator) { |
| | | if (cell == null) return null; |
| | | DataFormatter formatter = new DataFormatter(); |
| | | String text = formatter.formatCellValue(cell, evaluator).trim(); |
| | | java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("第\\s*(\\d+)\\s*æ¥").matcher(text); |
| | | return matcher.find() ? Integer.valueOf(matcher.group(1)) : null; |
| | | } |
| | | |
| | | private BigDecimal numberValue(Cell cell, FormulaEvaluator evaluator) { |
| | | if (cell == null) return null; |
| | | CellType type = cell.getCellType(); |
| | | if (type == CellType.FORMULA) { |
| | | type = evaluator.evaluateFormulaCell(cell); |
| | | } |
| | | if (type == CellType.BLANK) return null; |
| | | if (type == CellType.NUMERIC) return HolidayNumberUtils.excelValue(cell.getNumericCellValue()); |
| | | if (type == CellType.STRING) { |
| | | String text = cell.getStringCellValue().trim().replace(",", ""); |
| | | if (text.isEmpty() || "-".equals(text) || "â".equals(text)) return null; |
| | | try { |
| | | return HolidayNumberUtils.decimalValue(text); |
| | | } catch (NumberFormatException e) { |
| | | throw new IllegalArgumentException("åå
æ ¼ä¸æ¯æææ°å: " + text); |
| | | } |
| | | } |
| | | if (type == CellType.BOOLEAN) throw new IllegalArgumentException("åå
æ ¼ä¸è½æ¯å¸å°å¼"); |
| | | throw new IllegalArgumentException("åå
æ ¼ä¸æ¯æææ°å"); |
| | | } |
| | | |
| | | private void setNumeric(Sheet sheet, int rowIndex, int columnIndex, BigDecimal value) { |
| | | if (value == null) return; |
| | | Row row = sheet.getRow(rowIndex); |
| | | if (row == null) row = sheet.createRow(rowIndex); |
| | | Cell cell = row.getCell(columnIndex); |
| | | if (cell == null) cell = row.createCell(columnIndex); |
| | | cell.setCellValue(value.doubleValue()); |
| | | } |
| | | |
| | | private void setFormula(Sheet sheet, int rowIndex, int columnIndex, String formula) { |
| | | Row row = sheet.getRow(rowIndex); |
| | | if (row == null) row = sheet.createRow(rowIndex); |
| | | Cell cell = row.getCell(columnIndex); |
| | | if (cell == null) cell = row.createCell(columnIndex); |
| | | cell.setCellFormula(formula); |
| | | } |
| | | |
| | | private Map<String, Object> toMap(HolidayCalendar calendar) { |
| | | Map<String, Object> params = new LinkedHashMap<>(); |
| | | params.put("year", calendar.getYear()); |
| | | params.put("holidayType", calendar.getHolidayType()); |
| | | params.put("holidayDate", calendar.getHolidayDate().toString()); |
| | | params.put("dayIndex", calendar.getDayIndex()); |
| | | return params; |
| | | } |
| | | |
| | | @Data |
| | | public static class ImportSummary { |
| | | private int passengerRows; |
| | | private int vehicleRows; |
| | | private int skippedRows; |
| | | private boolean confirmationRequired; |
| | | private final List<String> errors = new ArrayList<>(); |
| | | private final List<String> historicalChanges = new ArrayList<>(); |
| | | |
| | | public void incrementPassenger() { |
| | | passengerRows++; |
| | | } |
| | | |
| | | public void incrementVehicle() { |
| | | vehicleRows++; |
| | | } |
| | | |
| | | public void incrementSkipped() { |
| | | skippedRows++; |
| | | } |
| | | |
| | | public void addError(String message) { |
| | | errors.add(message); |
| | | } |
| | | |
| | | public void addHistoricalChange(String message) { |
| | | if (historicalChanges.size() < 200) { |
| | | historicalChanges.add(message); |
| | | } |
| | | } |
| | | |
| | | public int getSuccessRows() { |
| | | return passengerRows + vehicleRows; |
| | | } |
| | | |
| | | public int getFailRows() { |
| | | return errors.size(); |
| | | } |
| | | } |
| | | } |
| | |
| | | Double aboveFreight = null; |
| | | Map<Integer, Double> h2032Buckets = h2032TonByCityMonth.get(city); |
| | | if (h2032Buckets != null) { |
| | | double ton; |
| | | if (cumulative) { |
| | | ton = 0.0; |
| | | double ton = 0.0; |
| | | for (int m = 1; m <= month; m++) ton += h2032Buckets.getOrDefault(m, 0.0); |
| | | aboveFreight = ton / 10000.0; |
| | | } else { |
| | | ton = h2032Buckets.getOrDefault(month, 0.0); |
| | | // 彿æ è§ä¸è®°å½ -> ç空ï¼ä¸åå 0ï¼ï¼æè®°å½ï¼å«çå® 0 å¼ï¼æåæ° |
| | | Double ton = h2032Buckets.get(month); |
| | | aboveFreight = ton == null ? null : ton / 10000.0; |
| | | } |
| | | aboveFreight = ton / 10000.0; |
| | | } |
| | | |
| | | FreightTurnoverImport ft = ftMap.get(city); |
| | |
| | | |
| | | // å
¨å±ç»åºï¼æ¸
é¤ç»å½æå¹¶åç»å½é¡µï¼å页é¢ä¾§è¾¹æ ãéåºç»å½ãèåå
±ç¨ï¼ |
| | | Vue.mixin({ |
| | | computed: { |
| | | operatorIsAdmin() { |
| | | return sessionStorage.getItem('username') === 'admin' |
| | | } |
| | | }, |
| | | methods: { |
| | | // el-menu åè·¯ç±å¯¼èªï¼NavigationDuplicatedï¼æ¶ Element UI 伿é«äº®åæ»ä¸ºç¹å»åæ§å¼ï¼ |
| | | // ç¹å»åå»¶è¿ä¸ä¸ªå®ä»»å¡å¼ºå¶çº æ£ï¼å®ä»»å¡æäº push ç微任å¡åè°ï¼ä¿è¯æç»é«äº®æ£ç¡®ï¼ã |
| | |
| | | { path: '/audit', name: 'AuditResult', component: () => import('@/views/AuditResult.vue') }, |
| | | { path: '/report', name: 'ReportExport', component: () => import('@/views/ReportExport.vue') }, |
| | | { path: '/rules', name: 'RuleManage', component: () => import('@/views/RuleManage.vue') }, |
| | | { path: '/users', name: 'UserManage', component: () => import('@/views/UserManage.vue') } |
| | | { path: '/users', name: 'UserManage', component: () => import('@/views/UserManage.vue') }, |
| | | { path: '/holiday-stat', name: 'HolidayStat', component: () => import('@/views/HolidayStat.vue') }, |
| | | { path: '/system-config', name: 'SystemConfig', component: () => import('@/views/SystemConfig.vue'), meta: { requireAdmin: true } } |
| | | ] |
| | | }) |
| | | |
| | |
| | | const token = sessionStorage.getItem('token') |
| | | if (to.path === '/login') { |
| | | next(token ? '/dashboard' : undefined) |
| | | } else if (!token) { |
| | | next('/login') |
| | | } else if (to.meta && to.meta.requireAdmin && sessionStorage.getItem('username') !== 'admin') { |
| | | next('/dashboard') |
| | | } else { |
| | | next(token ? undefined : '/login') |
| | | next() |
| | | } |
| | | }) |
| | | |
| | |
| | | <el-menu-item index="/explain">ä¼ä¸è§£é</el-menu-item> |
| | | <el-menu-item index="/audit">å®¡æ ¸ç»æ</el-menu-item> |
| | | <el-menu-item index="/report">æ¥è¡¨çæ</el-menu-item> |
| | | <el-submenu index="/special-stats"> |
| | | <template slot="title">ä¸é¡¹ç»è®¡</template> |
| | | <el-menu-item index="/holiday-stat">è忥</el-menu-item> |
| | | </el-submenu> |
| | | <el-menu-item index="/rules">è§å管ç</el-menu-item> |
| | | <el-menu-item index="/users">ç¨æ·ç®¡ç</el-menu-item> |
| | | </el-menu> |
| | | <el-menu-item v-if="operatorIsAdmin" index="/system-config">ç³»ç»é
ç½®</el-menu-item> |
| | | </el-menu> |
| | | </el-aside> |
| | | <el-main :class="{ 'main-focus': focusMode }" style="display:flex;flex-direction:column;min-height:0"> |
| | | <h3 v-show="!focusMode" style="flex:none;margin:0 0 10px">å®¡æ ¸ç»æ</h3> |
| | |
| | | <el-menu-item index="/explain">ä¼ä¸è§£é</el-menu-item> |
| | | <el-menu-item index="/audit">å®¡æ ¸ç»æ</el-menu-item> |
| | | <el-menu-item index="/report">æ¥è¡¨çæ</el-menu-item> |
| | | <el-submenu index="/special-stats"> |
| | | <template slot="title">ä¸é¡¹ç»è®¡</template> |
| | | <el-menu-item index="/holiday-stat">è忥</el-menu-item> |
| | | </el-submenu> |
| | | <el-menu-item index="/rules">è§å管ç</el-menu-item> |
| | | <el-menu-item index="/users">ç¨æ·ç®¡ç</el-menu-item></el-menu> |
| | | <el-menu-item index="/users">ç¨æ·ç®¡ç</el-menu-item> |
| | | <el-menu-item v-if="operatorIsAdmin" index="/system-config">ç³»ç»é
ç½®</el-menu-item> |
| | | </el-menu> |
| | | </el-aside> |
| | | <el-main> |
| | | <div class="page-scroll"> |
| | |
| | | <el-menu-item index="/explain">ä¼ä¸è§£é</el-menu-item> |
| | | <el-menu-item index="/audit">å®¡æ ¸ç»æ</el-menu-item> |
| | | <el-menu-item index="/report">æ¥è¡¨çæ</el-menu-item> |
| | | <el-submenu index="/special-stats"> |
| | | <template slot="title">ä¸é¡¹ç»è®¡</template> |
| | | <el-menu-item index="/holiday-stat">è忥</el-menu-item> |
| | | </el-submenu> |
| | | <el-menu-item index="/rules">è§å管ç</el-menu-item> |
| | | <el-menu-item index="/users">ç¨æ·ç®¡ç</el-menu-item> |
| | | </el-menu> |
| | | <el-menu-item v-if="operatorIsAdmin" index="/system-config">ç³»ç»é
ç½®</el-menu-item> |
| | | </el-menu> |
| | | </el-aside> |
| | | <el-main> |
| | | <div class="page-scroll"> |
| | |
| | | <el-menu-item index="/explain">ä¼ä¸è§£é</el-menu-item> |
| | | <el-menu-item index="/audit">å®¡æ ¸ç»æ</el-menu-item> |
| | | <el-menu-item index="/report">æ¥è¡¨çæ</el-menu-item> |
| | | <el-submenu index="/special-stats"> |
| | | <template slot="title">ä¸é¡¹ç»è®¡</template> |
| | | <el-menu-item index="/holiday-stat">è忥</el-menu-item> |
| | | </el-submenu> |
| | | <el-menu-item index="/rules">è§å管ç</el-menu-item> |
| | | <el-menu-item index="/users">ç¨æ·ç®¡ç</el-menu-item> |
| | | </el-menu> |
| | | <el-menu-item v-if="operatorIsAdmin" index="/system-config">ç³»ç»é
ç½®</el-menu-item> |
| | | </el-menu> |
| | | </el-aside> |
| | | <el-main> |
| | | <h3>导å
¥æ°æ®æ¥ç</h3> |
| | |
| | | <el-menu-item index="/explain">ä¼ä¸è§£é</el-menu-item> |
| | | <el-menu-item index="/audit">å®¡æ ¸ç»æ</el-menu-item> |
| | | <el-menu-item index="/report">æ¥è¡¨çæ</el-menu-item> |
| | | <el-submenu index="/special-stats"> |
| | | <template slot="title">ä¸é¡¹ç»è®¡</template> |
| | | <el-menu-item index="/holiday-stat">è忥</el-menu-item> |
| | | </el-submenu> |
| | | <el-menu-item index="/rules">è§å管ç</el-menu-item> |
| | | <el-menu-item index="/users">ç¨æ·ç®¡ç</el-menu-item> |
| | | </el-menu> |
| | | <el-menu-item v-if="operatorIsAdmin" index="/system-config">ç³»ç»é
ç½®</el-menu-item> |
| | | </el-menu> |
| | | </el-aside> |
| | | <el-main> |
| | | <h3>ä¼ä¸è§£é</h3> |
| New file |
| | |
| | | <template> |
| | | <el-container direction="vertical"> |
| | | <app-header title="交éç»è®¡æ¥è¡¨å®¡æ ¸ç³»ç» - ä¸é¡¹ç»è®¡"></app-header> |
| | | <el-container> |
| | | <el-aside width="200px"> |
| | | <el-menu ref="sideMenu" router :default-active="$route.path" @select="onMenuSelect"> |
| | | <el-menu-item index="/dashboard">å·¥ä½å°</el-menu-item> |
| | | <el-menu-item index="/import">æ°æ®å¯¼å
¥</el-menu-item> |
| | | <el-menu-item index="/data">æ°æ®æ¥ç</el-menu-item> |
| | | <el-menu-item index="/explain">ä¼ä¸è§£é</el-menu-item> |
| | | <el-menu-item index="/audit">å®¡æ ¸ç»æ</el-menu-item> |
| | | <el-menu-item index="/report">æ¥è¡¨çæ</el-menu-item> |
| | | <el-submenu index="/special-stats"> |
| | | <template slot="title">ä¸é¡¹ç»è®¡</template> |
| | | <el-menu-item index="/holiday-stat">è忥</el-menu-item> |
| | | </el-submenu> |
| | | <el-menu-item index="/rules">è§å管ç</el-menu-item> |
| | | <el-menu-item index="/users">ç¨æ·ç®¡ç</el-menu-item> |
| | | <el-menu-item v-if="operatorIsAdmin" index="/system-config">ç³»ç»é
ç½®</el-menu-item> |
| | | </el-menu> |
| | | </el-aside> |
| | | <el-main> |
| | | <div class="page-scroll"> |
| | | <h3>ä¸é¡¹ç»è®¡ - è忥</h3> |
| | | <div class="toolbar"> |
| | | <el-select v-model="year" style="width:120px" @change="loadOverview"> |
| | | <el-option v-for="item in years" :key="item" :label="item + 'å¹´'" :value="item"></el-option> |
| | | </el-select> |
| | | <el-select v-model="holidayType" style="width:150px" @change="loadOverview"> |
| | | <el-option v-for="item in holidayTypeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option> |
| | | </el-select> |
| | | <el-button icon="el-icon-refresh" @click="loadOverview">å·æ°</el-button> |
| | | <el-button icon="el-icon-download" @click="downloadTemplate">ä¸è½½æ¨¡æ¿</el-button> |
| | | </div> |
| | | |
| | | <el-card shadow="never" class="operation-card"> |
| | | <div class="operation-row"> |
| | | <div class="operation-item"> |
| | | <span class="label">导å
¥æ°æ®</span> |
| | | <el-upload action="#" :auto-upload="false" :show-file-list="true" :limit="1" |
| | | accept=".xlsx,.xls" :on-change="onFileChange" :on-remove="onFileRemove" :file-list="fileList"> |
| | | <el-button type="primary" plain icon="el-icon-folder-opened">éæ©æä»¶</el-button> |
| | | </el-upload> |
| | | <el-button type="primary" icon="el-icon-upload2" :loading="importing" :disabled="!selectedFile" @click="importData">导å
¥</el-button> |
| | | </div> |
| | | <div class="operation-item"> |
| | | <span class="label">å¯¼åºæ°æ®</span> |
| | | <el-button type="success" icon="el-icon-download" :loading="exporting" @click="exportData">å¯¼åº Excel</el-button> |
| | | </div> |
| | | <div class="operation-item"> |
| | | <span class="label">客æµå¿«æ¥</span> |
| | | <el-date-picker v-model="bulletinDate" type="date" value-format="yyyy-MM-dd" style="width:160px" placeholder="éæ©æ¥æ"></el-date-picker> |
| | | <el-button type="warning" icon="el-icon-document" :loading="bulletinLoading" @click="downloadBulletin">ä¸è½½å¿«æ¥</el-button> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <div v-if="importMessage" class="import-message"> |
| | | <pre>{{ importMessage }}</pre> |
| | | </div> |
| | | |
| | | <el-card shadow="never" style="margin-top:14px"> |
| | | <div slot="header">{{ year }}å¹´{{ holidayTypeLabel }}èåæ¥æ°æ®</div> |
| | | <el-table :data="rows" border stripe size="small" v-loading="loading" style="width:100%"> |
| | | <el-table-column prop="dayIndex" label="ç¬¬å æ¥" width="80" align="center"></el-table-column> |
| | | <el-table-column prop="date" label="æ¥æ" width="120" align="center"></el-table-column> |
| | | <el-table-column label="éè·¯ï¼ä¸äººæ¬¡ï¼" width="120" align="right"><template slot-scope="s">{{ formatNumber(s.row.rail) }}</template></el-table-column> |
| | | <el-table-column label="æ°èªï¼ä¸äººæ¬¡ï¼" width="120" align="right"><template slot-scope="s">{{ formatNumber(s.row.civilAviation) }}</template></el-table-column> |
| | | <el-table-column label="éè·¯ï¼ä¸äººæ¬¡ï¼" width="120" align="right"><template slot-scope="s">{{ formatNumber(s.row.road) }}</template></el-table-column> |
| | | <el-table-column label="æ°´è·¯ï¼ä¸äººæ¬¡ï¼" width="120" align="right"><template slot-scope="s">{{ formatNumber(s.row.water) }}</template></el-table-column> |
| | | <el-table-column label="é«é车æµï¼ä¸è¾æ¬¡ï¼" width="150" align="right"><template slot-scope="s">{{ formatNumber(s.row.expressway) }}</template></el-table-column> |
| | | <el-table-column label="å½çé车æµï¼ä¸è¾æ¬¡ï¼" width="160" align="right"><template slot-scope="s">{{ formatNumber(s.row.nationalProvincial) }}</template></el-table-column> |
| | | <el-table-column label="å½çéç¯æ¯åºæï¼ä¸è¾æ¬¡ï¼" width="190" align="right"><template slot-scope="s">{{ formatNumber(s.row.comparisonBase) }}</template></el-table-column> |
| | | <el-table-column label="å½çé车æµç¯æ¯" width="140" align="right"><template slot-scope="s">{{ formatRatio(s.row.nationalProvincialRingRatio) }}</template></el-table-column> |
| | | <el-table-column label="客è¿ç³»æ°" width="100" align="right"><template slot-scope="s">{{ formatCoefficient(s.row.coefficient) }}</template></el-table-column> |
| | | <el-table-column label="éè¥ä¸æ§äººåæµå¨éï¼ä¸äººæ¬¡ï¼" min-width="200" align="right"><template slot-scope="s">{{ formatNumber(s.row.nonBusinessPersonFlow) }}</template></el-table-column> |
| | | <el-table-column label="æ»äººåæµå¨éï¼ä¸äººæ¬¡ï¼" min-width="180" align="right"><template slot-scope="s">{{ formatNumber(s.row.totalPersonFlow) }}</template></el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | </div> |
| | | </el-main> |
| | | </el-container> |
| | | </el-container> |
| | | </template> |
| | | |
| | | <script> |
| | | import api from '@/api' |
| | | import AppHeader from '@/components/AppHeader.vue' |
| | | |
| | | export default { |
| | | components: { AppHeader }, |
| | | name: 'HolidayStat', |
| | | data() { |
| | | return { |
| | | years: [2024, 2025, 2026, 2027, 2028, 2029, 2030], |
| | | year: 2026, |
| | | holidayType: 'NATIONAL_DAY', |
| | | holidayTypeOptions: [], |
| | | rows: [], |
| | | loading: false, |
| | | importing: false, |
| | | exporting: false, |
| | | bulletinLoading: false, |
| | | selectedFile: null, |
| | | fileList: [], |
| | | importMessage: '', |
| | | allowHistoricalOverwrite: false, |
| | | bulletinDate: this.previousDateString() |
| | | } |
| | | }, |
| | | computed: { |
| | | holidayTypeLabel() { |
| | | const item = this.holidayTypeOptions.find(x => x.value === this.holidayType) |
| | | return item ? item.label : this.holidayType |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.loadOptions() |
| | | this.loadOverview() |
| | | }, |
| | | methods: { |
| | | previousDateString() { |
| | | const date = new Date() |
| | | date.setDate(date.getDate() - 1) |
| | | const month = String(date.getMonth() + 1).padStart(2, '0') |
| | | const day = String(date.getDate()).padStart(2, '0') |
| | | return date.getFullYear() + '-' + month + '-' + day |
| | | }, |
| | | loadOptions() { |
| | | api.get('/holiday/config/options').then(res => { |
| | | const map = (res.data && res.data.holidayTypes) || {} |
| | | this.holidayTypeOptions = Object.keys(map).map(key => ({ value: key, label: map[key] })) |
| | | }) |
| | | }, |
| | | loadOverview() { |
| | | this.loading = true |
| | | api.get('/holiday/overview', { params: { year: this.year, holidayType: this.holidayType } }) |
| | | .then(res => { |
| | | if (res.code !== 200) throw new Error(res.message || 'å 载失败') |
| | | this.rows = (res.data && res.data.rows) || [] |
| | | }) |
| | | .catch(err => this.showError(err, 'èåæ¥æ°æ®å 载失败')) |
| | | .finally(() => { this.loading = false }) |
| | | }, |
| | | onFileChange(file) { |
| | | this.selectedFile = file.raw |
| | | this.fileList = [file] |
| | | this.importMessage = '' |
| | | }, |
| | | onFileRemove() { |
| | | this.selectedFile = null |
| | | this.fileList = [] |
| | | }, |
| | | importData(allowHistoricalOverwrite) { |
| | | if (!this.selectedFile) return |
| | | const allowOverwrite = allowHistoricalOverwrite === true || this.allowHistoricalOverwrite === true |
| | | const form = new FormData() |
| | | form.append('file', this.selectedFile) |
| | | form.append('year', this.year) |
| | | form.append('holidayType', this.holidayType) |
| | | form.append('allowHistoricalOverwrite', allowOverwrite ? 'true' : 'false') |
| | | this.importing = true |
| | | this.importMessage = '' |
| | | api.post('/holiday/import', form, { timeout: 120000, headers: { 'Content-Type': 'multipart/form-data' } }) |
| | | .then(res => { |
| | | if (res.code !== 200) throw new Error(res.message || '导å
¥å¤±è´¥') |
| | | const data = res.data || {} |
| | | if (data.confirmationRequired) { |
| | | const changes = data.historicalChanges || [] |
| | | this.importMessage = ['æ£æµå°å岿¥æçæ°æ®åçååï¼å°æªå¯¼å
¥ã请确认æ¯å¦å
许è¦çï¼'].concat(changes).join('\n') |
| | | this.$message.warning('æ£æµå°å岿°æ®åçååï¼è¯·ç¡®è®¤æ¯å¦å
许导å
¥') |
| | | this.confirmHistoricalImport(changes) |
| | | return |
| | | } |
| | | const lines = [ |
| | | '导å
¥å®æï¼å®¢æµ ' + (data.passengerRows || 0) + ' æ¡ï¼è½¦æµ ' + (data.vehicleRows || 0) + ' æ¡ï¼è·³è¿ç©ºç½ ' + (data.skippedRows || 0) + ' æ¡ã' |
| | | ] |
| | | if (data.errors && data.errors.length) { |
| | | lines.push('失败æç»ï¼') |
| | | data.errors.forEach(item => lines.push(item)) |
| | | } |
| | | this.importMessage = lines.join('\n') |
| | | this.allowHistoricalOverwrite = false |
| | | this.$message.success('导å
¥å®æ') |
| | | this.loadOverview() |
| | | }) |
| | | .catch(err => { |
| | | this.allowHistoricalOverwrite = false |
| | | this.showError(err, '导å
¥å¤±è´¥') |
| | | }) |
| | | .finally(() => { this.importing = false }) |
| | | }, |
| | | confirmHistoricalImport(changes) { |
| | | const rows = (changes && changes.length ? changes : ['å岿°æ®åçåå']).map(this.escapeHtml) |
| | | const html = '<div>æ£æµå°å·²å叿¥æçæ°æ®åçååãå
许导å
¥å°è¦çè¿äºå岿°æ®ã</div>' |
| | | + '<pre style="max-height:320px;overflow:auto;white-space:pre-wrap;margin-top:10px;background:#f7f7f7;padding:8px;border:1px solid #e4e7ed;">' |
| | | + rows.join('\n') + '</pre>' |
| | | this.$confirm(html, 'å岿°æ®åçåå', { |
| | | dangerouslyUseHTMLString: true, |
| | | confirmButtonText: 'å
许导å
¥', |
| | | cancelButtonText: 'ä¸å
许', |
| | | type: 'warning', |
| | | distinguishCancelAndClose: true |
| | | }).then(() => { |
| | | this.allowHistoricalOverwrite = true |
| | | this.importData(true) |
| | | }).catch(() => { |
| | | this.allowHistoricalOverwrite = false |
| | | }) |
| | | }, |
| | | escapeHtml(value) { |
| | | return String(value === null || value === undefined ? '' : value) |
| | | .replace(/&/g, '&') |
| | | .replace(/</g, '<') |
| | | .replace(/>/g, '>') |
| | | .replace(/"/g, '"') |
| | | .replace(/'/g, ''') |
| | | }, |
| | | exportData() { |
| | | this.exporting = true |
| | | api.get('/holiday/export', { params: { year: this.year, holidayType: this.holidayType }, responseType: 'blob', timeout: 120000 }) |
| | | .then(res => { this.downloadBlob(res, this.year + 'å¹´' + this.holidayTypeLabel + 'æ°æ®.xlsx'); this.$message.success('å¯¼åºæå') }) |
| | | .catch(err => this.handleBlobError(err, '导åºå¤±è´¥')) |
| | | .finally(() => { this.exporting = false }) |
| | | }, |
| | | downloadTemplate() { |
| | | const templatePath = 'ä¸é¡¹ç»è®¡/éè¦è忥/2026å¹´å䏿°æ®.xlsx' |
| | | const encodedPath = templatePath.split('/').map(encodeURIComponent).join('/') |
| | | api.get('/templates/' + encodedPath, { responseType: 'blob' }) |
| | | .then(res => { this.downloadBlob(res, '2026å¹´å䏿°æ®.xlsx'); this.$message.success('模æ¿ä¸è½½æå') }) |
| | | .catch(err => this.handleBlobError(err, '模æ¿ä¸è½½å¤±è´¥')) |
| | | }, |
| | | downloadBulletin() { |
| | | this.bulletinLoading = true |
| | | api.get('/holiday/bulletin', { params: { date: this.bulletinDate }, responseType: 'blob', timeout: 120000 }) |
| | | .then(res => { |
| | | const day = Number((this.bulletinDate || '').slice(-2)) |
| | | this.downloadBlob(res, 'æ¹åçå½åºåæç»¼åè¿è¾å®¢æµå¿«æ¥ï¼10æ' + day + 'æ¥ï¼.docx') |
| | | this.$message.success('å¿«æ¥ä¸è½½æå') |
| | | }) |
| | | .catch(err => this.handleBlobError(err, 'å¿«æ¥çæå¤±è´¥')) |
| | | .finally(() => { this.bulletinLoading = false }) |
| | | }, |
| | | downloadBlob(data, fileName) { |
| | | const blob = data instanceof Blob ? data : new Blob([data]) |
| | | const url = window.URL.createObjectURL(blob) |
| | | const link = document.createElement('a') |
| | | link.href = url |
| | | link.download = fileName |
| | | document.body.appendChild(link) |
| | | link.click() |
| | | document.body.removeChild(link) |
| | | window.URL.revokeObjectURL(url) |
| | | }, |
| | | handleBlobError(err, fallback) { |
| | | if (err && err.response && err.response.data instanceof Blob) { |
| | | err.response.data.text().then(text => { |
| | | try { |
| | | const parsed = JSON.parse(text) |
| | | this.$message.error(parsed.message || fallback) |
| | | } catch (e) { |
| | | this.$message.error(fallback) |
| | | } |
| | | }) |
| | | return |
| | | } |
| | | this.showError(err, fallback) |
| | | }, |
| | | showError(err, fallback) { |
| | | if (err && err._toast) return |
| | | const data = err && err.response && err.response.data |
| | | this.$message.error((data && data.message) || (err && err.message) || fallback) |
| | | }, |
| | | formatNumber(value) { |
| | | if (value === null || value === undefined || value === '') return 'â' |
| | | return Number(value).toFixed(2) |
| | | }, |
| | | formatCoefficient(value) { |
| | | if (value === null || value === undefined || value === '') return 'â' |
| | | const number = Number(value) |
| | | if (!Number.isFinite(number)) return value |
| | | return number.toFixed(15).replace(/0+$/, '').replace(/\.$/, '') |
| | | }, |
| | | formatRatio(value) { |
| | | if (value === null || value === undefined || value === '') return 'â' |
| | | const number = Number(value) |
| | | if (!Number.isFinite(number)) return value |
| | | return (number * 100).toFixed(2) + '%' |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .toolbar { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; } |
| | | .operation-card { margin-top: 12px; } |
| | | .operation-row { display: flex; gap: 36px; align-items: flex-start; flex-wrap: wrap; } |
| | | .operation-item { display: flex; align-items: center; gap: 8px; } |
| | | .operation-item .label { color: #606266; font-size: 14px; } |
| | | .import-message { margin-top: 12px; padding: 10px 12px; background: #f5f7fa; border: 1px solid #e4e7ed; border-radius: 4px; } |
| | | .import-message pre { margin: 0; white-space: pre-wrap; font-family: inherit; color: #606266; } |
| | | </style> |
| | |
| | | <el-menu-item index="/explain">ä¼ä¸è§£é</el-menu-item> |
| | | <el-menu-item index="/audit">å®¡æ ¸ç»æ</el-menu-item> |
| | | <el-menu-item index="/report">æ¥è¡¨çæ</el-menu-item> |
| | | <el-submenu index="/special-stats"> |
| | | <template slot="title">ä¸é¡¹ç»è®¡</template> |
| | | <el-menu-item index="/holiday-stat">è忥</el-menu-item> |
| | | </el-submenu> |
| | | <el-menu-item index="/rules">è§å管ç</el-menu-item> |
| | | <el-menu-item index="/users">ç¨æ·ç®¡ç</el-menu-item> |
| | | <el-menu-item v-if="operatorIsAdmin" index="/system-config">ç³»ç»é
ç½®</el-menu-item> |
| | | </el-menu> |
| | | </el-aside> |
| | | <el-main> |
| | |
| | | <el-menu-item index="/explain">ä¼ä¸è§£é</el-menu-item> |
| | | <el-menu-item index="/audit">å®¡æ ¸ç»æ</el-menu-item> |
| | | <el-menu-item index="/report">æ¥è¡¨çæ</el-menu-item> |
| | | <el-submenu index="/special-stats"> |
| | | <template slot="title">ä¸é¡¹ç»è®¡</template> |
| | | <el-menu-item index="/holiday-stat">è忥</el-menu-item> |
| | | </el-submenu> |
| | | <el-menu-item index="/rules">è§å管ç</el-menu-item> |
| | | <el-menu-item index="/users">ç¨æ·ç®¡ç</el-menu-item> |
| | | <el-menu-item v-if="operatorIsAdmin" index="/system-config">ç³»ç»é
ç½®</el-menu-item> |
| | | </el-menu> |
| | | </el-aside> |
| | | <el-main> |
| New file |
| | |
| | | <template> |
| | | <el-container direction="vertical"> |
| | | <app-header title="交éç»è®¡æ¥è¡¨å®¡æ ¸ç³»ç» - ç³»ç»é
ç½®"></app-header> |
| | | <el-container> |
| | | <el-aside width="200px"> |
| | | <el-menu ref="sideMenu" router :default-active="$route.path" @select="onMenuSelect"> |
| | | <el-menu-item index="/dashboard">å·¥ä½å°</el-menu-item> |
| | | <el-menu-item index="/import">æ°æ®å¯¼å
¥</el-menu-item> |
| | | <el-menu-item index="/data">æ°æ®æ¥ç</el-menu-item> |
| | | <el-menu-item index="/explain">ä¼ä¸è§£é</el-menu-item> |
| | | <el-menu-item index="/audit">å®¡æ ¸ç»æ</el-menu-item> |
| | | <el-menu-item index="/report">æ¥è¡¨çæ</el-menu-item> |
| | | <el-submenu index="/special-stats"> |
| | | <template slot="title">ä¸é¡¹ç»è®¡</template> |
| | | <el-menu-item index="/holiday-stat">è忥</el-menu-item> |
| | | </el-submenu> |
| | | <el-menu-item index="/rules">è§å管ç</el-menu-item> |
| | | <el-menu-item index="/users">ç¨æ·ç®¡ç</el-menu-item> |
| | | <el-menu-item v-if="operatorIsAdmin" index="/system-config">ç³»ç»é
ç½®</el-menu-item> |
| | | </el-menu> |
| | | </el-aside> |
| | | <el-main> |
| | | <div class="page-scroll"> |
| | | <h3>ç³»ç»é
ç½®</h3> |
| | | <el-tabs v-model="activeTab" @tab-click="loadActiveTab"> |
| | | <el-tab-pane label="èåæ¥ç®¡ç" name="holidays"> |
| | | <div class="toolbar"> |
| | | <el-select v-model="holidayQuery.year" clearable placeholder="年份" style="width:130px" @change="loadHolidays"> |
| | | <el-option v-for="year in years" :key="year" :label="year + 'å¹´'" :value="year"></el-option> |
| | | </el-select> |
| | | <el-select v-model="holidayQuery.holidayType" clearable placeholder="è忥åç±»" style="width:150px" @change="loadHolidays"> |
| | | <el-option v-for="item in holidayTypeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option> |
| | | </el-select> |
| | | <el-button type="primary" icon="el-icon-search" @click="loadHolidays">æ¥è¯¢</el-button> |
| | | <el-button type="success" icon="el-icon-plus" @click="openHolidayDialog()">æ°å¢è忥</el-button> |
| | | </div> |
| | | <el-table :data="holidays" border stripe size="small" v-loading="loadingHolidays" style="width:100%"> |
| | | <el-table-column prop="year" label="年份" width="100" align="center"></el-table-column> |
| | | <el-table-column label="è忥åç±»" width="140"> |
| | | <template slot-scope="scope">{{ labelOf(holidayTypeOptions, scope.row.holidayType) }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="holidayDate" label="æ¥æ" width="150" align="center"></el-table-column> |
| | | <el-table-column prop="dayIndex" label="ç¬¬å æ¥" width="100" align="center"></el-table-column> |
| | | <el-table-column label="æä½" width="150" align="center"> |
| | | <template slot-scope="scope"> |
| | | <el-button type="text" size="mini" @click="openHolidayDialog(scope.row)">ç¼è¾</el-button> |
| | | <el-button type="text" size="mini" class="danger-text" @click="removeHoliday(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-tab-pane> |
| | | |
| | | <el-tab-pane label="客è¿ç³»æ°ç®¡ç" name="coefficients"> |
| | | <div class="toolbar"> |
| | | <el-select v-model="coefficientQuery.year" clearable placeholder="年份" style="width:130px" @change="loadCoefficients"> |
| | | <el-option v-for="year in years" :key="year" :label="year + 'å¹´'" :value="year"></el-option> |
| | | </el-select> |
| | | <el-button type="primary" icon="el-icon-search" @click="loadCoefficients">æ¥è¯¢</el-button> |
| | | <el-button type="success" icon="el-icon-plus" @click="openCoefficientDialog()">æ°å¢å®¢è¿ç³»æ°</el-button> |
| | | </div> |
| | | <el-table :data="coefficients" border stripe size="small" v-loading="loadingCoefficients" style="width:100%"> |
| | | <el-table-column prop="year" label="年份" width="120" align="center"></el-table-column> |
| | | <el-table-column label="æ¥æåç±»" width="160"> |
| | | <template slot-scope="scope">{{ labelOf(dateCategoryOptions, scope.row.dateCategory) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="客è¿ç³»æ°" width="160" align="center"> |
| | | <template slot-scope="scope">{{ formatCoefficient(scope.row.coefficient) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="150" align="center"> |
| | | <template slot-scope="scope"> |
| | | <el-button type="text" size="mini" @click="openCoefficientDialog(scope.row)">ç¼è¾</el-button> |
| | | <el-button type="text" size="mini" class="danger-text" @click="removeCoefficient(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | </div> |
| | | |
| | | <el-dialog :title="holidayForm.id ? 'ç¼è¾è忥' : 'æ°å¢è忥'" :visible.sync="holidayDialog" width="520px" v-dialog-drag> |
| | | <el-form ref="holidayForm" :model="holidayForm" :rules="holidayRules" label-width="110px"> |
| | | <el-form-item label="年份" prop="year"> |
| | | <el-select v-model="holidayForm.year" style="width:100%" @change="onHolidayYearChange"> |
| | | <el-option v-for="year in years" :key="year" :label="year + 'å¹´'" :value="year"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="è忥åç±»" prop="holidayType"> |
| | | <el-select v-model="holidayForm.holidayType" style="width:100%"> |
| | | <el-option v-for="item in holidayTypeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æ¥æ" prop="holidayDate"> |
| | | <el-date-picker v-model="holidayForm.holidayDate" type="date" value-format="yyyy-MM-dd" |
| | | style="width:100%" placeholder="éæ©æ¥æ"></el-date-picker> |
| | | </el-form-item> |
| | | <el-form-item label="ç¬¬å æ¥" prop="dayIndex"> |
| | | <el-input-number v-model="holidayForm.dayIndex" :min="1" :max="31" style="width:100%"></el-input-number> |
| | | </el-form-item> |
| | | </el-form> |
| | | <span slot="footer"> |
| | | <el-button @click="holidayDialog = false">åæ¶</el-button> |
| | | <el-button type="primary" :loading="saving" @click="saveHoliday">ä¿å</el-button> |
| | | </span> |
| | | </el-dialog> |
| | | |
| | | <el-dialog :title="coefficientForm.id ? 'ç¼è¾å®¢è¿ç³»æ°' : 'æ°å¢å®¢è¿ç³»æ°'" :visible.sync="coefficientDialog" width="520px" v-dialog-drag> |
| | | <el-form ref="coefficientForm" :model="coefficientForm" :rules="coefficientRules" label-width="110px"> |
| | | <el-form-item label="年份" prop="year"> |
| | | <el-select v-model="coefficientForm.year" style="width:100%"> |
| | | <el-option v-for="year in years" :key="year" :label="year + 'å¹´'" :value="year"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æ¥æåç±»" prop="dateCategory"> |
| | | <el-select v-model="coefficientForm.dateCategory" style="width:100%"> |
| | | <el-option v-for="item in dateCategoryOptions" :key="item.value" :label="item.label" :value="item.value"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="客è¿ç³»æ°" prop="coefficient"> |
| | | <el-input-number v-model="coefficientForm.coefficient" :min="0.01" :precision="4" :step="0.1" style="width:100%"></el-input-number> |
| | | </el-form-item> |
| | | </el-form> |
| | | <span slot="footer"> |
| | | <el-button @click="coefficientDialog = false">åæ¶</el-button> |
| | | <el-button type="primary" :loading="saving" @click="saveCoefficient">ä¿å</el-button> |
| | | </span> |
| | | </el-dialog> |
| | | </el-main> |
| | | </el-container> |
| | | </el-container> |
| | | </template> |
| | | |
| | | <script> |
| | | import api from '@/api' |
| | | import AppHeader from '@/components/AppHeader.vue' |
| | | |
| | | export default { |
| | | components: { AppHeader }, |
| | | name: 'SystemConfig', |
| | | data() { |
| | | return { |
| | | activeTab: 'holidays', |
| | | years: [2024, 2025, 2026, 2027, 2028, 2029, 2030], |
| | | holidayTypeOptions: [], |
| | | dateCategoryOptions: [], |
| | | holidayQuery: { year: 2026, holidayType: 'NATIONAL_DAY' }, |
| | | coefficientQuery: { year: 2026 }, |
| | | holidays: [], |
| | | coefficients: [], |
| | | loadingHolidays: false, |
| | | loadingCoefficients: false, |
| | | saving: false, |
| | | holidayDialog: false, |
| | | coefficientDialog: false, |
| | | holidayForm: { id: null, year: 2026, holidayType: 'NATIONAL_DAY', holidayDate: '', dayIndex: 1 }, |
| | | coefficientForm: { id: null, year: 2026, dateCategory: 'HOLIDAY', coefficient: 2.4 }, |
| | | holidayRules: { |
| | | year: [{ required: true, message: 'è¯·éæ©å¹´ä»½', trigger: 'change' }], |
| | | holidayType: [{ required: true, message: 'è¯·éæ©è忥åç±»', trigger: 'change' }], |
| | | holidayDate: [{ required: true, message: 'è¯·éæ©æ¥æ', trigger: 'change' }], |
| | | dayIndex: [{ required: true, message: '请è¾å
¥ç¬¬å æ¥', trigger: 'change' }] |
| | | }, |
| | | coefficientRules: { |
| | | year: [{ required: true, message: 'è¯·éæ©å¹´ä»½', trigger: 'change' }], |
| | | dateCategory: [{ required: true, message: 'è¯·éæ©æ¥æåç±»', trigger: 'change' }], |
| | | coefficient: [{ required: true, message: '请è¾å
¥å®¢è¿ç³»æ°', trigger: 'change' }] |
| | | } |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.loadOptions() |
| | | this.loadHolidays() |
| | | this.loadCoefficients() |
| | | }, |
| | | methods: { |
| | | loadOptions() { |
| | | api.get('/holiday/config/options').then(res => { |
| | | const data = res.data || {} |
| | | this.holidayTypeOptions = this.mapOptions(data.holidayTypes) |
| | | this.dateCategoryOptions = this.mapOptions(data.dateCategories) |
| | | }) |
| | | }, |
| | | mapOptions(map) { |
| | | if (!map) return [] |
| | | return Object.keys(map).map(key => ({ value: key, label: map[key] })) |
| | | }, |
| | | labelOf(options, value) { |
| | | const item = (options || []).find(x => x.value === value) |
| | | return item ? item.label : value |
| | | }, |
| | | loadActiveTab() { |
| | | if (this.activeTab === 'holidays') this.loadHolidays() |
| | | else this.loadCoefficients() |
| | | }, |
| | | loadHolidays() { |
| | | this.loadingHolidays = true |
| | | api.get('/holiday/config/holidays', { params: this.holidayQuery }) |
| | | .then(res => { this.holidays = res.data || [] }) |
| | | .catch(e => { if (!e || !e._toast) this.$message.error('è忥å 载失败') }) |
| | | .finally(() => { this.loadingHolidays = false }) |
| | | }, |
| | | loadCoefficients() { |
| | | this.loadingCoefficients = true |
| | | api.get('/holiday/config/coefficients', { params: this.coefficientQuery }) |
| | | .then(res => { this.coefficients = res.data || [] }) |
| | | .catch(e => { if (!e || !e._toast) this.$message.error('客è¿ç³»æ°å 载失败') }) |
| | | .finally(() => { this.loadingCoefficients = false }) |
| | | }, |
| | | openHolidayDialog(row) { |
| | | this.holidayDialog = true |
| | | this.$nextTick(() => { |
| | | this.holidayForm = row ? Object.assign({}, row) : { id: null, year: 2026, holidayType: 'NATIONAL_DAY', holidayDate: '', dayIndex: 1 } |
| | | if (this.$refs.holidayForm) this.$refs.holidayForm.clearValidate() |
| | | }) |
| | | }, |
| | | onHolidayYearChange(year) { |
| | | if (this.holidayForm.holidayDate && Number(this.holidayForm.holidayDate.slice(0, 4)) !== year) { |
| | | this.holidayForm.holidayDate = '' |
| | | } |
| | | }, |
| | | saveHoliday() { |
| | | this.$refs.holidayForm.validate(valid => { |
| | | if (!valid) return |
| | | this.saving = true |
| | | api.post('/holiday/config/holidays/save', this.holidayForm) |
| | | .then(res => { |
| | | if (res.code !== 200) throw new Error(res.message || 'ä¿å失败') |
| | | this.$message.success('ä¿åæå') |
| | | this.holidayDialog = false |
| | | this.loadHolidays() |
| | | }) |
| | | .catch(err => this.showError(err, 'ä¿å失败')) |
| | | .finally(() => { this.saving = false }) |
| | | }) |
| | | }, |
| | | removeHoliday(row) { |
| | | this.$confirm('ç¡®å®å é¤è¯¥èåæ¥æ¥æåï¼', 'å é¤ç¡®è®¤', { type: 'warning' }) |
| | | .then(() => api.post('/holiday/config/holidays/delete', null, { params: { id: row.id } })) |
| | | .then(res => { |
| | | if (res.code !== 200) throw new Error(res.message || 'å é¤å¤±è´¥') |
| | | this.$message.success('å·²å é¤') |
| | | this.loadHolidays() |
| | | }) |
| | | .catch(err => { if (err !== 'cancel') this.showError(err, 'å é¤å¤±è´¥') }) |
| | | }, |
| | | openCoefficientDialog(row) { |
| | | this.coefficientDialog = true |
| | | this.$nextTick(() => { |
| | | this.coefficientForm = row ? Object.assign({}, row) : { id: null, year: 2026, dateCategory: 'HOLIDAY', coefficient: 2.4 } |
| | | if (this.$refs.coefficientForm) this.$refs.coefficientForm.clearValidate() |
| | | }) |
| | | }, |
| | | saveCoefficient() { |
| | | this.$refs.coefficientForm.validate(valid => { |
| | | if (!valid) return |
| | | this.saving = true |
| | | api.post('/holiday/config/coefficients/save', this.coefficientForm) |
| | | .then(res => { |
| | | if (res.code !== 200) throw new Error(res.message || 'ä¿å失败') |
| | | this.$message.success('ä¿åæå') |
| | | this.coefficientDialog = false |
| | | this.loadCoefficients() |
| | | }) |
| | | .catch(err => this.showError(err, 'ä¿å失败')) |
| | | .finally(() => { this.saving = false }) |
| | | }) |
| | | }, |
| | | removeCoefficient(row) { |
| | | this.$confirm('ç¡®å®å é¤è¯¥å®¢è¿ç³»æ°åï¼', 'å é¤ç¡®è®¤', { type: 'warning' }) |
| | | .then(() => api.post('/holiday/config/coefficients/delete', null, { params: { id: row.id } })) |
| | | .then(res => { |
| | | if (res.code !== 200) throw new Error(res.message || 'å é¤å¤±è´¥') |
| | | this.$message.success('å·²å é¤') |
| | | this.loadCoefficients() |
| | | }) |
| | | .catch(err => { if (err !== 'cancel') this.showError(err, 'å é¤å¤±è´¥') }) |
| | | }, |
| | | formatCoefficient(value) { |
| | | if (value === null || value === undefined || value === '') return 'â' |
| | | const number = Number(value) |
| | | if (!Number.isFinite(number)) return value |
| | | return number.toFixed(15).replace(/0+$/, '').replace(/\.$/, '') |
| | | }, |
| | | showError(err, fallback) { |
| | | if (err && err._toast) return |
| | | const data = err && err.response && err.response.data |
| | | this.$message.error((data && data.message) || (err && err.message) || fallback) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .toolbar { margin-bottom: 12px; display: flex; gap: 8px; align-items: center; } |
| | | .danger-text { color: #f56c6c; } |
| | | </style> |
| | |
| | | <el-menu-item index="/explain">ä¼ä¸è§£é</el-menu-item> |
| | | <el-menu-item index="/audit">å®¡æ ¸ç»æ</el-menu-item> |
| | | <el-menu-item index="/report">æ¥è¡¨çæ</el-menu-item> |
| | | <el-submenu index="/special-stats"> |
| | | <template slot="title">ä¸é¡¹ç»è®¡</template> |
| | | <el-menu-item index="/holiday-stat">è忥</el-menu-item> |
| | | </el-submenu> |
| | | <el-menu-item index="/rules">è§å管ç</el-menu-item> |
| | | <el-menu-item index="/users">ç¨æ·ç®¡ç</el-menu-item> |
| | | <el-menu-item v-if="operatorIsAdmin" index="/system-config">ç³»ç»é
ç½®</el-menu-item> |
| | | </el-menu> |
| | | </el-aside> |
| | | <el-main> |