diff --git a/bin/prod/deploy_from_package.sh b/bin/prod/deploy_from_package.sh index bc855c7..811c5fe 100755 --- a/bin/prod/deploy_from_package.sh +++ b/bin/prod/deploy_from_package.sh @@ -56,23 +56,6 @@ require_command() { fi } -require_port_command() { - if command -v ss >/dev/null 2>&1; then - return 0 - fi - - if command -v lsof >/dev/null 2>&1; then - return 0 - fi - - if command -v netstat >/dev/null 2>&1; then - return 0 - fi - - log_error "缺少端口检测命令: ss、lsof 或 netstat" - exit 1 -} - find_release_archive() { archives=$(find "$SCRIPT_DIR" -maxdepth 1 -type f -name '*.zip' ! -name 'dist.zip') count=$(printf '%s\n' "$archives" | sed '/^$/d' | wc -l | tr -d ' ') @@ -134,106 +117,23 @@ deploy_backend_jar() { mv "$source_jar" "$BACKEND_JAR_TARGET" } -deploy_frontend_archive() { +deploy_frontend_dist() { source_dist_zip="$1" rm -f "$FRONTEND_DIST_ARCHIVE" + rm -rf "$FRONTEND_DIST_DIR" mv "$source_dist_zip" "$FRONTEND_DIST_ARCHIVE" -} + unzip -oq "$FRONTEND_DIST_ARCHIVE" -d "$FRONTEND_DIR" -resolve_frontend_source_dir() { - unzip_dir="$1" - - if [ -f "$unzip_dir/index.html" ]; then - printf '%s\n' "$unzip_dir" - return 0 - fi - - if [ -f "$unzip_dir/dist/index.html" ]; then - printf '%s\n' "$unzip_dir/dist" - return 0 - fi - - candidate=$(find "$unzip_dir" -type f -name 'index.html' | head -n 1) - if [ -z "${candidate:-}" ]; then - log_error "dist.zip 解压后未找到 index.html" + if [ ! -d "$FRONTEND_DIST_DIR" ]; then + log_error "dist.zip 解压后未找到 $FRONTEND_DIST_DIR" exit 1 fi - - dirname "$candidate" -} - -deploy_frontend_dist() { - dist_unpack_dir="$WORK_DIR/frontend" - - mkdir -p "$dist_unpack_dir" - unzip -oq "$FRONTEND_DIST_ARCHIVE" -d "$dist_unpack_dir" - - rm -rf "$FRONTEND_DIST_DIR" - mkdir -p "$FRONTEND_DIST_DIR" - cp -a "$(resolve_frontend_source_dir "$dist_unpack_dir")"/. "$FRONTEND_DIST_DIR"/ -} - -is_port_listening() { - port="$1" - - if command -v ss >/dev/null 2>&1; then - ss -lnt 2>/dev/null | grep -q ":$port " - return $? - fi - - if command -v lsof >/dev/null 2>&1; then - lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1 - return $? - fi - - netstat -an 2>/dev/null | grep -E "[\\.:]$port[[:space:]].*LISTEN" >/dev/null 2>&1 -} - -is_managed_backend_pid() { - pid="$1" - - if [ -z "${pid:-}" ] || ! kill -0 "$pid" 2>/dev/null; then - return 1 - fi - - process_line=$(ps -ef | awk -v target_pid="$pid" '$2 == target_pid {print $0}') - if [ -z "${process_line:-}" ]; then - return 1 - fi - - case "$process_line" in - *"$BACKEND_MARKER"*"$BACKEND_JAR_TARGET"*|*"$BACKEND_JAR_TARGET"*"$BACKEND_MARKER"*) - return 0 - ;; - esac - - return 1 } collect_backend_pids() { - pids="" - - if [ -f "$BACKEND_PID_FILE" ]; then - file_pid=$(cat "$BACKEND_PID_FILE" 2>/dev/null || true) - if [ -n "${file_pid:-}" ] && is_managed_backend_pid "$file_pid"; then - pids="$pids $file_pid" - fi - fi - - marker_pids=$( - ps -ef | awk -v marker="$BACKEND_MARKER" -v jar="$BACKEND_JAR_TARGET" ' - index($0, marker) > 0 && index($0, jar) > 0 {print $2} - ' - ) - if [ -n "${marker_pids:-}" ]; then - for pid in $marker_pids; do - if is_managed_backend_pid "$pid"; then - pids="$pids $pid" - fi - done - fi - - printf '%s\n' "$(echo "$pids" | xargs 2>/dev/null || true)" + ps -ef | awk -v marker="$BACKEND_MARKER" -v jar="$BACKEND_JAR_TARGET" ' + index($0, marker) > 0 && index($0, jar) > 0 {print $2} + ' | xargs 2>/dev/null || true } stop_backend() { @@ -310,19 +210,7 @@ start_backend() { exit 1 fi - wait_seconds=0 - while [ "$wait_seconds" -lt 30 ]; do - if is_port_listening "$BACKEND_PORT"; then - log_info "后端已监听端口: $BACKEND_PORT" - return 0 - fi - - sleep 1 - wait_seconds=$((wait_seconds + 1)) - done - - log_error "后端未在预期时间内监听端口 $BACKEND_PORT" - exit 1 + log_info "后端已启动,PID: $backend_pid" } main() { @@ -337,7 +225,6 @@ main() { require_command find require_command ps require_command nohup - require_port_command release_archive=$(find_release_archive) WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/deploy_from_package.XXXXXX") @@ -352,8 +239,7 @@ main() { backup_frontend_dist stop_backend deploy_backend_jar "$backend_jar_source" - deploy_frontend_archive "$frontend_dist_source" - deploy_frontend_dist + deploy_frontend_dist "$frontend_dist_source" start_backend log_info "部署完成" diff --git a/bin/prod/deploy_from_package_test.sh b/bin/prod/deploy_from_package_test.sh index 63a1371..fc6454f 100755 --- a/bin/prod/deploy_from_package_test.sh +++ b/bin/prod/deploy_from_package_test.sh @@ -44,17 +44,7 @@ if [ -z "$port" ]; then exit 1 fi -python3 -m http.server "$port" >/dev/null 2>&1 & -server_pid=$! - -cleanup() { - kill "$server_pid" 2>/dev/null || true - wait "$server_pid" 2>/dev/null || true -} - -trap cleanup EXIT INT TERM - -while kill -0 "$server_pid" 2>/dev/null; do +while :; do sleep 1 done EOF @@ -151,10 +141,6 @@ test_deploy_success() { backend_pid=$(cat "$release_dir/backend/backend.pid") kill -0 "$backend_pid" 2>/dev/null || fail "expected backend pid to be running" - if ! lsof -nP -iTCP:"$backend_port" -sTCP:LISTEN >/dev/null 2>&1; then - fail "expected fake backend to listen on $backend_port" - fi - trap - EXIT INT TERM cleanup_release_dir "$release_dir" } @@ -182,28 +168,6 @@ test_multiple_release_zip_should_fail() { cleanup_release_dir "$release_dir" } -test_netstat_fallback_should_work() { - release_dir=$(mktemp -d) - backend_port=$(find_free_port) - trap 'cleanup_release_dir "$release_dir"' EXIT INT TERM - - prepare_release_dir "$release_dir" "$backend_port" - mkdir -p "$release_dir/fake-port-bin" - ln -sf /usr/sbin/netstat "$release_dir/fake-port-bin/netstat" - - ( - cd "$release_dir" - PATH="$release_dir/fake-port-bin:/usr/bin:/bin" ./deploy_from_package.sh - ) - - assert_file_exists "$release_dir/frontend/dist/index.html" - backend_pid=$(cat "$release_dir/backend/backend.pid") - kill -0 "$backend_pid" 2>/dev/null || fail "expected backend pid to be running with netstat fallback" - - trap - EXIT INT TERM - cleanup_release_dir "$release_dir" -} - test_should_use_ps_ef_for_process_detection() { if rg -n 'pgrep' "$SCRIPT_UNDER_TEST" >/dev/null 2>&1; then fail "expected deploy_from_package.sh not to depend on pgrep" @@ -212,6 +176,10 @@ test_should_use_ps_ef_for_process_detection() { if ! rg -n 'ps -ef' "$SCRIPT_UNDER_TEST" >/dev/null 2>&1; then fail "expected deploy_from_package.sh to use ps -ef for process detection" fi + + if rg -n '\b(ss|lsof|netstat|resolve_frontend_source_dir|is_port_listening)\b' "$SCRIPT_UNDER_TEST" >/dev/null 2>&1; then + fail "expected deploy_from_package.sh to remove port detection and unzip compatibility helpers" + fi } main() { @@ -219,7 +187,6 @@ main() { test_should_use_ps_ef_for_process_detection test_deploy_success test_multiple_release_zip_should_fail - test_netstat_fallback_should_work printf 'PASS: deploy_from_package tests\n' } diff --git a/doc/implementation-report-2026-04-01-production-one-click-deploy-simplify.md b/doc/implementation-report-2026-04-01-production-one-click-deploy-simplify.md new file mode 100644 index 0000000..d3f586e --- /dev/null +++ b/doc/implementation-report-2026-04-01-production-one-click-deploy-simplify.md @@ -0,0 +1,38 @@ +# 生产一键部署脚本简化实施记录 + +## 修改内容 +- 简化 `bin/prod/deploy_from_package.sh` +- 删除端口检测逻辑,不再依赖 `ss`、`lsof` 或 `netstat` +- 删除前端解压兼容逻辑,不再探测多种 `dist.zip` 目录结构 +- 保留并简化进程识别逻辑,直接使用 `ps -ef` +- 简化 `bin/prod/deploy_from_package_test.sh` +- 删除端口监听断言和端口检测回退场景 +- 新增脚本文本断言,确认已移除端口检测和解压兼容 helper + +## 当前脚本边界 +- 仍然保留以下核心能力: + - 脚本同目录唯一发布 zip 校验 + - 发布包内唯一 `jar` 和唯一 `dist.zip` 校验 + - 旧版后端 `jar` 时间戳备份 + - 旧版 `frontend/dist` 时间戳备份 + - 使用 `ps -ef` 停止旧后端进程 + - 替换新 `jar` + - 将 `dist.zip` 直接解压到 `frontend/` + - 启动新的 Java 进程并写入 PID 文件 + +## 删除的复杂逻辑 +- 不再等待端口监听成功 +- 不再兼容 `ss`、`lsof`、`netstat` 三种端口检测方式 +- 不再兼容 `dist.zip` 根目录、`dist/index.html` 和自动 `find index.html` 多种结构 +- 当前前端解压只接受一种约定: + - `dist.zip` 解压到 `frontend/` 后必须得到 `frontend/dist/` + +## 验证结果 +- 已执行 `sh -n bin/prod/deploy_from_package.sh` +- 已执行 `sh bin/prod/deploy_from_package_test.sh` +- 自测确认: + - 脚本使用 `ps -ef` + - 脚本中已移除端口检测 helper + - 脚本中已移除前端解压兼容 helper + - 正常部署链路仍然通过 + - 多个发布 zip 失败场景仍然通过