#!/bin/bash #=============================================================================== # iConsulting 部署管理脚本 # # 用法: ./deploy.sh [service] [options] # # 命令: # build - 编译构建 # start - 启动服务 # stop - 停止服务 # restart - 重启服务 # status - 查看状态 # logs - 查看日志 # clean - 清理构建产物 # deploy - 完整部署(构建+启动) # db - 数据库操作 # help - 显示帮助 # # 服务: # all - 所有服务 # web-client - 用户前端 # admin-client - 管理后台前端 # conversation - 对话服务 # user - 用户服务 # payment - 支付服务 # knowledge - 知识库服务 # evolution - 进化服务 # kong - API网关 # postgres - PostgreSQL数据库 # redis - Redis缓存 # neo4j - Neo4j图数据库 # nginx - Nginx静态服务 # #=============================================================================== set -e # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' PURPLE='\033[0;35m' CYAN='\033[0;36m' NC='\033[0m' # No Color # 项目根目录 PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$PROJECT_ROOT" # 配置 COMPOSE_FILE="docker-compose.yml" ENV_FILE=".env" # Docker Compose 命令 (默认值,会在 check_environment 中更新) DOCKER_COMPOSE="docker-compose" # 域名配置 DOMAIN="${DOMAIN:-iconsulting.szaiai.com}" ADMIN_EMAIL="${ADMIN_EMAIL:-admin@szaiai.com}" # 服务端口配置 declare -A SERVICE_PORTS=( ["conversation"]=3004 ["user"]=3001 ["payment"]=3002 ["knowledge"]=3003 ["evolution"]=3005 ["kong"]=8000 ["postgres"]=5432 ["redis"]=6379 ["neo4j"]=7474 ["nginx"]=8080 ) # 服务目录映射 declare -A SERVICE_DIRS=( ["conversation"]="packages/services/conversation-service" ["user"]="packages/services/user-service" ["payment"]="packages/services/payment-service" ["knowledge"]="packages/services/knowledge-service" ["evolution"]="packages/services/evolution-service" ["web-client"]="packages/web-client" ["admin-client"]="packages/admin-client" ["shared"]="packages/shared" ) # Docker服务名映射 declare -A DOCKER_SERVICES=( ["conversation"]="conversation-service" ["user"]="user-service" ["payment"]="payment-service" ["knowledge"]="knowledge-service" ["evolution"]="evolution-service" ["web-client"]="web-client" ["admin-client"]="admin-client" ["kong"]="kong" ["postgres"]="postgres" ["redis"]="redis" ["neo4j"]="neo4j" ["nginx"]="nginx" ) #=============================================================================== # 工具函数 #=============================================================================== log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } log_step() { echo -e "${PURPLE}[STEP]${NC} $1" } # 检查命令是否存在 check_command() { if ! command -v "$1" &> /dev/null; then log_error "$1 未安装,请先安装" exit 1 fi } # 检查环境 (只需要 Docker) check_environment() { log_step "检查运行环境..." check_command "docker" # 检查 docker-compose 或 docker compose if command -v docker-compose &> /dev/null; then DOCKER_COMPOSE="docker-compose" elif docker compose version &> /dev/null 2>&1; then DOCKER_COMPOSE="docker compose" else log_error "docker-compose 或 docker compose 未安装" exit 1 fi log_success "环境检查通过 (使用 $DOCKER_COMPOSE)" } # Builder 镜像名称 BUILDER_IMAGE="iconsulting-builder:latest" # 构建 Builder 镜像 build_builder_image() { log_step "检查/构建 Builder 镜像..." # 检查镜像是否存在 if docker images | grep -q "iconsulting-builder"; then log_info "Builder 镜像已存在" return 0 fi log_info "创建 Builder 镜像..." # 创建临时 Dockerfile cat > "$PROJECT_ROOT/.builder.Dockerfile" << 'DOCKERFILE' FROM node:20-alpine # 安装 pnpm RUN corepack enable && corepack prepare pnpm@latest --activate # 设置工作目录 WORKDIR /app # 设置 pnpm store 目录 ENV PNPM_HOME=/root/.local/share/pnpm ENV PATH=$PNPM_HOME:$PATH CMD ["sh"] DOCKERFILE docker build -t "$BUILDER_IMAGE" -f "$PROJECT_ROOT/.builder.Dockerfile" "$PROJECT_ROOT" rm -f "$PROJECT_ROOT/.builder.Dockerfile" log_success "Builder 镜像创建完成" } # 加载环境变量 load_env() { if [ -f "$ENV_FILE" ]; then export $(grep -v '^#' "$ENV_FILE" | xargs) fi } # 等待服务就绪 wait_for_service() { local host=$1 local port=$2 local service=$3 local max_attempts=${4:-30} local attempt=1 log_info "等待 $service ($host:$port) 就绪..." while [ $attempt -le $max_attempts ]; do if nc -z "$host" "$port" 2>/dev/null; then log_success "$service 已就绪" return 0 fi echo -n "." sleep 2 attempt=$((attempt + 1)) done echo "" log_error "$service 启动超时" return 1 } #=============================================================================== # 构建函数 (使用 Docker 容器构建,无需在主机安装 Node.js) #=============================================================================== # 在容器中执行命令 run_in_builder() { docker run --rm \ -v "$PROJECT_ROOT:/app" \ -v "iconsulting-pnpm-store:/root/.local/share/pnpm/store" \ -w /app \ "$BUILDER_IMAGE" \ sh -c "$1" } # 安装依赖 (在容器中) install_deps() { log_step "安装项目依赖 (在 Docker 容器中)..." build_builder_image run_in_builder "pnpm install --frozen-lockfile || pnpm install" log_success "依赖安装完成" } # 构建共享包 (在容器中) build_shared() { log_step "构建 shared 包..." run_in_builder "cd packages/shared && pnpm run build" log_success "shared 构建完成" } # 构建单个后端服务 (在容器中) build_backend_service() { local service=$1 local dir="${SERVICE_DIRS[$service]}" if [ -z "$dir" ]; then log_error "未知服务: $service" return 1 fi log_step "构建 $service..." run_in_builder "cd $dir && rm -rf dist && pnpm run build" log_success "$service 构建完成" } # 构建单个前端 (在容器中) build_frontend() { local service=$1 local dir="${SERVICE_DIRS[$service]}" if [ -z "$dir" ]; then log_error "未知服务: $service" return 1 fi log_step "构建 $service..." run_in_builder "cd $dir && rm -rf dist && pnpm run build" log_success "$service 构建完成" } # 构建所有后端服务 build_all_backend() { build_shared for service in conversation user payment knowledge evolution; do build_backend_service "$service" done } # 构建所有前端 build_all_frontend() { for service in web-client admin-client; do build_frontend "$service" done } # 构建所有 build_all() { log_info "开始构建所有服务 (使用 Docker 容器)..." build_builder_image install_deps build_all_backend build_all_frontend log_success "所有服务构建完成" } # 构建入口 do_build() { local target=${1:-all} # 确保 builder 镜像存在 build_builder_image # 如果不是构建全部,先安装依赖 if [ "$target" != "all" ]; then install_deps fi case $target in all) build_all ;; shared) build_shared ;; backend) build_shared build_all_backend ;; frontend) build_all_frontend ;; web-client|admin-client) build_frontend "$target" ;; conversation|user|payment|knowledge|evolution) build_shared build_backend_service "$target" ;; *) log_error "未知构建目标: $target" exit 1 ;; esac } #=============================================================================== # Docker 操作函数 #=============================================================================== # 构建 Docker 镜像 build_docker_images() { local service=${1:-} log_step "构建 Docker 镜像..." if [ -n "$service" ] && [ "$service" != "all" ]; then local docker_service="${DOCKER_SERVICES[$service]}" if [ -n "$docker_service" ]; then $DOCKER_COMPOSE build "$docker_service" else log_error "未知服务: $service" return 1 fi else $DOCKER_COMPOSE build fi log_success "Docker 镜像构建完成" } # 启动基础设施 start_infrastructure() { log_step "启动基础设施服务..." $DOCKER_COMPOSE up -d postgres redis neo4j # 等待数据库就绪 wait_for_service localhost 5432 "PostgreSQL" wait_for_service localhost 6379 "Redis" wait_for_service localhost 7474 "Neo4j" log_success "基础设施启动完成" } # 启动 Kong 网关 start_kong() { log_step "启动 Kong API 网关..." $DOCKER_COMPOSE up -d kong-database sleep 5 # Kong 数据库迁移 $DOCKER_COMPOSE run --rm kong kong migrations bootstrap || true $DOCKER_COMPOSE up -d kong wait_for_service localhost 8000 "Kong" log_success "Kong 启动完成" } # 启动后端服务 (非 Docker 模式 - 已弃用,统一使用 Docker) # 注意: 本函数已不再使用,保留仅作参考 start_backend_service_local() { log_warning "本地模式已弃用,将使用 Docker 模式启动服务" start_backend_service_docker "$1" } # 启动后端服务 (Docker 模式) start_backend_service_docker() { local service=$1 local docker_service="${DOCKER_SERVICES[$service]}" log_step "启动 $service (Docker)..." $DOCKER_COMPOSE up -d "$docker_service" local port="${SERVICE_PORTS[$service]}" wait_for_service localhost "$port" "$service" } # 启动所有后端服务 start_all_backend() { local mode=${1:-docker} for service in user payment knowledge conversation evolution; do if [ "$mode" = "docker" ]; then start_backend_service_docker "$service" else start_backend_service_local "$service" fi done } # 启动 Nginx (静态文件服务) start_nginx() { log_step "启动 iConsulting Nginx..." $DOCKER_COMPOSE up -d nginx wait_for_service localhost 8080 "Nginx" log_success "iConsulting Nginx 启动完成 (端口 8080)" # 自动配置系统nginx反向代理 setup_system_nginx_proxy } # 自动配置系统nginx反向代理 (傻瓜式) setup_system_nginx_proxy() { log_step "配置系统 Nginx 反向代理..." # 检查系统nginx是否存在 if ! command -v nginx &> /dev/null; then log_warning "系统未安装 nginx,跳过反向代理配置" log_info "您可以通过 http://服务器IP:8080 直接访问" return 0 fi # 检查nginx配置目录 local nginx_available="/etc/nginx/sites-available" local nginx_enabled="/etc/nginx/sites-enabled" local nginx_conf_d="/etc/nginx/conf.d" # 生成配置文件内容 local proxy_conf="# iConsulting 反向代理配置 (自动生成) # 生成时间: $(date) server { listen 80; listen [::]:80; server_name $DOMAIN; # Let's Encrypt 验证 location /.well-known/acme-challenge/ { root /var/www/html; } # 反向代理到 iConsulting Docker Nginx location / { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade \$http_upgrade; proxy_set_header Connection \"upgrade\"; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } } " # 尝试写入配置 (需要sudo权限) if [ -d "$nginx_available" ]; then # Debian/Ubuntu 风格 echo "$proxy_conf" | sudo tee "$nginx_available/iconsulting.conf" > /dev/null 2>&1 if [ $? -eq 0 ]; then sudo ln -sf "$nginx_available/iconsulting.conf" "$nginx_enabled/iconsulting.conf" 2>/dev/null log_success "配置已写入 $nginx_available/iconsulting.conf" else log_warning "无法写入nginx配置,请手动配置或使用sudo运行" return 1 fi elif [ -d "$nginx_conf_d" ]; then # CentOS/RHEL 风格 echo "$proxy_conf" | sudo tee "$nginx_conf_d/iconsulting.conf" > /dev/null 2>&1 if [ $? -eq 0 ]; then log_success "配置已写入 $nginx_conf_d/iconsulting.conf" else log_warning "无法写入nginx配置,请手动配置或使用sudo运行" return 1 fi else log_warning "未找到nginx配置目录,请手动配置" return 1 fi # 测试nginx配置 log_info "测试 nginx 配置..." if sudo nginx -t 2>/dev/null; then log_success "nginx 配置测试通过" # 重载nginx log_info "重载 nginx..." sudo systemctl reload nginx 2>/dev/null || sudo nginx -s reload 2>/dev/null log_success "系统 nginx 已重载" echo "" log_success "反向代理配置完成!" echo -e "${CYAN}现在可以通过以下地址访问:${NC}" echo " http://$DOMAIN" echo "" echo -e "${YELLOW}如需配置 HTTPS,请执行:${NC}" echo " sudo certbot --nginx -d $DOMAIN" else log_error "nginx 配置测试失败,请检查配置" return 1 fi } # 启动所有服务 start_all() { local mode=${1:-docker} log_info "开始启动所有服务 (模式: $mode)..." # 创建必要目录 mkdir -p "$PROJECT_ROOT/logs" mkdir -p "$PROJECT_ROOT/pids" start_infrastructure start_kong start_all_backend "$mode" start_nginx log_success "所有服务启动完成" do_status } # 启动入口 do_start() { local target=${1:-all} local mode=${2:-docker} load_env case $target in all) start_all "$mode" ;; infra|infrastructure) start_infrastructure ;; kong) start_kong ;; nginx) start_nginx ;; postgres|redis|neo4j) $DOCKER_COMPOSE up -d "$target" ;; conversation|user|payment|knowledge|evolution) if [ "$mode" = "docker" ]; then start_backend_service_docker "$target" else start_backend_service_local "$target" fi ;; backend) start_all_backend "$mode" ;; *) log_error "未知启动目标: $target" exit 1 ;; esac } #=============================================================================== # 停止函数 #=============================================================================== # 停止单个服务 (本地模式) stop_service_local() { local service=$1 log_step "停止 $service..." if command -v pm2 &> /dev/null; then pm2 stop "iconsulting-$service" 2>/dev/null || true pm2 delete "iconsulting-$service" 2>/dev/null || true else local pid_file="$PROJECT_ROOT/pids/$service.pid" if [ -f "$pid_file" ]; then kill $(cat "$pid_file") 2>/dev/null || true rm -f "$pid_file" fi fi log_success "$service 已停止" } # 停止单个服务 (Docker 模式) stop_service_docker() { local service=$1 local docker_service="${DOCKER_SERVICES[$service]}" if [ -n "$docker_service" ]; then log_step "停止 $service..." $DOCKER_COMPOSE stop "$docker_service" log_success "$service 已停止" fi } # 停止所有服务 stop_all() { local mode=${1:-docker} log_info "停止所有服务..." if [ "$mode" = "docker" ]; then $DOCKER_COMPOSE down else for service in conversation user payment knowledge evolution; do stop_service_local "$service" done $DOCKER_COMPOSE down fi log_success "所有服务已停止" } # 停止入口 do_stop() { local target=${1:-all} local mode=${2:-docker} case $target in all) stop_all "$mode" ;; infra|infrastructure) $DOCKER_COMPOSE stop postgres redis neo4j ;; conversation|user|payment|knowledge|evolution) if [ "$mode" = "docker" ]; then stop_service_docker "$target" else stop_service_local "$target" fi ;; kong|postgres|redis|neo4j|nginx) $DOCKER_COMPOSE stop "$target" ;; *) log_error "未知停止目标: $target" exit 1 ;; esac } #=============================================================================== # 重启函数 #=============================================================================== do_restart() { local target=${1:-all} local mode=${2:-docker} log_info "重启 $target..." do_stop "$target" "$mode" sleep 2 do_start "$target" "$mode" } #=============================================================================== # 状态查看 #=============================================================================== do_status() { echo "" echo -e "${CYAN}═══════════════════════════════════════════════════════════════${NC}" echo -e "${CYAN} iConsulting 服务状态 ${NC}" echo -e "${CYAN}═══════════════════════════════════════════════════════════════${NC}" echo "" # Docker 服务状态 echo -e "${PURPLE}Docker 容器状态:${NC}" $DOCKER_COMPOSE ps echo "" # 端口检查 echo -e "${PURPLE}服务端口检查:${NC}" printf "%-20s %-10s %-10s\n" "服务" "端口" "状态" echo "----------------------------------------" for service in "${!SERVICE_PORTS[@]}"; do local port="${SERVICE_PORTS[$service]}" if nc -z localhost "$port" 2>/dev/null; then printf "%-20s %-10s ${GREEN}%-10s${NC}\n" "$service" "$port" "运行中" else printf "%-20s %-10s ${RED}%-10s${NC}\n" "$service" "$port" "未运行" fi done echo "" # PM2 状态 (如果使用) if command -v pm2 &> /dev/null; then echo -e "${PURPLE}PM2 进程状态:${NC}" pm2 list 2>/dev/null | grep iconsulting || echo "无 PM2 管理的服务" echo "" fi } #=============================================================================== # 日志查看 #=============================================================================== do_logs() { local service=${1:-all} local lines=${2:-100} if [ "$service" = "all" ]; then $DOCKER_COMPOSE logs -f --tail="$lines" else local docker_service="${DOCKER_SERVICES[$service]}" if [ -n "$docker_service" ]; then $DOCKER_COMPOSE logs -f --tail="$lines" "$docker_service" else # 本地日志 local log_file="$PROJECT_ROOT/logs/$service.log" if [ -f "$log_file" ]; then tail -f -n "$lines" "$log_file" else log_error "日志文件不存在: $log_file" fi fi fi } #=============================================================================== # 清理函数 #=============================================================================== do_clean() { local target=${1:-build} case $target in build) log_step "清理构建产物..." for dir in "${SERVICE_DIRS[@]}"; do rm -rf "$PROJECT_ROOT/$dir/dist" done log_success "构建产物已清理" ;; deps) log_step "清理依赖..." rm -rf node_modules for dir in "${SERVICE_DIRS[@]}"; do rm -rf "$PROJECT_ROOT/$dir/node_modules" done log_success "依赖已清理" ;; docker) log_step "清理 Docker 资源..." $DOCKER_COMPOSE down -v --rmi local docker system prune -f log_success "Docker 资源已清理" ;; logs) log_step "清理日志..." rm -rf "$PROJECT_ROOT/logs/*" log_success "日志已清理" ;; all) do_clean build do_clean deps do_clean docker do_clean logs ;; *) log_error "未知清理目标: $target (可选: build, deps, docker, logs, all)" exit 1 ;; esac } #=============================================================================== # 完整部署 #=============================================================================== do_deploy() { local mode=${1:-docker} log_info "开始完整部署 (模式: $mode)..." check_environment # 构建 do_build all # 如果是 Docker 模式,构建镜像 if [ "$mode" = "docker" ]; then build_docker_images fi # 启动 do_start all "$mode" # 配置 Kong 路由 setup_kong_routes log_success "部署完成!" echo "" echo -e "${CYAN}访问地址:${NC}" echo " 用户前端: https://$DOMAIN" echo " 管理后台: https://$DOMAIN/admin" echo " API 网关: https://$DOMAIN/api" echo " Kong 管理: http://localhost:8001 (仅本地)" echo "" echo -e "${YELLOW}提示: 如需配置 SSL 证书,请执行:${NC}" echo " ./deploy.sh ssl obtain" echo "" } # 完整部署 (含 SSL) do_deploy_full() { log_info "开始完整部署 (含 SSL 证书)..." # 基础部署 do_deploy docker # 安装并获取 SSL 证书 do_ssl obtain # 配置自动续期 do_ssl auto-renew log_success "完整部署完成 (含 SSL)!" echo "" echo -e "${CYAN}访问地址:${NC}" echo " 用户前端: https://$DOMAIN" echo " 管理后台: https://$DOMAIN/admin" echo "" } #=============================================================================== # SSL 证书管理 (Let's Encrypt) #=============================================================================== # 安装 Certbot install_certbot() { log_step "检查/安装 Certbot..." if command -v certbot &> /dev/null; then log_success "Certbot 已安装" return 0 fi # 检测操作系统 if [ -f /etc/debian_version ]; then # Debian/Ubuntu apt-get update apt-get install -y certbot python3-certbot-nginx elif [ -f /etc/redhat-release ]; then # CentOS/RHEL yum install -y epel-release yum install -y certbot python3-certbot-nginx elif [ -f /etc/alpine-release ]; then # Alpine apk add certbot certbot-nginx else log_error "无法识别的操作系统,请手动安装 certbot" exit 1 fi log_success "Certbot 安装完成" } # 申请 SSL 证书 obtain_ssl_cert() { local domain=${1:-$DOMAIN} local email=${2:-$ADMIN_EMAIL} log_step "申请 SSL 证书: $domain..." # 创建证书目录 mkdir -p "$PROJECT_ROOT/nginx/ssl" # 停止 Nginx (释放 80 端口) $DOCKER_COMPOSE stop nginx 2>/dev/null || true # 使用 standalone 模式申请证书 certbot certonly \ --standalone \ --non-interactive \ --agree-tos \ --email "$email" \ -d "$domain" \ --cert-path "$PROJECT_ROOT/nginx/ssl/cert.pem" \ --key-path "$PROJECT_ROOT/nginx/ssl/privkey.pem" \ --fullchain-path "$PROJECT_ROOT/nginx/ssl/fullchain.pem" # 复制证书到项目目录 if [ -d "/etc/letsencrypt/live/$domain" ]; then cp "/etc/letsencrypt/live/$domain/fullchain.pem" "$PROJECT_ROOT/nginx/ssl/" cp "/etc/letsencrypt/live/$domain/privkey.pem" "$PROJECT_ROOT/nginx/ssl/" chmod 644 "$PROJECT_ROOT/nginx/ssl/fullchain.pem" chmod 600 "$PROJECT_ROOT/nginx/ssl/privkey.pem" log_success "SSL 证书已获取并复制到 nginx/ssl/" else log_error "证书申请失败" return 1 fi # 重启 Nginx $DOCKER_COMPOSE up -d nginx log_success "SSL 证书配置完成" } # 续期 SSL 证书 renew_ssl_cert() { log_step "续期 SSL 证书..." certbot renew --quiet # 复制新证书 if [ -d "/etc/letsencrypt/live/$DOMAIN" ]; then cp "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" "$PROJECT_ROOT/nginx/ssl/" cp "/etc/letsencrypt/live/$DOMAIN/privkey.pem" "$PROJECT_ROOT/nginx/ssl/" # 重载 Nginx $DOCKER_COMPOSE exec nginx nginx -s reload log_success "SSL 证书续期完成" fi } # 配置证书自动续期 setup_ssl_auto_renew() { log_step "配置证书自动续期..." # 创建续期脚本 cat > /etc/cron.d/certbot-renew << EOF # 每天凌晨 3 点检查证书续期 0 3 * * * root certbot renew --quiet && cp /etc/letsencrypt/live/$DOMAIN/*.pem $PROJECT_ROOT/nginx/ssl/ && docker-compose -f $PROJECT_ROOT/docker-compose.yml exec -T nginx nginx -s reload EOF log_success "自动续期已配置 (每天 3:00 检查)" } # SSL 操作入口 do_ssl() { local action=${1:-status} case $action in install) install_certbot ;; obtain|get) install_certbot obtain_ssl_cert "$2" "$3" ;; renew) renew_ssl_cert ;; auto-renew) setup_ssl_auto_renew ;; status) echo -e "${PURPLE}SSL 证书状态:${NC}" if [ -f "$PROJECT_ROOT/nginx/ssl/fullchain.pem" ]; then echo "证书文件: 存在" openssl x509 -in "$PROJECT_ROOT/nginx/ssl/fullchain.pem" -noout -dates 2>/dev/null || echo "无法读取证书信息" else echo "证书文件: 不存在" fi if command -v certbot &> /dev/null; then echo "" echo "Certbot 证书列表:" certbot certificates 2>/dev/null || echo "无证书" fi ;; *) log_error "未知 SSL 操作: $action (可选: install, obtain, renew, auto-renew, status)" exit 1 ;; esac } #=============================================================================== # Kong API Gateway 配置 #=============================================================================== # 配置 Kong 路由 setup_kong_routes() { local kong_admin="${KONG_ADMIN_URL:-http://localhost:8001}" log_step "配置 Kong API Gateway 路由..." # 等待 Kong 就绪 local max_attempts=30 local attempt=1 while [ $attempt -le $max_attempts ]; do if curl -s "$kong_admin" > /dev/null 2>&1; then break fi echo -n "." sleep 2 attempt=$((attempt + 1)) done if [ $attempt -gt $max_attempts ]; then log_error "Kong Admin API 不可用" return 1 fi echo "" log_info "Kong Admin API 就绪" # 创建服务和路由 log_info "创建微服务..." # User Service curl -s -X POST "$kong_admin/services" \ -d "name=user-service" \ -d "url=http://user-service:3001" > /dev/null 2>&1 || true curl -s -X POST "$kong_admin/services/user-service/routes" \ -d "name=user-route" \ -d "paths[]=/v1/users" \ -d "paths[]=/v1/auth" \ -d "strip_path=false" > /dev/null 2>&1 || true # Payment Service curl -s -X POST "$kong_admin/services" \ -d "name=payment-service" \ -d "url=http://payment-service:3002" > /dev/null 2>&1 || true curl -s -X POST "$kong_admin/services/payment-service/routes" \ -d "name=payment-route" \ -d "paths[]=/v1/payments" \ -d "paths[]=/v1/balance" \ -d "strip_path=false" > /dev/null 2>&1 || true # Knowledge Service curl -s -X POST "$kong_admin/services" \ -d "name=knowledge-service" \ -d "url=http://knowledge-service:3003" > /dev/null 2>&1 || true curl -s -X POST "$kong_admin/services/knowledge-service/routes" \ -d "name=knowledge-route" \ -d "paths[]=/v1/knowledge" \ -d "strip_path=false" > /dev/null 2>&1 || true # Conversation Service curl -s -X POST "$kong_admin/services" \ -d "name=conversation-service" \ -d "url=http://conversation-service:3004" > /dev/null 2>&1 || true curl -s -X POST "$kong_admin/services/conversation-service/routes" \ -d "name=conversation-route" \ -d "paths[]=/v1/conversations" \ -d "strip_path=false" > /dev/null 2>&1 || true # Evolution Service (Admin) curl -s -X POST "$kong_admin/services" \ -d "name=evolution-service" \ -d "url=http://evolution-service:3005" > /dev/null 2>&1 || true curl -s -X POST "$kong_admin/services/evolution-service/routes" \ -d "name=evolution-route" \ -d "paths[]=/v1/evolution" \ -d "paths[]=/v1/memory" \ -d "paths[]=/v1/admin" \ -d "strip_path=false" > /dev/null 2>&1 || true log_info "配置全局插件..." # Rate Limiting curl -s -X POST "$kong_admin/plugins" \ -d "name=rate-limiting" \ -d "config.minute=100" \ -d "config.policy=local" > /dev/null 2>&1 || true # CORS curl -s -X POST "$kong_admin/plugins" \ -d "name=cors" \ -d "config.origins=https://$DOMAIN,http://localhost" \ -d "config.methods=GET,POST,PUT,DELETE,OPTIONS,PATCH" \ -d "config.headers=Accept,Authorization,Content-Type,X-User-Id,X-Request-Id" \ -d "config.credentials=true" \ -d "config.max_age=3600" > /dev/null 2>&1 || true # Request ID curl -s -X POST "$kong_admin/plugins" \ -d "name=correlation-id" \ -d "config.header_name=X-Request-Id" \ -d "config.generator=uuid" > /dev/null 2>&1 || true log_success "Kong 路由配置完成" # 显示配置结果 echo "" echo -e "${PURPLE}已配置的服务:${NC}" curl -s "$kong_admin/services" | grep -o '"name":"[^"]*"' | sed 's/"name":"//g;s/"//g' | while read name; do echo " - $name" done echo "" echo -e "${PURPLE}已配置的路由:${NC}" curl -s "$kong_admin/routes" | grep -o '"name":"[^"]*"' | sed 's/"name":"//g;s/"//g' | while read name; do echo " - $name" done } # Kong 操作入口 do_kong() { local action=${1:-status} case $action in setup|init) setup_kong_routes ;; status) local kong_admin="${KONG_ADMIN_URL:-http://localhost:8001}" echo -e "${PURPLE}Kong 状态:${NC}" curl -s "$kong_admin" > /dev/null 2>&1 && echo "Kong Admin API: 运行中" || echo "Kong Admin API: 未运行" curl -s "http://localhost:8000" > /dev/null 2>&1 && echo "Kong Proxy: 运行中" || echo "Kong Proxy: 未运行" echo "" echo "服务数量: $(curl -s "$kong_admin/services" 2>/dev/null | grep -o '"total":[0-9]*' | cut -d: -f2 || echo 0)" echo "路由数量: $(curl -s "$kong_admin/routes" 2>/dev/null | grep -o '"total":[0-9]*' | cut -d: -f2 || echo 0)" ;; services) curl -s "http://localhost:8001/services" | python3 -m json.tool 2>/dev/null || curl -s "http://localhost:8001/services" ;; routes) curl -s "http://localhost:8001/routes" | python3 -m json.tool 2>/dev/null || curl -s "http://localhost:8001/routes" ;; plugins) curl -s "http://localhost:8001/plugins" | python3 -m json.tool 2>/dev/null || curl -s "http://localhost:8001/plugins" ;; *) log_error "未知 Kong 操作: $action (可选: setup, status, services, routes, plugins)" exit 1 ;; esac } #=============================================================================== # 数据库操作 #=============================================================================== do_db() { local action=${1:-status} case $action in migrate) log_step "执行数据库迁移 (在 Docker 容器中)..." build_builder_image # 在容器中执行迁移命令 for service in user payment knowledge conversation evolution; do local dir="${SERVICE_DIRS[$service]}" run_in_builder "cd $dir && pnpm run migration:run 2>/dev/null" || log_warning "$service 无迁移或迁移失败" done log_success "数据库迁移完成" ;; seed) log_step "初始化种子数据..." # 添加种子数据脚本 log_success "种子数据初始化完成" ;; backup) local backup_dir="$PROJECT_ROOT/backups/$(date +%Y%m%d_%H%M%S)" mkdir -p "$backup_dir" log_step "备份数据库..." $DOCKER_COMPOSE exec -T postgres pg_dump -U postgres iconsulting > "$backup_dir/postgres.sql" log_success "数据库备份到: $backup_dir" ;; restore) local backup_file=$2 if [ -z "$backup_file" ]; then log_error "请指定备份文件: ./deploy.sh db restore " exit 1 fi log_step "恢复数据库..." $DOCKER_COMPOSE exec -T postgres psql -U postgres iconsulting < "$backup_file" log_success "数据库恢复完成" ;; reset) log_warning "这将删除所有数据!" read -p "确认继续? (y/N) " confirm if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then $DOCKER_COMPOSE down -v $DOCKER_COMPOSE up -d postgres redis neo4j wait_for_service localhost 5432 "PostgreSQL" do_db migrate log_success "数据库已重置" fi ;; status) echo -e "${PURPLE}数据库状态:${NC}" $DOCKER_COMPOSE exec postgres psql -U postgres -c "SELECT version();" 2>/dev/null || echo "PostgreSQL 未运行" $DOCKER_COMPOSE exec redis redis-cli ping 2>/dev/null || echo "Redis 未运行" curl -s http://localhost:7474 > /dev/null && echo "Neo4j 运行中" || echo "Neo4j 未运行" ;; *) log_error "未知数据库操作: $action (可选: migrate, seed, backup, restore, reset, status)" exit 1 ;; esac } #=============================================================================== # 帮助信息 #=============================================================================== show_help() { cat << EOF ╔═══════════════════════════════════════════════════════════════════════════════╗ ║ iConsulting 部署管理脚本 ║ ║ 域名: $DOMAIN ║ ╚═══════════════════════════════════════════════════════════════════════════════╝ 用法: ./deploy.sh [target] [options] 命令: build [target] 编译构建 target: all, shared, backend, frontend, conversation, user, payment, knowledge, evolution, web-client, admin-client start [target] [mode] 启动服务 target: all, infra, kong, nginx, backend, conversation, user, payment, knowledge, evolution, postgres, redis, neo4j mode: docker (默认), local stop [target] [mode] 停止服务 (target 同上) restart [target] [mode] 重启服务 (target 同上) status 查看所有服务状态 logs [service] [lines] 查看日志 service: 服务名或 all lines: 显示行数 (默认 100) clean [target] 清理 target: build, deps, docker, logs, all deploy [mode] 完整部署 (构建 + 启动) mode: docker (默认), local deploy-full 完整部署 (含 SSL 证书自动申请) ssl SSL 证书管理 (Let's Encrypt) action: install - 安装 certbot obtain - 申请证书 renew - 续期证书 auto-renew - 配置自动续期 status - 查看证书状态 kong Kong API Gateway 管理 action: setup - 配置路由和插件 status - 查看状态 services - 查看服务列表 routes - 查看路由列表 plugins - 查看插件列表 db 数据库操作 action: migrate, seed, backup, restore, reset, status help 显示此帮助信息 示例: ./deploy.sh deploy # 完整部署 ./deploy.sh deploy-full # 完整部署 (含 SSL) ./deploy.sh ssl obtain # 申请 SSL 证书 ./deploy.sh kong setup # 配置 Kong 路由 ./deploy.sh build conversation # 只构建对话服务 ./deploy.sh start backend local # 本地模式启动所有后端 ./deploy.sh restart user docker # 重启用户服务 (Docker) ./deploy.sh logs conversation 200 # 查看对话服务最近200行日志 ./deploy.sh clean all # 清理所有构建产物和依赖 ./deploy.sh db backup # 备份数据库 ./deploy.sh db migrate # 执行数据库迁移 环境变量: DOMAIN 域名 (默认: iconsulting.szaiai.com) ADMIN_EMAIL 管理员邮箱 (用于 SSL 证书) 系统要求: - Docker (必须) - Docker Compose 或 docker compose (必须) - 无需安装 Node.js / pnpm (构建在 Docker 容器中进行) EOF } #=============================================================================== # 主入口 #=============================================================================== main() { local command=${1:-help} shift || true # 对于 help 命令不需要检查环境 if [ "$command" != "help" ] && [ "$command" != "--help" ] && [ "$command" != "-h" ]; then check_environment fi case $command in build) do_build "$@" ;; start) do_start "$@" ;; stop) do_stop "$@" ;; restart) do_restart "$@" ;; status) do_status ;; logs) do_logs "$@" ;; clean) do_clean "$@" ;; deploy) do_deploy "$@" ;; deploy-full) do_deploy_full ;; ssl) do_ssl "$@" ;; kong) do_kong "$@" ;; db) do_db "$@" ;; help|--help|-h) show_help ;; *) log_error "未知命令: $command" show_help exit 1 ;; esac } # 执行主函数 main "$@"