实现结果总览员工结果聚合构建
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
package com.ruoyi.ccdi.project.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 结果总览员工命中明细行
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectOverviewEmployeeHitRowVO {
|
||||
|
||||
private Long projectId;
|
||||
|
||||
private String staffIdCard;
|
||||
|
||||
private String staffName;
|
||||
|
||||
private String staffCode;
|
||||
|
||||
private Long deptId;
|
||||
|
||||
private String deptName;
|
||||
|
||||
private String modelCode;
|
||||
|
||||
private String modelName;
|
||||
|
||||
private String ruleCode;
|
||||
|
||||
private String ruleName;
|
||||
|
||||
private String riskLevel;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ruoyi.ccdi.project.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 结果总览员工模型汇总
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectOverviewEmployeeModelSummaryVO {
|
||||
|
||||
private String modelCode;
|
||||
|
||||
private String modelName;
|
||||
|
||||
private Integer warningCount;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.ruoyi.ccdi.project.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 结果总览员工规则汇总
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectOverviewEmployeeRuleSummaryVO {
|
||||
|
||||
private String ruleCode;
|
||||
|
||||
private String ruleName;
|
||||
|
||||
private String riskLevel;
|
||||
|
||||
private Integer warningCount;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.ccdi.project.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewEmployeeHitRowVO;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
@@ -34,4 +35,12 @@ public interface CcdiProjectOverviewEmployeeResultMapper extends BaseMapper<Ccdi
|
||||
* @return 结果列表
|
||||
*/
|
||||
List<CcdiProjectOverviewEmployeeResult> selectByProjectId(@Param("projectId") Long projectId);
|
||||
|
||||
/**
|
||||
* 按项目查询员工归并命中明细
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 命中明细
|
||||
*/
|
||||
List<CcdiProjectOverviewEmployeeHitRowVO> selectEmployeeHitRowsByProjectId(@Param("projectId") Long projectId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
package com.ruoyi.ccdi.project.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewEmployeeHitRowVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewEmployeeModelSummaryVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewEmployeeRuleSummaryVO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 结果总览员工结果构建器
|
||||
*/
|
||||
@Component
|
||||
public class CcdiProjectOverviewEmployeeResultBuilder {
|
||||
|
||||
/**
|
||||
* 按员工归并命中明细并构建结果表实体
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param hitRows 命中明细
|
||||
* @param operator 操作人
|
||||
* @return 结果表实体列表
|
||||
*/
|
||||
public List<CcdiProjectOverviewEmployeeResult> build(Long projectId,
|
||||
List<CcdiProjectOverviewEmployeeHitRowVO> hitRows,
|
||||
String operator) {
|
||||
if (hitRows == null || hitRows.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
return hitRows.stream()
|
||||
.filter(item -> isNotBlank(item.getStaffIdCard()))
|
||||
.collect(Collectors.groupingBy(
|
||||
CcdiProjectOverviewEmployeeHitRowVO::getStaffIdCard,
|
||||
LinkedHashMap::new,
|
||||
Collectors.toList()
|
||||
))
|
||||
.entrySet()
|
||||
.stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.map(entry -> buildSingleResult(projectId, entry.getKey(), entry.getValue(), operator, now))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private CcdiProjectOverviewEmployeeResult buildSingleResult(Long projectId,
|
||||
String staffIdCard,
|
||||
List<CcdiProjectOverviewEmployeeHitRowVO> staffRows,
|
||||
String operator,
|
||||
Date now) {
|
||||
List<CcdiProjectOverviewEmployeeRuleSummaryVO> ruleSummaries = buildRuleSummaries(staffRows);
|
||||
List<CcdiProjectOverviewEmployeeModelSummaryVO> modelSummaries = buildModelSummaries(staffRows);
|
||||
|
||||
CcdiProjectOverviewEmployeeResult result = new CcdiProjectOverviewEmployeeResult();
|
||||
result.setProjectId(projectId);
|
||||
result.setStaffIdCard(staffIdCard);
|
||||
result.setStaffName(firstNonBlank(staffRows, CcdiProjectOverviewEmployeeHitRowVO::getStaffName));
|
||||
result.setStaffCode(firstNonBlank(staffRows, CcdiProjectOverviewEmployeeHitRowVO::getStaffCode));
|
||||
result.setDeptId(firstNonNull(staffRows, CcdiProjectOverviewEmployeeHitRowVO::getDeptId));
|
||||
result.setDeptName(firstNonBlank(staffRows, CcdiProjectOverviewEmployeeHitRowVO::getDeptName));
|
||||
result.setRuleCount(ruleSummaries.size());
|
||||
result.setModelCount(modelSummaries.size());
|
||||
result.setHitCount(staffRows.size());
|
||||
result.setRiskLevelCode(resolveRiskLevelCode(ruleSummaries.size()));
|
||||
result.setRiskPoint(ruleSummaries.stream()
|
||||
.map(CcdiProjectOverviewEmployeeRuleSummaryVO::getRuleName)
|
||||
.filter(this::isNotBlank)
|
||||
.collect(Collectors.joining("、")));
|
||||
result.setModelCodesCsv(modelSummaries.stream()
|
||||
.map(CcdiProjectOverviewEmployeeModelSummaryVO::getModelCode)
|
||||
.collect(Collectors.joining(",")));
|
||||
result.setModelNamesJson(JSON.toJSONString(modelSummaries.stream()
|
||||
.map(CcdiProjectOverviewEmployeeModelSummaryVO::getModelName)
|
||||
.toList()));
|
||||
result.setHitRulesJson(JSON.toJSONString(ruleSummaries));
|
||||
result.setModelHitSummaryJson(JSON.toJSONString(modelSummaries));
|
||||
result.setCreateBy(operator);
|
||||
result.setCreateTime(now);
|
||||
result.setUpdateBy(operator);
|
||||
result.setUpdateTime(now);
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<CcdiProjectOverviewEmployeeRuleSummaryVO> buildRuleSummaries(
|
||||
List<CcdiProjectOverviewEmployeeHitRowVO> staffRows
|
||||
) {
|
||||
return staffRows.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
CcdiProjectOverviewEmployeeHitRowVO::getRuleCode,
|
||||
LinkedHashMap::new,
|
||||
Collectors.toList()
|
||||
))
|
||||
.values()
|
||||
.stream()
|
||||
.map(rows -> {
|
||||
CcdiProjectOverviewEmployeeRuleSummaryVO summary = new CcdiProjectOverviewEmployeeRuleSummaryVO();
|
||||
CcdiProjectOverviewEmployeeHitRowVO first = rows.getFirst();
|
||||
summary.setRuleCode(first.getRuleCode());
|
||||
summary.setRuleName(first.getRuleName());
|
||||
summary.setRiskLevel(first.getRiskLevel());
|
||||
summary.setWarningCount(rows.size());
|
||||
return summary;
|
||||
})
|
||||
.sorted(Comparator.comparing(CcdiProjectOverviewEmployeeRuleSummaryVO::getWarningCount).reversed()
|
||||
.thenComparing(CcdiProjectOverviewEmployeeRuleSummaryVO::getRuleCode))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<CcdiProjectOverviewEmployeeModelSummaryVO> buildModelSummaries(
|
||||
List<CcdiProjectOverviewEmployeeHitRowVO> staffRows
|
||||
) {
|
||||
return staffRows.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
CcdiProjectOverviewEmployeeHitRowVO::getModelCode,
|
||||
LinkedHashMap::new,
|
||||
Collectors.toList()
|
||||
))
|
||||
.values()
|
||||
.stream()
|
||||
.map(rows -> {
|
||||
CcdiProjectOverviewEmployeeModelSummaryVO summary = new CcdiProjectOverviewEmployeeModelSummaryVO();
|
||||
CcdiProjectOverviewEmployeeHitRowVO first = rows.getFirst();
|
||||
summary.setModelCode(first.getModelCode());
|
||||
summary.setModelName(first.getModelName());
|
||||
summary.setWarningCount(rows.size());
|
||||
return summary;
|
||||
})
|
||||
.sorted(Comparator.comparing(CcdiProjectOverviewEmployeeModelSummaryVO::getModelCode))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private String resolveRiskLevelCode(int ruleCount) {
|
||||
if (ruleCount >= 5) {
|
||||
return "HIGH";
|
||||
}
|
||||
if (ruleCount >= 2) {
|
||||
return "MEDIUM";
|
||||
}
|
||||
return "LOW";
|
||||
}
|
||||
|
||||
private String firstNonBlank(List<CcdiProjectOverviewEmployeeHitRowVO> staffRows,
|
||||
java.util.function.Function<CcdiProjectOverviewEmployeeHitRowVO, String> getter) {
|
||||
return staffRows.stream()
|
||||
.map(getter)
|
||||
.filter(this::isNotBlank)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private <T> T firstNonNull(List<CcdiProjectOverviewEmployeeHitRowVO> staffRows,
|
||||
java.util.function.Function<CcdiProjectOverviewEmployeeHitRowVO, T> getter) {
|
||||
return staffRows.stream()
|
||||
.map(getter)
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private boolean isNotBlank(String value) {
|
||||
return value != null && !value.isBlank();
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,58 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="remark" column="remark"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="CcdiProjectOverviewEmployeeHitRowMap"
|
||||
type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewEmployeeHitRowVO">
|
||||
<result property="projectId" column="project_id"/>
|
||||
<result property="staffIdCard" column="staff_id_card"/>
|
||||
<result property="staffName" column="staff_name"/>
|
||||
<result property="staffCode" column="staff_code"/>
|
||||
<result property="deptId" column="dept_id"/>
|
||||
<result property="deptName" column="dept_name"/>
|
||||
<result property="modelCode" column="model_code"/>
|
||||
<result property="modelName" column="model_name"/>
|
||||
<result property="ruleCode" column="rule_code"/>
|
||||
<result property="ruleName" column="rule_name"/>
|
||||
<result property="riskLevel" column="risk_level"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="resolvedEmployeeHitRowsSql">
|
||||
select distinct
|
||||
tr.project_id,
|
||||
coalesce(direct_staff.id_card, statement_staff.id_card, family_staff.id_card) as staff_id_card,
|
||||
coalesce(direct_staff.name, statement_staff.name, family_staff.name) as staff_name,
|
||||
cast(coalesce(direct_staff.staff_id, statement_staff.staff_id, family_staff.staff_id) as char) as staff_code,
|
||||
coalesce(direct_staff.dept_id, statement_staff.dept_id, family_staff.dept_id) as dept_id,
|
||||
dept.dept_name,
|
||||
tr.model_code,
|
||||
tr.model_name,
|
||||
tr.rule_code,
|
||||
tr.rule_name,
|
||||
tr.risk_level
|
||||
from ccdi_bank_statement_tag_result tr
|
||||
left join ccdi_base_staff direct_staff
|
||||
on tr.object_type = 'STAFF_ID_CARD'
|
||||
and tr.object_key = direct_staff.id_card
|
||||
left join ccdi_bank_statement bs
|
||||
on tr.bank_statement_id = bs.bank_statement_id
|
||||
left join ccdi_base_staff statement_staff
|
||||
on (tr.object_key is null or tr.object_key = '')
|
||||
and bs.cret_no = statement_staff.id_card
|
||||
left join ccdi_staff_fmy_relation relation
|
||||
on relation.status = 1
|
||||
and (
|
||||
((tr.object_key is null or tr.object_key = '') and bs.cret_no = relation.relation_cert_no)
|
||||
or ((tr.object_key is not null and tr.object_key != '') and tr.object_type != 'STAFF_ID_CARD'
|
||||
and tr.object_key = relation.relation_cert_no)
|
||||
)
|
||||
left join ccdi_base_staff family_staff
|
||||
on relation.person_id = family_staff.id_card
|
||||
left join sys_dept dept
|
||||
on dept.dept_id = coalesce(direct_staff.dept_id, statement_staff.dept_id, family_staff.dept_id)
|
||||
where tr.project_id = #{projectId}
|
||||
and coalesce(direct_staff.id_card, statement_staff.id_card, family_staff.id_card) is not null
|
||||
</sql>
|
||||
|
||||
<delete id="deleteByProjectId">
|
||||
delete from ccdi_project_overview_employee_result
|
||||
where project_id = #{projectId}
|
||||
@@ -97,4 +149,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
order by id asc
|
||||
</select>
|
||||
|
||||
<select id="selectEmployeeHitRowsByProjectId" resultMap="CcdiProjectOverviewEmployeeHitRowMap">
|
||||
<include refid="resolvedEmployeeHitRowsSql"/>
|
||||
order by staff_id_card asc, model_code asc, rule_code asc
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.ruoyi.ccdi.project.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
class CcdiProjectOverviewEmployeeResultBuilderTest {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void shouldAggregateHitRowsIntoEmployeeResultSnapshots() throws Exception {
|
||||
Class<?> rowClass = Class.forName("com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewEmployeeHitRowVO");
|
||||
Class<?> builderClass = Class.forName("com.ruoyi.ccdi.project.service.impl.CcdiProjectOverviewEmployeeResultBuilder");
|
||||
|
||||
Object builder = builderClass.getDeclaredConstructor().newInstance();
|
||||
Method buildMethod = builderClass.getMethod("build", Long.class, List.class, String.class);
|
||||
|
||||
List<Object> hitRows = List.of(
|
||||
buildHitRow(rowClass, "330000000000000001", "李四", "E1001", 12L, "信息二部",
|
||||
"LARGE_TRANSACTION", "大额交易", "HOUSE_OR_CAR_EXPENSE", "房车消费支出交易", "HIGH"),
|
||||
buildHitRow(rowClass, "330000000000000001", "李四", "E1001", 12L, "信息二部",
|
||||
"LARGE_TRANSACTION", "大额交易", "HOUSE_OR_CAR_EXPENSE", "房车消费支出交易", "HIGH"),
|
||||
buildHitRow(rowClass, "330000000000000001", "李四", "E1001", 12L, "信息二部",
|
||||
"LARGE_TRANSACTION", "大额交易", "TAX_EXPENSE", "税务支出交易", "HIGH"),
|
||||
buildHitRow(rowClass, "330000000000000001", "李四", "E1001", 12L, "信息二部",
|
||||
"ABNORMAL_TRANSACTION", "异常交易", "ABNORMAL_CUSTOMER_TRANSACTION", "异常客户交易", "HIGH"),
|
||||
buildHitRow(rowClass, "330000000000000001", "李四", "E1001", 12L, "信息二部",
|
||||
"ABNORMAL_TRANSACTION", "异常交易", "ABNORMAL_CUSTOMER_TRANSACTION", "异常客户交易", "HIGH"),
|
||||
buildHitRow(rowClass, "330000000000000001", "李四", "E1001", 12L, "信息二部",
|
||||
"SUSPICIOUS_PART_TIME", "可疑兼职", "MONTHLY_FIXED_INCOME", "疑似兼职", "MEDIUM"),
|
||||
buildHitRow(rowClass, "330000000000000001", "李四", "E1001", 12L, "信息二部",
|
||||
"SUSPICIOUS_PROPERTY", "可疑财产", "HOUSE_REGISTRATION_MISMATCH", "房产登记不匹配", "LOW")
|
||||
);
|
||||
|
||||
List<CcdiProjectOverviewEmployeeResult> results =
|
||||
(List<CcdiProjectOverviewEmployeeResult>) buildMethod.invoke(builder, 40L, hitRows, "tester");
|
||||
|
||||
assertEquals(1, results.size());
|
||||
CcdiProjectOverviewEmployeeResult result = results.getFirst();
|
||||
assertEquals(40L, result.getProjectId());
|
||||
assertEquals("330000000000000001", result.getStaffIdCard());
|
||||
assertEquals("李四", result.getStaffName());
|
||||
assertEquals("E1001", result.getStaffCode());
|
||||
assertEquals(12L, result.getDeptId());
|
||||
assertEquals("信息二部", result.getDeptName());
|
||||
assertEquals(5, result.getRuleCount());
|
||||
assertEquals(4, result.getModelCount());
|
||||
assertEquals(7, result.getHitCount());
|
||||
assertEquals("HIGH", result.getRiskLevelCode());
|
||||
assertEquals("ABNORMAL_TRANSACTION,LARGE_TRANSACTION,SUSPICIOUS_PART_TIME,SUSPICIOUS_PROPERTY",
|
||||
result.getModelCodesCsv());
|
||||
assertNotNull(result.getRiskPoint());
|
||||
|
||||
JSONArray modelNames = JSON.parseArray(result.getModelNamesJson());
|
||||
assertEquals(List.of("异常交易", "大额交易", "可疑兼职", "可疑财产"),
|
||||
modelNames.toList(String.class));
|
||||
|
||||
JSONArray hitRules = JSON.parseArray(result.getHitRulesJson());
|
||||
assertEquals(5, hitRules.size());
|
||||
JSONObject firstRule = hitRules.getJSONObject(0);
|
||||
assertEquals("ABNORMAL_CUSTOMER_TRANSACTION", firstRule.getString("ruleCode"));
|
||||
assertEquals("异常客户交易", firstRule.getString("ruleName"));
|
||||
assertEquals("HIGH", firstRule.getString("riskLevel"));
|
||||
assertEquals(2, firstRule.getIntValue("warningCount"));
|
||||
|
||||
JSONArray modelSummary = JSON.parseArray(result.getModelHitSummaryJson());
|
||||
Map<String, Integer> warningCountByModel = modelSummary.toList(JSONObject.class).stream()
|
||||
.collect(java.util.stream.Collectors.toMap(
|
||||
item -> item.getString("modelCode"),
|
||||
item -> item.getIntValue("warningCount")
|
||||
));
|
||||
assertEquals(2, warningCountByModel.get("ABNORMAL_TRANSACTION"));
|
||||
assertEquals(3, warningCountByModel.get("LARGE_TRANSACTION"));
|
||||
assertEquals(1, warningCountByModel.get("SUSPICIOUS_PROPERTY"));
|
||||
assertEquals(1, warningCountByModel.get("SUSPICIOUS_PART_TIME"));
|
||||
}
|
||||
|
||||
private Object buildHitRow(Class<?> rowClass,
|
||||
String staffIdCard,
|
||||
String staffName,
|
||||
String staffCode,
|
||||
Long deptId,
|
||||
String deptName,
|
||||
String modelCode,
|
||||
String modelName,
|
||||
String ruleCode,
|
||||
String ruleName,
|
||||
String riskLevel) throws Exception {
|
||||
Object row = rowClass.getDeclaredConstructor().newInstance();
|
||||
setField(rowClass, row, "setProjectId", Long.class, 40L);
|
||||
setField(rowClass, row, "setStaffIdCard", String.class, staffIdCard);
|
||||
setField(rowClass, row, "setStaffName", String.class, staffName);
|
||||
setField(rowClass, row, "setStaffCode", String.class, staffCode);
|
||||
setField(rowClass, row, "setDeptId", Long.class, deptId);
|
||||
setField(rowClass, row, "setDeptName", String.class, deptName);
|
||||
setField(rowClass, row, "setModelCode", String.class, modelCode);
|
||||
setField(rowClass, row, "setModelName", String.class, modelName);
|
||||
setField(rowClass, row, "setRuleCode", String.class, ruleCode);
|
||||
setField(rowClass, row, "setRuleName", String.class, ruleName);
|
||||
setField(rowClass, row, "setRiskLevel", String.class, riskLevel);
|
||||
return row;
|
||||
}
|
||||
|
||||
private void setField(Class<?> rowClass, Object row, String methodName, Class<?> parameterType, Object value)
|
||||
throws Exception {
|
||||
Method method = rowClass.getMethod(methodName, parameterType);
|
||||
method.invoke(row, value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user