#!/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 </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 <