feat: 添加自动化部署方案(Docker + 远程服务器两套方案)

- 新增 deploy/docker/:Docker 本机模拟部署,含 Dockerfile、docker-compose、deploy.sh 一键脚本
- 新增 deploy/remote/:远程服务器部署,含 SSH 自动上传、重启、回滚脚本
- 新增 deploy/README.md:完整使用手册,含现状分析、落地调整工作清单、命令速查
- 新增 build.sh/start.sh:本地构建和启动脚本(含飞书通知)
- 新增前端 .env.docker 环境配置,API 指向测试服务器
- 前端 package.json 新增 build-docker 命令
- 更新 .gitignore:排除 IDE 配置、SQL 数据、Docker 敏感文件
- 前端 UI 样式优化(多个页面组件)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
xqzp2026
2026-04-15 18:41:15 +09:30
parent d27abbb529
commit 8373460096
71 changed files with 8003 additions and 350 deletions

355
deploy/docker/deploy.sh Executable file
View File

@@ -0,0 +1,355 @@
#!/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