From a52fb35bd32b4df6ae2a5ff7ba8c8af08b97a2fa Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Wed, 25 Mar 2026 15:15:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=BB=93=E6=9E=9C=E6=80=BB?= =?UTF-8?q?=E8=A7=88=E8=AF=A6=E6=83=85=E5=BC=B9=E7=AA=97=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...cdiProjectPersonAnalysisObjectFieldVO.java | 14 +++ ...diProjectPersonAnalysisObjectRecordVO.java | 4 +- .../impl/CcdiProjectOverviewServiceImpl.java | 46 ++++++++++ .../project/CcdiProjectOverviewMapper.xml | 87 +++++++++++++++++++ .../CcdiProjectOverviewMapperSqlTest.java | 22 +++++ .../CcdiProjectOverviewServiceImplTest.java | 17 ++++ ...alog-real-detail-backend-implementation.md | 55 ++++++++++++ ...dialog-real-detail-backend-verification.md | 42 +++++++++ 8 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectPersonAnalysisObjectFieldVO.java create mode 100644 docs/reports/implementation/2026-03-25-results-overview-project-analysis-dialog-real-detail-backend-implementation.md create mode 100644 docs/tests/records/2026-03-25-results-overview-project-analysis-dialog-real-detail-backend-verification.md diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectPersonAnalysisObjectFieldVO.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectPersonAnalysisObjectFieldVO.java new file mode 100644 index 00000000..8f36ea46 --- /dev/null +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectPersonAnalysisObjectFieldVO.java @@ -0,0 +1,14 @@ +package com.ruoyi.ccdi.project.domain.vo; + +import lombok.Data; + +/** + * 项目分析对象型异常补充字段 + */ +@Data +public class CcdiProjectPersonAnalysisObjectFieldVO { + + private String label; + + private String value; +} diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectPersonAnalysisObjectRecordVO.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectPersonAnalysisObjectRecordVO.java index 984e20e4..9efb7605 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectPersonAnalysisObjectRecordVO.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectPersonAnalysisObjectRecordVO.java @@ -1,7 +1,7 @@ package com.ruoyi.ccdi.project.domain.vo; +import java.util.ArrayList; import java.util.List; -import java.util.Map; import lombok.Data; /** @@ -18,5 +18,5 @@ public class CcdiProjectPersonAnalysisObjectRecordVO { private String summary; - private List> extraFields; + private List extraFields = new ArrayList<>(); } diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java index 584a00da..b28c0401 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java @@ -6,6 +6,7 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO; import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO; +import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisAbnormalDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisAbnormalGroupVO; @@ -23,14 +24,18 @@ 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.CcdiBankTagResultMapper; import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewEmployeeResultMapper; 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.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -51,6 +56,9 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi @Resource private CcdiProjectOverviewEmployeeResultMapper overviewEmployeeResultMapper; + @Resource + private CcdiBankTagResultMapper bankTagResultMapper; + @Resource private CcdiProjectOverviewEmployeeResultBuilder overviewEmployeeResultBuilder; @@ -124,6 +132,8 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi queryDTO.getProjectId(), queryDTO.getStaffIdCard() )); + attachStatementHitTags(statementRows, queryDTO.getProjectId()); + normalizeObjectRows(objectRows); CcdiProjectPersonAnalysisDetailVO detail = new CcdiProjectPersonAnalysisDetailVO(); detail.setBasicInfo(basicInfo == null ? new CcdiProjectPersonAnalysisBasicInfoVO() : basicInfo); @@ -314,6 +324,42 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi return group; } + private void attachStatementHitTags(List statementRows, Long projectId) { + if (statementRows.isEmpty() || projectId == null) { + return; + } + List bankStatementIds = statementRows.stream() + .map(CcdiBankStatementListVO::getBankStatementId) + .filter(item -> item != null) + .distinct() + .collect(Collectors.toList()); + if (bankStatementIds.isEmpty()) { + return; + } + Map> hitTagMap = defaultList( + bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(projectId, bankStatementIds) + ).stream().filter(item -> item.getBankStatementId() != null) + .collect(Collectors.groupingBy( + CcdiBankStatementHitTagVO::getBankStatementId, + LinkedHashMap::new, + Collectors.toList() + )); + statementRows.forEach(row -> row.setHitTags(new ArrayList<>( + hitTagMap.getOrDefault(row.getBankStatementId(), Collections.emptyList()) + ))); + } + + private void normalizeObjectRows(List objectRows) { + objectRows.forEach(row -> { + if (row.getRiskTags() == null) { + row.setRiskTags(new ArrayList<>()); + } + if (row.getExtraFields() == null) { + row.setExtraFields(new ArrayList<>()); + } + }); + } + private CcdiProject getRequiredProject(Long projectId) { CcdiProject project = projectMapper.selectById(projectId); if (project == null) { diff --git a/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml b/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml index b2b8e3d3..0d039b0b 100644 --- a/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml +++ b/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml @@ -377,6 +377,93 @@ json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].ruleCode'))) asc + + + + + + = 0, "missing select: " + selectId); int endIndex = xml.indexOf("", startIndex); + assertTrue(endIndex >= 0, "missing closing select tag: " + selectId); return xml.substring(startIndex, endIndex); } } diff --git a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImplTest.java b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImplTest.java index 5a895cc0..7c2ee2a8 100644 --- a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImplTest.java +++ b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImplTest.java @@ -20,6 +20,7 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO; import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper; +import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper; import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewEmployeeResultMapper; import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewMapper; import com.ruoyi.common.exception.ServiceException; @@ -56,6 +57,9 @@ class CcdiProjectOverviewServiceImplTest { @Mock private CcdiProjectOverviewEmployeeResultMapper overviewEmployeeResultMapper; + @Mock + private CcdiBankTagResultMapper bankTagResultMapper; + @Mock private CcdiProjectOverviewEmployeeResultBuilder overviewEmployeeResultBuilder; @@ -173,6 +177,15 @@ class CcdiProjectOverviewServiceImplTest { when(overviewMapper.selectPersonAnalysisStatementRows(40L, "330000000000000001")) .thenReturn(List.of(statementRow)); + com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO hitTag = + new com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO(); + hitTag.setBankStatementId(1L); + hitTag.setRuleCode("RULE_A"); + hitTag.setRuleName("大额转账"); + hitTag.setRiskLevel("HIGH"); + when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(40L, List.of(1L))) + .thenReturn(List.of(hitTag)); + CcdiProjectPersonAnalysisObjectRecordVO objectRow = new CcdiProjectPersonAnalysisObjectRecordVO(); objectRow.setTitle("张三"); objectRow.setSubtitle("关联人员"); @@ -191,6 +204,10 @@ class CcdiProjectOverviewServiceImplTest { assertEquals(2, result.getAbnormalDetail().getGroups().size()); assertEquals("BANK_STATEMENT", result.getAbnormalDetail().getGroups().get(0).getGroupType()); assertEquals("OBJECT", result.getAbnormalDetail().getGroups().get(1).getGroupType()); + List statementRecords = result.getAbnormalDetail().getGroups().get(0).getRecords(); + assertEquals(1, ((CcdiBankStatementListVO) statementRecords.getFirst()).getHitTags().size()); + List objectRecords = result.getAbnormalDetail().getGroups().get(1).getRecords(); + assertNotNull(((CcdiProjectPersonAnalysisObjectRecordVO) objectRecords.getFirst()).getExtraFields()); } @Test diff --git a/docs/reports/implementation/2026-03-25-results-overview-project-analysis-dialog-real-detail-backend-implementation.md b/docs/reports/implementation/2026-03-25-results-overview-project-analysis-dialog-real-detail-backend-implementation.md new file mode 100644 index 00000000..7538ed31 --- /dev/null +++ b/docs/reports/implementation/2026-03-25-results-overview-project-analysis-dialog-real-detail-backend-implementation.md @@ -0,0 +1,55 @@ +# 结果总览项目分析弹窗真实详情后端实施记录 + +**日期**: 2026-03-25 +**模块**: 初核项目详情 - 结果总览 + +## 本次实现 + +- 在结果总览控制器下新增详情接口 `GET /ccdi/project/overview/person-analysis/detail` +- 新增详情查询 DTO 与 VO 结构,统一承载: + - 人员基础信息 + - 异常明细分组 +- 在结果总览 Mapper 中新增 3 个详情查询入口: + - `selectPersonAnalysisBasicInfo` + - `selectPersonAnalysisStatementRows` + - `selectPersonAnalysisObjectRows` +- 在服务层新增详情组装逻辑: + - 基础信息查询 + - `BANK_STATEMENT` 分组组装 + - `OBJECT` 分组组装 + - 流水命中标签回填 + +## 主要文件 + +- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewController.java` +- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectOverviewService.java` +- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java` +- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapper.java` +- `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml` + +## 实现说明 + +### 1. 接口边界 + +- 详情能力继续收敛在结果总览域内,没有新建平行控制器 +- 控制器返回格式保持 `AjaxResult.success(data)` 一致 + +### 2. 基础信息来源 + +- 风险等级、工号、项目范围仍以 `ccdi_project_overview_employee_result` 为结果总览口径 +- 姓名、手机号、部门信息通过员工表与部门表补齐 + +### 3. 异常明细组装 + +- `BANK_STATEMENT` 分组直接复用流水详情字段口径 +- `OBJECT` 分组统一输出对象摘要记录 +- 服务层在返回前补齐: + - 流水命中标签 + - 对象记录默认空列表字段,避免前端拿到 `null` + +## 验证情况 + +- 已执行结果总览相关后端聚焦回归 +- 详情接口契约、服务层、Mapper SQL 和既有模型区回归全部通过 +- 详见: + - `docs/tests/records/2026-03-25-results-overview-project-analysis-dialog-real-detail-backend-verification.md` diff --git a/docs/tests/records/2026-03-25-results-overview-project-analysis-dialog-real-detail-backend-verification.md b/docs/tests/records/2026-03-25-results-overview-project-analysis-dialog-real-detail-backend-verification.md new file mode 100644 index 00000000..545265ea --- /dev/null +++ b/docs/tests/records/2026-03-25-results-overview-project-analysis-dialog-real-detail-backend-verification.md @@ -0,0 +1,42 @@ +# 结果总览项目分析弹窗真实详情后端验证记录 + +**日期**: 2026-03-25 +**模块**: 初核项目详情 - 结果总览 + +## 执行命令 + +```bash +mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewControllerContractTest,CcdiProjectOverviewServiceStructureTest +mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewServiceImplTest,CcdiProjectOverviewControllerTest +mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewMapperSqlTest,CcdiBankStatementMapperXmlTest +mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewControllerContractTest,CcdiProjectOverviewControllerTest,CcdiProjectOverviewServiceImplTest,CcdiProjectOverviewMapperSqlTest,CcdiProjectOverviewMapperRiskModelPeopleTest,CcdiProjectOverviewMapperRiskModelCardsTest +``` + +## 执行结果 + +- 上述命令全部执行成功 +- 新增详情接口契约测试通过 +- 服务层详情组装测试通过 +- 结果总览 Mapper 新增 3 个详情查询入口测试通过 +- 既有模型卡片与模型命中人员查询回归通过 + +## 关键验证点 + +1. 已新增 `GET /ccdi/project/overview/person-analysis/detail` +2. 详情接口入参固定为 `projectId + staffIdCard` +3. 返回结构包含: + - `basicInfo` + - `abnormalDetail.groups` +4. `basicInfo` 查询链路已关联: + - `ccdi_project_overview_employee_result` + - `ccdi_base_staff` + - `sys_dept` + - `ccdi_project` +5. `abnormalDetail.groups` 已支持: + - `BANK_STATEMENT` + - `OBJECT` +6. 流水型异常会附加真实命中标签列表 + +## 结论 + +结果总览项目分析弹窗后端真实详情链路已打通,且未影响既有结果总览模型区和人员区查询能力。