#!/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" # 服务端口配置 declare -A SERVICE_PORTS=( ["conversation"]=3004 ["user"]=3001 ["payment"]=3002 ["knowledge"]=3003 ["evolution"]=3005 ["kong"]=8000 ["postgres"]=5432 ["redis"]=6379 ["neo4j"]=7474 ["nginx"]=80 ) # 服务目录映射 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 } # 检查环境 check_environment() { log_step "检查运行环境..." check_command "node" check_command "pnpm" check_command "docker" check_command "docker-compose" # 检查 Node 版本 NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1) if [ "$NODE_VERSION" -lt 18 ]; then log_error "Node.js 版本需要 >= 18,当前版本: $(node -v)" exit 1 fi log_success "环境检查通过" } # 加载环境变量 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 } #=============================================================================== # 构建函数 #=============================================================================== # 安装依赖 install_deps() { log_step "安装项目依赖..." pnpm install log_success "依赖安装完成" } # 构建共享包 build_shared() { log_step "构建 shared 包..." cd "$PROJECT_ROOT/${SERVICE_DIRS[shared]}" pnpm run build cd "$PROJECT_ROOT" 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..." cd "$PROJECT_ROOT/$dir" # 清理旧构建 rm -rf dist # TypeScript 编译 pnpm run build cd "$PROJECT_ROOT" 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..." cd "$PROJECT_ROOT/$dir" # 清理旧构建 rm -rf dist # Vite 构建 pnpm run build cd "$PROJECT_ROOT" 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 "开始构建所有服务..." install_deps build_all_backend build_all_frontend log_success "所有服务构建完成" } # 构建入口 do_build() { local target=${1:-all} case $target in all) build_all ;; shared) build_shared ;; backend) build_all_backend ;; frontend) build_all_frontend ;; web-client|admin-client) build_frontend "$target" ;; conversation|user|payment|knowledge|evolution) 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 模式) start_backend_service_local() { local service=$1 local dir="${SERVICE_DIRS[$service]}" local port="${SERVICE_PORTS[$service]}" if [ -z "$dir" ]; then log_error "未知服务: $service" return 1 fi log_step "启动 $service (端口: $port)..." cd "$PROJECT_ROOT/$dir" # 检查是否已构建 if [ ! -d "dist" ]; then log_warning "$service 未构建,先进行构建..." pnpm run build fi # 使用 PM2 或直接启动 if command -v pm2 &> /dev/null; then pm2 start dist/main.js --name "iconsulting-$service" --cwd "$PROJECT_ROOT/$dir" else # 后台启动 nohup node dist/main.js > "$PROJECT_ROOT/logs/$service.log" 2>&1 & echo $! > "$PROJECT_ROOT/pids/$service.pid" fi cd "$PROJECT_ROOT" sleep 2 wait_for_service localhost "$port" "$service" 15 } # 启动后端服务 (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 "启动 Nginx..." docker-compose up -d nginx wait_for_service localhost 80 "Nginx" log_success "Nginx 启动完成" } # 启动所有服务 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" log_success "部署完成!" echo "" echo -e "${CYAN}访问地址:${NC}" echo " 用户前端: http://localhost" echo " 管理后台: http://localhost/admin" echo " API 网关: http://localhost:8000" echo " Kong 管理: http://localhost:8001" echo "" } #=============================================================================== # 数据库操作 #=============================================================================== do_db() { local action=${1:-status} case $action in migrate) log_step "执行数据库迁移..." # 可以添加 TypeORM 迁移命令 for service in user payment knowledge conversation evolution; do local dir="${SERVICE_DIRS[$service]}" cd "$PROJECT_ROOT/$dir" pnpm run migration:run 2>/dev/null || log_warning "$service 无迁移或迁移失败" cd "$PROJECT_ROOT" 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 部署管理脚本 ║ ╚═══════════════════════════════════════════════════════════════════════════════╝ 用法: ./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 db 数据库操作 action: migrate, seed, backup, restore, reset, status help 显示此帮助信息 示例: ./deploy.sh deploy # 完整部署 ./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 # 执行数据库迁移 EOF } #=============================================================================== # 主入口 #=============================================================================== main() { local command=${1:-help} shift || true 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 "$@" ;; db) do_db "$@" ;; help|--help|-h) show_help ;; *) log_error "未知命令: $command" show_help exit 1 ;; esac } # 执行主函数 main "$@"