diff --git a/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/controller/HotVehicleThreeInspectController.java b/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/controller/HotVehicleThreeInspectController.java index 3080485..d7ce771 100644 --- a/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/controller/HotVehicleThreeInspectController.java +++ b/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/controller/HotVehicleThreeInspectController.java @@ -5,6 +5,7 @@ import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeIn import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectProcessVo; import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeInspectVehicleQueryBo; import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectReceivedVehicleVo; +import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectSummaryVo; import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectUnreceivedVehicleVo; import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectVo; import com.hotwj.platform.config.vehicleThreeInspect.service.IHotVehicleThreeInspectPrintService; @@ -213,4 +214,13 @@ public class HotVehicleThreeInspectController extends BaseController { @PathVariable Long[] ids) { return toAjax(hotVehicleThreeInspectService.deleteWithValidByIds(List.of(ids), true)); } + + /** + * 查询车辆三检聚合列表 + */ + @GetMapping("/listSummary") + @Operation(summary = "分页查询车辆三检聚合列表") + public TableDataInfo listSummary(HotVehicleThreeInspectBo bo, PageQuery pageQuery) { + return hotVehicleThreeInspectService.querySummaryPageList(bo, pageQuery); + } } diff --git a/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/domain/vo/HotVehicleThreeInspectSummaryVo.java b/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/domain/vo/HotVehicleThreeInspectSummaryVo.java new file mode 100644 index 0000000..0253850 --- /dev/null +++ b/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/domain/vo/HotVehicleThreeInspectSummaryVo.java @@ -0,0 +1,100 @@ +package com.hotwj.platform.config.vehicleThreeInspect.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 车辆三检聚合列表视图对象 + * + * @author shihongwei + * @date 2026-06-01 + */ +@Data +public class HotVehicleThreeInspectSummaryVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 三检流程ID + */ + private Long inspectId; + + /** + * 公司ID + */ + private Long companyId; + + /** + * 车辆ID + */ + private Long vehicleId; + + /** + * 车牌号 + */ + private String plateNumber; + + /** + * 驾驶员ID + */ + private String driverId; + + /** + * 驾驶员姓名 + */ + private String driverName; + + /** + * 开始时间 + */ + private Date startTime; + + /** + * 结束时间 + */ + private Date endTime; + + /** + * 检查时间展示 + */ + private String inspectTime; + + /** + * 完成状态 + */ + private String completeStatus; + + /** + * 是否已完成 + */ + private Boolean completed; + + /** + * 审核完成状态 + */ + private String auditCompleteStatus; + + /** + * 是否完成审核 + */ + private Boolean auditCompleted; + + /** + * 出车检查 + */ + private HotVehicleThreeInspectVo outCheck; + + /** + * 行车检查 + */ + private HotVehicleThreeInspectVo drivingCheck; + + /** + * 收车检查 + */ + private HotVehicleThreeInspectVo backCheck; +} diff --git a/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/mapper/HotVehicleThreeInspectMapper.java b/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/mapper/HotVehicleThreeInspectMapper.java index 752cccc..e37df8c 100644 --- a/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/mapper/HotVehicleThreeInspectMapper.java +++ b/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/mapper/HotVehicleThreeInspectMapper.java @@ -6,6 +6,7 @@ import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeIn import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectProcessVo; import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectPrintVo; import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectReceivedVehicleVo; +import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectSummaryVo; import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectUnreceivedVehicleVo; import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectVo; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -44,6 +45,26 @@ public interface HotVehicleThreeInspectMapper extends BaseMapperPlus querySummaryPageList( + Page page, + @Param("bo") HotVehicleThreeInspectBo bo + ); + + /** + * 按流程ID集合查询三检明细 + * + * @param inspectIds 流程ID集合 + * @return 三检明细列表 + */ + List querySummaryDetailList(@Param("inspectIds") List inspectIds); + /** * 查询所有三检未收车的车辆列表 * diff --git a/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/service/IHotVehicleThreeInspectService.java b/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/service/IHotVehicleThreeInspectService.java index d0a0d95..aa86804 100644 --- a/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/service/IHotVehicleThreeInspectService.java +++ b/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/service/IHotVehicleThreeInspectService.java @@ -5,6 +5,7 @@ import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeIn import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeInspectVehicleQueryBo; import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectProcessVo; import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectReceivedVehicleVo; +import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectSummaryVo; import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectUnreceivedVehicleVo; import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectVo; import com.hotwj.platform.resourceManagement.vehicleManagement.domain.vo.HotVehicleVo; @@ -72,6 +73,15 @@ public interface IHotVehicleThreeInspectService { */ TableDataInfo queryProcessPageList(HotVehicleThreeInspectBo bo, PageQuery pageQuery); + /** + * 分页查询车辆三检聚合列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 车辆三检聚合分页列表 + */ + TableDataInfo querySummaryPageList(HotVehicleThreeInspectBo bo, PageQuery pageQuery); + /** * 查询符合条件的车辆三检列表 * diff --git a/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/service/impl/HotVehicleThreeInspectServiceImpl.java b/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/service/impl/HotVehicleThreeInspectServiceImpl.java index d033cee..4cefa3b 100644 --- a/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/service/impl/HotVehicleThreeInspectServiceImpl.java +++ b/src/main/java/com/hotwj/platform/config/vehicleThreeInspect/service/impl/HotVehicleThreeInspectServiceImpl.java @@ -11,6 +11,7 @@ import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeIn import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeInspectVehicleQueryBo; import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectProcessVo; import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectReceivedVehicleVo; +import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectSummaryVo; import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectUnreceivedVehicleVo; import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectVo; import com.hotwj.platform.config.vehicleThreeInspectConfig.domain.HotVehicleThreeInspectConfig; @@ -197,6 +198,47 @@ public class HotVehicleThreeInspectServiceImpl implements IHotVehicleThreeInspec return TableDataInfo.build(result); } + /** + * 分页查询车辆三检聚合列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 车辆三检聚合分页列表 + */ + @Override + public TableDataInfo querySummaryPageList(HotVehicleThreeInspectBo bo, PageQuery pageQuery) { + Page result = baseMapper.querySummaryPageList(pageQuery.build(), bo); + List summaryList = result.getRecords(); + if (summaryList == null || summaryList.isEmpty()) { + return TableDataInfo.build(result); + } + + List inspectIds = summaryList.stream() + .map(HotVehicleThreeInspectSummaryVo::getInspectId) + .filter(Objects::nonNull) + .toList(); + if (inspectIds.isEmpty()) { + return TableDataInfo.build(result); + } + + List detailList = baseMapper.querySummaryDetailList(inspectIds); + fillTaskId(detailList); + + Map summaryMap = new HashMap<>(summaryList.size()); + for (HotVehicleThreeInspectSummaryVo summary : summaryList) { + summaryMap.put(summary.getInspectId(), summary); + } + + for (HotVehicleThreeInspectVo detail : detailList) { + HotVehicleThreeInspectSummaryVo summary = summaryMap.get(detail.getInspectId()); + if (summary == null) { + continue; + } + attachPhaseDetail(summary, detail); + } + return TableDataInfo.build(result); + } + /** * 查询符合条件的车辆三检列表 * @@ -209,6 +251,39 @@ public class HotVehicleThreeInspectServiceImpl implements IHotVehicleThreeInspec return baseMapper.selectVoList(lqw); } + private void attachPhaseDetail(HotVehicleThreeInspectSummaryVo summary, HotVehicleThreeInspectVo detail) { + if (summary == null || detail == null || detail.getInspectPhase() == null) { + return; + } + if (detail.getInspectPhase() == 1L) { + if (summary.getOutCheck() == null) { + summary.setOutCheck(detail); + } + return; + } + if (detail.getInspectPhase() == 2L) { + if (summary.getDrivingCheck() == null) { + summary.setDrivingCheck(detail); + } + return; + } + if (detail.getInspectPhase() == 3L && summary.getBackCheck() == null) { + summary.setBackCheck(detail); + } + } + + private void fillTaskId(List list) { + if (list == null || list.isEmpty()) { + return; + } + String businessUserId = LoginHelper.getBusinessUserId(); + for (HotVehicleThreeInspectVo vo : list) { + if (StringUtils.isNotBlank(vo.getInstanceId())) { + vo.setTaskId(flowService.getTaskId(vo.getInstanceId(), businessUserId)); + } + } + } + private LambdaQueryWrapper buildQueryWrapper(HotVehicleThreeInspectBo bo) { Map params = bo.getParams(); LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); diff --git a/src/main/resources/deployment.env b/src/main/resources/deployment.env deleted file mode 100644 index 6efb8b9..0000000 --- a/src/main/resources/deployment.env +++ /dev/null @@ -1,35 +0,0 @@ -# 部署服务器信息 -SERVER=162.14.96.191 -PORT=6000 -USER=root -PASSWORD=a631223 -# KEYFILE=/home/user/.ssh/id_rsa -#FINGERPRINT=SHA256:REPLACE_WITH_SERVER_FINGERPRINT - -# 部署环境 -ENV=prod - -# 构建工具 -BUILD_TOOL=maven -PROJECT_DIR=G:\code\shw\hot\hot-platform-backend -MAVEN_HOME=G:\soft\apache-maven-3.9.3-bin\apache-maven-3.9.3 -# GRADLE_HOME= -# TARGET_JAR_NAME= - -# 远端部署目录 -REMOTE_DIR=/apps/hot/ - -# JVM 启动参数 -JVM_OPTS=-Xms512m -Xmx1024m - -# 部署验证接口地址 -DEPLOY_INFO_URL=http://api.xiaoshi98.top/deploy/info - -# 回滚告警通知地址(可选) -# ALERT_WEBHOOK=https://example.com/webhook - -# 运行选项 -# 演示模式 -DRY_RUN=false -# 跳过打包 -SKIP_BUILD=false diff --git a/src/main/resources/deployment.frontend.env b/src/main/resources/deployment.frontend.env deleted file mode 100644 index 3787493..0000000 --- a/src/main/resources/deployment.frontend.env +++ /dev/null @@ -1,30 +0,0 @@ -# 部署服务器信息 -SERVER=162.14.96.191 -PORT=6000 -USER=root -PASSWORD=a631223 -# KEYFILE=/home/user/.ssh/id_rsa -# FINGERPRINT=SHA256:REPLACE_WITH_SERVER_FINGERPRINT - -# 部署环境 -ENV=prod - -# 构建设置(前端) -# 构建命令;未设置时,脚本默认使用:npm run build:prod -BUILD_CMD=npm run build:prod -# 本地前端项目目录(请按实际路径调整) -PROJECT_DIR=G:\code\shw\hot\hot-platform-frontend -# 构建产物目录(相对 PROJECT_DIR 或绝对路径),未设置时默认为 PROJECT_DIR\dist -DIST_DIR=dist - -# 远端部署目录(服务器上前端静态资源目录) -REMOTE_DIR=/apps/hot/front-end/dist - -# 传输与运行选项 -TRANSFER_TIMEOUT=300 -# 演示模式(不执行真实操作) -DRY_RUN=false -# 跳过构建 -SKIP_BUILD=false -# 跳过清理远端目录 -SKIP_CLEAN_REMOTE=false diff --git a/src/main/resources/deployment.py b/src/main/resources/deployment.py deleted file mode 100644 index 7ec3b3f..0000000 --- a/src/main/resources/deployment.py +++ /dev/null @@ -1,734 +0,0 @@ -"""部署脚本(支持 .env/.json 配置) -实现以下规范: -- 构建:Maven/Gradle clean 并跳过测试,生成可执行 jar -- 严格校验:校验 BOOT-INF/classes 配置文件与 MANIFEST 元数据 -- 安全传输:SCP 非 22 端口 + 指纹校验 + MD5 验证 -- 预检查:JDK17+、磁盘空间 >= 2GB、ulimit 满足 SpringBoot3 -- 进程管理:优雅停止(SIGTERM 30s)超时强杀(SIGKILL) -- 备份保留:/opt/app/backups 保留最近 5 个版本 -- 启动与健康:nohup 启动、120s 日志无 ERROR、actuator/health=200 -- 回滚与告警:自动回滚最近可用版本并发送告警 -- 日志:JSON Lines 记录所有步骤与环境信息 - -配置优先级: -- 优先读取 DEPLOY_CONFIG 指定的路径(.env 或 .json) -- 未指定时优先加载同目录下 deployment.env,其次 deployment.config.json -""" -import base64 -import datetime -import hashlib -import json -import os -import re -import shutil -import socket -import subprocess -import sys -import time -import urllib.request -import urllib.error -import zipfile -import paramiko -from types import SimpleNamespace - - -def _ts_ms(): - """返回当前时间戳(毫秒)""" - return int(time.time() * 1000) - - -def _log_path(): - """返回部署日志文件路径(JSONL 格式)""" - return os.path.join(os.path.dirname(__file__), "deployment.log.jsonl") - - -def log_step(step, status, details=None, extra=None, env=None): - """记录部署步骤到 JSONL 日志 - step:步骤名称;status:ok/fail/skip 等; - details/extra:附加信息;env:环境信息(服务器、目录等) - """ - rec = { - "timestamp_ms": _ts_ms(), - "step": step, - "status": status, - "details": details or "", - "extra": extra or {}, - } - if env: - rec["env"] = env - with open(_log_path(), "a", encoding="utf-8") as f: - f.write(json.dumps(rec, ensure_ascii=False) + "\n") - - -def print_progress(percent, step, status="进行中", details=None): - try: - msg = f"[{str(percent).rjust(3)}%] {step} - {status}" - if details: - msg += f" | {details}" - print(msg, flush=True) - except Exception: - pass - - -def run_cmd(cmd, cwd=None, env=None, capture_output=True, timeout=None): - """运行本地命令并返回 (返回码, 标准输出, 标准错误)""" - try: - p = subprocess.run( - cmd, - shell=True, - cwd=cwd, - env=env, - stdout=subprocess.PIPE if capture_output else None, - stderr=subprocess.PIPE if capture_output else None, - timeout=timeout, - ) - out = p.stdout.decode("utf-8", errors="replace") if capture_output and p.stdout else "" - err = p.stderr.decode("utf-8", errors="replace") if capture_output and p.stderr else "" - return p.returncode, out, err - except subprocess.TimeoutExpired as te: - out = te.stdout.decode("utf-8", errors="replace") if capture_output and te.stdout else "" - err = te.stderr.decode("utf-8", errors="replace") if capture_output and te.stderr else "command timeout" - return 124, out, err - - -def md5_file(path): - """计算本地文件 MD5 摘要""" - h = hashlib.md5() - with open(path, "rb") as f: - for chunk in iter(lambda: f.read(8192), b""): - h.update(chunk) - return h.hexdigest() - - -def sha256_bytes(b): - """计算字节的 SHA256 并返回 base64 字符串(用于指纹)""" - return base64.b64encode(hashlib.sha256(b).digest()).decode("ascii") - - -def find_jar(project_dir, build_tool, target_jar_name=None): - """定位构建产物 jar 文件(支持指定文件名或自动选择最新)""" - if target_jar_name: - maven_path = os.path.join(project_dir, "target", target_jar_name) - gradle_path = os.path.join(project_dir, "build", "libs", target_jar_name) - if os.path.exists(maven_path): - return maven_path - if os.path.exists(gradle_path): - return gradle_path - return target_jar_name - if build_tool == "maven": - target = os.path.join(project_dir, "target") - else: - target = os.path.join(project_dir, "build", "libs") - if not os.path.isdir(target): - return None - jars = [] - for name in os.listdir(target): - if name.endswith(".jar"): - jars.append(os.path.join(target, name)) - if not jars: - return None - jars.sort(key=lambda p: os.path.getmtime(p), reverse=True) - return jars[0] - - -def verify_jar_integrity(jar_path): - """校验 jar 完整性(支持 Boot Jar 与 Boot War): - - 必须存在 application.yml、application-prod.yml、logback-plus.xml(在 classes 下) - - MANIFEST 必须包含 Start-Class / Implementation-Version / Build-Time - """ - z = zipfile.ZipFile(jar_path) - names = set(z.namelist()) - prefixes = ["BOOT-INF/classes/", "WEB-INF/classes/"] - files = ["application.yml", "application-prod.yml", "logback-plus.xml"] - missing = [] - for f in files: - if not any((p + f) in names for p in prefixes): - missing.append(f) - ok = len(missing) == 0 - manifest = None - if "META-INF/MANIFEST.MF" in names: - manifest = z.read("META-INF/MANIFEST.MF").decode("utf-8", errors="replace") - else: - ok = False - if manifest: - has_start_class = bool(re.search(r"^Start-Class:\s*.+", manifest, re.MULTILINE)) - has_impl_ver = bool(re.search(r"^Implementation-Version:\s*.+", manifest, re.MULTILINE)) - has_build_time = bool(re.search(r"^Build-Time:\s*.+", manifest, re.MULTILINE)) - if not (has_start_class and has_impl_ver and has_build_time): - ok = False - if not has_start_class: - missing.append("Start-Class") - if not has_impl_ver: - missing.append("Implementation-Version") - if not has_build_time: - missing.append("Build-Time") - z.close() - return ok, missing - - -def parse_manifest_info(jar_path): - """解析 MANIFEST.MF 中的关键字段信息""" - z = zipfile.ZipFile(jar_path) - info = {} - if "META-INF/MANIFEST.MF" in z.namelist(): - m = z.read("META-INF/MANIFEST.MF").decode("utf-8", errors="replace") - for k in ["Start-Class", "Implementation-Version", "Build-Time"]: - mobj = re.search(rf"^{k}:\s*(.+)$", m, re.MULTILINE) - if mobj: - info[k] = mobj.group(1).strip() - z.close() - return info - - -def maven_build(project_dir, maven_home): - """执行 Maven 构建(跳过测试)""" - mvn = os.path.join(maven_home, "bin", "mvn.cmd") if os.name == "nt" else os.path.join(maven_home, "bin", "mvn") - cmd = f'"{mvn}" clean package -DskipTests=true' - return run_cmd(cmd, cwd=project_dir, capture_output=False) - - -def gradle_build(project_dir, gradle_home=None): - """执行 Gradle 构建(跳过测试,优先使用 gradlew)""" - gradlew = os.path.join(project_dir, "gradlew.cmd") if os.name == "nt" else os.path.join(project_dir, "gradlew") - if os.path.exists(gradlew): - cmd = f'"{gradlew}" clean build -x test' - return run_cmd(cmd, cwd=project_dir, capture_output=False) - if gradle_home: - gradle_bin = os.path.join(gradle_home, "bin", "gradle") - if os.name == "nt": - gradle_bin += ".bat" - cmd = f'"{gradle_bin}" clean build -x test' - return run_cmd(cmd, cwd=project_dir, capture_output=False) - return 1, "", "gradle not found" - - -def connect_ssh(host, port, user, password=None, keyfile=None): - """建立 SSH 连接(密码或密钥两种方式)""" - ssh = paramiko.SSHClient() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - if keyfile: - ssh.connect(host, port=port, username=user, key_filename=keyfile) - else: - ssh.connect(host, port=port, username=user, password=password) - try: - ssh.get_transport().set_keepalive(15) - except Exception: - pass - return ssh - - -def ensure_fingerprint(host, port, user, password, keyfile, expect_fp): - """校验服务器指纹(要求与配置的 SHA256 指纹一致)""" - ssh = connect_ssh(host, port, user, password, keyfile) - k = ssh.get_transport().get_remote_server_key() - fp = "SHA256:" + sha256_bytes(base64.b64decode(k.get_base64())) - ssh.close() - return fp == expect_fp, fp - - -def scp_transfer(local_path, host, port, user, remote_path, timeout=None): - """使用 scp 非标准端口传输文件到远端目录""" - cmd = ( - f'scp -o BatchMode=yes ' - f'-o ConnectTimeout=30 -o ServerAliveInterval=15 -o ServerAliveCountMax=3 ' - f'-o StrictHostKeyChecking=accept-new -P {port} "{local_path}" {user}@{host}:"{remote_path}"' - ) - return run_cmd(cmd, timeout=timeout) - - -def sftp_transfer(ssh, local_path, remote_path): - sftp = ssh.open_sftp() - try: - sftp.put(local_path, remote_path) - return 0, "", "" - finally: - try: - sftp.close() - except Exception: - pass - - -def ssh_exec(ssh, cmd, timeout=None, print_output=True): - """执行远端命令并返回 (返回码, 标准输出, 标准错误)""" - if print_output: - print(f'SSH$ {cmd}', flush=True) - stdin, stdout, stderr = ssh.exec_command(cmd, timeout=timeout) - out = stdout.read().decode("utf-8", errors="replace") - err = stderr.read().decode("utf-8", errors="replace") - rc = stdout.channel.recv_exit_status() - if print_output: - if out: - try: - print(out if out.endswith("\n") else out + "\n", end="", flush=True) - except Exception: - pass - if err: - try: - print(err if err.endswith("\n") else err + "\n", end="", flush=True) - except Exception: - pass - return rc, out, err - - -def remote_md5(ssh, path): - """获取远端文件摘要(优先 md5sum,失败则尝试 sha256sum)""" - rc, out, _ = ssh_exec(ssh, f'md5sum "{path}" || sha256sum "{path}"') - algo = "md5" - if " " in out.strip(): - val = out.strip().split()[0] - if rc != 0: - return None, algo - return val, algo - return None, algo - - -def pre_checks(ssh, remote_dir): - ok = True - details = {} - rc, out, _ = ssh_exec(ssh, "java -version") - version_match = re.search(r'version \"(?P[\\d\\.]+)', out) or re.search(r'openjdk version \"(?P[\\d\\.]+)', out) - if version_match: - major = int(version_match.group("v").split(".")[0]) - details["java_major"] = major - details["java_ok"] = major >= 17 - ok = ok and details["java_ok"] - else: - details["java_ok"] = False - ok = False - rc, out, _ = ssh_exec(ssh, f'df -Pk "{remote_dir}" | tail -1') - parts = out.split() - if len(parts) >= 4: - try: - avail_k = int(parts[3]) - except: - avail_k = 0 - details["disk_avail_k"] = avail_k - details["disk_ok"] = avail_k >= 2 * 1024 * 1024 - ok = ok and details["disk_ok"] - else: - details["disk_ok"] = False - ok = False - rc, out, _ = ssh_exec(ssh, "ulimit -n") - try: - nofile = int(out.strip()) - details["ulimit_nofile"] = nofile - details["ulimit_nofile_ok"] = nofile >= 65535 - ok = ok and details["ulimit_nofile_ok"] - except: - details["ulimit_nofile_ok"] = False - ok = False - rc, out, _ = ssh_exec(ssh, "ulimit -u") - try: - nproc = int(out.strip()) - details["ulimit_nproc"] = nproc - details["ulimit_nproc_ok"] = nproc >= 4096 - ok = ok and details["ulimit_nproc_ok"] - except: - details["ulimit_nproc_ok"] = False - ok = False - return ok, details - - -def stop_process(ssh, jar_name): - """优雅停止现有服务:SIGTERM 等待 30s,超时则 SIGKILL""" - rc, out, _ = ssh_exec(ssh, f"ps -ef | grep 'java.*{re.escape(jar_name)}' | grep -v grep | awk '{{print $2}}'") - pids = [p.strip() for p in out.splitlines() if p.strip()] - for pid in pids: - ssh_exec(ssh, f"kill -15 {pid}") - deadline = time.time() + 30 - remaining = pids[:] - while time.time() < deadline and remaining: - rc, out, _ = ssh_exec(ssh, f"ps -o pid= -p {' '.join(remaining)}") - alive = [p.strip() for p in out.splitlines() if p.strip()] - remaining = alive - time.sleep(1) - for pid in remaining: - ssh_exec(ssh, f"kill -9 {pid}") - return True - - -def backup_remote(ssh, remote_dir, jar_name, impl_ver): - """备份当前版本至 /opt/app/backups,并保留最近 5 个版本""" - ssh_exec(ssh, f'mkdir -p "{remote_dir}/backups"') - ts = datetime.datetime.utcnow().strftime("%Y%m%d%H%M%S") - bn = os.path.splitext(jar_name)[0] - ver = impl_ver or "unknown" - backup_name = f"{bn}-{ver}-{ts}.jar" - ssh_exec(ssh, f'if [ -f "{remote_dir}/{jar_name}" ]; then cp -f "{remote_dir}/{jar_name}" "{remote_dir}/backups/{backup_name}"; fi') - rc, out, _ = ssh_exec(ssh, f'ls -1t "{remote_dir}/backups" | grep -E "^{re.escape(bn)}-.*\\.jar$"') - lines = [l.strip() for l in out.splitlines() if l.strip()] - if len(lines) > 5: - to_del = lines[5:] - names = " ".join([f'"{remote_dir}/backups/{x}"' for x in to_del]) - ssh_exec(ssh, f"rm -f {names}") - return backup_name - - -def start_service(ssh, remote_dir, jar_name, jvm_opts): - """使用 nohup 启动服务并返回 PID 与日志路径""" - log_path = f"{remote_dir}/log.out" - cmd = f'nohup java {jvm_opts} -jar "{remote_dir}/{jar_name}" > "{log_path}" 2>&1 &' - ssh_exec(ssh, cmd) - time.sleep(2) - rc, out, _ = ssh_exec(ssh, f"ps -ef | grep 'java.*{re.escape(jar_name)}' | grep -v grep | awk '{{print $2}}' | head -1") - pid = out.strip() if out.strip() else "" - return pid, log_path - - -def monitor_logs_no_error(ssh, log_path, seconds): - """在指定时长内监控启动日志,若出现 ERROR 则失败""" - end = time.time() + seconds - while time.time() < end: - rc, out, _ = ssh_exec(ssh, f'tail -n 200 "{log_path}" || true', print_output=False) - if "ERROR" in out: - return False - time.sleep(2) - return True - - -def detect_crash_within(ssh, jar_name, seconds): - """监控指定时长,若进程曾启动后又消失,判定为崩溃""" - end = time.time() + seconds - alive_once = False - while time.time() < end: - rc, out, _ = ssh_exec(ssh, f"ps -ef | grep 'java.*{re.escape(jar_name)}' | grep -v grep | awk '{{print $2}}'") - pids = [p.strip() for p in out.splitlines() if p.strip()] - if pids: - alive_once = True - else: - if alive_once: - return True - time.sleep(5) - return False - - -def verify_deploy_info(ssh, url, expected_version=None, expected_build_time=None): - """调用部署验证接口并校验版本与构建时间""" - rc, code_str, _ = ssh_exec(ssh, f'curl -s -o /dev/null -w "%{{http_code}}" "{url}"') - rc2, body, _ = ssh_exec(ssh, f'curl -s "{url}"') - http_code = code_str.strip() - ok_http = http_code == "200" - version = None - build_time = None - try: - data = json.loads(body) - payload = data.get("data") if isinstance(data, dict) and "data" in data else data - if isinstance(payload, dict): - version = payload.get("version") - build_time = payload.get("buildTime") - except: - pass - match_ver = (expected_version is None) or (version == expected_version) - match_bt = (not expected_build_time) or (build_time == expected_build_time) - ok = ok_http and match_ver and match_bt - return ok, {"http_code": http_code, "version": version, "buildTime": build_time} - - -def rollback(ssh, remote_dir, jar_name, jvm_opts): - """自动回滚到最近可用备份并重启服务""" - rc, out, _ = ssh_exec(ssh, f'ls -1t "{remote_dir}/backups" | grep -E "^{re.escape(os.path.splitext(jar_name)[0])}-.*\\.jar$" | head -1') - last = out.strip() - if not last: - return False - ssh_exec(ssh, f'cp -f "{remote_dir}/backups/{last}" "{remote_dir}/{jar_name}"') - pid, log_path = start_service(ssh, remote_dir, jar_name, jvm_opts) - return True - - -def send_alert(webhook, payload): - """向运维告警地址发送 JSON 通知(可选)""" - if not webhook: - return False - data = json.dumps(payload).encode("utf-8") - req = urllib.request.Request(webhook, data=data, headers={"Content-Type": "application/json"}) - try: - urllib.request.urlopen(req, timeout=10) - return True - except urllib.error.URLError: - return False - - -def parse_bool(s): - """解析布尔字符串(支持 true/false/1/0/yes/no/on/off)""" - if s is None: - return False - v = str(s).strip().lower() - return v in ("1", "true", "yes", "y", "on") - - -def load_env_file(path): - """加载 .env 配置文件:支持注释与引号包裹的值""" - data = {} - with open(path, "r", encoding="utf-8") as f: - for raw in f: - line = raw.strip() - if not line: - continue - if line.startswith("#") or line.startswith(";"): - continue - if "=" not in line: - continue - k, v = line.split("=", 1) - k = k.strip() - v = v.strip() - if (v.startswith('"') and v.endswith('"')) or (v.startswith("'") and v.endswith("'")): - v = v[1:-1] - data[k] = v - return { - "server": data.get("SERVER"), - "port": int(data.get("PORT")) if data.get("PORT") else None, - "user": data.get("USER"), - "password": data.get("PASSWORD"), - "keyfile": data.get("KEYFILE"), - "fingerprint": data.get("FINGERPRINT"), - "env": data.get("ENV"), - "build_tool": data.get("BUILD_TOOL"), - "project_dir": data.get("PROJECT_DIR"), - "maven_home": data.get("MAVEN_HOME"), - "gradle_home": data.get("GRADLE_HOME"), - "target_jar_name": data.get("TARGET_JAR_NAME"), - "remote_dir": data.get("REMOTE_DIR") or "/opt/app", - "jvm_opts": data.get("JVM_OPTS") or "-Xms512m -Xmx1024m", - "transfer_method": (data.get("TRANSFER_METHOD") or "sftp").lower(), - "transfer_timeout": int(data.get("TRANSFER_TIMEOUT")) if data.get("TRANSFER_TIMEOUT") else 300, - "deploy_info_url": data.get("DEPLOY_INFO_URL") or "http://localhost:8080/deploy/info", - "alert_webhook": data.get("ALERT_WEBHOOK"), - "dry_run": parse_bool(data.get("DRY_RUN")), - "skip_build": parse_bool(data.get("SKIP_BUILD")), - "skip_prechecks": parse_bool(data.get("SKIP_PRECHECKS")), - "precheck_strict": parse_bool(data.get("PRECHECK_STRICT")), - "crash_monitor_seconds": int(data.get("CRASH_MONITOR_SECONDS")) if data.get("CRASH_MONITOR_SECONDS") else 60, - "skip_crash_monitor": parse_bool(data.get("SKIP_CRASH_MONITOR")), - } - - -def main(): - """主流程:加载配置 -> 构建 -> 校验 -> 传输 -> 预检 -> 停止 -> 备份 -> 启动 -> 健康 -> 回滚/完成""" - default_env = os.path.join(os.path.dirname(__file__), "deployment.env") - cfg_path = os.getenv("DEPLOY_CONFIG") or default_env - # 仅支持 .env 配置文件,并校验必填字段 - if not os.path.isfile(cfg_path): - log_step("config_load", "fail", {"path": cfg_path}) - print(f"配置文件未找到:{cfg_path}", file=sys.stderr) - sys.exit(1) - cfg = load_env_file(cfg_path) - required = ["server", "port", "user", "env", "build_tool", "project_dir"] - missing = [] - for k in required: - v = cfg.get(k) - if v is None or (isinstance(v, str) and v.strip() == ""): - missing.append(k) - if missing: - log_step("config_validate", "fail", {"missing": missing, "path": cfg_path}) - print(f"配置错误:缺少必填项 {', '.join(missing)};配置文件:{cfg_path}", file=sys.stderr) - sys.exit(1) - print_progress(0, "启动部署", "进行中", f"配置文件:{cfg_path}") - - args = SimpleNamespace( - server=cfg["server"], - port=int(cfg["port"]), - user=cfg["user"], - password=cfg.get("password"), - keyfile=cfg.get("keyfile"), - fingerprint=cfg["fingerprint"], - env=cfg["env"], - build_tool=cfg["build_tool"], - project_dir=cfg["project_dir"], - maven_home=cfg.get("maven_home"), - gradle_home=cfg.get("gradle_home"), - target_jar_name=cfg.get("target_jar_name"), - remote_dir=cfg.get("remote_dir", "/opt/app"), - jvm_opts=cfg.get("jvm_opts", "-Xms512m -Xmx1024m"), - deploy_info_url=cfg.get("deploy_info_url", "http://localhost:8080/deploy/info"), - alert_webhook=cfg.get("alert_webhook"), - dry_run=bool(cfg.get("dry_run", False)), - skip_build=bool(cfg.get("skip_build", False)), - transfer_method=cfg.get("transfer_method", "sftp"), - transfer_timeout=int(cfg.get("transfer_timeout", 300)), - skip_prechecks=bool(cfg.get("skip_prechecks", False)), - precheck_strict=bool(cfg.get("precheck_strict", False)), - crash_monitor_seconds=int(cfg.get("crash_monitor_seconds", 60)), - skip_crash_monitor=bool(cfg.get("skip_crash_monitor", False)), - ) - - env_info = { - "server": args.server, - "port": args.port, - "env": args.env, - "build_tool": args.build_tool, - "project_dir": args.project_dir, - "remote_dir": args.remote_dir, - } - - try: - if args.dry_run: - log_step("start", "dry_run", {"config_path": cfg_path}, env=env_info) - print_progress(5, "加载配置", "完成") - else: - log_step("start", "running", {"config_path": cfg_path}, env=env_info) - print_progress(5, "加载配置", "完成") - - if not args.skip_build: - print_progress(10, "开始构建", "进行中", args.build_tool) - if args.build_tool == "maven": - rc, out, err = maven_build(args.project_dir, args.maven_home or "") - else: - rc, out, err = gradle_build(args.project_dir, args.gradle_home) - log_step("build", "ok" if rc == 0 else "fail", {"stdout": out[-1000:], "stderr": err[-1000:]}, env=env_info) - print_progress(20, "构建完成", "成功" if rc == 0 else "失败") - if rc != 0 and not args.dry_run: - sys.exit(1) - else: - print_progress(10, "跳过构建", "已跳过") - - jar_path = find_jar(args.project_dir, args.build_tool, args.target_jar_name) - if not jar_path or not os.path.isfile(jar_path): - log_step("find_jar", "fail", {"path": jar_path}, env=env_info) - print_progress(25, "查找构建产物", "失败") - if not args.dry_run: - sys.exit(1) - else: - log_step("find_jar", "ok", {"path": jar_path}, env=env_info) - print_progress(25, "查找构建产物", "成功", os.path.basename(jar_path)) - - if jar_path and os.path.isfile(jar_path): - ok, missing = verify_jar_integrity(jar_path) - manifest_info = parse_manifest_info(jar_path) - log_step("jar_integrity", "ok" if ok else "fail", {"manifest": manifest_info, "missing": missing}, env=env_info) - print_progress(35, "校验产物完整性", "成功" if ok else f"失败(缺失: {', '.join(missing)})") - if not ok and not args.dry_run: - sys.exit(1) - - if args.dry_run: - log_step("fingerprint_check", "skip", env=env_info) - log_step("transfer", "skip", env=env_info) - log_step("pre_checks", "skip", env=env_info) - log_step("stop_process", "skip", env=env_info) - log_step("backup", "skip", env=env_info) - log_step("start_service", "skip", env=env_info) - log_step("monitor_crash", "skip", env=env_info) - print_progress(100, "完成", "DRY-RUN") - log_step("done", "ok", env=env_info) - return - - if args.fingerprint: - print_progress(40, "校验服务器指纹", "进行中") - ok_fp, actual_fp = ensure_fingerprint(args.server, args.port, args.user, args.password, args.keyfile, args.fingerprint) - log_step("fingerprint_check", "ok" if ok_fp else "fail", {"actual": actual_fp}, env=env_info) - print_progress(45, "校验服务器指纹", "成功" if ok_fp else "失败") - if not ok_fp: - print(f"服务器指纹不匹配:期望 {args.fingerprint},实际 {actual_fp}", file=sys.stderr) - sys.exit(1) - else: - log_step("fingerprint_check", "skip", {"reason": "no_fingerprint_provided"}, env=env_info) - print_progress(40, "校验服务器指纹", "已跳过") - - print_progress(50, "连接服务器", "进行中") - ssh = connect_ssh(args.server, args.port, args.user, args.password, args.keyfile) - ssh.exec_command(f'mkdir -p "{args.remote_dir}"') - print_progress(55, "连接服务器", "成功") - - local_md5 = md5_file(jar_path) - print_progress(60, "传输文件", "进行中", os.path.basename(jar_path)) - if args.transfer_method == "scp": - rc, out, err = scp_transfer( - jar_path, - args.server, - args.port, - args.user, - f'{args.remote_dir}/{os.path.basename(jar_path)}', - timeout=args.transfer_timeout, - ) - else: - rc, out, err = sftp_transfer( - ssh, - jar_path, - f'{args.remote_dir}/{os.path.basename(jar_path)}', - ) - log_step("transfer", "ok" if rc == 0 else "fail", {"stdout": out[-1000:], "stderr": err[-1000:]}, env=env_info) - print_progress(65, "传输文件", "成功" if rc == 0 else "失败") - if rc != 0: - ssh.close() - sys.exit(1) - - remote_hash, algo = remote_md5(ssh, f'{args.remote_dir}/{os.path.basename(jar_path)}') - match = remote_hash == local_md5 if algo == "md5" and remote_hash else True - log_step("md5_verify", "ok" if match else "fail", {"local_md5": local_md5, "remote_hash": remote_hash, "algo": algo}, env=env_info) - print_progress(70, "校验文件摘要", "成功" if match else "失败") - if not match: - ssh.close() - sys.exit(1) - - if args.skip_prechecks: - log_step("pre_checks", "skip", env=env_info) - print_progress(75, "预部署检查", "已跳过") - else: - checks_ok, check_details = pre_checks(ssh, args.remote_dir) - status = "ok" if checks_ok else ("fail" if args.precheck_strict else "warn") - log_step("pre_checks", status, check_details, env=env_info) - print_progress(75, "预部署检查", "成功" if checks_ok else ("警告" if not args.precheck_strict else "失败")) - if not checks_ok and args.precheck_strict: - ssh.close() - sys.exit(1) - - jar_name = os.path.basename(jar_path) - print_progress(78, "停止现有服务", "进行中") - stop_process(ssh, jar_name) - log_step("stop_process", "ok", env=env_info) - print_progress(80, "停止现有服务", "完成") - - backup_name = backup_remote(ssh, args.remote_dir, jar_name, parse_manifest_info(jar_path).get("Implementation-Version")) - log_step("backup", "ok", {"backup": backup_name}, env=env_info) - print_progress(82, "备份旧版本", "完成", backup_name) - - pid, log_path = start_service(ssh, args.remote_dir, jar_name, args.jvm_opts) - log_step("start_service", "ok" if pid else "fail", {"pid": pid, "log": log_path}, env=env_info) - print_progress(85, "启动服务", "成功" if pid else "失败", f"pid={pid}") - if not pid: - ssh.close() - sys.exit(1) - - log_step("startup_logs", "skip", env=env_info) - print_progress(90, "监控启动日志", "已跳过") - - last_metrics = {} - - if args.skip_crash_monitor: - log_step("monitor_crash", "skip", env=env_info) - print_progress(97, f"崩溃监控({args.crash_monitor_seconds}秒)", "已跳过") - else: - crashed = detect_crash_within(ssh, jar_name, args.crash_monitor_seconds) - log_step("monitor_crash", "fail" if crashed else "ok", env=env_info) - print_progress(97, f"崩溃监控({args.crash_monitor_seconds}秒)", "失败" if crashed else "通过") - - ver_expected = parse_manifest_info(jar_path).get("Implementation-Version") - bt_expected = parse_manifest_info(jar_path).get("Build-Time") - print_progress(98, "验证部署接口", "进行中") - ok_deploy, deploy_info = verify_deploy_info(ssh, args.deploy_info_url, ver_expected, bt_expected) - log_step("deploy_info_check", "ok" if ok_deploy else "fail", {"expect_version": ver_expected, "expect_build_time": bt_expected, "response": deploy_info}, env=env_info) - print_progress(98, "验证部署接口", "成功" if ok_deploy else "失败") - - if crashed or not ok_deploy: - print_progress(98, "触发回滚", "进行中") - rb_ok = rollback(ssh, args.remote_dir, jar_name, args.jvm_opts) - log_step("rollback", "ok" if rb_ok else "fail", env=env_info) - print_progress(99, "回滚完成", "成功" if rb_ok else "失败") - send_alert(args.alert_webhook, {"event": "rollback", "server": args.server, "env": args.env, "reason": "deploy_info_fail_or_crash"}) - ssh.close() - if not rb_ok: - sys.exit(1) - sys.exit(0) - - log_step("done", "ok", {"metrics": last_metrics}, env=env_info) - print_progress(100, "部署完成", "成功") - ssh.close() - except Exception as e: - import traceback - log_step("exception", "fail", {"error": str(e), "trace": traceback.format_exc()}, env=env_info) - print_progress(100, "部署失败", "异常", str(e)) - print(traceback.format_exc(), file=sys.stderr) - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/src/main/resources/deployment_frontend.py b/src/main/resources/deployment_frontend.py deleted file mode 100644 index c8d985b..0000000 --- a/src/main/resources/deployment_frontend.py +++ /dev/null @@ -1,193 +0,0 @@ -import json -import os -import sys -import time -from types import SimpleNamespace - -from deployment import ( - log_step, - print_progress, - run_cmd, - connect_ssh, - ssh_exec, - parse_bool, -) - - -def load_frontend_env(path): - data = {} - with open(path, "r", encoding="utf-8") as f: - for raw in f: - line = raw.strip() - if not line: - continue - if line.startswith("#") or line.startswith(";"): - continue - if "=" not in line: - continue - k, v = line.split("=", 1) - k = k.strip() - v = v.strip() - if (v.startswith('"') and v.endswith('"')) or (v.startswith("'") and v.endswith("'")): - v = v[1:-1] - data[k] = v - return { - "server": data.get("SERVER"), - "port": int(data.get("PORT")) if data.get("PORT") else None, - "user": data.get("USER"), - "password": data.get("PASSWORD"), - "keyfile": data.get("KEYFILE"), - "fingerprint": data.get("FINGERPRINT"), - "env": data.get("ENV"), - "project_dir": data.get("PROJECT_DIR"), - "build_cmd": data.get("BUILD_CMD") or "npm run build:prod", - "dist_dir": data.get("DIST_DIR"), - "remote_dir": data.get("REMOTE_DIR") or "/opt/app/frontend", - "transfer_timeout": int(data.get("TRANSFER_TIMEOUT")) if data.get("TRANSFER_TIMEOUT") else 300, - "skip_build": parse_bool(data.get("SKIP_BUILD")), - "dry_run": parse_bool(data.get("DRY_RUN")), - "skip_clean_remote": parse_bool(data.get("SKIP_CLEAN_REMOTE")), - } - - -def sftp_upload_dir(ssh, local_dir, remote_dir): - sftp = ssh.open_sftp() - try: - try: - sftp.stat(remote_dir) - except IOError: - sftp.mkdir(remote_dir) - for root, dirs, files in os.walk(local_dir): - rel = os.path.relpath(root, local_dir) - if rel == ".": - remote_root = remote_dir - else: - remote_root = remote_dir.rstrip("/") + "/" + rel.replace(os.sep, "/") - try: - sftp.stat(remote_root) - except IOError: - sftp.mkdir(remote_root) - for name in files: - lp = os.path.join(root, name) - rp = remote_root.rstrip("/") + "/" + name - sftp.put(lp, rp) - finally: - try: - sftp.close() - except Exception: - pass - - -def main(): - default_env = os.path.join(os.path.dirname(__file__), "deployment.frontend.env") - cfg_path = os.getenv("DEPLOY_FRONTEND_CONFIG") or default_env - if not os.path.isfile(cfg_path): - log_step("fe_config_load", "fail", {"path": cfg_path}) - print(f"前端配置文件未找到:{cfg_path}", file=sys.stderr) - sys.exit(1) - - cfg = load_frontend_env(cfg_path) - required = ["server", "port", "user", "project_dir", "remote_dir"] - missing = [] - for k in required: - v = cfg.get(k) - if v is None or (isinstance(v, str) and v.strip() == ""): - missing.append(k) - if missing: - log_step("fe_config_validate", "fail", {"missing": missing, "path": cfg_path}) - print(f"前端配置错误:缺少必填项 {', '.join(missing)};配置文件:{cfg_path}", file=sys.stderr) - sys.exit(1) - - args = SimpleNamespace( - server=cfg["server"], - port=int(cfg["port"]), - user=cfg["user"], - password=cfg.get("password"), - keyfile=cfg.get("keyfile"), - fingerprint=cfg.get("fingerprint"), - env=cfg.get("env") or "prod", - project_dir=cfg["project_dir"], - build_cmd=cfg["build_cmd"], - dist_dir=cfg.get("dist_dir"), - remote_dir=cfg["remote_dir"], - transfer_timeout=int(cfg.get("transfer_timeout", 300)), - skip_build=bool(cfg.get("skip_build", False)), - dry_run=bool(cfg.get("dry_run", False)), - skip_clean_remote=bool(cfg.get("skip_clean_remote", False)), - ) - - if not args.dist_dir: - args.dist_dir = os.path.join(args.project_dir, "dist") - elif not os.path.isabs(args.dist_dir): - args.dist_dir = os.path.join(args.project_dir, args.dist_dir) - - env_info = { - "server": args.server, - "port": args.port, - "env": args.env, - "project_dir": args.project_dir, - "dist_dir": args.dist_dir, - "remote_dir": args.remote_dir, - } - - log_step("fe_start", "running", {"config_path": cfg_path}, env=env_info) - print_progress(0, "前端部署启动", "进行中", f"配置文件:{cfg_path}") - - try: - if not args.skip_build: - print_progress(10, "前端构建", "进行中", args.build_cmd) - rc, out, err = run_cmd(args.build_cmd, cwd=args.project_dir, capture_output=False) - log_step("fe_build", "ok" if rc == 0 else "fail", {}, env=env_info) - print_progress(20, "前端构建完成", "成功" if rc == 0 else "失败") - if rc != 0 and not args.dry_run: - sys.exit(1) - else: - print_progress(10, "跳过前端构建", "已跳过") - - if not os.path.isdir(args.dist_dir): - log_step("fe_dist_check", "fail", {"path": args.dist_dir}, env=env_info) - print_progress(25, "检查前端构建目录", "失败") - if not args.dry_run: - sys.exit(1) - else: - log_step("fe_dist_check", "ok", {"path": args.dist_dir}, env=env_info) - print_progress(25, "检查前端构建目录", "成功") - - if args.dry_run: - log_step("fe_connect", "skip", env=env_info) - log_step("fe_upload", "skip", env=env_info) - print_progress(100, "前端部署完成", "DRY-RUN") - log_step("fe_done", "ok", env=env_info) - return - - print_progress(40, "连接前端服务器", "进行中") - ssh = connect_ssh(args.server, args.port, args.user, args.password, args.keyfile) - print_progress(50, "连接前端服务器", "成功") - - ssh_exec(ssh, f'mkdir -p "{args.remote_dir}"') - - if not args.skip_clean_remote: - print_progress(60, "清理远端目录", "进行中") - ssh_exec(ssh, f'find "{args.remote_dir}" -mindepth 1 -maxdepth 1 -exec rm -rf {{}} + || true') - print_progress(65, "清理远端目录", "完成") - - print_progress(70, "上传前端静态资源", "进行中") - sftp_upload_dir(ssh, args.dist_dir, args.remote_dir) - log_step("fe_upload", "ok", {"dist": args.dist_dir}, env=env_info) - print_progress(90, "上传前端静态资源", "完成") - - log_step("fe_done", "ok", env=env_info) - print_progress(100, "前端部署完成", "成功") - ssh.close() - except Exception as e: - import traceback - - log_step("fe_exception", "fail", {"error": str(e), "trace": traceback.format_exc()}, env=env_info) - print_progress(100, "前端部署失败", "异常", str(e)) - print(traceback.format_exc(), file=sys.stderr) - sys.exit(1) - - -if __name__ == "__main__": - main() - diff --git a/src/main/resources/mapper/securityManagement/HotVehicleThreeInspectMapper.xml b/src/main/resources/mapper/securityManagement/HotVehicleThreeInspectMapper.xml index 7a0d46d..675d761 100644 --- a/src/main/resources/mapper/securityManagement/HotVehicleThreeInspectMapper.xml +++ b/src/main/resources/mapper/securityManagement/HotVehicleThreeInspectMapper.xml @@ -106,6 +106,96 @@ ORDER BY IFNULL(MAX(t.inspect_time), MAX(t.create_time)) DESC, MAX(t.create_time) DESC + + + +