iotsaas/deploy-all.bat
2995840376 6e3f3ea661 fix(E): 修复 iot-device 启动失败 + V22 迁移脚本重写为可执行版本
【生产故障与修复】
iot-device 部署后反复重启失败(status=1/FAILURE, restart counter 3+):
  UnsatisfiedDependencyException: No qualifying bean of type
  'com.iot.device.mapper.ProductActionMapper' available
根因: iot-device 模块【没有 @MapperScan】(仅 iot-rule 有), Mapper 靠
      @Mapper 注解被 mybatis-spring-boot-starter 自动扫描;
      新建的 ProductEventMapper / ProductActionMapper 漏写 @Mapper 注解,
      导致扫描不到 -> ControlSchemaServiceImpl 注入失败 -> 启动失败。
修复: 两个 Mapper 补 @Mapper 注解; 已全量核查 iot-device 31 个 Mapper 均有该注解。

【V22 迁移脚本重写】(实测发现原写法在本环境根本执行不了)
- 原写法使用存储过程 + DELIMITER, 实测三种执行方式(管道/文件重定向/source)
  全部失败: MySQL 客户端在 batch 模式会把 DELIMITER 当普通 SQL 发给服务器。
  生产库现存存储过程数为 0, 佐证历史 V19/V21 等含 DELIMITER 的脚本
  其存储过程部分从未生效(仅 ADD COLUMN 等语句生效)。
- 重写为零存储过程、零 DELIMITER 的普通语句序列:
  加列幂等 -> PREPARE + information_schema 判定(ADD COLUMN 走 PREPARE 实测可用)
  索引     -> 直接 DDL(ADD INDEX 走 PREPARE 实测报 1064, 直接执行可用)
- 库名统一用 DATABASE() 判定, 测试库克隆无需改写脚本
- 清理顺序调整: 唯一索引统一放在软删重复行清理之后, 避免索引创建失败

【生产验证】(R730 192.168.3.3)
- 全库备份: /opt/iotsaas/backup/iot_platform_before_v22.sql
- 克隆库演练: 建克隆库 -> 跑 V22 -> 校验 -> DROP, 生产数据零风险
- 正式执行: platform_type 8行 / product.protocol / product_property.grp,
  is_primary, enum_label / product_action.completion_mode 全部就位
- 唯一索引: uk_platform_product / uk_product_code(x3) / idx_product_id 全部就位
- 数据完整性: product=11 device=13, 与迁移前一致
- 数据清洗: 脏 model/manufacturer=0, 空串 platform_product_id=0, device.miot=0
- tracking 表已标记 V22, 避免 deploy 脚本重复执行

【部署脚本加固】
deploy-all.bat 迁移步骤加 --force: 迁移脚本中'已存在'的 DDL(重复列/索引)
属正常情况, 不应中断整体部署

验证: 4 服务全部 active; 后台首页/SPA路由/9081文档/9083文档 均 200;
产品列表 API 返回 401 未认证(路由与过滤器正常)
2026-10-27 13:49:15 +08:00

124 lines
7.1 KiB
Batchfile
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
title IoT SaaS 一键重新构建并部署(后端 4 服务 + 前端后台)
REM 非交互部署(CI/自动化):运行前先 set NONINTERACTIVE=1,例如
REM cmd /c "set NONINTERACTIVE=1 & f:\work\iotsaas\deploy-all.bat"
REM 可跳过末尾 pause,避免在自动化环境(非 TTY)用管道喂入干扰 plink。
:: ============ 环境配置(按本机实际路径修改) ============
set JAVA_HOME=C:\Program Files\Eclipse Adoptium\jdk-17.0.19.10-hotspot
set PATH=%JAVA_HOME%\bin;D:\dev\apache-maven-3.9.16\bin;%PATH%
set PLINK="C:\Program Files\PuTTY\plink.exe"
set PLINK_OPTS=-pw Tonzou@888 -hostkey aa:01:8c:1d:95:36:68:6d:c6:ad:d1:c5:45:0b:05:46 -batch tonzou@192.168.3.3
set SRV_JARS=/opt/iotsaas/backend/jars
set SRV_DIST=/opt/iotsaas/frontend/dist
set BACKEND_DIR=f:\work\iotsaas\backend
set ADMIN_DIR=f:\work\iotsaas\frontend\admin
:: iot-device 带 exec classifier,其余为 fat jar 原名
set JAR_DEV=iot-device-1.0.0-exec.jar
set JAR_DATA=iot-data-1.0.0.jar
set JAR_RULE=iot-rule-1.0.0.jar
set JAR_ALARM=iot-alarm-1.0.0.jar
:: ============ 1) 后端构建(含共享模块 iot-common) ============
cd /d %BACKEND_DIR%
echo ============================================
echo [1/7] Maven 重新构建 4 个服务(含共享模块 iot-common)
echo ============================================
call mvn clean package -pl iot-device,iot-data,iot-rule,iot-alarm -am -DskipTests
if %ERRORLEVEL% NEQ 0 ( echo [错误] 后端构建失败,已中止。 & pause & exit /b 1 )
echo [OK] 后端构建成功。
:: ============ 2) 上传 4 个 jar ============
echo ============================================
echo [2/7] 上传 4 个 jar 到服务器 %SRV_JARS%
echo ============================================
tar cf - -C %BACKEND_DIR%\iot-device\target %JAR_DEV% -C %BACKEND_DIR%\iot-data\target %JAR_DATA% -C %BACKEND_DIR%\iot-rule\target %JAR_RULE% -C %BACKEND_DIR%\iot-alarm\target %JAR_ALARM% | %PLINK% %PLINK_OPTS% tar xf - -C %SRV_JARS%
if %ERRORLEVEL% NEQ 0 ( echo [错误] jar 上传失败。 & pause & exit /b 1 )
echo [OK] jar 上传完成。
:: ============ 2.5) 数据库迁移(Flyway 在本环境未启用,按 DB 内 tracking 表应用未执行的 V 迁移) ============
echo ============================================
echo [3/7] 应用未执行的 SQL 迁移(%BACKEND_DIR%\sql\V*.sql,按 db_migrations_applied 去重)
echo ============================================
set MIG_DIR=%BACKEND_DIR%\sql
set CHK_FILE=%TEMP%\_mig_chk.txt
%PLINK% %PLINK_OPTS% "docker exec -i iot-mysql mysql -uroot -p'IoT.p2026.Xk9Qm' iot_platform -e \"CREATE TABLE IF NOT EXISTS db_migrations_applied (name VARCHAR(80) NOT NULL PRIMARY KEY, applied_at DATETIME DEFAULT CURRENT_TIMESTAMP)\"" 2>nul
REM 首次使用(tracking 表为空)则标记当前所有 V 文件为已应用:假定目标库已手工迁移到最新。
REM 全新库请先手动执行 sql/ 下全部 V 迁移,再运行本脚本(或清空 db_migrations_applied 让其补跑,需迁移文件幂等)。
%PLINK% %PLINK_OPTS% "docker exec -i iot-mysql mysql -uroot -p'IoT.p2026.Xk9Qm' iot_platform -N -e \"SELECT COUNT(*) FROM db_migrations_applied\"" 2>nul > "%CHK_FILE%"
set /p MIG_CNT=<"%CHK_FILE%"
if "!MIG_CNT!"=="0" (
echo 首次使用:标记当前所有 V 迁移为已应用(假定库已迁移到最新)
for /f "delims=" %%F in ('dir /b /on "%MIG_DIR%\V*.sql"') do call :mig_seed "%%F"
)
REM 应用未记录的 V 迁移(新增迁移文件会自动在此补跑,无需改本脚本)
for /f "delims=" %%F in ('dir /b /on "%MIG_DIR%\V*.sql"') do call :mig_apply "%%F"
echo [OK] 迁移步骤完成。
:: ============ 3) 重启 4 个后端服务 ============
echo ============================================
echo [4/7] 重启 4 个后端服务(systemd)
echo ============================================
%PLINK% %PLINK_OPTS% "echo 'Tonzou@888' | sudo -S -p '' systemctl restart iot-device iot-data iot-rule iot-alarm" 2>nul
if %ERRORLEVEL% NEQ 0 ( echo [错误] 服务重启失败。 & pause & exit /b 1 )
echo [OK] 重启命令已下发。
:: ============ 4) 构建前端后台(admin) ============
cd /d %ADMIN_DIR%
echo ============================================
echo [5/7] 构建前端后台(admin)— 跳过 vue-tsc 类型检查,直接 vite build
echo ============================================
where node >nul 2>nul || ( echo [错误] 未找到 node,请将其加入 PATH。 & pause & exit /b 1 )
call npx vite build
if %ERRORLEVEL% NEQ 0 ( echo [错误] 前端构建失败。 & pause & exit /b 1 )
echo [OK] 前端构建成功(dist 已生成)。
:: ============ 5) 上传前端 dist(先清空旧文件再解包,避免残留旧 hash 资源) ============
echo ============================================
echo [6/7] 上传前端 dist 到服务器 %SRV_DIST%
echo ============================================
%PLINK% %PLINK_OPTS% "echo 'Tonzou@888' | sudo -S -p '' rm -rf %SRV_DIST% && mkdir -p %SRV_DIST%"
if %ERRORLEVEL% NEQ 0 ( echo [错误] 清空远端 dist 失败。 & pause & exit /b 1 )
tar cf - -C %ADMIN_DIR%\dist . | %PLINK% %PLINK_OPTS% tar xf - -C %SRV_DIST%
if %ERRORLEVEL% NEQ 0 ( echo [错误] dist 上传失败。 & pause & exit /b 1 )
echo [OK] dist 上传完成。
:: ============ 6) 确认状态 ============
echo ============================================
echo [7/7] 等待启动并确认服务状态
echo ============================================
timeout /t 20 /nobreak >nul
%PLINK% %PLINK_OPTS% "echo 'Tonzou@888' | sudo -S -p '' systemctl is-active iot-device iot-data iot-rule iot-alarm" 2>nul
echo.
echo 部署完成!冒烟测试地址:
echo http://192.168.3.3:9081/doc.html (设备/鉴权/授权对接接口)
echo http://192.168.3.3/ (后台管理,含「系统管理 - 应用管理」)
echo http://192.168.3.3/api/doc/partner.html (授权对接接入指南)
echo.
REM 非交互部署(如 CI/自动化)设 set NONINTERACTIVE=1 可跳过等待按键,避免管道喂入干扰 plink
if not defined NONINTERACTIVE pause
goto :eof
:: ============ 迁移子例程(顶层执行,避开括号块内转义问题) ============
:mig_seed
%PLINK% %PLINK_OPTS% "docker exec -i iot-mysql mysql -uroot -p'IoT.p2026.Xk9Qm' iot_platform -e \"INSERT IGNORE INTO db_migrations_applied(name) VALUES('%1')\"" 2>nul
goto :eof
:mig_apply
set CHK=0
%PLINK% %PLINK_OPTS% "docker exec -i iot-mysql mysql -uroot -p'IoT.p2026.Xk9Qm' iot_platform -N -e \"SELECT COUNT(*) FROM db_migrations_applied WHERE name='%1'\"" 2>nul > "%CHK_FILE%"
set /p CHK=<"%CHK_FILE%"
if not "!CHK!"=="0" (
echo 跳过已应用 %1
goto :eof
)
echo 应用 %1 ...
REM --force: 迁移脚本中"已存在"的 DDL(重复列/索引)属正常情况,不应中断整体部署
type "%MIG_DIR%\%1" | %PLINK% %PLINK_OPTS% "docker exec -i iot-mysql mysql --force -uroot -p'IoT.p2026.Xk9Qm' iot_platform"
if !ERRORLEVEL! NEQ 0 ( echo [错误] 迁移 %1 执行失败。 & pause & exit /b 1 )
%PLINK% %PLINK_OPTS% "docker exec -i iot-mysql mysql -uroot -p'IoT.p2026.Xk9Qm' iot_platform -e \"INSERT IGNORE INTO db_migrations_applied(name) VALUES('%1')\"" 2>nul
goto :eof