iconsulting/deploy.sh

904 lines
24 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
#===============================================================================
# iConsulting 部署管理脚本
#
# 用法: ./deploy.sh <command> [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 <backup_file>"
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 <command> [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> 数据库操作
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 "$@"