xyc
2 天以前 8b71049bcca61723d586c7c8bceb791c82af7601
feat(version): add fixed-jar release version management
2个文件已添加
16个文件已修改
313 ■■■■■ 已修改文件
HANDOFF.md 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-templates/README-部署说明.txt 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-templates/backend/install-autostart.ps1 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-templates/backend/install-service.ps1 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-templates/backend/start-backend-service.cmd 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-templates/backend/start-backend.cmd 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-templates/backend/start-backend.sh 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-templates/backend/trafficAudit.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/打包部署说明.md 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pack-deploy.ps1 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-deploy.py 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/pom.xml 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/security/config/SecurityConfig.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/security/filter/JwtAuthFilter.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/system/controller/VersionController.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/resources/app-version.properties 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-web/src/components/AppHeader.vue 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-web/src/views/Login.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HANDOFF.md
@@ -4,7 +4,18 @@
> **每天结束时**:把当天进展、踩过的坑、新需求、未完成事项更新到本文件,然后可以放心开新对话。
> **协作约定(2026-08-31)**:说「提交」= æœ¬åœ° commit + æŽ¨é€åˆ°è¿œç«¯ git(origin main,Gitblit),不再只做本地 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`;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`(目录不入库)。
- **状态**:代码与文档已改完,准备提交并打 `v1.0.0` æ ‡ç­¾ï¼›å°šæœªé‡æ–°ç”Ÿæˆæ­£å¼å‘布包。
### 10-04 æ·±å¤œï¼šæŠ˜çº¿æ•°å€¼æ ‡ç­¾å›žåˆ°è´´è¿‘本点(v35,待用户验收)
- **用户反馈**:v31「避开折线」版本不如上一版 v27 â€”— è™½ç„¶æ ‡ç­¾ä¸åŽ‹çº¿äº†ï¼Œä½†å¾ˆå¤šæ ‡ç­¾ç¦»è‡ªå·±çš„æ•°æ®ç‚¹å¤ªè¿œï¼Œä¸€çœ¼çœ‹ä¸å‡ºæ ‡çš„æ˜¯å“ªä¸ªç‚¹ã€‚è¦æ±‚å›žåˆ°è´´è¿‘æœ¬ç‚¹çš„æ•ˆæžœã€‚
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
@@ -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 '      è­¦å‘Šï¼šjar å†…ç½® password ä¸æ˜¯ TRAFFIC_DB_PASSWORD å ä½ç¬¦å½¢å¼ï¼Œè¯·ç¡®è®¤æ¨¡æ¿ã€‚'
}
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。
@@ -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] æ•´åŒ…明文密钥扫描 + æ±‡æ€» ----------
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/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()
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
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>