anxin-ruoyi/docker/build.sh
2026-01-08 20:47:24 +08:00

817 lines
23 KiB
Bash
Executable File
Raw Permalink 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
# 主构建脚本 - 若依框架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 "$@"