迁移892-without-redis分支全量功能
This commit is contained in:
52
ruoyi-loan-pricing/pom.xml
Normal file
52
ruoyi-loan-pricing/pom.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<version>3.9.2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<artifactId>ruoyi-loan-pricing</artifactId>
|
||||
|
||||
<description>
|
||||
利率定价模块
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- 通用工具-->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-framework</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-system</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.36</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,100 @@
|
||||
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.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.PageDomain;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.core.page.TableSupport;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.loanpricing.domain.dto.CorporateLoanPricingCreateDTO;
|
||||
import com.ruoyi.loanpricing.domain.dto.PersonalLoanPricingCreateDTO;
|
||||
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
|
||||
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowListVO;
|
||||
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowVO;
|
||||
import com.ruoyi.loanpricing.service.ILoanPricingWorkflowService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 利率定价流程Controller
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-19
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/loanPricing/workflow")
|
||||
public class LoanPricingWorkflowController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private ILoanPricingWorkflowService loanPricingWorkflowService;
|
||||
|
||||
/**
|
||||
* 发起个人客户利率定价流程
|
||||
*/
|
||||
@Log(title = "个人客户利率定价流程", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/create/personal")
|
||||
public AjaxResult createPersonal(@Validated @RequestBody PersonalLoanPricingCreateDTO dto) {
|
||||
LoanPricingWorkflow result = loanPricingWorkflowService.createPersonalLoanPricing(dto);
|
||||
return success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起企业客户利率定价流程
|
||||
*/
|
||||
@Log(title = "企业客户利率定价流程", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/create/corporate")
|
||||
public AjaxResult createCorporate(@Validated @RequestBody CorporateLoanPricingCreateDTO dto)
|
||||
{
|
||||
LoanPricingWorkflow result = loanPricingWorkflowService.createCorporateLoanPricing(dto);
|
||||
return success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询利率定价流程列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(LoanPricingWorkflow loanPricingWorkflow)
|
||||
{
|
||||
PageDomain pageDomain = TableSupport.buildPageRequest();
|
||||
Page<LoanPricingWorkflowListVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
|
||||
IPage<LoanPricingWorkflowListVO> result = loanPricingWorkflowService.selectLoanPricingPage(page, loanPricingWorkflow);
|
||||
TableDataInfo rspData = new TableDataInfo();
|
||||
rspData.setCode(200);
|
||||
rspData.setMsg("查询成功");
|
||||
rspData.setRows(result.getRecords());
|
||||
rspData.setTotal(result.getTotal());
|
||||
return rspData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询利率定价流程详情
|
||||
*/
|
||||
@GetMapping("/{serialNum}")
|
||||
public AjaxResult getInfo(@PathVariable("serialNum") String serialNum)
|
||||
{
|
||||
LoanPricingWorkflowVO workflow = loanPricingWorkflowService.selectLoanPricingBySerialNum(serialNum);
|
||||
if (workflow == null)
|
||||
{
|
||||
return error("记录不存在");
|
||||
}
|
||||
return success(workflow);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设定执行利率
|
||||
*/
|
||||
@Log(title = "利率定价流程", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/{serialNum}/executeRate")
|
||||
public AjaxResult setExecuteRate(@PathVariable("serialNum") String serialNum,
|
||||
@RequestBody Map<String, String> request) {
|
||||
String executeRate = request.get("executeRate");
|
||||
boolean success = loanPricingWorkflowService.setExecuteRate(serialNum, executeRate);
|
||||
return success ? success() : error("设定失败");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.ruoyi.loanpricing.controller;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.loanpricing.domain.dto.ModelInvokeDTO;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
|
||||
/**
|
||||
* @Author 吴凯程
|
||||
* @Date 2025/11/10
|
||||
**/
|
||||
@RestController
|
||||
@RequestMapping("/rate/pricing/mock")
|
||||
public class LoanRatePricingMockController extends BaseController {
|
||||
|
||||
@Anonymous
|
||||
@PostMapping("/invokeModel")
|
||||
public AjaxResult invokeModel( ModelInvokeDTO modelInvokeDTO) {
|
||||
ObjectNode jsonNodes;
|
||||
if (modelInvokeDTO.getCustType().equals("个人")) {
|
||||
jsonNodes = loadJsonFromResource("data/retail_output.json");
|
||||
} else {
|
||||
jsonNodes = loadJsonFromResource("data/corp_output.json");
|
||||
}
|
||||
|
||||
return new AjaxResult(10000, "success", jsonNodes);
|
||||
}
|
||||
|
||||
private ObjectNode loadJsonFromResource(String resourcePath){
|
||||
ClassPathResource classPathResource = new ClassPathResource(resourcePath);
|
||||
try (InputStream inputStream = classPathResource.getInputStream();){
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
return objectMapper.readValue(inputStream, ObjectNode.class);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.ruoyi.loanpricing.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
/**
|
||||
* 企业客户利率定价发起DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-19
|
||||
*/
|
||||
@Data
|
||||
public class CorporateLoanPricingCreateDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@NotBlank(message = "客户内码不能为空")
|
||||
private String custIsn;
|
||||
|
||||
private String custName;
|
||||
|
||||
private String idType;
|
||||
|
||||
private String idNum;
|
||||
|
||||
@NotBlank(message = "担保方式不能为空")
|
||||
@Pattern(regexp = "^(信用|保证|抵押|质押)$", message = "担保方式必须是:信用、保证、抵押、质押之一")
|
||||
private String guarType;
|
||||
|
||||
@NotBlank(message = "申请金额不能为空")
|
||||
private String applyAmt;
|
||||
|
||||
private String loanTerm;
|
||||
|
||||
private String isAgriGuar;
|
||||
|
||||
private String isGreenLoan;
|
||||
|
||||
private String isTechEnt;
|
||||
|
||||
private String isTradeConstruction;
|
||||
|
||||
private String collType;
|
||||
|
||||
private String collThirdParty;
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package com.ruoyi.loanpricing.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author 吴凯程
|
||||
* @Date 2025/12/23
|
||||
**/
|
||||
@Data
|
||||
public class ModelInvokeDTO {
|
||||
/**
|
||||
* 业务方流水号(必填)
|
||||
* 可使用时间戳,或自定义随机生成
|
||||
*/
|
||||
private String serialNum;
|
||||
|
||||
/**
|
||||
* 机构编码(必填)
|
||||
* 固定值:892000
|
||||
*/
|
||||
private String orgCode;
|
||||
|
||||
/**
|
||||
* 运行模式(必填)
|
||||
* 固定值:1:同步
|
||||
*/
|
||||
private String runType = "1";
|
||||
|
||||
/**
|
||||
* 客户内码(必填)
|
||||
*/
|
||||
private String custIsn;
|
||||
|
||||
/**
|
||||
* 客户类型(必填)
|
||||
* 可选值:个人/企业
|
||||
*/
|
||||
private String custType;
|
||||
|
||||
/**
|
||||
* 担保方式(必填)
|
||||
* 可选值:信用,保证,抵押,质押
|
||||
*/
|
||||
private String guarType;
|
||||
|
||||
/**
|
||||
* 中间业务_个人_快捷支付(非必填)
|
||||
* 可选值:true/false
|
||||
*/
|
||||
private String midPerQuickPay;
|
||||
|
||||
/**
|
||||
* 中间业务_个人_电费代扣(非必填)
|
||||
* 可选值:true/false
|
||||
*/
|
||||
private String midPerEleDdc;
|
||||
|
||||
/**
|
||||
* 中间业务_企业_电费代扣(非必填)
|
||||
* 可选值:true/false
|
||||
*/
|
||||
private String midEntEleDdc;
|
||||
|
||||
/**
|
||||
* 中间业务_企业_水费代扣(非必填)
|
||||
* 可选值:true/false
|
||||
*/
|
||||
private String midEntWaterDdc;
|
||||
|
||||
/**
|
||||
* 申请金额(必填)
|
||||
* 单位:元
|
||||
*/
|
||||
private String applyAmt;
|
||||
|
||||
/**
|
||||
* 贷款期限(必填)
|
||||
* 单位:年
|
||||
*/
|
||||
private String loanTerm;
|
||||
|
||||
/**
|
||||
* 净身企业(非必填)
|
||||
* 可选值:true/false
|
||||
*/
|
||||
private String isCleanEnt;
|
||||
|
||||
/**
|
||||
* 开立基本结算账户(非必填)
|
||||
* 可选值:true/false
|
||||
*/
|
||||
private String hasSettleAcct;
|
||||
|
||||
/**
|
||||
* 制造业企业(非必填)
|
||||
* 可选值:true/false
|
||||
*/
|
||||
private String isManufacturing;
|
||||
|
||||
/**
|
||||
* 省农担担保贷款(非必填)
|
||||
* 可选值:true/false
|
||||
*/
|
||||
private String isAgriGuar;
|
||||
|
||||
/**
|
||||
* 是否纳税信用等级A级(非必填)
|
||||
* 可选值:true/false
|
||||
*/
|
||||
private String isTaxA;
|
||||
|
||||
/**
|
||||
* 是否县级及以上农业龙头企业(非必填)
|
||||
* 可选值:true/false
|
||||
*/
|
||||
private String isAgriLeading;
|
||||
|
||||
private String isInclusiveFinance;
|
||||
|
||||
/**
|
||||
* 贷款用途(非必填)
|
||||
* 可选值:consumer/business
|
||||
*/
|
||||
private String loanPurpose;
|
||||
|
||||
/**
|
||||
* 是否有经营佐证(非必填)
|
||||
* 可选值:0/1
|
||||
*/
|
||||
private String bizProof;
|
||||
|
||||
/**
|
||||
* 循环功能(非必填)
|
||||
* 可选值:0/1
|
||||
*/
|
||||
private String loanLoop;
|
||||
|
||||
/**
|
||||
* 抵质押类型(非必填)
|
||||
* 可选值:一类/二类/三类
|
||||
*/
|
||||
private String collType;
|
||||
|
||||
/**
|
||||
* 抵质押物是否三方所有(非必填)
|
||||
* 可选值:0/1
|
||||
*/
|
||||
private String collThirdParty;
|
||||
|
||||
// /**
|
||||
// * 贷款利率(必填)
|
||||
// */
|
||||
// private String loanRate;
|
||||
|
||||
/**
|
||||
* 客户名称(非必填)
|
||||
*/
|
||||
private String custName;
|
||||
|
||||
/**
|
||||
* 证件类型(非必填)
|
||||
*/
|
||||
private String idType;
|
||||
|
||||
/**
|
||||
* 证件号码(非必填)
|
||||
*/
|
||||
private String idNum;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.ruoyi.loanpricing.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
/**
|
||||
* 个人客户利率定价发起DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-19
|
||||
*/
|
||||
@Data
|
||||
public class PersonalLoanPricingCreateDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@NotBlank(message = "客户内码不能为空")
|
||||
private String custIsn;
|
||||
|
||||
private String custName;
|
||||
|
||||
private String idType;
|
||||
|
||||
private String idNum;
|
||||
|
||||
@NotBlank(message = "担保方式不能为空")
|
||||
@Pattern(regexp = "^(信用|保证|抵押|质押)$", message = "担保方式必须是:信用、保证、抵押、质押之一")
|
||||
private String guarType;
|
||||
|
||||
@NotBlank(message = "申请金额不能为空")
|
||||
private String applyAmt;
|
||||
|
||||
@NotBlank(message = "贷款用途不能为空")
|
||||
@Pattern(regexp = "^(consumer|business)$", message = "贷款用途必须是:consumer、business之一")
|
||||
private String loanPurpose;
|
||||
|
||||
@NotBlank(message = "借款期限不能为空")
|
||||
private String loanTerm;
|
||||
|
||||
private String bizProof;
|
||||
|
||||
private String loanLoop;
|
||||
|
||||
private String collType;
|
||||
|
||||
private String collThirdParty;
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package com.ruoyi.loanpricing.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 利率定价流程对象 loan_pricing_workflow
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-19
|
||||
*/
|
||||
@Data
|
||||
@TableName("loan_pricing_workflow")
|
||||
public class LoanPricingWorkflow implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 主键ID */
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 模型输出ID */
|
||||
private Long modelOutputId;
|
||||
|
||||
/** 业务方流水号 */
|
||||
private String serialNum;
|
||||
|
||||
/** 机构编码 */
|
||||
private String orgCode;
|
||||
|
||||
/** 运行模式: 1-同步 */
|
||||
private String runType;
|
||||
|
||||
/** 客户内码 */
|
||||
@NotBlank(message = "客户内码不能为空")
|
||||
private String custIsn;
|
||||
|
||||
/** 客户类型: 个人/企业 */
|
||||
@NotBlank(message = "客户类型不能为空")
|
||||
private String custType;
|
||||
|
||||
/** 担保方式: 信用/保证/抵押/质押 */
|
||||
@NotBlank(message = "担保方式不能为空")
|
||||
private String guarType;
|
||||
|
||||
/** 中间业务_个人_快捷支付: true/false */
|
||||
private String midPerQuickPay;
|
||||
|
||||
/** 中间业务_个人_电费代扣: true/false */
|
||||
private String midPerEleDdc;
|
||||
|
||||
/** 中间业务_企业_电费代扣: true/false */
|
||||
private String midEntEleDdc;
|
||||
|
||||
/** 中间业务_企业_水费代扣: true/false */
|
||||
private String midEntWaterDdc;
|
||||
|
||||
/** 申请金额(元) */
|
||||
@NotBlank(message = "申请金额不能为空")
|
||||
private String applyAmt;
|
||||
|
||||
/**
|
||||
* 贷款期限
|
||||
*/
|
||||
private String loanTerm;
|
||||
|
||||
/** 净身企业: true/false */
|
||||
private String isCleanEnt;
|
||||
|
||||
/** 开立基本结算账户: true/false */
|
||||
private String hasSettleAcct;
|
||||
|
||||
/** 制造业企业: true/false */
|
||||
private String isManufacturing;
|
||||
|
||||
/** 省农担担保贷款: true/false */
|
||||
private String isAgriGuar;
|
||||
|
||||
/**
|
||||
* 贸易和建筑业企业标识: true/false
|
||||
*/
|
||||
private String isTradeConstruction;
|
||||
|
||||
/**
|
||||
* 绿色贷款: true/false
|
||||
*/
|
||||
private String isGreenLoan;
|
||||
|
||||
/**
|
||||
* 科技型企业: true/false
|
||||
*/
|
||||
private String isTechEnt;
|
||||
|
||||
/** 是否纳税信用等级A级: true/false */
|
||||
private String isTaxA;
|
||||
|
||||
/** 是否县级及以上农业龙头企业: true/false */
|
||||
private String isAgriLeading;
|
||||
|
||||
/** 贷款用途: consumer-消费/business-经营 */
|
||||
private String loanPurpose;
|
||||
|
||||
/** 是否有经营佐证: true/false */
|
||||
private String bizProof;
|
||||
|
||||
/** 循环功能: true/false */
|
||||
private String loanLoop;
|
||||
|
||||
/** 抵质押类型: 一线/一类/二类 */
|
||||
private String collType;
|
||||
|
||||
/** 抵质押物是否三方所有: true/false */
|
||||
private String collThirdParty;
|
||||
|
||||
/** 贷款利率 */
|
||||
private String loanRate;
|
||||
|
||||
/**
|
||||
* 执行利率(%)
|
||||
*/
|
||||
private String executeRate;
|
||||
|
||||
/** 客户名称 */
|
||||
private String custName;
|
||||
|
||||
/**
|
||||
* 证件类型
|
||||
*/
|
||||
private String idType;
|
||||
|
||||
/** 证件号码 */
|
||||
private String idNum;
|
||||
|
||||
/** 是否普惠小微借款人: true/false */
|
||||
private String isInclusiveFinance;
|
||||
|
||||
/** 创建者 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
/** 创建时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/** 更新者 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
/** 更新时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.ruoyi.loanpricing.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 贷款定价模型输入参数对象
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-21
|
||||
*/
|
||||
@Data
|
||||
public class ModelCorpOutputFields {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
// 客户内码
|
||||
private String custIsn;
|
||||
// 客户类型
|
||||
private String custType;
|
||||
// 担保方式
|
||||
private String guarType;
|
||||
// 客户名称
|
||||
private String custName;
|
||||
// 证件类型
|
||||
private String idType;
|
||||
// 证件号码
|
||||
private String idNum;
|
||||
// 基准利率
|
||||
private String baseLoanRate;
|
||||
// 我行首贷客户
|
||||
private String isFirstLoan;
|
||||
// 用信天数
|
||||
private String faithDay;
|
||||
// BP_首贷
|
||||
private String bpFirstLoan;
|
||||
// BP_贷龄
|
||||
private String bpAgeLoan;
|
||||
// TOTAL_BP_忠诚度
|
||||
private String totalBpLoyalty;
|
||||
// 存款年日均
|
||||
private String balanceAvg;
|
||||
// 贷款年日均
|
||||
private String loanAvg;
|
||||
// 派生率
|
||||
private String derivationRate;
|
||||
// TOTAL_BP_贡献度
|
||||
private String totalBpContribution;
|
||||
// 中间业务_企业_企业互联
|
||||
private String midEntConnect;
|
||||
// 中间业务_企业_有效价值客户
|
||||
private String midEntEffect;
|
||||
// 中间业务_企业_国际业务
|
||||
private String midEntInter;
|
||||
// 中间业务_企业_承兑
|
||||
private String midEntAccept;
|
||||
// 中间业务_企业_贴现
|
||||
private String midEntDiscount;
|
||||
// 中间业务_企业_电费代扣
|
||||
private String midEntEleDdc;
|
||||
// 中间业务_企业_水费代扣
|
||||
private String midEntWaterDdc;
|
||||
// 中间业务_企业_税务代扣
|
||||
private String midEntTax;
|
||||
// BP_中间业务
|
||||
private String bpMid;
|
||||
// 代发工资户数
|
||||
private String payroll;
|
||||
// 存量贷款余额
|
||||
private String invLoanAmount;
|
||||
// BP_代发工资
|
||||
private String bpPayroll;
|
||||
// 净身企业
|
||||
private String isCleanEnt;
|
||||
// 开立基本结算账户
|
||||
private String hasSettleAcct;
|
||||
// 省农担担保贷款
|
||||
private String isAgriGuar;
|
||||
// 绿色贷款
|
||||
private String isGreenLoan;
|
||||
// 科技型企业
|
||||
private String isTechEnt;
|
||||
// BP_企业客户类别
|
||||
private String bpEntType;
|
||||
// TOTAL_BP_关联度
|
||||
private String totoalBpRelevance;
|
||||
// 贷款期限
|
||||
private String loanTerm;
|
||||
// BP_贷款期限
|
||||
private String bpLoanTerm;
|
||||
// 申请金额
|
||||
private String applyAmt;
|
||||
// BP_贷款额度
|
||||
private String bpLoanAmount;
|
||||
// 抵质押类型
|
||||
private String collType;
|
||||
// 抵质押物是否三方所有
|
||||
private String collThirdParty;
|
||||
// BP_抵押物
|
||||
private String bpCollateral;
|
||||
// 灰名单客户
|
||||
private String greyCust;
|
||||
// 本金逾期
|
||||
private String prinOverdue;
|
||||
// 利息逾期
|
||||
private String interestOverdue;
|
||||
// 信用卡逾期
|
||||
private String cardOverdue;
|
||||
// BP_灰名单与逾期
|
||||
private String bpGreyOverdue;
|
||||
// TOTAL_BP_风险度
|
||||
private String totoalBpRisk;
|
||||
// 浮动BP
|
||||
private String totalBp;
|
||||
// 测算利率
|
||||
private String calculateRate;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package com.ruoyi.loanpricing.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 贷款定价模型输入参数对象
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-21
|
||||
*/
|
||||
@Data
|
||||
public class ModelRetailOutputFields {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
// 客户内码
|
||||
private String custIsn;
|
||||
|
||||
// 客户类型
|
||||
private String custType;
|
||||
|
||||
// 担保方式
|
||||
private String guarType;
|
||||
|
||||
// 客户名称
|
||||
private String custName;
|
||||
|
||||
// 证件类型
|
||||
private String idType;
|
||||
|
||||
// 证件号码
|
||||
private String idNum;
|
||||
|
||||
// 基准利率
|
||||
private String baseLoanRate;
|
||||
|
||||
// 我行首贷客户
|
||||
private String isFirstLoan;
|
||||
|
||||
// 用信天数
|
||||
private String faithDay;
|
||||
|
||||
// 客户年龄
|
||||
private String custAge;
|
||||
|
||||
// BP_首贷
|
||||
private String bpFirstLoan;
|
||||
|
||||
// BP_贷龄
|
||||
private String bpAgeLoan;
|
||||
|
||||
// BP_年龄
|
||||
private String bpAge;
|
||||
|
||||
// TOTAL_BP_忠诚度
|
||||
private String totalBpLoyalty;
|
||||
|
||||
// 存款年日均
|
||||
private String balanceAvg;
|
||||
|
||||
// 贷款年日均
|
||||
private String loanAvg;
|
||||
|
||||
// 派生率
|
||||
private String derivationRate;
|
||||
|
||||
// TOTAL_BP_贡献度
|
||||
private String totalBpContribution;
|
||||
|
||||
// 中间业务_个人_信用卡
|
||||
private String midPerCard;
|
||||
|
||||
// 中间业务_个人_一码通
|
||||
private String midPerPass;
|
||||
|
||||
// 中间业务_个人_丰收互联
|
||||
private String midPerHarvest;
|
||||
|
||||
// 中间业务_个人_有效客户
|
||||
private String midPerEffect;
|
||||
|
||||
// 中间业务_个人_快捷支付
|
||||
private String midPerQuickPay;
|
||||
|
||||
// 中间业务_个人_电费代扣
|
||||
private String midPerEleDdc;
|
||||
|
||||
// 中间业务_个人_水费代扣
|
||||
private String midPerWaterDdc;
|
||||
|
||||
// 中间业务_个人_华数费代扣
|
||||
private String midPerHuashuDdc;
|
||||
|
||||
// 中间业务_个人_煤气费代扣
|
||||
private String MidPerGasDdc;
|
||||
|
||||
// 中间业务_个人_市民卡
|
||||
private String midPerCitizencard;
|
||||
|
||||
// 中间业务_个人_理财业务
|
||||
private String midPerFinMan;
|
||||
|
||||
// 中间业务_个人_etc
|
||||
private String midPerEtc;
|
||||
|
||||
// BP_中间业务
|
||||
private String bpMid;
|
||||
|
||||
// TOTAL_BP_关联度(注意原字段名拼写错误:totoalBpRelevance,已保留原拼写)
|
||||
private String totoalBpRelevance;
|
||||
|
||||
// 申请金额
|
||||
private String applyAmt;
|
||||
|
||||
// BP_贷款额度
|
||||
private String bpLoanAmount;
|
||||
|
||||
// 贷款用途
|
||||
private String loanPurpose;
|
||||
|
||||
// 是否有经营佐证
|
||||
private String bizProof;
|
||||
|
||||
// BP_贷款用途
|
||||
private String bpLoanUse;
|
||||
|
||||
// 循环功能
|
||||
private String loanLoop;
|
||||
|
||||
// BP_循环功能
|
||||
private String bpLoanLoop;
|
||||
|
||||
// 抵质押类型
|
||||
private String collType;
|
||||
|
||||
// 抵质押物是否三方所有
|
||||
private String collThirdParty;
|
||||
|
||||
// BP_抵押物
|
||||
private String bpCollateral;
|
||||
|
||||
// 灰名单客户
|
||||
private String greyCust;
|
||||
|
||||
// 本金逾期
|
||||
private String prinOverdue;
|
||||
|
||||
// 利息逾期
|
||||
private String interestOverdue;
|
||||
|
||||
// 信用卡逾期
|
||||
private String cardOverdue;
|
||||
|
||||
// BP_灰名单与逾期
|
||||
private String bpGreyOverdue;
|
||||
|
||||
// TOTAL_BP_风险度(注意原字段名拼写错误:totoalBpRisk,已保留原拼写)
|
||||
private String totoalBpRisk;
|
||||
|
||||
// 浮动BP
|
||||
private String totalBp;
|
||||
|
||||
// 测算利率
|
||||
private String calculateRate;
|
||||
|
||||
// 历史利率
|
||||
private String loanRateHistory;
|
||||
|
||||
// 产品最低利率下限
|
||||
private String minRateProduct;
|
||||
|
||||
// 平滑幅度
|
||||
private String smoothRange;
|
||||
|
||||
// 最终测算利率
|
||||
private String finalCalculateRate;
|
||||
|
||||
// 参考利率
|
||||
private String referenceRate;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.ruoyi.loanpricing.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class LoanPricingWorkflowListVO
|
||||
{
|
||||
private String serialNum;
|
||||
|
||||
private String custName;
|
||||
|
||||
private String custType;
|
||||
|
||||
private String guarType;
|
||||
|
||||
private String applyAmt;
|
||||
|
||||
private String calculateRate;
|
||||
|
||||
private String executeRate;
|
||||
|
||||
private Date createTime;
|
||||
|
||||
private String createBy;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ruoyi.loanpricing.domain.vo;
|
||||
|
||||
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
|
||||
import com.ruoyi.loanpricing.domain.entity.ModelCorpOutputFields;
|
||||
import com.ruoyi.loanpricing.domain.entity.ModelRetailOutputFields;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author: wkc
|
||||
* @CreateTime: 2026-01-21
|
||||
*/
|
||||
@Data
|
||||
public class LoanPricingWorkflowVO {
|
||||
|
||||
private LoanPricingWorkflow loanPricingWorkflow;
|
||||
|
||||
private ModelRetailOutputFields modelRetailOutputFields;
|
||||
|
||||
private ModelCorpOutputFields modelCorpOutputFields;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ruoyi.loanpricing.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
|
||||
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowListVO;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 利率定价流程Mapper接口
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-19
|
||||
*/
|
||||
public interface LoanPricingWorkflowMapper extends BaseMapper<LoanPricingWorkflow>
|
||||
{
|
||||
IPage<LoanPricingWorkflowListVO> selectWorkflowPageWithRates(Page<?> page,
|
||||
@Param("query") LoanPricingWorkflow query);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.ruoyi.loanpricing.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.loanpricing.domain.entity.ModelCorpOutputFields;
|
||||
|
||||
/**
|
||||
* 对公贷款定价模型输出字段Mapper接口
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-21
|
||||
*/
|
||||
public interface ModelCorpOutputFieldsMapper extends BaseMapper<ModelCorpOutputFields>
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.ruoyi.loanpricing.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.loanpricing.domain.entity.ModelRetailOutputFields;
|
||||
|
||||
/**
|
||||
* 零售贷款定价模型输出字段Mapper接口
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-21
|
||||
*/
|
||||
public interface ModelRetailOutputFieldsMapper extends BaseMapper<ModelRetailOutputFields>
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.ruoyi.loanpricing.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.loanpricing.domain.dto.CorporateLoanPricingCreateDTO;
|
||||
import com.ruoyi.loanpricing.domain.dto.PersonalLoanPricingCreateDTO;
|
||||
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
|
||||
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowListVO;
|
||||
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 利率定价流程Service接口
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-19
|
||||
*/
|
||||
public interface ILoanPricingWorkflowService
|
||||
{
|
||||
/**
|
||||
* 发起个人客户利率定价流程
|
||||
*
|
||||
* @param dto 个人客户发起DTO
|
||||
* @return 结果
|
||||
*/
|
||||
public LoanPricingWorkflow createPersonalLoanPricing(PersonalLoanPricingCreateDTO dto);
|
||||
|
||||
/**
|
||||
* 发起企业客户利率定价流程
|
||||
*
|
||||
* @param dto 企业客户发起DTO
|
||||
* @return 结果
|
||||
*/
|
||||
public LoanPricingWorkflow createCorporateLoanPricing(CorporateLoanPricingCreateDTO dto);
|
||||
|
||||
/**
|
||||
* 查询利率定价流程列表
|
||||
*
|
||||
* @param loanPricingWorkflow 利率定价流程信息
|
||||
* @return 利率定价流程集合
|
||||
*/
|
||||
public List<LoanPricingWorkflow> selectLoanPricingList(LoanPricingWorkflow loanPricingWorkflow);
|
||||
|
||||
/**
|
||||
* 分页查询利率定价流程列表
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param loanPricingWorkflow 利率定价流程信息
|
||||
* @return 分页结果
|
||||
*/
|
||||
public IPage<LoanPricingWorkflowListVO> selectLoanPricingPage(Page<LoanPricingWorkflowListVO> page, LoanPricingWorkflow loanPricingWorkflow);
|
||||
|
||||
/**
|
||||
* 查询利率定价流程详情
|
||||
*
|
||||
* @param serialNum 业务方流水号
|
||||
* @return 利率定价流程
|
||||
*/
|
||||
public LoanPricingWorkflowVO selectLoanPricingBySerialNum(String serialNum);
|
||||
|
||||
/**
|
||||
* 设定执行利率
|
||||
*
|
||||
* @param serialNum 业务方流水号
|
||||
* @param executeRate 执行利率
|
||||
* @return 是否成功
|
||||
*/
|
||||
public boolean setExecuteRate(String serialNum, String executeRate);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.ruoyi.loanpricing.service;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.common.utils.bean.BeanUtils;
|
||||
import com.ruoyi.loanpricing.domain.dto.ModelInvokeDTO;
|
||||
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
|
||||
import com.ruoyi.loanpricing.domain.entity.ModelCorpOutputFields;
|
||||
import com.ruoyi.loanpricing.domain.entity.ModelRetailOutputFields;
|
||||
import com.ruoyi.loanpricing.mapper.LoanPricingWorkflowMapper;
|
||||
import com.ruoyi.loanpricing.mapper.ModelCorpOutputFieldsMapper;
|
||||
import com.ruoyi.loanpricing.mapper.ModelRetailOutputFieldsMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* @Author: wkc
|
||||
* @CreateTime: 2026-01-21
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
@EnableAsync
|
||||
public class LoanPricingModelService {
|
||||
|
||||
@Resource
|
||||
private ModelService modelService;
|
||||
|
||||
@Resource
|
||||
private LoanPricingWorkflowMapper loanPricingWorkflowMapper;
|
||||
|
||||
@Resource
|
||||
private ModelRetailOutputFieldsMapper modelRetailOutputFieldsMapper;
|
||||
|
||||
@Resource
|
||||
private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper;
|
||||
|
||||
@Resource
|
||||
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
|
||||
|
||||
public void invokeModelAsync(Long workflowId) {
|
||||
LoanPricingWorkflow loanPricingWorkflow = loanPricingWorkflowMapper.selectById(workflowId);
|
||||
if (Objects.isNull(loanPricingWorkflow)){
|
||||
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()))
|
||||
{
|
||||
normalizePersonalModelInvokeDTO(modelInvokeDTO);
|
||||
}
|
||||
JSONObject response = modelService.invokeModel(modelInvokeDTO);
|
||||
if (loanPricingWorkflow.getCustType().equals("个人")){
|
||||
// 个人模型
|
||||
ModelRetailOutputFields modelRetailOutputFields = JSON.parseObject(response.toJSONString(), ModelRetailOutputFields.class);
|
||||
modelRetailOutputFieldsMapper.insert(modelRetailOutputFields);
|
||||
log.info("个人模型调用成功");
|
||||
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 workflowToUpdate = new LoanPricingWorkflow();
|
||||
workflowToUpdate.setId(loanPricingWorkflow.getId());
|
||||
workflowToUpdate.setModelOutputId(modelCorpOutputFields.getId());
|
||||
loanPricingWorkflowMapper.updateById(workflowToUpdate);
|
||||
log.info("更新流程信息成功");
|
||||
}
|
||||
}
|
||||
|
||||
private void normalizePersonalModelInvokeDTO(ModelInvokeDTO modelInvokeDTO)
|
||||
{
|
||||
modelInvokeDTO.setBizProof(toZeroOne(modelInvokeDTO.getBizProof()));
|
||||
modelInvokeDTO.setLoanLoop(toZeroOne(modelInvokeDTO.getLoanLoop()));
|
||||
modelInvokeDTO.setCollThirdParty(toZeroOne(modelInvokeDTO.getCollThirdParty()));
|
||||
}
|
||||
|
||||
private String toZeroOne(String value)
|
||||
{
|
||||
if ("true".equals(value) || "1".equals(value))
|
||||
{
|
||||
return "1";
|
||||
}
|
||||
if ("false".equals(value) || "0".equals(value))
|
||||
{
|
||||
return "0";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.ruoyi.loanpricing.service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@Service
|
||||
public class LoanPricingSensitiveDisplayService
|
||||
{
|
||||
public String maskCustName(String custName)
|
||||
{
|
||||
if (!StringUtils.hasText(custName))
|
||||
{
|
||||
return custName;
|
||||
}
|
||||
if (custName.contains("公司") && custName.length() > 4)
|
||||
{
|
||||
return custName.substring(0, 2) + "*".repeat(custName.length() - 4) + custName.substring(custName.length() - 2);
|
||||
}
|
||||
if (custName.length() == 1)
|
||||
{
|
||||
return custName;
|
||||
}
|
||||
return custName.substring(0, 1) + "*".repeat(custName.length() - 1);
|
||||
}
|
||||
|
||||
public String maskIdNum(String idNum)
|
||||
{
|
||||
if (!StringUtils.hasText(idNum))
|
||||
{
|
||||
return idNum;
|
||||
}
|
||||
if (idNum.startsWith("91") && idNum.length() == 18)
|
||||
{
|
||||
return idNum.substring(0, 2) + "*".repeat(13) + idNum.substring(idNum.length() - 3);
|
||||
}
|
||||
if (idNum.matches("\\d{17}[\\dXx]"))
|
||||
{
|
||||
return idNum.substring(0, 4) + "*".repeat(8) + idNum.substring(idNum.length() - 4);
|
||||
}
|
||||
if (idNum.length() > 5)
|
||||
{
|
||||
return idNum.substring(0, 2) + "*".repeat(idNum.length() - 5) + idNum.substring(idNum.length() - 3);
|
||||
}
|
||||
return "*".repeat(idNum.length());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.ruoyi.loanpricing.service;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import com.ruoyi.common.core.domain.entity.SysDictData;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.http.HttpUtils;
|
||||
import com.ruoyi.loanpricing.domain.dto.ModelInvokeDTO;
|
||||
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @Author 吴凯程
|
||||
* @Date 2025/12/11
|
||||
**/
|
||||
@Service
|
||||
@Slf4j
|
||||
@EnableAsync
|
||||
public class ModelService {
|
||||
|
||||
@Value("${model.url}")
|
||||
private String modelUrl;
|
||||
|
||||
|
||||
|
||||
public JSONObject invokeModel(ModelInvokeDTO modelInvokeDTO) {
|
||||
Map<String, String> requestBody = entityToMap(modelInvokeDTO);
|
||||
JSONObject response = HttpUtils.doPostFormUrlEncoded(modelUrl, requestBody, null, JSONObject.class);
|
||||
log.info("------------------->调用模型返回结果:" + JSON.toJSONString(response));
|
||||
if(Objects.nonNull(response) && response.containsKey("code") && response.getInteger("code") == 10000){
|
||||
JSONObject mappingOutputFields = response.getJSONObject("data").getJSONObject("mappingOutputFields");
|
||||
// return JSON.parseObject(mappingOutputFields.toJSONString(), ModelOutputFields.class);
|
||||
return mappingOutputFields;
|
||||
}else{
|
||||
log.error("------------------->调用模型失败,失败原因为:" + response.getString("message"));
|
||||
throw new ServiceException("调用模型失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用FastJSON将实体类转换为Map<String, String>
|
||||
* @param obj 待转换的实体类对象
|
||||
* @return 转换后的Map
|
||||
*/
|
||||
public static Map<String, String> entityToMap(Object obj) {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
// 先转为JSON字符串,再转换为指定类型的Map
|
||||
String jsonStr = JSON.toJSONString(obj);
|
||||
return JSON.parseObject(jsonStr, new TypeReference<Map<String, String>>() {});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.ruoyi.loanpricing.service;
|
||||
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
@Service
|
||||
public class SensitiveFieldCryptoService
|
||||
{
|
||||
private final String key;
|
||||
|
||||
public SensitiveFieldCryptoService(@Value("${loan-pricing.sensitive.key:}") String key)
|
||||
{
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String encrypt(String plainText)
|
||||
{
|
||||
validateKey();
|
||||
if (!StringUtils.hasText(plainText))
|
||||
{
|
||||
return plainText;
|
||||
}
|
||||
try
|
||||
{
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"));
|
||||
return Base64.getEncoder().encodeToString(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ServiceException("贷款定价敏感字段加密失败");
|
||||
}
|
||||
}
|
||||
|
||||
public String decrypt(String cipherText)
|
||||
{
|
||||
validateKey();
|
||||
if (!StringUtils.hasText(cipherText))
|
||||
{
|
||||
return cipherText;
|
||||
}
|
||||
try
|
||||
{
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"));
|
||||
return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)), StandardCharsets.UTF_8);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ServiceException("贷款定价敏感字段解密失败");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateKey()
|
||||
{
|
||||
if (!StringUtils.hasText(key))
|
||||
{
|
||||
throw new IllegalStateException("loan-pricing.sensitive.key 未配置");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
package com.ruoyi.loanpricing.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.loanpricing.domain.dto.CorporateLoanPricingCreateDTO;
|
||||
import com.ruoyi.loanpricing.domain.dto.PersonalLoanPricingCreateDTO;
|
||||
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
|
||||
import com.ruoyi.loanpricing.domain.entity.ModelCorpOutputFields;
|
||||
import com.ruoyi.loanpricing.domain.entity.ModelRetailOutputFields;
|
||||
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowListVO;
|
||||
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowVO;
|
||||
import com.ruoyi.loanpricing.mapper.LoanPricingWorkflowMapper;
|
||||
import com.ruoyi.loanpricing.mapper.ModelCorpOutputFieldsMapper;
|
||||
import com.ruoyi.loanpricing.mapper.ModelRetailOutputFieldsMapper;
|
||||
import com.ruoyi.loanpricing.service.ILoanPricingWorkflowService;
|
||||
import com.ruoyi.loanpricing.service.LoanPricingSensitiveDisplayService;
|
||||
import com.ruoyi.loanpricing.service.LoanPricingModelService;
|
||||
import com.ruoyi.loanpricing.service.SensitiveFieldCryptoService;
|
||||
import com.ruoyi.loanpricing.util.LoanPricingConverter;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 利率定价流程Service业务层处理
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-19
|
||||
*/
|
||||
@Service
|
||||
public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowService
|
||||
{
|
||||
@Resource
|
||||
private LoanPricingWorkflowMapper loanPricingWorkflowMapper;
|
||||
|
||||
@Resource
|
||||
private LoanPricingModelService loanPricingModelService;
|
||||
|
||||
@Resource
|
||||
private ModelRetailOutputFieldsMapper modelRetailOutputFieldsMapper;
|
||||
|
||||
@Resource
|
||||
private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper;
|
||||
|
||||
@Resource
|
||||
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
|
||||
|
||||
@Resource
|
||||
private LoanPricingSensitiveDisplayService loanPricingSensitiveDisplayService;
|
||||
|
||||
|
||||
/**
|
||||
* 发起利率定价流程
|
||||
*
|
||||
* @param loanPricingWorkflow 利率定价流程信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public LoanPricingWorkflow createLoanPricing(LoanPricingWorkflow loanPricingWorkflow)
|
||||
{
|
||||
// 自动生成业务方流水号(时间戳)
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
|
||||
String serialNum = sdf.format(new Date());
|
||||
loanPricingWorkflow.setSerialNum(serialNum);
|
||||
|
||||
// 设置默认值
|
||||
if (!StringUtils.hasText(loanPricingWorkflow.getOrgCode()))
|
||||
{
|
||||
loanPricingWorkflow.setOrgCode("892000");
|
||||
}
|
||||
if (!StringUtils.hasText(loanPricingWorkflow.getRunType()))
|
||||
{
|
||||
loanPricingWorkflow.setRunType("1");
|
||||
}
|
||||
|
||||
loanPricingWorkflow.setCustName(sensitiveFieldCryptoService.encrypt(loanPricingWorkflow.getCustName()));
|
||||
loanPricingWorkflow.setIdNum(sensitiveFieldCryptoService.encrypt(loanPricingWorkflow.getIdNum()));
|
||||
loanPricingWorkflowMapper.insert(loanPricingWorkflow);
|
||||
loanPricingModelService.invokeModelAsync(loanPricingWorkflow.getId());
|
||||
|
||||
return loanPricingWorkflow;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起个人客户利率定价流程
|
||||
*
|
||||
* @param dto 个人客户发起DTO
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public LoanPricingWorkflow createPersonalLoanPricing(PersonalLoanPricingCreateDTO dto) {
|
||||
LoanPricingWorkflow entity = LoanPricingConverter.toEntity(dto);
|
||||
return createLoanPricing(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起企业客户利率定价流程
|
||||
*
|
||||
* @param dto 企业客户发起DTO
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public LoanPricingWorkflow createCorporateLoanPricing(CorporateLoanPricingCreateDTO dto) {
|
||||
LoanPricingWorkflow entity = LoanPricingConverter.toEntity(dto);
|
||||
return createLoanPricing(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询利率定价流程列表
|
||||
*
|
||||
* @param loanPricingWorkflow 利率定价流程信息
|
||||
* @return 利率定价流程
|
||||
*/
|
||||
@Override
|
||||
public List<LoanPricingWorkflow> selectLoanPricingList(LoanPricingWorkflow loanPricingWorkflow)
|
||||
{
|
||||
LambdaQueryWrapper<LoanPricingWorkflow> wrapper = buildQueryWrapper(loanPricingWorkflow);
|
||||
// 按更新时间倒序
|
||||
wrapper.orderByDesc(LoanPricingWorkflow::getUpdateTime);
|
||||
return loanPricingWorkflowMapper.selectList(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询利率定价流程列表
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param loanPricingWorkflow 利率定价流程信息
|
||||
* @return 利率定价流程
|
||||
*/
|
||||
@Override
|
||||
public IPage<LoanPricingWorkflowListVO> selectLoanPricingPage(Page<LoanPricingWorkflowListVO> page, LoanPricingWorkflow loanPricingWorkflow)
|
||||
{
|
||||
IPage<LoanPricingWorkflowListVO> pageResult = loanPricingWorkflowMapper.selectWorkflowPageWithRates(page, loanPricingWorkflow);
|
||||
pageResult.getRecords().forEach(row -> row.setCustName(
|
||||
loanPricingSensitiveDisplayService.maskCustName(
|
||||
sensitiveFieldCryptoService.decrypt(row.getCustName()))));
|
||||
return pageResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询利率定价流程详情
|
||||
*
|
||||
* @param serialNum 业务方流水号
|
||||
* @return 利率定价流程
|
||||
*/
|
||||
@Override
|
||||
public LoanPricingWorkflowVO selectLoanPricingBySerialNum(String serialNum)
|
||||
{
|
||||
LoanPricingWorkflowVO loanPricingWorkflowVO = new LoanPricingWorkflowVO();
|
||||
|
||||
LambdaQueryWrapper<LoanPricingWorkflow> 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(loanPricingSensitiveDisplayService.maskCustName(plainCustName));
|
||||
loanPricingWorkflow.setIdNum(loanPricingSensitiveDisplayService.maskIdNum(plainIdNum));
|
||||
loanPricingWorkflowVO.setLoanPricingWorkflow(loanPricingWorkflow);
|
||||
|
||||
if (Objects.nonNull(loanPricingWorkflow.getModelOutputId())){
|
||||
if (loanPricingWorkflow.getCustType().equals("个人")){
|
||||
ModelRetailOutputFields modelRetailOutputFields = modelRetailOutputFieldsMapper.selectById(loanPricingWorkflow.getModelOutputId());
|
||||
if (Objects.nonNull(modelRetailOutputFields))
|
||||
{
|
||||
maskModelRetailOutputBasicInfo(modelRetailOutputFields);
|
||||
loanPricingWorkflow.setLoanRate(modelRetailOutputFields.getFinalCalculateRate());
|
||||
}
|
||||
loanPricingWorkflowVO.setModelRetailOutputFields(modelRetailOutputFields);
|
||||
}
|
||||
if (loanPricingWorkflow.getCustType().equals("企业")){
|
||||
ModelCorpOutputFields modelCorpOutputFields = modelCorpOutputFieldsMapper.selectById(loanPricingWorkflow.getModelOutputId());
|
||||
if (Objects.nonNull(modelCorpOutputFields))
|
||||
{
|
||||
maskModelCorpOutputBasicInfo(modelCorpOutputFields);
|
||||
loanPricingWorkflow.setLoanRate(modelCorpOutputFields.getCalculateRate());
|
||||
}
|
||||
loanPricingWorkflowVO.setModelCorpOutputFields(modelCorpOutputFields);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return loanPricingWorkflowVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件
|
||||
*
|
||||
* @param loanPricingWorkflow 利率定价流程信息
|
||||
* @return LambdaQueryWrapper
|
||||
*/
|
||||
private LambdaQueryWrapper<LoanPricingWorkflow> buildQueryWrapper(LoanPricingWorkflow loanPricingWorkflow)
|
||||
{
|
||||
LambdaQueryWrapper<LoanPricingWorkflow> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
// 按创建者筛选
|
||||
if (StringUtils.hasText(loanPricingWorkflow.getCreateBy()))
|
||||
{
|
||||
wrapper.like(LoanPricingWorkflow::getCreateBy, loanPricingWorkflow.getCreateBy());
|
||||
}
|
||||
|
||||
// 按客户内码模糊查询
|
||||
if (StringUtils.hasText(loanPricingWorkflow.getCustIsn()))
|
||||
{
|
||||
wrapper.like(LoanPricingWorkflow::getCustIsn, loanPricingWorkflow.getCustIsn());
|
||||
}
|
||||
|
||||
// 按机构号筛选
|
||||
if (StringUtils.hasText(loanPricingWorkflow.getOrgCode()))
|
||||
{
|
||||
wrapper.like(LoanPricingWorkflow::getOrgCode, loanPricingWorkflow.getOrgCode());
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
private void maskModelRetailOutputBasicInfo(ModelRetailOutputFields modelRetailOutputFields)
|
||||
{
|
||||
modelRetailOutputFields.setCustName(
|
||||
loanPricingSensitiveDisplayService.maskCustName(modelRetailOutputFields.getCustName()));
|
||||
modelRetailOutputFields.setIdNum(
|
||||
loanPricingSensitiveDisplayService.maskIdNum(modelRetailOutputFields.getIdNum()));
|
||||
}
|
||||
|
||||
private void maskModelCorpOutputBasicInfo(ModelCorpOutputFields modelCorpOutputFields)
|
||||
{
|
||||
modelCorpOutputFields.setCustName(
|
||||
loanPricingSensitiveDisplayService.maskCustName(modelCorpOutputFields.getCustName()));
|
||||
modelCorpOutputFields.setIdNum(
|
||||
loanPricingSensitiveDisplayService.maskIdNum(modelCorpOutputFields.getIdNum()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设定执行利率
|
||||
*
|
||||
* @param serialNum 业务方流水号
|
||||
* @param executeRate 执行利率
|
||||
* @return 是否成功
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean setExecuteRate(String serialNum, String executeRate) {
|
||||
LambdaQueryWrapper<LoanPricingWorkflow> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(LoanPricingWorkflow::getSerialNum, serialNum);
|
||||
LoanPricingWorkflow workflow = loanPricingWorkflowMapper.selectOne(wrapper);
|
||||
|
||||
if (workflow == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
workflow.setExecuteRate(executeRate);
|
||||
int result = loanPricingWorkflowMapper.updateById(workflow);
|
||||
return result > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.ruoyi.loanpricing.util;
|
||||
|
||||
import com.ruoyi.loanpricing.domain.dto.CorporateLoanPricingCreateDTO;
|
||||
import com.ruoyi.loanpricing.domain.dto.PersonalLoanPricingCreateDTO;
|
||||
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
|
||||
|
||||
/**
|
||||
* 利率定价转换器
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-19
|
||||
*/
|
||||
public class LoanPricingConverter {
|
||||
|
||||
/**
|
||||
* 个人客户DTO转Entity
|
||||
*
|
||||
* @param dto 个人客户发起DTO
|
||||
* @return 利率定价流程实体
|
||||
*/
|
||||
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.setLoanPurpose(dto.getLoanPurpose());
|
||||
entity.setLoanTerm(dto.getLoanTerm());
|
||||
entity.setCollType(dto.getCollType());
|
||||
entity.setCollThirdParty(dto.getCollThirdParty());
|
||||
// 映射个人特有字段
|
||||
entity.setBizProof(dto.getBizProof());
|
||||
entity.setLoanLoop(dto.getLoanLoop());
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 企业客户DTO转Entity
|
||||
*
|
||||
* @param dto 企业客户发起DTO
|
||||
* @return 利率定价流程实体
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
68
ruoyi-loan-pricing/src/main/resources/data/corp_output.json
Normal file
68
ruoyi-loan-pricing/src/main/resources/data/corp_output.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"traceId": "350626558347246735E7F4722CUZRWOMNRR53O0",
|
||||
"cost": 2267,
|
||||
"tokenId": "17364055486305E7F4722M8IPFWNL8TOBEB",
|
||||
"mappingOutputFields": {
|
||||
"custIsn": "CUST20260121001",
|
||||
"custType": "企业客户",
|
||||
"guarType": "抵押担保",
|
||||
"custName": "北京智联科技有限公司",
|
||||
"idType": "营业执照",
|
||||
"idNum": "91110108MA00XXXXXX",
|
||||
"baseLoanRate": "3.45",
|
||||
"isFirstLoan": "N",
|
||||
"faithDay": "730",
|
||||
"bpFirstLoan": "0",
|
||||
"bpAgeLoan": "5.2",
|
||||
"totalBpLoyalty": "8.5",
|
||||
"balanceAvg": "5000000.00",
|
||||
"loanAvg": "3000000.00",
|
||||
"derivationRate": "1.8",
|
||||
"totalBpContribution": "12.3",
|
||||
"midEntConnect": "100000.00",
|
||||
"midEntEffect": "50000.00",
|
||||
"midEntInter": "80000.00",
|
||||
"midEntAccept": "200000.00",
|
||||
"midEntDiscount": "150000.00",
|
||||
"midEntEleDdc": "30000.00",
|
||||
"midEntWaterDdc": "10000.00",
|
||||
"midEntTax": "40000.00",
|
||||
"bpMid": "6.8",
|
||||
"payroll": "200",
|
||||
"invLoanAmount": "2500000.00",
|
||||
"bpPayroll": "4.1",
|
||||
"isCleanEnt": "Y",
|
||||
"hasSettleAcct": "Y",
|
||||
"isAgriGuar": "N",
|
||||
"isGreenLoan": "Y",
|
||||
"isTechEnt": "Y",
|
||||
"bpEntType": "7.5",
|
||||
"totoalBpRelevance": "9.2",
|
||||
"loanTerm": "36",
|
||||
"bpLoanTerm": "3.3",
|
||||
"applyAmt": "5000000.00",
|
||||
"bpLoanAmount": "5.8",
|
||||
"collType": "房产抵押",
|
||||
"collThirdParty": "N",
|
||||
"bpCollateral": "4.5",
|
||||
"greyCust": "N",
|
||||
"prinOverdue": "N",
|
||||
"interestOverdue": "N",
|
||||
"cardOverdue": "N",
|
||||
"bpGreyOverdue": "0",
|
||||
"totoalBpRisk": "1.2",
|
||||
"totalBp": "48.2",
|
||||
"calculateRate": "3.932"
|
||||
},
|
||||
"extensionMap": {},
|
||||
"reasonMessage": "Running successfully",
|
||||
"bizTime": 1736405548630,
|
||||
"outputFields": {},
|
||||
"workflowCode": "TBKH",
|
||||
"orgCode": "802000",
|
||||
"bizId": "2025010914345",
|
||||
"reasonCode": 200,
|
||||
"workflowVersion": 14,
|
||||
"callTime": 1736405548630,
|
||||
"status": 1
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"traceId": "350626558347246735E7F4722CUZRWOMNRR53O0",
|
||||
"cost": 2267,
|
||||
"tokenId": "17364055486305E7F4722M8IPFWNL8TOBEB",
|
||||
"mappingOutputFields": {
|
||||
"custIsn": "CUST20260121001",
|
||||
"custType": "个人",
|
||||
"guarType": "信用担保",
|
||||
"custName": "张三",
|
||||
"idType": "身份证",
|
||||
"idNum": "330106199001011234",
|
||||
"baseLoanRate": "4.35",
|
||||
"isFirstLoan": "是",
|
||||
"faithDay": "365",
|
||||
"custAge": "36",
|
||||
"bpFirstLoan": "50",
|
||||
"bpAgeLoan": "30",
|
||||
"bpAge": "20",
|
||||
"totalBpLoyalty": "95",
|
||||
"balanceAvg": "50000.00",
|
||||
"loanAvg": "100000.00",
|
||||
"derivationRate": "1.2",
|
||||
"totalBpContribution": "88",
|
||||
"midPerCard": "1000.50",
|
||||
"midPerPass": "500.00",
|
||||
"midPerHarvest": "800.20",
|
||||
"midPerEffect": "是",
|
||||
"midPerQuickPay": "300.00",
|
||||
"midPerEleDdc": "150.00",
|
||||
"midPerWaterDdc": "80.00",
|
||||
"midPerHuashuDdc": "120.00",
|
||||
"MidPerGasDdc": "90.00",
|
||||
"midPerCitizencard": "200.00",
|
||||
"midPerFinMan": "5000.00",
|
||||
"midPerEtc": "180.00",
|
||||
"bpMid": "45",
|
||||
"totoalBpRelevance": "90",
|
||||
"applyAmt": "200000.00",
|
||||
"bpLoanAmount": "60",
|
||||
"loanPurpose": "个人消费",
|
||||
"bizProof": "有",
|
||||
"bpLoanUse": "55",
|
||||
"loanLoop": "支持",
|
||||
"bpLoanLoop": "40",
|
||||
"collType": "无抵质押",
|
||||
"collThirdParty": "否",
|
||||
"bpCollateral": "0",
|
||||
"greyCust": "否",
|
||||
"prinOverdue": "否",
|
||||
"interestOverdue": "否",
|
||||
"cardOverdue": "否",
|
||||
"bpGreyOverdue": "98",
|
||||
"totoalBpRisk": "95",
|
||||
"totalBp": "350",
|
||||
"calculateRate": "6.15",
|
||||
"loanRateHistory": "6.40",
|
||||
"minRateProduct": "5.50",
|
||||
"smoothRange": "-0.10",
|
||||
"finalCalculateRate": "6.05",
|
||||
"referenceRate": "5.95"
|
||||
},
|
||||
"extensionMap": {},
|
||||
"reasonMessage": "Running successfully",
|
||||
"bizTime": 1736405548630,
|
||||
"outputFields": {},
|
||||
"workflowCode": "TBKH",
|
||||
"orgCode": "802000",
|
||||
"bizId": "2025010914345",
|
||||
"reasonCode": 200,
|
||||
"workflowVersion": 14,
|
||||
"callTime": 1736405548630,
|
||||
"status": 1
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.loanpricing.mapper.LoanPricingWorkflowMapper">
|
||||
|
||||
<select id="selectWorkflowPageWithRates" resultType="com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowListVO">
|
||||
SELECT
|
||||
lpw.serial_num AS serialNum,
|
||||
lpw.cust_name AS custName,
|
||||
lpw.cust_type AS custType,
|
||||
lpw.guar_type AS guarType,
|
||||
lpw.apply_amt AS applyAmt,
|
||||
CASE
|
||||
WHEN lpw.cust_type = '个人' THEN mr.final_calculate_rate
|
||||
WHEN lpw.cust_type = '企业' THEN mc.calculate_rate
|
||||
ELSE NULL
|
||||
END AS calculateRate,
|
||||
lpw.execute_rate AS executeRate,
|
||||
lpw.create_time AS createTime,
|
||||
lpw.create_by AS createBy
|
||||
FROM loan_pricing_workflow lpw
|
||||
LEFT JOIN model_retail_output_fields mr ON lpw.model_output_id = mr.id
|
||||
LEFT JOIN model_corp_output_fields mc ON lpw.model_output_id = mc.id
|
||||
<where>
|
||||
<if test="query != null and query.createBy != null and query.createBy != ''">
|
||||
AND lpw.create_by LIKE CONCAT('%', #{query.createBy}, '%')
|
||||
</if>
|
||||
<if test="query != null and query.custIsn != null and query.custIsn != ''">
|
||||
AND lpw.cust_isn LIKE CONCAT('%', #{query.custIsn}, '%')
|
||||
</if>
|
||||
<if test="query != null and query.orgCode != null and query.orgCode != ''">
|
||||
AND lpw.org_code LIKE CONCAT('%', #{query.orgCode}, '%')
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY lpw.update_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.ruoyi.loanpricing.domain.entity;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ModelRetailOutputFieldsTest
|
||||
{
|
||||
@Test
|
||||
void shouldContainLatestRetailDisplayRateFields()
|
||||
{
|
||||
Set<String> fieldNames = Arrays.stream(ModelRetailOutputFields.class.getDeclaredFields())
|
||||
.map(Field::getName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
assertTrue(fieldNames.contains("loanRateHistory"), "缺少字段 loanRateHistory");
|
||||
assertTrue(fieldNames.contains("minRateProduct"), "缺少字段 minRateProduct");
|
||||
assertTrue(fieldNames.contains("smoothRange"), "缺少字段 smoothRange");
|
||||
assertTrue(fieldNames.contains("finalCalculateRate"), "缺少字段 finalCalculateRate");
|
||||
assertTrue(fieldNames.contains("referenceRate"), "缺少字段 referenceRate");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ruoyi.loanpricing.domain.vo;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class LoanPricingWorkflowListVOTest
|
||||
{
|
||||
@Test
|
||||
void shouldExposeCalculateRateAndExecuteRateFields()
|
||||
{
|
||||
LoanPricingWorkflowListVO vo = new LoanPricingWorkflowListVO();
|
||||
vo.setCalculateRate("6.15");
|
||||
vo.setExecuteRate("5.80");
|
||||
|
||||
assertEquals("6.15", vo.getCalculateRate());
|
||||
assertEquals("5.80", vo.getExecuteRate());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.ruoyi.loanpricing.mapper;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
class LoanPricingWorkflowMapperXmlTest
|
||||
{
|
||||
@Test
|
||||
void shouldUseRetailFinalCalculateRateInWorkflowListQuery() throws IOException
|
||||
{
|
||||
ClassPathResource resource = new ClassPathResource("mapper/loanpricing/LoanPricingWorkflowMapper.xml");
|
||||
String xml = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
|
||||
|
||||
assertTrue(xml.contains("WHEN lpw.cust_type = '个人' THEN mr.final_calculate_rate"));
|
||||
assertTrue(xml.contains("WHEN lpw.cust_type = '企业' THEN mc.calculate_rate"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.ruoyi.loanpricing.service;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.loanpricing.domain.dto.ModelInvokeDTO;
|
||||
import com.ruoyi.loanpricing.domain.dto.PersonalLoanPricingCreateDTO;
|
||||
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
|
||||
import com.ruoyi.loanpricing.mapper.LoanPricingWorkflowMapper;
|
||||
import com.ruoyi.loanpricing.mapper.ModelCorpOutputFieldsMapper;
|
||||
import com.ruoyi.loanpricing.mapper.ModelRetailOutputFieldsMapper;
|
||||
import com.ruoyi.loanpricing.util.LoanPricingConverter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LoanPricingModelServicePersonalParamsTest {
|
||||
|
||||
@Mock
|
||||
private ModelService modelService;
|
||||
|
||||
@Mock
|
||||
private LoanPricingWorkflowMapper loanPricingWorkflowMapper;
|
||||
|
||||
@Mock
|
||||
private ModelRetailOutputFieldsMapper modelRetailOutputFieldsMapper;
|
||||
|
||||
@Mock
|
||||
private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper;
|
||||
|
||||
@Mock
|
||||
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
|
||||
|
||||
@InjectMocks
|
||||
private LoanPricingModelService loanPricingModelService;
|
||||
|
||||
@Test
|
||||
void shouldContainLoanPurposeAndLoanTermInPersonalCreateDto() throws NoSuchFieldException {
|
||||
assertNotNull(PersonalLoanPricingCreateDTO.class.getDeclaredField("loanPurpose"));
|
||||
assertNotNull(PersonalLoanPricingCreateDTO.class.getDeclaredField("loanTerm"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMapLoanPurposeAndLoanTermFromPersonalDto() {
|
||||
PersonalLoanPricingCreateDTO dto = new PersonalLoanPricingCreateDTO();
|
||||
dto.setCustIsn("CUST001");
|
||||
dto.setCustName("张三");
|
||||
dto.setGuarType("信用");
|
||||
dto.setApplyAmt("100000");
|
||||
dto.setLoanPurpose("business");
|
||||
dto.setLoanTerm("3");
|
||||
|
||||
LoanPricingWorkflow workflow = LoanPricingConverter.toEntity(dto);
|
||||
|
||||
assertEquals("business", workflow.getLoanPurpose());
|
||||
assertEquals("3", workflow.getLoanTerm());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldContainLoanTermAndLoanLoopInModelInvokeDto() throws NoSuchFieldException {
|
||||
assertNotNull(ModelInvokeDTO.class.getDeclaredField("loanTerm"));
|
||||
assertNotNull(ModelInvokeDTO.class.getDeclaredField("loanLoop"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInvokePersonalModelWithExpectedParams() {
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setId(1L);
|
||||
workflow.setSerialNum("202604090001");
|
||||
workflow.setOrgCode("892000");
|
||||
workflow.setRunType("1");
|
||||
workflow.setCustIsn("CUST001");
|
||||
workflow.setCustType("个人");
|
||||
workflow.setCustName("cipher-name");
|
||||
workflow.setIdType("身份证");
|
||||
workflow.setIdNum("cipher-id");
|
||||
workflow.setGuarType("信用");
|
||||
workflow.setApplyAmt("100000");
|
||||
workflow.setLoanPurpose("business");
|
||||
workflow.setLoanTerm("3");
|
||||
workflow.setBizProof("true");
|
||||
workflow.setLoanLoop("false");
|
||||
workflow.setCollThirdParty("true");
|
||||
workflow.setCollType("一类");
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("calculateRate", "6.15");
|
||||
|
||||
when(loanPricingWorkflowMapper.selectById(1L)).thenReturn(workflow);
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
|
||||
when(modelService.invokeModel(any())).thenReturn(response);
|
||||
|
||||
loanPricingModelService.invokeModelAsync(1L);
|
||||
|
||||
verify(modelService).invokeModel(argThat((ModelInvokeDTO dto) ->
|
||||
Objects.equals("202604090001", dto.getSerialNum())
|
||||
&& Objects.equals("892000", dto.getOrgCode())
|
||||
&& Objects.equals("1", dto.getRunType())
|
||||
&& Objects.equals("CUST001", dto.getCustIsn())
|
||||
&& Objects.equals("个人", dto.getCustType())
|
||||
&& Objects.equals("张三", dto.getCustName())
|
||||
&& Objects.equals("身份证", dto.getIdType())
|
||||
&& Objects.equals("110101199001011234", dto.getIdNum())
|
||||
&& Objects.equals("信用", dto.getGuarType())
|
||||
&& Objects.equals("100000", dto.getApplyAmt())
|
||||
&& Objects.equals("business", dto.getLoanPurpose())
|
||||
&& Objects.equals("3", dto.getLoanTerm())
|
||||
&& Objects.equals("1", dto.getBizProof())
|
||||
&& Objects.equals("0", dto.getLoanLoop())
|
||||
&& Objects.equals("1", dto.getCollThirdParty())
|
||||
&& Objects.equals("一类", dto.getCollType())));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.ruoyi.loanpricing.service;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.loanpricing.domain.dto.ModelInvokeDTO;
|
||||
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
|
||||
import com.ruoyi.loanpricing.mapper.LoanPricingWorkflowMapper;
|
||||
import com.ruoyi.loanpricing.mapper.ModelCorpOutputFieldsMapper;
|
||||
import com.ruoyi.loanpricing.mapper.ModelRetailOutputFieldsMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LoanPricingModelServiceTest
|
||||
{
|
||||
@Mock
|
||||
private ModelService modelService;
|
||||
|
||||
@Mock
|
||||
private LoanPricingWorkflowMapper loanPricingWorkflowMapper;
|
||||
|
||||
@Mock
|
||||
private ModelRetailOutputFieldsMapper modelRetailOutputFieldsMapper;
|
||||
|
||||
@Mock
|
||||
private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper;
|
||||
|
||||
@Mock
|
||||
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
|
||||
|
||||
@InjectMocks
|
||||
private LoanPricingModelService loanPricingModelService;
|
||||
|
||||
@Test
|
||||
void shouldDecryptCustNameAndIdNumBeforeInvokeModel()
|
||||
{
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setId(1L);
|
||||
workflow.setCustType("个人");
|
||||
workflow.setCustName("cipher-name");
|
||||
workflow.setIdNum("cipher-id");
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("calculateRate", "6.15");
|
||||
|
||||
when(loanPricingWorkflowMapper.selectById(1L)).thenReturn(workflow);
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
|
||||
when(modelService.invokeModel(any())).thenReturn(response);
|
||||
|
||||
loanPricingModelService.invokeModelAsync(1L);
|
||||
|
||||
verify(modelService).invokeModel(argThat((ModelInvokeDTO dto) ->
|
||||
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())));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.ruoyi.loanpricing.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class LoanPricingSensitiveDisplayServiceTest
|
||||
{
|
||||
private final LoanPricingSensitiveDisplayService displayService = new LoanPricingSensitiveDisplayService();
|
||||
|
||||
@Test
|
||||
void shouldMaskPersonalNameAndIdNum()
|
||||
{
|
||||
assertEquals("张*", displayService.maskCustName("张三"));
|
||||
assertEquals("1101********1234", displayService.maskIdNum("110101199001011234"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMaskCorporateNameAndCreditCode()
|
||||
{
|
||||
assertEquals("测试****公司", displayService.maskCustName("测试科技有限公司"));
|
||||
assertEquals("91*************00X", displayService.maskIdNum("91110000100000000X"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.ruoyi.loanpricing.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class SensitiveFieldCryptoServiceTest
|
||||
{
|
||||
@Test
|
||||
void shouldEncryptAndDecryptCustNameAndIdNum()
|
||||
{
|
||||
SensitiveFieldCryptoService service = new SensitiveFieldCryptoService("1234567890abcdef");
|
||||
|
||||
String nameCipher = service.encrypt("张三");
|
||||
String idNumCipher = service.encrypt("110101199001011234");
|
||||
|
||||
assertNotEquals("张三", nameCipher);
|
||||
assertNotEquals("110101199001011234", idNumCipher);
|
||||
assertEquals("张三", service.decrypt(nameCipher));
|
||||
assertEquals("110101199001011234", service.decrypt(idNumCipher));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectBlankKeyConfiguration()
|
||||
{
|
||||
SensitiveFieldCryptoService service = new SensitiveFieldCryptoService("");
|
||||
|
||||
assertThrows(IllegalStateException.class, () -> service.encrypt("张三"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package com.ruoyi.loanpricing.service.impl;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
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.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
|
||||
import com.ruoyi.loanpricing.domain.entity.ModelCorpOutputFields;
|
||||
import com.ruoyi.loanpricing.domain.entity.ModelRetailOutputFields;
|
||||
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowListVO;
|
||||
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowVO;
|
||||
import com.ruoyi.loanpricing.mapper.LoanPricingWorkflowMapper;
|
||||
import com.ruoyi.loanpricing.mapper.ModelCorpOutputFieldsMapper;
|
||||
import com.ruoyi.loanpricing.mapper.ModelRetailOutputFieldsMapper;
|
||||
import com.ruoyi.loanpricing.service.LoanPricingSensitiveDisplayService;
|
||||
import com.ruoyi.loanpricing.service.LoanPricingModelService;
|
||||
import com.ruoyi.loanpricing.service.SensitiveFieldCryptoService;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LoanPricingWorkflowServiceImplTest
|
||||
{
|
||||
@Mock
|
||||
private LoanPricingWorkflowMapper loanPricingWorkflowMapper;
|
||||
|
||||
@Mock
|
||||
private LoanPricingModelService loanPricingModelService;
|
||||
|
||||
@Mock
|
||||
private ModelRetailOutputFieldsMapper modelRetailOutputFieldsMapper;
|
||||
|
||||
@Mock
|
||||
private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper;
|
||||
|
||||
@Mock
|
||||
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
|
||||
|
||||
@Mock
|
||||
private LoanPricingSensitiveDisplayService loanPricingSensitiveDisplayService;
|
||||
|
||||
@InjectMocks
|
||||
private LoanPricingWorkflowServiceImpl loanPricingWorkflowService;
|
||||
|
||||
@Test
|
||||
void shouldEncryptCustNameAndIdNumBeforeInsert()
|
||||
{
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setCustName("张三");
|
||||
workflow.setIdNum("110101199001011234");
|
||||
workflow.setCustIsn("CUST001");
|
||||
|
||||
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("CUST001", entity.getCustIsn())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnPagedWorkflowListWithCalculateRate()
|
||||
{
|
||||
LoanPricingWorkflowListVO row = new LoanPricingWorkflowListVO();
|
||||
row.setCalculateRate("6.15");
|
||||
|
||||
Page<LoanPricingWorkflowListVO> pageResult = new Page<>(1, 10);
|
||||
pageResult.setRecords(java.util.List.of(row));
|
||||
|
||||
when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(pageResult);
|
||||
|
||||
IPage<LoanPricingWorkflowListVO> result = loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), new LoanPricingWorkflow());
|
||||
|
||||
assertEquals("6.15", result.getRecords().get(0).getCalculateRate());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMaskCustNameWhenReturningPagedWorkflowList()
|
||||
{
|
||||
LoanPricingWorkflowListVO row = new LoanPricingWorkflowListVO();
|
||||
row.setCustName("cipher-name");
|
||||
|
||||
Page<LoanPricingWorkflowListVO> pageResult = new Page<>(1, 10);
|
||||
pageResult.setRecords(Collections.singletonList(row));
|
||||
|
||||
when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(pageResult);
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
|
||||
when(loanPricingSensitiveDisplayService.maskCustName("张三")).thenReturn("张*");
|
||||
|
||||
IPage<LoanPricingWorkflowListVO> result = loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), new LoanPricingWorkflow());
|
||||
|
||||
assertEquals("张*", result.getRecords().get(0).getCustName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseCustIsnInsteadOfCustNameAsQueryCondition()
|
||||
{
|
||||
LoanPricingWorkflow query = new LoanPricingWorkflow();
|
||||
query.setCustIsn("CUST001");
|
||||
query.setCustName("张三");
|
||||
|
||||
when(loanPricingWorkflowMapper.selectList(any())).thenReturn(Collections.emptyList());
|
||||
|
||||
loanPricingWorkflowService.selectLoanPricingList(query);
|
||||
|
||||
ArgumentCaptor<LambdaQueryWrapper<LoanPricingWorkflow>> wrapperCaptor = ArgumentCaptor.forClass(LambdaQueryWrapper.class);
|
||||
verify(loanPricingWorkflowMapper).selectList(wrapperCaptor.capture());
|
||||
|
||||
LambdaQueryWrapper<LoanPricingWorkflow> wrapper = wrapperCaptor.getValue();
|
||||
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), LoanPricingWorkflow.class);
|
||||
String sqlSegment = wrapper.getSqlSegment();
|
||||
|
||||
assertTrue(sqlSegment.contains("cust_isn"), sqlSegment);
|
||||
assertTrue(!sqlSegment.contains("cust_name"), sqlSegment);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseRetailModelOutputFinalCalculateRateForWorkflowDetail()
|
||||
{
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setSerialNum("P20260328001");
|
||||
workflow.setCustType("个人");
|
||||
workflow.setModelOutputId(11L);
|
||||
workflow.setLoanRate("4.35");
|
||||
|
||||
ModelRetailOutputFields retailOutputFields = new ModelRetailOutputFields();
|
||||
retailOutputFields.setCalculateRate("6.15");
|
||||
retailOutputFields.setFinalCalculateRate("6.05");
|
||||
|
||||
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
|
||||
when(modelRetailOutputFieldsMapper.selectById(11L)).thenReturn(retailOutputFields);
|
||||
|
||||
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001");
|
||||
|
||||
assertEquals("6.05", result.getLoanPricingWorkflow().getLoanRate());
|
||||
assertEquals("6.15", result.getModelRetailOutputFields().getCalculateRate());
|
||||
assertEquals("6.05", result.getModelRetailOutputFields().getFinalCalculateRate());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMaskCustNameAndIdNumWhenReturningWorkflowDetail()
|
||||
{
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setSerialNum("P20260328001");
|
||||
workflow.setCustType("个人");
|
||||
workflow.setCustName("cipher-name");
|
||||
workflow.setIdNum("cipher-id");
|
||||
|
||||
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
|
||||
when(loanPricingSensitiveDisplayService.maskCustName("张三")).thenReturn("张*");
|
||||
when(loanPricingSensitiveDisplayService.maskIdNum("110101199001011234")).thenReturn("1101********1234");
|
||||
|
||||
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001");
|
||||
|
||||
assertEquals("张*", result.getLoanPricingWorkflow().getCustName());
|
||||
assertEquals("1101********1234", result.getLoanPricingWorkflow().getIdNum());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMaskCustNameAndIdNumInRetailModelOutputBasicInfo()
|
||||
{
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setSerialNum("P20260328001");
|
||||
workflow.setCustType("个人");
|
||||
workflow.setCustName("cipher-name");
|
||||
workflow.setIdNum("cipher-id");
|
||||
workflow.setModelOutputId(11L);
|
||||
|
||||
ModelRetailOutputFields retailOutputFields = new ModelRetailOutputFields();
|
||||
retailOutputFields.setCustName("张三");
|
||||
retailOutputFields.setIdNum("110101199001011234");
|
||||
retailOutputFields.setCalculateRate("6.15");
|
||||
|
||||
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");
|
||||
when(loanPricingSensitiveDisplayService.maskCustName("张三")).thenReturn("张*");
|
||||
when(loanPricingSensitiveDisplayService.maskIdNum("110101199001011234")).thenReturn("1101********1234");
|
||||
|
||||
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001");
|
||||
|
||||
assertEquals("张*", result.getModelRetailOutputFields().getCustName());
|
||||
assertEquals("1101********1234", result.getModelRetailOutputFields().getIdNum());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseCorporateModelOutputCalculateRateForWorkflowDetail()
|
||||
{
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setSerialNum("C20260328001");
|
||||
workflow.setCustType("企业");
|
||||
workflow.setModelOutputId(22L);
|
||||
workflow.setLoanRate("3.80");
|
||||
|
||||
ModelCorpOutputFields corpOutputFields = new ModelCorpOutputFields();
|
||||
corpOutputFields.setCalculateRate("3.932");
|
||||
|
||||
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
|
||||
when(modelCorpOutputFieldsMapper.selectById(22L)).thenReturn(corpOutputFields);
|
||||
|
||||
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("C20260328001");
|
||||
|
||||
assertEquals("3.932", result.getLoanPricingWorkflow().getLoanRate());
|
||||
assertEquals("3.932", result.getModelCorpOutputFields().getCalculateRate());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMaskCustNameAndIdNumInCorporateModelOutputBasicInfo()
|
||||
{
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setSerialNum("C20260328001");
|
||||
workflow.setCustType("企业");
|
||||
workflow.setCustName("cipher-name");
|
||||
workflow.setIdNum("cipher-id");
|
||||
workflow.setModelOutputId(22L);
|
||||
|
||||
ModelCorpOutputFields corpOutputFields = new ModelCorpOutputFields();
|
||||
corpOutputFields.setCustName("测试科技有限公司");
|
||||
corpOutputFields.setIdNum("91110000100000000X");
|
||||
corpOutputFields.setCalculateRate("3.932");
|
||||
|
||||
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");
|
||||
when(loanPricingSensitiveDisplayService.maskCustName("测试科技有限公司")).thenReturn("测试****公司");
|
||||
when(loanPricingSensitiveDisplayService.maskIdNum("91110000100000000X")).thenReturn("91*************00X");
|
||||
|
||||
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("C20260328001");
|
||||
|
||||
assertEquals("测试****公司", result.getModelCorpOutputFields().getCustName());
|
||||
assertEquals("91*************00X", result.getModelCorpOutputFields().getIdNum());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user