diff --git a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowController.java b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowController.java index 347890a..0479710 100644 --- a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowController.java +++ b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowController.java @@ -2,6 +2,7 @@ package com.ruoyi.loanpricing.controller; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; @@ -139,6 +140,17 @@ public class LoanPricingWorkflowController extends BaseController return success(loanRateHistoryService.query(custIsn)); } + /** + * 解密历史流程客户名称和证件号码密文字段 + */ + @Anonymous + @Operation(summary = "解密历史流程客户敏感字段") + @PostMapping("/anonymous/decrypt-sensitive-fields") + public AjaxResult decryptSensitiveFields() + { + return success(loanPricingWorkflowService.decryptWorkflowSensitiveFields()); + } + /** * 查询利率定价流程列表 */ diff --git a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/mapper/LoanPricingWorkflowMapper.java b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/mapper/LoanPricingWorkflowMapper.java index 63746ef..e592636 100644 --- a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/mapper/LoanPricingWorkflowMapper.java +++ b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/mapper/LoanPricingWorkflowMapper.java @@ -17,4 +17,8 @@ public interface LoanPricingWorkflowMapper extends BaseMapper selectWorkflowPageWithRates(Page page, @Param("query") LoanPricingWorkflow query); + + int updateSensitiveFieldsById(@Param("id") Long id, + @Param("custName") String custName, + @Param("idNum") String idNum); } diff --git a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/ILoanPricingWorkflowService.java b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/ILoanPricingWorkflowService.java index 8abbd7a..c9e2cf4 100644 --- a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/ILoanPricingWorkflowService.java +++ b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/ILoanPricingWorkflowService.java @@ -9,6 +9,7 @@ import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowListVO; import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowVO; import java.util.List; +import java.util.Map; /** * 利率定价流程Service接口 @@ -93,4 +94,11 @@ public interface ILoanPricingWorkflowService * @return 是否成功 */ public boolean setExecuteRate(String serialNum, String executeRate); + + /** + * 解密历史流程客户名称和证件号码密文字段 + * + * @return 迁移统计 + */ + public Map decryptWorkflowSensitiveFields(); } diff --git a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java index 460f1e8..31dccd4 100644 --- a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java +++ b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java @@ -36,9 +36,6 @@ public class LoanPricingModelService { @Resource private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper; - @Resource - private SensitiveFieldCryptoService sensitiveFieldCryptoService; - public void invokeModelAsync(Long workflowId) { invokeModelAndSave(workflowId, false); } @@ -53,16 +50,6 @@ public class LoanPricingModelService { log.error("未找到对应的流程信息,未调用模型服务"); return; } - try - { - loanPricingWorkflow.setCustName(sensitiveFieldCryptoService.decrypt(loanPricingWorkflow.getCustName())); - loanPricingWorkflow.setIdNum(sensitiveFieldCryptoService.decrypt(loanPricingWorkflow.getIdNum())); - } - catch (RuntimeException ex) - { - log.error("贷款定价模型调用前敏感字段解密失败", ex); - throw ex; - } ModelInvokeDTO modelInvokeDTO = new ModelInvokeDTO(); BeanUtils.copyProperties(loanPricingWorkflow, modelInvokeDTO); if ("个人".equals(loanPricingWorkflow.getCustType())) diff --git a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImpl.java b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImpl.java index dbec45d..8501cb0 100644 --- a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImpl.java +++ b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImpl.java @@ -31,7 +31,9 @@ import org.springframework.util.StringUtils; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -94,8 +96,6 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi } loanPricingWorkflow.setDeptId(SecurityUtils.getDeptId()); - loanPricingWorkflow.setCustName(sensitiveFieldCryptoService.encrypt(loanPricingWorkflow.getCustName())); - loanPricingWorkflow.setIdNum(sensitiveFieldCryptoService.encrypt(loanPricingWorkflow.getIdNum())); loanPricingWorkflowMapper.insert(loanPricingWorkflow); loanPricingModelService.invokeModelAsync(loanPricingWorkflow.getId()); @@ -199,8 +199,6 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi { LoanPricingWorkflow workflow = selectWorkflowBySerialNum(serialNum); assertCurrentUserIsCreator(workflow); - workflow.setCustName(sensitiveFieldCryptoService.decrypt(workflow.getCustName())); - workflow.setIdNum(sensitiveFieldCryptoService.decrypt(workflow.getIdNum())); return workflow; } @@ -236,14 +234,12 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi validateBusinessTypeAndHistoryRate(editingWorkflow); validateCollateralTypeAndCouponRate(editingWorkflow); - String encryptedCustName = sensitiveFieldCryptoService.encrypt(editingWorkflow.getCustName()); - String encryptedIdNum = sensitiveFieldCryptoService.encrypt(editingWorkflow.getIdNum()); LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(LoanPricingWorkflow::getId, existingWorkflow.getId()) .set(LoanPricingWorkflow::getCustIsn, editingWorkflow.getCustIsn()) - .set(LoanPricingWorkflow::getCustName, encryptedCustName) + .set(LoanPricingWorkflow::getCustName, editingWorkflow.getCustName()) .set(LoanPricingWorkflow::getIdType, editingWorkflow.getIdType()) - .set(LoanPricingWorkflow::getIdNum, encryptedIdNum) + .set(LoanPricingWorkflow::getIdNum, editingWorkflow.getIdNum()) .set(LoanPricingWorkflow::getGuarType, editingWorkflow.getGuarType()) .set(LoanPricingWorkflow::getApplyAmt, editingWorkflow.getApplyAmt()) .set(LoanPricingWorkflow::getBusinessType, editingWorkflow.getBusinessType()) @@ -312,7 +308,6 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi { LoanPricingWorkflow scopedQuery = applyWorkflowListDataScope(loanPricingWorkflow); IPage pageResult = loanPricingWorkflowMapper.selectWorkflowPageWithRates(page, scopedQuery); - pageResult.getRecords().forEach(row -> row.setCustName(sensitiveFieldCryptoService.decrypt(row.getCustName()))); return pageResult; } @@ -330,10 +325,6 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(LoanPricingWorkflow::getSerialNum, serialNum); LoanPricingWorkflow loanPricingWorkflow = loanPricingWorkflowMapper.selectOne(wrapper); - String plainCustName = sensitiveFieldCryptoService.decrypt(loanPricingWorkflow.getCustName()); - String plainIdNum = sensitiveFieldCryptoService.decrypt(loanPricingWorkflow.getIdNum()); - loanPricingWorkflow.setCustName(plainCustName); - loanPricingWorkflow.setIdNum(plainIdNum); loanPricingWorkflowVO.setLoanPricingWorkflow(loanPricingWorkflow); if (Objects.nonNull(loanPricingWorkflow.getModelOutputId())){ @@ -468,4 +459,80 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi int result = loanPricingWorkflowMapper.updateById(workflow); return result > 0; } + + @Override + public Map decryptWorkflowSensitiveFields() + { + long processed = 0L; + long updated = 0L; + long skipped = 0L; + long failed = 0L; + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.and(q -> q.isNotNull(LoanPricingWorkflow::getCustName) + .or() + .isNotNull(LoanPricingWorkflow::getIdNum)); + List workflows = loanPricingWorkflowMapper.selectList(wrapper); + + for (LoanPricingWorkflow workflow : workflows) + { + processed++; + LoanPricingWorkflow update = new LoanPricingWorkflow(); + update.setId(workflow.getId()); + boolean changed = false; + + String decryptedCustName = tryDecrypt(workflow.getCustName()); + if (decryptedCustName != null && !Objects.equals(decryptedCustName, workflow.getCustName())) + { + update.setCustName(decryptedCustName); + changed = true; + } + + String decryptedIdNum = tryDecrypt(workflow.getIdNum()); + if (decryptedIdNum != null && !Objects.equals(decryptedIdNum, workflow.getIdNum())) + { + update.setIdNum(decryptedIdNum); + changed = true; + } + + if (!changed) + { + skipped++; + continue; + } + + try + { + loanPricingWorkflowMapper.updateSensitiveFieldsById(update.getId(), update.getCustName(), update.getIdNum()); + updated++; + } + catch (RuntimeException ex) + { + failed++; + } + } + + Map result = new LinkedHashMap<>(); + result.put("processed", processed); + result.put("updated", updated); + result.put("skipped", skipped); + result.put("failed", failed); + return result; + } + + private String tryDecrypt(String value) + { + if (!StringUtils.hasText(value)) + { + return null; + } + try + { + return sensitiveFieldCryptoService.decrypt(value); + } + catch (RuntimeException ex) + { + return null; + } + } } diff --git a/ruoyi-loan-pricing/src/main/resources/mapper/loanpricing/LoanPricingWorkflowMapper.xml b/ruoyi-loan-pricing/src/main/resources/mapper/loanpricing/LoanPricingWorkflowMapper.xml index 4a254dc..783228b 100644 --- a/ruoyi-loan-pricing/src/main/resources/mapper/loanpricing/LoanPricingWorkflowMapper.xml +++ b/ruoyi-loan-pricing/src/main/resources/mapper/loanpricing/LoanPricingWorkflowMapper.xml @@ -40,7 +40,11 @@ AND SUBSTRING_INDEX(lpw.create_by, '-', -1) LIKE CONCAT('%', #{query.createBy}, '%') - AND lpw.cust_isn LIKE CONCAT('%', #{query.custIsn}, '%') + AND ( + lpw.cust_isn LIKE CONCAT('%', #{query.custIsn}, '%') + OR lpw.id_num LIKE CONCAT('%', #{query.custIsn}, '%') + OR lpw.cust_name LIKE CONCAT('%', #{query.custIsn}, '%') + ) AND lpw.dept_id = #{query.deptId} @@ -49,4 +53,17 @@ ORDER BY lpw.update_time DESC + + UPDATE loan_pricing_workflow + + + cust_name = #{custName}, + + + id_num = #{idNum}, + + + WHERE id = #{id} + + diff --git a/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/mapper/LoanPricingWorkflowMapperXmlTest.java b/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/mapper/LoanPricingWorkflowMapperXmlTest.java index 2805783..7f2f2fc 100644 --- a/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/mapper/LoanPricingWorkflowMapperXmlTest.java +++ b/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/mapper/LoanPricingWorkflowMapperXmlTest.java @@ -1,6 +1,7 @@ package com.ruoyi.loanpricing.mapper; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -23,5 +24,23 @@ class LoanPricingWorkflowMapperXmlTest assertTrue(xml.contains("find_in_set(#{query.dataScopeDeptId}, ancestors)")); assertTrue(xml.contains("lpw.dept_id = #{query.deptId}")); assertTrue(xml.contains("SUBSTRING_INDEX(lpw.create_by, '-', -1) LIKE CONCAT('%', #{query.createBy}, '%')")); + assertTrue(xml.contains("lpw.cust_isn LIKE CONCAT('%', #{query.custIsn}, '%')")); + assertTrue(xml.contains("lpw.id_num LIKE CONCAT('%', #{query.custIsn}, '%')")); + assertTrue(xml.contains("lpw.cust_name LIKE CONCAT('%', #{query.custIsn}, '%')")); + } + + @Test + void shouldUpdateOnlySensitiveFieldsForAnonymousMigration() throws IOException + { + ClassPathResource resource = new ClassPathResource("mapper/loanpricing/LoanPricingWorkflowMapper.xml"); + String xml = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8); + String updateSql = xml.substring(xml.indexOf(""), + xml.indexOf("", xml.indexOf(""))); + + assertTrue(updateSql.contains("cust_name = #{custName}")); + assertTrue(updateSql.contains("id_num = #{idNum}")); + assertTrue(updateSql.contains("WHERE id = #{id}")); + assertFalse(updateSql.contains("update_by")); + assertFalse(updateSql.contains("update_time")); } } diff --git a/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java b/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java index 31ac3dd..e64b734 100644 --- a/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java +++ b/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java @@ -40,9 +40,6 @@ class LoanPricingModelServicePersonalParamsTest { @Mock private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper; - @Mock - private SensitiveFieldCryptoService sensitiveFieldCryptoService; - @InjectMocks private LoanPricingModelService loanPricingModelService; @@ -87,9 +84,9 @@ class LoanPricingModelServicePersonalParamsTest { workflow.setRunType("1"); workflow.setCustIsn("CUST001"); workflow.setCustType("个人"); - workflow.setCustName("cipher-name"); + workflow.setCustName("张三"); workflow.setIdType("身份证"); - workflow.setIdNum("cipher-id"); + workflow.setIdNum("110101199001011234"); workflow.setGuarType("信用"); workflow.setApplyAmt("100000"); workflow.setLoanTerm("3"); @@ -101,8 +98,6 @@ class LoanPricingModelServicePersonalParamsTest { response.setCalculateRate("6.15"); when(loanPricingWorkflowMapper.selectById(1L)).thenReturn(workflow); - when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三"); - when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234"); when(modelService.invokePersonalModel(any())).thenReturn(response); loanPricingModelService.invokeModelAsync(1L); diff --git a/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServiceTest.java b/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServiceTest.java index 8c9bc33..e3af163 100644 --- a/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServiceTest.java +++ b/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServiceTest.java @@ -1,7 +1,6 @@ package com.ruoyi.loanpricing.service; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; import com.ruoyi.loanpricing.domain.dto.ModelInvokeDTO; import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow; @@ -21,7 +20,7 @@ import org.junit.jupiter.api.Test; class LoanPricingModelServiceTest { @Test - void shouldDecryptCustNameAndIdNumBeforeInvokePersonalModel() throws Exception + void shouldPassPlainCustNameAndIdNumBeforeInvokePersonalModel() throws Exception { TestContext context = createContext(personalWorkflow(1L)); @@ -37,15 +36,16 @@ class LoanPricingModelServiceTest } @Test - void shouldNotWritePlainCustNameAndIdNumBackWhenUpdatingWorkflow() throws Exception + void shouldOnlyWriteModelOutputIdBackWhenUpdatingWorkflow() throws Exception { TestContext context = createContext(personalWorkflow(2L)); context.service.invokeModelAsync(2L); LoanPricingWorkflow updatedWorkflow = context.updatedWorkflow.get(); - assertNotEquals("张三", updatedWorkflow.getCustName()); - assertNotEquals("110101199001011234", updatedWorkflow.getIdNum()); + assertEquals(2L, updatedWorkflow.getId()); + assertEquals(null, updatedWorkflow.getCustName()); + assertEquals(null, updatedWorkflow.getIdNum()); } @Test @@ -54,8 +54,8 @@ class LoanPricingModelServiceTest LoanPricingWorkflow workflow = new LoanPricingWorkflow(); workflow.setId(3L); workflow.setCustType("企业"); - workflow.setCustName("cipher-name"); - workflow.setIdNum("cipher-id"); + workflow.setCustName("测试科技有限公司"); + workflow.setIdNum("91110000100000000X"); workflow.setRepayMethod("分期"); workflow.setResCover("true"); workflow.setIsGreenLoan("true"); @@ -86,8 +86,8 @@ class LoanPricingModelServiceTest workflow.setId(4L); workflow.setModelOutputId(88L); workflow.setCustType("企业"); - workflow.setCustName("cipher-name"); - workflow.setIdNum("cipher-id"); + workflow.setCustName("测试科技有限公司"); + workflow.setIdNum("91110000100000000X"); TestContext context = createContext(workflow); context.service.reinvokeModelAndOverwrite(4L); @@ -102,8 +102,8 @@ class LoanPricingModelServiceTest LoanPricingWorkflow workflow = new LoanPricingWorkflow(); workflow.setId(id); workflow.setCustType("个人"); - workflow.setCustName("cipher-name"); - workflow.setIdNum("cipher-id"); + workflow.setCustName("张三"); + workflow.setIdNum("110101199001011234"); workflow.setCouponRate("2.15"); return workflow; } @@ -127,7 +127,6 @@ class LoanPricingModelServiceTest savingMapper(ModelRetailOutputFieldsMapper.class, context.retailInsertCount, context.retailUpdateCount)); setField(context.service, "modelCorpOutputFieldsMapper", savingMapper(ModelCorpOutputFieldsMapper.class, context.corpInsertCount, context.corpUpdateCount)); - setField(context.service, "sensitiveFieldCryptoService", new TestSensitiveFieldCryptoService()); return context; } @@ -228,25 +227,4 @@ class LoanPricingModelServiceTest } } - private static class TestSensitiveFieldCryptoService extends SensitiveFieldCryptoService - { - private TestSensitiveFieldCryptoService() - { - super("1234567890abcdef"); - } - - @Override - public String decrypt(String cipherText) - { - if ("cipher-name".equals(cipherText)) - { - return "张三"; - } - if ("cipher-id".equals(cipherText)) - { - return "110101199001011234"; - } - return cipherText; - } - } } diff --git a/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImplTest.java b/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImplTest.java index dcfd3a9..5d727a6 100644 --- a/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImplTest.java +++ b/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImplTest.java @@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -45,6 +46,8 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.context.SecurityContextHolder; import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.Objects; @ExtendWith(MockitoExtension.class) @@ -81,7 +84,7 @@ class LoanPricingWorkflowServiceImplTest } @Test - void shouldEncryptCustNameAndIdNumBeforeInsert() + void shouldKeepCustNameAndIdNumPlainBeforeInsert() { LoanPricingWorkflow workflow = new LoanPricingWorkflow(); workflow.setCustName("张三"); @@ -89,16 +92,14 @@ class LoanPricingWorkflowServiceImplTest workflow.setCustIsn("CUST001"); workflow.setBusinessType("新增"); - when(sensitiveFieldCryptoService.encrypt("张三")).thenReturn("cipher-name"); - when(sensitiveFieldCryptoService.encrypt("110101199001011234")).thenReturn("cipher-id"); - loanPricingWorkflowService.createLoanPricing(workflow); verify(loanPricingWorkflowMapper).insert(argThat((LoanPricingWorkflow entity) -> - Objects.equals("cipher-name", entity.getCustName()) - && Objects.equals("cipher-id", entity.getIdNum()) + Objects.equals("张三", entity.getCustName()) + && Objects.equals("110101199001011234", entity.getIdNum()) && Objects.equals(100L, entity.getDeptId()) && Objects.equals("CUST001", entity.getCustIsn()))); + verify(sensitiveFieldCryptoService, never()).encrypt(any()); } @Test @@ -121,17 +122,17 @@ class LoanPricingWorkflowServiceImplTest void shouldReturnPlainCustNameWhenReturningPagedWorkflowList() { LoanPricingWorkflowListVO row = new LoanPricingWorkflowListVO(); - row.setCustName("cipher-name"); + row.setCustName("张三"); Page pageResult = new Page<>(1, 10); pageResult.setRecords(Collections.singletonList(row)); when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(pageResult); - when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三"); IPage result = loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), new LoanPricingWorkflow()); assertEquals("张三", result.getRecords().get(0).getCustName()); + verify(sensitiveFieldCryptoService, never()).decrypt(any()); } @Test @@ -229,7 +230,7 @@ class LoanPricingWorkflowServiceImplTest } @Test - void shouldUseCustIsnInsteadOfCustNameAsQueryCondition() + void shouldUseCustIsnAsQueryConditionInLegacyList() { LoanPricingWorkflow query = new LoanPricingWorkflow(); query.setCustIsn("CUST001"); @@ -384,17 +385,16 @@ class LoanPricingWorkflowServiceImplTest void shouldReturnEditableWorkflowWithPlainSensitiveFieldsForCreator() { LoanPricingWorkflow workflow = editableWorkflow("个人", "若依-admin"); - workflow.setCustName("cipher-name"); - workflow.setIdNum("cipher-id"); + workflow.setCustName("张三"); + workflow.setIdNum("110101199001011234"); when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow); - when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三"); - when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234"); LoanPricingWorkflow result = loanPricingWorkflowService.selectEditableLoanPricingBySerialNum("P20260525001"); assertEquals("张三", result.getCustName()); assertEquals("110101199001011234", result.getIdNum()); + verify(sensitiveFieldCryptoService, never()).decrypt(any()); } @Test @@ -414,10 +414,6 @@ class LoanPricingWorkflowServiceImplTest { LoanPricingWorkflow workflow = editableWorkflow("个人", "若依-admin"); when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow); - when(sensitiveFieldCryptoService.encrypt("张三")).thenReturn("cipher-name-new"); - when(sensitiveFieldCryptoService.encrypt("110101199001019999")).thenReturn("cipher-id-new"); - when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三"); - when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001019999"); PersonalLoanPricingCreateDTO dto = new PersonalLoanPricingCreateDTO(); dto.setCustIsn("CUST001"); @@ -434,7 +430,13 @@ class LoanPricingWorkflowServiceImplTest ArgumentCaptor updateWrapperCaptor = ArgumentCaptor.forClass(LambdaUpdateWrapper.class); verify(loanPricingWorkflowMapper).update(any(), updateWrapperCaptor.capture()); - assertTrue(updateWrapperCaptor.getValue().getSqlSet().contains("apply_amt")); + String sqlSet = updateWrapperCaptor.getValue().getSqlSet(); + assertTrue(sqlSet.contains("apply_amt")); + assertTrue(sqlSet.contains("cust_name")); + assertTrue(sqlSet.contains("id_num")); + assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("张三")); + assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("110101199001019999")); + verify(sensitiveFieldCryptoService, never()).encrypt(any()); verify(loanPricingModelService).reinvokeModelAndOverwrite(101L); } @@ -443,10 +445,6 @@ class LoanPricingWorkflowServiceImplTest { LoanPricingWorkflow workflow = editableWorkflow("企业", "若依-admin"); when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow); - when(sensitiveFieldCryptoService.encrypt("测试科技有限公司")).thenReturn("cipher-corp-name"); - when(sensitiveFieldCryptoService.encrypt("91110000100000000X")).thenReturn("cipher-corp-id"); - when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("测试科技有限公司"); - when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("91110000100000000X"); CorporateLoanPricingCreateDTO dto = new CorporateLoanPricingCreateDTO(); dto.setCustIsn("CORP001"); @@ -473,6 +471,9 @@ class LoanPricingWorkflowServiceImplTest assertTrue(sqlSet.contains("is_green_loan"), sqlSet); assertTrue(sqlSet.contains("is_trade_construction"), sqlSet); assertTrue(sqlSet.contains("coll_third_party"), sqlSet); + assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("测试科技有限公司")); + assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("91110000100000000X")); + verify(sensitiveFieldCryptoService, never()).encrypt(any()); verify(loanPricingModelService).reinvokeModelAndOverwrite(101L); } @@ -543,17 +544,16 @@ class LoanPricingWorkflowServiceImplTest LoanPricingWorkflow workflow = new LoanPricingWorkflow(); workflow.setSerialNum("P20260328001"); workflow.setCustType("个人"); - workflow.setCustName("cipher-name"); - workflow.setIdNum("cipher-id"); + workflow.setCustName("张三"); + workflow.setIdNum("110101199001011234"); when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow); - when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三"); - when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234"); LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001"); assertEquals("张三", result.getLoanPricingWorkflow().getCustName()); assertEquals("110101199001011234", result.getLoanPricingWorkflow().getIdNum()); + verify(sensitiveFieldCryptoService, never()).decrypt(any()); } @Test @@ -562,8 +562,8 @@ class LoanPricingWorkflowServiceImplTest LoanPricingWorkflow workflow = new LoanPricingWorkflow(); workflow.setSerialNum("P20260328001"); workflow.setCustType("个人"); - workflow.setCustName("cipher-name"); - workflow.setIdNum("cipher-id"); + workflow.setCustName("张三"); + workflow.setIdNum("110101199001011234"); workflow.setModelOutputId(11L); ModelRetailOutputFields retailOutputFields = new ModelRetailOutputFields(); @@ -573,8 +573,6 @@ class LoanPricingWorkflowServiceImplTest when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow); when(modelRetailOutputFieldsMapper.selectById(11L)).thenReturn(retailOutputFields); - when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三"); - when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234"); LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001"); @@ -611,8 +609,8 @@ class LoanPricingWorkflowServiceImplTest LoanPricingWorkflow workflow = new LoanPricingWorkflow(); workflow.setSerialNum("C20260328001"); workflow.setCustType("企业"); - workflow.setCustName("cipher-name"); - workflow.setIdNum("cipher-id"); + workflow.setCustName("测试科技有限公司"); + workflow.setIdNum("91110000100000000X"); workflow.setModelOutputId(22L); ModelCorpOutputFields corpOutputFields = new ModelCorpOutputFields(); @@ -622,8 +620,6 @@ class LoanPricingWorkflowServiceImplTest when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow); when(modelCorpOutputFieldsMapper.selectById(22L)).thenReturn(corpOutputFields); - when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("测试科技有限公司"); - when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("91110000100000000X"); LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("C20260328001"); @@ -631,6 +627,74 @@ class LoanPricingWorkflowServiceImplTest assertEquals("91110000100000000X", result.getModelCorpOutputFields().getIdNum()); } + @Test + void shouldDecryptEncryptedWorkflowFieldsAndSkipPlainFields() + { + LoanPricingWorkflow encrypted = new LoanPricingWorkflow(); + encrypted.setId(1L); + encrypted.setCustName("cipher-name"); + encrypted.setIdNum("cipher-id"); + LoanPricingWorkflow plain = new LoanPricingWorkflow(); + plain.setId(2L); + plain.setCustName("张三"); + plain.setIdNum(""); + + when(loanPricingWorkflowMapper.selectList(any())).thenReturn(List.of(encrypted, plain)); + when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三"); + when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234"); + when(sensitiveFieldCryptoService.decrypt("张三")).thenThrow(new ServiceException("贷款定价敏感字段解密失败")); + + Map result = loanPricingWorkflowService.decryptWorkflowSensitiveFields(); + + assertEquals(2L, result.get("processed")); + assertEquals(1L, result.get("updated")); + assertEquals(1L, result.get("skipped")); + assertEquals(0L, result.get("failed")); + verify(loanPricingWorkflowMapper).updateSensitiveFieldsById(1L, "张三", "110101199001011234"); + verify(loanPricingWorkflowMapper, never()).updateById(any(LoanPricingWorkflow.class)); + } + + @Test + void shouldUpdateOnlySuccessfullyDecryptedWorkflowField() + { + LoanPricingWorkflow workflow = new LoanPricingWorkflow(); + workflow.setId(3L); + workflow.setCustName("cipher-name"); + workflow.setIdNum("plain-id"); + + when(loanPricingWorkflowMapper.selectList(any())).thenReturn(List.of(workflow)); + when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三"); + when(sensitiveFieldCryptoService.decrypt("plain-id")).thenThrow(new ServiceException("贷款定价敏感字段解密失败")); + + Map result = loanPricingWorkflowService.decryptWorkflowSensitiveFields(); + + assertEquals(1L, result.get("processed")); + assertEquals(1L, result.get("updated")); + assertEquals(0L, result.get("skipped")); + assertEquals(0L, result.get("failed")); + verify(loanPricingWorkflowMapper).updateSensitiveFieldsById(3L, "张三", null); + verify(loanPricingWorkflowMapper, never()).updateById(any(LoanPricingWorkflow.class)); + } + + @Test + void shouldCountDatabaseUpdateExceptionAsMigrationFailure() + { + LoanPricingWorkflow workflow = new LoanPricingWorkflow(); + workflow.setId(4L); + workflow.setCustName("cipher-name"); + + when(loanPricingWorkflowMapper.selectList(any())).thenReturn(List.of(workflow)); + when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三"); + when(loanPricingWorkflowMapper.updateSensitiveFieldsById(4L, "张三", null)).thenThrow(new RuntimeException("db error")); + + Map result = loanPricingWorkflowService.decryptWorkflowSensitiveFields(); + + assertEquals(1L, result.get("processed")); + assertEquals(0L, result.get("updated")); + assertEquals(0L, result.get("skipped")); + assertEquals(1L, result.get("failed")); + } + private LoanPricingWorkflow validWorkflow() { LoanPricingWorkflow workflow = new LoanPricingWorkflow(); @@ -650,9 +714,9 @@ class LoanPricingWorkflowServiceImplTest workflow.setModelOutputId(201L); workflow.setCustIsn("CUST001"); workflow.setCustType(custType); - workflow.setCustName("cipher-name"); + workflow.setCustName("张三"); workflow.setIdType("身份证"); - workflow.setIdNum("cipher-id"); + workflow.setIdNum("110101199001011234"); workflow.setGuarType("信用"); workflow.setApplyAmt("100000"); workflow.setBusinessType("新增"); diff --git a/ruoyi-ui/src/views/loanPricing/workflow/index.vue b/ruoyi-ui/src/views/loanPricing/workflow/index.vue index 4b28928..34203da 100644 --- a/ruoyi-ui/src/views/loanPricing/workflow/index.vue +++ b/ruoyi-ui/src/views/loanPricing/workflow/index.vue @@ -1,10 +1,10 @@