2026-08-26 22:55:08 +08:00
|
|
|
|
#!/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; }
|
2026-08-27 03:44:46 +00:00
|
|
|
|
# v10.6 当日平台阻碍保护:已 platform_blocked 的账号当日拒绝启动(限额等不会当日恢复)
|
|
|
|
|
|
local st
|
|
|
|
|
|
st=$(python3 -c "
|
|
|
|
|
|
import json,datetime
|
|
|
|
|
|
try:
|
|
|
|
|
|
s=json.load(open('$STATE_DIR/state-$acct.json'))
|
|
|
|
|
|
bj=datetime.datetime.utcnow()+datetime.timedelta(hours=8)
|
|
|
|
|
|
print(s.get('status','') if s.get('date')==bj.strftime('%Y-%m-%d') else '')
|
|
|
|
|
|
except Exception: print('')" 2>/dev/null)
|
|
|
|
|
|
if [ "$st" = "platform_blocked" ]; then
|
|
|
|
|
|
echo "⛔ $acct 今日已碰到平台阻碍(限额类),按规则当日不再启动。如确认误判,删除 $STATE_DIR/state-$acct.json 后重试"
|
|
|
|
|
|
return 1
|
|
|
|
|
|
fi
|
2026-08-26 22:55:08 +08:00
|
|
|
|
# 启动前防重
|
|
|
|
|
|
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
|