一 、脚本功能目录

1. 基础配置模块

项目变量设置:项目名称、JAR包名称、端口号、默认环境等
目录结构配置:部署目录、配置目录、日志目录、备份目录等
Docker相关配置:镜像名称、容器名称、镜像加速器等

2. 日志管理模块

彩色日志输出:INFO(蓝色)、SUCCESS(绿色)、WARNING(黄色)、ERROR(红色)
时间戳记录:所有操作都带有精确的时间记录
分级日志系统:不同重要程度的日志使用不同颜色标识

3. 环境检测模块

命令检查:检测docker、curl、jq等必要命令是否存在
Docker状态检查:确保Docker守护进程正常运行
镜像加速器配置:自动配置并验证阿里云镜像加速器

4. 配置文件管理模块

智能环境检测:自动识别外部配置文件并选择对应环境
多环境支持:dev、prod、test环境自动切换
配置优先级:外部配置 > JAR包内默认配置
配置文件验证:检查配置文件存在性和权限设置

5. 镜像构建模块

基础镜像创建:预构建包含JDK17和必要依赖的基础镜像
Dockerfile动态生成:根据检测到的环境动态生成Dockerfile
镜像构建重试机制:网络问题时的自动重试功能
镜像版本管理:基于时间戳的镜像标签管理

6. 容器管理模块

容器生命周期管理:启动、停止、重启、删除
资源挂载配置:日志目录、配置文件目录挂载
时间同步机制:容器与宿主机时间同步校验
自动重启策略:配置容器总是自动重启

7. 部署模式管理

全新部署模式:检测新JAR包时进行完整部署流程
重启部署模式:无新JAR包时智能重启现有容器
版本回滚支持:备份机制支持快速回滚

8. 健康检查模块

容器状态监控:实时检查容器运行状态
应用健康检查:通过HTTP接口验证应用健康状态
启动日志分析:解析启动日志确认部署成功

9. 清理维护模块

版本清理策略:保留指定数量的历史版本
资源释放:自动清理旧的镜像、备份文件
空间管理:防止磁盘空间被过多版本占用

10. 环境情况

JDK版本:OpenJDK 17
操作系统:Ubuntu 22.04
Docker:必须安装并运行

二 、脚本核心功能简介

1. 智能环境识别

脚本能够自动检测配置文件目录中的环境配置文件,智能选择运行环境
存在 application-prod.yaml → 使用 prod 环境
存在 application-dev.yaml → 使用 dev 环境
存在 application-test.yaml → 使用 test 环境
存在 application.yaml → 读取其中的环境配置
无配置文件 → 使用默认 dev 环境(脚本中设置的默认)

2. 全自动部署流程

前置检查:验证Docker、命令、网络等基础环境
配置检测:智能识别外部配置文件和环境
镜像构建:动态生成Dockerfile并构建镜像
容器部署:挂载配置、设置环境变量、启动容器
健康验证:检查容器状态和应用健康度
清理维护:保留合理的历史版本数量

3. 高可用特性

重试机制:镜像构建失败时自动重试
时间同步:确保容器与宿主机时间一致
自动重启:配置容器异常退出时自动重启
资源隔离:独立的目录结构避免冲突

4. 完善的日志系统

彩色输出便于快速识别问题
详细的时间戳记录操作时序
分级日志帮助定位问题严重程度
部署完成后显示完整的部署信息

5. 适用场景

微服务部署:适合Spring Boot微服务项目
多环境管理:开发、测试、生产环境统一部署
持续集成:可与CI/CD流水线集成
运维自动化:减少手动操作,提高部署效率
依赖工具:docker, curl, jq


三、目录结构(本人是在/data/project/dev目录)

sudo mkdir -p /data/project/dev/{config,upload,deploy,backup,logs,docker/images} && sudo chmod -R 755 /data/project/dev && sudo chmod 777 /data/project/dev/logs
在这里插入图片描述

四、编写deploy.sh

提示:创建my-network,否则删除 --network my-network \

1. 脚本内容

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 启动部分加入了 远程调试,详细看以前的贴子,不需要的删除

#!/bin/bash

# Docker部署Java项目脚本 - 智能版(支持外部配置文件)
# 项目: yudao-server
# JDK版本: 17
# 系统: Ubuntu 22.04
# 端口: 48080
# 配置文件: 根据外部配置自动选择环境

# 设置变量
PROJECT_NAME="yudao-server"            # 项目名称
JAR_NAME="yudao-server.jar"            # jar包名称
PORT=48080                             # 端口号
DEFAULT_PROFILE="dev"                  # 默认环境(当没有外部配置时使用)
BASE_DIR="/software/project/reTrade"           # 部署文件存放的目录
CONFIG_DIR="${BASE_DIR}/config"        # 外部配置文件目录
DEFAULT_CONFIG_DIR="/app/config"       # 容器内默认配置目录
UPLOAD_DIR="${BASE_DIR}/upload"        # 上传jar包目录
DEPLOY_DIR="${BASE_DIR}/deploy"        # 实际部署目录
BACKUP_DIR="${BASE_DIR}/backup"        # 版本备份目录
LOG_DIR="${BASE_DIR}/logs"             # 日志目录
DOCKER_DIR="${BASE_DIR}/docker"        # Docker文件目录
IMAGE_DIR="${DOCKER_DIR}/images"       # 镜像存放目录
CONTAINER_NAME="reTrade"                   # 容器名称
BASE_IMAGE_NAME="${PROJECT_NAME}-base" # 基础镜像名称
ALIYUN_REGISTRY="https://bbj3j02h.mirror.aliyuncs.com" # 阿里云镜像加速器
MAX_VERSIONS=5                         # 保留的最大版本数量
MAX_TIME_DIFF=60                       # 允许的最大时间误差(秒)
NETWORK_NAME="my-network"              # Docker网络名称

# 配置文件名称
CONFIG_FILES=("application-dev.yaml" "application-prod.yaml" "application-test.yaml")

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# 日志函数
log_info() {
    echo -e "${BLUE}[INFO] [$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
}

log_success() {
    echo -e "${GREEN}[SUCCESS] [$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
}

log_warning() {
    echo -e "${YELLOW}[WARNING] [$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR] [$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
}

# 检查命令是否存在
check_command() {
    if ! command -v $1 &> /dev/null; then
        log_error "命令 $1 不存在,请安装后重试"
        exit 1
    fi
}

# 检查Docker是否运行
check_docker() {
    if ! docker info &> /dev/null; then
        log_error "Docker守护进程未运行,请启动Docker后重试"
        exit 1
    fi
}

# 检测外部配置文件并确定使用的环境
detect_profile_from_config() {
    local detected_profile=$DEFAULT_PROFILE
    
    # 首先检查通用的 application.yaml
    if [ -f "${CONFIG_DIR}/application.yaml" ]; then
        # 从 application.yaml 中提取配置的环境
        local active_profile=$(grep -E "^\s*active:" "${CONFIG_DIR}/application.yaml" | awk '{print $2}' | tr -d '"' | tr -d "'" | tr -d ' ')
        if [ -n "$active_profile" ] && [ "$active_profile" != "null" ]; then
            detected_profile=$active_profile
        fi
    fi
    
    # 检查是否存在特定环境的配置文件,优先使用文件检测
    for profile_file in "${CONFIG_FILES[@]}"; do
        if [ -f "${CONFIG_DIR}/${profile_file}" ]; then
            local profile=$(echo "$profile_file" | sed 's/application-//' | sed 's/\.yaml//')
            detected_profile=$profile
            break
        fi
    done
    
    echo "$detected_profile"
}

# 检查并初始化配置文件目录
init_config_directory() {
    log_info "检查配置文件目录: ${CONFIG_DIR}"
    
    # 创建配置目录(如果不存在)
    mkdir -p ${CONFIG_DIR}
    
    # 检查配置文件是否存在
    local config_exists=false
    for config_file in "${CONFIG_FILES[@]}"; do
        if [ -f "${CONFIG_DIR}/${config_file}" ]; then
            config_exists=true
            log_info "找到外部配置文件: ${config_file}"
        fi
    done
    
    # 检查通用的 application.yaml
    if [ -f "${CONFIG_DIR}/application.yaml" ]; then
        config_exists=true
        log_info "找到通用外部配置文件: application.yaml"
    fi
    
    if [ "$config_exists" = false ]; then
        log_warning "未找到外部配置文件,将使用JAR包内默认配置"
        log_info "您可以在 ${CONFIG_DIR} 目录下放置以下配置文件来覆盖默认配置:"
        log_info "  - application.yaml (通用配置)"
        for config_file in "${CONFIG_FILES[@]}"; do
            log_info "  - ${config_file} (环境特定配置)"
        done
    fi
    
    # 设置配置文件权限
    chmod -R 755 ${CONFIG_DIR}
}

# 检查外部配置文件是否存在
check_external_config() {
    # 检查是否存在任何配置文件
    if [ -f "${CONFIG_DIR}/application.yaml" ] || [ -f "${CONFIG_DIR}/application-dev.yaml" ] || [ -f "${CONFIG_DIR}/application-prod.yaml" ] || [ -f "${CONFIG_DIR}/application-test.yaml" ]; then
        echo "external"
    else
        echo "internal"
    fi
}

# 检查镜像加速是否已配置并生效
check_registry_mirror() {
    log_info "检查镜像加速配置..."
    
    # 检查Docker守护进程是否已配置镜像加速
    if docker info 2>/dev/null | grep -q "Registry Mirrors" && docker info 2>/dev/null | grep -q "mirror.aliyuncs.com"; then
        log_info "阿里云镜像加速器已配置并生效"
        return 0
    fi
    
    # 检查配置文件是否存在
    local docker_daemon_file="/etc/docker/daemon.json"
    if [ -f "$docker_daemon_file" ] && grep -q "mirror.aliyuncs.com" "$docker_daemon_file"; then
        log_info "阿里云镜像加速器已配置但未生效,需要重启Docker"
        return 1
    fi
    
    log_info "阿里云镜像加速器未配置"
    return 2
}

# 配置阿里云镜像加速(仅在需要时)
configure_registry_mirror() {
    check_registry_mirror
    local status=$?
    
    case $status in
        0)
            # 已配置并生效,无需操作
            return 0
            ;;
        1)
            # 已配置但未生效,需要重启Docker
            log_info "重启Docker服务以应用镜像加速配置..."
            sudo systemctl restart docker
            sleep 3
            check_docker
            log_success "Docker重启完成,镜像加速已生效"
            return 0
            ;;
        2)
            # 未配置,需要配置并重启
            log_info "配置阿里云镜像加速器..."
            
            local docker_daemon_file="/etc/docker/daemon.json"
            local temp_file=$(mktemp)
            
            if [ -f "$docker_daemon_file" ]; then
                # 已存在配置文件,检查并添加registry-mirrors
                if grep -q "registry-mirrors" "$docker_daemon_file"; then
                    # 已存在registry-mirrors,添加阿里云镜像
                    jq '.["registry-mirrors"] += ["https://bbj3j02h.mirror.aliyuncs.com"]' "$docker_daemon_file" > "$temp_file"
                else
                    # 不存在registry-mirrors,添加新配置
                    jq '. + {"registry-mirrors": ["https://bbj3j02h.mirror.aliyuncs.com"]}' "$docker_daemon_file" > "$temp_file"
                fi
            else
                # 创建新配置文件
                cat > "$temp_file" << EOF
{
  "registry-mirrors": ["https://bbj3j02h.mirror.aliyuncs.com"]
}
EOF
            fi
            
            # 应用配置
            sudo mv "$temp_file" "$docker_daemon_file"
            sudo chmod 600 "$docker_daemon_file"
            
            # 重启Docker服务
            log_info "重启Docker服务以应用配置..."
            sudo systemctl restart docker
            
            # 等待Docker启动
            sleep 3
            check_docker
            
            log_success "阿里云镜像加速器配置完成"
            return 0
            ;;
    esac
}

# 创建基础镜像(如果不存在)- 优化版,增加重试机制
create_base_image() {
    if docker images | grep -q "${BASE_IMAGE_NAME}"; then
        log_info "基础镜像已存在,跳过创建"
        return 0
    fi

    log_info "创建基础镜像..."
    local temp_dir=$(mktemp -d)
    
    cat > ${temp_dir}/Dockerfile << EOF
FROM openjdk:17-jdk-slim

# 设置时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/timezone

# 设置APT重试机制和超时
RUN echo 'Acquire::Retries "3";' > /etc/apt/apt.conf.d/80-retries && \\
    echo 'Acquire::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \\
    echo 'Acquire::ftp::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries

# 安装必要的字体、X11图形库和中文字体(简化版本)
RUN apt-get update && \\
    apt-get install -y --no-install-recommends \\
    fontconfig \\
    fonts-dejavu-core \\
    fonts-dejavu-extra \\
    fonts-liberation \\
    fonts-noto-cjk \\
    fonts-wqy-microhei \\
    fonts-wqy-zenhei \\
    libxrender1 \\
    libxtst6 \\
    libxi6 \\
    libxext6 \\
    libx11-6 \\
    libfontconfig1 && \\
    apt-get clean && \\
    rm -rf /var/lib/apt/lists/* && \\
    fc-cache -fv

# 可选:如果fonts-freefont-ttf下载失败,可以使用替代方案
RUN if ! apt-get install -y fonts-freefont-ttf; then \\
    echo "fonts-freefont-ttf安装失败,使用替代字体" && \\
    apt-get install -y fonts-liberation; \\
    fi

# 安装时间同步工具(用于时间校验)
RUN apt-get update && apt-get install -y --no-install-recommends ntpdate && apt-get clean

# 创建应用目录和配置目录
RUN mkdir -p /app/logs /app/config && chmod 755 /app/logs /app/config

WORKDIR /app
EOF

    # 增加构建重试机制
    local max_retries=3
    local retry_count=0
    local build_status=1
    
    while [ $retry_count -lt $max_retries ] && [ $build_status -ne 0 ]; do
        retry_count=$((retry_count + 1))
        log_info "尝试第 $retry_count 次构建基础镜像..."
        
        docker build -t ${BASE_IMAGE_NAME} ${temp_dir}
        build_status=$?
        
        if [ $build_status -ne 0 ]; then
            log_warning "基础镜像构建失败,等待10秒后重试..."
            sleep 10
        fi
    done
    
    rm -rf ${temp_dir}
    
    if [ $build_status -eq 0 ]; then
        log_success "基础镜像创建成功"
    else
        log_error "基础镜像创建失败,请检查网络连接后重试"
        exit 1
    fi
}

# 创建必要的目录
create_directories() {
    log_info "创建必要的目录..."
    mkdir -p ${UPLOAD_DIR} ${DEPLOY_DIR} ${BACKUP_DIR} ${LOG_DIR} ${IMAGE_DIR} ${CONFIG_DIR}
    # 确保日志目录有写入权限
    chmod 777 ${LOG_DIR}
    chmod 755 ${CONFIG_DIR}
    log_info "目录创建完成: ${BASE_DIR}"
    log_info "设置日志目录权限: ${LOG_DIR}"
    log_info "设置配置目录权限: ${CONFIG_DIR}"
}

# 检查是否有新jar包并决定部署模式
check_deployment_mode() {
    if [ ! -f "${UPLOAD_DIR}/${JAR_NAME}" ]; then
        log_info "没有找到新上传的jar包,将使用现有镜像进行重启"
        return 1  # 重启模式
    else
        log_info "发现新jar包,开始全新部署"
        return 0  # 全新部署模式
    fi
}

# 获取最新的镜像标签
get_latest_image_tag() {
    # 从镜像文件中获取最新的镜像标签
    local latest_tar=$(ls -t ${IMAGE_DIR}/*.tar 2>/dev/null | head -1)
    if [ -n "$latest_tar" ]; then
        LATEST_IMAGE_TAG=$(basename ${latest_tar} .tar)
        LATEST_IMAGE_NAME="${PROJECT_NAME}:${LATEST_IMAGE_TAG}"
        log_info "找到最新镜像: ${LATEST_IMAGE_NAME}"
        return 0
    else
        log_error "没有找到可用的镜像文件"
        return 1
    fi
}

# 加载最新的镜像
load_latest_image() {
    local latest_tar=$(ls -t ${IMAGE_DIR}/*.tar 2>/dev/null | head -1)
    if [ -n "$latest_tar" ]; then
        log_info "加载最新镜像: ${latest_tar}"
        docker load -i ${latest_tar}
        if [ $? -eq 0 ]; then
            log_success "镜像加载成功"
            LATEST_IMAGE_NAME=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep ${PROJECT_NAME} | head -1)
            return 0
        else
            log_error "镜像加载失败"
            return 1
        fi
    else
        log_error "没有找到可用的镜像文件"
        return 1
    fi
}

# 备份当前jar包
backup_current_jar() {
    if [ -f "${DEPLOY_DIR}/${JAR_NAME}" ]; then
        log_info "备份当前运行的jar包..."
        BACKUP_SUFFIX=$(date +"%Y%m%d%H%M%S")
        mv ${DEPLOY_DIR}/${JAR_NAME} ${BACKUP_DIR}/${JAR_NAME}.${BACKUP_SUFFIX}
        log_info "已备份到: ${BACKUP_DIR}/${JAR_NAME}.${BACKUP_SUFFIX}"
    fi
}

# 准备部署文件
prepare_deployment() {
    log_info "拷贝新jar包到部署目录..."
    cp ${UPLOAD_DIR}/${JAR_NAME} ${DEPLOY_DIR}/

    log_info "删除上传的临时jar包..."
    rm -f ${UPLOAD_DIR}/${JAR_NAME}

    # 检测要使用的环境
    local detected_profile=$(detect_profile_from_config)
    log_info "确定使用的环境: $detected_profile"

    log_info "创建Dockerfile..."
    cat > ${DEPLOY_DIR}/Dockerfile << EOF
# 使用预构建的基础镜像
FROM ${BASE_IMAGE_NAME}

# 复制jar包到容器中
COPY ${JAR_NAME} /app/

# 暴露端口
EXPOSE ${PORT}

# 设置JVM参数
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -Djava.awt.headless=true"
ENV SPRING_PROFILES_ACTIVE=${detected_profile}

# 启动应用 - 支持外部配置和内部配置
ENTRYPOINT ["sh", "-c", "if [ -f \"/app/config/application.yaml\" ] || [ -f \"/app/config/application-dev.yaml\" ] || [ -f \"/app/config/application-prod.yaml\" ] || [ -f \"/app/config/application-test.yaml\" ]; then \\
    echo \"=== 使用外部配置文件 ===\"; \\
    if [ -f \"/app/config/application.yaml\" ]; then \\
        echo \"  - /app/config/application.yaml\"; \\
    fi; \\
    if [ -f \"/app/config/application-dev.yaml\" ]; then \\
        echo \"  - /app/config/application-dev.yaml\"; \\
    fi; \\
    if [ -f \"/app/config/application-prod.yaml\" ]; then \\
        echo \"  - /app/config/application-prod.yaml\"; \\
    fi; \\
    if [ -f \"/app/config/application-test.yaml\" ]; then \\
        echo \"  - /app/config/application-test.yaml\"; \\
    fi; \\
    java \$JAVA_OPTS -Djava.awt.headless=true -Dspring.config.additional-location=file:/app/config/  -jar ${JAR_NAME} 2>&1 | tee -a /app/logs/console.log; \\
else \\
    echo \"=== 使用JAR包内默认配置文件 ===\"; \\
    echo \"  - 环境: \${SPRING_PROFILES_ACTIVE}\"; \\
    java \$JAVA_OPTS -Djava.awt.headless=true -Dspring.profiles.active=\${SPRING_PROFILES_ACTIVE}  -jar ${JAR_NAME} 2>&1 | tee -a /app/logs/console.log; \\
fi"]
EOF
}

# 构建Docker镜像
build_docker_image() {
    log_info "构建Docker镜像..."
    IMAGE_TAG=$(date +"%Y%m%d%H%M%S")
    IMAGE_NAME="${PROJECT_NAME}:${IMAGE_TAG}"

    cd ${DEPLOY_DIR}
    if docker build -t ${IMAGE_NAME} .; then
        log_success "Docker镜像构建成功"
    else
        log_error "Docker镜像构建失败!"
        exit 1
    fi

    log_info "保存镜像到文件: ${IMAGE_DIR}/${IMAGE_TAG}.tar"
    docker save -o ${IMAGE_DIR}/${IMAGE_TAG}.tar ${IMAGE_NAME}
}

# 停止并删除现有容器
stop_existing_container() {
    if docker ps -a | grep -q ${CONTAINER_NAME}; then
        log_info "停止并删除现有容器..."
        docker stop ${CONTAINER_NAME} > /dev/null 2>&1
        docker rm ${CONTAINER_NAME} > /dev/null 2>&1
    fi
}

# 检查容器与宿主机时间差异
check_time_difference() {
    log_info "检查容器与宿主机时间差异..."
    
    local container_name=$1
    local max_diff=$MAX_TIME_DIFF
    
    # 获取宿主机当前时间(秒)
    local host_time=$(date +%s)
    
    # 获取容器内时间(秒)
    local container_time=$(docker exec ${container_name} date +%s 2>/dev/null || echo "")
    
    if [ -z "$container_time" ]; then
        log_warning "无法获取容器时间,可能容器尚未完全启动"
        return 1
    fi
    
    # 计算时间差绝对值
    local time_diff=$((host_time - container_time))
    if [ $time_diff -lt 0 ]; then
        time_diff=$(( -time_diff ))
    fi
    
    log_info "宿主机时间: $(date -d @${host_time} '+%Y-%m-%d %H:%M:%S')"
    log_info "容器时间: $(docker exec ${container_name} date '+%Y-%m-%d %H:%M:%S')"
    log_info "时间差异: ${time_diff} 秒"
    
    if [ $time_diff -gt $max_diff ]; then
        log_warning "时间差异超过 ${max_diff} 秒,尝试同步时间..."
        
        # 尝试同步容器时间
        if docker exec ${container_name} ntpdate -s time.nist.gov 2>/dev/null || \
           docker exec ${container_name} ntpdate -s ntp.aliyun.com 2>/dev/null || \
           docker exec ${container_name} ntpdate -s pool.ntp.org 2>/dev/null; then
            log_success "容器时间同步成功"
            
            # 重新检查时间差异
            local new_container_time=$(docker exec ${container_name} date +%s)
            local new_time_diff=$((host_time - new_container_time))
            if [ $new_time_diff -lt 0 ]; then
                new_time_diff=$(( -new_time_diff ))
            fi
            
            log_info "同步后时间差异: ${new_time_diff} 秒"
            
            if [ $new_time_diff -le $max_diff ]; then
                log_success "时间同步后差异在允许范围内"
                return 0
            else
                log_warning "时间同步后差异仍然超过阈值"
                return 1
            fi
        else
            log_warning "无法同步容器时间,请检查网络连接或手动处理"
            return 1
        fi
    else
        log_success "时间差异在允许范围内 (≤ ${MAX_TIME_DIFF} 秒)"
        return 0
    fi
}

# 启动新容器(使用 --restart always 确保Docker重启后自动启动)
start_new_container() {
    local image_name=$1
    log_info "启动新的容器(配置为总是重启)..."
    
    # 检测要使用的环境
    local detected_profile=$(detect_profile_from_config)
    log_info "确定使用的环境: $detected_profile"
    
    # 检查外部配置文件
    local config_source=$(check_external_config)
    
    # 构建挂载参数
    local mount_args="-v ${LOG_DIR}:/app/logs"
    mount_args+=" -v /etc/timezone:/etc/timezone:ro"
    mount_args+=" -v /etc/localtime:/etc/localtime:ro"
    
    # 只有在配置文件目录不为空时才挂载
    if [ "$config_source" != "internal" ]; then
        mount_args+=" -v ${CONFIG_DIR}:/app/config:ro"
        log_info "挂载外部配置文件目录: ${CONFIG_DIR} -> /app/config"
        
        # 列出检测到的配置文件
        for config_file in "${CONFIG_FILES[@]}"; do
            if [ -f "${CONFIG_DIR}/${config_file}" ]; then
                log_info "检测到配置文件: ${config_file}"
            fi
        done
        if [ -f "${CONFIG_DIR}/application.yaml" ]; then
            log_info "检测到配置文件: application.yaml"
        fi
    else
        log_info "使用JAR包内默认配置,不挂载外部配置目录"
    fi
    
    if docker run -d \
        --name ${CONTAINER_NAME} \
        -p ${PORT}:${PORT} \
        -e SPRING_PROFILES_ACTIVE=${detected_profile} \
        ${mount_args} \
        --restart always \
        --network ${NETWORK_NAME} \  
        ${image_name}; then
        log_success "容器启动命令执行成功"
        log_info "容器已配置为自动重启策略:always"
        log_info "使用的环境: ${detected_profile}"
        log_info "配置来源: ${config_source}"
        
        # 等待容器完全启动
        sleep 3
        
        # 检查时间差异
        if check_time_difference ${CONTAINER_NAME}; then
            log_success "容器时间校验通过"
        else
            log_warning "容器时间校验未通过,但容器已启动"
        fi
        
        return 0
    else
        log_error "容器启动失败"
        return 1
    fi
}

# 重启现有容器
restart_existing_container() {
    log_info "重启现有容器..."
    
    # 检测要使用的环境
    local detected_profile=$(detect_profile_from_config)
    log_info "确定使用的环境: $detected_profile"
    
    # 检查外部配置文件
    local config_source=$(check_external_config)
    log_info "使用的环境: ${detected_profile}"
    log_info "配置来源: ${config_source}"
    
    if docker restart ${CONTAINER_NAME}; then
        log_success "容器重启成功"
        
        # 等待容器完全启动
        sleep 3
        
        # 检查时间差异
        if check_time_difference ${CONTAINER_NAME}; then
            log_success "容器时间校验通过"
        else
            log_warning "容器时间校验未通过,但容器已重启"
        fi
        
        return 0
    else
        log_error "容器重启失败,尝试重新创建容器..."
        # 如果重启失败,尝试使用最新镜像重新创建容器
        stop_existing_container
        if get_latest_image_tag; then
            start_new_container "${LATEST_IMAGE_NAME}"
        else
            log_error "无法找到可用镜像,请先进行全新部署"
            exit 1
        fi
    fi
}

# 检查容器状态
check_container_status() {
    log_info "检查容器状态..."
    local max_attempts=10
    local attempt=1
    
    while [ $attempt -le $max_attempts ]; do
        if docker ps | grep -q ${CONTAINER_NAME}; then
            log_success "容器已启动并运行中"
            return 0
        fi
        sleep 2
        attempt=$((attempt + 1))
    done
    
    log_error "容器启动超时"
    docker logs ${CONTAINER_NAME}
    exit 1
}

# 检查应用健康状态
check_application_health() {
    log_info "检查应用健康状态..."
    sleep 5
    
    # 检查启动日志,显示使用的配置文件
    local container_logs=$(docker logs ${CONTAINER_NAME} --tail 20 2>/dev/null)
    
    if echo "$container_logs" | grep -q "使用外部配置文件"; then
        log_success "检测到使用外部配置文件"
    elif echo "$container_logs" | grep -q "使用JAR包内默认配置文件"; then
        log_success "检测到使用JAR包内默认配置"
    fi
    
    if echo "$container_logs" | grep -q "Started.*in.*seconds"; then
        log_success "应用启动成功"
    else
        log_warning "应用启动中,请查看日志确认状态"
    fi
    
    # 测试应用访问
    log_info "测试应用访问..."
    local max_health_checks=5
    local check_count=0
    
    while [ $check_count -lt $max_health_checks ]; do
        local status_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${PORT}/actuator/health --connect-timeout 5 || echo "000")
        
        if [[ $status_code =~ ^(200|301|302)$ ]]; then
            log_success "应用健康检查通过 (HTTP $status_code)"
            return 0
        fi
        
        check_count=$((check_count + 1))
        sleep 3
    done
    
    log_warning "应用健康检查未通过,请手动验证"
}

# 清理旧版本
cleanup_old_versions() {
    log_info "清理旧的备份文件(保留最近${MAX_VERSIONS}个版本)..."
    
    # 清理镜像备份
    local image_count=$(ls -t ${IMAGE_DIR}/*.tar 2>/dev/null | wc -l)
    if [ $image_count -gt $MAX_VERSIONS ]; then
        ls -t ${IMAGE_DIR}/*.tar | tail -n +$(($MAX_VERSIONS + 1)) | xargs rm -f 2>/dev/null || true
        log_info "已清理 $(($image_count - $MAX_VERSIONS)) 个旧镜像备份"
    fi
    
    # 清理jar包备份
    local jar_count=$(ls -t ${BACKUP_DIR}/*.jar.* 2>/dev/null | wc -l)
    if [ $jar_count -gt $MAX_VERSIONS ]; then
        ls -t ${BACKUP_DIR}/*.jar.* | tail -n +$(($MAX_VERSIONS + 1)) | xargs rm -f 2>/dev/null || true
        log_info "已清理 $(($jar_count - $MAX_VERSIONS)) 个旧jar包备份"
    fi
    
    # 清理Docker镜像
    local docker_image_count=$(docker images ${PROJECT_NAME} --format "{{.Tag}}" | wc -l)
    if [ $docker_image_count -gt $MAX_VERSIONS ]; then
        docker images ${PROJECT_NAME} --format "{{.ID}} {{.Tag}}" | sort -k2 -r | tail -n +$(($MAX_VERSIONS + 1)) | awk '{print $1}' | xargs docker rmi -f 2>/dev/null || true
        log_info "已清理 $(($docker_image_count - $MAX_VERSIONS)) 个旧Docker镜像"
    fi
}

# 显示部署信息
show_deployment_info() {
    local mode=$1
    local image_name=$2
    local detected_profile=$(detect_profile_from_config)
    local config_source=$(check_external_config)
    
    log_success "${mode}完成!"
    echo "=================================================="
    echo "项目名称:    ${PROJECT_NAME}"
    echo "环境:        ${detected_profile}"
    echo "配置来源:    ${config_source}"
    echo "端口:        ${PORT}"
    echo "容器名称:    ${CONTAINER_NAME}"
    echo "部署模式:    ${mode}"
    echo "使用镜像:    ${image_name}"
    echo "日志目录:    ${LOG_DIR}"
    echo "配置目录:    ${CONFIG_DIR}"
    echo "备份目录:    ${BACKUP_DIR}"
    echo "保留版本数:  ${MAX_VERSIONS}"
    echo "时间误差:    ≤ ${MAX_TIME_DIFF} 秒"
    echo "重启策略:    总是重启 (Docker重启后自动启动)"
    echo "时间同步:    已挂载宿主机时区文件 + NTP同步"
    echo "配置策略:    外部配置优先,不存在时使用JAR包内配置"
    echo "配置文件:    application.yaml, application-dev.yaml, application-prod.yaml, application-test.yaml"
    echo "=================================================="
    echo "常用命令:"
    echo "查看日志:    docker logs -f ${CONTAINER_NAME}"
    echo "进入容器:    docker exec -it ${CONTAINER_NAME} sh"
    echo "停止容器:    docker stop ${CONTAINER_NAME}"
    echo "启动容器:    docker start ${CONTAINER_NAME}"
    echo "重启容器:    docker restart ${CONTAINER_NAME}"
    echo "重启策略:    docker update --restart always ${CONTAINER_NAME}"
    echo "时间校验:    docker exec ${CONTAINER_NAME} date"
    echo "查看配置:    ls -la ${CONFIG_DIR}"
    echo "=================================================="
}

# 主函数
main() {
    echo "开始部署 ${PROJECT_NAME} [$(date '+%Y-%m-%d %H:%M:%S')]..."
    
    # 检查前置条件
    check_command docker
    check_command curl
    check_command jq
    check_docker
    
    # 配置阿里云镜像加速(智能判断,避免不必要的重启)
    configure_registry_mirror
    
    # 执行部署流程
    create_base_image
    create_directories
    init_config_directory
    
    # 检查部署模式
    check_deployment_mode
    local deployment_mode=$?
    
    if [ $deployment_mode -eq 0 ]; then
        # 全新部署模式
        log_info "开始全新部署流程..."
        backup_current_jar
        prepare_deployment
        build_docker_image
        stop_existing_container
        start_new_container "${IMAGE_NAME}"
        check_container_status
        check_application_health
        cleanup_old_versions
        show_deployment_info "全新部署" "${IMAGE_NAME}"
    else
        # 重启模式
        log_info "开始重启部署流程..."
        if load_latest_image; then
            # 如果容器存在,重启容器
            if docker ps -a | grep -q ${CONTAINER_NAME}; then
                restart_existing_container
            else
                # 如果容器不存在,创建新容器
                stop_existing_container
                start_new_container "${LATEST_IMAGE_NAME}"
            fi
            check_container_status
            check_application_health
            show_deployment_info "重启部署" "${LATEST_IMAGE_NAME}"
        else
            log_error "无法找到可用镜像,请先进行全新部署"
            exit 1
        fi
    fi
}

# 执行主函数
main "$@"




2.需要安装jq

sudo apt-get update && sudo apt-get install -y jq

结果

3.权限和运行命令

1、cd  /data/project/dev(我脚本存在的目录)
2、sudo chmod +x deploy.sh
3、./deploy.sh

![![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/15dcdc04e07e4817873b3fb70dbc2dfa.png](https://i-blog.csdnimg.cn/direct/e7eb2303942048d382cc7f2aa9971602.png


Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐