Compare commits
68 Commits
7c563b3315
...
892-withou
| Author | SHA1 | Date | |
|---|---|---|---|
| 83e1959b94 | |||
| 0164b5eb33 | |||
| 986383b347 | |||
| 21cde7c131 | |||
| f11d20abc7 | |||
| eef36ff0ec | |||
| 998f0b3c48 | |||
| cc6836804e | |||
| a6e7ef6105 | |||
| a4f927fdcb | |||
| a22c83ba7e | |||
| 9ed6fc2d38 | |||
| 6ef3cfcaea | |||
| a50b25e4ec | |||
| 947c955415 | |||
| 6c949fee40 | |||
| 366d8e499a | |||
| abc8b127e1 | |||
| 709a314107 | |||
| e4a8cf4a13 | |||
| 938f9bb28e | |||
| 764bc7f363 | |||
| 01a0bee5c0 | |||
| 761b9a0612 | |||
| 801310ee10 | |||
| 289937f264 | |||
| 6081ee87f2 | |||
| ade4100aeb | |||
| 33365a0d74 | |||
| b8b8d21b09 | |||
| 6a91cd7ea6 | |||
| ca19ba754d | |||
| 0f2a22f2c6 | |||
| 6dbd56dc52 | |||
| 86b8704ad9 | |||
| 3180c33500 | |||
| a90ab42be6 | |||
| ddf2f976f5 | |||
| 886cba6b3d | |||
| 571f7bc075 | |||
| 5a80653917 | |||
| bfe1b346d9 | |||
| ef40675422 | |||
| f1e4b26800 | |||
| 235672304a | |||
| ec4a7c09db | |||
| 5839a76f87 | |||
| fa0b446699 | |||
| f001047d0c | |||
| 09707d312e | |||
| 8e6eb5b382 | |||
| 62784ee81a | |||
| 1e9340bbda | |||
| 541969a837 | |||
| 351fae8cd3 | |||
| 54eabaebd8 | |||
| f874e2d942 | |||
| 9b35d04e50 | |||
| 3a8f37f547 | |||
| f8b2bf2afc | |||
| 3ce3c438a9 | |||
| db5735897d | |||
| 99cdaacf10 | |||
| 0f9c9b30cd | |||
| 14e72f0e5e | |||
| 1c2171ba24 | |||
| d96b8a8740 | |||
| 4db4f542a5 |
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"permissions": {
|
|
||||||
"allow": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"permissions": {
|
|
||||||
"allow": [
|
|
||||||
"Bash(java:*)",
|
|
||||||
"Bash(binrun.bat:*)",
|
|
||||||
"Bash(mvn clean package:*)",
|
|
||||||
"Bash(curl:*)",
|
|
||||||
"Bash(pkill:*)",
|
|
||||||
"Bash(bash:*)",
|
|
||||||
"Bash(pip install:*)",
|
|
||||||
"Bash(findstr:*)",
|
|
||||||
"Bash(chcp:*)",
|
|
||||||
"Bash(cmd.exe:*)",
|
|
||||||
"Bash(powershell -Command:*)",
|
|
||||||
"Bash(git add:*)",
|
|
||||||
"Bash(cd:*)",
|
|
||||||
"mcp__zai-mcp-server__extract_text_from_screenshot",
|
|
||||||
"Bash(mvn test:*)",
|
|
||||||
"Bash(mvn install:*)",
|
|
||||||
"Bash(mvn clean install:*)",
|
|
||||||
"mcp__web-reader__webReader",
|
|
||||||
"Skill(superpowers:brainstorming)",
|
|
||||||
"Skill(superpowers:writing-plans)",
|
|
||||||
"Skill(superpowers:executing-plans)"
|
|
||||||
],
|
|
||||||
"additionalDirectories": [
|
|
||||||
"d:\\利率定价\\loan-pricing-892\\loan-pricing-892-v2.0"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"paths": {
|
|
||||||
"specs": ".claude/specs",
|
|
||||||
"steering": ".claude/steering",
|
|
||||||
"settings": ".claude/settings"
|
|
||||||
},
|
|
||||||
"views": {
|
|
||||||
"specs": {
|
|
||||||
"visible": true
|
|
||||||
},
|
|
||||||
"steering": {
|
|
||||||
"visible": true
|
|
||||||
},
|
|
||||||
"mcp": {
|
|
||||||
"visible": true
|
|
||||||
},
|
|
||||||
"hooks": {
|
|
||||||
"visible": true
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"visible": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -48,3 +48,16 @@ nbdist/
|
|||||||
|
|
||||||
|
|
||||||
logs/
|
logs/
|
||||||
|
ruoyi-ui/dist.zip
|
||||||
|
????????_892.zip
|
||||||
|
*/src/test/
|
||||||
|
ruoyi-ui/tests
|
||||||
|
.playwright-cli
|
||||||
|
|
||||||
|
tongweb_63310.properties
|
||||||
|
audit.log
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
*/.DS_Store
|
||||||
|
|
||||||
|
.codegraph/
|
||||||
30
AGENTS.md
30
AGENTS.md
@@ -1,30 +0,0 @@
|
|||||||
# AGENTS.md - AI Coding Assistant Guide
|
|
||||||
|
|
||||||
## GIT
|
|
||||||
- git提交时使用中文添加描述
|
|
||||||
- 无视`.DS_Store`
|
|
||||||
|
|
||||||
## AGENT
|
|
||||||
- 不开启subagent
|
|
||||||
|
|
||||||
## 文档
|
|
||||||
- 根据设计文档产出前后端项目的实施计划时,输出两份执行文档,一份为后端的实施计划,一份为前端的实施计划
|
|
||||||
- 每一次改动都需要留下实施文档,记录修改的内容
|
|
||||||
- 每次写设计文档的时候,都要检查一下保存路径是否正确
|
|
||||||
|
|
||||||
## 测试
|
|
||||||
- 开发完成后必须执行与本次改动直接对应的验证步骤,完成验证后才能结束本次任务
|
|
||||||
- 如果是接口开发完成,先重启后端进程,确保最新代码已经生效,再调用接口进行测试
|
|
||||||
- 接口测试时必须覆盖多种情况,至少包含正常场景、必填/参数错误场景、分支场景;如接口逻辑包含状态、类型、金额、期限等关键条件,需要分别验证对应分支
|
|
||||||
- 如果是前端页面开发完成,必须启动前端页面并调用浏览器检查功能是否正常,确认页面展示、交互流程、接口联动和关键提示信息符合预期
|
|
||||||
- 测试结束后,自动结束测试时开启的前后端进程
|
|
||||||
|
|
||||||
## 开发
|
|
||||||
- 在开发前端的时候,不需要使用git worktree,直接在当前分支进行开发
|
|
||||||
|
|
||||||
## 方案规范
|
|
||||||
- 当需要你给出方案时,必须符合以下规范
|
|
||||||
- 不允许给出兼容性或补丁性的方案
|
|
||||||
- 不允许过度设计,保持最短路径实现,且不能违反上一条要求
|
|
||||||
- 不允许自行给出我提供的需求以外的方案,例如一些兜底和降级方案,这可能导致业务逻辑偏移问题
|
|
||||||
- 必须确保方案的逻辑正确,必须经过全链路的逻辑验证
|
|
||||||
BIN
.DS_Store → bin/.DS_Store
vendored
BIN
.DS_Store → bin/.DS_Store
vendored
Binary file not shown.
257
bin/prod/deploy_from_package.sh
Executable file
257
bin/prod/deploy_from_package.sh
Executable 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 "$@"
|
||||||
262
bin/prod/deploy_from_package_test.sh
Executable file
262
bin/prod/deploy_from_package_test.sh
Executable 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
245
bin/prod/deploy_release.sh
Executable 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
244
bin/prod/install_env.sh
Executable 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
208
bin/prod/restart_java.sh
Executable 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 "$@"
|
||||||
116
bin/prod/restart_java_test.sh
Normal file
116
bin/prod/restart_java_test.sh
Normal 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 "$@"
|
||||||
@@ -83,7 +83,16 @@ collect_pids() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
marker_pids=$(pgrep -f "$APP_MARKER" 2>/dev/null || true)
|
marker_pids=$(ps -ef | awk -v marker="$APP_MARKER" -v jar="$JAR_NAME" '
|
||||||
|
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
|
if [ -n "${marker_pids:-}" ]; then
|
||||||
for pid in $marker_pids; do
|
for pid in $marker_pids; do
|
||||||
if is_managed_backend_pid "$pid"; then
|
if is_managed_backend_pid "$pid"; then
|
||||||
@@ -225,7 +234,6 @@ restart_action() {
|
|||||||
main() {
|
main() {
|
||||||
ensure_command mvn
|
ensure_command mvn
|
||||||
ensure_command lsof
|
ensure_command lsof
|
||||||
ensure_command pgrep
|
|
||||||
ensure_command ps
|
ensure_command ps
|
||||||
ensure_command tail
|
ensure_command tail
|
||||||
|
|
||||||
|
|||||||
42
bin/restart_java_backend_test.sh
Normal file
42
bin/restart_java_backend_test.sh
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT_DIR=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
|
||||||
|
SCRIPT_UNDER_TEST="$ROOT_DIR/bin/restart_java_backend.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
|
||||||
|
}
|
||||||
|
|
||||||
|
test_script_contract() {
|
||||||
|
assert_grep 'ps -ef' "$SCRIPT_UNDER_TEST"
|
||||||
|
assert_not_grep 'pgrep' "$SCRIPT_UNDER_TEST"
|
||||||
|
assert_grep 'APP_MARKER=' "$SCRIPT_UNDER_TEST"
|
||||||
|
assert_grep 'status_backend\(\)' "$SCRIPT_UNDER_TEST"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
[ -f "$SCRIPT_UNDER_TEST" ] || fail "script under test not found: $SCRIPT_UNDER_TEST"
|
||||||
|
test_script_contract
|
||||||
|
printf 'PASS: restart_java_backend tests\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
95
build_release_892.sh
Executable file
95
build_release_892.sh
Executable file
@@ -0,0 +1,95 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
|
||||||
|
DATE_STAMP=$(date "+%Y%m%d")
|
||||||
|
RELEASE_ZIP="$ROOT_DIR/${DATE_STAMP}_892.zip"
|
||||||
|
BACKEND_JAR_SOURCE="$ROOT_DIR/ruoyi-admin/target/ruoyi-admin.jar"
|
||||||
|
FRONTEND_DIR="$ROOT_DIR/ruoyi-ui"
|
||||||
|
FRONTEND_DIST_DIR="$FRONTEND_DIR/dist"
|
||||||
|
FRONTEND_DIST_ZIP="$FRONTEND_DIR/dist.zip"
|
||||||
|
NODE_VERSION="14"
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
require_command() {
|
||||||
|
if ! command -v "$1" >/dev/null 2>&1; then
|
||||||
|
log_error "缺少命令: $1"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [ -n "${WORK_DIR:-}" ] && [ -d "$WORK_DIR" ]; then
|
||||||
|
rm -rf "$WORK_DIR"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
build_backend() {
|
||||||
|
log_info "开始构建后端生产 jar"
|
||||||
|
(
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
mvn -pl ruoyi-admin -am clean package -DskipTests
|
||||||
|
)
|
||||||
|
|
||||||
|
if [ ! -f "$BACKEND_JAR_SOURCE" ]; then
|
||||||
|
log_error "未生成后端 jar: $BACKEND_JAR_SOURCE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
build_frontend() {
|
||||||
|
log_info "开始构建前端生产 dist"
|
||||||
|
ROOT_DIR="$ROOT_DIR" NODE_VERSION="$NODE_VERSION" zsh -lic 'nvm use "$NODE_VERSION" >/dev/null && npm --prefix "$ROOT_DIR/ruoyi-ui" run build:prod'
|
||||||
|
|
||||||
|
if [ ! -f "$FRONTEND_DIST_DIR/index.html" ]; then
|
||||||
|
log_error "前端生产构建失败,未找到: $FRONTEND_DIST_DIR/index.html"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$FRONTEND_DIST_ZIP"
|
||||||
|
(
|
||||||
|
cd "$FRONTEND_DIR"
|
||||||
|
zip -qr "$FRONTEND_DIST_ZIP" dist
|
||||||
|
)
|
||||||
|
|
||||||
|
if [ ! -f "$FRONTEND_DIST_ZIP" ]; then
|
||||||
|
log_error "未生成前端压缩包: $FRONTEND_DIST_ZIP"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
package_release() {
|
||||||
|
WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/loan_pricing_release.XXXXXX")
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
cp "$BACKEND_JAR_SOURCE" "$WORK_DIR/ruoyi-admin.jar"
|
||||||
|
cp "$FRONTEND_DIST_ZIP" "$WORK_DIR/dist.zip"
|
||||||
|
|
||||||
|
rm -f "$RELEASE_ZIP"
|
||||||
|
(
|
||||||
|
cd "$WORK_DIR"
|
||||||
|
zip -qr "$RELEASE_ZIP" ruoyi-admin.jar dist.zip
|
||||||
|
)
|
||||||
|
|
||||||
|
log_info "上线压缩包已生成: $RELEASE_ZIP"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
require_command mvn
|
||||||
|
require_command zsh
|
||||||
|
require_command zip
|
||||||
|
|
||||||
|
build_backend
|
||||||
|
build_frontend
|
||||||
|
package_release
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
190
deploy/2026-03-31-local-nginx-java-install-manual.md
Normal file
190
deploy/2026-03-31-local-nginx-java-install-manual.md
Normal 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
BIN
deploy/deploy.zip
Normal file
Binary file not shown.
45
deploy/nginx.conf
Normal file
45
deploy/nginx.conf
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
84
doc/2026-04-03-retail-display-fields-backend-plan.md
Normal file
84
doc/2026-04-03-retail-display-fields-backend-plan.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# 个人模型详情缺失展示字段补齐后端实施计划
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED: Use superpowers:executing-plans to implement this plan in this repository. Do not use subagents. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** 补齐个人模型输出对象与零售模型输出表结构的新增字段,确保个人详情接口可承载并持久化最新展示字段。
|
||||||
|
|
||||||
|
**Architecture:** 后端补齐 `ModelRetailOutputFields` 实体字段,并为 `model_retail_output_fields` 表增加 5 个对应列。保持现有控制器、服务与 Mapper 链路不变,让模型返回字段按既有流程直接反序列化、入库并回查。
|
||||||
|
|
||||||
|
**Tech Stack:** Java 17、Spring Boot、MyBatis Plus、Maven、JUnit 5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: 通过测试锁定缺失字段
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/domain/entity/ModelRetailOutputFieldsTest.java`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 编写实体字段断言测试**
|
||||||
|
|
||||||
|
新增测试,断言 `ModelRetailOutputFields` 包含以下字段:
|
||||||
|
|
||||||
|
```java
|
||||||
|
"loanRateHistory",
|
||||||
|
"minRateProduct",
|
||||||
|
"smoothRange",
|
||||||
|
"finalCalculateRate",
|
||||||
|
"referenceRate"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 运行测试并确认先失败**
|
||||||
|
|
||||||
|
Run: `mvn -pl ruoyi-loan-pricing -Dtest=ModelRetailOutputFieldsTest test`
|
||||||
|
Expected: FAIL,提示缺少新增字段。
|
||||||
|
|
||||||
|
### Task 2: 补齐个人模型输出实体字段
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/entity/ModelRetailOutputFields.java`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 在实体中新增 5 个字段**
|
||||||
|
|
||||||
|
新增以下字段:
|
||||||
|
|
||||||
|
- `loanRateHistory`
|
||||||
|
- `minRateProduct`
|
||||||
|
- `smoothRange`
|
||||||
|
- `finalCalculateRate`
|
||||||
|
- `referenceRate`
|
||||||
|
|
||||||
|
- [ ] **Step 2: 保持现有命名与注释风格一致**
|
||||||
|
|
||||||
|
新增字段使用与现有实体一致的 `private String` 定义和中文注释,不引入额外注解。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 重新运行测试确认通过**
|
||||||
|
|
||||||
|
Run: `mvn -pl ruoyi-loan-pricing -Dtest=ModelRetailOutputFieldsTest test`
|
||||||
|
Expected: PASS
|
||||||
|
|
||||||
|
### Task 3: 补齐零售模型输出表结构
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `sql/add_model_retail_output_rate_fields_20260403.sql`
|
||||||
|
- Modify: `sql/model_retail.sql`
|
||||||
|
- Modify: `sql/loan_pricing_schema_20260328.sql`
|
||||||
|
- Modify: `sql/loan_pricing_prod_init_20260331.sql`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 新增数据库迁移脚本**
|
||||||
|
|
||||||
|
在迁移脚本中为 `model_retail_output_fields` 增加以下列:
|
||||||
|
|
||||||
|
- `loan_rate_history`
|
||||||
|
- `min_rate_product`
|
||||||
|
- `smooth_range`
|
||||||
|
- `final_calculate_rate`
|
||||||
|
- `reference_rate`
|
||||||
|
|
||||||
|
- [ ] **Step 2: 同步更新建表基线 SQL**
|
||||||
|
|
||||||
|
将相同列同步到仓库中的零售模型输出表建表脚本,避免新环境继续缺列。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 对开发库执行迁移并验证列存在**
|
||||||
|
|
||||||
|
Run: `mysql ... < sql/add_model_retail_output_rate_fields_20260403.sql`
|
||||||
|
Expected: 迁移执行成功,`SHOW COLUMNS FROM model_retail_output_fields` 可看到新增 5 列
|
||||||
113
doc/2026-04-03-retail-display-fields-design.md
Normal file
113
doc/2026-04-03-retail-display-fields-design.md
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# 个人模型详情缺失展示字段补齐设计文档
|
||||||
|
|
||||||
|
## 1. 背景
|
||||||
|
|
||||||
|
个人模型接口返回字段有更新。根据 `doc/上虞对私利率测算_上传字段与展示字段.xlsx` 的 `展示指标` sheet,当前个人详情页仍缺少部分应展示字段,需要补齐页面展示并保证接口链路字段完整。
|
||||||
|
|
||||||
|
## 2. 已确认范围
|
||||||
|
|
||||||
|
- 仅处理个人客户详情页
|
||||||
|
- 仅补齐 `展示指标` sheet 中当前缺失的 6 个字段
|
||||||
|
- 不调整企业客户页面
|
||||||
|
- 不新增兼容逻辑、兜底逻辑或额外展示区域
|
||||||
|
- 保持现有页面结构和分组,按最短路径补齐
|
||||||
|
|
||||||
|
## 3. 缺失字段
|
||||||
|
|
||||||
|
经核对,当前页面缺少以下字段:
|
||||||
|
|
||||||
|
- `loanTerm` 借款期限
|
||||||
|
- `loanRateHistory` 历史利率
|
||||||
|
- `minRateProduct` 产品最低利率下限
|
||||||
|
- `smoothRange` 平滑幅度
|
||||||
|
- `finalCalculateRate` 最终测算利率
|
||||||
|
- `referenceRate` 参考利率
|
||||||
|
|
||||||
|
其中:
|
||||||
|
|
||||||
|
- `loanTerm` 属于流程明细字段,来自 `LoanPricingWorkflow`
|
||||||
|
- 其余 5 个字段属于个人模型输出字段,来自 `ModelRetailOutputFields`
|
||||||
|
- 其余 5 个字段同时要求 `model_retail_output_fields` 表具备对应列,否则新流程详情无法完整落库展示
|
||||||
|
|
||||||
|
## 4. 现状分析
|
||||||
|
|
||||||
|
### 4.1 前端现状
|
||||||
|
|
||||||
|
个人详情页由两个主要区域组成:
|
||||||
|
|
||||||
|
- `PersonalWorkflowDetail.vue` 负责流程详情与左侧关键信息
|
||||||
|
- `ModelOutputDisplay.vue` 负责个人模型输出分组展示
|
||||||
|
|
||||||
|
当前页面已覆盖大部分 `展示指标` 字段,但个人详情页“业务信息”中未展示 `loanTerm`,个人模型输出“测算结果”中也未展示 5 个新增利率相关字段。
|
||||||
|
|
||||||
|
### 4.2 后端现状
|
||||||
|
|
||||||
|
- `LoanPricingWorkflow` 已包含 `loanTerm`
|
||||||
|
- `ModelRetailOutputFields` 当前未包含 `loanRateHistory`、`minRateProduct`、`smoothRange`、`finalCalculateRate`、`referenceRate`
|
||||||
|
|
||||||
|
因此前端目前无法从个人模型输出对象中读取这 5 个字段。
|
||||||
|
|
||||||
|
同时,开发库 `model_retail_output_fields` 表当前也未包含这 5 个字段列。如果只补代码而不补表结构,新的个人流程在模型结果入库时将无法完整保存这些字段。
|
||||||
|
|
||||||
|
## 5. 方案对比
|
||||||
|
|
||||||
|
### 方案一:在现有分组内补齐字段
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 在个人详情页“业务信息”区域补 `loanTerm`
|
||||||
|
- 在个人模型输出“测算结果”区域补 5 个新增利率字段
|
||||||
|
- 后端仅补 `ModelRetailOutputFields` 缺失字段定义
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 改动最小
|
||||||
|
- 不影响现有页面结构
|
||||||
|
- 与现有字段分组最贴合
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 需要同时修改前后端
|
||||||
|
|
||||||
|
### 方案二:单独新增“利率结果扩展”分组
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 新增一个专门的 Tab 或卡片展示 5 个新增利率字段
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 新增字段集中展示
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 页面改动更大
|
||||||
|
- 用户认知路径变化
|
||||||
|
- 不符合本次最短路径要求
|
||||||
|
|
||||||
|
## 6. 设计结论
|
||||||
|
|
||||||
|
采用方案一。
|
||||||
|
|
||||||
|
实现方式如下:
|
||||||
|
|
||||||
|
- 后端在 `ModelRetailOutputFields` 中新增 5 个字段定义,保证接口对象具备完整返回结构
|
||||||
|
- 数据库为 `model_retail_output_fields` 新增 5 个对应列,保证模型输出可正常落库
|
||||||
|
- 前端在 `PersonalWorkflowDetail.vue` 的“业务信息”中补齐 `loanTerm`
|
||||||
|
- 前端在 `ModelOutputDisplay.vue` 的个人“测算结果”中补齐 `loanRateHistory`、`minRateProduct`、`smoothRange`、`finalCalculateRate`、`referenceRate`
|
||||||
|
|
||||||
|
## 7. 验证设计
|
||||||
|
|
||||||
|
本次按最小可执行验证:
|
||||||
|
|
||||||
|
- 后端新增一个实体字段断言测试,先验证缺失字段不存在并失败,再补齐后验证通过
|
||||||
|
- 前端新增一个源码断言脚本,先验证缺失展示未实现并失败,再补齐后验证通过
|
||||||
|
- 对开发库执行表结构迁移
|
||||||
|
- 创建新的个人流程并打开详情页,确认新增字段可在真实页面展示
|
||||||
|
- 最后执行前端生产构建,确认页面代码可正常打包
|
||||||
|
|
||||||
|
## 8. 非目标
|
||||||
|
|
||||||
|
- 不调整企业详情页
|
||||||
|
- 不修改模型计算逻辑
|
||||||
|
- 不重构页面布局
|
||||||
81
doc/2026-04-03-retail-display-fields-frontend-plan.md
Normal file
81
doc/2026-04-03-retail-display-fields-frontend-plan.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# 个人模型详情缺失展示字段补齐前端实施计划
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED: Use superpowers:executing-plans to implement this plan in this repository. Do not use subagents. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** 在个人详情页补齐 `展示指标` sheet 中缺失的 6 个字段展示。
|
||||||
|
|
||||||
|
**Architecture:** 前端沿用现有个人详情页结构,在流程详情的“业务信息”中补 `loanTerm`,在模型输出“测算结果”中补 5 个新增利率字段,不新增页面分组和交互。
|
||||||
|
|
||||||
|
**Tech Stack:** Vue 2、Element UI、Node.js
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: 通过前端断言测试锁定缺失展示
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `ruoyi-ui/tests/retail-display-fields.test.js`
|
||||||
|
- Modify: `ruoyi-ui/package.json`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 编写源码断言脚本**
|
||||||
|
|
||||||
|
断言以下展示已存在:
|
||||||
|
|
||||||
|
- `PersonalWorkflowDetail.vue` 包含 `借款期限` 和 `detailData.loanTerm`
|
||||||
|
- `ModelOutputDisplay.vue` 包含以下字段展示:
|
||||||
|
- `loanRateHistory`
|
||||||
|
- `minRateProduct`
|
||||||
|
- `smoothRange`
|
||||||
|
- `finalCalculateRate`
|
||||||
|
- `referenceRate`
|
||||||
|
|
||||||
|
- [ ] **Step 2: 运行脚本并确认先失败**
|
||||||
|
|
||||||
|
Run: `npm --prefix ruoyi-ui run test:retail-display-fields`
|
||||||
|
Expected: FAIL,提示缺失展示实现。
|
||||||
|
|
||||||
|
### Task 2: 补齐个人详情页字段展示
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue`
|
||||||
|
- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue`
|
||||||
|
- Reference: `ruoyi-loan-pricing/src/main/resources/data/retail_output.json`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 在业务信息中补齐借款期限**
|
||||||
|
|
||||||
|
在 `PersonalWorkflowDetail.vue` 的“业务信息”区域新增:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<el-descriptions-item label="借款期限">{{ detailData.loanTerm || '-' }}</el-descriptions-item>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 在个人测算结果中补齐 5 个字段**
|
||||||
|
|
||||||
|
在 `ModelOutputDisplay.vue` 的个人“测算结果”中新增:
|
||||||
|
|
||||||
|
- 历史利率
|
||||||
|
- 产品最低利率下限
|
||||||
|
- 平滑幅度
|
||||||
|
- 最终测算利率
|
||||||
|
- 参考利率
|
||||||
|
|
||||||
|
- [ ] **Step 3: 重新运行前端断言脚本**
|
||||||
|
|
||||||
|
Run: `npm --prefix ruoyi-ui run test:retail-display-fields`
|
||||||
|
Expected: PASS
|
||||||
|
|
||||||
|
- [ ] **Step 4: 执行前端构建验证**
|
||||||
|
|
||||||
|
Run: `npm --prefix ruoyi-ui run build:prod`
|
||||||
|
Expected: 构建成功,输出包含 `Build complete.`
|
||||||
|
|
||||||
|
- [ ] **Step 5: 启动前后端并打开个人流程详情页验证**
|
||||||
|
|
||||||
|
使用浏览器打开新的个人流程详情页,确认:
|
||||||
|
|
||||||
|
- 流程详情“业务信息”出现 `借款期限`
|
||||||
|
- 模型输出“测算结果”出现并可查看以下字段
|
||||||
|
- 历史利率
|
||||||
|
- 产品最低利率下限
|
||||||
|
- 平滑幅度
|
||||||
|
- 最终测算利率
|
||||||
|
- 参考利率
|
||||||
238
doc/2026-04-09-shangyu-retail-input-params-backend-plan.md
Normal file
238
doc/2026-04-09-shangyu-retail-input-params-backend-plan.md
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
# 上虞个人利率测算输入参数后端 Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** 补齐个人创建 DTO、流程转换和模型调用参数,使个人模型请求完整覆盖 Excel 要求的输入字段。
|
||||||
|
|
||||||
|
**Architecture:** 维持现有“页面提交 -> DTO -> Workflow -> ModelInvokeDTO -> form-urlencoded 调用”的链路,只在个人创建与模型调用相关文件中补齐字段和转换规则。通过后端单元测试与真实接口联调覆盖必填、正常和分支场景。
|
||||||
|
|
||||||
|
**Tech Stack:** Spring Boot、MyBatis-Plus、Lombok、JUnit、Maven
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 后端模型输入参数确认
|
||||||
|
|
||||||
|
个人链路最终需要发给模型的 16 个参数如下:
|
||||||
|
|
||||||
|
- `serialNum`:服务层自动生成
|
||||||
|
- `orgCode`:服务层默认值,当前代码为 `892000`
|
||||||
|
- `runType`:服务层默认值 `1`
|
||||||
|
- `custIsn`:页面输入透传
|
||||||
|
- `custType`:个人链路固定 `个人`
|
||||||
|
- `custName`:页面输入,调用模型前解密后透传
|
||||||
|
- `idType`:页面输入透传
|
||||||
|
- `idNum`:页面输入,调用模型前解密后透传
|
||||||
|
- `guarType`:页面输入透传
|
||||||
|
- `applyAmt`:页面输入透传
|
||||||
|
- `loanPurpose`:页面输入透传
|
||||||
|
- `loanTerm`:页面输入透传
|
||||||
|
- `bizProof`:页面开关,调用模型前转 `0/1`
|
||||||
|
- `loanLoop`:页面开关,调用模型前转 `0/1`
|
||||||
|
- `collThirdParty`:页面开关,调用模型前转 `0/1`
|
||||||
|
- `collType`:页面下拉透传
|
||||||
|
|
||||||
|
调用方式确认:
|
||||||
|
|
||||||
|
- 参数载体:`ModelInvokeDTO`
|
||||||
|
- 组装方式:`BeanUtils.copyProperties(loanPricingWorkflow, modelInvokeDTO)`
|
||||||
|
- 请求格式:`application/x-www-form-urlencoded`
|
||||||
|
- 发送入口:`ModelService#invokeModel`
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
- Modify: [ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java)
|
||||||
|
- 增加 `loanPurpose`、`loanTerm`
|
||||||
|
- Modify: [ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java)
|
||||||
|
- 将新增字段映射到 `LoanPricingWorkflow`
|
||||||
|
- Modify: [ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java)
|
||||||
|
- 增加 `loanTerm`、`loanLoop`
|
||||||
|
- Modify: [ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java)
|
||||||
|
- 在个人模型调用前规范化 `0/1`
|
||||||
|
- Create: [ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java)
|
||||||
|
- 覆盖字段存在与值转换
|
||||||
|
|
||||||
|
### Task 1: 为新增字段补失败测试
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 编写字段与转换测试**
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Test
|
||||||
|
void shouldContainLoanPurposeLoanTermAndLoanLoop() {
|
||||||
|
assertThat(ModelInvokeDTO.class.getDeclaredField("loanTerm")).isNotNull();
|
||||||
|
assertThat(ModelInvokeDTO.class.getDeclaredField("loanLoop")).isNotNull();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Test
|
||||||
|
void shouldConvertPersonalBooleanFlagsToZeroOne() {
|
||||||
|
// 构造个人流程对象,断言模型请求中的 bizProof/loanLoop/collThirdParty 为 1/0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 运行测试并确认先失败**
|
||||||
|
|
||||||
|
Run: `mvn -pl ruoyi-loan-pricing -Dtest=LoanPricingModelServicePersonalParamsTest test`
|
||||||
|
|
||||||
|
Expected: FAIL,提示字段不存在或转换逻辑未实现
|
||||||
|
|
||||||
|
- [ ] **Step 3: 保持测试只覆盖本次改动相关链路**
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 仅断言 PersonalLoanPricingCreateDTO / LoanPricingConverter / ModelInvokeDTO / LoanPricingModelService
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 增加最终模型请求参数断言**
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 断言 requestBody 包含 serialNum、orgCode、runType、custIsn、custType、custName、
|
||||||
|
// idType、idNum、guarType、applyAmt、loanPurpose、loanTerm、bizProof、
|
||||||
|
// loanLoop、collThirdParty、collType
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: 再次运行测试确认失败原因稳定**
|
||||||
|
|
||||||
|
Run: `mvn -pl ruoyi-loan-pricing -Dtest=LoanPricingModelServicePersonalParamsTest test`
|
||||||
|
|
||||||
|
Expected: FAIL,失败点与新增字段缺失一致
|
||||||
|
|
||||||
|
- [ ] **Step 6: 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java
|
||||||
|
git commit -m "新增个人测算输入参数后端测试"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 2: 补齐个人创建 DTO 与流程映射
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java`
|
||||||
|
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 在个人 DTO 中增加 `loanPurpose`**
|
||||||
|
|
||||||
|
```java
|
||||||
|
@NotBlank(message = "贷款用途不能为空")
|
||||||
|
@Pattern(regexp = "^(consumer|business)$", message = "贷款用途必须是 consumer 或 business")
|
||||||
|
private String loanPurpose;
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 在个人 DTO 中增加 `loanTerm`**
|
||||||
|
|
||||||
|
```java
|
||||||
|
@NotBlank(message = "借款期限不能为空")
|
||||||
|
private String loanTerm;
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 在转换器中映射新增字段**
|
||||||
|
|
||||||
|
```java
|
||||||
|
entity.setLoanPurpose(dto.getLoanPurpose());
|
||||||
|
entity.setLoanTerm(dto.getLoanTerm());
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 运行相关测试确认 DTO 与映射通过**
|
||||||
|
|
||||||
|
Run: `mvn -pl ruoyi-loan-pricing -Dtest=LoanPricingModelServicePersonalParamsTest test`
|
||||||
|
|
||||||
|
Expected: 仍可能 FAIL,但失败点已推进到模型调用层
|
||||||
|
|
||||||
|
- [ ] **Step 5: 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java
|
||||||
|
git commit -m "补齐个人测算创建参数字段"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 3: 补齐模型调用 DTO 与个人参数规范化
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java`
|
||||||
|
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 在模型调用 DTO 中增加缺失字段**
|
||||||
|
|
||||||
|
```java
|
||||||
|
private String loanTerm;
|
||||||
|
private String loanLoop;
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 在个人模型调用前做 `0/1` 转换**
|
||||||
|
|
||||||
|
```java
|
||||||
|
if ("个人".equals(loanPricingWorkflow.getCustType())) {
|
||||||
|
modelInvokeDTO.setBizProof(toZeroOne(modelInvokeDTO.getBizProof()));
|
||||||
|
modelInvokeDTO.setLoanLoop(toZeroOne(modelInvokeDTO.getLoanLoop()));
|
||||||
|
modelInvokeDTO.setCollThirdParty(toZeroOne(modelInvokeDTO.getCollThirdParty()));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 抽出最小辅助方法,避免散落重复逻辑**
|
||||||
|
|
||||||
|
```java
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 运行测试确认通过**
|
||||||
|
|
||||||
|
Run: `mvn -pl ruoyi-loan-pricing -Dtest=LoanPricingModelServicePersonalParamsTest test`
|
||||||
|
|
||||||
|
Expected: PASS
|
||||||
|
|
||||||
|
- [ ] **Step 5: 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java
|
||||||
|
git commit -m "规范个人测算模型调用参数"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 4: 后端联调与接口验证
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Verify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowController.java`
|
||||||
|
- Verify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImpl.java`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 重新编译并重启后端进程**
|
||||||
|
|
||||||
|
Run: `mvn -pl ruoyi-admin -am package -DskipTests`
|
||||||
|
|
||||||
|
Expected: 打包成功,随后重启运行中的后端服务使最新代码生效
|
||||||
|
|
||||||
|
- [ ] **Step 2: 验证正常场景**
|
||||||
|
|
||||||
|
Run: 调用 `POST /loanPricing/workflow/create/personal`
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- 返回创建成功
|
||||||
|
- 请求体包含 `loanPurpose`、`loanTerm`
|
||||||
|
- 模型请求中完整带出 16 个参数
|
||||||
|
- 其中 `bizProof`、`loanLoop`、`collThirdParty` 为 `0/1`
|
||||||
|
|
||||||
|
- [ ] **Step 3: 验证必填缺失场景**
|
||||||
|
|
||||||
|
Run: 缺少 `loanPurpose` 或 `loanTerm` 分别调用接口
|
||||||
|
|
||||||
|
Expected: 返回参数校验失败
|
||||||
|
|
||||||
|
- [ ] **Step 4: 验证分支场景**
|
||||||
|
|
||||||
|
Run: 以不同开关组合调用接口
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- `bizProof=true` -> 模型入参 `1`
|
||||||
|
- `bizProof=false` -> 模型入参 `0`
|
||||||
|
- `loanLoop=true/false` 与 `collThirdParty=true/false` 同理
|
||||||
|
|
||||||
|
- [ ] **Step 5: 验证结束后停止后端进程并提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java
|
||||||
|
git commit -m "完成个人测算输入参数后端联调"
|
||||||
|
```
|
||||||
322
doc/2026-04-09-shangyu-retail-input-params-design.md
Normal file
322
doc/2026-04-09-shangyu-retail-input-params-design.md
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
# 上虞个人利率测算输入参数对齐设计文档
|
||||||
|
|
||||||
|
## 1. 背景
|
||||||
|
|
||||||
|
根据 [doc/上虞利率测算接口文档.xlsx](/Users/wkc/Desktop/loan-pricing/loan-pricing/doc/上虞利率测算接口文档.xlsx) 的 `入参` sheet,个人利率测算模型当前要求的输入参数为:
|
||||||
|
|
||||||
|
- `serialNum`
|
||||||
|
- `orgCode`
|
||||||
|
- `runType`
|
||||||
|
- `custIsn`
|
||||||
|
- `custType`
|
||||||
|
- `custName`
|
||||||
|
- `idType`
|
||||||
|
- `idNum`
|
||||||
|
- `guarType`
|
||||||
|
- `applyAmt`
|
||||||
|
- `loanPurpose`
|
||||||
|
- `loanTerm`
|
||||||
|
- `bizProof`
|
||||||
|
- `loanLoop`
|
||||||
|
- `collThirdParty`
|
||||||
|
- `collType`
|
||||||
|
|
||||||
|
现有个人新增弹窗与模型调用链路未完全覆盖该输入集合:
|
||||||
|
|
||||||
|
- 页面缺少 `loanPurpose`
|
||||||
|
- 页面缺少 `loanTerm`
|
||||||
|
- `collType` 选项与 Excel 不一致
|
||||||
|
- 页面开关字段当前提交的是 `true/false`
|
||||||
|
- 模型调用 DTO 当前缺少 `loanTerm`、`loanLoop`
|
||||||
|
|
||||||
|
本次目标是按 Excel 的个人输入参数定义,对齐个人新增弹窗输入项和模型调用入参,不扩展到企业链路,不引入兜底或兼容分支。
|
||||||
|
|
||||||
|
## 2. 已确认范围
|
||||||
|
|
||||||
|
- 仅处理个人新增弹窗
|
||||||
|
- 仅处理个人创建流程到模型调用的入参链路
|
||||||
|
- 保持现有页面交互结构,不新增系统字段输入区
|
||||||
|
- `loanTerm` 使用固定年限下拉,选项按 Excel 定义
|
||||||
|
- 系统字段继续自动生成或默认赋值
|
||||||
|
- 不修改企业新增弹窗
|
||||||
|
- 不修改模型计算规则
|
||||||
|
|
||||||
|
## 3. 输入参数获取方式整理
|
||||||
|
|
||||||
|
### 3.1 系统自动带值
|
||||||
|
|
||||||
|
以下字段不放到新增弹窗中,由现有服务链路自动提供:
|
||||||
|
|
||||||
|
- `serialNum`
|
||||||
|
- 由 `LoanPricingWorkflowServiceImpl#createLoanPricing` 按时间戳生成
|
||||||
|
- `orgCode`
|
||||||
|
- 由 `LoanPricingWorkflowServiceImpl#createLoanPricing` 在空值时补默认值
|
||||||
|
- `runType`
|
||||||
|
- 由 `LoanPricingWorkflowServiceImpl#createLoanPricing` 在空值时补默认值 `1`
|
||||||
|
- `custType`
|
||||||
|
- 由 `LoanPricingConverter#toEntity(PersonalLoanPricingCreateDTO)` 固定写为 `个人`
|
||||||
|
|
||||||
|
### 3.2 用户直接输入
|
||||||
|
|
||||||
|
- 文本输入:
|
||||||
|
- `custIsn`
|
||||||
|
- `custName`
|
||||||
|
- `idNum`
|
||||||
|
- `applyAmt`
|
||||||
|
- 下拉选择:
|
||||||
|
- `idType`
|
||||||
|
- `guarType`
|
||||||
|
- `loanPurpose`
|
||||||
|
- `loanTerm`
|
||||||
|
- `collType`
|
||||||
|
- 开关选择:
|
||||||
|
- `bizProof`
|
||||||
|
- `loanLoop`
|
||||||
|
- `collThirdParty`
|
||||||
|
|
||||||
|
## 4. 现状分析
|
||||||
|
|
||||||
|
### 4.1 前端现状
|
||||||
|
|
||||||
|
[PersonalCreateDialog.vue](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue) 当前已经提供:
|
||||||
|
|
||||||
|
- `custIsn`
|
||||||
|
- `custName`
|
||||||
|
- `idType`
|
||||||
|
- `idNum`
|
||||||
|
- `guarType`
|
||||||
|
- `applyAmt`
|
||||||
|
- `bizProof`
|
||||||
|
- `loanLoop`
|
||||||
|
- `collType`
|
||||||
|
- `collThirdParty`
|
||||||
|
|
||||||
|
当前缺失或不一致点:
|
||||||
|
|
||||||
|
- 缺少 `loanPurpose`
|
||||||
|
- 缺少 `loanTerm`
|
||||||
|
- `collType` 选项为 `一线/一类/二类`,与 Excel 的 `一类/二类/三类` 不一致
|
||||||
|
- 开关字段提交值为 `true/false`
|
||||||
|
|
||||||
|
### 4.2 后端现状
|
||||||
|
|
||||||
|
[PersonalLoanPricingCreateDTO.java](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java) 当前未定义:
|
||||||
|
|
||||||
|
- `loanPurpose`
|
||||||
|
- `loanTerm`
|
||||||
|
|
||||||
|
[LoanPricingConverter.java](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java) 当前未把以上字段映射到 `LoanPricingWorkflow`。
|
||||||
|
|
||||||
|
[ModelInvokeDTO.java](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java) 当前未定义:
|
||||||
|
|
||||||
|
- `loanTerm`
|
||||||
|
- `loanLoop`
|
||||||
|
|
||||||
|
这意味着即使页面补齐字段,当前模型调用也无法完整带出 Excel 要求的个人入参。
|
||||||
|
|
||||||
|
## 5. 方案对比
|
||||||
|
|
||||||
|
### 方案一:页面补齐用户输入,系统字段继续自动带值
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 在个人新增弹窗新增 `loanPurpose` 下拉
|
||||||
|
- 在个人新增弹窗新增 `loanTerm` 固定年限下拉
|
||||||
|
- 修正 `collType` 选项
|
||||||
|
- 后端 DTO、转换器、模型调用 DTO 同步补字段
|
||||||
|
- 模型调用前统一将开关字段转换为 Excel 要求的 `0/1`
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 与现有个人/企业创建方式一致
|
||||||
|
- 改动最小
|
||||||
|
- 页面输入、流程入库、模型调用职责边界清晰
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 需要同时修改前后端
|
||||||
|
|
||||||
|
### 方案二:前端少改,后端在模型调用前兜底补参数
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 页面只补部分字段
|
||||||
|
- 其余由后端按默认逻辑拼接模型参数
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 页面改动更少
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 页面输入和真实模型入参不一致
|
||||||
|
- 后续排查问题时难以定位参数来源
|
||||||
|
- 不符合本次“按现有输入方式整理字段获取方式”的要求
|
||||||
|
|
||||||
|
### 方案三:Excel 全量字段全部暴露到弹窗
|
||||||
|
|
||||||
|
做法:
|
||||||
|
|
||||||
|
- 把 `serialNum`、`orgCode`、`runType`、`custType` 也作为页面字段给用户填写
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 页面可见字段与 Excel 完全一一对应
|
||||||
|
|
||||||
|
缺点:
|
||||||
|
|
||||||
|
- 与当前产品交互方式不一致
|
||||||
|
- 增加误填风险
|
||||||
|
- 不符合最短路径要求
|
||||||
|
|
||||||
|
## 6. 设计结论
|
||||||
|
|
||||||
|
采用方案一。
|
||||||
|
|
||||||
|
### 6.1 页面设计
|
||||||
|
|
||||||
|
个人新增弹窗保留现有分组结构,在“贷款信息”区域补齐:
|
||||||
|
|
||||||
|
- `loanPurpose`
|
||||||
|
- 下拉选项:`consumer`、`business`
|
||||||
|
- `loanTerm`
|
||||||
|
- 固定年限下拉
|
||||||
|
- 选项固定为 `1/2/3/4/5/6`
|
||||||
|
|
||||||
|
同时修正:
|
||||||
|
|
||||||
|
- `collType` 选项改为 `一类/二类/三类`
|
||||||
|
|
||||||
|
### 6.2 参数来源设计
|
||||||
|
|
||||||
|
- 系统带值:
|
||||||
|
- `serialNum`
|
||||||
|
- `orgCode`
|
||||||
|
- `runType`
|
||||||
|
- `custType`
|
||||||
|
- 页面透传:
|
||||||
|
- `custIsn`
|
||||||
|
- `custName`
|
||||||
|
- `idType`
|
||||||
|
- `idNum`
|
||||||
|
- `guarType`
|
||||||
|
- `applyAmt`
|
||||||
|
- `loanPurpose`
|
||||||
|
- `loanTerm`
|
||||||
|
- `collType`
|
||||||
|
- 页面开关,经模型调用层转换:
|
||||||
|
- `bizProof`
|
||||||
|
- `loanLoop`
|
||||||
|
- `collThirdParty`
|
||||||
|
|
||||||
|
### 6.3 链路设计
|
||||||
|
|
||||||
|
1. 前端提交个人创建请求时,补齐 `loanPurpose`、`loanTerm`
|
||||||
|
2. 后端个人创建 DTO 接收新增字段
|
||||||
|
3. 转换器将新增字段写入 `LoanPricingWorkflow`
|
||||||
|
4. 模型调用 DTO 增加 `loanPurpose`、`loanTerm`、`loanLoop`
|
||||||
|
5. `LoanPricingModelService` 在调用模型前,将个人链路中的开关字段转换为 `0/1`
|
||||||
|
6. `ModelService` 继续以 `application/x-www-form-urlencoded` 方式调用模型接口
|
||||||
|
|
||||||
|
### 6.3.1 后端模型调用输入参数确认
|
||||||
|
|
||||||
|
后端最终发给模型的个人入参,按 Excel 要求确认为以下 16 个字段:
|
||||||
|
|
||||||
|
- `serialNum`
|
||||||
|
- 来源:`LoanPricingWorkflowServiceImpl#createLoanPricing` 自动生成
|
||||||
|
- `orgCode`
|
||||||
|
- 来源:`LoanPricingWorkflowServiceImpl#createLoanPricing` 默认赋值
|
||||||
|
- 当前代码值:`892000`
|
||||||
|
- `runType`
|
||||||
|
- 来源:`LoanPricingWorkflowServiceImpl#createLoanPricing`
|
||||||
|
- 当前值:`1`
|
||||||
|
- `custIsn`
|
||||||
|
- 来源:页面输入,经个人创建 DTO 和转换器透传
|
||||||
|
- `custType`
|
||||||
|
- 来源:`LoanPricingConverter#toEntity(PersonalLoanPricingCreateDTO)`
|
||||||
|
- 当前值:固定 `个人`
|
||||||
|
- `custName`
|
||||||
|
- 来源:页面输入
|
||||||
|
- 说明:入库时加密,调用模型前解密
|
||||||
|
- `idType`
|
||||||
|
- 来源:页面输入
|
||||||
|
- `idNum`
|
||||||
|
- 来源:页面输入
|
||||||
|
- 说明:入库时加密,调用模型前解密
|
||||||
|
- `guarType`
|
||||||
|
- 来源:页面输入
|
||||||
|
- `applyAmt`
|
||||||
|
- 来源:页面输入
|
||||||
|
- `loanPurpose`
|
||||||
|
- 来源:页面输入
|
||||||
|
- 当前状态:需补齐到个人 DTO、流程实体映射和模型 DTO
|
||||||
|
- `loanTerm`
|
||||||
|
- 来源:页面输入
|
||||||
|
- 当前状态:需补齐到个人 DTO、流程实体映射和模型 DTO
|
||||||
|
- `bizProof`
|
||||||
|
- 来源:页面开关
|
||||||
|
- 模型值:调用模型前统一转换为 `0/1`
|
||||||
|
- `loanLoop`
|
||||||
|
- 来源:页面开关
|
||||||
|
- 当前状态:需补齐到模型 DTO
|
||||||
|
- 模型值:调用模型前统一转换为 `0/1`
|
||||||
|
- `collThirdParty`
|
||||||
|
- 来源:页面开关
|
||||||
|
- 模型值:调用模型前统一转换为 `0/1`
|
||||||
|
- `collType`
|
||||||
|
- 来源:页面下拉
|
||||||
|
- 模型值:按 `一类/二类/三类` 直接透传
|
||||||
|
|
||||||
|
后端调用方式确认如下:
|
||||||
|
|
||||||
|
- 参数载体:`ModelInvokeDTO`
|
||||||
|
- 参数来源:`BeanUtils.copyProperties(loanPricingWorkflow, modelInvokeDTO)`
|
||||||
|
- 请求构造:`ModelService#entityToMap`
|
||||||
|
- 请求格式:`application/x-www-form-urlencoded`
|
||||||
|
- 发送入口:`ModelService#invokeModel`
|
||||||
|
|
||||||
|
### 6.4 展示闭环
|
||||||
|
|
||||||
|
为保证输入项可在详情页回看,个人详情页同步补齐:
|
||||||
|
|
||||||
|
- `loanPurpose`
|
||||||
|
|
||||||
|
`loanTerm` 详情展示已存在,不需要新增区域。
|
||||||
|
|
||||||
|
## 7. 校验与错误处理
|
||||||
|
|
||||||
|
- 前端新增 `loanPurpose` 必选校验
|
||||||
|
- 前端新增 `loanTerm` 必选校验
|
||||||
|
- `loanTerm` 只能通过固定下拉选择,不提供自由输入
|
||||||
|
- 后端 DTO 对 `loanPurpose`、`loanTerm` 增加必填约束
|
||||||
|
- 保持现有创建失败与模型调用失败的错误提示方式
|
||||||
|
- 不新增兼容逻辑、兜底逻辑或补丁式分支
|
||||||
|
|
||||||
|
## 8. 验证设计
|
||||||
|
|
||||||
|
- 前端源码断言个人新增弹窗已出现 `loanPurpose`、`loanTerm`
|
||||||
|
- 前端源码断言 `loanTerm` 为固定下拉、`collType` 选项为 `一类/二类/三类`
|
||||||
|
- 后端测试或源码断言 `PersonalLoanPricingCreateDTO`、`LoanPricingConverter`、`ModelInvokeDTO` 已补齐字段
|
||||||
|
- 后端测试或日志断言调用模型前最终请求参数完整包含以上 16 个字段
|
||||||
|
- 重启后端后,覆盖以下接口验证:
|
||||||
|
- 正常场景:完整参数创建成功
|
||||||
|
- 必填缺失场景:缺少 `loanPurpose` 或 `loanTerm` 被拦截
|
||||||
|
- 分支场景:`bizProof`、`loanLoop`、`collThirdParty` 开关不同组合能正确转换为 `0/1`
|
||||||
|
- 启动前端页面并通过浏览器检查:
|
||||||
|
- 新增弹窗展示正确
|
||||||
|
- 提交流程后详情页能回显 `loanPurpose`、`loanTerm`
|
||||||
|
- 验证完成后停止本次启动的前后端进程
|
||||||
|
|
||||||
|
## 9. 已确认项
|
||||||
|
|
||||||
|
- `orgCode` 统一为 `892000`
|
||||||
|
- `ModelInvokeDTO` 注释已统一为 `892000`
|
||||||
|
- 数据库 `loan_pricing_workflow.org_code` 默认值已统一为 `892000`
|
||||||
|
- 存量 `loan_pricing_workflow.org_code` 数据已通过迁移脚本统一为 `892000`
|
||||||
|
|
||||||
|
## 10. 非目标
|
||||||
|
|
||||||
|
- 不调整企业新增弹窗
|
||||||
|
- 不修改企业模型调用参数
|
||||||
|
- 不修改流程列表逻辑
|
||||||
|
- 不改模型返回字段映射逻辑
|
||||||
212
doc/2026-04-09-shangyu-retail-input-params-frontend-plan.md
Normal file
212
doc/2026-04-09-shangyu-retail-input-params-frontend-plan.md
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
# 上虞个人利率测算输入参数前端 Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** 按 Excel 补齐个人新增弹窗输入项,确保页面提交字段与个人模型入参要求一致。
|
||||||
|
|
||||||
|
**Architecture:** 仅修改个人创建弹窗和个人详情页,沿用当前 Element UI 表单结构,不新增页面层级。通过前端源码断言覆盖新增字段、选项和值转换,确保页面输入与现有交互方式保持一致。
|
||||||
|
|
||||||
|
**Tech Stack:** Vue 2、Element UI、RuoYi 前端请求封装、Node 源码断言脚本
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
- 修改: [ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue)
|
||||||
|
- 补齐 `loanPurpose`、`loanTerm`
|
||||||
|
- 修正 `collType` 选项
|
||||||
|
- 调整个人开关字段提交值
|
||||||
|
- 修改: [ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue)
|
||||||
|
- 补齐 `loanPurpose` 展示
|
||||||
|
- 如有需要,扩展布尔格式化兼容 `0/1`
|
||||||
|
- 新增: [ruoyi-ui/tests/personal-create-input-params.test.js](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-ui/tests/personal-create-input-params.test.js)
|
||||||
|
- 断言新增弹窗字段、选项和值转换逻辑
|
||||||
|
- 修改: [ruoyi-ui/package.json](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-ui/package.json)
|
||||||
|
- 新增针对本次改动的测试命令
|
||||||
|
|
||||||
|
### Task 1: 为个人新增弹窗补失败断言
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `ruoyi-ui/tests/personal-create-input-params.test.js`
|
||||||
|
- Modify: `ruoyi-ui/package.json`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 编写失败断言脚本**
|
||||||
|
|
||||||
|
```js
|
||||||
|
const requiredFields = ['form.loanPurpose', 'form.loanTerm']
|
||||||
|
const requiredOptions = ['value="consumer"', 'value="business"', 'label="一类"', 'label="二类"', 'label="三类"']
|
||||||
|
const requiredConversions = [
|
||||||
|
"bizProof: this.form.bizProof ? '1' : '0'",
|
||||||
|
"loanLoop: this.form.loanLoop ? '1' : '0'",
|
||||||
|
"collThirdParty: this.form.collThirdParty ? '1' : '0'"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 运行断言并确认先失败**
|
||||||
|
|
||||||
|
Run: `npm --prefix ruoyi-ui run test:personal-create-input-params`
|
||||||
|
|
||||||
|
Expected: FAIL,提示缺少 `loanPurpose` 或 `loanTerm` 或值转换未按 `1/0`
|
||||||
|
|
||||||
|
- [ ] **Step 3: 在 `package.json` 注册测试命令**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"test:personal-create-input-params": "node tests/personal-create-input-params.test.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 再次运行断言并确认仍处于失败态**
|
||||||
|
|
||||||
|
Run: `npm --prefix ruoyi-ui run test:personal-create-input-params`
|
||||||
|
|
||||||
|
Expected: FAIL,失败原因与新增字段缺失一致
|
||||||
|
|
||||||
|
- [ ] **Step 5: 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add ruoyi-ui/tests/personal-create-input-params.test.js ruoyi-ui/package.json
|
||||||
|
git commit -m "新增个人测算输入参数前端断言"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 2: 补齐个人新增弹窗字段与选项
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 增加 `loanPurpose` 表单项**
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<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>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 增加 `loanTerm` 固定年限下拉**
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<el-form-item label="借款期限(年)" prop="loanTerm">
|
||||||
|
<el-select v-model="form.loanTerm" placeholder="请选择借款期限" style="width: 100%">
|
||||||
|
<el-option v-for="item in ['1', '2', '3', '4', '5', '6']" :key="item" :label="item" :value="item" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 修正 `collType` 选项为 Excel 定义**
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<el-option label="一类" value="一类" />
|
||||||
|
<el-option label="二类" value="二类" />
|
||||||
|
<el-option label="三类" value="三类" />
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 为新增字段补表单状态与校验**
|
||||||
|
|
||||||
|
```js
|
||||||
|
form: {
|
||||||
|
loanPurpose: undefined,
|
||||||
|
loanTerm: undefined
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
loanPurpose: [{ required: true, message: '请选择贷款用途', trigger: 'change' }],
|
||||||
|
loanTerm: [{ required: true, message: '请选择借款期限', trigger: 'change' }]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue
|
||||||
|
git commit -m "补齐个人新增弹窗输入字段"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 3: 调整前端提交值与详情展示
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue`
|
||||||
|
- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 在提交逻辑中改为 `1/0` 值**
|
||||||
|
|
||||||
|
```js
|
||||||
|
const data = {
|
||||||
|
...this.form,
|
||||||
|
bizProof: this.form.bizProof ? '1' : '0',
|
||||||
|
loanLoop: this.form.loanLoop ? '1' : '0',
|
||||||
|
collThirdParty: this.form.collThirdParty ? '1' : '0'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 在详情页补齐 `loanPurpose` 展示**
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<el-descriptions-item label="贷款用途">{{ detailData.loanPurpose || '-' }}</el-descriptions-item>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 兼容详情页 `0/1` 布尔展示**
|
||||||
|
|
||||||
|
```js
|
||||||
|
if (value === 'true' || value === true || value === '1' || value === 1) return '是'
|
||||||
|
if (value === 'false' || value === false || value === '0' || value === 0) return '否'
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 运行前端源码断言并确认通过**
|
||||||
|
|
||||||
|
Run: `npm --prefix ruoyi-ui run test:personal-create-input-params`
|
||||||
|
|
||||||
|
Expected: PASS,输出断言通过信息
|
||||||
|
|
||||||
|
- [ ] **Step 5: 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue
|
||||||
|
git commit -m "调整个人测算前端提交与展示"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 4: 页面联调与回归验证
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Verify: `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue`
|
||||||
|
- Verify: `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 启动前端开发服务**
|
||||||
|
|
||||||
|
Run: `npm --prefix ruoyi-ui run dev`
|
||||||
|
|
||||||
|
Expected: 成功启动并输出本地访问地址
|
||||||
|
|
||||||
|
- [ ] **Step 2: 打开流程页面验证新增弹窗**
|
||||||
|
|
||||||
|
Run: 在浏览器中进入 `/loanPricing/workflow`
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- 出现 `贷款用途`
|
||||||
|
- 出现 `借款期限(年)` 固定下拉
|
||||||
|
- `抵质押类型` 选项为 `一类/二类/三类`
|
||||||
|
|
||||||
|
- [ ] **Step 3: 结合后端联调创建个人流程**
|
||||||
|
|
||||||
|
Run: 在页面中填写完整参数并提交
|
||||||
|
|
||||||
|
Expected: 创建成功,不出现参数缺失报错
|
||||||
|
|
||||||
|
- [ ] **Step 4: 打开详情页验证回显**
|
||||||
|
|
||||||
|
Run: 打开刚创建的个人流程详情
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- 页面展示 `贷款用途`
|
||||||
|
- 页面展示 `借款期限`
|
||||||
|
- 开关字段显示为“是/否”
|
||||||
|
|
||||||
|
- [ ] **Step 5: 验证结束后停止前端进程并提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue ruoyi-ui/tests/personal-create-input-params.test.js ruoyi-ui/package.json
|
||||||
|
git commit -m "完成个人测算输入参数前端联调"
|
||||||
|
```
|
||||||
52
doc/2026-04-16-shangyu-corporate-alignment-backend-plan.md
Normal file
52
doc/2026-04-16-shangyu-corporate-alignment-backend-plan.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# 上虞对公利率测算字段对齐后端实施计划
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
- 对齐对公创建接口、模型调用入参、流程详情返回、mock 返回和 SQL 基线。
|
||||||
|
|
||||||
|
## 实施内容
|
||||||
|
- 创建请求字段改为 Excel `上传指标` 口径:
|
||||||
|
- 新增 `repayMethod`
|
||||||
|
- `isTradeConstruction` 改为 `isTradeBuildEnt`
|
||||||
|
- 移除对公创建链路中的 `isAgriGuar`、`isTechEnt`
|
||||||
|
- 流程主表实体补 `repayMethod`,并将 `isTradeBuildEnt` 映射到数据库列 `is_trade_construction`
|
||||||
|
- 对公模型输出实体补齐:
|
||||||
|
- `repayMethod`
|
||||||
|
- `isTradeBuildEnt`
|
||||||
|
- `loanRateHistory`
|
||||||
|
- `minRateProduct`
|
||||||
|
- `smoothRange`
|
||||||
|
- `finalCalculateRate`
|
||||||
|
- `referenceRate`
|
||||||
|
- 对公模型输出实体不再暴露:
|
||||||
|
- `isAgriGuar`
|
||||||
|
- `midEntTax`
|
||||||
|
- `cardOverdue`
|
||||||
|
- 企业模型入参统一值域:
|
||||||
|
- `isGreenLoan`、`isTradeBuildEnt`、`collThirdParty` 发送 `0/1`
|
||||||
|
- `repayMethod` 发送 `分期/不分期`
|
||||||
|
- 企业流程详情主利率改为 `finalCalculateRate`
|
||||||
|
- mock 继续保留 `data.mappingOutputFields` 包装层,只更新企业字段集合和值域
|
||||||
|
|
||||||
|
## SQL 调整
|
||||||
|
- `loan_pricing_workflow` 新增 `repay_method`
|
||||||
|
- `model_corp_output_fields` 新增:
|
||||||
|
- `repay_method`
|
||||||
|
- `is_trade_build_ent`
|
||||||
|
- `loan_rate_history`
|
||||||
|
- `min_rate_product`
|
||||||
|
- `smooth_range`
|
||||||
|
- `final_calculate_rate`
|
||||||
|
- `reference_rate`
|
||||||
|
- 已同步更新:
|
||||||
|
- `sql/loan_pricing_workflow.sql`
|
||||||
|
- `sql/model_corp.sql`
|
||||||
|
- `sql/loan_pricing_schema_20260328.sql`
|
||||||
|
- `sql/loan_pricing_prod_init_20260331.sql`
|
||||||
|
- `sql/2026-04-16-shangyu-corporate-alignment.sql`
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
- 运行后端定向单测,确认对公字段和详情主利率断言通过
|
||||||
|
- 使用 `/login/test` 获取 token 后调用对公创建和详情接口,确认:
|
||||||
|
- 正常场景成功
|
||||||
|
- 缺少 `repayMethod` 返回校验错误
|
||||||
|
- 详情返回包含新增字段且 `loanRate = finalCalculateRate`
|
||||||
48
doc/2026-04-16-shangyu-corporate-alignment-frontend-plan.md
Normal file
48
doc/2026-04-16-shangyu-corporate-alignment-frontend-plan.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# 上虞对公利率测算字段对齐前端实施计划
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
- 对齐对公新增弹窗和企业流程详情页展示,严格跟随 Excel `上传指标` 与 `展示指标`。
|
||||||
|
|
||||||
|
## 实施内容
|
||||||
|
- 对公新增弹窗调整为 Excel `上传指标`:
|
||||||
|
- 新增 `repayMethod`
|
||||||
|
- `isTradeConstruction` 改为 `isTradeBuildEnt`
|
||||||
|
- 删除 `省农担担保贷款`、`科技型企业`
|
||||||
|
- `loanTerm` 文案改为按年
|
||||||
|
- `collType` 选项改为 `一类/二类/三类/四类`
|
||||||
|
- `isGreenLoan`、`isTradeBuildEnt`、`collThirdParty` 提交值改为 `1/0`
|
||||||
|
- 企业详情左侧关键信息:
|
||||||
|
- 标签改为 `最终测算利率`
|
||||||
|
- 读取 `corpOutput.finalCalculateRate`
|
||||||
|
- 企业流程详情业务信息:
|
||||||
|
- 新增展示 `repayMethod`
|
||||||
|
- 新增展示 `isTradeBuildEnt`
|
||||||
|
- 保留 `isGreenLoan`
|
||||||
|
- 移除不在本次口径内的企业业务展示
|
||||||
|
- 企业模型输出补齐展示:
|
||||||
|
- `repayMethod`
|
||||||
|
- `isTradeBuildEnt`
|
||||||
|
- `loanRateHistory`
|
||||||
|
- `minRateProduct`
|
||||||
|
- `smoothRange`
|
||||||
|
- `finalCalculateRate`
|
||||||
|
- `referenceRate`
|
||||||
|
- 企业模型输出移除展示:
|
||||||
|
- `isAgriGuar`
|
||||||
|
- `midEntTax`
|
||||||
|
- `cardOverdue`
|
||||||
|
|
||||||
|
## 测试脚本
|
||||||
|
- 新增:
|
||||||
|
- `ruoyi-ui/tests/corporate-create-input-params.test.js`
|
||||||
|
- `ruoyi-ui/tests/corporate-display-fields.test.js`
|
||||||
|
- 更新 `ruoyi-ui/package.json`,补充对应 npm scripts
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
- `nvm use default` 后执行两个对公静态断言脚本
|
||||||
|
- 执行前端生产构建
|
||||||
|
- 启动前端页面并在浏览器中确认:
|
||||||
|
- 对公新增弹窗字段和选项正确
|
||||||
|
- 创建成功后列表刷新
|
||||||
|
- 企业详情页显示 `最终测算利率`
|
||||||
|
- 企业详情页和模型输出出现新增字段
|
||||||
@@ -32,10 +32,12 @@
|
|||||||
| idNum | String | 否 | 证件号码 |
|
| idNum | String | 否 | 证件号码 |
|
||||||
| guarType | String | 是 | 担保方式,可选值: 信用/保证/抵押/质押 |
|
| guarType | String | 是 | 担保方式,可选值: 信用/保证/抵押/质押 |
|
||||||
| applyAmt | String | 是 | 申请金额,单位: 元 |
|
| applyAmt | String | 是 | 申请金额,单位: 元 |
|
||||||
| bizProof | String | 否 | 是否有经营佐证,值: true/false |
|
| loanPurpose | String | 是 | 贷款用途,可选值: consumer/business |
|
||||||
| loanLoop | String | 否 | 循环功能,值: true/false |
|
| loanTerm | String | 是 | 借款期限(年),固定下拉选项按模型文档配置 |
|
||||||
| collType | String | 否 | 抵质押类型,可选值: 一线/一类/二类 |
|
| bizProof | String | 否 | 是否有经营佐证,值: 0/1 |
|
||||||
| collThirdParty | String | 否 | 抵质押物是否三方所有,值: true/false |
|
| loanLoop | String | 否 | 循环功能,值: 0/1 |
|
||||||
|
| collType | String | 否 | 抵质押类型,可选值: 一类/二类/三类 |
|
||||||
|
| collThirdParty | String | 否 | 抵质押物是否三方所有,值: 0/1 |
|
||||||
|
|
||||||
**请求示例:**
|
**请求示例:**
|
||||||
|
|
||||||
@@ -47,10 +49,12 @@
|
|||||||
"idNum": "110101199001011234",
|
"idNum": "110101199001011234",
|
||||||
"guarType": "抵押",
|
"guarType": "抵押",
|
||||||
"applyAmt": "500000",
|
"applyAmt": "500000",
|
||||||
"bizProof": "true",
|
"loanPurpose": "business",
|
||||||
"loanLoop": "false",
|
"loanTerm": "3",
|
||||||
|
"bizProof": "1",
|
||||||
|
"loanLoop": "0",
|
||||||
"collType": "一类",
|
"collType": "一类",
|
||||||
"collThirdParty": "false"
|
"collThirdParty": "0"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -64,12 +68,14 @@
|
|||||||
"id": 1,
|
"id": 1,
|
||||||
"modelOutputId": 100,
|
"modelOutputId": 100,
|
||||||
"serialNum": "20250119143025123",
|
"serialNum": "20250119143025123",
|
||||||
"orgCode": "931000",
|
"orgCode": "892000",
|
||||||
"runType": "1",
|
"runType": "1",
|
||||||
"custIsn": "CUST001",
|
"custIsn": "CUST001",
|
||||||
"custType": "个人",
|
"custType": "个人",
|
||||||
"guarType": "抵押",
|
"guarType": "抵押",
|
||||||
"applyAmt": "500000",
|
"applyAmt": "500000",
|
||||||
|
"loanPurpose": "business",
|
||||||
|
"loanTerm": "3",
|
||||||
"custName": "张三",
|
"custName": "张三",
|
||||||
"idType": "身份证",
|
"idType": "身份证",
|
||||||
"createTime": "2025-01-19 14:30:25",
|
"createTime": "2025-01-19 14:30:25",
|
||||||
@@ -136,7 +142,7 @@
|
|||||||
"id": 2,
|
"id": 2,
|
||||||
"modelOutputId": 101,
|
"modelOutputId": 101,
|
||||||
"serialNum": "20250119143125456",
|
"serialNum": "20250119143125456",
|
||||||
"orgCode": "931000",
|
"orgCode": "892000",
|
||||||
"runType": "1",
|
"runType": "1",
|
||||||
"custIsn": "CORP001",
|
"custIsn": "CORP001",
|
||||||
"custType": "企业",
|
"custType": "企业",
|
||||||
@@ -189,7 +195,7 @@ GET /loanPricing/workflow/list?pageNum=1&pageSize=10&custName=科技
|
|||||||
"id": 1,
|
"id": 1,
|
||||||
"modelOutputId": 100,
|
"modelOutputId": 100,
|
||||||
"serialNum": "20250119143025123",
|
"serialNum": "20250119143025123",
|
||||||
"orgCode": "931000",
|
"orgCode": "892000",
|
||||||
"custIsn": "CUST001",
|
"custIsn": "CUST001",
|
||||||
"custType": "企业",
|
"custType": "企业",
|
||||||
"guarType": "抵押",
|
"guarType": "抵押",
|
||||||
@@ -240,7 +246,7 @@ GET /loanPricing/workflow/20250119143025123
|
|||||||
"id": 1,
|
"id": 1,
|
||||||
"modelOutputId": 100,
|
"modelOutputId": 100,
|
||||||
"serialNum": "20250119143025123",
|
"serialNum": "20250119143025123",
|
||||||
"orgCode": "931000",
|
"orgCode": "892000",
|
||||||
"runType": "1",
|
"runType": "1",
|
||||||
"custIsn": "CUST001",
|
"custIsn": "CUST001",
|
||||||
"custType": "企业",
|
"custType": "企业",
|
||||||
@@ -278,6 +284,7 @@ GET /loanPricing/workflow/20250119143025123
|
|||||||
"idType": "身份证",
|
"idType": "身份证",
|
||||||
"idNum": "330102199001011234",
|
"idNum": "330102199001011234",
|
||||||
"baseLoanRate": "3.45",
|
"baseLoanRate": "3.45",
|
||||||
|
"greyBlackCust": "1",
|
||||||
"isFirstLoan": "true",
|
"isFirstLoan": "true",
|
||||||
"faithDay": "365",
|
"faithDay": "365",
|
||||||
"custAge": "35",
|
"custAge": "35",
|
||||||
@@ -302,7 +309,7 @@ GET /loanPricing/workflow/20250119143025123
|
|||||||
"midPerFinMan": "false",
|
"midPerFinMan": "false",
|
||||||
"midPerEtc": "true",
|
"midPerEtc": "true",
|
||||||
"bpMid": "-15",
|
"bpMid": "-15",
|
||||||
"totoalBpRelevance": "-50",
|
"totalBpRelevance": "-50",
|
||||||
"applyAmt": "500000",
|
"applyAmt": "500000",
|
||||||
"bpLoanAmount": "0",
|
"bpLoanAmount": "0",
|
||||||
"loanPurpose": "consumer",
|
"loanPurpose": "consumer",
|
||||||
@@ -318,7 +325,7 @@ GET /loanPricing/workflow/20250119143025123
|
|||||||
"interestOverdue": "false",
|
"interestOverdue": "false",
|
||||||
"cardOverdue": "false",
|
"cardOverdue": "false",
|
||||||
"bpGreyOverdue": "0",
|
"bpGreyOverdue": "0",
|
||||||
"totoalBpRisk": "0",
|
"totalBpRisk": "0",
|
||||||
"totalBp": "-80",
|
"totalBp": "-80",
|
||||||
"calculateRate": "2.65"
|
"calculateRate": "2.65"
|
||||||
},
|
},
|
||||||
@@ -357,7 +364,7 @@ GET /loanPricing/workflow/20250119143025123
|
|||||||
"isGreenLoan": "false",
|
"isGreenLoan": "false",
|
||||||
"isTechEnt": "true",
|
"isTechEnt": "true",
|
||||||
"bpEntType": "-25",
|
"bpEntType": "-25",
|
||||||
"totoalBpRelevance": "-55",
|
"totalBpRelevance": "-55",
|
||||||
"loanTerm": "36",
|
"loanTerm": "36",
|
||||||
"bpLoanTerm": "0",
|
"bpLoanTerm": "0",
|
||||||
"applyAmt": "1000000",
|
"applyAmt": "1000000",
|
||||||
@@ -370,7 +377,7 @@ GET /loanPricing/workflow/20250119143025123
|
|||||||
"interestOverdue": "false",
|
"interestOverdue": "false",
|
||||||
"cardOverdue": "false",
|
"cardOverdue": "false",
|
||||||
"bpGreyOverdue": "0",
|
"bpGreyOverdue": "0",
|
||||||
"totoalBpRisk": "0",
|
"totalBpRisk": "0",
|
||||||
"totalBp": "-85",
|
"totalBp": "-85",
|
||||||
"calculateRate": "2.60"
|
"calculateRate": "2.60"
|
||||||
}
|
}
|
||||||
|
|||||||
22
doc/implementation-report-2026-03-31-deploy-folder-docs.md
Normal file
22
doc/implementation-report-2026-03-31-deploy-folder-docs.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# deploy 目录文档整理实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 新增 `deploy` 目录下的本地安装手册:
|
||||||
|
- `deploy/2026-03-31-local-nginx-java-install-manual.md`
|
||||||
|
- 新增 `deploy` 目录下的独立 Nginx 配置文件:
|
||||||
|
- `deploy/nginx.conf`
|
||||||
|
- 安装手册中的 Nginx 配置说明调整为直接引用 `deploy/nginx.conf`
|
||||||
|
- 删除原 `doc/2026-03-31-local-nginx-java-install-manual.md`,避免同一手册在仓库内出现两份路径
|
||||||
|
|
||||||
|
## 路径检查
|
||||||
|
|
||||||
|
- 已确认安装手册当前保存路径为 `deploy/2026-03-31-local-nginx-java-install-manual.md`
|
||||||
|
- 已确认 Nginx 配置文件当前保存路径为 `deploy/nginx.conf`
|
||||||
|
- 已确认本次实施记录保存路径为 `doc/implementation-report-2026-03-31-deploy-folder-docs.md`
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- 已执行 `ls -l deploy/2026-03-31-local-nginx-java-install-manual.md deploy/nginx.conf`
|
||||||
|
- 已人工核对 `deploy/nginx.conf` 与 `bin/prod/install_env.sh` 中写入的 Nginx 配置保持一致
|
||||||
|
- 已人工核对手册中的目录、端口和脚本引用与当前交付物保持一致
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# 本地安装 Nginx 和 Java 手册实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 新增本地安装手册 `deploy/2026-03-31-local-nginx-java-install-manual.md`
|
||||||
|
- 手册内容与当前生产安装脚本保持一致:
|
||||||
|
- Java 安装目录 `/home/webapp/env/java`
|
||||||
|
- Nginx 安装目录 `/home/webapp/env/nginx`
|
||||||
|
- 前端端口 `63311`
|
||||||
|
- 后端端口 `63310`
|
||||||
|
- 手册补充了系统依赖安装、目录初始化、Java 安装、Nginx 编译安装、配置写入、配置校验、启动与验证步骤
|
||||||
|
|
||||||
|
## 路径检查
|
||||||
|
|
||||||
|
- 已确认本次新增手册保存路径为 `deploy/2026-03-31-local-nginx-java-install-manual.md`
|
||||||
|
- 已确认本次实施记录保存路径为 `doc/implementation-report-2026-03-31-local-nginx-java-install-manual.md`
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- 已人工核对手册中的安装路径、端口、Nginx 配置和现有脚本 `bin/prod/install_env.sh`
|
||||||
|
- 已确认手册内容与以下脚本约定一致:
|
||||||
|
- `bin/prod/install_env.sh`
|
||||||
|
- `bin/prod/deploy_release.sh`
|
||||||
|
- `bin/prod/restart_java.sh`
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# 生产初始化数据库导出后端实施记录
|
||||||
|
|
||||||
|
## 本次改动
|
||||||
|
|
||||||
|
- 新增生产初始化总脚本 `sql/loan_pricing_prod_init_20260331.sql`
|
||||||
|
- 直接复用 `sql/ry_20250522.sql` 作为若依基础表和初始化数据来源
|
||||||
|
- 并入 `sql/loan_pricing_menu.sql` 中的贷款定价菜单初始化内容
|
||||||
|
- 追加 3 张贷款定价业务表结构:
|
||||||
|
- `loan_pricing_workflow`
|
||||||
|
- `model_corp_output_fields`
|
||||||
|
- `model_retail_output_fields`
|
||||||
|
|
||||||
|
## 结构来源
|
||||||
|
|
||||||
|
- 业务表最终结构以 `sql/loan_pricing_schema_20260328.sql` 为主来源
|
||||||
|
- 已核对 `loan_pricing_workflow` 的补字段和注释修正历史脚本,确认总脚本使用的是最终字段版本
|
||||||
|
|
||||||
|
## 数据范围
|
||||||
|
|
||||||
|
- 保留若依基础初始化数据
|
||||||
|
- 保留贷款定价功能菜单初始化数据:
|
||||||
|
- `sys_menu` 中的 `2000`、`2001`、`2002`
|
||||||
|
- `sys_role_menu` 中管理员角色对上述菜单的关联
|
||||||
|
- 不导出任何贷款定价业务数据
|
||||||
|
- 未写入 `loan_pricing_workflow`、`model_corp_output_fields`、`model_retail_output_fields` 的 `INSERT` 或 `DELETE` 语句
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- 已完成静态检查,确认 3 张业务表在总脚本中各只定义 1 次
|
||||||
|
- 已确认总脚本保留若依基础 `sys_user`、`sys_role`、`sys_menu` 初始化数据
|
||||||
|
- 已使用临时验证库导入总脚本并完成计数检查
|
||||||
|
- 导入后校验结果:
|
||||||
|
- `sys_menu` 中贷款定价菜单 `2000/2001/2002 = 3`
|
||||||
|
- `sys_role_menu` 中管理员角色菜单关联 `2000/2001/2002 = 3`
|
||||||
|
- `sys_user = 2`
|
||||||
|
- `sys_role = 2`
|
||||||
|
- `sys_menu = 88`
|
||||||
|
- `loan_pricing_workflow = 0`
|
||||||
|
- `model_corp_output_fields = 0`
|
||||||
|
- `model_retail_output_fields = 0`
|
||||||
|
- 验证结束后已删除临时验证库
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# 生产初始化数据库导出前端实施记录
|
||||||
|
|
||||||
|
## 范围确认
|
||||||
|
|
||||||
|
- 已根据 `docs/superpowers/specs/2026-03-31-production-db-init-export-design.md` 确认本次交付物仅为数据库初始化单文件 SQL
|
||||||
|
- 本次任务不涉及前端页面、接口契约、构建配置或部署产物调整
|
||||||
|
|
||||||
|
## 本次结论
|
||||||
|
|
||||||
|
- `ruoyi-ui` 工程不存在需要随本次任务修改的页面、接口或构建配置
|
||||||
|
- 本次前端范围为无代码改动
|
||||||
|
- 执行过程中应保持 `ruoyi-ui` 目录不变
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- 已复核本次任务提交范围,前端代码未纳入本次 SQL 导出实现
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# 生产初始化数据库导出计划文档实施记录
|
||||||
|
|
||||||
|
## 本次改动
|
||||||
|
|
||||||
|
- 新增设计文档 `docs/superpowers/specs/2026-03-31-production-db-init-export-design.md`
|
||||||
|
- 新增后端实施计划 `docs/superpowers/plans/2026-03-31-production-db-init-export-backend-plan.md`
|
||||||
|
- 新增前端实施计划 `docs/superpowers/plans/2026-03-31-production-db-init-export-frontend-plan.md`
|
||||||
|
|
||||||
|
## 设计结论
|
||||||
|
|
||||||
|
- 最终交付物是单一可执行 SQL 文件
|
||||||
|
- 基础部分直接复用 `sql/ry_20250522.sql`
|
||||||
|
- 业务增量仅包含 `loan_pricing_workflow`、`model_corp_output_fields`、`model_retail_output_fields` 三张表结构
|
||||||
|
- 不导出任何业务数据
|
||||||
|
|
||||||
|
## 说明
|
||||||
|
|
||||||
|
- 按仓库规范未开启 subagent
|
||||||
|
- 本次阶段仅完成设计与实施计划编写,尚未开始生成最终 SQL 文件
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# 生产环境安装与部署脚本实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 新增生产环境安装脚本 `bin/prod/install_env.sh`
|
||||||
|
- 新增生产环境部署脚本 `bin/prod/deploy_release.sh`
|
||||||
|
- 新增生产环境 Java 管理脚本 `bin/prod/restart_java.sh`
|
||||||
|
- 两份脚本需要同步放置到生产容器 `/home/webapp` 目录,便于在目标环境直接执行
|
||||||
|
- 部署脚本改为复用独立的 Java 管理脚本完成后端启停
|
||||||
|
- 安装脚本固定将 Java 安装到 `/home/webapp/env/java`,将 Nginx 安装到 `/home/webapp/env/nginx`
|
||||||
|
- 安装脚本会创建 `/home/webapp/loan-pricing` 下的 `backend`、`frontend`、`backup`、`logs`、`run`、`tmp` 目录,并写入 Nginx 配置
|
||||||
|
- 部署脚本约定发布包内必须包含 1 个后端 `jar` 和 1 个 `dist.zip`
|
||||||
|
- 部署脚本在发布前会备份旧版后端 jar 与旧版前端 `dist` 目录,再完成替换、启动后端和重载 Nginx
|
||||||
|
- Nginx 前端监听端口固定为 `63311`,后端应用启动端口固定为 `63310`
|
||||||
|
|
||||||
|
## 环境勘察结论
|
||||||
|
- 已连接生产服务器 `116.62.17.81:9444` 并进入 `loan-pricing` 容器核对目录结构
|
||||||
|
- 容器内实际工作目录为 `/home/webapp`
|
||||||
|
- 已确认当前容器中存在安装包:
|
||||||
|
- `/home/webapp/openjdk-21.0.2_linux-aarch64_bin.tar.gz`
|
||||||
|
- `/home/webapp/nginx-1.20.2.tar.gz`
|
||||||
|
- 已确认当前容器尚不存在 `/home/webapp/loan-pricing`
|
||||||
|
- 已确认当前容器当前没有运行中的 Java 或 Nginx 进程
|
||||||
|
- 当前被勘察容器基础镜像为 Ubuntu;但脚本已按需求改为基于 `yum` 安装系统依赖,适配正式生产环境约束
|
||||||
|
- 已确认当前容器无法直接安装原生 `yum` 包,但系统仓库提供 `dnf` 包,可通过 `dnf` 提供 `yum` 兼容执行入口
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已执行 `sh -n bin/prod/install_env.sh`
|
||||||
|
- 已执行 `sh -n bin/prod/deploy_release.sh`
|
||||||
|
- 已将两份脚本同步到生产 `loan-pricing` 容器:
|
||||||
|
- `/home/webapp/install_env.sh`
|
||||||
|
- `/home/webapp/deploy_release.sh`
|
||||||
|
- 已将 Java 管理脚本同步到生产 `loan-pricing` 容器:
|
||||||
|
- `/home/webapp/restart_java.sh`
|
||||||
|
- 已在容器内执行 `ls -l /home/webapp/install_env.sh /home/webapp/deploy_release.sh /home/webapp/restart_java.sh`,确认三份脚本均已落盘且具备可执行权限
|
||||||
|
- 已在容器内执行:
|
||||||
|
- `sh -n /home/webapp/install_env.sh`
|
||||||
|
- `sh -n /home/webapp/deploy_release.sh`
|
||||||
|
- `sh -n /home/webapp/restart_java.sh`
|
||||||
|
三份线上脚本语法校验均已通过
|
||||||
|
- 已确认 Ubuntu 24.04 仓库中 `yum` 包候选为空,`dnf` 包候选为 `4.14.0-4.1ubuntu1`
|
||||||
|
- 已在生产 `loan-pricing` 容器执行 `apt-get install -y dnf dnf-plugins-core`
|
||||||
|
- 已在生产 `loan-pricing` 容器创建 `yum` 兼容入口:
|
||||||
|
- `/usr/local/bin/yum -> /usr/bin/dnf`
|
||||||
|
- 已执行 `yum --version`,返回 `4.14.0`
|
||||||
|
- 已人工核对脚本中的关键路径、端口与部署约束:
|
||||||
|
- Java 安装目录 `/home/webapp/env/java`
|
||||||
|
- Nginx 安装目录 `/home/webapp/env/nginx`
|
||||||
|
- 项目部署目录 `/home/webapp/loan-pricing`
|
||||||
|
- 前端端口 `63311`
|
||||||
|
- 后端端口 `63310`
|
||||||
|
- 由于当前已连接勘察容器为 Ubuntu 24.04,不具备本次脚本要求的 `yum` 安装前提,因此未在该容器直接执行安装流程,仅完成语法校验与逻辑核对
|
||||||
15
doc/implementation-report-2026-04-01-agents-plan-rule.md
Normal file
15
doc/implementation-report-2026-04-01-agents-plan-rule.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# AGENTS.md 双计划约束调整实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 更新仓库规则文件 `AGENTS.md`
|
||||||
|
- 将“根据设计文档产出前后端项目的实施计划时,输出两份执行文档”调整为“如果是前后端开发任务,根据设计文档产出实施计划时,输出两份执行文档”
|
||||||
|
- 明确双计划要求只适用于前后端开发类任务,不再默认覆盖文档类、脚本类或其他非前后端开发任务
|
||||||
|
|
||||||
|
## 调整原因
|
||||||
|
- 用户明确要求将“双 plan”约束收窄为仅对前后端开发任务生效
|
||||||
|
- 避免后续在纯文档、纯脚本或非前后端开发任务中重复产出不必要的前后端两份计划文档
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已检查 `AGENTS.md` 中“文档”章节的规则文字已更新
|
||||||
|
- 已检查本次实施记录保存路径为 `doc/implementation-report-2026-04-01-agents-plan-rule.md`
|
||||||
|
- 本次变更仅修改规则文档,未涉及代码或脚本执行
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# 后端启动配置切换为 uat 实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 将 `bin/prod/deploy_from_package.sh` 的后端启动 profile 从 `pro` 调整为 `uat`
|
||||||
|
- 将 `bin/prod/restart_java.sh` 的后端启动 profile 从 `pro` 调整为 `uat`
|
||||||
|
- 线上宿主机挂载脚本同步改为 `uat`,用于当前容器直接生效
|
||||||
|
|
||||||
|
## 原因说明
|
||||||
|
- 当前 `pro` 配置依赖的数据库地址 `64.127.23.7:3306` 从部署主机与容器内均不可达
|
||||||
|
- `uat` 配置依赖的数据库地址 `192.168.0.111:40628` 从部署主机可达,满足当前启动条件
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已验证宿主机到 `192.168.0.111:40628` 端口连通
|
||||||
|
- 已在仓库脚本中完成 `uat` 切换
|
||||||
|
- 已在宿主机挂载脚本 `/volume1/webapp/loan-pricing/deploy_from_package.sh` 与 `/volume1/webapp/restart_java.sh` 同步切换为 `uat`
|
||||||
|
- 已执行容器内命令 `/home/webapp/restart_java.sh restart`
|
||||||
|
- 已执行 `curl -I http://116.62.17.81:63310/`,返回 `HTTP/1.1 200`
|
||||||
|
- 已执行 `curl -X POST http://116.62.17.81:63311/prod-api/login/test ...`,返回 `{"code":200,...}`,确认 Nginx 反代与后端 `uat` 启动正常
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Nginx 目录权限修正实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 修正宿主机挂载目录 `/volume1/webapp` 的遍历权限,允许容器内 Nginx worker 访问业务目录
|
||||||
|
- 修正宿主机挂载目录 `/volume1/webapp/loan-pricing`、`/volume1/webapp/loan-pricing/frontend`、`/volume1/webapp/loan-pricing/frontend/dist` 的遍历权限
|
||||||
|
- 修正宿主机挂载目录 `/volume1/webapp/env`、`/volume1/webapp/env/nginx`、`/volume1/webapp/env/nginx/html` 的遍历权限
|
||||||
|
|
||||||
|
## 原因说明
|
||||||
|
- `loan-pricing` 容器内 Nginx worker 进程以 `nobody` 运行
|
||||||
|
- 宿主机挂载目录此前存在 `d---------` 或缺少其他用户执行权限的情况
|
||||||
|
- 结果导致容器内访问 `/home/webapp/loan-pricing/frontend/dist/index.html` 和 `/home/webapp/env/nginx/html/50x.html` 时出现 `Permission denied`
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已执行 `curl -I http://116.62.17.81:63311/`,返回 `HTTP/1.1 200 OK`
|
||||||
|
- 已执行 `curl http://116.62.17.81:63311/`,成功返回首页 HTML
|
||||||
|
- 已确认宿主机相关目录权限已调整为可供 Nginx worker 读取和遍历
|
||||||
19
doc/implementation-report-2026-04-01-nginx-worker-user.md
Normal file
19
doc/implementation-report-2026-04-01-nginx-worker-user.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Nginx Worker 用户显式配置实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 在 `deploy/nginx.conf` 中显式增加 `user nobody;`
|
||||||
|
- 在 `bin/prod/install_env.sh` 生成的 Nginx 配置模板中显式增加 `user nobody;`
|
||||||
|
- 计划将线上实际使用的 `/volume1/webapp/env/nginx/conf/nginx.conf` 同步改为显式 `user nobody;`
|
||||||
|
|
||||||
|
## 原因说明
|
||||||
|
- 当前线上 Nginx 实际以 `nobody` worker 进程运行
|
||||||
|
- 但配置文件未显式声明 worker 用户,后续重写配置时容易与实际运行态不一致
|
||||||
|
- 显式声明 `user nobody;` 可以让配置意图与当前运行方式保持一致
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已完成仓库配置文件与安装脚本模板修改
|
||||||
|
- 已同步修改线上实际配置 `/volume1/webapp/env/nginx/conf/nginx.conf`
|
||||||
|
- 已执行 `nginx -t -c /home/webapp/env/nginx/conf/nginx.conf`,语法校验通过
|
||||||
|
- 已执行 Nginx reload,容器内进程显示 `nobody` 作为 worker 用户运行
|
||||||
|
- 已执行 `curl -I http://116.62.17.81:63311/`,返回 `HTTP/1.1 200 OK`
|
||||||
|
- 已执行 `curl http://116.62.17.81:63311/prod-api/login/test`,返回状态码 `200`
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# 生产一键部署脚本后端实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 新增生产一键部署脚本 `bin/prod/deploy_from_package.sh`
|
||||||
|
- 新增部署脚本自测文件 `bin/prod/deploy_from_package_test.sh`
|
||||||
|
- 脚本内固定 `JAVA_BIN="/home/webapp/env/java/bin/java"`
|
||||||
|
- 新增脚本同目录唯一发布 zip 校验、包内唯一 `jar` 校验
|
||||||
|
- 新增旧版后端 `jar` 时间戳备份规则
|
||||||
|
- 新增后端 PID 文件、托管进程标记、停止旧进程、启动新进程和端口监听校验逻辑
|
||||||
|
|
||||||
|
## 实现说明
|
||||||
|
- 新脚本执行目录固定为脚本所在目录,要求同目录存在:
|
||||||
|
- `backend/`
|
||||||
|
- `frontend/`
|
||||||
|
- 1 个发布 zip
|
||||||
|
- 后端目标文件固定落到 `backend/ruoyi-admin.jar`
|
||||||
|
- 旧版后端 `jar` 通过 `ruoyi-admin.jar.<时间戳>.bak` 方式原地备份
|
||||||
|
- 启动时附加 `-Dloan.pricing.home=<脚本目录>`,用于识别当前脚本托管进程
|
||||||
|
- PID 文件固定写入 `backend/backend.pid`
|
||||||
|
- 后端日志固定写入 `backend/backend-console.log`
|
||||||
|
- 端口监听检测优先使用 `ss`,当前环境没有 `ss` 时改为使用 `lsof` 完成同一条校验
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已执行 `sh -n bin/prod/deploy_from_package.sh`,语法校验通过
|
||||||
|
- 已执行 `sh bin/prod/deploy_from_package_test.sh`
|
||||||
|
- 自测覆盖以下场景:
|
||||||
|
- 正常部署场景:
|
||||||
|
- 旧版 `jar` 被重命名为时间戳备份文件
|
||||||
|
- 新版 `jar` 落到 `backend/ruoyi-admin.jar`
|
||||||
|
- 后端 PID 文件和日志文件生成成功
|
||||||
|
- 假后端进程启动成功并监听测试端口
|
||||||
|
- 异常场景:
|
||||||
|
- 脚本同目录存在多个发布 zip 时,脚本按预期失败并输出错误信息
|
||||||
|
- 自测使用临时目录和临时假 `java` 进程,测试结束后已自动清理对应进程和目录
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# 生产一键部署脚本仅识别当前项目 jar 实施记录
|
||||||
|
|
||||||
|
## 问题现象
|
||||||
|
- 进程检测需要确认运行中的 `jar` 包必须是当前项目的正式后端包
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
- 之前 `collect_backend_pids()` 使用 `ps -ef` 时,只做了“命令行包含当前项目 jar 路径”的判断
|
||||||
|
- 这种包含匹配会把以下情况误算为当前项目运行进程:
|
||||||
|
- `-jar /当前项目/backend/ruoyi-admin.jar.bak`
|
||||||
|
- 其他仅把正式 jar 路径作为前缀的命令参数
|
||||||
|
- 结果会导致脚本误报“检测到后端已在运行,请先停止旧进程”
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 更新 `bin/prod/deploy_from_package.sh`
|
||||||
|
- `collect_backend_pids()` 继续使用 `ps -ef`
|
||||||
|
- 但匹配规则改为:
|
||||||
|
- 命令行中必须存在 `-jar`
|
||||||
|
- 且 `-jar` 的下一个参数必须严格等于当前项目的 `backend/ruoyi-admin.jar`
|
||||||
|
- 同时仍要求包含当前脚本的 `-Dloan.pricing.home=<脚本目录>` 标记
|
||||||
|
- 更新 `bin/prod/deploy_from_package_test.sh`
|
||||||
|
- 新增自测场景:
|
||||||
|
- 若 `ps -ef` 中存在 `-jar .../ruoyi-admin.jar.bak`,脚本必须忽略该进程并继续正常部署
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已执行 `sh -n bin/prod/deploy_from_package.sh`
|
||||||
|
- 已执行 `sh bin/prod/deploy_from_package_test.sh`
|
||||||
|
- 自测结果确认:
|
||||||
|
- 只有当前项目正式 `backend/ruoyi-admin.jar` 才会被识别为运行中的后端进程
|
||||||
|
- `.jar.bak` 等非正式后端包不会再误判
|
||||||
|
- 正常部署链路仍然通过
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# 生产一键部署脚本忽略 defunct 进程实施记录
|
||||||
|
|
||||||
|
## 问题现象
|
||||||
|
- 执行部署脚本时出现报错:
|
||||||
|
- `检测到后端已在运行,请先停止旧进程`
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
- 当前脚本使用 `ps -ef` 收集托管后端进程
|
||||||
|
- 简化后的实现只要在 `ps -ef` 中匹配到:
|
||||||
|
- `-Dloan.pricing.home=<脚本目录>`
|
||||||
|
- `backend/ruoyi-admin.jar`
|
||||||
|
就会返回对应 PID
|
||||||
|
- 如果系统中存在已经退出但仍显示为 `<defunct>` 的历史 Java 进程,该 PID 也会被误判为“旧后端仍在运行”
|
||||||
|
- 随后 `start_backend()` 在启动前再次调用 `collect_backend_pids()`,因此会直接报“检测到后端已在运行,请先停止旧进程”
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 更新 `bin/prod/deploy_from_package.sh`
|
||||||
|
- 在 `collect_backend_pids()` 中继续使用 `ps -ef`,但显式忽略包含 `<defunct>` 的进程行
|
||||||
|
- 更新 `bin/prod/deploy_from_package_test.sh`
|
||||||
|
- 新增自测场景:
|
||||||
|
- `ps -ef` 输出中存在匹配当前脚本标记和 jar 路径的 `<defunct>` 进程
|
||||||
|
- 脚本应忽略该记录并继续正常部署
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已执行 `sh -n bin/prod/deploy_from_package.sh`
|
||||||
|
- 已执行 `sh bin/prod/deploy_from_package_test.sh`
|
||||||
|
- 自测结果确认:
|
||||||
|
- 正常部署链路通过
|
||||||
|
- 多个发布 zip 失败场景通过
|
||||||
|
- `<defunct>` 进程不会再阻塞新后端启动
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# 生产一键部署脚本设计文档实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 新增设计文档 `docs/superpowers/specs/2026-04-01-production-one-click-deploy-design.md`
|
||||||
|
- 设计文档明确本次交付为单脚本自包含部署方案
|
||||||
|
- 设计文档明确 Java 路径写在脚本内,发布包从脚本同目录读取
|
||||||
|
- 设计文档明确旧版后端 `jar` 与旧版前端 `dist` 使用时间戳重命名备份
|
||||||
|
- 设计文档明确后端启停逻辑、PID 管理、端口校验和失败退出规则
|
||||||
|
- 设计文档明确交付文件边界与验证范围
|
||||||
|
|
||||||
|
## 约束确认
|
||||||
|
- 已按用户确认采用“方案一:单脚本自包含部署”
|
||||||
|
- 已按用户确认后端启动参数继续沿用 `--spring.profiles.active=pro --server.port=63310`
|
||||||
|
- 已按用户确认 Java 路径直接写在脚本内
|
||||||
|
- 已按用户确认部署逻辑全部写在同一个脚本里
|
||||||
|
|
||||||
|
## 评审说明
|
||||||
|
- 仓库 `AGENTS.md` 明确要求“不开启 subagent”
|
||||||
|
- 因此本次未执行 brainstorming 技能中的 subagent 评审环节,改为人工自检设计文档是否与已确认约束一致
|
||||||
|
- 已重点核对以下内容:
|
||||||
|
- 单脚本边界是否与用户要求一致
|
||||||
|
- 备份方式是否为“重命名 + 时间戳”
|
||||||
|
- 发布源是否限定为脚本同目录 zip
|
||||||
|
- 后端端口与 profile 是否与现有生产约束一致
|
||||||
|
- 设计中未引入额外兼容、补丁或兜底方案
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已检查设计文档保存路径为 `docs/superpowers/specs/2026-04-01-production-one-click-deploy-design.md`
|
||||||
|
- 已检查本次实施记录保存路径为 `doc/implementation-report-2026-04-01-production-one-click-deploy-design.md`
|
||||||
|
- 已人工核对设计文档中的方案对比、设计结论、执行流程、启停规则、失败处理、交付物和验证范围
|
||||||
|
- 本次变更仅新增文档,未修改脚本或代码,因此未执行运行类验证
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# 生产一键部署脚本前端实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 在 `bin/prod/deploy_from_package.sh` 中新增前端 `dist.zip` 唯一校验逻辑
|
||||||
|
- 新增旧版 `frontend/dist` 时间戳备份规则
|
||||||
|
- 新增新版 `frontend/dist.zip` 替换逻辑
|
||||||
|
- 新增前端静态资源解压到 `frontend/dist/` 的逻辑
|
||||||
|
- 新增 `resolve_frontend_source_dir`,支持从 `dist.zip` 解压结果中定位实际前端根目录
|
||||||
|
|
||||||
|
## 范围确认
|
||||||
|
- 本次前端交付物仅为部署脚本中的静态包部署链路
|
||||||
|
- 未修改 `ruoyi-ui` 下任何页面、接口、构建配置或打包脚本
|
||||||
|
- 如后续出现页面需求,需要回到新需求重新做设计和计划
|
||||||
|
|
||||||
|
## 实现说明
|
||||||
|
- 脚本会校验发布包中必须且只能存在 1 个 `dist.zip`
|
||||||
|
- 若 `frontend/dist` 已存在,则原地重命名为 `dist-<时间戳>`
|
||||||
|
- 新版前端压缩包统一替换到 `frontend/dist.zip`
|
||||||
|
- 新版前端资源统一解压到 `frontend/dist/`
|
||||||
|
- 解压结果支持以下结构:
|
||||||
|
- 解压根目录直接为前端文件
|
||||||
|
- 解压后为 `dist/index.html`
|
||||||
|
- 其他情况下通过 `find index.html` 自动定位前端根目录
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已执行 `sh bin/prod/deploy_from_package_test.sh`
|
||||||
|
- 自测覆盖以下前端链路:
|
||||||
|
- 旧版 `frontend/dist` 被重命名为时间戳备份目录
|
||||||
|
- 新版 `frontend/dist.zip` 成功替换
|
||||||
|
- 新版前端资源成功解压到 `frontend/dist/index.html`
|
||||||
|
- 解压后的页面内容与发布包内容一致
|
||||||
|
- 已执行 `git status --short ruoyi-ui`
|
||||||
|
- 已确认 `ruoyi-ui` 本次没有新增或修改的源码文件被纳入改动范围
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# 生产一键部署脚本 netstat 端口检测兼容实施记录
|
||||||
|
|
||||||
|
## 问题现象
|
||||||
|
- 运行 `bin/prod/deploy_from_package.sh` 时出现报错:
|
||||||
|
- `[2026-04-01 02:45:09] 缺少端口检测命令: ss 或 lsof`
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
- 脚本启动后端前会先检查端口检测命令
|
||||||
|
- 之前的实现只支持 `ss` 和 `lsof`
|
||||||
|
- 用户实际环境中两者都不可用,因此脚本在前置校验阶段直接退出
|
||||||
|
- 当前仓库开发环境中还存在 `netstat`,说明“只支持 `ss`/`lsof`”不是部署链路本身的要求,而是脚本实现约束过窄
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 更新 `bin/prod/deploy_from_package.sh`
|
||||||
|
- 将端口检测命令支持范围从:
|
||||||
|
- `ss`
|
||||||
|
- `lsof`
|
||||||
|
扩展为:
|
||||||
|
- `ss`
|
||||||
|
- `lsof`
|
||||||
|
- `netstat`
|
||||||
|
- 更新端口检测失败提示文案为“缺少端口检测命令: ss、lsof 或 netstat”
|
||||||
|
- 更新 `bin/prod/deploy_from_package_test.sh`
|
||||||
|
- 新增 `netstat` 回退场景自测,验证在 `PATH` 中无 `ss`、无 `lsof`、仅有 `netstat` 时脚本仍可正常完成部署
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已执行 `sh -n bin/prod/deploy_from_package.sh`
|
||||||
|
- 已执行 `sh bin/prod/deploy_from_package_test.sh`
|
||||||
|
- 自测结果覆盖:
|
||||||
|
- 正常部署成功
|
||||||
|
- 多个发布 zip 失败
|
||||||
|
- 仅 `netstat` 可用时,端口监听检测仍然通过
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# 生产一键部署脚本实施计划文档实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 新增后端实施计划文档 `docs/superpowers/plans/2026-04-01-production-one-click-deploy-backend-plan.md`
|
||||||
|
- 新增前端实施计划文档 `docs/superpowers/plans/2026-04-01-production-one-click-deploy-frontend-plan.md`
|
||||||
|
- 后端计划明确单脚本实现主体由 `bin/prod/deploy_from_package.sh` 承担
|
||||||
|
- 后端计划明确发布包校验、旧版 jar 备份、PID 管理、端口等待和后端实施记录要求
|
||||||
|
- 前端计划明确本次不修改 `ruoyi-ui` 源码,只处理部署脚本中的 `dist.zip` 校验、旧版 `dist` 备份、解压与前端实施记录
|
||||||
|
|
||||||
|
## 计划拆分说明
|
||||||
|
- 已根据仓库 `AGENTS.md` 要求,按设计文档产出两份执行文档:
|
||||||
|
- 一份后端实施计划
|
||||||
|
- 一份前端实施计划
|
||||||
|
- 两份计划均基于设计文档 `docs/superpowers/specs/2026-04-01-production-one-click-deploy-design.md`
|
||||||
|
- 后端计划负责脚本主体实现,前端计划负责前端静态包部署链路与无前端源码改动边界确认
|
||||||
|
|
||||||
|
## 评审说明
|
||||||
|
- `writing-plans` 技能要求在计划完成后走 reviewer subagent 评审环节
|
||||||
|
- 仓库 `AGENTS.md` 明确要求“不开启 subagent”
|
||||||
|
- 因此本次未开启 plan reviewer subagent,改为人工自检以下内容:
|
||||||
|
- 两份计划文件路径是否正确
|
||||||
|
- 后端计划是否覆盖单脚本实现、验证与实施记录
|
||||||
|
- 前端计划是否覆盖 `dist.zip`、`frontend/dist` 和 `ruoyi-ui` 无改动边界
|
||||||
|
- 计划中的提交命令是否使用中文提交信息
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已检查后端计划保存路径为 `docs/superpowers/plans/2026-04-01-production-one-click-deploy-backend-plan.md`
|
||||||
|
- 已检查前端计划保存路径为 `docs/superpowers/plans/2026-04-01-production-one-click-deploy-frontend-plan.md`
|
||||||
|
- 已人工核对两份计划的 Header、Goal、Architecture、Tech Stack、Task 和 Step 结构
|
||||||
|
- 已人工核对计划中引用的脚本路径、设计文档路径和实施记录路径与仓库当前目录结构一致
|
||||||
|
- 本次变更仅新增计划文档与实施记录,未执行脚本实现或运行类验证
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# 生产一键部署脚本改用 ps -ef 识别进程实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 更新 `bin/prod/deploy_from_package.sh`
|
||||||
|
- 将后端进程识别与收集方式从 `pgrep` 改为 `ps -ef`
|
||||||
|
- 删除脚本对 `pgrep` 命令的前置依赖
|
||||||
|
- 更新 `bin/prod/deploy_from_package_test.sh`
|
||||||
|
- 新增断言,要求脚本不能再依赖 `pgrep`,并必须包含 `ps -ef` 进程识别逻辑
|
||||||
|
|
||||||
|
## 调整原因
|
||||||
|
- 用户要求使用 `ps -ef` 判断进程
|
||||||
|
- 旧实现依赖 `pgrep -f` 收集托管进程,不符合当前要求
|
||||||
|
|
||||||
|
## 实现说明
|
||||||
|
- `is_managed_backend_pid` 现在通过 `ps -ef | awk` 按 PID 读取目标进程行
|
||||||
|
- `collect_backend_pids` 现在通过 `ps -ef | awk` 同时匹配:
|
||||||
|
- `-Dloan.pricing.home=<脚本目录>`
|
||||||
|
- `backend/ruoyi-admin.jar`
|
||||||
|
- 只有同时满足托管标记和目标 jar 路径的进程才会被纳入停止范围
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已执行 `sh -n bin/prod/deploy_from_package.sh`
|
||||||
|
- 已执行 `sh bin/prod/deploy_from_package_test.sh`
|
||||||
|
- 自测结果确认:
|
||||||
|
- 脚本中已不存在 `pgrep`
|
||||||
|
- 脚本中已使用 `ps -ef`
|
||||||
|
- 正常部署链路仍然通过
|
||||||
|
- 多个发布 zip 失败场景仍然通过
|
||||||
|
- `netstat` 端口检测回退场景仍然通过
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# 生产一键部署脚本参考 deploy.zip 调整实施记录
|
||||||
|
|
||||||
|
## 参考压缩包
|
||||||
|
- 参考文件:`deploy/deploy.zip`
|
||||||
|
- 已核对压缩包结构:
|
||||||
|
- `deploy/ruoyi-admin.jar`
|
||||||
|
- `deploy/dist.zip`
|
||||||
|
- `__MACOSX/deploy/._ruoyi-admin.jar`
|
||||||
|
|
||||||
|
## 问题原因
|
||||||
|
- 原脚本按 `find ... -name '*.jar'` 统计后端产物
|
||||||
|
- 参考压缩包中包含 `__MACOSX/deploy/._ruoyi-admin.jar`
|
||||||
|
- 该文件会被误算成第二个 `jar`,导致脚本报错“后端 jar 数量不正确,期望 1 个,实际 2 个”
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 更新 `bin/prod/deploy_from_package.sh`
|
||||||
|
- 在后端 `jar` 和前端 `dist.zip` 搜索时忽略:
|
||||||
|
- `__MACOSX` 目录下文件
|
||||||
|
- `._*` 资源分叉文件
|
||||||
|
- 更新 `bin/prod/deploy_from_package_test.sh`
|
||||||
|
- 自测发布包结构改为贴近真实 `deploy/deploy.zip`:
|
||||||
|
- 外层为 `deploy/ruoyi-admin.jar`
|
||||||
|
- 外层为 `deploy/dist.zip`
|
||||||
|
- 带 `__MACOSX` 资源文件
|
||||||
|
- 内层 `dist.zip` 也带 `dist/` 和 `__MACOSX/`
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已执行 `sh -n bin/prod/deploy_from_package.sh`
|
||||||
|
- 已执行 `sh bin/prod/deploy_from_package_test.sh`
|
||||||
|
- 自测结果确认:
|
||||||
|
- 脚本可正确识别参考压缩包结构
|
||||||
|
- `__MACOSX` 和 `._*` 不会再被误判为有效发布产物
|
||||||
|
- 正常部署链路仍然通过
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# 生产一键部署脚本简化实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 简化 `bin/prod/deploy_from_package.sh`
|
||||||
|
- 删除端口检测逻辑,不再依赖 `ss`、`lsof` 或 `netstat`
|
||||||
|
- 删除前端解压兼容逻辑,不再探测多种 `dist.zip` 目录结构
|
||||||
|
- 保留并简化进程识别逻辑,直接使用 `ps -ef`
|
||||||
|
- 简化 `bin/prod/deploy_from_package_test.sh`
|
||||||
|
- 删除端口监听断言和端口检测回退场景
|
||||||
|
- 新增脚本文本断言,确认已移除端口检测和解压兼容 helper
|
||||||
|
|
||||||
|
## 当前脚本边界
|
||||||
|
- 仍然保留以下核心能力:
|
||||||
|
- 脚本同目录唯一发布 zip 校验
|
||||||
|
- 发布包内唯一 `jar` 和唯一 `dist.zip` 校验
|
||||||
|
- 旧版后端 `jar` 时间戳备份
|
||||||
|
- 旧版 `frontend/dist` 时间戳备份
|
||||||
|
- 使用 `ps -ef` 停止旧后端进程
|
||||||
|
- 替换新 `jar`
|
||||||
|
- 将 `dist.zip` 直接解压到 `frontend/`
|
||||||
|
- 启动新的 Java 进程并写入 PID 文件
|
||||||
|
|
||||||
|
## 删除的复杂逻辑
|
||||||
|
- 不再等待端口监听成功
|
||||||
|
- 不再兼容 `ss`、`lsof`、`netstat` 三种端口检测方式
|
||||||
|
- 不再兼容 `dist.zip` 根目录、`dist/index.html` 和自动 `find index.html` 多种结构
|
||||||
|
- 当前前端解压只接受一种约定:
|
||||||
|
- `dist.zip` 解压到 `frontend/` 后必须得到 `frontend/dist/`
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已执行 `sh -n bin/prod/deploy_from_package.sh`
|
||||||
|
- 已执行 `sh bin/prod/deploy_from_package_test.sh`
|
||||||
|
- 自测确认:
|
||||||
|
- 脚本使用 `ps -ef`
|
||||||
|
- 脚本中已移除端口检测 helper
|
||||||
|
- 脚本中已移除前端解压兼容 helper
|
||||||
|
- 正常部署链路仍然通过
|
||||||
|
- 多个发布 zip 失败场景仍然通过
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# 2026-04-03 登录页默认账号密码移除实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 移除登录页 `ruoyi-ui/src/views/login.vue` 中硬编码的默认账号 `admin` 与默认密码 `admin123`。
|
||||||
|
- 保留 `getCookie()` 现有逻辑,确保用户勾选“记住密码”后仍可通过 cookie 回填登录信息。
|
||||||
|
- 新增前端回归脚本 `ruoyi-ui/tests/login-default-credentials.test.js`,校验默认值为空且 cookie 回填逻辑未被破坏。
|
||||||
|
|
||||||
|
## 验证记录
|
||||||
|
- 变更前执行 `node tests/login-default-credentials.test.js`,断言“登录页默认用户名应为空字符串”失败,证明问题可复现。
|
||||||
|
- 变更后再次执行 `node tests/login-default-credentials.test.js`,预期全部断言通过。
|
||||||
|
- 启动前端页面后在浏览器访问登录页,确认账号、密码输入框默认不再预填内容。
|
||||||
|
|
||||||
|
## 影响范围
|
||||||
|
- 仅影响登录页初始化展示行为,不修改登录接口、加密逻辑、验证码逻辑与记住密码逻辑。
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# 生产后端重启脚本实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 收敛生产后端重启脚本 `bin/prod/restart_java.sh`
|
||||||
|
- 脚本固定面向已部署的 `backend/ruoyi-admin.jar` 执行启停,不再包含构建逻辑
|
||||||
|
- 后端启动 profile 固定为 `pro`
|
||||||
|
- Java 路径统一为 `/home/webapp/env/java/bin/java`,与现有生产安装脚本保持一致
|
||||||
|
- 移除 `root` 执行校验与端口监听校验,只保留 `start|stop|restart|status` 所需的最小启停逻辑
|
||||||
|
- 新增脚本自测文件 `bin/prod/restart_java_test.sh`
|
||||||
|
|
||||||
|
## 实现说明
|
||||||
|
- `start` 仅检查 Java 可执行文件、目标 jar 是否存在以及当前是否已有同脚本托管进程
|
||||||
|
- `stop` 继续基于 PID 文件和 `-Dloan.pricing.home=/home/webapp/loan-pricing` 进程标记识别并停止当前后端进程
|
||||||
|
- `restart` 按“先停后起”执行,适用于生产环境已部署 jar 的直接重启
|
||||||
|
- `status` 仅返回脚本托管进程状态,不再增加端口占用类附加判断
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已执行 `sh bin/prod/restart_java_test.sh`
|
||||||
|
- 已验证以下场景:
|
||||||
|
- 脚本固定使用 `/home/webapp/env/java`
|
||||||
|
- 脚本固定使用 `--spring.profiles.active=pro`
|
||||||
|
- 脚本不包含 `mvn`、`require_root`、`ss/lsof/netstat` 相关依赖
|
||||||
|
- `start -> status -> restart -> stop` 流程执行通过
|
||||||
|
- 自测使用临时目录中的假 `java` 进程完成,测试结束后已自动清理对应进程和临时目录
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# 个人模型详情缺失展示字段补齐实施记录
|
||||||
|
|
||||||
|
## 实施时间
|
||||||
|
- 2026-04-03
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 补齐个人详情页“业务信息”中的 `借款期限`
|
||||||
|
- 补齐个人模型输出“测算结果”中的 5 个字段:
|
||||||
|
- `历史利率`
|
||||||
|
- `产品最低利率下限`
|
||||||
|
- `平滑幅度`
|
||||||
|
- `最终测算利率`
|
||||||
|
- `参考利率`
|
||||||
|
- 在后端 `ModelRetailOutputFields` 中新增对应 5 个字段定义
|
||||||
|
- 在零售模型 mock 数据中补齐对应 5 个字段样例值
|
||||||
|
- 新增零售模型输出表结构迁移脚本,并同步更新建表基线 SQL
|
||||||
|
- 新增后端字段断言测试与前端源码断言脚本
|
||||||
|
|
||||||
|
## 修改文件
|
||||||
|
- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/entity/ModelRetailOutputFields.java`
|
||||||
|
- `ruoyi-loan-pricing/src/main/resources/data/retail_output.json`
|
||||||
|
- `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/domain/entity/ModelRetailOutputFieldsTest.java`
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue`
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue`
|
||||||
|
- `ruoyi-ui/tests/retail-display-fields.test.js`
|
||||||
|
- `ruoyi-ui/package.json`
|
||||||
|
- `sql/add_model_retail_output_rate_fields_20260403.sql`
|
||||||
|
- `sql/model_retail.sql`
|
||||||
|
- `sql/loan_pricing_schema_20260328.sql`
|
||||||
|
- `sql/loan_pricing_prod_init_20260331.sql`
|
||||||
|
- `doc/2026-04-03-retail-display-fields-design.md`
|
||||||
|
- `doc/2026-04-03-retail-display-fields-backend-plan.md`
|
||||||
|
- `doc/2026-04-03-retail-display-fields-frontend-plan.md`
|
||||||
|
- `doc/implementation-report-2026-04-03-retail-display-fields.md`
|
||||||
|
|
||||||
|
## 验证方式
|
||||||
|
1. 新增后端测试,断言 `ModelRetailOutputFields` 包含 5 个新增字段,先失败后通过
|
||||||
|
2. 新增前端源码断言脚本,断言个人详情页与模型输出页已补齐字段,先失败后通过
|
||||||
|
3. 执行前端生产构建,确认页面代码可正常打包
|
||||||
|
4. 检查开发库 `model_retail_output_fields` 表结构,确认最初缺少 5 个新列
|
||||||
|
5. 执行 `sql/add_model_retail_output_rate_fields_20260403.sql` 到开发库,并再次确认 5 个新列存在
|
||||||
|
6. 重新编译并重启后端,确保新的实体字段已进入运行中的 SQL 映射
|
||||||
|
7. 创建新的个人流程 `20260403100514909`,调用详情接口确认返回以下真实值:
|
||||||
|
- `loanRateHistory = 6.40`
|
||||||
|
- `minRateProduct = 5.50`
|
||||||
|
- `smoothRange = -0.10`
|
||||||
|
- `finalCalculateRate = 6.05`
|
||||||
|
- `referenceRate = 5.95`
|
||||||
|
8. 启动前端开发服务并使用浏览器自动化打开详情页,确认:
|
||||||
|
- 页面出现 `借款期限`
|
||||||
|
- 切换到“测算结果”页签后,5 个新增字段及对应值均可见
|
||||||
|
9. 验证结束后,停止本次启动的前后端进程
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -Dtest=ModelRetailOutputFieldsTest test` 首次失败,补齐后通过
|
||||||
|
- `npm --prefix ruoyi-ui run test:retail-display-fields` 首次失败,补齐后通过
|
||||||
|
- `npm --prefix ruoyi-ui run build:prod` 成功,输出包含 `Build complete.`
|
||||||
|
- 已确认开发库 `model_retail_output_fields` 初始缺少:
|
||||||
|
- `loan_rate_history`
|
||||||
|
- `min_rate_product`
|
||||||
|
- `smooth_range`
|
||||||
|
- `final_calculate_rate`
|
||||||
|
- `reference_rate`
|
||||||
|
- 已执行迁移脚本并确认以上 5 列存在
|
||||||
|
- 已确认旧后端进程因未加载最新依赖导致 SQL 仍缺新列,重编译并重启后问题消失
|
||||||
|
- 已创建个人流程 `20260403100514909` 并通过详情接口拿到 5 个新增字段的真实值
|
||||||
|
- 已通过浏览器自动化确认个人详情页展示位与“测算结果”页签展示均正确
|
||||||
|
- 本次验证期间启动的前后端进程均已停止
|
||||||
|
|
||||||
|
## 说明
|
||||||
|
- `loanTerm` 本次仅补齐详情页展示位;个人创建表单当前无该字段录入入口,不属于本次“模型返回字段更新”范围
|
||||||
|
- 为保证新字段在新环境中也可正常落库,本次同步更新了零售模型输出表的建表基线 SQL
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# 2026-04-09 默认切换 Node 25 以支持 Playwright 实施记录
|
||||||
|
|
||||||
|
## 变更内容
|
||||||
|
- 将 `nvm` 默认别名从 `14` 调整为 `25`
|
||||||
|
- 清理了本次验证过程中残留的 Playwright 浏览器安装进程
|
||||||
|
|
||||||
|
## 执行命令
|
||||||
|
- `zsh -lic 'nvm alias default 25'`
|
||||||
|
|
||||||
|
## 变更结果
|
||||||
|
- 新开的交互式 `zsh` 默认 Node 版本变为 `v25.9.0`
|
||||||
|
- 默认 npm/npx 版本变为 `11.12.1`
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- `zsh -lic 'node -v && npm -v && npx -v && nvm current && nvm alias default'`
|
||||||
|
- `node v25.9.0`
|
||||||
|
- `npm 11.12.1`
|
||||||
|
- `npx 11.12.1`
|
||||||
|
- `nvm current = v25.9.0`
|
||||||
|
- `default -> 25 (-> v25.9.0 *)`
|
||||||
|
- `zsh -lic '... playwright_cli.sh --help'`
|
||||||
|
- Playwright CLI 帮助输出正常
|
||||||
|
- `zsh -lic '... playwright_cli.sh --session verify-default-25 open https://example.com && snapshot && close && list'`
|
||||||
|
- 页面成功打开
|
||||||
|
- 页面标题为 `Example Domain`
|
||||||
|
- 快照成功输出
|
||||||
|
- 浏览器关闭后 `list` 返回 `(no browsers)`
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
- 默认 shell 环境下已可直接使用 Playwright,无需再先手动执行 `nvm use 25`
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# 2026-04-09 安装 Node 25 与 Node 14 实施记录
|
||||||
|
|
||||||
|
## 变更内容
|
||||||
|
- 使用 `nvm` 安装 `node v25.9.0`
|
||||||
|
- 使用 `nvm` 安装 `node v14.21.3`
|
||||||
|
- 调整 `/Users/wkc/.npmrc`,删除与 `nvm` 冲突的 `prefix=~/.npm-global`
|
||||||
|
- 保留 npm 镜像配置:`registry=https://registry.npmmirror.com`
|
||||||
|
|
||||||
|
## 处理过程
|
||||||
|
- `node 25` 通过 `nvm` 正常安装成功
|
||||||
|
- `node 14` 在 Apple Silicon 原生环境下无法直接下载 `darwin-arm64` 安装包
|
||||||
|
- 原生源码编译 `node 14` 失败,错误来自当前 macOS Command Line Tools/SDK 与旧版 `node 14` 源码不兼容
|
||||||
|
- 改为通过 Rosetta 以 `x64` 方式安装 `node 14`,安装成功
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- `zsh -lic 'nvm use 25 && node -v && npm -v'` 验证结果:
|
||||||
|
- `node v25.9.0`
|
||||||
|
- `npm 11.12.1`
|
||||||
|
- `zsh -lic 'nvm use 14 && node -v && npm -v'` 验证结果:
|
||||||
|
- `node v14.21.3`
|
||||||
|
- `npm 6.14.18`
|
||||||
|
- `arch -x86_64 /bin/zsh -lic 'nvm use 14 && node -v && npm -v'` 验证结果:
|
||||||
|
- `node v14.21.3`
|
||||||
|
- `npm 6.14.18`
|
||||||
|
- 新开的交互式 `zsh` 默认版本:
|
||||||
|
- `node v14.21.3`
|
||||||
|
- `npm 6.14.18`
|
||||||
|
|
||||||
|
## 备注
|
||||||
|
- `nvm` 当前默认别名已指向 `14`
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# 2026-04-09 Node 卸载与 nvm 安装实施记录
|
||||||
|
|
||||||
|
## 变更内容
|
||||||
|
- 卸载了 Homebrew 安装的 `node 25.8.1_1`
|
||||||
|
- 删除了本地目录 `/Users/wkc/.local/node-v14.21.3-darwin-x64`
|
||||||
|
- 更新了 `/Users/wkc/.zshrc`
|
||||||
|
- 安装了 `nvm 0.40.4`
|
||||||
|
|
||||||
|
## shell 配置调整
|
||||||
|
- 删除旧配置:`export PATH="/Users/wkc/.local/node-v14.21.3-darwin-x64/bin:$PATH"`
|
||||||
|
- 新增 `nvm` 初始化配置:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export NVM_DIR="$HOME/.nvm"
|
||||||
|
[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh"
|
||||||
|
[ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && \. "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 交互式 `zsh` 下 `nvm --version` 返回 `0.40.4`
|
||||||
|
- `node` 命令已不存在,说明当前环境中已无非 `nvm` 管理的 Node 版本
|
||||||
|
- `nvm ls` 返回 `N/A`,说明尚未通过 `nvm` 安装任何 Node 版本
|
||||||
|
|
||||||
|
## 备注
|
||||||
|
- `brew uninstall node` 过程中触发了 Homebrew 自动移除若干仅供该版本 Node 使用的依赖库
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# 证件输入校验移除实施记录
|
||||||
|
|
||||||
|
## 实施时间
|
||||||
|
- 2026-04-09
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 移除个人新增弹窗中的证件号码格式校验
|
||||||
|
- 移除企业新增弹窗中的证件号码格式校验
|
||||||
|
- 两个新增弹窗的证件号码规则统一保留为必填校验
|
||||||
|
- 新增前端源码断言,约束后续不再恢复证件号码格式校验
|
||||||
|
|
||||||
|
## 修改文件
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue`
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue`
|
||||||
|
- `ruoyi-ui/tests/id-number-validation-removal.test.js`
|
||||||
|
- `ruoyi-ui/package.json`
|
||||||
|
- `doc/implementation-report-2026-04-09-remove-id-number-validation.md`
|
||||||
|
|
||||||
|
## 验证方式
|
||||||
|
1. `npm --prefix ruoyi-ui run test:id-number-validation-removal`
|
||||||
|
2. `npm --prefix ruoyi-ui run build:prod`
|
||||||
|
|
||||||
|
## 说明
|
||||||
|
- 本次移除的是前端证件号码格式校验,不影响证件号码必填约束
|
||||||
|
- 后端本次未新增或调整证件号码格式校验逻辑
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# 上虞个人利率测算输入参数文档产出记录
|
||||||
|
|
||||||
|
## 实施时间
|
||||||
|
- 2026-04-09
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 新增个人利率测算输入参数对齐设计文档
|
||||||
|
- 新增个人利率测算输入参数前端实施计划
|
||||||
|
- 新增个人利率测算输入参数后端实施计划
|
||||||
|
- 按 Excel `入参` sheet 整理个人新增弹窗字段获取方式和模型调用参数来源
|
||||||
|
- 明确 `loanTerm` 使用固定年限下拉,选项按 Excel 组织
|
||||||
|
- 明确系统字段 `serialNum`、`orgCode`、`runType`、`custType` 继续自动带值
|
||||||
|
- 明确个人开关字段在模型调用层转换为 `0/1`
|
||||||
|
- 补充确认后端最终发给模型的 16 个输入参数、来源、请求格式与验证口径
|
||||||
|
|
||||||
|
## 修改文件
|
||||||
|
- `doc/2026-04-09-shangyu-retail-input-params-design.md`
|
||||||
|
- `doc/2026-04-09-shangyu-retail-input-params-frontend-plan.md`
|
||||||
|
- `doc/2026-04-09-shangyu-retail-input-params-backend-plan.md`
|
||||||
|
- `doc/implementation-report-2026-04-09-shangyu-retail-input-params-plans.md`
|
||||||
|
|
||||||
|
## 说明
|
||||||
|
- 设计文档保存路径已核对为项目现有的 `doc/` 目录
|
||||||
|
- 按项目要求,本次实施计划拆分为前端与后端两份文档
|
||||||
|
- 由于仓库约束为“不开启 subagent”,文档评审环节未使用子代理,后续执行时将在当前会话内推进
|
||||||
|
- 本次仅产出设计与计划文档,尚未进入代码实施阶段
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# 上虞个人利率测算输入参数对齐实施记录
|
||||||
|
|
||||||
|
## 实施时间
|
||||||
|
- 2026-04-09
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 个人新增弹窗补齐 `loanPurpose`、`loanTerm`
|
||||||
|
- 个人新增弹窗 `loanTerm` 固定为 `1-6` 年
|
||||||
|
- 个人新增弹窗 `collType` 选项统一为 `一类/二类/三类`
|
||||||
|
- 个人新增弹窗开关字段提交值由 `true/false` 调整为 `1/0`
|
||||||
|
- 个人详情页补齐 `贷款用途` 展示
|
||||||
|
- 个人与企业详情、模型输出布尔展示兼容 `1/0`
|
||||||
|
- 后端个人创建 DTO 补齐 `loanPurpose`、`loanTerm`
|
||||||
|
- 后端个人 DTO 到流程实体映射补齐 `loanPurpose`、`loanTerm`
|
||||||
|
- 后端模型调用 DTO 补齐 `loanTerm`、`loanLoop`
|
||||||
|
- 后端个人模型调用前统一将 `bizProof`、`loanLoop`、`collThirdParty` 规范为 `0/1`
|
||||||
|
- `orgCode` 统一为 `892000`
|
||||||
|
- `ModelInvokeDTO` 注释、接口文档、SQL 基线和迁移脚本同步统一为 `892000`
|
||||||
|
- 新增前端源码断言与后端单元测试
|
||||||
|
|
||||||
|
## 修改文件
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue`
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue`
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue`
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/components/CorporateWorkflowDetail.vue`
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue`
|
||||||
|
- `ruoyi-ui/tests/personal-create-input-params.test.js`
|
||||||
|
- `ruoyi-ui/package.json`
|
||||||
|
- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java`
|
||||||
|
- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java`
|
||||||
|
- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java`
|
||||||
|
- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java`
|
||||||
|
- `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java`
|
||||||
|
- `doc/api/loan-pricing-workflow-api.md`
|
||||||
|
- `sql/loan_pricing_workflow.sql`
|
||||||
|
- `sql/loan_pricing_schema_20260328.sql`
|
||||||
|
- `sql/loan_pricing_prod_init_20260331.sql`
|
||||||
|
- `sql/fix_comments.sql`
|
||||||
|
- `sql/fix_all_comments.sql`
|
||||||
|
- `sql/update_org_code_default_20260409.sql`
|
||||||
|
- `doc/2026-04-09-shangyu-retail-input-params-design.md`
|
||||||
|
- `doc/2026-04-09-shangyu-retail-input-params-frontend-plan.md`
|
||||||
|
- `doc/implementation-report-2026-04-09-shangyu-retail-input-params.md`
|
||||||
|
|
||||||
|
## 数据库处理
|
||||||
|
1. 执行 `sql/update_org_code_default_20260409.sql`
|
||||||
|
2. 将 `loan_pricing_workflow.org_code` 默认值修改为 `892000`
|
||||||
|
3. 将存量 `loan_pricing_workflow.org_code` 非 `892000` 的记录统一更新为 `892000`
|
||||||
|
|
||||||
|
## 验证方式
|
||||||
|
1. 前端源码断言:
|
||||||
|
- `npm --prefix ruoyi-ui run test:personal-create-input-params`
|
||||||
|
- `npm --prefix ruoyi-ui run test:retail-display-fields`
|
||||||
|
2. 后端单元测试:
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -Dtest=LoanPricingModelServiceTest,LoanPricingModelServicePersonalParamsTest test`
|
||||||
|
3. 前端构建:
|
||||||
|
- `npm --prefix ruoyi-ui run build:prod`
|
||||||
|
4. 数据库验证:
|
||||||
|
- 查询 `loan_pricing_workflow.org_code` 字段默认值
|
||||||
|
- 查询存量数据中是否仍存在非 `892000` 记录
|
||||||
|
5. 接口验证:
|
||||||
|
- `/login/test` 获取 token
|
||||||
|
- `POST /loanPricing/workflow/create/personal` 正常场景
|
||||||
|
- `POST /loanPricing/workflow/create/personal` 缺少 `loanPurpose` 场景
|
||||||
|
- `POST /loanPricing/workflow/create/personal` 分支值场景
|
||||||
|
- `GET /loanPricing/workflow/{serialNum}` 验证回显
|
||||||
|
6. 页面验证:
|
||||||
|
- 启动前端 dev server
|
||||||
|
- 使用浏览器打开流程列表页
|
||||||
|
- 校验新增弹窗下拉选项
|
||||||
|
- 页面创建个人流程并打开详情页确认回显
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- `npm --prefix ruoyi-ui run test:personal-create-input-params` 通过
|
||||||
|
- `npm --prefix ruoyi-ui run test:retail-display-fields` 通过
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -Dtest=LoanPricingModelServiceTest,LoanPricingModelServicePersonalParamsTest test` 通过
|
||||||
|
- `npm --prefix ruoyi-ui run build:prod` 通过,输出 `Build complete.`
|
||||||
|
- 数据库验证结果:
|
||||||
|
- `loan_pricing_workflow.org_code` 默认值为 `892000`
|
||||||
|
- 存量非 `892000` 记录数为 `0`
|
||||||
|
- 接口验证结果:
|
||||||
|
- 正常场景创建成功,返回 `orgCode=892000`,并持久化 `loanPurpose`、`loanTerm`
|
||||||
|
- 缺少 `loanPurpose` 时返回 `贷款用途不能为空`
|
||||||
|
- 分支场景详情回显 `bizProof=0`、`loanLoop=1`、`collThirdParty=0`
|
||||||
|
- 页面验证结果:
|
||||||
|
- 新增弹窗显示 `贷款用途`
|
||||||
|
- 借款期限下拉仅包含 `1-6`
|
||||||
|
- 抵质押类型下拉为 `一类/二类/三类`
|
||||||
|
- 页面创建流程成功后,详情页展示 `贷款用途=经营`、`借款期限=6`
|
||||||
|
|
||||||
|
## 说明
|
||||||
|
- 浏览器验证使用系统 `Google Chrome.app`
|
||||||
|
- 本次验证期间启动的后端、前端和浏览器进程已在任务结束前关闭
|
||||||
26
doc/implementation-report-2026-04-09-start-script-ps-ef.md
Normal file
26
doc/implementation-report-2026-04-09-start-script-ps-ef.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# 启动脚本进程判断改为 ps -ef 实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 将 `bin/prod/restart_java.sh` 中的后端进程收集逻辑由 `pgrep -f` 改为 `ps -ef | awk`
|
||||||
|
- 将 `bin/restart_java_backend.sh` 中的后端进程收集逻辑由 `pgrep -f` 改为 `ps -ef | awk`
|
||||||
|
- 删除 `bin/restart_java_backend.sh` 中对 `pgrep` 命令的依赖校验
|
||||||
|
- 更新 `bin/prod/restart_java_test.sh`,补充 `ps -ef` / `pgrep` 约束校验,并修正测试夹具中的 JDK 目录
|
||||||
|
- 新增 `bin/restart_java_backend_test.sh`,校验本地后端重启脚本已改用 `ps -ef`
|
||||||
|
|
||||||
|
## 实现说明
|
||||||
|
|
||||||
|
- 两份脚本都只在 `ps -ef` 结果中匹配同时满足“包含脚本标记参数”和“`-jar` 指向目标 jar”这两个条件的 Java 进程
|
||||||
|
- 进程筛选时继续忽略 `<defunct>` 记录,避免误判僵尸进程
|
||||||
|
- 现有 PID 文件校验逻辑保持不变,本次只收敛“扫描当前是否已有进程”的实现方式
|
||||||
|
|
||||||
|
## 路径检查
|
||||||
|
|
||||||
|
- 已确认本次实施记录保存路径为 `doc/implementation-report-2026-04-09-start-script-ps-ef.md`
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- 已执行 `sh bin/prod/restart_java_test.sh`
|
||||||
|
- 已执行 `sh bin/restart_java_backend_test.sh`
|
||||||
|
- 已执行 `sh -n bin/prod/restart_java.sh && sh -n bin/restart_java_backend.sh`
|
||||||
|
- 已确认测试中拉起的假 Java 进程在脚本收尾阶段自动停止并清理
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# 2026-04-10 登录 Shell 默认使用 Node 25 实施记录
|
||||||
|
|
||||||
|
## 变更内容
|
||||||
|
- 保持 `nvm` 默认别名为 `25`
|
||||||
|
- 在 `~/.zprofile` 中补充 `nvm` 初始化,并在登录 shell 启动时自动执行 `nvm use default`
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
- `nvm alias default 25` 已经存在,但仅在交互式 shell 中可用
|
||||||
|
- `zsh -lc` 启动的是登录非交互 shell,不会读取 `~/.zshrc`
|
||||||
|
- 因此这类场景下 `node`、`npm`、`npx` 未进入 PATH,表现为 `npx` 启动失败
|
||||||
|
|
||||||
|
## 修改文件
|
||||||
|
- `~/.zprofile`
|
||||||
|
- `doc/implementation-report-2026-04-10-login-shell-default-node25.md`
|
||||||
|
|
||||||
|
## 验证项
|
||||||
|
- 验证登录 shell 在不手动执行 `nvm use` 的情况下可直接识别 `node`
|
||||||
|
- 验证登录 shell 在不手动执行 `nvm use` 的情况下可直接识别 `npx`
|
||||||
|
- 验证 `nvm` 默认别名仍然指向 `25`
|
||||||
|
|
||||||
|
## 执行命令
|
||||||
|
- `zsh -lc 'nvm alias default 25'`
|
||||||
|
- `zsh -lc 'echo NODE=$(node -v); echo NPM=$(npm -v); echo NPX=$(npx -v); echo NODE_PATH=$(command -v node); echo NPX_PATH=$(command -v npx); echo NVM_CURRENT=$(nvm current); echo NVM_ALIAS=$(nvm alias default | tail -n 1)'`
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- `nvm` 默认别名输出为 `default -> 25 (-> v25.9.0 *)`
|
||||||
|
- 登录 shell 输出 `NODE=v25.9.0`
|
||||||
|
- 登录 shell 输出 `NPM=11.12.1`
|
||||||
|
- 登录 shell 输出 `NPX=11.12.1`
|
||||||
|
- 登录 shell 输出 `NODE_PATH=/Users/wkc/.nvm/versions/node/v25.9.0/bin/node`
|
||||||
|
- 登录 shell 输出 `NPX_PATH=/Users/wkc/.nvm/versions/node/v25.9.0/bin/npx`
|
||||||
|
- 登录 shell 输出 `NVM_CURRENT=v25.9.0`
|
||||||
|
- 登录 shell 输出 `NVM_ALIAS=default -> 25 (-> v25.9.0 *)`
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
- `zsh -lc` 场景下已默认切换到 Node `25.9.0`
|
||||||
|
- `npx` 在登录 shell 中已可直接使用,无需先手动执行 `nvm use 25`
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# 个人流程最终测算利率展示实施记录
|
||||||
|
|
||||||
|
## 修改时间
|
||||||
|
- 2026-04-11
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 调整流程列表后端查询:个人客户列表“测算利率”改为读取 `model_retail_output_fields.final_calculate_rate`
|
||||||
|
- 调整个人流程详情左侧展示:标签改为“最终测算利率”,显示字段改为 `retailOutput.finalCalculateRate`
|
||||||
|
- 调整个人流程详情后端组装:`loanPricingWorkflow.loanRate` 改为取个人模型输出的 `finalCalculateRate`
|
||||||
|
|
||||||
|
## 影响范围
|
||||||
|
- 个人流程列表
|
||||||
|
- 个人流程详情左侧关键信息
|
||||||
|
- 企业流程列表与详情保持现状,不做改动
|
||||||
|
|
||||||
|
## 验证计划
|
||||||
|
- 后端单元测试验证个人详情取 `finalCalculateRate`
|
||||||
|
- 后端静态测试验证列表 SQL 的个人分支查询 `mr.final_calculate_rate`
|
||||||
|
- 前端静态测试验证个人详情展示 `finalCalculateRate`
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# 流程详情卡片顺序调整实施记录
|
||||||
|
|
||||||
|
## 修改时间
|
||||||
|
- 2026-04-11
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 调整个人流程详情页右侧卡片顺序,将模型输出卡片移动到流程详情卡片上方
|
||||||
|
- 调整企业流程详情页右侧卡片顺序,将模型输出卡片移动到流程详情卡片上方
|
||||||
|
- 新增前端静态校验,约束个人与企业详情组件的卡片顺序
|
||||||
|
|
||||||
|
## 影响范围
|
||||||
|
- 个人流程详情页面布局
|
||||||
|
- 企业流程详情页面布局
|
||||||
|
|
||||||
|
## 验证计划
|
||||||
|
- 执行前端静态测试,确认卡片顺序断言通过
|
||||||
|
- 启动前端页面并在浏览器中检查个人、企业详情页卡片顺序
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# 上虞对公利率测算字段对齐实施记录
|
||||||
|
|
||||||
|
## 修改时间
|
||||||
|
- 2026-04-16
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 对齐对公创建请求字段,新增 `repayMethod`,将 `isTradeConstruction` 统一为 `isTradeBuildEnt`
|
||||||
|
- 对齐企业详情返回与页面展示,左侧主利率改为 `finalCalculateRate`
|
||||||
|
- 对齐对公模型输出字段,补齐 `loanRateHistory`、`minRateProduct`、`smoothRange`、`finalCalculateRate`、`referenceRate`
|
||||||
|
- 裁剪企业模型输出和页面展示,不再暴露 `isAgriGuar`、`midEntTax`、`cardOverdue`
|
||||||
|
- 对公新增弹窗中的 `贷款期限(年)` 调整为下拉框,选项固定为 `1-10` 年
|
||||||
|
- 更新企业 mock 返回和 SQL 基线、迁移脚本
|
||||||
|
|
||||||
|
## 文档与脚本
|
||||||
|
- `doc/2026-04-16-shangyu-corporate-alignment-backend-plan.md`
|
||||||
|
- `doc/2026-04-16-shangyu-corporate-alignment-frontend-plan.md`
|
||||||
|
- `sql/2026-04-16-shangyu-corporate-alignment.sql`
|
||||||
|
|
||||||
|
## 验证记录
|
||||||
|
- 后端单测:
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -Dtest=ModelCorpOutputFieldsTest,LoanPricingModelServiceTest,LoanPricingWorkflowServiceImplTest test`
|
||||||
|
- 结果:13 个测试全部通过
|
||||||
|
- 前端静态断言:
|
||||||
|
- `zsh -lic 'nvm use default >/dev/null && npm --prefix ruoyi-ui run test:corporate-create-input-params'`
|
||||||
|
- `zsh -lic 'nvm use default >/dev/null && npm --prefix ruoyi-ui run test:corporate-display-fields'`
|
||||||
|
- 结果:两个脚本均通过
|
||||||
|
- 前端构建:
|
||||||
|
- `zsh -lic 'nvm use default >/dev/null && npm --prefix ruoyi-ui run build:prod'`
|
||||||
|
- 结果:构建成功,仅有体积告警
|
||||||
|
- 接口联调:
|
||||||
|
- 使用 `/login/test` 获取 token
|
||||||
|
- 验证了对公创建正常场景、缺少 `repayMethod` 的参数错误场景、`分期/不分期` 与 `1/0` 分支场景
|
||||||
|
- 详情接口确认返回新增字段,且 `loanPricingWorkflow.loanRate = modelCorpOutputFields.finalCalculateRate`
|
||||||
|
- 浏览器联调:
|
||||||
|
- 启动前端开发服务并打开流程列表
|
||||||
|
- 验证对公新增弹窗字段、选项、提交流程
|
||||||
|
- 验证创建后列表新增记录
|
||||||
|
- 验证企业详情页出现 `最终测算利率`、`还款方式`、`贸易和建筑业企业`、`历史利率`、`产品最低利率下限`、`平滑幅度`、`参考利率`
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# 流程详情页模型输出平铺展示实施记录
|
||||||
|
|
||||||
|
## 改动日期
|
||||||
|
- 2026-04-16
|
||||||
|
|
||||||
|
## 改动范围
|
||||||
|
- 前端:`ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue`
|
||||||
|
- 前端测试:`ruoyi-ui/tests/model-output-flat-display.test.js`
|
||||||
|
- 前端脚本:`ruoyi-ui/package.json`
|
||||||
|
|
||||||
|
## 改动内容
|
||||||
|
- 取消流程详情页“模型输出”区域的 Tab 切换结构。
|
||||||
|
- 保留原有分组顺序与字段内容,将“基本信息”“忠诚度分析”“贡献度分析”等分组改为自上而下平铺展示。
|
||||||
|
- 按最新要求,将“测算结果”分组前移到“基本信息”下方,优先展示最终测算相关结果。
|
||||||
|
- 按最新要求,将“测算结果”中的“最终测算利率”调整到最后一行展示。
|
||||||
|
- 移除组件内仅用于 Tab 默认选中的 `activeTab` 和相关监听逻辑。
|
||||||
|
- 新增最小回归测试,校验模型输出组件不再包含 `el-tabs`、`el-tab-pane`,并具备平铺分组区块,同时校验“基本信息”后紧跟“测算结果”。
|
||||||
|
|
||||||
|
## 验证计划
|
||||||
|
- 使用 `nvm` 显式切换前端 Node 版本后执行 `npm run test:model-output-flat-display`。
|
||||||
|
- 启动前端页面,在浏览器中打开流程详情页,确认模型输出区域已按分组平铺展示,且不再出现 Tab 切换。
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# 对公还款方式移除与抵质押字段联动实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 对公新增弹窗移除 `还款方式` 输入项、初始化字段、重置字段、必填校验和提交字段。
|
||||||
|
- 对公详情页与模型输出展示移除 `还款方式`。
|
||||||
|
- 对公、对私新增弹窗中,`担保方式` 为 `抵押` 或 `质押` 时才展示 `抵质押类型`、`抵质押物是否第三方所有`。
|
||||||
|
- `抵质押类型` 根据担保方式动态切换:
|
||||||
|
- `抵押`:`一类`、`二类`、`三类`、`四类`、`其他`
|
||||||
|
- `质押`:`存单质押`、`其他`
|
||||||
|
- 担保方式切换时清空已选抵质押类型和第三方所有标识,隐藏抵质押字段时不向后端提交。
|
||||||
|
- 对公创建 DTO 取消 `repayMethod` 必填与枚举校验;`collType` 不再全局必填,合法值调整为 `一类/二类/三类/四类/其他/存单质押`。
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- 前端静态测试通过:
|
||||||
|
- `npm run test:corporate-create-input-params`
|
||||||
|
- `npm run test:corporate-display-fields`
|
||||||
|
- `npm run test:personal-create-input-params`
|
||||||
|
- 后端编译与单测通过:
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -am -Dtest=ModelCorpOutputFieldsTest -Dsurefire.failIfNoSpecifiedTests=false test`
|
||||||
|
- 后端接口验证通过:
|
||||||
|
- `信用` 不传 `repayMethod`、不传抵质押字段可创建。
|
||||||
|
- `抵押` 传 `一类` 且不传 `repayMethod` 可创建。
|
||||||
|
- `质押` 传 `存单质押` 且不传 `repayMethod` 可创建。
|
||||||
|
- 缺少 `custIsn`、缺少 `guarType`、非法 `guarType` 仍返回参数错误。
|
||||||
|
- 真实前端页面验证通过:
|
||||||
|
- 对公新增弹窗不显示 `还款方式`。
|
||||||
|
- 对公、对私新增弹窗在 `信用/保证` 下隐藏抵质押字段。
|
||||||
|
- 对公、对私新增弹窗在 `抵押/质押` 下显示抵质押字段,且选项分别符合规则。
|
||||||
|
- 对公详情页与模型输出区域不再显示 `还款方式`。
|
||||||
|
|
||||||
|
## 说明
|
||||||
|
|
||||||
|
- 本次不删除数据库字段和实体字段,仅停止创建入口要求和页面展示,保留历史数据结构。
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# 个人/企业模型接口拆分实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 将统一模型接口配置 `model.url` 拆分为 `model.personal-url` 和 `model.corporate-url`。
|
||||||
|
- `dev`、`uat` 环境分别指向本地个人/企业 mock:
|
||||||
|
- `http://localhost:63310/rate/pricing/mock/invokeModel/personal`
|
||||||
|
- `http://localhost:63310/rate/pricing/mock/invokeModel/corporate`
|
||||||
|
- `pro` 环境改为从 `MODEL_PERSONAL_URL`、`MODEL_CORPORATE_URL` 读取真实接口地址。
|
||||||
|
- `ModelService` 拆分为 `invokePersonalModel` 和 `invokeCorporateModel`,分别返回 `ModelRetailOutputFields`、`ModelCorpOutputFields`。
|
||||||
|
- `LoanPricingModelService` 根据 `custType` 调用对应模型接口,个人只写个人模型输出表,企业只写企业模型输出表。
|
||||||
|
- mock 控制器拆分为个人、企业两个入口,不再保留统一 mock 路径作为业务调用入口。
|
||||||
|
|
||||||
|
## 字段管理
|
||||||
|
|
||||||
|
- 个人模型返回字段继续由 `ModelRetailOutputFields` 与 `model_retail_output_fields` 管理。
|
||||||
|
- 企业模型返回字段继续由 `ModelCorpOutputFields` 与 `model_corp_output_fields` 管理。
|
||||||
|
- 未新增统一返回对象,避免个人/企业字段混在同一套结构中。
|
||||||
|
|
||||||
|
## 验证记录
|
||||||
|
|
||||||
|
- 后端单测:
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingModelServiceTest,ModelRetailOutputFieldsTest,ModelCorpOutputFieldsTest -Dsurefire.failIfNoSpecifiedTests=false test`
|
||||||
|
- 结果:通过,`Tests run: 5, Failures: 0, Errors: 0`
|
||||||
|
- 后端打包与启动:
|
||||||
|
- `./bin/restart_java_backend.sh restart`
|
||||||
|
- 结果:打包成功,提升权限后启动成功,后端监听 `63310`。
|
||||||
|
- 真实接口验证:
|
||||||
|
- `/login/test` 获取 token 成功。
|
||||||
|
- 调用 `/loanPricing/workflow/create/personal` 创建个人流程,流水号 `20260427150819677`。
|
||||||
|
- 查询个人详情,返回 `modelRetailOutputFields.finalCalculateRate=6.05`,`modelCorpOutputFields=null`。
|
||||||
|
- 调用 `/loanPricing/workflow/create/corporate` 创建企业流程,流水号 `20260427150820494`。
|
||||||
|
- 查询企业详情,返回 `modelCorpOutputFields.finalCalculateRate=3.732`,`modelRetailOutputFields=null`。
|
||||||
|
- 缺少 `custIsn` 的个人创建请求返回 `客户内码不能为空`。
|
||||||
|
- 后端日志确认个人命中 `/rate/pricing/mock/invokeModel/personal`,企业命中 `/rate/pricing/mock/invokeModel/corporate`。
|
||||||
|
- 测试结束后已执行 `./bin/restart_java_backend.sh stop` 停止本次启动的后端进程。
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 生产环境启动前必须提供 `MODEL_PERSONAL_URL`、`MODEL_CORPORATE_URL`。
|
||||||
|
- 本次不改前端页面和现有业务接口路径。
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# 2026-04-27 个人模型输出灰黑名单客户字段实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 后端个人模型输出实体 `ModelRetailOutputFields` 新增 `greyBlackCust` 字段,承接个人模型返回的 `0/1` 输出值。
|
||||||
|
- 个人模型 mock 返回文件 `retail_output.json` 新增 `greyBlackCust: "1"`,用于本地模型调用链路验证。
|
||||||
|
- `model_retail_output_fields` 表结构新增 `grey_black_cust` 字段,并补充增量迁移脚本 `sql/add_model_retail_grey_black_cust_20260427.sql`。
|
||||||
|
- 前端模型输出组件在个人客户“基本信息”分组中展示“灰黑名单客户”,直接展示后端返回值 `0/1`。
|
||||||
|
- 接口文档示例补充 `greyBlackCust` 返回字段。
|
||||||
|
|
||||||
|
## 涉及文件
|
||||||
|
|
||||||
|
- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/entity/ModelRetailOutputFields.java`
|
||||||
|
- `ruoyi-loan-pricing/src/main/resources/data/retail_output.json`
|
||||||
|
- `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/domain/entity/ModelRetailOutputFieldsTest.java`
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue`
|
||||||
|
- `ruoyi-ui/tests/retail-display-fields.test.js`
|
||||||
|
- `sql/model_retail.sql`
|
||||||
|
- `sql/loan_pricing_schema_20260328.sql`
|
||||||
|
- `sql/loan_pricing_prod_init_20260331.sql`
|
||||||
|
- `sql/add_model_retail_grey_black_cust_20260427.sql`
|
||||||
|
- `doc/api/loan-pricing-workflow-api.md`
|
||||||
|
|
||||||
|
## 数据库变更
|
||||||
|
|
||||||
|
- 已在开发库 `loan-pricing.model_retail_output_fields` 执行新增列:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE model_retail_output_fields
|
||||||
|
ADD COLUMN grey_black_cust varchar(100) DEFAULT '' COMMENT '灰黑名单客户' AFTER base_loan_rate;
|
||||||
|
```
|
||||||
|
|
||||||
|
- 回查结果确认 `grey_black_cust` 字段存在。
|
||||||
|
|
||||||
|
## 验证记录
|
||||||
|
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -Dtest=ModelRetailOutputFieldsTest,LoanPricingModelServiceTest -Dsurefire.failIfNoSpecifiedTests=false test`
|
||||||
|
- 通过,确认实体字段存在,个人/企业模型调用基础链路未回归。
|
||||||
|
- `zsh -lic 'nvm use 14 >/dev/null && npm --prefix ruoyi-ui run test:retail-display-fields && npm --prefix ruoyi-ui run test:model-output-flat-display'`
|
||||||
|
- 通过,确认前端包含 `retailOutput.greyBlackCust` 且字段位于个人模型输出“基本信息”分组。
|
||||||
|
- `zsh -lic 'nvm use 14 >/dev/null && npm --prefix ruoyi-ui run build:prod'`
|
||||||
|
- 通过,存在既有包体积 warning,无编译错误。
|
||||||
|
- 后端真实接口验证:
|
||||||
|
- 重启后端后调用个人流程创建接口,业务流水号 `20260427153305173`。
|
||||||
|
- 调用详情接口返回 `modelRetailOutputFields.greyBlackCust = 1`。
|
||||||
|
- 数据库联表回查 `model_retail_output_fields.grey_black_cust = 1`。
|
||||||
|
- browser-use 真实页面验证:
|
||||||
|
- 使用前端开发服务 `http://127.0.0.1:63311/` 打开真实流程详情页。
|
||||||
|
- 页面 `模型输出 > 基本信息` 中可见“灰黑名单客户”,展示值为 `1`。
|
||||||
|
- 测试结束后已停止本次启动的后端 `63310` 与前端 `63311` 进程,并回查端口不再监听。
|
||||||
|
|
||||||
|
## 备注
|
||||||
|
|
||||||
|
- 组合执行 `LoanPricingModelServicePersonalParamsTest` 时,当前本机 JDK 21 下 Mockito inline ByteBuddy 自附加失败;该失败与本次字段改动无关。已单独执行本次直接相关的非 Mockito 失败用例并通过。
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# 根目录 892 上线压缩包生成脚本实施记录
|
||||||
|
|
||||||
|
## 保存路径检查
|
||||||
|
- 脚本保存路径:项目根目录 `build_release_892.sh`
|
||||||
|
- 实施记录保存路径:`doc/implementation-report-2026-04-27-root-release-package-892.md`
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 新增根目录脚本 `build_release_892.sh`
|
||||||
|
- 脚本执行后自动在项目根目录生成 `YYYYMMDD_892.zip`
|
||||||
|
- 压缩包根层结构固定为:
|
||||||
|
- `ruoyi-admin.jar`
|
||||||
|
- `dist.zip`
|
||||||
|
- 后端产物来自最新执行的 `mvn -pl ruoyi-admin -am clean package -DskipTests`
|
||||||
|
- 前端产物来自 `nvm use 14` 后执行的 `npm --prefix ruoyi-ui run build:prod`
|
||||||
|
- 前端构建完成后重新生成 `ruoyi-ui/dist.zip`
|
||||||
|
- 更新 `.gitignore`,忽略根目录生成的 `????????_892.zip`
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已执行 `sh -n build_release_892.sh`,语法校验通过
|
||||||
|
- 已执行 `./build_release_892.sh`,后端 Maven 构建成功,前端生产构建成功
|
||||||
|
- 前端构建过程中仅出现原有包体积 warning 与 npm 更新检查权限提示,不影响产物生成
|
||||||
|
- 已生成根目录压缩包:`20260427_892.zip`
|
||||||
|
- 已按最新要求调整压缩包结构,根层直接放置两个文件,不再包含 `deploy/` 目录
|
||||||
|
- 已执行 `unzip -l 20260427_892.zip`,确认压缩包内容为:
|
||||||
|
- `ruoyi-admin.jar`
|
||||||
|
- `dist.zip`
|
||||||
|
- 已执行 `git check-ignore -v 20260427_892.zip ruoyi-ui/dist.zip`,确认根目录上线压缩包和前端临时压缩包均不会进入 git
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# 面包屑重复 key 告警处理实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 修复流程列表首页进入后控制台出现 `Duplicate keys detected: '/index'` 的问题。
|
||||||
|
- 根因是当前首页实际路由为 `/index`,面包屑组件仍只按路由名 `Index` 判断首页,导致额外追加的“首页”项与“流程列表”项使用相同路径 `/index` 作为 key。
|
||||||
|
- 将面包屑首页判断补充为同时识别 `path === '/index'`,避免在首页路由重复追加“首页”项。
|
||||||
|
|
||||||
|
## 验证方式
|
||||||
|
|
||||||
|
- 启动前端开发服务后,使用真实浏览器访问 `/index`。
|
||||||
|
- 检查控制台不再出现 `Duplicate keys detected: '/index'`。
|
||||||
|
- 检查流程列表页面仍可正常展示。
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- 已使用 Node 14.21.3 启动前端开发服务并通过 Playwright 访问真实页面 `http://127.0.0.1:8080/index`。
|
||||||
|
- 页面成功进入“流程列表”,面包屑仅展示“流程列表”,未再重复追加“首页”。
|
||||||
|
- 浏览器控制台统计为 `Errors: 0, Warnings: 0`,未再出现 `Duplicate keys detected: '/index'`。
|
||||||
|
- 验证结束后已关闭本次启动的前端 `8080` 进程;后端 `63310` 为验证前已有进程,未做关闭处理。
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# 2026-04-29 业务种类与历史贷款利率设计记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 新增设计文档 `docs/superpowers/specs/2026-04-29-business-type-history-rate-design.md`。
|
||||||
|
- 明确个人和企业新增流程同时增加业务种类和历史贷款利率。
|
||||||
|
- 明确业务种类保存到流程表并在详情展示,但不进入模型入参。
|
||||||
|
- 明确历史贷款利率保存到流程表,并作为模型调用入参上传。
|
||||||
|
- 明确存量转贷时必须查询历史合同并单选一条;未选择历史合同禁止提交。
|
||||||
|
- 明确历史合同查询采用后端代理外部接口、前端列表单选回填的方案。
|
||||||
|
- 根据审查意见补充 `LoanPricingConverter` 字段映射、服务层跨字段校验、固定 mock 测试场景、直接接口测试覆盖和历史合同 URL 拼参方式。
|
||||||
|
|
||||||
|
## 验证说明
|
||||||
|
|
||||||
|
- 本次仅完成设计文档,未进入代码实现。
|
||||||
|
- 首轮设计审查发现文档存在 5 个实施风险,已按意见补充到设计文档。
|
||||||
|
- 第二轮设计审查结论为 Approved。
|
||||||
|
- 后续实施时需要按设计文档分别覆盖后端接口验证、前端交互验证和真实页面浏览器验证。
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# 2026-04-29 业务种类与历史贷款利率实施计划记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 新增后端实施计划 `docs/superpowers/plans/2026-04-29-business-type-history-rate-backend-plan.md`。
|
||||||
|
- 新增前端实施计划 `docs/superpowers/plans/2026-04-29-business-type-history-rate-frontend-plan.md`。
|
||||||
|
- 后端计划覆盖字段、转换器、服务层校验、历史合同代理接口、mock、SQL 和后端测试。
|
||||||
|
- 前端计划覆盖历史合同查询 API、历史合同单选弹窗、个人/企业新增弹窗、详情展示、静态测试和 browser-use 真实页面验证。
|
||||||
|
- 根据计划审查意见,补充固定客户号 mock 场景 `HISTORY_EMPTY` / `HISTORY_EMPTY_RATE`,确保 browser-use 真实页面测试能稳定覆盖空列表和空历史利率。
|
||||||
|
- 根据计划审查意见,修正前端客户号选择测试命令,避免调用不存在的 npm script。
|
||||||
|
|
||||||
|
## 验证说明
|
||||||
|
|
||||||
|
- 本次仅产出实施计划,未进入代码实现。
|
||||||
|
- 真实页面测试已按用户要求明确使用 `browser-use:browser`,并禁止打开 prototype 页面。
|
||||||
|
- 首轮计划审查发现 4 个执行风险,已按意见补充和修订。
|
||||||
|
- 第二轮计划审查结论为 Approved。
|
||||||
|
- 后续实施完成后需要补充 `doc/implementation-report-2026-04-29-business-type-history-rate.md` 记录代码改动和验证结果。
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# 业务种类与历史贷款利率实施记录
|
||||||
|
|
||||||
|
## 后端实施
|
||||||
|
|
||||||
|
- 个人/企业利率定价创建 DTO 新增 `businessType`、`loanRateHistory` 字段。
|
||||||
|
- 利率定价流程实体新增 `businessType`、`loanRateHistory` 持久化字段。
|
||||||
|
- 模型调用 DTO 新增 `loanRateHistory` 字段,保持不新增 `businessType`。
|
||||||
|
- 个人/企业创建转换器已映射业务种类和历史贷款利率。
|
||||||
|
- 流程创建服务新增业务种类校验:必填,限定 `新客`、`存量新增`、`存量转贷`;`存量转贷` 必须选择历史贷款合同。
|
||||||
|
- 新增历史贷款合同代理服务 `LoanRateHistoryService` 和接口 `GET /loanPricing/workflow/history-contract`。
|
||||||
|
- 本地 mock 新增 `GET /rate/pricing/mock/history-contract`,覆盖正常、无历史合同、历史利率为空场景。
|
||||||
|
- 本地 mock 客户号映射新增固定测试客户号 `HISTORY_EMPTY` 和 `HISTORY_EMPTY_RATE`。
|
||||||
|
- dev/uat/pro 配置新增 `loan-rate-history.url`。
|
||||||
|
- SQL 迁移和初始化脚本新增 `business_type`、`loan_rate_history` 字段。
|
||||||
|
|
||||||
|
## 后端验证
|
||||||
|
|
||||||
|
- 首次按计划运行 `mvn -pl ruoyi-loan-pricing -am -Dtest=... test` 时,`ruoyi-common` 因未匹配测试触发 Surefire 失败;后续按本仓库多模块测试习惯补充 `-Dsurefire.failIfNoSpecifiedTests=false`。
|
||||||
|
- 当前 Oracle JDK 21 环境下 Mockito inline mock maker 需要预加载 Byte Buddy agent,验证命令使用 `JAVA_TOOL_OPTIONS=-javaagent:/Users/wkc/.m2/repository/net/bytebuddy/byte-buddy-agent/1.17.8/byte-buddy-agent-1.17.8.jar`。
|
||||||
|
- 已执行并通过:
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingModelServicePersonalParamsTest,HistoryLoanContractVOTest -Dsurefire.failIfNoSpecifiedTests=false test`
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingModelServicePersonalParamsTest,LoanPricingModelServiceTest,LoanPricingWorkflowServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test`
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanRateHistoryServiceTest,LoanRatePricingMockControllerHistoryContractTest,LoanPricingWorkflowControllerHistoryContractTest,LoanRatePricingMockControllerCustomerMapTest -Dsurefire.failIfNoSpecifiedTests=false test`
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingModelServicePersonalParamsTest,LoanPricingModelServiceTest -Dsurefire.failIfNoSpecifiedTests=false test`
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -am -Dtest=HistoryLoanContractVOTest,LoanRateHistoryServiceTest,LoanPricingWorkflowControllerHistoryContractTest,LoanRatePricingMockControllerHistoryContractTest,LoanPricingWorkflowServiceImplTest,LoanPricingModelServicePersonalParamsTest,LoanPricingModelServiceTest -Dsurefire.failIfNoSpecifiedTests=false test`
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingCustomerMapServiceTest,LoanPricingWorkflowControllerCustomerMapTest,LoanRatePricingMockControllerCustomerMapTest,CustomerMapRecordVOTest -Dsurefire.failIfNoSpecifiedTests=false test`
|
||||||
|
|
||||||
|
## 前端实施
|
||||||
|
|
||||||
|
- `workflow.js` 新增 `queryHistoryContracts(custIsn)`,请求 `GET /loanPricing/workflow/history-contract`。
|
||||||
|
- 新增共享组件 `HistoryContractSelector.vue`,按单选方式展示历史贷款合同,字段包含客户内码、历史贷款合同号、历史贷款担保方式、历史贷款产品代码、历史贷款利率、历史贷款金额、历史贷款签订时间。
|
||||||
|
- 个人/企业新增弹窗新增 `业务种类`,选项为 `新客`、`存量新增`、`存量转贷`。
|
||||||
|
- 当业务种类为 `存量转贷` 时,按客户内码查询历史贷款合同并弹出单选弹窗;未选合同、无历史合同、历史贷款利率为空时禁止提交。
|
||||||
|
- 非 `存量转贷` 创建时不提交 `loanRateHistory`。
|
||||||
|
- 个人/企业详情页在业务信息中展示 `业务种类`、`历史贷款利率`。
|
||||||
|
|
||||||
|
## 前端静态验证
|
||||||
|
|
||||||
|
- 已执行并通过:
|
||||||
|
- `zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:business-type-history-rate'`
|
||||||
|
- `zsh -lic 'nvm use 14.21.3 >/dev/null && node ruoyi-ui/tests/customer-map-selection.test.js && npm --prefix ruoyi-ui run test:personal-create-input-params && npm --prefix ruoyi-ui run test:corporate-create-input-params'`
|
||||||
|
- `zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run build:prod'`
|
||||||
|
- `build:prod` 通过,仍存在项目原有资源体积 warning。
|
||||||
|
|
||||||
|
## 数据库变更验证
|
||||||
|
|
||||||
|
- 已按 SQL 脚本对当前开发库执行:
|
||||||
|
- `ALTER TABLE loan_pricing_workflow ADD COLUMN business_type varchar(20) DEFAULT NULL COMMENT '业务种类' AFTER loan_purpose, ADD COLUMN loan_rate_history varchar(100) DEFAULT NULL COMMENT '历史贷款利率' AFTER business_type;`
|
||||||
|
- 已回查字段存在:
|
||||||
|
- `business_type varchar(20)`
|
||||||
|
- `loan_rate_history varchar(100)`
|
||||||
|
|
||||||
|
## 真实页面验证
|
||||||
|
|
||||||
|
- 后端已通过 `bin/restart_java_backend.sh restart` 重启并加载最新代码。
|
||||||
|
- 前端已通过 `zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run dev -- --port 8080'` 启动。
|
||||||
|
- 使用 in-app browser 打开真实页面 `http://localhost:8080/index`,未使用 prototype 页面。
|
||||||
|
- 已验证个人 `存量转贷`:
|
||||||
|
- 测试客户内码 `81000922431`
|
||||||
|
- 历史合同弹窗展示 7 个字段并支持单选。
|
||||||
|
- 选择合同后提交成功。
|
||||||
|
- 详情页展示 `业务种类=存量转贷`、`历史贷款利率=3.65`。
|
||||||
|
- 已验证企业 `存量转贷`:
|
||||||
|
- 测试客户内码 `81000329003`
|
||||||
|
- 历史合同弹窗展示 7 个字段并支持单选。
|
||||||
|
- 选择合同后提交成功。
|
||||||
|
- 详情页展示 `业务种类=存量转贷`、`历史贷款利率=3.65`。
|
||||||
|
- 已验证个人 `新客`:
|
||||||
|
- 测试客户内码 `81000525694`
|
||||||
|
- 不弹出历史贷款合同选择。
|
||||||
|
- 提交成功。
|
||||||
|
- 详情页展示 `业务种类=新客`,历史贷款利率为空值展示。
|
||||||
|
- 已验证企业 `存量新增`:
|
||||||
|
- 测试客户内码 `81000769824`
|
||||||
|
- 不弹出历史贷款合同选择。
|
||||||
|
- 提交成功。
|
||||||
|
- 详情页展示 `业务种类=存量新增`,历史贷款利率为空值展示。
|
||||||
|
- 已验证拦截场景:
|
||||||
|
- `存量转贷` 打开历史合同弹窗后未选择合同,提示 `请选择历史贷款合同`,禁止提交。
|
||||||
|
- 固定客户号 `HISTORY_EMPTY` 映射到 `EMPTY_HISTORY`,历史合同查询为空,提示 `未查询到历史贷款合同`,提交时校验 `请选择历史贷款合同`。
|
||||||
|
- 固定客户号 `HISTORY_EMPTY_RATE` 映射到 `EMPTY_RATE`,历史合同存在但历史贷款利率为空,选择时提示 `历史贷款利率不能为空`,提交时仍校验 `请选择历史贷款合同`。
|
||||||
|
- 已回查数据库:
|
||||||
|
- `81000922431 / 个人 / 存量转贷 / 3.65 / 321000`
|
||||||
|
- `81000329003 / 企业 / 存量转贷 / 3.65 / 654000`
|
||||||
|
- `81000525694 / 个人 / 新客 / NULL / 321000`
|
||||||
|
- `81000769824 / 企业 / 存量新增 / NULL / 654000`
|
||||||
|
|
||||||
|
## 进程清理
|
||||||
|
|
||||||
|
- 页面验证结束后已停止本次测试启动的前端和后端进程。
|
||||||
|
- 已确认 `8080`、`63310` 端口无监听进程。
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# 2026-04-29 客户号查询选择客户内码设计记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 新增设计文档 `docs/superpowers/specs/2026-04-29-customer-map-selection-design.md`。
|
||||||
|
- 明确新增流程从“选择客户类型后直接打开新增弹窗”调整为“选择客户类型 -> 客户号查询 -> 选择客户内码 -> 打开新增弹窗”。
|
||||||
|
- 明确后端新增个人/企业客户号映射业务接口和两个 mock 接口,配置地址先指向本项目 mock。
|
||||||
|
- 明确客户号映射返回字段保持下划线命名。
|
||||||
|
- 明确新增弹窗中的客户内码和客户名称由选择结果自动带入并只读。
|
||||||
|
|
||||||
|
## 验证说明
|
||||||
|
|
||||||
|
- 本次仅完成设计文档,未进入代码实现。
|
||||||
|
- 设计文档已通过审查子代理审查,结论为 Approved,无阻塞问题。
|
||||||
|
- 后续实施时需按设计文档中的后端、前端和真实页面测试范围执行验证。
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# 2026-04-29 客户号查询选择客户内码实施计划记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 新增后端实施计划 `docs/superpowers/plans/2026-04-29-customer-map-selection-backend-plan.md`。
|
||||||
|
- 新增前端实施计划 `docs/superpowers/plans/2026-04-29-customer-map-selection-frontend-plan.md`。
|
||||||
|
- 后端计划覆盖客户号映射 VO、服务、业务接口、mock 接口、profile 配置和接口验证。
|
||||||
|
- 前端计划覆盖客户号查询 API、查询选择弹窗、列表页流程串联、个人/企业新增弹窗只读带入和真实页面验证。
|
||||||
|
|
||||||
|
## 验证说明
|
||||||
|
|
||||||
|
- 本次仅完成实施计划文档,未进入代码实现。
|
||||||
|
- 计划已按已确认设计拆分为前端和后端两份执行文档。
|
||||||
|
- 实施计划已通过审查子代理审查,结论为 Approved,无阻塞问题。
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# 2026-04-29 客户号查询选择客户内码实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 后端新增个人/企业客户号映射业务接口:
|
||||||
|
- `GET /loanPricing/workflow/customer-map/personal?custId=...`
|
||||||
|
- `GET /loanPricing/workflow/customer-map/corporate?custId=...`
|
||||||
|
- 后端新增个人/企业客户号映射 mock 接口:
|
||||||
|
- `GET /rate/pricing/mock/customer-map/personal?cust_id=...`
|
||||||
|
- `GET /rate/pricing/mock/customer-map/corporate?cust_id=...`
|
||||||
|
- 配置文件新增 `customer-map` 个人/企业地址,并在 `dev`、`uat`、`pro` profile 中统一指向本项目 mock。
|
||||||
|
- 前端新增客户号查询选择弹窗。
|
||||||
|
- 客户号查询选择弹窗宽度调整为窗口宽度的 `80%`。
|
||||||
|
- 客户号查询输入值在查询前去除前后空格;后端转发个人/企业客户号映射接口前同步去除前后空格,并对下游 `cust_id` 查询参数做 URI 编码,避免尾随空格进入 request target 触发 TongWeb `HTTP Status 400`。
|
||||||
|
- 选中客户号查询结果后,返回参数 `cust_id` 去除前三位后自动回填到新增弹窗基础信息的 `证件号码` 字段。
|
||||||
|
- 个人/企业新增流程改为先查询客户号、选择客户内码,再打开新增弹窗。
|
||||||
|
- 新增弹窗客户内码和客户名称由选中记录自动带入并只读。
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- 后端针对性测试:通过。
|
||||||
|
- 命令:`mvn -pl ruoyi-loan-pricing -am -Dtest=CustomerMapRecordVOTest,LoanPricingCustomerMapServiceTest,LoanRatePricingMockControllerCustomerMapTest,LoanPricingWorkflowControllerCustomerMapTest -Dsurefire.failIfNoSpecifiedTests=false test`
|
||||||
|
- 结果:`Tests run: 8, Failures: 0, Errors: 0, Skipped: 0`。
|
||||||
|
- 客户号空格 400 修复补充测试:通过。
|
||||||
|
- 命令:`mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingCustomerMapServiceTest,LoanRatePricingMockControllerCustomerMapTest -Dsurefire.failIfNoSpecifiedTests=false test`
|
||||||
|
- 结果:`Tests run: 6, Failures: 0, Errors: 0, Skipped: 0`。
|
||||||
|
- 后端打包与启动验证:通过。
|
||||||
|
- 命令:`mvn -pl ruoyi-admin -am -DskipTests package`
|
||||||
|
- 命令:`java -jar ruoyi-admin/target/ruoyi-admin.jar --spring.profiles.active=dev`
|
||||||
|
- 结果:后端以 `dev` profile 启动成功,端口 `63310` 可用。
|
||||||
|
- 接口验证:通过。
|
||||||
|
- `GET /rate/pricing/mock/customer-map/personal?cust_id=P001` 返回随机个人客户映射列表,字段为 `cust_id`、`cust_isn`、`cust_name`、`faith_day`、`balance_avg`、`loan_count_his`、`last_loan_date`。
|
||||||
|
- `GET /rate/pricing/mock/customer-map/corporate?cust_id=C001` 返回随机企业客户映射列表,字段同上。
|
||||||
|
- `GET /rate/pricing/mock/customer-map/personal?cust_id=` 返回 `客户号不能为空`。
|
||||||
|
- 登录后调用 `GET /loanPricing/workflow/customer-map/personal?custId=P001` 和 `GET /loanPricing/workflow/customer-map/corporate?custId=C001` 均返回对应映射列表。
|
||||||
|
- 登录后调用 `GET /loanPricing/workflow/customer-map/personal?custId=` 返回 `客户号不能为空`。
|
||||||
|
- 补充验证:以最新后端临时端口 `63311` 调用 `GET /loanPricing/workflow/customer-map/personal?custId=1w0xc20xb7%20` 返回 `code=200`,返回 `cust_id` 为 `1w0xc20xb7`;调用 `GET /loanPricing/workflow/customer-map/corporate?custId=C001%20` 返回 `code=200`,返回 `cust_id` 为 `C001`;调用 `GET /loanPricing/workflow/customer-map/personal?custId=%20` 返回 `客户号不能为空`。
|
||||||
|
- 前端针对性测试:通过。
|
||||||
|
- 命令:`zsh -lic 'nvm use 14.21.3 >/dev/null && node ruoyi-ui/tests/customer-map-selection.test.js && npm --prefix ruoyi-ui run test:personal-create-input-params && npm --prefix ruoyi-ui run test:corporate-create-input-params && node ruoyi-ui/tests/workflow-index-refresh.test.js'`
|
||||||
|
- 结果:`customer map selection assertions passed`、`personal create input params assertions passed`、`corporate create input params assertions passed`、`workflow-index-refresh test passed`。
|
||||||
|
- 补充命令:`zsh -lic 'nvm use 14.21.3 >/dev/null && node ruoyi-ui/tests/customer-map-selection.test.js'`
|
||||||
|
- 补充结果:`customer map selection assertions passed`,覆盖客户号去前三位回填证件号码断言。
|
||||||
|
- 补充回归命令:`zsh -lic 'nvm use 14.21.3 >/dev/null && node ruoyi-ui/tests/customer-map-selection.test.js && npm --prefix ruoyi-ui run test:personal-create-input-params && npm --prefix ruoyi-ui run test:corporate-create-input-params && node ruoyi-ui/tests/id-number-validation-removal.test.js'`
|
||||||
|
- 补充回归结果:`customer map selection assertions passed`、`personal create input params assertions passed`、`corporate create input params assertions passed`、`id number validation removal assertions passed`。
|
||||||
|
- 前端生产构建:通过。
|
||||||
|
- 命令:`zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run build:prod'`
|
||||||
|
- 结果:构建成功;输出 2 个既有体积 warning。
|
||||||
|
- 真实页面验证:通过。
|
||||||
|
- 启动前端 `http://localhost:18080/`,登录后进入真实页面 `http://localhost:18080/loanPricing/workflow`。
|
||||||
|
- 个人客户:点击 `新增` -> `个人客户` -> 客户号查询输入 `PTEST003` -> 返回多条客户映射 -> 选择首行 -> 打开 `新增个人利率定价流程`,客户内码自动带入 `81000450472`,客户名称自动带入 `个人客户1`,两个字段均为 `readonly`。
|
||||||
|
- 企业客户:点击 `新增` -> `企业客户` -> 客户号查询输入 `CTEST001` -> 返回多条客户映射 -> 选择首行 -> 打开 `新增企业利率定价流程`,客户内码自动带入 `81000448819`,客户名称自动带入 `企业客户1`,两个字段均为 `readonly`。
|
||||||
|
- 弹窗宽度补充验证:点击 `新增` -> `个人客户` 后,`客户号查询` 弹窗 DOM 样式为 `margin-top: 15vh; width: 80%;`。
|
||||||
|
- 客户号空格补充验证:点击 `新增` -> `个人客户`,客户号输入 `1w0xc20xb7 ` 后查询,输入框显示为 `1w0xc20xb7`,列表正常返回客户映射,页面未出现 `400` 或 `Bad Request`。
|
||||||
|
- 客户号到证件号码补充验证:点击 `新增` -> `个人客户`,客户号输入 `ABC123456789` 并选择返回行后,新增个人弹窗基础信息的 `证件号码` 自动填入 `123456789`。
|
||||||
|
- 页面验证仅验证查询选择与自动回填链路,未提交新增表单,避免写入额外流程测试数据。
|
||||||
|
- 回归补充说明:
|
||||||
|
- `LoanPricingModelServiceTest` 已通过。
|
||||||
|
- 组合回归命令包含 `LoanPricingWorkflowServiceImplTest` 时,当前本机 Oracle JDK 21 环境下 Mockito inline/Byte Buddy self-attach 失败,属于既有测试环境限制,不是本次客户号映射功能断言失败。
|
||||||
|
- 进程清理:已关闭本次启动的后端和前端进程。
|
||||||
|
- 本次宽度与客户号空格补充验证结束后,`18080`、`63311` 无监听;`63310` 为验证前已存在的后端进程,未处理。
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# 历史贷款合同单选异常文本修复实施记录
|
||||||
|
|
||||||
|
## 问题
|
||||||
|
|
||||||
|
- 历史贷款合同选择弹窗的选择列出现 `{ "cus...` 这类异常文本。
|
||||||
|
- 原因是 `el-radio` 使用整行对象 `scope.row` 作为 `label`,Element UI 会把对象值渲染到单选文案区域。
|
||||||
|
|
||||||
|
## 修改
|
||||||
|
|
||||||
|
- `HistoryContractSelector.vue` 将单选绑定值从整行对象调整为 `selectedContractKey`。
|
||||||
|
- 新增 `contractRadioValue(row, index)` 生成稳定单选值,优先使用历史贷款合同号。
|
||||||
|
- 保留 `selectedContract` 单独保存选中行对象,提交时仍向父组件返回完整合同记录。
|
||||||
|
- 隐藏单选组件内部 label 文案,选择列只展示单选圆点,不展示对象文本。
|
||||||
|
- `business-type-history-rate.test.js` 增加断言,禁止再出现 `:label="scope.row"`。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- 已执行并通过:
|
||||||
|
- `zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:business-type-history-rate'`
|
||||||
|
- 已启动后端和前端后使用 in-app browser 真实页面验证:
|
||||||
|
- 打开 `http://localhost:8080/index`。
|
||||||
|
- 新增个人客户,选择业务种类 `存量转贷`。
|
||||||
|
- 历史贷款合同选择弹窗正常展示客户内码、历史贷款合同号、历史贷款利率等字段。
|
||||||
|
- 选择列文本为空,不再出现 `{ "cus...` 或行对象 JSON 文本。
|
||||||
|
- 验证结果:`hasObjectText=false`。
|
||||||
|
- 验证结束后已关闭测试弹窗。
|
||||||
|
- 验证结束后已停止本次启动的前端和后端进程,并确认 `8080`、`63310` 端口无监听。
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# 实施记录 - 外部接口调用日志
|
||||||
|
|
||||||
|
## 日期
|
||||||
|
|
||||||
|
2026-04-30
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 在根目录 `AGENTS.md` 的测试规范中新增外部接口调用日志要求:每次调用外部接口进行测试或联调时,必须在后端日志中完整输出请求 URL、请求参数和返回参数。
|
||||||
|
- 补齐客户映射接口、历史贷款合同接口、通用 `HttpUtils` 外部接口调用日志,输出请求 URL、请求参数和返回参数。
|
||||||
|
- 将外部接口日志调整为多行可读格式:请求 URL、请求参数、返回参数分段输出,参数对象和返回对象使用 pretty JSON 展开。
|
||||||
|
- 调整单元测试,覆盖客户映射外呼日志、历史贷款合同外呼日志。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- 已检查规则保存路径为项目根目录 `AGENTS.md`。
|
||||||
|
- 已执行 `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingCustomerMapServiceTest,LoanRateHistoryServiceTest -Dsurefire.failIfNoSpecifiedTests=false test`,测试通过。
|
||||||
34
doc/implementation-report-2026-05-09-operation-manual.md
Normal file
34
doc/implementation-report-2026-05-09-operation-manual.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# 上虞利率定价系统操作手册生成实施记录
|
||||||
|
|
||||||
|
## 基本信息
|
||||||
|
|
||||||
|
- 日期:2026-05-09
|
||||||
|
- 任务:生成系统操作文档,覆盖主要业务流程、用户管理、部门管理,并在合适位置加入真实页面截图
|
||||||
|
- 产物:`doc/上虞利率定价系统操作手册-2026-05-09.docx`
|
||||||
|
|
||||||
|
## 实施内容
|
||||||
|
|
||||||
|
- 梳理前端路由、菜单 SQL、业务流程组件和系统管理页面,确认操作手册覆盖范围。
|
||||||
|
- 临时启动前端开发服务,复用本机 63310 后端服务,通过真实浏览器获取页面截图。
|
||||||
|
- 截图覆盖登录页、流程列表、客户类型选择、客户号查询、新增个人定价流程、流程详情、用户管理、部门管理。
|
||||||
|
- 使用 `python-docx` 生成 Word 操作手册,内容包含:
|
||||||
|
- 文档说明与角色范围
|
||||||
|
- 登录与页面布局
|
||||||
|
- 利率定价主要业务流程
|
||||||
|
- 用户管理操作说明
|
||||||
|
- 部门管理操作说明
|
||||||
|
- 日常使用注意事项
|
||||||
|
|
||||||
|
## 验证情况
|
||||||
|
|
||||||
|
- 已确认前端服务可访问:`http://localhost:8080`
|
||||||
|
- 已通过 `/login/test` 获取测试登录令牌并进入真实页面截图。
|
||||||
|
- 已抽查关键截图显示正常,流程详情截图已改为从列表真实记录进入,避免使用无效流水号。
|
||||||
|
- 已生成 Word 文件:`doc/上虞利率定价系统操作手册-2026-05-09.docx`
|
||||||
|
- DOCX 渲染检查尝试使用文档技能提供的 `render_docx.py`,当前机器缺少 `soffice`,无法完成逐页 PNG 渲染;截图已内嵌在 Word 文档中。
|
||||||
|
- 已使用 macOS Quick Look 生成首屏缩略图进行抽查,封面、标题、表格和首张截图显示正常。
|
||||||
|
|
||||||
|
## 临时文件
|
||||||
|
|
||||||
|
- 截图目录、生成脚本、Playwright 临时依赖均位于 `output/` 下。
|
||||||
|
- 本次收尾时已清理 `output/`,避免误提交临时文件。
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# 2026-05-11 外部查询接口 GET 参数调用修复实施记录
|
||||||
|
|
||||||
|
## 实施内容
|
||||||
|
|
||||||
|
- 修复客户号查询客户内码的个人、企业两个外部接口调用方式。
|
||||||
|
- `LoanPricingCustomerMapService` 构建请求地址时先移除同名 `appCode`、`cust_id` 参数,再通过 GET query param 追加配置中的公共 `appCode` 和真实客户号。
|
||||||
|
- 修复历史贷款记录查询外部接口调用方式。
|
||||||
|
- `LoanRateHistoryService` 构建请求地址时先移除同名 `appCode`、`cust_isn` 参数,再通过 GET query param 追加配置中的公共 `appCode` 和真实客户内码。
|
||||||
|
- 调整 profile 外部地址配置。
|
||||||
|
- `application-pro.yml`、`application-dev.yml`、`application-uat.yml` 新增同一个 `loan-pricing-external.app-code` 配置项。
|
||||||
|
- 生产 profile 三条查询 URL 仅保留接口地址,不再在 URL 中写 `appCode` 或空业务参数。
|
||||||
|
- 补充服务层单元测试。
|
||||||
|
- 覆盖个人客户映射、企业客户映射、历史贷款记录三条接口最终均按 GET query param 生成公共 `appCode` 和各自业务参数。
|
||||||
|
- 测试文件位于 `*/src/test/`,按仓库 `.gitignore` 规则不纳入提交范围,仅用于本地验证。
|
||||||
|
|
||||||
|
## 涉及文件
|
||||||
|
|
||||||
|
- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapService.java`
|
||||||
|
- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanRateHistoryService.java`
|
||||||
|
- `ruoyi-admin/src/main/resources/application-pro.yml`
|
||||||
|
- `ruoyi-admin/src/main/resources/application-dev.yml`
|
||||||
|
- `ruoyi-admin/src/main/resources/application-uat.yml`
|
||||||
|
- `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapServiceTest.java`
|
||||||
|
- `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanRateHistoryServiceTest.java`
|
||||||
|
|
||||||
|
## 验证记录
|
||||||
|
|
||||||
|
- 已执行:`mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingCustomerMapServiceTest,LoanRateHistoryServiceTest -Dsurefire.failIfNoSpecifiedTests=false test`
|
||||||
|
- 结果:通过,`Tests run: 9, Failures: 0, Errors: 0, Skipped: 0`。
|
||||||
|
- 日志验证:
|
||||||
|
- 个人客户映射最终请求 URL 为 `http://mock/personal?appCode=abc&cust_id=P001`。
|
||||||
|
- 企业客户映射最终请求 URL 为 `http://mock/corporate?appCode=abc&cust_id=C001`。
|
||||||
|
- 历史贷款记录最终请求 URL 为 `http://mock/history?appCode=abc&cust_isn=81033011438`。
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# 2026-05-11 上虞利率定价字段口径调整实施计划记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 新增后端实施计划:`docs/superpowers/plans/2026-05-11-shangyu-pricing-field-adjustment-backend-plan.md`。
|
||||||
|
- 新增前端实施计划:`docs/superpowers/plans/2026-05-11-shangyu-pricing-field-adjustment-frontend-plan.md`。
|
||||||
|
- 后端计划覆盖创建 DTO、流程实体、模型入参、转换器、服务层校验、SQL schema 和后端测试。
|
||||||
|
- 前端计划覆盖个人/企业新增弹窗、业务种类选项、抵质押类型选项、`couponRate` 条件必填、静态断言、构建和 Playwright 真实页面验证。
|
||||||
|
|
||||||
|
## 范围说明
|
||||||
|
|
||||||
|
- 计划依据:`docs/superpowers/specs/2026-05-11-shangyu-pricing-field-adjustment-design.md`。
|
||||||
|
- 对公 `businessType` 上传模型这一条已按用户确认从本次实施范围排除;计划只覆盖 `couponRate` 的模型入参新增。
|
||||||
|
- 本次仅产出实施计划,未进入业务代码实现。
|
||||||
|
|
||||||
|
## 待验证
|
||||||
|
|
||||||
|
- 计划需通过计划审查后再进入实施。
|
||||||
|
- 后续实现完成后需要补充 `doc/implementation-report-2026-05-11-shangyu-pricing-field-adjustment.md`,记录真实代码改动、测试命令和页面验证结果。
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# 上虞利率定价字段调整实施记录
|
||||||
|
|
||||||
|
## 基本信息
|
||||||
|
|
||||||
|
- 日期:2026-05-11
|
||||||
|
- 范围:上虞利率定价个人/企业新增链路、服务端校验、模型入参、表结构脚本
|
||||||
|
- 目标:按已确认需求调整业务种类、抵质押类型、存单票面利率字段,以及对私新增入口字段剔除
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
|
||||||
|
- 个人新增 DTO:
|
||||||
|
- 业务种类调整为 `新增/存量新增/存量转贷`。
|
||||||
|
- 移除 `loanPurpose`、`bizProof` 新增入口字段。
|
||||||
|
- 新增 `couponRate`。
|
||||||
|
- 企业新增 DTO:
|
||||||
|
- 业务种类调整为 `新增/存量新增/存量转贷`。
|
||||||
|
- 企业抵押类型调整为 `一类/二类/三类/四类/排污权抵押/设备等其他不动产抵押`。
|
||||||
|
- 企业质押类型调整为 `存单质押/股权质押/其他质押`。
|
||||||
|
- 新增 `couponRate`。
|
||||||
|
- 流程实体和模型入参:
|
||||||
|
- `LoanPricingWorkflow` 新增 `couponRate`。
|
||||||
|
- `ModelInvokeDTO` 新增 `couponRate`,未增加 `businessType` 模型入参。
|
||||||
|
- 转换器:
|
||||||
|
- 个人/企业新增 DTO 均映射 `couponRate`。
|
||||||
|
- 个人新增 DTO 不再映射 `loanPurpose`、`bizProof`。
|
||||||
|
- 服务校验:
|
||||||
|
- 业务种类仅允许 `新增/存量新增/存量转贷`。
|
||||||
|
- 仅 `存量转贷` 要求历史贷款合同。
|
||||||
|
- 抵押/质押时要求选择抵质押类型。
|
||||||
|
- 对私/对公按客户类型和担保方式校验各自抵质押类型。
|
||||||
|
- `质押 + 存单质押` 时要求填写 `couponRate`。
|
||||||
|
- SQL:
|
||||||
|
- 新增 `sql/add_coupon_rate_20260511.sql`。
|
||||||
|
- 同步更新 `loan_pricing_workflow` 建表脚本中的 `coupon_rate` 字段。
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
- 个人新增弹窗:
|
||||||
|
- 业务种类调整为 `新增/存量新增/存量转贷`。
|
||||||
|
- 移除 `贷款用途` 和 `是否有经营佐证`。
|
||||||
|
- 抵押类型调整为 `一线/一类/二类/三类`。
|
||||||
|
- 质押类型调整为 `存单质押/其他质押`。
|
||||||
|
- `质押 + 存单质押` 时显示并必填 `存单票面利率`。
|
||||||
|
- 企业新增弹窗:
|
||||||
|
- 业务种类调整为 `新增/存量新增/存量转贷`。
|
||||||
|
- 抵押类型调整为 `一类/二类/三类/四类/排污权抵押/设备等其他不动产抵押`。
|
||||||
|
- 质押类型调整为 `存单质押/股权质押/其他质押`。
|
||||||
|
- `质押 + 存单质押` 时显示并必填 `存单票面利率`。
|
||||||
|
- 共同逻辑:
|
||||||
|
- 仅 `存量转贷` 触发历史贷款合同查询。
|
||||||
|
- 非存单质押提交时清理 `couponRate`。
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- 后端单元测试:
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowServiceImplTest,LoanPricingModelServicePersonalParamsTest,LoanPricingModelServiceTest -Dsurefire.failIfNoSpecifiedTests=false test`
|
||||||
|
- 结果:通过,23 个测试全部成功。
|
||||||
|
- 前端静态断言:
|
||||||
|
- `zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:personal-create-input-params && npm --prefix ruoyi-ui run test:corporate-create-input-params && npm --prefix ruoyi-ui run test:business-type-history-rate'`
|
||||||
|
- 结果:通过。
|
||||||
|
- 前端生产构建:
|
||||||
|
- `zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run build:prod'`
|
||||||
|
- 结果:构建通过,仅存在既有包体积 warning。
|
||||||
|
- 真实页面验证:
|
||||||
|
- 使用 Playwright 打开 `http://localhost:1024/index`。
|
||||||
|
- 使用 `/login/test` 获取登录 token 后访问真实流程列表页面。
|
||||||
|
- 个人新增弹窗验证:已移除 `贷款用途/是否有经营佐证`;业务种类仅 `存量转贷` 触发历史利率逻辑;个人抵押/质押选项正确;`存单质押` 下 `couponRate` 显示并进入必填校验。
|
||||||
|
- 企业新增弹窗验证:抵押/质押选项正确;`存单质押` 下 `couponRate` 显示并进入必填校验;业务种类仅 `存量转贷` 触发历史利率逻辑。
|
||||||
|
- 验证后已关闭 Playwright 浏览器会话;本次未新启动前后端进程。
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 控制台中的 `sockjs-node` 报错来自本地 dev-server HMR 连接内网地址失败,不影响本次页面功能验证。
|
||||||
|
- 表单校验 warning 来自验证时故意触发必填校验。
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# 登录页背景图替换实施记录
|
||||||
|
|
||||||
|
## 修改时间
|
||||||
|
|
||||||
|
2026-05-12
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 将登录页背景资源 `ruoyi-ui/src/assets/images/login-background.jpg` 替换为上虞农商银行“心乐为新未来”宣传图。
|
||||||
|
- 保持登录页现有样式引用不变,继续由 `login.vue` 的 `.login` 背景图样式加载该资源。
|
||||||
|
|
||||||
|
## 涉及文件
|
||||||
|
|
||||||
|
- `ruoyi-ui/src/assets/images/login-background.jpg`
|
||||||
|
|
||||||
|
## 验证情况
|
||||||
|
|
||||||
|
- 已确认 `ruoyi-ui/src/assets/images/login-background.jpg` 与原始图片 SHA256 一致。
|
||||||
|
- 已使用 Node 14.21.3 执行 `npm --prefix ruoyi-ui run build:prod`,构建成功;仅存在项目原有包体积 warning。
|
||||||
|
- 已使用 browser-use 打开真实登录页 `http://localhost:9527/login`,确认新背景图已渲染,账号、密码和登录按钮显示正常。
|
||||||
18
doc/implementation-report-2026-05-12-login-title-removal.md
Normal file
18
doc/implementation-report-2026-05-12-login-title-removal.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 登录页标题文案移除实施记录
|
||||||
|
|
||||||
|
## 修改时间
|
||||||
|
|
||||||
|
2026-05-12
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 移除登录框顶部标题展示,不再显示“上虞利率定价系统”。
|
||||||
|
|
||||||
|
## 涉及文件
|
||||||
|
|
||||||
|
- `ruoyi-ui/src/views/login.vue`
|
||||||
|
|
||||||
|
## 验证情况
|
||||||
|
|
||||||
|
- 已检查登录页模板,确认登录框内不再渲染标题节点。
|
||||||
|
- 已使用 browser-use 打开 `http://localhost:9527/login` 进行实际页面验证,确认页面跳转到登录页后不再出现“上虞利率定价系统”,账号和密码输入项仍正常显示。
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# 流程列表角色数据权限实施记录
|
||||||
|
|
||||||
|
## 修改日期
|
||||||
|
|
||||||
|
2026-05-12
|
||||||
|
|
||||||
|
## 需求范围
|
||||||
|
|
||||||
|
- 仅控制 `GET /loanPricing/workflow/list` 流程列表接口。
|
||||||
|
- 超级管理员 `user_id=1`、启用角色名为“管理员”或角色标识为 `headAdmin` 的用户可查看全部流程。
|
||||||
|
- 非管理员用户只能查看 `loan_pricing_workflow.create_by` 精确等于当前登录人 `昵称-柜员号` 的流程。
|
||||||
|
- 列表页“创建者”查询参数继续保留,但只按 `create_by` 中的柜员号部分进行模糊匹配。
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- `LoanPricingWorkflow` 增加非表字段 `dataScopeCreateBy`,专用于后端内部数据权限精确过滤。
|
||||||
|
- `LoanPricingWorkflowServiceImpl.selectLoanPricingPage` 增加流程列表数据权限裁剪:
|
||||||
|
- 管理员不写入 `dataScopeCreateBy`。
|
||||||
|
- 非管理员写入 `dataScopeCreateBy = nickName + "-" + username`。
|
||||||
|
- 前端传入 `createBy` 时仍保留原查询参数,但不能扩大非管理员可见范围。
|
||||||
|
- `LoanPricingWorkflowMapper.xml` 增加 `lpw.create_by = #{query.dataScopeCreateBy}` 精确权限条件。
|
||||||
|
- `LoanPricingWorkflowMapper.xml` 将 `createBy` 查询调整为 `SUBSTRING_INDEX(lpw.create_by, '-', -1) LIKE ...`,即只按柜员号模糊匹配。
|
||||||
|
- 补充 `LoanPricingWorkflowServiceImplTest` 和 `LoanPricingWorkflowMapperXmlTest`,覆盖管理员、业务管理员、客户经理、越权创建者查询参数和 XML 条件。
|
||||||
|
|
||||||
|
## 验证记录
|
||||||
|
|
||||||
|
- 单元测试通过:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowServiceImplTest,LoanPricingWorkflowMapperXmlTest -Dsurefire.failIfNoSpecifiedTests=false test
|
||||||
|
```
|
||||||
|
|
||||||
|
- 后端打包通过:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -pl ruoyi-admin -am clean package -DskipTests
|
||||||
|
```
|
||||||
|
|
||||||
|
- API 验证通过:
|
||||||
|
- `admin/admin123` 查询流程列表返回全量数据,包含测试行和 `若依-admin` 历史行。
|
||||||
|
- `8929999/123456` 业务管理员查询流程列表返回全量数据。
|
||||||
|
- `8920001/123456` 客户经理查询流程列表只返回本人创建的测试行。
|
||||||
|
- 客户经理按 `createBy=8920001` 查询可返回本人测试行。
|
||||||
|
- 客户经理按昵称 `createBy=测试客户经理` 查询返回 0 条。
|
||||||
|
- 客户经理按其他柜员号 `createBy=admin` 查询返回 0 条。
|
||||||
|
|
||||||
|
- browser-use 真实页面验证通过:
|
||||||
|
- 管理员登录真实流程列表页,页面显示 `共 40 条`,可见本人测试行和 `若依-admin` 历史行。
|
||||||
|
- 客户经理登录真实流程列表页,页面只显示本人创建的测试行。
|
||||||
|
- 客户经理在“创建者”输入 `admin` 后页面显示暂无数据。
|
||||||
|
- 客户经理在“创建者”输入 `8920001` 后页面重新显示本人测试行。
|
||||||
|
|
||||||
|
## 验证数据与清理
|
||||||
|
|
||||||
|
- 因当前开发库创建流程接口依赖的模型输出表缺少 `coupon_rate` 字段,无法通过页面新增生成验证流程。
|
||||||
|
- 本次验证使用一条临时 SQL 测试数据:`serial_num = ROLE_SCOPE_20260512_001`,`create_by = 测试客户经理-8920001`。
|
||||||
|
- 验证结束后已删除该临时数据,回查剩余数量为 0。
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# 贷款定价单脚本部署改造实施记录
|
||||||
|
|
||||||
|
## 保存路径检查
|
||||||
|
- 参考脚本:`/Users/wkc/Desktop/ccdi/ccdi/deploy/ccdi_function.sh`
|
||||||
|
- 新增脚本保存路径:`bin/prod/loan_pricing_function.sh`
|
||||||
|
- 实施记录保存路径:`doc/implementation-report-2026-05-13-loan-pricing-function-script.md`
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 新增 `loan_pricing_function.sh`,按 `ccdi_function.sh` 的 `deploy`、`restart`、`stop` 三命令结构改造为贷款定价可用脚本。
|
||||||
|
- 按贷款定价现有生产目录约定调整:
|
||||||
|
- 后端 Jar:`backend/ruoyi-admin.jar`
|
||||||
|
- 前端静态目录:`frontend/dist/`
|
||||||
|
- 日志目录:`logs/backend-console.log`
|
||||||
|
- PID 文件:`run/backend.pid`
|
||||||
|
- 临时目录:`tmp/loan-pricing-function/`
|
||||||
|
- 备份目录:`backup/YYYYMMDDHHMMSS/`
|
||||||
|
- 按贷款定价运行参数调整:
|
||||||
|
- Java 默认目录:`/home/webapp/env/java`
|
||||||
|
- 后端进程标记:`-Dloan.pricing.home=<脚本目录>`
|
||||||
|
- Spring Profile:`uat`
|
||||||
|
- 后端端口:`63310`
|
||||||
|
- 上线包结构固定为根层包含:
|
||||||
|
- `ruoyi-admin.jar`
|
||||||
|
- `dist.zip`
|
||||||
|
- 前端 `dist.zip` 解压后必须包含 `dist/index.html`,部署时写入 `frontend/dist/`。
|
||||||
|
- 默认保持参考脚本的启动后持续输出日志行为,并支持 `FOLLOW_LOGS=0` 供自动化验证跳过持续日志输出。
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- 已执行 `sh -n bin/prod/loan_pricing_function.sh`,语法校验通过。
|
||||||
|
- 已在临时目录构造 `backend/`、`frontend/dist/`、根层发布 zip 和假 Java 进程,验证 `deploy` 可完成备份、替换、启动和日志落盘。
|
||||||
|
- 已验证 `stop` 可停止脚本标记的后端进程并清理 PID 文件。
|
||||||
|
- 验证过程中产生的临时测试目录已删除,未新增仓库内测试文件。
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# 浏览器页签标题调整实施记录
|
||||||
|
|
||||||
|
## 修改时间
|
||||||
|
|
||||||
|
2026-05-15
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 将前端浏览器页签标题从“上虞利率定价系统”调整为“贷款利率定价系统”。
|
||||||
|
- 同步更新开发、测试、生产环境的 `VUE_APP_TITLE` 配置,确保本地运行和打包产物标题一致。
|
||||||
|
|
||||||
|
## 涉及文件
|
||||||
|
|
||||||
|
- `ruoyi-ui/.env.development`
|
||||||
|
- `ruoyi-ui/.env.staging`
|
||||||
|
- `ruoyi-ui/.env.production`
|
||||||
|
|
||||||
|
## 验证情况
|
||||||
|
|
||||||
|
- 已通过源码检索确认 `ruoyi-ui` 中页面标题配置已统一为“贷款利率定价系统”。
|
||||||
|
- 已使用 browser-use 打开 `http://localhost:9527/login` 进行真实页面验证,浏览器标签页标题与 `document.title` 均为“贷款利率定价系统”。
|
||||||
|
- 验证时仅启动前端服务;因本地后端 `localhost:63310` 未启动,验证码接口代理返回 `ECONNREFUSED`,不影响本次页签标题验证。
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# 利率前端两位小数展示实施记录
|
||||||
|
|
||||||
|
## 修改时间
|
||||||
|
- 2026-05-15
|
||||||
|
|
||||||
|
## 修改范围
|
||||||
|
- `ruoyi-ui/src/utils/rate.js`
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/index.vue`
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue`
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue`
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/components/CorporateWorkflowDetail.vue`
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/components/HistoryContractSelector.vue`
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue`
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue`
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 新增 `formatRate` 前端格式化方法,统一将可解析的利率数值展示为小数点后两位。
|
||||||
|
- 利率定价流程列表的测算利率、执行利率改为通过 `formatRate` 展示。
|
||||||
|
- 个人/企业流程详情的基准利率、最终测算利率、执行利率初始展示值、历史贷款利率改为两位小数展示。
|
||||||
|
- 模型输出中的基准利率、测算利率、历史利率、产品最低利率下限、平滑幅度、参考利率、最终测算利率、派生率改为两位小数展示。
|
||||||
|
- 历史贷款合同选择弹窗和新增流程弹窗中的历史贷款利率展示改为两位小数。
|
||||||
|
|
||||||
|
## 影响说明
|
||||||
|
- 本次仅调整前端展示格式,不改后端接口、数据库字段和模型调用逻辑。
|
||||||
|
- 历史贷款利率在新增弹窗中仅格式化展示,表单内部仍保留接口返回的原始值。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
- 已执行 `source ~/.nvm/nvm.sh && nvm use 14.21.3 && npm run build:prod`,构建通过,仅存在资源体积 warning。
|
||||||
|
- 已启动后端 `http://localhost:63310` 和前端 `http://localhost:9527/`,通过 browser-use 打开真实页面验证。
|
||||||
|
- 流程列表接口原始返回中存在 `calculateRate = 3.932` 的数据,流程列表页面 `测算利率(%)` 展示为 `3.93`。
|
||||||
|
- 流程列表页面 `执行利率(%)` 展示为 `3.88`、`6.18` 或 `-`,已确认非空利率均为小数点后两位。
|
||||||
|
- 流程详情页模型输出中,`finalCalculateRate = 3.732` 对应页面展示为 `3.73`,其他利率字段也按两位小数展示。
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# 流程列表支行管理员数据权限实施记录
|
||||||
|
|
||||||
|
## 修改日期
|
||||||
|
|
||||||
|
2026-05-18
|
||||||
|
|
||||||
|
## 需求范围
|
||||||
|
|
||||||
|
- 在 `loan_pricing_workflow` 新增 `dept_id`,保存新创建流程的创建人机构号。
|
||||||
|
- `GET /loanPricing/workflow/list` 增加支行管理员数据权限:
|
||||||
|
- 超级管理员、角色名“管理员”或角色标识 `headAdmin` 查看全部流程。
|
||||||
|
- 角色名“支行管理员”或角色标识 `branchAdmin` 查看本人机构及下级机构创建的流程。
|
||||||
|
- 其他用户继续只查看本人 `create_by` 精确匹配的流程。
|
||||||
|
- 不回填历史流程数据,历史 `dept_id` 为空的数据不纳入支行管理员机构权限。
|
||||||
|
- 本次无前端代码改动。
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 新增 `sql/add_workflow_dept_id_20260518.sql`,为流程表增加 `dept_id` 字段和 `idx_dept_id` 索引。
|
||||||
|
- 同步更新流程表建表脚本中的 `dept_id` 字段和索引定义。
|
||||||
|
- `LoanPricingWorkflow` 增加表字段 `deptId` 和列表内部权限字段 `dataScopeDeptId`。
|
||||||
|
- `LoanPricingWorkflowServiceImpl.createLoanPricing` 在插入前写入当前登录人的 `deptId`。
|
||||||
|
- `LoanPricingWorkflowServiceImpl.selectLoanPricingPage` 增加支行管理员分支:
|
||||||
|
- `headAdmin` 不加权限过滤。
|
||||||
|
- `branchAdmin` 写入当前登录人的 `dataScopeDeptId`。
|
||||||
|
- 客户经理继续写入 `dataScopeCreateBy`。
|
||||||
|
- `LoanPricingWorkflowMapper.xml` 增加基于 `lpw.dept_id` 与 `sys_dept.ancestors` 的本机构及下级机构过滤。
|
||||||
|
- 创建者查询参数仍按 `SUBSTRING_INDEX(lpw.create_by, '-', -1)` 只模糊匹配柜员号,并与数据权限条件取交集。
|
||||||
|
|
||||||
|
## 验证记录
|
||||||
|
|
||||||
|
- 单元测试通过:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowServiceImplTest,LoanPricingWorkflowMapperXmlTest -Dsurefire.failIfNoSpecifiedTests=false test
|
||||||
|
```
|
||||||
|
|
||||||
|
- 后端打包通过:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -pl ruoyi-admin -am clean package -DskipTests
|
||||||
|
```
|
||||||
|
|
||||||
|
- 已在开发库执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mysql ... loan-pricing < sql/add_workflow_dept_id_20260518.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
- 数据库回查确认 `loan_pricing_workflow.dept_id` 和 `idx_dept_id` 已存在。
|
||||||
|
- API 验证通过:
|
||||||
|
- `8929999/headAdmin` 查询临时流程返回 6 条,包含全部测试数据。
|
||||||
|
- `8920100/branchAdmin` 查询临时流程返回 4 条,仅包含本机构、本人和下级机构数据。
|
||||||
|
- `8920001/客户经理` 查询临时流程返回 1 条,仅包含本人创建数据。
|
||||||
|
- `8920100/branchAdmin` 使用其他支行创建者 `8920201` 作为查询条件时返回 0 条,创建者查询参数不能扩大数据权限。
|
||||||
|
- 真实创建接口验证通过:
|
||||||
|
- `8920100/branchAdmin` 调用个人流程创建接口后,新流程落库 `dept_id=101`,`create_by=测试支行管理员-8920100`。
|
||||||
|
- browser-use 真实页面验证通过:
|
||||||
|
- 支行管理员登录真实流程列表页,页面显示 `共 4 条`。
|
||||||
|
- 页面只显示 `BRANCH_SCOPE_20260518_CREATE`、`BRANCH_SCOPE_20260518_SELF`、`BRANCH_SCOPE_20260518_SAME`、`BRANCH_SCOPE_20260518_CHILD`。
|
||||||
|
- 页面未显示其他支行 `BRANCH_SCOPE_20260518_OTHER` 和普通客户经理本人 `BRANCH_SCOPE_20260518_MANAGER`。
|
||||||
|
|
||||||
|
## 测试数据保留
|
||||||
|
|
||||||
|
- 按复测要求,本次验证保留测试用户和测试流程数据,不做清理。
|
||||||
|
- 保留临时用户:
|
||||||
|
- `8920100`,昵称 `测试支行管理员`,角色 `branchAdmin`,机构 `101`。
|
||||||
|
- 保留流程数据:
|
||||||
|
- `BRANCH_SCOPE_20260518_SELF`,机构 `101`,创建者 `测试支行管理员-8920100`。
|
||||||
|
- `BRANCH_SCOPE_20260518_SAME`,机构 `101`,创建者 `同支行客户经理-8920101`。
|
||||||
|
- `BRANCH_SCOPE_20260518_CHILD`,机构 `103`,创建者 `下级客户经理-8920103`。
|
||||||
|
- `BRANCH_SCOPE_20260518_OTHER`,机构 `102`,创建者 `其他支行客户经理-8920201`。
|
||||||
|
- `BRANCH_SCOPE_20260518_MANAGER`,机构 `100`,创建者 `测试客户经理-8920001`。
|
||||||
|
- `BRANCH_SCOPE_20260518_CREATE`,机构 `101`,创建者 `测试支行管理员-8920100`,由真实新增接口创建。
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# 流程列表客户内码字段实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 后端流程列表查询 `selectWorkflowPageWithRates` 增加 `lpw.cust_isn AS custIsn`,确保列表接口返回客户内码。
|
||||||
|
- 流程列表返回对象 `LoanPricingWorkflowListVO` 增加 `custIsn` 字段,承接接口返回值。
|
||||||
|
- 前端流程列表页新增“客户内码”表格列,字段绑定 `custIsn`,支持超长内容 tooltip 展示。
|
||||||
|
|
||||||
|
## 影响范围
|
||||||
|
|
||||||
|
- 仅影响利率定价流程列表 `/loanPricing/workflow/list` 的返回字段和页面展示。
|
||||||
|
- 不修改新增流程、详情页、筛选条件和数据库结构。
|
||||||
|
|
||||||
|
## 验证记录
|
||||||
|
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowListVOTest -Dsurefire.failIfNoSpecifiedTests=false test` 通过。
|
||||||
|
- `source ~/.nvm/nvm.sh && nvm use 14.21.3 && node tests/customer-map-selection.test.js` 通过。
|
||||||
|
- 使用真实后端接口 `/loanPricing/workflow/list?pageNum=1&pageSize=3` 验证返回 `custIsn`,前三条返回值为 `81000529053`、`81000791269`、`81000769824`。
|
||||||
|
- 使用 browser-use 打开真实流程列表页 `http://localhost:1024/index`,确认表头包含“客户内码”,前三条列表行客户内码展示为 `81000529053`、`81000791269`、`81000769824`。
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# 流程列表最终测算利率展示实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
- 流程列表联表查询中,企业客户分支由 `model_corp_output_fields.calculate_rate` 改为 `model_corp_output_fields.final_calculate_rate`。
|
||||||
|
- 个人客户分支保持读取 `model_retail_output_fields.final_calculate_rate`。
|
||||||
|
- 前端流程列表列名由“测算利率(%)”调整为“最终测算利率(%)”,继续复用列表接口字段 `calculateRate` 展示,避免扩大接口字段变更范围。
|
||||||
|
|
||||||
|
## 影响范围
|
||||||
|
|
||||||
|
- 仅影响利率定价流程列表 `/loanPricing/workflow/list` 的利率来源和列名展示。
|
||||||
|
- 不修改详情页、新增流程、执行利率和数据库结构。
|
||||||
|
|
||||||
|
## 验证记录
|
||||||
|
|
||||||
|
- 已更新 `LoanPricingWorkflowMapperXmlTest`,约束个人、企业流程列表均取 `final_calculate_rate`。
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowMapperXmlTest,LoanPricingWorkflowListVOTest -Dsurefire.failIfNoSpecifiedTests=false test` 通过。
|
||||||
|
- `source ~/.nvm/nvm.sh && nvm use 14.21.3 && node tests/customer-map-selection.test.js` 通过。
|
||||||
|
- 使用 Node 静态断言确认流程列表列名为“最终测算利率(%)”,并且不再展示旧列名“测算利率(%)”。
|
||||||
|
- `mvn -pl ruoyi-admin -am clean package -DskipTests` 打包通过,并重启本地 63310 后端。
|
||||||
|
- 使用真实后端接口 `/loanPricing/workflow/list?pageNum=1&pageSize=3` 验证返回值,前三条 `calculateRate` 分别为 `3.732`、`6.05`、`3.732`。
|
||||||
|
- 使用 browser-use 打开真实流程列表页 `http://localhost:1024/index`,确认列名为“最终测算利率(%)”,前三条页面展示值为 `3.73`、`6.05`、`3.73`,旧列名未出现。
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# 流程列表列宽连续展示实施记录
|
||||||
|
|
||||||
|
## 修改时间
|
||||||
|
- 2026-05-18
|
||||||
|
|
||||||
|
## 修改范围
|
||||||
|
- `ruoyi-ui/src/views/loanPricing/workflow/index.vue`
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 将流程列表表格字段改为最小列宽布局,宽屏下自动铺满容器,窄屏下通过表格横向滚动查看。
|
||||||
|
- 加宽“业务方流水号”“客户内码”“客户名称”“创建者”等关键字段最小列宽,避免当前业务数据被省略或换行截断。
|
||||||
|
- 将关键字段列的 `show-overflow-tooltip` 省略展示方式改为最小列宽完整展示,并覆盖 Element UI 默认省略号样式。
|
||||||
|
- 将“操作”列固定在右侧,并通过最小列宽布局让主体列自动铺满,避免固定列与主体列之间出现中间空白断开。
|
||||||
|
|
||||||
|
## 影响说明
|
||||||
|
- 本次仅调整流程列表前端表格展示,不修改接口、后端查询逻辑、数据库结构和权限逻辑。
|
||||||
|
- 字段内容较长时优先按最小列宽完整展示,页面可通过横向滚动查看完整列表。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
- 已执行 `source ~/.nvm/nvm.sh && nvm use 14.21.3 && npm run build:prod`,前端生产构建通过,仅存在既有资源体积 warning。
|
||||||
|
- 已复用本机前端 `http://localhost:1024/` 与后端 `http://localhost:63310` 进行真实页面验证。
|
||||||
|
- 已通过 browser-use 打开真实流程列表页 `http://localhost:1024/loanPricing/workflow` 验证页面标题为“贷款利率定价系统”,页面非空且无浏览器 error/warn 日志。
|
||||||
|
- 在真实页面验证流程列表:
|
||||||
|
- 在 `1707x517` 视口下,表格主体 `scrollWidth=2020`、`clientWidth=1607`,确认窄屏仍可横向滚动。
|
||||||
|
- 在 `2167x542` 视口下,表格主体 `scrollWidth=2067`、`clientWidth=2067`,确认宽屏下表格自动铺满容器。
|
||||||
|
- “操作”列固定在表格右侧,固定列宽度约 `112px`。
|
||||||
|
- 固定“操作”列左侧间距 `gapBeforeFixed=0`,右侧尾部间距 `trailingGapAfterFixed=0`,未出现中间或尾部大块空白。
|
||||||
|
- “业务方流水号”“客户内码”“客户名称”“创建者”单元格样式为 `text-overflow: clip`、`overflow: hidden`、`white-space: nowrap`,当前列表关键字段 `BRANCH_SCOPE_20260518_MANAGER`、`测试支行管理员-8920100` 等完整展示且未使用省略号截断。
|
||||||
50
doc/implementation-report-2026-05-19-sys-user-role-sql.md
Normal file
50
doc/implementation-report-2026-05-19-sys-user-role-sql.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# sys_user_role 角色关系 SQL 生成记录
|
||||||
|
|
||||||
|
## 修改日期
|
||||||
|
|
||||||
|
2026-05-20
|
||||||
|
|
||||||
|
## 需求范围
|
||||||
|
|
||||||
|
- 根据 `/Users/wkc/Downloads/892.xlsx` 的 `Sheet1` 生成 `sys_user_role` 插入语句。
|
||||||
|
- Excel 字段使用规则:`柜员号` 匹配 `sys_user.user_name` 后取 `sys_user.user_id`,`类型` 匹配 `sys_role.role_name` 后取 `sys_role.role_id`。
|
||||||
|
- 未将 Excel 柜员号直接写入 `sys_user_role.user_id`。
|
||||||
|
- 本次只生成 SQL 文件,未执行写入数据库。
|
||||||
|
|
||||||
|
## 输出文件
|
||||||
|
|
||||||
|
- `sql/insert_sys_user_role_892_20260519.sql`
|
||||||
|
|
||||||
|
## 数据统计
|
||||||
|
|
||||||
|
- Excel 有效数据行:257 行。
|
||||||
|
- 管理员:4 行。
|
||||||
|
- 支行管理员:37 行。
|
||||||
|
- 客户经理:213 行。
|
||||||
|
- Excel 中 3 个重复柜员号同时出现客户经理和支行管理员,本次按支行管理员优先处理,不插入客户经理角色。
|
||||||
|
|
||||||
|
## 重复柜员号
|
||||||
|
|
||||||
|
- `8922557`: 裘朝山 / 892220 / 客户经理 / Excel 第 26 行、裘朝山 / 892220 / 支行管理员 / Excel 第 117 行
|
||||||
|
- `8922667`: 徐华源 / 892080 / 客户经理 / Excel 第 66 行、徐华源 / 892080 / 支行管理员 / Excel 第 121 行
|
||||||
|
- `8923504`: 陈俊杰 / 892170 / 客户经理 / Excel 第 28 行、陈俊杰 / 892170 / 支行管理员 / Excel 第 118 行
|
||||||
|
- 处理规则:以上重复柜员号只保留在支行管理员插入语句中。
|
||||||
|
|
||||||
|
## 数据库映射确认
|
||||||
|
|
||||||
|
- 已查询开发库 `sys_role`,当前有效角色为:
|
||||||
|
- `管理员` -> `role_id=100`, `role_key=headAdmin`
|
||||||
|
- `客户经理` -> `role_id=101`, `role_key=common`
|
||||||
|
- `支行管理员` -> `role_id=102`, `role_key=branchAdmin`
|
||||||
|
- SQL 文件不硬编码 `role_id`,执行时按 `sys_role.role_name` 动态匹配,避免不同环境角色自增 ID 不一致。
|
||||||
|
- SQL 使用 `INSERT IGNORE`,重复执行不会重复写入相同 `(user_id, role_id)`。
|
||||||
|
- SQL 不创建临时表,已按 `管理员`、`支行管理员`、`客户经理` 拆成三条独立插入语句,便于分角色执行和核对影响范围。
|
||||||
|
|
||||||
|
## 验证记录
|
||||||
|
|
||||||
|
- 已读取 Excel 并校验 `类型` 只包含 `管理员`、`支行管理员`、`客户经理`。
|
||||||
|
- 已确认 `sys_user_role` 表结构为 `user_id`、`role_id` 联合主键。
|
||||||
|
- 已对开发库做不写入目标表的干跑校验:257 行角色均可匹配,当前库 Excel 柜员号匹配到的有效用户数为 0。
|
||||||
|
- 批量执行该 SQL 前需先导入 Excel 对应用户,否则三条插入语句因匹配不到 `sys_user.user_name` 不会写入关系。
|
||||||
|
- 已按要求移除临时表建表、临时表插入和临时表删除语句。
|
||||||
|
- 已在事务内执行 SQL 脚本并 `ROLLBACK`,确认三条插入语句语法通过且未落库。
|
||||||
38
doc/implementation-report-2026-05-22-corporate-res-cover.md
Normal file
38
doc/implementation-report-2026-05-22-corporate-res-cover.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# 对公余值覆盖字段实施记录
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
对公新增流程需要在“贷款信息”分组补充“余值覆盖”开关,字段名为 `resCover`,提交到后端和模型调用时使用 `0/1` 值。
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
1. 前端企业新增弹窗 `CorporateCreateDialog.vue`
|
||||||
|
- 在“贷款信息”分组新增“余值覆盖”开关。
|
||||||
|
- 表单默认值为 `false`。
|
||||||
|
- 提交时转换为 `resCover: '1'` 或 `resCover: '0'`。
|
||||||
|
|
||||||
|
2. 后端字段链路
|
||||||
|
- `CorporateLoanPricingCreateDTO` 增加 `resCover`。
|
||||||
|
- `LoanPricingWorkflow` 增加 `resCover`,对应数据库字段 `res_cover`。
|
||||||
|
- `LoanPricingConverter` 将 DTO 字段写入流程实体。
|
||||||
|
- `ModelInvokeDTO` 增加 `resCover`。
|
||||||
|
- `LoanPricingModelService` 在企业模型调用前将 `resCover` 归一化为 `0/1`。
|
||||||
|
|
||||||
|
3. 页面展示
|
||||||
|
- 企业流程详情页展示“余值覆盖”。
|
||||||
|
|
||||||
|
4. SQL
|
||||||
|
- 新增 `sql/add_res_cover_20260522.sql`。
|
||||||
|
- 同步更新 `sql/loan_pricing_workflow.sql`、`sql/loan_pricing_schema_20260328.sql`、`sql/loan_pricing_prod_init_20260331.sql`。
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- 已执行前端字段静态断言:`npm run test:corporate-create-input-params`,通过。
|
||||||
|
- 已执行前端详情字段静态断言:`npm run test:corporate-display-fields`,通过。
|
||||||
|
- 已执行后端单测:`mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingModelServiceTest,LoanPricingWorkflowServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test`,通过。
|
||||||
|
- 已执行后端打包:`mvn -pl ruoyi-admin -am -DskipTests package`,通过。
|
||||||
|
- 已执行开发库 SQL:`sql/add_res_cover_20260522.sql`,回查 `loan_pricing_workflow.res_cover` 为 `varchar(10)`。
|
||||||
|
- 已启动真实前端页面,通过浏览器打开企业新增弹窗,确认“贷款信息”分组中展示“余值覆盖”开关,且位置在“企业标识”分组之前。
|
||||||
|
- 已用临时后端端口发起企业创建接口,响应中 `resCover` 为 `1`,后端外部模型调用日志确认请求参数包含 `"resCover":"1"`。
|
||||||
|
- 接口验证产生的测试流程数据已清理,回查 `cust_isn = 'RES_COVER_TEST_20260522'` 的流程记录数为 `0`。
|
||||||
|
- 测试时启动的前端和临时后端进程已关闭。
|
||||||
14
doc/implementation-report-2026-05-25-user-dialog-width.md
Normal file
14
doc/implementation-report-2026-05-25-user-dialog-width.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 用户管理新增弹窗宽度前端实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 将用户管理“添加或修改用户配置对话框”的宽度从 `600px` 调整为页面宽度的 `80%`。
|
||||||
|
- 修改范围仅限前端页面 `ruoyi-ui/src/views/system/user/index.vue`,不涉及后端接口、数据结构或权限逻辑。
|
||||||
|
|
||||||
|
## 验证记录
|
||||||
|
- `source ~/.nvm/nvm.sh && nvm use 14.21.3 >/dev/null && npm run build:prod`
|
||||||
|
- 结果:通过;仅保留项目既有的 webpack 体积提示。
|
||||||
|
- 真实页面验证:
|
||||||
|
- 前端地址:`http://localhost:1024/system/user`。
|
||||||
|
- 登录用户:`admin`。
|
||||||
|
- 点击用户管理页面“新增”后打开 `添加用户` 弹窗。
|
||||||
|
- 浏览器视口宽度为 `1067px`,弹窗实际宽度为 `853.33px`,宽度比例为 `0.7998`,符合页面宽度 `80%` 的要求。
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# 流程列表编辑功能后端实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 在利率定价流程接口新增编辑查询接口:`GET /loanPricing/workflow/{serialNum}/edit`。
|
||||||
|
- 新增个人流程编辑接口:`PUT /loanPricing/workflow/{serialNum}/personal`。
|
||||||
|
- 新增企业流程编辑接口:`PUT /loanPricing/workflow/{serialNum}/corporate`。
|
||||||
|
- 编辑接口按当前登录用户的 `昵称-柜员号` 校验创建者,只允许流程创建者编辑。
|
||||||
|
- 编辑时保持原业务方流水号、客户类型、创建者、创建时间和创建人部门,只覆盖表单字段。
|
||||||
|
- 编辑保存后重新调用模型服务;已有模型输出记录时覆盖原模型输出,并保持流程关联。
|
||||||
|
|
||||||
|
## 验证记录
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowServiceImplTest,LoanPricingModelServiceTest -Dsurefire.failIfNoSpecifiedTests=false test`
|
||||||
|
- 结果:通过。
|
||||||
|
- 覆盖:创建者编辑、非创建者拒绝、客户类型不匹配拒绝、编辑数据解密返回、重新测算覆盖模型结果。
|
||||||
|
- `mvn -pl ruoyi-loan-pricing -am test`
|
||||||
|
- 结果:通过。
|
||||||
|
- 覆盖:利率定价模块现有单测和本次新增单测。
|
||||||
|
- `mvn -pl ruoyi-admin -am -DskipTests package`
|
||||||
|
- 结果:通过,重新打包 `ruoyi-admin/target/ruoyi-admin.jar` 用于真实接口验证。
|
||||||
|
- 真实接口验证:
|
||||||
|
- 创建临时个人流程 `20260525110739953`,创建者为 `若依-admin`。
|
||||||
|
- 创建者调用 `GET /loanPricing/workflow/20260525110739953/edit` 成功返回原始客户名称 `编辑测试客户` 和原始证件号 `330103199901019999`。
|
||||||
|
- 创建者通过页面编辑提交后,编辑详情接口返回 `applyAmt=120000`,并保持原 `serialNum`、`custType`、`createBy`、`createTime`、`deptId`。
|
||||||
|
- 非创建者 `8929999` 调用编辑详情接口返回 `只有创建者可以编辑该流程`。
|
||||||
|
- 非创建者 `8929999` 调用个人更新接口返回 `只有创建者可以编辑该流程`。
|
||||||
|
- 验证完成后已按精确流水号删除临时流程和关联 `model_retail_output_fields` 记录,清理后计数均为 0。
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# 流程列表编辑功能前端实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 在流程列表操作列新增“编辑”按钮。
|
||||||
|
- 编辑按钮只在 `row.createBy` 等于当前登录用户 `nickName-name` 时展示。
|
||||||
|
- 点击编辑后直接查询流程编辑数据,并按客户类型打开个人或企业弹窗,不再进入客户类型选择和客户号选择流程。
|
||||||
|
- 个人和企业新增弹窗复用为新增/编辑双模式:
|
||||||
|
- 新增模式继续调用原新增接口。
|
||||||
|
- 编辑模式显示编辑标题、回显原始数据,并调用对应更新接口。
|
||||||
|
- 编辑回显时跳过担保方式和抵质押类型监听中的清空逻辑,避免抵质押字段被初始化过程误清除。
|
||||||
|
|
||||||
|
## 验证记录
|
||||||
|
- `source ~/.nvm/nvm.sh && nvm use 14.21.3 >/dev/null && node tests/workflow-index-refresh.test.js && node tests/personal-create-input-params.test.js && node tests/corporate-create-input-params.test.js`
|
||||||
|
- 结果:通过。
|
||||||
|
- 覆盖:操作列编辑按钮、创建者展示控制、编辑数据查询、个人/企业弹窗编辑模式和更新接口调用。
|
||||||
|
- `source ~/.nvm/nvm.sh && nvm use 14.21.3 >/dev/null && npm run build:prod`
|
||||||
|
- 结果:通过;仅保留项目既有的 webpack 体积提示。
|
||||||
|
- 真实页面验证:
|
||||||
|
- 前端地址:`http://localhost:1024/loanPricing/workflow`。
|
||||||
|
- 创建者 `admin` 登录后,临时流水 `20260525110739953` 操作列显示“查看”和“编辑”。
|
||||||
|
- 点击“编辑”直接打开 `编辑个人利率定价流程` 弹窗,回显客户内码 `EDITTEST20260525`、客户名称 `编辑测试客户`、证件号 `330103199901019999`、担保方式 `信用`、申请金额 `100000`、借款期限 `3`、业务种类 `新增`。
|
||||||
|
- 将申请金额改为 `120000` 后提交,页面提示“编辑成功”,列表刷新后该流水申请金额变为 `120000`。
|
||||||
|
- 非创建者 `8929999` 登录后,同一流水仍可查看,但操作列只显示“查看”,不显示“编辑”。
|
||||||
|
- 浏览器控制台无相关 error;仅出现登录和表单过程中的 `async-validator` 校验 warning。
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# 取消贷款定价页面脱敏展示后端实施记录
|
||||||
|
|
||||||
|
## 需求范围
|
||||||
|
|
||||||
|
- 取消贷款定价流程页面展示层的客户名称、证件号码脱敏效果。
|
||||||
|
- 列表、详情、模型输出基本信息返回完整展示值。
|
||||||
|
- 保留流程表客户名称、证件号码存储加密,以及创建、编辑、模型调用链路中的加解密逻辑。
|
||||||
|
|
||||||
|
## 实施内容
|
||||||
|
|
||||||
|
- 调整 `LoanPricingWorkflowServiceImpl` 分页列表返回逻辑,客户名称解密后直接返回。
|
||||||
|
- 调整 `LoanPricingWorkflowServiceImpl` 详情返回逻辑,流程主信息中的客户名称、证件号码解密后直接返回。
|
||||||
|
- 取消个人、企业模型输出基本信息中的客户名称、证件号码返回前脱敏处理。
|
||||||
|
- 删除不再使用的贷款定价专用展示脱敏服务及对应单元测试。
|
||||||
|
- 更新工作流服务单元测试,验证列表、详情、个人模型输出、企业模型输出均返回完整展示值。
|
||||||
|
|
||||||
|
## 未改动内容
|
||||||
|
|
||||||
|
- 未修改数据库表结构和数据迁移脚本。
|
||||||
|
- 未修改 `SensitiveFieldCryptoService` 存储加解密逻辑。
|
||||||
|
- 未修改登录、密码传输加密逻辑。
|
||||||
|
- 未修改通用 `@Sensitive` 脱敏机制。
|
||||||
|
- 未修改前端业务代码。
|
||||||
|
|
||||||
|
## 验证计划
|
||||||
|
|
||||||
|
- 执行 `LoanPricingWorkflowServiceImplTest`,确认返回展示值与原加解密边界符合预期。
|
||||||
|
- 启动真实前后端页面,进入贷款定价流程列表与详情页,确认页面展示完整客户名称和证件号码。
|
||||||
BIN
doc/~$上虞利率测算接口文档.xlsx
Normal file
BIN
doc/~$上虞利率测算接口文档.xlsx
Normal file
Binary file not shown.
BIN
doc/上虞_客户内码客户_历史利率_映射表.xlsx
Normal file
BIN
doc/上虞_客户内码客户_历史利率_映射表.xlsx
Normal file
Binary file not shown.
BIN
doc/上虞利率定价系统操作手册.docx
Normal file
BIN
doc/上虞利率定价系统操作手册.docx
Normal file
Binary file not shown.
BIN
doc/上虞利率测算接口文档.xlsx
Normal file
BIN
doc/上虞利率测算接口文档.xlsx
Normal file
Binary file not shown.
BIN
doc/上虞对公利率测算_上传字段与展示字段 .xlsx
Normal file
BIN
doc/上虞对公利率测算_上传字段与展示字段 .xlsx
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user