实现结果总览风险接口并完成回写联调

This commit is contained in:
wkc
2026-03-19 15:23:52 +08:00
parent 8ff6570ba8
commit cb8e144564
23 changed files with 1172 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
package com.ruoyi.ccdi.project.controller;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 结果总览控制器
*/
@RestController
@RequestMapping("/ccdi/project/overview")
@Tag(name = "项目结果总览")
public class CcdiProjectOverviewController extends BaseController {
@Resource
private ICcdiProjectOverviewService overviewService;
/**
* 查询风险仪表盘
*/
@GetMapping("/dashboard")
@Operation(summary = "查询风险仪表盘")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getDashboard(Long projectId) {
CcdiProjectOverviewDashboardVO dashboard = overviewService.getDashboard(projectId);
return AjaxResult.success(dashboard);
}
/**
* 查询风险人员总览
*/
@GetMapping("/risk-people")
@Operation(summary = "查询风险人员总览")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getRiskPeople(Long projectId) {
CcdiProjectRiskPeopleOverviewVO overview = overviewService.getRiskPeopleOverview(projectId);
return AjaxResult.success(overview);
}
/**
* 查询中高风险人员TOP10
*/
@GetMapping("/top-risk-people")
@Operation(summary = "查询中高风险人员TOP10")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getTopRiskPeople(Long projectId) {
CcdiProjectTopRiskPeopleVO topRiskPeople = overviewService.getTopRiskPeople(projectId);
return AjaxResult.success(topRiskPeople);
}
}

View File

@@ -0,0 +1,32 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
/**
* 员工风险聚合结果
*/
@Data
public class CcdiProjectEmployeeRiskAggregateVO {
private String staffIdCard;
private String staffName;
private Long deptId;
private String deptName;
private Integer ruleCount;
private Integer modelCount;
private String topRuleCode;
private String topRuleName;
private String riskLevelCode;
private String riskLevelName;
private Integer riskLevelSort;
}

View File

@@ -0,0 +1,17 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.util.List;
import lombok.Data;
/**
* 结果总览风险仪表盘
*/
@Data
public class CcdiProjectOverviewDashboardVO {
private String title;
private String subtitle;
private List<CcdiProjectOverviewStatVO> stats;
}

View File

@@ -0,0 +1,16 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
/**
* 结果总览统计项
*/
@Data
public class CcdiProjectOverviewStatVO {
private String key;
private String label;
private Integer value;
}

View File

@@ -0,0 +1,22 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
/**
* 风险人员总览项
*/
@Data
public class CcdiProjectRiskPeopleOverviewItemVO {
private String name;
private String idNo;
private String department;
private Integer riskCount;
private String riskPoint;
private String actionLabel;
}

View File

@@ -0,0 +1,13 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.util.List;
import lombok.Data;
/**
* 风险人员总览
*/
@Data
public class CcdiProjectRiskPeopleOverviewVO {
private List<CcdiProjectRiskPeopleOverviewItemVO> overviewList;
}

View File

@@ -0,0 +1,24 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
/**
* 中高风险人员项
*/
@Data
public class CcdiProjectTopRiskPeopleItemVO {
private String name;
private String idNo;
private String department;
private String riskLevel;
private String riskLevelType;
private Integer modelCount;
private String actionLabel;
}

View File

@@ -0,0 +1,13 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.util.List;
import lombok.Data;
/**
* 中高风险人员TOP10
*/
@Data
public class CcdiProjectTopRiskPeopleVO {
private List<CcdiProjectTopRiskPeopleItemVO> topRiskList;
}

View File

@@ -23,4 +23,20 @@ public interface CcdiProjectMapper extends BaseMapper<CcdiProject> {
* @return 分页结果
*/
Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, @Param("queryDTO") CcdiProjectQueryDTO queryDTO);
/**
* 更新项目风险人数
*
* @param projectId 项目ID
* @param highRiskCount 高风险人数
* @param mediumRiskCount 中风险人数
* @param lowRiskCount 低风险人数
* @param updateBy 更新人
* @return 更新行数
*/
int updateRiskCountsByProjectId(@Param("projectId") Long projectId,
@Param("highRiskCount") Integer highRiskCount,
@Param("mediumRiskCount") Integer mediumRiskCount,
@Param("lowRiskCount") Integer lowRiskCount,
@Param("updateBy") String updateBy);
}

View File

@@ -0,0 +1,47 @@
package com.ruoyi.ccdi.project.mapper;
import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 结果总览Mapper
*/
@Mapper
public interface CcdiProjectOverviewMapper {
/**
* 查询仪表盘基础数据
*
* @param projectId 项目ID
* @return 项目基础数据
*/
CcdiProject selectDashboardBaseByProjectId(@Param("projectId") Long projectId);
/**
* 查询风险人员总览
*
* @param projectId 项目ID
* @return 风险人员聚合列表
*/
List<CcdiProjectEmployeeRiskAggregateVO> selectRiskPeopleOverviewByProjectId(@Param("projectId") Long projectId);
/**
* 查询中高风险TOP10
*
* @param projectId 项目ID
* @return 中高风险人员列表
*/
List<CcdiProjectEmployeeRiskAggregateVO> selectTopRiskPeopleByProjectId(@Param("projectId") Long projectId);
/**
* 查询项目风险人数汇总
*
* @param projectId 项目ID
* @return 风险人数汇总
*/
Map<String, Object> selectRiskCountSummaryByProjectId(@Param("projectId") Long projectId);
}

View File

@@ -0,0 +1,43 @@
package com.ruoyi.ccdi.project.service;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
/**
* 结果总览服务接口
*/
public interface ICcdiProjectOverviewService {
/**
* 查询风险仪表盘
*
* @param projectId 项目ID
* @return 风险仪表盘
*/
CcdiProjectOverviewDashboardVO getDashboard(Long projectId);
/**
* 查询风险人员总览
*
* @param projectId 项目ID
* @return 风险人员总览
*/
CcdiProjectRiskPeopleOverviewVO getRiskPeopleOverview(Long projectId);
/**
* 查询中高风险人员TOP10
*
* @param projectId 项目ID
* @return 中高风险人员TOP10
*/
CcdiProjectTopRiskPeopleVO getTopRiskPeople(Long projectId);
/**
* 刷新项目风险人数
*
* @param projectId 项目ID
* @param operator 操作人
*/
void refreshProjectRiskCounts(Long projectId, String operator);
}

View File

@@ -14,6 +14,7 @@ import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
import com.ruoyi.ccdi.project.mapper.CcdiBankTagRuleMapper;
import com.ruoyi.ccdi.project.mapper.CcdiBankTagTaskMapper;
import com.ruoyi.ccdi.project.service.ICcdiBankTagService;
import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService;
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
@@ -59,6 +60,9 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
@Resource
private ICcdiProjectService projectService;
@Resource
private ICcdiProjectOverviewService projectOverviewService;
@Resource
@Qualifier("tagRuleExecutor")
private Executor tagRuleExecutor;
@@ -125,6 +129,8 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
resultMapper.insertBatch(allResults);
}
projectOverviewService.refreshProjectRiskCounts(projectId, operator);
task.setStatus(STATUS_SUCCESS);
task.setSuccessRuleCount(rules.size());
task.setFailedRuleCount(0);

View File

@@ -0,0 +1,179 @@
package com.ruoyi.ccdi.project.service.impl;
import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewStatVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewMapper;
import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService;
import com.ruoyi.common.exception.ServiceException;
import jakarta.annotation.Resource;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 结果总览服务实现
*/
@Service
public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewService {
private static final String ACTION_LABEL = "查看详情";
@Resource
private CcdiProjectOverviewMapper overviewMapper;
@Resource
private CcdiProjectMapper projectMapper;
@Override
public CcdiProjectOverviewDashboardVO getDashboard(Long projectId) {
CcdiProject project = overviewMapper.selectDashboardBaseByProjectId(projectId);
if (project == null) {
throw new ServiceException("项目不存在");
}
int targetCount = defaultZero(project.getTargetCount());
int highRiskCount = defaultZero(project.getHighRiskCount());
int mediumRiskCount = defaultZero(project.getMediumRiskCount());
int lowRiskCount = defaultZero(project.getLowRiskCount());
int noRiskCount = targetCount - highRiskCount - mediumRiskCount - lowRiskCount;
CcdiProjectOverviewDashboardVO dashboard = new CcdiProjectOverviewDashboardVO();
dashboard.setTitle("风险仪表盘");
dashboard.setSubtitle("风险仪表盘数据概览");
dashboard.setStats(List.of(
buildStat("people", "总人数", targetCount),
buildStat("riskPeople", "高风险", highRiskCount),
buildStat("medium", "中风险", mediumRiskCount),
buildStat("low", "低风险", lowRiskCount),
buildStat("count", "无风险人员", noRiskCount)
));
return dashboard;
}
@Override
public CcdiProjectRiskPeopleOverviewVO getRiskPeopleOverview(Long projectId) {
ensureProjectExists(projectId);
List<CcdiProjectRiskPeopleOverviewItemVO> overviewList = overviewMapper.selectRiskPeopleOverviewByProjectId(projectId)
.stream()
.map(this::buildRiskPeopleItem)
.toList();
CcdiProjectRiskPeopleOverviewVO overview = new CcdiProjectRiskPeopleOverviewVO();
overview.setOverviewList(overviewList);
return overview;
}
@Override
public CcdiProjectTopRiskPeopleVO getTopRiskPeople(Long projectId) {
ensureProjectExists(projectId);
List<CcdiProjectTopRiskPeopleItemVO> topRiskList = overviewMapper.selectTopRiskPeopleByProjectId(projectId)
.stream()
.map(this::buildTopRiskPeopleItem)
.toList();
CcdiProjectTopRiskPeopleVO topRiskPeople = new CcdiProjectTopRiskPeopleVO();
topRiskPeople.setTopRiskList(topRiskList);
return topRiskPeople;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void refreshProjectRiskCounts(Long projectId, String operator) {
getRequiredProject(projectId);
Map<String, Object> summary = overviewMapper.selectRiskCountSummaryByProjectId(projectId);
projectMapper.updateRiskCountsByProjectId(
projectId,
readCount(summary, "highRiskCount"),
readCount(summary, "mediumRiskCount"),
readCount(summary, "lowRiskCount"),
operator
);
}
private CcdiProjectRiskPeopleOverviewItemVO buildRiskPeopleItem(CcdiProjectEmployeeRiskAggregateVO aggregate) {
CcdiProjectRiskPeopleOverviewItemVO item = new CcdiProjectRiskPeopleOverviewItemVO();
item.setName(aggregate.getStaffName());
item.setIdNo(aggregate.getStaffIdCard());
item.setDepartment(aggregate.getDeptName());
item.setRiskCount(defaultZero(aggregate.getRuleCount()));
item.setRiskPoint(aggregate.getTopRuleName());
item.setActionLabel(ACTION_LABEL);
return item;
}
private CcdiProjectTopRiskPeopleItemVO buildTopRiskPeopleItem(CcdiProjectEmployeeRiskAggregateVO aggregate) {
CcdiProjectTopRiskPeopleItemVO item = new CcdiProjectTopRiskPeopleItemVO();
item.setName(aggregate.getStaffName());
item.setIdNo(aggregate.getStaffIdCard());
item.setDepartment(aggregate.getDeptName());
item.setRiskLevel(resolveRiskLevelName(aggregate.getRiskLevelCode()));
item.setRiskLevelType(resolveRiskLevelType(aggregate.getRiskLevelCode()));
item.setModelCount(defaultZero(aggregate.getModelCount()));
item.setActionLabel(ACTION_LABEL);
return item;
}
private void ensureProjectExists(Long projectId) {
getRequiredProject(projectId);
}
private CcdiProjectOverviewStatVO buildStat(String key, String label, Integer value) {
CcdiProjectOverviewStatVO stat = new CcdiProjectOverviewStatVO();
stat.setKey(key);
stat.setLabel(label);
stat.setValue(value);
return stat;
}
private Integer readCount(Map<String, Object> summary, String key) {
if (summary == null) {
return 0;
}
Object value = summary.get(key);
if (value == null) {
return 0;
}
if (value instanceof Number number) {
return number.intValue();
}
throw new ServiceException("项目风险人数统计结果类型异常");
}
private Integer defaultZero(Integer value) {
return value == null ? 0 : value;
}
private CcdiProject getRequiredProject(Long projectId) {
CcdiProject project = projectMapper.selectById(projectId);
if (project == null) {
throw new ServiceException("项目不存在");
}
return project;
}
private String resolveRiskLevelName(String riskLevelCode) {
return switch (riskLevelCode) {
case "HIGH" -> "高风险";
case "MEDIUM" -> "中风险";
default -> "低风险";
};
}
private String resolveRiskLevelType(String riskLevelCode) {
return switch (riskLevelCode) {
case "HIGH" -> "danger";
case "MEDIUM" -> "warning";
default -> "info";
};
}
}

View File

@@ -40,4 +40,15 @@
</where>
ORDER BY p.update_time DESC
</select>
<update id="updateRiskCountsByProjectId">
update ccdi_project
set high_risk_count = #{highRiskCount},
medium_risk_count = #{mediumRiskCount},
low_risk_count = #{lowRiskCount},
update_by = #{updateBy},
update_time = now()
where project_id = #{projectId}
and del_flag = '0'
</update>
</mapper>

View File

@@ -0,0 +1,175 @@
<?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.ccdi.project.mapper.CcdiProjectOverviewMapper">
<resultMap id="EmployeeRiskAggregateResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO">
<result property="staffIdCard" column="staff_id_card"/>
<result property="staffName" column="staff_name"/>
<result property="deptId" column="dept_id"/>
<result property="deptName" column="dept_name"/>
<result property="ruleCount" column="rule_count"/>
<result property="modelCount" column="model_count"/>
<result property="topRuleCode" column="top_rule_code"/>
<result property="topRuleName" column="top_rule_name"/>
<result property="riskLevelCode" column="risk_level_code"/>
<result property="riskLevelName" column="risk_level_name"/>
<result property="riskLevelSort" column="risk_level_sort"/>
</resultMap>
<sql id="resolvedEmployeeRiskBaseSql">
select distinct
tr.id,
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,
coalesce(direct_staff.dept_id, statement_staff.dept_id, family_staff.dept_id) as dept_id,
tr.rule_code,
tr.rule_name,
tr.model_code
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
where tr.project_id = #{projectId}
and coalesce(direct_staff.id_card, statement_staff.id_card, family_staff.id_card) is not null
</sql>
<sql id="employeeRiskAggregateSql">
select
agg.staff_id_card,
agg.staff_name,
agg.dept_id,
dept.dept_name,
agg.rule_count,
agg.model_count,
rule_pick.rule_code as top_rule_code,
rule_pick.rule_name as top_rule_name,
case
when agg.rule_count >= 5 then 'HIGH'
when agg.rule_count between 2 and 4 then 'MEDIUM'
else 'LOW'
end as risk_level_code,
case
when agg.rule_count >= 5 then '高风险'
when agg.rule_count between 2 and 4 then '中风险'
else '低风险'
end as risk_level_name,
case
when agg.rule_count >= 5 then 1
when agg.rule_count between 2 and 4 then 2
else 3
end as risk_level_sort
from (
select
base.staff_id_card,
max(base.staff_name) as staff_name,
max(base.dept_id) as dept_id,
count(distinct base.rule_code) as rule_count,
count(distinct base.model_code) as model_count
from (
<include refid="resolvedEmployeeRiskBaseSql"/>
) base
group by base.staff_id_card
) agg
left join sys_dept dept on agg.dept_id = dept.dept_id
left join (
select
chosen.staff_id_card,
chosen.rule_code,
chosen.rule_name
from (
select
grouped.staff_id_card,
grouped.rule_code,
grouped.rule_name,
grouped.hit_count
from (
select
base.staff_id_card,
base.rule_code,
max(base.rule_name) as rule_name,
count(1) as hit_count
from (
<include refid="resolvedEmployeeRiskBaseSql"/>
) base
group by base.staff_id_card, base.rule_code
) grouped
where not exists (
select 1
from (
select
base.staff_id_card,
base.rule_code,
max(base.rule_name) as rule_name,
count(1) as hit_count
from (
<include refid="resolvedEmployeeRiskBaseSql"/>
) base
group by base.staff_id_card, base.rule_code
) challenger
where challenger.staff_id_card = grouped.staff_id_card
and (
challenger.hit_count > grouped.hit_count
or (challenger.hit_count = grouped.hit_count
and challenger.rule_code &lt; grouped.rule_code)
)
)
) chosen
) rule_pick on rule_pick.staff_id_card = agg.staff_id_card
</sql>
<select id="selectDashboardBaseByProjectId" resultType="com.ruoyi.ccdi.project.domain.CcdiProject">
select
project_id,
project_name,
target_count,
high_risk_count,
medium_risk_count,
low_risk_count
from ccdi_project
where project_id = #{projectId}
and del_flag = '0'
</select>
<select id="selectRiskPeopleOverviewByProjectId" resultMap="EmployeeRiskAggregateResultMap">
<include refid="employeeRiskAggregateSql"/>
order by risk_level_sort asc, model_count desc, rule_count desc, staff_id_card asc
</select>
<select id="selectTopRiskPeopleByProjectId" resultMap="EmployeeRiskAggregateResultMap">
<include refid="employeeRiskAggregateSql"/>
where rule_count >= 2
order by risk_level_sort asc, model_count desc, rule_count desc, staff_id_card asc
limit 10
</select>
<select id="selectRiskCountSummaryByProjectId" resultType="map">
select
coalesce(sum(case when agg.rule_count >= 5 then 1 else 0 end), 0) as highRiskCount,
coalesce(sum(case when agg.rule_count between 2 and 4 then 1 else 0 end), 0) as mediumRiskCount,
coalesce(sum(case when agg.rule_count = 1 then 1 else 0 end), 0) as lowRiskCount
from (
select
base.staff_id_card,
count(distinct base.rule_code) as rule_count
from (
<include refid="resolvedEmployeeRiskBaseSql"/>
) base
group by base.staff_id_card
) agg
</select>
</mapper>