xyc
4 天以前 02c8117f54a6d6a8b1e6f588b1e03abf34284f60
feat(部署): 新增 Windows 服务方式开机自启(WinSW)并随包分发
4个文件已添加
5个文件已修改
630 ■■■■■ 已修改文件
.gitignore 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HANDOFF.md 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-templates/README-部署说明.txt 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-templates/backend/install-autostart.ps1 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-templates/backend/install-service.ps1 235 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-templates/backend/start-backend-service.cmd 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-templates/backend/trafficAudit.xml 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/打包部署说明.md 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pack-deploy.ps1 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -72,6 +72,9 @@
web/
traffic-audit-server/web/
# ==== Windows æœåŠ¡åŒ…è£…å™¨ï¼ˆWinSW äºŒè¿›åˆ¶ï¼Œ17.4MB;由 pack-deploy.ps1 ä¸‹è½½å¹¶æ ¡éªŒ SHA-256,不入库)====
deploy-templates/tools/
# ==== éƒ¨ç½²äº§ç‰©ï¼ˆä¸å…¥åº“)====
deploy/
traffic-audit-deploy-*.zip
HANDOFF.md
@@ -28,6 +28,22 @@
- **Git/工作区(本段更新前)**:`HEAD = origin/main = 37f5209`;当前无已跟踪文件改动。未跟踪项有 `_tmp_holiday_verify/`(历史验证产物)和 `docs.zip`(约 31.6 MB,用户本地产物);二者均保留,新对话不要误删或误提交。
- **下一步**:如需部署,直接使用上面发布目录或 zip;如需继续修复发布工具,先实现“占位符模板自动切换 + `backend/web` è‡ªåŠ¨ç»„è£… + jar åŽ»å¯†è‡ªæ£€â€åŽå†é‡æ–°äº§å‡ºå‘å¸ƒåŒ…ã€‚
### Windows å¼€æœºè‡ªå¯ï¼ˆ10-03 è¿½åŠ ï¼Œç”¨æˆ·é€‰â€œæ–¹æ¡ˆ B”:注册成 Windows æœåŠ¡ï¼‰
- **先回答过的问题**:`start-backend.cmd` **不能**直接做开机自启(末尾有 `pause`、无日志重定向、依赖 PATH)。给出 A(计划任务)/B(Windows æœåŠ¡ï¼‰/C ä¸‰æ–¹æ¡ˆåŽï¼Œç”¨æˆ·é€‰ **B**。
- **新增模板(已入库)**:
  - `deploy-templates/backend/trafficAudit.xml`:WinSW æœåŠ¡é…ç½®â€”â€”id `TrafficAuditBackend`、`startmode=Automatic` + `delayedAutoStart=true`(等 MySQL å…ˆå°±ç»ªï¼‰ã€`onfailure restart 10s/30s/60s` + `resetfailure 1 hour`、`workingdirectory=%BASE%`、日志 `logs\trafficAudit.out.log|err.log`(10MB Ã— 10 æ»šåŠ¨ï¼‰ã€‚
  - `deploy-templates/backend/install-service.ps1`:管理员安装/查看/重启/删除(`-Status/-Restart/-Remove/-NoStart/-JavaPath`);自动定位 java(JAVA_HOME â†’ PATH â†’ JavaSoft æ³¨å†Œè¡¨ â†’ Program Files)并把绝对路径写进 XML çš„ `executable` å…ƒç´ ï¼›é‡å¤æ‰§è¡Œå¹‚等;非管理员直接拒绝。
  - `start-backend-service.cmd` + `install-autostart.ps1`(计划任务方式)保留为**备选**;注意 `New-ScheduledTask*` éœ€ Win8/Server 2012+,且**不可与服务方式同时启用**(会抢 8090)。
  - `pack-deploy.ps1`:新增 WinSW èŽ·å–ï¼ˆç¼“å­˜ `deploy-templates/tools/winsw.exe` å¹¶æ ¡ SHA-256,缺则从 GitHub ä¸‹è½½ v2.12.0 x64)与 `-SkipServiceWrapper`;包内输出 `backend\trafficAudit.exe`(改名以匹配 XML æ–‡ä»¶åï¼‰ã€‚`.gitignore` æ–°å¢ž `deploy-templates/tools/`(17.4MB äºŒè¿›åˆ¶ä¸å…¥åº“)。
- **WinSW = Windows Service Wrapper**:把普通程序包成 Windows æœåŠ¡ã€‚java.exe è‡ªå·±ä¸æ˜¯æœåŠ¡ç¨‹åºï¼Œç›´æŽ¥ `sc create` ä¼šæŠ¥â€œæœåŠ¡æ²¡æœ‰åŠæ—¶å“åº”å¯åŠ¨æˆ–æŽ§åˆ¶è¯·æ±‚â€ï¼›WinSW å®žçŽ°äº†æœåŠ¡æŽ§åˆ¶åè®®å†æ‹‰èµ· `java -jar`,并管重启与日志。
- **实测验证(本机,未真正安装服务)**:`trafficAudit.exe status` èƒ½è¯»æ‰“包 XML å¹¶è¾“出 `NonExistent`(解析通过);故意写坏 XML â†’ `FATAL - The configuration file cound not be loaded`(证明该校验路径真实有效);`install-service.ps1 -Status` æ­£å¸¸è¾“出“未安装”,无参数 / `-Remove` åœ¨éžç®¡ç†å‘˜ä¸‹æŠ›â€œå¿…须管理员”且不产生注册项。
- **踩坑(重要)**:XML æ”¹å†™æœ€åˆç”¨æ­£åˆ™ `(?s)<executable>.*?</executable>`,会**先命中注释里的 `<executable>` å­—æ ·**,把注释头与 service å¼€å¤´æ•´æ®µåžæŽ‰ â†’ XML éžæ³•。已改为 DOM(`SelectSingleNode('/service/executable')`)并把注释里的裸标签改掉。另:新增打包代码曾多一个 `}` å¯¼è‡´è¯­æ³•错,已用 `Parser::ParseFile` AST å¤æ ¸ï¼Œä¸¤ä¸ª ps1 çŽ°åœ¨è¯­æ³• OK ä¸”均为 UTF-8 with BOM。
- **兼容性(用户问“Win7/Win10/Win11/Server æ˜¯ä¸æ˜¯éƒ½å¯ä»¥â€ï¼‰**:服务机制完全一样(自动启动/延迟启动/失败重启都是系统能力),差别只在包装器依赖什么运行时。实测二进制:`WinSW-x64.exe` 2.12.0 æ˜¯**自包含 .NET 6 å•文件**(含 `singlefilehost`/`System.Private.CoreLib`/`Microsoft.NETCore.App 6.0.13`/`net6.0`),**不需要装 .NET Framework**;老系统(Win7 SP1/2008R2/2012R2)若报 `api-ms-win-*.dll` ç¼ºå¤±ï¼Œå¯æ¢å®˜æ–¹ `WinSW.NET461.exe`(0.6MB,需 .NET 4.6.1+,改名成 `trafficAudit.exe` å³å¯ï¼ŒXML ä¸åŠ¨ï¼‰ï¼›32 ä½ç³»ç»Ÿæ¢ `WinSW-x86.exe`。
- **安全提示**:WinSW å®˜æ–¹äº§ç‰©**未做 Authenticode ç­¾å**(本机 `Get-AuthenticodeSignature` = NotSigned),因此以固定 SHA-256 ä½œå®Œæ•´æ€§æ ¡éªŒï¼š`05B82D46AD331CC16BDC00DE5C6332C1EF818DF8CEEFCD49C726553209B3A0DA`(与 GitHub å‘布资产 18,243,033 å­—节一致)。
- **未验证项(需在目标机做一次)**:本机当前不是管理员,**没真正 install è¿‡æœåŠ¡**;开机自启、失败重启、日志滚动要在服务器上按 README 3.1 æ‰§è¡Œä¸€æ¬¡ç¡®è®¤ã€‚
- **新包(本地,不入库)**:`deploy/traffic-audit-deploy-20261003-3/`(393 æ–‡ä»¶ / 110.49 MB,zip 83.77 MB)。已验证:服务三件套在 `backend\`、包内 `trafficAudit.exe` SHA-256 ä¸Žå›ºå®šå€¼ä¸€è‡´ã€`backend\web` ä¸Ž `frontend/dist` å„ 44 æ–‡ä»¶ã€jar åŽ»å¯†è‡ªæ£€é€šè¿‡ã€æ— æ˜Žæ–‡ key。**旧的 `traffic-audit-deploy-20261003/` ä¸Ž `-2/` å·²è¢«å–代**,部署请用 `-3`(旧包待用户确认后再删)。
- **运行状态(写本条时)**:开发用后端 8090(PID 43040)、前端 8080(PID 43152)均在跑;数据库 `127.0.0.1:3305/traffic_audit`。
## æœ€æ–°ï¼ˆ2026-10-02)
- **新模块(用户已验收,本次提交/推送)**:新增“专项统计-重要节假日”模块,含节假日配置、营业性客流、非营业性车流、客运系数 4 å¼ è¡¨ï¼›ç³»ç»Ÿé…ç½®èœå•ä»… admin å¯ç»´æŠ¤ï¼Œä¸“项统计-节假日支持导入、导出、模板下载和 Word å¿«æŠ¥ä¸‹è½½ã€‚
deploy-templates/README-²¿Êð˵Ã÷.txt
@@ -6,7 +6,12 @@
    traffic-audit-server-1.0.0-SNAPSHOT.jar  åŽç«¯ç¨‹åºï¼ˆå«å…¨éƒ¨ä¾èµ–,**已内嵌前端**)
    docs/                                    æŠ¥è¡¨æ¨¡æ¿ä¸Žè¿è¡Œæ•°æ®ç›®å½•(必需,勿删)
    application.yml                          å¤–部配置(改这里即可,无需重新打包)
    start-backend.cmd / start-backend.sh     å¯åŠ¨è„šæœ¬
    start-backend.cmd / start-backend.sh     å‰å°å¯åŠ¨è„šæœ¬ï¼ˆäººå·¥è°ƒè¯•ç”¨ï¼Œå…³äº†çª—å£å°±åœï¼‰
    trafficAudit.exe                         WinSW æœåŠ¡åŒ…è£…å™¨ï¼ˆæŠŠ java å˜æˆ Windows æœåŠ¡ï¼Œå‹¿åˆ å‹¿æ”¹åï¼‰
    trafficAudit.xml                         WinSW æœåŠ¡é…ç½®ï¼ˆæœåŠ¡å/启动方式/日志/失败重启策略)
    install-service.ps1                      æ³¨å†Œ/查看/重启/删除 Windows æœåŠ¡ï¼ˆå¼€æœºè‡ªå¯ï¼ŒæŽ¨èï¼‰
    start-backend-service.cmd                éžäº¤äº’启动器(无 pause、写 app.log,配合计划任务用)
    install-autostart.ps1                    å¤‡é€‰ï¼šæ³¨å†Œâ€œè®¡åˆ’任务”方式的开机自启
  frontend/
    dist/                                    å‰ç«¯æž„建产物(备用:想用 nginx æ‰˜ç®¡æ—¶ç”¨ï¼‰
    nginx.conf.example                       nginx é…ç½®ç¤ºä¾‹
@@ -25,6 +30,21 @@
  MySQL 5.7 / 8.0(必需)
  **不需要 Redis**(代码未使用;旧版说明里的 Redis æ˜¯é—留配置,可忽略)
  **不需要 nginx**(前端已内嵌进 jar,访问 8090 å³å¯ï¼›nginx ä»…为备选方案)
【操作系统兼容性】
  å¼€æœºè‡ªå¯ç”¨çš„æ˜¯ Windows æœåŠ¡ï¼Œè€Œâ€œè‡ªåŠ¨å¯åŠ¨ / å»¶è¿Ÿå¯åЍ / å¤±è´¥è‡ªåŠ¨é‡å¯â€æ˜¯ç³»ç»Ÿè‡ªå¸¦èƒ½åŠ›ï¼Œ
  ä»Ž Win7 SP1 åˆ° Win11、Server 2008 R2 åˆ° Server 2022/2025 æœºåˆ¶å®Œå…¨ä¸€æ ·ã€‚差别只在服务包装器
  trafficAudit.exe ä¾èµ–什么运行时:
  - æœ¬åŒ…内的 trafficAudit.exe æ˜¯ WinSW 2.12.0 çš„ x64 è‡ªåŒ…含构建(自带 .NET 6 è¿è¡Œæ—¶ï¼Œå·²æ ¡éªŒ SHA-256),
    **不需要另外安装 .NET Framework**;推荐用于 64 ä½ Win10 / Win11 / Server 2016 åŠä»¥ä¸Šã€‚
  - Win7 SP1 / Server 2008 R2 / Server 2012(R2):自包含版通常也能跑(Win7 SP1 éœ€å…ˆæ‰“ KB2533623);
    è‹¥æŠ¥ç¼ºå°‘ api-ms-win-*.dll ä¹‹ç±»ï¼Œæ¢æˆå®˜æ–¹ thin æž„建:把 WinSW.NET461.exe ä¸‹è½½åŽæ”¹åæˆ
    trafficAudit.exe è¦†ç›–(配置 trafficAudit.xml ä¸ç”¨æ”¹ï¼‰ï¼Œå®ƒéœ€ .NET Framework 4.6.1+:
      https://github.com/winsw/winsw/releases/download/v2.12.0/WinSW.NET461.exe
  - 32 ä½ç³»ç»Ÿï¼šæŠŠåŒ…装器换成 WinSW-x86.exe(同一下载页)。
  - Win7 / Server 2012 æœ¬èº«å·²è¿‡å¾®è½¯æ”¯æŒæœŸï¼Œæ–°æœåŠ¡å™¨å»ºè®®ç›´æŽ¥ä¸Š Server 2016 åŠä»¥ä¸Šã€‚
  - è„šæœ¬è¦æ±‚ï¼šinstall-service.ps1 åœ¨ Windows PowerShell 2.0+ ä¸‹å¯ç”¨ï¼ˆWin7 è‡ªå¸¦ 2.0,已做兼容);
    3.2 çš„计划任务脚本用到了 New-ScheduledTask* å‘½ä»¤ï¼Œéœ€è¦ Win8 / Server 2012 åŠä»¥ä¸Šã€‚
【部署步骤】
  1. åˆå§‹åŒ–数据库(4 ä¸ªè„šæœ¬éƒ½è¦æ‰§è¡Œï¼Œé¡ºåºå¦‚ä¸‹ï¼‰
@@ -45,6 +65,40 @@
     Windows:双击 start-backend.cmd
     Linux:  sh start-backend.sh
     æˆåŠŸæ ‡å¿—ï¼šå‡ºçŽ° "Started TrafficAuditApplication" ä¸Ž "Tomcat started on port(s): 8090"
  3.1 Windows å¼€æœºè‡ªå¯ï¼ˆæŽ¨èï¼šæ³¨å†Œæˆ Windows æœåŠ¡ï¼Œä¸€æ¬¡é…ç½®é•¿æœŸç”Ÿæ•ˆï¼‰
     å‰æï¼šbackend ç›®å½•下的 trafficAudit.exe / trafficAudit.xml / install-service.ps1 éƒ½å·²éšæœ¬éƒ¨ç½²åŒ…提供,
           ä¸éœ€è¦å¦å¤–下载 WinSW,也不需要给 java è£…别的东西。
     åœ¨ backend ç›®å½•下,用【管理员】PowerShell æ‰§è¡Œï¼š
       powershell -NoProfile -ExecutionPolicy Bypass -File install-service.ps1
     æ•ˆæžœï¼šæ³¨å†Œ Windows æœåŠ¡ TrafficAuditBackend,开机自动启动(延迟启动,先等 MySQL å°±ç»ªï¼‰ï¼Œ
           ä¸éœ€è¦ä»»ä½•用户登录;java è¿›ç¨‹å¼‚常退出由服务管理器自动重启(10 ç§’ / 30 ç§’ / 60 ç§’递增重试,
           1 å°æ—¶åŽé‡è¯•计数清零)。服务以 LocalSystem èº«ä»½è¿è¡Œã€‚
     è„šæœ¬ä¼šè‡ªåŠ¨å®šä½ java.exe(JAVA_HOME â†’ PATH â†’ JavaSoft æ³¨å†Œè¡¨ â†’ Program Files),
     å¹¶æŠŠç»å¯¹è·¯å¾„写进 trafficAudit.xml(SYSTEM æœåŠ¡çš„ PATH ä¸Žç™»å½•用户不同,写绝对路径更稳)。
     å¸¸ç”¨å‚数:
       -Status                           æŸ¥çœ‹æœåŠ¡çŠ¶æ€ï¼ˆä¸éœ€è¦ç®¡ç†å‘˜ï¼‰
       -Restart                          é‡å¯æœåŠ¡        -Remove  åœæ­¢å¹¶åˆ é™¤æœåŠ¡
       -NoStart                          åªæ³¨å†Œä¸å¯åЍ    -JavaPath "D:\jdk8\bin\java.exe"  æ‰‹åŠ¨æŒ‡å®š JDK
     æ—¥å¿—:backend\logs\trafficAudit.out.log(运行输出)与 trafficAudit.err.log(错误输出),
           å•个文件 10MB,保留 10 ä¸ªæ»šåŠ¨æ–‡ä»¶ã€‚
     éªŒè¯ï¼šæµè§ˆå™¨æ‰“å¼€ http://<部署机IP>:8090 ï¼Œæˆ–
           (Invoke-WebRequest http://127.0.0.1:8090 -UseBasicParsing).StatusCode
     æ³¨æ„ï¼š
       - å¼€æœºè‡ªå¯é æœåŠ¡çš„â€œè‡ªåŠ¨â€å¯åŠ¨ç±»åž‹ï¼›å¦‚æžœæŠŠå®ƒæ”¹æˆâ€œæ‰‹åŠ¨â€ï¼Œå°±ä¸ä¼šå†éšæœºå™¨å¯åŠ¨ã€‚
       - é‡å¤æ‰§è¡Œ install-service.ps1 æ˜¯å®‰å…¨çš„:会先停服务再重新注册并启动。
       - åŒä¸€å°æœºä¸è¦åŒæ—¶å¯ç”¨ä¸‹é¢çš„计划任务方式,两个进程会抢 8090 ç«¯å£ã€‚
       - å»ºè®®æŠŠéƒ¨ç½²åŒ…放在 D:\trafficAudit è¿™ç±»ç®€å•路径;放在某些用户目录下时 LocalSystem
         å¯èƒ½æ²¡æœ‰å†™æƒé™ï¼Œæ—¥å¿—会报权限错误。
  3.2 å¤‡é€‰ï¼šè®¡åˆ’任务方式(不想把后端装成服务时用,与 3.1 äºŒé€‰ä¸€ï¼‰
     åœ¨ backend ç›®å½•下,用【管理员】PowerShell æ‰§è¡Œï¼š
       powershell -NoProfile -ExecutionPolicy Bypass -File install-autostart.ps1
     æ•ˆæžœï¼šæ³¨å†Œè®¡åˆ’任务 TrafficAuditBackend,开机延迟 60 ç§’后以 SYSTEM èº«ä»½è¿è¡Œ start-backend-service.cmd,
           ä¸éœ€è¦ç”¨æˆ·ç™»å½•,失败自动重试 3 æ¬¡ï¼Œè¿è¡Œæ—¥å¿—写在 backend\app.log。
     å¸¸ç”¨å‚数:-DelaySeconds 90(改延迟秒数)  -StartNow(注册后立即启动一次)
               -Status(查看状态)             -Remove(删除开机自启)
     æ³¨ï¼šå¯åŠ¨å™¨ä¼˜å…ˆç”¨ JAVA_HOME\bin\java.exe,找不到时回退 PATH;检测到 8090 å·²åœ¨ç›‘听则直接退出。
  4. è®¿é—®ç³»ç»Ÿ
     æµè§ˆå™¨æ‰“å¼€ http://<部署机IP>:8090  ï¼ˆå‰ç«¯å·²å†…嵌,页面与接口同源)
@@ -69,7 +123,15 @@
  3. 8090 ç«¯å£è¢«å ç”¨ï¼šæ”¹ application.yml çš„ server.port åŽé‡å¯ã€‚
  4. é¡µé¢æ‰“开是空白:确认访问的是 http://<IP>:8090 æ ¹è·¯å¾„,且浏览器强制刷新(Ctrl+F5)。
  5. ä¸Šä¼ å¤§æ–‡ä»¶å¤±è´¥ï¼šåŽç«¯é™åˆ¶ 100MB,已配置;如前面还挂了 nginx,需同步放大 client_max_body_size。
  6. æœåŠ¡å·²æ³¨å†Œä½† 8090 æ‰“不开:看 backend\logs\trafficAudit.err.log çš„æŠ¥é”™å†…容;
     æœåŠ¡æ–¹å¼ä¸‹ç”¨çš„æ˜¯ backend\application.yml,password å¿…须是真实 MySQL å¯†ç ï¼ˆå ä½æ–‡å­—会导致 using password: NO)。
  7. æœåŠ¡æŠ¥â€œæ‹’ç»è®¿é—®â€æˆ–å†™ä¸äº†æ—¥å¿—ï¼šéƒ¨ç½²ç›®å½•æƒé™ä¸è¶³ã€‚
     æŠŠéƒ¨ç½²åŒ…放到 D:\trafficAudit è¿™ç±»ç®€å•路径,或给 LocalSystem æŽˆäºˆè¯¥ç›®å½•的读写权限。
  8. æƒ³ä¸´æ—¶å–消开机自启但保留服务:Set-Service TrafficAuditBackend -StartupType Manual
【停止服务】
  Windows:关闭 start-backend.cmd çª—口
  æœåŠ¡æ–¹å¼ï¼š Stop-Service TrafficAuditBackend        ï¼ˆé‡æ–°å¯åŠ¨ï¼šStart-Service TrafficAuditBackend)
             å½»åº•删除开机自启:install-service.ps1 -Remove
  å‰å°å¯åŠ¨ï¼š å…³é—­ start-backend.cmd çª—口
  è®¡åˆ’任务: Stop-ScheduledTask -TaskName TrafficAuditBackend
  Linux:  Ctrl+C,或 kill å¯¹åº” java è¿›ç¨‹ï¼ˆps -ef | grep java)
deploy-templates/backend/install-autostart.ps1
New file
@@ -0,0 +1,109 @@
<#
  Register / remove / inspect a Windows Scheduled Task that starts the TrafficAudit
  backend at machine boot, with no user session required.
  Run from an ELEVATED PowerShell in the backend folder (jar + application.yml +
  this script + start-backend-service.cmd):
    powershell -NoProfile -ExecutionPolicy Bypass -File install-autostart.ps1
    powershell -NoProfile -ExecutionPolicy Bypass -File install-autostart.ps1 -DelaySeconds 90
    powershell -NoProfile -ExecutionPolicy Bypass -File install-autostart.ps1 -StartNow
    powershell -NoProfile -ExecutionPolicy Bypass -File install-autostart.ps1 -Status
    powershell -NoProfile -ExecutionPolicy Bypass -File install-autostart.ps1 -Remove
  What it does:
    - trigger  : AtStartup, delayed by -DelaySeconds (default 60s, so MySQL is up first)
    - principal: SYSTEM, highest privileges, "run whether user is logged on or not"
    - settings : no execution time limit, start when available, restart 3x / 1 min on failure
    - action   : cmd.exe /c start-backend-service.cmd (logs to app.log next to the jar)
  Notes:
    - Re-running without -Remove is idempotent (-Force overwrites the task).
    - Start/stop manually with: Start-ScheduledTask / Stop-ScheduledTask -TaskName <name>
    - Keep this file ASCII-only: Windows PowerShell 5.1 reads a BOM-less .ps1 as ANSI.
#>
param(
  [string]$TaskName = 'TrafficAuditBackend',
  [int]$DelaySeconds = 60,
  [switch]$Remove,
  [switch]$Status,
  [switch]$StartNow
)
$ErrorActionPreference = 'Stop'
$root     = Split-Path -Parent $MyInvocation.MyCommand.Path
$launcher = Join-Path $root 'start-backend-service.cmd'
$logFile  = Join-Path $root 'app.log'
function Assert-Admin {
  $identity  = [Security.Principal.WindowsIdentity]::GetCurrent()
  $principal = New-Object Security.Principal.WindowsPrincipal($identity)
  if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    throw 'This script must run from an elevated (Run as Administrator) PowerShell.'
  }
}
if ($Status) {
  $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
  if (-not $task) { Write-Host "Scheduled task '$TaskName' is not registered."; exit 1 }
  $task | Select-Object TaskName, TaskPath, State | Format-List
  Get-ScheduledTaskInfo -TaskName $TaskName |
    Select-Object LastRunTime, LastTaskResult, NextRunTime, NumberOfMissedRuns | Format-List
  exit 0
}
if ($Remove) {
  Assert-Admin
  if (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue) {
    Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
    Write-Host "Removed scheduled task '$TaskName'."
  } else {
    Write-Host "Scheduled task '$TaskName' was not registered; nothing to remove."
  }
  exit 0
}
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'))) {
  Write-Host "WARN: jar not found in $root - the task will be registered, but startup will fail until the jar is there."
}
$action = New-ScheduledTaskAction -Execute 'cmd.exe' -Argument ('/c "' + $launcher + '"') -WorkingDirectory $root
$trigger = New-ScheduledTaskTrigger -AtStartup
if ($DelaySeconds -gt 0) { $trigger.Delay = 'PT' + $DelaySeconds + 'S' }
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet `
  -AllowStartIfOnBatteries `
  -DontStopIfGoingOnBatteries `
  -StartWhenAvailable `
  -MultipleInstances IgnoreNew `
  -RestartCount 3 `
  -RestartInterval (New-TimeSpan -Minutes 1) `
  -ExecutionTimeLimit ([TimeSpan]::Zero)
Register-ScheduledTask `
  -TaskName    $TaskName `
  -Action      $action `
  -Trigger     $trigger `
  -Principal   $principal `
  -Settings    $settings `
  -Description 'TrafficAudit backend auto-start at machine boot (script-managed).' `
  -Force | Out-Null
Write-Host "Registered scheduled task '$TaskName'."
Write-Host "  Trigger  : At startup, +${DelaySeconds}s"
Write-Host "  Principal: SYSTEM (highest privileges, no logon required)"
Write-Host "  Launcher : $launcher"
Write-Host "  Log      : $logFile"
Write-Host ''
Write-Host "Service address after boot: http://<server-ip>:8090"
Write-Host "Manage it with: -Status | -StartNow | -Remove"
if ($StartNow) {
  Start-ScheduledTask -TaskName $TaskName
  Write-Host "Started task '$TaskName' now."
}
deploy-templates/backend/install-service.ps1
New file
@@ -0,0 +1,235 @@
<#
  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".
  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.
  Run from an ELEVATED PowerShell in the backend folder
  (the folder that contains the jar, application.yml, docs, web and trafficAudit.exe):
    powershell -NoProfile -ExecutionPolicy Bypass -File install-service.ps1
    powershell -NoProfile -ExecutionPolicy Bypass -File install-service.ps1 -Status
    powershell -NoProfile -ExecutionPolicy Bypass -File install-service.ps1 -Restart
    powershell -NoProfile -ExecutionPolicy Bypass -File install-service.ps1 -Remove
    powershell -NoProfile -ExecutionPolicy Bypass -File install-service.ps1 -NoStart
    powershell -NoProfile -ExecutionPolicy Bypass -File install-service.ps1 -JavaPath 'D:\jdk8\bin\java.exe'
  What it does:
    1. locates java.exe (JAVA_HOME -> PATH -> JavaSoft registry -> Program Files)
    2. writes that absolute path into trafficAudit.xml (<executable>)
    3. installs or reinstalls the service and starts it (unless -NoStart)
  Notes:
    - Re-running is idempotent: an existing service is stopped and reinstalled.
    - Logs: logs\trafficAudit.out.log / logs\trafficAudit.err.log (10 MB x 10 roll).
    - Do NOT enable the scheduled-task autostart (install-autostart.ps1) at the
      same time: two hosts would race for the same jar and port 8090.
    - Keep this file ASCII-only; Windows PowerShell 5.1 reads a BOM-less .ps1 as ANSI.
#>
param(
  [switch]$Remove,
  [switch]$Status,
  [switch]$Restart,
  [switch]$NoStart,
  [string]$JavaPath
)
$ErrorActionPreference = 'Stop'
$root      = Split-Path -Parent $MyInvocation.MyCommand.Path
$wrapper   = Join-Path $root 'trafficAudit.exe'
$config    = Join-Path $root 'trafficAudit.xml'
$appConfig = Join-Path $root 'application.yml'
$serviceId = 'TrafficAuditBackend'
function Assert-Admin {
  $identity  = [Security.Principal.WindowsIdentity]::GetCurrent()
  $principal = New-Object Security.Principal.WindowsPrincipal($identity)
  if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    throw 'This script must run from an elevated (Run as Administrator) PowerShell.'
  }
}
function Resolve-JavaPath {
  param([string]$Explicit)
  if ($Explicit) {
    if (-not (Test-Path $Explicit)) { throw "JavaPath does not exist: $Explicit" }
    return (Resolve-Path $Explicit).Path
  }
  if ($env:JAVA_HOME) {
    $candidate = Join-Path $env:JAVA_HOME 'bin\java.exe'
    if (Test-Path $candidate) { return (Resolve-Path $candidate).Path }
  }
  $onPath = Get-Command java.exe -ErrorAction SilentlyContinue
  if ($onPath -and $onPath.Source) { return $onPath.Source }
  # A SYSTEM service inherits the machine PATH, which often lacks the JDK.
  # Fall back to the install locations used by Oracle / OpenJDK installers.
  foreach ($reg in @('HKLM:\SOFTWARE\JavaSoft\JDK', 'HKLM:\SOFTWARE\JavaSoft\Java Development Kit')) {
    if (-not (Test-Path $reg)) { continue }
    foreach ($ver in (Get-ChildItem $reg -ErrorAction SilentlyContinue | Sort-Object Name -Descending)) {
      $home = (Get-ItemProperty -Path $ver.PSPath -Name JavaHome -ErrorAction SilentlyContinue).JavaHome
      if ($home) {
        $candidate = Join-Path $home 'bin\java.exe'
        if (Test-Path $candidate) { return (Resolve-Path $candidate).Path }
      }
    }
  }
  $roots = @($env:ProgramFiles)
  if (${env:ProgramFiles(x86)}) { $roots += ${env:ProgramFiles(x86)} }
  foreach ($base in $roots) {
    foreach ($vendor in @('Java', 'Eclipse Adoptium', 'AdoptOpenJDK', 'Microsoft')) {
      $hit = Get-ChildItem -Path (Join-Path $base ($vendor + '\*\bin\java.exe')) -ErrorAction SilentlyContinue |
             Sort-Object FullName -Descending | Select-Object -First 1
      if ($hit) { return $hit.FullName }
    }
  }
  throw 'java.exe not found. Install a JDK 8 and re-run, or pass -JavaPath "D:\path\to\bin\java.exe".'
}
function Set-WrapperJavaPath {
  param([string]$JavaExe)
  if (-not (Test-Path $config)) { throw "Missing config file: $config" }
  # Edit through the DOM: a regex would also hit the executable mention inside
  # the header comment and would mangle the whole document.
  $doc = New-Object System.Xml.XmlDocument
  $doc.PreserveWhitespace = $true
  $doc.Load($config)
  $node = $doc.SelectSingleNode('/service/executable')
  if (-not $node) { throw "No /service/executable element found in $config" }
  if ($node.InnerText -eq $JavaExe) {
    Write-Host "Config already points at $JavaExe"
    return
  }
  $node.InnerText = $JavaExe
  $settings = New-Object System.Xml.XmlWriterSettings
  $settings.Encoding = New-Object System.Text.UTF8Encoding $false
  $settings.Indent = $true
  $settings.IndentChars = '  '
  $writer = [System.Xml.XmlWriter]::Create($config, $settings)
  try { $doc.Save($writer) } finally { $writer.Close() }
  Write-Host "Updated $config -> $JavaExe"
}
function Get-ServiceState {
  Get-Service -Name $serviceId -ErrorAction SilentlyContinue
}
# ---------- read-only status: no elevation needed ----------
if ($Status) {
  $service = Get-ServiceState
  if (-not $service) {
    Write-Host "Service '$serviceId' is NOT installed."
    exit 1
  }
  $service | Select-Object Name, DisplayName, Status, StartType | Format-List
  # Get-CimInstance needs PowerShell 3+; fall back to WMI for Windows 7 / PowerShell 2.
  $detail = $null
  try {
    $detail = Get-CimInstance Win32_Service -Filter "Name='$serviceId'" -ErrorAction Stop
  } catch {
    $detail = Get-WmiObject Win32_Service -Filter "Name='$serviceId'" -ErrorAction SilentlyContinue
  }
  if ($detail) { $detail | Select-Object ProcessId, StartMode, DelayedAutoStart, PathName | Format-List }
  if (Test-Path $wrapper) { & $wrapper status }
  exit 0
}
if ($Restart) {
  Assert-Admin
  if (-not (Get-ServiceState)) { throw "Service '$serviceId' is not installed; run this script without -Restart first." }
  Write-Host "Restarting service '$serviceId'..."
  Restart-Service -Name $serviceId -Force
  Start-Sleep -Seconds 3
  Get-ServiceState | Select-Object Name, Status | Format-List
  exit 0
}
if ($Remove) {
  Assert-Admin
  if (-not (Test-Path $wrapper)) { throw "Missing wrapper executable: $wrapper" }
  if (Get-ServiceState) {
    Write-Host "Stopping and removing service '$serviceId'..."
    & $wrapper stop | Out-Null
    Start-Sleep -Seconds 3
    & $wrapper uninstall | Out-Null
    Write-Host "Removed service '$serviceId'. Files in $root were left untouched."
  } else {
    Write-Host "Service '$serviceId' is not installed; nothing to remove."
  }
  exit 0
}
# ---------- install / reinstall ----------
Assert-Admin
if (-not (Test-Path $wrapper)) {
  throw "Missing wrapper executable: $wrapper`nRebuild the deploy package with pack-deploy.ps1 (it provides WinSW), or copy WinSW-x64.exe here renamed to trafficAudit.exe."
}
if (-not (Test-Path $appConfig)) {
  Write-Host "WARN: application.yml is missing next to the jar."
  Write-Host "      The built-in password placeholder resolves to an empty string and startup will fail with"
  Write-Host "      Java SQLException: Access denied for user 'root'@'localhost' (using password: NO)."
} else {
  $line = Select-String -LiteralPath $appConfig -Pattern '^\s*password\s*:' | Select-Object -First 1
  if (-not $line) {
    Write-Host "WARN: no 'password:' entry found in application.yml; check the datasource block."
  } else {
    $value = ($line.Line -replace '^\s*password\s*:\s*', '').Trim(' ''', '"')
    if ($value -eq '') {
      Write-Host "WARN: spring.datasource.password is empty in application.yml; startup may fail with '(using password: NO)'."
    } elseif ($value -match '[^\x00-\x7F]') {
      Write-Host "WARN: the datasource password contains non-ASCII characters."
      Write-Host "      If it is still the Chinese placeholder text, replace it with the real MySQL password first."
    }
  }
}
$java = Resolve-JavaPath -Explicit $JavaPath
Set-WrapperJavaPath -JavaExe $java
$existing = Get-ServiceState
if ($existing) {
  Write-Host "Service '$serviceId' already exists (status: $($existing.Status)); reinstalling..."
  if ($existing.Status -ne 'Stopped') {
    & $wrapper stop | Out-Null
    Start-Sleep -Seconds 3
  }
  & $wrapper uninstall | Out-Null
  Start-Sleep -Seconds 2
}
Write-Host "Installing service '$serviceId'..."
& $wrapper install
if ($LASTEXITCODE -ne 0) { throw "WinSW install failed with exit code $LASTEXITCODE" }
if (-not $NoStart) {
  Write-Host "Starting service '$serviceId' (delayed auto-start is used at boot)..."
  & $wrapper start
  Start-Sleep -Seconds 8
  $service = Get-ServiceState
  $service | Select-Object Name, DisplayName, Status, StartType | Format-List
  $serviceDir = Join-Path $root 'logs'
  Write-Host ''
  Write-Host "Logs        : $serviceDir\trafficAudit.out.log / trafficAudit.err.log"
  Write-Host "Health check: (Invoke-WebRequest http://127.0.0.1:8090 -UseBasicParsing).StatusCode"
  Write-Host ''
  Write-Host "Manage it with: Stop-Service $serviceId | Start-Service $serviceId"
  Write-Host "Or: .\install-service.ps1 -Status | -Restart | -Remove"
  Write-Host ''
  Write-Host 'The service auto-starts on every boot (Automatic, delayed start).'
}
deploy-templates/backend/start-backend-service.cmd
New file
@@ -0,0 +1,49 @@
@echo off
rem ============================================================
rem  Non-interactive launcher for Task Scheduler / boot auto-start.
rem  Differences from start-backend.cmd (which is meant for a human):
rem    - no "pause" at the end (nobody can press a key during boot)
rem    - all output goes to app.log next to this file
rem    - resolves java via JAVA_HOME first, then PATH
rem    - exits 0 immediately if port 8090 is already listening
rem  ASCII-only on purpose: cmd.exe reads this file with the OEM
rem  code page, so non-ASCII text here would be garbled.
rem  Registered by install-autostart.ps1; run under the account that
rem  owns the deployment folder (SYSTEM by default).
rem ============================================================
setlocal
cd /d "%~dp0"
set "APP_JAR=traffic-audit-server-1.0.0-SNAPSHOT.jar"
set "LOG=%~dp0app.log"
netstat -ano | findstr /C:":8090" | findstr /I "LISTENING" >nul 2>nul
if not errorlevel 1 (
  echo [%date% %time%] port 8090 is already listening; nothing to do. >> "%LOG%"
  exit /b 0
)
if not exist "%~dp0%APP_JAR%" (
  echo [%date% %time%] ERROR: %APP_JAR% not found in "%CD%". >> "%LOG%"
  exit /b 1
)
set "JAVA_EXE="
if defined JAVA_HOME if exist "%JAVA_HOME%\bin\java.exe" set "JAVA_EXE=%JAVA_HOME%\bin\java.exe"
if not defined JAVA_EXE for %%J in (java.exe) do set "JAVA_EXE=%%~$PATH:J"
if not defined JAVA_EXE (
  echo [%date% %time%] ERROR: java.exe not found. Install JDK 8 or set JAVA_HOME / PATH. >> "%LOG%"
  exit /b 1
)
if not exist "%~dp0application.yml" (
  echo [%date% %time%] WARN: application.yml is missing next to the jar. >> "%LOG%"
  echo [%date% %time%]       The DB password placeholder will resolve to empty and startup will fail with >> "%LOG%"
  echo [%date% %time%]       "Access denied ... (using password: NO)". >> "%LOG%"
)
echo [%date% %time%] starting backend: "%JAVA_EXE%" -jar "%APP_JAR%"  (cwd "%CD%") >> "%LOG%"
"%JAVA_EXE%" -jar "%~dp0%APP_JAR%" >> "%LOG%" 2>&1
set "RC=%ERRORLEVEL%"
echo [%date% %time%] backend exited with code %RC%. >> "%LOG%"
endlocal & exit /b %RC%
deploy-templates/backend/trafficAudit.xml
New file
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
  WinSW (Windows Service Wrapper) configuration for the TrafficAudit backend.
  Layout requirement: this file MUST stay in the same folder as the wrapper
  executable named trafficAudit.exe, the application jar, application.yml,
  docs\ and web\. WinSW looks for a config file whose name matches the
  executable (trafficAudit.exe -> trafficAudit.xml).
  %BASE% is expanded by WinSW to the folder that holds this file, so the
  service always runs with that folder as its working directory.
  The executable element below is rewritten with an absolute java.exe path by
  install-service.ps1 (a SYSTEM service does not inherit the interactive
  user PATH, so relying on PATH alone is fragile).
-->
<service>
  <id>TrafficAuditBackend</id>
  <name>TrafficAudit Backend</name>
  <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>
  <workingdirectory>%BASE%</workingdirectory>
  <!-- Start at boot, but delayed so a local MySQL service is ready first. -->
  <startmode>Automatic</startmode>
  <delayedAutoStart>true</delayedAutoStart>
  <!-- Restart on crash: quickly at first, then back off. Counter resets after 1 hour. -->
  <onfailure action="restart" delay="10 sec"/>
  <onfailure action="restart" delay="30 sec"/>
  <onfailure action="restart" delay="60 sec"/>
  <resetfailure>1 hour</resetfailure>
  <stoptimeout>30 sec</stoptimeout>
  <logpath>%BASE%\logs</logpath>
  <log mode="roll-by-size">
    <sizeThreshold>10240</sizeThreshold>
    <keepFiles>10</keepFiles>
  </log>
  <outfile>trafficAudit.out.log</outfile>
  <errfile>trafficAudit.err.log</errfile>
</service>
docs/´ò°ü²¿Êð˵Ã÷.md
@@ -55,7 +55,13 @@
│  â”œâ”€ traffic-audit-server-1.0.0-SNAPSHOT.jar   åŽç«¯ + å†…嵌前端(约 55 MB)
│  â”œâ”€ docs/                                     æ¨¡æ¿ä¸Žè¿è¡Œæ•°æ®ï¼ˆå¿…需,勿删)
│  â”œâ”€ application.yml                           å¤–部配置(改这里即可,无需重打包)
│  â””─ start-backend.cmd / start-backend.sh       å¯åŠ¨è„šæœ¬
│  â”œâ”€ start-backend.cmd / start-backend.sh      å¯åŠ¨è„šæœ¬ï¼ˆäººå·¥åŒå‡»/命令行)
│  â”œâ”€ trafficAudit.exe                          WinSW æœåŠ¡åŒ…è£…å™¨ï¼ˆ2.12.0 x64,自包含 .NET 6)
│  â”œâ”€ trafficAudit.xml                          WinSW æœåŠ¡é…ç½®ï¼ˆè‡ªåŠ¨/延迟启动、失败重启、日志滚动)
│  â”œâ”€ install-service.ps1                       æ³¨å†Œ/查看/重启/删除 Windows æœåŠ¡ï¼ˆå¼€æœºè‡ªå¯ï¼ŒæŽ¨èï¼‰
│  â”œâ”€ start-backend-service.cmd                 å¤‡é€‰ï¼šéžäº¤äº’启动器(无 pause、写 app.log)
│  â”œâ”€ install-autostart.ps1                     å¤‡é€‰ï¼šæ³¨å†Œ/查看/删除 Windows å¼€æœºè‡ªå¯è®¡åˆ’任务
│  â””─ web/                                     å‰ç«¯é™æ€æ–‡ä»¶ï¼ˆç”± dist è‡ªåŠ¨åŒæ­¥ï¼Œ8090 å•端口模式必需)
├─ frontend/
│  â”œâ”€ dist/                                     å‰ç«¯äº§ç‰©ï¼ˆä»… nginx æ–¹æ¡ˆéœ€è¦ï¼‰
│  â””─ nginx.conf.example                         nginx é…ç½®ç¤ºä¾‹
@@ -163,7 +169,41 @@
成功标志:日志出现 `Started TrafficAuditApplication` ä¸Ž `Tomcat started on port(s): 8090`。
### 5.4 è®¿é—®
### 5.4 Windows å¼€æœºè‡ªå¯ï¼ˆæŽ¨èï¼šæ³¨å†Œæˆ Windows æœåŠ¡ï¼‰
在 `backend` ç›®å½•下用**管理员** PowerShell æ‰§è¡Œï¼ˆ`trafficAudit.exe` / `trafficAudit.xml` / `install-service.ps1` å·²éšåŒ…提供):
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File install-service.ps1
```
| é¡¹ | å€¼ |
|----|----|
| æœåŠ¡å | `TrafficAuditBackend`(services.msc é‡Œæ˜¾ç¤ºä¸º TrafficAudit Backend) |
| å¯åŠ¨ç±»åž‹ | è‡ªåЍ + å»¶è¿Ÿå¯åŠ¨ï¼ˆå…ˆç­‰ MySQL å°±ç»ªï¼‰ï¼Œä¸éœ€è¦ä»»ä½•用户登录 |
| è¿è¡Œèº«ä»½ | LocalSystem |
| å¤±è´¥é‡å¯ | 10s / 30s / 60s é€’增,1 å°æ—¶åŽè®¡æ•°æ¸…零(由服务控制管理器执行) |
| æ—¥å¿— | `backend\logs\trafficAudit.out.log`、`trafficAudit.err.log`(10MB Ã— 10 æ»šåŠ¨ï¼‰ |
常用参数:`-Status`(无需管理员)、`-Restart`、`-Remove`、`-NoStart`、`-JavaPath "D:\jdk8\bin\java.exe"`。
脚本会自动定位 `java.exe`(JAVA_HOME â†’ PATH â†’ JavaSoft æ³¨å†Œè¡¨ â†’ Program Files)并把**绝对路径**写进 `trafficAudit.xml`(SYSTEM æœåŠ¡çš„ PATH ä¸Žç™»å½•用户不同,写绝对路径更稳)。重复执行是幂等的:先停服务再重新注册并启动。
> åŒä¸€å°æœº**不要**同时启用计划任务方式(`install-autostart.ps1`),两个宿主会抢 8090 ç«¯å£ã€‚
#### æ“ä½œç³»ç»Ÿå…¼å®¹æ€§
服务本身(自动启动 / å»¶è¿Ÿå¯åЍ / å¤±è´¥é‡å¯ï¼‰ä»Ž Win7 SP1 åˆ° Win11、Server 2008 R2 åˆ° Server 2022/2025 æœºåˆ¶ä¸€è‡´ï¼Œå·®åˆ«åªåœ¨åŒ…装器依赖的运行时:
| ç›®æ ‡ç³»ç»Ÿ | ç”¨å“ªä¸ªåŒ…装器 | é¢å¤–依赖 |
|----------|--------------|----------|
| Win10 / Win11 / Server 2016 åŠä»¥ä¸Šï¼ˆæŽ¨èï¼‰ | åŒ…内 `trafficAudit.exe`(WinSW 2.12.0 x64 è‡ªåŒ…含) | æ— ï¼ˆè‡ªå¸¦ .NET 6 è¿è¡Œæ—¶ï¼‰ |
| Win7 SP1 / Server 2008 R2 / Server 2012(R2) | å…ˆè¯•包内自包含版(Win7 éœ€ KB2533623);报 `api-ms-win-*.dll` ç¼ºå¤±æ—¶æ¢ `WinSW.NET461.exe` æ”¹åæˆ `trafficAudit.exe` | .NET Framework 4.6.1+ |
| 32 ä½ç³»ç»Ÿ | `WinSW-x86.exe` æ”¹åæˆ `trafficAudit.exe` | æ— ï¼ˆè‡ªå¸¦ .NET 6 è¿è¡Œæ—¶ï¼‰ |
下载页:<https://github.com/winsw/winsw/releases/tag/v2.12.0> â€”— ä»»ä½•构建都用同一份 `trafficAudit.xml`,改的只是包装器文件名。
`install-service.ps1` åœ¨ Windows PowerShell 2.0+ å¯ç”¨ï¼›`install-autostart.ps1` ç”¨åˆ° `New-ScheduledTask*`,需 Win8 / Server 2012 åŠä»¥ä¸Šã€‚
### 5.5 è®¿é—®
浏览器打开 `http://<部署机IP>:8090`(前端已内嵌,页面与接口同源,无需 nginx),账号 `admin / admin123`。
@@ -211,6 +251,10 @@
| æ•°æ®åº“连接失败 | æ£€æŸ¥ `application.yml`;确认 5 ä¸ª SQL è„šæœ¬å·²æ‰§è¡Œ |
| å¯åŠ¨æŠ¥ `Access denied ... (using password: NO)` | åŽç«¯æ‹¿åˆ°çš„库口令是**空串**:jar å†…ç½® `${TRAFFIC_DB_PASSWORD:}` æœªè¢«å¤–置配置覆盖。把 `backend/application.yml` æ”¾åœ¨ jar åŒçº§å¹¶å¡« `spring.datasource.password`,或设环境变量 `TRAFFIC_DB_PASSWORD`;改完重启,无需重打包 |
| ç«¯å£å ç”¨ | æ”¹ `server.port`;前端开发端口 8080 |
| æœåŠ¡å·²æ³¨å†Œä½† 8090 æ‰“不开 | çœ‹ `backend\logs\trafficAudit.err.log`;服务方式读的是 `backend\application.yml`,`password` å¿…须是真实口令 |
| æœåŠ¡æŠ¥â€œæ‹’ç»è®¿é—®â€/写不了日志 | éƒ¨ç½²ç›®å½•权限不足:换到 `D:\trafficAudit` è¿™ç±»ç®€å•路径,或给 LocalSystem æŽˆå†™æƒé™ |
| ä¸æƒ³å¼€æœºè‡ªå¯ä½†ä¿ç•™æœåŠ¡ | `Set-Service TrafficAuditBackend -StartupType Manual` |
| Win7/2008R2 ä¸ŠæœåŠ¡å¯ä¸äº†ï¼ˆæŠ¥ `api-ms-win-*.dll`) | è§ 5.4「操作系统兼容性」:把 `WinSW.NET461.exe` æ”¹åæˆ `trafficAudit.exe` æ›¿æ¢ |
## åã€å¼€å‘环境日常启动
@@ -222,6 +266,7 @@
## åä¸€ã€å˜æ›´è®°å½•
- **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 æ–¹æ¡ˆï¼‰ã€‚
@@ -272,14 +317,17 @@
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 å•端口模式),输出到 `<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>`;
   WinSW ä¼˜å…ˆç”¨ `deploy-templates\tools\winsw.exe` ç¼“存(校验 SHA-256),缓存缺失或哈希不符时自动从 GitHub å®˜æ–¹å‘布页下载 v2.12.0 x64(该产物未做 Authenticode ç­¾åï¼Œå› æ­¤ä»¥å›ºå®š SHA-256 ä½œå®Œæ•´æ€§æ ¡éªŒï¼‰ï¼›
5. å¯¹ç»„装后的整包再扫一遍明文密钥。
可选参数:`-OutRoot` æŒ‡å®šè¾“出目录(默认仓库 `deploy\`)、`-SkipFrontend` å¤ç”¨çŽ°æœ‰ dist、`-SkipBackend` å¤ç”¨çŽ°æœ‰ jar(只重组包、不重编译,用于快速补包)。
可选参数:`-OutRoot` æŒ‡å®šè¾“出目录(默认仓库 `deploy\`)、`-SkipFrontend` å¤ç”¨çŽ°æœ‰ dist、`-SkipBackend` å¤ç”¨çŽ°æœ‰ jar(只重组包、不重编译,用于快速补包)、`-SkipServiceWrapper` ä¸æŠŠ WinSW æœåŠ¡åŒ…è£…å™¨æ‰“è¿›åŒ…ï¼ˆå³ä¸è¦â€œæ³¨å†Œæˆ Windows æœåŠ¡â€çš„å¼€æœºè‡ªå¯ï¼‰ã€‚
> è®°å½•(2026-10-03):此前手工产出的 10-02 åŒ…只有 jar+web+dist,缺 `application.yml`/`docs`/`db`/启动脚本,部署机启动报 `Access denied ... (using password: NO)`。现已由脚本自动补齐这些文件并在打包前替换占位符配置。
模板件统一放在 **`deploy-templates/`**(已入库):外部 `application.yml`、`start-backend.cmd/.sh`、`nginx.conf.example`、`db\{init.sql,sql_city_bus,sql_city_taxi,sql_wyc}.sql`、`README-部署说明.txt`。改模板即改打包产物。
模板件统一放在 **`deploy-templates/`**(已入库):外部 `application.yml`、`start-backend.cmd/.sh`、`trafficAudit.xml`、`install-service.ps1`、`start-backend-service.cmd`、`install-autostart.ps1`、`nginx.conf.example`、`db\{init.sql,sql_city_bus,sql_city_taxi,sql_wyc}.sql`、`README-部署说明.txt`。改模板即改打包产物。
> `deploy-templates\tools\`(WinSW äºŒè¿›åˆ¶ï¼Œ17.4MB)**不入库**(已加 `.gitignore`),由打包脚本按需下载并校验 SHA-256。
### è½®æ¢å¯†é’¥åŽåŒæ­¥æœ¬æœº
pack-deploy.ps1
@@ -9,7 +9,9 @@
       æ‰§è¡Œ 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 / db / README
       Windows æœåŠ¡åŒ…è£…å™¨ WinSW:优先用 deploy-templates\tools\winsw.exe ç¼“存(校验 SHA-256),
       ç¼ºå¤±æ—¶è‡ªåŠ¨ä¸‹è½½ v2.12.0 x64 åˆ°è¯¥ç¼“存目录;随包输出为 backend\trafficAudit.exe
    5) å¯¹ç»„装后的整包再扫一遍明文密钥
  ä¿®è®¢èƒŒæ™¯ï¼ˆ2026-10-03):2026-10-02 æ‰‹å·¥äº§å‡ºçš„包缺 application.yml / docs / db / å¯åŠ¨è„šæœ¬ï¼Œ
@@ -20,7 +22,8 @@
param(
  [string]$OutRoot = '',
  [switch]$SkipFrontend,
  [switch]$SkipBackend      # å¤ç”¨å·²å­˜åœ¨çš„ jar(不重编译、不动源码配置),仅用于补全/重组发布包
  [switch]$SkipBackend,     # å¤ç”¨å·²å­˜åœ¨çš„ jar(不重编译、不动源码配置),仅用于补全/重组发布包
  [switch]$SkipServiceWrapper   # ä¸æŠŠ WinSW æœåŠ¡åŒ…è£…å™¨æ‰“è¿›åŒ…ï¼ˆå³ä¸æä¾›â€œæ³¨å†Œæˆ Windows æœåŠ¡â€çš„å¼€æœºè‡ªå¯ï¼‰
)
$ErrorActionPreference = 'Stop'
@@ -117,6 +120,40 @@
}
if (-not (Test-Path $webDist)) { throw "未找到前端产物:$webDist(去掉 -SkipFrontend é‡æ–°æž„建)" }
# ---------- WinSW(Windows æœåŠ¡åŒ…è£…å™¨ï¼‰å‡†å¤‡ï¼šæœ¬åœ°ç¼“å­˜ä¼˜å…ˆï¼Œç¼ºå¤±æ—¶ä»Žå®˜æ–¹å‘å¸ƒé¡µä¸‹è½½å¹¶æ ¡éªŒ SHA-256 ----------
# WinSW v2.12.0 å®˜æ–¹äº§ç‰©æœªåš Authenticode ç­¾åï¼Œæ‰€ä»¥ä»¥å›ºå®š SHA-256 ä½œä¸ºå®Œæ•´æ€§æ ¡éªŒã€‚
$winswUrl    = 'https://github.com/winsw/winsw/releases/download/v2.12.0/WinSW-x64.exe'
$winswSha256 = '05B82D46AD331CC16BDC00DE5C6332C1EF818DF8CEEFCD49C726553209B3A0DA'
$winswSrc    = Join-Path $tplDir 'tools\winsw.exe'
function Test-WinswCache {
  param([string]$Path)
  if (-not (Test-Path -LiteralPath $Path)) { return $false }
  $hash = (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash
  if ($hash -ne $winswSha256) {
    Write-Host ('      è­¦å‘Šï¼šWinSW ç¼“å­˜ SHA-256 ä¸åŒ¹é…ï¼Œå°†é‡æ–°ä¸‹è½½ï¼ˆæœŸæœ› {0}...,实际 {1}...)' -f $winswSha256.Substring(0, 12), $hash.Substring(0, 12))
    return $false
  }
  return $true
}
if ($SkipServiceWrapper) {
  Write-Host '      å·²æŒ‰ -SkipServiceWrapper è·³è¿‡ï¼šåŒ…内不含 Windows æœåŠ¡æ–¹å¼å¼€æœºè‡ªå¯'
} else {
  if (-not (Test-WinswCache $winswSrc)) {
    New-Item -ItemType Directory -Force -Path (Split-Path -Parent $winswSrc) | Out-Null
    Write-Host '      ä¸‹è½½ WinSW v2.12.0 x64 ...'
    try {
      [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
      Invoke-WebRequest -Uri $winswUrl -OutFile $winswSrc -UseBasicParsing
    } catch {
      throw ("下载 WinSW å¤±è´¥ï¼š{0}。`n请手动下载 {1} å­˜ä¸º {2} åŽé‡è¯•,或用 -SkipServiceWrapper è·³è¿‡ã€‚" -f $_.Exception.Message, $winswUrl, $winswSrc)
    }
    if (-not (Test-WinswCache $winswSrc)) { throw "WinSW æ ¡éªŒå¤±è´¥ï¼Œå·²ä¸­æ­¢æ‰“包:$winswSrc" }
  }
  Write-Host ('      WinSW å°±ç»ªï¼ˆSHA-256 å·²æ ¡éªŒï¼‰ï¼š{0:N1} MB' -f ((Get-Item -LiteralPath $winswSrc).Length / 1MB))
}
# ---------- [4/5] ç»„装完整部署目录 ----------
Write-Host '[4/5] ç»„装部署目录...'
$stamp = Get-Date -Format 'yyyyMMdd'
@@ -129,6 +166,14 @@
Copy-Item (Join-Path $tplDir 'backend\application.yml') (Join-Path $dest 'backend') -Force
Copy-Item (Join-Path $tplDir 'backend\start-backend.cmd') (Join-Path $dest 'backend') -Force
Copy-Item (Join-Path $tplDir 'backend\start-backend.sh') (Join-Path $dest 'backend') -Force
Copy-Item (Join-Path $tplDir 'backend\start-backend-service.cmd') (Join-Path $dest 'backend') -Force
Copy-Item (Join-Path $tplDir 'backend\install-autostart.ps1') (Join-Path $dest 'backend') -Force
if (-not $SkipServiceWrapper) {
  # WinSW è¦æ±‚配置文件名与 exe åŒåï¼štrafficAudit.exe è¯»åŒç›®å½•çš„ trafficAudit.xml
  Copy-Item -LiteralPath $winswSrc (Join-Path $dest 'backend\trafficAudit.exe') -Force
  Copy-Item (Join-Path $tplDir 'backend\trafficAudit.xml') (Join-Path $dest 'backend') -Force
  Copy-Item (Join-Path $tplDir 'backend\install-service.ps1') (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
Copy-Item (Join-Path $tplDir 'db\*.sql') (Join-Path $dest 'db') -Force