#!/usr/bin/env bash # # 大时代 Development Startup Script # ================================ # # 启动模式说明: # ------------- # 本脚本只支持当前开发主路径: # 启动 4 个独立服务 + 由 runtime_service 托管的 Gateway # - agent_service (端口 8000): Agent 生命周期管理 # - runtime_service (端口 8003): 运行时配置和 Pipeline 执行 # - trading_service (端口 8001): 市场数据和交易操作 # - news_service (端口 8002): 新闻采集和富化 # - gateway (端口 8765): 由 runtime_service 拉起的 WebSocket 网关 # # 用法: # ./start-dev.sh # 启动开发环境 # ./start-dev.sh --help # 显示帮助信息 # set -euo pipefail # ============================================ # 配置与常量 # ============================================ readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly SCRIPT_NAME="$(basename "$0")" # 服务端点配置 readonly AGENT_SERVICE_PORT=8000 readonly TRADING_SERVICE_PORT=8001 readonly NEWS_SERVICE_PORT=8002 readonly RUNTIME_SERVICE_PORT=8003 readonly GATEWAY_PORT=8765 # 颜色定义 readonly RED='\033[0;31m' readonly GREEN='\033[0;32m' readonly YELLOW='\033[1;33m' readonly BLUE='\033[0;34m' readonly CYAN='\033[0;36m' readonly NC='\033[0m' # No Color # 进程 ID 数组 PIDS=() # ============================================ # 工具函数 # ============================================ log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } log_step() { echo -e "${CYAN}[STEP]${NC} $1" } log_debug() { echo -e "${BLUE}[DEBUG]${NC} $1" } # ============================================ # 帮助信息 # ============================================ show_help() { cat << 'EOF' 大时代 Development Startup Script 用法: ./start-dev.sh [选项] 选项: --help, -h 显示此帮助信息 开发模式: 启动 4 个独立微服务 + 托管 Gateway,各服务独立进程,便于单独调试 - agent_service: http://localhost:8000 (Agent 生命周期) - trading_service: http://localhost:8001 (市场数据) - news_service: http://localhost:8002 (新闻服务) - runtime_service: http://localhost:8003 (运行时管理) - gateway: ws://localhost:8765 (由 runtime_service 托管) 环境要求: - Python 3.9+ - 虚拟环境 (推荐 .venv) - .env 文件 (可选但推荐) 示例: ./start-dev.sh # 启动开发环境 EOF } # ============================================ # 参数解析 # ============================================ parse_args() { while [[ $# -gt 0 ]]; do case "$1" in --help|-h) show_help exit 0 ;; *) log_warn "未知选项: $1" log_info "使用 --help 查看帮助信息" exit 1 ;; esac done } # ============================================ # 启动前检查 # ============================================ check_python_version() { log_step "检查 Python 版本..." if ! command -v python >/dev/null 2>&1; then log_error "未找到 python 命令" exit 1 fi local python_version python_version=$(python --version 2>&1 | awk '{print $2}') log_debug "Python 版本: $python_version" python - <<'PY' || { import sys if sys.version_info < (3, 9): print(f"Python 3.9+ 是必需的,当前版本: {sys.version}") sys.exit(1) print(f"Python 版本检查通过: {sys.version.split()[0]}") PY log_error "Python 版本不符合要求 (需要 3.9+)" exit 1 } } check_virtual_env() { log_step "检查虚拟环境..." if [[ -n "${VIRTUAL_ENV:-}" ]]; then log_info "已激活虚拟环境: $VIRTUAL_ENV" return 0 fi if [[ -f "$SCRIPT_DIR/.venv/bin/activate" ]]; then log_warn "未激活虚拟环境,自动激活 .venv" # shellcheck disable=SC1091 source "$SCRIPT_DIR/.venv/bin/activate" log_info "虚拟环境已激活: $VIRTUAL_ENV" else log_warn "未找到虚拟环境,使用系统 Python" fi } check_required_commands() { log_step "检查必要命令..." local missing=() if ! command -v python >/dev/null 2>&1; then missing+=("python") fi if ! command -v lsof >/dev/null 2>&1; then missing+=("lsof") fi if [[ ${#missing[@]} -gt 0 ]]; then log_error "缺少必要命令: ${missing[*]}" log_info "请安装缺失的命令后重试" exit 1 fi log_info "所有必要命令已安装" } check_python_modules() { log_step "检查 Python 依赖模块..." local modules=("fastapi" "uvicorn" "websockets" "yaml" "dotenv") local missing=() for module in "${modules[@]}"; do if ! python -c "import $module" 2>/dev/null; then missing+=("$module") fi done if [[ ${#missing[@]} -gt 0 ]]; then log_error "缺少 Python 模块: ${missing[*]}" log_info "请安装依赖: uv pip install -e '.[dev]' 或 pip install -r requirements.txt" exit 1 fi log_info "所有依赖模块已安装" } check_env_file() { log_step "检查环境配置文件..." if [[ -f "$SCRIPT_DIR/.env" ]]; then log_info "加载环境变量: .env" set -a # shellcheck disable=SC1091 source "$SCRIPT_DIR/.env" set +a else log_warn ".env 文件不存在,使用默认配置" log_info "提示: 复制 env.template 到 .env 并配置您的 API 密钥" fi } check_ports() { log_step "检查端口占用情况..." local ports=($AGENT_SERVICE_PORT $TRADING_SERVICE_PORT $NEWS_SERVICE_PORT $RUNTIME_SERVICE_PORT $GATEWAY_PORT) local occupied=() for port in "${ports[@]}"; do if lsof -Pi :"$port" -sTCP:LISTEN -t >/dev/null 2>&1; then occupied+=("$port") fi done if [[ ${#occupied[@]} -gt 0 ]]; then log_warn "以下端口已被占用: ${occupied[*]}" log_info "尝试释放端口..." for port in "${occupied[@]}"; do kill_port "$port" done else log_info "所有端口可用" fi } kill_port() { local port="$1" local pids pids=$(lsof -ti :"$port" 2>/dev/null || true) if [[ -n "$pids" ]]; then log_warn "释放端口 $port (PID: $pids)" echo "$pids" | xargs kill -9 2>/dev/null || true sleep 0.5 fi } check_optional_services() { log_step "检查可选服务..." # 检查 npm(用于前端) if ! command -v npm >/dev/null 2>&1; then log_warn "npm 未安装,前端启动功能不可用" else log_info "npm 已安装" fi # 检查 OpenClaw gateway check_openclaw_gateway } check_openclaw_gateway() { local target_host="127.0.0.1" local target_port="18789" if python - </dev/null 2>&1; then import socket sock = socket.socket() sock.settimeout(1.0) sock.connect(("${target_host}", ${target_port})) sock.close() PY log_info "OpenClaw gateway 可连接: ws://${target_host}:${target_port}" else log_warn "OpenClaw gateway 未启动: ws://${target_host}:${target_port}" log_info " OpenClaw 面板功能将不可用" fi } # ============================================ # 服务启动函数 # ============================================ start_service() { local name="$1" local app_path="$2" local port="$3" log_info "启动 ${name} (端口 ${port})..." SERVICE_NAME="${name}" python -m uvicorn "${app_path}" \ --host 0.0.0.0 \ --port "${port}" \ --reload \ --reload-dir backend \ --log-level warning \ --no-access-log & PIDS+=($!) } wait_for_runtime_service() { log_step "等待 runtime_service 就绪..." local runtime_url="http://127.0.0.1:${RUNTIME_SERVICE_PORT}/health" local attempts=30 for ((i=1; i<=attempts; i++)); do if python - </dev/null 2>&1; then import urllib.request with urllib.request.urlopen("${runtime_url}", timeout=1.5) as resp: raise SystemExit(0 if resp.status == 200 else 1) PY log_info "runtime_service 已就绪: ${runtime_url}" return 0 fi sleep 1 done log_error "runtime_service 未在预期时间内就绪" return 1 } start_managed_runtime() { log_step "通过 runtime_service 创建默认运行时..." local runtime_api="http://127.0.0.1:${RUNTIME_SERVICE_PORT}/api/runtime/start" if ! python - < int: value = os.getenv(name, "").strip() return int(value) if value else default def _env_float(name: str, default: float) -> float: value = os.getenv(name, "").strip() return float(value) if value else default payload = { "launch_mode": "fresh", "tickers": tickers, "schedule_mode": os.getenv("SCHEDULE_MODE", "daily").strip() or "daily", "interval_minutes": _env_int("INTERVAL_MINUTES", 60), "trigger_time": os.getenv("TRIGGER_TIME", "09:30").strip() or "09:30", "max_comm_cycles": _env_int("MAX_COMM_CYCLES", 2), "initial_cash": _env_float("INITIAL_CASH", 100000.0), "margin_requirement": _env_float("MARGIN_REQUIREMENT", 0.0), "enable_memory": os.getenv("ENABLE_MEMORY", "").strip().lower() in {"1", "true", "yes", "on"}, "mode": os.getenv("RUNTIME_MODE", "live").strip() or "live", "poll_interval": _env_int("POLL_INTERVAL", 10), } data = json.dumps(payload).encode("utf-8") req = urllib.request.Request( "${runtime_api}", data=data, headers={"Content-Type": "application/json"}, method="POST", ) try: with urllib.request.urlopen(req, timeout=30) as resp: body = json.loads(resp.read().decode("utf-8")) except Exception as exc: print(f"FAILED: {exc}", file=sys.stderr) raise print(json.dumps(body, ensure_ascii=False)) PY log_error "通过 runtime_service 创建运行时失败" return 1 fi log_info "默认运行时已创建,Gateway 将由 runtime_service 托管" } # ============================================ # 微服务模式启动 # ============================================ start_microservices_mode() { log_step "启动微服务模式..." echo "" echo -e "${CYAN}==========================================${NC}" echo -e "${CYAN} 服务端点 ${NC}" echo -e "${CYAN}==========================================${NC}" echo -e " agent_service: http://localhost:${AGENT_SERVICE_PORT}" echo -e " runtime_service: http://localhost:${RUNTIME_SERVICE_PORT}" echo -e " trading_service: http://localhost:${TRADING_SERVICE_PORT}" echo -e " news_service: http://localhost:${NEWS_SERVICE_PORT}" echo -e " gateway: ws://localhost:${GATEWAY_PORT} (由 runtime_service 拉起)" echo -e "${CYAN}==========================================${NC}" echo "" # 设置服务 URL 环境变量 export TRADING_SERVICE_URL="${TRADING_SERVICE_URL:-http://localhost:${TRADING_SERVICE_PORT}}" export NEWS_SERVICE_URL="${NEWS_SERVICE_URL:-http://localhost:${NEWS_SERVICE_PORT}}" export RUNTIME_SERVICE_URL="${RUNTIME_SERVICE_URL:-http://localhost:${RUNTIME_SERVICE_PORT}}" export OPENCLAW_SERVICE_URL="${OPENCLAW_SERVICE_URL:-http://localhost:18789}" export ENABLE_DASHBOARD_COMPAT_EXPORTS="${ENABLE_DASHBOARD_COMPAT_EXPORTS:-true}" log_debug "环境变量:" log_debug " TRADING_SERVICE_URL=${TRADING_SERVICE_URL}" log_debug " NEWS_SERVICE_URL=${NEWS_SERVICE_URL}" log_debug " RUNTIME_SERVICE_URL=${RUNTIME_SERVICE_URL}" log_debug " OPENCLAW_SERVICE_URL=${OPENCLAW_SERVICE_URL}" echo "" # 启动 4 个微服务 start_service "agent_service" "backend.apps.agent_service:app" "$AGENT_SERVICE_PORT" start_service "runtime_service" "backend.apps.runtime_service:app" "$RUNTIME_SERVICE_PORT" start_service "trading_service" "backend.apps.trading_service:app" "$TRADING_SERVICE_PORT" start_service "news_service" "backend.apps.news_service:app" "$NEWS_SERVICE_PORT" wait_for_runtime_service start_managed_runtime echo "" log_info "所有服务已启动" log_info "按 Ctrl+C 停止所有服务" echo "" } # ============================================ # 清理与信号处理 # ============================================ cleanup() { if [[ ${#PIDS[@]} -gt 0 ]]; then echo "" log_step "正在停止服务..." for pid in "${PIDS[@]}"; do if kill -0 "$pid" 2>/dev/null; then kill "$pid" 2>/dev/null || true fi done # 等待进程结束 wait "${PIDS[@]}" 2>/dev/null || true log_info "所有服务已停止" fi } trap cleanup EXIT INT TERM # ============================================ # 主程序 # ============================================ main() { # 解析命令行参数 parse_args "$@" # 显示启动横幅 echo "" echo -e "${CYAN}==========================================${NC}" echo -e "${CYAN} 大时代 Development Environment ${NC}" echo -e "${CYAN}==========================================${NC}" echo "" # 切换到项目根目录 cd "$SCRIPT_DIR" log_debug "工作目录: $SCRIPT_DIR" # 启动前检查 check_required_commands check_python_version check_virtual_env check_python_modules check_env_file check_ports check_optional_services echo "" echo -e "${GREEN}==========================================${NC}" echo -e "${GREEN} 启动前检查完成 ${NC}" echo -e "${GREEN}==========================================${NC}" echo "" start_microservices_mode # 等待所有后台进程 wait } # 执行主程序 main "$@"