From 8b71049bcca61723d586c7c8bceb791c82af7601 Mon Sep 17 00:00:00 2001
From: xyc <jc_xiong@hotmail.com>
Date: 星期一, 05 十月 2026 08:31:22 +0800
Subject: [PATCH] feat(version): add fixed-jar release version management

---
 traffic-audit-server/src/main/java/com/trafficaudit/system/controller/VersionController.java |   83 +++++++++++++
 deploy-templates/backend/install-service.ps1                                                 |    4 
 deploy-templates/backend/start-backend.sh                                                    |    2 
 deploy-templates/backend/install-autostart.ps1                                               |    2 
 deploy-templates/backend/start-backend-service.cmd                                           |    2 
 traffic-audit-server/src/main/resources/app-version.properties                               |    3 
 traffic-audit-web/src/components/AppHeader.vue                                               |   29 ++++
 deploy-templates/backend/start-backend.cmd                                                   |    2 
 HANDOFF.md                                                                                   |   13 ++
 deploy-templates/README-部署说明.txt                                                             |   14 ++
 traffic-audit-server/src/main/java/com/trafficaudit/security/filter/JwtAuthFilter.java       |    5 
 traffic-audit-web/src/views/Login.vue                                                        |   12 +
 docs/打包部署说明.md                                                                               |   48 ++++++-
 traffic-audit-server/pom.xml                                                                 |   30 ++++
 package-deploy.py                                                                            |   23 ++-
 deploy-templates/backend/trafficAudit.xml                                                    |    2 
 pack-deploy.ps1                                                                              |   39 +++++
 traffic-audit-server/src/main/java/com/trafficaudit/security/config/SecurityConfig.java      |    4 
 18 files changed, 277 insertions(+), 40 deletions(-)

diff --git a/HANDOFF.md b/HANDOFF.md
index d04e7b3..f785602 100644
--- a/HANDOFF.md
+++ b/HANDOFF.md
@@ -4,7 +4,18 @@
 > **姣忓ぉ缁撴潫鏃�**锛氭妸褰撳ぉ杩涘睍銆佽俯杩囩殑鍧戙�佹柊闇�姹傘�佹湭瀹屾垚浜嬮」鏇存柊鍒版湰鏂囦欢锛岀劧鍚庡彲浠ユ斁蹇冨紑鏂板璇濄��
 > **鍗忎綔绾﹀畾锛�2026-08-31锛�**锛氳銆屾彁浜ゃ��= 鏈湴 commit + 鎺ㄩ�佸埌杩滅 git锛坥rigin main锛孏itblit锛夛紝涓嶅啀鍙仛鏈湴 commit銆�
 
-## 鏈�鏂帮紙2026-10-04锛�
+## 鏈�鏂帮紙2026-10-05锛�
+
+### 10-05锛氱増鏈鐞嗛噰鐢ㄦ柟妗� A锛堝浐瀹� jar 鍚嶏紝宸插畬鎴愬苟閫氳繃娴嬭瘯锛�
+
+- **鐢ㄦ埛鍐崇瓥**锛氶噰鐢ㄦ柟妗� A銆俙traffic-audit-server/pom.xml` 涓哄敮涓�鐗堟湰婧愶紝褰撳墠瀵瑰鐗堟湰 `1.0.0`銆�
+- **鏋勫缓鏀归��**锛歚<finalName>traffic-audit-server</finalName>`锛屼骇鐗╁浐瀹氫负 `target/traffic-audit-server.jar`锛汳aven 璧勬簮杩囨护鐢熸垚 `app-version.properties`锛圫pring Boot 鐖� POM 浣跨敤 `@project.version@` 鍒嗛殧绗︼級锛宍maven-jar-plugin` 鍐欏叆 `Implementation-Version`銆�
+- **椤甸潰鏄剧ず**锛氭柊澧炲厤鐧诲綍 `GET /api/version`锛涚櫥褰曢〉鍜岄〉澶存樉绀� `V1.0.0`銆傚悓鏃朵慨澶嶅彲鎵ц jar 涓� `buildTime` 涓� `null` 鐨勫祵濂� URI 瑙f瀽闂銆�
+- **鍙戝竷閾捐矾**锛氭湇鍔� 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`锛堢洰褰曚笉鍏ュ簱锛夈��
+- **鐘舵��**锛氫唬鐮佷笌鏂囨。宸叉敼瀹岋紝鍑嗗鎻愪氦骞舵墦 `v1.0.0` 鏍囩锛涘皻鏈噸鏂扮敓鎴愭寮忓彂甯冨寘銆�
+
 ### 10-04 娣卞锛氭姌绾挎暟鍊兼爣绛惧洖鍒拌创杩戞湰鐐癸紙v35锛屽緟鐢ㄦ埛楠屾敹锛�
 
 - **鐢ㄦ埛鍙嶉**锛歷31銆岄伩寮�鎶樼嚎銆嶇増鏈笉濡備笂涓�鐗� v27 鈥斺�� 铏界劧鏍囩涓嶅帇绾夸簡锛屼絾寰堝鏍囩绂昏嚜宸辩殑鏁版嵁鐐瑰お杩滐紝涓�鐪肩湅涓嶅嚭鏍囩殑鏄摢涓偣銆傝姹傚洖鍒拌创杩戞湰鐐圭殑鏁堟灉銆�
diff --git "a/deploy-templates/README-\351\203\250\347\275\262\350\257\264\346\230\216.txt" "b/deploy-templates/README-\351\203\250\347\275\262\350\257\264\346\230\216.txt"
index c14e75a..81fa0a9 100644
--- "a/deploy-templates/README-\351\203\250\347\275\262\350\257\264\346\230\216.txt"
+++ "b/deploy-templates/README-\351\203\250\347\275\262\350\257\264\346\230\216.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     鍓嶅彴鍚姩鑴氭湰锛堜汉宸ヨ皟璇曠敤锛屽叧浜嗙獥鍙e氨鍋滐級
@@ -64,6 +64,16 @@
   - 鑴氭湰瑕佹眰锛歩nstall-service.ps1 鍦� Windows PowerShell 2.0+ 涓嬪彲鐢紙Win7 鑷甫 2.0锛屽凡鍋氬吋瀹癸級锛�
     3.2 鐨勮鍒掍换鍔¤剼鏈敤鍒颁簡 New-ScheduledTask* 鍛戒护锛岄渶瑕� Win8 / Server 2012 鍙婁互涓娿��
 
+銆愪粠鏈増璧凤細鍚庣 jar 浣跨敤鍥哄畾鏂囦欢鍚嶃��
+  backend\traffic-audit-server.jar
+  - 鏂版湇鍔″櫒锛氭寜涓嬮潰鐨勬櫘閫氶儴缃叉楠ゆ墽琛屽嵆鍙��
+  - 宸插畨瑁呮棫鐗堟湇鍔$殑鏈嶅姟鍣紙棣栨鍗囩骇鍒版湰鐗堬級锛�
+      1) 鍋滄鏈嶅姟锛歋top-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
diff --git a/deploy-templates/backend/install-autostart.ps1 b/deploy-templates/backend/install-autostart.ps1
index 7507d98..fed0b89 100644
--- a/deploy-templates/backend/install-autostart.ps1
+++ b/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."
 }
 
diff --git a/deploy-templates/backend/install-service.ps1 b/deploy-templates/backend/install-service.ps1
index 98373d6..8ff8a19 100644
--- a/deploy-templates/backend/install-service.ps1
+++ b/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.
diff --git a/deploy-templates/backend/start-backend-service.cmd b/deploy-templates/backend/start-backend-service.cmd
index e3e8c16..eb6c71c 100644
--- a/deploy-templates/backend/start-backend-service.cmd
+++ b/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
diff --git a/deploy-templates/backend/start-backend.cmd b/deploy-templates/backend/start-backend.cmd
index 5ddb279..b2db8f6 100644
--- a/deploy-templates/backend/start-backend.cmd
+++ b/deploy-templates/backend/start-backend.cmd
@@ -22,5 +22,5 @@
 echo  鍓嶇宸插唴宓岋紝鐩存帴璁块棶 8090 鍗冲彲锛堜笉闇�瑕� nginx锛�
 echo  鏃ュ織鏄剧ず鍦ㄦ湰绐楀彛锛屽叧闂獥鍙e嵆鍋滄鏈嶅姟
 echo ============================================
-java -jar traffic-audit-server-1.0.0-SNAPSHOT.jar
+java -jar traffic-audit-server.jar
 pause
diff --git a/deploy-templates/backend/start-backend.sh b/deploy-templates/backend/start-backend.sh
index 6d2dd7f..8a18884 100644
--- a/deploy-templates/backend/start-backend.sh
+++ b/deploy-templates/backend/start-backend.sh
@@ -7,4 +7,4 @@
   echo "[WARN] application.yml 閲� spring.datasource.password 杩樻槸鍗犱綅鏂囧瓧锛岃鍏堟敼鎴愮湡瀹� MySQL 瀵嗙爜"
 fi
 echo "TrafficAudit 鍚姩涓細http://localhost:8090 锛圕trl+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
diff --git a/deploy-templates/backend/trafficAudit.xml b/deploy-templates/backend/trafficAudit.xml
index 6c6919a..43e5234 100644
--- a/deploy-templates/backend/trafficAudit.xml
+++ b/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. -->
diff --git "a/docs/\346\211\223\345\214\205\351\203\250\347\275\262\350\257\264\346\230\216.md" "b/docs/\346\211\223\345\214\205\351\203\250\347\275\262\350\257\264\346\230\216.md"
index 9b69a64..c166e81 100644
--- "a/docs/\346\211\223\345\214\205\351\203\250\347\275\262\350\257\264\346\230\216.md"
+++ "b/docs/\346\211\223\345\214\205\351\203\250\347\275\262\350\257\264\346\230\216.md"
@@ -1,6 +1,6 @@
 # 浜ら�氱粺璁℃姤琛ㄥ鏍哥郴缁� 鈥� 鎵撳寘涓庨儴缃茶鏄�
 
-> 閫傜敤锛歮ain 鍒嗘敮锛堟渶鏂版洿鏂� 2026-09-13锛夈�傚悗绔� Spring Boot 2.7锛圝ava 8锛夛紝鍓嶇 Vue 2 + Element UI銆�
+> 閫傜敤锛歮ain 鍒嗘敮锛堟渶鏂版洿鏂� 2026-10-05锛夈�傚悗绔� Spring Boot 2.7锛圝ava 8锛夛紝鍓嶇 Vue 2 + Element UI銆�
 > 鏈枃妗i潰鍚戝紑鍙戜笌閮ㄧ讲鍚屼簨锛屽懡浠ゅ潎鍦� 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`銆乯ar 鍐� `app-version.properties`銆乯ar 娓呭崟涓夎�呯増鏈竴鑷达紝涓嶄竴鑷寸洿鎺ヤ腑姝㈡墦鍖�
+
+鍙戞柊鐗堟湰娴佺▼锛�
+
+1. 鍏堟敼 `traffic-audit-server/pom.xml` 鐨� `<version>`锛屼緥濡� `1.0.1`
+2. 杩愯 `pack-deploy.ps1` 鎵撳寘
+3. 鎻愪氦浠g爜骞舵墦 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`锛沯ar 鏀逛负鍐呭祵鍓嶇锛堥儴缃叉満涓嶅啀闇�瑕� 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. **瑙e帇 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 鍗曠鍙fā寮忥級锛屼互鍙� 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 鍗曠鍙fā寮忥級锛屼互鍙� 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: "浣犵殑鏂発ey"`锛堢暀绌哄垯椤甸潰 AI 鍒嗘瀽涓嶅彲鐢紝鍏朵綑鍔熻兘姝e父锛夛紱
-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 鎻愪氦杩涗换浣曚粨搴撴枃浠躲��
 
diff --git a/pack-deploy.ps1 b/pack-deploy.ps1
index c0bd2cb..98f78f9 100644
--- a/pack-deploy.ps1
+++ b/pack-deploy.ps1
@@ -32,11 +32,17 @@
 
 $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'
 
@@ -86,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) {
@@ -108,6 +126,19 @@
   Write-Host '      璀﹀憡锛歫ar 鍐呯疆 password 涓嶆槸 TRAFFIC_DB_PASSWORD 鍗犱綅绗﹀舰寮忥紝璇风‘璁ゆā鏉裤��'
 }
 Write-Host '      閫氳繃锛歫ar 鍐呯疆閰嶇疆鍏ㄩ儴涓哄崰浣嶇锛屾棤鏄庢枃瀵嗛挜/鍙d护銆�'
+
+$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] 鍓嶇鏋勫缓 ----------
 # 鍗曠鍙i儴缃插繀椤绘湁 backend\web锛屾墍浠ュ嵆浣� -NoFrontendDist 涔熼渶瑕� dist锛涘彧鏈� -SkipFrontend 鎵嶅鐢ㄧ幇鏈� dist銆�
@@ -162,9 +193,9 @@
 # ---------- [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++ }
+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 {
@@ -204,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] 鏁村寘鏄庢枃瀵嗛挜鎵弿 + 姹囨�� ----------
diff --git a/package-deploy.py b/package-deploy.py
index 41d0c19..2e2149c 100644
--- a/package-deploy.py
+++ b/package-deploy.py
@@ -2,16 +2,21 @@
 """TrafficAudit 涓�閿墦鍖呰剼鏈細鍓嶇鏋勫缓 -> 鍐呭祵杩涘悗绔� jar -> 缁勮閮ㄧ讲鍖� -> 鎵� zip
 鐢ㄦ硶锛歱ython 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  鏃ュ織鏄剧ず鍦ㄦ湰绐楀彛锛屽叧闂獥鍙e嵆鍋滄鏈嶅姟
 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 锛圕trl+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 閰嶇疆绀轰緥锛堝彧鏈夊湪涓嶆兂鐢ㄣ�宩ar 鍐呭祵鍓嶇銆嶆椂鎵嶉渶瑕侊級
@@ -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     鍚姩鑴氭湰
diff --git a/traffic-audit-server/pom.xml b/traffic-audit-server/pom.xml
index a146b34..dea28f5 100644
--- a/traffic-audit-server/pom.xml
+++ b/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>
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/security/config/SecurityConfig.java b/traffic-audit-server/src/main/java/com/trafficaudit/security/config/SecurityConfig.java
index 6adee4c..f1e60cd 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/security/config/SecurityConfig.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/security/config/SecurityConfig.java
@@ -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();
     }
-}
\ No newline at end of file
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/security/filter/JwtAuthFilter.java b/traffic-audit-server/src/main/java/com/trafficaudit/security/filter/JwtAuthFilter.java
index 0b5226b..1cf05f4 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/security/filter/JwtAuthFilter.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/security/filter/JwtAuthFilter.java
@@ -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);
     }
-}
\ No newline at end of file
+}
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/system/controller/VersionController.java b/traffic-audit-server/src/main/java/com/trafficaudit/system/controller/VersionController.java
new file mode 100644
index 0000000..a4d9de6
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/system/controller/VersionController.java
@@ -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));
+    }
+}
diff --git a/traffic-audit-server/src/main/resources/app-version.properties b/traffic-audit-server/src/main/resources/app-version.properties
new file mode 100644
index 0000000..af2a560
--- /dev/null
+++ b/traffic-audit-server/src/main/resources/app-version.properties
@@ -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@
diff --git a/traffic-audit-web/src/components/AppHeader.vue b/traffic-audit-web/src/components/AppHeader.vue
index aeec948..f077c9a 100644
--- a/traffic-audit-web/src/components/AppHeader.vue
+++ b/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; }
diff --git a/traffic-audit-web/src/views/Login.vue b/traffic-audit-web/src/views/Login.vue
index fbe98ac..c08362c 100644
--- a/traffic-audit-web/src/views/Login.vue
+++ b/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>

--
Gitblit v1.9.1