Files

210 lines
7.7 KiB
Bash
Raw Permalink Normal View History

#!/usr/bin/env bash
# ==============================================================================
# s-file 一键部署/运维脚本(生产环境)
#
# 用法:
# ./sfile-ctl.sh start 启动服务
# ./sfile-ctl.sh stop 停止服务
# ./sfile-ctl.sh restart 重启服务
# ./sfile-ctl.sh status 查看状态
# ./sfile-ctl.sh deploy <jar路径> 发布新版 jar(自动备份旧版并重启)
# ./sfile-ctl.sh log [行数] 查看启动日志(默认 200 行)
# ./sfile-ctl.sh db-upgrade <sql路径> 在达梦库执行升级 SQL(需配置 disql)
# 上线用: db/upgrade-20260915-release.sql
#
# 部署目录结构(脚本首次 start 时自动创建,配置需手工放置):
# /opt/sfile/
# ├── s-file.jar 服务 jar
# ├── config/application.yml 生产配置(模板见 docs/生产部署说明.md)
# ├── backup/ 历史版本备份
# ├── logs/ 日志目录
# └── data/ 上传/摆渡目录(upload/transfer/send/transfer/receive)
# ==============================================================================
set -euo pipefail
# ------------------------- 环境变量(按服务器实际修改) -------------------------
APP_NAME="s-file"
INSTALL_DIR="${SFILE_INSTALL_DIR:-/opt/sfile}"
JAR_FILE="${INSTALL_DIR}/${APP_NAME}.jar"
CONFIG_FILE="${INSTALL_DIR}/config/application.yml"
LOG_DIR="${INSTALL_DIR}/logs"
BACKUP_DIR="${INSTALL_DIR}/backup"
STDOUT_LOG="${LOG_DIR}/stdout.log"
GC_LOG="${LOG_DIR}/gc.log"
# 运行用户(root 部署时可改为专用低权用户,如 sfile)
RUN_USER="${SFILE_RUN_USER:-$(whoami)}"
# JVM 参数:按机器内存调整 -Xmx;生产建议 2g 起
JAVA_OPTS="${SFILE_JAVA_OPTS:--Xms1g -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOG_DIR} -Xloggc:${GC_LOG} -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M}"
# 健康检查地址与超时
HEALTH_URL="http://127.0.0.1:8080/user/info"
START_TIMEOUT=120
# 达梦 disql(db-upgrade 用;达梦容器则为 docker exec -i <容器> /opt/dmdbms/bin/disql ...)
DISQL_CMD="${SFILE_DISQL:-disql <DB_USER>/<DB_PASSWORD>@<DB_HOST>:5236}"
# -----------------------------------------------------------------------------
GREEN='\033[32m'; RED='\033[31m'; YELLOW='\033[33m'; NC='\033[0m'
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
err() { echo -e "${RED}[ERROR]${NC} $*"; }
get_pid() {
pgrep -f "java .*${JAR_FILE}" 2>/dev/null || true
}
require_env() {
[ -f "${JAR_FILE}" ] || { err "jar 不存在: ${JAR_FILE},先用 deploy 发布"; exit 1; }
[ -f "${CONFIG_FILE}" ] || { err "配置不存在: ${CONFIG_FILE}(模板见 docs/生产部署说明.md)"; exit 1; }
# 项目要求 JDK 8 运行(字节码 52.0),防护误用高版本 JDK
if command -v java >/dev/null 2>&1; then
local major
major=$(java -version 2>&1 | head -1 | sed -E 's/.*"([0-9]+)\..*"/\1/')
if [ "${major}" != "1" ] && [ "${major}" != "8" ]; then
err "检测到 java 版本为 ${major},本项目要求 JDK 8(请 export JAVA_HOME 指向 JDK8 并确认 java 命令)"
exit 1
fi
else
err "未找到 java 命令,请安装 JDK 8 并配置 PATH"; exit 1
fi
}
prepare_dirs() {
mkdir -p "${INSTALL_DIR}/config" "${LOG_DIR}" "${BACKUP_DIR}" \
"${INSTALL_DIR}/data/upload" \
"${INSTALL_DIR}/data/transfer/send" \
"${INSTALL_DIR}/data/transfer/receive"
}
start() {
require_env
local pid
pid=$(get_pid)
if [ -n "${pid}" ]; then
warn "${APP_NAME} 已在运行 (PID ${pid}),如需重启请先 stop"
exit 0
fi
prepare_dirs
info "启动 ${APP_NAME} ..."
# workerId 说明:多节点部署时每台机器配置不同的 code.snowflake.workid.<本机IP>,
# 最简单的方式是用环境变量(. 映射为 _),如:
# export CODE_SNOWFLAKE_WORKID_192_168_1_10=1
# shellcheck disable=SC2086
if [ "${RUN_USER}" = "$(whoami)" ]; then
(cd "${INSTALL_DIR}" && nohup java ${JAVA_OPTS} \
-Dspring.config.additional-location=file:${CONFIG_FILE} \
-jar ${JAR_FILE} >> ${STDOUT_LOG} 2>&1 & echo $! > "${INSTALL_DIR}/app.pid")
else
su -s /bin/bash "${RUN_USER}" -c "cd ${INSTALL_DIR} && nohup java ${JAVA_OPTS} \
-Dspring.config.additional-location=file:${CONFIG_FILE} \
-jar ${JAR_FILE} >> ${STDOUT_LOG} 2>&1 & echo \$! > ${INSTALL_DIR}/app.pid"
fi
# 启动健康检查轮询
info "等待服务就绪(最多 ${START_TIMEOUT}s)..."
local waited=0
while [ ${waited} -lt ${START_TIMEOUT} ]; do
if curl -sf -o /dev/null --max-time 3 "${HEALTH_URL}"; then
info "启动成功 (PID $(cat "${INSTALL_DIR}/app.pid")),耗时约 ${waited}s"
info "日志: tail -f ${STDOUT_LOG}"
return 0
fi
# 进程若已退出则立即报错,不干等
if [ -z "$(get_pid)" ]; then
err "进程已退出,最近日志:"
tail -30 "${STDOUT_LOG}"
exit 1
fi
sleep 3; waited=$((waited + 3))
done
err "${START_TIMEOUT}s 内未就绪,请检查日志: tail -100 ${STDOUT_LOG}"
exit 1
}
stop() {
local pid
pid=$(get_pid)
if [ -z "${pid}" ]; then
info "${APP_NAME} 未在运行"
return 0
fi
info "停止 ${APP_NAME} (PID ${pid}) ..."
kill "${pid}"
local waited=0
while kill -0 "${pid}" 2>/dev/null && [ ${waited} -lt 60 ]; do
sleep 2; waited=$((waited + 2))
done
if kill -0 "${pid}" 2>/dev/null; then
warn "60s 未退出,强制 kill -9"
kill -9 "${pid}" 2>/dev/null || true
fi
rm -f "${INSTALL_DIR}/app.pid"
info "已停止"
}
status() {
local pid
pid=$(get_pid)
if [ -z "${pid}" ]; then
info "${APP_NAME}: 未运行"
exit 1
fi
info "${APP_NAME}: 运行中 (PID ${pid})"
if curl -sf -o /dev/null --max-time 3 "${HEALTH_URL}"; then
info "健康检查: 正常"
else
warn "健康检查: 8080 未响应(可能仍在启动)"
fi
info "内存: $(ps -o rss= -p "${pid}" | awk '{printf "%.0fMB", $1/1024}')"
}
deploy() {
local new_jar="$1"
[ -f "${new_jar}" ] || { err "找不到 jar: ${new_jar}"; exit 1; }
prepare_dirs
local ts; ts=$(date +%Y%m%d-%H%M%S)
if [ -f "${JAR_FILE}" ]; then
# 运行中先停
if [ -n "$(get_pid)" ]; then
stop
fi
cp "${JAR_FILE}" "${BACKUP_DIR}/${APP_NAME}-${ts}.jar"
info "旧版本已备份: ${BACKUP_DIR}/${APP_NAME}-${ts}.jar"
fi
cp "${new_jar}" "${JAR_FILE}"
# 保留最近 5 个备份
ls -1t "${BACKUP_DIR}/${APP_NAME}-"*.jar 2>/dev/null | tail -n +6 | xargs -r rm -f
info "新版本已就位: ${JAR_FILE} ($(du -h "${JAR_FILE}" | cut -f1))"
start
}
db_upgrade() {
local sql_file="$1"
[ -f "${sql_file}" ] || { err "找不到 SQL: ${sql_file}"; exit 1; }
case "${DISQL_CMD}" in
*"<DB_USER>"*) err "请先在脚本头部或环境变量 SFILE_DISQL 配置真实 disql 连接串"; exit 1;;
esac
info "执行升级 SQL: ${sql_file}"
# 达梦 disql 执行 SQL 文件语法: disql 用户/密码@host:port \`文件路径\`
${DISQL_CMD} \`"${sql_file}"
info "执行完成,请核对上方输出无 ERROR 后再继续部署"
}
log() {
local lines="${1:-200}"
tail -n "${lines}" "${STDOUT_LOG}" 2>/dev/null || warn "暂无日志: ${STDOUT_LOG}"
}
case "${1:-}" in
start) start ;;
stop) stop ;;
restart) stop; start ;;
status) status ;;
deploy) [ $# -ge 2 ] || { err "用法: $0 deploy <jar路径>"; exit 1; }; deploy "$2" ;;
log) log "${2:-200}" ;;
db-upgrade) [ $# -ge 2 ] || { err "用法: $0 db-upgrade <sql路径>"; exit 1; }; db_upgrade "$2" ;;
*) sed -n '2,20p' "$0"; exit 1 ;;
esac