diff --git a/bin/prod/restart_java.sh b/bin/prod/restart_java.sh index e891d91..accedc1 100755 --- a/bin/prod/restart_java.sh +++ b/bin/prod/restart_java.sh @@ -5,7 +5,7 @@ set -eu WEBAPP_ROOT="/home/webapp" ENV_ROOT="$WEBAPP_ROOT/env" APP_ROOT="$WEBAPP_ROOT/loan-pricing" -JAVA_HOME="$ENV_ROOT/jdk" +JAVA_HOME="$ENV_ROOT/java" BACKEND_DIR="$APP_ROOT/backend" LOG_DIR="$APP_ROOT/logs" RUN_DIR="$APP_ROOT/run" @@ -37,13 +37,6 @@ usage() { EOF } -require_root() { - if [ "$(id -u)" -ne 0 ]; then - log_error "请使用 root 用户执行脚本" - exit 1 - fi -} - ensure_runtime_dirs() { mkdir -p "$BACKEND_DIR" "$LOG_DIR" "$RUN_DIR" } @@ -133,8 +126,10 @@ stop_backend() { } start_backend() { + ensure_runtime_dirs + if [ ! -x "$JAVA_HOME/bin/java" ]; then - log_error "未检测到 Java,可先执行 /home/webapp/install_env.sh" + log_error "未检测到可执行 Java: $JAVA_HOME/bin/java" exit 1 fi @@ -164,19 +159,7 @@ start_backend() { exit 1 fi - wait_seconds=0 - while [ "$wait_seconds" -lt 30 ]; do - if ss -lnt 2>/dev/null | grep -q ":$BACKEND_PORT "; then - log_info "后端已监听端口: $BACKEND_PORT" - return 0 - fi - - sleep 1 - wait_seconds=$((wait_seconds + 1)) - done - - log_error "后端未在预期时间内监听端口 $BACKEND_PORT" - exit 1 + log_info "后端已启动,PID: $backend_pid" } status_backend() { @@ -186,11 +169,6 @@ status_backend() { return 0 fi - if ss -lnt 2>/dev/null | grep -q ":$BACKEND_PORT "; then - log_info "未识别到脚本托管进程,但端口 $BACKEND_PORT 已被占用" - return 0 - fi - log_info "后端未运行" } diff --git a/bin/prod/restart_java_test.sh b/bin/prod/restart_java_test.sh new file mode 100644 index 0000000..4e1a119 --- /dev/null +++ b/bin/prod/restart_java_test.sh @@ -0,0 +1,114 @@ +#!/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/java/bin" "$work_dir/loan-pricing/backend" "$work_dir/loan-pricing/logs" "$work_dir/loan-pricing/run" + create_fake_java "$work_dir/env/java/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/java"' "$SCRIPT_UNDER_TEST" + assert_grep '--spring\.profiles\.active=pro' "$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 "$@" diff --git a/doc/2026-04-03-retail-display-fields-backend-plan.md b/doc/2026-04-03-retail-display-fields-backend-plan.md new file mode 100644 index 0000000..3cc3ccb --- /dev/null +++ b/doc/2026-04-03-retail-display-fields-backend-plan.md @@ -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 列 diff --git a/doc/2026-04-03-retail-display-fields-design.md b/doc/2026-04-03-retail-display-fields-design.md new file mode 100644 index 0000000..ad53cf2 --- /dev/null +++ b/doc/2026-04-03-retail-display-fields-design.md @@ -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. 非目标 + +- 不调整企业详情页 +- 不修改模型计算逻辑 +- 不重构页面布局 diff --git a/doc/2026-04-03-retail-display-fields-frontend-plan.md b/doc/2026-04-03-retail-display-fields-frontend-plan.md new file mode 100644 index 0000000..451a7b4 --- /dev/null +++ b/doc/2026-04-03-retail-display-fields-frontend-plan.md @@ -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 +{{ detailData.loanTerm || '-' }} +``` + +- [ ] **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: 启动前后端并打开个人流程详情页验证** + +使用浏览器打开新的个人流程详情页,确认: + +- 流程详情“业务信息”出现 `借款期限` +- 模型输出“测算结果”出现并可查看以下字段 + - 历史利率 + - 产品最低利率下限 + - 平滑幅度 + - 最终测算利率 + - 参考利率 diff --git a/doc/implementation-report-2026-04-03-production-backend-restart-script.md b/doc/implementation-report-2026-04-03-production-backend-restart-script.md new file mode 100644 index 0000000..1d90c6b --- /dev/null +++ b/doc/implementation-report-2026-04-03-production-backend-restart-script.md @@ -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` 进程完成,测试结束后已自动清理对应进程和临时目录 diff --git a/doc/implementation-report-2026-04-03-retail-display-fields.md b/doc/implementation-report-2026-04-03-retail-display-fields.md new file mode 100644 index 0000000..2a879bb --- /dev/null +++ b/doc/implementation-report-2026-04-03-retail-display-fields.md @@ -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 diff --git a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/entity/ModelRetailOutputFields.java b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/entity/ModelRetailOutputFields.java index 3eb775e..ee72e3a 100644 --- a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/entity/ModelRetailOutputFields.java +++ b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/entity/ModelRetailOutputFields.java @@ -170,6 +170,21 @@ public class ModelRetailOutputFields { // 测算利率 private String calculateRate; + // 历史利率 + private String loanRateHistory; + + // 产品最低利率下限 + private String minRateProduct; + + // 平滑幅度 + private String smoothRange; + + // 最终测算利率 + private String finalCalculateRate; + + // 参考利率 + private String referenceRate; + @TableField(fill = FieldFill.INSERT) private Date createTime; } diff --git a/ruoyi-loan-pricing/src/main/resources/data/retail_output.json b/ruoyi-loan-pricing/src/main/resources/data/retail_output.json index b01601b..38777c1 100644 --- a/ruoyi-loan-pricing/src/main/resources/data/retail_output.json +++ b/ruoyi-loan-pricing/src/main/resources/data/retail_output.json @@ -52,7 +52,12 @@ "bpGreyOverdue": "98", "totoalBpRisk": "95", "totalBp": "350", - "calculateRate": "6.15" + "calculateRate": "6.15", + "loanRateHistory": "6.40", + "minRateProduct": "5.50", + "smoothRange": "-0.10", + "finalCalculateRate": "6.05", + "referenceRate": "5.95" }, "extensionMap": {}, "reasonMessage": "Running successfully", @@ -65,4 +70,4 @@ "workflowVersion": 14, "callTime": 1736405548630, "status": 1 -} \ No newline at end of file +} diff --git a/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/domain/entity/ModelRetailOutputFieldsTest.java b/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/domain/entity/ModelRetailOutputFieldsTest.java new file mode 100644 index 0000000..bdd49ee --- /dev/null +++ b/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/domain/entity/ModelRetailOutputFieldsTest.java @@ -0,0 +1,26 @@ +package com.ruoyi.loanpricing.domain.entity; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +class ModelRetailOutputFieldsTest +{ + @Test + void shouldContainLatestRetailDisplayRateFields() + { + Set fieldNames = Arrays.stream(ModelRetailOutputFields.class.getDeclaredFields()) + .map(Field::getName) + .collect(Collectors.toSet()); + + assertTrue(fieldNames.contains("loanRateHistory"), "缺少字段 loanRateHistory"); + assertTrue(fieldNames.contains("minRateProduct"), "缺少字段 minRateProduct"); + assertTrue(fieldNames.contains("smoothRange"), "缺少字段 smoothRange"); + assertTrue(fieldNames.contains("finalCalculateRate"), "缺少字段 finalCalculateRate"); + assertTrue(fieldNames.contains("referenceRate"), "缺少字段 referenceRate"); + } +} diff --git a/ruoyi-ui/package.json b/ruoyi-ui/package.json index d728724..0897d9c 100644 --- a/ruoyi-ui/package.json +++ b/ruoyi-ui/package.json @@ -9,7 +9,8 @@ "build:prod": "vue-cli-service build", "build:stage": "vue-cli-service build --mode staging", "preview": "node build/index.js --preview", - "test:password-transfer": "node tests/password-transfer-api.test.js" + "test:password-transfer": "node tests/password-transfer-api.test.js", + "test:retail-display-fields": "node tests/retail-display-fields.test.js" }, "keywords": [ "vue", diff --git a/ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue b/ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue index 97c06f5..cf1b660 100644 --- a/ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue +++ b/ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue @@ -93,6 +93,11 @@ {{ retailOutput.totalBp || '-' }} {{ retailOutput.calculateRate || '-' }} % + {{ retailOutput.loanRateHistory || '-' }} + {{ retailOutput.minRateProduct || '-' }} + {{ retailOutput.smoothRange || '-' }} + {{ retailOutput.finalCalculateRate || '-' }} % + {{ retailOutput.referenceRate || '-' }} % diff --git a/ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue b/ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue index 6365313..55145d3 100644 --- a/ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue +++ b/ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue @@ -72,6 +72,7 @@ {{ detailData.guarType }} {{ detailData.applyAmt }} 元 + {{ detailData.loanTerm || '-' }} {{ formatBoolean(detailData.bizProof) }} diff --git a/ruoyi-ui/tests/retail-display-fields.test.js b/ruoyi-ui/tests/retail-display-fields.test.js new file mode 100644 index 0000000..530cb85 --- /dev/null +++ b/ruoyi-ui/tests/retail-display-fields.test.js @@ -0,0 +1,29 @@ +const fs = require('fs') +const path = require('path') +const assert = require('assert') + +function read(relativePath) { + return fs.readFileSync(path.join(__dirname, '..', relativePath), 'utf8') +} + +const personalDetail = read('src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue') +const modelOutput = read('src/views/loanPricing/workflow/components/ModelOutputDisplay.vue') + +assert( + personalDetail.includes('label="借款期限"') && personalDetail.includes('detailData.loanTerm'), + '个人详情页缺少借款期限展示' +) + +const requiredRetailFields = [ + 'retailOutput.loanRateHistory', + 'retailOutput.minRateProduct', + 'retailOutput.smoothRange', + 'retailOutput.finalCalculateRate', + 'retailOutput.referenceRate' +] + +requiredRetailFields.forEach((field) => { + assert(modelOutput.includes(field), `模型输出缺少字段展示: ${field}`) +}) + +console.log('retail display fields assertions passed') diff --git a/sql/add_model_retail_output_rate_fields_20260403.sql b/sql/add_model_retail_output_rate_fields_20260403.sql new file mode 100644 index 0000000..287bcc1 --- /dev/null +++ b/sql/add_model_retail_output_rate_fields_20260403.sql @@ -0,0 +1,6 @@ +ALTER TABLE `model_retail_output_fields` + ADD COLUMN `loan_rate_history` varchar(100) DEFAULT '' COMMENT '历史利率' AFTER `calculate_rate`, + ADD COLUMN `min_rate_product` varchar(100) DEFAULT '' COMMENT '产品最低利率下限' AFTER `loan_rate_history`, + ADD COLUMN `smooth_range` varchar(100) DEFAULT '' COMMENT '平滑幅度' AFTER `min_rate_product`, + ADD COLUMN `final_calculate_rate` varchar(100) DEFAULT '' COMMENT '最终测算利率' AFTER `smooth_range`, + ADD COLUMN `reference_rate` varchar(100) DEFAULT '' COMMENT '参考利率' AFTER `final_calculate_rate`; diff --git a/sql/loan_pricing_prod_init_20260331.sql b/sql/loan_pricing_prod_init_20260331.sql index 3adef4c..e091b48 100644 --- a/sql/loan_pricing_prod_init_20260331.sql +++ b/sql/loan_pricing_prod_init_20260331.sql @@ -908,6 +908,11 @@ CREATE TABLE `model_retail_output_fields` ( `totoal_bp_risk` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'TOTAL_BP_风险度', `total_bp` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '浮动BP', `calculate_rate` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '测算利率', + `loan_rate_history` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '历史利率', + `min_rate_product` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '产品最低利率下限', + `smooth_range` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '平滑幅度', + `final_calculate_rate` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '最终测算利率', + `reference_rate` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '参考利率', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='零售模型输出字段表'; diff --git a/sql/loan_pricing_schema_20260328.sql b/sql/loan_pricing_schema_20260328.sql index 040e818..7366339 100644 --- a/sql/loan_pricing_schema_20260328.sql +++ b/sql/loan_pricing_schema_20260328.sql @@ -490,6 +490,11 @@ CREATE TABLE `model_retail_output_fields` ( `totoal_bp_risk` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'TOTAL_BP_风险度', `total_bp` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '浮动BP', `calculate_rate` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '测算利率', + `loan_rate_history` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '历史利率', + `min_rate_product` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '产品最低利率下限', + `smooth_range` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '平滑幅度', + `final_calculate_rate` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '最终测算利率', + `reference_rate` varchar(100) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '参考利率', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='零售模型输出字段表'; diff --git a/sql/model_retail.sql b/sql/model_retail.sql index 950eab4..e79a8ff 100644 --- a/sql/model_retail.sql +++ b/sql/model_retail.sql @@ -106,9 +106,19 @@ CREATE TABLE IF NOT EXISTS model_retail_output_fields ( total_bp VARCHAR(100) DEFAULT '' COMMENT '浮动BP', -- 测算利率(百分比,如6.15) calculate_rate VARCHAR(100) DEFAULT '' COMMENT '测算利率', + -- 历史利率 + loan_rate_history VARCHAR(100) DEFAULT '' COMMENT '历史利率', + -- 产品最低利率下限 + min_rate_product VARCHAR(100) DEFAULT '' COMMENT '产品最低利率下限', + -- 平滑幅度 + smooth_range VARCHAR(100) DEFAULT '' COMMENT '平滑幅度', + -- 最终测算利率 + final_calculate_rate VARCHAR(100) DEFAULT '' COMMENT '最终测算利率', + -- 参考利率 + reference_rate VARCHAR(100) DEFAULT '' COMMENT '参考利率', -- 创建时间(审计字段) create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 主键约束 PRIMARY KEY (id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='零售模型输出字段表'; \ No newline at end of file +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='零售模型输出字段表';