补充贷款定价敏感字段后端实施记录
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
# 贷款定价敏感字段加密后端实施记录
|
||||
|
||||
## 修改内容
|
||||
- 在 `ruoyi-loan-pricing` 新增 `SensitiveFieldCryptoService`,统一处理 `custName`、`idNum` 的 AES/ECB/PKCS5Padding + Base64 加解密。
|
||||
- 在 `ruoyi-loan-pricing` 新增 `LoanPricingSensitiveDisplayService`,统一处理个人姓名、企业名称、身份证号、统一社会信用代码的脱敏展示。
|
||||
- 在 `LoanPricingWorkflowServiceImpl` 的创建链路对 `custName`、`idNum` 加密后入库,并在列表、详情链路解密后做脱敏返回。
|
||||
- 在 `LoanPricingModelService` 调用模型前显式解密 `custName`、`idNum`,保证模型入参不接收密文;同时补齐 `ModelInvokeDTO.idNum` 字段。
|
||||
- 修复模型调用后更新 `modelOutputId` 时把解密后的 `custName`、`idNum` 明文回写数据库的问题,改为仅更新 `modelOutputId`。
|
||||
- 在 `LoanPricingWorkflowMapper.xml` 和服务查询条件中移除按 `custName` 查询,改为按 `custIsn` 查询。
|
||||
- 新增 `sql/clear_loan_pricing_workflow_history.sql`,用于清理贷款定价流程及模型输出历史数据。
|
||||
|
||||
## 新增测试
|
||||
- `SensitiveFieldCryptoServiceTest`
|
||||
- `LoanPricingSensitiveDisplayServiceTest`
|
||||
- `LoanPricingWorkflowServiceImplTest`
|
||||
- `LoanPricingModelServiceTest`
|
||||
|
||||
## 验证结果
|
||||
- 执行 `mvn -pl ruoyi-loan-pricing -am -Dtest=SensitiveFieldCryptoServiceTest,LoanPricingSensitiveDisplayServiceTest -Dsurefire.failIfNoSpecifiedTests=false test`,结果通过。
|
||||
- 执行 `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test`,结果通过。
|
||||
- 执行 `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowServiceImplTest,LoanPricingModelServiceTest -Dsurefire.failIfNoSpecifiedTests=false test`,结果通过。
|
||||
- 执行 `mvn -pl ruoyi-loan-pricing -am -Dsurefire.failIfNoSpecifiedTests=false test`,结果通过。
|
||||
- 手工启动 `ruoyi-admin` 到 `18080` 端口,并将 `model.url` 指向 `http://localhost:18080/rate/pricing/mock/invokeModel` 后完成联调:
|
||||
- 个人流程 `PSEC2026033004` 与企业流程 `CSEC2026033003` 创建成功,接口即时返回的 `custName`、`idNum` 为密文。
|
||||
- 通过 MySQL 查询 `loan_pricing_workflow`,确认 `cust_name`、`id_num` 落库为密文,同时 `model_output_id` 正常回填。
|
||||
- 调用列表接口 `/loanPricing/workflow/list?custIsn=PSEC2026033004`,确认返回 `custName` 为 `张*`。
|
||||
- 调用详情接口 `/loanPricing/workflow/20260330110314523`,确认个人 `custName` 为 `张*`、`idNum` 为 `1101********1234`。
|
||||
- 调用详情接口 `/loanPricing/workflow/20260330110252133`,确认企业 `custName` 为 `测试****公司`、`idNum` 为 `91*************00X`。
|
||||
|
||||
## 备注
|
||||
- 联调过程中发现 `serialNum` 仍使用毫秒时间戳生成,并发创建可能触发 `uk_serial_num` 冲突;该问题为本次验证中暴露的既有风险,本次未纳入敏感字段加密方案范围内处理。
|
||||
@@ -66,16 +66,20 @@ public class LoanPricingModelService {
|
||||
ModelRetailOutputFields modelRetailOutputFields = JSON.parseObject(response.toJSONString(), ModelRetailOutputFields.class);
|
||||
modelRetailOutputFieldsMapper.insert(modelRetailOutputFields);
|
||||
log.info("个人模型调用成功");
|
||||
loanPricingWorkflow.setModelOutputId(modelRetailOutputFields.getId());
|
||||
loanPricingWorkflowMapper.updateById(loanPricingWorkflow);
|
||||
LoanPricingWorkflow workflowToUpdate = new LoanPricingWorkflow();
|
||||
workflowToUpdate.setId(loanPricingWorkflow.getId());
|
||||
workflowToUpdate.setModelOutputId(modelRetailOutputFields.getId());
|
||||
loanPricingWorkflowMapper.updateById(workflowToUpdate);
|
||||
log.info("更新流程信息成功");
|
||||
}else if (loanPricingWorkflow.getCustType().equals("企业")){
|
||||
// 企业模型
|
||||
ModelCorpOutputFields modelCorpOutputFields = JSON.parseObject(response.toJSONString(), ModelCorpOutputFields.class);
|
||||
modelCorpOutputFieldsMapper.insert(modelCorpOutputFields);
|
||||
log.info("企业模型调用成功");
|
||||
loanPricingWorkflow.setModelOutputId(modelCorpOutputFields.getId());
|
||||
loanPricingWorkflowMapper.updateById(loanPricingWorkflow);
|
||||
LoanPricingWorkflow workflowToUpdate = new LoanPricingWorkflow();
|
||||
workflowToUpdate.setId(loanPricingWorkflow.getId());
|
||||
workflowToUpdate.setModelOutputId(modelCorpOutputFields.getId());
|
||||
loanPricingWorkflowMapper.updateById(workflowToUpdate);
|
||||
log.info("更新流程信息成功");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,4 +63,28 @@ class LoanPricingModelServiceTest
|
||||
Objects.equals("张三", dto.getCustName())
|
||||
&& Objects.equals("110101199001011234", dto.getIdNum())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotWritePlainCustNameAndIdNumBackWhenUpdatingWorkflow()
|
||||
{
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setId(2L);
|
||||
workflow.setCustType("个人");
|
||||
workflow.setCustName("cipher-name");
|
||||
workflow.setIdNum("cipher-id");
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("calculateRate", "6.15");
|
||||
|
||||
when(loanPricingWorkflowMapper.selectById(2L)).thenReturn(workflow);
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
|
||||
when(modelService.invokeModel(any())).thenReturn(response);
|
||||
|
||||
loanPricingModelService.invokeModelAsync(2L);
|
||||
|
||||
verify(loanPricingWorkflowMapper).updateById(argThat((LoanPricingWorkflow entity) ->
|
||||
!Objects.equals("张三", entity.getCustName())
|
||||
&& !Objects.equals("110101199001011234", entity.getIdNum())));
|
||||
}
|
||||
}
|
||||
|
||||
6
sql/clear_loan_pricing_workflow_history.sql
Normal file
6
sql/clear_loan_pricing_workflow_history.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
-- 清理贷款定价流程历史数据
|
||||
-- 执行日期: 2026-03-30
|
||||
|
||||
DELETE FROM model_retail_output_fields;
|
||||
DELETE FROM model_corp_output_fields;
|
||||
DELETE FROM loan_pricing_workflow;
|
||||
Reference in New Issue
Block a user