From f753b8c6c3fbb917bc96b76824b588c207d20bab Mon Sep 17 00:00:00 2001 From: wren <“porlong@qq.com”> Date: Mon, 11 May 2026 02:06:23 +0800 Subject: [PATCH] chore: add local dev helper scripts --- .gitignore | 1 + leaudit.sh | 441 +++++++++++++++++++++++++++++++++++++++++++++++++ scripts/dev.sh | 5 + 3 files changed, 447 insertions(+) create mode 100755 leaudit.sh create mode 100755 scripts/dev.sh diff --git a/.gitignore b/.gitignore index 1d7d5a2..2eeb553 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,7 @@ coverage.xml # Playwright MCP cache .playwright-mcp/ +.codex-run/ # Rules cache rules/**/__pycache__/ diff --git a/leaudit.sh b/leaudit.sh new file mode 100755 index 0000000..9660f55 --- /dev/null +++ b/leaudit.sh @@ -0,0 +1,441 @@ +#!/bin/bash + +PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" +FRONTEND_DIR="$PROJECT_DIR/legal-platform-frontend" +BACKEND_DIR="$PROJECT_DIR" +LOG_DIR="$PROJECT_DIR/.codex-run" +BACKEND_PID_FILE="$LOG_DIR/backend.pid" +FRONTEND_PID_FILE="$LOG_DIR/frontend.pid" +BACKEND_LOG="$LOG_DIR/backend.log" +FRONTEND_LOG="$LOG_DIR/frontend.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 +} + +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" + ( + cd "$BACKEND_DIR" + exec "$BACKEND_PYTHON" run.py + ) >> "$BACKEND_LOG" 2>&1 & + pid=$! + echo "$pid" > "$BACKEND_PID_FILE" + sleep 2 + + if pid_alive "$pid"; then + 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" + ( + cd "$FRONTEND_DIR" + exec npm run dev:dev + ) >> "$FRONTEND_LOG" 2>&1 & + pid=$! + echo "$pid" > "$FRONTEND_PID_FILE" + sleep 4 + + if pid_alive "$pid"; then + 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 +} + +stop_service() { + local name=$1 + local pid_file=$2 + local pid + pid=$(service_pid "$pid_file") + + if ! pid_alive "$pid"; then + rm -f "$pid_file" + log_warn "$name 未运行" + return 0 + fi + + log_info "停止 $name (PID: $pid)..." + kill "$pid" 2>/dev/null || true + for _ in $(seq 1 10); do + if ! pid_alive "$pid"; then + break + fi + sleep 0.5 + done + if pid_alive "$pid"; then + log_warn "$name 未响应,强制终止..." + kill -9 "$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 + + 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} 日志目录: $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 "前端" "$FRONTEND_PID_FILE" + stop_service "后端" "$BACKEND_PID_FILE" + echo "" +} + +do_restart() { + do_stop + sleep 1 + do_start +} + +do_status() { + cleanup_pid_file "$BACKEND_PID_FILE" + cleanup_pid_file "$FRONTEND_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 + + echo "" + echo " 后端日志: $BACKEND_LOG" + echo " 前端日志: $FRONTEND_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" + ;; + all) + [ -f "$BACKEND_LOG" ] || touch "$BACKEND_LOG" + [ -f "$FRONTEND_LOG" ] || touch "$FRONTEND_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}--- Ctrl+C 退出 ---${NC}" + tail -n 0 -f "$BACKEND_LOG" "$FRONTEND_LOG" 2>/dev/null | awk ' + /^==> .*backend\.log <==$/ { current="[backend]"; next } + /^==> .*frontend\.log <==$/ { current="[frontend]"; next } + { print current " " $0; fflush() } + ' + ;; + *) + echo "用法: ./leaudit.sh logs [backend|frontend|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 + + 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 "" + 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 "" +} + +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 启动前后端" + echo " stop 停止前后端" + echo " restart 重启前后端" + echo " status 查看前后端运行状态" + echo " logs 查看前后端日志并持续跟踪" + echo " logs backend 只看后端日志" + echo " logs frontend 50 看前端最近 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 diff --git a/scripts/dev.sh b/scripts/dev.sh new file mode 100755 index 0000000..f2de248 --- /dev/null +++ b/scripts/dev.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +exec "$ROOT_DIR/leaudit.sh" "${1:-start}" "${2:-}" "${3:-}"