142 lines
5.0 KiB
Bash
Executable File
142 lines
5.0 KiB
Bash
Executable File
#!/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
|