fix(部署): pack-deploy 补齐外置配置/启动脚本/docs/web 并修正模板端口(8090)
1个文件已添加
8个文件已修改
264 ■■■■■ 已修改文件
HANDOFF.md 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-templates/README-部署说明.txt 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-templates/backend/application.yml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-templates/backend/start-backend.cmd 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-templates/backend/start-backend.sh 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-templates/jar-application.yml 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/打包部署说明.md 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pack-deploy.ps1 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/resources/application.yml.example 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HANDOFF.md
@@ -6,6 +6,15 @@
## æœ€æ–°ï¼ˆ2026-10-03)
### è¡¥å……(10-03 ä¸‹åˆï¼‰ï¼šæœåŠ¡å™¨éƒ¨ç½²æŠ¥ â€œAccess denied ... (using password: NO)” â€”— æ ¹å› ä¸Žä¿®å¤
- **现象**:把 `deploy/traffic-audit-release-20261002/` éƒ¨ç½²åˆ° `61.183.254.94` åŽï¼ŒåŽç«¯å¯åŠ¨æŠ¥ `java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: NO)`。
- **根因**:jar å†…置配置里库口令是占位符 `${TRAFFIC_DB_PASSWORD:}`(默认空串),而 10-02 é‚£ä¸ªåŒ…**只含 jar + web + dist**,没有外置 `application.yml`、没有 `docs/`、`db/`、启动脚本。部署机没有任何外部配置覆盖,Spring å°±æ‹¿ç©ºå¯†ç è¿žæœ¬æœº MySQL â†’ `using password: NO`(注意:这是“末传密码”,不是密码错)。
- **服务器侧一步修复(无需重打包)**:在 jar åŒçº§æ”¾ `application.yml`,把 `spring.datasource.url/username/password` æ”¹æˆçŽ°åœºçœŸå®žå€¼åŽé‡å¯å³å¯ï¼›ä¹Ÿå¯ç”¨çŽ¯å¢ƒå˜é‡ `TRAFFIC_DB_PASSWORD` ä»£æ›¿ã€‚启动脚本已加预检提醒(缺 `application.yml` / password ä»æ˜¯å ä½æ–‡å­—都会 WARN)。修复后若报“未找到数据目录 docs”则说明包内缺 `backend/docs`,从新包或源机器拷。
- **打包工具已修(原 â€œæ‰“包工具待修” ä¸¤é¡¹å‡å·²è½å®žï¼‰**:`pack-deploy.ps1` é‡å†™ä¸ºäº”步:① ç”¨æ–°å¢žæ¨¡æ¿ `deploy-templates/jar-application.yml`(**全占位符**)临时替换源码配置后 `mvn clean package`,无论成败都还原真实配置;② è§£åŽ‹ jar åŽ»å¯†è‡ªæ£€ï¼›â‘¢ å‰ç«¯æž„建;④ ç»„装**完整包**:`backend/{jar, application.yml, start-backend.cmd/.sh, docs/, web/}` + `frontend/dist` + `db/*.sql` + README(`backend/web` ä»Ž dist è‡ªåŠ¨åŒæ­¥ï¼Œé€æ–‡ä»¶ä¸€è‡´ï¼‰ï¼›â‘¤ æ•´åŒ…明文密钥扫描。新增 `-SkipBackend` å¯å¤ç”¨çŽ°æœ‰ jar å¿«é€Ÿé‡ç»„包。
- **模板同步修正**:`deploy-templates/backend/application.yml` çš„ `server.port` ç”± 8080 æ”¹å›ž 8090(原值会导致后端起在 8080)、补 `static-locations: file:./web/` ä¸Ž password é™·é˜±æ³¨é‡Šï¼›`README-部署说明.txt` å¸¸è§é—®é¢˜æ–°å¢žè¯¥æŠ¥é”™å¤„置;`docs/打包部署说明.md` æŽ’错表新增该行;`application.yml.example` çŽ¯å¢ƒå˜é‡åç»Ÿä¸€ä¸º `TRAFFIC_DB_USER/TRAFFIC_DB_PASSWORD/TRAFFIC_JWT_SECRET`(原来误用 `MYSQL_PASSWORD`/`JWT_SECRET`,与 `start-dev.ps1` ä¸ä¸€è‡´ï¼‰ã€‚
- **新包(本地,不入库)**:`deploy/traffic-audit-deploy-20261003/`(388 æ–‡ä»¶ / 93.07 MB,zip 76.37 MB),已验证:jar å†…置配置全占位符(去密自检通过)、`backend/web` ä¸Ž `frontend/dist` é€æ–‡ä»¶ä¸€è‡´ï¼ˆå„ 44 ä¸ªï¼‰ã€`backend/docs/生成汇总大表` æ¯ç‰ˆå­˜åœ¨ã€`功能测试报告/工作日结/问题汇总/_cache` å·²æŽ’除。
- **踩坑(重要)**:本机 PowerShell 5.1 æŠŠæ—  BOM çš„ `.ps1` å½“ ANSI è¯»ï¼Œä¸­æ–‡æ³¨é‡Šä¼šå˜æˆä¹±ç å¹¶å¯¼è‡´è„šæœ¬è§£æžå¤±è´¥ã€‚`pack-deploy.ps1` å¿…须保存为 **UTF-8 with BOM**(仓库其他 `*.ps1` ä¹Ÿéƒ½æ˜¯ BOM);排查中曾实际踩到。
- **运行状态(写交接时)**:后端 8090、前端 8080 å‡æœªç›‘听;新对话如继续开发或测试,先执行 `powershell -NoProfile -ExecutionPolicy Bypass -File start-dev.ps1`。数据库仍为 `127.0.0.1:3305/traffic_audit`,MySQL æœåŠ¡æœªåœã€‚
- **发布构建(已完成)**:应“发布编译”要求,已完成后端 `mvn clean package -DskipTests` å’Œå‰ç«¯ `npm run build`。jar å†…已做去密自检,只保留环境变量占位符;构建后本机真实 `src/main/resources/application.yml` å·²åŽŸæ ·æ¢å¤ã€‚
- **发布产物(本地,未入库)**:
deploy-templates/README-²¿Êð˵Ã÷.txt
@@ -58,6 +58,10 @@
  æ³¨æ„ï¼šå…¨é‡ dump ä¼šå¸¦ä¸Šæœ¬æœºçš„账号密码,覆盖 init.sql é¢„置的 admin è´¦å·ã€‚
【常见问题】
  0. å¯åŠ¨æŠ¥ java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: NO):
     â€œusing password: NO” = åŽç«¯æ‹¿åˆ°çš„æ•°æ®åº“密码是空串(不是密码错)。
     æ£€æŸ¥ backend/application.yml æ˜¯å¦ä¸Ž jar åŒçº§ä¸” spring.datasource.password å·²å¡«çœŸå®žå¯†ç ï¼›
     è‹¥æœªæ”¹ application.yml,也可用环境变量 TRAFFIC_DB_PASSWORD ä»£æ›¿ã€‚改完重启即可,无需重新打包。
  1. å¯åŠ¨æŠ¥ã€Œæœªæ‰¾åˆ°æ•°æ®ç›®å½• docs」/ å¯¼å‡ºæŠ¥ã€Œæœªæ‰¾åˆ°æ¨¡æ¿æ–‡ä»¶ã€ï¼š
     å¿…须在 backend ç›®å½•下启动(jar ä¸Ž docs åŒçº§ï¼‰ï¼Œä¸è¦åªæ‹· jar。
  2. æŠ¥è¡¨å¯¼å‡ºæŠ¥ã€Œæœªæ‰¾åˆ°æ±‡æ€»è¡¨æ¯ç‰ˆã€ŠYYYYå¹´M月道路运输量汇总表.xlsx》」:
deploy-templates/backend/application.yml
@@ -3,15 +3,19 @@
# éƒ¨ç½²åˆ°æ–°æœºå™¨åŽï¼Œè¯·**至少**修改下面标注 TODO çš„三处数据库配置!
# ============================================================
server:
  port: 8080
  port: 8090            # å•端口模式:后端 8090 åŒæ—¶æ‰˜ç®¡å‰ç«¯é¡µé¢ï¼ˆä¸è¦æ”¹æˆ 8080)
app:
  captcha:
    required: false        # ç”Ÿäº§çŽ¯å¢ƒå¦‚éœ€å›¾å½¢éªŒè¯ç ï¼Œæ”¹ä¸º true
spring:
  web:
    resources:
      # å•端口模式:优先读取 jar åŒçº§ web\ ç›®å½•(打包时由前端 dist è‡ªåŠ¨ç”Ÿæˆï¼‰
      static-locations: file:./web/,classpath:/static/
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/traffic_audit?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
    username: root                     # TODO æ”¹æˆä½ çš„ MySQL è´¦å·
    password: è¯·æ”¹æˆä½ çš„æ•°æ®åº“密码      # TODO æ”¹æˆä½ çš„ MySQL å¯†ç 
    password: è¯·æ”¹æˆä½ çš„æ•°æ®åº“密码      # TODO æ”¹æˆä½ çš„ MySQL å¯†ç ï¼›ç•™ç©ºä¼šæŠ¥ Access denied ... (using password: NO)
    driver-class-name: com.mysql.cj.jdbc.Driver
  servlet:
    multipart:
deploy-templates/backend/start-backend.cmd
@@ -7,6 +7,16 @@
  pause
  exit /b 1
)
if not exist "%~dp0application.yml" (
  echo [WARN] æœªæ‰¾åˆ° %~dp0application.yml
  echo        æ²¡æœ‰å¤–置配置时,jar å†…置密码占位符会解析为空,
  echo        å¯åŠ¨å°†æŠ¥ java.sql.SQLException: Access denied ... ^(using password: NO^)
)
findstr /C:"请改成你的数据库密码" "%~dp0application.yml" >nul 2>nul
if not errorlevel 1 (
  echo [WARN] application.yml é‡Œ spring.datasource.password è¿˜æ˜¯å ä½æ–‡å­—,
  echo        è¯·å…ˆæ”¹æˆéƒ¨ç½²æœºçœŸå®žçš„ MySQL å¯†ç å†å¯åŠ¨ã€‚
)
echo ============================================
echo  TrafficAudit å¯åŠ¨ä¸­ï¼šhttp://localhost:8090
echo  å‰ç«¯å·²å†…嵌,直接访问 8090 å³å¯ï¼ˆä¸éœ€è¦ nginx)
deploy-templates/backend/start-backend.sh
@@ -1,4 +1,10 @@
#!/bin/sh
cd "$(dirname "$0")" || exit 1
if [ ! -f application.yml ]; then
  echo "[WARN] æœªæ‰¾åˆ° application.yml(jar åŒçº§ï¼‰ï¼šæ•°æ®åº“密码会解析为空,可能报 Access denied ... (using password: NO)"
fi
if grep -q "请改成你的数据库密码" application.yml 2>/dev/null; then
  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
deploy-templates/jar-application.yml
New file
@@ -0,0 +1,94 @@
# ============================================================
# æ‰“包用「进 jar çš„默认配置」——只允许占位符,禁止真实密钥/口令!
#
# pack-deploy.ps1 æž„建前会用本文件临时替换
#   traffic-audit-server/src/main/resources/application.yml
# æž„建完成(含去密自检)后自动还原开发机的真实配置。
#
# éƒ¨ç½²æœºçœŸå®žå€¼ç”±ä¸‹é¢ä¸¤ç§æ–¹å¼è¦†ç›–,优先级 çŽ¯å¢ƒå˜é‡ > jar åŒçº§ application.yml > æœ¬æ–‡ä»¶ï¼š
#   1) jar åŒçº§ï¼ˆå¯åŠ¨å·¥ä½œç›®å½•ï¼‰æ”¾ application.yml,见 deploy-templates/backend/application.yml
#   2) çŽ¯å¢ƒå˜é‡ TRAFFIC_DB_URL / TRAFFIC_DB_USER / TRAFFIC_DB_PASSWORD /
#      TRAFFIC_JWT_SECRET / DEEPSEEK_API_KEY
#
# æ³¨æ„ï¼špassword é»˜è®¤å€¼ä¸ºç©ºä¸²ï¼Œè‹¥éƒ¨ç½²æœºæ—¢æ²¡æœ‰å¤–ç½® application.yml ä¹Ÿæ²¡è®¾
# TRAFFIC_DB_PASSWORD,启动会报 java.sql.SQLException: Access denied ... (using password: NO)
# ============================================================
server:
  port: 8090
  tomcat:
    basedir: .
    threads:
      max: 500
    accesslog:
      enabled: true
      directory: logs
      prefix: access
      suffix: .log
      pattern: '%h %l %u %t "%r" %s %b %D'
spring:
  web:
    resources:
      static-locations: file:./web/,classpath:/static/
  datasource:
    url: ${TRAFFIC_DB_URL:jdbc:mysql://127.0.0.1:3306/traffic_audit?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true}
    username: ${TRAFFIC_DB_USER:root}
    password: ${TRAFFIC_DB_PASSWORD:}
    driver-class-name: com.mysql.cj.jdbc.Driver
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: Asia/Shanghai
mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml
  type-aliases-package: com.trafficaudit
  configuration:
    map-underscore-to-camel-case: true
    # æ˜¾å¼æŒ‡å®š Slf4j:MyBatis-Plus é»˜è®¤ StdOutImpl ä¼šæŠŠæ•´è¡Œæ•°æ®ï¼ˆå«ä¼ä¸šå/电话)打进日志
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
  global-config:
    db-config:
      id-type: auto
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0
jwt:
  secret: ${TRAFFIC_JWT_SECRET:8f2c1d64a7b34e09c5d81f6a2b0e7c93}
  expiration: 86400000
docs:
  template-root: docs
city-passenger:
  template-dir: docs/城市客运
passenger:
  template-dir: docs/公路旅客
freight:
  template-dir: docs/货运
energy:
  template-dir: docs/能耗
investment:
  template-dir: docs/投资/模板
summary:
  template-dir: docs/生成汇总大表
deepseek:
  api-key: ${DEEPSEEK_API_KEY:}
  api-url: https://api.deepseek.com/v1/chat/completions
  model: deepseek-chat
app:
  cors:
    allowed-origins: http://localhost:*,http://127.0.0.1:*,http://192.168.*:*,http://10.*:*,http://172.*:*
  captcha:
    required: false
docs/´ò°ü²¿Êð˵Ã÷.md
@@ -209,6 +209,7 @@
| ç™»å½•接口 500 | `jwt.secret` å¡«äº†éžæ³•字符(如中文);改成 32 ä½åå…­è¿›åˆ¶ä¸² |
| é¡µé¢èƒ½å¼€ä½†æŽ¥å£ 404 | èµ° nginx æ–¹æ¡ˆæ—¶æ²¡é… `/api` åä»£ï¼›æ”¹ç”¨ jar å†…嵌访问 8090 |
| æ•°æ®åº“连接失败 | æ£€æŸ¥ `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 |
## åã€å¼€å‘环境日常启动
@@ -268,10 +269,15 @@
```
脚本做四件事:
1. `mvn clean package -DskipTests` æž„建后端 jar;
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),输出到 `<OutRoot>\traffic-audit-deploy-<yyyyMMdd>`。
4. æŒ‰ `deploy-templates/` ç»„装完整部署包(backend / frontend / db / README-部署说明.txt)——`backend/` å« jar、`application.yml`、启动脚本、`docs/`、`web/`(由 dist è‡ªåŠ¨åŒæ­¥ï¼Œä¾› 8090 å•端口模式),输出到 `<OutRoot>\traffic-audit-deploy-<yyyyMMdd>`;
5. å¯¹ç»„装后的整包再扫一遍明文密钥。
可选参数:`-OutRoot` æŒ‡å®šè¾“出目录(默认仓库 `deploy\`)、`-SkipFrontend` å¤ç”¨çŽ°æœ‰ dist、`-SkipBackend` å¤ç”¨çŽ°æœ‰ jar(只重组包、不重编译,用于快速补包)。
> è®°å½•(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`。改模板即改打包产物。
pack-deploy.ps1
@@ -1,34 +1,75 @@
<#
  ä¸€é”®æ‰“包部署包(含"去密自检")—— 2026-09-27 æ–°å¢ž
  ä¸€é”®æ‰“包部署包(含"去密自检")—— 2026-09-27 æ–°å¢žï¼Œ2026-10-03 ä¿®è®¢
  ç”¨æ³•:
    powershell -NoProfile -ExecutionPolicy Bypass -File pack-deploy.ps1
    powershell -NoProfile -ExecutionPolicy Bypass -File pack-deploy.ps1 -OutRoot "E:\试验" -SkipFrontend
  åšå››ä»¶äº‹ï¼š
    1) mvn clean package æž„建后端 jar
  åšäº”件事:
    1) ç”¨ deploy-templates\jar-application.yml(全占位符)临时替换源码 application.yml,
       æ‰§è¡Œ mvn clean package;无论成败都还原开发机真实配置
    2) è§£åŽ‹ jar æ£€æŸ¥å†…ç½® application.yml:必须全是占位符(禁止 sk- å¯†é’¥ / æ˜Žæ–‡å£ä»¤ï¼‰ï¼Œä¸åˆæ ¼ç›´æŽ¥ä¸­æ­¢
    3) npm run build æž„建前端 dist(可 -SkipFrontend)
    4) ç»„装 deploy ç›®å½•结构(backend / frontend / db / README-部署说明.txt),输出到 <OutRoot>\traffic-audit-deploy-<yyyyMMdd>
    3) npm run build é‡å»ºå‰ç«¯ dist(可 -SkipFrontend)
    4) ç»„装完整部署包:backend(jar + application.yml + å¯åŠ¨è„šæœ¬ + docs + web) / frontend/dist / db / README
    5) å¯¹ç»„装后的整包再扫一遍明文密钥
  ä¿®è®¢èƒŒæ™¯ï¼ˆ2026-10-03):2026-10-02 æ‰‹å·¥äº§å‡ºçš„包缺 application.yml / docs / db / å¯åŠ¨è„šæœ¬ï¼Œ
  ä¸”没有把 dist åŒæ­¥åˆ° backend\web,部署机启动直接报
  "java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: NO)"。
  æœ¬ç‰ˆæŠŠä¸Šè¿°æ–‡ä»¶å…¨éƒ¨è¡¥é½ï¼Œå¹¶æŒ‰ jar å†…置占位符配置重新构建,避免再次出现。
#>
param(
  [string]$OutRoot = 'E:\试验',
  [switch]$SkipFrontend
  [string]$OutRoot = '',
  [switch]$SkipFrontend,
  [switch]$SkipBackend      # å¤ç”¨å·²å­˜åœ¨çš„ jar(不重编译、不动源码配置),仅用于补全/重组发布包
)
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
$tplDir = Join-Path $root 'deploy-templates'                  # æ‰“包模板件(已入库,2026-09-27 èµ·ï¼‰
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'
$jar = Join-Path $root $jarRel
$webDist   = Join-Path $root 'traffic-audit-web\dist'
Write-Host '[1/5] æž„建后端(mvn clean package -DskipTests)...'
if (-not (Test-Path $tplJarCfg)) { throw "缺少打包模板:$tplJarCfg" }
# ---------- [1/5] ç”¨å ä½ç¬¦é…ç½®æž„建 jar,构建后还原开发机真实配置 ----------
if ($SkipBackend) {
  Write-Host '[1/5] å·²æŒ‡å®š -SkipBackend,跳过后端构建,复用现有 jar'
  if (-not (Test-Path $jar)) { throw "未找到现有 jar:$jar(去掉 -SkipBackend é‡æ–°æž„建)" }
} else {
Write-Host '[1/5] æž„建后端(mvn clean package -DskipTests,构建期使用占位符配置)...'
$backupCfg = $null
$hadCfg = Test-Path $srcCfg
try {
  if ($hadCfg) {
    $backupCfg = [System.IO.Path]::GetTempFileName()
    Copy-Item $srcCfg $backupCfg -Force
  }
  Copy-Item $tplJarCfg $srcCfg -Force
Push-Location $root
  try {
cmd /c "mvn.cmd -q clean package -DskipTests -f traffic-audit-server\pom.xml > pack-build.log 2>&1"
$rc = $LASTEXITCODE
Pop-Location
  } finally { Pop-Location }
if ($rc -ne 0) { throw '后端构建失败,详见 pack-build.log' }
} finally {
  if ($backupCfg) {
    Copy-Item $backupCfg $srcCfg -Force
    Remove-Item $backupCfg -Force -ErrorAction SilentlyContinue
    Write-Host '      å·²è¿˜åŽŸå¼€å‘æœº src\main\resources\application.yml'
  } elseif (-not $hadCfg) {
    Remove-Item $srcCfg -Force -ErrorAction SilentlyContinue
    Write-Host '      æ³¨æ„ï¼šæž„建前源码目录本来没有 application.yml,已清掉临时占位符文件'
  }
}
if (-not (Test-Path $jar)) { throw "未找到构建产物:$jar" }
}
Write-Host ("      äº§ç‰©ï¼š{0:N1} MB" -f ((Get-Item $jar).Length / 1MB))
# ---------- [2/5] åŽ»å¯†è‡ªæ£€ï¼šjar å†…置配置必须全是占位符 ----------
Write-Host '[2/5] åŽ»å¯†è‡ªæ£€ï¼šè§£åŽ‹ jar æ£€æŸ¥ BOOT-INF/classes/application.yml ...'
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [System.IO.Compression.ZipFile]::OpenRead($jar)
@@ -40,29 +81,43 @@
  $sr.Close()
} finally { $zip.Dispose() }
$bad = @()
if ($text -match 'sk-[A-Za-z0-9]{16,}') { $bad += '疑似明文 API key(sk-…)' }
foreach ($line in ($text -split "`n")) {
function Get-SecretHits([string]$ymlText) {
  $hits = @()
  if ($ymlText -match 'sk-[A-Za-z0-9]{16,}') { $hits += '疑似明文 API key(sk-...)' }
  foreach ($line in ($ymlText -split "`n")) {
  if ($line -match '^\s*(password|api-key|secret)\s*:\s*(.+)$') {
    $v = $matches[2].Trim().Trim('"')
    if ($v -and -not $v.StartsWith('${')) { $bad += ("$($matches[1]) ä¸æ˜¯å ä½ç¬¦") }
      if ($v -and -not $v.StartsWith('${')) { $hits += ("$($matches[1]) ä¸æ˜¯å ä½ç¬¦") }
  }
}
  return $hits
}
$bad = Get-SecretHits $text
if ($bad.Count -gt 0) {
  throw ('去密自检未通过:' + ($bad -join ';') + '。请检查 traffic-audit-server\src\main\resources\application.yml')
  throw ('去密自检未通过:' + ($bad -join ';') + "。请检查 $tplJarCfg")
}
if ($text -notmatch 'password:\s*\$\{TRAFFIC_DB_PASSWORD') {
  Write-Host '      è­¦å‘Šï¼šjar å†…ç½® password ä¸æ˜¯ TRAFFIC_DB_PASSWORD å ä½ç¬¦å½¢å¼ï¼Œè¯·ç¡®è®¤æ¨¡æ¿ã€‚'
}
Write-Host '      é€šè¿‡ï¼šjar å†…置配置全部为占位符,无明文密钥/口令。'
# ---------- [3/5] å‰ç«¯æž„建 ----------
Write-Host '[3/5] æž„建前端(npm run build,可 -SkipFrontend è·³è¿‡ï¼‰...'
if (-not $SkipFrontend) {
  Push-Location (Join-Path $root 'traffic-audit-web')
  try {
  cmd /c "npm.cmd run build > ..\pack-front.log 2>&1"
  $rc2 = $LASTEXITCODE
  Pop-Location
  } finally { Pop-Location }
  if ($rc2 -ne 0) { throw '前端构建失败,详见 pack-front.log' }
  Write-Host '      å‰ç«¯ dist å·²é‡å»º'
} else {
  Write-Host '      å·²æŒ‰ -SkipFrontend è·³è¿‡ï¼Œä½¿ç”¨çŽ°æœ‰ dist'
}
if (-not (Test-Path $webDist)) { throw "未找到前端产物:$webDist(去掉 -SkipFrontend é‡æ–°æž„建)" }
# ---------- [4/5] ç»„装完整部署目录 ----------
Write-Host '[4/5] ç»„装部署目录...'
$stamp = Get-Date -Format 'yyyyMMdd'
$dest = Join-Path $OutRoot ("traffic-audit-deploy-" + $stamp)
@@ -74,9 +129,12 @@
Copy-Item (Join-Path $tplDir 'backend\application.yml') (Join-Path $dest 'backend') -Force
Copy-Item (Join-Path $tplDir 'backend\start-backend.cmd') (Join-Path $dest 'backend') -Force
Copy-Item (Join-Path $tplDir 'backend\start-backend.sh') (Join-Path $dest 'backend') -Force
Copy-Item (Join-Path $root 'traffic-audit-web\dist') (Join-Path $dest 'frontend') -Recurse -Force
Copy-Item $webDist (Join-Path $dest 'frontend') -Recurse -Force
Copy-Item (Join-Path $tplDir 'frontend\nginx.conf.example') (Join-Path $dest 'frontend') -Force
Copy-Item (Join-Path $tplDir 'db\*.sql') (Join-Path $dest 'db') -Force
# å•端口 8090 æ¨¡å¼ï¼šåŽç«¯ä»Ž jar åŒçº§ web\ è¯»å‰ç«¯é™æ€æ–‡ä»¶ï¼Œå¿…须和 dist å†…容一致
Copy-Item $webDist (Join-Path $dest 'backend\web') -Recurse -Force
# docs:运行时模板 + ç”¨æˆ·æ–‡æ¡£ï¼›æŽ’除开发过程文档与缓存/备份(减小体积、避免把内部清单带出去)
$docsDest = Join-Path $dest 'backend\docs'
@@ -86,13 +144,29 @@
# db è„šæœ¬ä¹Ÿæ”¾ä¸€ä»½åˆ° docs(与旧包一致:docs/init.sql ç­‰ï¼‰
Copy-Item (Join-Path $tplDir 'db\init.sql'), (Join-Path $tplDir 'db\sql_city_bus.sql'), (Join-Path $tplDir 'db\sql_city_taxi.sql'), (Join-Path $tplDir 'db\sql_wyc.sql'), (Join-Path $tplDir 'db\sql_holiday.sql'), (Join-Path $tplDir 'db\sql_holiday_decimal_migration.sql'), (Join-Path $tplDir 'db\sql_holiday_comparison_base_migration.sql') $docsDest -Force -ErrorAction SilentlyContinue
# README:用旧包模板并替换日期占位
# README:用模板并替换日期占位
$readme = Get-Content (Join-Path $tplDir 'README-部署说明.txt') -Raw -Encoding UTF8
$readme = $readme.Replace('{stamp}', $stamp)
Set-Content -LiteralPath (Join-Path $dest 'README-部署说明.txt') -Value $readme -Encoding UTF8
Write-Host '[5/5] æ±‡æ€»...'
# ---------- [5/5] æ•´åŒ…明文密钥扫描 + æ±‡æ€» ----------
Write-Host '[5/5] æ•´åŒ…扫描 + æ±‡æ€»...'
$leak = @()
foreach ($f in (Get-ChildItem (Join-Path $dest 'backend') -File | Where-Object { $_.Extension -in '.yml', '.yaml', '.cmd', '.sh', '.txt' })) {
  $t = Get-Content $f.FullName -Raw -Encoding UTF8 -ErrorAction SilentlyContinue
  if ($t -and $t -match 'sk-[A-Za-z0-9]{16,}') { $leak += $f.Name + '(疑似明文 API key)' }
}
if ($leak.Count -gt 0) { throw ('整包扫描未通过:' + ($leak -join ';')) }
$files = Get-ChildItem $dest -Recurse -File
$mb = (($files | Measure-Object Length -Sum).Sum / 1MB)
Write-Host ("      è¾“出目录:{0}" -f $dest)
Write-Host ("      æ–‡ä»¶æ•°ï¼š{0} 合计:{1:N2} MB" -f $files.Count, (($files | Measure-Object Length -Sum).Sum / 1MB))
Write-Host '      æé†’:把包拷到部署机后,只需改 backend\application.yml é‡Œçš„æ•°æ®åº“三处 + éœ€è¦æ—¶å¡« deepseek.api-key。'
Write-Host ("      æ–‡ä»¶æ•°ï¼š{0}  åˆè®¡ï¼š{1:N2} MB" -f $files.Count, $mb)
$zipPath = $dest + '.zip'
if (Get-Command Compress-Archive -ErrorAction SilentlyContinue) {
  if (Test-Path $zipPath) { Remove-Item $zipPath -Force }
  Compress-Archive -Path (Join-Path $dest '*') -DestinationPath $zipPath -CompressionLevel Optimal
  Write-Host ("      åŽ‹ç¼©åŒ…ï¼š{0}({1:N2} MB)" -f $zipPath, ((Get-Item $zipPath).Length / 1MB))
}
Write-Host '      æé†’:拷到部署机后,务必先改 backend\application.yml çš„ spring.datasource(url/username/password)再启动。'
traffic-audit-server/src/main/resources/application.yml.example
@@ -15,8 +15,8 @@
spring:
  datasource:
    url: jdbc:mysql://localhost:3305/traffic_audit?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
    username: root
    password: ${MYSQL_PASSWORD:root}
    username: ${TRAFFIC_DB_USER:root}
    password: ${TRAFFIC_DB_PASSWORD:root}
    driver-class-name: com.mysql.cj.jdbc.Driver
  servlet:
    multipart:
@@ -41,7 +41,8 @@
      logic-not-delete-value: 0
jwt:
  secret: ${JWT_SECRET:change-me-jwt-secret}
  # å¿…须是「Base64 åˆæ³•字符 + é•¿åº¦æ˜¯ 4 çš„倍数」;下面这串 32 ä½åå…­è¿›åˆ¶å¯ç›´æŽ¥ç”¨
  secret: ${TRAFFIC_JWT_SECRET:8f2c1d64a7b34e09c5d81f6a2b0e7c93}
  expiration: 86400000
docs: