#!/bin/bash # # SmartClean 一键部署脚本(Docker 版 — 本机模拟) # # 用法: # ./deploy.sh # 构建并部署所有服务 # ./deploy.sh web # 仅重建部署 Web 服务 # ./deploy.sh task # 仅重建部署 Task 服务 # ./deploy.sh front # 仅重建部署前端 # ./deploy.sh rollback # 回滚到上一版本 # ./deploy.sh status # 查看容器状态 # ./deploy.sh logs [服务名] # 查看日志 # ./deploy.sh stop # 停止所有容器 # ./deploy.sh clean # 停止并清理(含数据卷) set -e DEPLOY_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT_DIR="$(dirname "$(dirname "$DEPLOY_DIR")")" source "$DEPLOY_DIR/.env" # ===== 版本号 ===== GIT_HASH=$(cd "$ROOT_DIR" && git rev-parse --short HEAD) TIMESTAMP=$(date +%Y%m%d-%H%M%S) VERSION="${TIMESTAMP}-${GIT_HASH}" BRANCH=$(cd "$ROOT_DIR" && git rev-parse --abbrev-ref HEAD) COMMIT=$(cd "$ROOT_DIR" && git log -1 --format='%h %s') BACKUP_FILE="$DEPLOY_DIR/.last-version" CURRENT_FILE="$DEPLOY_DIR/.current-version" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } log_step() { echo -e "${CYAN}[STEP]${NC} $1"; } DC="docker compose -f $DEPLOY_DIR/docker-compose.yml" # ===== 飞书通知 ===== notify_feishu() { local title="$1" content="$2" color="$3" curl -s -X POST "$FEISHU_WEBHOOK" \ -H "Content-Type: application/json" \ -d "{ \"msg_type\": \"interactive\", \"card\": { \"header\": {\"title\":{\"tag\":\"plain_text\",\"content\":\"$title\"},\"template\":\"$color\"}, \"elements\": [{\"tag\":\"markdown\",\"content\":\"$content\"}] } }" > /dev/null 2>&1 } # ===== 检查 Docker ===== check_docker() { if ! command -v docker &> /dev/null; then log_error "Docker 未安装,请先安装 Docker Desktop" exit 1 fi if ! docker info > /dev/null 2>&1; then log_error "Docker 未启动,请先启动 Docker Desktop" exit 1 fi } # ===== 备份当前版本号 ===== backup_version() { if [ -f "$CURRENT_FILE" ]; then cp "$CURRENT_FILE" "$BACKUP_FILE" log_info "已备份当前版本: $(cat "$BACKUP_FILE")" fi } # ===== 本地构建产物 ===== build_local() { local target="${1:-all}" local start_time=$(date +%s) cd "$ROOT_DIR" case "$target" in web) log_step "本地构建 Web 服务..." bash build.sh web ;; task) log_step "本地构建 Task 服务..." bash build.sh task ;; front) log_step "本地构建前端 (docker 模式)..." cd "$ROOT_DIR/frontend/witcleansystem" npm run build-docker ;; all) log_step "本地构建全部..." bash build.sh backend cd "$ROOT_DIR/frontend/witcleansystem" npm run build-docker ;; esac local elapsed=$(( $(date +%s) - start_time )) log_info "本地构建完成 (${elapsed}s)" } # ===== 打包 Docker 镜像 ===== build_images() { local target="${1:-all}" local start_time=$(date +%s) cd "$DEPLOY_DIR" case "$target" in web) log_step "打包 Web 镜像 ($VERSION)..." $DC build --no-cache web docker tag smartclean-web:latest smartclean-web:$VERSION 2>/dev/null || true ;; task) log_step "打包 Task 镜像 ($VERSION)..." $DC build --no-cache task docker tag smartclean-task:latest smartclean-task:$VERSION 2>/dev/null || true ;; front) log_step "打包前端镜像 ($VERSION)..." $DC build --no-cache frontend docker tag smartclean-front:latest smartclean-front:$VERSION 2>/dev/null || true ;; all) log_step "打包全部镜像 ($VERSION)..." $DC build --no-cache web frontend docker tag smartclean-web:latest smartclean-web:$VERSION 2>/dev/null || true docker tag smartclean-front:latest smartclean-front:$VERSION 2>/dev/null || true ;; esac local elapsed=$(( $(date +%s) - start_time )) log_info "镜像打包完成 (${elapsed}s)" } # ===== 部署服务 ===== deploy_services() { local target="${1:-all}" cd "$DEPLOY_DIR" log_step "部署应用服务..." case "$target" in web) $DC down 2>/dev/null || true $DC up -d web frontend ;; task) $DC up -d --no-deps task ;; front) $DC down 2>/dev/null || true $DC up -d web frontend ;; all) $DC down 2>/dev/null || true $DC up -d web frontend ;; esac log_info "容器已启动" } # ===== 健康检查 ===== healthcheck() { local target="${1:-all}" local max_retries=30 local interval=3 log_step "健康检查..." # Web 服务 if [ "$target" = "all" ] || [ "$target" = "web" ]; then log_info " 检查 Web 服务 (http://localhost:$WEB_PORT)..." for i in $(seq 1 $max_retries); do if curl -sf -X POST "http://localhost:$WEB_PORT/dropDown/districtTree" > /dev/null 2>&1; then log_info " Web 服务健康 (第${i}次检查通过)" break fi if [ $i -eq $max_retries ]; then log_error " Web 服务健康检查失败" log_error " 查看日志: $DC logs web" return 1 fi printf "." sleep $interval done fi # 前端 if [ "$target" = "all" ] || [ "$target" = "front" ]; then log_info " 检查前端服务 (http://localhost:$FRONT_PORT)..." for i in $(seq 1 15); do if curl -sf "http://localhost:$FRONT_PORT/" > /dev/null 2>&1; then log_info " 前端服务健康 (第${i}次检查通过)" break fi if [ $i -eq 15 ]; then log_error " 前端服务健康检查失败" return 1 fi sleep 2 done fi log_info "健康检查通过" return 0 } # ===== 回滚 ===== rollback() { if [ ! -f "$BACKUP_FILE" ]; then log_error "没有可回滚的版本" exit 1 fi local old_version=$(cat "$BACKUP_FILE") log_warn "回滚到版本: $old_version" local has_images=true docker image inspect smartclean-web:$old_version > /dev/null 2>&1 || has_images=false docker image inspect smartclean-front:$old_version > /dev/null 2>&1 || has_images=false if [ "$has_images" = false ]; then log_error "旧版本镜像不存在 ($old_version),无法回滚" exit 1 fi export VERSION="$old_version" cd "$DEPLOY_DIR" $DC down 2>/dev/null || true $DC up -d web frontend echo "$old_version" > "$CURRENT_FILE" log_info "回滚完成" notify_feishu "⚠️ SmartClean 已回滚 (Docker)" \ "**回滚版本:** $old_version\\n**触发原因:** 健康检查失败" \ "yellow" } # ===== 清理旧镜像 ===== cleanup_images() { log_info "清理旧镜像(保留最近 5 个版本)..." for name in smartclean-web smartclean-task smartclean-front; do docker images "$name" --format "{{.Tag}}" | grep -v "latest" | sort -r | tail -n +6 | while read tag; do docker rmi "$name:$tag" 2>/dev/null && echo " 删除 $name:$tag" done done } # ===== 主流程 ===== TARGET="${1:-all}" DEPLOY_START=$(date +%s) case "$TARGET" in status) $DC ps exit 0 ;; logs) shift $DC logs -f $@ exit 0 ;; stop) log_info "停止所有容器..." $DC down log_info "已停止" exit 0 ;; clean) log_warn "停止所有容器并清理数据卷..." $DC down -v --rmi all log_info "已清理" exit 0 ;; rollback) check_docker rollback exit 0 ;; all|web|task|front) ;; *) echo "用法: $0 {all|web|task|front|rollback|status|logs|stop|clean}" exit 1 ;; esac echo "" log_info "======================================" log_info " SmartClean 自动化部署 (Docker)" log_info " 版本: $VERSION" log_info " 分支: $BRANCH" log_info " 提交: $COMMIT" log_info " 目标: $TARGET" log_info "======================================" echo "" # 1. 检查 Docker check_docker # 2. 备份当前版本号 backup_version # 3. 本地构建产物(Maven + npm) build_local "$TARGET" # 4. 打包 Docker 镜像(COPY 本地产物) build_images "$TARGET" # 5. 部署容器 deploy_services "$TARGET" # 6. 健康检查 if healthcheck "$TARGET"; then echo "$VERSION" > "$CURRENT_FILE" cleanup_images ELAPSED=$(( $(date +%s) - DEPLOY_START )) echo "" log_info "======================================" log_info " ✅ 部署成功!" log_info " 版本: $VERSION" log_info " 耗时: ${ELAPSED}s" log_info " 前端: http://localhost:$FRONT_PORT" log_info " Web: http://localhost:$WEB_PORT" log_info "======================================" notify_feishu "✅ SmartClean 部署成功 (Docker)" \ "**版本:** $VERSION\\n**分支:** $BRANCH\\n**提交:** $COMMIT\\n**目标:** $TARGET\\n**耗时:** ${ELAPSED}s\\n**前端:** http://localhost:$FRONT_PORT\\n**Web:** http://localhost:$WEB_PORT" \ "green" else log_error "健康检查失败,自动回滚..." rollback ELAPSED=$(( $(date +%s) - DEPLOY_START )) notify_feishu "❌ SmartClean 部署失败 (Docker,已回滚)" \ "**版本:** $VERSION\\n**分支:** $BRANCH\\n**提交:** $COMMIT\\n**耗时:** ${ELAPSED}s\\n**状态:** 健康检查失败,已自动回滚" \ "red" exit 1 fi