切换结果总览查询到员工结果表

This commit is contained in:
wkc
2026-03-22 11:52:09 +08:00
parent f539c4ba27
commit ef106169dc
6 changed files with 192 additions and 79 deletions

View File

@@ -8,6 +8,8 @@ import lombok.Data;
@Data @Data
public class CcdiProjectOverviewEmployeeRuleSummaryVO { public class CcdiProjectOverviewEmployeeRuleSummaryVO {
private String modelCode;
private String ruleCode; private String ruleCode;
private String ruleName; private String ruleName;

View File

@@ -103,6 +103,7 @@ public class CcdiProjectOverviewEmployeeResultBuilder {
.map(rows -> { .map(rows -> {
CcdiProjectOverviewEmployeeRuleSummaryVO summary = new CcdiProjectOverviewEmployeeRuleSummaryVO(); CcdiProjectOverviewEmployeeRuleSummaryVO summary = new CcdiProjectOverviewEmployeeRuleSummaryVO();
CcdiProjectOverviewEmployeeHitRowVO first = rows.getFirst(); CcdiProjectOverviewEmployeeHitRowVO first = rows.getFirst();
summary.setModelCode(first.getModelCode());
summary.setRuleCode(first.getRuleCode()); summary.setRuleCode(first.getRuleCode());
summary.setRuleName(first.getRuleName()); summary.setRuleName(first.getRuleName());
summary.setRiskLevel(first.getRiskLevel()); summary.setRiskLevel(first.getRiskLevel());

View File

@@ -33,6 +33,29 @@
select="selectRiskHitTagsByScope"/> select="selectRiskHitTagsByScope"/>
</resultMap> </resultMap>
<sql id="digitTableSql">
select 0 as digit
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9
</sql>
<sql id="jsonArrayIndexSql">
select ones.digit + tens.digit * 10 as idx
from (
<include refid="digitTableSql"/>
) ones
cross join (
<include refid="digitTableSql"/>
) tens
</sql>
<sql id="resolvedEmployeeRiskBaseSql"> <sql id="resolvedEmployeeRiskBaseSql">
select distinct select distinct
tr.id, tr.id,
@@ -186,14 +209,60 @@
</select> </select>
<select id="selectRiskPeopleOverviewByProjectId" resultMap="EmployeeRiskAggregateResultMap"> <select id="selectRiskPeopleOverviewByProjectId" resultMap="EmployeeRiskAggregateResultMap">
<include refid="employeeRiskAggregateSql"/> select
order by risk_level_sort asc, model_count desc, rule_count desc, staff_id_card asc result.staff_id_card,
result.staff_name,
result.dept_id,
result.dept_name,
result.rule_count,
result.model_count,
result.hit_count,
null as top_rule_code,
null as top_rule_name,
result.risk_point,
result.risk_level_code,
case
when result.risk_level_code = 'HIGH' then '高风险'
when result.risk_level_code = 'MEDIUM' then '中风险'
else '低风险'
end as risk_level_name,
case
when result.risk_level_code = 'HIGH' then 1
when result.risk_level_code = 'MEDIUM' then 2
else 3
end as risk_level_sort
from ccdi_project_overview_employee_result result
where result.project_id = #{projectId}
order by risk_level_sort asc, result.model_count desc, result.rule_count desc, result.staff_id_card asc
</select> </select>
<select id="selectTopRiskPeopleByProjectId" resultMap="EmployeeRiskAggregateResultMap"> <select id="selectTopRiskPeopleByProjectId" resultMap="EmployeeRiskAggregateResultMap">
<include refid="employeeRiskAggregateSql"/> select
where rule_count >= 2 result.staff_id_card,
order by risk_level_sort asc, model_count desc, rule_count desc, staff_id_card asc result.staff_name,
result.dept_id,
result.dept_name,
result.rule_count,
result.model_count,
result.hit_count,
null as top_rule_code,
null as top_rule_name,
result.risk_point,
result.risk_level_code,
case
when result.risk_level_code = 'HIGH' then '高风险'
when result.risk_level_code = 'MEDIUM' then '中风险'
else '低风险'
end as risk_level_name,
case
when result.risk_level_code = 'HIGH' then 1
when result.risk_level_code = 'MEDIUM' then 2
else 3
end as risk_level_sort
from ccdi_project_overview_employee_result result
where result.project_id = #{projectId}
and result.risk_level_code in ('HIGH', 'MEDIUM')
order by risk_level_sort asc, result.model_count desc, result.rule_count desc, result.staff_id_card asc
limit 10 limit 10
</select> </select>
@@ -213,13 +282,18 @@
) models ) models
left join ( left join (
select select
base.model_code, json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelCode'))) as model_code,
count(1) as warning_count, sum(cast(json_unquote(json_extract(
count(distinct base.staff_id_card) as people_count result.model_hit_summary_json,
from ( concat('$[', idx.idx, '].warningCount')
<include refid="resolvedEmployeeRiskBaseSql"/> )) as unsigned)) as warning_count,
) base count(distinct result.staff_id_card) as people_count
group by base.model_code from ccdi_project_overview_employee_result result
join (
<include refid="jsonArrayIndexSql"/>
) idx on idx.idx &lt; json_length(result.model_hit_summary_json)
where result.project_id = #{projectId}
group by json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelCode')))
) stats on models.model_code = stats.model_code ) stats on models.model_code = stats.model_code
order by warning_count desc, model_code asc order by warning_count desc, model_code asc
</select> </select>
@@ -227,73 +301,88 @@
<select id="selectRiskModelPeoplePage" resultMap="RiskModelPeopleItemResultMap"> <select id="selectRiskModelPeoplePage" resultMap="RiskModelPeopleItemResultMap">
<bind name="projectId" value="query.projectId"/> <bind name="projectId" value="query.projectId"/>
select select
base.project_id, result.project_id,
base.staff_id_card, result.staff_id_card,
max(base.staff_name) as staff_name, result.staff_name,
max(base.staff_code) as staff_code, result.staff_code,
max(dept.dept_name) as department, result.dept_name as department,
#{query.modelCodesCsv} as selected_model_codes #{query.modelCodesCsv} as selected_model_codes
from ( from ccdi_project_overview_employee_result result
<include refid="resolvedEmployeeRiskBaseSql"/>
) base
left join sys_dept dept on base.dept_id = dept.dept_id
where 1 = 1 where 1 = 1
and result.project_id = #{query.projectId}
<if test="query.modelCodes != null and query.modelCodes.size() > 0"> <if test="query.modelCodes != null and query.modelCodes.size() > 0">
and base.model_code in <choose>
<foreach collection="query.modelCodes" item="modelCode" open="(" separator="," close=")"> <when test="query.matchMode == 'ALL'">
#{modelCode} <foreach collection="query.modelCodes" item="modelCode">
and find_in_set(#{modelCode}, result.model_codes_csv)
</foreach> </foreach>
</when>
<otherwise>
and (
<foreach collection="query.modelCodes" item="modelCode" separator=" or ">
find_in_set(#{modelCode}, result.model_codes_csv)
</foreach>
)
</otherwise>
</choose>
</if> </if>
<if test="query.keyword != null and query.keyword != ''"> <if test="query.keyword != null and query.keyword != ''">
and ( and (
base.staff_name like concat('%', trim(#{query.keyword}), '%') result.staff_name like concat('%', trim(#{query.keyword}), '%')
or cast(base.staff_code as char) like concat('%', trim(#{query.keyword}), '%') or result.staff_code like concat('%', trim(#{query.keyword}), '%')
) )
</if> </if>
<if test="query.deptId != null"> <if test="query.deptId != null">
and base.dept_id = #{query.deptId} and result.dept_id = #{query.deptId}
</if> </if>
group by base.project_id, base.staff_id_card order by result.staff_name asc, result.staff_id_card asc
<if test="query.modelCodes != null and query.modelCodes.size() > 0 and query.matchMode == 'ALL'">
having count(distinct base.model_code) = #{query.modelCodesCount}
</if>
order by max(base.staff_name) asc, base.staff_id_card asc
</select> </select>
<select id="selectRiskModelNamesByScope" resultType="java.lang.String"> <select id="selectRiskModelNamesByScope" resultType="java.lang.String">
select scoped.model_name select
from ( json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelName'))) as model_name
<include refid="resolvedEmployeeRiskBaseSql"/> from ccdi_project_overview_employee_result result
) scoped join (
where scoped.project_id = #{projectId} <include refid="jsonArrayIndexSql"/>
and scoped.staff_id_card = #{staffIdCard} ) idx on idx.idx &lt; json_length(result.model_hit_summary_json)
where result.project_id = #{projectId}
and result.staff_id_card = #{staffIdCard}
<if test="selectedModelCodes != null and selectedModelCodes != ''"> <if test="selectedModelCodes != null and selectedModelCodes != ''">
and find_in_set(scoped.model_code, #{selectedModelCodes}) and find_in_set(
json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelCode'))),
#{selectedModelCodes}
)
</if> </if>
group by scoped.model_code, scoped.model_name group by
order by scoped.model_code asc json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelCode'))),
json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelName')))
order by json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelCode'))) asc
</select> </select>
<select id="selectRiskHitTagsByScope" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO"> <select id="selectRiskHitTagsByScope" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO">
select select
scoped.rule_code, json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].ruleCode'))) as rule_code,
max(scoped.rule_name) as rule_name, max(json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].ruleName')))) as rule_name,
max(scoped.risk_level) as risk_level max(json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].riskLevel')))) as risk_level
from ( from ccdi_project_overview_employee_result result
<include refid="resolvedEmployeeRiskBaseSql"/> join (
) scoped <include refid="jsonArrayIndexSql"/>
where scoped.project_id = #{projectId} ) idx on idx.idx &lt; json_length(result.hit_rules_json)
and scoped.staff_id_card = #{staffIdCard} where result.project_id = #{projectId}
and result.staff_id_card = #{staffIdCard}
<if test="selectedModelCodes != null and selectedModelCodes != ''"> <if test="selectedModelCodes != null and selectedModelCodes != ''">
and find_in_set(scoped.model_code, #{selectedModelCodes}) and find_in_set(
json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].modelCode'))),
#{selectedModelCodes}
)
</if> </if>
group by scoped.rule_code group by json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].ruleCode')))
order by case max(scoped.risk_level) order by case max(json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].riskLevel'))))
when 'HIGH' then 1 when 'HIGH' then 1
when 'MEDIUM' then 2 when 'MEDIUM' then 2
else 3 else 3
end, end,
scoped.rule_code asc json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].ruleCode'))) asc
</select> </select>
<select id="selectRiskCountSummaryByProjectId" resultType="map"> <select id="selectRiskCountSummaryByProjectId" resultType="map">

View File

@@ -17,18 +17,19 @@ class CcdiProjectOverviewMapperRiskModelCardsTest {
} }
@Test @Test
void shouldDefineRiskModelCardsSqlUsingEmployeeResolvedBase() throws Exception { void shouldDefineRiskModelCardsSqlUsingEmployeeResultSnapshot() throws Exception {
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml")); String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
assertTrue(xml.contains("<select id=\"selectRiskModelCardsByProjectId\"")); assertTrue(xml.contains("<select id=\"selectRiskModelCardsByProjectId\""));
assertTrue(xml.contains("from (")); assertTrue(xml.contains("from ccdi_project_overview_employee_result"));
assertTrue(xml.contains("from ccdi_bank_tag_rule")); assertTrue(xml.contains("from ccdi_bank_tag_rule"));
assertTrue(xml.contains("where enabled = 1")); assertTrue(xml.contains("where enabled = 1"));
assertTrue(xml.contains("left join (")); assertTrue(xml.contains("left join ("));
assertTrue(xml.contains("<include refid=\"resolvedEmployeeRiskBaseSql\"/>")); assertTrue(xml.contains("model_hit_summary_json"));
assertTrue(xml.contains("json_extract("));
assertTrue(xml.contains("coalesce(stats.warning_count, 0) as warning_count")); assertTrue(xml.contains("coalesce(stats.warning_count, 0) as warning_count"));
assertTrue(xml.contains("coalesce(stats.people_count, 0) as people_count")); assertTrue(xml.contains("coalesce(stats.people_count, 0) as people_count"));
assertTrue(xml.contains("count(1) as warning_count")); assertTrue(xml.contains(".warningCount"));
assertTrue(xml.contains("order by warning_count desc, model_code asc")); assertTrue(xml.contains("order by warning_count desc, model_code asc"));
} }
} }

View File

@@ -34,18 +34,19 @@ class CcdiProjectOverviewMapperRiskModelPeopleTest {
assertTrue(xml.contains("query.modelCodes != null and query.modelCodes.size() > 0")); assertTrue(xml.contains("query.modelCodes != null and query.modelCodes.size() > 0"));
assertTrue(xml.contains("query.matchMode == 'ALL'")); assertTrue(xml.contains("query.matchMode == 'ALL'"));
assertFalse(xml.contains("#{query.modelCodes.size}")); assertFalse(xml.contains("#{query.modelCodes.size}"));
assertTrue(xml.contains("count(distinct base.model_code) = #{query.modelCodesCount}")); assertTrue(xml.contains("find_in_set(#{modelCode}, result.model_codes_csv)"));
assertTrue(xml.contains("<bind name=\"projectId\" value=\"query.projectId\"/>")); assertTrue(xml.contains("<bind name=\"projectId\" value=\"query.projectId\"/>"));
assertTrue(xml.contains("base.staff_name like concat('%', trim(#{query.keyword}), '%')")); assertTrue(xml.contains("result.staff_name like concat('%', trim(#{query.keyword}), '%')"));
assertTrue(xml.contains("cast(base.staff_code as char) like concat('%', trim(#{query.keyword}), '%')")); assertTrue(xml.contains("result.staff_code like concat('%', trim(#{query.keyword}), '%')"));
assertTrue(xml.contains("base.dept_id = #{query.deptId}")); assertTrue(xml.contains("result.dept_id = #{query.deptId}"));
assertTrue(xml.contains("select=\"selectRiskModelNamesByScope\"")); assertTrue(xml.contains("select=\"selectRiskModelNamesByScope\""));
assertTrue(xml.contains("select=\"selectRiskHitTagsByScope\"")); assertTrue(xml.contains("select=\"selectRiskHitTagsByScope\""));
assertTrue(xml.contains("find_in_set(scoped.model_code, #{selectedModelCodes})")); assertTrue(xml.contains("model_hit_summary_json"));
assertFalse(xml.contains("select distinct scoped.model_name")); assertTrue(xml.contains("hit_rules_json"));
assertTrue(xml.contains("group by scoped.model_code, scoped.model_name")); assertTrue(xml.contains("json_extract("));
assertTrue(xml.contains("order by scoped.model_code asc")); assertTrue(xml.contains(".modelCode"));
assertTrue(xml.contains("order by case max(scoped.risk_level)")); assertTrue(xml.contains(".modelName"));
assertTrue(xml.contains("scoped.rule_code asc")); assertTrue(xml.contains(".ruleCode"));
assertTrue(xml.contains(".riskLevel"));
} }
} }

View File

@@ -11,18 +11,30 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class CcdiProjectOverviewMapperSqlTest { class CcdiProjectOverviewMapperSqlTest {
@Test @Test
void shouldContainEmployeeRiskAggregationSql() throws Exception { void shouldReadOverviewQueriesFromEmployeeResultTable() throws Exception {
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml")); String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
String riskPeopleSql = extractSelect(xml, "selectRiskPeopleOverviewByProjectId");
String topRiskPeopleSql = extractSelect(xml, "selectTopRiskPeopleByProjectId");
String riskModelCardsSql = extractSelect(xml, "selectRiskModelCardsByProjectId");
String riskModelPeopleSql = extractSelect(xml, "selectRiskModelPeoplePage");
assertTrue(xml.contains("count(distinct base.rule_code)")); assertTrue(riskPeopleSql.contains("from ccdi_project_overview_employee_result"));
assertTrue(xml.contains("count(distinct base.model_code)")); assertTrue(riskPeopleSql.contains("risk_level_code"));
assertTrue(xml.contains("count(1) as hit_count")); assertTrue(riskPeopleSql.contains("model_count"));
assertTrue(xml.contains("agg.hit_count")); assertTrue(riskPeopleSql.contains("risk_point"));
assertTrue(xml.contains("when agg.rule_count >= 5 then 'HIGH'")); assertFalse(riskPeopleSql.contains("resolvedEmployeeRiskBaseSql"));
assertTrue(xml.contains("when agg.rule_count between 2 and 4 then 'MEDIUM'"));
assertTrue(xml.contains("group_concat(")); assertTrue(topRiskPeopleSql.contains("from ccdi_project_overview_employee_result"));
assertTrue(xml.contains("as risk_point")); assertTrue(topRiskPeopleSql.contains("risk_level_code in ('HIGH', 'MEDIUM')"));
assertTrue(xml.contains("order by grouped.hit_count desc, grouped.rule_code asc")); assertFalse(topRiskPeopleSql.contains("resolvedEmployeeRiskBaseSql"));
assertTrue(riskModelCardsSql.contains("from ccdi_project_overview_employee_result"));
assertTrue(riskModelCardsSql.contains("model_hit_summary_json"));
assertFalse(riskModelCardsSql.contains("resolvedEmployeeRiskBaseSql"));
assertTrue(riskModelPeopleSql.contains("from ccdi_project_overview_employee_result"));
assertTrue(riskModelPeopleSql.contains("model_codes_csv"));
assertFalse(riskModelPeopleSql.contains("resolvedEmployeeRiskBaseSql"));
} }
@Test @Test
@@ -30,6 +42,13 @@ class CcdiProjectOverviewMapperSqlTest {
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml")); String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
assertFalse(xml.contains("row_number() over"), xml); assertFalse(xml.contains("row_number() over"), xml);
assertTrue(xml.contains("not exists"), xml); assertFalse(xml.contains("json_table("), xml);
}
private String extractSelect(String xml, String selectId) {
String start = "<select id=\"" + selectId + "\"";
int startIndex = xml.indexOf(start);
int endIndex = xml.indexOf("</select>", startIndex);
return xml.substring(startIndex, endIndex);
} }
} }