完善贷款定价前后端实现与联调

This commit is contained in:
wkc
2026-04-03 09:45:15 +08:00
parent 351fae8cd3
commit 541969a837
19 changed files with 1098 additions and 4 deletions

BIN
bin/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -4,7 +4,7 @@ set -eu
JAVA_BIN="/home/webapp/env/java/bin/java"
BACKEND_PORT=63310
SPRING_PROFILE="pro"
SPRING_PROFILE="uat"
JAVA_OPTS="-Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError"
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd)

245
bin/prod/deploy_release.sh Executable file
View File

@@ -0,0 +1,245 @@
#!/bin/sh
set -eu
WEBAPP_ROOT="/home/webapp"
ENV_ROOT="$WEBAPP_ROOT/env"
APP_ROOT="$WEBAPP_ROOT/loan-pricing"
JAVA_HOME="$ENV_ROOT/java"
NGINX_HOME="$ENV_ROOT/nginx"
NGINX_CONF="$NGINX_HOME/conf/nginx.conf"
BACKEND_DIR="$APP_ROOT/backend"
FRONTEND_DIR="$APP_ROOT/frontend"
FRONTEND_DIST_DIR="$FRONTEND_DIR/dist"
BACKUP_DIR="$APP_ROOT/backup"
LOG_DIR="$APP_ROOT/logs"
RUN_DIR="$APP_ROOT/run"
TMP_DIR="$APP_ROOT/tmp"
BACKEND_JAR="$BACKEND_DIR/ruoyi-admin.jar"
FRONTEND_PORT=63311
JAVA_RESTART_SCRIPT="$WEBAPP_ROOT/restart_java.sh"
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'
用法:
./bin/prod/deploy_release.sh <发布压缩包路径>
EOF
}
require_root() {
if [ "$(id -u)" -ne 0 ]; then
log_error "请使用 root 用户执行部署脚本"
exit 1
fi
}
require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
log_error "缺少命令: $1"
exit 1
fi
}
ensure_runtime_dirs() {
mkdir -p "$BACKEND_DIR" "$FRONTEND_DIR" "$BACKUP_DIR" "$LOG_DIR" "$RUN_DIR" "$TMP_DIR"
}
cleanup() {
if [ -n "${WORK_DIR:-}" ] && [ -d "$WORK_DIR" ]; then
rm -rf "$WORK_DIR"
fi
}
extract_release_package() {
release_archive="$1"
release_extract_dir="$2"
mkdir -p "$release_extract_dir"
case "$release_archive" in
*.zip)
unzip -oq "$release_archive" -d "$release_extract_dir"
;;
*.tar.gz|*.tgz)
tar -xzf "$release_archive" -C "$release_extract_dir"
;;
*.tar)
tar -xf "$release_archive" -C "$release_extract_dir"
;;
*)
log_error "不支持的发布包格式: $release_archive"
exit 1
;;
esac
}
assert_single_file() {
search_dir="$1"
file_name="$2"
description="$3"
count=$(find "$search_dir" -type f -name "$file_name" | wc -l | tr -d ' ')
if [ "$count" -ne 1 ]; then
log_error "$description 数量不正确,期望 1 个,实际 $count"
exit 1
fi
find "$search_dir" -type f -name "$file_name" | head -n 1
}
assert_single_jar() {
search_dir="$1"
count=$(find "$search_dir" -type f -name '*.jar' | wc -l | tr -d ' ')
if [ "$count" -ne 1 ]; then
log_error "后端 jar 数量不正确,期望 1 个,实际 $count"
exit 1
fi
find "$search_dir" -type f -name '*.jar' | head -n 1
}
backup_current_release() {
backup_stamp=$(date "+%Y%m%d%H%M%S")
CURRENT_BACKUP_DIR="$BACKUP_DIR/$backup_stamp"
mkdir -p "$CURRENT_BACKUP_DIR/backend" "$CURRENT_BACKUP_DIR/frontend"
if [ -f "$BACKEND_JAR" ]; then
cp -a "$BACKEND_JAR" "$CURRENT_BACKUP_DIR/backend/"
fi
if [ -d "$FRONTEND_DIST_DIR" ]; then
cp -a "$FRONTEND_DIST_DIR" "$CURRENT_BACKUP_DIR/frontend/"
fi
log_info "旧版本已备份到: $CURRENT_BACKUP_DIR"
}
deploy_backend() {
source_jar="$1"
rm -f "$BACKEND_DIR"/*.jar
cp "$source_jar" "$BACKEND_JAR"
}
resolve_frontend_source_dir() {
unzip_dir="$1"
if [ -f "$unzip_dir/index.html" ]; then
printf '%s\n' "$unzip_dir"
return 0
fi
if [ -f "$unzip_dir/dist/index.html" ]; then
printf '%s\n' "$unzip_dir/dist"
return 0
fi
candidate=$(find "$unzip_dir" -type f -name 'index.html' | head -n 1)
if [ -z "${candidate:-}" ]; then
log_error "dist.zip 解压后未找到 index.html"
exit 1
fi
dirname "$candidate"
}
deploy_frontend() {
dist_zip="$1"
dist_unpack_dir="$WORK_DIR/frontend"
mkdir -p "$dist_unpack_dir"
unzip -oq "$dist_zip" -d "$dist_unpack_dir"
frontend_source_dir=$(resolve_frontend_source_dir "$dist_unpack_dir")
rm -rf "$FRONTEND_DIST_DIR"
mkdir -p "$FRONTEND_DIST_DIR"
cp -a "$frontend_source_dir"/. "$FRONTEND_DIST_DIR"/
}
reload_nginx() {
nginx_pid_file="$RUN_DIR/nginx.pid"
"$NGINX_HOME/sbin/nginx" -t -c "$NGINX_CONF"
if [ -f "$nginx_pid_file" ]; then
nginx_pid=$(cat "$nginx_pid_file" 2>/dev/null || true)
if [ -n "${nginx_pid:-}" ] && kill -0 "$nginx_pid" 2>/dev/null; then
"$NGINX_HOME/sbin/nginx" -c "$NGINX_CONF" -s reload
log_info "Nginx 已重载,前端端口: $FRONTEND_PORT"
return 0
fi
fi
"$NGINX_HOME/sbin/nginx" -c "$NGINX_CONF"
log_info "Nginx 已启动,前端端口: $FRONTEND_PORT"
}
main() {
if [ "$#" -ne 1 ]; then
usage
exit 1
fi
require_root
require_command tar
require_command unzip
require_command find
release_archive="$1"
if [ ! -f "$release_archive" ]; then
log_error "发布压缩包不存在: $release_archive"
exit 1
fi
if [ ! -x "$JAVA_HOME/bin/java" ]; then
log_error "未检测到 Java请先执行 ./bin/prod/install_env.sh"
exit 1
fi
if [ ! -x "$NGINX_HOME/sbin/nginx" ]; then
log_error "未检测到 Nginx请先执行 ./bin/prod/install_env.sh"
exit 1
fi
if [ ! -x "$JAVA_RESTART_SCRIPT" ]; then
log_error "未检测到 Java 重启脚本: $JAVA_RESTART_SCRIPT"
exit 1
fi
ensure_runtime_dirs
WORK_DIR=$(mktemp -d "$TMP_DIR/release.XXXXXX")
trap cleanup EXIT INT TERM
extract_release_package "$release_archive" "$WORK_DIR/package"
backend_jar_source=$(assert_single_jar "$WORK_DIR/package")
frontend_dist_source=$(assert_single_file "$WORK_DIR/package" 'dist.zip' '前端 dist.zip')
backup_current_release
"$JAVA_RESTART_SCRIPT" stop
deploy_backend "$backend_jar_source"
deploy_frontend "$frontend_dist_source"
"$JAVA_RESTART_SCRIPT" start
reload_nginx
log_info "部署完成"
log_info "后端 jar: $BACKEND_JAR"
log_info "前端目录: $FRONTEND_DIST_DIR"
log_info "备份目录: $CURRENT_BACKUP_DIR"
}
main "$@"

244
bin/prod/install_env.sh Executable file
View File

@@ -0,0 +1,244 @@
#!/bin/sh
set -eu
WEBAPP_ROOT="/home/webapp"
ENV_ROOT="$WEBAPP_ROOT/env"
APP_ROOT="$WEBAPP_ROOT/loan-pricing"
JAVA_HOME="$ENV_ROOT/java"
NGINX_HOME="$ENV_ROOT/nginx"
NGINX_CONF="$NGINX_HOME/conf/nginx.conf"
BACKEND_DIR="$APP_ROOT/backend"
FRONTEND_DIR="$APP_ROOT/frontend"
BACKUP_DIR="$APP_ROOT/backup"
LOG_DIR="$APP_ROOT/logs"
RUN_DIR="$APP_ROOT/run"
TMP_DIR="$APP_ROOT/tmp"
BACKEND_PORT=63310
FRONTEND_PORT=63311
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
}
require_root() {
if [ "$(id -u)" -ne 0 ]; then
log_error "请使用 root 用户执行安装脚本"
exit 1
fi
}
require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
log_error "缺少命令: $1"
exit 1
fi
}
ensure_base_dirs() {
mkdir -p "$ENV_ROOT" "$BACKEND_DIR" "$FRONTEND_DIR" "$BACKUP_DIR" "$LOG_DIR" "$RUN_DIR" "$TMP_DIR"
}
find_archive() {
search_kind="$1"
found=""
case "$search_kind" in
java)
set -- "$WEBAPP_ROOT/openjdk" "$WEBAPP_ROOT"
patterns='openjdk*.tar.gz openjdk*.tgz jdk*.tar.gz jdk*.tgz'
;;
nginx)
set -- "$WEBAPP_ROOT/nginx" "$ENV_ROOT" "$WEBAPP_ROOT"
patterns='nginx-*.tar.gz nginx-*.tgz'
;;
*)
log_error "未知的安装包类型: $search_kind"
exit 1
;;
esac
for dir in "$@"; do
if [ ! -d "$dir" ]; then
continue
fi
for pattern in $patterns; do
candidate=$(find "$dir" -maxdepth 1 -type f -name "$pattern" | sort | head -n 1)
if [ -n "${candidate:-}" ]; then
found="$candidate"
break
fi
done
if [ -n "${found:-}" ]; then
break
fi
done
if [ -z "${found:-}" ]; then
log_error "未找到 $search_kind 安装包"
exit 1
fi
printf '%s\n' "$found"
}
install_yum_dependencies() {
require_command yum
log_info "安装系统依赖"
yum install -y \
gcc \
make \
pcre \
pcre-devel \
zlib \
zlib-devel \
openssl \
openssl-devel \
tar \
gzip \
unzip \
which \
findutils \
procps-ng \
iproute
}
install_java() {
java_archive="$1"
log_info "安装 Java: $java_archive"
rm -rf "$JAVA_HOME"
mkdir -p "$JAVA_HOME"
tar -xzf "$java_archive" -C "$JAVA_HOME" --strip-components=1
if [ ! -x "$JAVA_HOME/bin/java" ]; then
log_error "Java 安装失败,未找到 $JAVA_HOME/bin/java"
exit 1
fi
"$JAVA_HOME/bin/java" -version >/dev/null 2>&1
}
install_nginx() {
nginx_archive="$1"
build_dir=$(mktemp -d "$ENV_ROOT/nginx-build.XXXXXX")
log_info "编译安装 Nginx: $nginx_archive"
rm -rf "$NGINX_HOME"
mkdir -p "$NGINX_HOME"
tar -xzf "$nginx_archive" -C "$build_dir"
source_dir=$(find "$build_dir" -mindepth 1 -maxdepth 1 -type d | head -n 1)
if [ -z "${source_dir:-}" ]; then
rm -rf "$build_dir"
log_error "Nginx 源码目录解析失败"
exit 1
fi
jobs=$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 1)
(
cd "$source_dir"
./configure --prefix="$NGINX_HOME" --with-http_ssl_module
make -j"$jobs"
make install
)
rm -rf "$build_dir"
if [ ! -x "$NGINX_HOME/sbin/nginx" ]; then
log_error "Nginx 安装失败,未找到 $NGINX_HOME/sbin/nginx"
exit 1
fi
}
write_nginx_conf() {
log_info "写入 Nginx 配置: $NGINX_CONF"
mkdir -p "$NGINX_HOME/conf" "$NGINX_HOME/logs" "$FRONTEND_DIR/dist"
cat > "$NGINX_CONF" <<EOF
user nobody;
worker_processes 1;
error_log $LOG_DIR/nginx-error.log warn;
pid $RUN_DIR/nginx.pid;
events {
worker_connections 1024;
}
http {
include $NGINX_HOME/conf/mime.types;
default_type application/octet-stream;
log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" '
'\$status \$body_bytes_sent "\$http_referer" '
'"\$http_user_agent" "\$http_x_forwarded_for"';
access_log $LOG_DIR/nginx-access.log main;
sendfile on;
keepalive_timeout 65;
client_max_body_size 100m;
server {
listen $FRONTEND_PORT;
server_name _;
root $FRONTEND_DIR/dist;
index index.html;
location /prod-api/ {
proxy_pass http://127.0.0.1:$BACKEND_PORT/;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
location / {
try_files \$uri \$uri/ /index.html;
}
}
}
EOF
"$NGINX_HOME/sbin/nginx" -t -c "$NGINX_CONF"
}
main() {
require_root
require_command tar
require_command find
ensure_base_dirs
install_yum_dependencies
java_archive=$(find_archive java)
nginx_archive=$(find_archive nginx)
log_info "检测到 Java 安装包: $java_archive"
log_info "检测到 Nginx 安装包: $nginx_archive"
install_java "$java_archive"
install_nginx "$nginx_archive"
write_nginx_conf
log_info "环境安装完成"
log_info "JAVA_HOME=$JAVA_HOME"
log_info "NGINX_HOME=$NGINX_HOME"
log_info "前端端口=$FRONTEND_PORT,后端端口=$BACKEND_PORT"
}
main "$@"

221
bin/prod/restart_java.sh Executable file
View File

@@ -0,0 +1,221 @@
#!/bin/sh
set -eu
WEBAPP_ROOT="/home/webapp"
ENV_ROOT="$WEBAPP_ROOT/env"
APP_ROOT="$WEBAPP_ROOT/loan-pricing"
JAVA_HOME="$ENV_ROOT/jdk"
BACKEND_DIR="$APP_ROOT/backend"
LOG_DIR="$APP_ROOT/logs"
RUN_DIR="$APP_ROOT/run"
BACKEND_PID_FILE="$RUN_DIR/backend.pid"
BACKEND_JAR="$BACKEND_DIR/ruoyi-admin.jar"
BACKEND_CONSOLE_LOG="$LOG_DIR/backend-console.log"
BACKEND_PORT=63310
BACKEND_MARKER="-Dloan.pricing.home=$APP_ROOT"
JAVA_OPTS="$BACKEND_MARKER -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError"
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'
用法: ./restart_java.sh [start|stop|restart|status]
默认动作:
restart 重启后端 Java 进程
EOF
}
require_root() {
if [ "$(id -u)" -ne 0 ]; then
log_error "请使用 root 用户执行脚本"
exit 1
fi
}
ensure_runtime_dirs() {
mkdir -p "$BACKEND_DIR" "$LOG_DIR" "$RUN_DIR"
}
is_managed_backend_pid() {
pid="$1"
if [ -z "${pid:-}" ] || ! kill -0 "$pid" 2>/dev/null; then
return 1
fi
args=$(ps -o args= -p "$pid" 2>/dev/null || true)
if [ -z "${args:-}" ]; then
return 1
fi
case "$args" in
*"$BACKEND_MARKER"*"$BACKEND_JAR"*|*"$BACKEND_JAR"*"$BACKEND_MARKER"*)
return 0
;;
esac
return 1
}
collect_backend_pids() {
pids=""
if [ -f "$BACKEND_PID_FILE" ]; then
file_pid=$(cat "$BACKEND_PID_FILE" 2>/dev/null || true)
if [ -n "${file_pid:-}" ] && is_managed_backend_pid "$file_pid"; then
pids="$pids $file_pid"
fi
fi
marker_pids=$(pgrep -f "$BACKEND_MARKER" 2>/dev/null || true)
if [ -n "${marker_pids:-}" ]; then
for pid in $marker_pids; do
if is_managed_backend_pid "$pid"; then
pids="$pids $pid"
fi
done
fi
printf '%s\n' "$(echo "$pids" | xargs 2>/dev/null || true)"
}
stop_backend() {
pids=$(collect_backend_pids)
if [ -z "${pids:-}" ]; then
rm -f "$BACKEND_PID_FILE"
log_info "未发现运行中的后端进程"
return 0
fi
log_info "停止后端进程: $pids"
for pid in $pids; do
kill -TERM "$pid" 2>/dev/null || true
done
elapsed=0
while [ "$elapsed" -lt 30 ]; do
remaining=""
for pid in $pids; do
if kill -0 "$pid" 2>/dev/null; then
remaining="$remaining $pid"
fi
done
remaining=$(echo "$remaining" | xargs 2>/dev/null || true)
if [ -z "${remaining:-}" ]; then
break
fi
sleep 1
elapsed=$((elapsed + 1))
done
if [ -n "${remaining:-}" ]; then
log_info "执行强制停止: $remaining"
for pid in $remaining; do
kill -KILL "$pid" 2>/dev/null || true
done
fi
rm -f "$BACKEND_PID_FILE"
}
start_backend() {
if [ ! -x "$JAVA_HOME/bin/java" ]; then
log_error "未检测到 Java可先执行 /home/webapp/install_env.sh"
exit 1
fi
if [ ! -f "$BACKEND_JAR" ]; then
log_error "未找到后端 jar: $BACKEND_JAR"
exit 1
fi
if [ -n "$(collect_backend_pids)" ]; then
log_error "检测到后端已在运行,请先执行 stop 或 restart"
exit 1
fi
printf '\n===== %s restart =====\n' "$(timestamp)" >> "$BACKEND_CONSOLE_LOG"
(
cd "$BACKEND_DIR"
nohup "$JAVA_HOME/bin/java" $JAVA_OPTS -jar "$BACKEND_JAR" --spring.profiles.active=pro --server.port="$BACKEND_PORT" >> "$BACKEND_CONSOLE_LOG" 2>&1 &
echo $! > "$BACKEND_PID_FILE"
)
sleep 3
backend_pid=$(cat "$BACKEND_PID_FILE" 2>/dev/null || true)
if [ -z "${backend_pid:-}" ] || ! kill -0 "$backend_pid" 2>/dev/null; then
log_error "后端启动失败,请检查日志: $BACKEND_CONSOLE_LOG"
exit 1
fi
wait_seconds=0
while [ "$wait_seconds" -lt 30 ]; do
if ss -lnt 2>/dev/null | grep -q ":$BACKEND_PORT "; then
log_info "后端已监听端口: $BACKEND_PORT"
return 0
fi
sleep 1
wait_seconds=$((wait_seconds + 1))
done
log_error "后端未在预期时间内监听端口 $BACKEND_PORT"
exit 1
}
status_backend() {
pids=$(collect_backend_pids)
if [ -n "${pids:-}" ]; then
log_info "后端正在运行,进程: $pids"
return 0
fi
if ss -lnt 2>/dev/null | grep -q ":$BACKEND_PORT "; then
log_info "未识别到脚本托管进程,但端口 $BACKEND_PORT 已被占用"
return 0
fi
log_info "后端未运行"
}
main() {
action="${1:-restart}"
case "$action" in
start)
start_backend
;;
stop)
stop_backend
;;
restart)
stop_backend
start_backend
;;
status)
status_backend
;;
*)
usage
exit 1
;;
esac
}
main "$@"