#!/usr/bin/env bash # 一键:构建 V2 → sync-jars → 后台启动 V1/V2(默认同 deploy 配置、异端口)→ 健康等待 → 跑完整 API 套件。 # # 【能力边界|审阅说明】 # - 会做:按 api_catalog.json 对 V1/V2 **顺序**发 HTTP(冒烟 + 双端对拍),比对返回(compare_mode:code_only / deep / status_only), # 产出 report/smoke-*.md、parity-*.md、SUITE-*.md;strict 模式下不一致则 pytest 失败、脚本非 0。 # - 不会做:**并发压测**(无固定 RPS、并发连接数、持续时间、p95 延迟统计);若需压测请另用 hey/wrk/k6 等指向同一 ELEVATOR_BASE_OLD/NEW。 # # 用法(在 maven-cw-elevator-application 目录): # ./scripts/run_v1v2_parity_automated.sh # # 环境变量(节选): # ELEVATOR_SKIP_BUILD=1 跳过 mvn package(已有 target JAR) # ELEVATOR_SKIP_SYNC=1 跳过 deploy/sync-jars.sh(deploy 下已有 JAR) # ELEVATOR_SKIP_START=1 不启动进程,仅跑套件(需已监听 ELEVATOR_BASE_OLD/NEW) # ELEVATOR_SUITE_STRICT=1 默认值;smoke/parity 失败则脚本非 0(传给 run_full_elevator_api_suite.sh) # ELEVATOR_SUITE_STRICT=0 smoke/parity 与旧版一致用 || true,便于只看报告 # ELEVATOR_PARITY_BOUNDARY=0 对拍仅基线体,不合并 boundary_patches(默认本脚本为 1) # ELEVATOR_PORT_V1=18080 ELEVATOR_PORT_V2=18081 # ELEVATOR_STARTUP_TIMEOUT=240 单端就绪最长等待秒数 # ELEVATOR_AUTO_KILL=1 默认退出时结束本脚本启动的 java 子进程 # ELEVATOR_JAVA_OPTS 传给两边的 JVM(如 -Xmx512m) # ELEVATOR_USE_ENV_JAVA=1 使用当前 JAVA_HOME(见 deploy/common-java.sh) # ELEVATOR_PARITY_OFFLINE=1 默认:禁用 Consul(笔记本无 8500 / 避免误连 JAR 内嵌机房地址)。 # ELEVATOR_PARITY_OFFLINE=0 使用 deploy/*/bootstrap.properties 中的 Consul(默认 host 192.168.3.12:8500,与该机 Docker consul 一致)。 # set -euo pipefail REPO="$(cd "$(dirname "$0")/.." && pwd)" DEPLOY="${REPO}/deploy" PARITY_OFFLINE_PROPS="${DEPLOY}/parity-offline-overrides.properties" TOOL="${REPO}/tools/elevator_api_parity" SCRIPTS="${REPO}/scripts" # shellcheck source=../deploy/common-java.sh source "${DEPLOY}/common-java.sh" MERGE="${DEPLOY}/merge-redis-json.sh" PORT_V1="${ELEVATOR_PORT_V1:-18080}" PORT_V2="${ELEVATOR_PORT_V2:-18081}" BASE_V1="${ELEVATOR_BASE_OLD:-http://127.0.0.1:${PORT_V1}}" BASE_V2="${ELEVATOR_BASE_NEW:-http://127.0.0.1:${PORT_V2}}" START_TIMEOUT="${ELEVATOR_STARTUP_TIMEOUT:-240}" AUTO_KILL="${ELEVATOR_AUTO_KILL:-1}" export ELEVATOR_BASE_OLD="$BASE_V1" export ELEVATOR_BASE_NEW="$BASE_V2" export ELEVATOR_SUITE_STRICT="${ELEVATOR_SUITE_STRICT:-1}" # 对拍时展开 api_catalog 中 boundary_patches(入参边界);设为 0 仅跑基线 default 体以缩短耗时 export ELEVATOR_PARITY_BOUNDARY="${ELEVATOR_PARITY_BOUNDARY:-1}" PID_DIR="" STARTED_V1="" STARTED_V2="" cleanup() { if [[ "${AUTO_KILL}" != "1" ]] || [[ -z "${PID_DIR}" ]] || [[ ! -d "${PID_DIR}" ]]; then return 0 fi for key in v1 v2; do f="${PID_DIR}/${key}.pid" if [[ -f "$f" ]]; then pid="$(cat "$f" 2>/dev/null || true)" if [[ -n "${pid}" ]] && kill -0 "$pid" 2>/dev/null; then echo "==> 停止 ${key} (pid ${pid})" kill "$pid" 2>/dev/null || true wait "$pid" 2>/dev/null || true fi fi done rm -rf "${PID_DIR}" } trap cleanup EXIT INT TERM _port_in_use() { local port="$1" if command -v nc >/dev/null 2>&1; then nc -z 127.0.0.1 "$port" 2>/dev/null return $? fi timeout 0.3 bash -c "echo >/dev/tcp/127.0.0.1/${port}" 2>/dev/null } _wait_healthy() { local base="$1" local label="$2" local pid_file="$3" local log_file="$4" local end=$(( $(date +%s) + START_TIMEOUT )) echo "==> 等待就绪 ${label} (${base}),最长 ${START_TIMEOUT}s" while (( $(date +%s) < end )); do if [[ -f "${pid_file}" ]]; then local pid="" pid="$(cat "${pid_file}" 2>/dev/null || true)" if [[ -n "${pid}" ]] && ! kill -0 "${pid}" 2>/dev/null; then echo "ERROR: ${label} 进程已退出(pid=${pid}),无需继续等待健康检查。" >&2 echo " 请检查日志: ${log_file}" >&2 return 1 fi fi for path in /actuator/health /health; do code="$(curl -s -o /tmp/elev_h$$ -w '%{http_code}' "${base}${path}" 2>/dev/null || echo 0)" if [[ "${code}" == "200" ]] && grep -qiE 'up|"status"[[:space:]]*:[[:space:]]*"OK"' /tmp/elev_h$$ 2>/dev/null; then rm -f /tmp/elev_h$$ echo " OK ${label} ${path}" return 0 fi done sleep 2 done rm -f /tmp/elev_h$$ echo "ERROR: ${label} 在 ${START_TIMEOUT}s 内未通过健康检查: ${base}" >&2 return 1 } _pick_java_home if [[ ! -x "${JAVA_HOME}/bin/java" ]]; then echo "ERROR: 未找到 JDK。请安装 openjdk-8-jdk 或设置 JAVA_HOME / ELEVATOR_USE_ENV_JAVA=1" >&2 exit 1 fi JAVA="${JAVA_HOME}/bin/java" OPEN_FLAGS=() while IFS= read -r line; do [[ -n "${line}" ]] && OPEN_FLAGS+=("${line}") done < <(_jdk8_open_flags "$JAVA") if [[ ! -x "${MERGE}" ]]; then chmod +x "${MERGE}" 2>/dev/null || true fi if [[ "${ELEVATOR_SKIP_BUILD:-0}" != "1" ]]; then OLD_TARGET_JAR="${REPO}/cw-elevator-application-starter/target/cw-elevator-application-2.0.0.jar" OLD_DEPLOY_JAR="${DEPLOY}/v2-maven/cw-elevator-application-2.0.0.jar" echo "==> 删除旧 V2 JAR(确保全量重编译产物)" rm -f "${OLD_TARGET_JAR}" "${OLD_DEPLOY_JAR}" echo "==> mvn package (skip tests)" (cd "${REPO}" && mvn -q -DskipTests clean package) else echo "==> 跳过构建 (ELEVATOR_SKIP_BUILD=1)" fi if [[ "${ELEVATOR_SKIP_SYNC:-0}" != "1" ]]; then echo "==> sync-jars(V1 来自仓库根 cw-elevator-application-V1.0.0.20211103/)" # 默认用当前 Maven data 模块覆盖 V1 fat-jar 内嵌 data JAR,避免历史包与源码分片实现分叉导致 record_page 等对拍假差异;纯正历史 V1 请 ELEVATOR_SKIP_SYNC=1 或 PARITY_PATCH_V1_DATA=0 export PARITY_PATCH_V1_DATA="${PARITY_PATCH_V1_DATA:-1}" bash "${DEPLOY}/sync-jars.sh" else echo "==> 跳过 sync-jars (ELEVATOR_SKIP_SYNC=1)" fi V1_DIR="${DEPLOY}/v1-legacy" V2_DIR="${DEPLOY}/v2-maven" JAR_V1="${V1_DIR}/cw-elevator-application-V1.0.0.20211103.jar" JAR_V2="${V2_DIR}/cw-elevator-application-2.0.0.jar" if [[ ! -f "${JAR_V1}" ]] || [[ ! -f "${JAR_V2}" ]]; then echo "ERROR: 缺少 JAR。请执行 ${DEPLOY}/sync-jars.sh 或先 mvn package。" >&2 echo " 期望: ${JAR_V1}" >&2 echo " ${JAR_V2}" >&2 exit 1 fi LOG_DIR="${REPO}/.elevator-parity-logs" mkdir -p "${LOG_DIR}" if [[ "${ELEVATOR_SKIP_START:-0}" != "1" ]]; then if _port_in_use "${PORT_V1}" && [[ "${ELEVATOR_ALLOW_BUSY_PORTS:-0}" != "1" ]]; then echo "ERROR: 端口 ${PORT_V1} 已被占用。可先停占用进程,或设 ELEVATOR_SKIP_START=1 使用已启动实例。" >&2 exit 1 fi if _port_in_use "${PORT_V2}" && [[ "${ELEVATOR_ALLOW_BUSY_PORTS:-0}" != "1" ]]; then echo "ERROR: 端口 ${PORT_V2} 已被占用。" >&2 exit 1 fi PID_DIR="$(mktemp -d "${REPO}/.elevator-parity-pids.XXXXXX")" # Consul:若脚本未加载 bootstrap、且无本地 Consul,JAR 内嵌地址会导致 agentServiceRegister 失败→整进程退出(日志见 TransportException 8500)。 OFF_JVM=() LOC_CHAIN_V1="file:${V1_DIR}/bootstrap.properties,file:${V1_DIR}/application.properties,file:${V1_DIR}/redis-override.properties" LOC_CHAIN_V2="file:${V2_DIR}/bootstrap.properties,file:${V2_DIR}/application.properties,file:${V2_DIR}/redis-override.properties" if [[ "${ELEVATOR_PARITY_OFFLINE:-1}" == "1" ]] && [[ -f "${PARITY_OFFLINE_PROPS}" ]]; then LOC_CHAIN_V1+=",file:${PARITY_OFFLINE_PROPS}" LOC_CHAIN_V2+=",file:${PARITY_OFFLINE_PROPS}" # 覆盖 Spring Cloud 先于 spring.config 解析的 classpath bootstrap,避免仍指向机房 Consul OFF_JVM=("-Dspring.cloud.consul.enabled=false") echo "==> Consul: 离线模式(ELEVATOR_PARITY_OFFLINE=1,禁用注册)。若在本机 192.168.3.12 已有 Docker Consul,可改用 ELEVATOR_PARITY_OFFLINE=0" else echo "==> Consul: 联机模式(ELEVATOR_PARITY_OFFLINE=0,按 bootstrap 连接 Consul,deploy 默认 192.168.3.12:8500)" fi echo "==> 启动 V1 :${PORT_V1}" export SPRING_APPLICATION_JSON="$("${MERGE}" "${V1_DIR}/redis-override.properties")" # shellcheck disable=SC2086 nohup "${JAVA}" "${OPEN_FLAGS[@]}" "${OFF_JVM[@]}" ${ELEVATOR_JAVA_OPTS:-} -jar "${JAR_V1}" \ --spring.config.location="${LOC_CHAIN_V1}" \ --server.port="${PORT_V1}" >>"${LOG_DIR}/v1-legacy.log" 2>&1 & echo $! > "${PID_DIR}/v1.pid" STARTED_V1=1 echo "==> 启动 V2 :${PORT_V2}" export SPRING_APPLICATION_JSON="$("${MERGE}" "${V2_DIR}/redis-override.properties")" # shellcheck disable=SC2086 nohup "${JAVA}" "${OPEN_FLAGS[@]}" "${OFF_JVM[@]}" ${ELEVATOR_JAVA_OPTS:-} -jar "${JAR_V2}" \ --spring.config.location="${LOC_CHAIN_V2}" \ --server.port="${PORT_V2}" >>"${LOG_DIR}/v2-maven.log" 2>&1 & echo $! > "${PID_DIR}/v2.pid" STARTED_V2=1 unset SPRING_APPLICATION_JSON echo " V1 pid=$(cat "${PID_DIR}/v1.pid") log=${LOG_DIR}/v1-legacy.log" echo " V2 pid=$(cat "${PID_DIR}/v2.pid") log=${LOG_DIR}/v2-maven.log" _wait_healthy "${BASE_V1}" "V1" "${PID_DIR}/v1.pid" "${LOG_DIR}/v1-legacy.log" || exit 1 _wait_healthy "${BASE_V2}" "V2" "${PID_DIR}/v2.pid" "${LOG_DIR}/v2-maven.log" || exit 1 else echo "==> 跳过启动 (ELEVATOR_SKIP_START=1),假定 ${BASE_V1} / ${BASE_V2} 已可用" fi echo "==> 完整 API 套件(ELEVATOR_SUITE_STRICT=${ELEVATOR_SUITE_STRICT})" bash "${SCRIPTS}/run_full_elevator_api_suite.sh" SUITE_EXIT=$? exit "${SUITE_EXIT}"