Files
loan-pricing/openspec/changes/split-pricing-creation-interface/design.md
2026-02-02 15:25:38 +08:00

361 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Design: 拆分个人和企业利率定价发起接口
## 数据库变更
### 缺失字段分析
经过对比 `person.csv``corp.csv` 中定义的字段与现有数据库表 `loan_pricing_workflow`,发现以下字段缺失:
#### 个人客户缺失字段
| 字段名 | 中文名 | 类型 | 说明 |
|-----------|------|--------------|-------------------------|
| id_num | 证件号码 | varchar(100) | 存储个人身份证号或其他证件号码 |
| loan_loop | 循环功能 | varchar(10) | 贷款合同是否开通循环功能true/false |
#### 企业客户缺失字段
| 字段名 | 中文名 | 类型 | 说明 |
|-----------------------|------------|--------------|----------------------------------|
| id_num | 证件号码 | varchar(100) | 存储企业统一社会信用代码或其他证件号码 |
| is_trade_construction | 贸易和建筑业企业标识 | varchar(10) | 抵押类贸易和建筑业企业上调20BPtrue/false |
| is_green_loan | 绿色贷款 | varchar(10) | 绿色贷款标识true/false |
| is_tech_ent | 科技型企业 | varchar(10) | 科技型企业标识true/false |
| loan_term | 贷款期限 | varchar(50) | 贷款期限,单位:月/年 |
### 数据库迁移 SQL
```sql
-- 添加缺失的字段到 loan_pricing_workflow 表
-- 个人和企业共同需要的字段
ALTER TABLE `loan_pricing_workflow` ADD COLUMN `id_num` varchar(100) DEFAULT NULL COMMENT '证件号码' AFTER `id_type`;
-- 个人客户专用字段
ALTER TABLE `loan_pricing_workflow` ADD COLUMN `loan_loop` varchar(10) DEFAULT NULL COMMENT '循环功能: true/false' AFTER `biz_proof`;
-- 企业客户专用字段
ALTER TABLE `loan_pricing_workflow` ADD COLUMN `is_trade_construction` varchar(10) DEFAULT NULL COMMENT '贸易和建筑业企业标识: true/false抵质押类上调20BP' AFTER `is_agri_guar`;
ALTER TABLE `loan_pricing_workflow` ADD COLUMN `is_green_loan` varchar(10) DEFAULT NULL COMMENT '绿色贷款: true/false' AFTER `is_agri_guar`;
ALTER TABLE `loan_pricing_workflow` ADD COLUMN `is_tech_ent` varchar(10) DEFAULT NULL COMMENT '科技型企业: true/false' AFTER `is_agri_guar`;
ALTER TABLE `loan_pricing_workflow` ADD COLUMN `loan_term` varchar(50) DEFAULT NULL COMMENT '贷款期限' AFTER `apply_amt`;
```
### Entity 类更新
`LoanPricingWorkflow.java` 需要添加以下属性:
```java
/** 证件号码 */
private String idNum;
/** 循环功能: true/false */
private String loanLoop;
/** 贸易和建筑业企业标识: true/false */
private String isTradeConstruction;
/** 绿色贷款: true/false */
private String isGreenLoan;
/** 科技型企业: true/false */
private String isTechEnt;
/** 贷款期限 */
private String loanTerm;
```
## 架构设计
### 1. DTO 结构设计
#### PersonalLoanPricingCreateDTO个人客户发起DTO
```java
@Data
public class PersonalLoanPricingCreateDTO {
@NotBlank(message = "客户内码不能为空")
private String custIsn;
@NotBlank(message = "客户类型不能为空")
private String custType; // 固定值 "个人"
@NotBlank(message = "担保方式不能为空")
private String guarType; // 可选值:信用,保证,抵押,质押
private String custName;
private String idType;
private String idNum;
@NotBlank(message = "申请金额不能为空")
private String applyAmt; // 单位:元
private String bizProof; // 是否有经营佐证
private String loanLoop; // 循环功能
private String collType; // 抵质押类型
private String collThirdParty; // 抵质押物是否三方所有
}
```
#### CorporateLoanPricingCreateDTO企业客户发起DTO
```java
@Data
public class CorporateLoanPricingCreateDTO {
@NotBlank(message = "客户内码不能为空")
private String custIsn;
@NotBlank(message = "客户类型不能为空")
private String custType; // 固定值 "企业"
@NotBlank(message = "担保方式不能为空")
private String guarType; // 可选值:信用,保证,抵押,质押
private String custName;
private String idType;
private String idNum;
@NotBlank(message = "申请金额不能为空")
private String applyAmt; // 单位:元
private String isAgriGuar; // 省农担担保贷款
private String isGreenLoan; // 绿色贷款
private String isTechEnt; // 科技型企业
private String loanTerm; // 贷款期限
private String collType; // 抵质押类型
private String collThirdParty; // 抵质押物是否三方所有
}
```
### 2. 接口设计
#### Controller 层
```java
@RestController
@RequestMapping("/loanPricing/workflow")
public class LoanPricingWorkflowController extends BaseController {
// 原有接口(保留)
@PostMapping("/create")
public AjaxResult create(@Validated @RequestBody LoanPricingWorkflow loanPricingWorkflow)
// 新增:个人客户发起接口
@PostMapping("/create/personal")
public AjaxResult createPersonal(@Validated @RequestBody PersonalLoanPricingCreateDTO dto)
// 新增:企业客户发起接口
@PostMapping("/create/corporate")
public AjaxResult createCorporate(@Validated @RequestBody CorporateLoanPricingCreateDTO dto)
}
```
#### Service 层接口
```java
public interface ILoanPricingWorkflowService {
// 原有方法(保留兼容)
LoanPricingWorkflow createLoanPricing(LoanPricingWorkflow loanPricingWorkflow);
// 新增:个人客户发起
LoanPricingWorkflow createPersonalLoanPricing(PersonalLoanPricingCreateDTO dto);
// 新增:企业客户发起
LoanPricingWorkflow createCorporateLoanPricing(CorporateLoanPricingCreateDTO dto);
}
```
### 3. 字段映射关系
| 字段类别 | 个人字段 | 企业字段 |
|------|------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
| 共同字段 | custIsn, custType, guarType, custName, idType, idNum, applyAmt, collType, collThirdParty | |
| 个人特有 | bizProof是否有经营佐证, loanLoop循环功能 | |
| 企业特有 | | isAgriGuar省农担担保贷款, isGreenLoan绿色贷款, isTechEnt科技型企业, isTradeConstruction贸易和建筑业企业, loanTerm贷款期限 |
### 4. 实现细节
#### DTO 转 Entity 转换器
创建一个转换工具类,将 DTO 转换为 `LoanPricingWorkflow` 实体:
```java
public class LoanPricingConverter {
public static LoanPricingWorkflow toEntity(PersonalLoanPricingCreateDTO dto) {
LoanPricingWorkflow entity = new LoanPricingWorkflow();
// 映射共同字段
entity.setCustIsn(dto.getCustIsn());
entity.setCustType("个人");
entity.setCustName(dto.getCustName());
entity.setIdType(dto.getIdType());
entity.setIdNum(dto.getIdNum());
entity.setGuarType(dto.getGuarType());
entity.setApplyAmt(dto.getApplyAmt());
entity.setCollType(dto.getCollType());
entity.setCollThirdParty(dto.getCollThirdParty());
// 映射个人特有字段
entity.setBizProof(dto.getBizProof());
entity.setLoanLoop(dto.getLoanLoop());
return entity;
}
public static LoanPricingWorkflow toEntity(CorporateLoanPricingCreateDTO dto) {
LoanPricingWorkflow entity = new LoanPricingWorkflow();
// 映射共同字段
entity.setCustIsn(dto.getCustIsn());
entity.setCustType("企业");
entity.setCustName(dto.getCustName());
entity.setIdType(dto.getIdType());
entity.setIdNum(dto.getIdNum());
entity.setGuarType(dto.getGuarType());
entity.setApplyAmt(dto.getApplyAmt());
entity.setCollType(dto.getCollType());
entity.setCollThirdParty(dto.getCollThirdParty());
// 映射企业特有字段
entity.setIsAgriGuar(dto.getIsAgriGuar());
entity.setIsGreenLoan(dto.getIsGreenLoan());
entity.setIsTechEnt(dto.getIsTechEnt());
entity.setIsTradeConstruction(dto.getIsTradeConstruction());
entity.setLoanTerm(dto.getLoanTerm());
return entity;
}
}
```
### 5. 验证策略
#### 个人客户验证
- 必填字段custIsn, custType, guarType, applyAmt
- 枚举验证guarType 必须是"信用/保证/抵押/质押"之一
#### 企业客户验证
- 必填字段custIsn, custType, guarType, applyAmt
- 枚举验证guarType 必须是"信用/保证/抵押/质押"之一
### 6. 向后兼容性策略
1. 保留原有 `POST /loanPricing/workflow/create` 接口
2. 新旧接口共存,前端可自由选择使用
3. 不添加 @Deprecated 标记,保持接口的可用性
## 技术决策
### 为什么使用两个独立的 DTO 而不是一个带条件验证的 DTO
1. **类型安全**:编译期就能发现字段错误
2. **API 文档更清晰**Swagger 只显示相关字段
3. **验证更简单**:不需要在 DTO 内部根据类型做条件验证
### 为什么保留原有接口?
1. **多接口共存**:前端可以选择使用新接口或继续使用原有接口
2. **降级方案**:如果新接口有问题,可以快速回退
3. **兼容性**:可能有其他系统调用该接口
## 文件清单
### 数据库相关
- **新增**: `sql/add_missing_fields.sql` - 数据库迁移脚本
### 新增文件
- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java`
- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/CorporateLoanPricingCreateDTO.java`
- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java`
### 测试文件
- `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTOTest.java`
- `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/domain/dto/CorporateLoanPricingCreateDTOTest.java`
- `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/util/LoanPricingConverterTest.java`
- `test_api/test_personal_create.http` - 个人客户发起接口 HTTP 测试脚本
- `test_api/test_corporate_create.http` - 企业客户发起接口 HTTP 测试脚本
- `test_api/test_backward_compatibility.http` - 向后兼容性测试脚本
### 修改文件
- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/entity/LoanPricingWorkflow.java` - 添加缺失字段的属性
- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowController.java`
- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/ILoanPricingWorkflowService.java`
- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImpl.java`
## 测试脚本示例
### HTTP 测试脚本格式
测试脚本使用 IntelliJ IDEA 的 .http 文件格式,示例如下:
```http
### Token
POST http://localhost:8080/login/test
Content-Type: application/json
{
"username": "admin",
"password": "admin123"
}
> {%
client.test("Request executed successfully", function() {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 200, "Response code is 200");
client.global.set("token", response.body.data.token);
});
%}
### -
POST http://localhost:8080/loanPricing/workflow/create/personal
Authorization: Bearer {{token}}
Content-Type: application/json
{
"custIsn": "TEST001",
"custName": "",
"idType": "",
"idNum": "110101199001011234",
"guarType": "",
"applyAmt": "500000",
"bizProof": "true",
"loanLoop": "false"
}
> {%
client.test("Personal loan creation successful", function() {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 200, "Response code is 200");
client.assert(response.body.data.custType === "", "Customer type is ");
});
%}
### -
POST http://localhost:8080/loanPricing/workflow/create/corporate
Authorization: Bearer {{token}}
Content-Type: application/json
{
"custIsn": "CORP001",
"custName": "",
"idType": "",
"idNum": "91110000100000000X",
"guarType": "",
"applyAmt": "1000000",
"isAgriGuar": "false",
"isGreenLoan": "true",
"isTechEnt": "true",
"loanTerm": "36"
}
> {%
client.test("Corporate loan creation successful", function() {
client.assert(response.status === 200, "Response status is 200");
client.assert(response.body.code === 200, "Response code is 200");
client.assert(response.body.data.custType === "", "Customer type is ");
});
%}
```