迁移892-without-redis分支全量功能

This commit is contained in:
wkc
2026-04-15 14:18:56 +08:00
parent 9fe1bffe0d
commit 79c5317414
97 changed files with 10922 additions and 232 deletions

257
bin/prod/deploy_from_package.sh Executable file
View File

@@ -0,0 +1,257 @@
#!/bin/sh
set -eu
JAVA_BIN="/home/webapp/env/java/bin/java"
BACKEND_PORT=63310
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)
BACKEND_DIR="$SCRIPT_DIR/backend"
FRONTEND_DIR="$SCRIPT_DIR/frontend"
BACKEND_JAR_TARGET="$BACKEND_DIR/ruoyi-admin.jar"
BACKEND_PID_FILE="$BACKEND_DIR/backend.pid"
BACKEND_LOG_FILE="$BACKEND_DIR/backend-console.log"
FRONTEND_DIST_ARCHIVE="$FRONTEND_DIR/dist.zip"
FRONTEND_DIST_DIR="$FRONTEND_DIR/dist"
BACKEND_MARKER="-Dloan.pricing.home=$SCRIPT_DIR"
usage() {
cat <<'EOF'
用法:
./deploy_from_package.sh
EOF
}
timestamp() {
date "+%Y%m%d%H%M%S"
}
log_info() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1"
}
log_error() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >&2
}
cleanup() {
if [ -n "${WORK_DIR:-}" ] && [ -d "$WORK_DIR" ]; then
rm -rf "$WORK_DIR"
fi
}
require_dir() {
if [ ! -d "$1" ]; then
log_error "缺少目录: $1"
exit 1
fi
}
require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
log_error "缺少命令: $1"
exit 1
fi
}
find_release_archive() {
archives=$(find "$SCRIPT_DIR" -maxdepth 1 -type f -name '*.zip' ! -name 'dist.zip')
count=$(printf '%s\n' "$archives" | sed '/^$/d' | wc -l | tr -d ' ')
if [ "$count" -ne 1 ]; then
log_error "脚本同目录发布 zip 数量不正确,期望 1 个,实际 $count"
exit 1
fi
printf '%s\n' "$archives"
}
extract_release_package() {
release_archive="$1"
release_extract_dir="$2"
mkdir -p "$release_extract_dir"
unzip -oq "$release_archive" -d "$release_extract_dir"
}
assert_single_jar() {
search_dir="$1"
count=$(find "$search_dir" -type f -name '*.jar' ! -path '*/__MACOSX/*' ! -name '._*' | wc -l | tr -d ' ')
if [ "$count" -ne 1 ]; then
log_error "后端 jar 数量不正确,期望 1 个,实际 $count"
exit 1
fi
find "$search_dir" -type f -name '*.jar' ! -path '*/__MACOSX/*' ! -name '._*' | head -n 1
}
assert_single_dist_zip() {
search_dir="$1"
count=$(find "$search_dir" -type f -name 'dist.zip' ! -path '*/__MACOSX/*' ! -name '._*' | wc -l | tr -d ' ')
if [ "$count" -ne 1 ]; then
log_error "前端 dist.zip 数量不正确,期望 1 个,实际 $count"
exit 1
fi
find "$search_dir" -type f -name 'dist.zip' ! -path '*/__MACOSX/*' ! -name '._*' | head -n 1
}
backup_backend_jar() {
if [ -f "$BACKEND_JAR_TARGET" ]; then
mv "$BACKEND_JAR_TARGET" "$BACKEND_JAR_TARGET.$(timestamp).bak"
fi
}
backup_frontend_dist() {
if [ -d "$FRONTEND_DIST_DIR" ]; then
mv "$FRONTEND_DIST_DIR" "$FRONTEND_DIR/dist-$(timestamp)"
fi
}
deploy_backend_jar() {
source_jar="$1"
mv "$source_jar" "$BACKEND_JAR_TARGET"
}
deploy_frontend_dist() {
source_dist_zip="$1"
rm -f "$FRONTEND_DIST_ARCHIVE"
rm -rf "$FRONTEND_DIST_DIR"
mv "$source_dist_zip" "$FRONTEND_DIST_ARCHIVE"
unzip -oq "$FRONTEND_DIST_ARCHIVE" -d "$FRONTEND_DIR"
if [ ! -d "$FRONTEND_DIST_DIR" ]; then
log_error "dist.zip 解压后未找到 $FRONTEND_DIST_DIR"
exit 1
fi
}
collect_backend_pids() {
ps -ef | awk -v marker="$BACKEND_MARKER" -v jar="$BACKEND_JAR_TARGET" '
index($0, "<defunct>") == 0 && index($0, marker) > 0 {
for (i = 1; i < NF; i++) {
if ($i == "-jar" && $(i + 1) == jar) {
print $2
break
}
}
}
' | 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
remaining="$pids"
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_BIN" ]; then
log_error "未检测到可执行 Java: $JAVA_BIN"
exit 1
fi
if [ ! -f "$BACKEND_JAR_TARGET" ]; then
log_error "未找到后端 jar: $BACKEND_JAR_TARGET"
exit 1
fi
if [ -n "$(collect_backend_pids)" ]; then
log_error "检测到后端已在运行,请先停止旧进程"
exit 1
fi
printf '\n===== %s deploy =====\n' "$(date '+%Y-%m-%d %H:%M:%S')" >> "$BACKEND_LOG_FILE"
nohup "$JAVA_BIN" $JAVA_OPTS "$BACKEND_MARKER" -jar "$BACKEND_JAR_TARGET" \
--spring.profiles.active="$SPRING_PROFILE" \
--server.port="$BACKEND_PORT" >> "$BACKEND_LOG_FILE" 2>&1 &
backend_pid=$!
printf '%s\n' "$backend_pid" > "$BACKEND_PID_FILE"
sleep 1
if ! kill -0 "$backend_pid" 2>/dev/null; then
log_error "后端启动失败,请检查日志: $BACKEND_LOG_FILE"
exit 1
fi
log_info "后端已启动PID: $backend_pid"
}
main() {
if [ "$#" -ne 0 ]; then
usage
exit 1
fi
require_dir "$BACKEND_DIR"
require_dir "$FRONTEND_DIR"
require_command unzip
require_command find
require_command ps
require_command nohup
release_archive=$(find_release_archive)
WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/deploy_from_package.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_dist_zip "$WORK_DIR/package")
backup_backend_jar
backup_frontend_dist
stop_backend
deploy_backend_jar "$backend_jar_source"
deploy_frontend_dist "$frontend_dist_source"
start_backend
log_info "部署完成"
log_info "后端 jar: $BACKEND_JAR_TARGET"
log_info "前端目录: $FRONTEND_DIST_DIR"
}
main "$@"

View File

@@ -0,0 +1,262 @@
#!/bin/sh
set -eu
ROOT_DIR=$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd)
SCRIPT_UNDER_TEST="$ROOT_DIR/bin/prod/deploy_from_package.sh"
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
assert_file_exists() {
file_path="$1"
[ -e "$file_path" ] || fail "expected file to exist: $file_path"
}
assert_grep() {
pattern="$1"
target="$2"
if ! grep -Eq "$pattern" "$target"; then
fail "expected pattern [$pattern] in $target"
fi
}
create_fake_java() {
fake_java="$1"
cat > "$fake_java" <<'EOF'
#!/bin/sh
set -eu
port=""
for arg in "$@"; do
case "$arg" in
--server.port=*)
port=${arg#--server.port=}
;;
esac
done
if [ -z "$port" ]; then
echo "missing port" >&2
exit 1
fi
while :; do
sleep 1
done
EOF
chmod +x "$fake_java"
}
create_release_zip() {
release_dir="$1"
release_zip_name="$2"
mkdir -p "$release_dir/package/deploy" "$release_dir/package/__MACOSX/deploy"
mkdir -p "$release_dir/package/frontend_payload/dist" "$release_dir/package/frontend_payload/__MACOSX/dist"
printf 'new-jar\n' > "$release_dir/package/deploy/ruoyi-admin.jar"
printf 'macos-meta\n' > "$release_dir/package/__MACOSX/deploy/._ruoyi-admin.jar"
printf '<html>new</html>\n' > "$release_dir/package/frontend_payload/dist/index.html"
printf 'macos-meta\n' > "$release_dir/package/frontend_payload/__MACOSX/dist/._index.html"
(
cd "$release_dir/package/frontend_payload"
zip -qr "$release_dir/package/dist.zip" dist __MACOSX
)
mv "$release_dir/package/dist.zip" "$release_dir/package/deploy/dist.zip"
(
cd "$release_dir/package"
zip -qr "$release_dir/$release_zip_name" deploy __MACOSX
)
}
find_free_port() {
python3 - <<'PY'
import socket
sock = socket.socket()
sock.bind(("127.0.0.1", 0))
print(sock.getsockname()[1])
sock.close()
PY
}
prepare_release_dir() {
release_dir="$1"
backend_port="$2"
mkdir -p "$release_dir/backend" "$release_dir/frontend" "$release_dir/fake-java-bin"
printf 'old-jar\n' > "$release_dir/backend/ruoyi-admin.jar"
mkdir -p "$release_dir/frontend/dist"
printf '<html>old</html>\n' > "$release_dir/frontend/dist/index.html"
create_fake_java "$release_dir/fake-java-bin/java"
create_release_zip "$release_dir" "deploy.zip"
cp "$SCRIPT_UNDER_TEST" "$release_dir/deploy_from_package.sh"
perl -0pi -e "s#JAVA_BIN=\"/home/webapp/env/java/bin/java\"#JAVA_BIN=\"$release_dir/fake-java-bin/java\"#" \
"$release_dir/deploy_from_package.sh"
perl -0pi -e "s/BACKEND_PORT=63310/BACKEND_PORT=$backend_port/" \
"$release_dir/deploy_from_package.sh"
chmod +x "$release_dir/deploy_from_package.sh"
}
cleanup_release_dir() {
release_dir="$1"
if [ -f "$release_dir/backend/backend.pid" ]; then
backend_pid=$(cat "$release_dir/backend/backend.pid" 2>/dev/null || true)
if [ -n "${backend_pid:-}" ]; then
kill "$backend_pid" 2>/dev/null || true
wait "$backend_pid" 2>/dev/null || true
fi
fi
rm -rf "$release_dir"
}
test_deploy_success() {
release_dir=$(mktemp -d)
backend_port=$(find_free_port)
trap 'cleanup_release_dir "$release_dir"' EXIT INT TERM
prepare_release_dir "$release_dir" "$backend_port"
(
cd "$release_dir"
./deploy_from_package.sh
)
assert_file_exists "$release_dir/backend/ruoyi-admin.jar"
assert_file_exists "$release_dir/frontend/dist.zip"
assert_file_exists "$release_dir/frontend/dist/index.html"
assert_file_exists "$release_dir/backend/backend.pid"
assert_file_exists "$release_dir/backend/backend-console.log"
assert_grep 'new' "$release_dir/frontend/dist/index.html"
backup_jar_count=$(find "$release_dir/backend" -maxdepth 1 -type f -name 'ruoyi-admin.jar.*.bak' | wc -l | tr -d ' ')
[ "$backup_jar_count" -eq 1 ] || fail "expected one backup jar, got $backup_jar_count"
backup_dist_count=$(find "$release_dir/frontend" -maxdepth 1 -type d -name 'dist-*' | wc -l | tr -d ' ')
[ "$backup_dist_count" -eq 1 ] || fail "expected one backup dist dir, got $backup_dist_count"
backend_pid=$(cat "$release_dir/backend/backend.pid")
kill -0 "$backend_pid" 2>/dev/null || fail "expected backend pid to be running"
trap - EXIT INT TERM
cleanup_release_dir "$release_dir"
}
test_multiple_release_zip_should_fail() {
release_dir=$(mktemp -d)
backend_port=$(find_free_port)
trap 'cleanup_release_dir "$release_dir"' EXIT INT TERM
prepare_release_dir "$release_dir" "$backend_port"
cp "$release_dir/deploy.zip" "$release_dir/deploy-copy.zip"
if (
cd "$release_dir"
./deploy_from_package.sh >/tmp/deploy_from_package_test.stderr 2>&1
); then
fail "expected deploy_from_package.sh to fail when multiple release zips exist"
fi
assert_file_exists /tmp/deploy_from_package_test.stderr
assert_grep '发布 zip 数量不正确' /tmp/deploy_from_package_test.stderr
rm -f /tmp/deploy_from_package_test.stderr
trap - EXIT INT TERM
cleanup_release_dir "$release_dir"
}
test_defunct_process_should_be_ignored() {
release_dir=$(mktemp -d)
backend_port=$(find_free_port)
trap 'cleanup_release_dir "$release_dir"' EXIT INT TERM
prepare_release_dir "$release_dir" "$backend_port"
mkdir -p "$release_dir/fake-ps-bin"
cat > "$release_dir/fake-ps-bin/ps" <<EOF
#!/bin/sh
if [ "\$1" = "-ef" ]; then
cat <<'PSOUT'
UID PID PPID C STIME TTY TIME CMD
root 99999 1 0 00:00 ? 00:00:00 [java] <defunct> -Dloan.pricing.home=$release_dir -jar $release_dir/backend/ruoyi-admin.jar
PSOUT
exit 0
fi
/bin/ps "\$@"
EOF
chmod +x "$release_dir/fake-ps-bin/ps"
(
cd "$release_dir"
PATH="$release_dir/fake-ps-bin:/usr/bin:/bin" ./deploy_from_package.sh
)
backend_pid=$(cat "$release_dir/backend/backend.pid")
kill -0 "$backend_pid" 2>/dev/null || fail "expected backend pid to be running when defunct process is ignored"
trap - EXIT INT TERM
cleanup_release_dir "$release_dir"
}
test_only_current_project_jar_should_match() {
release_dir=$(mktemp -d)
backend_port=$(find_free_port)
trap 'cleanup_release_dir "$release_dir"' EXIT INT TERM
prepare_release_dir "$release_dir" "$backend_port"
mkdir -p "$release_dir/fake-ps-bin"
cat > "$release_dir/fake-ps-bin/ps" <<EOF
#!/bin/sh
if [ "\$1" = "-ef" ]; then
cat <<'PSOUT'
UID PID PPID C STIME TTY TIME CMD
root 88888 1 0 00:00 ? 00:00:00 java -Dloan.pricing.home=$release_dir -jar $release_dir/backend/ruoyi-admin.jar.bak --spring.profiles.active=pro
PSOUT
exit 0
fi
/bin/ps "\$@"
EOF
chmod +x "$release_dir/fake-ps-bin/ps"
(
cd "$release_dir"
PATH="$release_dir/fake-ps-bin:/usr/bin:/bin" ./deploy_from_package.sh
)
backend_pid=$(cat "$release_dir/backend/backend.pid")
kill -0 "$backend_pid" 2>/dev/null || fail "expected backend pid to be running when non-target jar process is ignored"
trap - EXIT INT TERM
cleanup_release_dir "$release_dir"
}
test_should_use_ps_ef_for_process_detection() {
if rg -n 'pgrep' "$SCRIPT_UNDER_TEST" >/dev/null 2>&1; then
fail "expected deploy_from_package.sh not to depend on pgrep"
fi
if ! rg -n 'ps -ef' "$SCRIPT_UNDER_TEST" >/dev/null 2>&1; then
fail "expected deploy_from_package.sh to use ps -ef for process detection"
fi
if rg -n '\b(ss|lsof|netstat|resolve_frontend_source_dir|is_port_listening)\b' "$SCRIPT_UNDER_TEST" >/dev/null 2>&1; then
fail "expected deploy_from_package.sh to remove port detection and unzip compatibility helpers"
fi
}
main() {
[ -f "$SCRIPT_UNDER_TEST" ] || fail "script under test not found: $SCRIPT_UNDER_TEST"
test_should_use_ps_ef_for_process_detection
test_deploy_success
test_multiple_release_zip_should_fail
test_defunct_process_should_be_ignored
test_only_current_project_jar_should_match
printf 'PASS: deploy_from_package tests\n'
}
main "$@"

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 "$@"

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

@@ -0,0 +1,208 @@
#!/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
}
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=$(ps -ef | awk -v marker="$BACKEND_MARKER" -v jar="$BACKEND_JAR" '
index($0, "<defunct>") == 0 && index($0, marker) > 0 {
for (i = 1; i < NF; i++) {
if ($i == "-jar" && $(i + 1) == jar) {
print $2
break
}
}
}
' | xargs 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() {
ensure_runtime_dirs
if [ ! -x "$JAVA_HOME/bin/java" ]; then
log_error "未检测到可执行 Java: $JAVA_HOME/bin/java"
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
log_info "后端已启动PID: $backend_pid"
}
status_backend() {
pids=$(collect_backend_pids)
if [ -n "${pids:-}" ]; then
log_info "后端正在运行,进程: $pids"
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 "$@"

View File

@@ -0,0 +1,116 @@
#!/bin/sh
set -eu
ROOT_DIR=$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd)
SCRIPT_UNDER_TEST="$ROOT_DIR/bin/prod/restart_java.sh"
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
assert_grep() {
pattern="$1"
target="$2"
if ! grep -Eq -- "$pattern" "$target"; then
fail "expected pattern [$pattern] in $target"
fi
}
assert_not_grep() {
pattern="$1"
target="$2"
if grep -Eq -- "$pattern" "$target"; then
fail "did not expect pattern [$pattern] in $target"
fi
}
create_fake_java() {
fake_java="$1"
cat > "$fake_java" <<'EOF'
#!/bin/sh
set -eu
while :; do
sleep 1
done
EOF
chmod +x "$fake_java"
}
prepare_script_env() {
work_dir="$1"
mkdir -p "$work_dir/env/jdk/bin" "$work_dir/loan-pricing/backend" "$work_dir/loan-pricing/logs" "$work_dir/loan-pricing/run"
create_fake_java "$work_dir/env/jdk/bin/java"
printf 'fake-jar\n' > "$work_dir/loan-pricing/backend/ruoyi-admin.jar"
cp "$SCRIPT_UNDER_TEST" "$work_dir/restart_java.sh"
perl -0pi -e "s#WEBAPP_ROOT=\"/home/webapp\"#WEBAPP_ROOT=\"$work_dir\"#" "$work_dir/restart_java.sh"
chmod +x "$work_dir/restart_java.sh"
}
cleanup_work_dir() {
work_dir="$1"
if [ -f "$work_dir/loan-pricing/run/backend.pid" ]; then
backend_pid=$(cat "$work_dir/loan-pricing/run/backend.pid" 2>/dev/null || true)
if [ -n "${backend_pid:-}" ]; then
kill "$backend_pid" 2>/dev/null || true
wait "$backend_pid" 2>/dev/null || true
fi
fi
rm -rf "$work_dir"
}
test_script_contract() {
assert_grep 'JAVA_HOME="\$ENV_ROOT/jdk"' "$SCRIPT_UNDER_TEST"
assert_grep '--spring\.profiles\.active=pro' "$SCRIPT_UNDER_TEST"
assert_grep 'ps -ef' "$SCRIPT_UNDER_TEST"
assert_not_grep 'pgrep' "$SCRIPT_UNDER_TEST"
assert_not_grep 'mvn' "$SCRIPT_UNDER_TEST"
assert_not_grep 'require_root' "$SCRIPT_UNDER_TEST"
assert_not_grep '\b(ss|lsof|netstat)\b' "$SCRIPT_UNDER_TEST"
}
test_restart_flow() {
work_dir=$(mktemp -d)
trap 'cleanup_work_dir "$work_dir"' EXIT INT TERM
prepare_script_env "$work_dir"
"$work_dir/restart_java.sh" start
if [ ! -f "$work_dir/loan-pricing/run/backend.pid" ]; then
fail "expected backend pid file after start"
fi
backend_pid=$(cat "$work_dir/loan-pricing/run/backend.pid")
kill -0 "$backend_pid" 2>/dev/null || fail "expected backend process to be running after start"
status_output=$("$work_dir/restart_java.sh" status 2>&1 || true)
printf '%s\n' "$status_output" | grep -q '后端正在运行' || fail "expected status output to show running"
"$work_dir/restart_java.sh" restart
restarted_pid=$(cat "$work_dir/loan-pricing/run/backend.pid")
kill -0 "$restarted_pid" 2>/dev/null || fail "expected backend process to be running after restart"
"$work_dir/restart_java.sh" stop
if [ -f "$work_dir/loan-pricing/run/backend.pid" ]; then
fail "expected backend pid file to be removed after stop"
fi
trap - EXIT INT TERM
cleanup_work_dir "$work_dir"
}
main() {
[ -f "$SCRIPT_UNDER_TEST" ] || fail "script under test not found: $SCRIPT_UNDER_TEST"
test_script_contract
test_restart_flow
printf 'PASS: restart_java tests\n'
}
main "$@"

View File

@@ -0,0 +1,190 @@
# 本地安装 Nginx 和 Java 手册
## 适用范围
本手册适用于需要在本地 Linux 环境手动安装贷款定价系统运行环境的场景,安装结果与当前生产脚本约定保持一致:
- Java 安装到 `/home/webapp/env/java`
- Nginx 安装到 `/home/webapp/env/nginx`
- 项目部署目录使用 `/home/webapp/loan-pricing`
- 后端服务端口固定为 `63310`
- 前端 Nginx 端口固定为 `63311`
## 前置条件
安装前请先确认:
- 当前用户具备 `root` 权限
- 本机已配置可用的 `yum`
- `/home/webapp` 目录已存在
- `/home/webapp` 下已准备安装包:
- `openjdk-21.0.2_linux-aarch64_bin.tar.gz`
- `nginx-1.20.2.tar.gz`
如果安装包文件名不同,只要仍是 Java 的 `tar.gz` 包和 Nginx 的源码 `tar.gz` 包,也可以使用同样步骤。
## 目录规划
安装完成后目录结构如下:
```text
/home/webapp
├── env
│ ├── java
│ └── nginx
└── loan-pricing
├── backend
├── frontend
├── backup
├── logs
├── run
└── tmp
```
## 第一步:安装系统依赖
执行以下命令安装编译 Nginx 和运行部署脚本所需依赖:
```sh
yum install -y \
gcc \
make \
pcre \
pcre-devel \
zlib \
zlib-devel \
openssl \
openssl-devel \
tar \
gzip \
unzip \
which \
findutils \
procps-ng \
iproute
```
## 第二步:创建目录
执行以下命令初始化目录:
```sh
mkdir -p \
/home/webapp/env \
/home/webapp/loan-pricing/backend \
/home/webapp/loan-pricing/frontend \
/home/webapp/loan-pricing/backup \
/home/webapp/loan-pricing/logs \
/home/webapp/loan-pricing/run \
/home/webapp/loan-pricing/tmp
```
## 第三步:安装 Java
解压 Java 安装包到目标目录:
```sh
rm -rf /home/webapp/env/java
mkdir -p /home/webapp/env/java
tar -xzf /home/webapp/openjdk-21.0.2_linux-aarch64_bin.tar.gz -C /home/webapp/env/java --strip-components=1
```
验证安装结果:
```sh
/home/webapp/env/java/bin/java -version
```
如果能正常输出 Java 版本,说明安装成功。
## 第四步:安装 Nginx
Nginx 安装包为源码包,需要先解压、编译、安装:
```sh
rm -rf /home/webapp/env/nginx
mkdir -p /home/webapp/env/nginx
mkdir -p /home/webapp/env/nginx-build
tar -xzf /home/webapp/nginx-1.20.2.tar.gz -C /home/webapp/env/nginx-build
cd /home/webapp/env/nginx-build/nginx-1.20.2
./configure --prefix=/home/webapp/env/nginx --with-http_ssl_module
make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 1)"
make install
```
安装完成后可执行文件位置为:
```text
/home/webapp/env/nginx/sbin/nginx
```
## 第五步:写入 Nginx 配置
仓库已提供可直接参考的配置文件:
```text
deploy/nginx.conf
```
将该文件内容写入 `/home/webapp/env/nginx/conf/nginx.conf` 即可。
## 第六步:校验 Nginx 配置
执行:
```sh
/home/webapp/env/nginx/sbin/nginx -t -c /home/webapp/env/nginx/conf/nginx.conf
```
如果输出 `syntax is ok``test is successful`,说明配置可用。
## 第七步:启动 Nginx
执行:
```sh
/home/webapp/env/nginx/sbin/nginx -c /home/webapp/env/nginx/conf/nginx.conf
```
如果后续修改了配置,可执行:
```sh
/home/webapp/env/nginx/sbin/nginx -c /home/webapp/env/nginx/conf/nginx.conf -s reload
```
## 第八步:验证端口
执行:
```sh
ss -lnt | grep 63311
```
如果能看到 `63311` 监听记录,说明前端 Nginx 已启动成功。
## 建议执行方式
如果本机已经放置了以下脚本,也可以直接使用脚本完成安装:
```sh
cd /home/webapp
./install_env.sh
```
如果只需要管理后端 Java 进程,可执行:
```sh
cd /home/webapp
./restart_java.sh start
./restart_java.sh stop
./restart_java.sh restart
./restart_java.sh status
```
## 常见检查项
- `yum` 不可用:先确认系统已配置可用的 `yum`
- Java 未安装成功:检查 `/home/webapp/openjdk-*.tar.gz` 是否存在且未损坏
- Nginx 编译失败:检查 `gcc``make``pcre-devel``zlib-devel``openssl-devel` 是否已安装
- Nginx 启动失败:先执行 `nginx -t` 查看配置是否正确
- 前端无法访问后端:检查本机 `63310` 端口是否已有 Java 服务监听

BIN
deploy/deploy.zip Normal file

Binary file not shown.

45
deploy/nginx.conf Normal file
View File

@@ -0,0 +1,45 @@
user nobody;
worker_processes 1;
error_log /home/webapp/loan-pricing/logs/nginx-error.log warn;
pid /home/webapp/loan-pricing/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /home/webapp/env/nginx/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 /home/webapp/loan-pricing/logs/nginx-access.log main;
sendfile on;
keepalive_timeout 65;
client_max_body_size 100m;
server {
listen 63311;
server_name _;
root /home/webapp/loan-pricing/frontend/dist;
index index.html;
location /prod-api/ {
proxy_pass http://127.0.0.1:63310/;
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;
}
}
}

View File

@@ -0,0 +1,63 @@
# 全量迁移 `892-without-redis` 前端实施记录
## 修改时间
- 2026-04-15
## 本次完成内容
- 迁入贷款定价前端页面与组件:
- [workflow/index.vue](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-ui/src/views/loanPricing/workflow/index.vue)
- [workflow/detail.vue](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-ui/src/views/loanPricing/workflow/detail.vue)
- [workflow/components/PersonalCreateDialog.vue](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue)
- [workflow/components/CorporateCreateDialog.vue](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue)
- [workflow/components/PersonalWorkflowDetail.vue](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue)
- [workflow/components/CorporateWorkflowDetail.vue](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-ui/src/views/loanPricing/workflow/components/CorporateWorkflowDetail.vue)
- [workflow/components/ModelOutputDisplay.vue](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue)
- 迁入贷款定价 API
- [workflow.js](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-ui/src/api/loanPricing/workflow.js)
- 迁入密码传输工具:
- [passwordTransfer.js](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-ui/src/utils/passwordTransfer.js)
- 接入前端密码传输调用:
- [src/api/login.js](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-ui/src/api/login.js)
- [src/api/system/user.js](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-ui/src/api/system/user.js)
- 调整登录页默认值为空,移除默认账号密码回填:
- [src/views/login.vue](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-ui/src/views/login.vue)
- 补充前端密码传输环境变量:
- [ruoyi-ui/.env.development](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-ui/.env.development)
- [ruoyi-ui/.env.production](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-ui/.env.production)
- 补充前端依赖:
- `crypto-js`
- `html-webpack-plugin`
- 迁入目标分支中的前端静态测试:
- `ruoyi-ui/tests/*`
## 关键整合说明
- 前端密码传输使用目标分支的 AES ECB 方案,但仍按当前仓库结构挂载到现有 `src/api`
- 登录页默认用户名和默认密码已清空,同时保留 cookie 回填逻辑
- 依赖安装时使用 `nvm use 14.21.3`,满足仓库对前端 Node 版本由 `nvm` 控制的要求
- `npm install` 后补了 `html-webpack-plugin`,用于修复现有构建链缺失 peer 依赖导致的生产构建失败
## 验证结果
### 前端静态测试
- `source ~/.nvm/nvm.sh && cd ruoyi-ui && nvm use 14.21.3 >/dev/null && node tests/password-transfer-api.test.js && node tests/login-default-credentials.test.js && node tests/personal-create-input-params.test.js && node tests/retail-display-fields.test.js && node tests/personal-final-calculate-rate-display.test.js && node tests/workflow-detail-card-order.test.js && node tests/workflow-index-refresh.test.js`
- 结果:通过
### 依赖安装
- `source ~/.nvm/nvm.sh && cd ruoyi-ui && nvm use 14.21.3 && npm install`
- 结果:通过
### 生产构建
- `source ~/.nvm/nvm.sh && cd ruoyi-ui && nvm use 14.21.3 >/dev/null && npm run build:prod`
- 结果:通过
- 备注:有 asset size warning但构建成功`dist/` 已生成
## 未在本记录中执行的内容
- 未启动前端 dev server 做交互式页面冒烟
- 因此没有需要额外清理的前端测试进程

View File

@@ -0,0 +1,71 @@
# 全量迁移 `892-without-redis` 后端实施记录
## 修改时间
- 2026-04-15
## 本次完成内容
- 新增并接入 `ruoyi-loan-pricing` 模块,纳入根 `pom.xml``ruoyi-admin/pom.xml`
- 保留 `MyBatis-Plus + Lombok`,将贷款定价模块中的 `jakarta.*` 兼容替换为当前基线可运行的 `javax.*`
-`ruoyi-framework` 中接入 `MyBatis-Plus`
- `MybatisSqlSessionFactoryBean`
- `MybatisPlusInterceptor`
- MySQL 分页拦截器
- 迁入贷款定价后端主链:
- Controller / DTO / Entity / VO / Mapper / Service / XML
- 敏感字段加解密与脱敏服务
- 个人测算入参对齐
- 列表联表查询测算利率
- 详情页个人最终测算利率取值
- 补充 `HttpUtils#doPostFormUrlEncoded`,满足模型表单调用
- 新增 `PasswordTransferCryptoService`
- 接入登录密码传输后端链路:
- [SysLoginController](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java)
- [SysRegisterController](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java)
- [SysProfileController](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java)
- [SysUserController](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java)
- 补齐密码传输配置:
- `security.password-transfer.key`
- 迁入目标分支中的部署脚本、环境配置和 SQL 资产:
- `bin/prod/*`
- `deploy/*`
- `sql/loan_pricing_*.sql`
- `sql/model_*.sql`
- `test_api/*`
## 关键整合说明
- 没有回退当前分支的 `JDK8` 与“去 Redis 改为内存缓存”基线
- 贷款定价模块没有改写为普通 MyBatis而是保留 `MyBatis-Plus` 风格实现
- 由于当前主工程是 `Spring Boot 2.5 / JDK8`,没有原样保留 `jakarta.*`,而是按你的确认改成 `javax.*`
- 贷款定价模块中的 Swagger v3 注解未继续保留,避免为非业务注解引入额外运行时依赖
## 验证结果
### 构建验证
- `mvn -pl ruoyi-loan-pricing -am -DskipTests package`
- 结果:通过
- `mvn -pl ruoyi-admin -am -DskipTests package`
- 结果:通过
### 定向测试
- `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowMapperXmlTest,LoanPricingWorkflowServiceImplTest,LoanPricingModelServicePersonalParamsTest,SensitiveFieldCryptoServiceTest,LoanPricingSensitiveDisplayServiceTest,ModelRetailOutputFieldsTest -Dsurefire.failIfNoSpecifiedTests=false test`
- 结果:通过
- 统计22 tests run, 0 failures, 0 errors
- `mvn -pl ruoyi-admin -am -Dtest=SysLoginControllerPasswordTransferTest,SysRegisterControllerPasswordTransferTest,SysProfileControllerPasswordTransferTest,SysUserControllerPasswordTransferTest,CacheControllerTest -Dsurefire.failIfNoSpecifiedTests=false test`
- 结果:通过
- 统计7 tests run, 0 failures, 0 errors
### 全量后端测试
- `mvn test`
- 结果:通过
## 未在本记录中执行的内容
- 未执行真实数据库初始化和真实模型接口联调
- 未在本记录中启动长期运行的后端进程,因此无需额外清理测试进程

63
pom.xml
View File

@@ -34,8 +34,9 @@
<tomcat.version>9.0.112</tomcat.version>
<logback.version>1.2.13</logback.version>
<spring-security.version>5.7.14</spring-security.version>
<spring-framework.version>5.3.39</spring-framework.version>
</properties>
<spring-framework.version>5.3.39</spring-framework.version>
<mybatis-plus.version>3.5.7</mybatis-plus.version>
</properties>
<!-- 依赖声明 -->
<dependencyManagement>
@@ -114,12 +115,18 @@
<version>${yauaa.version}</version>
</dependency>
<!-- pagehelper 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.boot.version}</version>
</dependency>
<!-- pagehelper 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.boot.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- 获取系统信息 -->
<dependency>
@@ -204,17 +211,24 @@
<version>${ruoyi.version}</version>
</dependency>
<!-- 系统模块-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-system</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<!-- 通用工具-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
<!-- 系统模块-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-system</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<!-- 利率定价模块-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-loan-pricing</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<!-- 通用工具-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
<version>${ruoyi.version}</version>
</dependency>
@@ -225,10 +239,11 @@
<module>ruoyi-admin</module>
<module>ruoyi-framework</module>
<module>ruoyi-system</module>
<module>ruoyi-quartz</module>
<module>ruoyi-generator</module>
<module>ruoyi-common</module>
</modules>
<module>ruoyi-quartz</module>
<module>ruoyi-generator</module>
<module>ruoyi-common</module>
<module>ruoyi-loan-pricing</module>
</modules>
<packaging>pom</packaging>
<build>
@@ -271,4 +286,4 @@
</pluginRepository>
</pluginRepositories>
</project>
</project>

View File

@@ -61,6 +61,12 @@
<artifactId>ruoyi-generator</artifactId>
</dependency>
<!-- 利率定价模块-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-loan-pricing</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -13,14 +13,15 @@ import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysMenu;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginBody;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.service.SysLoginService;
import com.ruoyi.framework.web.service.SysPermissionService;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.service.PasswordTransferCryptoService;
import com.ruoyi.framework.web.service.SysLoginService;
import com.ruoyi.framework.web.service.SysPermissionService;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysMenuService;
@@ -44,8 +45,11 @@ public class SysLoginController
@Autowired
private TokenService tokenService;
@Autowired
private ISysConfigService configService;
@Autowired
private ISysConfigService configService;
@Autowired
private PasswordTransferCryptoService passwordTransferCryptoService;
/**
* 登录方法
@@ -54,12 +58,13 @@ public class SysLoginController
* @return 结果
*/
@PostMapping("/login")
public AjaxResult login(@RequestBody LoginBody loginBody)
{
AjaxResult ajax = AjaxResult.success();
// 生成令牌
String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
loginBody.getUuid());
public AjaxResult login(@RequestBody LoginBody loginBody)
{
AjaxResult ajax = AjaxResult.success();
loginBody.setPassword(passwordTransferCryptoService.decrypt(loginBody.getPassword()));
// 生成令牌
String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
loginBody.getUuid());
ajax.put(Constants.TOKEN, token);
return ajax;
}

View File

@@ -18,13 +18,14 @@ import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.common.utils.file.MimeTypeUtils;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.common.utils.file.MimeTypeUtils;
import com.ruoyi.framework.web.service.PasswordTransferCryptoService;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.ISysUserService;
/**
* 个人信息 业务处理
@@ -38,8 +39,11 @@ public class SysProfileController extends BaseController
@Autowired
private ISysUserService userService;
@Autowired
private TokenService tokenService;
@Autowired
private TokenService tokenService;
@Autowired
private PasswordTransferCryptoService passwordTransferCryptoService;
/**
* 个人信息
@@ -90,13 +94,13 @@ public class SysProfileController extends BaseController
*/
@Log(title = "个人信息", businessType = BusinessType.UPDATE)
@PutMapping("/updatePwd")
public AjaxResult updatePwd(@RequestBody Map<String, String> params)
{
String oldPassword = params.get("oldPassword");
String newPassword = params.get("newPassword");
LoginUser loginUser = getLoginUser();
Long userId = loginUser.getUserId();
SysUser user = userService.selectUserById(userId);
public AjaxResult updatePwd(@RequestBody Map<String, String> params)
{
String oldPassword = passwordTransferCryptoService.decrypt(params.get("oldPassword"));
String newPassword = passwordTransferCryptoService.decrypt(params.get("newPassword"));
LoginUser loginUser = getLoginUser();
Long userId = loginUser.getUserId();
SysUser user = userService.selectUserById(userId);
String password = user.getPassword();
if (!SecurityUtils.matchesPassword(oldPassword, password))
{

View File

@@ -4,12 +4,13 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.model.RegisterBody;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.service.SysRegisterService;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.model.RegisterBody;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.service.PasswordTransferCryptoService;
import com.ruoyi.framework.web.service.SysRegisterService;
import com.ruoyi.system.service.ISysConfigService;
/**
* 注册验证
@@ -22,17 +23,21 @@ public class SysRegisterController extends BaseController
@Autowired
private SysRegisterService registerService;
@Autowired
private ISysConfigService configService;
@Autowired
private ISysConfigService configService;
@Autowired
private PasswordTransferCryptoService passwordTransferCryptoService;
@PostMapping("/register")
public AjaxResult register(@RequestBody RegisterBody user)
{
if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser"))))
{
return error("当前系统没有开启注册功能!");
}
String msg = registerService.register(user);
return StringUtils.isEmpty(msg) ? success() : error(msg);
}
}
if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser"))))
{
return error("当前系统没有开启注册功能!");
}
user.setPassword(passwordTransferCryptoService.decrypt(user.getPassword()));
String msg = registerService.register(user);
return StringUtils.isEmpty(msg) ? success() : error(msg);
}
}

View File

@@ -24,12 +24,13 @@ import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.system.service.ISysDeptService;
import com.ruoyi.system.service.ISysPostService;
import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.service.PasswordTransferCryptoService;
import com.ruoyi.system.service.ISysDeptService;
import com.ruoyi.system.service.ISysPostService;
import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.ISysUserService;
/**
@@ -50,8 +51,11 @@ public class SysUserController extends BaseController
@Autowired
private ISysDeptService deptService;
@Autowired
private ISysPostService postService;
@Autowired
private ISysPostService postService;
@Autowired
private PasswordTransferCryptoService passwordTransferCryptoService;
/**
* 获取用户列表
@@ -134,13 +138,14 @@ public class SysUserController extends BaseController
{
return error("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
}
else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user))
{
return error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
user.setCreateBy(getUsername());
user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
return toAjax(userService.insertUser(user));
else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user))
{
return error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
user.setCreateBy(getUsername());
user.setPassword(passwordTransferCryptoService.decrypt(user.getPassword()));
user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
return toAjax(userService.insertUser(user));
}
/**
@@ -192,13 +197,14 @@ public class SysUserController extends BaseController
@PreAuthorize("@ss.hasPermi('system:user:resetPwd')")
@Log(title = "用户管理", businessType = BusinessType.UPDATE)
@PutMapping("/resetPwd")
public AjaxResult resetPwd(@RequestBody SysUser user)
{
userService.checkUserAllowed(user);
userService.checkUserDataScope(user.getUserId());
user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
user.setUpdateBy(getUsername());
return toAjax(userService.resetPwd(user));
public AjaxResult resetPwd(@RequestBody SysUser user)
{
userService.checkUserAllowed(user);
userService.checkUserDataScope(user.getUserId());
user.setPassword(passwordTransferCryptoService.decrypt(user.getPassword()));
user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
user.setUpdateBy(getUsername());
return toAjax(userService.resetPwd(user));
}
/**

View File

@@ -0,0 +1,86 @@
# 开发环境配置
server:
# 服务器的HTTP端口默认为63310
port: 63310
servlet:
# 应用的访问路径
context-path: /
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# 连接数满后的排队数默认为100
accept-count: 1000
threads:
# tomcat最大线程数默认为200
max: 800
# Tomcat启动初始化的线程数默认值10
min-spare: 100
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://64.127.23.7:3306/loan-pricing?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: lrdb
password: Synx2024
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置连接超时时间
connectTimeout: 30000
# 配置网络超时时间
socketTimeout: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: ruoyi
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
model:
url: http://64.202.32.40:8083/api/service/interface/invokeService/syllcs
security:
password-transfer:
key: "1234567890abcdef"

View File

@@ -0,0 +1,86 @@
# 开发环境配置
server:
# 服务器的HTTP端口默认为63310
port: 63310
servlet:
# 应用的访问路径
context-path: /
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# 连接数满后的排队数默认为100
accept-count: 1000
threads:
# tomcat最大线程数默认为200
max: 800
# Tomcat启动初始化的线程数默认值10
min-spare: 100
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://192.168.0.111:40628/loan-pricing?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: Kfcx@1234
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置连接超时时间
connectTimeout: 30000
# 配置网络超时时间
socketTimeout: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: ruoyi
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
model:
url: http://localhost:63310/rate/pricing/mock/invokeModel
security:
password-transfer:
key: "1234567890abcdef"

View File

@@ -98,14 +98,18 @@ swagger:
pathMapping: /dev-api
# 防盗链配置
referer:
# 防盗链开关
enabled: false
# 允许的域名列表
allowed-domains: localhost,127.0.0.1,ruoyi.vip,www.ruoyi.vip
# 防止XSS攻击
xss:
referer:
# 防盗链开关
enabled: false
# 允许的域名列表
allowed-domains: localhost,127.0.0.1,ruoyi.vip,www.ruoyi.vip
security:
password-transfer:
key: "1234567890abcdef"
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)

View File

@@ -0,0 +1,40 @@
package com.ruoyi.web.controller.system;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import com.ruoyi.framework.web.service.PasswordTransferCryptoService;
import com.ruoyi.framework.web.service.SysLoginService;
class SysLoginControllerPasswordTransferTest
{
@Test
void shouldDecryptPasswordBeforeCallingLoginService() throws Exception
{
SysLoginService loginService = mock(SysLoginService.class);
PasswordTransferCryptoService passwordTransferCryptoService = mock(PasswordTransferCryptoService.class);
when(passwordTransferCryptoService.decrypt("cipher")).thenReturn("admin123");
when(loginService.login("admin", "admin123", "1", "u")).thenReturn("token");
SysLoginController controller = new SysLoginController();
ReflectionTestUtils.setField(controller, "loginService", loginService);
ReflectionTestUtils.setField(controller, "passwordTransferCryptoService", passwordTransferCryptoService);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
mockMvc.perform(post("/login")
.contentType("application/json")
.content("{\"username\":\"admin\",\"password\":\"cipher\",\"code\":\"1\",\"uuid\":\"u\"}"))
.andExpect(status().isOk());
verify(passwordTransferCryptoService).decrypt("cipher");
verify(loginService).login("admin", "admin123", "1", "u");
}
}

View File

@@ -0,0 +1,72 @@
package com.ruoyi.web.controller.system;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.Collections;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.web.service.PasswordTransferCryptoService;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.ISysUserService;
class SysProfileControllerPasswordTransferTest
{
@AfterEach
void tearDown()
{
SecurityContextHolder.clearContext();
}
@Test
void shouldDecryptPasswordsBeforeCheckingOldPassword() throws Exception
{
ISysUserService userService = mock(ISysUserService.class);
TokenService tokenService = mock(TokenService.class);
PasswordTransferCryptoService passwordTransferCryptoService = mock(PasswordTransferCryptoService.class);
when(passwordTransferCryptoService.decrypt("oldCipher")).thenReturn("oldPlain");
when(passwordTransferCryptoService.decrypt("newCipher")).thenReturn("newPlain");
when(userService.resetUserPwd(org.mockito.ArgumentMatchers.anyLong(), org.mockito.ArgumentMatchers.anyString()))
.thenReturn(1);
SysUser storedUser = new SysUser();
storedUser.setUserId(2L);
storedUser.setPassword(SecurityUtils.encryptPassword("oldPlain"));
when(userService.selectUserById(2L)).thenReturn(storedUser);
SysUser currentUser = new SysUser();
currentUser.setUserId(2L);
currentUser.setUserName("admin");
LoginUser loginUser = new LoginUser(2L, 1L, currentUser, Collections.emptySet());
SecurityContextHolder.getContext()
.setAuthentication(new UsernamePasswordAuthenticationToken(loginUser, null, Collections.emptyList()));
SysProfileController controller = new SysProfileController();
ReflectionTestUtils.setField(controller, "userService", userService);
ReflectionTestUtils.setField(controller, "tokenService", tokenService);
ReflectionTestUtils.setField(controller, "passwordTransferCryptoService", passwordTransferCryptoService);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
mockMvc.perform(put("/system/user/profile/updatePwd")
.contentType("application/json")
.content("{\"oldPassword\":\"oldCipher\",\"newPassword\":\"newCipher\"}"))
.andExpect(status().isOk());
verify(passwordTransferCryptoService).decrypt("oldCipher");
verify(passwordTransferCryptoService).decrypt("newCipher");
verify(userService).resetUserPwd(org.mockito.ArgumentMatchers.eq(2L), org.mockito.ArgumentMatchers.anyString());
verify(tokenService).setLoginUser(loginUser);
}
}

View File

@@ -0,0 +1,50 @@
package com.ruoyi.web.controller.system;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import com.ruoyi.common.core.domain.model.RegisterBody;
import com.ruoyi.framework.web.service.PasswordTransferCryptoService;
import com.ruoyi.framework.web.service.SysRegisterService;
import com.ruoyi.system.service.ISysConfigService;
class SysRegisterControllerPasswordTransferTest
{
@Test
void shouldDecryptPasswordBeforeCallingRegisterService() throws Exception
{
SysRegisterService registerService = mock(SysRegisterService.class);
ISysConfigService configService = mock(ISysConfigService.class);
PasswordTransferCryptoService passwordTransferCryptoService = mock(PasswordTransferCryptoService.class);
when(configService.selectConfigByKey("sys.account.registerUser")).thenReturn("true");
when(passwordTransferCryptoService.decrypt("cipher")).thenReturn("admin123");
when(registerService.register(any(RegisterBody.class))).thenReturn("");
SysRegisterController controller = new SysRegisterController();
ReflectionTestUtils.setField(controller, "registerService", registerService);
ReflectionTestUtils.setField(controller, "configService", configService);
ReflectionTestUtils.setField(controller, "passwordTransferCryptoService", passwordTransferCryptoService);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
mockMvc.perform(post("/register")
.contentType("application/json")
.content("{\"username\":\"u1\",\"password\":\"cipher\",\"code\":\"1\",\"uuid\":\"u\"}"))
.andExpect(status().isOk());
verify(passwordTransferCryptoService).decrypt("cipher");
ArgumentCaptor<RegisterBody> captor = ArgumentCaptor.forClass(RegisterBody.class);
verify(registerService).register(captor.capture());
assertEquals("admin123", captor.getValue().getPassword());
}
}

View File

@@ -0,0 +1,113 @@
package com.ruoyi.web.controller.system;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.Collections;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.web.service.PasswordTransferCryptoService;
import com.ruoyi.system.service.ISysDeptService;
import com.ruoyi.system.service.ISysPostService;
import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.ISysUserService;
class SysUserControllerPasswordTransferTest
{
@AfterEach
void tearDown()
{
SecurityContextHolder.clearContext();
}
@Test
void shouldDecryptPasswordBeforeAddingUser() throws Exception
{
ISysUserService userService = mock(ISysUserService.class);
ISysRoleService roleService = mock(ISysRoleService.class);
ISysDeptService deptService = mock(ISysDeptService.class);
ISysPostService postService = mock(ISysPostService.class);
PasswordTransferCryptoService passwordTransferCryptoService = mock(PasswordTransferCryptoService.class);
when(passwordTransferCryptoService.decrypt("cipher")).thenReturn("initPwd");
when(userService.checkUserNameUnique(org.mockito.ArgumentMatchers.any(SysUser.class))).thenReturn(true);
when(userService.insertUser(org.mockito.ArgumentMatchers.any(SysUser.class))).thenReturn(1);
setAuthentication();
SysUserController controller = new SysUserController();
ReflectionTestUtils.setField(controller, "userService", userService);
ReflectionTestUtils.setField(controller, "roleService", roleService);
ReflectionTestUtils.setField(controller, "deptService", deptService);
ReflectionTestUtils.setField(controller, "postService", postService);
ReflectionTestUtils.setField(controller, "passwordTransferCryptoService", passwordTransferCryptoService);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
mockMvc.perform(post("/system/user")
.contentType("application/json")
.content("{\"userName\":\"u1\",\"nickName\":\"n1\",\"deptId\":1,\"password\":\"cipher\"}"))
.andExpect(status().isOk());
verify(passwordTransferCryptoService).decrypt("cipher");
ArgumentCaptor<SysUser> captor = ArgumentCaptor.forClass(SysUser.class);
verify(userService).insertUser(captor.capture());
assertTrue(SecurityUtils.matchesPassword("initPwd", captor.getValue().getPassword()));
}
@Test
void shouldDecryptPasswordBeforeResettingUserPassword() throws Exception
{
ISysUserService userService = mock(ISysUserService.class);
ISysRoleService roleService = mock(ISysRoleService.class);
ISysDeptService deptService = mock(ISysDeptService.class);
ISysPostService postService = mock(ISysPostService.class);
PasswordTransferCryptoService passwordTransferCryptoService = mock(PasswordTransferCryptoService.class);
when(passwordTransferCryptoService.decrypt("cipher")).thenReturn("resetPwd");
when(userService.resetPwd(org.mockito.ArgumentMatchers.any(SysUser.class))).thenReturn(1);
setAuthentication();
SysUserController controller = new SysUserController();
ReflectionTestUtils.setField(controller, "userService", userService);
ReflectionTestUtils.setField(controller, "roleService", roleService);
ReflectionTestUtils.setField(controller, "deptService", deptService);
ReflectionTestUtils.setField(controller, "postService", postService);
ReflectionTestUtils.setField(controller, "passwordTransferCryptoService", passwordTransferCryptoService);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
mockMvc.perform(put("/system/user/resetPwd")
.contentType("application/json")
.content("{\"userId\":2,\"password\":\"cipher\"}"))
.andExpect(status().isOk());
verify(passwordTransferCryptoService).decrypt("cipher");
ArgumentCaptor<SysUser> captor = ArgumentCaptor.forClass(SysUser.class);
verify(userService).resetPwd(captor.capture());
assertTrue(SecurityUtils.matchesPassword("resetPwd", captor.getValue().getPassword()));
}
private void setAuthentication()
{
SysUser currentUser = new SysUser();
currentUser.setUserId(1L);
currentUser.setUserName("admin");
LoginUser loginUser = new LoginUser(1L, 1L, currentUser, Collections.emptySet());
SecurityContextHolder.getContext()
.setAuthentication(new UsernamePasswordAuthenticationToken(loginUser, null, Collections.emptyList()));
}
}

View File

@@ -10,18 +10,27 @@ import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.StringUtils;
import org.springframework.http.MediaType;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
/**
* 通用http发送方法
@@ -282,12 +291,38 @@ public class HttpUtils
}
}
private static class TrustAnyHostnameVerifier implements HostnameVerifier
{
@Override
public boolean verify(String hostname, SSLSession session)
{
return true;
}
}
}
private static class TrustAnyHostnameVerifier implements HostnameVerifier
{
@Override
public boolean verify(String hostname, SSLSession session)
{
return true;
}
}
public static <T> T doPostFormUrlEncoded(String url, Map<String, String> params, HttpHeaders headers, Class<T> responseType)
{
MultiValueMap<String, String> formParams = new LinkedMultiValueMap<String, String>();
if (params != null && !params.isEmpty())
{
formParams.setAll(params);
}
HttpHeaders requestHeaders = headers == null ? new HttpHeaders() : headers;
requestHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
requestHeaders.setAcceptCharset(Collections.singletonList(StandardCharsets.UTF_8));
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(formParams, requestHeaders);
RestTemplate restTemplate = new RestTemplate();
try
{
ResponseEntity<T> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, responseType);
log.info("POST(form-urlencoded) 请求成功URL{},响应结果:{}", url, response.getBody());
return response.getBody();
}
catch (Exception e)
{
throw new RuntimeException("POST(form-urlencoded) 请求失败URL" + url + ",异常信息:" + e.getMessage(), e);
}
}
}

View File

@@ -29,11 +29,16 @@
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- 验证码 -->
<dependency>

View File

@@ -1,9 +1,12 @@
package com.ruoyi.framework.config;
import java.util.TimeZone;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
package com.ruoyi.framework.config;
import java.util.TimeZone;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@@ -23,8 +26,16 @@ public class ApplicationConfig
* 时区配置
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization()
{
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
}
}
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization()
{
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
}
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor()
{
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

View File

@@ -1,28 +1,29 @@
package com.ruoyi.framework.config;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import javax.sql.DataSource;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import com.ruoyi.common.utils.StringUtils;
package com.ruoyi.framework.config;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import javax.sql.DataSource;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import com.ruoyi.common.utils.StringUtils;
/**
* Mybatis支持*匹配扫描包
@@ -32,8 +33,11 @@ import com.ruoyi.common.utils.StringUtils;
@Configuration
public class MyBatisConfig
{
@Autowired
private Environment env;
@Autowired
private Environment env;
@Autowired(required = false)
private Interceptor[] interceptors;
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
@@ -122,11 +126,15 @@ public class MyBatisConfig
typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
VFS.addImplClass(SpringBootVFS.class);
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
return sessionFactory.getObject();
}
}
final MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
if (interceptors != null)
{
sessionFactory.setPlugins(interceptors);
}
return sessionFactory.getObject();
}
}

View File

@@ -0,0 +1,30 @@
package com.ruoyi.framework.web.service;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.ruoyi.common.exception.ServiceException;
@Service
public class PasswordTransferCryptoService
{
@Value("${security.password-transfer.key}")
private String key;
public String decrypt(String cipherText)
{
try
{
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"));
return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)), StandardCharsets.UTF_8);
}
catch (Exception ex)
{
throw new ServiceException("密码解密失败");
}
}
}

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.9.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>ruoyi-loan-pricing</artifactId>
<description>
利率定价模块
</description>
<dependencies>
<!-- 通用工具-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-framework</artifactId>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-system</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,100 @@
package com.ruoyi.loanpricing.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.PageDomain;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.page.TableSupport;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.loanpricing.domain.dto.CorporateLoanPricingCreateDTO;
import com.ruoyi.loanpricing.domain.dto.PersonalLoanPricingCreateDTO;
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowListVO;
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowVO;
import com.ruoyi.loanpricing.service.ILoanPricingWorkflowService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* 利率定价流程Controller
*
* @author ruoyi
* @date 2025-01-19
*/
@RestController
@RequestMapping("/loanPricing/workflow")
public class LoanPricingWorkflowController extends BaseController
{
@Autowired
private ILoanPricingWorkflowService loanPricingWorkflowService;
/**
* 发起个人客户利率定价流程
*/
@Log(title = "个人客户利率定价流程", businessType = BusinessType.INSERT)
@PostMapping("/create/personal")
public AjaxResult createPersonal(@Validated @RequestBody PersonalLoanPricingCreateDTO dto) {
LoanPricingWorkflow result = loanPricingWorkflowService.createPersonalLoanPricing(dto);
return success(result);
}
/**
* 发起企业客户利率定价流程
*/
@Log(title = "企业客户利率定价流程", businessType = BusinessType.INSERT)
@PostMapping("/create/corporate")
public AjaxResult createCorporate(@Validated @RequestBody CorporateLoanPricingCreateDTO dto)
{
LoanPricingWorkflow result = loanPricingWorkflowService.createCorporateLoanPricing(dto);
return success(result);
}
/**
* 查询利率定价流程列表
*/
@GetMapping("/list")
public TableDataInfo list(LoanPricingWorkflow loanPricingWorkflow)
{
PageDomain pageDomain = TableSupport.buildPageRequest();
Page<LoanPricingWorkflowListVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
IPage<LoanPricingWorkflowListVO> result = loanPricingWorkflowService.selectLoanPricingPage(page, loanPricingWorkflow);
TableDataInfo rspData = new TableDataInfo();
rspData.setCode(200);
rspData.setMsg("查询成功");
rspData.setRows(result.getRecords());
rspData.setTotal(result.getTotal());
return rspData;
}
/**
* 查询利率定价流程详情
*/
@GetMapping("/{serialNum}")
public AjaxResult getInfo(@PathVariable("serialNum") String serialNum)
{
LoanPricingWorkflowVO workflow = loanPricingWorkflowService.selectLoanPricingBySerialNum(serialNum);
if (workflow == null)
{
return error("记录不存在");
}
return success(workflow);
}
/**
* 设定执行利率
*/
@Log(title = "利率定价流程", businessType = BusinessType.UPDATE)
@PutMapping("/{serialNum}/executeRate")
public AjaxResult setExecuteRate(@PathVariable("serialNum") String serialNum,
@RequestBody Map<String, String> request) {
String executeRate = request.get("executeRate");
boolean success = loanPricingWorkflowService.setExecuteRate(serialNum, executeRate);
return success ? success() : error("设定失败");
}
}

View File

@@ -0,0 +1,50 @@
package com.ruoyi.loanpricing.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.loanpricing.domain.dto.ModelInvokeDTO;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.bind.annotation.*;
import java.io.InputStream;
/**
* @Author 吴凯程
* @Date 2025/11/10
**/
@RestController
@RequestMapping("/rate/pricing/mock")
public class LoanRatePricingMockController extends BaseController {
@Anonymous
@PostMapping("/invokeModel")
public AjaxResult invokeModel( ModelInvokeDTO modelInvokeDTO) {
ObjectNode jsonNodes;
if (modelInvokeDTO.getCustType().equals("个人")) {
jsonNodes = loadJsonFromResource("data/retail_output.json");
} else {
jsonNodes = loadJsonFromResource("data/corp_output.json");
}
return new AjaxResult(10000, "success", jsonNodes);
}
private ObjectNode loadJsonFromResource(String resourcePath){
ClassPathResource classPathResource = new ClassPathResource(resourcePath);
try (InputStream inputStream = classPathResource.getInputStream();){
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(inputStream, ObjectNode.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,48 @@
package com.ruoyi.loanpricing.domain.dto;
import lombok.Data;
import java.io.Serializable;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
/**
* 企业客户利率定价发起DTO
*
* @author ruoyi
* @date 2025-01-19
*/
@Data
public class CorporateLoanPricingCreateDTO implements Serializable {
private static final long serialVersionUID = 1L;
@NotBlank(message = "客户内码不能为空")
private String custIsn;
private String custName;
private String idType;
private String idNum;
@NotBlank(message = "担保方式不能为空")
@Pattern(regexp = "^(信用|保证|抵押|质押)$", message = "担保方式必须是:信用、保证、抵押、质押之一")
private String guarType;
@NotBlank(message = "申请金额不能为空")
private String applyAmt;
private String loanTerm;
private String isAgriGuar;
private String isGreenLoan;
private String isTechEnt;
private String isTradeConstruction;
private String collType;
private String collThirdParty;
}

View File

@@ -0,0 +1,170 @@
package com.ruoyi.loanpricing.domain.dto;
import lombok.Data;
/**
* @Author 吴凯程
* @Date 2025/12/23
**/
@Data
public class ModelInvokeDTO {
/**
* 业务方流水号(必填)
* 可使用时间戳,或自定义随机生成
*/
private String serialNum;
/**
* 机构编码(必填)
* 固定值892000
*/
private String orgCode;
/**
* 运行模式(必填)
* 固定值1:同步
*/
private String runType = "1";
/**
* 客户内码(必填)
*/
private String custIsn;
/**
* 客户类型(必填)
* 可选值:个人/企业
*/
private String custType;
/**
* 担保方式(必填)
* 可选值:信用,保证,抵押,质押
*/
private String guarType;
/**
* 中间业务_个人_快捷支付非必填
* 可选值true/false
*/
private String midPerQuickPay;
/**
* 中间业务_个人_电费代扣非必填
* 可选值true/false
*/
private String midPerEleDdc;
/**
* 中间业务_企业_电费代扣非必填
* 可选值true/false
*/
private String midEntEleDdc;
/**
* 中间业务_企业_水费代扣非必填
* 可选值true/false
*/
private String midEntWaterDdc;
/**
* 申请金额(必填)
* 单位:元
*/
private String applyAmt;
/**
* 贷款期限(必填)
* 单位:年
*/
private String loanTerm;
/**
* 净身企业(非必填)
* 可选值true/false
*/
private String isCleanEnt;
/**
* 开立基本结算账户(非必填)
* 可选值true/false
*/
private String hasSettleAcct;
/**
* 制造业企业(非必填)
* 可选值true/false
*/
private String isManufacturing;
/**
* 省农担担保贷款(非必填)
* 可选值true/false
*/
private String isAgriGuar;
/**
* 是否纳税信用等级A级非必填
* 可选值true/false
*/
private String isTaxA;
/**
* 是否县级及以上农业龙头企业(非必填)
* 可选值true/false
*/
private String isAgriLeading;
private String isInclusiveFinance;
/**
* 贷款用途(非必填)
* 可选值consumer/business
*/
private String loanPurpose;
/**
* 是否有经营佐证(非必填)
* 可选值0/1
*/
private String bizProof;
/**
* 循环功能(非必填)
* 可选值0/1
*/
private String loanLoop;
/**
* 抵质押类型(非必填)
* 可选值:一类/二类/三类
*/
private String collType;
/**
* 抵质押物是否三方所有(非必填)
* 可选值0/1
*/
private String collThirdParty;
// /**
// * 贷款利率(必填)
// */
// private String loanRate;
/**
* 客户名称(非必填)
*/
private String custName;
/**
* 证件类型(非必填)
*/
private String idType;
/**
* 证件号码(非必填)
*/
private String idNum;
}

View File

@@ -0,0 +1,49 @@
package com.ruoyi.loanpricing.domain.dto;
import lombok.Data;
import java.io.Serializable;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
/**
* 个人客户利率定价发起DTO
*
* @author ruoyi
* @date 2025-01-19
*/
@Data
public class PersonalLoanPricingCreateDTO implements Serializable {
private static final long serialVersionUID = 1L;
@NotBlank(message = "客户内码不能为空")
private String custIsn;
private String custName;
private String idType;
private String idNum;
@NotBlank(message = "担保方式不能为空")
@Pattern(regexp = "^(信用|保证|抵押|质押)$", message = "担保方式必须是:信用、保证、抵押、质押之一")
private String guarType;
@NotBlank(message = "申请金额不能为空")
private String applyAmt;
@NotBlank(message = "贷款用途不能为空")
@Pattern(regexp = "^(consumer|business)$", message = "贷款用途必须是consumer、business之一")
private String loanPurpose;
@NotBlank(message = "借款期限不能为空")
private String loanTerm;
private String bizProof;
private String loanLoop;
private String collType;
private String collThirdParty;
}

View File

@@ -0,0 +1,159 @@
package com.ruoyi.loanpricing.domain.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.Date;
/**
* 利率定价流程对象 loan_pricing_workflow
*
* @author ruoyi
* @date 2025-01-19
*/
@Data
@TableName("loan_pricing_workflow")
public class LoanPricingWorkflow implements Serializable
{
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(type = IdType.AUTO)
private Long id;
/** 模型输出ID */
private Long modelOutputId;
/** 业务方流水号 */
private String serialNum;
/** 机构编码 */
private String orgCode;
/** 运行模式: 1-同步 */
private String runType;
/** 客户内码 */
@NotBlank(message = "客户内码不能为空")
private String custIsn;
/** 客户类型: 个人/企业 */
@NotBlank(message = "客户类型不能为空")
private String custType;
/** 担保方式: 信用/保证/抵押/质押 */
@NotBlank(message = "担保方式不能为空")
private String guarType;
/** 中间业务_个人_快捷支付: true/false */
private String midPerQuickPay;
/** 中间业务_个人_电费代扣: true/false */
private String midPerEleDdc;
/** 中间业务_企业_电费代扣: true/false */
private String midEntEleDdc;
/** 中间业务_企业_水费代扣: true/false */
private String midEntWaterDdc;
/** 申请金额(元) */
@NotBlank(message = "申请金额不能为空")
private String applyAmt;
/**
* 贷款期限
*/
private String loanTerm;
/** 净身企业: true/false */
private String isCleanEnt;
/** 开立基本结算账户: true/false */
private String hasSettleAcct;
/** 制造业企业: true/false */
private String isManufacturing;
/** 省农担担保贷款: true/false */
private String isAgriGuar;
/**
* 贸易和建筑业企业标识: true/false
*/
private String isTradeConstruction;
/**
* 绿色贷款: true/false
*/
private String isGreenLoan;
/**
* 科技型企业: true/false
*/
private String isTechEnt;
/** 是否纳税信用等级A级: true/false */
private String isTaxA;
/** 是否县级及以上农业龙头企业: true/false */
private String isAgriLeading;
/** 贷款用途: consumer-消费/business-经营 */
private String loanPurpose;
/** 是否有经营佐证: true/false */
private String bizProof;
/** 循环功能: true/false */
private String loanLoop;
/** 抵质押类型: 一线/一类/二类 */
private String collType;
/** 抵质押物是否三方所有: true/false */
private String collThirdParty;
/** 贷款利率 */
private String loanRate;
/**
* 执行利率(%)
*/
private String executeRate;
/** 客户名称 */
private String custName;
/**
* 证件类型
*/
private String idType;
/** 证件号码 */
private String idNum;
/** 是否普惠小微借款人: true/false */
private String isInclusiveFinance;
/** 创建者 */
@TableField(fill = FieldFill.INSERT)
private String createBy;
/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/** 更新者 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}

View File

@@ -0,0 +1,125 @@
package com.ruoyi.loanpricing.domain.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.util.Date;
/**
* 贷款定价模型输入参数对象
*
* @author ruoyi
* @date 2025-01-21
*/
@Data
public class ModelCorpOutputFields {
@TableId(type = IdType.AUTO)
private Long id;
// 客户内码
private String custIsn;
// 客户类型
private String custType;
// 担保方式
private String guarType;
// 客户名称
private String custName;
// 证件类型
private String idType;
// 证件号码
private String idNum;
// 基准利率
private String baseLoanRate;
// 我行首贷客户
private String isFirstLoan;
// 用信天数
private String faithDay;
// BP_首贷
private String bpFirstLoan;
// BP_贷龄
private String bpAgeLoan;
// TOTAL_BP_忠诚度
private String totalBpLoyalty;
// 存款年日均
private String balanceAvg;
// 贷款年日均
private String loanAvg;
// 派生率
private String derivationRate;
// TOTAL_BP_贡献度
private String totalBpContribution;
// 中间业务_企业_企业互联
private String midEntConnect;
// 中间业务_企业_有效价值客户
private String midEntEffect;
// 中间业务_企业_国际业务
private String midEntInter;
// 中间业务_企业_承兑
private String midEntAccept;
// 中间业务_企业_贴现
private String midEntDiscount;
// 中间业务_企业_电费代扣
private String midEntEleDdc;
// 中间业务_企业_水费代扣
private String midEntWaterDdc;
// 中间业务_企业_税务代扣
private String midEntTax;
// BP_中间业务
private String bpMid;
// 代发工资户数
private String payroll;
// 存量贷款余额
private String invLoanAmount;
// BP_代发工资
private String bpPayroll;
// 净身企业
private String isCleanEnt;
// 开立基本结算账户
private String hasSettleAcct;
// 省农担担保贷款
private String isAgriGuar;
// 绿色贷款
private String isGreenLoan;
// 科技型企业
private String isTechEnt;
// BP_企业客户类别
private String bpEntType;
// TOTAL_BP_关联度
private String totoalBpRelevance;
// 贷款期限
private String loanTerm;
// BP_贷款期限
private String bpLoanTerm;
// 申请金额
private String applyAmt;
// BP_贷款额度
private String bpLoanAmount;
// 抵质押类型
private String collType;
// 抵质押物是否三方所有
private String collThirdParty;
// BP_抵押物
private String bpCollateral;
// 灰名单客户
private String greyCust;
// 本金逾期
private String prinOverdue;
// 利息逾期
private String interestOverdue;
// 信用卡逾期
private String cardOverdue;
// BP_灰名单与逾期
private String bpGreyOverdue;
// TOTAL_BP_风险度
private String totoalBpRisk;
// 浮动BP
private String totalBp;
// 测算利率
private String calculateRate;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
}

View File

@@ -0,0 +1,190 @@
package com.ruoyi.loanpricing.domain.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.util.Date;
/**
* 贷款定价模型输入参数对象
*
* @author ruoyi
* @date 2025-01-21
*/
@Data
public class ModelRetailOutputFields {
@TableId(type = IdType.AUTO)
private Long id;
// 客户内码
private String custIsn;
// 客户类型
private String custType;
// 担保方式
private String guarType;
// 客户名称
private String custName;
// 证件类型
private String idType;
// 证件号码
private String idNum;
// 基准利率
private String baseLoanRate;
// 我行首贷客户
private String isFirstLoan;
// 用信天数
private String faithDay;
// 客户年龄
private String custAge;
// BP_首贷
private String bpFirstLoan;
// BP_贷龄
private String bpAgeLoan;
// BP_年龄
private String bpAge;
// TOTAL_BP_忠诚度
private String totalBpLoyalty;
// 存款年日均
private String balanceAvg;
// 贷款年日均
private String loanAvg;
// 派生率
private String derivationRate;
// TOTAL_BP_贡献度
private String totalBpContribution;
// 中间业务_个人_信用卡
private String midPerCard;
// 中间业务_个人_一码通
private String midPerPass;
// 中间业务_个人_丰收互联
private String midPerHarvest;
// 中间业务_个人_有效客户
private String midPerEffect;
// 中间业务_个人_快捷支付
private String midPerQuickPay;
// 中间业务_个人_电费代扣
private String midPerEleDdc;
// 中间业务_个人_水费代扣
private String midPerWaterDdc;
// 中间业务_个人_华数费代扣
private String midPerHuashuDdc;
// 中间业务_个人_煤气费代扣
private String MidPerGasDdc;
// 中间业务_个人_市民卡
private String midPerCitizencard;
// 中间业务_个人_理财业务
private String midPerFinMan;
// 中间业务_个人_etc
private String midPerEtc;
// BP_中间业务
private String bpMid;
// TOTAL_BP_关联度注意原字段名拼写错误totoalBpRelevance已保留原拼写
private String totoalBpRelevance;
// 申请金额
private String applyAmt;
// BP_贷款额度
private String bpLoanAmount;
// 贷款用途
private String loanPurpose;
// 是否有经营佐证
private String bizProof;
// BP_贷款用途
private String bpLoanUse;
// 循环功能
private String loanLoop;
// BP_循环功能
private String bpLoanLoop;
// 抵质押类型
private String collType;
// 抵质押物是否三方所有
private String collThirdParty;
// BP_抵押物
private String bpCollateral;
// 灰名单客户
private String greyCust;
// 本金逾期
private String prinOverdue;
// 利息逾期
private String interestOverdue;
// 信用卡逾期
private String cardOverdue;
// BP_灰名单与逾期
private String bpGreyOverdue;
// TOTAL_BP_风险度注意原字段名拼写错误totoalBpRisk已保留原拼写
private String totoalBpRisk;
// 浮动BP
private String totalBp;
// 测算利率
private String calculateRate;
// 历史利率
private String loanRateHistory;
// 产品最低利率下限
private String minRateProduct;
// 平滑幅度
private String smoothRange;
// 最终测算利率
private String finalCalculateRate;
// 参考利率
private String referenceRate;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
}

View File

@@ -0,0 +1,27 @@
package com.ruoyi.loanpricing.domain.vo;
import lombok.Data;
import java.util.Date;
@Data
public class LoanPricingWorkflowListVO
{
private String serialNum;
private String custName;
private String custType;
private String guarType;
private String applyAmt;
private String calculateRate;
private String executeRate;
private Date createTime;
private String createBy;
}

View File

@@ -0,0 +1,21 @@
package com.ruoyi.loanpricing.domain.vo;
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
import com.ruoyi.loanpricing.domain.entity.ModelCorpOutputFields;
import com.ruoyi.loanpricing.domain.entity.ModelRetailOutputFields;
import lombok.Data;
/**
* @Author: wkc
* @CreateTime: 2026-01-21
*/
@Data
public class LoanPricingWorkflowVO {
private LoanPricingWorkflow loanPricingWorkflow;
private ModelRetailOutputFields modelRetailOutputFields;
private ModelCorpOutputFields modelCorpOutputFields;
}

View File

@@ -0,0 +1,20 @@
package com.ruoyi.loanpricing.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowListVO;
import org.apache.ibatis.annotations.Param;
/**
* 利率定价流程Mapper接口
*
* @author ruoyi
* @date 2025-01-19
*/
public interface LoanPricingWorkflowMapper extends BaseMapper<LoanPricingWorkflow>
{
IPage<LoanPricingWorkflowListVO> selectWorkflowPageWithRates(Page<?> page,
@Param("query") LoanPricingWorkflow query);
}

View File

@@ -0,0 +1,15 @@
package com.ruoyi.loanpricing.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.loanpricing.domain.entity.ModelCorpOutputFields;
/**
* 对公贷款定价模型输出字段Mapper接口
*
* @author ruoyi
* @date 2025-01-21
*/
public interface ModelCorpOutputFieldsMapper extends BaseMapper<ModelCorpOutputFields>
{
}

View File

@@ -0,0 +1,15 @@
package com.ruoyi.loanpricing.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.loanpricing.domain.entity.ModelRetailOutputFields;
/**
* 零售贷款定价模型输出字段Mapper接口
*
* @author ruoyi
* @date 2025-01-21
*/
public interface ModelRetailOutputFieldsMapper extends BaseMapper<ModelRetailOutputFields>
{
}

View File

@@ -0,0 +1,70 @@
package com.ruoyi.loanpricing.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.loanpricing.domain.dto.CorporateLoanPricingCreateDTO;
import com.ruoyi.loanpricing.domain.dto.PersonalLoanPricingCreateDTO;
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowListVO;
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowVO;
import java.util.List;
/**
* 利率定价流程Service接口
*
* @author ruoyi
* @date 2025-01-19
*/
public interface ILoanPricingWorkflowService
{
/**
* 发起个人客户利率定价流程
*
* @param dto 个人客户发起DTO
* @return 结果
*/
public LoanPricingWorkflow createPersonalLoanPricing(PersonalLoanPricingCreateDTO dto);
/**
* 发起企业客户利率定价流程
*
* @param dto 企业客户发起DTO
* @return 结果
*/
public LoanPricingWorkflow createCorporateLoanPricing(CorporateLoanPricingCreateDTO dto);
/**
* 查询利率定价流程列表
*
* @param loanPricingWorkflow 利率定价流程信息
* @return 利率定价流程集合
*/
public List<LoanPricingWorkflow> selectLoanPricingList(LoanPricingWorkflow loanPricingWorkflow);
/**
* 分页查询利率定价流程列表
*
* @param page 分页参数
* @param loanPricingWorkflow 利率定价流程信息
* @return 分页结果
*/
public IPage<LoanPricingWorkflowListVO> selectLoanPricingPage(Page<LoanPricingWorkflowListVO> page, LoanPricingWorkflow loanPricingWorkflow);
/**
* 查询利率定价流程详情
*
* @param serialNum 业务方流水号
* @return 利率定价流程
*/
public LoanPricingWorkflowVO selectLoanPricingBySerialNum(String serialNum);
/**
* 设定执行利率
*
* @param serialNum 业务方流水号
* @param executeRate 执行利率
* @return 是否成功
*/
public boolean setExecuteRate(String serialNum, String executeRate);
}

View File

@@ -0,0 +1,109 @@
package com.ruoyi.loanpricing.service;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.loanpricing.domain.dto.ModelInvokeDTO;
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
import com.ruoyi.loanpricing.domain.entity.ModelCorpOutputFields;
import com.ruoyi.loanpricing.domain.entity.ModelRetailOutputFields;
import com.ruoyi.loanpricing.mapper.LoanPricingWorkflowMapper;
import com.ruoyi.loanpricing.mapper.ModelCorpOutputFieldsMapper;
import com.ruoyi.loanpricing.mapper.ModelRetailOutputFieldsMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import java.util.Objects;
import javax.annotation.Resource;
/**
* @Author: wkc
* @CreateTime: 2026-01-21
*/
@Service
@Slf4j
@EnableAsync
public class LoanPricingModelService {
@Resource
private ModelService modelService;
@Resource
private LoanPricingWorkflowMapper loanPricingWorkflowMapper;
@Resource
private ModelRetailOutputFieldsMapper modelRetailOutputFieldsMapper;
@Resource
private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper;
@Resource
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
public void invokeModelAsync(Long workflowId) {
LoanPricingWorkflow loanPricingWorkflow = loanPricingWorkflowMapper.selectById(workflowId);
if (Objects.isNull(loanPricingWorkflow)){
log.error("未找到对应的流程信息,未调用模型服务");
return;
}
try
{
loanPricingWorkflow.setCustName(sensitiveFieldCryptoService.decrypt(loanPricingWorkflow.getCustName()));
loanPricingWorkflow.setIdNum(sensitiveFieldCryptoService.decrypt(loanPricingWorkflow.getIdNum()));
}
catch (RuntimeException ex)
{
log.error("贷款定价模型调用前敏感字段解密失败", ex);
throw ex;
}
ModelInvokeDTO modelInvokeDTO = new ModelInvokeDTO();
BeanUtils.copyProperties(loanPricingWorkflow, modelInvokeDTO);
if ("个人".equals(loanPricingWorkflow.getCustType()))
{
normalizePersonalModelInvokeDTO(modelInvokeDTO);
}
JSONObject response = modelService.invokeModel(modelInvokeDTO);
if (loanPricingWorkflow.getCustType().equals("个人")){
// 个人模型
ModelRetailOutputFields modelRetailOutputFields = JSON.parseObject(response.toJSONString(), ModelRetailOutputFields.class);
modelRetailOutputFieldsMapper.insert(modelRetailOutputFields);
log.info("个人模型调用成功");
LoanPricingWorkflow workflowToUpdate = new LoanPricingWorkflow();
workflowToUpdate.setId(loanPricingWorkflow.getId());
workflowToUpdate.setModelOutputId(modelRetailOutputFields.getId());
loanPricingWorkflowMapper.updateById(workflowToUpdate);
log.info("更新流程信息成功");
}else if (loanPricingWorkflow.getCustType().equals("企业")){
// 企业模型
ModelCorpOutputFields modelCorpOutputFields = JSON.parseObject(response.toJSONString(), ModelCorpOutputFields.class);
modelCorpOutputFieldsMapper.insert(modelCorpOutputFields);
log.info("企业模型调用成功");
LoanPricingWorkflow workflowToUpdate = new LoanPricingWorkflow();
workflowToUpdate.setId(loanPricingWorkflow.getId());
workflowToUpdate.setModelOutputId(modelCorpOutputFields.getId());
loanPricingWorkflowMapper.updateById(workflowToUpdate);
log.info("更新流程信息成功");
}
}
private void normalizePersonalModelInvokeDTO(ModelInvokeDTO modelInvokeDTO)
{
modelInvokeDTO.setBizProof(toZeroOne(modelInvokeDTO.getBizProof()));
modelInvokeDTO.setLoanLoop(toZeroOne(modelInvokeDTO.getLoanLoop()));
modelInvokeDTO.setCollThirdParty(toZeroOne(modelInvokeDTO.getCollThirdParty()));
}
private String toZeroOne(String value)
{
if ("true".equals(value) || "1".equals(value))
{
return "1";
}
if ("false".equals(value) || "0".equals(value))
{
return "0";
}
return value;
}
}

View File

@@ -0,0 +1,46 @@
package com.ruoyi.loanpricing.service;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@Service
public class LoanPricingSensitiveDisplayService
{
public String maskCustName(String custName)
{
if (!StringUtils.hasText(custName))
{
return custName;
}
if (custName.contains("公司") && custName.length() > 4)
{
return custName.substring(0, 2) + "*".repeat(custName.length() - 4) + custName.substring(custName.length() - 2);
}
if (custName.length() == 1)
{
return custName;
}
return custName.substring(0, 1) + "*".repeat(custName.length() - 1);
}
public String maskIdNum(String idNum)
{
if (!StringUtils.hasText(idNum))
{
return idNum;
}
if (idNum.startsWith("91") && idNum.length() == 18)
{
return idNum.substring(0, 2) + "*".repeat(13) + idNum.substring(idNum.length() - 3);
}
if (idNum.matches("\\d{17}[\\dXx]"))
{
return idNum.substring(0, 4) + "*".repeat(8) + idNum.substring(idNum.length() - 4);
}
if (idNum.length() > 5)
{
return idNum.substring(0, 2) + "*".repeat(idNum.length() - 5) + idNum.substring(idNum.length() - 3);
}
return "*".repeat(idNum.length());
}
}

View File

@@ -0,0 +1,70 @@
package com.ruoyi.loanpricing.service;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.loanpricing.domain.dto.ModelInvokeDTO;
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
import org.springframework.beans.factory.annotation.Value;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.Objects;
/**
* @Author 吴凯程
* @Date 2025/12/11
**/
@Service
@Slf4j
@EnableAsync
public class ModelService {
@Value("${model.url}")
private String modelUrl;
public JSONObject invokeModel(ModelInvokeDTO modelInvokeDTO) {
Map<String, String> requestBody = entityToMap(modelInvokeDTO);
JSONObject response = HttpUtils.doPostFormUrlEncoded(modelUrl, requestBody, null, JSONObject.class);
log.info("------------------->调用模型返回结果:" + JSON.toJSONString(response));
if(Objects.nonNull(response) && response.containsKey("code") && response.getInteger("code") == 10000){
JSONObject mappingOutputFields = response.getJSONObject("data").getJSONObject("mappingOutputFields");
// return JSON.parseObject(mappingOutputFields.toJSONString(), ModelOutputFields.class);
return mappingOutputFields;
}else{
log.error("------------------->调用模型失败,失败原因为:" + response.getString("message"));
throw new ServiceException("调用模型失败");
}
}
/**
* 使用FastJSON将实体类转换为Map<String, String>
* @param obj 待转换的实体类对象
* @return 转换后的Map
*/
public static Map<String, String> entityToMap(Object obj) {
if (obj == null) {
return null;
}
// 先转为JSON字符串再转换为指定类型的Map
String jsonStr = JSON.toJSONString(obj);
return JSON.parseObject(jsonStr, new TypeReference<Map<String, String>>() {});
}
}

View File

@@ -0,0 +1,68 @@
package com.ruoyi.loanpricing.service;
import com.ruoyi.common.exception.ServiceException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
@Service
public class SensitiveFieldCryptoService
{
private final String key;
public SensitiveFieldCryptoService(@Value("${loan-pricing.sensitive.key:}") String key)
{
this.key = key;
}
public String encrypt(String plainText)
{
validateKey();
if (!StringUtils.hasText(plainText))
{
return plainText;
}
try
{
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"));
return Base64.getEncoder().encodeToString(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)));
}
catch (Exception ex)
{
throw new ServiceException("贷款定价敏感字段加密失败");
}
}
public String decrypt(String cipherText)
{
validateKey();
if (!StringUtils.hasText(cipherText))
{
return cipherText;
}
try
{
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"));
return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)), StandardCharsets.UTF_8);
}
catch (Exception ex)
{
throw new ServiceException("贷款定价敏感字段解密失败");
}
}
private void validateKey()
{
if (!StringUtils.hasText(key))
{
throw new IllegalStateException("loan-pricing.sensitive.key 未配置");
}
}
}

View File

@@ -0,0 +1,262 @@
package com.ruoyi.loanpricing.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.loanpricing.domain.dto.CorporateLoanPricingCreateDTO;
import com.ruoyi.loanpricing.domain.dto.PersonalLoanPricingCreateDTO;
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
import com.ruoyi.loanpricing.domain.entity.ModelCorpOutputFields;
import com.ruoyi.loanpricing.domain.entity.ModelRetailOutputFields;
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowListVO;
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowVO;
import com.ruoyi.loanpricing.mapper.LoanPricingWorkflowMapper;
import com.ruoyi.loanpricing.mapper.ModelCorpOutputFieldsMapper;
import com.ruoyi.loanpricing.mapper.ModelRetailOutputFieldsMapper;
import com.ruoyi.loanpricing.service.ILoanPricingWorkflowService;
import com.ruoyi.loanpricing.service.LoanPricingSensitiveDisplayService;
import com.ruoyi.loanpricing.service.LoanPricingModelService;
import com.ruoyi.loanpricing.service.SensitiveFieldCryptoService;
import com.ruoyi.loanpricing.util.LoanPricingConverter;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import javax.annotation.Resource;
/**
* 利率定价流程Service业务层处理
*
* @author ruoyi
* @date 2025-01-19
*/
@Service
public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowService
{
@Resource
private LoanPricingWorkflowMapper loanPricingWorkflowMapper;
@Resource
private LoanPricingModelService loanPricingModelService;
@Resource
private ModelRetailOutputFieldsMapper modelRetailOutputFieldsMapper;
@Resource
private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper;
@Resource
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
@Resource
private LoanPricingSensitiveDisplayService loanPricingSensitiveDisplayService;
/**
* 发起利率定价流程
*
* @param loanPricingWorkflow 利率定价流程信息
* @return 结果
*/
@Transactional(rollbackFor = Exception.class)
public LoanPricingWorkflow createLoanPricing(LoanPricingWorkflow loanPricingWorkflow)
{
// 自动生成业务方流水号(时间戳)
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
String serialNum = sdf.format(new Date());
loanPricingWorkflow.setSerialNum(serialNum);
// 设置默认值
if (!StringUtils.hasText(loanPricingWorkflow.getOrgCode()))
{
loanPricingWorkflow.setOrgCode("892000");
}
if (!StringUtils.hasText(loanPricingWorkflow.getRunType()))
{
loanPricingWorkflow.setRunType("1");
}
loanPricingWorkflow.setCustName(sensitiveFieldCryptoService.encrypt(loanPricingWorkflow.getCustName()));
loanPricingWorkflow.setIdNum(sensitiveFieldCryptoService.encrypt(loanPricingWorkflow.getIdNum()));
loanPricingWorkflowMapper.insert(loanPricingWorkflow);
loanPricingModelService.invokeModelAsync(loanPricingWorkflow.getId());
return loanPricingWorkflow;
}
/**
* 发起个人客户利率定价流程
*
* @param dto 个人客户发起DTO
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public LoanPricingWorkflow createPersonalLoanPricing(PersonalLoanPricingCreateDTO dto) {
LoanPricingWorkflow entity = LoanPricingConverter.toEntity(dto);
return createLoanPricing(entity);
}
/**
* 发起企业客户利率定价流程
*
* @param dto 企业客户发起DTO
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public LoanPricingWorkflow createCorporateLoanPricing(CorporateLoanPricingCreateDTO dto) {
LoanPricingWorkflow entity = LoanPricingConverter.toEntity(dto);
return createLoanPricing(entity);
}
/**
* 查询利率定价流程列表
*
* @param loanPricingWorkflow 利率定价流程信息
* @return 利率定价流程
*/
@Override
public List<LoanPricingWorkflow> selectLoanPricingList(LoanPricingWorkflow loanPricingWorkflow)
{
LambdaQueryWrapper<LoanPricingWorkflow> wrapper = buildQueryWrapper(loanPricingWorkflow);
// 按更新时间倒序
wrapper.orderByDesc(LoanPricingWorkflow::getUpdateTime);
return loanPricingWorkflowMapper.selectList(wrapper);
}
/**
* 分页查询利率定价流程列表
*
* @param page 分页参数
* @param loanPricingWorkflow 利率定价流程信息
* @return 利率定价流程
*/
@Override
public IPage<LoanPricingWorkflowListVO> selectLoanPricingPage(Page<LoanPricingWorkflowListVO> page, LoanPricingWorkflow loanPricingWorkflow)
{
IPage<LoanPricingWorkflowListVO> pageResult = loanPricingWorkflowMapper.selectWorkflowPageWithRates(page, loanPricingWorkflow);
pageResult.getRecords().forEach(row -> row.setCustName(
loanPricingSensitiveDisplayService.maskCustName(
sensitiveFieldCryptoService.decrypt(row.getCustName()))));
return pageResult;
}
/**
* 查询利率定价流程详情
*
* @param serialNum 业务方流水号
* @return 利率定价流程
*/
@Override
public LoanPricingWorkflowVO selectLoanPricingBySerialNum(String serialNum)
{
LoanPricingWorkflowVO loanPricingWorkflowVO = new LoanPricingWorkflowVO();
LambdaQueryWrapper<LoanPricingWorkflow> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(LoanPricingWorkflow::getSerialNum, serialNum);
LoanPricingWorkflow loanPricingWorkflow = loanPricingWorkflowMapper.selectOne(wrapper);
String plainCustName = sensitiveFieldCryptoService.decrypt(loanPricingWorkflow.getCustName());
String plainIdNum = sensitiveFieldCryptoService.decrypt(loanPricingWorkflow.getIdNum());
loanPricingWorkflow.setCustName(loanPricingSensitiveDisplayService.maskCustName(plainCustName));
loanPricingWorkflow.setIdNum(loanPricingSensitiveDisplayService.maskIdNum(plainIdNum));
loanPricingWorkflowVO.setLoanPricingWorkflow(loanPricingWorkflow);
if (Objects.nonNull(loanPricingWorkflow.getModelOutputId())){
if (loanPricingWorkflow.getCustType().equals("个人")){
ModelRetailOutputFields modelRetailOutputFields = modelRetailOutputFieldsMapper.selectById(loanPricingWorkflow.getModelOutputId());
if (Objects.nonNull(modelRetailOutputFields))
{
maskModelRetailOutputBasicInfo(modelRetailOutputFields);
loanPricingWorkflow.setLoanRate(modelRetailOutputFields.getFinalCalculateRate());
}
loanPricingWorkflowVO.setModelRetailOutputFields(modelRetailOutputFields);
}
if (loanPricingWorkflow.getCustType().equals("企业")){
ModelCorpOutputFields modelCorpOutputFields = modelCorpOutputFieldsMapper.selectById(loanPricingWorkflow.getModelOutputId());
if (Objects.nonNull(modelCorpOutputFields))
{
maskModelCorpOutputBasicInfo(modelCorpOutputFields);
loanPricingWorkflow.setLoanRate(modelCorpOutputFields.getCalculateRate());
}
loanPricingWorkflowVO.setModelCorpOutputFields(modelCorpOutputFields);
}
}
return loanPricingWorkflowVO;
}
/**
* 构建查询条件
*
* @param loanPricingWorkflow 利率定价流程信息
* @return LambdaQueryWrapper
*/
private LambdaQueryWrapper<LoanPricingWorkflow> buildQueryWrapper(LoanPricingWorkflow loanPricingWorkflow)
{
LambdaQueryWrapper<LoanPricingWorkflow> wrapper = new LambdaQueryWrapper<>();
// 按创建者筛选
if (StringUtils.hasText(loanPricingWorkflow.getCreateBy()))
{
wrapper.like(LoanPricingWorkflow::getCreateBy, loanPricingWorkflow.getCreateBy());
}
// 按客户内码模糊查询
if (StringUtils.hasText(loanPricingWorkflow.getCustIsn()))
{
wrapper.like(LoanPricingWorkflow::getCustIsn, loanPricingWorkflow.getCustIsn());
}
// 按机构号筛选
if (StringUtils.hasText(loanPricingWorkflow.getOrgCode()))
{
wrapper.like(LoanPricingWorkflow::getOrgCode, loanPricingWorkflow.getOrgCode());
}
return wrapper;
}
private void maskModelRetailOutputBasicInfo(ModelRetailOutputFields modelRetailOutputFields)
{
modelRetailOutputFields.setCustName(
loanPricingSensitiveDisplayService.maskCustName(modelRetailOutputFields.getCustName()));
modelRetailOutputFields.setIdNum(
loanPricingSensitiveDisplayService.maskIdNum(modelRetailOutputFields.getIdNum()));
}
private void maskModelCorpOutputBasicInfo(ModelCorpOutputFields modelCorpOutputFields)
{
modelCorpOutputFields.setCustName(
loanPricingSensitiveDisplayService.maskCustName(modelCorpOutputFields.getCustName()));
modelCorpOutputFields.setIdNum(
loanPricingSensitiveDisplayService.maskIdNum(modelCorpOutputFields.getIdNum()));
}
/**
* 设定执行利率
*
* @param serialNum 业务方流水号
* @param executeRate 执行利率
* @return 是否成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean setExecuteRate(String serialNum, String executeRate) {
LambdaQueryWrapper<LoanPricingWorkflow> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(LoanPricingWorkflow::getSerialNum, serialNum);
LoanPricingWorkflow workflow = loanPricingWorkflowMapper.selectOne(wrapper);
if (workflow == null) {
return false;
}
workflow.setExecuteRate(executeRate);
int result = loanPricingWorkflowMapper.updateById(workflow);
return result > 0;
}
}

View File

@@ -0,0 +1,67 @@
package com.ruoyi.loanpricing.util;
import com.ruoyi.loanpricing.domain.dto.CorporateLoanPricingCreateDTO;
import com.ruoyi.loanpricing.domain.dto.PersonalLoanPricingCreateDTO;
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
/**
* 利率定价转换器
*
* @author ruoyi
* @date 2025-01-19
*/
public class LoanPricingConverter {
/**
* 个人客户DTO转Entity
*
* @param dto 个人客户发起DTO
* @return 利率定价流程实体
*/
public static LoanPricingWorkflow toEntity(PersonalLoanPricingCreateDTO dto) {
LoanPricingWorkflow entity = new LoanPricingWorkflow();
// 映射共同字段
entity.setCustIsn(dto.getCustIsn());
entity.setCustType("个人");
entity.setCustName(dto.getCustName());
entity.setIdType(dto.getIdType());
entity.setIdNum(dto.getIdNum());
entity.setGuarType(dto.getGuarType());
entity.setApplyAmt(dto.getApplyAmt());
entity.setLoanPurpose(dto.getLoanPurpose());
entity.setLoanTerm(dto.getLoanTerm());
entity.setCollType(dto.getCollType());
entity.setCollThirdParty(dto.getCollThirdParty());
// 映射个人特有字段
entity.setBizProof(dto.getBizProof());
entity.setLoanLoop(dto.getLoanLoop());
return entity;
}
/**
* 企业客户DTO转Entity
*
* @param dto 企业客户发起DTO
* @return 利率定价流程实体
*/
public static LoanPricingWorkflow toEntity(CorporateLoanPricingCreateDTO dto) {
LoanPricingWorkflow entity = new LoanPricingWorkflow();
// 映射共同字段
entity.setCustIsn(dto.getCustIsn());
entity.setCustType("企业");
entity.setCustName(dto.getCustName());
entity.setIdType(dto.getIdType());
entity.setIdNum(dto.getIdNum());
entity.setGuarType(dto.getGuarType());
entity.setApplyAmt(dto.getApplyAmt());
entity.setCollType(dto.getCollType());
entity.setCollThirdParty(dto.getCollThirdParty());
// 映射企业特有字段
entity.setIsAgriGuar(dto.getIsAgriGuar());
entity.setIsGreenLoan(dto.getIsGreenLoan());
entity.setIsTechEnt(dto.getIsTechEnt());
entity.setIsTradeConstruction(dto.getIsTradeConstruction());
entity.setLoanTerm(dto.getLoanTerm());
return entity;
}
}

View File

@@ -0,0 +1,68 @@
{
"traceId": "350626558347246735E7F4722CUZRWOMNRR53O0",
"cost": 2267,
"tokenId": "17364055486305E7F4722M8IPFWNL8TOBEB",
"mappingOutputFields": {
"custIsn": "CUST20260121001",
"custType": "企业客户",
"guarType": "抵押担保",
"custName": "北京智联科技有限公司",
"idType": "营业执照",
"idNum": "91110108MA00XXXXXX",
"baseLoanRate": "3.45",
"isFirstLoan": "N",
"faithDay": "730",
"bpFirstLoan": "0",
"bpAgeLoan": "5.2",
"totalBpLoyalty": "8.5",
"balanceAvg": "5000000.00",
"loanAvg": "3000000.00",
"derivationRate": "1.8",
"totalBpContribution": "12.3",
"midEntConnect": "100000.00",
"midEntEffect": "50000.00",
"midEntInter": "80000.00",
"midEntAccept": "200000.00",
"midEntDiscount": "150000.00",
"midEntEleDdc": "30000.00",
"midEntWaterDdc": "10000.00",
"midEntTax": "40000.00",
"bpMid": "6.8",
"payroll": "200",
"invLoanAmount": "2500000.00",
"bpPayroll": "4.1",
"isCleanEnt": "Y",
"hasSettleAcct": "Y",
"isAgriGuar": "N",
"isGreenLoan": "Y",
"isTechEnt": "Y",
"bpEntType": "7.5",
"totoalBpRelevance": "9.2",
"loanTerm": "36",
"bpLoanTerm": "3.3",
"applyAmt": "5000000.00",
"bpLoanAmount": "5.8",
"collType": "房产抵押",
"collThirdParty": "N",
"bpCollateral": "4.5",
"greyCust": "N",
"prinOverdue": "N",
"interestOverdue": "N",
"cardOverdue": "N",
"bpGreyOverdue": "0",
"totoalBpRisk": "1.2",
"totalBp": "48.2",
"calculateRate": "3.932"
},
"extensionMap": {},
"reasonMessage": "Running successfully",
"bizTime": 1736405548630,
"outputFields": {},
"workflowCode": "TBKH",
"orgCode": "802000",
"bizId": "2025010914345",
"reasonCode": 200,
"workflowVersion": 14,
"callTime": 1736405548630,
"status": 1
}

View File

@@ -0,0 +1,73 @@
{
"traceId": "350626558347246735E7F4722CUZRWOMNRR53O0",
"cost": 2267,
"tokenId": "17364055486305E7F4722M8IPFWNL8TOBEB",
"mappingOutputFields": {
"custIsn": "CUST20260121001",
"custType": "个人",
"guarType": "信用担保",
"custName": "张三",
"idType": "身份证",
"idNum": "330106199001011234",
"baseLoanRate": "4.35",
"isFirstLoan": "是",
"faithDay": "365",
"custAge": "36",
"bpFirstLoan": "50",
"bpAgeLoan": "30",
"bpAge": "20",
"totalBpLoyalty": "95",
"balanceAvg": "50000.00",
"loanAvg": "100000.00",
"derivationRate": "1.2",
"totalBpContribution": "88",
"midPerCard": "1000.50",
"midPerPass": "500.00",
"midPerHarvest": "800.20",
"midPerEffect": "是",
"midPerQuickPay": "300.00",
"midPerEleDdc": "150.00",
"midPerWaterDdc": "80.00",
"midPerHuashuDdc": "120.00",
"MidPerGasDdc": "90.00",
"midPerCitizencard": "200.00",
"midPerFinMan": "5000.00",
"midPerEtc": "180.00",
"bpMid": "45",
"totoalBpRelevance": "90",
"applyAmt": "200000.00",
"bpLoanAmount": "60",
"loanPurpose": "个人消费",
"bizProof": "有",
"bpLoanUse": "55",
"loanLoop": "支持",
"bpLoanLoop": "40",
"collType": "无抵质押",
"collThirdParty": "否",
"bpCollateral": "0",
"greyCust": "否",
"prinOverdue": "否",
"interestOverdue": "否",
"cardOverdue": "否",
"bpGreyOverdue": "98",
"totoalBpRisk": "95",
"totalBp": "350",
"calculateRate": "6.15",
"loanRateHistory": "6.40",
"minRateProduct": "5.50",
"smoothRange": "-0.10",
"finalCalculateRate": "6.05",
"referenceRate": "5.95"
},
"extensionMap": {},
"reasonMessage": "Running successfully",
"bizTime": 1736405548630,
"outputFields": {},
"workflowCode": "TBKH",
"orgCode": "802000",
"bizId": "2025010914345",
"reasonCode": 200,
"workflowVersion": 14,
"callTime": 1736405548630,
"status": 1
}

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.loanpricing.mapper.LoanPricingWorkflowMapper">
<select id="selectWorkflowPageWithRates" resultType="com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowListVO">
SELECT
lpw.serial_num AS serialNum,
lpw.cust_name AS custName,
lpw.cust_type AS custType,
lpw.guar_type AS guarType,
lpw.apply_amt AS applyAmt,
CASE
WHEN lpw.cust_type = '个人' THEN mr.final_calculate_rate
WHEN lpw.cust_type = '企业' THEN mc.calculate_rate
ELSE NULL
END AS calculateRate,
lpw.execute_rate AS executeRate,
lpw.create_time AS createTime,
lpw.create_by AS createBy
FROM loan_pricing_workflow lpw
LEFT JOIN model_retail_output_fields mr ON lpw.model_output_id = mr.id
LEFT JOIN model_corp_output_fields mc ON lpw.model_output_id = mc.id
<where>
<if test="query != null and query.createBy != null and query.createBy != ''">
AND lpw.create_by LIKE CONCAT('%', #{query.createBy}, '%')
</if>
<if test="query != null and query.custIsn != null and query.custIsn != ''">
AND lpw.cust_isn LIKE CONCAT('%', #{query.custIsn}, '%')
</if>
<if test="query != null and query.orgCode != null and query.orgCode != ''">
AND lpw.org_code LIKE CONCAT('%', #{query.orgCode}, '%')
</if>
</where>
ORDER BY lpw.update_time DESC
</select>
</mapper>

View File

@@ -0,0 +1,26 @@
package com.ruoyi.loanpricing.domain.entity;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
class ModelRetailOutputFieldsTest
{
@Test
void shouldContainLatestRetailDisplayRateFields()
{
Set<String> fieldNames = Arrays.stream(ModelRetailOutputFields.class.getDeclaredFields())
.map(Field::getName)
.collect(Collectors.toSet());
assertTrue(fieldNames.contains("loanRateHistory"), "缺少字段 loanRateHistory");
assertTrue(fieldNames.contains("minRateProduct"), "缺少字段 minRateProduct");
assertTrue(fieldNames.contains("smoothRange"), "缺少字段 smoothRange");
assertTrue(fieldNames.contains("finalCalculateRate"), "缺少字段 finalCalculateRate");
assertTrue(fieldNames.contains("referenceRate"), "缺少字段 referenceRate");
}
}

View File

@@ -0,0 +1,19 @@
package com.ruoyi.loanpricing.domain.vo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class LoanPricingWorkflowListVOTest
{
@Test
void shouldExposeCalculateRateAndExecuteRateFields()
{
LoanPricingWorkflowListVO vo = new LoanPricingWorkflowListVO();
vo.setCalculateRate("6.15");
vo.setExecuteRate("5.80");
assertEquals("6.15", vo.getCalculateRate());
assertEquals("5.80", vo.getExecuteRate());
}
}

View File

@@ -0,0 +1,22 @@
package com.ruoyi.loanpricing.mapper;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StreamUtils;
class LoanPricingWorkflowMapperXmlTest
{
@Test
void shouldUseRetailFinalCalculateRateInWorkflowListQuery() throws IOException
{
ClassPathResource resource = new ClassPathResource("mapper/loanpricing/LoanPricingWorkflowMapper.xml");
String xml = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
assertTrue(xml.contains("WHEN lpw.cust_type = '个人' THEN mr.final_calculate_rate"));
assertTrue(xml.contains("WHEN lpw.cust_type = '企业' THEN mc.calculate_rate"));
}
}

View File

@@ -0,0 +1,125 @@
package com.ruoyi.loanpricing.service;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.loanpricing.domain.dto.ModelInvokeDTO;
import com.ruoyi.loanpricing.domain.dto.PersonalLoanPricingCreateDTO;
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
import com.ruoyi.loanpricing.mapper.LoanPricingWorkflowMapper;
import com.ruoyi.loanpricing.mapper.ModelCorpOutputFieldsMapper;
import com.ruoyi.loanpricing.mapper.ModelRetailOutputFieldsMapper;
import com.ruoyi.loanpricing.util.LoanPricingConverter;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class LoanPricingModelServicePersonalParamsTest {
@Mock
private ModelService modelService;
@Mock
private LoanPricingWorkflowMapper loanPricingWorkflowMapper;
@Mock
private ModelRetailOutputFieldsMapper modelRetailOutputFieldsMapper;
@Mock
private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper;
@Mock
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
@InjectMocks
private LoanPricingModelService loanPricingModelService;
@Test
void shouldContainLoanPurposeAndLoanTermInPersonalCreateDto() throws NoSuchFieldException {
assertNotNull(PersonalLoanPricingCreateDTO.class.getDeclaredField("loanPurpose"));
assertNotNull(PersonalLoanPricingCreateDTO.class.getDeclaredField("loanTerm"));
}
@Test
void shouldMapLoanPurposeAndLoanTermFromPersonalDto() {
PersonalLoanPricingCreateDTO dto = new PersonalLoanPricingCreateDTO();
dto.setCustIsn("CUST001");
dto.setCustName("张三");
dto.setGuarType("信用");
dto.setApplyAmt("100000");
dto.setLoanPurpose("business");
dto.setLoanTerm("3");
LoanPricingWorkflow workflow = LoanPricingConverter.toEntity(dto);
assertEquals("business", workflow.getLoanPurpose());
assertEquals("3", workflow.getLoanTerm());
}
@Test
void shouldContainLoanTermAndLoanLoopInModelInvokeDto() throws NoSuchFieldException {
assertNotNull(ModelInvokeDTO.class.getDeclaredField("loanTerm"));
assertNotNull(ModelInvokeDTO.class.getDeclaredField("loanLoop"));
}
@Test
void shouldInvokePersonalModelWithExpectedParams() {
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
workflow.setId(1L);
workflow.setSerialNum("202604090001");
workflow.setOrgCode("892000");
workflow.setRunType("1");
workflow.setCustIsn("CUST001");
workflow.setCustType("个人");
workflow.setCustName("cipher-name");
workflow.setIdType("身份证");
workflow.setIdNum("cipher-id");
workflow.setGuarType("信用");
workflow.setApplyAmt("100000");
workflow.setLoanPurpose("business");
workflow.setLoanTerm("3");
workflow.setBizProof("true");
workflow.setLoanLoop("false");
workflow.setCollThirdParty("true");
workflow.setCollType("一类");
JSONObject response = new JSONObject();
response.put("calculateRate", "6.15");
when(loanPricingWorkflowMapper.selectById(1L)).thenReturn(workflow);
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
when(modelService.invokeModel(any())).thenReturn(response);
loanPricingModelService.invokeModelAsync(1L);
verify(modelService).invokeModel(argThat((ModelInvokeDTO dto) ->
Objects.equals("202604090001", dto.getSerialNum())
&& Objects.equals("892000", dto.getOrgCode())
&& Objects.equals("1", dto.getRunType())
&& Objects.equals("CUST001", dto.getCustIsn())
&& Objects.equals("个人", dto.getCustType())
&& Objects.equals("张三", dto.getCustName())
&& Objects.equals("身份证", dto.getIdType())
&& Objects.equals("110101199001011234", dto.getIdNum())
&& Objects.equals("信用", dto.getGuarType())
&& Objects.equals("100000", dto.getApplyAmt())
&& Objects.equals("business", dto.getLoanPurpose())
&& Objects.equals("3", dto.getLoanTerm())
&& Objects.equals("1", dto.getBizProof())
&& Objects.equals("0", dto.getLoanLoop())
&& Objects.equals("1", dto.getCollThirdParty())
&& Objects.equals("一类", dto.getCollType())));
}
}

View File

@@ -0,0 +1,90 @@
package com.ruoyi.loanpricing.service;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.loanpricing.domain.dto.ModelInvokeDTO;
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
import com.ruoyi.loanpricing.mapper.LoanPricingWorkflowMapper;
import com.ruoyi.loanpricing.mapper.ModelCorpOutputFieldsMapper;
import com.ruoyi.loanpricing.mapper.ModelRetailOutputFieldsMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Objects;
@ExtendWith(MockitoExtension.class)
class LoanPricingModelServiceTest
{
@Mock
private ModelService modelService;
@Mock
private LoanPricingWorkflowMapper loanPricingWorkflowMapper;
@Mock
private ModelRetailOutputFieldsMapper modelRetailOutputFieldsMapper;
@Mock
private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper;
@Mock
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
@InjectMocks
private LoanPricingModelService loanPricingModelService;
@Test
void shouldDecryptCustNameAndIdNumBeforeInvokeModel()
{
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
workflow.setId(1L);
workflow.setCustType("个人");
workflow.setCustName("cipher-name");
workflow.setIdNum("cipher-id");
JSONObject response = new JSONObject();
response.put("calculateRate", "6.15");
when(loanPricingWorkflowMapper.selectById(1L)).thenReturn(workflow);
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
when(modelService.invokeModel(any())).thenReturn(response);
loanPricingModelService.invokeModelAsync(1L);
verify(modelService).invokeModel(argThat((ModelInvokeDTO dto) ->
Objects.equals("张三", dto.getCustName())
&& Objects.equals("110101199001011234", dto.getIdNum())));
}
@Test
void shouldNotWritePlainCustNameAndIdNumBackWhenUpdatingWorkflow()
{
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
workflow.setId(2L);
workflow.setCustType("个人");
workflow.setCustName("cipher-name");
workflow.setIdNum("cipher-id");
JSONObject response = new JSONObject();
response.put("calculateRate", "6.15");
when(loanPricingWorkflowMapper.selectById(2L)).thenReturn(workflow);
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
when(modelService.invokeModel(any())).thenReturn(response);
loanPricingModelService.invokeModelAsync(2L);
verify(loanPricingWorkflowMapper).updateById(argThat((LoanPricingWorkflow entity) ->
!Objects.equals("张三", entity.getCustName())
&& !Objects.equals("110101199001011234", entity.getIdNum())));
}
}

View File

@@ -0,0 +1,24 @@
package com.ruoyi.loanpricing.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class LoanPricingSensitiveDisplayServiceTest
{
private final LoanPricingSensitiveDisplayService displayService = new LoanPricingSensitiveDisplayService();
@Test
void shouldMaskPersonalNameAndIdNum()
{
assertEquals("张*", displayService.maskCustName("张三"));
assertEquals("1101********1234", displayService.maskIdNum("110101199001011234"));
}
@Test
void shouldMaskCorporateNameAndCreditCode()
{
assertEquals("测试****公司", displayService.maskCustName("测试科技有限公司"));
assertEquals("91*************00X", displayService.maskIdNum("91110000100000000X"));
}
}

View File

@@ -0,0 +1,32 @@
package com.ruoyi.loanpricing.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
class SensitiveFieldCryptoServiceTest
{
@Test
void shouldEncryptAndDecryptCustNameAndIdNum()
{
SensitiveFieldCryptoService service = new SensitiveFieldCryptoService("1234567890abcdef");
String nameCipher = service.encrypt("张三");
String idNumCipher = service.encrypt("110101199001011234");
assertNotEquals("张三", nameCipher);
assertNotEquals("110101199001011234", idNumCipher);
assertEquals("张三", service.decrypt(nameCipher));
assertEquals("110101199001011234", service.decrypt(idNumCipher));
}
@Test
void shouldRejectBlankKeyConfiguration()
{
SensitiveFieldCryptoService service = new SensitiveFieldCryptoService("");
assertThrows(IllegalStateException.class, () -> service.encrypt("张三"));
}
}

View File

@@ -0,0 +1,256 @@
package com.ruoyi.loanpricing.service.impl;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
import com.ruoyi.loanpricing.domain.entity.ModelCorpOutputFields;
import com.ruoyi.loanpricing.domain.entity.ModelRetailOutputFields;
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowListVO;
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowVO;
import com.ruoyi.loanpricing.mapper.LoanPricingWorkflowMapper;
import com.ruoyi.loanpricing.mapper.ModelCorpOutputFieldsMapper;
import com.ruoyi.loanpricing.mapper.ModelRetailOutputFieldsMapper;
import com.ruoyi.loanpricing.service.LoanPricingSensitiveDisplayService;
import com.ruoyi.loanpricing.service.LoanPricingModelService;
import com.ruoyi.loanpricing.service.SensitiveFieldCryptoService;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Collections;
import java.util.Objects;
@ExtendWith(MockitoExtension.class)
class LoanPricingWorkflowServiceImplTest
{
@Mock
private LoanPricingWorkflowMapper loanPricingWorkflowMapper;
@Mock
private LoanPricingModelService loanPricingModelService;
@Mock
private ModelRetailOutputFieldsMapper modelRetailOutputFieldsMapper;
@Mock
private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper;
@Mock
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
@Mock
private LoanPricingSensitiveDisplayService loanPricingSensitiveDisplayService;
@InjectMocks
private LoanPricingWorkflowServiceImpl loanPricingWorkflowService;
@Test
void shouldEncryptCustNameAndIdNumBeforeInsert()
{
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
workflow.setCustName("张三");
workflow.setIdNum("110101199001011234");
workflow.setCustIsn("CUST001");
when(sensitiveFieldCryptoService.encrypt("张三")).thenReturn("cipher-name");
when(sensitiveFieldCryptoService.encrypt("110101199001011234")).thenReturn("cipher-id");
loanPricingWorkflowService.createLoanPricing(workflow);
verify(loanPricingWorkflowMapper).insert(argThat((LoanPricingWorkflow entity) ->
Objects.equals("cipher-name", entity.getCustName())
&& Objects.equals("cipher-id", entity.getIdNum())
&& Objects.equals("CUST001", entity.getCustIsn())));
}
@Test
void shouldReturnPagedWorkflowListWithCalculateRate()
{
LoanPricingWorkflowListVO row = new LoanPricingWorkflowListVO();
row.setCalculateRate("6.15");
Page<LoanPricingWorkflowListVO> pageResult = new Page<>(1, 10);
pageResult.setRecords(java.util.List.of(row));
when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(pageResult);
IPage<LoanPricingWorkflowListVO> result = loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), new LoanPricingWorkflow());
assertEquals("6.15", result.getRecords().get(0).getCalculateRate());
}
@Test
void shouldMaskCustNameWhenReturningPagedWorkflowList()
{
LoanPricingWorkflowListVO row = new LoanPricingWorkflowListVO();
row.setCustName("cipher-name");
Page<LoanPricingWorkflowListVO> pageResult = new Page<>(1, 10);
pageResult.setRecords(Collections.singletonList(row));
when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(pageResult);
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
when(loanPricingSensitiveDisplayService.maskCustName("张三")).thenReturn("张*");
IPage<LoanPricingWorkflowListVO> result = loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), new LoanPricingWorkflow());
assertEquals("张*", result.getRecords().get(0).getCustName());
}
@Test
void shouldUseCustIsnInsteadOfCustNameAsQueryCondition()
{
LoanPricingWorkflow query = new LoanPricingWorkflow();
query.setCustIsn("CUST001");
query.setCustName("张三");
when(loanPricingWorkflowMapper.selectList(any())).thenReturn(Collections.emptyList());
loanPricingWorkflowService.selectLoanPricingList(query);
ArgumentCaptor<LambdaQueryWrapper<LoanPricingWorkflow>> wrapperCaptor = ArgumentCaptor.forClass(LambdaQueryWrapper.class);
verify(loanPricingWorkflowMapper).selectList(wrapperCaptor.capture());
LambdaQueryWrapper<LoanPricingWorkflow> wrapper = wrapperCaptor.getValue();
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), LoanPricingWorkflow.class);
String sqlSegment = wrapper.getSqlSegment();
assertTrue(sqlSegment.contains("cust_isn"), sqlSegment);
assertTrue(!sqlSegment.contains("cust_name"), sqlSegment);
}
@Test
void shouldUseRetailModelOutputFinalCalculateRateForWorkflowDetail()
{
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
workflow.setSerialNum("P20260328001");
workflow.setCustType("个人");
workflow.setModelOutputId(11L);
workflow.setLoanRate("4.35");
ModelRetailOutputFields retailOutputFields = new ModelRetailOutputFields();
retailOutputFields.setCalculateRate("6.15");
retailOutputFields.setFinalCalculateRate("6.05");
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
when(modelRetailOutputFieldsMapper.selectById(11L)).thenReturn(retailOutputFields);
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001");
assertEquals("6.05", result.getLoanPricingWorkflow().getLoanRate());
assertEquals("6.15", result.getModelRetailOutputFields().getCalculateRate());
assertEquals("6.05", result.getModelRetailOutputFields().getFinalCalculateRate());
}
@Test
void shouldMaskCustNameAndIdNumWhenReturningWorkflowDetail()
{
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
workflow.setSerialNum("P20260328001");
workflow.setCustType("个人");
workflow.setCustName("cipher-name");
workflow.setIdNum("cipher-id");
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
when(loanPricingSensitiveDisplayService.maskCustName("张三")).thenReturn("张*");
when(loanPricingSensitiveDisplayService.maskIdNum("110101199001011234")).thenReturn("1101********1234");
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001");
assertEquals("张*", result.getLoanPricingWorkflow().getCustName());
assertEquals("1101********1234", result.getLoanPricingWorkflow().getIdNum());
}
@Test
void shouldMaskCustNameAndIdNumInRetailModelOutputBasicInfo()
{
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
workflow.setSerialNum("P20260328001");
workflow.setCustType("个人");
workflow.setCustName("cipher-name");
workflow.setIdNum("cipher-id");
workflow.setModelOutputId(11L);
ModelRetailOutputFields retailOutputFields = new ModelRetailOutputFields();
retailOutputFields.setCustName("张三");
retailOutputFields.setIdNum("110101199001011234");
retailOutputFields.setCalculateRate("6.15");
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
when(modelRetailOutputFieldsMapper.selectById(11L)).thenReturn(retailOutputFields);
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
when(loanPricingSensitiveDisplayService.maskCustName("张三")).thenReturn("张*");
when(loanPricingSensitiveDisplayService.maskIdNum("110101199001011234")).thenReturn("1101********1234");
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001");
assertEquals("张*", result.getModelRetailOutputFields().getCustName());
assertEquals("1101********1234", result.getModelRetailOutputFields().getIdNum());
}
@Test
void shouldUseCorporateModelOutputCalculateRateForWorkflowDetail()
{
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
workflow.setSerialNum("C20260328001");
workflow.setCustType("企业");
workflow.setModelOutputId(22L);
workflow.setLoanRate("3.80");
ModelCorpOutputFields corpOutputFields = new ModelCorpOutputFields();
corpOutputFields.setCalculateRate("3.932");
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
when(modelCorpOutputFieldsMapper.selectById(22L)).thenReturn(corpOutputFields);
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("C20260328001");
assertEquals("3.932", result.getLoanPricingWorkflow().getLoanRate());
assertEquals("3.932", result.getModelCorpOutputFields().getCalculateRate());
}
@Test
void shouldMaskCustNameAndIdNumInCorporateModelOutputBasicInfo()
{
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
workflow.setSerialNum("C20260328001");
workflow.setCustType("企业");
workflow.setCustName("cipher-name");
workflow.setIdNum("cipher-id");
workflow.setModelOutputId(22L);
ModelCorpOutputFields corpOutputFields = new ModelCorpOutputFields();
corpOutputFields.setCustName("测试科技有限公司");
corpOutputFields.setIdNum("91110000100000000X");
corpOutputFields.setCalculateRate("3.932");
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
when(modelCorpOutputFieldsMapper.selectById(22L)).thenReturn(corpOutputFields);
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("测试科技有限公司");
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("91110000100000000X");
when(loanPricingSensitiveDisplayService.maskCustName("测试科技有限公司")).thenReturn("测试****公司");
when(loanPricingSensitiveDisplayService.maskIdNum("91110000100000000X")).thenReturn("91*************00X");
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("C20260328001");
assertEquals("测试****公司", result.getModelCorpOutputFields().getCustName());
assertEquals("91*************00X", result.getModelCorpOutputFields().getIdNum());
}
}

View File

@@ -4,8 +4,9 @@ VUE_APP_TITLE = 若依管理系统
# 开发环境配置
ENV = 'development'
# 若依管理系统/开发环境
VUE_APP_BASE_API = '/dev-api'
# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true
# 若依管理系统/开发环境
VUE_APP_BASE_API = '/dev-api'
VUE_APP_PASSWORD_TRANSFER_KEY = '1234567890abcdef'
# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true

View File

@@ -4,5 +4,6 @@ VUE_APP_TITLE = 若依管理系统
# 生产环境配置
ENV = 'production'
# 若依管理系统/生产环境
VUE_APP_BASE_API = '/prod-api'
# 若依管理系统/生产环境
VUE_APP_BASE_API = '/prod-api'
VUE_APP_PASSWORD_TRANSFER_KEY = '1234567890abcdef'

View File

@@ -28,6 +28,7 @@
"axios": "0.30.3",
"clipboard": "2.0.8",
"core-js": "3.37.1",
"crypto-js": "4.2.0",
"echarts": "5.4.0",
"element-ui": "2.15.14",
"file-saver": "2.0.5",
@@ -54,6 +55,7 @@
"chalk": "4.1.0",
"compression-webpack-plugin": "6.1.2",
"connect": "3.6.6",
"html-webpack-plugin": "3.2.0",
"sass": "1.32.13",
"sass-loader": "10.1.1",
"script-ext-html-webpack-plugin": "2.1.5",

View File

@@ -0,0 +1,45 @@
import request from '@/utils/request'
// 查询利率定价流程列表
export function listWorkflow(query) {
return request({
url: '/loanPricing/workflow/list',
method: 'get',
params: query
})
}
// 查询利率定价流程详情
export function getWorkflow(serialNum) {
return request({
url: '/loanPricing/workflow/' + serialNum,
method: 'get'
})
}
// 创建个人客户利率定价流程
export function createPersonalWorkflow(data) {
return request({
url: '/loanPricing/workflow/create/personal',
method: 'post',
data: data
})
}
// 创建企业客户利率定价流程
export function createCorporateWorkflow(data) {
return request({
url: '/loanPricing/workflow/create/corporate',
method: 'post',
data: data
})
}
// 设定执行利率
export function setExecuteRate(serialNum, executeRate) {
return request({
url: '/loanPricing/workflow/' + serialNum + '/executeRate',
method: 'put',
data: { executeRate: executeRate }
})
}

View File

@@ -1,14 +1,15 @@
import request from '@/utils/request'
// 登录方法
export function login(username, password, code, uuid) {
const data = {
username,
password,
code,
uuid
}
return request({
import request from '@/utils/request'
import { encryptPasswordFields } from '@/utils/passwordTransfer'
// 登录方法
export function login(username, password, code, uuid) {
const data = encryptPasswordFields({
username,
password,
code,
uuid
}, ['password'], process.env.VUE_APP_PASSWORD_TRANSFER_KEY)
return request({
url: '/login',
headers: {
isToken: false,
@@ -17,19 +18,20 @@ export function login(username, password, code, uuid) {
method: 'post',
data: data
})
}
// 注册方法
export function register(data) {
return request({
url: '/register',
}
// 注册方法
export function register(data) {
const payload = encryptPasswordFields(data, ['password'], process.env.VUE_APP_PASSWORD_TRANSFER_KEY)
return request({
url: '/register',
headers: {
isToken: false
},
method: 'post',
data: data
})
}
},
method: 'post',
data: payload
})
}
// 获取用户详细信息
export function getInfo() {
@@ -66,4 +68,4 @@ export function getCodeImg() {
method: 'get',
timeout: 20000
})
}
}

View File

@@ -1,5 +1,6 @@
import request from '@/utils/request'
import { parseStrEmpty } from "@/utils/ruoyi"
import request from '@/utils/request'
import { parseStrEmpty } from "@/utils/ruoyi"
import { encryptPasswordFields } from '@/utils/passwordTransfer'
// 查询用户列表
export function listUser(query) {
@@ -19,13 +20,14 @@ export function getUser(userId) {
}
// 新增用户
export function addUser(data) {
return request({
url: '/system/user',
method: 'post',
data: data
})
}
export function addUser(data) {
const payload = encryptPasswordFields(data, ['password'], process.env.VUE_APP_PASSWORD_TRANSFER_KEY)
return request({
url: '/system/user',
method: 'post',
data: payload
})
}
// 修改用户
export function updateUser(data) {
@@ -45,12 +47,12 @@ export function delUser(userId) {
}
// 用户密码重置
export function resetUserPwd(userId, password) {
const data = {
userId,
password
}
return request({
export function resetUserPwd(userId, password) {
const data = encryptPasswordFields({
userId,
password
}, ['password'], process.env.VUE_APP_PASSWORD_TRANSFER_KEY)
return request({
url: '/system/user/resetPwd',
method: 'put',
data: data
@@ -88,12 +90,12 @@ export function updateUserProfile(data) {
}
// 用户密码重置
export function updateUserPwd(oldPassword, newPassword) {
const data = {
oldPassword,
newPassword
}
return request({
export function updateUserPwd(oldPassword, newPassword) {
const data = encryptPasswordFields({
oldPassword,
newPassword
}, ['oldPassword', 'newPassword'], process.env.VUE_APP_PASSWORD_TRANSFER_KEY)
return request({
url: '/system/user/profile/updatePwd',
method: 'put',
data: data

View File

@@ -0,0 +1,14 @@
import CryptoJS from 'crypto-js'
export function encryptPasswordFields(payload, fields, key) {
const next = { ...payload }
fields.forEach((field) => {
if (next[field]) {
next[field] = CryptoJS.AES.encrypt(next[field], CryptoJS.enc.Utf8.parse(key), {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString()
}
})
return next
}

View File

@@ -0,0 +1,100 @@
<template>
<el-card class="bargaining-pool-card">
<div slot="header" class="card-header">
<span class="card-title">议价池</span>
</div>
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="网点议价池">
{{ displayBranchPool }}
</el-descriptions-item>
<el-descriptions-item label="支行议价池">
{{ displaySubBranchPool }}
</el-descriptions-item>
<el-descriptions-item label="私域池">
{{ displayPrivateDomainPool }}
</el-descriptions-item>
<el-descriptions-item label="超利分成">
{{ displayExcessProfitShare }}
</el-descriptions-item>
</el-descriptions>
</el-card>
</template>
<script>
export default {
name: "BargainingPoolDisplay",
props: {
branchPool: {
type: [Number, String],
default: 0
},
subBranchPool: {
type: [Number, String],
default: 0
},
privateDomainPool: {
type: [Number, String],
default: 0
},
excessProfitShare: {
type: [Number, String],
default: 0
}
},
computed: {
displayBranchPool() {
const value = this.branchPool
if (value === null || value === undefined || value === '') {
return '0'
}
return value
},
displaySubBranchPool() {
const value = this.subBranchPool
if (value === null || value === undefined || value === '') {
return '0'
}
return value
},
displayPrivateDomainPool() {
const value = this.privateDomainPool
if (value === null || value === undefined || value === '') {
return '0'
}
return value
},
displayExcessProfitShare() {
const value = this.excessProfitShare
if (value === null || value === undefined || value === '') {
return '0'
}
return value
}
}
}
</script>
<style lang="scss" scoped>
.bargaining-pool-card {
::v-deep .el-card__header {
padding: 16px 20px;
background-color: #fafafa;
border-bottom: 1px solid #ebeef5;
}
.card-header {
display: flex;
align-items: center;
.card-title {
font-size: 16px;
font-weight: 500;
color: #303133;
}
}
::v-deep .el-card__body {
padding: 20px;
}
}
</style>

View File

@@ -0,0 +1,303 @@
<template>
<el-dialog title="新增企业利率定价流程" :visible.sync="dialogVisible" width="900px" append-to-body
@close="handleClose">
<el-form ref="form" :model="form" :rules="rules" label-width="140px" class="workflow-create-form">
<!-- 基本信息 -->
<el-divider content-position="left">基本信息</el-divider>
<el-row>
<el-col :span="12">
<el-form-item label="客户内码" prop="custIsn">
<el-input v-model="form.custIsn" placeholder="请输入客户内码"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="客户名称" prop="custName">
<el-input v-model="form.custName" placeholder="请输入客户名称"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="证件类型" prop="idType">
<el-select v-model="form.idType" placeholder="请选择证件类型" style="width: 100%">
<el-option label="统一社会信用代码" value="统一社会信用代码"/>
<el-option label="其他" value="其他"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="证件号码" prop="idNum">
<el-input v-model="form.idNum" placeholder="请输入证件号码"/>
</el-form-item>
</el-col>
</el-row>
<!-- 贷款信息 -->
<el-divider content-position="left">贷款信息</el-divider>
<el-row>
<el-col :span="12">
<el-form-item label="担保方式" prop="guarType">
<el-select v-model="form.guarType" placeholder="请选择担保方式" style="width: 100%">
<el-option label="信用" value="信用"/>
<el-option label="保证" value="保证"/>
<el-option label="抵押" value="抵押"/>
<el-option label="质押" value="质押"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="申请金额(元)" prop="applyAmt">
<el-input v-model="form.applyAmt" placeholder="请输入申请金额"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="贷款期限(月)" prop="loanTerm">
<el-input v-model.number="form.loanTerm" type="number" placeholder="请输入贷款期限"/>
</el-form-item>
</el-col>
</el-row>
<!-- 企业标识 -->
<el-divider content-position="left">企业标识</el-divider>
<el-row>
<el-col :span="12">
<el-form-item label="省农担担保贷款" prop="isAgriGuar">
<el-switch v-model="form.isAgriGuar"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="绿色贷款" prop="isGreenLoan">
<el-switch v-model="form.isGreenLoan"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="科技型企业" prop="isTechEnt">
<el-switch v-model="form.isTechEnt"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="贸易和建筑业企业" prop="isTradeConstruction">
<el-switch v-model="form.isTradeConstruction"/>
</el-form-item>
</el-col>
</el-row>
<!-- 抵质押信息 -->
<el-divider content-position="left">抵质押信息</el-divider>
<el-row>
<el-col :span="12">
<el-form-item label="抵质押类型" prop="collType">
<el-select v-model="form.collType" placeholder="请选择抵质押类型" style="width: 100%">
<el-option label="一线" value="一线"/>
<el-option label="一类" value="一类"/>
<el-option label="二类" value="二类"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="抵质押物三方所有" prop="collThirdParty">
<el-switch v-model="form.collThirdParty"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" :loading="submitting" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</template>
<script>
import {createCorporateWorkflow} from "@/api/loanPricing/workflow"
export default {
name: "CorporateCreateDialog",
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
// 金额验证
const validateApplyAmt = (rule, value, callback) => {
if (!value) {
callback(new Error('请输入申请金额'))
} else {
const num = parseFloat(value)
if (isNaN(num) || num <= 0) {
callback(new Error('请输入有效的金额'))
} else if (num > 999999999.99) {
callback(new Error('金额不能超过 999999999.99'))
} else {
callback()
}
}
}
// 贷款期限验证
const validateLoanTerm = (rule, value, callback) => {
if (!value && value !== 0) {
callback(new Error('请输入贷款期限'))
} else {
const num = parseInt(value)
if (isNaN(num) || num <= 0) {
callback(new Error('请输入有效的贷款期限'))
} else if (num > 360) {
callback(new Error('贷款期限不能超过 360 个月'))
} else {
callback()
}
}
}
return {
submitting: false,
form: {
orgCode: '892000',
runType: '1',
custIsn: undefined,
custName: undefined,
idType: undefined,
idNum: undefined,
guarType: undefined,
applyAmt: undefined,
loanTerm: undefined,
isAgriGuar: false,
isGreenLoan: false,
isTechEnt: false,
isTradeConstruction: false,
collType: undefined,
collThirdParty: false
},
rules: {
custIsn: [
{required: true, message: "客户内码不能为空", trigger: "blur"},
{min: 1, max: 50, message: "长度在 1 到 50 个字符", trigger: "blur"}
],
custName: [
{required: true, message: "客户名称不能为空", trigger: "blur"},
{min: 1, max: 100, message: "长度在 1 到 100 个字符", trigger: "blur"}
],
idType: [
{required: true, message: "请选择证件类型", trigger: "change"}
],
idNum: [
{required: true, message: "证件号码不能为空", trigger: "blur"}
],
guarType: [
{required: true, message: "请选择担保方式", trigger: "change"}
],
applyAmt: [
{required: true, validator: validateApplyAmt, trigger: "blur"}
],
loanTerm: [
{required: true, validator: validateLoanTerm, trigger: "blur"}
],
collType: [
{required: true, message: "请选择抵质押类型", trigger: "change"}
]
}
}
},
computed: {
dialogVisible: {
get() {
return this.visible
},
set(val) {
this.$emit('update:visible', val)
}
}
},
watch: {
visible(val) {
if (val) {
this.reset()
}
}
},
methods: {
/** 表单重置 */
reset() {
this.form = {
orgCode: '892000',
runType: '1',
custIsn: undefined,
custName: undefined,
idType: undefined,
idNum: undefined,
guarType: undefined,
applyAmt: undefined,
loanTerm: undefined,
isAgriGuar: false,
isGreenLoan: false,
isTechEnt: false,
isTradeConstruction: false,
collType: undefined,
collThirdParty: false
}
this.submitting = false
this.resetForm("form")
},
/** 对话框关闭处理 */
handleClose() {
this.cancel()
},
/** 取消按钮 */
cancel() {
this.dialogVisible = false
this.reset()
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
this.submitting = true
// 转换开关值为字符串
const data = {
...this.form,
isAgriGuar: this.form.isAgriGuar ? 'true' : 'false',
isGreenLoan: this.form.isGreenLoan ? 'true' : 'false',
isTechEnt: this.form.isTechEnt ? 'true' : 'false',
isTradeConstruction: this.form.isTradeConstruction ? 'true' : 'false',
collThirdParty: this.form.collThirdParty ? 'true' : 'false'
}
createCorporateWorkflow(data).then(response => {
this.$modal.msgSuccess("新增成功")
this.dialogVisible = false
this.$emit('success')
}).catch(error => {
// 保留表单,显示错误信息
console.error('创建失败:', error)
}).finally(() => {
this.submitting = false
})
}
})
}
}
}
</script>
<style scoped>
.workflow-create-form .el-divider {
margin: 8px 0 16px 0;
font-weight: 500;
font-size: 14px;
}
@media screen and (max-width: 768px) {
.workflow-create-form .el-col {
width: 100% !important;
max-width: 100% !important;
flex: 0 0 100% !important;
}
}
</style>

View File

@@ -0,0 +1,324 @@
<template>
<div class="corporate-workflow-detail" v-loading="loading">
<!-- 两栏布局左侧关键信息 + 右侧流程详情+模型输出 -->
<div v-if="!loading && detailData" class="detail-layout">
<!-- 左侧关键信息卡片 -->
<div class="left-panel">
<el-card class="summary-card">
<div slot="header" class="card-header">
<span class="card-title">关键信息</span>
</div>
<el-descriptions :column="1" direction="vertical" border>
<el-descriptions-item label="业务方流水号">{{ detailData.serialNum }}</el-descriptions-item>
<el-descriptions-item label="客户名称">{{ detailData.custName }}</el-descriptions-item>
<el-descriptions-item label="客户类型">{{ detailData.custType }}</el-descriptions-item>
<el-descriptions-item label="申请金额">{{ detailData.applyAmt }} </el-descriptions-item>
<el-descriptions-item label="基准利率">
<span class="rate-value">{{ getBaseLoanRate() }}</span> %
</el-descriptions-item>
<el-descriptions-item label="浮动BP">
<span class="total-bp-value">{{ getTotalBp() }}</span>
</el-descriptions-item>
<el-descriptions-item label="测算利率">
<span class="calculate-rate">{{ getCalculateRate() }}</span> %
</el-descriptions-item>
<el-descriptions-item label="执行利率">
<div class="execute-rate-input-wrapper">
<el-input
v-model="executeRateInput"
class="execute-rate-input"
placeholder="请输入执行利率"
>
<template slot="append">%</template>
</el-input>
<el-button
type="primary"
size="small"
@click="handleSetExecuteRate"
>
确定
</el-button>
</div>
</el-descriptions-item>
</el-descriptions>
</el-card>
</div>
<!-- 右侧面板 -->
<div class="right-panel">
<!-- 模型输出卡片 -->
<ModelOutputDisplay
:cust-type="detailData.custType"
:retail-output="null"
:corp-output="corpOutput"
/>
<!-- 流程详情卡片 -->
<el-card class="detail-card">
<div slot="header" class="card-header">
<span class="card-title">流程详情</span>
</div>
<!-- 基本信息组 -->
<div class="info-section">
<h4 class="section-title">基本信息</h4>
<el-descriptions :column="2" border>
<el-descriptions-item label="机构编码">{{ detailData.orgCode }}</el-descriptions-item>
<el-descriptions-item label="运行模式">{{ detailData.runType }}</el-descriptions-item>
<el-descriptions-item label="客户内码">{{ detailData.custIsn }}</el-descriptions-item>
<el-descriptions-item label="证件类型">{{ detailData.idType }}</el-descriptions-item>
<el-descriptions-item label="证件号码">{{ detailData.idNum }}</el-descriptions-item>
<el-descriptions-item label="贷款期限">{{ detailData.loanTerm || '-' }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ detailData.createTime }}</el-descriptions-item>
<el-descriptions-item label="创建者">{{ detailData.createBy }}</el-descriptions-item>
</el-descriptions>
</div>
<!-- 业务信息组 -->
<div class="info-section">
<h4 class="section-title">业务信息</h4>
<el-descriptions :column="2" border>
<el-descriptions-item label="担保方式">{{ detailData.guarType }}</el-descriptions-item>
<el-descriptions-item label="申请金额">{{ detailData.applyAmt }} </el-descriptions-item>
<el-descriptions-item label="省农担担保贷款">{{
formatBoolean(detailData.isAgriGuar)
}}
</el-descriptions-item>
<el-descriptions-item label="绿色贷款">{{ formatBoolean(detailData.isGreenLoan) }}</el-descriptions-item>
<el-descriptions-item label="科技型企业">{{ formatBoolean(detailData.isTechEnt) }}</el-descriptions-item>
<el-descriptions-item label="抵质押类型">{{ detailData.collType || '-' }}</el-descriptions-item>
<el-descriptions-item label="抵质押物是否三方所有">{{
formatBoolean(detailData.collThirdParty)
}}
</el-descriptions-item>
</el-descriptions>
</div>
</el-card>
<!-- 议价池卡片 -->
<BargainingPoolDisplay
v-if="bargainingPool"
:branch-pool="bargainingPool.branchPool"
:sub-branch-pool="bargainingPool.subBranchPool"
:private-domain-pool="bargainingPool.privateDomainPool"
:excess-profit-share="bargainingPool.excessProfitShare"
/>
</div>
</div>
</div>
</template>
<script>
import {setExecuteRate} from "@/api/loanPricing/workflow"
import ModelOutputDisplay from "./ModelOutputDisplay.vue"
import BargainingPoolDisplay from "./BargainingPoolDisplay.vue"
export default {
name: "CorporateWorkflowDetail",
components: {
ModelOutputDisplay,
BargainingPoolDisplay
},
props: {
detailData: {
type: Object,
required: true
},
corpOutput: {
type: Object,
default: null
},
bargainingPool: {
type: Object,
default: null
}
},
data() {
return {
loading: false,
executeRateInput: ''
}
},
watch: {
'detailData.executeRate': {
handler(newVal) {
this.executeRateInput = newVal || ''
},
immediate: true
}
},
methods: {
/** 格式化布尔值为中文 */
formatBoolean(value) {
if (value === 'true' || value === true || value === '1' || value === 1) return '是'
if (value === 'false' || value === false || value === '0' || value === 0) return '否'
return value || '-'
},
/** 获取基准利率 */
getBaseLoanRate() {
return this.corpOutput?.baseLoanRate || '-'
},
/** 获取浮动BP */
getTotalBp() {
return this.corpOutput?.totalBp || '-'
},
/** 获取测算利率 */
getCalculateRate() {
return this.corpOutput?.calculateRate || '-'
},
/** 设定执行利率 */
handleSetExecuteRate() {
const value = this.executeRateInput
if (value === null || value === undefined || value === '') {
this.$modal.msgError("请输入执行利率")
return
}
const numValue = parseFloat(value)
if (isNaN(numValue)) {
this.$modal.msgError("请输入有效的数字")
return
}
if (numValue < 0 || numValue > 100) {
this.$modal.msgError("执行利率必须在 0 到 100 之间")
return
}
this.loading = true
setExecuteRate(this.detailData.serialNum, value.toString()).then(() => {
this.$modal.msgSuccess("执行利率设定成功")
this.$emit('refresh')
this.loading = false
}).catch(error => {
this.$modal.msgError("设定失败:" + (error.msg || error.message || "未知错误"))
this.loading = false
})
}
}
}
</script>
<style lang="scss" scoped>
.corporate-workflow-detail {
.detail-layout {
display: flex;
gap: 20px;
align-items: flex-start;
.left-panel {
flex: 0 0 280px;
max-width: 280px;
.summary-card {
::v-deep .el-card__header {
padding: 16px 20px;
background-color: #fafafa;
border-bottom: 1px solid #ebeef5;
}
.card-header {
display: flex;
align-items: center;
.card-title {
font-size: 16px;
font-weight: 500;
color: #303133;
}
}
::v-deep .el-card__body {
padding: 16px;
}
.execute-rate-input-wrapper {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
.execute-rate-input {
width: 100%;
}
}
.rate-value {
color: #67c23a;
font-weight: 500;
}
.total-bp-value {
color: #e6a23c;
font-weight: 600;
}
.calculate-rate {
color: #f56c6c;
font-weight: 600;
font-size: 16px;
}
}
}
.right-panel {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 20px;
.detail-card {
::v-deep .el-card__header {
padding: 16px 20px;
background-color: #fafafa;
border-bottom: 1px solid #ebeef5;
}
.card-header {
display: flex;
align-items: center;
.card-title {
font-size: 16px;
font-weight: 500;
color: #303133;
}
}
::v-deep .el-card__body {
padding: 20px;
}
.info-section {
&:not(:last-child) {
margin-bottom: 24px;
}
.section-title {
margin: 0 0 16px 0;
font-size: 14px;
font-weight: 500;
color: #606266;
padding-bottom: 8px;
border-bottom: 1px solid #ebeef5;
}
}
}
}
}
}
@media screen and (max-width: 992px) {
.corporate-workflow-detail {
.detail-layout {
flex-direction: column;
.left-panel,
.right-panel {
flex: 1 1 100%;
max-width: 100%;
}
}
}
}
</style>

View File

@@ -0,0 +1,105 @@
<template>
<el-dialog title="选择客户类型" :visible.sync="dialogVisible" width="500px" append-to-body
:close-on-click-modal="false">
<div class="customer-type-selector">
<div class="type-card" @click="selectType('personal')">
<div class="card-icon">
<i class="el-icon-user"></i>
</div>
<div class="card-content">
<div class="card-title">个人客户</div>
<div class="card-desc">个人客户利率定价</div>
</div>
</div>
<div class="type-card" @click="selectType('corporate')">
<div class="card-icon">
<i class="el-icon-office-building"></i>
</div>
<div class="card-content">
<div class="card-title">企业客户</div>
<div class="card-desc">企业客户利率定价</div>
</div>
</div>
</div>
</el-dialog>
</template>
<script>
export default {
name: "CustomerTypeSelector",
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
return {}
},
computed: {
dialogVisible: {
get() {
return this.visible
},
set(val) {
this.$emit('update:visible', val)
}
}
},
methods: {
selectType(type) {
this.$emit('select', type)
this.dialogVisible = false
}
}
}
</script>
<style scoped>
.customer-type-selector {
display: flex;
gap: 20px;
padding: 20px 0;
}
.type-card {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 30px 20px;
border: 2px solid #e0e0e0;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
}
.type-card:hover {
border-color: #409EFF;
background-color: #f0f7ff;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
}
.card-icon {
font-size: 48px;
color: #409EFF;
margin-bottom: 15px;
}
.card-content {
text-align: center;
}
.card-title {
font-size: 18px;
font-weight: 500;
color: #303133;
margin-bottom: 8px;
}
.card-desc {
font-size: 14px;
color: #909399;
}
</style>

View File

@@ -0,0 +1,311 @@
<template>
<el-card class="model-output-card" v-if="custType && (retailOutput || corpOutput)">
<div slot="header" class="card-header">
<span class="card-title">模型输出</span>
</div>
<el-tabs v-model="activeTab">
<!-- 个人客户模型输出 -->
<template v-if="custType === '个人' && retailOutput">
<!-- 基本信息 -->
<el-tab-pane label="基本信息" name="retail-basic">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="客户内码">{{ retailOutput.custIsn || '-' }}</el-descriptions-item>
<el-descriptions-item label="客户名称">{{ retailOutput.custName || '-' }}</el-descriptions-item>
<el-descriptions-item label="证件类型">{{ retailOutput.idType || '-' }}</el-descriptions-item>
<el-descriptions-item label="证件号码">{{ retailOutput.idNum || '-' }}</el-descriptions-item>
<el-descriptions-item label="基准利率"><span class="rate-value">{{ retailOutput.baseLoanRate || '-' }}</span> %</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<!-- 忠诚度分析 -->
<el-tab-pane label="忠诚度分析" name="retail-loyalty">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="我行首贷客户">{{ formatBoolean(retailOutput.isFirstLoan) }}</el-descriptions-item>
<el-descriptions-item label="用信天数">{{ retailOutput.faithDay || '-' }}</el-descriptions-item>
<el-descriptions-item label="客户年龄">{{ retailOutput.custAge || '-' }}</el-descriptions-item>
<el-descriptions-item label="BP_首贷"><span class="bp-value">{{ retailOutput.bpFirstLoan || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="BP_贷龄"><span class="bp-value">{{ retailOutput.bpAgeLoan || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="BP_年龄"><span class="bp-value">{{ retailOutput.bpAge || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="TOTAL_BP_忠诚度" :span="2"><span class="total-bp-value">{{ retailOutput.totalBpLoyalty || '-' }}</span></el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<!-- 贡献度分析 -->
<el-tab-pane label="贡献度分析" name="retail-contribution">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="存款年日均">{{ retailOutput.balanceAvg || '-' }}</el-descriptions-item>
<el-descriptions-item label="贷款年日均">{{ retailOutput.loanAvg || '-' }}</el-descriptions-item>
<el-descriptions-item label="派生率">{{ retailOutput.derivationRate || '-' }}</el-descriptions-item>
<el-descriptions-item label="TOTAL_BP_贡献度"><span class="total-bp-value">{{ retailOutput.totalBpContribution || '-' }}</span></el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<!-- 关联度分析 -->
<el-tab-pane label="关联度分析" name="retail-relevance">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="中间业务_个人_信用卡">{{ formatBoolean(retailOutput.midPerCard) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_个人_一码通">{{ formatBoolean(retailOutput.midPerPass) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_个人_丰收互联">{{ formatBoolean(retailOutput.midPerHarvest) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_个人_有效客户">{{ formatBoolean(retailOutput.midPerEffect) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_个人_快捷支付">{{ formatBoolean(retailOutput.midPerQuickPay) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_个人_电费代扣">{{ formatBoolean(retailOutput.midPerEleDdc) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_个人_水费代扣">{{ formatBoolean(retailOutput.midPerWaterDdc) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_个人_华数费代扣">{{ formatBoolean(retailOutput.midPerHuashuDdc) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_个人_煤气费代扣">{{ formatBoolean(retailOutput.MidPerGasDdc) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_个人_市民卡">{{ formatBoolean(retailOutput.midPerCitizencard) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_个人_理财业务">{{ formatBoolean(retailOutput.midPerFinMan) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_个人_etc">{{ formatBoolean(retailOutput.midPerEtc) }}</el-descriptions-item>
<el-descriptions-item label="BP_中间业务"><span class="bp-value">{{ retailOutput.bpMid || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="TOTAL_BP_关联度"><span class="total-bp-value">{{ retailOutput.totoalBpRelevance || '-' }}</span></el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<!-- 贷款特征 -->
<el-tab-pane label="贷款特征" name="retail-loan">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="申请金额">{{ retailOutput.applyAmt || '-' }}</el-descriptions-item>
<el-descriptions-item label="BP_贷款额度"><span class="bp-value">{{ retailOutput.bpLoanAmount || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="贷款用途">{{ formatLoanPurpose(retailOutput.loanPurpose) }}</el-descriptions-item>
<el-descriptions-item label="是否有经营佐证">{{ formatBoolean(retailOutput.bizProof) }}</el-descriptions-item>
<el-descriptions-item label="BP_贷款用途"><span class="bp-value">{{ retailOutput.bpLoanUse || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="循环功能">{{ formatBoolean(retailOutput.loanLoop) }}</el-descriptions-item>
<el-descriptions-item label="BP_循环功能"><span class="bp-value">{{ retailOutput.bpLoanLoop || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="抵质押类型">{{ retailOutput.collType || '-' }}</el-descriptions-item>
<el-descriptions-item label="抵质押物三方所有">{{ formatBoolean(retailOutput.collThirdParty) }}</el-descriptions-item>
<el-descriptions-item label="BP_抵押物"><span class="bp-value">{{ retailOutput.bpCollateral || '-' }}</span></el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<!-- 风险度分析 -->
<el-tab-pane label="风险度分析" name="retail-risk">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="灰名单客户">{{ formatBoolean(retailOutput.greyCust) }}</el-descriptions-item>
<el-descriptions-item label="本金逾期">{{ formatBoolean(retailOutput.prinOverdue) }}</el-descriptions-item>
<el-descriptions-item label="利息逾期">{{ formatBoolean(retailOutput.interestOverdue) }}</el-descriptions-item>
<el-descriptions-item label="信用卡逾期">{{ formatBoolean(retailOutput.cardOverdue) }}</el-descriptions-item>
<el-descriptions-item label="BP_灰名单与逾期"><span class="bp-value">{{ retailOutput.bpGreyOverdue || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="TOTAL_BP_风险度"><span class="total-bp-value">{{ retailOutput.totoalBpRisk || '-' }}</span></el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<!-- 测算结果 -->
<el-tab-pane label="测算结果" name="retail-result">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="浮动BP"><span class="total-bp-value">{{ retailOutput.totalBp || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="测算利率"><span class="calculate-rate">{{ retailOutput.calculateRate || '-' }}</span> %</el-descriptions-item>
<el-descriptions-item label="历史利率">{{ retailOutput.loanRateHistory || '-' }}</el-descriptions-item>
<el-descriptions-item label="产品最低利率下限">{{ retailOutput.minRateProduct || '-' }}</el-descriptions-item>
<el-descriptions-item label="平滑幅度">{{ retailOutput.smoothRange || '-' }}</el-descriptions-item>
<el-descriptions-item label="最终测算利率"><span class="calculate-rate">{{ retailOutput.finalCalculateRate || '-' }}</span> %</el-descriptions-item>
<el-descriptions-item label="参考利率"><span class="calculate-rate">{{ retailOutput.referenceRate || '-' }}</span> %</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
</template>
<!-- 企业客户模型输出 -->
<template v-else-if="custType === '企业' && corpOutput">
<!-- 基本信息 -->
<el-tab-pane label="基本信息" name="corp-basic">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="客户内码">{{ corpOutput.custIsn || '-' }}</el-descriptions-item>
<el-descriptions-item label="客户名称">{{ corpOutput.custName || '-' }}</el-descriptions-item>
<el-descriptions-item label="证件类型">{{ corpOutput.idType || '-' }}</el-descriptions-item>
<el-descriptions-item label="证件号码">{{ corpOutput.idNum || '-' }}</el-descriptions-item>
<el-descriptions-item label="基准利率"><span class="rate-value">{{ corpOutput.baseLoanRate || '-' }}</span> %</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<!-- 忠诚度分析 -->
<el-tab-pane label="忠诚度分析" name="corp-loyalty">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="我行首贷客户">{{ formatBoolean(corpOutput.isFirstLoan) }}</el-descriptions-item>
<el-descriptions-item label="用信天数">{{ corpOutput.faithDay || '-' }}</el-descriptions-item>
<el-descriptions-item label="BP_首贷"><span class="bp-value">{{ corpOutput.bpFirstLoan || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="BP_贷龄"><span class="bp-value">{{ corpOutput.bpAgeLoan || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="TOTAL_BP_忠诚度" :span="2"><span class="total-bp-value">{{ corpOutput.totalBpLoyalty || '-' }}</span></el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<!-- 贡献度分析 -->
<el-tab-pane label="贡献度分析" name="corp-contribution">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="存款年日均">{{ corpOutput.balanceAvg || '-' }}</el-descriptions-item>
<el-descriptions-item label="贷款年日均">{{ corpOutput.loanAvg || '-' }}</el-descriptions-item>
<el-descriptions-item label="派生率">{{ corpOutput.derivationRate || '-' }}</el-descriptions-item>
<el-descriptions-item label="TOTAL_BP_贡献度"><span class="total-bp-value">{{ corpOutput.totalBpContribution || '-' }}</span></el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<!-- 关联度分析 -->
<el-tab-pane label="关联度分析" name="corp-relevance">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="中间业务_企业_企业互联">{{ formatBoolean(corpOutput.midEntConnect) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_企业_有效价值客户">{{ formatBoolean(corpOutput.midEntEffect) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_企业_国际业务">{{ formatBoolean(corpOutput.midEntInter) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_企业_承兑">{{ formatBoolean(corpOutput.midEntAccept) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_企业_贴现">{{ formatBoolean(corpOutput.midEntDiscount) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_企业_电费代扣">{{ formatBoolean(corpOutput.midEntEleDdc) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_企业_水费代扣">{{ formatBoolean(corpOutput.midEntWaterDdc) }}</el-descriptions-item>
<el-descriptions-item label="中间业务_企业_税务代扣">{{ formatBoolean(corpOutput.midEntTax) }}</el-descriptions-item>
<el-descriptions-item label="BP_中间业务"><span class="bp-value">{{ corpOutput.bpMid || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="代发工资户数">{{ corpOutput.payroll || '-' }}</el-descriptions-item>
<el-descriptions-item label="存量贷款余额">{{ corpOutput.invLoanAmount || '-' }}</el-descriptions-item>
<el-descriptions-item label="BP_代发工资"><span class="bp-value">{{ corpOutput.bpPayroll || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="TOTAL_BP_关联度"><span class="total-bp-value">{{ corpOutput.totoalBpRelevance || '-' }}</span></el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<!-- 企业类别 -->
<el-tab-pane label="企业类别" name="corp-category">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="净身企业">{{ formatBoolean(corpOutput.isCleanEnt) }}</el-descriptions-item>
<el-descriptions-item label="开立基本结算账户">{{ formatBoolean(corpOutput.hasSettleAcct) }}</el-descriptions-item>
<el-descriptions-item label="省农担担保贷款">{{ formatBoolean(corpOutput.isAgriGuar) }}</el-descriptions-item>
<el-descriptions-item label="绿色贷款">{{ formatBoolean(corpOutput.isGreenLoan) }}</el-descriptions-item>
<el-descriptions-item label="科技型企业">{{ formatBoolean(corpOutput.isTechEnt) }}</el-descriptions-item>
<el-descriptions-item label="BP_企业客户类别"><span class="bp-value">{{ corpOutput.bpEntType || '-' }}</span></el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<!-- 贷款特征 -->
<el-tab-pane label="贷款特征" name="corp-loan">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="贷款期限">{{ corpOutput.loanTerm || '-' }}</el-descriptions-item>
<el-descriptions-item label="BP_贷款期限"><span class="bp-value">{{ corpOutput.bpLoanTerm || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="申请金额">{{ corpOutput.applyAmt || '-' }}</el-descriptions-item>
<el-descriptions-item label="BP_贷款额度"><span class="bp-value">{{ corpOutput.bpLoanAmount || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="抵质押类型">{{ corpOutput.collType || '-' }}</el-descriptions-item>
<el-descriptions-item label="抵质押物三方所有">{{ formatBoolean(corpOutput.collThirdParty) }}</el-descriptions-item>
<el-descriptions-item label="BP_抵押物"><span class="bp-value">{{ corpOutput.bpCollateral || '-' }}</span></el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<!-- 风险度分析 -->
<el-tab-pane label="风险度分析" name="corp-risk">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="灰名单客户">{{ formatBoolean(corpOutput.greyCust) }}</el-descriptions-item>
<el-descriptions-item label="本金逾期">{{ formatBoolean(corpOutput.prinOverdue) }}</el-descriptions-item>
<el-descriptions-item label="利息逾期">{{ formatBoolean(corpOutput.interestOverdue) }}</el-descriptions-item>
<el-descriptions-item label="信用卡逾期">{{ formatBoolean(corpOutput.cardOverdue) }}</el-descriptions-item>
<el-descriptions-item label="BP_灰名单与逾期"><span class="bp-value">{{ corpOutput.bpGreyOverdue || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="TOTAL_BP_风险度"><span class="total-bp-value">{{ corpOutput.totoalBpRisk || '-' }}</span></el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<!-- 测算结果 -->
<el-tab-pane label="测算结果" name="corp-result">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="浮动BP"><span class="total-bp-value">{{ corpOutput.totalBp || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="测算利率"><span class="calculate-rate">{{ corpOutput.calculateRate || '-' }}</span> %</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
</template>
</el-tabs>
</el-card>
</template>
<script>
export default {
name: "ModelOutputDisplay",
props: {
custType: {
type: String,
default: null
},
retailOutput: {
type: Object,
default: null
},
corpOutput: {
type: Object,
default: null
}
},
data() {
return {
activeTab: ''
}
},
watch: {
custType: {
immediate: true,
handler(val) {
// 根据客户类型设置默认 tab
if (val === '个人') {
this.activeTab = 'retail-basic'
} else if (val === '企业') {
this.activeTab = 'corp-basic'
}
}
}
},
methods: {
/** 格式化布尔值为中文 */
formatBoolean(value) {
if (value === 'true' || value === true || value === '1' || value === 1) return '是'
if (value === 'false' || value === false || value === '0' || value === 0) return '否'
return value || '-'
},
/** 格式化贷款用途 */
formatLoanPurpose(value) {
if (value === 'consumer') return '消费贷款'
if (value === 'business') return '经营贷款'
return value || '-'
}
}
}
</script>
<style lang="scss" scoped>
.model-output-card {
::v-deep .el-card__header {
padding: 16px 20px;
background-color: #fafafa;
border-bottom: 1px solid #ebeef5;
}
.card-header {
display: flex;
align-items: center;
.card-title {
font-size: 16px;
font-weight: 500;
color: #303133;
}
}
::v-deep .el-card__body {
padding: 20px;
}
::v-deep .el-tabs__header {
margin-bottom: 20px;
}
// BP 值样式
.bp-value {
color: #409eff;
font-weight: 500;
}
// TOTAL_BP 样式
.total-bp-value {
color: #e6a23c;
font-weight: 600;
}
// 测算利率高亮样式
.calculate-rate {
color: #f56c6c;
font-weight: 600;
font-size: 16px;
}
// 利率值样式
.rate-value {
color: #67c23a;
font-weight: 500;
}
}
</style>

View File

@@ -0,0 +1,281 @@
<template>
<el-dialog title="新增个人利率定价流程" :visible.sync="dialogVisible" width="900px" append-to-body
@close="handleClose">
<el-form ref="form" :model="form" :rules="rules" label-width="140px" class="workflow-create-form">
<!-- 基本信息 -->
<el-divider content-position="left">基本信息</el-divider>
<el-row>
<el-col :span="12">
<el-form-item label="客户内码" prop="custIsn">
<el-input v-model="form.custIsn" placeholder="请输入客户内码"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="客户名称" prop="custName">
<el-input v-model="form.custName" placeholder="请输入客户名称"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="证件类型" prop="idType">
<el-select v-model="form.idType" placeholder="请选择证件类型" style="width: 100%">
<el-option label="身份证" value="身份证"/>
<el-option label="其他证件" value="其他证件"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="证件号码" prop="idNum">
<el-input v-model="form.idNum" placeholder="请输入证件号码"/>
</el-form-item>
</el-col>
</el-row>
<!-- 贷款信息 -->
<el-divider content-position="left">贷款信息</el-divider>
<el-row>
<el-col :span="12">
<el-form-item label="担保方式" prop="guarType">
<el-select v-model="form.guarType" placeholder="请选择担保方式" style="width: 100%">
<el-option label="信用" value="信用"/>
<el-option label="保证" value="保证"/>
<el-option label="抵押" value="抵押"/>
<el-option label="质押" value="质押"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="申请金额(元)" prop="applyAmt">
<el-input v-model="form.applyAmt" placeholder="请输入申请金额"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="贷款用途" prop="loanPurpose">
<el-select v-model="form.loanPurpose" placeholder="请选择贷款用途" style="width: 100%">
<el-option label="消费" value="consumer"/>
<el-option label="经营" value="business"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="借款期限(年)" prop="loanTerm">
<el-select v-model="form.loanTerm" placeholder="请选择借款期限" style="width: 100%">
<el-option v-for="item in loanTermOptions" :key="item" :label="item" :value="item"/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="是否有经营佐证" prop="bizProof">
<el-switch v-model="form.bizProof"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="循环功能" prop="loanLoop">
<el-switch v-model="form.loanLoop"/>
</el-form-item>
</el-col>
</el-row>
<!-- 抵质押信息 -->
<el-divider content-position="left">抵质押信息</el-divider>
<el-row>
<el-col :span="12">
<el-form-item label="抵质押类型" prop="collType">
<el-select v-model="form.collType" placeholder="请选择抵质押类型" style="width: 100%">
<el-option label="一类" value="一类"/>
<el-option label="二类" value="二类"/>
<el-option label="三类" value="三类"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="抵质押物三方所有" prop="collThirdParty">
<el-switch v-model="form.collThirdParty"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" :loading="submitting" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</template>
<script>
import {createPersonalWorkflow} from "@/api/loanPricing/workflow"
export default {
name: "PersonalCreateDialog",
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
// 金额验证
const validateApplyAmt = (rule, value, callback) => {
if (!value) {
callback(new Error('请输入申请金额'))
} else {
const num = parseFloat(value)
if (isNaN(num) || num <= 0) {
callback(new Error('请输入有效的金额'))
} else if (num > 999999999.99) {
callback(new Error('金额不能超过 999999999.99'))
} else {
callback()
}
}
}
return {
loanTermOptions: [
'1', '2', '3', '4', '5', '6'
],
submitting: false,
form: {
orgCode: '892000',
runType: '1',
custIsn: undefined,
custName: undefined,
idType: undefined,
idNum: undefined,
guarType: undefined,
applyAmt: undefined,
loanPurpose: undefined,
loanTerm: undefined,
bizProof: false,
loanLoop: false,
collType: undefined,
collThirdParty: false
},
rules: {
custIsn: [
{required: true, message: "客户内码不能为空", trigger: "blur"},
{min: 1, max: 50, message: "长度在 1 到 50 个字符", trigger: "blur"}
],
custName: [
{required: true, message: "客户名称不能为空", trigger: "blur"},
{min: 1, max: 100, message: "长度在 1 到 100 个字符", trigger: "blur"}
],
idType: [
{required: true, message: "请选择证件类型", trigger: "change"}
],
idNum: [
{required: true, message: "证件号码不能为空", trigger: "blur"}
],
guarType: [
{required: true, message: "请选择担保方式", trigger: "change"}
],
applyAmt: [
{required: true, validator: validateApplyAmt, trigger: "blur"}
],
loanPurpose: [
{required: true, message: "请选择贷款用途", trigger: "change"}
],
loanTerm: [
{required: true, message: "请选择借款期限", trigger: "change"}
]
}
}
},
computed: {
dialogVisible: {
get() {
return this.visible
},
set(val) {
this.$emit('update:visible', val)
}
}
},
watch: {
visible(val) {
if (val) {
this.reset()
}
}
},
methods: {
/** 表单重置 */
reset() {
this.form = {
orgCode: '892000',
runType: '1',
custIsn: undefined,
custName: undefined,
idType: undefined,
idNum: undefined,
guarType: undefined,
applyAmt: undefined,
loanPurpose: undefined,
loanTerm: undefined,
bizProof: false,
loanLoop: false,
collType: undefined,
collThirdParty: false
}
this.submitting = false
this.resetForm("form")
},
/** 对话框关闭处理 */
handleClose() {
this.cancel()
},
/** 取消按钮 */
cancel() {
this.dialogVisible = false
this.reset()
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
this.submitting = true
// 转换开关值为字符串
const data = {
...this.form,
bizProof: this.form.bizProof ? '1' : '0',
loanLoop: this.form.loanLoop ? '1' : '0',
collThirdParty: this.form.collThirdParty ? '1' : '0'
}
createPersonalWorkflow(data).then(response => {
this.$modal.msgSuccess("新增成功")
this.dialogVisible = false
this.$emit('success')
}).catch(error => {
// 保留表单,显示错误信息
console.error('创建失败:', error)
}).finally(() => {
this.submitting = false
})
}
})
}
}
}
</script>
<style scoped>
.workflow-create-form .el-divider {
margin: 8px 0 16px 0;
font-weight: 500;
font-size: 14px;
}
@media screen and (max-width: 768px) {
.workflow-create-form .el-col {
width: 100% !important;
max-width: 100% !important;
flex: 0 0 100% !important;
}
}
</style>

View File

@@ -0,0 +1,330 @@
<template>
<div class="personal-workflow-detail" v-loading="loading">
<!-- 两栏布局左侧关键信息 + 右侧流程详情+模型输出 -->
<div v-if="!loading && detailData" class="detail-layout">
<!-- 左侧关键信息卡片 -->
<div class="left-panel">
<el-card class="summary-card">
<div slot="header" class="card-header">
<span class="card-title">关键信息</span>
</div>
<el-descriptions :column="1" direction="vertical" border>
<el-descriptions-item label="业务方流水号">{{ detailData.serialNum }}</el-descriptions-item>
<el-descriptions-item label="客户名称">{{ detailData.custName }}</el-descriptions-item>
<el-descriptions-item label="客户类型">{{ detailData.custType }}</el-descriptions-item>
<el-descriptions-item label="申请金额">{{ detailData.applyAmt }} </el-descriptions-item>
<el-descriptions-item label="基准利率">
<span class="rate-value">{{ getBaseLoanRate() }}</span> %
</el-descriptions-item>
<el-descriptions-item label="浮动BP">
<span class="total-bp-value">{{ getTotalBp() }}</span>
</el-descriptions-item>
<el-descriptions-item label="最终测算利率">
<span class="calculate-rate">{{ getCalculateRate() }}</span> %
</el-descriptions-item>
<el-descriptions-item label="执行利率">
<div class="execute-rate-input-wrapper">
<el-input
v-model="executeRateInput"
class="execute-rate-input"
placeholder="请输入执行利率"
>
<template slot="append">%</template>
</el-input>
<el-button
type="primary"
size="small"
@click="handleSetExecuteRate"
>
确定
</el-button>
</div>
</el-descriptions-item>
</el-descriptions>
</el-card>
</div>
<!-- 右侧面板 -->
<div class="right-panel">
<!-- 模型输出卡片 -->
<ModelOutputDisplay
:cust-type="detailData.custType"
:retail-output="retailOutput"
:corp-output="null"
/>
<!-- 流程详情卡片 -->
<el-card class="detail-card">
<div slot="header" class="card-header">
<span class="card-title">流程详情</span>
</div>
<!-- 基本信息组 -->
<div class="info-section">
<h4 class="section-title">基本信息</h4>
<el-descriptions :column="2" border>
<el-descriptions-item label="机构编码">{{ detailData.orgCode }}</el-descriptions-item>
<el-descriptions-item label="运行模式">{{ detailData.runType }}</el-descriptions-item>
<el-descriptions-item label="客户内码">{{ detailData.custIsn }}</el-descriptions-item>
<el-descriptions-item label="证件类型">{{ detailData.idType }}</el-descriptions-item>
<el-descriptions-item label="证件号码">{{ detailData.idNum }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ detailData.createTime }}</el-descriptions-item>
<el-descriptions-item label="创建者">{{ detailData.createBy }}</el-descriptions-item>
</el-descriptions>
</div>
<!-- 业务信息组 -->
<div class="info-section">
<h4 class="section-title">业务信息</h4>
<el-descriptions :column="2" border>
<el-descriptions-item label="担保方式">{{ detailData.guarType }}</el-descriptions-item>
<el-descriptions-item label="申请金额">{{ detailData.applyAmt }} </el-descriptions-item>
<el-descriptions-item label="贷款用途">{{ formatLoanPurpose(detailData.loanPurpose) }}</el-descriptions-item>
<el-descriptions-item label="借款期限">{{ detailData.loanTerm || '-' }}</el-descriptions-item>
<el-descriptions-item label="是否有经营佐证">{{
formatBoolean(detailData.bizProof)
}}
</el-descriptions-item>
<el-descriptions-item label="循环功能">{{ formatBoolean(detailData.loanLoop) }}</el-descriptions-item>
<el-descriptions-item label="抵质押类型">{{ detailData.collType || '-' }}</el-descriptions-item>
<el-descriptions-item label="抵质押物是否三方所有">{{
formatBoolean(detailData.collThirdParty)
}}
</el-descriptions-item>
</el-descriptions>
</div>
</el-card>
<!-- 议价池卡片 -->
<BargainingPoolDisplay
v-if="bargainingPool"
:branch-pool="bargainingPool.branchPool"
:sub-branch-pool="bargainingPool.subBranchPool"
:private-domain-pool="bargainingPool.privateDomainPool"
:excess-profit-share="bargainingPool.excessProfitShare"
/>
</div>
</div>
</div>
</template>
<script>
import {setExecuteRate} from "@/api/loanPricing/workflow"
import ModelOutputDisplay from "./ModelOutputDisplay.vue"
import BargainingPoolDisplay from "./BargainingPoolDisplay.vue"
export default {
name: "PersonalWorkflowDetail",
components: {
ModelOutputDisplay,
BargainingPoolDisplay
},
props: {
detailData: {
type: Object,
required: true
},
retailOutput: {
type: Object,
default: null
},
bargainingPool: {
type: Object,
default: null
}
},
data() {
return {
loading: false,
executeRateInput: ''
}
},
watch: {
'detailData.executeRate': {
handler(newVal) {
this.executeRateInput = newVal || ''
},
immediate: true
}
},
methods: {
/** 格式化布尔值为中文 */
formatBoolean(value) {
if (value === 'true' || value === true || value === '1' || value === 1) return '是'
if (value === 'false' || value === false || value === '0' || value === 0) return '否'
return value || '-'
},
/** 格式化贷款用途 */
formatLoanPurpose(value) {
if (value === 'consumer') return '消费'
if (value === 'business') return '经营'
return value || '-'
},
/** 获取基准利率 */
getBaseLoanRate() {
return this.retailOutput?.baseLoanRate || '-'
},
/** 获取浮动BP */
getTotalBp() {
return this.retailOutput?.totalBp || '-'
},
/** 获取最终测算利率 */
getCalculateRate() {
return this.retailOutput?.finalCalculateRate || '-'
},
/** 设定执行利率 */
handleSetExecuteRate() {
const value = this.executeRateInput
if (value === null || value === undefined || value === '') {
this.$modal.msgError("请输入执行利率")
return
}
const numValue = parseFloat(value)
if (isNaN(numValue)) {
this.$modal.msgError("请输入有效的数字")
return
}
if (numValue < 0 || numValue > 100) {
this.$modal.msgError("执行利率必须在 0 到 100 之间")
return
}
this.loading = true
setExecuteRate(this.detailData.serialNum, value.toString()).then(() => {
this.$modal.msgSuccess("执行利率设定成功")
this.$emit('refresh')
this.loading = false
}).catch(error => {
this.$modal.msgError("设定失败:" + (error.msg || error.message || "未知错误"))
this.loading = false
})
}
}
}
</script>
<style lang="scss" scoped>
.personal-workflow-detail {
.detail-layout {
display: flex;
gap: 20px;
align-items: flex-start;
.left-panel {
flex: 0 0 280px;
max-width: 280px;
.summary-card {
::v-deep .el-card__header {
padding: 16px 20px;
background-color: #fafafa;
border-bottom: 1px solid #ebeef5;
}
.card-header {
display: flex;
align-items: center;
.card-title {
font-size: 16px;
font-weight: 500;
color: #303133;
}
}
::v-deep .el-card__body {
padding: 16px;
}
.execute-rate-input-wrapper {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
.execute-rate-input {
width: 100%;
}
}
.rate-value {
color: #67c23a;
font-weight: 500;
}
.total-bp-value {
color: #e6a23c;
font-weight: 600;
}
.calculate-rate {
color: #f56c6c;
font-weight: 600;
font-size: 16px;
}
}
}
.right-panel {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 20px;
.detail-card {
::v-deep .el-card__header {
padding: 16px 20px;
background-color: #fafafa;
border-bottom: 1px solid #ebeef5;
}
.card-header {
display: flex;
align-items: center;
.card-title {
font-size: 16px;
font-weight: 500;
color: #303133;
}
}
::v-deep .el-card__body {
padding: 20px;
}
.info-section {
&:not(:last-child) {
margin-bottom: 24px;
}
.section-title {
margin: 0 0 16px 0;
font-size: 14px;
font-weight: 500;
color: #606266;
padding-bottom: 8px;
border-bottom: 1px solid #ebeef5;
}
}
}
}
}
}
@media screen and (max-width: 992px) {
.personal-workflow-detail {
.detail-layout {
flex-direction: column;
.left-panel,
.right-panel {
flex: 1 1 100%;
max-width: 100%;
}
}
}
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<div class="app-container workflow-detail-container" v-loading="loading">
<!-- 页面头部标题和返回按钮 -->
<div class="page-header">
<h2 class="page-title">流程详情</h2>
<el-button icon="el-icon-back" size="small" @click="goBack">返回</el-button>
</div>
<!-- 根据客户类型渲染对应的详情组件 -->
<personal-workflow-detail
v-if="!loading && workflowDetail && workflowDetail.custType === '个人'"
:detail-data="workflowDetail"
:retail-output="retailOutput"
:bargaining-pool="bargainingPool"
@refresh="getDetail"
/>
<corporate-workflow-detail
v-if="!loading && workflowDetail && workflowDetail.custType === '企业'"
:detail-data="workflowDetail"
:corp-output="corpOutput"
:bargaining-pool="bargainingPool"
@refresh="getDetail"
/>
</div>
</template>
<script>
import {getWorkflow} from "@/api/loanPricing/workflow"
import PersonalWorkflowDetail from "./components/PersonalWorkflowDetail.vue"
import CorporateWorkflowDetail from "./components/CorporateWorkflowDetail.vue"
export default {
name: "LoanPricingWorkflowDetail",
components: {
PersonalWorkflowDetail,
CorporateWorkflowDetail
},
data() {
return {
loading: true,
workflowDetail: null,
retailOutput: null,
corpOutput: null,
bargainingPool: null
}
},
created() {
this.getDetail()
},
methods: {
/** 获取流程详情 */
getDetail() {
const serialNum = this.$route.params.serialNum
if (!serialNum) {
this.$modal.msgError("缺少业务方流水号参数")
this.goBack()
return
}
getWorkflow(serialNum).then(response => {
this.workflowDetail = response.data.loanPricingWorkflow
this.retailOutput = response.data.modelRetailOutputFields
this.corpOutput = response.data.modelCorpOutputFields
this.bargainingPool = response.data.bargainingPool
this.loading = false
}).catch(error => {
this.$modal.msgError("获取流程详情失败:" + (error.message || "未知错误"))
this.loading = false
})
},
/** 返回上一页 */
goBack() {
this.$router.go(-1)
}
}
}
</script>
<style lang="scss" scoped>
.workflow-detail-container {
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 0 4px;
.page-title {
margin: 0;
font-size: 20px;
font-weight: 500;
color: #303133;
}
}
}
</style>

View File

@@ -0,0 +1,182 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="客户内码" prop="custIsn">
<el-input
v-model="queryParams.custIsn"
placeholder="请输入客户内码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="创建者" prop="createBy">
<el-input
v-model="queryParams.createBy"
placeholder="请输入创建者"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="机构号" prop="orgCode">
<el-input
v-model="queryParams.orgCode"
placeholder="请输入机构号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
>新增</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="workflowList">
<el-table-column label="业务方流水号" align="center" prop="serialNum" width="180" :show-overflow-tooltip="true" />
<el-table-column label="客户名称" align="center" prop="custName" :show-overflow-tooltip="true" />
<el-table-column label="客户类型" align="center" prop="custType" width="100" />
<el-table-column label="担保方式" align="center" prop="guarType" width="100" />
<el-table-column label="申请金额(元)" align="center" prop="applyAmt" width="120" />
<el-table-column label="测算利率(%)" align="center" prop="calculateRate" width="100" />
<el-table-column label="执行利率(%)" align="center" prop="executeRate" width="100" />
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="创建者" align="center" prop="createBy" width="120" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleView(scope.row)"
>查看</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 客户类型选择对话框 -->
<customer-type-selector :visible.sync="showTypeSelector" @select="handleSelectType"/>
<!-- 个人客户创建对话框 -->
<personal-create-dialog :visible.sync="showPersonalDialog" @success="handleCreateSuccess"/>
<!-- 企业客户创建对话框 -->
<corporate-create-dialog :visible.sync="showCorporateDialog" @success="handleCreateSuccess"/>
</div>
</template>
<script>
import {listWorkflow} from "@/api/loanPricing/workflow"
import CustomerTypeSelector from "./components/CustomerTypeSelector"
import PersonalCreateDialog from "./components/PersonalCreateDialog"
import CorporateCreateDialog from "./components/CorporateCreateDialog"
export default {
name: "LoanPricingWorkflow",
components: {
CustomerTypeSelector,
PersonalCreateDialog,
CorporateCreateDialog
},
data() {
return {
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 利率定价流程表格数据
workflowList: [],
// 是否显示客户类型选择弹出层
showTypeSelector: false,
// 是否显示个人客户创建弹出层
showPersonalDialog: false,
// 是否显示企业客户创建弹出层
showCorporateDialog: false,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
custIsn: undefined,
createBy: undefined,
orgCode: undefined
}
}
},
created() {
this.getList()
},
activated() {
this.getList()
},
methods: {
/** 查询利率定价流程列表 */
getList() {
this.loading = true
listWorkflow(this.queryParams).then(response => {
this.workflowList = response.rows
this.total = response.total
this.loading = false
})
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm")
this.handleQuery()
},
/** 查看详情操作 */
handleView(row) {
this.$router.push({
name: 'LoanPricingWorkflowDetail',
params: { serialNum: row.serialNum }
})
},
/** 新增按钮操作 */
handleAdd() {
this.showTypeSelector = true
},
/** 选择客户类型回调 */
handleSelectType(type) {
if (type === 'personal') {
this.showPersonalDialog = true
} else if (type === 'corporate') {
this.showCorporateDialog = true
}
},
/** 创建成功回调 */
handleCreateSuccess() {
this.getList()
}
}
}
</script>

View File

@@ -73,13 +73,13 @@ export default {
return {
title: process.env.VUE_APP_TITLE,
footerContent: defaultSettings.footerContent,
codeUrl: "",
loginForm: {
username: "admin",
password: "admin123",
rememberMe: false,
code: "",
uuid: ""
codeUrl: "",
loginForm: {
username: "",
password: "",
rememberMe: false,
code: "",
uuid: ""
},
loginRules: {
username: [

View File

@@ -0,0 +1,34 @@
const fs = require('fs')
const path = require('path')
const assert = require('assert')
function read(relativePath) {
return fs.readFileSync(path.join(__dirname, '..', relativePath), 'utf8')
}
const personalCreateDialog = read('src/views/loanPricing/workflow/components/PersonalCreateDialog.vue')
const corporateCreateDialog = read('src/views/loanPricing/workflow/components/CorporateCreateDialog.vue')
assert(
!personalCreateDialog.includes('const validateIdNum ='),
'个人新增弹窗仍包含证件号码格式校验函数'
)
assert(
!corporateCreateDialog.includes('const validateIdNum ='),
'企业新增弹窗仍包含证件号码格式校验函数'
)
assert(
personalCreateDialog.includes("idNum: [") &&
personalCreateDialog.includes('{required: true, message: "证件号码不能为空", trigger: "blur"}'),
'个人新增弹窗证件号码规则应仅保留必填'
)
assert(
corporateCreateDialog.includes("idNum: [") &&
corporateCreateDialog.includes('{required: true, message: "证件号码不能为空", trigger: "blur"}'),
'企业新增弹窗证件号码规则应仅保留必填'
)
console.log('id number validation removal assertions passed')

View File

@@ -0,0 +1,30 @@
const fs = require('fs')
const path = require('path')
const assert = require('assert')
const loginViewSource = fs.readFileSync(
path.join(__dirname, '../src/views/login.vue'),
'utf8'
)
assert(
/loginForm:\s*\{[\s\S]*username:\s*""/.test(loginViewSource),
'登录页默认用户名应为空字符串'
)
assert(
/loginForm:\s*\{[\s\S]*password:\s*""/.test(loginViewSource),
'登录页默认密码应为空字符串'
)
assert(
/username:\s*username === undefined \? this\.loginForm\.username : username/.test(loginViewSource),
'登录页应继续支持从 cookie 回填用户名'
)
assert(
/password:\s*password === undefined \? this\.loginForm\.password : decrypt\(password\)/.test(loginViewSource),
'登录页应继续支持从 cookie 回填密码'
)
console.log('login default credentials assertions passed')

View File

@@ -0,0 +1,88 @@
const assert = require('assert')
const fs = require('fs')
const path = require('path')
const vm = require('vm')
function loadModule(filePath, stubs = {}) {
const source = fs.readFileSync(filePath, 'utf8')
const exportedNames = []
const transformed = source
.replace(/^import .*$/gm, '')
.replace(/export function\s+([A-Za-z0-9_]+)\s*\(/g, (_, name) => {
exportedNames.push(name)
return `function ${name}(`
})
.replace(/export default\s+/g, 'module.exports = ')
const sandbox = {
module: { exports: {} },
exports: {},
require,
console,
process: {
env: {
VUE_APP_PASSWORD_TRANSFER_KEY: '1234567890abcdef'
}
},
...stubs
}
vm.runInNewContext(
`${transformed}\nmodule.exports = { ${exportedNames.join(', ')} };`,
sandbox,
{ filename: filePath }
)
return sandbox.module.exports
}
const passwordTransferModule = loadModule(
path.resolve(__dirname, '../src/utils/passwordTransfer.js'),
{ CryptoJS: require('crypto-js') }
)
const { encryptPasswordFields } = passwordTransferModule
const encrypted = encryptPasswordFields(
{ password: 'admin123', code: '8888' },
['password'],
'1234567890abcdef'
)
assert.notStrictEqual(encrypted.password, 'admin123')
assert.strictEqual(encrypted.code, '8888')
const request = config => config
const loginModule = loadModule(
path.resolve(__dirname, '../src/api/login.js'),
{ request, encryptPasswordFields }
)
const loginConfig = loginModule.login('admin', 'admin123', '8888', 'uuid-1')
assert.notStrictEqual(loginConfig.data.password, 'admin123')
assert.strictEqual(loginConfig.data.username, 'admin')
const registerConfig = loginModule.register({ username: 'u1', password: 'p1', confirmPassword: 'p1', code: '8888' })
assert.notStrictEqual(registerConfig.data.password, 'p1')
assert.strictEqual(registerConfig.data.confirmPassword, 'p1')
const userModule = loadModule(
path.resolve(__dirname, '../src/api/system/user.js'),
{
request,
encryptPasswordFields,
parseStrEmpty: value => value
}
)
const updatePwdConfig = userModule.updateUserPwd('oldPwd', 'newPwd')
assert.notStrictEqual(updatePwdConfig.data.oldPassword, 'oldPwd')
assert.notStrictEqual(updatePwdConfig.data.newPassword, 'newPwd')
const addUserConfig = userModule.addUser({ userName: 'u1', password: 'initPwd', nickName: 'n1' })
assert.notStrictEqual(addUserConfig.data.password, 'initPwd')
const resetUserPwdConfig = userModule.resetUserPwd(2, 'resetPwd')
assert.notStrictEqual(resetUserPwdConfig.data.password, 'resetPwd')
console.log('password-transfer-api test passed')

View File

@@ -0,0 +1,65 @@
const fs = require('fs')
const path = require('path')
const assert = require('assert')
function read(relativePath) {
return fs.readFileSync(path.join(__dirname, '..', relativePath), 'utf8')
}
const personalCreateDialog = read('src/views/loanPricing/workflow/components/PersonalCreateDialog.vue')
const personalDetail = read('src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue')
assert(
personalCreateDialog.includes('label="贷款用途"') && personalCreateDialog.includes('prop="loanPurpose"'),
'个人新增弹窗缺少贷款用途字段'
)
assert(
personalCreateDialog.includes('label="借款期限(年)"') && personalCreateDialog.includes('prop="loanTerm"'),
'个人新增弹窗缺少借款期限字段'
)
assert(
personalCreateDialog.includes("value=\"consumer\"") && personalCreateDialog.includes("value=\"business\""),
'个人新增弹窗缺少贷款用途选项'
)
assert(
personalCreateDialog.includes('loanTermOptions') &&
personalCreateDialog.includes("'1'") &&
personalCreateDialog.includes("'6'") &&
!personalCreateDialog.includes("'7'"),
'个人新增弹窗借款期限选项应限制为 1-6 年'
)
assert(
personalCreateDialog.includes('label="一类"') &&
personalCreateDialog.includes('label="二类"') &&
personalCreateDialog.includes('label="三类"') &&
!personalCreateDialog.includes('label="一线"'),
'个人新增弹窗抵质押类型选项未按 Excel 对齐'
)
assert(
!personalCreateDialog.includes('{required: true, message: "请选择抵质押类型", trigger: "change"}'),
'个人新增弹窗仍将抵质押类型设为必填'
)
assert(
personalCreateDialog.includes("bizProof: this.form.bizProof ? '1' : '0'") &&
personalCreateDialog.includes("loanLoop: this.form.loanLoop ? '1' : '0'") &&
personalCreateDialog.includes("collThirdParty: this.form.collThirdParty ? '1' : '0'"),
'个人新增弹窗开关字段未按 1/0 提交'
)
assert(
personalDetail.includes('label="贷款用途"') && personalDetail.includes('detailData.loanPurpose'),
'个人详情页缺少贷款用途展示'
)
assert(
personalDetail.includes("value === '1'") && personalDetail.includes("value === '0'"),
'个人详情页布尔格式化未兼容 1/0'
)
console.log('personal create input params assertions passed')

View File

@@ -0,0 +1,21 @@
const fs = require('fs')
const path = require('path')
const assert = require('assert')
function read(relativePath) {
return fs.readFileSync(path.join(__dirname, '..', relativePath), 'utf8')
}
const personalDetail = read('src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue')
assert(
/label="最终测算利率"/.test(personalDetail),
'个人流程详情左侧缺少“最终测算利率”标签'
)
assert(
/return this\.retailOutput\?\.finalCalculateRate \|\| '-'/.test(personalDetail),
'个人流程详情没有使用 finalCalculateRate 展示最终测算利率'
)
console.log('personal final calculate rate display assertions passed')

View File

@@ -0,0 +1,29 @@
const fs = require('fs')
const path = require('path')
const assert = require('assert')
function read(relativePath) {
return fs.readFileSync(path.join(__dirname, '..', relativePath), 'utf8')
}
const personalDetail = read('src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue')
const modelOutput = read('src/views/loanPricing/workflow/components/ModelOutputDisplay.vue')
assert(
personalDetail.includes('label="借款期限"') && personalDetail.includes('detailData.loanTerm'),
'个人详情页缺少借款期限展示'
)
const requiredRetailFields = [
'retailOutput.loanRateHistory',
'retailOutput.minRateProduct',
'retailOutput.smoothRange',
'retailOutput.finalCalculateRate',
'retailOutput.referenceRate'
]
requiredRetailFields.forEach((field) => {
assert(modelOutput.includes(field), `模型输出缺少字段展示: ${field}`)
})
console.log('retail display fields assertions passed')

View File

@@ -0,0 +1,27 @@
const fs = require('fs')
const path = require('path')
const assert = require('assert')
function read(relativePath) {
return fs.readFileSync(path.join(__dirname, '..', relativePath), 'utf8')
}
function assertModelOutputBeforeDetailCard(source, label) {
const modelOutputIndex = source.indexOf('<ModelOutputDisplay')
const detailCardIndex = source.indexOf('<el-card class="detail-card">')
assert(modelOutputIndex !== -1, `${label} 缺少模型输出卡片`)
assert(detailCardIndex !== -1, `${label} 缺少流程详情卡片`)
assert(
modelOutputIndex < detailCardIndex,
`${label} 的模型输出卡片应位于流程详情卡片上方`
)
}
const personalDetail = read('src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue')
const corporateDetail = read('src/views/loanPricing/workflow/components/CorporateWorkflowDetail.vue')
assertModelOutputBeforeDetailCard(personalDetail, '个人流程详情')
assertModelOutputBeforeDetailCard(corporateDetail, '企业流程详情')
console.log('workflow detail card order assertions passed')

View File

@@ -0,0 +1,52 @@
const assert = require('assert')
const fs = require('fs')
const path = require('path')
const vm = require('vm')
function loadComponentOptions(filePath) {
const source = fs.readFileSync(filePath, 'utf8')
const scriptMatch = source.match(/<script>([\s\S]*?)<\/script>/)
if (!scriptMatch) {
throw new Error('未找到组件脚本内容')
}
const importNames = []
const importPattern = /^import\s+([A-Za-z0-9_]+)\s+from\s+.*$/gm
let importMatch = importPattern.exec(scriptMatch[1])
while (importMatch) {
importNames.push(importMatch[1])
importMatch = importPattern.exec(scriptMatch[1])
}
const stubImports = importNames.map(name => `const ${name} = {};`).join('\n')
const transformed = `${stubImports}\n${scriptMatch[1]}`
.replace(/^import .*$/gm, '')
.replace(/export default/, 'module.exports =')
const sandbox = {
module: { exports: {} },
exports: {},
require,
console
}
vm.runInNewContext(transformed, sandbox, { filename: filePath })
return sandbox.module.exports
}
const filePath = path.resolve(__dirname, '../src/views/loanPricing/workflow/index.vue')
const component = loadComponentOptions(filePath)
assert.strictEqual(typeof component.activated, 'function', '流程列表页应在激活时刷新数据')
let refreshCount = 0
component.activated.call({
getList() {
refreshCount += 1
}
})
assert.strictEqual(refreshCount, 1, '流程列表页激活时应调用一次 getList')
console.log('workflow-index-refresh test passed')

24
sql/loan_pricing_menu.sql Normal file
View File

@@ -0,0 +1,24 @@
-- 利率定价流程菜单配置
-- 注意Windows 环境下执行后可能需要使用 CONVERT 修复编码问题
-- 删除旧数据
DELETE FROM sys_role_menu WHERE menu_id IN (2000, 2001, 2002);
DELETE FROM sys_menu WHERE menu_id IN (2000, 2001, 2002);
-- 一级菜单:利率定价管理
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
VALUES(2000, '利率定价管理', 0, 5, 'loanPricing', NULL, 1, 0, 'M', '0', '0', '', 'money', 'admin', NOW(), '');
-- 二级菜单:流程列表
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
VALUES(2001, '流程列表', 2000, 1, 'workflow', 'loanPricing/workflow/index', 1, 0, 'C', '0', '0', 'loanPricing:workflow:list', 'list', 'admin', NOW(), '');
-- 流程列表查询按钮
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
VALUES(2002, '流程查询', 2001, 1, '', '', 1, 0, 'F', '0', '0', 'loanPricing:workflow:query', '#', 'admin', NOW(), '');
-- 关联管理员角色
INSERT INTO sys_role_menu VALUES(1, 2000), (1, 2001), (1, 2002);
-- 如果 Windows 环境导致中文乱码,执行以下语句修复:
-- UPDATE sys_menu SET menu_name = CONVERT(CAST(CONVERT(menu_name USING latin1) AS BINARY) USING utf8mb4) WHERE menu_id IN (2000, 2001, 2002);

View File

@@ -0,0 +1,923 @@
-- 说明:
-- 1. 本文件用于生产环境数据库初始化
-- 2. 基于 sql/ry_20250522.sql 追加贷款定价业务表结构生成
-- 3. 包含若依基础初始化数据,不包含任何贷款定价业务数据
-- 4. 建议在空库中执行;执行前请确认数据库账号具备建库建表权限
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS=0;
CREATE DATABASE IF NOT EXISTS `loan-pricing` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE `loan-pricing`;
-- ----------------------------
-- 1、部门表
-- ----------------------------
drop table if exists sys_dept;
create table sys_dept (
dept_id bigint(20) not null auto_increment comment '部门id',
parent_id bigint(20) default 0 comment '父部门id',
ancestors varchar(50) default '' comment '祖级列表',
dept_name varchar(30) default '' comment '部门名称',
order_num int(4) default 0 comment '显示顺序',
leader varchar(20) default null comment '负责人',
phone varchar(11) default null comment '联系电话',
email varchar(50) default null comment '邮箱',
status char(1) default '0' comment '部门状态0正常 1停用',
del_flag char(1) default '0' comment '删除标志0代表存在 2代表删除',
create_by varchar(64) default '' comment '创建者',
create_time datetime comment '创建时间',
update_by varchar(64) default '' comment '更新者',
update_time datetime comment '更新时间',
primary key (dept_id)
) engine=innodb auto_increment=200 comment = '部门表';
-- ----------------------------
-- 初始化-部门表数据
-- ----------------------------
insert into sys_dept values(100, 0, '0', '若依科技', 0, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
insert into sys_dept values(101, 100, '0,100', '深圳总公司', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
insert into sys_dept values(102, 100, '0,100', '长沙分公司', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
insert into sys_dept values(103, 101, '0,100,101', '研发部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
insert into sys_dept values(104, 101, '0,100,101', '市场部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
insert into sys_dept values(105, 101, '0,100,101', '测试部门', 3, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
insert into sys_dept values(106, 101, '0,100,101', '财务部门', 4, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
insert into sys_dept values(107, 101, '0,100,101', '运维部门', 5, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
insert into sys_dept values(108, 102, '0,100,102', '市场部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
insert into sys_dept values(109, 102, '0,100,102', '财务部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
-- ----------------------------
-- 2、用户信息表
-- ----------------------------
drop table if exists sys_user;
create table sys_user (
user_id bigint(20) not null auto_increment comment '用户ID',
dept_id bigint(20) default null comment '部门ID',
user_name varchar(30) not null comment '用户账号',
nick_name varchar(30) not null comment '用户昵称',
user_type varchar(2) default '00' comment '用户类型00系统用户',
email varchar(50) default '' comment '用户邮箱',
phonenumber varchar(11) default '' comment '手机号码',
sex char(1) default '0' comment '用户性别0男 1女 2未知',
avatar varchar(100) default '' comment '头像地址',
password varchar(100) default '' comment '密码',
status char(1) default '0' comment '账号状态0正常 1停用',
del_flag char(1) default '0' comment '删除标志0代表存在 2代表删除',
login_ip varchar(128) default '' comment '最后登录IP',
login_date datetime comment '最后登录时间',
pwd_update_date datetime comment '密码最后更新时间',
create_by varchar(64) default '' comment '创建者',
create_time datetime comment '创建时间',
update_by varchar(64) default '' comment '更新者',
update_time datetime comment '更新时间',
remark varchar(500) default null comment '备注',
primary key (user_id)
) engine=innodb auto_increment=100 comment = '用户信息表';
-- ----------------------------
-- 初始化-用户信息表数据
-- ----------------------------
insert into sys_user values(1, 103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), sysdate(), 'admin', sysdate(), '', null, '管理员');
insert into sys_user values(2, 105, 'ry', '若依', '00', 'ry@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), sysdate(), 'admin', sysdate(), '', null, '测试员');
-- ----------------------------
-- 3、岗位信息表
-- ----------------------------
drop table if exists sys_post;
create table sys_post
(
post_id bigint(20) not null auto_increment comment '岗位ID',
post_code varchar(64) not null comment '岗位编码',
post_name varchar(50) not null comment '岗位名称',
post_sort int(4) not null comment '显示顺序',
status char(1) not null comment '状态0正常 1停用',
create_by varchar(64) default '' comment '创建者',
create_time datetime comment '创建时间',
update_by varchar(64) default '' comment '更新者',
update_time datetime comment '更新时间',
remark varchar(500) default null comment '备注',
primary key (post_id)
) engine=innodb comment = '岗位信息表';
-- ----------------------------
-- 初始化-岗位信息表数据
-- ----------------------------
insert into sys_post values(1, 'ceo', '董事长', 1, '0', 'admin', sysdate(), '', null, '');
insert into sys_post values(2, 'se', '项目经理', 2, '0', 'admin', sysdate(), '', null, '');
insert into sys_post values(3, 'hr', '人力资源', 3, '0', 'admin', sysdate(), '', null, '');
insert into sys_post values(4, 'user', '普通员工', 4, '0', 'admin', sysdate(), '', null, '');
-- ----------------------------
-- 4、角色信息表
-- ----------------------------
drop table if exists sys_role;
create table sys_role (
role_id bigint(20) not null auto_increment comment '角色ID',
role_name varchar(30) not null comment '角色名称',
role_key varchar(100) not null comment '角色权限字符串',
role_sort int(4) not null comment '显示顺序',
data_scope char(1) default '1' comment '数据范围1全部数据权限 2自定数据权限 3本部门数据权限 4本部门及以下数据权限',
menu_check_strictly tinyint(1) default 1 comment '菜单树选择项是否关联显示',
dept_check_strictly tinyint(1) default 1 comment '部门树选择项是否关联显示',
status char(1) not null comment '角色状态0正常 1停用',
del_flag char(1) default '0' comment '删除标志0代表存在 2代表删除',
create_by varchar(64) default '' comment '创建者',
create_time datetime comment '创建时间',
update_by varchar(64) default '' comment '更新者',
update_time datetime comment '更新时间',
remark varchar(500) default null comment '备注',
primary key (role_id)
) engine=innodb auto_increment=100 comment = '角色信息表';
-- ----------------------------
-- 初始化-角色信息表数据
-- ----------------------------
insert into sys_role values('1', '超级管理员', 'admin', 1, 1, 1, 1, '0', '0', 'admin', sysdate(), '', null, '超级管理员');
insert into sys_role values('2', '普通角色', 'common', 2, 2, 1, 1, '0', '0', 'admin', sysdate(), '', null, '普通角色');
-- ----------------------------
-- 5、菜单权限表
-- ----------------------------
drop table if exists sys_menu;
create table sys_menu (
menu_id bigint(20) not null auto_increment comment '菜单ID',
menu_name varchar(50) not null comment '菜单名称',
parent_id bigint(20) default 0 comment '父菜单ID',
order_num int(4) default 0 comment '显示顺序',
path varchar(200) default '' comment '路由地址',
component varchar(255) default null comment '组件路径',
query varchar(255) default null comment '路由参数',
route_name varchar(50) default '' comment '路由名称',
is_frame int(1) default 1 comment '是否为外链0是 1否',
is_cache int(1) default 0 comment '是否缓存0缓存 1不缓存',
menu_type char(1) default '' comment '菜单类型M目录 C菜单 F按钮',
visible char(1) default 0 comment '菜单状态0显示 1隐藏',
status char(1) default 0 comment '菜单状态0正常 1停用',
perms varchar(100) default null comment '权限标识',
icon varchar(100) default '#' comment '菜单图标',
create_by varchar(64) default '' comment '创建者',
create_time datetime comment '创建时间',
update_by varchar(64) default '' comment '更新者',
update_time datetime comment '更新时间',
remark varchar(500) default '' comment '备注',
primary key (menu_id)
) engine=innodb auto_increment=2000 comment = '菜单权限表';
-- ----------------------------
-- 初始化-菜单信息表数据
-- ----------------------------
-- 一级菜单
insert into sys_menu values('1', '系统管理', '0', '1', 'system', null, '', '', 1, 0, 'M', '0', '0', '', 'system', 'admin', sysdate(), '', null, '系统管理目录');
insert into sys_menu values('2', '系统监控', '0', '2', 'monitor', null, '', '', 1, 0, 'M', '0', '0', '', 'monitor', 'admin', sysdate(), '', null, '系统监控目录');
insert into sys_menu values('3', '系统工具', '0', '3', 'tool', null, '', '', 1, 0, 'M', '0', '0', '', 'tool', 'admin', sysdate(), '', null, '系统工具目录');
-- 二级菜单
insert into sys_menu values('100', '用户管理', '1', '1', 'user', 'system/user/index', '', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', sysdate(), '', null, '用户管理菜单');
insert into sys_menu values('101', '角色管理', '1', '2', 'role', 'system/role/index', '', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', sysdate(), '', null, '角色管理菜单');
insert into sys_menu values('102', '菜单管理', '1', '3', 'menu', 'system/menu/index', '', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 'admin', sysdate(), '', null, '菜单管理菜单');
insert into sys_menu values('103', '部门管理', '1', '4', 'dept', 'system/dept/index', '', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 'admin', sysdate(), '', null, '部门管理菜单');
insert into sys_menu values('104', '岗位管理', '1', '5', 'post', 'system/post/index', '', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 'admin', sysdate(), '', null, '岗位管理菜单');
insert into sys_menu values('105', '字典管理', '1', '6', 'dict', 'system/dict/index', '', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 'admin', sysdate(), '', null, '字典管理菜单');
insert into sys_menu values('106', '参数设置', '1', '7', 'config', 'system/config/index', '', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 'admin', sysdate(), '', null, '参数设置菜单');
insert into sys_menu values('107', '通知公告', '1', '8', 'notice', 'system/notice/index', '', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 'admin', sysdate(), '', null, '通知公告菜单');
insert into sys_menu values('108', '日志管理', '1', '9', 'log', '', '', '', 1, 0, 'M', '0', '0', '', 'log', 'admin', sysdate(), '', null, '日志管理菜单');
insert into sys_menu values('109', '在线用户', '2', '1', 'online', 'monitor/online/index', '', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 'admin', sysdate(), '', null, '在线用户菜单');
insert into sys_menu values('110', '定时任务', '2', '2', 'job', 'monitor/job/index', '', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', 'admin', sysdate(), '', null, '定时任务菜单');
insert into sys_menu values('111', '数据监控', '2', '3', 'druid', 'monitor/druid/index', '', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', 'admin', sysdate(), '', null, '数据监控菜单');
insert into sys_menu values('112', '服务监控', '2', '4', 'server', 'monitor/server/index', '', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', 'admin', sysdate(), '', null, '服务监控菜单');
insert into sys_menu values('113', '缓存监控', '2', '5', 'cache', 'monitor/cache/index', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 'admin', sysdate(), '', null, '缓存监控菜单');
insert into sys_menu values('114', '缓存列表', '2', '6', 'cacheList', 'monitor/cache/list', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis-list', 'admin', sysdate(), '', null, '缓存列表菜单');
insert into sys_menu values('115', '表单构建', '3', '1', 'build', 'tool/build/index', '', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', 'admin', sysdate(), '', null, '表单构建菜单');
insert into sys_menu values('116', '代码生成', '3', '2', 'gen', 'tool/gen/index', '', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 'admin', sysdate(), '', null, '代码生成菜单');
insert into sys_menu values('117', '系统接口', '3', '3', 'swagger', 'tool/swagger/index', '', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', 'admin', sysdate(), '', null, '系统接口菜单');
-- 三级菜单
insert into sys_menu values('500', '操作日志', '108', '1', 'operlog', 'monitor/operlog/index', '', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 'admin', sysdate(), '', null, '操作日志菜单');
insert into sys_menu values('501', '登录日志', '108', '2', 'logininfor', 'monitor/logininfor/index', '', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 'admin', sysdate(), '', null, '登录日志菜单');
-- 用户管理按钮
insert into sys_menu values('1000', '用户查询', '100', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1001', '用户新增', '100', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1002', '用户修改', '100', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1003', '用户删除', '100', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1004', '用户导出', '100', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1005', '用户导入', '100', '6', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1006', '重置密码', '100', '7', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', sysdate(), '', null, '');
-- 角色管理按钮
insert into sys_menu values('1007', '角色查询', '101', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1008', '角色新增', '101', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1009', '角色修改', '101', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1010', '角色删除', '101', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1011', '角色导出', '101', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', sysdate(), '', null, '');
-- 菜单管理按钮
insert into sys_menu values('1012', '菜单查询', '102', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1013', '菜单新增', '102', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1014', '菜单修改', '102', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1015', '菜单删除', '102', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', sysdate(), '', null, '');
-- 部门管理按钮
insert into sys_menu values('1016', '部门查询', '103', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1017', '部门新增', '103', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1018', '部门修改', '103', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1019', '部门删除', '103', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', sysdate(), '', null, '');
-- 岗位管理按钮
insert into sys_menu values('1020', '岗位查询', '104', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1021', '岗位新增', '104', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1022', '岗位修改', '104', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1023', '岗位删除', '104', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1024', '岗位导出', '104', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', sysdate(), '', null, '');
-- 字典管理按钮
insert into sys_menu values('1025', '字典查询', '105', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1026', '字典新增', '105', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1027', '字典修改', '105', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1028', '字典删除', '105', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1029', '字典导出', '105', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', sysdate(), '', null, '');
-- 参数设置按钮
insert into sys_menu values('1030', '参数查询', '106', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1031', '参数新增', '106', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1032', '参数修改', '106', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1033', '参数删除', '106', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1034', '参数导出', '106', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', sysdate(), '', null, '');
-- 通知公告按钮
insert into sys_menu values('1035', '公告查询', '107', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1036', '公告新增', '107', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1037', '公告修改', '107', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1038', '公告删除', '107', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', sysdate(), '', null, '');
-- 操作日志按钮
insert into sys_menu values('1039', '操作查询', '500', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1040', '操作删除', '500', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1041', '日志导出', '500', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', sysdate(), '', null, '');
-- 登录日志按钮
insert into sys_menu values('1042', '登录查询', '501', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1043', '登录删除', '501', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1044', '日志导出', '501', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1045', '账户解锁', '501', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', 'admin', sysdate(), '', null, '');
-- 在线用户按钮
insert into sys_menu values('1046', '在线查询', '109', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1047', '批量强退', '109', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1048', '单条强退', '109', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', sysdate(), '', null, '');
-- 定时任务按钮
insert into sys_menu values('1049', '任务查询', '110', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1050', '任务新增', '110', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1051', '任务修改', '110', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1052', '任务删除', '110', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1053', '状态修改', '110', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1054', '任务导出', '110', '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', sysdate(), '', null, '');
-- 代码生成按钮
insert into sys_menu values('1055', '生成查询', '116', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1056', '生成修改', '116', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1057', '生成删除', '116', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1058', '导入代码', '116', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1059', '预览代码', '116', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu values('1060', '生成代码', '116', '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', sysdate(), '', null, '');
-- ----------------------------
-- 6、用户和角色关联表 用户N-1角色
-- ----------------------------
drop table if exists sys_user_role;
create table sys_user_role (
user_id bigint(20) not null comment '用户ID',
role_id bigint(20) not null comment '角色ID',
primary key(user_id, role_id)
) engine=innodb comment = '用户和角色关联表';
-- ----------------------------
-- 初始化-用户和角色关联表数据
-- ----------------------------
insert into sys_user_role values ('1', '1');
insert into sys_user_role values ('2', '2');
-- ----------------------------
-- 7、角色和菜单关联表 角色1-N菜单
-- ----------------------------
drop table if exists sys_role_menu;
create table sys_role_menu (
role_id bigint(20) not null comment '角色ID',
menu_id bigint(20) not null comment '菜单ID',
primary key(role_id, menu_id)
) engine=innodb comment = '角色和菜单关联表';
-- ----------------------------
-- 初始化-角色和菜单关联表数据
-- ----------------------------
insert into sys_role_menu values ('2', '1');
insert into sys_role_menu values ('2', '2');
insert into sys_role_menu values ('2', '3');
insert into sys_role_menu values ('2', '4');
insert into sys_role_menu values ('2', '100');
insert into sys_role_menu values ('2', '101');
insert into sys_role_menu values ('2', '102');
insert into sys_role_menu values ('2', '103');
insert into sys_role_menu values ('2', '104');
insert into sys_role_menu values ('2', '105');
insert into sys_role_menu values ('2', '106');
insert into sys_role_menu values ('2', '107');
insert into sys_role_menu values ('2', '108');
insert into sys_role_menu values ('2', '109');
insert into sys_role_menu values ('2', '110');
insert into sys_role_menu values ('2', '111');
insert into sys_role_menu values ('2', '112');
insert into sys_role_menu values ('2', '113');
insert into sys_role_menu values ('2', '114');
insert into sys_role_menu values ('2', '115');
insert into sys_role_menu values ('2', '116');
insert into sys_role_menu values ('2', '117');
insert into sys_role_menu values ('2', '500');
insert into sys_role_menu values ('2', '501');
insert into sys_role_menu values ('2', '1000');
insert into sys_role_menu values ('2', '1001');
insert into sys_role_menu values ('2', '1002');
insert into sys_role_menu values ('2', '1003');
insert into sys_role_menu values ('2', '1004');
insert into sys_role_menu values ('2', '1005');
insert into sys_role_menu values ('2', '1006');
insert into sys_role_menu values ('2', '1007');
insert into sys_role_menu values ('2', '1008');
insert into sys_role_menu values ('2', '1009');
insert into sys_role_menu values ('2', '1010');
insert into sys_role_menu values ('2', '1011');
insert into sys_role_menu values ('2', '1012');
insert into sys_role_menu values ('2', '1013');
insert into sys_role_menu values ('2', '1014');
insert into sys_role_menu values ('2', '1015');
insert into sys_role_menu values ('2', '1016');
insert into sys_role_menu values ('2', '1017');
insert into sys_role_menu values ('2', '1018');
insert into sys_role_menu values ('2', '1019');
insert into sys_role_menu values ('2', '1020');
insert into sys_role_menu values ('2', '1021');
insert into sys_role_menu values ('2', '1022');
insert into sys_role_menu values ('2', '1023');
insert into sys_role_menu values ('2', '1024');
insert into sys_role_menu values ('2', '1025');
insert into sys_role_menu values ('2', '1026');
insert into sys_role_menu values ('2', '1027');
insert into sys_role_menu values ('2', '1028');
insert into sys_role_menu values ('2', '1029');
insert into sys_role_menu values ('2', '1030');
insert into sys_role_menu values ('2', '1031');
insert into sys_role_menu values ('2', '1032');
insert into sys_role_menu values ('2', '1033');
insert into sys_role_menu values ('2', '1034');
insert into sys_role_menu values ('2', '1035');
insert into sys_role_menu values ('2', '1036');
insert into sys_role_menu values ('2', '1037');
insert into sys_role_menu values ('2', '1038');
insert into sys_role_menu values ('2', '1039');
insert into sys_role_menu values ('2', '1040');
insert into sys_role_menu values ('2', '1041');
insert into sys_role_menu values ('2', '1042');
insert into sys_role_menu values ('2', '1043');
insert into sys_role_menu values ('2', '1044');
insert into sys_role_menu values ('2', '1045');
insert into sys_role_menu values ('2', '1046');
insert into sys_role_menu values ('2', '1047');
insert into sys_role_menu values ('2', '1048');
insert into sys_role_menu values ('2', '1049');
insert into sys_role_menu values ('2', '1050');
insert into sys_role_menu values ('2', '1051');
insert into sys_role_menu values ('2', '1052');
insert into sys_role_menu values ('2', '1053');
insert into sys_role_menu values ('2', '1054');
insert into sys_role_menu values ('2', '1055');
insert into sys_role_menu values ('2', '1056');
insert into sys_role_menu values ('2', '1057');
insert into sys_role_menu values ('2', '1058');
insert into sys_role_menu values ('2', '1059');
insert into sys_role_menu values ('2', '1060');
-- ----------------------------
-- 8、角色和部门关联表 角色1-N部门
-- ----------------------------
drop table if exists sys_role_dept;
create table sys_role_dept (
role_id bigint(20) not null comment '角色ID',
dept_id bigint(20) not null comment '部门ID',
primary key(role_id, dept_id)
) engine=innodb comment = '角色和部门关联表';
-- ----------------------------
-- 初始化-角色和部门关联表数据
-- ----------------------------
insert into sys_role_dept values ('2', '100');
insert into sys_role_dept values ('2', '101');
insert into sys_role_dept values ('2', '105');
-- ----------------------------
-- 9、用户与岗位关联表 用户1-N岗位
-- ----------------------------
drop table if exists sys_user_post;
create table sys_user_post
(
user_id bigint(20) not null comment '用户ID',
post_id bigint(20) not null comment '岗位ID',
primary key (user_id, post_id)
) engine=innodb comment = '用户与岗位关联表';
-- ----------------------------
-- 初始化-用户与岗位关联表数据
-- ----------------------------
insert into sys_user_post values ('1', '1');
insert into sys_user_post values ('2', '2');
-- ----------------------------
-- 10、操作日志记录
-- ----------------------------
drop table if exists sys_oper_log;
create table sys_oper_log (
oper_id bigint(20) not null auto_increment comment '日志主键',
title varchar(50) default '' comment '模块标题',
business_type int(2) default 0 comment '业务类型0其它 1新增 2修改 3删除',
method varchar(200) default '' comment '方法名称',
request_method varchar(10) default '' comment '请求方式',
operator_type int(1) default 0 comment '操作类别0其它 1后台用户 2手机端用户',
oper_name varchar(50) default '' comment '操作人员',
dept_name varchar(50) default '' comment '部门名称',
oper_url varchar(255) default '' comment '请求URL',
oper_ip varchar(128) default '' comment '主机地址',
oper_location varchar(255) default '' comment '操作地点',
oper_param varchar(2000) default '' comment '请求参数',
json_result varchar(2000) default '' comment '返回参数',
status int(1) default 0 comment '操作状态0正常 1异常',
error_msg varchar(2000) default '' comment '错误消息',
oper_time datetime comment '操作时间',
cost_time bigint(20) default 0 comment '消耗时间',
primary key (oper_id),
key idx_sys_oper_log_bt (business_type),
key idx_sys_oper_log_s (status),
key idx_sys_oper_log_ot (oper_time)
) engine=innodb auto_increment=100 comment = '操作日志记录';
-- ----------------------------
-- 11、字典类型表
-- ----------------------------
drop table if exists sys_dict_type;
create table sys_dict_type
(
dict_id bigint(20) not null auto_increment comment '字典主键',
dict_name varchar(100) default '' comment '字典名称',
dict_type varchar(100) default '' comment '字典类型',
status char(1) default '0' comment '状态0正常 1停用',
create_by varchar(64) default '' comment '创建者',
create_time datetime comment '创建时间',
update_by varchar(64) default '' comment '更新者',
update_time datetime comment '更新时间',
remark varchar(500) default null comment '备注',
primary key (dict_id),
unique (dict_type)
) engine=innodb auto_increment=100 comment = '字典类型表';
insert into sys_dict_type values(1, '用户性别', 'sys_user_sex', '0', 'admin', sysdate(), '', null, '用户性别列表');
insert into sys_dict_type values(2, '菜单状态', 'sys_show_hide', '0', 'admin', sysdate(), '', null, '菜单状态列表');
insert into sys_dict_type values(3, '系统开关', 'sys_normal_disable', '0', 'admin', sysdate(), '', null, '系统开关列表');
insert into sys_dict_type values(4, '任务状态', 'sys_job_status', '0', 'admin', sysdate(), '', null, '任务状态列表');
insert into sys_dict_type values(5, '任务分组', 'sys_job_group', '0', 'admin', sysdate(), '', null, '任务分组列表');
insert into sys_dict_type values(6, '系统是否', 'sys_yes_no', '0', 'admin', sysdate(), '', null, '系统是否列表');
insert into sys_dict_type values(7, '通知类型', 'sys_notice_type', '0', 'admin', sysdate(), '', null, '通知类型列表');
insert into sys_dict_type values(8, '通知状态', 'sys_notice_status', '0', 'admin', sysdate(), '', null, '通知状态列表');
insert into sys_dict_type values(9, '操作类型', 'sys_oper_type', '0', 'admin', sysdate(), '', null, '操作类型列表');
insert into sys_dict_type values(10, '系统状态', 'sys_common_status', '0', 'admin', sysdate(), '', null, '登录状态列表');
-- ----------------------------
-- 12、字典数据表
-- ----------------------------
drop table if exists sys_dict_data;
create table sys_dict_data
(
dict_code bigint(20) not null auto_increment comment '字典编码',
dict_sort int(4) default 0 comment '字典排序',
dict_label varchar(100) default '' comment '字典标签',
dict_value varchar(100) default '' comment '字典键值',
dict_type varchar(100) default '' comment '字典类型',
css_class varchar(100) default null comment '样式属性(其他样式扩展)',
list_class varchar(100) default null comment '表格回显样式',
is_default char(1) default 'N' comment '是否默认Y是 N否',
status char(1) default '0' comment '状态0正常 1停用',
create_by varchar(64) default '' comment '创建者',
create_time datetime comment '创建时间',
update_by varchar(64) default '' comment '更新者',
update_time datetime comment '更新时间',
remark varchar(500) default null comment '备注',
primary key (dict_code)
) engine=innodb auto_increment=100 comment = '字典数据表';
insert into sys_dict_data values(1, 1, '', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', sysdate(), '', null, '性别男');
insert into sys_dict_data values(2, 2, '', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别女');
insert into sys_dict_data values(3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别未知');
insert into sys_dict_data values(4, 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '显示菜单');
insert into sys_dict_data values(5, 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '隐藏菜单');
insert into sys_dict_data values(6, 1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态');
insert into sys_dict_data values(7, 2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态');
insert into sys_dict_data values(8, 1, '正常', '0', 'sys_job_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态');
insert into sys_dict_data values(9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态');
insert into sys_dict_data values(10, 1, '默认', 'DEFAULT', 'sys_job_group', '', '', 'Y', '0', 'admin', sysdate(), '', null, '默认分组');
insert into sys_dict_data values(11, 2, '系统', 'SYSTEM', 'sys_job_group', '', '', 'N', '0', 'admin', sysdate(), '', null, '系统分组');
insert into sys_dict_data values(12, 1, '', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '系统默认是');
insert into sys_dict_data values(13, 2, '', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '系统默认否');
insert into sys_dict_data values(14, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 'admin', sysdate(), '', null, '通知');
insert into sys_dict_data values(15, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '公告');
insert into sys_dict_data values(16, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态');
insert into sys_dict_data values(17, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '关闭状态');
insert into sys_dict_data values(18, 99, '其他', '0', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '其他操作');
insert into sys_dict_data values(19, 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '新增操作');
insert into sys_dict_data values(20, 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '修改操作');
insert into sys_dict_data values(21, 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '删除操作');
insert into sys_dict_data values(22, 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '授权操作');
insert into sys_dict_data values(23, 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导出操作');
insert into sys_dict_data values(24, 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导入操作');
insert into sys_dict_data values(25, 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '强退操作');
insert into sys_dict_data values(26, 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '生成操作');
insert into sys_dict_data values(27, 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '清空操作');
insert into sys_dict_data values(28, 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '正常状态');
insert into sys_dict_data values(29, 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态');
-- ----------------------------
-- 13、参数配置表
-- ----------------------------
drop table if exists sys_config;
create table sys_config (
config_id int(5) not null auto_increment comment '参数主键',
config_name varchar(100) default '' comment '参数名称',
config_key varchar(100) default '' comment '参数键名',
config_value varchar(500) default '' comment '参数键值',
config_type char(1) default 'N' comment '系统内置Y是 N否',
create_by varchar(64) default '' comment '创建者',
create_time datetime comment '创建时间',
update_by varchar(64) default '' comment '更新者',
update_time datetime comment '更新时间',
remark varchar(500) default null comment '备注',
primary key (config_id)
) engine=innodb auto_increment=100 comment = '参数配置表';
insert into sys_config values(1, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'admin', sysdate(), '', null, '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow' );
insert into sys_config values(2, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', sysdate(), '', null, '初始化密码 123456' );
insert into sys_config values(3, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 'admin', sysdate(), '', null, '深色主题theme-dark浅色主题theme-light' );
insert into sys_config values(4, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', sysdate(), '', null, '是否开启验证码功能true开启false关闭');
insert into sys_config values(5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'admin', sysdate(), '', null, '是否开启注册用户功能true开启false关闭');
insert into sys_config values(6, '用户登录-黑名单列表', 'sys.login.blackIPList', '', 'Y', 'admin', sysdate(), '', null, '设置登录IP黑名单限制多个匹配项以;分隔,支持匹配(*通配、网段)');
insert into sys_config values(7, '用户管理-初始密码修改策略', 'sys.account.initPasswordModify', '1', 'Y', 'admin', sysdate(), '', null, '0初始密码修改策略关闭没有任何提示1提醒用户如果未修改初始密码则在登录时就会提醒修改密码对话框');
insert into sys_config values(8, '用户管理-账号密码更新周期', 'sys.account.passwordValidateDays', '0', 'Y', 'admin', sysdate(), '', null, '密码更新周期填写数字数据初始化值为0不限制若修改必须为大于0小于365的正整数如果超过这个周期登录系统时则在登录时就会提醒修改密码对话框');
-- ----------------------------
-- 14、系统访问记录
-- ----------------------------
drop table if exists sys_logininfor;
create table sys_logininfor (
info_id bigint(20) not null auto_increment comment '访问ID',
user_name varchar(50) default '' comment '用户账号',
ipaddr varchar(128) default '' comment '登录IP地址',
login_location varchar(255) default '' comment '登录地点',
browser varchar(50) default '' comment '浏览器类型',
os varchar(50) default '' comment '操作系统',
status char(1) default '0' comment '登录状态0成功 1失败',
msg varchar(255) default '' comment '提示消息',
login_time datetime comment '访问时间',
primary key (info_id),
key idx_sys_logininfor_s (status),
key idx_sys_logininfor_lt (login_time)
) engine=innodb auto_increment=100 comment = '系统访问记录';
-- ----------------------------
-- 15、定时任务调度表
-- ----------------------------
drop table if exists sys_job;
create table sys_job (
job_id bigint(20) not null auto_increment comment '任务ID',
job_name varchar(64) default '' comment '任务名称',
job_group varchar(64) default 'DEFAULT' comment '任务组名',
invoke_target varchar(500) not null comment '调用目标字符串',
cron_expression varchar(255) default '' comment 'cron执行表达式',
misfire_policy varchar(20) default '3' comment '计划执行错误策略1立即执行 2执行一次 3放弃执行',
concurrent char(1) default '1' comment '是否并发执行0允许 1禁止',
status char(1) default '0' comment '状态0正常 1暂停',
create_by varchar(64) default '' comment '创建者',
create_time datetime comment '创建时间',
update_by varchar(64) default '' comment '更新者',
update_time datetime comment '更新时间',
remark varchar(500) default '' comment '备注信息',
primary key (job_id, job_name, job_group)
) engine=innodb auto_increment=100 comment = '定时任务调度表';
insert into sys_job values(1, '系统默认(无参)', 'DEFAULT', 'ryTask.ryNoParams', '0/10 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, '');
insert into sys_job values(2, '系统默认(有参)', 'DEFAULT', 'ryTask.ryParams(\'ry\')', '0/15 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, '');
insert into sys_job values(3, '系统默认(多参)', 'DEFAULT', 'ryTask.ryMultipleParams(\'ry\', true, 2000L, 316.50D, 100)', '0/20 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, '');
-- ----------------------------
-- 16、定时任务调度日志表
-- ----------------------------
drop table if exists sys_job_log;
create table sys_job_log (
job_log_id bigint(20) not null auto_increment comment '任务日志ID',
job_name varchar(64) not null comment '任务名称',
job_group varchar(64) not null comment '任务组名',
invoke_target varchar(500) not null comment '调用目标字符串',
job_message varchar(500) comment '日志信息',
status char(1) default '0' comment '执行状态0正常 1失败',
exception_info varchar(2000) default '' comment '异常信息',
create_time datetime comment '创建时间',
primary key (job_log_id)
) engine=innodb comment = '定时任务调度日志表';
-- ----------------------------
-- 17、通知公告表
-- ----------------------------
drop table if exists sys_notice;
create table sys_notice (
notice_id int(4) not null auto_increment comment '公告ID',
notice_title varchar(50) not null comment '公告标题',
notice_type char(1) not null comment '公告类型1通知 2公告',
notice_content longblob default null comment '公告内容',
status char(1) default '0' comment '公告状态0正常 1关闭',
create_by varchar(64) default '' comment '创建者',
create_time datetime comment '创建时间',
update_by varchar(64) default '' comment '更新者',
update_time datetime comment '更新时间',
remark varchar(255) default null comment '备注',
primary key (notice_id)
) engine=innodb auto_increment=10 comment = '通知公告表';
-- ----------------------------
-- 初始化-公告信息表数据
-- ----------------------------
insert into sys_notice values('1', '温馨提醒2018-07-01 若依新版本发布啦', '2', '新版本内容', '0', 'admin', sysdate(), '', null, '管理员');
insert into sys_notice values('2', '维护通知2018-07-01 若依系统凌晨维护', '1', '维护内容', '0', 'admin', sysdate(), '', null, '管理员');
-- ----------------------------
-- 18、代码生成业务表
-- ----------------------------
drop table if exists gen_table;
create table gen_table (
table_id bigint(20) not null auto_increment comment '编号',
table_name varchar(200) default '' comment '表名称',
table_comment varchar(500) default '' comment '表描述',
sub_table_name varchar(64) default null comment '关联子表的表名',
sub_table_fk_name varchar(64) default null comment '子表关联的外键名',
class_name varchar(100) default '' comment '实体类名称',
tpl_category varchar(200) default 'crud' comment '使用的模板crud单表操作 tree树表操作',
tpl_web_type varchar(30) default '' comment '前端模板类型element-ui模版 element-plus模版',
package_name varchar(100) comment '生成包路径',
module_name varchar(30) comment '生成模块名',
business_name varchar(30) comment '生成业务名',
function_name varchar(50) comment '生成功能名',
function_author varchar(50) comment '生成功能作者',
gen_type char(1) default '0' comment '生成代码方式0zip压缩包 1自定义路径',
gen_path varchar(200) default '/' comment '生成路径(不填默认项目路径)',
options varchar(1000) comment '其它生成选项',
create_by varchar(64) default '' comment '创建者',
create_time datetime comment '创建时间',
update_by varchar(64) default '' comment '更新者',
update_time datetime comment '更新时间',
remark varchar(500) default null comment '备注',
primary key (table_id)
) engine=innodb auto_increment=1 comment = '代码生成业务表';
-- ----------------------------
-- 19、代码生成业务表字段
-- ----------------------------
drop table if exists gen_table_column;
create table gen_table_column (
column_id bigint(20) not null auto_increment comment '编号',
table_id bigint(20) comment '归属表编号',
column_name varchar(200) comment '列名称',
column_comment varchar(500) comment '列描述',
column_type varchar(100) comment '列类型',
java_type varchar(500) comment 'JAVA类型',
java_field varchar(200) comment 'JAVA字段名',
is_pk char(1) comment '是否主键1是',
is_increment char(1) comment '是否自增1是',
is_required char(1) comment '是否必填1是',
is_insert char(1) comment '是否为插入字段1是',
is_edit char(1) comment '是否编辑字段1是',
is_list char(1) comment '是否列表字段1是',
is_query char(1) comment '是否查询字段1是',
query_type varchar(200) default 'EQ' comment '查询方式(等于、不等于、大于、小于、范围)',
html_type varchar(200) comment '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)',
dict_type varchar(200) default '' comment '字典类型',
sort int comment '排序',
create_by varchar(64) default '' comment '创建者',
create_time datetime comment '创建时间',
update_by varchar(64) default '' comment '更新者',
update_time datetime comment '更新时间',
primary key (column_id)
) engine=innodb auto_increment=1 comment = '代码生成业务表字段';
-- ----------------------------
-- 贷款定价菜单初始化
-- ----------------------------
DELETE FROM sys_role_menu WHERE menu_id IN (2000, 2001, 2002);
DELETE FROM sys_menu WHERE menu_id IN (2000, 2001, 2002);
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
VALUES(2000, '利率定价管理', 0, 5, 'loanPricing', NULL, 1, 0, 'M', '0', '0', '', 'money', 'admin', NOW(), '');
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
VALUES(2001, '流程列表', 2000, 1, 'workflow', 'loanPricing/workflow/index', 1, 0, 'C', '0', '0', 'loanPricing:workflow:list', 'list', 'admin', NOW(), '');
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
VALUES(2002, '流程查询', 2001, 1, '', '', 1, 0, 'F', '0', '0', 'loanPricing:workflow:query', '#', 'admin', NOW(), '');
INSERT INTO sys_role_menu VALUES(1, 2000), (1, 2001), (1, 2002);
-- Table structure for table `loan_pricing_workflow`
--
DROP TABLE IF EXISTS `loan_pricing_workflow`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `loan_pricing_workflow` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`serial_num` varchar(50) NOT NULL COMMENT '业务方流水号',
`model_output_id` bigint(20) DEFAULT NULL COMMENT '模型输出ID',
`org_code` varchar(20) NOT NULL DEFAULT '892000' COMMENT '机构编码(统一值892000)',
`run_type` varchar(10) NOT NULL DEFAULT '1' COMMENT '运行模式: 1-同步',
`cust_isn` varchar(50) NOT NULL COMMENT '客户内码',
`cust_type` varchar(20) NOT NULL COMMENT '客户类型: 个人/企业',
`guar_type` varchar(20) NOT NULL COMMENT '担保方式: 信用/保证/抵押/质押',
`mid_per_quick_pay` varchar(10) DEFAULT NULL COMMENT '中间业务_个人_快捷支付: true/false',
`mid_per_ele_ddc` varchar(10) DEFAULT NULL COMMENT '中间业务_个人_电费代扣: true/false',
`mid_ent_ele_ddc` varchar(10) DEFAULT NULL COMMENT '中间业务_企业_电费代扣: true/false',
`mid_ent_water_ddc` varchar(10) DEFAULT NULL COMMENT '中间业务_企业_水费代扣: true/false',
`apply_amt` varchar(50) NOT NULL COMMENT '申请金额(元)',
`loan_term` varchar(50) DEFAULT NULL COMMENT '贷款期限',
`is_clean_ent` varchar(10) DEFAULT NULL COMMENT '净身企业: true/false',
`has_settle_acct` varchar(10) DEFAULT NULL COMMENT '开立基本结算账户: true/false',
`is_manufacturing` varchar(10) DEFAULT NULL COMMENT '制造业企业: true/false',
`is_agri_guar` varchar(10) DEFAULT NULL COMMENT '省农担担保贷款: true/false',
`is_tech_ent` varchar(10) DEFAULT NULL COMMENT '科技型企业: true/false科技型企业最多下降5BP',
`is_green_loan` varchar(10) DEFAULT NULL COMMENT '绿色贷款: true/false绿色贷款最多下降5BP',
`is_trade_construction` varchar(10) DEFAULT NULL COMMENT '贸易和建筑业企业标识: true/false抵质押类上调20BP',
`is_tax_a` varchar(10) DEFAULT NULL COMMENT '是否纳税信用等级A级: true/false',
`is_agri_leading` varchar(10) DEFAULT NULL COMMENT '是否县级及以上农业龙头企业: true/false',
`loan_purpose` varchar(20) DEFAULT NULL COMMENT '贷款用途: consumer-消费/business-经营',
`biz_proof` varchar(10) DEFAULT NULL COMMENT '是否有经营佐证: true/false',
`loan_loop` varchar(10) DEFAULT NULL COMMENT '循环功能: true/false贷款合同是否开通循环功能',
`coll_type` varchar(20) DEFAULT NULL COMMENT '抵质押类型: 一线/一类/二类',
`coll_third_party` varchar(10) DEFAULT NULL COMMENT '抵质押物是否三方所有: true/false',
`loan_rate` varchar(20) DEFAULT NULL COMMENT '贷款利率',
`execute_rate` varchar(20) DEFAULT NULL COMMENT '执行利率(%)',
`cust_name` varchar(100) DEFAULT NULL COMMENT '客户名称',
`id_type` varchar(50) DEFAULT NULL COMMENT '证件类型',
`id_num` varchar(100) DEFAULT NULL COMMENT '证件号码',
`is_inclusive_finance` varchar(10) DEFAULT NULL COMMENT '是否普惠小微借款人: true/false',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_serial_num` (`serial_num`),
KEY `idx_org_code` (`org_code`),
KEY `idx_create_by` (`create_by`),
KEY `idx_cust_name` (`cust_name`),
KEY `idx_update_time` (`update_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='利率定价流程表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `model_corp_output_fields`
--
DROP TABLE IF EXISTS `model_corp_output_fields`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `model_corp_output_fields` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键ID',
`cust_isn` varchar(100) DEFAULT NULL COMMENT '客户内码',
`cust_type` varchar(100) DEFAULT NULL COMMENT '客户类型',
`guar_type` varchar(100) DEFAULT NULL COMMENT '担保方式',
`cust_name` varchar(100) DEFAULT NULL COMMENT '客户名称',
`id_type` varchar(100) DEFAULT NULL COMMENT '证件类型',
`id_num` varchar(100) DEFAULT NULL COMMENT '证件号码',
`base_loan_rate` varchar(100) DEFAULT NULL COMMENT '基准利率',
`is_first_loan` varchar(100) DEFAULT NULL COMMENT '我行首贷客户',
`faith_day` varchar(100) DEFAULT NULL COMMENT '用信天数',
`bp_first_loan` varchar(100) DEFAULT NULL COMMENT 'BP_首贷',
`bp_age_loan` varchar(100) DEFAULT NULL COMMENT 'BP_贷龄',
`total_bp_loyalty` varchar(100) DEFAULT NULL COMMENT 'TOTAL_BP_忠诚度',
`balance_avg` varchar(100) DEFAULT NULL COMMENT '存款年日均',
`loan_avg` varchar(100) DEFAULT NULL COMMENT '贷款年日均',
`derivation_rate` varchar(100) DEFAULT NULL COMMENT '派生率',
`total_bp_contribution` varchar(100) DEFAULT NULL COMMENT 'TOTAL_BP_贡献度',
`mid_ent_connect` varchar(100) DEFAULT NULL COMMENT '中间业务_企业_企业互联',
`mid_ent_effect` varchar(100) DEFAULT NULL COMMENT '中间业务_企业_有效价值客户',
`mid_ent_inter` varchar(100) DEFAULT NULL COMMENT '中间业务_企业_国际业务',
`mid_ent_accept` varchar(100) DEFAULT NULL COMMENT '中间业务_企业_承兑',
`mid_ent_discount` varchar(100) DEFAULT NULL COMMENT '中间业务_企业_贴现',
`mid_ent_ele_ddc` varchar(100) DEFAULT NULL COMMENT '中间业务_企业_电费代扣',
`mid_ent_water_ddc` varchar(100) DEFAULT NULL COMMENT '中间业务_企业_水费代扣',
`mid_ent_tax` varchar(100) DEFAULT NULL COMMENT '中间业务_企业_税务代扣',
`bp_mid` varchar(100) DEFAULT NULL COMMENT 'BP_中间业务',
`payroll` varchar(100) DEFAULT NULL COMMENT '代发工资户数',
`inv_loan_amount` varchar(100) DEFAULT NULL COMMENT '存量贷款余额',
`bp_payroll` varchar(100) DEFAULT NULL COMMENT 'BP_代发工资',
`is_clean_ent` varchar(100) DEFAULT NULL COMMENT '净身企业',
`has_settle_acct` varchar(100) DEFAULT NULL COMMENT '开立基本结算账户',
`is_agri_guar` varchar(100) DEFAULT NULL COMMENT '省农担担保贷款',
`is_green_loan` varchar(100) DEFAULT NULL COMMENT '绿色贷款',
`is_tech_ent` varchar(100) DEFAULT NULL COMMENT '科技型企业',
`bp_ent_type` varchar(100) DEFAULT NULL COMMENT 'BP_企业客户类别',
`totoal_bp_relevance` varchar(100) DEFAULT NULL COMMENT 'TOTAL_BP_关联度',
`loan_term` varchar(100) DEFAULT NULL COMMENT '贷款期限',
`bp_loan_term` varchar(100) DEFAULT NULL COMMENT 'BP_贷款期限',
`apply_amt` varchar(100) DEFAULT NULL COMMENT '申请金额',
`bp_loan_amount` varchar(100) DEFAULT NULL COMMENT 'BP_贷款额度',
`coll_type` varchar(100) DEFAULT NULL COMMENT '抵质押类型',
`coll_third_party` varchar(100) DEFAULT NULL COMMENT '抵质押物是否三方所有',
`bp_collateral` varchar(100) DEFAULT NULL COMMENT 'BP_抵押物',
`grey_cust` varchar(100) DEFAULT NULL COMMENT '灰名单客户',
`prin_overdue` varchar(100) DEFAULT NULL COMMENT '本金逾期',
`interest_overdue` varchar(100) DEFAULT NULL COMMENT '利息逾期',
`card_overdue` varchar(100) DEFAULT NULL COMMENT '信用卡逾期',
`bp_grey_overdue` varchar(100) DEFAULT NULL COMMENT 'BP_灰名单与逾期',
`totoal_bp_risk` varchar(100) DEFAULT NULL COMMENT 'TOTAL_BP_风险度',
`total_bp` varchar(100) DEFAULT NULL COMMENT '浮动BP',
`calculate_rate` varchar(100) DEFAULT NULL COMMENT '测算利率',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='客户贷款利率测算表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `model_retail_output_fields`
--
DROP TABLE IF EXISTS `model_retail_output_fields`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `model_retail_output_fields` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`cust_isn` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT '客户内码',
`cust_type` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '客户类型',
`guar_type` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '担保方式',
`cust_name` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '客户名称',
`id_type` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '证件类型',
`id_num` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '证件号码',
`base_loan_rate` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '基准利率',
`is_first_loan` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '我行首贷客户',
`faith_day` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用信天数',
`cust_age` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '客户年龄',
`bp_first_loan` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_首贷',
`bp_age_loan` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_贷龄',
`bp_age` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_年龄',
`total_bp_loyalty` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'TOTAL_BP_忠诚度',
`balance_avg` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '存款年日均',
`loan_avg` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '贷款年日均',
`derivation_rate` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '派生率',
`total_bp_contribution` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'TOTAL_BP_贡献度',
`mid_per_card` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_信用卡',
`mid_per_pass` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_一码通',
`mid_per_harvest` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_丰收互联',
`mid_per_effect` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_有效客户',
`mid_per_quick_pay` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_快捷支付',
`mid_per_ele_ddc` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_电费代扣',
`mid_per_water_ddc` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_水费代扣',
`mid_per_huashu_ddc` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_华数费代扣',
`mid_per_gas_ddc` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_煤气费代扣',
`mid_per_citizencard` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_市民卡',
`mid_per_fin_man` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_理财业务',
`mid_per_etc` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_etc',
`bp_mid` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_中间业务',
`totoal_bp_relevance` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'TOTAL_BP_关联度',
`apply_amt` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '申请金额',
`bp_loan_amount` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_贷款额度',
`loan_purpose` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '贷款用途',
`biz_proof` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '是否有经营佐证',
`bp_loan_use` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_贷款用途',
`loan_loop` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '循环功能',
`bp_loan_loop` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_循环功能',
`coll_type` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '抵质押类型',
`coll_third_party` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '抵质押物是否三方所有',
`bp_collateral` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_抵押物',
`grey_cust` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '灰名单客户',
`prin_overdue` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '本金逾期',
`interest_overdue` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '利息逾期',
`card_overdue` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '信用卡逾期',
`bp_grey_overdue` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_灰名单与逾期',
`totoal_bp_risk` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'TOTAL_BP_风险度',
`total_bp` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '浮动BP',
`calculate_rate` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '测算利率',
`loan_rate_history` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '历史利率',
`min_rate_product` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '产品最低利率下限',
`smooth_range` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '平滑幅度',
`final_calculate_rate` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '最终测算利率',
`reference_rate` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '参考利率',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='零售模型输出字段表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
SET FOREIGN_KEY_CHECKS=1;

View File

@@ -0,0 +1,373 @@
-- 说明:
-- 1. 本文件从源库 116.62.17.81:3306/loan-pricing 以 utf8mb4 正确导出
-- 2. 本文件仅包含迁移后系统可运行所需的基础配置数据和当前业务关键数据
-- 3. 已包含的表: loan_pricing_workflow, model_corp_output_fields, model_retail_output_fields,
-- sys_config, sys_dept, sys_dict_data, sys_dict_type, sys_job, sys_menu, sys_notice,
-- sys_post, sys_role, sys_role_dept, sys_role_menu, sys_user, sys_user_post, sys_user_role
-- 4. 未包含日志和运行态数据: sys_job_log, sys_logininfor, sys_oper_log, QRTZ_* 运行态表
-- 5. 建议先执行表结构 SQL, 再执行本文件
SET NAMES utf8mb4;
USE `loan-pricing`;
SET FOREIGN_KEY_CHECKS=0;
DELETE FROM `loan_pricing_workflow`;
INSERT INTO `loan_pricing_workflow` (`id`, `serial_num`, `model_output_id`, `org_code`, `run_type`, `cust_isn`, `cust_type`, `guar_type`, `mid_per_quick_pay`, `mid_per_ele_ddc`, `mid_ent_ele_ddc`, `mid_ent_water_ddc`, `apply_amt`, `loan_term`, `is_clean_ent`, `has_settle_acct`, `is_manufacturing`, `is_agri_guar`, `is_tech_ent`, `is_green_loan`, `is_trade_construction`, `is_tax_a`, `is_agri_leading`, `loan_purpose`, `biz_proof`, `loan_loop`, `coll_type`, `coll_third_party`, `loan_rate`, `execute_rate`, `cust_name`, `id_type`, `id_num`, `is_inclusive_finance`, `create_by`, `create_time`, `update_by`, `update_time`) VALUES
(1, '20260130160640382', 1, '892000', '1', '1234', '个人', '信用', 'false', 'false', 'false', 'false', '10000000', NULL, 'false', 'false', 'false', 'false', NULL, NULL, NULL, 'false', 'false', NULL, 'false', NULL, NULL, 'false', '11', NULL, NULL, '身份证', NULL, 'false', '若依-admin', '2026-01-30 16:06:40', '若依-admin', '2026-01-30 16:06:41'),
(2, '20260130163824202', 1, '892000', '1', '82821892198', '企业', '保证', 'false', 'false', 'false', 'false', '100000', NULL, 'false', 'false', 'false', 'false', NULL, NULL, NULL, 'false', 'false', 'consumer', 'false', NULL, NULL, 'false', '10', '4.5', '吴总', '统一社会信用代码', NULL, 'false', '若依-admin', '2026-01-30 16:38:24', '若依-admin', '2026-01-30 16:39:10'),
(3, '20260202140048990', 2, '892000', '1', 'TEST001', '个人', '信用', NULL, NULL, NULL, NULL, '500000', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'true', 'false', NULL, NULL, NULL, NULL, '张三', '身份证', '110101199001011234', NULL, '若依-admin', '2026-02-02 14:00:49', '若依-admin', '2026-02-02 14:00:50'),
(4, '20260202140101592', 3, '892000', '1', 'TEST002', '个人', '质押', NULL, NULL, NULL, NULL, '100000', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '若依-admin', '2026-02-02 14:01:02', '若依-admin', '2026-02-02 14:01:02'),
(5, '20260202140102337', 4, '892000', '1', 'TEST003', '个人', '抵押', NULL, NULL, NULL, NULL, '800000', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'true', 'true', '一类', 'false', NULL, NULL, '孙七', '身份证', '110101199001011239', NULL, '若依-admin', '2026-02-02 14:01:02', '若依-admin', '2026-02-02 14:01:03'),
(6, '20260202140119035', 2, '892000', '1', 'CORP001', '企业', '抵押', NULL, NULL, NULL, NULL, '1000000', '36', NULL, NULL, NULL, 'false', 'true', 'true', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '测试科技有限公司', '统一社会信用代码', '91110000100000000X', NULL, '若依-admin', '2026-02-02 14:01:19', '若依-admin', '2026-02-02 14:01:19'),
(7, '20260202140128799', 3, '892000', '1', 'CORP007', '企业', '信用', NULL, NULL, NULL, NULL, '3000000', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '若依-admin', '2026-02-02 14:01:29', '若依-admin', '2026-02-02 14:01:29'),
(8, '20260202140129480', 4, '892000', '1', 'CORP005', '企业', '保证', NULL, NULL, NULL, NULL, '2000000', '60', NULL, NULL, NULL, 'true', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '农业科技有限公司', '统一社会信用代码', '91110000100000005X', NULL, '若依-admin', '2026-02-02 14:01:29', '若依-admin', '2026-02-02 14:01:30'),
(9, '20260202140138984', 5, '892000', '1', 'OLD001', '个人', '信用', NULL, NULL, NULL, NULL, '500000', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'true', 'false', NULL, NULL, '4.5', NULL, '测试用户1', '身份证', NULL, NULL, '若依-admin', '2026-02-02 14:01:39', '若依-admin', '2026-02-02 14:01:39'),
(10, '20260202140145591', 5, '892000', '1', 'OLD002', '企业', '抵押', NULL, NULL, NULL, NULL, '1000000', '36', NULL, NULL, NULL, 'false', 'true', 'true', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '4.0', NULL, '测试企业1', '统一社会信用代码', NULL, NULL, '若依-admin', '2026-02-02 14:01:46', '若依-admin', '2026-02-02 14:01:46'),
(11, '20260202144250835', 6, '892000', '1', '1234567', '个人', '信用', NULL, NULL, NULL, NULL, '100000', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'true', 'true', '一线', 'true', NULL, NULL, '个人测试', '身份证', '330103199912311231', NULL, '若依-admin', '2026-02-02 14:42:51', '若依-admin', '2026-02-02 14:42:51'),
(12, '20260202151445788', 6, '892000', '1', 'test1234', '企业', '信用', NULL, NULL, NULL, NULL, '200000', '23', NULL, NULL, NULL, 'true', 'true', 'true', 'true', NULL, NULL, NULL, NULL, NULL, '一线', 'true', NULL, NULL, 'test1234', '统一社会信用代码', '91110000100000000X', NULL, '若依-admin', '2026-02-02 15:14:46', '若依-admin', '2026-02-02 15:14:46');
DELETE FROM `model_corp_output_fields`;
INSERT INTO `model_corp_output_fields` (`id`, `cust_isn`, `cust_type`, `guar_type`, `cust_name`, `id_type`, `id_num`, `base_loan_rate`, `is_first_loan`, `faith_day`, `bp_first_loan`, `bp_age_loan`, `total_bp_loyalty`, `balance_avg`, `loan_avg`, `derivation_rate`, `total_bp_contribution`, `mid_ent_connect`, `mid_ent_effect`, `mid_ent_inter`, `mid_ent_accept`, `mid_ent_discount`, `mid_ent_ele_ddc`, `mid_ent_water_ddc`, `mid_ent_tax`, `bp_mid`, `payroll`, `inv_loan_amount`, `bp_payroll`, `is_clean_ent`, `has_settle_acct`, `is_agri_guar`, `is_green_loan`, `is_tech_ent`, `bp_ent_type`, `totoal_bp_relevance`, `loan_term`, `bp_loan_term`, `apply_amt`, `bp_loan_amount`, `coll_type`, `coll_third_party`, `bp_collateral`, `grey_cust`, `prin_overdue`, `interest_overdue`, `card_overdue`, `bp_grey_overdue`, `totoal_bp_risk`, `total_bp`, `calculate_rate`, `create_time`) VALUES
(1, 'CUST20260121001', '企业客户', '抵押担保', '北京智联科技有限公司', '营业执照', '91110108MA00XXXXXX', '3.45', 'N', '730', '0', '5.2', '8.5', '5000000.00', '3000000.00', '1.8', '12.3', '100000.00', '50000.00', '80000.00', '200000.00', '150000.00', '30000.00', '10000.00', '40000.00', '6.8', '200', '2500000.00', '4.1', 'Y', 'Y', 'N', 'Y', 'Y', '7.5', '9.2', '36', '3.3', '5000000.00', '5.8', '房产抵押', 'N', '4.5', 'N', 'N', 'N', 'N', '0', '1.2', '48.2', '3.932', '2026-01-30 16:38:24'),
(2, 'CUST20260121001', '企业客户', '抵押担保', '北京智联科技有限公司', '营业执照', '91110108MA00XXXXXX', '3.45', 'N', '730', '0', '5.2', '8.5', '5000000.00', '3000000.00', '1.8', '12.3', '100000.00', '50000.00', '80000.00', '200000.00', '150000.00', '30000.00', '10000.00', '40000.00', '6.8', '200', '2500000.00', '4.1', 'Y', 'Y', 'N', 'Y', 'Y', '7.5', '9.2', '36', '3.3', '5000000.00', '5.8', '房产抵押', 'N', '4.5', 'N', 'N', 'N', 'N', '0', '1.2', '48.2', '3.932', '2026-02-02 14:01:19'),
(3, 'CUST20260121001', '企业客户', '抵押担保', '北京智联科技有限公司', '营业执照', '91110108MA00XXXXXX', '3.45', 'N', '730', '0', '5.2', '8.5', '5000000.00', '3000000.00', '1.8', '12.3', '100000.00', '50000.00', '80000.00', '200000.00', '150000.00', '30000.00', '10000.00', '40000.00', '6.8', '200', '2500000.00', '4.1', 'Y', 'Y', 'N', 'Y', 'Y', '7.5', '9.2', '36', '3.3', '5000000.00', '5.8', '房产抵押', 'N', '4.5', 'N', 'N', 'N', 'N', '0', '1.2', '48.2', '3.932', '2026-02-02 14:01:29'),
(4, 'CUST20260121001', '企业客户', '抵押担保', '北京智联科技有限公司', '营业执照', '91110108MA00XXXXXX', '3.45', 'N', '730', '0', '5.2', '8.5', '5000000.00', '3000000.00', '1.8', '12.3', '100000.00', '50000.00', '80000.00', '200000.00', '150000.00', '30000.00', '10000.00', '40000.00', '6.8', '200', '2500000.00', '4.1', 'Y', 'Y', 'N', 'Y', 'Y', '7.5', '9.2', '36', '3.3', '5000000.00', '5.8', '房产抵押', 'N', '4.5', 'N', 'N', 'N', 'N', '0', '1.2', '48.2', '3.932', '2026-02-02 14:01:30'),
(5, 'CUST20260121001', '企业客户', '抵押担保', '北京智联科技有限公司', '营业执照', '91110108MA00XXXXXX', '3.45', 'N', '730', '0', '5.2', '8.5', '5000000.00', '3000000.00', '1.8', '12.3', '100000.00', '50000.00', '80000.00', '200000.00', '150000.00', '30000.00', '10000.00', '40000.00', '6.8', '200', '2500000.00', '4.1', 'Y', 'Y', 'N', 'Y', 'Y', '7.5', '9.2', '36', '3.3', '5000000.00', '5.8', '房产抵押', 'N', '4.5', 'N', 'N', 'N', 'N', '0', '1.2', '48.2', '3.932', '2026-02-02 14:01:46'),
(6, 'CUST20260121001', '企业客户', '抵押担保', '北京智联科技有限公司', '营业执照', '91110108MA00XXXXXX', '3.45', 'N', '730', '0', '5.2', '8.5', '5000000.00', '3000000.00', '1.8', '12.3', '100000.00', '50000.00', '80000.00', '200000.00', '150000.00', '30000.00', '10000.00', '40000.00', '6.8', '200', '2500000.00', '4.1', 'Y', 'Y', 'N', 'Y', 'Y', '7.5', '9.2', '36', '3.3', '5000000.00', '5.8', '房产抵押', 'N', '4.5', 'N', 'N', 'N', 'N', '0', '1.2', '48.2', '3.932', '2026-02-02 15:14:46');
DELETE FROM `model_retail_output_fields`;
INSERT INTO `model_retail_output_fields` (`id`, `cust_isn`, `cust_type`, `guar_type`, `cust_name`, `id_type`, `id_num`, `base_loan_rate`, `is_first_loan`, `faith_day`, `cust_age`, `bp_first_loan`, `bp_age_loan`, `bp_age`, `total_bp_loyalty`, `balance_avg`, `loan_avg`, `derivation_rate`, `total_bp_contribution`, `mid_per_card`, `mid_per_pass`, `mid_per_harvest`, `mid_per_effect`, `mid_per_quick_pay`, `mid_per_ele_ddc`, `mid_per_water_ddc`, `mid_per_huashu_ddc`, `mid_per_gas_ddc`, `mid_per_citizencard`, `mid_per_fin_man`, `mid_per_etc`, `bp_mid`, `totoal_bp_relevance`, `apply_amt`, `bp_loan_amount`, `loan_purpose`, `biz_proof`, `bp_loan_use`, `loan_loop`, `bp_loan_loop`, `coll_type`, `coll_third_party`, `bp_collateral`, `grey_cust`, `prin_overdue`, `interest_overdue`, `card_overdue`, `bp_grey_overdue`, `totoal_bp_risk`, `total_bp`, `calculate_rate`, `create_time`) VALUES
(1, 'CUST20260121001', '个人', '信用担保', '张三', '身份证', '330106199001011234', '4.35', '', '365', '36', '50', '30', '20', '95', '50000.00', '100000.00', '1.2', '88', '1000.50', '500.00', '800.20', '', '300.00', '150.00', '80.00', '120.00', '90.00', '200.00', '5000.00', '180.00', '45', '90', '200000.00', '60', '个人消费', '', '55', '支持', '40', '无抵质押', '', '0', '', '', '', '', '98', '95', '350', '6.15', '2026-01-30 16:06:41'),
(2, 'CUST20260121001', '个人', '信用担保', '张三', '身份证', '330106199001011234', '4.35', '', '365', '36', '50', '30', '20', '95', '50000.00', '100000.00', '1.2', '88', '1000.50', '500.00', '800.20', '', '300.00', '150.00', '80.00', '120.00', '90.00', '200.00', '5000.00', '180.00', '45', '90', '200000.00', '60', '个人消费', '', '55', '支持', '40', '无抵质押', '', '0', '', '', '', '', '98', '95', '350', '6.15', '2026-02-02 14:00:49'),
(3, 'CUST20260121001', '个人', '信用担保', '张三', '身份证', '330106199001011234', '4.35', '', '365', '36', '50', '30', '20', '95', '50000.00', '100000.00', '1.2', '88', '1000.50', '500.00', '800.20', '', '300.00', '150.00', '80.00', '120.00', '90.00', '200.00', '5000.00', '180.00', '45', '90', '200000.00', '60', '个人消费', '', '55', '支持', '40', '无抵质押', '', '0', '', '', '', '', '98', '95', '350', '6.15', '2026-02-02 14:01:02'),
(4, 'CUST20260121001', '个人', '信用担保', '张三', '身份证', '330106199001011234', '4.35', '', '365', '36', '50', '30', '20', '95', '50000.00', '100000.00', '1.2', '88', '1000.50', '500.00', '800.20', '', '300.00', '150.00', '80.00', '120.00', '90.00', '200.00', '5000.00', '180.00', '45', '90', '200000.00', '60', '个人消费', '', '55', '支持', '40', '无抵质押', '', '0', '', '', '', '', '98', '95', '350', '6.15', '2026-02-02 14:01:03'),
(5, 'CUST20260121001', '个人', '信用担保', '张三', '身份证', '330106199001011234', '4.35', '', '365', '36', '50', '30', '20', '95', '50000.00', '100000.00', '1.2', '88', '1000.50', '500.00', '800.20', '', '300.00', '150.00', '80.00', '120.00', '90.00', '200.00', '5000.00', '180.00', '45', '90', '200000.00', '60', '个人消费', '', '55', '支持', '40', '无抵质押', '', '0', '', '', '', '', '98', '95', '350', '6.15', '2026-02-02 14:01:39'),
(6, 'CUST20260121001', '个人', '信用担保', '张三', '身份证', '330106199001011234', '4.35', '', '365', '36', '50', '30', '20', '95', '50000.00', '100000.00', '1.2', '88', '1000.50', '500.00', '800.20', '', '300.00', '150.00', '80.00', '120.00', '90.00', '200.00', '5000.00', '180.00', '45', '90', '200000.00', '60', '个人消费', '', '55', '支持', '40', '无抵质押', '', '0', '', '', '', '', '98', '95', '350', '6.15', '2026-02-02 14:42:51');
DELETE FROM `sys_config`;
INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(1, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'admin', '2026-01-30 07:57:17', '', NULL, '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow'),
(2, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', '2026-01-30 07:57:18', '', NULL, '初始化密码 123456'),
(3, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 'admin', '2026-01-30 07:57:18', '', NULL, '深色主题theme-dark浅色主题theme-light'),
(4, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', '2026-01-30 07:57:18', '', NULL, '是否开启验证码功能true开启false关闭'),
(5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'admin', '2026-01-30 07:57:19', '', NULL, '是否开启注册用户功能true开启false关闭'),
(6, '用户登录-黑名单列表', 'sys.login.blackIPList', '', 'Y', 'admin', '2026-01-30 07:57:19', '', NULL, '设置登录IP黑名单限制多个匹配项以;分隔,支持匹配(*通配、网段)'),
(7, '用户管理-初始密码修改策略', 'sys.account.initPasswordModify', '1', 'Y', 'admin', '2026-01-30 07:57:19', '', NULL, '0初始密码修改策略关闭没有任何提示1提醒用户如果未修改初始密码则在登录时就会提醒修改密码对话框'),
(8, '用户管理-账号密码更新周期', 'sys.account.passwordValidateDays', '0', 'Y', 'admin', '2026-01-30 07:57:20', '', NULL, '密码更新周期填写数字数据初始化值为0不限制若修改必须为大于0小于365的正整数如果超过这个周期登录系统时则在登录时就会提醒修改密码对话框');
DELETE FROM `sys_dept`;
INSERT INTO `sys_dept` (`dept_id`, `parent_id`, `ancestors`, `dept_name`, `order_num`, `leader`, `phone`, `email`, `status`, `del_flag`, `create_by`, `create_time`, `update_by`, `update_time`) VALUES
(100, 0, '0', '若依科技', 0, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2026-01-30 07:55:45', '', NULL),
(101, 100, '0,100', '深圳总公司', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2026-01-30 07:55:45', '', NULL),
(102, 100, '0,100', '长沙分公司', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2026-01-30 07:55:46', '', NULL),
(103, 101, '0,100,101', '研发部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2026-01-30 07:55:46', '', NULL),
(104, 101, '0,100,101', '市场部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2026-01-30 07:55:46', '', NULL),
(105, 101, '0,100,101', '测试部门', 3, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2026-01-30 07:55:47', '', NULL),
(106, 101, '0,100,101', '财务部门', 4, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2026-01-30 07:55:47', '', NULL),
(107, 101, '0,100,101', '运维部门', 5, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2026-01-30 07:55:47', '', NULL),
(108, 102, '0,100,102', '市场部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2026-01-30 07:55:48', '', NULL),
(109, 102, '0,100,102', '财务部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2026-01-30 07:55:48', '', NULL);
DELETE FROM `sys_dict_data`;
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(1, 1, '', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', '2026-01-30 07:57:06', '', NULL, '性别男'),
(2, 2, '', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', '2026-01-30 07:57:07', '', NULL, '性别女'),
(3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', '2026-01-30 07:57:07', '', NULL, '性别未知'),
(4, 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', '2026-01-30 07:57:07', '', NULL, '显示菜单'),
(5, 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', '2026-01-30 07:57:08', '', NULL, '隐藏菜单'),
(6, 1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'admin', '2026-01-30 07:57:08', '', NULL, '正常状态'),
(7, 2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'admin', '2026-01-30 07:57:08', '', NULL, '停用状态'),
(8, 1, '正常', '0', 'sys_job_status', '', 'primary', 'Y', '0', 'admin', '2026-01-30 07:57:09', '', NULL, '正常状态'),
(9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', '0', 'admin', '2026-01-30 07:57:09', '', NULL, '停用状态'),
(10, 1, '默认', 'DEFAULT', 'sys_job_group', '', '', 'Y', '0', 'admin', '2026-01-30 07:57:10', '', NULL, '默认分组'),
(11, 2, '系统', 'SYSTEM', 'sys_job_group', '', '', 'N', '0', 'admin', '2026-01-30 07:57:10', '', NULL, '系统分组'),
(12, 1, '', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', '2026-01-30 07:57:10', '', NULL, '系统默认是'),
(13, 2, '', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', '2026-01-30 07:57:11', '', NULL, '系统默认否'),
(14, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 'admin', '2026-01-30 07:57:11', '', NULL, '通知'),
(15, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 'admin', '2026-01-30 07:57:11', '', NULL, '公告'),
(16, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', '2026-01-30 07:57:12', '', NULL, '正常状态'),
(17, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', '2026-01-30 07:57:12', '', NULL, '关闭状态'),
(18, 99, '其他', '0', 'sys_oper_type', '', 'info', 'N', '0', 'admin', '2026-01-30 07:57:12', '', NULL, '其他操作'),
(19, 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 'admin', '2026-01-30 07:57:13', '', NULL, '新增操作'),
(20, 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 'admin', '2026-01-30 07:57:13', '', NULL, '修改操作'),
(21, 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', '2026-01-30 07:57:13', '', NULL, '删除操作'),
(22, 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', '2026-01-30 07:57:14', '', NULL, '授权操作'),
(23, 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', '2026-01-30 07:57:14', '', NULL, '导出操作'),
(24, 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', '2026-01-30 07:57:14', '', NULL, '导入操作'),
(25, 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', '2026-01-30 07:57:15', '', NULL, '强退操作'),
(26, 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', '2026-01-30 07:57:15', '', NULL, '生成操作'),
(27, 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', '2026-01-30 07:57:16', '', NULL, '清空操作'),
(28, 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', '2026-01-30 07:57:16', '', NULL, '正常状态'),
(29, 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', '2026-01-30 07:57:16', '', NULL, '停用状态');
DELETE FROM `sys_dict_type`;
INSERT INTO `sys_dict_type` (`dict_id`, `dict_name`, `dict_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(1, '用户性别', 'sys_user_sex', '0', 'admin', '2026-01-30 07:57:02', '', NULL, '用户性别列表'),
(2, '菜单状态', 'sys_show_hide', '0', 'admin', '2026-01-30 07:57:02', '', NULL, '菜单状态列表'),
(3, '系统开关', 'sys_normal_disable', '0', 'admin', '2026-01-30 07:57:03', '', NULL, '系统开关列表'),
(4, '任务状态', 'sys_job_status', '0', 'admin', '2026-01-30 07:57:03', '', NULL, '任务状态列表'),
(5, '任务分组', 'sys_job_group', '0', 'admin', '2026-01-30 07:57:03', '', NULL, '任务分组列表'),
(6, '系统是否', 'sys_yes_no', '0', 'admin', '2026-01-30 07:57:04', '', NULL, '系统是否列表'),
(7, '通知类型', 'sys_notice_type', '0', 'admin', '2026-01-30 07:57:04', '', NULL, '通知类型列表'),
(8, '通知状态', 'sys_notice_status', '0', 'admin', '2026-01-30 07:57:04', '', NULL, '通知状态列表'),
(9, '操作类型', 'sys_oper_type', '0', 'admin', '2026-01-30 07:57:05', '', NULL, '操作类型列表'),
(10, '系统状态', 'sys_common_status', '0', 'admin', '2026-01-30 07:57:05', '', NULL, '登录状态列表');
DELETE FROM `sys_job`;
INSERT INTO `sys_job` (`job_id`, `job_name`, `job_group`, `invoke_target`, `cron_expression`, `misfire_policy`, `concurrent`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(1, '系统默认(无参)', 'DEFAULT', 'ryTask.ryNoParams', '0/10 * * * * ?', '3', '1', '1', 'admin', '2026-01-30 07:57:21', '', NULL, ''),
(2, '系统默认(有参)', 'DEFAULT', 'ryTask.ryParams(\'ry\')', '0/15 * * * * ?', '3', '1', '1', 'admin', '2026-01-30 07:57:22', '', NULL, ''),
(3, '系统默认(多参)', 'DEFAULT', 'ryTask.ryMultipleParams(\'ry\', true, 2000L, 316.50D, 100)', '0/20 * * * * ?', '3', '1', '1', 'admin', '2026-01-30 07:57:22', '', NULL, '');
DELETE FROM `sys_menu`;
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(1, '系统管理', 0, 1, 'system', NULL, '', '', 1, 0, 'M', '0', '0', '', 'system', 'admin', '2026-01-30 07:55:54', '', NULL, '系统管理目录'),
(2, '系统监控', 0, 2, 'monitor', NULL, '', '', 1, 0, 'M', '0', '0', '', 'monitor', 'admin', '2026-01-30 07:55:55', '', NULL, '系统监控目录'),
(3, '系统工具', 0, 3, 'tool', NULL, '', '', 1, 0, 'M', '0', '0', '', 'tool', 'admin', '2026-01-30 07:55:55', '', NULL, '系统工具目录'),
(4, '若依官网', 0, 4, 'http://ruoyi.vip', NULL, '', '', 0, 0, 'M', '0', '0', '', 'guide', 'admin', '2026-01-30 07:55:55', '', NULL, '若依官网地址'),
(100, '用户管理', 1, 1, 'user', 'system/user/index', '', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', '2026-01-30 07:55:56', '', NULL, '用户管理菜单'),
(101, '角色管理', 1, 2, 'role', 'system/role/index', '', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', '2026-01-30 07:55:56', '', NULL, '角色管理菜单'),
(102, '菜单管理', 1, 3, 'menu', 'system/menu/index', '', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 'admin', '2026-01-30 07:55:56', '', NULL, '菜单管理菜单'),
(103, '部门管理', 1, 4, 'dept', 'system/dept/index', '', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 'admin', '2026-01-30 07:55:57', '', NULL, '部门管理菜单'),
(104, '岗位管理', 1, 5, 'post', 'system/post/index', '', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 'admin', '2026-01-30 07:55:57', '', NULL, '岗位管理菜单'),
(105, '字典管理', 1, 6, 'dict', 'system/dict/index', '', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 'admin', '2026-01-30 07:55:57', '', NULL, '字典管理菜单'),
(106, '参数设置', 1, 7, 'config', 'system/config/index', '', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 'admin', '2026-01-30 07:55:58', '', NULL, '参数设置菜单'),
(107, '通知公告', 1, 8, 'notice', 'system/notice/index', '', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 'admin', '2026-01-30 07:55:58', '', NULL, '通知公告菜单'),
(108, '日志管理', 1, 9, 'log', '', '', '', 1, 0, 'M', '0', '0', '', 'log', 'admin', '2026-01-30 07:55:58', '', NULL, '日志管理菜单'),
(109, '在线用户', 2, 1, 'online', 'monitor/online/index', '', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 'admin', '2026-01-30 07:55:59', '', NULL, '在线用户菜单'),
(110, '定时任务', 2, 2, 'job', 'monitor/job/index', '', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', 'admin', '2026-01-30 07:55:59', '', NULL, '定时任务菜单'),
(111, '数据监控', 2, 3, 'druid', 'monitor/druid/index', '', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', 'admin', '2026-01-30 07:55:59', '', NULL, '数据监控菜单'),
(112, '服务监控', 2, 4, 'server', 'monitor/server/index', '', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', 'admin', '2026-01-30 07:56:00', '', NULL, '服务监控菜单'),
(113, '缓存监控', 2, 5, 'cache', 'monitor/cache/index', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 'admin', '2026-01-30 07:56:00', '', NULL, '缓存监控菜单'),
(114, '缓存列表', 2, 6, 'cacheList', 'monitor/cache/list', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis-list', 'admin', '2026-01-30 07:56:01', '', NULL, '缓存列表菜单'),
(115, '表单构建', 3, 1, 'build', 'tool/build/index', '', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', 'admin', '2026-01-30 07:56:01', '', NULL, '表单构建菜单'),
(116, '代码生成', 3, 2, 'gen', 'tool/gen/index', '', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 'admin', '2026-01-30 07:56:01', '', NULL, '代码生成菜单'),
(117, '系统接口', 3, 3, 'swagger', 'tool/swagger/index', '', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', 'admin', '2026-01-30 07:56:02', '', NULL, '系统接口菜单'),
(500, '操作日志', 108, 1, 'operlog', 'monitor/operlog/index', '', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 'admin', '2026-01-30 07:56:02', '', NULL, '操作日志菜单'),
(501, '登录日志', 108, 2, 'logininfor', 'monitor/logininfor/index', '', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 'admin', '2026-01-30 07:56:02', '', NULL, '登录日志菜单'),
(1000, '用户查询', 100, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', '2026-01-30 07:56:03', '', NULL, ''),
(1001, '用户新增', 100, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', '2026-01-30 07:56:03', '', NULL, ''),
(1002, '用户修改', 100, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', '2026-01-30 07:56:03', '', NULL, ''),
(1003, '用户删除', 100, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', '2026-01-30 07:56:04', '', NULL, ''),
(1004, '用户导出', 100, 5, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', '2026-01-30 07:56:04', '', NULL, ''),
(1005, '用户导入', 100, 6, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', '2026-01-30 07:56:05', '', NULL, ''),
(1006, '重置密码', 100, 7, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', '2026-01-30 07:56:05', '', NULL, ''),
(1007, '角色查询', 101, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', '2026-01-30 07:56:05', '', NULL, ''),
(1008, '角色新增', 101, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', '2026-01-30 07:56:06', '', NULL, ''),
(1009, '角色修改', 101, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', '2026-01-30 07:56:06', '', NULL, ''),
(1010, '角色删除', 101, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', '2026-01-30 07:56:06', '', NULL, ''),
(1011, '角色导出', 101, 5, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', '2026-01-30 07:56:07', '', NULL, ''),
(1012, '菜单查询', 102, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', '2026-01-30 07:56:07', '', NULL, ''),
(1013, '菜单新增', 102, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', '2026-01-30 07:56:07', '', NULL, ''),
(1014, '菜单修改', 102, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', '2026-01-30 07:56:08', '', NULL, ''),
(1015, '菜单删除', 102, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', '2026-01-30 07:56:08', '', NULL, ''),
(1016, '部门查询', 103, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', '2026-01-30 07:56:09', '', NULL, ''),
(1017, '部门新增', 103, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', '2026-01-30 07:56:09', '', NULL, ''),
(1018, '部门修改', 103, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', '2026-01-30 07:56:09', '', NULL, ''),
(1019, '部门删除', 103, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', '2026-01-30 07:56:10', '', NULL, ''),
(1020, '岗位查询', 104, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', '2026-01-30 07:56:10', '', NULL, ''),
(1021, '岗位新增', 104, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', '2026-01-30 07:56:10', '', NULL, ''),
(1022, '岗位修改', 104, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', '2026-01-30 07:56:11', '', NULL, ''),
(1023, '岗位删除', 104, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', '2026-01-30 07:56:11', '', NULL, ''),
(1024, '岗位导出', 104, 5, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', '2026-01-30 07:56:12', '', NULL, ''),
(1025, '字典查询', 105, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', '2026-01-30 07:56:12', '', NULL, ''),
(1026, '字典新增', 105, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', '2026-01-30 07:56:12', '', NULL, ''),
(1027, '字典修改', 105, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', '2026-01-30 07:56:13', '', NULL, ''),
(1028, '字典删除', 105, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', '2026-01-30 07:56:13', '', NULL, ''),
(1029, '字典导出', 105, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', '2026-01-30 07:56:13', '', NULL, ''),
(1030, '参数查询', 106, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', '2026-01-30 07:56:14', '', NULL, ''),
(1031, '参数新增', 106, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', '2026-01-30 07:56:14', '', NULL, ''),
(1032, '参数修改', 106, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', '2026-01-30 07:56:15', '', NULL, ''),
(1033, '参数删除', 106, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', '2026-01-30 07:56:15', '', NULL, ''),
(1034, '参数导出', 106, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', '2026-01-30 07:56:15', '', NULL, ''),
(1035, '公告查询', 107, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', '2026-01-30 07:56:16', '', NULL, ''),
(1036, '公告新增', 107, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', '2026-01-30 07:56:16', '', NULL, ''),
(1037, '公告修改', 107, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', '2026-01-30 07:56:16', '', NULL, ''),
(1038, '公告删除', 107, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', '2026-01-30 07:56:17', '', NULL, ''),
(1039, '操作查询', 500, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', '2026-01-30 07:56:17', '', NULL, ''),
(1040, '操作删除', 500, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', '2026-01-30 07:56:18', '', NULL, ''),
(1041, '日志导出', 500, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', '2026-01-30 07:56:18', '', NULL, ''),
(1042, '登录查询', 501, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', '2026-01-30 07:56:18', '', NULL, ''),
(1043, '登录删除', 501, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', '2026-01-30 07:56:19', '', NULL, ''),
(1044, '日志导出', 501, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', '2026-01-30 07:56:19', '', NULL, ''),
(1045, '账户解锁', 501, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', 'admin', '2026-01-30 07:56:19', '', NULL, ''),
(1046, '在线查询', 109, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', '2026-01-30 07:56:20', '', NULL, ''),
(1047, '批量强退', 109, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', '2026-01-30 07:56:20', '', NULL, ''),
(1048, '单条强退', 109, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', '2026-01-30 07:56:20', '', NULL, ''),
(1049, '任务查询', 110, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', '2026-01-30 07:56:21', '', NULL, ''),
(1050, '任务新增', 110, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', '2026-01-30 07:56:21', '', NULL, ''),
(1051, '任务修改', 110, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', '2026-01-30 07:56:22', '', NULL, ''),
(1052, '任务删除', 110, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', '2026-01-30 07:56:22', '', NULL, ''),
(1053, '状态修改', 110, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', '2026-01-30 07:56:22', '', NULL, ''),
(1054, '任务导出', 110, 6, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', '2026-01-30 07:56:23', '', NULL, ''),
(1055, '生成查询', 116, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', '2026-01-30 07:56:23', '', NULL, ''),
(1056, '生成修改', 116, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', '2026-01-30 07:56:23', '', NULL, ''),
(1057, '生成删除', 116, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', '2026-01-30 07:56:24', '', NULL, ''),
(1058, '导入代码', 116, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', '2026-01-30 07:56:24', '', NULL, ''),
(1059, '预览代码', 116, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', '2026-01-30 07:56:24', '', NULL, ''),
(1060, '生成代码', 116, 6, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', '2026-01-30 07:56:25', '', NULL, ''),
(2000, '利率定价管理', 0, 5, 'loanPricing', NULL, NULL, '', 1, 0, 'M', '1', '0', '', 'money', 'admin', '2026-01-30 07:59:15', 'admin', '2026-01-30 08:05:37', ''),
(2001, '流程列表', 2000, 1, 'workflow', 'loanPricing/workflow/index', NULL, '', 1, 0, 'C', '0', '0', 'loanPricing:workflow:list', 'list', 'admin', '2026-01-30 07:59:15', '', NULL, ''),
(2002, '流程查询', 2001, 1, '', '', NULL, '', 1, 0, 'F', '0', '0', 'loanPricing:workflow:query', '#', 'admin', '2026-01-30 07:59:16', '', NULL, '');
DELETE FROM `sys_notice`;
INSERT INTO `sys_notice` (`notice_id`, `notice_title`, `notice_type`, `notice_content`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(1, '温馨提醒2018-07-01 若依新版本发布啦', '2', 0xe696b0e78988e69cace58685e5aeb9, '0', 'admin', '2026-01-30 07:57:24', '', NULL, '管理员'),
(2, '维护通知2018-07-01 若依系统凌晨维护', '1', 0xe7bbb4e68aa4e58685e5aeb9, '0', 'admin', '2026-01-30 07:57:24', '', NULL, '管理员');
DELETE FROM `sys_post`;
INSERT INTO `sys_post` (`post_id`, `post_code`, `post_name`, `post_sort`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(1, 'ceo', '董事长', 1, '0', 'admin', '2026-01-30 07:55:51', '', NULL, ''),
(2, 'se', '项目经理', 2, '0', 'admin', '2026-01-30 07:55:51', '', NULL, ''),
(3, 'hr', '人力资源', 3, '0', 'admin', '2026-01-30 07:55:51', '', NULL, ''),
(4, 'user', '普通员工', 4, '0', 'admin', '2026-01-30 07:55:52', '', NULL, '');
DELETE FROM `sys_role`;
INSERT INTO `sys_role` (`role_id`, `role_name`, `role_key`, `role_sort`, `data_scope`, `menu_check_strictly`, `dept_check_strictly`, `status`, `del_flag`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(1, '超级管理员', 'admin', 1, '1', 1, 1, '0', '0', 'admin', '2026-01-30 07:55:53', '', NULL, '超级管理员'),
(2, '普通角色', 'common', 2, '2', 1, 1, '0', '0', 'admin', '2026-01-30 07:55:53', '', NULL, '普通角色'),
(100, '管理员', 'headAdmin', 0, '1', 1, 1, '0', '0', 'admin', '2026-01-30 08:43:12', '', NULL, NULL);
DELETE FROM `sys_role_dept`;
INSERT INTO `sys_role_dept` (`role_id`, `dept_id`) VALUES
(2, 100),
(2, 101),
(2, 105);
DELETE FROM `sys_role_menu`;
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
(1, 2000),
(1, 2001),
(1, 2002),
(2, 1),
(2, 2),
(2, 3),
(2, 4),
(2, 100),
(2, 101),
(2, 102),
(2, 103),
(2, 104),
(2, 105),
(2, 106),
(2, 107),
(2, 108),
(2, 109),
(2, 110),
(2, 111),
(2, 112),
(2, 113),
(2, 114),
(2, 115),
(2, 116),
(2, 117),
(2, 500),
(2, 501),
(2, 1000),
(2, 1001),
(2, 1002),
(2, 1003),
(2, 1004),
(2, 1005),
(2, 1006),
(2, 1007),
(2, 1008),
(2, 1009),
(2, 1010),
(2, 1011),
(2, 1012),
(2, 1013),
(2, 1014),
(2, 1015),
(2, 1016),
(2, 1017),
(2, 1018),
(2, 1019),
(2, 1020),
(2, 1021),
(2, 1022),
(2, 1023),
(2, 1024),
(2, 1025),
(2, 1026),
(2, 1027),
(2, 1028),
(2, 1029),
(2, 1030),
(2, 1031),
(2, 1032),
(2, 1033),
(2, 1034),
(2, 1035),
(2, 1036),
(2, 1037),
(2, 1038),
(2, 1039),
(2, 1040),
(2, 1041),
(2, 1042),
(2, 1043),
(2, 1044),
(2, 1045),
(2, 1046),
(2, 1047),
(2, 1048),
(2, 1049),
(2, 1050),
(2, 1051),
(2, 1052),
(2, 1053),
(2, 1054),
(2, 1055),
(2, 1056),
(2, 1057),
(2, 1058),
(2, 1059),
(2, 1060),
(100, 1),
(100, 100),
(100, 101),
(100, 102),
(100, 103),
(100, 1000),
(100, 1001),
(100, 1002),
(100, 1003),
(100, 1004),
(100, 1005),
(100, 1006),
(100, 1007),
(100, 1008),
(100, 1009),
(100, 1010),
(100, 1011),
(100, 1012),
(100, 1013),
(100, 1014),
(100, 1015),
(100, 1016),
(100, 1017),
(100, 1018),
(100, 1019),
(100, 2000),
(100, 2001),
(100, 2002);
DELETE FROM `sys_user`;
INSERT INTO `sys_user` (`user_id`, `dept_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `del_flag`, `login_ip`, `login_date`, `pwd_update_date`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(1, 103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2026-03-28 09:16:04', '2026-01-30 07:55:49', 'admin', '2026-01-30 07:55:49', '', NULL, '管理员'),
(2, 105, 'ry', '若依', '00', 'ry@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2026-01-30 07:55:50', '2026-01-30 07:55:50', 'admin', '2026-01-30 07:55:50', '', NULL, '测试员'),
(100, 100, '8929999', '测试管理员', '00', '', '', '0', '', '$2a$10$i1YqCkEgZK4YhJC09y3qAOovK13Sc5oOENYtFGZRU6R/z8jJB/C/W', '0', '0', '127.0.0.1', '2026-01-30 16:44:03', NULL, 'admin', '2026-01-30 08:43:45', '', NULL, NULL);
DELETE FROM `sys_user_post`;
INSERT INTO `sys_user_post` (`user_id`, `post_id`) VALUES
(1, 1),
(2, 2);
DELETE FROM `sys_user_role`;
INSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES
(1, 1),
(2, 2),
(100, 100);
SET FOREIGN_KEY_CHECKS=1;

View File

@@ -0,0 +1,898 @@
-- 说明:
-- 1. 本文件由源库 116.62.17.81:3306/loan-pricing 导出
-- 2. 用途是初始化目标库 116.62.17.81:3307 的全部表结构
-- 3. 导入前请先创建目标数据库账号并确认拥有建库建表权限
-- MySQL dump 10.13 Distrib 9.6.0, for macos15 (arm64)
--
-- Host: 116.62.17.81 Database: loan-pricing
-- ------------------------------------------------------
-- Server version 5.7.44
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Current Database: `loan-pricing`
--
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `loan-pricing` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */;
USE `loan-pricing`;
--
-- Table structure for table `QRTZ_BLOB_TRIGGERS`
--
DROP TABLE IF EXISTS `QRTZ_BLOB_TRIGGERS`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `QRTZ_BLOB_TRIGGERS` (
`sched_name` varchar(120) NOT NULL COMMENT '调度名称',
`trigger_name` varchar(200) NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键',
`trigger_group` varchar(200) NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键',
`blob_data` blob COMMENT '存放持久化Trigger对象',
PRIMARY KEY (`sched_name`,`trigger_name`,`trigger_group`),
CONSTRAINT `QRTZ_BLOB_TRIGGERS_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `QRTZ_TRIGGERS` (`sched_name`, `trigger_name`, `trigger_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='Blob类型的触发器表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `QRTZ_CALENDARS`
--
DROP TABLE IF EXISTS `QRTZ_CALENDARS`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `QRTZ_CALENDARS` (
`sched_name` varchar(120) NOT NULL COMMENT '调度名称',
`calendar_name` varchar(200) NOT NULL COMMENT '日历名称',
`calendar` blob NOT NULL COMMENT '存放持久化calendar对象',
PRIMARY KEY (`sched_name`,`calendar_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='日历信息表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `QRTZ_CRON_TRIGGERS`
--
DROP TABLE IF EXISTS `QRTZ_CRON_TRIGGERS`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `QRTZ_CRON_TRIGGERS` (
`sched_name` varchar(120) NOT NULL COMMENT '调度名称',
`trigger_name` varchar(200) NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键',
`trigger_group` varchar(200) NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键',
`cron_expression` varchar(200) NOT NULL COMMENT 'cron表达式',
`time_zone_id` varchar(80) DEFAULT NULL COMMENT '时区',
PRIMARY KEY (`sched_name`,`trigger_name`,`trigger_group`),
CONSTRAINT `QRTZ_CRON_TRIGGERS_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `QRTZ_TRIGGERS` (`sched_name`, `trigger_name`, `trigger_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='Cron类型的触发器表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `QRTZ_FIRED_TRIGGERS`
--
DROP TABLE IF EXISTS `QRTZ_FIRED_TRIGGERS`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `QRTZ_FIRED_TRIGGERS` (
`sched_name` varchar(120) NOT NULL COMMENT '调度名称',
`entry_id` varchar(95) NOT NULL COMMENT '调度器实例id',
`trigger_name` varchar(200) NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键',
`trigger_group` varchar(200) NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键',
`instance_name` varchar(200) NOT NULL COMMENT '调度器实例名',
`fired_time` bigint(13) NOT NULL COMMENT '触发的时间',
`sched_time` bigint(13) NOT NULL COMMENT '定时器制定的时间',
`priority` int(11) NOT NULL COMMENT '优先级',
`state` varchar(16) NOT NULL COMMENT '状态',
`job_name` varchar(200) DEFAULT NULL COMMENT '任务名称',
`job_group` varchar(200) DEFAULT NULL COMMENT '任务组名',
`is_nonconcurrent` varchar(1) DEFAULT NULL COMMENT '是否并发',
`requests_recovery` varchar(1) DEFAULT NULL COMMENT '是否接受恢复执行',
PRIMARY KEY (`sched_name`,`entry_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='已触发的触发器表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `QRTZ_JOB_DETAILS`
--
DROP TABLE IF EXISTS `QRTZ_JOB_DETAILS`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `QRTZ_JOB_DETAILS` (
`sched_name` varchar(120) NOT NULL COMMENT '调度名称',
`job_name` varchar(200) NOT NULL COMMENT '任务名称',
`job_group` varchar(200) NOT NULL COMMENT '任务组名',
`description` varchar(250) DEFAULT NULL COMMENT '相关介绍',
`job_class_name` varchar(250) NOT NULL COMMENT '执行任务类名称',
`is_durable` varchar(1) NOT NULL COMMENT '是否持久化',
`is_nonconcurrent` varchar(1) NOT NULL COMMENT '是否并发',
`is_update_data` varchar(1) NOT NULL COMMENT '是否更新数据',
`requests_recovery` varchar(1) NOT NULL COMMENT '是否接受恢复执行',
`job_data` blob COMMENT '存放持久化job对象',
PRIMARY KEY (`sched_name`,`job_name`,`job_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='任务详细信息表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `QRTZ_LOCKS`
--
DROP TABLE IF EXISTS `QRTZ_LOCKS`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `QRTZ_LOCKS` (
`sched_name` varchar(120) NOT NULL COMMENT '调度名称',
`lock_name` varchar(40) NOT NULL COMMENT '悲观锁名称',
PRIMARY KEY (`sched_name`,`lock_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='存储的悲观锁信息表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `QRTZ_PAUSED_TRIGGER_GRPS`
--
DROP TABLE IF EXISTS `QRTZ_PAUSED_TRIGGER_GRPS`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `QRTZ_PAUSED_TRIGGER_GRPS` (
`sched_name` varchar(120) NOT NULL COMMENT '调度名称',
`trigger_group` varchar(200) NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键',
PRIMARY KEY (`sched_name`,`trigger_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='暂停的触发器表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `QRTZ_SCHEDULER_STATE`
--
DROP TABLE IF EXISTS `QRTZ_SCHEDULER_STATE`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `QRTZ_SCHEDULER_STATE` (
`sched_name` varchar(120) NOT NULL COMMENT '调度名称',
`instance_name` varchar(200) NOT NULL COMMENT '实例名称',
`last_checkin_time` bigint(13) NOT NULL COMMENT '上次检查时间',
`checkin_interval` bigint(13) NOT NULL COMMENT '检查间隔时间',
PRIMARY KEY (`sched_name`,`instance_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='调度器状态表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `QRTZ_SIMPLE_TRIGGERS`
--
DROP TABLE IF EXISTS `QRTZ_SIMPLE_TRIGGERS`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `QRTZ_SIMPLE_TRIGGERS` (
`sched_name` varchar(120) NOT NULL COMMENT '调度名称',
`trigger_name` varchar(200) NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键',
`trigger_group` varchar(200) NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键',
`repeat_count` bigint(7) NOT NULL COMMENT '重复的次数统计',
`repeat_interval` bigint(12) NOT NULL COMMENT '重复的间隔时间',
`times_triggered` bigint(10) NOT NULL COMMENT '已经触发的次数',
PRIMARY KEY (`sched_name`,`trigger_name`,`trigger_group`),
CONSTRAINT `QRTZ_SIMPLE_TRIGGERS_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `QRTZ_TRIGGERS` (`sched_name`, `trigger_name`, `trigger_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='简单触发器的信息表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `QRTZ_SIMPROP_TRIGGERS`
--
DROP TABLE IF EXISTS `QRTZ_SIMPROP_TRIGGERS`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `QRTZ_SIMPROP_TRIGGERS` (
`sched_name` varchar(120) NOT NULL COMMENT '调度名称',
`trigger_name` varchar(200) NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键',
`trigger_group` varchar(200) NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键',
`str_prop_1` varchar(512) DEFAULT NULL COMMENT 'String类型的trigger的第一个参数',
`str_prop_2` varchar(512) DEFAULT NULL COMMENT 'String类型的trigger的第二个参数',
`str_prop_3` varchar(512) DEFAULT NULL COMMENT 'String类型的trigger的第三个参数',
`int_prop_1` int(11) DEFAULT NULL COMMENT 'int类型的trigger的第一个参数',
`int_prop_2` int(11) DEFAULT NULL COMMENT 'int类型的trigger的第二个参数',
`long_prop_1` bigint(20) DEFAULT NULL COMMENT 'long类型的trigger的第一个参数',
`long_prop_2` bigint(20) DEFAULT NULL COMMENT 'long类型的trigger的第二个参数',
`dec_prop_1` decimal(13,4) DEFAULT NULL COMMENT 'decimal类型的trigger的第一个参数',
`dec_prop_2` decimal(13,4) DEFAULT NULL COMMENT 'decimal类型的trigger的第二个参数',
`bool_prop_1` varchar(1) DEFAULT NULL COMMENT 'Boolean类型的trigger的第一个参数',
`bool_prop_2` varchar(1) DEFAULT NULL COMMENT 'Boolean类型的trigger的第二个参数',
PRIMARY KEY (`sched_name`,`trigger_name`,`trigger_group`),
CONSTRAINT `QRTZ_SIMPROP_TRIGGERS_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `QRTZ_TRIGGERS` (`sched_name`, `trigger_name`, `trigger_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='同步机制的行锁表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `QRTZ_TRIGGERS`
--
DROP TABLE IF EXISTS `QRTZ_TRIGGERS`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `QRTZ_TRIGGERS` (
`sched_name` varchar(120) NOT NULL COMMENT '调度名称',
`trigger_name` varchar(200) NOT NULL COMMENT '触发器的名字',
`trigger_group` varchar(200) NOT NULL COMMENT '触发器所属组的名字',
`job_name` varchar(200) NOT NULL COMMENT 'qrtz_job_details表job_name的外键',
`job_group` varchar(200) NOT NULL COMMENT 'qrtz_job_details表job_group的外键',
`description` varchar(250) DEFAULT NULL COMMENT '相关介绍',
`next_fire_time` bigint(13) DEFAULT NULL COMMENT '上一次触发时间(毫秒)',
`prev_fire_time` bigint(13) DEFAULT NULL COMMENT '下一次触发时间(默认为-1表示不触发',
`priority` int(11) DEFAULT NULL COMMENT '优先级',
`trigger_state` varchar(16) NOT NULL COMMENT '触发器状态',
`trigger_type` varchar(8) NOT NULL COMMENT '触发器的类型',
`start_time` bigint(13) NOT NULL COMMENT '开始时间',
`end_time` bigint(13) DEFAULT NULL COMMENT '结束时间',
`calendar_name` varchar(200) DEFAULT NULL COMMENT '日程表名称',
`misfire_instr` smallint(2) DEFAULT NULL COMMENT '补偿执行的策略',
`job_data` blob COMMENT '存放持久化job对象',
PRIMARY KEY (`sched_name`,`trigger_name`,`trigger_group`),
KEY `sched_name` (`sched_name`,`job_name`,`job_group`),
CONSTRAINT `QRTZ_TRIGGERS_ibfk_1` FOREIGN KEY (`sched_name`, `job_name`, `job_group`) REFERENCES `QRTZ_JOB_DETAILS` (`sched_name`, `job_name`, `job_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='触发器详细信息表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `gen_table`
--
DROP TABLE IF EXISTS `gen_table`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `gen_table` (
`table_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`table_name` varchar(200) DEFAULT '' COMMENT '表名称',
`table_comment` varchar(500) DEFAULT '' COMMENT '表描述',
`sub_table_name` varchar(64) DEFAULT NULL COMMENT '关联子表的表名',
`sub_table_fk_name` varchar(64) DEFAULT NULL COMMENT '子表关联的外键名',
`class_name` varchar(100) DEFAULT '' COMMENT '实体类名称',
`tpl_category` varchar(200) DEFAULT 'crud' COMMENT '使用的模板crud单表操作 tree树表操作',
`tpl_web_type` varchar(30) DEFAULT '' COMMENT '前端模板类型element-ui模版 element-plus模版',
`package_name` varchar(100) DEFAULT NULL COMMENT '生成包路径',
`module_name` varchar(30) DEFAULT NULL COMMENT '生成模块名',
`business_name` varchar(30) DEFAULT NULL COMMENT '生成业务名',
`function_name` varchar(50) DEFAULT NULL COMMENT '生成功能名',
`function_author` varchar(50) DEFAULT NULL COMMENT '生成功能作者',
`gen_type` char(1) DEFAULT '0' COMMENT '生成代码方式0zip压缩包 1自定义路径',
`gen_path` varchar(200) DEFAULT '/' COMMENT '生成路径(不填默认项目路径)',
`options` varchar(1000) DEFAULT NULL COMMENT '其它生成选项',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`table_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='代码生成业务表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `gen_table_column`
--
DROP TABLE IF EXISTS `gen_table_column`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `gen_table_column` (
`column_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`table_id` bigint(20) DEFAULT NULL COMMENT '归属表编号',
`column_name` varchar(200) DEFAULT NULL COMMENT '列名称',
`column_comment` varchar(500) DEFAULT NULL COMMENT '列描述',
`column_type` varchar(100) DEFAULT NULL COMMENT '列类型',
`java_type` varchar(500) DEFAULT NULL COMMENT 'JAVA类型',
`java_field` varchar(200) DEFAULT NULL COMMENT 'JAVA字段名',
`is_pk` char(1) DEFAULT NULL COMMENT '是否主键1是',
`is_increment` char(1) DEFAULT NULL COMMENT '是否自增1是',
`is_required` char(1) DEFAULT NULL COMMENT '是否必填1是',
`is_insert` char(1) DEFAULT NULL COMMENT '是否为插入字段1是',
`is_edit` char(1) DEFAULT NULL COMMENT '是否编辑字段1是',
`is_list` char(1) DEFAULT NULL COMMENT '是否列表字段1是',
`is_query` char(1) DEFAULT NULL COMMENT '是否查询字段1是',
`query_type` varchar(200) DEFAULT 'EQ' COMMENT '查询方式(等于、不等于、大于、小于、范围)',
`html_type` varchar(200) DEFAULT NULL COMMENT '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)',
`dict_type` varchar(200) DEFAULT '' COMMENT '字典类型',
`sort` int(11) DEFAULT NULL COMMENT '排序',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`column_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='代码生成业务表字段';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `loan_pricing_workflow`
--
DROP TABLE IF EXISTS `loan_pricing_workflow`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `loan_pricing_workflow` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`serial_num` varchar(50) NOT NULL COMMENT '业务方流水号',
`model_output_id` bigint(20) DEFAULT NULL COMMENT '模型输出ID',
`org_code` varchar(20) NOT NULL DEFAULT '892000' COMMENT '机构编码(统一值892000)',
`run_type` varchar(10) NOT NULL DEFAULT '1' COMMENT '运行模式: 1-同步',
`cust_isn` varchar(50) NOT NULL COMMENT '客户内码',
`cust_type` varchar(20) NOT NULL COMMENT '客户类型: 个人/企业',
`guar_type` varchar(20) NOT NULL COMMENT '担保方式: 信用/保证/抵押/质押',
`mid_per_quick_pay` varchar(10) DEFAULT NULL COMMENT '中间业务_个人_快捷支付: true/false',
`mid_per_ele_ddc` varchar(10) DEFAULT NULL COMMENT '中间业务_个人_电费代扣: true/false',
`mid_ent_ele_ddc` varchar(10) DEFAULT NULL COMMENT '中间业务_企业_电费代扣: true/false',
`mid_ent_water_ddc` varchar(10) DEFAULT NULL COMMENT '中间业务_企业_水费代扣: true/false',
`apply_amt` varchar(50) NOT NULL COMMENT '申请金额(元)',
`loan_term` varchar(50) DEFAULT NULL COMMENT '贷款期限',
`is_clean_ent` varchar(10) DEFAULT NULL COMMENT '净身企业: true/false',
`has_settle_acct` varchar(10) DEFAULT NULL COMMENT '开立基本结算账户: true/false',
`is_manufacturing` varchar(10) DEFAULT NULL COMMENT '制造业企业: true/false',
`is_agri_guar` varchar(10) DEFAULT NULL COMMENT '省农担担保贷款: true/false',
`is_tech_ent` varchar(10) DEFAULT NULL COMMENT '科技型企业: true/false科技型企业最多下降5BP',
`is_green_loan` varchar(10) DEFAULT NULL COMMENT '绿色贷款: true/false绿色贷款最多下降5BP',
`is_trade_construction` varchar(10) DEFAULT NULL COMMENT '贸易和建筑业企业标识: true/false抵质押类上调20BP',
`is_tax_a` varchar(10) DEFAULT NULL COMMENT '是否纳税信用等级A级: true/false',
`is_agri_leading` varchar(10) DEFAULT NULL COMMENT '是否县级及以上农业龙头企业: true/false',
`loan_purpose` varchar(20) DEFAULT NULL COMMENT '贷款用途: consumer-消费/business-经营',
`biz_proof` varchar(10) DEFAULT NULL COMMENT '是否有经营佐证: true/false',
`loan_loop` varchar(10) DEFAULT NULL COMMENT '循环功能: true/false贷款合同是否开通循环功能',
`coll_type` varchar(20) DEFAULT NULL COMMENT '抵质押类型: 一线/一类/二类',
`coll_third_party` varchar(10) DEFAULT NULL COMMENT '抵质押物是否三方所有: true/false',
`loan_rate` varchar(20) DEFAULT NULL COMMENT '贷款利率',
`execute_rate` varchar(20) DEFAULT NULL COMMENT '执行利率(%)',
`cust_name` varchar(100) DEFAULT NULL COMMENT '客户名称',
`id_type` varchar(50) DEFAULT NULL COMMENT '证件类型',
`id_num` varchar(100) DEFAULT NULL COMMENT '证件号码',
`is_inclusive_finance` varchar(10) DEFAULT NULL COMMENT '是否普惠小微借款人: true/false',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_serial_num` (`serial_num`),
KEY `idx_org_code` (`org_code`),
KEY `idx_create_by` (`create_by`),
KEY `idx_cust_name` (`cust_name`),
KEY `idx_update_time` (`update_time`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='利率定价流程表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `model_corp_output_fields`
--
DROP TABLE IF EXISTS `model_corp_output_fields`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `model_corp_output_fields` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键ID',
`cust_isn` varchar(100) DEFAULT NULL COMMENT '客户内码',
`cust_type` varchar(100) DEFAULT NULL COMMENT '客户类型',
`guar_type` varchar(100) DEFAULT NULL COMMENT '担保方式',
`cust_name` varchar(100) DEFAULT NULL COMMENT '客户名称',
`id_type` varchar(100) DEFAULT NULL COMMENT '证件类型',
`id_num` varchar(100) DEFAULT NULL COMMENT '证件号码',
`base_loan_rate` varchar(100) DEFAULT NULL COMMENT '基准利率',
`is_first_loan` varchar(100) DEFAULT NULL COMMENT '我行首贷客户',
`faith_day` varchar(100) DEFAULT NULL COMMENT '用信天数',
`bp_first_loan` varchar(100) DEFAULT NULL COMMENT 'BP_首贷',
`bp_age_loan` varchar(100) DEFAULT NULL COMMENT 'BP_贷龄',
`total_bp_loyalty` varchar(100) DEFAULT NULL COMMENT 'TOTAL_BP_忠诚度',
`balance_avg` varchar(100) DEFAULT NULL COMMENT '存款年日均',
`loan_avg` varchar(100) DEFAULT NULL COMMENT '贷款年日均',
`derivation_rate` varchar(100) DEFAULT NULL COMMENT '派生率',
`total_bp_contribution` varchar(100) DEFAULT NULL COMMENT 'TOTAL_BP_贡献度',
`mid_ent_connect` varchar(100) DEFAULT NULL COMMENT '中间业务_企业_企业互联',
`mid_ent_effect` varchar(100) DEFAULT NULL COMMENT '中间业务_企业_有效价值客户',
`mid_ent_inter` varchar(100) DEFAULT NULL COMMENT '中间业务_企业_国际业务',
`mid_ent_accept` varchar(100) DEFAULT NULL COMMENT '中间业务_企业_承兑',
`mid_ent_discount` varchar(100) DEFAULT NULL COMMENT '中间业务_企业_贴现',
`mid_ent_ele_ddc` varchar(100) DEFAULT NULL COMMENT '中间业务_企业_电费代扣',
`mid_ent_water_ddc` varchar(100) DEFAULT NULL COMMENT '中间业务_企业_水费代扣',
`mid_ent_tax` varchar(100) DEFAULT NULL COMMENT '中间业务_企业_税务代扣',
`bp_mid` varchar(100) DEFAULT NULL COMMENT 'BP_中间业务',
`payroll` varchar(100) DEFAULT NULL COMMENT '代发工资户数',
`inv_loan_amount` varchar(100) DEFAULT NULL COMMENT '存量贷款余额',
`bp_payroll` varchar(100) DEFAULT NULL COMMENT 'BP_代发工资',
`is_clean_ent` varchar(100) DEFAULT NULL COMMENT '净身企业',
`has_settle_acct` varchar(100) DEFAULT NULL COMMENT '开立基本结算账户',
`is_agri_guar` varchar(100) DEFAULT NULL COMMENT '省农担担保贷款',
`is_green_loan` varchar(100) DEFAULT NULL COMMENT '绿色贷款',
`is_tech_ent` varchar(100) DEFAULT NULL COMMENT '科技型企业',
`bp_ent_type` varchar(100) DEFAULT NULL COMMENT 'BP_企业客户类别',
`totoal_bp_relevance` varchar(100) DEFAULT NULL COMMENT 'TOTAL_BP_关联度',
`loan_term` varchar(100) DEFAULT NULL COMMENT '贷款期限',
`bp_loan_term` varchar(100) DEFAULT NULL COMMENT 'BP_贷款期限',
`apply_amt` varchar(100) DEFAULT NULL COMMENT '申请金额',
`bp_loan_amount` varchar(100) DEFAULT NULL COMMENT 'BP_贷款额度',
`coll_type` varchar(100) DEFAULT NULL COMMENT '抵质押类型',
`coll_third_party` varchar(100) DEFAULT NULL COMMENT '抵质押物是否三方所有',
`bp_collateral` varchar(100) DEFAULT NULL COMMENT 'BP_抵押物',
`grey_cust` varchar(100) DEFAULT NULL COMMENT '灰名单客户',
`prin_overdue` varchar(100) DEFAULT NULL COMMENT '本金逾期',
`interest_overdue` varchar(100) DEFAULT NULL COMMENT '利息逾期',
`card_overdue` varchar(100) DEFAULT NULL COMMENT '信用卡逾期',
`bp_grey_overdue` varchar(100) DEFAULT NULL COMMENT 'BP_灰名单与逾期',
`totoal_bp_risk` varchar(100) DEFAULT NULL COMMENT 'TOTAL_BP_风险度',
`total_bp` varchar(100) DEFAULT NULL COMMENT '浮动BP',
`calculate_rate` varchar(100) DEFAULT NULL COMMENT '测算利率',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='客户贷款利率测算表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `model_retail_output_fields`
--
DROP TABLE IF EXISTS `model_retail_output_fields`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `model_retail_output_fields` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`cust_isn` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT '客户内码',
`cust_type` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '客户类型',
`guar_type` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '担保方式',
`cust_name` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '客户名称',
`id_type` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '证件类型',
`id_num` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '证件号码',
`base_loan_rate` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '基准利率',
`is_first_loan` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '我行首贷客户',
`faith_day` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用信天数',
`cust_age` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '客户年龄',
`bp_first_loan` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_首贷',
`bp_age_loan` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_贷龄',
`bp_age` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_年龄',
`total_bp_loyalty` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'TOTAL_BP_忠诚度',
`balance_avg` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '存款年日均',
`loan_avg` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '贷款年日均',
`derivation_rate` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '派生率',
`total_bp_contribution` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'TOTAL_BP_贡献度',
`mid_per_card` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_信用卡',
`mid_per_pass` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_一码通',
`mid_per_harvest` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_丰收互联',
`mid_per_effect` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_有效客户',
`mid_per_quick_pay` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_快捷支付',
`mid_per_ele_ddc` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_电费代扣',
`mid_per_water_ddc` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_水费代扣',
`mid_per_huashu_ddc` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_华数费代扣',
`mid_per_gas_ddc` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_煤气费代扣',
`mid_per_citizencard` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_市民卡',
`mid_per_fin_man` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_理财业务',
`mid_per_etc` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '中间业务_个人_etc',
`bp_mid` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_中间业务',
`totoal_bp_relevance` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'TOTAL_BP_关联度',
`apply_amt` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '申请金额',
`bp_loan_amount` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_贷款额度',
`loan_purpose` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '贷款用途',
`biz_proof` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '是否有经营佐证',
`bp_loan_use` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_贷款用途',
`loan_loop` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '循环功能',
`bp_loan_loop` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_循环功能',
`coll_type` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '抵质押类型',
`coll_third_party` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '抵质押物是否三方所有',
`bp_collateral` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_抵押物',
`grey_cust` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '灰名单客户',
`prin_overdue` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '本金逾期',
`interest_overdue` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '利息逾期',
`card_overdue` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '信用卡逾期',
`bp_grey_overdue` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'BP_灰名单与逾期',
`totoal_bp_risk` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'TOTAL_BP_风险度',
`total_bp` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '浮动BP',
`calculate_rate` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '测算利率',
`loan_rate_history` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '历史利率',
`min_rate_product` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '产品最低利率下限',
`smooth_range` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '平滑幅度',
`final_calculate_rate` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '最终测算利率',
`reference_rate` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '参考利率',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='零售模型输出字段表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sys_config`
--
DROP TABLE IF EXISTS `sys_config`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_config` (
`config_id` int(5) NOT NULL AUTO_INCREMENT COMMENT '参数主键',
`config_name` varchar(100) DEFAULT '' COMMENT '参数名称',
`config_key` varchar(100) DEFAULT '' COMMENT '参数键名',
`config_value` varchar(500) DEFAULT '' COMMENT '参数键值',
`config_type` char(1) DEFAULT 'N' COMMENT '系统内置Y是 N否',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`config_id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='参数配置表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sys_dept`
--
DROP TABLE IF EXISTS `sys_dept`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_dept` (
`dept_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '部门id',
`parent_id` bigint(20) DEFAULT '0' COMMENT '父部门id',
`ancestors` varchar(50) DEFAULT '' COMMENT '祖级列表',
`dept_name` varchar(30) DEFAULT '' COMMENT '部门名称',
`order_num` int(4) DEFAULT '0' COMMENT '显示顺序',
`leader` varchar(20) DEFAULT NULL COMMENT '负责人',
`phone` varchar(11) DEFAULT NULL COMMENT '联系电话',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
`status` char(1) DEFAULT '0' COMMENT '部门状态0正常 1停用',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志0代表存在 2代表删除',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`dept_id`)
) ENGINE=InnoDB AUTO_INCREMENT=110 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='部门表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sys_dict_data`
--
DROP TABLE IF EXISTS `sys_dict_data`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_dict_data` (
`dict_code` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '字典编码',
`dict_sort` int(4) DEFAULT '0' COMMENT '字典排序',
`dict_label` varchar(100) DEFAULT '' COMMENT '字典标签',
`dict_value` varchar(100) DEFAULT '' COMMENT '字典键值',
`dict_type` varchar(100) DEFAULT '' COMMENT '字典类型',
`css_class` varchar(100) DEFAULT NULL COMMENT '样式属性(其他样式扩展)',
`list_class` varchar(100) DEFAULT NULL COMMENT '表格回显样式',
`is_default` char(1) DEFAULT 'N' COMMENT '是否默认Y是 N否',
`status` char(1) DEFAULT '0' COMMENT '状态0正常 1停用',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`dict_code`)
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='字典数据表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sys_dict_type`
--
DROP TABLE IF EXISTS `sys_dict_type`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_dict_type` (
`dict_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '字典主键',
`dict_name` varchar(100) DEFAULT '' COMMENT '字典名称',
`dict_type` varchar(100) DEFAULT '' COMMENT '字典类型',
`status` char(1) DEFAULT '0' COMMENT '状态0正常 1停用',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`dict_id`),
UNIQUE KEY `dict_type` (`dict_type`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='字典类型表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sys_job`
--
DROP TABLE IF EXISTS `sys_job`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_job` (
`job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务ID',
`job_name` varchar(64) NOT NULL DEFAULT '' COMMENT '任务名称',
`job_group` varchar(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '任务组名',
`invoke_target` varchar(500) NOT NULL COMMENT '调用目标字符串',
`cron_expression` varchar(255) DEFAULT '' COMMENT 'cron执行表达式',
`misfire_policy` varchar(20) DEFAULT '3' COMMENT '计划执行错误策略1立即执行 2执行一次 3放弃执行',
`concurrent` char(1) DEFAULT '1' COMMENT '是否并发执行0允许 1禁止',
`status` char(1) DEFAULT '0' COMMENT '状态0正常 1暂停',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT '' COMMENT '备注信息',
PRIMARY KEY (`job_id`,`job_name`,`job_group`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='定时任务调度表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sys_job_log`
--
DROP TABLE IF EXISTS `sys_job_log`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_job_log` (
`job_log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务日志ID',
`job_name` varchar(64) NOT NULL COMMENT '任务名称',
`job_group` varchar(64) NOT NULL COMMENT '任务组名',
`invoke_target` varchar(500) NOT NULL COMMENT '调用目标字符串',
`job_message` varchar(500) DEFAULT NULL COMMENT '日志信息',
`status` char(1) DEFAULT '0' COMMENT '执行状态0正常 1失败',
`exception_info` varchar(2000) DEFAULT '' COMMENT '异常信息',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`job_log_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='定时任务调度日志表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sys_logininfor`
--
DROP TABLE IF EXISTS `sys_logininfor`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_logininfor` (
`info_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '访问ID',
`user_name` varchar(50) DEFAULT '' COMMENT '用户账号',
`ipaddr` varchar(128) DEFAULT '' COMMENT '登录IP地址',
`login_location` varchar(255) DEFAULT '' COMMENT '登录地点',
`browser` varchar(50) DEFAULT '' COMMENT '浏览器类型',
`os` varchar(50) DEFAULT '' COMMENT '操作系统',
`status` char(1) DEFAULT '0' COMMENT '登录状态0成功 1失败',
`msg` varchar(255) DEFAULT '' COMMENT '提示消息',
`login_time` datetime DEFAULT NULL COMMENT '访问时间',
PRIMARY KEY (`info_id`),
KEY `idx_sys_logininfor_s` (`status`),
KEY `idx_sys_logininfor_lt` (`login_time`)
) ENGINE=InnoDB AUTO_INCREMENT=111 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='系统访问记录';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sys_menu`
--
DROP TABLE IF EXISTS `sys_menu`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_menu` (
`menu_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
`menu_name` varchar(50) NOT NULL COMMENT '菜单名称',
`parent_id` bigint(20) DEFAULT '0' COMMENT '父菜单ID',
`order_num` int(4) DEFAULT '0' COMMENT '显示顺序',
`path` varchar(200) DEFAULT '' COMMENT '路由地址',
`component` varchar(255) DEFAULT NULL COMMENT '组件路径',
`query` varchar(255) DEFAULT NULL COMMENT '路由参数',
`route_name` varchar(50) DEFAULT '' COMMENT '路由名称',
`is_frame` int(1) DEFAULT '1' COMMENT '是否为外链0是 1否',
`is_cache` int(1) DEFAULT '0' COMMENT '是否缓存0缓存 1不缓存',
`menu_type` char(1) DEFAULT '' COMMENT '菜单类型M目录 C菜单 F按钮',
`visible` char(1) DEFAULT '0' COMMENT '菜单状态0显示 1隐藏',
`status` char(1) DEFAULT '0' COMMENT '菜单状态0正常 1停用',
`perms` varchar(100) DEFAULT NULL COMMENT '权限标识',
`icon` varchar(100) DEFAULT '#' COMMENT '菜单图标',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT '' COMMENT '备注',
PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2003 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='菜单权限表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sys_notice`
--
DROP TABLE IF EXISTS `sys_notice`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_notice` (
`notice_id` int(4) NOT NULL AUTO_INCREMENT COMMENT '公告ID',
`notice_title` varchar(50) NOT NULL COMMENT '公告标题',
`notice_type` char(1) NOT NULL COMMENT '公告类型1通知 2公告',
`notice_content` longblob COMMENT '公告内容',
`status` char(1) DEFAULT '0' COMMENT '公告状态0正常 1关闭',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`notice_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='通知公告表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sys_oper_log`
--
DROP TABLE IF EXISTS `sys_oper_log`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_oper_log` (
`oper_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主键',
`title` varchar(50) DEFAULT '' COMMENT '模块标题',
`business_type` int(2) DEFAULT '0' COMMENT '业务类型0其它 1新增 2修改 3删除',
`method` varchar(200) DEFAULT '' COMMENT '方法名称',
`request_method` varchar(10) DEFAULT '' COMMENT '请求方式',
`operator_type` int(1) DEFAULT '0' COMMENT '操作类别0其它 1后台用户 2手机端用户',
`oper_name` varchar(50) DEFAULT '' COMMENT '操作人员',
`dept_name` varchar(50) DEFAULT '' COMMENT '部门名称',
`oper_url` varchar(255) DEFAULT '' COMMENT '请求URL',
`oper_ip` varchar(128) DEFAULT '' COMMENT '主机地址',
`oper_location` varchar(255) DEFAULT '' COMMENT '操作地点',
`oper_param` varchar(2000) DEFAULT '' COMMENT '请求参数',
`json_result` varchar(2000) DEFAULT '' COMMENT '返回参数',
`status` int(1) DEFAULT '0' COMMENT '操作状态0正常 1异常',
`error_msg` varchar(2000) DEFAULT '' COMMENT '错误消息',
`oper_time` datetime DEFAULT NULL COMMENT '操作时间',
`cost_time` bigint(20) DEFAULT '0' COMMENT '消耗时间',
PRIMARY KEY (`oper_id`),
KEY `idx_sys_oper_log_bt` (`business_type`),
KEY `idx_sys_oper_log_s` (`status`),
KEY `idx_sys_oper_log_ot` (`oper_time`)
) ENGINE=InnoDB AUTO_INCREMENT=125 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='操作日志记录';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sys_post`
--
DROP TABLE IF EXISTS `sys_post`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_post` (
`post_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '岗位ID',
`post_code` varchar(64) NOT NULL COMMENT '岗位编码',
`post_name` varchar(50) NOT NULL COMMENT '岗位名称',
`post_sort` int(4) NOT NULL COMMENT '显示顺序',
`status` char(1) NOT NULL COMMENT '状态0正常 1停用',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`post_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='岗位信息表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sys_role`
--
DROP TABLE IF EXISTS `sys_role`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_role` (
`role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_name` varchar(30) NOT NULL COMMENT '角色名称',
`role_key` varchar(100) NOT NULL COMMENT '角色权限字符串',
`role_sort` int(4) NOT NULL COMMENT '显示顺序',
`data_scope` char(1) DEFAULT '1' COMMENT '数据范围1全部数据权限 2自定数据权限 3本部门数据权限 4本部门及以下数据权限',
`menu_check_strictly` tinyint(1) DEFAULT '1' COMMENT '菜单树选择项是否关联显示',
`dept_check_strictly` tinyint(1) DEFAULT '1' COMMENT '部门树选择项是否关联显示',
`status` char(1) NOT NULL COMMENT '角色状态0正常 1停用',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志0代表存在 2代表删除',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色信息表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sys_role_dept`
--
DROP TABLE IF EXISTS `sys_role_dept`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_role_dept` (
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
`dept_id` bigint(20) NOT NULL COMMENT '部门ID',
PRIMARY KEY (`role_id`,`dept_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色和部门关联表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sys_role_menu`
--
DROP TABLE IF EXISTS `sys_role_menu`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_role_menu` (
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
`menu_id` bigint(20) NOT NULL COMMENT '菜单ID',
PRIMARY KEY (`role_id`,`menu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色和菜单关联表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sys_user`
--
DROP TABLE IF EXISTS `sys_user`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_user` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`dept_id` bigint(20) DEFAULT NULL COMMENT '部门ID',
`user_name` varchar(30) NOT NULL COMMENT '用户账号',
`nick_name` varchar(30) NOT NULL COMMENT '用户昵称',
`user_type` varchar(2) DEFAULT '00' COMMENT '用户类型00系统用户',
`email` varchar(50) DEFAULT '' COMMENT '用户邮箱',
`phonenumber` varchar(11) DEFAULT '' COMMENT '手机号码',
`sex` char(1) DEFAULT '0' COMMENT '用户性别0男 1女 2未知',
`avatar` varchar(100) DEFAULT '' COMMENT '头像地址',
`password` varchar(100) DEFAULT '' COMMENT '密码',
`status` char(1) DEFAULT '0' COMMENT '账号状态0正常 1停用',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志0代表存在 2代表删除',
`login_ip` varchar(128) DEFAULT '' COMMENT '最后登录IP',
`login_date` datetime DEFAULT NULL COMMENT '最后登录时间',
`pwd_update_date` datetime DEFAULT NULL COMMENT '密码最后更新时间',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户信息表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sys_user_post`
--
DROP TABLE IF EXISTS `sys_user_post`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_user_post` (
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`post_id` bigint(20) NOT NULL COMMENT '岗位ID',
PRIMARY KEY (`user_id`,`post_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户与岗位关联表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `sys_user_role`
--
DROP TABLE IF EXISTS `sys_user_role`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sys_user_role` (
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户和角色关联表';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2026-03-28 9:20:02

View File

@@ -0,0 +1,42 @@
-- 利率定价流程表
DROP TABLE IF EXISTS `loan_pricing_workflow`;
CREATE TABLE `loan_pricing_workflow` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`serial_num` varchar(50) NOT NULL COMMENT '业务方流水号',
`model_output_id` bigint(20) NULL COMMENT '模型输出id',
`org_code` varchar(20) NOT NULL DEFAULT '892000' COMMENT '机构编码(统一值892000)',
`run_type` varchar(10) NOT NULL DEFAULT '1' COMMENT '运行模式: 1-同步',
`cust_isn` varchar(50) NOT NULL COMMENT '客户内码',
`cust_type` varchar(20) NOT NULL COMMENT '客户类型: 个人/企业',
`guar_type` varchar(20) NOT NULL COMMENT '担保方式: 信用/保证/抵押/质押',
`mid_per_quick_pay` varchar(10) DEFAULT NULL COMMENT '中间业务_个人_快捷支付: true/false',
`mid_per_ele_ddc` varchar(10) DEFAULT NULL COMMENT '中间业务_个人_电费代扣: true/false',
`mid_ent_ele_ddc` varchar(10) DEFAULT NULL COMMENT '中间业务_企业_电费代扣: true/false',
`mid_ent_water_ddc` varchar(10) DEFAULT NULL COMMENT '中间业务_企业_水费代扣: true/false',
`apply_amt` varchar(50) NOT NULL COMMENT '申请金额(元)',
`is_clean_ent` varchar(10) DEFAULT NULL COMMENT '净身企业: true/false',
`has_settle_acct` varchar(10) DEFAULT NULL COMMENT '开立基本结算账户: true/false',
`is_manufacturing` varchar(10) DEFAULT NULL COMMENT '制造业企业: true/false',
`is_agri_guar` varchar(10) DEFAULT NULL COMMENT '省农担担保贷款: true/false',
`is_tax_a` varchar(10) DEFAULT NULL COMMENT '是否纳税信用等级A级: true/false',
`is_agri_leading` varchar(10) DEFAULT NULL COMMENT '是否县级及以上农业龙头企业: true/false',
`loan_purpose` varchar(20) DEFAULT NULL COMMENT '贷款用途: consumer-消费/business-经营',
`biz_proof` varchar(10) DEFAULT NULL COMMENT '是否有经营佐证: true/false',
`coll_type` varchar(20) DEFAULT NULL COMMENT '抵质押类型: 一线/一类/二类',
`coll_third_party` varchar(10) DEFAULT NULL COMMENT '抵质押物是否三方所有: true/false',
`loan_rate` varchar(20) NOT NULL COMMENT '贷款利率',
`execute_rate` varchar(20) DEFAULT NULL COMMENT '执行利率(%)',
`cust_name` varchar(100) DEFAULT NULL COMMENT '客户名称',
`id_type` varchar(50) DEFAULT NULL COMMENT '证件类型',
`is_inclusive_finance` varchar(10) DEFAULT NULL COMMENT '是否普惠小微借款人: true/false',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_serial_num` (`serial_num`),
KEY `idx_org_code` (`org_code`),
KEY `idx_create_by` (`create_by`),
KEY `idx_cust_name` (`cust_name`),
KEY `idx_update_time` (`update_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='利率定价流程表';

59
sql/model_corp.sql Normal file
View File

@@ -0,0 +1,59 @@
/*
* 客户贷款利率测算表
*/
DROP TABLE IF EXISTS model_corp_output_fields;
CREATE TABLE `model_corp_output_fields` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键ID',
`cust_isn` VARCHAR(100) COMMENT '客户内码',
`cust_type` VARCHAR(100) COMMENT '客户类型',
`guar_type` VARCHAR(100) COMMENT '担保方式',
`cust_name` VARCHAR(100) COMMENT '客户名称',
`id_type` VARCHAR(100) COMMENT '证件类型',
`id_num` VARCHAR(100) COMMENT '证件号码',
`base_loan_rate` VARCHAR(100) COMMENT '基准利率',
`is_first_loan` VARCHAR(100) COMMENT '我行首贷客户',
`faith_day` VARCHAR(100) COMMENT '用信天数',
`bp_first_loan` VARCHAR(100) COMMENT 'BP_首贷',
`bp_age_loan` VARCHAR(100) COMMENT 'BP_贷龄',
`total_bp_loyalty` VARCHAR(100) COMMENT 'TOTAL_BP_忠诚度',
`balance_avg` VARCHAR(100) COMMENT '存款年日均',
`loan_avg` VARCHAR(100) COMMENT '贷款年日均',
`derivation_rate` VARCHAR(100) COMMENT '派生率',
`total_bp_contribution` VARCHAR(100) COMMENT 'TOTAL_BP_贡献度',
`mid_ent_connect` VARCHAR(100) COMMENT '中间业务_企业_企业互联',
`mid_ent_effect` VARCHAR(100) COMMENT '中间业务_企业_有效价值客户',
`mid_ent_inter` VARCHAR(100) COMMENT '中间业务_企业_国际业务',
`mid_ent_accept` VARCHAR(100) COMMENT '中间业务_企业_承兑',
`mid_ent_discount` VARCHAR(100) COMMENT '中间业务_企业_贴现',
`mid_ent_ele_ddc` VARCHAR(100) COMMENT '中间业务_企业_电费代扣',
`mid_ent_water_ddc` VARCHAR(100) COMMENT '中间业务_企业_水费代扣',
`mid_ent_tax` VARCHAR(100) COMMENT '中间业务_企业_税务代扣',
`bp_mid` VARCHAR(100) COMMENT 'BP_中间业务',
`payroll` VARCHAR(100) COMMENT '代发工资户数',
`inv_loan_amount` VARCHAR(100) COMMENT '存量贷款余额',
`bp_payroll` VARCHAR(100) COMMENT 'BP_代发工资',
`is_clean_ent` VARCHAR(100) COMMENT '净身企业',
`has_settle_acct` VARCHAR(100) COMMENT '开立基本结算账户',
`is_agri_guar` VARCHAR(100) COMMENT '省农担担保贷款',
`is_green_loan` VARCHAR(100) COMMENT '绿色贷款',
`is_tech_ent` VARCHAR(100) COMMENT '科技型企业',
`bp_ent_type` VARCHAR(100) COMMENT 'BP_企业客户类别',
`totoal_bp_relevance` VARCHAR(100) COMMENT 'TOTAL_BP_关联度',
`loan_term` VARCHAR(100) COMMENT '贷款期限',
`bp_loan_term` VARCHAR(100) COMMENT 'BP_贷款期限',
`apply_amt` VARCHAR(100) COMMENT '申请金额',
`bp_loan_amount` VARCHAR(100) COMMENT 'BP_贷款额度',
`coll_type` VARCHAR(100) COMMENT '抵质押类型',
`coll_third_party` VARCHAR(100) COMMENT '抵质押物是否三方所有',
`bp_collateral` VARCHAR(100) COMMENT 'BP_抵押物',
`grey_cust` VARCHAR(100) COMMENT '灰名单客户',
`prin_overdue` VARCHAR(100) COMMENT '本金逾期',
`interest_overdue` VARCHAR(100) COMMENT '利息逾期',
`card_overdue` VARCHAR(100) COMMENT '信用卡逾期',
`bp_grey_overdue` VARCHAR(100) COMMENT 'BP_灰名单与逾期',
`totoal_bp_risk` VARCHAR(100) COMMENT 'TOTAL_BP_风险度',
`total_bp` VARCHAR(100) COMMENT '浮动BP',
`calculate_rate` VARCHAR(100) COMMENT '测算利率',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='客户贷款利率测算表';

124
sql/model_retail.sql Normal file
View File

@@ -0,0 +1,124 @@
/*
* 零售模型输出字段表
* 存储客户基本信息、BP评分、资产信息、风险信息等贷款测算相关数据
*/
DROP TABLE IF EXISTS model_retail_output_fields;
CREATE TABLE IF NOT EXISTS model_retail_output_fields (
-- 主键ID自增用于表的唯一标识
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-- 客户内码(业务唯一标识)
cust_isn VARCHAR(100) NOT NULL COMMENT '客户内码',
-- 客户类型(如:个人/企业)
cust_type VARCHAR(100) DEFAULT '' COMMENT '客户类型',
-- 担保方式(如:信用担保/抵押担保)
guar_type VARCHAR(100) DEFAULT '' COMMENT '担保方式',
-- 客户名称
cust_name VARCHAR(100) DEFAULT '' COMMENT '客户名称',
-- 证件类型(如:身份证/营业执照)
id_type VARCHAR(100) DEFAULT '' COMMENT '证件类型',
-- 证件号码(脱敏存储)
id_num VARCHAR(100) DEFAULT '' COMMENT '证件号码',
-- 基准利率百分比如4.35
base_loan_rate VARCHAR(100) DEFAULT '' COMMENT '基准利率',
-- 我行首贷客户(是/否)
is_first_loan VARCHAR(100) DEFAULT '' COMMENT '我行首贷客户',
-- 用信天数
faith_day VARCHAR(100) DEFAULT '' COMMENT '用信天数',
-- 客户年龄
cust_age VARCHAR(100) DEFAULT '' COMMENT '客户年龄',
-- BP_首贷
bp_first_loan VARCHAR(100) DEFAULT '' COMMENT 'BP_首贷',
-- BP_贷龄
bp_age_loan VARCHAR(100) DEFAULT '' COMMENT 'BP_贷龄',
-- BP_年龄
bp_age VARCHAR(100) DEFAULT '' COMMENT 'BP_年龄',
-- TOTAL_BP_忠诚度
total_bp_loyalty VARCHAR(100) DEFAULT '' COMMENT 'TOTAL_BP_忠诚度',
-- 存款年日均(元)
balance_avg VARCHAR(100) DEFAULT '' COMMENT '存款年日均',
-- 贷款年日均(元)
loan_avg VARCHAR(100) DEFAULT '' COMMENT '贷款年日均',
-- 派生率
derivation_rate VARCHAR(100) DEFAULT '' COMMENT '派生率',
-- TOTAL_BP_贡献度
total_bp_contribution VARCHAR(100) DEFAULT '' COMMENT 'TOTAL_BP_贡献度',
-- 中间业务_个人_信用卡
mid_per_card VARCHAR(100) DEFAULT '' COMMENT '中间业务_个人_信用卡',
-- 中间业务_个人_一码通
mid_per_pass VARCHAR(100) DEFAULT '' COMMENT '中间业务_个人_一码通',
-- 中间业务_个人_丰收互联
mid_per_harvest VARCHAR(100) DEFAULT '' COMMENT '中间业务_个人_丰收互联',
-- 中间业务_个人_有效客户是/否)
mid_per_effect VARCHAR(100) DEFAULT '' COMMENT '中间业务_个人_有效客户',
-- 中间业务_个人_快捷支付
mid_per_quick_pay VARCHAR(100) DEFAULT '' COMMENT '中间业务_个人_快捷支付',
-- 中间业务_个人_电费代扣
mid_per_ele_ddc VARCHAR(100) DEFAULT '' COMMENT '中间业务_个人_电费代扣',
-- 中间业务_个人_水费代扣
mid_per_water_ddc VARCHAR(100) DEFAULT '' COMMENT '中间业务_个人_水费代扣',
-- 中间业务_个人_华数费代扣
mid_per_huashu_ddc VARCHAR(100) DEFAULT '' COMMENT '中间业务_个人_华数费代扣',
-- 中间业务_个人_煤气费代扣
mid_per_gas_ddc VARCHAR(100) DEFAULT '' COMMENT '中间业务_个人_煤气费代扣',
-- 中间业务_个人_市民卡
mid_per_citizencard VARCHAR(100) DEFAULT '' COMMENT '中间业务_个人_市民卡',
-- 中间业务_个人_理财业务
mid_per_fin_man VARCHAR(100) DEFAULT '' COMMENT '中间业务_个人_理财业务',
-- 中间业务_个人_etc
mid_per_etc VARCHAR(100) DEFAULT '' COMMENT '中间业务_个人_etc',
-- BP_中间业务
bp_mid VARCHAR(100) DEFAULT '' COMMENT 'BP_中间业务',
-- TOTAL_BP_关联度
totoal_bp_relevance VARCHAR(100) DEFAULT '' COMMENT 'TOTAL_BP_关联度',
-- 申请金额(元)
apply_amt VARCHAR(100) DEFAULT '' COMMENT '申请金额',
-- BP_贷款额度
bp_loan_amount VARCHAR(100) DEFAULT '' COMMENT 'BP_贷款额度',
-- 贷款用途(如:个人消费/经营)
loan_purpose VARCHAR(100) DEFAULT '' COMMENT '贷款用途',
-- 是否有经营佐证(是/否)
biz_proof VARCHAR(100) DEFAULT '' COMMENT '是否有经营佐证',
-- BP_贷款用途
bp_loan_use VARCHAR(100) DEFAULT '' COMMENT 'BP_贷款用途',
-- 循环功能(支持/不支持)
loan_loop VARCHAR(100) DEFAULT '' COMMENT '循环功能',
-- BP_循环功能
bp_loan_loop VARCHAR(100) DEFAULT '' COMMENT 'BP_循环功能',
-- 抵质押类型(如:无抵质押/房产抵押)
coll_type VARCHAR(100) DEFAULT '' COMMENT '抵质押类型',
-- 抵质押物是否三方所有(是/否)
coll_third_party VARCHAR(100) DEFAULT '' COMMENT '抵质押物是否三方所有',
-- BP_抵押物
bp_collateral VARCHAR(100) DEFAULT '' COMMENT 'BP_抵押物',
-- 灰名单客户(是/否)
grey_cust VARCHAR(100) DEFAULT '' COMMENT '灰名单客户',
-- 本金逾期(是/否)
prin_overdue VARCHAR(100) DEFAULT '' COMMENT '本金逾期',
-- 利息逾期(是/否)
interest_overdue VARCHAR(100) DEFAULT '' COMMENT '利息逾期',
-- 信用卡逾期(是/否)
card_overdue VARCHAR(100) DEFAULT '' COMMENT '信用卡逾期',
-- BP_灰名单与逾期
bp_grey_overdue VARCHAR(100) DEFAULT '' COMMENT 'BP_灰名单与逾期',
-- TOTAL_BP_风险度
totoal_bp_risk VARCHAR(100) DEFAULT '' COMMENT 'TOTAL_BP_风险度',
-- 浮动BP
total_bp VARCHAR(100) DEFAULT '' COMMENT '浮动BP',
-- 测算利率百分比如6.15
calculate_rate VARCHAR(100) DEFAULT '' COMMENT '测算利率',
-- 历史利率
loan_rate_history VARCHAR(100) DEFAULT '' COMMENT '历史利率',
-- 产品最低利率下限
min_rate_product VARCHAR(100) DEFAULT '' COMMENT '产品最低利率下限',
-- 平滑幅度
smooth_range VARCHAR(100) DEFAULT '' COMMENT '平滑幅度',
-- 最终测算利率
final_calculate_rate VARCHAR(100) DEFAULT '' COMMENT '最终测算利率',
-- 参考利率
reference_rate VARCHAR(100) DEFAULT '' COMMENT '参考利率',
-- 创建时间(审计字段)
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-- 主键约束
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='零售模型输出字段表';

View File

@@ -0,0 +1,224 @@
### ============================================================
### 企业客户利率定价流程接口测试脚本
### 用途:测试企业客户发起接口的各项场景
### ============================================================
### ============================================================
### 1. 获取测试 Token如果未获取
### ============================================================
POST http://localhost:63310/login/test
Content-Type: application/json
{
"username": "admin",
"password": "admin123"
}
> {%
client.test("Request executed successfully", function () {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 200, "Response code is 200");
client.global.set("token", response.body.data.token);
});
%}
### ============================================================
### 2. 企业客户发起 - 成功场景(完整必填字段)
### ============================================================
POST http://localhost:63310/loanPricing/workflow/create/corporate
Authorization: Bearer {{token}}
Content-Type: application/json
{
"custIsn": "CORP001",
"custName": "测试科技有限公司",
"idType": "统一社会信用代码",
"idNum": "91110000100000000X",
"guarType": "抵押",
"applyAmt": "1000000",
"loanTerm": "36",
"isAgriGuar": "false",
"isGreenLoan": "true",
"isTechEnt": "true"
}
> {%
client.test("Corporate loan creation successful", function () {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 200, "Response code is 200");
client.assert(response.body.data.custType === "企业", "Customer type is 企业");
client.assert(response.body.data.serialNum !== null, "Serial number is generated");
client.assert(response.body.data.loanTerm === "36", "Loan term is correct");
});
%}
### ============================================================
### 3. 企业客户发起 - 缺少必填字段 custIsn
### ============================================================
POST http://localhost:63310/loanPricing/workflow/create/corporate
Authorization: Bearer {{token}}
Content-Type: application/json
{
"custName": "测试企业2",
"idType": "统一社会信用代码",
"idNum": "91110000100000001X",
"guarType": "信用",
"applyAmt": "500000"
}
> {%
client.test("Missing custIsn validation works", function () {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 500, "Response code indicates validation error");
});
%}
### ============================================================
### 4. 企业客户发起 - 缺少必填字段 guarType
### ============================================================
POST http://localhost:63310/loanPricing/workflow/create/corporate
Authorization: Bearer {{token}}
Content-Type: application/json
{
"custIsn": "CORP002",
"custName": "测试企业3",
"idType": "统一社会信用代码",
"idNum": "91110000100000002X",
"applyAmt": "800000"
}
> {%
client.test("Missing guarType validation works", function () {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 500, "Response code indicates validation error");
});
%}
### ============================================================
### 5. 企业客户发起 - 缺少必填字段 applyAmt
### ============================================================
POST http://localhost:63310/loanPricing/workflow/create/corporate
Authorization: Bearer {{token}}
Content-Type: application/json
{
"custIsn": "CORP003",
"custName": "测试企业4",
"guarType": "保证",
"idType": "统一社会信用代码",
"idNum": "91110000100000003X"
}
> {%
client.test("Missing applyAmt validation works", function () {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 500, "Response code indicates validation error");
});
%}
### ============================================================
### 6. 企业客户发起 - 担保方式枚举验证失败
### ============================================================
POST http://localhost:63310/loanPricing/workflow/create/corporate
Authorization: Bearer {{token}}
Content-Type: application/json
{
"custIsn": "CORP004",
"custName": "测试企业5",
"idType": "统一社会信用代码",
"idNum": "91110000100000004X",
"guarType": "无效值",
"applyAmt": "600000"
}
> {%
client.test("Invalid guarType validation works", function () {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 500, "Response code indicates validation error");
});
%}
### ============================================================
### 7. 企业客户发起 - 包含省农担担保贷款标识
### ============================================================
POST http://localhost:63310/loanPricing/workflow/create/corporate
Authorization: Bearer {{token}}
Content-Type: application/json
{
"custIsn": "CORP005",
"custName": "农业科技有限公司",
"idType": "统一社会信用代码",
"idNum": "91110000100000005X",
"guarType": "保证",
"applyAmt": "2000000",
"loanTerm": "60",
"isAgriGuar": "true"
}
> {%
client.test("Corporate loan with agricultural guarantee", function () {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 200, "Response code is 200");
client.assert(response.body.data.isAgriGuar === "true", "Agricultural guarantee is set");
});
%}
### ============================================================
### 8. 企业客户发起 - 贸易和建筑业企业标识
### ============================================================
POST http://localhost:63310/loanPricing/workflow/create/corporate
Authorization: Bearer {{token}}
Content-Type: application/json
{
"custIsn": "CORP006",
"custName": "建筑工程有限公司",
"idType": "统一社会信用代码",
"idNum": "91110000100000006X",
"guarType": "质押",
"applyAmt": "1500000",
"loanTerm": "24",
"isTradeConstruction": "true"
}
> {%
client.test("Corporate loan for trade/construction", function () {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 200, "Response code is 200");
client.assert(response.body.data.isTradeConstruction === "true", "Trade/construction flag is set");
});
%}
### ============================================================
### 9. 企业客户发起 - 所有字段必填(信用贷款场景)
### ============================================================
POST http://localhost:63310/loanPricing/workflow/create/corporate
Authorization: Bearer {{token}}
Content-Type: application/json
{
"custIsn": "CORP007",
"custName": "科技创新有限公司",
"idType": "统一社会信用代码",
"idNum": "91110000100000007X",
"guarType": "信用",
"applyAmt": "3000000",
"loanTerm": "12",
"isAgriGuar": "false",
"isGreenLoan": "true",
"isTechEnt": "true",
"isTradeConstruction": "false",
"collType": "一类",
"collThirdParty": "false"
}
> {%
client.test("Corporate loan with all required fields", function () {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 200, "Response code is 200");
});
%}

View File

@@ -0,0 +1,311 @@
#!/bin/bash
# ============================================================
# 企业客户利率定价流程接口测试脚本
# 用途:测试企业客户发起接口的各项场景
# 使用方法: ./test_corporate_create.sh
# ============================================================
# 配置
BASE_URL="http://localhost:63310"
LOGIN_URL="${BASE_URL}/login/test"
CORPORATE_CREATE_URL="${BASE_URL}/loanPricing/workflow/create/corporate"
# 颜色输出
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 测试结果统计
TESTS_PASSED=0
TESTS_FAILED=0
# ============================================================
# 函数:打印测试结果
# ============================================================
print_result() {
local test_name="$1"
local result="$2"
if [ "$result" == "0" ]; then
echo -e "${GREEN}${NC} $test_name - ${GREEN}PASSED${NC}"
((TESTS_PASSED++))
else
echo -e "${RED}${NC} $test_name - ${RED}FAILED${NC}"
((TESTS_FAILED++))
fi
}
# ============================================================
# 步骤 1: 获取测试 Token
# ============================================================
echo "=========================================="
echo "步骤 1: 获取测试 Token"
echo "=========================================="
TOKEN_RESPONSE=$(curl -s -X POST "$LOGIN_URL" \
-H "Content-Type: application/json" \
-d '{
"username": "admin",
"password": "admin123"
}')
TOKEN=$(echo "$TOKEN_RESPONSE" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
if [ -z "$TOKEN" ]; then
echo -e "${RED}获取 Token 失败${NC}"
echo "响应: $TOKEN_RESPONSE"
exit 1
fi
echo -e "${GREEN}Token 获取成功${NC}"
echo "Token: ${TOKEN:0:20}..."
# ============================================================
# 步骤 2: 测试企业客户发起 - 成功场景
# ============================================================
echo ""
echo "=========================================="
echo "步骤 2: 测试企业客户发起 - 成功场景"
echo "=========================================="
RESPONSE=$(curl -s -X POST "$CORPORATE_CREATE_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"custIsn": "CORP001",
"custName": "测试科技有限公司",
"idType": "统一社会信用代码",
"idNum": "91110000100000000X",
"guarType": "抵押",
"applyAmt": "1000000",
"loanTerm": "36",
"isAgriGuar": "false",
"isGreenLoan": "true",
"isTechEnt": "true"
}')
# 检查响应
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | cut -d':' -f2)
CUST_TYPE=$(echo "$RESPONSE" | grep -o '"custType":"[^"]*"' | cut -d'"' -f4)
SERIAL_NUM=$(echo "$RESPONSE" | grep -o '"serialNum":"[^"]*"' | cut -d'"' -f4)
if [ "$CODE" == "200" ] && [ "$CUST_TYPE" == "企业" ] && [ -n "$SERIAL_NUM" ]; then
print_result "企业客户发起成功场景" "0"
echo " 业务流水号: $SERIAL_NUM"
else
print_result "企业客户发起成功场景" "1"
echo " 响应: $RESPONSE"
fi
# ============================================================
# 步骤 3: 测试缺少必填字段 custIsn
# ============================================================
echo ""
echo "=========================================="
echo "步骤 3: 测试缺少必填字段 custIsn"
echo "=========================================="
RESPONSE=$(curl -s -X POST "$CORPORATE_CREATE_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"custName": "测试企业2",
"idType": "统一社会信用代码",
"idNum": "91110000100000001X",
"guarType": "信用",
"applyAmt": "500000"
}')
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | cut -d':' -f2)
if [ "$CODE" == "500" ]; then
print_result "缺少 custIsn 字段验证" "0"
else
print_result "缺少 custIsn 字段验证" "1"
fi
# ============================================================
# 步骤 4: 测试缺少必填字段 guarType
# ============================================================
echo ""
echo "=========================================="
echo "步骤 4: 测试缺少必填字段 guarType"
echo "=========================================="
RESPONSE=$(curl -s -X POST "$CORPORATE_CREATE_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"custIsn": "CORP002",
"custName": "测试企业3",
"idType": "统一社会信用代码",
"idNum": "91110000100000002X",
"applyAmt": "800000"
}')
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | cut -d':' -f2)
if [ "$CODE" == "500" ]; then
print_result "缺少 guarType 字段验证" "0"
else
print_result "缺少 guarType 字段验证" "1"
fi
# ============================================================
# 步骤 5: 测试缺少必填字段 applyAmt
# ============================================================
echo ""
echo "=========================================="
echo "步骤 5: 测试缺少必填字段 applyAmt"
echo "=========================================="
RESPONSE=$(curl -s -X POST "$CORPORATE_CREATE_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"custIsn": "CORP003",
"custName": "测试企业4",
"guarType": "保证",
"idType": "统一社会信用代码",
"idNum": "91110000100000003X"
}')
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | cut -d':' -f2)
if [ "$CODE" == "500" ]; then
print_result "缺少 applyAmt 字段验证" "0"
else
print_result "缺少 applyAmt 字段验证" "1"
fi
# ============================================================
# 步骤 6: 测试担保方式枚举验证失败
# ============================================================
echo ""
echo "=========================================="
echo "步骤 6: 测试担保方式枚举验证失败"
echo "=========================================="
RESPONSE=$(curl -s -X POST "$CORPORATE_CREATE_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"custIsn": "CORP004",
"custName": "测试企业5",
"idType": "统一社会信用代码",
"idNum": "91110000100000004X",
"guarType": "无效值",
"applyAmt": "600000"
}')
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | cut -d':' -f2)
if [ "$CODE" == "500" ]; then
print_result "担保方式枚举验证" "0"
else
print_result "担保方式枚举验证" "1"
fi
# ============================================================
# 步骤 7: 测试省农担担保贷款标识
# ============================================================
echo ""
echo "=========================================="
echo "步骤 7: 测试省农担担保贷款标识"
echo "=========================================="
RESPONSE=$(curl -s -X POST "$CORPORATE_CREATE_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"custIsn": "CORP005",
"custName": "农业科技有限公司",
"idType": "统一社会信用代码",
"idNum": "91110000100000005X",
"guarType": "保证",
"applyAmt": "2000000",
"loanTerm": "60",
"isAgriGuar": "true"
}')
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | cut -d':' -f2)
IS_AGRI_GUAR=$(echo "$RESPONSE" | grep -o '"isAgriGuar":"[^"]*"' | cut -d'"' -f4)
if [ "$CODE" == "200" ] && [ "$IS_AGRI_GUAR" == "true" ]; then
print_result "省农担担保贷款标识正确保存" "0"
echo " 省农担标识: $IS_AGRI_GUAR"
else
print_result "省农担担保贷款标识正确保存" "1"
fi
# ============================================================
# 步骤 8: 测试贸易和建筑业企业标识
# ============================================================
echo ""
echo "=========================================="
echo "步骤 8: 测试贸易和建筑业企业标识"
echo "=========================================="
RESPONSE=$(curl -s -X POST "$CORPORATE_CREATE_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"custIsn": "CORP006",
"custName": "建筑工程有限公司",
"idType": "统一社会信用代码",
"idNum": "91110000100000006X",
"guarType": "质押",
"applyAmt": "1500000",
"loanTerm": "24",
"isTradeConstruction": "true"
}')
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | cut -d':' -f2)
IS_TRADE_CONSTRUCTION=$(echo "$RESPONSE" | grep -o '"isTradeConstruction":"[^"]*"' | cut -d'"' -f4)
if [ "$CODE" == "200" ] && [ "$IS_TRADE_CONSTRUCTION" == "true" ]; then
print_result "贸易和建筑业企业标识正确保存" "0"
echo " 贸易建筑标识: $IS_TRADE_CONSTRUCTION"
else
print_result "贸易和建筑业企业标识正确保存" "1"
fi
# ============================================================
# 步骤 9: 测试最小必填字段
# ============================================================
echo ""
echo "=========================================="
echo "步骤 9: 测试最小必填字段"
echo "=========================================="
RESPONSE=$(curl -s -X POST "$CORPORATE_CREATE_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"custIsn": "CORP007",
"guarType": "信用",
"applyAmt": "3000000"
}')
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | cut -d':' -f2)
if [ "$CODE" == "200" ]; then
print_result "最小必填字段测试" "0"
else
print_result "最小必填字段测试" "1"
fi
# ============================================================
# 测试结果汇总
# ============================================================
echo ""
echo "=========================================="
echo "测试结果汇总"
echo "=========================================="
echo -e "${GREEN}通过: $TESTS_PASSED${NC}"
echo -e "${RED}失败: $TESTS_FAILED${NC}"
echo "总计: $((TESTS_PASSED + TESTS_FAILED))"
if [ $TESTS_FAILED -eq 0 ]; then
echo -e "\n${GREEN}所有测试通过!${NC}"
exit 0
else
echo -e "\n${RED}存在测试失败,请检查日志${NC}"
exit 1
fi

View File

@@ -0,0 +1,196 @@
### ============================================================
### 个人客户利率定价流程接口测试脚本
### 用途:测试个人客户发起接口的各项场景
### ============================================================
### ============================================================
### 1. 获取测试 Token
### ============================================================
POST http://localhost:63310/login/test
Content-Type: application/json
{
"username": "admin",
"password": "admin123"
}
> {%
client.test("Request executed successfully", function () {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 200, "Response code is 200");
client.global.set("token", response.body.data.token);
});
%}
### ============================================================
### 2. 个人客户发起 - 成功场景(完整必填字段)
### ============================================================
POST http://localhost:63310/loanPricing/workflow/create/personal
Authorization: Bearer {{token}}
Content-Type: application/json
{
"custIsn": "TEST001",
"custName": "张三",
"idType": "身份证",
"idNum": "110101199001011234",
"guarType": "信用",
"applyAmt": "500000",
"bizProof": "true",
"loanLoop": "false"
}
> {%
client.test("Personal loan creation successful", function () {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 200, "Response code is 200");
client.assert(response.body.data.custType === "个人", "Customer type is 个人");
client.assert(response.body.data.serialNum !== null, "Serial number is generated");
});
%}
### ============================================================
### 3. 个人客户发起 - 缺少必填字段 custIsn
### ============================================================
POST http://localhost:63310/loanPricing/workflow/create/personal
Authorization: Bearer {{token}}
Content-Type: application/json
{
"custName": "张三",
"idType": "身份证",
"idNum": "110101199001011234",
"guarType": "信用",
"applyAmt": "500000"
}
> {%
client.test("Missing custIsn validation works", function () {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 500, "Response code indicates validation error");
});
%}
### ============================================================
### 4. 个人客户发起 - 缺少必填字段 guarType
### ============================================================
POST http://localhost:63310/loanPricing/workflow/create/personal
Authorization: Bearer {{token}}
Content-Type: application/json
{
"custIsn": "TEST002",
"custName": "李四",
"idType": "身份证",
"idNum": "110101199001011235",
"applyAmt": "300000"
}
> {%
client.test("Missing guarType validation works", function () {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 500, "Response code indicates validation error");
});
%}
### ============================================================
### 5. 个人客户发起 - 缺少必填字段 applyAmt
### ============================================================
POST http://localhost:63310/loanPricing/workflow/create/personal
Authorization: Bearer {{token}}
Content-Type: application/json
{
"custIsn": "TEST003",
"custName": "王五",
"guarType": "保证",
"idType": "身份证",
"idNum": "110101199001011236"
}
> {%
client.test("Missing applyAmt validation works", function () {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 500, "Response code indicates validation error");
});
%}
### ============================================================
### 6. 个人客户发起 - 担保方式枚举验证失败
### ============================================================
POST http://localhost:63310/loanPricing/workflow/create/personal
Authorization: Bearer {{token}}
Content-Type: application/json
{
"custIsn": "TEST004",
"custName": "赵六",
"idType": "身份证",
"idNum": "110101199001011237",
"guarType": "无效值",
"applyAmt": "200000"
}
> {%
client.test("Invalid guarType validation works", function () {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 500, "Response code indicates validation error");
});
%}
### ============================================================
### 7. 个人客户发起 - 包含抵质押信息
### ============================================================
POST http://localhost:63310/loanPricing/workflow/create/personal
Authorization: Bearer {{token}}
Content-Type: application/json
{
"custIsn": "TEST005",
"custName": "孙七",
"idType": "身份证",
"idNum": "110101199001011238",
"guarType": "抵押",
"applyAmt": "800000",
"bizProof": "true",
"loanLoop": "true",
"collType": "一类",
"collThirdParty": "false"
}
> {%
client.test("Personal loan with collateral info", function () {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 200, "Response code is 200");
client.assert(response.body.data.collType === "一类", "Collateral type is correct");
client.assert(response.body.data.loanLoop === "true", "Loan loop is set");
});
%}
### ============================================================
### 8. 个人客户发起 - 所有字段必填(质押贷款场景)
### ============================================================
POST http://localhost:63310/loanPricing/workflow/create/personal
Authorization: Bearer {{token}}
Content-Type: application/json
{
"custIsn": "TEST006",
"custName": "周八",
"idType": "身份证",
"idNum": "110101199001011239",
"guarType": "质押",
"applyAmt": "100000",
"bizProof": "false",
"loanLoop": "false",
"collType": "二类",
"collThirdParty": "true"
}
> {%
client.test("Personal loan with all required fields", function () {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 200, "Response code is 200");
client.assert(response.body.data.collThirdParty === "true", "Collateral third party is set");
});
%}

View File

@@ -0,0 +1,281 @@
#!/bin/bash
# ============================================================
# 个人客户利率定价流程接口测试脚本
# 用途:测试个人客户发起接口的各项场景
# 使用方法: ./test_personal_create.sh
# ============================================================
# 配置
BASE_URL="http://localhost:63310"
LOGIN_URL="${BASE_URL}/login/test"
PERSONAL_CREATE_URL="${BASE_URL}/loanPricing/workflow/create/personal"
# 颜色输出
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 测试结果统计
TESTS_PASSED=0
TESTS_FAILED=0
# ============================================================
# 函数:打印测试结果
# ============================================================
print_result() {
local test_name="$1"
local result="$2"
if [ "$result" == "0" ]; then
echo -e "${GREEN}${NC} $test_name - ${GREEN}PASSED${NC}"
((TESTS_PASSED++))
else
echo -e "${RED}${NC} $test_name - ${RED}FAILED${NC}"
((TESTS_FAILED++))
fi
}
# ============================================================
# 步骤 1: 获取测试 Token
# ============================================================
echo "=========================================="
echo "步骤 1: 获取测试 Token"
echo "=========================================="
TOKEN_RESPONSE=$(curl -s -X POST "$LOGIN_URL" \
-H "Content-Type: application/json" \
-d '{
"username": "admin",
"password": "admin123"
}')
TOKEN=$(echo "$TOKEN_RESPONSE" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
if [ -z "$TOKEN" ]; then
echo -e "${RED}获取 Token 失败${NC}"
echo "响应: $TOKEN_RESPONSE"
exit 1
fi
echo -e "${GREEN}Token 获取成功${NC}"
echo "Token: ${TOKEN:0:20}..."
# ============================================================
# 步骤 2: 测试个人客户发起 - 成功场景
# ============================================================
echo ""
echo "=========================================="
echo "步骤 2: 测试个人客户发起 - 成功场景"
echo "=========================================="
RESPONSE=$(curl -s -X POST "$PERSONAL_CREATE_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"custIsn": "TEST001",
"custName": "张三",
"idType": "身份证",
"idNum": "110101199001011234",
"guarType": "信用",
"applyAmt": "500000",
"bizProof": "true",
"loanLoop": "false"
}')
# 检查响应
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | cut -d':' -f2)
CUST_TYPE=$(echo "$RESPONSE" | grep -o '"custType":"[^"]*"' | cut -d'"' -f4)
SERIAL_NUM=$(echo "$RESPONSE" | grep -o '"serialNum":"[^"]*"' | cut -d'"' -f4)
if [ "$CODE" == "200" ] && [ "$CUST_TYPE" == "个人" ] && [ -n "$SERIAL_NUM" ]; then
print_result "个人客户发起成功场景" "0"
echo " 业务流水号: $SERIAL_NUM"
else
print_result "个人客户发起成功场景" "1"
echo " 响应: $RESPONSE"
fi
# ============================================================
# 步骤 3: 测试缺少必填字段 custIsn
# ============================================================
echo ""
echo "=========================================="
echo "步骤 3: 测试缺少必填字段 custIsn"
echo "=========================================="
RESPONSE=$(curl -s -X POST "$PERSONAL_CREATE_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"custName": "李四",
"idType": "身份证",
"idNum": "110101199001011235",
"guarType": "信用",
"applyAmt": "300000"
}')
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | cut -d':' -f2)
if [ "$CODE" == "500" ]; then
print_result "缺少 custIsn 字段验证" "0"
else
print_result "缺少 custIsn 字段验证" "1"
fi
# ============================================================
# 步骤 4: 测试缺少必填字段 guarType
# ============================================================
echo ""
echo "=========================================="
echo "步骤 4: 测试缺少必填字段 guarType"
echo "=========================================="
RESPONSE=$(curl -s -X POST "$PERSONAL_CREATE_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"custIsn": "TEST002",
"custName": "王五",
"idType": "身份证",
"idNum": "110101199001011236",
"applyAmt": "300000"
}')
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | cut -d':' -f2)
if [ "$CODE" == "500" ]; then
print_result "缺少 guarType 字段验证" "0"
else
print_result "缺少 guarType 字段验证" "1"
fi
# ============================================================
# 步骤 5: 测试缺少必填字段 applyAmt
# ============================================================
echo ""
echo "=========================================="
echo "步骤 5: 测试缺少必填字段 applyAmt"
echo "=========================================="
RESPONSE=$(curl -s -X POST "$PERSONAL_CREATE_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"custIsn": "TEST003",
"custName": "赵六",
"idType": "身份证",
"idNum": "110101199001011237",
"guarType": "保证"
}')
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | cut -d':' -f2)
if [ "$CODE" == "500" ]; then
print_result "缺少 applyAmt 字段验证" "0"
else
print_result "缺少 applyAmt 字段验证" "1"
fi
# ============================================================
# 步骤 6: 测试担保方式枚举验证失败
# ============================================================
echo ""
echo "=========================================="
echo "步骤 6: 测试担保方式枚举验证失败"
echo "=========================================="
RESPONSE=$(curl -s -X POST "$PERSONAL_CREATE_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"custIsn": "TEST004",
"custName": "孙七",
"idType": "身份证",
"idNum": "110101199001011238",
"guarType": "无效值",
"applyAmt": "200000"
}')
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | cut -d':' -f2)
if [ "$CODE" == "500" ]; then
print_result "担保方式枚举验证" "0"
else
print_result "担保方式枚举验证" "1"
fi
# ============================================================
# 步骤 7: 测试包含抵质押信息
# ============================================================
echo ""
echo "=========================================="
echo "步骤 7: 测试包含抵质押信息"
echo "=========================================="
RESPONSE=$(curl -s -X POST "$PERSONAL_CREATE_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"custIsn": "TEST005",
"custName": "周八",
"idType": "身份证",
"idNum": "110101199001011239",
"guarType": "抵押",
"applyAmt": "800000",
"bizProof": "true",
"loanLoop": "true",
"collType": "一类",
"collThirdParty": "false"
}')
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | cut -d':' -f2)
COLL_TYPE=$(echo "$RESPONSE" | grep -o '"collType":"[^"]*"' | cut -d'"' -f4)
LOAN_LOOP=$(echo "$RESPONSE" | grep -o '"loanLoop":"[^"]*"' | cut -d'"' -f4)
if [ "$CODE" == "200" ] && [ "$COLL_TYPE" == "一类" ] && [ "$LOAN_LOOP" == "true" ]; then
print_result "抵质押信息正确保存" "0"
echo " 抵质押类型: $COLL_TYPE"
echo " 循环功能: $LOAN_LOOP"
else
print_result "抵质押信息正确保存" "1"
fi
# ============================================================
# 步骤 8: 测试最小必填字段
# ============================================================
echo ""
echo "=========================================="
echo "步骤 8: 测试最小必填字段"
echo "=========================================="
RESPONSE=$(curl -s -X POST "$PERSONAL_CREATE_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"custIsn": "TEST006",
"guarType": "质押",
"applyAmt": "100000"
}')
CODE=$(echo "$RESPONSE" | grep -o '"code":[0-9]*' | cut -d':' -f2)
if [ "$CODE" == "200" ]; then
print_result "最小必填字段测试" "0"
else
print_result "最小必填字段测试" "1"
fi
# ============================================================
# 测试结果汇总
# ============================================================
echo ""
echo "=========================================="
echo "测试结果汇总"
echo "=========================================="
echo -e "${GREEN}通过: $TESTS_PASSED${NC}"
echo -e "${RED}失败: $TESTS_FAILED${NC}"
echo "总计: $((TESTS_PASSED + TESTS_FAILED))"
if [ $TESTS_FAILED -eq 0 ]; then
echo -e "\n${GREEN}所有测试通过!${NC}"
exit 0
else
echo -e "\n${RED}存在测试失败,请检查日志${NC}"
exit 1
fi