#!/bin/bash set -euo pipefail # ============================================ # 用户配置区域(请根据需要修改以下变量) # ============================================ ENV="dev" # 环境标识 (dev/test/uat/prod) SRC_DIR="/home/lq/lq_workspace/LQStandardsService/source/LQStandardsService" # 源代码目录 COMPOSE_RUN_DIR="/home/lq/lq_workspace/LQConstructionReviewServer/app/docker" # docker-compose 执行目录(cd到此目录运行命令) COMPOSE_FILE_DIR="/home/lq/lq_workspace/LQStandardsService/app/docker" # compose 配置文件所在目录 COMPOSE_FILE_NAME="docker-compose.${ENV}.yaml" # compose 配置文件名 SERVICES=("LQStandardsService") # 需要更新和部署的服务列表(按依赖顺序) GIT_USER="lingmin" # Git 用户名 GIT_PASS="123456" # Git 密码或token # 可选:指定用于提取版本号的服务(默认为 SERVICES 中的第一个) VERSION_SOURCE_SERVICE="${SERVICES[0]}" # ============================================ # 以下为脚本逻辑,一般无需修改 # ============================================ COMPOSE_FILE_PATH="${COMPOSE_FILE_DIR}/${COMPOSE_FILE_NAME}" log_info() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*" } log_error() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*" >&2 } # 检查必要文件/目录 if [ ! -f "$COMPOSE_FILE_PATH" ]; then log_error "docker-compose 配置文件不存在: $COMPOSE_FILE_PATH" exit 1 fi if [ ! -d "$SRC_DIR" ]; then log_error "源代码目录不存在: $SRC_DIR" exit 1 fi if [ ! -d "$COMPOSE_RUN_DIR" ]; then log_error "docker-compose 运行目录不存在: $COMPOSE_RUN_DIR" exit 1 fi cd "$COMPOSE_RUN_DIR" # ---------------------------------------------------------------------- # 辅助函数: 从指定服务的 image 字段中提取当前版本号 get_current_version() { local service=$1 local compose_file=$2 local image_line image_line=$(grep -A 10 "^\s*${service}:" "$compose_file" | grep -E "^\s*image:" | head -1) || { log_error "未在 compose 文件中找到服务 $service 的 image 字段" exit 1 } # 去除行内注释(# 及其后面的内容) image_line=$(echo "$image_line" | sed 's/#.*$//') local tag tag=$(echo "$image_line" | sed -E 's/.*:([^:]*)$/\1/' | tr -d '[:space:]') if [[ -z "$tag" ]]; then log_error "无法解析镜像标签 (tag)" exit 1 fi echo "$tag" } # 辅助函数: 版本号递增 (v数字.数字) increment_version() { local ver=$1 if [[ ! $ver =~ ^v[0-9]+\.[0-9]+$ ]]; then log_error "版本号格式错误,期望如 v0.05 或 v1.2,实际: $ver" exit 1 fi local prefix="${ver%.*}" local last_digit="${ver##*.}" local len=${#last_digit} local num=$((10#$last_digit + 1)) local new_last=$(printf "%0${len}d" "$num") echo "${prefix}.${new_last}" } # 辅助函数: 更新 compose 文件中多个服务的镜像版本号 update_compose_version_for_services() { local compose_file=$1 local new_version=$2 shift 2 local services=("$@") cp "$compose_file" "${compose_file}.bak" for svc in "${services[@]}"; do # 替换镜像版本号,同时保留行末可能存在的注释 sed -i -E "/^\s*${svc}:/,/^\s*image:/ s/(image:\s*.*:)([^[:space:]#]*)([[:space:]]*#.*)?$/\1${new_version}\3/" "$compose_file" log_info "已更新服务 $svc 的镜像版本为 $new_version" done } # 辅助函数: 获取镜像的所有历史版本(用于清理) get_sorted_image_tags() { local image_name=$1 docker images --format "{{.Repository}}:{{.Tag}}" | grep "^${image_name}:" | sed "s|${image_name}:||" | sort -V } # 辅助函数: 清理所有服务的旧镜像(每个服务保留最新3个) cleanup_old_images_for_services() { local services=("$@") for svc in "${services[@]}"; do log_info "清理服务 $svc 的旧镜像" local all_tags mapfile -t all_tags < <(get_sorted_image_tags "$svc") if [ ${#all_tags[@]} -le 3 ]; then log_info "服务 $svc 镜像数量 <= 3,跳过清理" continue fi local to_delete=$(( ${#all_tags[@]} - 3 )) for (( i=0; i/dev/null || echo "not_found") if [ "$status" != "running" ]; then all_running=false log_info "服务 $svc 状态: $status (等待中...)" break fi done if [ "$all_running" = true ]; then log_info "所有服务均正常运行" return 0 fi log_info "等待所有容器启动... (${i}/15)" done log_error "部分服务未能正常启动" return 1 } # 辅助函数: 停止并删除所有服务的容器 stop_and_remove_containers() { local services=("$@") for svc in "${services[@]}"; do docker compose -f "$COMPOSE_FILE_PATH" stop "$svc" >/dev/null 2>&1 || true docker compose -f "$COMPOSE_FILE_PATH" rm -f "$svc" >/dev/null 2>&1 || true done } # 辅助函数: 启动所有服务的容器 start_containers() { local services=("$@") docker compose -f "$COMPOSE_FILE_PATH" up -d "${services[@]}" } # URL 编码函数(针对密码中的特殊字符) urlencode() { local string="$1" local strlen=${#string} local encoded="" local pos c o for (( pos=0; pos