HANDOFF.md
@@ -4,7 +4,68 @@ > **æ¯å¤©ç»ææ¶**ï¼æå½å¤©è¿å±ã踩è¿çåãæ°éæ±ãæªå®æäºé¡¹æ´æ°å°æ¬æä»¶ï¼ç¶åå¯ä»¥æ¾å¿å¼æ°å¯¹è¯ã > **åä½çº¦å®ï¼2026-08-31ï¼**ï¼è¯´ãæäº¤ã= æ¬å° commit + æ¨éå°è¿ç«¯ gitï¼origin mainï¼Gitblitï¼ï¼ä¸ååªåæ¬å° commitã ## ææ°ï¼2026-10-04ï¼ ## ææ°ï¼2026-10-05ï¼ ### 10-05 ä¸åï¼å ³æºå¿«ç §ï¼å½åç¶æï¼ - **已宿**ï¼v1.0.0 æ£å¼åå¸å `deploy/traffic-audit-deploy-20261005-v1.0.0/`ï¼zip **77.78 MB**ï¼å·²çæï¼å»å¯èªæ£ãçæ¬èªæ£ãç»ææ ¸å¯¹ä¸çå®å¯å¨åçåéè¿ï¼å¾ ä¸ä¼ æå¡å¨ãçæ¬ç®¡ç代ç ä¸ææ¡£åå·²æäº¤æ¨éï¼`main` = `93b36c8`ï¼ã - **è¿è¡ç¶æ**ï¼å ³æºåæ¬æº dev æå¡ä»å¨è¿è¡ï¼å端 8090 java PID 64112ãå端 8080 node PID 46472ï¼ï¼éå ³æºåæ¢ï¼æ éå¤çï¼æå åçç¨ç 8095 å®ä¾å·²åæ¢ã端å£å·²éæ¾ã - **å·¥ä½åº**ï¼é¤è¥å¹²æ¬å°ä¸´æ¶æä»¶ï¼`_tmp_*`ï¼ä¸ä¸ä»½ä¸é¡¹ç»è®¡ xls å¤ï¼git å·¥ä½åºä¸è¿ç«¯ `main` ä¸è´ï¼`docs/工使¥ç»/`ã`docs/åè½æµè¯æ¥å/` çæçº¦å®ä¸å ¥åºã - **䏿¬¡æ¥æ**ï¼å è¯»æ ¹ç®å½ `AGENTS.md` + æ¬æä»¶ï¼å¾ åè§ç¬¬ä¸èï¼æ°å¼ç²¾åº¦ç»ä¸æ¹é ãæå¿æç¥å¾ãAI 婿 RAGãç»æµææ è¯ç®çï¼ï¼é¦è¦äºé¡¹æ¯è·è¿æ£å¼å 卿å¡å¨çé¨ç½²ç»æã ### 10-05 ä¸åï¼æ£å¼åå¸å ææ°çæ¬ç®¡çéæ°çæï¼v1.0.0ï¼å端å£ç¦å ï¼ - **èæ¯**ï¼`8b71049` å·²ææå èæ¬æ¹ä¸ºåºå® jar å + çæ¬èªæ£ï¼`traffic-audit-server.jar`ãå å带 `v{version}`ï¼ï¼ä½æ£å¼åå¸å è¿æ²¡ææ°èæ¬éæ°çæï¼çæ¬æ¹é å产åºç `deploy/traffic-audit-deploy-20261005-2/` 仿¯æ§ jar å `traffic-audit-server-1.0.0-SNAPSHOT.jar`ã - **éæ°çæ**ï¼`powershell -NoProfile -ExecutionPolicy Bypass -File pack-deploy.ps1 -NoFrontendDist -SkipServiceWrapper`ï¼å端 clean package é建ãå端 `npm run build` é建ï¼ã - **产ç©**ï¼`deploy/traffic-audit-deploy-20261005-v1.0.0/`ï¼351 æä»¶ / 94.58 MBï¼ï¼zip **77.78 MB**ã - **æ ¡éª**ï¼å»å¯èªæ£ + çæ¬èªæ£éè¿ï¼pom / `app-version.properties` / `MANIFEST.MF` ä¸è å `1.0.0`ï¼ï¼å å `backend\traffic-audit-server.jar` 为åºå®åï¼æ `frontend/`ãæ `trafficAudit.exe/.xml/install-service*`ï¼`backend\web` 44 æä»¶ä¸ `frontend\dist` éæä»¶ SHA-256 ä¸è´ï¼`README-é¨ç½²è¯´æ.txt` 头é¨ä¸ºã20261005ï¼çæ¬ v1.0.0ã并嫿§æå¡å¨ä¸æ¬¡æ§å级说æã - **å¯å¨åçï¼çå®å ãåªè¯»ï¼**ï¼ç¨æ¬å°å¼ååºé ç½®å¨ 8095 端å£å¯å¨å å jarï¼`GET /api/version` è¿å `{"version":"1.0.0",...}`ï¼`GET /` è¿åç»å½é¡µ 200ï¼éªè¯åå·²æ PID 58652 ç²¾ç¡®åæ¢åçè¿ç¨ã - **ç¶æ**ï¼å¾ ç¨æ·ä¸ä¼ æå¡å¨ã`deploy/traffic-audit-deploy-20261005-2/`ï¼åæ´æ©ç `20261005/`ï¼ä½åºå¿ç¨ãæ§æå¡å¨é¦æ¬¡å级æ READMEï¼å æ¹ `trafficAudit.xml` ç `<arguments>` 为 `-Dfile.encoding=UTF-8 -jar "%BASE%\traffic-audit-server.jar"` 忢 jarï¼ä»¥ååªæ¿æ¢ jarã ### 10-05ï¼çæ¬ç®¡çéç¨æ¹æ¡ Aï¼åºå® jar åï¼ç¨æ·éªæ¶éè¿ï¼ - **ç¨æ·å³ç**ï¼éç¨æ¹æ¡ Aã`traffic-audit-server/pom.xml` 为å¯ä¸çæ¬æºï¼å½å对å¤çæ¬ `1.0.0`ã - **æå»ºæ¹é **ï¼`<finalName>traffic-audit-server</finalName>`ï¼äº§ç©åºå®ä¸º `target/traffic-audit-server.jar`ï¼Maven èµæºè¿æ»¤çæ `app-version.properties`ï¼Spring Boot ç¶ POM ä½¿ç¨ `@project.version@` åé符ï¼ï¼`maven-jar-plugin` åå ¥ `Implementation-Version`ã - **页颿¾ç¤º**ï¼æ°å¢å ç»å½ `GET /api/version`ï¼ç»å½é¡µå页头æ¾ç¤º `V1.0.0`ãåæ¶ä¿®å¤å¯æ§è¡ jar ä¸ `buildTime` 为 `null` çåµå¥ URI è§£æé®é¢ã - **åå¸é¾è·¯**ï¼æå¡ XMLãå¯å¨èæ¬ãæå èæ¬å ¨é¨æ¹ç¨åºå® jar åï¼`pack-deploy.ps1` 伿 ¡éª pom / `app-version.properties` / `MANIFEST.MF` ä¸è çæ¬ä¸è´ï¼åå¸å å为 `traffic-audit-deploy-YYYYMMDD-v1.0.0(.zip)`ï¼å¹¶è¾åºå¸¦ `{version}` çé¨ç½² READMEã - **æ§æå¡å¨é¦æ¬¡å级ï¼ä» 䏿¬¡ï¼**ï¼åæ¢æå¡åï¼æ `trafficAudit.xml` ç `<arguments>` æ¹ä¸º `-Dfile.encoding=UTF-8 -jar "%BASE%\traffic-audit-server.jar"`ï¼æ¿æ¢ jar åå¯å¨ã以ååçåªæ¿æ¢ jar å¹¶éå¯ï¼ä¸åæ¹ XMLã - **éªè¯**ï¼`mvn -o test`ã`mvn -o clean package -DskipTests`ãåºå®å jar å®é å¯å¨å¹¶è®¿é® `/api/version`ãå端 `npm run build`ãæ¬å°æµè§å¨ç»å½é¡µæ¾ç¤º `V1.0.0`ã`pack-deploy.ps1 -SkipFrontend -NoFrontendDist -SkipServiceWrapper` ç»è£ /å»å¯/çæ¬èªæ£å ¨é¨éè¿ï¼è´åéªè¯ç¡®è®¤çæ¬ä¸å¹é å `SNAPSHOT` é½ä¼å¨æå å䏿¢ãæµè¯æ¥åï¼æ¬å° `docs/åè½æµè¯æ¥å/åè½æµè¯æ¥å_çæ¬ç®¡çæ¹æ¡A_2026-10-05.md`ï¼ç®å½ä¸å ¥åºï¼ã - **ç¶æ**ï¼çæ¬ç®¡çä»£ç æäº¤ä¸º `8b71049`ï¼`main` 已忥ï¼`v1.0.0` å·²æ¨éå°å ¬å¸ Gitblitå¹¶æå `21a5bf7`ã2026-10-05 ç¨æ·ç¡®è®¤æ¬å°ç»å½é¡µåææç»å½å页é¢åæ£ç¡®æ¾ç¤º `V1.0.0`ãæ£å¼åå¸å å·²äº 10-05 ä¸åç¨æ°çæå èæ¬éæ°çæï¼è§ä¸æ¹ 10-05 ä¸åä¸èï¼ã ### 10-04 æ·±å¤ï¼æçº¿æ°å¼æ ç¾åå°è´´è¿æ¬ç¹ï¼v35ï¼å¾ ç¨æ·éªæ¶ï¼ - **ç¨æ·åé¦**ï¼v31ãé¿å¼æçº¿ãçæ¬ä¸å¦ä¸ä¸ç v27 ââ è½ç¶æ ç¾ä¸å线äºï¼ä½å¾å¤æ ç¾ç¦»èªå·±çæ°æ®ç¹å¤ªè¿ï¼ä¸ç¼çä¸åºæ çæ¯åªä¸ªç¹ãè¦æ±åå°è´´è¿æ¬ç¹çææã - **æ°é®é¢ä¸æ§éæ±çå²çª**ï¼æ´æ©çåå§è¯æ±æ¯ãæ°åå线ç䏿¸ ãï¼è¿æ¬¡è¯æ±æ¯ãæ°åå¿ é¡»è´´è¿æ¬ç¹ãã两è å¹¶åï¼å¿ é¡»åæ¶æ»¡è¶³ã - **å®ç°ï¼`HolidayTrendChartService`ï¼**ï¼ - æ ç¾æç**宿´åéå° v27 ç®æ³**ï¼æåæ¥ç¹ä½è·ç¦»å代价ï¼`ownDistance Ã10`ï¼ï¼åéå«è¿/è¿çä¸ãä¸ãå·¦ãå³ååè§ï¼å¨æè§åä¿è¯åæ¥æ ç¾ä¸ç¹ä½ä¸ä¸é¡ºåºä¸è´ï¼`lineLabelCost`ã`lineLabelCandidates`ï¼ãv31 ççº¿æ¡æ©èãChamfer è·ç¦»åºãé¢è²äº²åé¡¹å ¨é¨ç§»é¤ã - **æ°å¢ç²ç½æè¾¹ï¼haloï¼** è§£å³ãå线ç䏿¸ ãï¼`drawLabelWithHalo` ç¨ `TextLayout` åå形轮å»ï¼å 以ç½è² `HALO_OUTLINE_WIDTH=4.0f` åè§æè¾¹ï¼åç¨æ¬è²å¡«å ãæ°åå³ä½¿è½å¨æçº¿ä¸ä¹æ¸ æ°å¯è¯»ï¼ä»èå 许æ ç¾è´´åæ¬ç¹ãæçº¿å¾ãå å å¾å¤ç½®æ ç¾ã饼徿 ç¾ç»ä¸èµ°è¯¥æ¹æ³ã - é¡ºå¸¦æ¸ çè°è¯éçï¼å é¤ `LABEL_DEBUG`/`CHART_SEQ`/`TRENDDBG` æå°ï¼ä»¥åä¸å使ç¨ç `lineNormal(...)` ä¸ `DayLabel.normalX/normalY`ã - **éåéªè¯ï¼RapidOCR æ ç¾å å´æ¡ä¸å¿ â ç宿°æ®ç¹åæ ï¼**ï¼æ ç¾å°æ¬ç¹å¹³åè·ç¦» v27=`36.8px`ãv31=`53.2px`ãv35=`36.7px`ï¼è¶å°è¶è´´è¿æ¬ç¹ï¼ãv35 ä¸ç¨æ·è®¤å¯ç v27 åºæ¬ä¸è´ï¼ææ¾ä¼äºè¢«å¦ç v31ã - **å ¶ä½éªè¯**ï¼`mvn compile`/`mvn test` éè¿ï¼æ ç¼è¯åè¦éçï¼ï¼Word COM 8 页 15 å¾ï¼1+2Ã7ï¼ï¼RapidOCR 夿£ 12 å¼ æçº¿å¾æ°å¼æ ç¾å å´æ¡éå æ° = 0ï¼`label_affinity` 夿£å¾ 11 æ¯ä¸ªæ°æ®æ ç¾å°æ¬çº¿ 3~8px 䏿¬çº¿æ¯æè¿çº¿ï¼æ ãCLOSER TO å ¶ä»è²ãï¼ï¼æ¾å¤§ 6Ã å¤æ ¸å¾ 11 左䏿 ç¾ç¡®ä¸º `630.99`ï¼ä½å辨ç OCR æ¾è¯¯è¯»ä¸º `1630.99`ï¼ãv27âv35 åç´ å·®çº¦ 1%ï¼éä¸å¨æ ç¾å¤ï¼å³ç½æè¾¹çæä¸æªç ´åæçº¿ã - **产ç©ä¸ç¶æ**ï¼`_tmp_holiday_verify/holiday_trend_2026-10-04_v35.docx`ï¼ä¸è½½å¯æ¬ `C:\Users\jcxiong\Downloads\2026å¹´åä¸åæå ¨ç综åè¿è¾å®¢æµéèµ°å¿å¾ï¼ä¿®æ¹ç-æ°åè´´è¿æçº¿-ç½è¾¹æ¸ æ°ï¼.docx`ãæªå¯¼å ¥ãè¦çæé坼任使°æ®ãå¾ ç¨æ·éªæ¶ï¼éªæ¶åä¸æäº¤ã䏿¨éï¼ç°æ `deploy/traffic-audit-deploy-20261004.zip` 仿¯ v24 è¡ä¸ºï¼éªæ¶åéé建ã ### 10-05ï¼èµ°å¿å¾ v35 éªæ¶éè¿ + å端å£åå¸å ï¼-NoFrontendDist -SkipServiceWrapperï¼ - **éªæ¶**ï¼v35ï¼æçº¿æ°å¼æ ç¾åå°è´´è¿æ¬ç¹ + ç½æè¾¹ï¼2026-10-05 ç¨æ·éªæ¶éè¿ã - **æå éæ±ï¼ç¨æ·æ¾æ¸ ï¼**ï¼æå¡å¨å端å£ï¼åªéè¦ `backend\web`ï¼ä¸è¦ç¬ç«ç `frontend/dist`ï¼ä¸ºä¿è¯å 宿´ï¼`backend\web` + jar + docs é½è¦ãæå¡å¨é馿¬¡åå¸ãæå¡å·²è£ ï¼å¯å `-SkipServiceWrapper` 廿æå¡æ³¨åå¨ã - **æå å·¥å ·ä¿®æ£**ï¼å åå ç `-NoFrontend` è¯ä¹éäºï¼æ `backend\web` ä¹å»æäºï¼ï¼ç°æ¹ä¸º `-NoFrontendDist`ãå®åªè·³è¿ç¬ç« `frontend\` ç®å½ï¼ä¸å¤å¶ dist / nginx 示ä¾ï¼ï¼**ä»æå»ºï¼æè¦æ±ï¼dist 并忥 `backend\web`**ï¼å端å£å¿ éï¼å¦åé¡µé¢ 404ï¼ã`-SkipFrontend` ä»ä¸ºâä¸é建ãå¤ç¨ç°æ distâã - **åå¸å **ï¼`deploy/traffic-audit-deploy-20261005-2/`ï¼351 æä»¶ / 94.55 MBï¼zip **77.78 MB**ï¼ãå·²æ ¸å¯¹ï¼æ `frontend/`ãæ `trafficAudit.exe/.xml/install-service*`ï¼æå¡å è£ å¨å·²æé¤ï¼ï¼`backend\web` 44 æä»¶ä¸ `frontend\dist` éæä»¶åå¸ä¸è´ï¼jar å ç½®é ç½®å ¨å ä½ç¬¦ï¼å»å¯èªæ£éè¿ï¼ï¼å å `HolidayTrendChartService.class` ä¸ `target/classes` ä¸è´ï¼å« v35 代ç ï¼ãè¾ä¸ä¸å®æ´å ï¼110.49 MB / zip 83.77 MBï¼ç约 16 MB è§£å / 6 MB å缩ã - **ä½åº**ï¼`deploy/traffic-audit-deploy-20261005/`ï¼é误è¯ä¹ä¸ç产ç©ï¼ç¼º `backend\web`ï¼å·²è¢« `-2` å代ï¼å¿ç¨ã - **é¨ç½²æ³¨æ**ï¼è¦ç jarï¼åæé `application.yml`ï¼å³å¯ï¼`backend\web` å·²éå æä¾ãæå¡å·²è£ ï¼æ ééæ°æ³¨åã - **ç¶æ**ï¼å·²æå ï¼å¾ ç¨æ·ä¸ä¼ å°æå¡å¨ï¼è¯¥å ä¸ºçæ¬æ¹é å产ç©ï¼å·²ç± 10-05 ä¸åç `traffic-audit-deploy-20261005-v1.0.0` å代ï¼å¿ç¨ï¼ã ### 10-04 æ·±å¤ï¼æçº¿æ°å¼æ ç¾é¿å¼æçº¿å¹¶è´´è¿æ¬çº¿ï¼v31ï¼å¾ ç¨æ·éªæ¶ï¼ - **ç¨æ·åé¦ï¼é¢å¯¼çåï¼ä¸¤è½®ï¼**ï¼â å年份æ°å离æ¬çº¿æè¿æè¿ï¼å¸æè´´è¿åèªè²çº¿ä¸ä¸éä¹±ï¼â¡ 仿æ°ååå¨æçº¿ä¸ç䏿¸ ï¼ç¬¬ä¸è½®åªæ¯âè´´è¿âï¼æ ç¾ä¼è½å¨çº¿ä¸ï¼ãç¨æ·å»ºè®®ï¼è°çºµè½´å»åº¦æå¼æçº¿ãæå å·¦å³é¿è®©è®©æ ç¾ä¸å线ã - **å®ç°ï¼`HolidayTrendChartService`ï¼**ï¼ - æ ç¾æçæ¹ä¸ºã卿è§å + 线æ¡åç´ é¿è®©ããå¾è¡¨å ç»æçº¿åæ è®°ï¼åæææç³»åé¢è²åç´ ï¼å®¹å·® 45ï¼åææ©èï¼å¯¹æ¯ä¸ªåéæ ç¾ä½ç½®ç»è®¡å ¶è¦çç线æ¡åç´ æ°ï¼`Ã700` éç½ââæ°åä¸åå线ã - ä¸ºæ¯æ¡ç³»åå»ºç« Chamfer è·ç¦»åºï¼å°è¯¥è²æè¿åç´ çè·ç¦»ï¼ã代价æ°å¢ä¸¤é¡¹ï¼`max(0, èªå·±é£æ¡çº¿çè·ç¦»-3)Ã40`ï¼ææ ç¾æåæ¬çº¿æï¼é²æ¢é£è¿ï¼ä¸é¢è²äº²å项 `max(0, èªå·±çº¿è· - å«ççº¿è· + 4)Ã200`ï¼è¦æ±æ¬çº¿æ¯æè¿ç线ï¼é¿å è´´å°å«çç³»åï¼ã - æ§çã离å«ç忥ç¹ä½æ´è¿ãæ©ç½ç± `Ã5000` éå° `Ã400`ï¼é¿å å®åå亲å项ï¼`ITEM_LABEL_GAP` 12â10ï¼åéå«è¿/è¿çä¸ãä¸ãå·¦ãå³ååè§ã - ä¿ç忥æ ç¾ä¸æ°æ®ç¹ä¸è´çä¸ä¸é¡ºåºï¼é¿å æ ç¾ä¸æçº¿ä¸ä¸å转ã - **è°åè¿ç¨æç¤º**ï¼v27ï¼åªæç¹ä½è·ç¦»æåºï¼â æ ç¾å线ï¼v28ï¼åªå è¦çåç´ æ©ç½ï¼â æ ç¾ç¦»æ¬çº¿ãè´´å°å«çè²çº¿æï¼v31ï¼è¦çæ©ç½ + æ¬çº¿è·ç¦» + é¢è²äº²åï¼â 两è å ¼é¡¾ãéªè¯æ ç¾æ¯å¦âè´´é线âä¸è½åªç OCR å å´æ¡å°å¼è²çè·ç¦»ï¼ç¸é»çå¼è²**æ ç¾**ä¼è¢«è¯¯å¤ä¸ºå¼è²**线**ï¼å¿ é¡»èç¼æ ¸å¯¹æ´å¾ã - **éªè¯**ï¼`mvn test -f traffic-audit-server\pom.xml` éè¿ãçå®åºåªè¯»çæ `_tmp_holiday_verify/holiday_trend_2026-10-04_v31.docx`ï¼Word COM 8 页 15 å¾ï¼1+2Ã7ï¼ãRapidOCR 夿£ 15 å¼ å¾æ°å¼æ ç¾å å´æ¡éå æ°ä¸º 0ãèç¼æ ¸å¯¹å¾ 1ï¼è·¨åºåäººåæµå¨éï¼ãå¾ 2ï¼ç´¯è®¡ï¼ãå¾ 4ï¼èªé©¾ï¼ãå¾ 6ï¼æ°´è·¯ï¼ãå¾ 9ï¼é«éï¼ãå¾ 11ï¼è½¦æµæ»éï¼ï¼æ°å¼æ ç¾åç¦»å¼æçº¿ãä½äºåèªè²çº¿æãé¢è²å¯¹åºæ£ç¡®ï¼å¾ 11 ç 630.99/601.04/571.31 åå«ç´§è´´è/红/绿æçº¿ã - **产ç©ä¸ç¶æ**ï¼`C:\Users\jcxiong\Downloads\2026å¹´åä¸åæå ¨ç综åè¿è¾å®¢æµéèµ°å¿å¾ï¼ä¿®æ¹ç-æ°å¼æ ç¾é¿å¼æçº¿ï¼.docx`ãæªå¯¼å ¥ãè¦çæé坼任使°æ®ãå¾ ç¨æ·éªæ¶ï¼éªæ¶åä¸æäº¤ã䏿¨éï¼ç°æ `deploy/traffic-audit-deploy-20261004.zip` 仿¯ v24 è¡ä¸ºï¼ééªæ¶åé建ã ### 10-04 æï¼èµ°å¿å¾ 3 é¡¹åæ è½´è°æ´ï¼v24ï¼ç¨æ·å·²éªæ¶ï¼ - **ç¨æ·ä¿®æ¹é¡¹**ï¼â åæ è½´åä½ï¼ä¸äººæ¬¡ãä¸è¾æ¬¡ãå ¬é/å°æ¶ãæ¥æ¤åº¦ãæ¥æï¼æ¹æåå»åº¦ä¸æ ·çé¢è²ï¼â¡ 两个å å å¾çåæ è½´æ¹æä¸å ¶ä»å¾ä¸æ ·çé»è²ï¼â¢ âä¸äººæ¬¡/å æ¯âç»§ç»æ¾å¨å å å¾å³ä¸è§ã deploy-templates/README-²¿Êð˵Ã÷.txt
@@ -1,4 +1,4 @@ TrafficAudit 交éè´§è¿ç»è®¡å®¡æ ¸ç³»ç» ââ é¨ç½²å ï¼{stamp}ï¼ TrafficAudit 交éè´§è¿ç»è®¡å®¡æ ¸ç³»ç» ââ é¨ç½²å ï¼{stamp}ï¼çæ¬ v{version}ï¼ ======================================================== ãæå¡å¨ä¸åªè¦åè¿ 3 æ¥ãï¼å ¶å®æä»¶é½ä¸ç¨å¨ï¼ @@ -18,7 +18,7 @@ ãç®å½ç»æã backend/ traffic-audit-server-1.0.0-SNAPSHOT.jar å端ç¨åºï¼å«å ¨é¨ä¾èµï¼**å·²å åµå端**ï¼ traffic-audit-server.jar å端ç¨åºï¼å«å ¨é¨ä¾èµï¼**å·²å åµå端**ï¼åºå®æä»¶åï¼ docs/ æ¥è¡¨æ¨¡æ¿ä¸è¿è¡æ°æ®ç®å½ï¼å¿ éï¼å¿å ï¼ application.yml å¤é¨é ç½®ï¼æ¹è¿éå³å¯ï¼æ ééæ°æå ï¼ start-backend.cmd / start-backend.sh åå°å¯å¨èæ¬ï¼äººå·¥è°è¯ç¨ï¼å ³äºçªå£å°±åï¼ @@ -64,6 +64,16 @@ - èæ¬è¦æ±ï¼install-service.ps1 å¨ Windows PowerShell 2.0+ ä¸å¯ç¨ï¼Win7 èªå¸¦ 2.0ï¼å·²åå ¼å®¹ï¼ï¼ 3.2 ç计åä»»å¡èæ¬ç¨å°äº New-ScheduledTask* å½ä»¤ï¼éè¦ Win8 / Server 2012 å以ä¸ã ã仿¬çèµ·ï¼å端 jar 使ç¨åºå®æä»¶åã backend\traffic-audit-server.jar - æ°æå¡å¨ï¼æä¸é¢çæ®éé¨ç½²æ¥éª¤æ§è¡å³å¯ã - å·²å®è£ æ§çæå¡çæå¡å¨ï¼é¦æ¬¡åçº§å°æ¬çï¼ï¼ 1) 忢æå¡ï¼Stop-Service TrafficAuditBackend 2) å¤ä»½å¹¶ç¼è¾ backend\trafficAudit.xmlï¼æ <arguments> éçæ§ jar åæ¹æ traffic-audit-server.jar 3) æ¿æ¢ backend\traffic-audit-server.jarï¼åæ§è¡ Start-Service TrafficAuditBackend - ä»¥åæ¯æ¬¡å级ï¼åªæ¿æ¢ backend\traffic-audit-server.jarï¼åé坿å¡ï¼ä¸åéè¦ä¿®æ¹ trafficAudit.xmlã - 页é¢ç»å½é¡µåå³ä¸è§æ¾ç¤ºççæ¬å·ä¸æ¬åå¸å åç§°ä¸ççæ¬å·ä¸è´ã ãé¨ç½²æ¥éª¤ã 1. åå§åæ°æ®åºï¼4 ä¸ªèæ¬é½è¦æ§è¡ï¼é¡ºåºå¦ä¸ï¼ mysql --default-character-set=utf8mb4 -u root -p < db/init.sql deploy-templates/backend/install-autostart.ps1
@@ -65,7 +65,7 @@ Assert-Admin if (-not (Test-Path $launcher)) { throw "Missing launcher: $launcher" } if (-not (Test-Path (Join-Path $root 'traffic-audit-server-1.0.0-SNAPSHOT.jar'))) { if (-not (Test-Path (Join-Path $root 'traffic-audit-server.jar'))) { Write-Host "WARN: jar not found in $root - the task will be registered, but startup will fail until the jar is there." } deploy-templates/backend/install-service.ps1
@@ -1,8 +1,8 @@ <# <# Install / update / remove the TrafficAudit backend as a Windows service. The service is a WinSW (Windows Service Wrapper) host: trafficAudit.exe reads trafficAudit.xml and runs "java -jar traffic-audit-server-<version>.jar". trafficAudit.xml and runs "java -jar traffic-audit-server.jar". Because it is a real Windows service it starts at boot with no user logged on, is listed in services.msc / Get-Service, and is restarted by the Service Control Manager when the java process dies. deploy-templates/backend/start-backend-service.cmd
@@ -14,7 +14,7 @@ setlocal cd /d "%~dp0" set "APP_JAR=traffic-audit-server-1.0.0-SNAPSHOT.jar" set "APP_JAR=traffic-audit-server.jar" set "LOG=%~dp0app.log" netstat -ano | findstr /C:":8090" | findstr /I "LISTENING" >nul 2>nul deploy-templates/backend/start-backend.cmd
@@ -22,5 +22,5 @@ echo å端已å åµï¼ç´æ¥è®¿é® 8090 å³å¯ï¼ä¸éè¦ nginxï¼ echo æ¥å¿æ¾ç¤ºå¨æ¬çªå£ï¼å ³éçªå£å³åæ¢æå¡ echo ============================================ java -jar traffic-audit-server-1.0.0-SNAPSHOT.jar java -jar traffic-audit-server.jar pause deploy-templates/backend/start-backend.sh
@@ -7,4 +7,4 @@ echo "[WARN] application.yml é spring.datasource.password è¿æ¯å 使åï¼è¯·å æ¹æçå® MySQL å¯ç " fi echo "TrafficAudit å¯å¨ä¸ï¼http://localhost:8090 ï¼Ctrl+C 忢ï¼" java -jar traffic-audit-server-1.0.0-SNAPSHOT.jar 2>&1 | tee app.log java -jar traffic-audit-server.jar 2>&1 | tee app.log deploy-templates/backend/trafficAudit.xml
@@ -20,7 +20,7 @@ <description>TrafficAudit freight statistics audit backend (Spring Boot, HTTP port 8090).</description> <executable>java.exe</executable> <arguments>-Dfile.encoding=UTF-8 -jar "%BASE%\traffic-audit-server-1.0.0-SNAPSHOT.jar"</arguments> <arguments>-Dfile.encoding=UTF-8 -jar "%BASE%\traffic-audit-server.jar"</arguments> <workingdirectory>%BASE%</workingdirectory> <!-- Start at boot, but delayed so a local MySQL service is ready first. --> docs/´ò°ü²¿Êð˵Ã÷.md
@@ -1,6 +1,6 @@ # 交éç»è®¡æ¥è¡¨å®¡æ ¸ç³»ç» â æå ä¸é¨ç½²è¯´æ > éç¨ï¼main åæ¯ï¼ææ°æ´æ° 2026-09-13ï¼ãå端 Spring Boot 2.7ï¼Java 8ï¼ï¼å端 Vue 2 + Element UIã > éç¨ï¼main åæ¯ï¼ææ°æ´æ° 2026-10-05ï¼ãå端 Spring Boot 2.7ï¼Java 8ï¼ï¼å端 Vue 2 + Element UIã > æ¬ææ¡£é¢åå¼åä¸é¨ç½²åäºï¼å½ä»¤åå¨ Windows ä¸éªè¯ã ## ä¸ã项ç®ç»æ @@ -34,8 +34,8 @@ 1. `traffic-audit-web` ä¸ `npm run build` â `dist`ï¼ 2. æ `dist` å¤å¶è¿ `traffic-audit-server/src/main/resources/static`ï¼ä½¿ **jar èªå¸¦å端**ï¼å `mvn clean package -DskipTests`ï¼è·å®èªå¨æ¸ 空 staticï¼ä¿æä»åºå¹²åï¼ï¼ 3. ç»è£ `deploy/traffic-audit-deploy-YYYYMMDD/`ï¼ 4. å缩为 `traffic-audit-deploy-YYYYMMDD.zip`ï¼çº¦ 70 MBï¼ã 3. ç»è£ `deploy/traffic-audit-deploy-YYYYMMDD-v1.0.0/`ï¼ 4. å缩为 `traffic-audit-deploy-YYYYMMDD-v1.0.0.zip`ï¼çº¦ 70 MBï¼ã æå·¥çä»·å½ä»¤ï¼ä¸æ³ç¨èæ¬æ¶ï¼ï¼ @@ -47,12 +47,41 @@ > 注æï¼æå·¥æ¹å¼è·å®è®°å¾å æ `traffic-audit-server/src/main/resources/static` ä¸çå 容ï¼å¦å伿±¡ææºç ç®å½ã ### çæ¬ç®¡çä¸åå¸ï¼2026-10-05 èµ·ï¼ éç¨æ¹æ¡ Aï¼å端 jar 使ç¨åºå®æä»¶åï¼çæ¬å·ç± `pom.xml` å䏿¥æºçæï¼åå¸å åå git tag å¸¦çæ¬ã - åä¸çæ¬æºï¼`traffic-audit-server/pom.xml` ç `<version>`ï¼ä¾å¦ `1.0.0` - æå»ºäº§ç©åºå®ä¸º `traffic-audit-server/target/traffic-audit-server.jar` - æå»ºæ¶èªå¨æçæ¬åè¿ï¼ - `BOOT-INF/classes/app-version.properties` - `META-INF/MANIFEST.MF` ç `Implementation-Version` - åå¸å å½åï¼`traffic-audit-deploy-YYYYMMDD-v1.0.0(.zip)` - git tag å½åï¼`v1.0.0` - 页é¢çæ¬ï¼å端 `GET /api/version`ï¼ç»å½é¡µå页头æ¾ç¤º `V1.0.0` - `pack-deploy.ps1` 伿 ¡éª `pom.xml`ãjar å `app-version.properties`ãjar æ¸ åä¸è çæ¬ä¸è´ï¼ä¸ä¸è´ç´æ¥ä¸æ¢æå åæ°çæ¬æµç¨ï¼ 1. å æ¹ `traffic-audit-server/pom.xml` ç `<version>`ï¼ä¾å¦ `1.0.1` 2. è¿è¡ `pack-deploy.ps1` æå 3. æäº¤ä»£ç å¹¶æ git tagï¼`git tag -a v1.0.1 -m "v1.0.1"` 4. æå¡å¨åæ¢æå¡ï¼æ¿æ¢ `traffic-audit-server.jar`ï¼åå¯å¨æå¡ æ§æå¡å¨é¦æ¬¡å级ï¼åªéå䏿¬¡ï¼ï¼å·²æ `trafficAudit.xml` é常ä»åæ»æ§ jar å `traffic-audit-server-1.0.0-SNAPSHOT.jar`ï¼æ `<arguments>` æ¹æåºå®æä»¶åï¼ ```xml <arguments>-Dfile.encoding=UTF-8 -jar "%BASE%\traffic-audit-server.jar"</arguments> ``` ä»¥åæ¯æ¬¡åå¸åªæ¿æ¢ jar å¹¶é坿å¡ï¼ä¸åä¿®æ¹ `trafficAudit.xml`ã ## åãé¨ç½²å å 容 ``` traffic-audit-deploy-YYYYMMDD/ traffic-audit-deploy-YYYYMMDD-v1.0.0/ ââ backend/ â ââ traffic-audit-server-1.0.0-SNAPSHOT.jar å端 + å åµå端ï¼çº¦ 55 MBï¼ â ââ traffic-audit-server.jar å端 + å åµå端ï¼çº¦ 55 MBï¼åºå®æä»¶åï¼ â ââ docs/ 模æ¿ä¸è¿è¡æ°æ®ï¼å¿ éï¼å¿å ï¼ â ââ application.yml å¤é¨é ç½®ï¼æ¹è¿éå³å¯ï¼æ ééæå ï¼ â ââ start-backend.cmd / start-backend.sh å¯å¨èæ¬ï¼äººå·¥åå»/å½ä»¤è¡ï¼ @@ -161,7 +190,7 @@ ```powershell # Windowsï¼åå» start-backend.cmdï¼æ cd backend ; java -jar traffic-audit-server-1.0.0-SNAPSHOT.jar cd backend ; java -jar traffic-audit-server.jar ``` ```bash @@ -270,6 +299,7 @@ ## åä¸ãåæ´è®°å½ - **2026-10-05**ï¼çæ¬ç®¡çéç¨æ¹æ¡ Aã`pom.xml` 为å¯ä¸çæ¬æºï¼jar åºå®å为 `traffic-audit-server.jar`ï¼æå»ºèªå¨æ³¨å ¥ `app-version.properties` ä¸ `MANIFEST.MF`ï¼åå¸å åå¸¦çæ¬ï¼æ°å¢ `/api/version` 页颿¾ç¤ºï¼é¦æ¬¡åçº§æ§æå¡å¨é䏿¬¡æ§ä¿®æ¹ `trafficAudit.xml`ï¼ä»¥ååªæ¿æ¢ jar å¹¶éå¯ã - **2026-10-03**ï¼`pack-deploy.ps1` è¡¥é½å¤ç½® `application.yml`/`docs`/`db`/å¯å¨èæ¬å¹¶åæ¥ `backend\web`ï¼ä¿® `Access denied ... (using password: NO)`ï¼ï¼æ¨¡æ¿ç«¯å£ 8080â8090ï¼æ°å¢ Windows æå¡æ¹å¼å¼æºèªå¯ï¼WinSW 2.12.0 `trafficAudit.exe` + `trafficAudit.xml` + `install-service.ps1`ï¼èªå¨/å»¶è¿å¯å¨ã失败éå¯ãæ¥å¿æ»å¨ï¼ï¼è®¡å任塿¹å¼é为å¤éï¼è¡¥æä½ç³»ç»å ¼å®¹æ§è¯´æã - **2026-09-13**ï¼æ°å¢ä¸é®æå èæ¬ `package-deploy.py`ï¼jar æ¹ä¸ºå åµå端ï¼é¨ç½²æºä¸åéè¦ nginxï¼ï¼å é¤ Redis ç¸å ³è¯´æï¼ä»£ç æªä½¿ç¨ï¼ï¼è¡¥ `sql_wyc.sql` ä¸ `summary.template-dir`ï¼è¡¥ `jwt.secret` åå¼è§åï¼è¡¥é¨ç½²åéªè¯æ¸ åã - 2026-08-27ï¼é¦çï¼zip é¨ç½²å + nginx æ¹æ¡ï¼ã @@ -306,7 +336,7 @@ # 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:" python -c "import zipfile;print(zipfile.ZipFile('traffic-audit-server/target/traffic-audit-server.jar').read('BOOT-INF/classes/application.yml').decode('utf-8'))" | Select-String "password:|api-key:" ``` @@ -321,7 +351,7 @@ 1. ç¨ `deploy-templates/jar-application.yml`ï¼å ¨å ä½ç¬¦ï¼ä¸´æ¶æ¿æ¢æºç `application.yml`ï¼å `mvn clean package -DskipTests`ï¼æå»ºåèªå¨è¿åå¼åæºçå®é ç½®ï¼ 2. **è§£å jar èªæ£**ï¼æ£æ¥ `BOOT-INF/classes/application.yml` æ¯å¦å ¨ä¸ºå ä½ç¬¦ââ䏿¦åç° `sk-` æææå£ä»¤**ç´æ¥ä¸æ¢æå **ï¼ 3. `npm run build` é建å端 `dist`ï¼ 4. æ `deploy-templates/` ç»è£ 宿´é¨ç½²å ï¼backend / frontend / db / README-é¨ç½²è¯´æ.txtï¼ââ`backend/` å« jarã`application.yml`ãå¯å¨èæ¬ã`docs/`ã`web/`ï¼ç± dist èªå¨åæ¥ï¼ä¾ 8090 åç«¯å£æ¨¡å¼ï¼ï¼ä»¥å Windows æå¡ä¸ä»¶å¥ `trafficAudit.exe` + `trafficAudit.xml` + `install-service.ps1`ï¼è¾åºå° `<OutRoot>\traffic-audit-deploy-<yyyyMMdd>`ï¼ 4. æ `deploy-templates/` ç»è£ 宿´é¨ç½²å ï¼backend / frontend / db / README-é¨ç½²è¯´æ.txtï¼ââ`backend/` å« jarã`application.yml`ãå¯å¨èæ¬ã`docs/`ã`web/`ï¼ç± dist èªå¨åæ¥ï¼ä¾ 8090 åç«¯å£æ¨¡å¼ï¼ï¼ä»¥å Windows æå¡ä¸ä»¶å¥ `trafficAudit.exe` + `trafficAudit.xml` + `install-service.ps1`ï¼è¾åºå° `<OutRoot>\traffic-audit-deploy-<yyyyMMdd>-v<version>`ï¼ WinSW ä¼å ç¨ `deploy-templates\tools\winsw.exe` ç¼åï¼æ ¡éª SHA-256ï¼ï¼ç¼å缺失æåå¸ä¸ç¬¦æ¶èªå¨ä» GitHub 宿¹åå¸é¡µä¸è½½ v2.12.0 x64ï¼è¯¥äº§ç©æªå Authenticode ç¾åï¼å æ¤ä»¥åºå® SHA-256 ä½å®æ´æ§æ ¡éªï¼ï¼ 5. 对ç»è£ åçæ´å åæ«ä¸éææå¯é¥ã @@ -344,7 +374,7 @@ ### é¨ç½²æºä¸å /æ¢ 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`ï¼ï¼ 2. éå¯å端ï¼`start-backend.cmd`ï¼æ `java -jar traffic-audit-server.jar`ï¼ï¼ 3. 页é¢éç¹ä¸æ¬¡ AI åæç¡®è®¤å¯ç¨ï¼ 4. **ä¸è¦**廿¹ jar å é¨çé ç½®ï¼æ¹äºä¸æ¬¡åå 就丢ï¼ï¼ä¹ä¸è¦æ key æäº¤è¿ä»»ä½ä»åºæä»¶ã pack-deploy.ps1
@@ -3,13 +3,16 @@ ç¨æ³ï¼ powershell -NoProfile -ExecutionPolicy Bypass -File pack-deploy.ps1 powershell -NoProfile -ExecutionPolicy Bypass -File pack-deploy.ps1 -OutRoot "E:\è¯éª" -SkipFrontend powershell -NoProfile -ExecutionPolicy Bypass -File pack-deploy.ps1 -NoFrontendDist -SkipServiceWrapper ââ å端å£åå¸ï¼ä¸çæç¬ç«ç frontend\dist ç®å½ï¼ä»æå»ºå¹¶åæ¥ backend\webï¼å端å£å¿ éï¼ï¼ æå¡å¨å·²è£ æå¡æ¶é å -SkipServiceWrapper å»æçº¦ 17 MB ç WinSWï¼å¯æ¾èåå°å ä½ç§¯ã åäºä»¶äºï¼ 1) ç¨ deploy-templates\jar-application.ymlï¼å ¨å ä½ç¬¦ï¼ä¸´æ¶æ¿æ¢æºç application.ymlï¼ æ§è¡ mvn clean packageï¼æ 论æè´¥é½è¿åå¼åæºçå®é ç½® 2) è§£å jar æ£æ¥å ç½® application.ymlï¼å¿ é¡»å ¨æ¯å ä½ç¬¦ï¼ç¦æ¢ sk- å¯é¥ / ææå£ä»¤ï¼ï¼ä¸åæ ¼ç´æ¥ä¸æ¢ 3) npm run build é建å端 distï¼å¯ -SkipFrontendï¼ 4) ç»è£ 宿´é¨ç½²å ï¼backend(jar + application.yml + å¯å¨èæ¬ + æå¡å è£ å¨ + docs + web) / frontend/dist / db / README 4) ç»è£ 宿´é¨ç½²å ï¼backend(jar + application.yml + å¯å¨èæ¬ + æå¡å è£ å¨ + docs + web) / frontend/dist(å¯éï¼-NoFrontendDist 䏿) / db / README Windows æå¡å è£ å¨ WinSWï¼ä¼å ç¨ deploy-templates\tools\winsw.exe ç¼åï¼æ ¡éª SHA-256ï¼ï¼ 缺失æ¶èªå¨ä¸è½½ v2.12.0 x64 å°è¯¥ç¼åç®å½ï¼éå è¾åºä¸º backend\trafficAudit.exe 5) 对ç»è£ åçæ´å åæ«ä¸éææå¯é¥ @@ -22,17 +25,24 @@ param( [string]$OutRoot = '', [switch]$SkipFrontend, [switch]$NoFrontendDist, # å端å£åå¸ï¼ä¸æç¬ç«ç frontend\dist ç®å½ï¼ä½ä»æå»º/忥 backend\webï¼å端å£å¿ éï¼ [switch]$SkipBackend, # å¤ç¨å·²åå¨ç jarï¼ä¸éç¼è¯ãä¸å¨æºç é ç½®ï¼ï¼ä» ç¨äºè¡¥å ¨/éç»åå¸å [switch]$SkipServiceWrapper # 䏿 WinSW æå¡å è£ å¨æè¿å ï¼å³ä¸æä¾âæ³¨åæ Windows æå¡âç弿ºèªå¯ï¼ ) $ErrorActionPreference = 'Stop' $root = Split-Path -Parent $MyInvocation.MyCommand.Path $pomPath = Join-Path $root 'traffic-audit-server\pom.xml' if (-not (Test-Path $pomPath)) { throw "Missing Maven project: $pomPath" } [xml]$pom = Get-Content -LiteralPath $pomPath -Raw -Encoding UTF8 $productVersion = [string]$pom.project.version if (-not $productVersion) { throw 'Missing <version> in traffic-audit-server\pom.xml' } if ($productVersion -match '(?i)-SNAPSHOT$') { throw "Release builds must not use a SNAPSHOT version: $productVersion" } if (-not $OutRoot) { $OutRoot = Join-Path $root 'deploy' } # é»è®¤è¾åºå°ä»åº deploy\ï¼å·² gitignoreï¼ $tplDir = Join-Path $root 'deploy-templates' # æå 模æ¿ä»¶ï¼å·²å ¥åºï¼ $tplJarCfg = Join-Path $tplDir 'jar-application.yml' # è¿ jar çå ä½ç¬¦é ç½® $srcCfg = Join-Path $root 'traffic-audit-server\src\main\resources\application.yml' $jarRel = 'traffic-audit-server\target\traffic-audit-server-1.0.0-SNAPSHOT.jar' $jarRel = 'traffic-audit-server\target\traffic-audit-server.jar' $jar = Join-Path $root $jarRel $webDist = Join-Path $root 'traffic-audit-web\dist' @@ -82,6 +92,18 @@ $sr = New-Object System.IO.StreamReader($entry.Open()) $text = $sr.ReadToEnd() $sr.Close() $versionEntry = $zip.Entries | Where-Object { $_.FullName -eq 'BOOT-INF/classes/app-version.properties' } if (-not $versionEntry) { throw 'jar å ç¼ºå° BOOT-INF/classes/app-version.properties' } $vr = New-Object System.IO.StreamReader($versionEntry.Open()) $versionText = $vr.ReadToEnd() $vr.Close() $manifestEntry = $zip.Entries | Where-Object { $_.FullName -eq 'META-INF/MANIFEST.MF' } if (-not $manifestEntry) { throw 'jar å ç¼ºå° META-INF/MANIFEST.MF' } $mr = New-Object System.IO.StreamReader($manifestEntry.Open()) $manifestText = $mr.ReadToEnd() $mr.Close() } finally { $zip.Dispose() } function Get-SecretHits([string]$ymlText) { @@ -105,7 +127,21 @@ } Write-Host ' éè¿ï¼jar å ç½®é ç½®å ¨é¨ä¸ºå ä½ç¬¦ï¼æ ææå¯é¥/å£ä»¤ã' $builtVersion = '' if ($versionText -match '(?m)^\s*version\s*=\s*(.+?)\s*$') { $builtVersion = $matches[1].Trim() } if ($builtVersion -ne $productVersion) { throw "çæ¬ä¸ä¸è´ï¼pom.xml=$productVersion, app-version.properties=$builtVersion" } $manifestVersion = '' if ($manifestText -match '(?m)^Implementation-Version:\s*(.+?)\s*$') { $manifestVersion = $matches[1].Trim() } if ($manifestVersion -ne $productVersion) { throw "çæ¬ä¸ä¸è´ï¼pom.xml=$productVersion, MANIFEST.MF=$manifestVersion" } Write-Host " çæ¬èªæ£éè¿ï¼$productVersion (pom / app-version.properties / MANIFEST.MF)" # ---------- [3/5] å端æå»º ---------- # å端å£é¨ç½²å¿ é¡»æ backend\webï¼æä»¥å³ä½¿ -NoFrontendDist ä¹éè¦ distï¼åªæ -SkipFrontend æå¤ç¨ç°æ distã Write-Host '[3/5] æå»ºå端ï¼npm run buildï¼å¯ -SkipFrontend è·³è¿ï¼...' if (-not $SkipFrontend) { Push-Location (Join-Path $root 'traffic-audit-web') @@ -157,10 +193,14 @@ # ---------- [4/5] ç»è£ 宿´é¨ç½²ç®å½ ---------- Write-Host '[4/5] ç»è£ é¨ç½²ç®å½...' $stamp = Get-Date -Format 'yyyyMMdd' $dest = Join-Path $OutRoot ("traffic-audit-deploy-" + $stamp) $dest = Join-Path $OutRoot ("traffic-audit-deploy-" + $stamp + "-v" + $productVersion) $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 while (Test-Path $dest) { $dest = Join-Path $OutRoot ("traffic-audit-deploy-" + $stamp + "-v" + $productVersion + "-" + $n); $n++ } if ($NoFrontendDist) { New-Item -ItemType Directory -Force -Path (Join-Path $dest 'backend'), (Join-Path $dest 'db') | Out-Null } else { 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 @@ -175,11 +215,14 @@ Copy-Item (Join-Path $tplDir 'backend\install-service.ps1') (Join-Path $dest 'backend') -Force Copy-Item (Join-Path $tplDir 'backend\install-service.cmd') (Join-Path $dest 'backend') -Force } Copy-Item $webDist (Join-Path $dest 'frontend') -Recurse -Force Copy-Item (Join-Path $tplDir 'frontend\nginx.conf.example') (Join-Path $dest 'frontend') -Force if (-not $NoFrontendDist) { Copy-Item $webDist (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 # åç«¯å£ 8090 模å¼ï¼åç«¯ä» jar å级 web\ 读åç«¯éææä»¶ï¼å¿ é¡»å dist å 容ä¸è´ # åç«¯å£ 8090 模å¼ï¼åç«¯ä» jar å级 web\ 读åç«¯éææä»¶ï¼å¿ é¡»å dist å 容ä¸è´ã # å端å£åå¸ï¼-NoFrontendDistï¼åæ ·å¿ é¡»åæ¥ï¼å¦åé¡µé¢ 404ã Copy-Item $webDist (Join-Path $dest 'backend\web') -Recurse -Force # docsï¼è¿è¡æ¶æ¨¡æ¿ + ç¨æ·ææ¡£ï¼æé¤å¼åè¿ç¨ææ¡£ä¸ç¼å/å¤ä»½ï¼åå°ä½ç§¯ãé¿å æå 鍿¸ å带åºå»ï¼ @@ -192,7 +235,7 @@ # READMEï¼ç¨æ¨¡æ¿å¹¶æ¿æ¢æ¥æå ä½ $readme = Get-Content (Join-Path $tplDir 'README-é¨ç½²è¯´æ.txt') -Raw -Encoding UTF8 $readme = $readme.Replace('{stamp}', $stamp) $readme = $readme.Replace('{stamp}', $stamp).Replace('{version}', $productVersion) Set-Content -LiteralPath (Join-Path $dest 'README-é¨ç½²è¯´æ.txt') -Value $readme -Encoding UTF8 # ---------- [5/5] æ´å ææå¯é¥æ«æ + æ±æ» ---------- package-deploy.py
@@ -2,16 +2,21 @@ """TrafficAudit ä¸é®æå èæ¬ï¼å端æå»º -> å åµè¿å端 jar -> ç»è£ é¨ç½²å -> æ zip ç¨æ³ï¼python package-deploy.py [--no-build] """ import io, os, sys, shutil, zipfile, subprocess, datetime import io, os, sys, shutil, zipfile, subprocess, datetime, xml.etree.ElementTree as ET ROOT = os.path.dirname(os.path.abspath(__file__)) WEB = os.path.join(ROOT, "traffic-audit-web") SERVER = os.path.join(ROOT, "traffic-audit-server") STATIC = os.path.join(SERVER, "src", "main", "resources", "static") _pom_tree = ET.parse(os.path.join(SERVER, "pom.xml")) _pom_ns = {"m": "http://maven.apache.org/POM/4.0.0"} PRODUCT_VERSION = (_pom_tree.getroot().findtext("m:version", namespaces=_pom_ns) or "").strip() if not PRODUCT_VERSION: raise SystemExit("Missing <version> in traffic-audit-server/pom.xml") STAMP = datetime.datetime.now().strftime("%Y%m%d") STAGE = os.path.join(ROOT, "deploy", "traffic-audit-deploy-" + STAMP) ZIP = os.path.join(ROOT, "traffic-audit-deploy-" + STAMP + ".zip") JAR_NAME = "traffic-audit-server-1.0.0-SNAPSHOT.jar" STAGE = os.path.join(ROOT, "deploy", "traffic-audit-deploy-" + STAMP + "-v" + PRODUCT_VERSION) ZIP = os.path.join(ROOT, "traffic-audit-deploy-" + STAMP + "-v" + PRODUCT_VERSION + ".zip") JAR_NAME = "traffic-audit-server.jar" SKIP_DOC_DIRS = {"ai_demo", "usage_screenshots", "åè½æµè¯æ¥å", "工使¥ç»"} def rmtree_force(path): @@ -97,7 +102,7 @@ io.open(os.path.join(be, "start-backend.cmd"), "w", encoding="utf-8", newline="\r\n").write(START_CMD) io.open(os.path.join(be, "start-backend.sh"), "w", encoding="utf-8", newline="\n").write(START_SH) io.open(os.path.join(STAGE, "frontend", "nginx.conf.example"), "w", encoding="utf-8", newline="\n").write(NGINX) io.open(os.path.join(STAGE, "README-é¨ç½²è¯´æ.txt"), "w", encoding="utf-8", newline="\r\n").write(README) io.open(os.path.join(STAGE, "README-é¨ç½²è¯´æ.txt"), "w", encoding="utf-8", newline="\r\n").write(README.replace("{version}", PRODUCT_VERSION)) print("[5/6] æ zip ...") if os.path.isfile(ZIP): @@ -183,14 +188,14 @@ echo å端已å åµï¼ç´æ¥è®¿é® 8090 å³å¯ï¼ä¸éè¦ nginxï¼ echo æ¥å¿æ¾ç¤ºå¨æ¬çªå£ï¼å ³éçªå£å³åæ¢æå¡ echo ============================================ java -jar traffic-audit-server-1.0.0-SNAPSHOT.jar java -jar traffic-audit-server.jar pause """ START_SH = u"""#!/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 java -jar traffic-audit-server.jar 2>&1 | tee app.log """ NGINX = u"""# å端 nginx é 置示ä¾ï¼åªæå¨ä¸æ³ç¨ãjar å åµåç«¯ãæ¶æéè¦ï¼ @@ -210,12 +215,12 @@ } """ README = u"""TrafficAudit 交éè´§è¿ç»è®¡å®¡æ ¸ç³»ç» ââ é¨ç½²å ï¼{stamp}ï¼ README = u"""TrafficAudit 交éè´§è¿ç»è®¡å®¡æ ¸ç³»ç» ââ é¨ç½²å ï¼{stamp}ï¼çæ¬ v{version}ï¼ ======================================================== ãç®å½ç»æã backend/ traffic-audit-server-1.0.0-SNAPSHOT.jar å端ç¨åºï¼å«å ¨é¨ä¾èµï¼**å·²å åµå端**ï¼ traffic-audit-server.jar å端ç¨åºï¼å«å ¨é¨ä¾èµï¼**å·²å åµå端**ï¼åºå®æä»¶åï¼ docs/ æ¥è¡¨æ¨¡æ¿ä¸è¿è¡æ°æ®ç®å½ï¼å¿ éï¼å¿å ï¼ application.yml å¤é¨é ç½®ï¼æ¹è¿éå³å¯ï¼æ ééæ°æå ï¼ start-backend.cmd / start-backend.sh å¯å¨èæ¬ traffic-audit-server/pom.xml
@@ -10,7 +10,7 @@ </parent> <groupId>com.trafficaudit</groupId> <artifactId>traffic-audit-server</artifactId> <version>1.0.0-SNAPSHOT</version> <version>1.0.0</version> <packaging>jar</packaging> <name>traffic-audit-server</name> <properties> @@ -76,8 +76,36 @@ </dependency> </dependencies> <build> <finalName>traffic-audit-server</finalName> <resources> <resource> <directory>src/main/resources</directory> <filtering>false</filtering> <excludes> <exclude>app-version.properties</exclude> </excludes> </resource> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> <includes> <include>app-version.properties</include> </includes> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <addDefaultImplementationEntries>true</addDefaultImplementationEntries> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> traffic-audit-server/src/main/java/com/trafficaudit/holiday/service/HolidayTrendChartService.java
@@ -74,7 +74,8 @@ private static final Color MUTED_TEXT_COLOR = new Color(102, 102, 102); private static final double UNIT_LABEL_BAND = 58.0; private static final double RIGHT_LABEL_BAND = 74.0; private static final double ITEM_LABEL_GAP = 12.0; private static final double ITEM_LABEL_GAP = 10.0; private static final float HALO_OUTLINE_WIDTH = 4.0f; private static final Color ITEM_LABEL_COLOR = new Color(55, 55, 55); private static final Color[] SERIES_COLORS = { new Color(192, 80, 77), @@ -841,7 +842,6 @@ graphics.setFont(ITEM_LABEL_FONT); FontMetrics metrics = graphics.getFontMetrics(); List<Rectangle2D> placedLabels = new ArrayList<>(); List<Rectangle2D> markers = new ArrayList<>(); if (unitLabel != null && !unitLabel.isEmpty()) { placedLabels.add(new Rectangle2D.Double(0.0, 0.0, dataArea.getMinX() + 100.0, dataArea.getMinY() + 30.0)); @@ -849,13 +849,6 @@ int itemCount = 0; for (int seriesIndex = 0; seriesIndex < dataset.getSeriesCount(); seriesIndex++) { itemCount = Math.max(itemCount, dataset.getItemCount(seriesIndex)); for (int itemIndex = 0; itemIndex < dataset.getItemCount(seriesIndex); itemIndex++) { Point2D point = itemPoint(plot, dataset, seriesIndex, itemIndex, dataArea); if (point != null) { markers.add(new Rectangle2D.Double(point.getX() - 9.0, point.getY() - 9.0, 18.0, 18.0)); } } } for (int itemIndex = 0; itemIndex < itemCount; itemIndex++) { @@ -876,35 +869,95 @@ return Double.compare(first.getPoint().getY(), second.getPoint().getY()); } }); double previousBottom = Double.NEGATIVE_INFINITY; List<List<LineLabelCandidate>> candidateSets = new ArrayList<>(); for (DayLabel label : labels) { int width = metrics.stringWidth(label.getText()); int height = metrics.getHeight(); double x = label.getPoint().getX() - width / 2.0; double top = Math.max(label.getPoint().getY() - ITEM_LABEL_GAP - height, previousBottom + 3.0); Rectangle2D chosen = null; double bestPenalty = Double.MAX_VALUE; for (int step = 0; step <= 4; step++) { Rectangle2D candidate = boundToDataArea(new Rectangle2D.Double( x, top + step * (height + 4.0), width, height), dataArea); if (candidate == null) continue; double penalty = overlapArea(candidate, placedLabels) * 1000.0 + overlapArea(candidate, markers); if (penalty < bestPenalty) { bestPenalty = penalty; chosen = candidate; } if (penalty <= 0.0) break; List<LineLabelCandidate> candidates = lineLabelCandidates( label, metrics, dataArea); if (candidates.isEmpty()) { candidateSets.clear(); break; } if (chosen == null) continue; candidateSets.add(candidates); } if (candidateSets.isEmpty()) continue; // Dynamic programming keeps each label near its own point while preserving // the same top-to-bottom order as the data points for that date. List<int[]> backPointers = new ArrayList<>(); List<LineLabelCandidate> firstCandidates = candidateSets.get(0); double[] previousCosts = new double[firstCandidates.size()]; for (int i = 0; i < firstCandidates.size(); i++) { previousCosts[i] = lineLabelCost(firstCandidates.get(i), labels.get(0), labels, placedLabels); } for (int labelIndex = 1; labelIndex < candidateSets.size(); labelIndex++) { List<LineLabelCandidate> previousCandidates = candidateSets.get(labelIndex - 1); List<LineLabelCandidate> currentCandidates = candidateSets.get(labelIndex); double[] currentCosts = new double[currentCandidates.size()]; Arrays.fill(currentCosts, Double.MAX_VALUE); int[] back = new int[currentCandidates.size()]; Arrays.fill(back, -1); for (int currentIndex = 0; currentIndex < currentCandidates.size(); currentIndex++) { LineLabelCandidate current = currentCandidates.get(currentIndex); double currentCost = lineLabelCost(current, labels.get(labelIndex), labels, placedLabels); for (int previousIndex = 0; previousIndex < previousCandidates.size(); previousIndex++) { if (previousCosts[previousIndex] == Double.MAX_VALUE) continue; LineLabelCandidate previous = previousCandidates.get(previousIndex); if (previous.getRectangle().getCenterY() > current.getRectangle().getCenterY() + 0.1) { continue; } double overlapPenalty = overlapArea(previous.getRectangle(), Collections.singletonList(current.getRectangle())) * 1000000.0; double cost = previousCosts[previousIndex] + currentCost + overlapPenalty; if (cost < currentCosts[currentIndex]) { currentCosts[currentIndex] = cost; back[currentIndex] = previousIndex; } } } previousCosts = currentCosts; backPointers.add(back); } int bestLastIndex = -1; double bestCost = Double.MAX_VALUE; for (int i = 0; i < previousCosts.length; i++) { if (previousCosts[i] < bestCost) { bestCost = previousCosts[i]; bestLastIndex = i; } } if (bestLastIndex < 0) continue; int[] chosenIndexes = new int[candidateSets.size()]; chosenIndexes[chosenIndexes.length - 1] = bestLastIndex; for (int labelIndex = chosenIndexes.length - 2; labelIndex >= 0; labelIndex--) { int nextIndex = chosenIndexes[labelIndex + 1]; chosenIndexes[labelIndex] = backPointers.get(labelIndex)[nextIndex]; } for (int labelIndex = 0; labelIndex < chosenIndexes.length; labelIndex++) { DayLabel label = labels.get(labelIndex); Rectangle2D chosen = candidateSets.get(labelIndex) .get(chosenIndexes[labelIndex]).getRectangle(); drawLabelWithHalo(graphics, label.getText(), chosen, metrics, seriesColor(label.getSeriesIndex())); placedLabels.add(new Rectangle2D.Double(chosen.getX() - 5.0, chosen.getY() - 5.0, chosen.getWidth() + 10.0, chosen.getHeight() + 10.0)); previousBottom = chosen.getMaxY(); placedLabels.add(new Rectangle2D.Double(chosen.getX() - 4.0, chosen.getY() - 4.0, chosen.getWidth() + 8.0, chosen.getHeight() + 8.0)); } } } private static class LineLabelCandidate { private final Rectangle2D rectangle; private LineLabelCandidate(Rectangle2D rectangle) { this.rectangle = rectangle; } private Rectangle2D getRectangle() { return rectangle; } } private static class DayLabel { @@ -937,10 +990,99 @@ return new Point2D.Double(x, y); } private void addLabelCandidate(List<Rectangle2D> result, double x, double y, int width, int height, double dx, double dy) { result.add(new Rectangle2D.Double(x + dx - width / 2.0, y + dy - height / 2.0, width, height)); private List<LineLabelCandidate> lineLabelCandidates(DayLabel label, FontMetrics metrics, Rectangle2D dataArea) { List<LineLabelCandidate> result = new ArrayList<>(); int width = metrics.stringWidth(label.getText()); int height = metrics.getHeight(); double x = label.getPoint().getX(); double y = label.getPoint().getY(); double horizontalOffset = ITEM_LABEL_GAP + width / 2.0; double farHorizontalOffset = horizontalOffset + 18.0; double verticalOffset = ITEM_LABEL_GAP + height / 2.0; double farVerticalOffset = verticalOffset + height + 6.0; addLabelCandidate(result, x, y, width, height, 0.0, -verticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, 0.0, verticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, horizontalOffset, 0.0, dataArea); addLabelCandidate(result, x, y, width, height, -horizontalOffset, 0.0, dataArea); addLabelCandidate(result, x, y, width, height, farHorizontalOffset, 0.0, dataArea); addLabelCandidate(result, x, y, width, height, -farHorizontalOffset, 0.0, dataArea); addLabelCandidate(result, x, y, width, height, horizontalOffset, -verticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, horizontalOffset, verticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, -horizontalOffset, -verticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, -horizontalOffset, verticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, farHorizontalOffset, -verticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, farHorizontalOffset, verticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, -farHorizontalOffset, -verticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, -farHorizontalOffset, verticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, 0.0, -farVerticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, 0.0, farVerticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, horizontalOffset, -farVerticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, horizontalOffset, farVerticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, -horizontalOffset, -farVerticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, -horizontalOffset, farVerticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, farHorizontalOffset, -farVerticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, farHorizontalOffset, farVerticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, -farHorizontalOffset, -farVerticalOffset, dataArea); addLabelCandidate(result, x, y, width, height, -farHorizontalOffset, farVerticalOffset, dataArea); return result; } private void addLabelCandidate(List<LineLabelCandidate> result, double x, double y, int width, int height, double dx, double dy, Rectangle2D dataArea) { Rectangle2D candidate = boundToDataArea(new Rectangle2D.Double( x + dx - width / 2.0, y + dy - height / 2.0, width, height), dataArea); if (candidate == null) return; for (LineLabelCandidate existing : result) { if (Math.abs(existing.getRectangle().getX() - candidate.getX()) < 0.5 && Math.abs(existing.getRectangle().getY() - candidate.getY()) < 0.5) { return; } } result.add(new LineLabelCandidate(candidate)); } private double lineLabelCost(LineLabelCandidate candidate, DayLabel label, List<DayLabel> sameDayLabels, List<Rectangle2D> placedLabels) { Rectangle2D rectangle = candidate.getRectangle(); double dx = rectangle.getCenterX() - label.getPoint().getX(); double dy = rectangle.getCenterY() - label.getPoint().getY(); double ownDistance = Math.sqrt(dx * dx + dy * dy); double cost = ownDistance * 10.0 + overlapArea(rectangle, placedLabels) * 10000.0; Rectangle2D ownMarker = new Rectangle2D.Double(label.getPoint().getX() - 9.0, label.getPoint().getY() - 9.0, 18.0, 18.0); if (rectangle.intersects(ownMarker)) cost += 3000.0; for (DayLabel other : sameDayLabels) { if (other == label) continue; double otherDx = rectangle.getCenterX() - other.getPoint().getX(); double otherDy = rectangle.getCenterY() - other.getPoint().getY(); double otherDistance = Math.sqrt(otherDx * otherDx + otherDy * otherDy); if (otherDistance < ownDistance - 1.0) { cost += (ownDistance - otherDistance) * 5000.0; } Rectangle2D otherMarker = new Rectangle2D.Double(other.getPoint().getX() - 9.0, other.getPoint().getY() - 9.0, 18.0, 18.0); if (rectangle.intersects(otherMarker)) cost += 2000.0; } return cost; } private Rectangle2D boundToDataArea(Rectangle2D rectangle, Rectangle2D dataArea) { @@ -979,14 +1121,18 @@ FontMetrics metrics, Color color) { int x = (int) Math.round(rectangle.getX()); int y = (int) Math.round(rectangle.getY() + metrics.getAscent()); // A stroked white outline keeps the digit legible even when a line runs // underneath it, which lets labels stay right next to their own point. java.awt.font.TextLayout layout = new java.awt.font.TextLayout( text, graphics.getFont(), graphics.getFontRenderContext()); java.awt.Shape outline = layout.getOutline( java.awt.geom.AffineTransform.getTranslateInstance(x, y)); graphics.setColor(Color.WHITE); for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { if (dx != 0 || dy != 0) graphics.drawString(text, x + dx, y + dy); } } graphics.setStroke(new BasicStroke(HALO_OUTLINE_WIDTH, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); graphics.draw(outline); graphics.setColor(color); graphics.drawString(text, x, y); graphics.fill(outline); } public static final List<String> DAY_LABELS = Arrays.asList( traffic-audit-server/src/main/java/com/trafficaudit/security/config/SecurityConfig.java
@@ -35,7 +35,7 @@ .authorizeRequests() // ç»å½åå¿ é¡»å¯ç¨çæ¥å£ .antMatchers("/api/auth/login", "/api/auth/captcha", "/api/auth/captcha/verify", "/api/auth/captcha-config").permitAll() "/api/auth/captcha-config", "/api/version").permitAll() // å端éæèµæº .antMatchers("/", "/index.html", "/favicon.ico", "/js/**", "/css/**", "/fonts/**", "/img/**", "/static/**").permitAll() .anyRequest().permitAll() @@ -44,4 +44,4 @@ .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } } } traffic-audit-server/src/main/java/com/trafficaudit/security/filter/JwtAuthFilter.java
@@ -27,7 +27,8 @@ "/api/auth/login", "/api/auth/captcha", "/api/auth/captcha/verify", "/api/auth/captcha-config" "/api/auth/captcha-config", "/api/version" ); @Resource @@ -61,4 +62,4 @@ request.setAttribute("loginUser", username); chain.doFilter(request, response); } } } traffic-audit-server/src/main/java/com/trafficaudit/system/controller/VersionController.java
New file @@ -0,0 +1,83 @@ package com.trafficaudit.system.controller; import com.trafficaudit.common.Result; import org.springframework.core.io.ClassPathResource; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.InputStream; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; /** * Exposes the product version so the UI (and operators) can confirm which build * is actually running on a server. Kept public on purpose: the login page shows * the version before any token exists. */ @RestController @RequestMapping("/api") public class VersionController { private static final String VERSION_FILE = "app-version.properties"; @GetMapping("/version") public Result<Map<String, Object>> version() { Map<String, Object> info = new LinkedHashMap<>(); info.put("version", readVersion()); info.put("buildTime", resolveBuildTime()); return Result.ok(info); } private String readVersion() { try (InputStream in = new ClassPathResource(VERSION_FILE).getInputStream()) { Properties props = new Properties(); props.load(in); String value = props.getProperty("version"); return value == null || value.trim().isEmpty() ? "unknown" : value.trim(); } catch (Exception e) { return "unknown"; } } /** * Build time of the running artifact. In a packaged run this is the jar file's * last-modified time; when running from exploded classes (mvn spring-boot:run) * there is no jar, so the value is null. */ private String resolveBuildTime() { try { URI location = VersionController.class.getProtectionDomain() .getCodeSource().getLocation().toURI(); Path path = resolveArtifactPath(location); if (path != null && Files.isRegularFile(path)) { return Files.getLastModifiedTime(path).toInstant().toString(); } } catch (Exception ignored) { // Fall through: build time is optional information. } return null; } /** * Spring Boot executable jars expose a nested classpath such as * "jar:file:/app.jar!/BOOT-INF/classes!/". Strip the nested part before * asking the filesystem for the jar timestamp. */ private Path resolveArtifactPath(URI location) { String value = location.toString(); int nested = value.indexOf("!/"); if (nested >= 0) { value = value.substring(0, nested); } if (value.startsWith("jar:")) { value = value.substring(4); } return Paths.get(URI.create(value)); } } traffic-audit-server/src/main/resources/app-version.properties
New file @@ -0,0 +1,3 @@ # Supplied from pom.xml during Maven resource filtering. # Bump <version> in pom.xml on every release; do not hard-code a version here. version=@project.version@ traffic-audit-web/src/components/AppHeader.vue
@@ -1,6 +1,9 @@ <template> <el-header class="app-header"> <span class="header-title">{{ title }}</span> <span class="header-title"> {{ systemName }}<span class="header-version" v-if="version">V{{ version }}</span> <template v-if="pageName"> - {{ pageName }}</template> </span> <div class="header-right"> <span class="header-user"> <i class="el-icon-user"></i> {{ username }} @@ -43,6 +46,7 @@ }, data() { return { version: '', pwdDialogVisible: false, pwdSaving: false, pwdForm: { oldPassword: '', newPassword: '', confirmPassword: '' }, @@ -65,7 +69,22 @@ computed: { username() { return sessionStorage.getItem('username') || 'admin' }, // The system name always comes first so the version can sit right after it. systemName() { return this.title.split(' - ')[0] }, pageName() { const parts = this.title.split(' - ') return parts.length > 1 ? parts.slice(1).join(' - ') : '' } }, created() { api.get('/version') .then(res => { if (res && res.data && res.data.version) this.version = res.data.version }) .catch(() => {}) }, methods: { openChangePwd() { @@ -134,6 +153,14 @@ line-height: normal; } .header-user { opacity: 0.92; } .header-version { font-size: 12px; line-height: 1.6; margin-left: 8px; padding: 2px 8px; border-radius: 8px; background: rgba(255, 255, 255, 0.18); } .header-link { color: #fff; font-size: 14px; padding: 0; } .header-link:hover { color: #ffd04b; } .header-logout { color: #fff; font-size: 14px; padding: 0; } traffic-audit-web/src/views/Login.vue
@@ -1,7 +1,7 @@ <template> <div class="login-container"> <el-card class="login-card"> <h2>交éç»è®¡æ¥è¡¨å®¡æ ¸ç³»ç»</h2> <h2>交éç»è®¡æ¥è¡¨å®¡æ ¸ç³»ç»<span class="login-version" v-if="version">V{{ version }}</span></h2> <el-form ref="form" :model="form" :rules="rules"> <el-form-item prop="username"> <el-input v-model="form.username" placeholder="ç¨æ·å" @keyup.enter.native="login"></el-input> @@ -40,10 +40,17 @@ captchaId: '', clickIds: [], loggingIn: false, captchaRequired: false captchaRequired: false, version: '' } }, created() { axios.get('/api/version', { timeout: 10000 }) .then(res => { const d = res.data if (d && d.data && d.data.version) this.version = d.data.version }) .catch(() => {}) // éªè¯ç æ¯å¦å¯ç¨ä¸å¾ä»¥å端 /api/auth/captcha-config 为åï¼ // é»è®¤ä¸æ¾ç¤ºï¼åªæå端æç¡®è¿å required=true ææ¸²æéªè¯ç ç»ä»¶ã // ä¿®å¤ï¼æ¤åé»è®¤ true ä¸è¯·æ±å¤±è´¥æ¶éé»å¿½ç¥ï¼åæ¢ç½ç»å该é 置读ä¸å°ï¼ @@ -114,5 +121,6 @@ .captcha-skip-tip { font-size: 12px; color: #909399; line-height: 1.6; padding: 8px 10px; background: #F5F7FA; border-radius: 4px; } .login-card { width: 400px; } .login-card h2 { text-align: center; margin-bottom: 18px; } .login-version { font-size: 13px; font-weight: normal; color: #909399; margin-left: 8px; vertical-align: 3px; } .login-card .el-form-item { margin-bottom: 16px; } </style>