361 lines
13 KiB
Markdown
361 lines
13 KiB
Markdown
# 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) | 抵(质)押类:贸易和建筑业企业上调20BP,true/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 企业");
|
||
});
|
||
%}
|
||
```
|