Files
leaudit-platform-backend/leaudit.sh
T
2026-05-18 14:35:25 +08:00

633 lines
19 KiB
Bash
Executable File

#!/bin/bash
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
FRONTEND_DIR="$PROJECT_DIR/legal-platform-frontend"
BACKEND_DIR="$PROJECT_DIR"
WORKER_SCRIPT="$PROJECT_DIR/scripts/start_worker.sh"
BEAT_SCRIPT="$PROJECT_DIR/scripts/start_beat.sh"
LOG_DIR="$PROJECT_DIR/.codex-run"
BACKEND_PID_FILE="$LOG_DIR/backend.pid"
FRONTEND_PID_FILE="$LOG_DIR/frontend.pid"
WORKER_PID_FILE="$LOG_DIR/worker.pid"
BEAT_PID_FILE="$LOG_DIR/beat.pid"
BACKEND_LOG="$LOG_DIR/backend.log"
FRONTEND_LOG="$LOG_DIR/frontend.log"
WORKER_LOG="$LOG_DIR/worker.log"
BEAT_LOG="$LOG_DIR/beat.log"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
mkdir -p "$LOG_DIR"
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
resolve_python() {
local -a candidates=(
"$PROJECT_DIR/.venv/bin/python"
"$PROJECT_DIR/.venv/bin/python3"
"$(command -v python3 2>/dev/null)"
"$(command -v python 2>/dev/null)"
)
local candidate=""
for candidate in "${candidates[@]}"; do
if [ -n "$candidate" ] && [ -x "$candidate" ]; then
echo "$candidate"
return 0
fi
done
return 1
}
read_env_value() {
local file=$1
local key=$2
if [ -f "$file" ]; then
awk -F= -v key="$key" '$1 == key {print substr($0, index($0, "=") + 1); exit}' "$file"
fi
}
FRONTEND_PUBLIC_PORT="$(read_env_value "$FRONTEND_DIR/.env" "PORT")"
FRONTEND_PUBLIC_PORT="${FRONTEND_PUBLIC_PORT:-5173}"
FRONTEND_DEV_PORT=5193
BACKEND_PYTHON="$(resolve_python || true)"
BACKEND_PORT="$("${BACKEND_PYTHON:-python3}" - <<'PY' 2>/dev/null || true
from fastapi_admin.config import APP_PORT
print(APP_PORT)
PY
)"
BACKEND_PORT="${BACKEND_PORT:-8000}"
pid_alive() {
local pid=$1
[ -n "$pid" ] && kill -0 "$pid" 2>/dev/null
}
service_pid() {
local pid_file=$1
if [ -f "$pid_file" ]; then
tr -d '[:space:]' < "$pid_file"
fi
}
cleanup_pid_file() {
local pid_file=$1
local pid
pid=$(service_pid "$pid_file")
if [ -n "$pid" ] && ! pid_alive "$pid"; then
rm -f "$pid_file"
fi
}
port_pid() {
local port=$1
local pid=""
pid=$(lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null | head -n 1)
if [ -n "$pid" ]; then
echo "$pid"
return 0
fi
pid=$(ss -ltnp 2>/dev/null | awk -v port=":$port" '
index($0, port) {
if (match($0, /pid=[0-9]+/)) {
value = substr($0, RSTART + 4, RLENGTH - 4)
print value
exit
}
}
')
[ -n "$pid" ] && echo "$pid"
}
pid_command() {
local pid=$1
ps -p "$pid" -o args= 2>/dev/null | xargs
}
kill_process_tree() {
local pid=$1
[ -n "$pid" ] || return 0
local children
children=$(pgrep -P "$pid" 2>/dev/null || true)
if [ -n "$children" ]; then
local child
for child in $children; do
kill_process_tree "$child"
done
fi
kill "$pid" 2>/dev/null || true
}
ensure_port_free() {
local name=$1
local port=$2
local expected_pid=${3:-}
local running_pid
running_pid=$(port_pid "$port")
if [ -n "$running_pid" ] && [ "$running_pid" != "$expected_pid" ]; then
local cmd
cmd="$(pid_command "$running_pid")"
log_error "$name 目标端口 $port 已被其他进程占用 (PID: $running_pid)"
[ -n "$cmd" ] && log_error "占用进程命令: $cmd"
return 1
fi
return 0
}
start_backend() {
cleanup_pid_file "$BACKEND_PID_FILE"
local pid
pid=$(service_pid "$BACKEND_PID_FILE")
if pid_alive "$pid"; then
log_warn "后端已在运行 (PID: $pid)"
return 0
fi
ensure_port_free "后端" "$BACKEND_PORT" || return 1
if [ -z "$BACKEND_PYTHON" ]; then
log_error "未找到可用 Python"
return 1
fi
log_info "启动后端服务 (端口: $BACKEND_PORT)..."
: > "$BACKEND_LOG"
nohup bash -lc "cd \"$BACKEND_DIR\" && exec \"$BACKEND_PYTHON\" run.py" \
>> "$BACKEND_LOG" 2>&1 < /dev/null &
pid=$!
sleep 2
local listening_pid
listening_pid=$(port_pid "$BACKEND_PORT")
if pid_alive "$pid" || [ -n "$listening_pid" ]; then
echo "${listening_pid:-$pid}" > "$BACKEND_PID_FILE"
log_success "后端启动成功 (PID: $pid, 端口: $BACKEND_PORT)"
return 0
fi
log_error "后端启动失败,查看日志: $BACKEND_LOG"
tail -20 "$BACKEND_LOG" 2>/dev/null || true
rm -f "$BACKEND_PID_FILE"
return 1
}
start_frontend() {
cleanup_pid_file "$FRONTEND_PID_FILE"
local pid
pid=$(service_pid "$FRONTEND_PID_FILE")
if pid_alive "$pid"; then
log_warn "前端已在运行 (PID: $pid)"
return 0
fi
ensure_port_free "前端开发服务" "$FRONTEND_DEV_PORT" || return 1
log_info "启动前端开发服务 (端口: $FRONTEND_DEV_PORT,代理入口: $FRONTEND_PUBLIC_PORT)..."
: > "$FRONTEND_LOG"
nohup bash -lc "cd \"$FRONTEND_DIR\" && exec npm run dev:dev" \
>> "$FRONTEND_LOG" 2>&1 < /dev/null &
pid=$!
sleep 4
local listening_pid
listening_pid=$(port_pid "$FRONTEND_DEV_PORT")
if pid_alive "$pid" || [ -n "$listening_pid" ]; then
echo "${listening_pid:-$pid}" > "$FRONTEND_PID_FILE"
log_success "前端启动成功 (PID: $pid, 开发端口: $FRONTEND_DEV_PORT, 访问端口: $FRONTEND_PUBLIC_PORT)"
return 0
fi
log_error "前端启动失败,查看日志: $FRONTEND_LOG"
tail -20 "$FRONTEND_LOG" 2>/dev/null || true
rm -f "$FRONTEND_PID_FILE"
return 1
}
start_worker() {
cleanup_pid_file "$WORKER_PID_FILE"
local pid
pid=$(service_pid "$WORKER_PID_FILE")
if pid_alive "$pid"; then
log_warn "Worker 已在运行 (PID: $pid)"
return 0
fi
if [ ! -x "$WORKER_SCRIPT" ]; then
log_error "Worker 启动脚本不存在或不可执行: $WORKER_SCRIPT"
return 1
fi
log_info "启动 Worker 服务..."
: > "$WORKER_LOG"
nohup bash -lc "cd \"$PROJECT_DIR\" && exec \"$WORKER_SCRIPT\"" \
>> "$WORKER_LOG" 2>&1 < /dev/null &
pid=$!
echo "$pid" > "$WORKER_PID_FILE"
sleep 2
if pid_alive "$pid"; then
log_success "Worker 启动成功 (PID: $pid)"
return 0
fi
log_error "Worker 启动失败,查看日志: $WORKER_LOG"
tail -20 "$WORKER_LOG" 2>/dev/null || true
rm -f "$WORKER_PID_FILE"
return 1
}
start_beat() {
cleanup_pid_file "$BEAT_PID_FILE"
local pid
pid=$(service_pid "$BEAT_PID_FILE")
if pid_alive "$pid"; then
log_warn "Beat 已在运行 (PID: $pid)"
return 0
fi
if [ ! -x "$BEAT_SCRIPT" ]; then
log_error "Beat 启动脚本不存在或不可执行: $BEAT_SCRIPT"
return 1
fi
log_info "启动 Beat 调度服务..."
: > "$BEAT_LOG"
nohup bash -lc "cd \"$PROJECT_DIR\" && exec \"$BEAT_SCRIPT\"" \
>> "$BEAT_LOG" 2>&1 < /dev/null &
pid=$!
echo "$pid" > "$BEAT_PID_FILE"
sleep 2
if pid_alive "$pid"; then
log_success "Beat 启动成功 (PID: $pid)"
return 0
fi
log_error "Beat 启动失败,查看日志: $BEAT_LOG"
tail -20 "$BEAT_LOG" 2>/dev/null || true
rm -f "$BEAT_PID_FILE"
return 1
}
stop_service() {
local name=$1
local pid_file=$2
local port=${3:-}
local pid
pid=$(service_pid "$pid_file")
if ! pid_alive "$pid"; then
if [ -n "$port" ]; then
local stray_pid
stray_pid=$(port_pid "$port")
if [ -n "$stray_pid" ]; then
log_warn "$name PID 文件已失效,正在清理端口 $port 上的残留进程 (PID: $stray_pid)..."
kill_process_tree "$stray_pid"
sleep 1
if pid_alive "$stray_pid"; then
kill -9 "$stray_pid" 2>/dev/null || true
fi
rm -f "$pid_file"
log_success "$name 残留进程已停止"
return 0
fi
fi
rm -f "$pid_file"
log_warn "$name 未运行"
return 0
fi
log_info "停止 $name (PID: $pid)..."
kill_process_tree "$pid"
for _ in $(seq 1 10); do
local current_port_pid=""
if [ -n "$port" ]; then
current_port_pid=$(port_pid "$port")
fi
if ! pid_alive "$pid" && [ -z "$current_port_pid" ]; then
break
fi
sleep 0.5
done
local stubborn_pid=""
if [ -n "$port" ]; then
stubborn_pid=$(port_pid "$port")
fi
if pid_alive "$pid" || [ -n "$stubborn_pid" ]; then
log_warn "$name 未响应,强制终止..."
kill -9 "$pid" 2>/dev/null || true
[ -n "$stubborn_pid" ] && kill -9 "$stubborn_pid" 2>/dev/null || true
fi
rm -f "$pid_file"
log_success "$name 已停止"
}
do_start() {
echo ""
echo -e "${CYAN}============================================${NC}"
echo -e "${CYAN} 启动 LeAudit 前后端${NC}"
echo -e "${CYAN}============================================${NC}"
echo ""
start_backend || return 1
start_frontend || return 1
start_worker || return 1
start_beat || return 1
echo ""
echo -e "${GREEN}============================================${NC}"
echo -e "${GREEN} 前端: http://localhost:$FRONTEND_PUBLIC_PORT (开发服务: $FRONTEND_DEV_PORT)${NC}"
echo -e "${GREEN} 后端: http://localhost:$BACKEND_PORT${NC}"
echo -e "${GREEN} Worker: $WORKER_SCRIPT${NC}"
echo -e "${GREEN} Beat: $BEAT_SCRIPT${NC}"
echo -e "${GREEN} 日志目录: $LOG_DIR${NC}"
echo -e "${GREEN}============================================${NC}"
echo ""
}
do_stop() {
echo ""
echo -e "${YELLOW}============================================${NC}"
echo -e "${YELLOW} 停止 LeAudit 前后端${NC}"
echo -e "${YELLOW}============================================${NC}"
echo ""
stop_service "Beat" "$BEAT_PID_FILE"
stop_service "Worker" "$WORKER_PID_FILE"
stop_service "前端" "$FRONTEND_PID_FILE" "$FRONTEND_DEV_PORT"
stop_service "后端" "$BACKEND_PID_FILE" "$BACKEND_PORT"
echo ""
}
do_restart() {
do_stop
sleep 1
do_start
}
do_status() {
cleanup_pid_file "$BACKEND_PID_FILE"
cleanup_pid_file "$FRONTEND_PID_FILE"
cleanup_pid_file "$WORKER_PID_FILE"
cleanup_pid_file "$BEAT_PID_FILE"
echo ""
echo -e "${CYAN}============================================${NC}"
echo -e "${CYAN} 服务状态${NC}"
echo -e "${CYAN}============================================${NC}"
echo ""
local pid
pid=$(service_pid "$BACKEND_PID_FILE")
if pid_alive "$pid"; then
echo -e " 后端: ${GREEN}● 运行中${NC} PID=$pid 端口=$BACKEND_PORT"
else
echo -e " 后端: ${RED}○ 已停止${NC}"
fi
pid=$(service_pid "$FRONTEND_PID_FILE")
if pid_alive "$pid"; then
echo -e " 前端: ${GREEN}● 运行中${NC} PID=$pid 开发端口=$FRONTEND_DEV_PORT 访问端口=$FRONTEND_PUBLIC_PORT"
else
echo -e " 前端: ${RED}○ 已停止${NC}"
fi
pid=$(service_pid "$WORKER_PID_FILE")
if pid_alive "$pid"; then
echo -e " Worker: ${GREEN}● 运行中${NC} PID=$pid"
else
echo -e " Worker: ${RED}○ 已停止${NC}"
fi
pid=$(service_pid "$BEAT_PID_FILE")
if pid_alive "$pid"; then
echo -e " Beat: ${GREEN}● 运行中${NC} PID=$pid"
else
echo -e " Beat: ${RED}○ 已停止${NC}"
fi
echo ""
echo " 后端日志: $BACKEND_LOG"
echo " 前端日志: $FRONTEND_LOG"
echo " Worker日志: $WORKER_LOG"
echo " Beat日志: $BEAT_LOG"
echo ""
}
do_logs() {
local target=${2:-all}
local lines=${3:-100}
case "$target" in
backend)
[ -f "$BACKEND_LOG" ] || touch "$BACKEND_LOG"
echo -e "${CYAN}--- 后端日志 (最近 $lines 行) ---${NC}"
tail -n "$lines" "$BACKEND_LOG"
echo -e "${CYAN}--- Ctrl+C 退出 ---${NC}"
tail -f "$BACKEND_LOG"
;;
frontend)
[ -f "$FRONTEND_LOG" ] || touch "$FRONTEND_LOG"
echo -e "${CYAN}--- 前端日志 (最近 $lines 行) ---${NC}"
tail -n "$lines" "$FRONTEND_LOG"
echo -e "${CYAN}--- Ctrl+C 退出 ---${NC}"
tail -f "$FRONTEND_LOG"
;;
worker)
[ -f "$WORKER_LOG" ] || touch "$WORKER_LOG"
echo -e "${CYAN}--- Worker 日志 (最近 $lines 行) ---${NC}"
tail -n "$lines" "$WORKER_LOG"
echo -e "${CYAN}--- Ctrl+C 退出 ---${NC}"
tail -f "$WORKER_LOG"
;;
beat)
[ -f "$BEAT_LOG" ] || touch "$BEAT_LOG"
echo -e "${CYAN}--- Beat 日志 (最近 $lines 行) ---${NC}"
tail -n "$lines" "$BEAT_LOG"
echo -e "${CYAN}--- Ctrl+C 退出 ---${NC}"
tail -f "$BEAT_LOG"
;;
all)
[ -f "$BACKEND_LOG" ] || touch "$BACKEND_LOG"
[ -f "$FRONTEND_LOG" ] || touch "$FRONTEND_LOG"
[ -f "$WORKER_LOG" ] || touch "$WORKER_LOG"
[ -f "$BEAT_LOG" ] || touch "$BEAT_LOG"
echo -e "${CYAN}--- 后端日志 (最近 $lines 行) ---${NC}"
tail -n "$lines" "$BACKEND_LOG"
echo ""
echo -e "${CYAN}--- 前端日志 (最近 $lines 行) ---${NC}"
tail -n "$lines" "$FRONTEND_LOG"
echo ""
echo -e "${CYAN}--- Worker 日志 (最近 $lines 行) ---${NC}"
tail -n "$lines" "$WORKER_LOG"
echo ""
echo -e "${CYAN}--- Beat 日志 (最近 $lines 行) ---${NC}"
tail -n "$lines" "$BEAT_LOG"
echo ""
echo -e "${CYAN}--- Ctrl+C 退出 ---${NC}"
tail -n 0 -f "$BACKEND_LOG" "$FRONTEND_LOG" "$WORKER_LOG" "$BEAT_LOG" 2>/dev/null | awk '
/^==> .*backend\.log <==$/ { current="[backend]"; next }
/^==> .*frontend\.log <==$/ { current="[frontend]"; next }
/^==> .*worker\.log <==$/ { current="[worker]"; next }
/^==> .*beat\.log <==$/ { current="[beat]"; next }
{ print current " " $0; fflush() }
'
;;
*)
echo "用法: ./leaudit.sh logs [backend|frontend|worker|beat|all] [行数]"
exit 1
;;
esac
}
do_doctor() {
echo ""
echo -e "${CYAN}============================================${NC}"
echo -e "${CYAN} 运行环境检查${NC}"
echo -e "${CYAN}============================================${NC}"
echo ""
local pid cmd
pid=$(port_pid "$FRONTEND_PUBLIC_PORT")
if [ -n "$pid" ]; then
cmd="$(pid_command "$pid")"
echo -e " 代理端口 5173: ${GREEN}● 已监听${NC} PID=$pid"
[ -n "$cmd" ] && echo " 命令: $cmd"
else
echo -e " 代理端口 5173: ${RED}○ 未监听${NC}"
fi
pid=$(port_pid "$FRONTEND_DEV_PORT")
if [ -n "$pid" ]; then
cmd="$(pid_command "$pid")"
echo -e " 前端开发 5193: ${GREEN}● 已监听${NC} PID=$pid"
[ -n "$cmd" ] && echo " 命令: $cmd"
else
echo -e " 前端开发 5193: ${RED}○ 未监听${NC}"
fi
pid=$(port_pid "$BACKEND_PORT")
if [ -n "$pid" ]; then
cmd="$(pid_command "$pid")"
echo -e " 后端服务 $BACKEND_PORT: ${GREEN}● 已监听${NC} PID=$pid"
[ -n "$cmd" ] && echo " 命令: $cmd"
else
echo -e " 后端服务 $BACKEND_PORT: ${RED}○ 未监听${NC}"
fi
local worker_pid
worker_pid=$(service_pid "$WORKER_PID_FILE")
if pid_alive "$worker_pid"; then
cmd="$(pid_command "$worker_pid")"
echo -e " Worker 进程: ${GREEN}● 运行中${NC} PID=$worker_pid"
[ -n "$cmd" ] && echo " 命令: $cmd"
else
echo -e " Worker 进程: ${RED}○ 未运行${NC}"
fi
local beat_pid
beat_pid=$(service_pid "$BEAT_PID_FILE")
if pid_alive "$beat_pid"; then
cmd="$(pid_command "$beat_pid")"
echo -e " Beat 进程: ${GREEN}● 运行中${NC} PID=$beat_pid"
[ -n "$cmd" ] && echo " 命令: $cmd"
else
echo -e " Beat 进程: ${RED}○ 未运行${NC}"
fi
echo ""
}
do_open() {
echo ""
echo -e "${CYAN}============================================${NC}"
echo -e "${CYAN} 访问地址与日志摘要${NC}"
echo -e "${CYAN}============================================${NC}"
echo ""
echo " 前端访问: http://localhost:$FRONTEND_PUBLIC_PORT"
echo " 前端开发: http://127.0.0.1:$FRONTEND_DEV_PORT"
echo " 后端访问: http://localhost:$BACKEND_PORT"
echo " Worker脚本: $WORKER_SCRIPT"
echo " Beat脚本: $BEAT_SCRIPT"
echo ""
echo -e "${CYAN}--- 后端最近 5 行 ---${NC}"
tail -n 5 "$BACKEND_LOG" 2>/dev/null || echo "(无后端日志)"
echo ""
echo -e "${CYAN}--- 前端最近 5 行 ---${NC}"
tail -n 5 "$FRONTEND_LOG" 2>/dev/null || echo "(无前端日志)"
echo ""
echo -e "${CYAN}--- Worker最近 5 行 ---${NC}"
tail -n 5 "$WORKER_LOG" 2>/dev/null || echo "(无Worker日志)"
echo ""
echo -e "${CYAN}--- Beat最近 5 行 ---${NC}"
tail -n 5 "$BEAT_LOG" 2>/dev/null || echo "(无Beat日志)"
echo ""
}
case "${1:-help}" in
start)
do_start
;;
stop)
do_stop
;;
restart)
do_restart
;;
status)
do_status
;;
logs)
do_logs "$@"
;;
doctor)
do_doctor
;;
open)
do_open
;;
help|--help|-h)
echo "用法: ./leaudit.sh <命令>"
echo ""
echo "命令说明:"
echo " start 启动前后端、Worker、Beat"
echo " stop 停止前后端、Worker、Beat"
echo " restart 重启前后端、Worker、Beat"
echo " status 查看前后端、Worker、Beat 运行状态"
echo " logs 查看前后端、Worker、Beat 日志并持续跟踪"
echo " logs backend 只看后端日志"
echo " logs frontend 50 看前端最近 50 行日志并持续跟踪"
echo " logs worker 50 看 Worker 最近 50 行日志并持续跟踪"
echo " logs beat 50 看 Beat 最近 50 行日志并持续跟踪"
echo " doctor 检查 5173 / 5193 / 8096 端口占用情况"
echo " open 打印访问地址和最近日志摘要"
echo ""
echo "示例:"
echo " ./leaudit.sh start"
echo " ./leaudit.sh status"
echo " ./leaudit.sh logs"
echo " ./leaudit.sh doctor"
echo " ./leaudit.sh open"
;;
*)
log_error "未知命令: $1"
echo "运行 ./leaudit.sh help 查看帮助"
exit 1
;;
esac