1. 用户管理:新增启用(user/enable)/禁用(user/disable)/注销(user/admin_cancel)接口,
平台超管可操作全部用户,机构管理员限本机构;注销带分布式锁、禁自己/超管守卫
2. 禁用/注销立即踢下线:Redis黑名单(user:disabled:{uid}) + JWT过滤器拦截,
TTL与token有效期一致
3. 系统管理员查看各机构账号:GET user/page/org_account(机构/账号/手机号等筛选)
4. 按手机号查上传/发送/接收记录:GET user_file/record/page_by_phone,
发送/接收方向按部署侧 file.transfer.location 自动映射
5. 机构上传配置:新表 x_upload_config + upload_config/info|modify|check 接口,
上传总开关/后缀白名单/单文件大小限制,网盘落库时强制校验
6. 加固:UserInfoDTO.setPassword 缺省密码未配置时不再抛 rawPassword cannot be null
7. 新增 x_upload_config 建表脚本(upgrade-20260915-release.sql 为上线版)、
生产部署说明、前端接口文档×2、运维脚本 sfile-ctl.sh、AGENTS.md
210 lines
7.7 KiB
Bash
Executable File
210 lines
7.7 KiB
Bash
Executable File
#!/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
|