Files
zhaopin/scripts/stop-tasks.sh
T

142 lines
5.0 KiB
Bash
Raw Normal View History

#!/bin/bash
# stop-tasks.sh — 随时叫停/重启统一入口 v1.0(2026-08-26)
#
# 用法:
# bash stop-tasks.sh stop # 停止全部(hr1+hr2)
# bash stop-tasks.sh stop hr1 # 只停 hr1
# bash stop-tasks.sh restart hr2 # 重启 hr2(先干净停,再启动)
# bash stop-tasks.sh status # 查询状态(含卡死检测)
#
# 三层停止机制:
# 1. 优雅:创建 STOP.flag 旗标 → runner/scrape 每轮检查自行退出(最多等45秒)
# 2. 强杀:SIGTERM → 3秒 → SIGKILL,父runner和子scrape都杀
# 3. 验证:杀完复查 ps,任何残留即报错;同时清理旗标防止影响下次启动
set -u
STATE_DIR=/opt/data/logs/recruitment/state
LOG_DIR=/opt/data/logs/recruitment
SCRIPTS=/opt/data/skills/recruitment-resume-screener/scripts
STOP_FLAG=$STATE_DIR/STOP.flag
mkdir -p "$STATE_DIR"
# 进程发现:宽匹配(nohup/环境变量/直接参数三种启动方式都能覆盖)
find_pids() { # $1=acct 或空=全部
local pat="daily-runner|zhaopin-scrape"
[ -n "${1:-}" ] && pat="daily-runner.*$1|ACCOUNT=$1"
ps aux | grep -E "$pat" | grep -v -E "grep|stop-tasks" | awk '{print $2}'
}
kill_wait() { # $@ = pids
[ $# -eq 0 ] && return 0
kill "$@" 2>/dev/null || true
for i in 1 2 3; do sleep 1; ps -p "$@" >/dev/null 2>&1 || return 0; done
kill -9 "$@" 2>/dev/null || true
sleep 1
}
verify_none() { # $1=acct 或空;返回0=干净
local left
left=$(find_pids "${1:-}")
if [ -n "$left" ]; then
echo "❌ 仍有残留进程: $(echo $left | tr '\n' ' ')"
echo "$left" | while read -r p; do
ps -p "$p" -o pid,stat,etime,cputime,cmd --no-headers 2>/dev/null
done
return 1
fi
return 0
}
do_stop() {
local target="${1:-}"
echo "🛑 停止筛选任务${target:+($target)}..."
touch "$STOP_FLAG" # 第1层:优雅退出信号
local pids; pids=$(find_pids "$target")
if [ -n "$pids" ]; then
echo " 等待进程优雅退出(最多45秒): $(echo $pids | tr '\n' ' ')"
for i in $(seq 1 45); do
sleep 1
pids=$(find_pids "$target")
[ -z "$pids" ] && break
done
fi
pids=$(find_pids "$target")
if [ -n "$pids" ]; then
echo " 仍有进程,强制终止: $(echo $pids | tr '\n' ' ')"
kill_wait $pids # 第2层:TERM→KILL
fi
rm -f "$STOP_FLAG" # 清旗标,不影响下次启动
if verify_none "$target"; then # 第3层:验证
echo "✅ 已全部停止,验证无残留"
return 0
else
echo "⚠️ 建议稍后重跑 status 确认;若仍残留,联系管理员检查"
return 1
fi
}
do_start() { # $1=acct
local acct="$1" port label
[ "$acct" = "hr1" ] && { port=9223; label=Chrome; } || { port=9225; label=Edge; }
# 启动前防重
local existing; existing=$(find_pids "$acct")
if [ -n "$existing" ]; then
echo "⚠️ $acct 已在运行(PID $(echo $existing | tr '\n' ' ')),先停止再重启"
do_stop "$acct"
fi
# 清掉旧状态,标记新运行
rm -f "$STATE_DIR/state-$acct.json"
cd "$SCRIPTS"
ACCOUNT=$acct CDP_PORT=$port nohup node daily-runner.js "$acct" "$port" \
> "$LOG_DIR/runner-$acct-$(date +%F-%H%M%S).log" 2>&1 &
local pid=$!
sleep 3
if ps -p "$pid" >/dev/null 2>&1; then
echo "✅ $acct($label) 已启动 PID=$pid 日志=$(ls -t $LOG_DIR/runner-$acct-*.log | head -1)"
return 0
else
echo "❌ $acct 启动失败,查看日志: $(ls -t $LOG_DIR/runner-$acct-*.log | head -1)"
tail -5 "$(ls -t $LOG_DIR/runner-$acct-*.log | head -1)"
return 1
fi
}
do_restart() {
do_stop "$1" && do_start "$1"
}
do_status() {
echo "📋 筛选任务状态($(TZ=Asia/Shanghai date '+%F %T'))"
local found=0
for acct in hr1 hr2; do
local pids; pids=$(find_pids "$acct")
if [ -n "$pids" ]; then
found=1
echo " $acct: ▶️ 运行中"
echo "$pids" | while read -r p; do
local info
info=$(ps -p "$p" -o pid,stat,etime,cputime,cmd --no-headers 2>/dev/null | cut -c1-120)
# 卡死检测:cputime 为 00:00 且 etime>10min
local et cpu
et=$(echo "$info" | awk '{print $3}'); cpu=$(echo "$info" | awk '{print $4}')
local mark=""
[ "$cpu" = "00:00:00" ] && case "$et" in *:*:*) mark=" ⚠️疑似卡死(cputime=0)";; esac
echo " $info$mark"
done
local st; st=$(cat "$STATE_DIR/state-$acct.json" 2>/dev/null || echo "无状态文件")
echo " 状态: $(echo "$st" | head -c 200)"
else
echo " $acct: ⏹️ 未运行(状态: $(cat "$STATE_DIR/state-$acct.json" 2>/dev/null | head -c 120 || echo '无'))"
fi
done
[ "$found" = "0" ] && echo " (全部停止)"
[ -f "$STOP_FLAG" ] && echo " ⚠️ 存在停止旗标 STOP.flag(新批次将被阻止启动)"
}
case "${1:-status}" in
stop) do_stop "${2:-}" ;;
start) do_start "${2:?用法: start hr1|hr2}" ;;
restart) do_restart "${2:?用法: restart hr1|hr2}" ;;
status) do_status ;;
*) echo "用法: $0 stop [hr1|hr2] | start hr1|hr2 | restart hr1|hr2 | status"; exit 1 ;;
esac