Add anonymous migration for loan pricing sensitive fields

This commit is contained in:
wkc
2026-06-22 09:21:15 +08:00
parent 0164b5eb33
commit 83e1959b94
11 changed files with 256 additions and 105 deletions

View File

@@ -2,6 +2,7 @@ package com.ruoyi.loanpricing.controller;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
@@ -139,6 +140,17 @@ public class LoanPricingWorkflowController extends BaseController
return success(loanRateHistoryService.query(custIsn)); return success(loanRateHistoryService.query(custIsn));
} }
/**
* 解密历史流程客户名称和证件号码密文字段
*/
@Anonymous
@Operation(summary = "解密历史流程客户敏感字段")
@PostMapping("/anonymous/decrypt-sensitive-fields")
public AjaxResult decryptSensitiveFields()
{
return success(loanPricingWorkflowService.decryptWorkflowSensitiveFields());
}
/** /**
* 查询利率定价流程列表 * 查询利率定价流程列表
*/ */

View File

@@ -17,4 +17,8 @@ public interface LoanPricingWorkflowMapper extends BaseMapper<LoanPricingWorkflo
{ {
IPage<LoanPricingWorkflowListVO> selectWorkflowPageWithRates(Page<?> page, IPage<LoanPricingWorkflowListVO> selectWorkflowPageWithRates(Page<?> page,
@Param("query") LoanPricingWorkflow query); @Param("query") LoanPricingWorkflow query);
int updateSensitiveFieldsById(@Param("id") Long id,
@Param("custName") String custName,
@Param("idNum") String idNum);
} }

View File

@@ -9,6 +9,7 @@ import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowListVO;
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowVO; import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowVO;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 利率定价流程Service接口 * 利率定价流程Service接口
@@ -93,4 +94,11 @@ public interface ILoanPricingWorkflowService
* @return 是否成功 * @return 是否成功
*/ */
public boolean setExecuteRate(String serialNum, String executeRate); public boolean setExecuteRate(String serialNum, String executeRate);
/**
* 解密历史流程客户名称和证件号码密文字段
*
* @return 迁移统计
*/
public Map<String, Long> decryptWorkflowSensitiveFields();
} }

View File

@@ -36,9 +36,6 @@ public class LoanPricingModelService {
@Resource @Resource
private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper; private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper;
@Resource
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
public void invokeModelAsync(Long workflowId) { public void invokeModelAsync(Long workflowId) {
invokeModelAndSave(workflowId, false); invokeModelAndSave(workflowId, false);
} }
@@ -53,16 +50,6 @@ public class LoanPricingModelService {
log.error("未找到对应的流程信息,未调用模型服务"); log.error("未找到对应的流程信息,未调用模型服务");
return; 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(); ModelInvokeDTO modelInvokeDTO = new ModelInvokeDTO();
BeanUtils.copyProperties(loanPricingWorkflow, modelInvokeDTO); BeanUtils.copyProperties(loanPricingWorkflow, modelInvokeDTO);
if ("个人".equals(loanPricingWorkflow.getCustType())) if ("个人".equals(loanPricingWorkflow.getCustType()))

View File

@@ -31,7 +31,9 @@ import org.springframework.util.StringUtils;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
/** /**
@@ -94,8 +96,6 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
} }
loanPricingWorkflow.setDeptId(SecurityUtils.getDeptId()); loanPricingWorkflow.setDeptId(SecurityUtils.getDeptId());
loanPricingWorkflow.setCustName(sensitiveFieldCryptoService.encrypt(loanPricingWorkflow.getCustName()));
loanPricingWorkflow.setIdNum(sensitiveFieldCryptoService.encrypt(loanPricingWorkflow.getIdNum()));
loanPricingWorkflowMapper.insert(loanPricingWorkflow); loanPricingWorkflowMapper.insert(loanPricingWorkflow);
loanPricingModelService.invokeModelAsync(loanPricingWorkflow.getId()); loanPricingModelService.invokeModelAsync(loanPricingWorkflow.getId());
@@ -199,8 +199,6 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
{ {
LoanPricingWorkflow workflow = selectWorkflowBySerialNum(serialNum); LoanPricingWorkflow workflow = selectWorkflowBySerialNum(serialNum);
assertCurrentUserIsCreator(workflow); assertCurrentUserIsCreator(workflow);
workflow.setCustName(sensitiveFieldCryptoService.decrypt(workflow.getCustName()));
workflow.setIdNum(sensitiveFieldCryptoService.decrypt(workflow.getIdNum()));
return workflow; return workflow;
} }
@@ -236,14 +234,12 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
validateBusinessTypeAndHistoryRate(editingWorkflow); validateBusinessTypeAndHistoryRate(editingWorkflow);
validateCollateralTypeAndCouponRate(editingWorkflow); validateCollateralTypeAndCouponRate(editingWorkflow);
String encryptedCustName = sensitiveFieldCryptoService.encrypt(editingWorkflow.getCustName());
String encryptedIdNum = sensitiveFieldCryptoService.encrypt(editingWorkflow.getIdNum());
LambdaUpdateWrapper<LoanPricingWorkflow> updateWrapper = new LambdaUpdateWrapper<>(); LambdaUpdateWrapper<LoanPricingWorkflow> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(LoanPricingWorkflow::getId, existingWorkflow.getId()) updateWrapper.eq(LoanPricingWorkflow::getId, existingWorkflow.getId())
.set(LoanPricingWorkflow::getCustIsn, editingWorkflow.getCustIsn()) .set(LoanPricingWorkflow::getCustIsn, editingWorkflow.getCustIsn())
.set(LoanPricingWorkflow::getCustName, encryptedCustName) .set(LoanPricingWorkflow::getCustName, editingWorkflow.getCustName())
.set(LoanPricingWorkflow::getIdType, editingWorkflow.getIdType()) .set(LoanPricingWorkflow::getIdType, editingWorkflow.getIdType())
.set(LoanPricingWorkflow::getIdNum, encryptedIdNum) .set(LoanPricingWorkflow::getIdNum, editingWorkflow.getIdNum())
.set(LoanPricingWorkflow::getGuarType, editingWorkflow.getGuarType()) .set(LoanPricingWorkflow::getGuarType, editingWorkflow.getGuarType())
.set(LoanPricingWorkflow::getApplyAmt, editingWorkflow.getApplyAmt()) .set(LoanPricingWorkflow::getApplyAmt, editingWorkflow.getApplyAmt())
.set(LoanPricingWorkflow::getBusinessType, editingWorkflow.getBusinessType()) .set(LoanPricingWorkflow::getBusinessType, editingWorkflow.getBusinessType())
@@ -312,7 +308,6 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
{ {
LoanPricingWorkflow scopedQuery = applyWorkflowListDataScope(loanPricingWorkflow); LoanPricingWorkflow scopedQuery = applyWorkflowListDataScope(loanPricingWorkflow);
IPage<LoanPricingWorkflowListVO> pageResult = loanPricingWorkflowMapper.selectWorkflowPageWithRates(page, scopedQuery); IPage<LoanPricingWorkflowListVO> pageResult = loanPricingWorkflowMapper.selectWorkflowPageWithRates(page, scopedQuery);
pageResult.getRecords().forEach(row -> row.setCustName(sensitiveFieldCryptoService.decrypt(row.getCustName())));
return pageResult; return pageResult;
} }
@@ -330,10 +325,6 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
LambdaQueryWrapper<LoanPricingWorkflow> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<LoanPricingWorkflow> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(LoanPricingWorkflow::getSerialNum, serialNum); wrapper.eq(LoanPricingWorkflow::getSerialNum, serialNum);
LoanPricingWorkflow loanPricingWorkflow = loanPricingWorkflowMapper.selectOne(wrapper); 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); loanPricingWorkflowVO.setLoanPricingWorkflow(loanPricingWorkflow);
if (Objects.nonNull(loanPricingWorkflow.getModelOutputId())){ if (Objects.nonNull(loanPricingWorkflow.getModelOutputId())){
@@ -468,4 +459,80 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
int result = loanPricingWorkflowMapper.updateById(workflow); int result = loanPricingWorkflowMapper.updateById(workflow);
return result > 0; return result > 0;
} }
@Override
public Map<String, Long> decryptWorkflowSensitiveFields()
{
long processed = 0L;
long updated = 0L;
long skipped = 0L;
long failed = 0L;
LambdaQueryWrapper<LoanPricingWorkflow> wrapper = new LambdaQueryWrapper<>();
wrapper.and(q -> q.isNotNull(LoanPricingWorkflow::getCustName)
.or()
.isNotNull(LoanPricingWorkflow::getIdNum));
List<LoanPricingWorkflow> 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<String, Long> 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;
}
}
} }

View File

@@ -40,7 +40,11 @@
AND SUBSTRING_INDEX(lpw.create_by, '-', -1) LIKE CONCAT('%', #{query.createBy}, '%') AND SUBSTRING_INDEX(lpw.create_by, '-', -1) LIKE CONCAT('%', #{query.createBy}, '%')
</if> </if>
<if test="query != null and query.custIsn != null and query.custIsn != ''"> <if test="query != null and query.custIsn != null and query.custIsn != ''">
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}, '%')
)
</if> </if>
<if test="query != null and query.deptId != null"> <if test="query != null and query.deptId != null">
AND lpw.dept_id = #{query.deptId} AND lpw.dept_id = #{query.deptId}
@@ -49,4 +53,17 @@
ORDER BY lpw.update_time DESC ORDER BY lpw.update_time DESC
</select> </select>
<update id="updateSensitiveFieldsById">
UPDATE loan_pricing_workflow
<set>
<if test="custName != null">
cust_name = #{custName},
</if>
<if test="idNum != null">
id_num = #{idNum},
</if>
</set>
WHERE id = #{id}
</update>
</mapper> </mapper>

View File

@@ -1,6 +1,7 @@
package com.ruoyi.loanpricing.mapper; package com.ruoyi.loanpricing.mapper;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -23,5 +24,23 @@ class LoanPricingWorkflowMapperXmlTest
assertTrue(xml.contains("find_in_set(#{query.dataScopeDeptId}, ancestors)")); assertTrue(xml.contains("find_in_set(#{query.dataScopeDeptId}, ancestors)"));
assertTrue(xml.contains("lpw.dept_id = #{query.deptId}")); assertTrue(xml.contains("lpw.dept_id = #{query.deptId}"));
assertTrue(xml.contains("SUBSTRING_INDEX(lpw.create_by, '-', -1) LIKE CONCAT('%', #{query.createBy}, '%')")); 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("<update id=\"updateSensitiveFieldsById\">"),
xml.indexOf("</update>", xml.indexOf("<update id=\"updateSensitiveFieldsById\">")));
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"));
} }
} }

View File

@@ -40,9 +40,6 @@ class LoanPricingModelServicePersonalParamsTest {
@Mock @Mock
private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper; private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper;
@Mock
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
@InjectMocks @InjectMocks
private LoanPricingModelService loanPricingModelService; private LoanPricingModelService loanPricingModelService;
@@ -87,9 +84,9 @@ class LoanPricingModelServicePersonalParamsTest {
workflow.setRunType("1"); workflow.setRunType("1");
workflow.setCustIsn("CUST001"); workflow.setCustIsn("CUST001");
workflow.setCustType("个人"); workflow.setCustType("个人");
workflow.setCustName("cipher-name"); workflow.setCustName("张三");
workflow.setIdType("身份证"); workflow.setIdType("身份证");
workflow.setIdNum("cipher-id"); workflow.setIdNum("110101199001011234");
workflow.setGuarType("信用"); workflow.setGuarType("信用");
workflow.setApplyAmt("100000"); workflow.setApplyAmt("100000");
workflow.setLoanTerm("3"); workflow.setLoanTerm("3");
@@ -101,8 +98,6 @@ class LoanPricingModelServicePersonalParamsTest {
response.setCalculateRate("6.15"); response.setCalculateRate("6.15");
when(loanPricingWorkflowMapper.selectById(1L)).thenReturn(workflow); 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); when(modelService.invokePersonalModel(any())).thenReturn(response);
loanPricingModelService.invokeModelAsync(1L); loanPricingModelService.invokeModelAsync(1L);

View File

@@ -1,7 +1,6 @@
package com.ruoyi.loanpricing.service; package com.ruoyi.loanpricing.service;
import static org.junit.jupiter.api.Assertions.assertEquals; 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.dto.ModelInvokeDTO;
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow; import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
@@ -21,7 +20,7 @@ import org.junit.jupiter.api.Test;
class LoanPricingModelServiceTest class LoanPricingModelServiceTest
{ {
@Test @Test
void shouldDecryptCustNameAndIdNumBeforeInvokePersonalModel() throws Exception void shouldPassPlainCustNameAndIdNumBeforeInvokePersonalModel() throws Exception
{ {
TestContext context = createContext(personalWorkflow(1L)); TestContext context = createContext(personalWorkflow(1L));
@@ -37,15 +36,16 @@ class LoanPricingModelServiceTest
} }
@Test @Test
void shouldNotWritePlainCustNameAndIdNumBackWhenUpdatingWorkflow() throws Exception void shouldOnlyWriteModelOutputIdBackWhenUpdatingWorkflow() throws Exception
{ {
TestContext context = createContext(personalWorkflow(2L)); TestContext context = createContext(personalWorkflow(2L));
context.service.invokeModelAsync(2L); context.service.invokeModelAsync(2L);
LoanPricingWorkflow updatedWorkflow = context.updatedWorkflow.get(); LoanPricingWorkflow updatedWorkflow = context.updatedWorkflow.get();
assertNotEquals("张三", updatedWorkflow.getCustName()); assertEquals(2L, updatedWorkflow.getId());
assertNotEquals("110101199001011234", updatedWorkflow.getIdNum()); assertEquals(null, updatedWorkflow.getCustName());
assertEquals(null, updatedWorkflow.getIdNum());
} }
@Test @Test
@@ -54,8 +54,8 @@ class LoanPricingModelServiceTest
LoanPricingWorkflow workflow = new LoanPricingWorkflow(); LoanPricingWorkflow workflow = new LoanPricingWorkflow();
workflow.setId(3L); workflow.setId(3L);
workflow.setCustType("企业"); workflow.setCustType("企业");
workflow.setCustName("cipher-name"); workflow.setCustName("测试科技有限公司");
workflow.setIdNum("cipher-id"); workflow.setIdNum("91110000100000000X");
workflow.setRepayMethod("分期"); workflow.setRepayMethod("分期");
workflow.setResCover("true"); workflow.setResCover("true");
workflow.setIsGreenLoan("true"); workflow.setIsGreenLoan("true");
@@ -86,8 +86,8 @@ class LoanPricingModelServiceTest
workflow.setId(4L); workflow.setId(4L);
workflow.setModelOutputId(88L); workflow.setModelOutputId(88L);
workflow.setCustType("企业"); workflow.setCustType("企业");
workflow.setCustName("cipher-name"); workflow.setCustName("测试科技有限公司");
workflow.setIdNum("cipher-id"); workflow.setIdNum("91110000100000000X");
TestContext context = createContext(workflow); TestContext context = createContext(workflow);
context.service.reinvokeModelAndOverwrite(4L); context.service.reinvokeModelAndOverwrite(4L);
@@ -102,8 +102,8 @@ class LoanPricingModelServiceTest
LoanPricingWorkflow workflow = new LoanPricingWorkflow(); LoanPricingWorkflow workflow = new LoanPricingWorkflow();
workflow.setId(id); workflow.setId(id);
workflow.setCustType("个人"); workflow.setCustType("个人");
workflow.setCustName("cipher-name"); workflow.setCustName("张三");
workflow.setIdNum("cipher-id"); workflow.setIdNum("110101199001011234");
workflow.setCouponRate("2.15"); workflow.setCouponRate("2.15");
return workflow; return workflow;
} }
@@ -127,7 +127,6 @@ class LoanPricingModelServiceTest
savingMapper(ModelRetailOutputFieldsMapper.class, context.retailInsertCount, context.retailUpdateCount)); savingMapper(ModelRetailOutputFieldsMapper.class, context.retailInsertCount, context.retailUpdateCount));
setField(context.service, "modelCorpOutputFieldsMapper", setField(context.service, "modelCorpOutputFieldsMapper",
savingMapper(ModelCorpOutputFieldsMapper.class, context.corpInsertCount, context.corpUpdateCount)); savingMapper(ModelCorpOutputFieldsMapper.class, context.corpInsertCount, context.corpUpdateCount));
setField(context.service, "sensitiveFieldCryptoService", new TestSensitiveFieldCryptoService());
return context; 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;
}
}
} }

View File

@@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -45,6 +46,8 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@@ -81,7 +84,7 @@ class LoanPricingWorkflowServiceImplTest
} }
@Test @Test
void shouldEncryptCustNameAndIdNumBeforeInsert() void shouldKeepCustNameAndIdNumPlainBeforeInsert()
{ {
LoanPricingWorkflow workflow = new LoanPricingWorkflow(); LoanPricingWorkflow workflow = new LoanPricingWorkflow();
workflow.setCustName("张三"); workflow.setCustName("张三");
@@ -89,16 +92,14 @@ class LoanPricingWorkflowServiceImplTest
workflow.setCustIsn("CUST001"); workflow.setCustIsn("CUST001");
workflow.setBusinessType("新增"); workflow.setBusinessType("新增");
when(sensitiveFieldCryptoService.encrypt("张三")).thenReturn("cipher-name");
when(sensitiveFieldCryptoService.encrypt("110101199001011234")).thenReturn("cipher-id");
loanPricingWorkflowService.createLoanPricing(workflow); loanPricingWorkflowService.createLoanPricing(workflow);
verify(loanPricingWorkflowMapper).insert(argThat((LoanPricingWorkflow entity) -> verify(loanPricingWorkflowMapper).insert(argThat((LoanPricingWorkflow entity) ->
Objects.equals("cipher-name", entity.getCustName()) Objects.equals("张三", entity.getCustName())
&& Objects.equals("cipher-id", entity.getIdNum()) && Objects.equals("110101199001011234", entity.getIdNum())
&& Objects.equals(100L, entity.getDeptId()) && Objects.equals(100L, entity.getDeptId())
&& Objects.equals("CUST001", entity.getCustIsn()))); && Objects.equals("CUST001", entity.getCustIsn())));
verify(sensitiveFieldCryptoService, never()).encrypt(any());
} }
@Test @Test
@@ -121,17 +122,17 @@ class LoanPricingWorkflowServiceImplTest
void shouldReturnPlainCustNameWhenReturningPagedWorkflowList() void shouldReturnPlainCustNameWhenReturningPagedWorkflowList()
{ {
LoanPricingWorkflowListVO row = new LoanPricingWorkflowListVO(); LoanPricingWorkflowListVO row = new LoanPricingWorkflowListVO();
row.setCustName("cipher-name"); row.setCustName("张三");
Page<LoanPricingWorkflowListVO> pageResult = new Page<>(1, 10); Page<LoanPricingWorkflowListVO> pageResult = new Page<>(1, 10);
pageResult.setRecords(Collections.singletonList(row)); pageResult.setRecords(Collections.singletonList(row));
when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(pageResult); when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(pageResult);
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
IPage<LoanPricingWorkflowListVO> result = loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), new LoanPricingWorkflow()); IPage<LoanPricingWorkflowListVO> result = loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), new LoanPricingWorkflow());
assertEquals("张三", result.getRecords().get(0).getCustName()); assertEquals("张三", result.getRecords().get(0).getCustName());
verify(sensitiveFieldCryptoService, never()).decrypt(any());
} }
@Test @Test
@@ -229,7 +230,7 @@ class LoanPricingWorkflowServiceImplTest
} }
@Test @Test
void shouldUseCustIsnInsteadOfCustNameAsQueryCondition() void shouldUseCustIsnAsQueryConditionInLegacyList()
{ {
LoanPricingWorkflow query = new LoanPricingWorkflow(); LoanPricingWorkflow query = new LoanPricingWorkflow();
query.setCustIsn("CUST001"); query.setCustIsn("CUST001");
@@ -384,17 +385,16 @@ class LoanPricingWorkflowServiceImplTest
void shouldReturnEditableWorkflowWithPlainSensitiveFieldsForCreator() void shouldReturnEditableWorkflowWithPlainSensitiveFieldsForCreator()
{ {
LoanPricingWorkflow workflow = editableWorkflow("个人", "若依-admin"); LoanPricingWorkflow workflow = editableWorkflow("个人", "若依-admin");
workflow.setCustName("cipher-name"); workflow.setCustName("张三");
workflow.setIdNum("cipher-id"); workflow.setIdNum("110101199001011234");
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow); when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
LoanPricingWorkflow result = loanPricingWorkflowService.selectEditableLoanPricingBySerialNum("P20260525001"); LoanPricingWorkflow result = loanPricingWorkflowService.selectEditableLoanPricingBySerialNum("P20260525001");
assertEquals("张三", result.getCustName()); assertEquals("张三", result.getCustName());
assertEquals("110101199001011234", result.getIdNum()); assertEquals("110101199001011234", result.getIdNum());
verify(sensitiveFieldCryptoService, never()).decrypt(any());
} }
@Test @Test
@@ -414,10 +414,6 @@ class LoanPricingWorkflowServiceImplTest
{ {
LoanPricingWorkflow workflow = editableWorkflow("个人", "若依-admin"); LoanPricingWorkflow workflow = editableWorkflow("个人", "若依-admin");
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow); 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(); PersonalLoanPricingCreateDTO dto = new PersonalLoanPricingCreateDTO();
dto.setCustIsn("CUST001"); dto.setCustIsn("CUST001");
@@ -434,7 +430,13 @@ class LoanPricingWorkflowServiceImplTest
ArgumentCaptor<LambdaUpdateWrapper> updateWrapperCaptor = ArgumentCaptor.forClass(LambdaUpdateWrapper.class); ArgumentCaptor<LambdaUpdateWrapper> updateWrapperCaptor = ArgumentCaptor.forClass(LambdaUpdateWrapper.class);
verify(loanPricingWorkflowMapper).update(any(), updateWrapperCaptor.capture()); 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); verify(loanPricingModelService).reinvokeModelAndOverwrite(101L);
} }
@@ -443,10 +445,6 @@ class LoanPricingWorkflowServiceImplTest
{ {
LoanPricingWorkflow workflow = editableWorkflow("企业", "若依-admin"); LoanPricingWorkflow workflow = editableWorkflow("企业", "若依-admin");
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow); 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(); CorporateLoanPricingCreateDTO dto = new CorporateLoanPricingCreateDTO();
dto.setCustIsn("CORP001"); dto.setCustIsn("CORP001");
@@ -473,6 +471,9 @@ class LoanPricingWorkflowServiceImplTest
assertTrue(sqlSet.contains("is_green_loan"), sqlSet); assertTrue(sqlSet.contains("is_green_loan"), sqlSet);
assertTrue(sqlSet.contains("is_trade_construction"), sqlSet); assertTrue(sqlSet.contains("is_trade_construction"), sqlSet);
assertTrue(sqlSet.contains("coll_third_party"), 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); verify(loanPricingModelService).reinvokeModelAndOverwrite(101L);
} }
@@ -543,17 +544,16 @@ class LoanPricingWorkflowServiceImplTest
LoanPricingWorkflow workflow = new LoanPricingWorkflow(); LoanPricingWorkflow workflow = new LoanPricingWorkflow();
workflow.setSerialNum("P20260328001"); workflow.setSerialNum("P20260328001");
workflow.setCustType("个人"); workflow.setCustType("个人");
workflow.setCustName("cipher-name"); workflow.setCustName("张三");
workflow.setIdNum("cipher-id"); workflow.setIdNum("110101199001011234");
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow); when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001"); LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001");
assertEquals("张三", result.getLoanPricingWorkflow().getCustName()); assertEquals("张三", result.getLoanPricingWorkflow().getCustName());
assertEquals("110101199001011234", result.getLoanPricingWorkflow().getIdNum()); assertEquals("110101199001011234", result.getLoanPricingWorkflow().getIdNum());
verify(sensitiveFieldCryptoService, never()).decrypt(any());
} }
@Test @Test
@@ -562,8 +562,8 @@ class LoanPricingWorkflowServiceImplTest
LoanPricingWorkflow workflow = new LoanPricingWorkflow(); LoanPricingWorkflow workflow = new LoanPricingWorkflow();
workflow.setSerialNum("P20260328001"); workflow.setSerialNum("P20260328001");
workflow.setCustType("个人"); workflow.setCustType("个人");
workflow.setCustName("cipher-name"); workflow.setCustName("张三");
workflow.setIdNum("cipher-id"); workflow.setIdNum("110101199001011234");
workflow.setModelOutputId(11L); workflow.setModelOutputId(11L);
ModelRetailOutputFields retailOutputFields = new ModelRetailOutputFields(); ModelRetailOutputFields retailOutputFields = new ModelRetailOutputFields();
@@ -573,8 +573,6 @@ class LoanPricingWorkflowServiceImplTest
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow); when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
when(modelRetailOutputFieldsMapper.selectById(11L)).thenReturn(retailOutputFields); when(modelRetailOutputFieldsMapper.selectById(11L)).thenReturn(retailOutputFields);
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001"); LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001");
@@ -611,8 +609,8 @@ class LoanPricingWorkflowServiceImplTest
LoanPricingWorkflow workflow = new LoanPricingWorkflow(); LoanPricingWorkflow workflow = new LoanPricingWorkflow();
workflow.setSerialNum("C20260328001"); workflow.setSerialNum("C20260328001");
workflow.setCustType("企业"); workflow.setCustType("企业");
workflow.setCustName("cipher-name"); workflow.setCustName("测试科技有限公司");
workflow.setIdNum("cipher-id"); workflow.setIdNum("91110000100000000X");
workflow.setModelOutputId(22L); workflow.setModelOutputId(22L);
ModelCorpOutputFields corpOutputFields = new ModelCorpOutputFields(); ModelCorpOutputFields corpOutputFields = new ModelCorpOutputFields();
@@ -622,8 +620,6 @@ class LoanPricingWorkflowServiceImplTest
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow); when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
when(modelCorpOutputFieldsMapper.selectById(22L)).thenReturn(corpOutputFields); when(modelCorpOutputFieldsMapper.selectById(22L)).thenReturn(corpOutputFields);
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("测试科技有限公司");
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("91110000100000000X");
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("C20260328001"); LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("C20260328001");
@@ -631,6 +627,74 @@ class LoanPricingWorkflowServiceImplTest
assertEquals("91110000100000000X", result.getModelCorpOutputFields().getIdNum()); 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<String, Long> 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<String, Long> 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<String, Long> 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() private LoanPricingWorkflow validWorkflow()
{ {
LoanPricingWorkflow workflow = new LoanPricingWorkflow(); LoanPricingWorkflow workflow = new LoanPricingWorkflow();
@@ -650,9 +714,9 @@ class LoanPricingWorkflowServiceImplTest
workflow.setModelOutputId(201L); workflow.setModelOutputId(201L);
workflow.setCustIsn("CUST001"); workflow.setCustIsn("CUST001");
workflow.setCustType(custType); workflow.setCustType(custType);
workflow.setCustName("cipher-name"); workflow.setCustName("张三");
workflow.setIdType("身份证"); workflow.setIdType("身份证");
workflow.setIdNum("cipher-id"); workflow.setIdNum("110101199001011234");
workflow.setGuarType("信用"); workflow.setGuarType("信用");
workflow.setApplyAmt("100000"); workflow.setApplyAmt("100000");
workflow.setBusinessType("新增"); workflow.setBusinessType("新增");

View File

@@ -1,10 +1,10 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px"> <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="客户内码" prop="custIsn"> <el-form-item label="客户信息" prop="custIsn">
<el-input <el-input
v-model="queryParams.custIsn" v-model="queryParams.custIsn"
placeholder="请输入客户内码" placeholder="请输入客户内码/证件号/客户名"
clearable clearable
@keyup.enter.native="handleQuery" @keyup.enter.native="handleQuery"
/> />