chore: 导入后端 s-file(alita-bjy-uygur, f分支)至 backend/
This commit is contained in:
209
backend/scripts/sfile-ctl.sh
Executable file
209
backend/scripts/sfile-ctl.sh
Executable file
@@ -0,0 +1,209 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user