Files
smartClean/deploy/remote/deploy.sh
xqzp2026 8373460096 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>
2026-04-15 18:41:15 +09:30

411 lines
13 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
#
# SmartClean 一键部署脚本(无 Docker 版 — 部署到远程服务器)
#
# 用法:
# ./deploy.sh # 构建并部署所有服务
# ./deploy.sh web # 仅构建部署 Web 服务
# ./deploy.sh task # 仅构建部署 Task 服务
# ./deploy.sh front # 仅构建部署前端(生产包)
# ./deploy.sh front-test # 构建测试环境前端并部署
# ./deploy.sh backend # 构建部署后端web + task
# ./deploy.sh rollback # 回滚到上一版本
# ./deploy.sh setup # 首次初始化服务器目录
# ./deploy.sh status # 查看服务器服务状态
set -e
DEPLOY_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(dirname "$(dirname "$DEPLOY_DIR")")"
source "$DEPLOY_DIR/config.sh"
# ===== 版本号 =====
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')
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"; }
SSH_CMD="ssh $DEPLOY_USER@$DEPLOY_HOST"
SCP_CMD="scp"
# ===== 飞书通知 =====
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
}
# ===== 检查 SSH 连接 =====
check_ssh() {
log_info "检查 SSH 连接 ($DEPLOY_USER@$DEPLOY_HOST)..."
if ! $SSH_CMD "echo ok" > /dev/null 2>&1; then
log_error "无法连接到 $DEPLOY_USER@$DEPLOY_HOST"
log_error "请先配置 SSH 免密登录:"
log_error " ssh-keygen -t ed25519"
log_error " ssh-copy-id $DEPLOY_USER@$DEPLOY_HOST"
exit 1
fi
log_info "SSH 连接正常"
}
# ===== 首次初始化服务器 =====
setup_server() {
log_step "1/2 初始化服务器目录结构..."
$SCP_CMD "$DEPLOY_DIR/scripts/setup.sh" "$DEPLOY_USER@$DEPLOY_HOST:/tmp/smartclean-setup.sh"
$SSH_CMD "bash /tmp/smartclean-setup.sh && rm -f /tmp/smartclean-setup.sh"
log_step "2/2 上传管理脚本..."
$SCP_CMD "$DEPLOY_DIR/scripts/restart-web.sh" "$DEPLOY_USER@$DEPLOY_HOST:$REMOTE_SCRIPTS_DIR/"
$SCP_CMD "$DEPLOY_DIR/scripts/restart-task.sh" "$DEPLOY_USER@$DEPLOY_HOST:$REMOTE_SCRIPTS_DIR/"
$SCP_CMD "$DEPLOY_DIR/scripts/restart-front.sh" "$DEPLOY_USER@$DEPLOY_HOST:$REMOTE_SCRIPTS_DIR/"
$SSH_CMD "chmod +x $REMOTE_SCRIPTS_DIR/*.sh"
log_info "服务器初始化完成"
}
# ===== 查看服务器状态 =====
show_status() {
log_info "服务器状态 ($DEPLOY_HOST):"
$SSH_CMD bash <<'EOF'
echo ""
echo "===== Web 服务 (Tomcat) ====="
TOMCAT_PID=$(ps -ef | grep "[c]atalina" | awk '{print $2}')
if [ -n "$TOMCAT_PID" ]; then
echo " 状态: 运行中 (PID=$TOMCAT_PID)"
else
echo " 状态: 未运行"
fi
echo ""
echo "===== Task 服务 ====="
if [ -f /opt/smartclean/task/task.pid ]; then
TASK_PID=$(cat /opt/smartclean/task/task.pid)
if kill -0 "$TASK_PID" 2>/dev/null; then
echo " 状态: 运行中 (PID=$TASK_PID)"
else
echo " 状态: 未运行 (PID 文件残留)"
fi
else
echo " 状态: 未运行"
fi
echo ""
echo "===== Nginx ====="
if nginx -t 2>/dev/null; then
echo " 状态: 运行中"
else
echo " 状态: 异常"
fi
echo ""
echo "===== 备份列表 ====="
ls -dt /opt/smartclean/backups/*/ 2>/dev/null | head -5 || echo " 无备份"
echo ""
EOF
}
# ===== 本地构建 =====
build_local() {
local target="$1"
local build_target="$target"
# 映射 deploy 参数到 build.sh 参数
case "$target" in
all) build_target="all" ;;
web) build_target="web" ;;
task) build_target="task" ;;
backend) build_target="backend" ;;
front) build_target="front" ;;
front-test) build_target="front-test" ;;
esac
log_step "本地构建 (目标: $build_target)..."
cd "$ROOT_DIR"
bash build.sh "$build_target"
# 验证产物
case "$target" in
web|backend|all)
[ ! -f "$ROOT_DIR/$LOCAL_WAR" ] && log_error "产物不存在: $LOCAL_WAR" && exit 1
log_info " ROOT.war: $(du -h "$ROOT_DIR/$LOCAL_WAR" | cut -f1)"
;;&
task|backend|all)
[ ! -f "$ROOT_DIR/$LOCAL_TASK_JAR" ] && log_error "产物不存在: $LOCAL_TASK_JAR" && exit 1
log_info " task.jar: $(du -h "$ROOT_DIR/$LOCAL_TASK_JAR" | cut -f1)"
;;&
front|front-test|all)
[ ! -d "$ROOT_DIR/$LOCAL_FRONT_DIST" ] && log_error "产物不存在: $LOCAL_FRONT_DIST" && exit 1
log_info " dist/: $(du -sh "$ROOT_DIR/$LOCAL_FRONT_DIST" | cut -f1)"
;;
esac
log_info "本地构建完成"
}
# ===== 远程备份 =====
backup_remote() {
local target="$1"
log_step "远程备份当前版本..."
$SSH_CMD bash <<BKEOF
BACKUP="$REMOTE_BACKUP_DIR/$VERSION"
mkdir -p "\$BACKUP"
case "$target" in
web|backend|all)
cp "$TOMCAT_WEBAPPS/ROOT.war" "\$BACKUP/" 2>/dev/null && echo " 备份 ROOT.war" || echo " ROOT.war 不存在,跳过"
;;&
task|backend|all)
cp "$REMOTE_TASK_DIR/task.jar" "\$BACKUP/" 2>/dev/null && echo " 备份 task.jar" || echo " task.jar 不存在,跳过"
;;&
front|front-test|all)
if [ -d "$REMOTE_FRONT_DIR/dist" ]; then
cp -r "$REMOTE_FRONT_DIR/dist" "\$BACKUP/"
echo " 备份 front/dist"
fi
;;
esac
# 清理过期备份
cd "$REMOTE_BACKUP_DIR"
TOTAL=\$(ls -dt */ 2>/dev/null | wc -l)
if [ \$TOTAL -gt $MAX_BACKUPS ]; then
ls -dt */ | tail -n +\$(($MAX_BACKUPS + 1)) | xargs rm -rf
echo " 已清理过期备份,保留最近 $MAX_BACKUPS 个"
fi
BKEOF
log_info "远程备份完成"
}
# ===== 上传产物 =====
upload_artifacts() {
local target="$1"
log_step "上传构建产物..."
case "$target" in
web|backend|all)
log_info " 上传 ROOT.war..."
$SCP_CMD "$ROOT_DIR/$LOCAL_WAR" "$DEPLOY_USER@$DEPLOY_HOST:$TOMCAT_WEBAPPS/ROOT.war"
;;&
task|backend|all)
log_info " 上传 task.jar..."
$SCP_CMD "$ROOT_DIR/$LOCAL_TASK_JAR" "$DEPLOY_USER@$DEPLOY_HOST:$REMOTE_TASK_DIR/task.jar"
;;&
front|front-test|all)
log_info " 上传前端文件..."
$SSH_CMD "rm -rf $REMOTE_FRONT_DIR/dist"
$SCP_CMD -r "$ROOT_DIR/$LOCAL_FRONT_DIST" "$DEPLOY_USER@$DEPLOY_HOST:$REMOTE_FRONT_DIR/dist"
;;
esac
log_info "产物上传完成"
}
# ===== 远程重启服务 =====
restart_services() {
local target="$1"
log_step "远程重启服务..."
case "$target" in
web|backend|all)
log_info " 重启 Web 服务..."
$SSH_CMD "bash $REMOTE_SCRIPTS_DIR/restart-web.sh"
;;&
task|backend|all)
log_info " 重启 Task 服务..."
$SSH_CMD "bash $REMOTE_SCRIPTS_DIR/restart-task.sh $TASK_PROFILE '$TASK_JVM_OPTS'"
;;&
front|front-test|all)
log_info " 重新加载前端..."
$SSH_CMD "bash $REMOTE_SCRIPTS_DIR/restart-front.sh"
;;
esac
log_info "服务重启完成"
}
# ===== 健康检查 =====
healthcheck() {
local target="$1"
log_step "健康检查..."
# Web 服务检查
if [ "$target" = "all" ] || [ "$target" = "web" ] || [ "$target" = "backend" ]; then
log_info " 检查 Web 服务 ($HEALTHCHECK_URL)..."
for i in $(seq 1 $HEALTHCHECK_RETRIES); do
if curl -sf "$HEALTHCHECK_URL" > /dev/null 2>&1; then
log_info " Web 服务健康 (第${i}次检查通过)"
break
fi
if [ $i -eq $HEALTHCHECK_RETRIES ]; then
log_error " Web 服务健康检查失败 (${HEALTHCHECK_RETRIES}次重试)"
return 1
fi
printf "."
sleep $HEALTHCHECK_INTERVAL
done
fi
# 前端检查
if [ "$target" = "all" ] || [ "$target" = "front" ] || [ "$target" = "front-test" ]; then
log_info " 检查前端服务..."
for i in $(seq 1 10); do
if curl -sf "http://$DEPLOY_HOST/" > /dev/null 2>&1; then
log_info " 前端服务健康"
break
fi
if [ $i -eq 10 ]; then
log_error " 前端服务健康检查失败"
return 1
fi
sleep 2
done
fi
log_info "健康检查通过"
return 0
}
# ===== 回滚 =====
rollback() {
log_warn "开始回滚..."
LATEST_BACKUP=$($SSH_CMD "ls -dt $REMOTE_BACKUP_DIR/*/ 2>/dev/null | head -1 | xargs basename 2>/dev/null")
if [ -z "$LATEST_BACKUP" ]; then
log_error "没有可回滚的备份"
exit 1
fi
log_warn "回滚到版本: $LATEST_BACKUP"
$SSH_CMD bash <<RBEOF
BACKUP="$REMOTE_BACKUP_DIR/$LATEST_BACKUP"
if [ -f "\$BACKUP/ROOT.war" ]; then
cp "\$BACKUP/ROOT.war" "$TOMCAT_WEBAPPS/ROOT.war"
echo " 已还原 ROOT.war"
fi
if [ -f "\$BACKUP/task.jar" ]; then
cp "\$BACKUP/task.jar" "$REMOTE_TASK_DIR/task.jar"
echo " 已还原 task.jar"
fi
if [ -d "\$BACKUP/dist" ]; then
rm -rf "$REMOTE_FRONT_DIR/dist"
cp -r "\$BACKUP/dist" "$REMOTE_FRONT_DIR/dist"
echo " 已还原 front/dist"
fi
RBEOF
restart_services "all"
log_info "回滚完成 (版本: $LATEST_BACKUP)"
notify_feishu "⚠️ SmartClean 已回滚" \
"**回滚版本:** $LATEST_BACKUP\\n**服务器:** $DEPLOY_HOST\\n**操作人:** $(whoami)@$(hostname)" \
"yellow"
}
# ===== 主流程 =====
TARGET="${1:-all}"
DEPLOY_START=$(date +%s)
case "$TARGET" in
setup)
check_ssh
setup_server
exit 0
;;
rollback)
check_ssh
rollback
exit 0
;;
status)
check_ssh
show_status
exit 0
;;
all|web|task|backend|front|front-test)
;;
*)
echo "用法: $0 {all|web|task|backend|front|front-test|rollback|setup|status}"
exit 1
;;
esac
echo ""
log_info "======================================"
log_info " SmartClean 自动化部署 (远程服务器)"
log_info " 版本: $VERSION"
log_info " 分支: $BRANCH"
log_info " 提交: $COMMIT"
log_info " 目标: $TARGET"
log_info " 服务器: $DEPLOY_USER@$DEPLOY_HOST"
log_info "======================================"
echo ""
# 1. 检查连接
check_ssh
# 2. 本地构建
build_local "$TARGET"
# 3. 远程备份
backup_remote "$TARGET"
# 4. 上传产物
upload_artifacts "$TARGET"
# 5. 重启服务
restart_services "$TARGET"
# 6. 健康检查
if healthcheck "$TARGET"; then
ELAPSED=$(( $(date +%s) - DEPLOY_START ))
echo ""
log_info "======================================"
log_info " ✅ 部署成功!"
log_info " 版本: $VERSION"
log_info " 耗时: ${ELAPSED}s"
log_info " 前端: http://$DEPLOY_HOST"
log_info " Web: http://$DEPLOY_HOST:8095"
log_info " Task: http://$DEPLOY_HOST:8097"
log_info "======================================"
notify_feishu "✅ SmartClean 部署成功" \
"**版本:** $VERSION\\n**分支:** $BRANCH\\n**提交:** $COMMIT\\n**目标:** $TARGET\\n**服务器:** $DEPLOY_HOST\\n**耗时:** ${ELAPSED}s\\n**操作人:** $(whoami)@$(hostname)" \
"green"
else
log_error "健康检查失败,自动回滚..."
rollback
ELAPSED=$(( $(date +%s) - DEPLOY_START ))
notify_feishu "❌ SmartClean 部署失败(已回滚)" \
"**版本:** $VERSION\\n**分支:** $BRANCH\\n**提交:** $COMMIT\\n**服务器:** $DEPLOY_HOST\\n**耗时:** ${ELAPSED}s\\n**状态:** 健康检查失败,已自动回滚" \
"red"
exit 1
fi