#!/bin/bash # 主构建脚本 - 若依框架Docker部署方案 # 实现一键构建所有镜像、代码拉取更新和环境配置切换功能 # Requirements: 5.1, 5.2, 5.3 set -e # =========================================== # 脚本配置 # =========================================== SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" DOCKER_DIR="$SCRIPT_DIR" # 默认配置 DEFAULT_ENVIRONMENT="development" DEFAULT_TAG="latest" BUILD_ALL=false PULL_CODE=false NO_CACHE=false CLEAN_BUILD=false VERBOSE=false # 镜像配置 FRONTEND_IMAGE="anxin-frontend" BACKEND_IMAGE="anxin-backend" MYSQL_IMAGE="mysql:8.0.36" # =========================================== # 颜色定义 # =========================================== 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 # =========================================== # 日志函数 # =========================================== log_info() { echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" } log_error() { echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" } log_debug() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${PURPLE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" fi } log_step() { echo -e "${CYAN}[STEP]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" } # =========================================== # 帮助信息 # =========================================== show_help() { cat << EOF 若依框架Docker部署 - 主构建脚本 用法: $0 [选项] [命令] 命令: build 构建指定组件的Docker镜像 build-all 构建所有Docker镜像 (默认) pull 拉取最新代码 deploy 构建并部署到指定环境 clean 清理构建缓存和临时文件 status 查看当前构建状态 help 显示此帮助信息 选项: -e, --env ENV 指定环境 (development|staging|production) [默认: development] -t, --tag TAG 指定镜像标签 [默认: latest] -c, --component COMP 指定构建组件 (frontend|backend|all) [默认: all] --pull 构建前拉取最新代码 --no-cache 不使用Docker构建缓存 --clean 构建前清理缓存 --verbose 显示详细日志 -h, --help 显示此帮助信息 环境配置: development (dev) 开发环境 - 启用调试模式,使用开发配置 staging (stage) 测试环境 - 接近生产的测试配置 production (prod) 生产环境 - 优化的生产配置 示例: $0 # 构建所有镜像 (开发环境) $0 build-all -e production # 构建所有镜像 (生产环境) $0 build -c frontend -t v1.0.0 # 构建前端镜像并标记为v1.0.0 $0 deploy -e staging --pull # 拉取代码并部署到测试环境 $0 pull # 仅拉取最新代码 $0 clean # 清理构建缓存 Requirements Coverage: 5.1 - 一键构建所有镜像功能 5.2 - 代码拉取和更新功能 5.3 - 环境配置切换功能 EOF } # =========================================== # 参数解析 # =========================================== parse_args() { while [[ $# -gt 0 ]]; do case $1 in build) COMMAND="build" shift ;; build-all) COMMAND="build-all" BUILD_ALL=true shift ;; pull) COMMAND="pull" shift ;; deploy) COMMAND="deploy" BUILD_ALL=true shift ;; clean) COMMAND="clean" shift ;; status) COMMAND="status" shift ;; -e|--env) ENVIRONMENT="$2" shift 2 ;; -t|--tag) TAG="$2" shift 2 ;; -c|--component) COMPONENT="$2" shift 2 ;; --pull) PULL_CODE=true shift ;; --no-cache) NO_CACHE=true shift ;; --clean) CLEAN_BUILD=true shift ;; --verbose) VERBOSE=true shift ;; -h|--help) show_help exit 0 ;; *) log_error "未知参数: $1" show_help exit 1 ;; esac done # 设置默认值 COMMAND=${COMMAND:-"build-all"} ENVIRONMENT=${ENVIRONMENT:-$DEFAULT_ENVIRONMENT} TAG=${TAG:-$DEFAULT_TAG} COMPONENT=${COMPONENT:-"all"} # 如果没有指定组件但命令是build,则构建所有组件 if [[ "$COMMAND" == "build" && "$COMPONENT" == "all" ]]; then BUILD_ALL=true fi } # =========================================== # 环境验证和配置 # =========================================== validate_environment() { case $ENVIRONMENT in development|dev) ENVIRONMENT="development" ;; staging|stage) ENVIRONMENT="staging" ;; production|prod) ENVIRONMENT="production" ;; *) log_error "无效的环境: $ENVIRONMENT" log_info "支持的环境: development, staging, production" exit 1 ;; esac log_debug "环境验证通过: $ENVIRONMENT" } # 加载环境配置 load_environment_config() { local env_file="${DOCKER_DIR}/environments/.env.${ENVIRONMENT}" if [[ ! -f "$env_file" ]]; then log_error "环境配置文件不存在: $env_file" exit 1 fi log_info "加载环境配置: $ENVIRONMENT" source "$env_file" # 导出关键环境变量 export ENVIRONMENT export COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-"anxin-${ENVIRONMENT}"} export SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE:-"docker"} log_debug "环境配置加载完成" log_debug "项目名称: $COMPOSE_PROJECT_NAME" log_debug "Spring配置: $SPRING_PROFILES_ACTIVE" } # =========================================== # 系统检查 # =========================================== check_prerequisites() { log_step "检查系统依赖..." # 检查Docker if ! command -v docker &> /dev/null; then log_error "Docker未安装或不在PATH中" exit 1 fi # 检查Docker Compose if ! command -v docker-compose &> /dev/null; then log_error "Docker Compose未安装或不在PATH中" exit 1 fi # 检查Docker守护进程 if ! docker info &> /dev/null; then log_error "Docker守护进程未运行" exit 1 fi # 检查Git (如果需要拉取代码) if [[ "$PULL_CODE" == "true" ]] && ! command -v git &> /dev/null; then log_error "Git未安装或不在PATH中,无法拉取代码" exit 1 fi log_success "系统依赖检查通过" } # =========================================== # 代码拉取和更新功能 (Requirement 5.2) # =========================================== pull_latest_code() { log_step "拉取最新代码..." cd "$PROJECT_ROOT" # 检查是否为Git仓库 if [[ ! -d ".git" ]]; then log_warn "当前目录不是Git仓库,跳过代码拉取" return 0 fi # 获取当前分支 local current_branch=$(git branch --show-current) log_info "当前分支: $current_branch" # 检查是否有未提交的更改 if ! git diff-index --quiet HEAD --; then log_warn "检测到未提交的更改" read -p "是否继续拉取代码? (y/N): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then log_info "取消代码拉取" return 1 fi fi # 拉取最新代码 log_info "从远程仓库拉取最新代码..." if git pull origin "$current_branch"; then log_success "代码拉取成功" # 检查是否有子模块 if [[ -f ".gitmodules" ]]; then log_info "更新Git子模块..." git submodule update --init --recursive fi # 显示最新提交信息 log_info "最新提交信息:" git log --oneline -5 else log_error "代码拉取失败" return 1 fi } # =========================================== # 构建准备 # =========================================== prepare_build() { log_step "准备构建环境..." # 从环境变量获取路径,如果没有则使用默认值 local mysql_data_path=${MYSQL_DATA_PATH:-"./data/${ENVIRONMENT}/mysql"} local mysql_log_path=${MYSQL_LOG_PATH:-"./data/${ENVIRONMENT}/mysql-logs"} local backend_log_path=${BACKEND_LOG_PATH:-"./data/${ENVIRONMENT}/backend-logs"} local backend_upload_path=${BACKEND_UPLOAD_PATH:-"./data/${ENVIRONMENT}/uploads"} local frontend_log_path=${FRONTEND_LOG_PATH:-"./data/${ENVIRONMENT}/nginx-logs"} local data_dirs=( "$mysql_data_path" "$mysql_log_path" "$backend_log_path" "$backend_upload_path" "$frontend_log_path" ) # 创建必要的目录 for dir in "${data_dirs[@]}"; do # 转换相对路径为绝对路径 local abs_dir="${DOCKER_DIR}/${dir#./}" if [[ ! -d "$abs_dir" ]]; then log_debug "创建目录: $abs_dir" mkdir -p "$abs_dir" fi done # 设置目录权限 local base_data_dir="${DOCKER_DIR}/data/${ENVIRONMENT}" if [[ -d "$base_data_dir" ]]; then chmod -R 755 "$base_data_dir" fi # 清理构建缓存 (如果指定) if [[ "$CLEAN_BUILD" == "true" ]]; then clean_build_cache fi log_success "构建环境准备完成" } # 清理构建缓存 clean_build_cache() { log_info "清理构建缓存..." # 清理Docker构建缓存 log_debug "清理Docker构建缓存..." docker builder prune -f # 清理Maven缓存 (如果存在) if command -v mvn &> /dev/null; then log_debug "清理Maven缓存..." cd "$PROJECT_ROOT" mvn clean -q fi # 清理npm缓存 (如果存在) if command -v npm &> /dev/null; then log_debug "清理npm缓存..." cd "${PROJECT_ROOT}/RuoYi-Vue3" if [[ -d "node_modules" ]]; then rm -rf node_modules fi if [[ -f "package-lock.json" ]]; then rm -f package-lock.json fi fi log_success "构建缓存清理完成" } # =========================================== # 镜像构建功能 (Requirement 5.1) # =========================================== build_frontend_image() { log_step "构建前端镜像..." # 根据环境设置镜像标签 local env_tag case $ENVIRONMENT in production) env_tag="prod" ;; staging) env_tag="staging" ;; development) env_tag="dev" ;; esac local image_name="${FRONTEND_IMAGE}:${env_tag}" local dockerfile_path="${DOCKER_DIR}/frontend/Dockerfile" local build_args="" # 根据环境设置构建参数 case $ENVIRONMENT in production) build_args="--build-arg NODE_ENV=production --build-arg API_BASE_URL=${API_BASE_URL:-http://localhost:8080} --build-arg ENVIRONMENT=prod" ;; staging) build_args="--build-arg NODE_ENV=production --build-arg API_BASE_URL=${API_BASE_URL:-http://localhost:8080} --build-arg ENVIRONMENT=staging" ;; development) build_args="--build-arg NODE_ENV=development --build-arg API_BASE_URL=${API_BASE_URL:-http://localhost:8080} --build-arg ENVIRONMENT=dev" ;; esac # 构建命令 local build_cmd="docker build -f $dockerfile_path -t $image_name $build_args" if [[ "$NO_CACHE" == "true" ]]; then build_cmd="$build_cmd --no-cache" fi build_cmd="$build_cmd $PROJECT_ROOT" log_info "构建前端镜像: $image_name" log_debug "构建命令: $build_cmd" if eval "$build_cmd"; then # 如果指定了自定义标签,也打上自定义标签 if [[ "$TAG" != "latest" && "$TAG" != "$env_tag" ]]; then local custom_image_name="${FRONTEND_IMAGE}:${TAG}" log_info "添加自定义标签: $custom_image_name" docker tag "$image_name" "$custom_image_name" fi log_success "前端镜像构建成功: $image_name" # 显示镜像信息 local image_size=$(docker images --format "table {{.Size}}" "$image_name" | tail -n 1) log_info "镜像大小: $image_size" return 0 else log_error "前端镜像构建失败" return 1 fi } build_backend_image() { log_step "构建后端镜像..." # 根据环境设置镜像标签 local env_tag case $ENVIRONMENT in production) env_tag="prod" ;; staging) env_tag="staging" ;; development) env_tag="dev" ;; esac local image_name="${BACKEND_IMAGE}:${env_tag}" local dockerfile_path="${DOCKER_DIR}/backend/Dockerfile" local build_args="" # 根据环境设置构建参数 case $ENVIRONMENT in production) build_args="--build-arg SPRING_PROFILES_ACTIVE=docker,prod" ;; staging) build_args="--build-arg SPRING_PROFILES_ACTIVE=docker,staging" ;; development) build_args="--build-arg SPRING_PROFILES_ACTIVE=docker,dev" ;; esac # 构建命令 local build_cmd="docker build -f $dockerfile_path -t $image_name $build_args" if [[ "$NO_CACHE" == "true" ]]; then build_cmd="$build_cmd --no-cache" fi build_cmd="$build_cmd $PROJECT_ROOT" log_info "构建后端镜像: $image_name" log_debug "构建命令: $build_cmd" if eval "$build_cmd"; then # 如果指定了自定义标签,也打上自定义标签 if [[ "$TAG" != "latest" && "$TAG" != "$env_tag" ]]; then local custom_image_name="${BACKEND_IMAGE}:${TAG}" log_info "添加自定义标签: $custom_image_name" docker tag "$image_name" "$custom_image_name" fi log_success "后端镜像构建成功: $image_name" # 显示镜像信息 local image_size=$(docker images --format "table {{.Size}}" "$image_name" | tail -n 1) log_info "镜像大小: $image_size" return 0 else log_error "后端镜像构建失败" return 1 fi } # 构建所有镜像 build_all_images() { log_step "开始构建所有镜像..." local build_start_time=$(date +%s) local failed_builds=() # 构建前端镜像 if ! build_frontend_image; then failed_builds+=("frontend") fi # 构建后端镜像 if ! build_backend_image; then failed_builds+=("backend") fi # 拉取MySQL镜像 log_info "拉取MySQL镜像: $MYSQL_IMAGE" if docker pull "$MYSQL_IMAGE"; then log_success "MySQL镜像拉取成功" else log_warn "MySQL镜像拉取失败,将使用本地镜像" fi local build_end_time=$(date +%s) local build_duration=$((build_end_time - build_start_time)) # 构建结果汇总 if [[ ${#failed_builds[@]} -eq 0 ]]; then log_success "所有镜像构建成功! 耗时: ${build_duration}秒" show_build_summary return 0 else log_error "以下镜像构建失败: ${failed_builds[*]}" return 1 fi } # 显示构建摘要 show_build_summary() { log_info "构建摘要:" echo "----------------------------------------" docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedAt}}\t{{.Size}}" | grep -E "(anxin-|REPOSITORY)" echo "----------------------------------------" } # =========================================== # 部署功能 # =========================================== deploy_to_environment() { log_step "部署到 $ENVIRONMENT 环境..." local compose_file="${DOCKER_DIR}/docker-compose.${ENVIRONMENT}.yml" local env_file="${DOCKER_DIR}/environments/.env.${ENVIRONMENT}" # 检查文件是否存在 if [[ ! -f "$compose_file" ]]; then log_error "Docker Compose文件不存在: $compose_file" return 1 fi if [[ ! -f "$env_file" ]]; then log_error "环境配置文件不存在: $env_file" return 1 fi # 停止现有服务 log_info "停止现有服务..." cd "$DOCKER_DIR" docker-compose -f "$compose_file" --env-file "$env_file" down --remove-orphans || true # 启动新服务 log_info "启动服务..." if docker-compose -f "$compose_file" --env-file "$env_file" up -d; then log_success "服务启动成功" # 等待服务就绪 log_info "等待服务就绪..." sleep 15 # 检查服务状态 check_deployment_status "$compose_file" "$env_file" return 0 else log_error "服务启动失败" return 1 fi } # 检查部署状态 check_deployment_status() { local compose_file="$1" local env_file="$2" log_info "检查服务状态..." cd "$DOCKER_DIR" docker-compose -f "$compose_file" --env-file "$env_file" ps # 健康检查 local services=("anxin-mysql" "anxin-backend" "anxin-frontend") local healthy_count=0 for service in "${services[@]}"; do local container_name="${service}-${ENVIRONMENT}" if docker ps --format "{{.Names}}" | grep -q "$service"; then local health=$(docker inspect --format='{{.State.Health.Status}}' "$service" 2>/dev/null || echo "no-healthcheck") case $health in "healthy"|"no-healthcheck") log_success "✓ $service: 运行正常" ((healthy_count++)) ;; "starting") log_warn "⚠ $service: 正在启动中..." ;; *) log_error "✗ $service: 健康检查失败 ($health)" ;; esac else log_error "✗ $service: 容器未运行" fi done if [[ $healthy_count -eq ${#services[@]} ]]; then log_success "所有服务运行正常" show_access_info else log_warn "部分服务可能存在问题,请检查日志" fi } # 显示访问信息 show_access_info() { log_info "服务访问信息:" echo "----------------------------------------" echo "前端应用: http://localhost:${FRONTEND_PORT:-80}" echo "后端API: http://localhost:${BACKEND_PORT:-8080}" echo "数据库: localhost:${DB_PORT:-3306}" echo "----------------------------------------" echo "查看日志: docker-compose -f docker-compose.${ENVIRONMENT}.yml logs -f" echo "停止服务: docker-compose -f docker-compose.${ENVIRONMENT}.yml down" echo "----------------------------------------" } # =========================================== # 状态检查 # =========================================== show_status() { log_info "当前构建状态:" echo "----------------------------------------" echo "环境: $ENVIRONMENT" echo "标签: $TAG" echo "项目根目录: $PROJECT_ROOT" echo "Docker目录: $DOCKER_DIR" echo "----------------------------------------" log_info "Docker镜像:" docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedAt}}\t{{.Size}}" | grep -E "(anxin-|REPOSITORY)" || echo "未找到相关镜像" log_info "运行中的容器:" docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(anxin-|NAMES)" || echo "未找到相关容器" } # =========================================== # 清理功能 # =========================================== clean_all() { log_step "清理所有构建产物和缓存..." # 停止所有相关容器 log_info "停止相关容器..." docker ps -a --format "{{.Names}}" | grep "anxin-" | xargs -r docker stop docker ps -a --format "{{.Names}}" | grep "anxin-" | xargs -r docker rm # 删除相关镜像 log_info "删除相关镜像..." docker images --format "{{.Repository}}:{{.Tag}}" | grep "anxin-" | xargs -r docker rmi -f # 清理构建缓存 clean_build_cache # 清理数据目录 (谨慎操作) read -p "是否清理数据目录? 这将删除所有持久化数据 (y/N): " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then log_warn "清理数据目录..." rm -rf "${DOCKER_DIR}/data" mkdir -p "${DOCKER_DIR}/data" fi log_success "清理完成" } # =========================================== # 主函数 # =========================================== main() { # 显示脚本信息 log_info "若依框架Docker部署 - 主构建脚本" log_info "脚本版本: 1.0.0" log_info "执行时间: $(date '+%Y-%m-%d %H:%M:%S')" # 解析参数 parse_args "$@" # 验证环境 validate_environment # 加载环境配置 load_environment_config # 检查系统依赖 check_prerequisites # 根据命令执行相应操作 case $COMMAND in pull) pull_latest_code ;; build) if [[ "$PULL_CODE" == "true" ]]; then pull_latest_code fi prepare_build case $COMPONENT in frontend) build_frontend_image ;; backend) build_backend_image ;; all) build_all_images ;; *) log_error "无效的组件: $COMPONENT" exit 1 ;; esac ;; build-all) if [[ "$PULL_CODE" == "true" ]]; then pull_latest_code fi prepare_build build_all_images ;; deploy) if [[ "$PULL_CODE" == "true" ]]; then pull_latest_code fi prepare_build build_all_images if [[ $? -eq 0 ]]; then deploy_to_environment else log_error "构建失败,取消部署" exit 1 fi ;; status) show_status ;; clean) clean_all ;; *) log_error "未知命令: $COMMAND" show_help exit 1 ;; esac log_success "脚本执行完成!" } # =========================================== # 错误处理 # =========================================== trap 'log_error "脚本执行被中断"; exit 1' INT TERM # 执行主函数 main "$@"