#!/bin/sh set -eu SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd) BACKEND_DIR="${SCRIPT_DIR}/backend" FRONTEND_DIR="${SCRIPT_DIR}/frontend" START_SCRIPT="${SCRIPT_DIR}/start-java-backend-prod.sh" BACKUP_ROOT="${SCRIPT_DIR}/backups" WORK_ROOT="${SCRIPT_DIR}/.deploy-work" TIMESTAMP=$(date '+%Y%m%d%H%M%S') BACKUP_DIR="${BACKUP_ROOT}/${TIMESTAMP}" WORK_DIR="${WORK_ROOT}/release-${TIMESTAMP}" RELEASE_ZIP="${1:-}" RELEASE_JAR="" RELEASE_DIST_ZIP="" FRONTEND_SOURCE_DIR="" timestamp() { date '+%Y-%m-%d %H:%M:%S' } log_info() { printf '[%s] %s\n' "$(timestamp)" "$1" } log_error() { printf '[%s] %s\n' "$(timestamp)" "$1" >&2 } usage() { cat <<'EOF' 用法: ./deploy-release-prod.sh [上线压缩包路径] 目录要求: deploy-release-prod.sh start-java-backend-prod.sh backend/ frontend/ ccdi_YYYYMMDD.zip 说明: 未传上线压缩包路径时,脚本会自动使用当前脚本目录下唯一的 .zip 文件。 上线压缩包根层必须包含 ruoyi-admin.jar 和 dist.zip。 EOF } require_command() { if ! command -v "$1" >/dev/null 2>&1; then log_error "缺少命令: $1" exit 1 fi } resolve_path() { input_path="$1" case "${input_path}" in /*) printf '%s\n' "${input_path}" ;; *) input_dir=$(dirname "${input_path}") input_base=$(basename "${input_path}") printf '%s/%s\n' "$(CDPATH= cd -- "${input_dir}" && pwd)" "${input_base}" ;; esac } resolve_release_zip() { if [ -n "${RELEASE_ZIP}" ]; then RELEASE_ZIP=$(resolve_path "${RELEASE_ZIP}") else mkdir -p "${WORK_ROOT}" candidate_file="${WORK_ROOT}/zip-candidates-${TIMESTAMP}.txt" find "${SCRIPT_DIR}" -maxdepth 1 -type f -name '*.zip' ! -name 'dist.zip' | sort > "${candidate_file}" candidate_count=$(wc -l < "${candidate_file}" | tr -d ' ') if [ "${candidate_count}" -eq 0 ]; then log_error "未在脚本目录找到上线压缩包,请传入压缩包路径" exit 1 fi if [ "${candidate_count}" -gt 1 ]; then log_error "脚本目录存在多个上线压缩包,请显式传入压缩包路径" cat "${candidate_file}" >&2 exit 1 fi RELEASE_ZIP=$(sed -n '1p' "${candidate_file}") rm -f "${candidate_file}" fi if [ ! -f "${RELEASE_ZIP}" ]; then log_error "上线压缩包不存在: ${RELEASE_ZIP}" exit 1 fi } assert_layout() { if [ ! -d "${BACKEND_DIR}" ]; then log_error "未找到后端目录: ${BACKEND_DIR}" exit 1 fi if [ ! -d "${FRONTEND_DIR}" ]; then log_error "未找到前端目录: ${FRONTEND_DIR}" exit 1 fi if [ ! -f "${START_SCRIPT}" ]; then log_error "未找到后端启动脚本: ${START_SCRIPT}" exit 1 fi } backup_dir() { source_dir="$1" target_dir="$2" mkdir -p "${target_dir}" if [ -n "$(find "${source_dir}" -mindepth 1 -maxdepth 1 -print -quit)" ]; then cp -a "${source_dir}/." "${target_dir}/" log_info "已备份 ${source_dir} 到 ${target_dir}" else log_info "目录为空,已创建空备份目录: ${target_dir}" fi } backup_current_files() { mkdir -p "${BACKUP_DIR}" backup_dir "${BACKEND_DIR}" "${BACKUP_DIR}/backend" backup_dir "${FRONTEND_DIR}" "${BACKUP_DIR}/frontend" } extract_release_package() { mkdir -p "${WORK_DIR}/release" "${WORK_DIR}/frontend" log_info "开始解压上线压缩包: ${RELEASE_ZIP}" unzip -q "${RELEASE_ZIP}" -d "${WORK_DIR}/release" RELEASE_JAR="${WORK_DIR}/release/ruoyi-admin.jar" RELEASE_DIST_ZIP="${WORK_DIR}/release/dist.zip" if [ ! -f "${RELEASE_JAR}" ]; then log_error "上线压缩包根层缺少 ruoyi-admin.jar" exit 1 fi if [ ! -f "${RELEASE_DIST_ZIP}" ]; then log_error "上线压缩包根层缺少 dist.zip" exit 1 fi unzip -q "${RELEASE_DIST_ZIP}" -d "${WORK_DIR}/frontend" FRONTEND_SOURCE_DIR="${WORK_DIR}/frontend/dist" if [ ! -f "${FRONTEND_SOURCE_DIR}/index.html" ]; then log_error "dist.zip 解压后未找到 dist/index.html" exit 1 fi } deploy_backend() { target_jar="${BACKEND_DIR}/ruoyi-admin.jar" deploying_jar="${BACKEND_DIR}/.ruoyi-admin.jar.deploying" cp "${RELEASE_JAR}" "${deploying_jar}" mv "${deploying_jar}" "${target_jar}" log_info "后端 Jar 已部署: ${target_jar}" } deploy_frontend() { find "${FRONTEND_DIR}" -mindepth 1 -maxdepth 1 -exec rm -rf {} + cp -a "${FRONTEND_SOURCE_DIR}/." "${FRONTEND_DIR}/" log_info "前端文件已部署到: ${FRONTEND_DIR}" } cleanup_work_dir() { rm -rf "${WORK_DIR}" } main() { case "${1:-}" in -h|--help|help) usage exit 0 ;; esac if [ "$#" -gt 1 ]; then usage exit 1 fi require_command "unzip" require_command "find" resolve_release_zip assert_layout backup_current_files trap cleanup_work_dir 0 extract_release_package deploy_backend deploy_frontend cleanup_work_dir trap - 0 log_info "部署完成,备份目录: ${BACKUP_DIR}" log_info "开始重启后端并输出日志" bash "${START_SCRIPT}" restart } main "$@"