新增生产统一部署脚本
This commit is contained in:
504
deploy/ccdi_function.sh
Executable file
504
deploy/ccdi_function.sh
Executable file
@@ -0,0 +1,504 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
|
||||||
|
|
||||||
|
# ==================== 生产配置区:按服务器实际路径修改 ====================
|
||||||
|
|
||||||
|
# JDK 安装目录。留空时使用服务器已有 JAVA_HOME;仍为空时使用 PATH 中的 java。
|
||||||
|
BACKEND_JAVA_HOME=""
|
||||||
|
|
||||||
|
# 后端 Jar 所在目录。生产目录结构为:脚本在外层,Jar 位于 backend/ruoyi-admin.jar。
|
||||||
|
APP_HOME="${SCRIPT_DIR}/backend"
|
||||||
|
|
||||||
|
# 后端 Jar 文件名。
|
||||||
|
JAR_NAME="ruoyi-admin.jar"
|
||||||
|
|
||||||
|
# Spring Profile。
|
||||||
|
SPRING_PROFILES_ACTIVE="uat"
|
||||||
|
|
||||||
|
# JVM 参数。
|
||||||
|
JAVA_OPTS="-Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError"
|
||||||
|
|
||||||
|
# 额外应用启动参数,例如:--server.port=8080
|
||||||
|
APP_ARGS=""
|
||||||
|
|
||||||
|
# 停止进程等待秒数。
|
||||||
|
STOP_WAIT_SECONDS=30
|
||||||
|
|
||||||
|
# ==================== 以下为脚本逻辑,一般不需要修改 ====================
|
||||||
|
|
||||||
|
if [ "${APP_HOME#/}" = "${APP_HOME}" ]; then
|
||||||
|
APP_HOME="${SCRIPT_DIR}/${APP_HOME}"
|
||||||
|
fi
|
||||||
|
APP_HOME="${APP_HOME%/}"
|
||||||
|
JAR_PATH="${APP_HOME}/${JAR_NAME}"
|
||||||
|
FRONTEND_DIR="${SCRIPT_DIR}/frontend"
|
||||||
|
LOG_DIR="${APP_HOME}/logs"
|
||||||
|
CONSOLE_LOG="${LOG_DIR}/backend-console.log"
|
||||||
|
PID_FILE="${LOG_DIR}/backend-java.pid"
|
||||||
|
APP_MARKER="-Dccdi.backend.prod.home=${APP_HOME}"
|
||||||
|
JAVA_CMD="java"
|
||||||
|
RELATIVE_JAR_PATH=""
|
||||||
|
TIMESTAMP=$(date '+%Y%m%d%H%M%S')
|
||||||
|
BACKUP_ROOT="${SCRIPT_DIR}/backups"
|
||||||
|
BACKUP_DIR="${BACKUP_ROOT}/${TIMESTAMP}"
|
||||||
|
WORK_ROOT="${SCRIPT_DIR}/.deploy-work"
|
||||||
|
WORK_DIR="${WORK_ROOT}/release-${TIMESTAMP}"
|
||||||
|
RELEASE_ZIP=""
|
||||||
|
RELEASE_JAR=""
|
||||||
|
RELEASE_DIST_ZIP=""
|
||||||
|
FRONTEND_SOURCE_DIR=""
|
||||||
|
|
||||||
|
case "${APP_HOME}" in
|
||||||
|
"${SCRIPT_DIR}"/*)
|
||||||
|
RELATIVE_JAR_PATH="${APP_HOME#${SCRIPT_DIR}/}/${JAR_NAME}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
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'
|
||||||
|
用法: ./ccdi_function.sh [deploy|restart|stop] [上线压缩包路径]
|
||||||
|
|
||||||
|
命令:
|
||||||
|
deploy 备份 backend/ 与 frontend/,部署上线包,然后重启后端并持续输出日志
|
||||||
|
restart 停止旧后端进程,启动 backend/ruoyi-admin.jar,并持续输出日志
|
||||||
|
stop 停止当前脚本目录对应的 backend/ruoyi-admin.jar 进程
|
||||||
|
|
||||||
|
上线包要求:
|
||||||
|
未传上线压缩包路径时,脚本会自动使用当前脚本目录下唯一的 .zip 文件。
|
||||||
|
上线压缩包根层必须包含 ruoyi-admin.jar 和 dist.zip。
|
||||||
|
dist.zip 解压后必须包含 dist/index.html。
|
||||||
|
|
||||||
|
生产目录示例:
|
||||||
|
ccdi_function.sh
|
||||||
|
backend/
|
||||||
|
frontend/
|
||||||
|
ccdi_YYYYMMDD.zip
|
||||||
|
|
||||||
|
说明:
|
||||||
|
restart 和 deploy 启动成功后会持续输出 backend/logs/backend-console.log。
|
||||||
|
按 Ctrl+C 仅退出日志查看,不会停止已启动的后端进程。
|
||||||
|
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_java_cmd() {
|
||||||
|
configured_java_home="${BACKEND_JAVA_HOME}"
|
||||||
|
if [ -z "${configured_java_home}" ]; then
|
||||||
|
configured_java_home="${JAVA_HOME:-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${configured_java_home}" ]; then
|
||||||
|
configured_java_home="${configured_java_home%/}"
|
||||||
|
if [ ! -x "${configured_java_home}/bin/java" ]; then
|
||||||
|
log_error "配置的 JAVA_HOME 无效,未找到可执行文件: ${configured_java_home}/bin/java"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
JAVA_HOME="${configured_java_home}"
|
||||||
|
export JAVA_HOME
|
||||||
|
JAVA_CMD="${JAVA_HOME}/bin/java"
|
||||||
|
else
|
||||||
|
require_command "java"
|
||||||
|
JAVA_CMD="java"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "使用 Java 命令: ${JAVA_CMD}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_process_table() {
|
||||||
|
if ! ps -ef 2>/dev/null; then
|
||||||
|
log_error "执行 ps -ef 失败,无法扫描旧进程"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
is_backend_process_line() {
|
||||||
|
process_line="$1"
|
||||||
|
|
||||||
|
case "${process_line}" in
|
||||||
|
*"<defunct>"*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
*" -jar ${JAR_PATH}"*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*"${APP_MARKER}"*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -n "${RELATIVE_JAR_PATH}" ]; then
|
||||||
|
case "${process_line}" in
|
||||||
|
*" -jar ${RELATIVE_JAR_PATH}"*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
is_managed_pid() {
|
||||||
|
check_pid="$1"
|
||||||
|
if [ -z "${check_pid}" ] || ! kill -0 "${check_pid}" 2>/dev/null; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! process_table=$(get_process_table); then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r process_line; do
|
||||||
|
set -- ${process_line}
|
||||||
|
line_pid="${2:-}"
|
||||||
|
if [ "${line_pid}" = "${check_pid}" ] && is_backend_process_line "${process_line}"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done <<EOF
|
||||||
|
${process_table}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_pids() {
|
||||||
|
all_pids=""
|
||||||
|
unique_pids=""
|
||||||
|
|
||||||
|
if ! process_table=$(get_process_table); then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "${PID_FILE}" ]; then
|
||||||
|
pid_file_value=$(sed -n '1p' "${PID_FILE}" 2>/dev/null || true)
|
||||||
|
if is_managed_pid "${pid_file_value}"; then
|
||||||
|
all_pids="${all_pids} ${pid_file_value}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r process_line; do
|
||||||
|
set -- ${process_line}
|
||||||
|
scan_pid="${2:-}"
|
||||||
|
case "${scan_pid}" in
|
||||||
|
''|*[!0-9]*)
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if is_backend_process_line "${process_line}"; then
|
||||||
|
all_pids="${all_pids} ${scan_pid}"
|
||||||
|
fi
|
||||||
|
done <<EOF
|
||||||
|
${process_table}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
for scan_pid in ${all_pids}; do
|
||||||
|
case " ${unique_pids} " in
|
||||||
|
*" ${scan_pid} "*)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
unique_pids="${unique_pids} ${scan_pid}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '%s\n' "${unique_pids}" | xargs 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
start_backend() {
|
||||||
|
resolve_java_cmd
|
||||||
|
|
||||||
|
if [ ! -f "${JAR_PATH}" ]; then
|
||||||
|
log_error "未找到后端 Jar: ${JAR_PATH}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! running_pids=$(collect_pids); then
|
||||||
|
log_error "扫描后端进程失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -n "${running_pids}" ]; then
|
||||||
|
log_error "检测到后端已在运行: ${running_pids}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${LOG_DIR}"
|
||||||
|
printf '\n===== %s start =====\n' "$(timestamp)" >>"${CONSOLE_LOG}"
|
||||||
|
|
||||||
|
profile_arg=""
|
||||||
|
if [ -n "${SPRING_PROFILES_ACTIVE}" ]; then
|
||||||
|
profile_arg="--spring.profiles.active=${SPRING_PROFILES_ACTIVE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "开始启动后端 Jar: ${JAR_PATH}"
|
||||||
|
nohup "${JAVA_CMD}" "${APP_MARKER}" ${JAVA_OPTS} -jar "${JAR_PATH}" ${profile_arg} ${APP_ARGS} >>"${CONSOLE_LOG}" 2>&1 &
|
||||||
|
echo $! >"${PID_FILE}"
|
||||||
|
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
starter_pid=$(sed -n '1p' "${PID_FILE}" 2>/dev/null || true)
|
||||||
|
if [ -z "${starter_pid}" ] || ! kill -0 "${starter_pid}" 2>/dev/null; then
|
||||||
|
log_error "启动命令未保持运行,请检查日志: ${CONSOLE_LOG}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "后端启动完成,PID: ${starter_pid}"
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_backend() {
|
||||||
|
if ! pids=$(collect_pids); then
|
||||||
|
log_error "扫描后端进程失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${pids}" ]; then
|
||||||
|
log_info "未发现运行中的后端进程"
|
||||||
|
rm -f "${PID_FILE}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "准备停止后端进程: ${pids}"
|
||||||
|
for pid in ${pids}; do
|
||||||
|
kill -TERM "${pid}" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
elapsed=0
|
||||||
|
remaining_pids="${pids}"
|
||||||
|
while [ -n "${remaining_pids}" ] && [ "${elapsed}" -lt "${STOP_WAIT_SECONDS}" ]; do
|
||||||
|
sleep 1
|
||||||
|
elapsed=$((elapsed + 1))
|
||||||
|
remaining_pids=""
|
||||||
|
for pid in ${pids}; do
|
||||||
|
if kill -0 "${pid}" 2>/dev/null; then
|
||||||
|
remaining_pids="${remaining_pids} ${pid}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
remaining_pids=$(printf '%s\n' "${remaining_pids}" | xargs 2>/dev/null || true)
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "${remaining_pids}" ]; then
|
||||||
|
log_info "仍有进程未退出,执行强制停止: ${remaining_pids}"
|
||||||
|
for pid in ${remaining_pids}; do
|
||||||
|
kill -KILL "${pid}" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "${PID_FILE}"
|
||||||
|
log_info "后端停止完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
follow_logs() {
|
||||||
|
require_command "tail"
|
||||||
|
|
||||||
|
mkdir -p "${LOG_DIR}"
|
||||||
|
touch "${CONSOLE_LOG}"
|
||||||
|
log_info "持续输出日志中,按 Ctrl+C 仅退出日志查看,不会停止后端进程"
|
||||||
|
tail -n 200 -F "${CONSOLE_LOG}"
|
||||||
|
}
|
||||||
|
|
||||||
|
restart_action() {
|
||||||
|
stop_backend
|
||||||
|
start_backend
|
||||||
|
follow_logs
|
||||||
|
}
|
||||||
|
|
||||||
|
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_deploy_layout() {
|
||||||
|
if [ ! -d "${APP_HOME}" ]; then
|
||||||
|
log_error "未找到后端目录: ${APP_HOME}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "${FRONTEND_DIR}" ]; then
|
||||||
|
log_error "未找到前端目录: ${FRONTEND_DIR}"
|
||||||
|
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 "${APP_HOME}" "${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/${JAR_NAME}"
|
||||||
|
RELEASE_DIST_ZIP="${WORK_DIR}/release/dist.zip"
|
||||||
|
if [ ! -f "${RELEASE_JAR}" ]; then
|
||||||
|
log_error "上线压缩包根层缺少 ${JAR_NAME}"
|
||||||
|
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="${APP_HOME}/${JAR_NAME}"
|
||||||
|
deploying_jar="${APP_HOME}/.${JAR_NAME}.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}"
|
||||||
|
}
|
||||||
|
|
||||||
|
deploy_action() {
|
||||||
|
RELEASE_ZIP="${1:-}"
|
||||||
|
|
||||||
|
require_command "find"
|
||||||
|
require_command "unzip"
|
||||||
|
|
||||||
|
resolve_release_zip
|
||||||
|
assert_deploy_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 "开始重启后端并输出日志"
|
||||||
|
restart_action
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
action="${1:-help}"
|
||||||
|
case "${action}" in
|
||||||
|
deploy)
|
||||||
|
shift
|
||||||
|
if [ "$#" -gt 1 ]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
deploy_action "${1:-}"
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
shift
|
||||||
|
if [ "$#" -ne 0 ]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
restart_action
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
shift
|
||||||
|
if [ "$#" -ne 0 ]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
stop_backend
|
||||||
|
;;
|
||||||
|
-h|--help|help)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
# 生产统一脚本实施记录
|
||||||
|
|
||||||
|
## 保存路径确认
|
||||||
|
|
||||||
|
- 本次为生产部署与启停脚本改动,实施记录保存到 `docs/reports/implementation/`,符合仓库实施文档目录规范。
|
||||||
|
- 生产脚本保存到 `deploy/ccdi_function.sh`,与既有生产部署脚本位于同一目录,便于上线包或生产目录统一维护。
|
||||||
|
|
||||||
|
## 修改目标
|
||||||
|
|
||||||
|
- 新增一个生产统一入口脚本 `ccdi_function.sh`。
|
||||||
|
- 脚本需要支持 `deploy`、`restart`、`stop`。
|
||||||
|
- `deploy` 集成生产备份、部署、重启流程,参考 `deploy/deploy-release-prod.sh`。
|
||||||
|
- `restart` 和 `stop` 集成生产后端进程控制流程,参考 `deploy/start-java-backend-prod.sh`。
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 新增 `deploy/ccdi_function.sh`
|
||||||
|
- 使用 `/bin/sh` 写法,避免服务器执行 `sh ccdi_function.sh` 时出现 Bash 专属语法兼容问题。
|
||||||
|
- 保留脚本顶部生产配置区,可直接配置 `BACKEND_JAVA_HOME`、`APP_HOME`、`JAR_NAME`、`SPRING_PROFILES_ACTIVE`、`JAVA_OPTS`、`APP_ARGS` 和 `STOP_WAIT_SECONDS`。
|
||||||
|
- `deploy` 支持显式传入上线包路径:`./ccdi_function.sh deploy /path/to/ccdi_YYYYMMDD.zip`。
|
||||||
|
- `deploy` 未传上线包路径时,自动使用脚本同级目录下唯一的 `.zip` 文件,并排除 `dist.zip`。
|
||||||
|
- 上线包根层校验 `ruoyi-admin.jar` 和 `dist.zip`。
|
||||||
|
- `dist.zip` 解压后校验 `dist/index.html`。
|
||||||
|
- 部署前备份当前 `backend/` 与 `frontend/` 到 `backups/YYYYMMDDHHMMSS/`。
|
||||||
|
- 后端部署为原子替换 `backend/ruoyi-admin.jar`。
|
||||||
|
- 前端部署为清空 `frontend/` 后复制 `dist/` 内文件。
|
||||||
|
- `deploy` 完成文件部署后直接调用脚本内 `restart_action`,不再依赖外部 `start-java-backend-prod.sh`。
|
||||||
|
- `restart` 流程为 `stop_backend`、`start_backend`、`follow_logs`。
|
||||||
|
- `stop` 使用 `ps -ef` 扫描当前生产目录对应的 `backend/ruoyi-admin.jar` 进程,忽略 `<defunct>` 行,并清理 PID 文件。
|
||||||
|
- `restart` 与 `deploy` 启动成功后持续输出 `backend/logs/backend-console.log`,按 `Ctrl+C` 仅退出日志查看,不停止后端进程。
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
生产目录示例:
|
||||||
|
|
||||||
|
```text
|
||||||
|
上线目录/
|
||||||
|
├── ccdi_function.sh
|
||||||
|
├── backend/
|
||||||
|
├── frontend/
|
||||||
|
└── ccdi_YYYYMMDD.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
部署:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./ccdi_function.sh deploy
|
||||||
|
./ccdi_function.sh deploy /path/to/ccdi_YYYYMMDD.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
重启:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./ccdi_function.sh restart
|
||||||
|
```
|
||||||
|
|
||||||
|
停止:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./ccdi_function.sh stop
|
||||||
|
```
|
||||||
|
|
||||||
|
## 验证记录
|
||||||
|
|
||||||
|
- 执行 `sh -n deploy/ccdi_function.sh`
|
||||||
|
- 结果:通过
|
||||||
|
- 说明:脚本 Shell 语法正确。
|
||||||
|
- 执行 `sh deploy/ccdi_function.sh --help`
|
||||||
|
- 结果:通过
|
||||||
|
- 说明:帮助信息正常输出,包含 `deploy`、`restart`、`stop` 三个入口。
|
||||||
|
- 使用 `/tmp` 构造最小生产目录、旧 `backend/`、旧 `frontend/`、上线压缩包、假 Java 和假日志输出命令后执行 `deploy`
|
||||||
|
- 结果:通过
|
||||||
|
- 说明:已验证旧文件备份、新 Jar 覆盖、前端 `dist/` 文件部署、部署后重启流程。
|
||||||
|
- 在同一 `/tmp` 验证环境执行 `stop`
|
||||||
|
- 结果:通过
|
||||||
|
- 说明:已验证 `stop` 可停止由统一脚本启动的后端进程。
|
||||||
|
|
||||||
|
## 影响范围
|
||||||
|
|
||||||
|
- 仅新增生产统一脚本与本实施记录。
|
||||||
|
- 不修改 Java 业务代码、前端业务代码、数据库脚本和既有生产脚本。
|
||||||
Reference in New Issue
Block a user