diff --git a/.DS_Store b/.DS_Store index 04c8fe28..0adf18c3 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/assets/员工账户.xlsx b/assets/员工账户.xlsx new file mode 100644 index 00000000..0e6e9201 Binary files /dev/null and b/assets/员工账户.xlsx differ diff --git a/bin/restart_java_backend.sh b/bin/restart_java_backend.sh index db1629a9..eebe6aeb 100755 --- a/bin/restart_java_backend.sh +++ b/bin/restart_java_backend.sh @@ -11,8 +11,8 @@ TARGET_DIR="$ROOT_DIR/ruoyi-admin/target" JAR_NAME="ruoyi-admin.jar" SERVER_PORT=62318 STOP_WAIT_SECONDS=30 -APP_KEYWORD="$JAR_NAME" -JAVA_OPTS="-Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" +APP_MARKER="-Dccdi.backend.root=$ROOT_DIR" +JAVA_OPTS="$APP_MARKER -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" timestamp() { date "+%Y-%m-%d %H:%M:%S" @@ -42,24 +42,54 @@ ensure_command() { fi } +is_managed_backend_pid() { + pid="$1" + if [ -z "${pid:-}" ] || ! kill -0 "$pid" 2>/dev/null; then + return 1 + fi + + args=$(ps -o args= -p "$pid" 2>/dev/null || true) + if [ -z "${args:-}" ]; then + return 1 + fi + + case "$args" in + *"$APP_MARKER"*"$JAR_NAME"*|*"$JAR_NAME"*"$APP_MARKER"*) + return 0 + ;; + esac + + if [ -f "$PID_FILE" ]; then + file_pid=$(cat "$PID_FILE" 2>/dev/null || true) + if [ "${file_pid:-}" = "$pid" ]; then + case "$args" in + *"java"*"-jar"*"$JAR_NAME"*) + return 0 + ;; + esac + fi + fi + + return 1 +} + collect_pids() { all_pids="" if [ -f "$PID_FILE" ]; then file_pid=$(cat "$PID_FILE" 2>/dev/null || true) - if [ -n "${file_pid:-}" ] && kill -0 "$file_pid" 2>/dev/null; then + if [ -n "${file_pid:-}" ] && is_managed_backend_pid "$file_pid"; then all_pids="$all_pids $file_pid" fi fi - port_pids=$(lsof -tiTCP:"$SERVER_PORT" -sTCP:LISTEN 2>/dev/null || true) - if [ -n "${port_pids:-}" ]; then - all_pids="$all_pids $port_pids" - fi - - app_pids=$(pgrep -f "$APP_KEYWORD" 2>/dev/null || true) - if [ -n "${app_pids:-}" ]; then - all_pids="$all_pids $app_pids" + marker_pids=$(pgrep -f "$APP_MARKER" 2>/dev/null || true) + if [ -n "${marker_pids:-}" ]; then + for pid in $marker_pids; do + if is_managed_backend_pid "$pid"; then + all_pids="$all_pids $pid" + fi + done fi unique_pids="" @@ -155,6 +185,12 @@ status_backend() { pids=$(collect_pids) if [ -n "${pids:-}" ]; then log_info "后端正在运行,进程: $pids" + return 0 + fi + + port_pids=$(lsof -tiTCP:"$SERVER_PORT" -sTCP:LISTEN 2>/dev/null || true) + if [ -n "${port_pids:-}" ]; then + log_info "未发现脚本托管的后端进程,但端口 $SERVER_PORT 被其他进程占用: $port_pids" else log_info "后端未运行" fi @@ -190,6 +226,7 @@ main() { ensure_command mvn ensure_command lsof ensure_command pgrep + ensure_command ps ensure_command tail action="${1:-restart}" diff --git a/docs/reports/implementation/2026-03-30-restart-java-backend-stop-scope-fix.md b/docs/reports/implementation/2026-03-30-restart-java-backend-stop-scope-fix.md new file mode 100644 index 00000000..1d845393 --- /dev/null +++ b/docs/reports/implementation/2026-03-30-restart-java-backend-stop-scope-fix.md @@ -0,0 +1,48 @@ +# 后端启停脚本停机范围收敛实施记录 + +## 修改目标 + +- 修复 `bin/restart_java_backend.sh` 在执行 `stop` / `restart` 时可能误杀非脚本托管 Java 进程的问题 +- 保持现有 `java -jar ruoyi-admin.jar` 启动方式不变,仅收敛停机识别范围 + +## 根因分析 + +- 原脚本在 `stop_backend()` 中通过 `collect_pids()` 同时合并以下来源后统一执行 `kill`: + - `logs/backend-java.pid` 中记录的 PID + - 监听 `62318` 端口的进程 + - 命令行中包含 `ruoyi-admin.jar` 关键字的进程 +- 该逻辑会把“不是由本脚本拉起、但恰好占用端口或命中关键字”的外部进程也纳入停机列表,导致关闭后端时误杀其他进程。 + +## 修改内容 + +- 调整 [`bin/restart_java_backend.sh`](/Users/wkc/Desktop/ccdi/ccdi/bin/restart_java_backend.sh) + - 新增 `APP_MARKER="-Dccdi.backend.root=$ROOT_DIR"`,启动时把仓库根路径标记写入 Java 启动参数 + - 新增 `is_managed_backend_pid()`,只把带有脚本标记的 Java 进程视为脚本托管进程 + - `collect_pids()` 改为仅收集: + - `PID_FILE` 中仍存活且校验通过的进程 + - 命令行里带脚本标记的进程 + - 不再把“端口占用者”或“仅命中 jar 名的进程”直接纳入停机目标 + - `status` 增加提示:若未发现脚本托管进程,但 `62318` 被其他进程占用,会明确输出占用 PID,避免误判为脚本自身后端 +- 调整 [`docs/tests/scripts/test-restart-java-backend.sh`](/Users/wkc/Desktop/ccdi/ccdi/docs/tests/scripts/test-restart-java-backend.sh) + - 增加对 `APP_MARKER` 的静态校验,防止后续移除托管标记 +- 新增 [`docs/tests/scripts/test-restart-java-backend-stop-scope.sh`](/Users/wkc/Desktop/ccdi/ccdi/docs/tests/scripts/test-restart-java-backend-stop-scope.sh) + - 在临时目录复制启停脚本 + - 启动一个未托管的外部占口进程 + - 验证执行 `stop` 后该外部进程仍然存活,防止回归到误杀外部进程的行为 + +## 验证记录 + +- 执行 `sh docs/tests/scripts/test-restart-java-backend-stop-scope.sh` + - 结果:通过 + - 说明:已验证 `stop` 不会误杀未由脚本托管的占口进程 +- 执行 `sh docs/tests/scripts/test-restart-java-backend.sh` + - 结果:通过 + - 说明:已验证脚本仍使用 `java -jar ruoyi-admin.jar` 启动,且保留托管进程标记 +- 执行 `sh -n bin/restart_java_backend.sh` + - 结果:通过 + - 说明:脚本语法正确 + +## 影响范围 + +- 仅影响本地后端启停辅助脚本与对应测试脚本 +- 不涉及 Java 业务代码、数据库、前端页面及 Mock 服务逻辑 diff --git a/docs/tests/scripts/test-restart-java-backend-stop-scope.sh b/docs/tests/scripts/test-restart-java-backend-stop-scope.sh new file mode 100644 index 00000000..73a78f2a --- /dev/null +++ b/docs/tests/scripts/test-restart-java-backend-stop-scope.sh @@ -0,0 +1,57 @@ +#!/bin/sh + +set -eu + +ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/../../.." && pwd) +SOURCE_SCRIPT="$ROOT_DIR/bin/restart_java_backend.sh" +TMP_DIR=$(mktemp -d) +TEST_ROOT="$TMP_DIR/app" +TEST_BIN_DIR="$TEST_ROOT/bin" +TEST_LOG_DIR="$TEST_ROOT/logs" +TEST_PORT=62481 +EXTERNAL_PID="" + +cleanup() { + if [ -n "${EXTERNAL_PID:-}" ] && kill -0 "$EXTERNAL_PID" 2>/dev/null; then + kill "$EXTERNAL_PID" 2>/dev/null || true + wait "$EXTERNAL_PID" 2>/dev/null || true + fi + rm -rf "$TMP_DIR" +} + +trap cleanup EXIT INT TERM + +mkdir -p "$TEST_BIN_DIR" "$TEST_LOG_DIR" +cp "$SOURCE_SCRIPT" "$TEST_BIN_DIR/restart_java_backend.sh" +chmod +x "$TEST_BIN_DIR/restart_java_backend.sh" + +python3 -m http.server "$TEST_PORT" --bind 127.0.0.1 > "$TMP_DIR/external.log" 2>&1 & +EXTERNAL_PID=$! + +sleep 1 + +if ! kill -0 "$EXTERNAL_PID" 2>/dev/null; then + echo "失败: 未能启动外部占口进程" + exit 1 +fi + +python3 - "$TEST_BIN_DIR/restart_java_backend.sh" "$TEST_PORT" <<'PY' +from pathlib import Path +import sys + +script_path = Path(sys.argv[1]) +port = sys.argv[2] +content = script_path.read_text(encoding="utf-8") +content = content.replace("SERVER_PORT=62318", f"SERVER_PORT={port}") +script_path.write_text(content, encoding="utf-8") +PY + +echo "[检查] stop 不能误杀未由脚本托管的占口进程" +sh "$TEST_BIN_DIR/restart_java_backend.sh" stop >/dev/null 2>&1 || true + +if ! kill -0 "$EXTERNAL_PID" 2>/dev/null; then + echo "失败: stop 误杀了未托管的外部进程" + exit 1 +fi + +echo "通过" diff --git a/docs/tests/scripts/test-restart-java-backend.sh b/docs/tests/scripts/test-restart-java-backend.sh index c57c4f95..53f0fc09 100644 --- a/docs/tests/scripts/test-restart-java-backend.sh +++ b/docs/tests/scripts/test-restart-java-backend.sh @@ -27,8 +27,8 @@ if grep -Fq 'spring-boot:run' "$SCRIPT_FILE"; then exit 1 fi -if ! grep -Fq 'APP_KEYWORD="$JAR_NAME"' "$SCRIPT_FILE"; then - echo "失败: 进程识别未切换到 jar 关键字" +if ! grep -Fq 'APP_MARKER="-Dccdi.backend.root=$ROOT_DIR"' "$SCRIPT_FILE"; then + echo "失败: 未为脚本托管的后端进程写入唯一标记" exit 1 fi