2026-05-14 18:09:15 +08:00
|
|
|
|
#!/bin/bash
|
|
|
|
|
|
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
|
|
cd "$ROOT_DIR"
|
|
|
|
|
|
|
|
|
|
|
|
VENV_DIR=".venv"
|
|
|
|
|
|
VENV_PYTHON="$VENV_DIR/bin/python"
|
|
|
|
|
|
LOG_DIR="logs"
|
|
|
|
|
|
API_PID_FILE="$LOG_DIR/api.pid"
|
|
|
|
|
|
FRONTEND_PID_FILE="$LOG_DIR/frontend.pid"
|
|
|
|
|
|
API_LOG_FILE="$LOG_DIR/api.log"
|
|
|
|
|
|
FRONTEND_LOG_FILE="$LOG_DIR/frontend.log"
|
2026-05-20 23:34:08 +08:00
|
|
|
|
DISPLAY_HOST="localhost"
|
|
|
|
|
|
SERVICE_HOST="6.86.80.8"
|
2026-05-14 18:09:15 +08:00
|
|
|
|
|
|
|
|
|
|
RED='\033[0;31m'
|
|
|
|
|
|
GREEN='\033[0;32m'
|
|
|
|
|
|
YELLOW='\033[1;33m'
|
|
|
|
|
|
CYAN='\033[0;36m'
|
|
|
|
|
|
NC='\033[0m'
|
|
|
|
|
|
|
|
|
|
|
|
load_env() {
|
2026-05-18 16:32:42 +08:00
|
|
|
|
load_env_file ".env"
|
|
|
|
|
|
load_env_file ".env.development"
|
|
|
|
|
|
|
|
|
|
|
|
API_HOST="${API_HOST:-0.0.0.0}"
|
|
|
|
|
|
API_PORT="${API_PORT:-8000}"
|
|
|
|
|
|
FRONTEND_PORT="${FRONTEND_PORT:-5173}"
|
|
|
|
|
|
FRONTEND_MODE="${FRONTEND_MODE:-dev}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
load_env_file() {
|
|
|
|
|
|
local env_file="$1"
|
|
|
|
|
|
|
|
|
|
|
|
if [ -f "$env_file" ]; then
|
2026-05-14 18:09:15 +08:00
|
|
|
|
while IFS='=' read -r key value; do
|
|
|
|
|
|
key="${key%$'\r'}"
|
|
|
|
|
|
value="${value%$'\r'}"
|
|
|
|
|
|
|
|
|
|
|
|
case "$key" in
|
|
|
|
|
|
""|\#*)
|
|
|
|
|
|
continue
|
|
|
|
|
|
;;
|
|
|
|
|
|
API_HOST|API_PORT|FRONTEND_PORT|FRONTEND_MODE)
|
|
|
|
|
|
export "$key=$value"
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
2026-05-18 16:32:42 +08:00
|
|
|
|
done < "$env_file"
|
2026-05-14 18:09:15 +08:00
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ensure_log_dir() {
|
|
|
|
|
|
mkdir -p "$LOG_DIR"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-20 23:34:08 +08:00
|
|
|
|
check_tcp_connectivity() {
|
|
|
|
|
|
local host="$1"
|
|
|
|
|
|
local port="$2"
|
|
|
|
|
|
|
|
|
|
|
|
if command -v nc > /dev/null 2>&1; then
|
|
|
|
|
|
nc -z -w 3 "$host" "$port" > /dev/null 2>&1
|
|
|
|
|
|
return
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
require_python_bootstrap
|
|
|
|
|
|
"$PYTHON_BOOTSTRAP" - <<PY > /dev/null 2>&1
|
|
|
|
|
|
import socket
|
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
with socket.create_connection(("${host}", ${port}), timeout=3):
|
|
|
|
|
|
pass
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
PY
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
check_foundation_services() {
|
|
|
|
|
|
local name
|
|
|
|
|
|
local port
|
|
|
|
|
|
|
|
|
|
|
|
for service in \
|
|
|
|
|
|
"Milvus:19530" \
|
|
|
|
|
|
"MinIO API:9000" \
|
|
|
|
|
|
"MinIO Console:9001" \
|
|
|
|
|
|
"Redis:6379" \
|
|
|
|
|
|
"PostgreSQL:5432"
|
|
|
|
|
|
do
|
|
|
|
|
|
name="${service%%:*}"
|
|
|
|
|
|
port="${service##*:}"
|
|
|
|
|
|
if check_tcp_connectivity "$SERVICE_HOST" "$port"; then
|
|
|
|
|
|
success "${name}: ${SERVICE_HOST}:${port} 可连通"
|
|
|
|
|
|
else
|
|
|
|
|
|
warn "${name}: ${SERVICE_HOST}:${port} 不可连通"
|
|
|
|
|
|
fi
|
|
|
|
|
|
done
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-14 18:09:15 +08:00
|
|
|
|
print_header() {
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e "${CYAN}========================================${NC}"
|
|
|
|
|
|
echo -e "${CYAN} $1${NC}"
|
|
|
|
|
|
echo -e "${CYAN}========================================${NC}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
info() {
|
|
|
|
|
|
echo -e "${CYAN}$1${NC}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
success() {
|
|
|
|
|
|
echo -e "${GREEN}$1${NC}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
warn() {
|
|
|
|
|
|
echo -e "${YELLOW}$1${NC}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
error() {
|
|
|
|
|
|
echo -e "${RED}$1${NC}" >&2
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
die() {
|
|
|
|
|
|
error "$1"
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
is_pid_running() {
|
|
|
|
|
|
local pid="$1"
|
|
|
|
|
|
[ -n "$pid" ] && ps -p "$pid" > /dev/null 2>&1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
read_pid() {
|
|
|
|
|
|
local pid_file="$1"
|
|
|
|
|
|
if [ -f "$pid_file" ]; then
|
|
|
|
|
|
cat "$pid_file"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cleanup_stale_pid() {
|
|
|
|
|
|
local pid_file="$1"
|
|
|
|
|
|
local pid
|
|
|
|
|
|
pid="$(read_pid "$pid_file")"
|
|
|
|
|
|
if [ -n "$pid" ] && ! is_pid_running "$pid"; then
|
|
|
|
|
|
rm -f "$pid_file"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
port_pid() {
|
|
|
|
|
|
local port="$1"
|
|
|
|
|
|
|
|
|
|
|
|
if command -v lsof > /dev/null 2>&1; then
|
|
|
|
|
|
lsof -ti tcp:"$port" 2>/dev/null | head -n 1 || true
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if command -v ss > /dev/null 2>&1; then
|
|
|
|
|
|
ss -lptn "sport = :$port" 2>/dev/null | awk -F 'pid=' 'NR>1 && NF>1 {split($2,a,/,/); print a[1]; exit}' || true
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if command -v netstat > /dev/null 2>&1; then
|
|
|
|
|
|
netstat -lntp 2>/dev/null | awk -v port=":$port" '$4 ~ port {split($7,a,"/"); if (a[1] != "-") {print a[1]; exit}}' || true
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
require_python_bootstrap() {
|
|
|
|
|
|
if command -v python3 > /dev/null 2>&1; then
|
|
|
|
|
|
PYTHON_BOOTSTRAP="python3"
|
|
|
|
|
|
elif command -v python > /dev/null 2>&1; then
|
|
|
|
|
|
PYTHON_BOOTSTRAP="python"
|
|
|
|
|
|
else
|
|
|
|
|
|
die "未找到 Python,请先安装 Python 3.10+。"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
require_venv() {
|
|
|
|
|
|
[ -x "$VENV_PYTHON" ] || die "虚拟环境不存在,请先运行 ./dev.sh setup"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ensure_frontend_deps() {
|
|
|
|
|
|
if [ ! -d "frontend" ]; then
|
|
|
|
|
|
die "前端目录不存在: frontend"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [ ! -d "frontend/node_modules" ] || [ ! -d "frontend/node_modules/vite" ]; then
|
|
|
|
|
|
warn "前端依赖不存在或不完整,正在执行 npm install..."
|
|
|
|
|
|
npm --prefix frontend install
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
validate_frontend_mode() {
|
|
|
|
|
|
case "$1" in
|
|
|
|
|
|
dev|static)
|
|
|
|
|
|
;;
|
|
|
|
|
|
*)
|
|
|
|
|
|
die "前端模式仅支持 dev 或 static,当前值: $1"
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
check_python_version() {
|
|
|
|
|
|
local python_cmd="$1"
|
|
|
|
|
|
"$python_cmd" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
run_setup() {
|
|
|
|
|
|
load_env
|
|
|
|
|
|
ensure_log_dir
|
|
|
|
|
|
print_header "AI+合规智能中枢 - 环境初始化"
|
|
|
|
|
|
|
|
|
|
|
|
require_python_bootstrap
|
|
|
|
|
|
|
|
|
|
|
|
info "[1/4] 检查 Python 版本"
|
|
|
|
|
|
check_python_version "$PYTHON_BOOTSTRAP" || die "需要 Python 3.10+"
|
|
|
|
|
|
success "Python 版本检查通过"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
|
|
info "[2/4] 准备虚拟环境"
|
|
|
|
|
|
if [ ! -d "$VENV_DIR" ]; then
|
|
|
|
|
|
"$PYTHON_BOOTSTRAP" -m venv "$VENV_DIR"
|
|
|
|
|
|
success "已创建虚拟环境: $VENV_DIR"
|
|
|
|
|
|
else
|
|
|
|
|
|
success "虚拟环境已存在: $VENV_DIR"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
"$VENV_PYTHON" -m pip install --upgrade pip
|
|
|
|
|
|
"$VENV_PYTHON" -m pip install -r backend/requirements.txt
|
|
|
|
|
|
success "后端依赖安装完成"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
|
|
info "[3/4] 准备前端依赖"
|
|
|
|
|
|
if ! command -v npm > /dev/null 2>&1; then
|
|
|
|
|
|
die "未找到 npm,请先安装 Node.js 20+。"
|
|
|
|
|
|
fi
|
|
|
|
|
|
npm --prefix frontend install
|
|
|
|
|
|
success "前端依赖安装完成"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
2026-05-20 23:34:08 +08:00
|
|
|
|
info "[4/4] 检查 6.86.80.8 基础服务连通性"
|
|
|
|
|
|
check_foundation_services
|
2026-05-14 18:09:15 +08:00
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
success "环境初始化完成"
|
|
|
|
|
|
echo "后续常用命令:"
|
|
|
|
|
|
echo " ./dev.sh start"
|
|
|
|
|
|
echo " ./dev.sh status"
|
|
|
|
|
|
echo " ./dev.sh logs api --follow"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
api_health_ok() {
|
|
|
|
|
|
if command -v curl > /dev/null 2>&1; then
|
2026-05-20 23:34:08 +08:00
|
|
|
|
curl -fsS "http://${DISPLAY_HOST}:$API_PORT/health" > /dev/null 2>&1
|
2026-05-14 18:09:15 +08:00
|
|
|
|
return
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
require_venv
|
|
|
|
|
|
"$VENV_PYTHON" - <<PY > /dev/null 2>&1
|
|
|
|
|
|
import sys
|
|
|
|
|
|
from urllib.request import urlopen
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
2026-05-20 23:34:08 +08:00
|
|
|
|
with urlopen("http://${DISPLAY_HOST}:${API_PORT}/health", timeout=3) as response:
|
2026-05-14 18:09:15 +08:00
|
|
|
|
body = response.read().decode("utf-8", errors="ignore")
|
|
|
|
|
|
sys.exit(0 if "healthy" in body.lower() else 1)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
PY
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
start_api() {
|
|
|
|
|
|
local mode="${1:-background}"
|
|
|
|
|
|
load_env
|
|
|
|
|
|
ensure_log_dir
|
|
|
|
|
|
require_venv
|
|
|
|
|
|
cleanup_stale_pid "$API_PID_FILE"
|
|
|
|
|
|
|
|
|
|
|
|
local pid
|
|
|
|
|
|
pid="$(read_pid "$API_PID_FILE")"
|
|
|
|
|
|
if is_pid_running "$pid"; then
|
|
|
|
|
|
warn "API 已在运行 (PID: $pid)"
|
|
|
|
|
|
return
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
export PYTHONPATH="backend${PYTHONPATH:+:$PYTHONPATH}"
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$mode" = "foreground" ]; then
|
|
|
|
|
|
print_header "AI+合规智能中枢 - 启动 API"
|
|
|
|
|
|
echo "运行模式: 前台调试(带 --reload)"
|
2026-05-20 23:34:08 +08:00
|
|
|
|
echo "服务地址: http://${DISPLAY_HOST}:$API_PORT"
|
|
|
|
|
|
echo "文档地址: http://${DISPLAY_HOST}:$API_PORT/docs"
|
|
|
|
|
|
echo "健康检查: http://${DISPLAY_HOST}:$API_PORT/health"
|
2026-05-14 18:09:15 +08:00
|
|
|
|
echo ""
|
|
|
|
|
|
exec "$VENV_PYTHON" -m uvicorn app.main:app --host "$API_HOST" --port "$API_PORT" --reload
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
nohup "$VENV_PYTHON" -m uvicorn app.main:app --host "$API_HOST" --port "$API_PORT" > "$API_LOG_FILE" 2>&1 &
|
|
|
|
|
|
pid=$!
|
|
|
|
|
|
echo "$pid" > "$API_PID_FILE"
|
|
|
|
|
|
sleep 3
|
|
|
|
|
|
|
|
|
|
|
|
if is_pid_running "$pid"; then
|
|
|
|
|
|
success "API 启动成功 (PID: $pid)"
|
2026-05-20 23:34:08 +08:00
|
|
|
|
echo " 地址: http://${DISPLAY_HOST}:$API_PORT"
|
|
|
|
|
|
echo " 文档: http://${DISPLAY_HOST}:$API_PORT/docs"
|
2026-05-14 18:09:15 +08:00
|
|
|
|
echo " 日志: $API_LOG_FILE"
|
|
|
|
|
|
else
|
|
|
|
|
|
rm -f "$API_PID_FILE"
|
|
|
|
|
|
die "API 启动失败,请查看日志: $API_LOG_FILE"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
start_frontend() {
|
|
|
|
|
|
local mode="${1:-$FRONTEND_MODE}"
|
|
|
|
|
|
load_env
|
|
|
|
|
|
ensure_log_dir
|
|
|
|
|
|
cleanup_stale_pid "$FRONTEND_PID_FILE"
|
|
|
|
|
|
|
|
|
|
|
|
local pid
|
|
|
|
|
|
pid="$(read_pid "$FRONTEND_PID_FILE")"
|
|
|
|
|
|
if is_pid_running "$pid"; then
|
|
|
|
|
|
warn "前端已在运行 (PID: $pid)"
|
|
|
|
|
|
return
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if ! command -v npm > /dev/null 2>&1; then
|
|
|
|
|
|
die "未找到 npm,请先安装 Node.js 20+。"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
ensure_frontend_deps
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$mode" = "static" ]; then
|
|
|
|
|
|
require_python_bootstrap
|
|
|
|
|
|
npm --prefix frontend run build
|
|
|
|
|
|
nohup "$PYTHON_BOOTSTRAP" -m http.server "$FRONTEND_PORT" --bind 0.0.0.0 --directory frontend/dist > "$FRONTEND_LOG_FILE" 2>&1 &
|
|
|
|
|
|
else
|
|
|
|
|
|
nohup npm --prefix frontend run dev -- --host 0.0.0.0 --port "$FRONTEND_PORT" > "$FRONTEND_LOG_FILE" 2>&1 &
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
pid=$!
|
|
|
|
|
|
echo "$pid" > "$FRONTEND_PID_FILE"
|
|
|
|
|
|
sleep 4
|
|
|
|
|
|
|
|
|
|
|
|
if is_pid_running "$pid"; then
|
|
|
|
|
|
success "前端启动成功 (PID: $pid)"
|
2026-05-20 23:34:08 +08:00
|
|
|
|
echo " 地址: http://${DISPLAY_HOST}:$FRONTEND_PORT"
|
2026-05-14 18:09:15 +08:00
|
|
|
|
echo " 模式: $mode"
|
|
|
|
|
|
echo " 日志: $FRONTEND_LOG_FILE"
|
|
|
|
|
|
else
|
|
|
|
|
|
rm -f "$FRONTEND_PID_FILE"
|
|
|
|
|
|
die "前端启动失败,请查看日志: $FRONTEND_LOG_FILE"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
kill_pid_file_process() {
|
|
|
|
|
|
local pid_file="$1"
|
|
|
|
|
|
local label="$2"
|
|
|
|
|
|
local pid
|
|
|
|
|
|
pid="$(read_pid "$pid_file")"
|
|
|
|
|
|
|
|
|
|
|
|
if ! is_pid_running "$pid"; then
|
|
|
|
|
|
rm -f "$pid_file"
|
|
|
|
|
|
warn "$label 未通过 PID 文件发现运行中的进程"
|
|
|
|
|
|
return 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
kill "$pid" 2>/dev/null || true
|
|
|
|
|
|
sleep 2
|
|
|
|
|
|
if is_pid_running "$pid"; then
|
|
|
|
|
|
kill -9 "$pid" 2>/dev/null || true
|
|
|
|
|
|
fi
|
|
|
|
|
|
rm -f "$pid_file"
|
|
|
|
|
|
success "$label 已停止"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stop_api() {
|
|
|
|
|
|
load_env
|
|
|
|
|
|
ensure_log_dir
|
|
|
|
|
|
|
|
|
|
|
|
if kill_pid_file_process "$API_PID_FILE" "API"; then
|
|
|
|
|
|
return
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
local port_listener
|
|
|
|
|
|
port_listener="$(port_pid "$API_PORT")"
|
|
|
|
|
|
if [ -n "$port_listener" ]; then
|
|
|
|
|
|
kill "$port_listener" 2>/dev/null || true
|
|
|
|
|
|
sleep 1
|
|
|
|
|
|
if is_pid_running "$port_listener"; then
|
|
|
|
|
|
kill -9 "$port_listener" 2>/dev/null || true
|
|
|
|
|
|
fi
|
|
|
|
|
|
success "已停止监听 API 端口 $API_PORT 的进程 (PID: $port_listener)"
|
|
|
|
|
|
else
|
|
|
|
|
|
warn "未发现运行中的 API 服务"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
rm -f "$API_PID_FILE"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stop_frontend() {
|
|
|
|
|
|
load_env
|
|
|
|
|
|
ensure_log_dir
|
|
|
|
|
|
|
|
|
|
|
|
if kill_pid_file_process "$FRONTEND_PID_FILE" "前端"; then
|
|
|
|
|
|
return
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
local port_listener
|
|
|
|
|
|
port_listener="$(port_pid "$FRONTEND_PORT")"
|
|
|
|
|
|
if [ -n "$port_listener" ]; then
|
|
|
|
|
|
kill "$port_listener" 2>/dev/null || true
|
|
|
|
|
|
sleep 1
|
|
|
|
|
|
if is_pid_running "$port_listener"; then
|
|
|
|
|
|
kill -9 "$port_listener" 2>/dev/null || true
|
|
|
|
|
|
fi
|
|
|
|
|
|
success "已停止监听前端端口 $FRONTEND_PORT 的进程 (PID: $port_listener)"
|
|
|
|
|
|
else
|
|
|
|
|
|
warn "未发现运行中的前端服务"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
rm -f "$FRONTEND_PID_FILE"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
run_status() {
|
|
|
|
|
|
load_env
|
|
|
|
|
|
ensure_log_dir
|
|
|
|
|
|
print_header "AI+合规智能中枢 - 服务状态"
|
|
|
|
|
|
|
|
|
|
|
|
cleanup_stale_pid "$API_PID_FILE"
|
|
|
|
|
|
cleanup_stale_pid "$FRONTEND_PID_FILE"
|
|
|
|
|
|
|
|
|
|
|
|
local api_running=false
|
|
|
|
|
|
local frontend_running=false
|
|
|
|
|
|
local pid
|
|
|
|
|
|
local port_listener
|
2026-05-20 23:34:08 +08:00
|
|
|
|
local service_name
|
|
|
|
|
|
local service_port
|
2026-05-14 18:09:15 +08:00
|
|
|
|
|
|
|
|
|
|
echo -e "${YELLOW}API 服务:${NC}"
|
|
|
|
|
|
pid="$(read_pid "$API_PID_FILE")"
|
|
|
|
|
|
if is_pid_running "$pid"; then
|
|
|
|
|
|
api_running=true
|
|
|
|
|
|
success " 状态: 运行中"
|
|
|
|
|
|
echo " PID: $pid"
|
|
|
|
|
|
else
|
|
|
|
|
|
port_listener="$(port_pid "$API_PORT")"
|
|
|
|
|
|
if [ -n "$port_listener" ]; then
|
|
|
|
|
|
api_running=true
|
|
|
|
|
|
success " 状态: 运行中 (无 PID 文件)"
|
|
|
|
|
|
echo " PID: $port_listener"
|
|
|
|
|
|
else
|
|
|
|
|
|
error " 状态: 已停止"
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$api_running" = true ]; then
|
|
|
|
|
|
if api_health_ok; then
|
|
|
|
|
|
success " 健康检查: 正常"
|
|
|
|
|
|
else
|
|
|
|
|
|
warn " 健康检查: 未通过"
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
2026-05-20 23:34:08 +08:00
|
|
|
|
echo " 地址: http://${DISPLAY_HOST}:$API_PORT"
|
|
|
|
|
|
echo " 文档: http://${DISPLAY_HOST}:${API_PORT}/docs"
|
2026-05-14 18:09:15 +08:00
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
|
|
echo -e "${YELLOW}前端服务:${NC}"
|
|
|
|
|
|
pid="$(read_pid "$FRONTEND_PID_FILE")"
|
|
|
|
|
|
if is_pid_running "$pid"; then
|
|
|
|
|
|
frontend_running=true
|
|
|
|
|
|
success " 状态: 运行中"
|
|
|
|
|
|
echo " PID: $pid"
|
|
|
|
|
|
else
|
|
|
|
|
|
port_listener="$(port_pid "$FRONTEND_PORT")"
|
|
|
|
|
|
if [ -n "$port_listener" ]; then
|
|
|
|
|
|
frontend_running=true
|
|
|
|
|
|
success " 状态: 运行中 (无 PID 文件)"
|
|
|
|
|
|
echo " PID: $port_listener"
|
|
|
|
|
|
else
|
|
|
|
|
|
error " 状态: 已停止"
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
echo " 模式: $FRONTEND_MODE"
|
2026-05-20 23:34:08 +08:00
|
|
|
|
echo " 地址: http://${DISPLAY_HOST}:$FRONTEND_PORT"
|
2026-05-14 18:09:15 +08:00
|
|
|
|
echo ""
|
|
|
|
|
|
|
2026-05-20 23:34:08 +08:00
|
|
|
|
echo -e "${YELLOW}基础服务连通性:${NC}"
|
|
|
|
|
|
for service in \
|
|
|
|
|
|
"Milvus:19530" \
|
|
|
|
|
|
"MinIO API:9000" \
|
|
|
|
|
|
"MinIO Console:9001" \
|
|
|
|
|
|
"Redis:6379" \
|
|
|
|
|
|
"PostgreSQL:5432"
|
|
|
|
|
|
do
|
|
|
|
|
|
service_name="${service%%:*}"
|
|
|
|
|
|
service_port="${service##*:}"
|
|
|
|
|
|
if check_tcp_connectivity "$SERVICE_HOST" "$service_port"; then
|
|
|
|
|
|
success " ${service_name}: ${SERVICE_HOST}:${service_port} 可连通"
|
|
|
|
|
|
else
|
|
|
|
|
|
warn " ${service_name}: ${SERVICE_HOST}:${service_port} 不可连通"
|
|
|
|
|
|
fi
|
|
|
|
|
|
done
|
2026-05-14 18:09:15 +08:00
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
if [ "$api_running" = true ] && [ "$frontend_running" = true ]; then
|
|
|
|
|
|
success "所有核心服务均在运行"
|
|
|
|
|
|
else
|
|
|
|
|
|
warn "存在未运行的服务,可使用 ./dev.sh start 启动"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
run_logs() {
|
|
|
|
|
|
local target="${1:-}"
|
|
|
|
|
|
local follow="${2:-}"
|
|
|
|
|
|
local log_file
|
|
|
|
|
|
|
|
|
|
|
|
case "$target" in
|
|
|
|
|
|
api)
|
|
|
|
|
|
log_file="$API_LOG_FILE"
|
|
|
|
|
|
;;
|
|
|
|
|
|
frontend)
|
|
|
|
|
|
log_file="$FRONTEND_LOG_FILE"
|
|
|
|
|
|
;;
|
|
|
|
|
|
*)
|
|
|
|
|
|
die "请指定日志类型: api 或 frontend"
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
|
|
|
|
|
|
[ -f "$log_file" ] || die "日志文件不存在: $log_file"
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$follow" = "--follow" ]; then
|
|
|
|
|
|
tail -f "$log_file"
|
|
|
|
|
|
else
|
|
|
|
|
|
tail -n 50 "$log_file"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
show_help() {
|
|
|
|
|
|
cat <<'EOF'
|
|
|
|
|
|
AI+合规智能中枢统一脚本
|
|
|
|
|
|
|
|
|
|
|
|
用法:
|
|
|
|
|
|
./dev.sh help
|
|
|
|
|
|
./dev.sh setup
|
|
|
|
|
|
./dev.sh start [all|api|frontend] [--foreground] [--mode dev|static]
|
|
|
|
|
|
./dev.sh stop [all|api|frontend]
|
|
|
|
|
|
./dev.sh restart [all|api|frontend] [--mode dev|static]
|
|
|
|
|
|
./dev.sh status
|
|
|
|
|
|
./dev.sh logs <api|frontend> [--follow]
|
|
|
|
|
|
|
|
|
|
|
|
命令说明:
|
|
|
|
|
|
help
|
|
|
|
|
|
输出完整帮助信息、默认端口、日志目录和常见示例。
|
|
|
|
|
|
|
|
|
|
|
|
setup
|
|
|
|
|
|
进行一次性的本地初始化。
|
|
|
|
|
|
包含 Python 版本检查、.venv 虚拟环境创建、后端依赖安装、前端 npm install、
|
2026-05-20 23:34:08 +08:00
|
|
|
|
以及 6.86.80.8 基础服务端口连通性检查。
|
2026-05-14 18:09:15 +08:00
|
|
|
|
|
|
|
|
|
|
start
|
|
|
|
|
|
启动服务。默认行为等同于 ./dev.sh start all。
|
|
|
|
|
|
可选目标:
|
|
|
|
|
|
all 同时启动 API 和前端。
|
|
|
|
|
|
api 只启动后端 API。
|
|
|
|
|
|
frontend 只启动前端。
|
|
|
|
|
|
可选参数:
|
|
|
|
|
|
--foreground 仅对 start api 生效,前台运行并开启 --reload,便于调试。
|
|
|
|
|
|
--mode dev 前端使用 Vite 开发服务器,默认端口 5173。
|
|
|
|
|
|
--mode static 前端先执行 npm run build,再以静态文件服务器方式启动。
|
|
|
|
|
|
|
|
|
|
|
|
stop
|
|
|
|
|
|
停止服务。默认行为等同于 ./dev.sh stop all。
|
|
|
|
|
|
会优先读取 logs/*.pid,PID 文件失效时会回退到端口探测。
|
|
|
|
|
|
|
|
|
|
|
|
restart
|
|
|
|
|
|
先停止再启动,支持 all/api/frontend。
|
|
|
|
|
|
restart frontend --mode static 可直接切换前端启动模式。
|
|
|
|
|
|
|
|
|
|
|
|
status
|
2026-05-20 23:34:08 +08:00
|
|
|
|
查看 API、前端、6.86.80.8 基础服务的状态。
|
2026-05-14 18:09:15 +08:00
|
|
|
|
API 状态包含健康检查;前端状态包含当前模式和访问地址。
|
|
|
|
|
|
|
|
|
|
|
|
logs
|
|
|
|
|
|
查看日志。默认输出最后 50 行。
|
|
|
|
|
|
追加 --follow 后会持续跟踪日志输出。
|
|
|
|
|
|
|
|
|
|
|
|
默认约定:
|
|
|
|
|
|
API_HOST 默认 0.0.0.0
|
|
|
|
|
|
API_PORT 默认 8000
|
|
|
|
|
|
FRONTEND_PORT 默认 5173
|
|
|
|
|
|
FRONTEND_MODE 默认 dev
|
|
|
|
|
|
日志目录 logs/
|
|
|
|
|
|
PID 文件 logs/api.pid, logs/frontend.pid
|
|
|
|
|
|
|
|
|
|
|
|
常用示例:
|
|
|
|
|
|
./dev.sh setup
|
|
|
|
|
|
./dev.sh start
|
|
|
|
|
|
./dev.sh start api --foreground
|
|
|
|
|
|
./dev.sh start frontend --mode static
|
|
|
|
|
|
./dev.sh restart frontend --mode dev
|
|
|
|
|
|
./dev.sh status
|
|
|
|
|
|
./dev.sh logs api --follow
|
|
|
|
|
|
./dev.sh logs frontend
|
|
|
|
|
|
EOF
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
parse_target() {
|
|
|
|
|
|
local default_target="$1"
|
|
|
|
|
|
local candidate="${2:-}"
|
|
|
|
|
|
case "$candidate" in
|
|
|
|
|
|
all|api|frontend)
|
|
|
|
|
|
echo "$candidate"
|
|
|
|
|
|
;;
|
|
|
|
|
|
*)
|
|
|
|
|
|
echo "$default_target"
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
main() {
|
|
|
|
|
|
local command="${1:-help}"
|
|
|
|
|
|
local target="all"
|
|
|
|
|
|
local mode=""
|
|
|
|
|
|
local foreground=false
|
|
|
|
|
|
local log_target=""
|
|
|
|
|
|
shift || true
|
|
|
|
|
|
load_env
|
|
|
|
|
|
|
|
|
|
|
|
case "$command" in
|
|
|
|
|
|
help|-h|--help)
|
|
|
|
|
|
show_help
|
|
|
|
|
|
;;
|
|
|
|
|
|
setup)
|
|
|
|
|
|
run_setup
|
|
|
|
|
|
;;
|
|
|
|
|
|
start)
|
|
|
|
|
|
target="$(parse_target all "${1:-}")"
|
|
|
|
|
|
if [ "$target" != "all" ]; then
|
|
|
|
|
|
shift || true
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
while [ $# -gt 0 ]; do
|
|
|
|
|
|
case "$1" in
|
|
|
|
|
|
--foreground)
|
|
|
|
|
|
foreground=true
|
|
|
|
|
|
;;
|
|
|
|
|
|
--mode)
|
|
|
|
|
|
shift || die "--mode 需要指定 dev 或 static"
|
|
|
|
|
|
mode="$1"
|
|
|
|
|
|
validate_frontend_mode "$mode"
|
|
|
|
|
|
;;
|
|
|
|
|
|
*)
|
|
|
|
|
|
die "未知参数: $1"
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
shift || true
|
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
|
|
case "$target" in
|
|
|
|
|
|
all)
|
|
|
|
|
|
[ "$foreground" = false ] || die "start all 不支持 --foreground,请使用 start api --foreground"
|
|
|
|
|
|
print_header "AI+合规智能中枢 - 启动服务"
|
|
|
|
|
|
start_api background
|
|
|
|
|
|
start_frontend "${mode:-$FRONTEND_MODE}"
|
|
|
|
|
|
;;
|
|
|
|
|
|
api)
|
|
|
|
|
|
if [ "$foreground" = true ]; then
|
|
|
|
|
|
start_api foreground
|
|
|
|
|
|
else
|
|
|
|
|
|
print_header "AI+合规智能中枢 - 启动 API"
|
|
|
|
|
|
start_api background
|
|
|
|
|
|
fi
|
|
|
|
|
|
;;
|
|
|
|
|
|
frontend)
|
|
|
|
|
|
print_header "AI+合规智能中枢 - 启动前端"
|
|
|
|
|
|
start_frontend "${mode:-$FRONTEND_MODE}"
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
;;
|
|
|
|
|
|
stop)
|
|
|
|
|
|
target="$(parse_target all "${1:-}")"
|
|
|
|
|
|
print_header "AI+合规智能中枢 - 停止服务"
|
|
|
|
|
|
case "$target" in
|
|
|
|
|
|
all)
|
|
|
|
|
|
stop_frontend
|
|
|
|
|
|
stop_api
|
|
|
|
|
|
;;
|
|
|
|
|
|
api)
|
|
|
|
|
|
stop_api
|
|
|
|
|
|
;;
|
|
|
|
|
|
frontend)
|
|
|
|
|
|
stop_frontend
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
;;
|
|
|
|
|
|
restart)
|
|
|
|
|
|
target="$(parse_target all "${1:-}")"
|
|
|
|
|
|
if [ "$target" != "all" ]; then
|
|
|
|
|
|
shift || true
|
|
|
|
|
|
fi
|
|
|
|
|
|
while [ $# -gt 0 ]; do
|
|
|
|
|
|
case "$1" in
|
|
|
|
|
|
--mode)
|
|
|
|
|
|
shift || die "--mode 需要指定 dev 或 static"
|
|
|
|
|
|
mode="$1"
|
|
|
|
|
|
validate_frontend_mode "$mode"
|
|
|
|
|
|
;;
|
|
|
|
|
|
*)
|
|
|
|
|
|
die "未知参数: $1"
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
shift || true
|
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
|
|
print_header "AI+合规智能中枢 - 重启服务"
|
|
|
|
|
|
case "$target" in
|
|
|
|
|
|
all)
|
|
|
|
|
|
stop_frontend
|
|
|
|
|
|
stop_api
|
|
|
|
|
|
start_api background
|
|
|
|
|
|
start_frontend "${mode:-$FRONTEND_MODE}"
|
|
|
|
|
|
;;
|
|
|
|
|
|
api)
|
|
|
|
|
|
stop_api
|
|
|
|
|
|
start_api background
|
|
|
|
|
|
;;
|
|
|
|
|
|
frontend)
|
|
|
|
|
|
stop_frontend
|
|
|
|
|
|
start_frontend "${mode:-$FRONTEND_MODE}"
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
;;
|
|
|
|
|
|
status)
|
|
|
|
|
|
run_status
|
|
|
|
|
|
;;
|
|
|
|
|
|
logs)
|
|
|
|
|
|
log_target="${1:-}"
|
|
|
|
|
|
run_logs "$log_target" "${2:-}"
|
|
|
|
|
|
;;
|
|
|
|
|
|
*)
|
|
|
|
|
|
die "未知命令: $command。可使用 ./dev.sh help 查看帮助。"
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
main "$@"
|