From 144897237bf9163c456a3ad88df3756624a3b7cf Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Thu, 19 Mar 2026 10:20:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B5=81=E6=B0=B4=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E6=A0=87=E7=AD=BE=E5=B1=95=E7=A4=BA=E4=B8=8E=E5=AF=BC?= =?UTF-8?q?=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/excel/CcdiBankStatementExcel.java | 4 + .../domain/vo/CcdiBankStatementDetailVO.java | 5 + .../domain/vo/CcdiBankStatementHitTagVO.java | 25 +++ .../domain/vo/CcdiBankStatementListVO.java | 5 + .../mapper/CcdiBankTagResultMapper.java | 13 ++ .../impl/CcdiBankStatementServiceImpl.java | 74 ++++++++- .../ccdi/project/CcdiBankTagResultMapper.xml | 25 +++ .../CcdiBankStatementHitTagsContractTest.java | 31 ++++ .../CcdiBankTagResultMapperXmlTest.java | 24 +++ .../CcdiBankStatementServiceImplTest.java | 58 +++++++ ...atement-hit-tags-backend-implementation.md | 26 ++++ ...tement-hit-tags-frontend-implementation.md | 22 +++ ...statement-hit-tags-backend-verification.md | 27 ++++ ...tatement-hit-tags-frontend-verification.md | 22 +++ .../components/detail/DetailQuery.vue | 145 +++++++++++++++++- .../unit/detail-query-detail-dialog.test.js | 10 ++ .../unit/detail-query-hit-tags-list.test.js | 19 +++ 17 files changed, 530 insertions(+), 5 deletions(-) create mode 100644 ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementHitTagVO.java create mode 100644 ccdi-project/src/test/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementHitTagsContractTest.java create mode 100644 docs/reports/implementation/2026-03-19-bank-statement-hit-tags-backend-implementation.md create mode 100644 docs/reports/implementation/2026-03-19-bank-statement-hit-tags-frontend-implementation.md create mode 100644 docs/tests/records/2026-03-19-bank-statement-hit-tags-backend-verification.md create mode 100644 docs/tests/records/2026-03-19-bank-statement-hit-tags-frontend-verification.md create mode 100644 ruoyi-ui/tests/unit/detail-query-hit-tags-list.test.js diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiBankStatementExcel.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiBankStatementExcel.java index 7f536bcd..ebbe7cfb 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiBankStatementExcel.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiBankStatementExcel.java @@ -41,6 +41,10 @@ public class CcdiBankStatementExcel { @Excel(name = "交易类型") private String cashType; + /** 异常标签 */ + @Excel(name = "异常标签") + private String hitTags; + /** 交易金额 */ @Excel(name = "交易金额") private BigDecimal displayAmount; diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementDetailVO.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementDetailVO.java index 0bbcd44e..f95bcbab 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementDetailVO.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementDetailVO.java @@ -3,7 +3,9 @@ package com.ruoyi.ccdi.project.domain.vo; import lombok.Data; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Date; +import java.util.List; /** * 流水明细详情VO @@ -96,4 +98,7 @@ public class CcdiBankStatementDetailVO { /** 原始文件上传时间 */ private Date uploadTime; + + /** 命中异常标签 */ + private List hitTags = new ArrayList<>(); } diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementHitTagVO.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementHitTagVO.java new file mode 100644 index 00000000..ad315692 --- /dev/null +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementHitTagVO.java @@ -0,0 +1,25 @@ +package com.ruoyi.ccdi.project.domain.vo; + +import lombok.Data; + +/** + * 流水命中异常标签VO + */ +@Data +public class CcdiBankStatementHitTagVO { + + /** 规则编码 */ + private String ruleCode; + + /** 规则名称 */ + private String ruleName; + + /** 风险等级 */ + private String riskLevel; + + /** 命中原因 */ + private String reasonDetail; + + /** 流水ID */ + private Long bankStatementId; +} diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementListVO.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementListVO.java index 74531cf0..f81aedd5 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementListVO.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementListVO.java @@ -3,6 +3,8 @@ package com.ruoyi.ccdi.project.domain.vo; import lombok.Data; import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; /** * 流水明细列表VO @@ -38,4 +40,7 @@ public class CcdiBankStatementListVO { /** 页面展示金额 */ private BigDecimal displayAmount; + + /** 命中异常标签 */ + private List hitTags = new ArrayList<>(); } diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagResultMapper.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagResultMapper.java index f1c3ebf4..9482af5b 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagResultMapper.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagResultMapper.java @@ -2,6 +2,7 @@ package com.ruoyi.ccdi.project.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagResult; +import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO; import org.apache.ibatis.annotations.Param; import java.util.List; @@ -20,6 +21,18 @@ public interface CcdiBankTagResultMapper extends BaseMapper { */ int deleteByProjectAndModel(@Param("projectId") Long projectId, @Param("modelCode") String modelCode); + /** + * 按项目和流水ID批量查询命中的异常标签 + * + * @param projectId 项目ID + * @param bankStatementIds 流水ID列表 + * @return 命中的异常标签列表 + */ + List selectStatementTagsByProjectAndStatementIds( + @Param("projectId") Long projectId, + @Param("bankStatementIds") List bankStatementIds + ); + /** * 批量插入结果 * diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImpl.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImpl.java index 5555027f..b2c9d798 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImpl.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImpl.java @@ -5,16 +5,21 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiBankStatementQueryDTO; import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO; +import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO; import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper; +import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper; import com.ruoyi.ccdi.project.service.ICcdiBankStatementService; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Set; -import java.util.Collections; +import java.util.Map; import java.util.stream.Collectors; /** @@ -31,6 +36,9 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService { @Resource private CcdiBankStatementMapper bankStatementMapper; + @Resource + private CcdiBankTagResultMapper bankTagResultMapper; + @Override public CcdiBankStatementFilterOptionsVO getFilterOptions(Long projectId) { CcdiBankStatementFilterOptionsVO options = bankStatementMapper.selectFilterOptions(projectId); @@ -42,7 +50,9 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService { CcdiBankStatementQueryDTO queryDTO) { CcdiBankStatementQueryDTO normalizedQuery = queryDTO == null ? new CcdiBankStatementQueryDTO() : queryDTO; normalizeQuery(normalizedQuery); - return bankStatementMapper.selectStatementPage(page, normalizedQuery); + Page result = bankStatementMapper.selectStatementPage(page, normalizedQuery); + attachHitTags(result == null ? Collections.emptyList() : result.getRecords(), normalizedQuery.getProjectId()); + return result; } @Override @@ -53,12 +63,58 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService { if (rows == null || rows.isEmpty()) { return Collections.emptyList(); } + attachHitTags(rows, normalizedQuery.getProjectId()); return rows.stream().map(this::toExcel).collect(Collectors.toList()); } @Override public CcdiBankStatementDetailVO getStatementDetail(Long bankStatementId) { - return bankStatementMapper.selectStatementDetailById(bankStatementId); + CcdiBankStatementDetailVO detail = bankStatementMapper.selectStatementDetailById(bankStatementId); + if (detail == null || detail.getProjectId() == null || detail.getBankStatementId() == null) { + return detail; + } + Map> hitTagMap = loadHitTagMap( + detail.getProjectId(), + List.of(detail.getBankStatementId()) + ); + detail.setHitTags(new ArrayList<>(hitTagMap.getOrDefault(detail.getBankStatementId(), Collections.emptyList()))); + return detail; + } + + private void attachHitTags(List rows, Long projectId) { + if (rows == null || rows.isEmpty() || projectId == null) { + return; + } + List bankStatementIds = rows.stream() + .map(CcdiBankStatementListVO::getBankStatementId) + .filter(item -> item != null) + .distinct() + .collect(Collectors.toList()); + if (bankStatementIds.isEmpty()) { + return; + } + Map> hitTagMap = loadHitTagMap(projectId, bankStatementIds); + rows.forEach(row -> row.setHitTags(new ArrayList<>( + hitTagMap.getOrDefault(row.getBankStatementId(), Collections.emptyList()) + ))); + } + + private Map> loadHitTagMap(Long projectId, List bankStatementIds) { + if (projectId == null || bankStatementIds == null || bankStatementIds.isEmpty()) { + return Collections.emptyMap(); + } + List hitTags = + bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(projectId, bankStatementIds); + if (hitTags == null || hitTags.isEmpty()) { + return Collections.emptyMap(); + } + return hitTags.stream() + .filter(item -> item.getBankStatementId() != null) + .collect(Collectors.groupingBy( + CcdiBankStatementHitTagVO::getBankStatementId, + LinkedHashMap::new, + Collectors.toList() + )); } private void normalizeQuery(CcdiBankStatementQueryDTO queryDTO) { @@ -138,7 +194,19 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService { excel.setCustomerAccountNo(row.getCustomerAccountNo()); excel.setUserMemo(row.getUserMemo()); excel.setCashType(row.getCashType()); + excel.setHitTags(formatHitTags(row.getHitTags())); excel.setDisplayAmount(row.getDisplayAmount()); return excel; } + + private String formatHitTags(List hitTags) { + if (hitTags == null || hitTags.isEmpty()) { + return ""; + } + return hitTags.stream() + .map(CcdiBankStatementHitTagVO::getRuleName) + .filter(item -> item != null && !item.isBlank()) + .distinct() + .collect(Collectors.joining("、")); + } } diff --git a/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagResultMapper.xml b/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagResultMapper.xml index 820d7375..5299ef35 100644 --- a/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagResultMapper.xml +++ b/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagResultMapper.xml @@ -29,6 +29,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + + + + + + delete from ccdi_bank_statement_tag_result where project_id = #{projectId} @@ -37,6 +45,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + insert into ccdi_bank_statement_tag_result ( project_id, model_code, model_name, rule_code, rule_name, indicator_code, diff --git a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementHitTagsContractTest.java b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementHitTagsContractTest.java new file mode 100644 index 00000000..01feff6a --- /dev/null +++ b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementHitTagsContractTest.java @@ -0,0 +1,31 @@ +package com.ruoyi.ccdi.project.domain.vo; + +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class CcdiBankStatementHitTagsContractTest { + + @Test + void listVo_shouldExposeHitTagsField() throws Exception { + Field field = CcdiBankStatementListVO.class.getDeclaredField("hitTags"); + + assertNotNull(field, "流水列表VO应返回命中异常标签"); + } + + @Test + void detailVo_shouldExposeHitTagsField() throws Exception { + Field field = CcdiBankStatementDetailVO.class.getDeclaredField("hitTags"); + + assertNotNull(field, "流水详情VO应返回命中异常标签"); + } + + @Test + void excelVo_shouldExposeHitTagsField() throws Exception { + Field field = com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel.class.getDeclaredField("hitTags"); + + assertNotNull(field, "流水导出对象应返回异常标签文本列"); + } +} diff --git a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagResultMapperXmlTest.java b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagResultMapperXmlTest.java index 5a57a8f4..92f43398 100644 --- a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagResultMapperXmlTest.java +++ b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagResultMapperXmlTest.java @@ -2,9 +2,12 @@ package com.ruoyi.ccdi.project.mapper; import org.junit.jupiter.api.Test; +import java.lang.reflect.Method; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; class CcdiBankTagResultMapperXmlTest { @@ -20,4 +23,25 @@ class CcdiBankTagResultMapperXmlTest { assertTrue(xml.contains("model_code = #{modelCode}")); } } + + @Test + void mapper_shouldExposeStatementTagQueryForBankStatementDetails() { + Method method = Arrays.stream(CcdiBankTagResultMapper.class.getDeclaredMethods()) + .filter(item -> "selectStatementTagsByProjectAndStatementIds".equals(item.getName())) + .findFirst() + .orElse(null); + + assertNotNull(method, "应提供按项目和流水ID批量查询异常标签的方法"); + } + + @Test + void xml_shouldDefineStatementTagQueryForBankStatementDetails() throws Exception { + try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(RESOURCE)) { + String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + + assertTrue(xml.contains("selectStatementTagsByProjectAndStatementIds"), xml); + assertTrue(xml.contains("bank_statement_id IN"), xml); + assertTrue(xml.contains("project_id = #{projectId}"), xml); + } + } } diff --git a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java index fcdf8f2e..a89b3e2f 100644 --- a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java +++ b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java @@ -5,9 +5,11 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiBankStatementQueryDTO; import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO; +import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementOptionVO; import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper; +import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -36,6 +38,9 @@ class CcdiBankStatementServiceImplTest { @Mock private CcdiBankStatementMapper bankStatementMapper; + @Mock + private CcdiBankTagResultMapper bankTagResultMapper; + @Test void getFilterOptions_shouldReturnProjectWideOptions() { CcdiBankStatementFilterOptionsVO options = new CcdiBankStatementFilterOptionsVO(); @@ -54,6 +59,7 @@ class CcdiBankStatementServiceImplTest { queryDTO.setProjectId(100L); queryDTO.setOrderBy("amount"); queryDTO.setOrderDirection("desc"); + page.setRecords(List.of(new CcdiBankStatementListVO())); doReturn(page).when(bankStatementMapper).selectStatementPage(eq(page), same(queryDTO)); service.selectStatementPage(page, queryDTO); @@ -111,14 +117,66 @@ class CcdiBankStatementServiceImplTest { assertEquals("6222", result.get(0).getLeAccountNo()); } + @Test + void selectStatementListForExport_shouldIncludeHitTagNames() { + CcdiBankStatementQueryDTO queryDTO = new CcdiBankStatementQueryDTO(); + queryDTO.setProjectId(43L); + CcdiBankStatementListVO row = new CcdiBankStatementListVO(); + row.setBankStatementId(51274L); + row.setLeAccountNo("6222"); + when(bankStatementMapper.selectStatementListForExport(same(queryDTO))).thenReturn(List.of(row)); + + CcdiBankStatementHitTagVO hitTag = new CcdiBankStatementHitTagVO(); + hitTag.setBankStatementId(51274L); + hitTag.setRuleName("大额存现交易"); + when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(43L, List.of(51274L))) + .thenReturn(List.of(hitTag)); + + List result = service.selectStatementListForExport(queryDTO); + + assertEquals(1, result.size()); + assertEquals("大额存现交易", result.get(0).getHitTags()); + } + + @Test + void selectStatementPage_shouldAttachHitTags() { + Page page = new Page<>(1, 10); + CcdiBankStatementQueryDTO queryDTO = new CcdiBankStatementQueryDTO(); + queryDTO.setProjectId(43L); + CcdiBankStatementListVO row = new CcdiBankStatementListVO(); + row.setBankStatementId(51274L); + page.setRecords(List.of(row)); + doReturn(page).when(bankStatementMapper).selectStatementPage(eq(page), same(queryDTO)); + + CcdiBankStatementHitTagVO hitTag = new CcdiBankStatementHitTagVO(); + hitTag.setBankStatementId(51274L); + hitTag.setRuleCode("LARGE_CASH_DEPOSIT"); + hitTag.setRuleName("大额存现交易"); + when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(43L, List.of(51274L))) + .thenReturn(List.of(hitTag)); + + Page result = service.selectStatementPage(page, queryDTO); + + assertEquals(1, result.getRecords().get(0).getHitTags().size()); + assertEquals("LARGE_CASH_DEPOSIT", result.getRecords().get(0).getHitTags().get(0).getRuleCode()); + } + @Test void getStatementDetail_shouldDelegateToMapper() { CcdiBankStatementDetailVO detailVO = new CcdiBankStatementDetailVO(); detailVO.setBankStatementId(200L); + detailVO.setProjectId(43L); when(bankStatementMapper.selectStatementDetailById(200L)).thenReturn(detailVO); + CcdiBankStatementHitTagVO hitTag = new CcdiBankStatementHitTagVO(); + hitTag.setBankStatementId(200L); + hitTag.setRuleName("大额存现交易"); + when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(43L, List.of(200L))) + .thenReturn(List.of(hitTag)); CcdiBankStatementDetailVO result = service.getStatementDetail(200L); assertSame(detailVO, result); + assertEquals(1, result.getHitTags().size()); + assertEquals("大额存现交易", result.getHitTags().get(0).getRuleName()); } } diff --git a/docs/reports/implementation/2026-03-19-bank-statement-hit-tags-backend-implementation.md b/docs/reports/implementation/2026-03-19-bank-statement-hit-tags-backend-implementation.md new file mode 100644 index 00000000..804b3efa --- /dev/null +++ b/docs/reports/implementation/2026-03-19-bank-statement-hit-tags-backend-implementation.md @@ -0,0 +1,26 @@ +# 流水明细异常标签后端实施记录 + +## 修改内容 +- 流水列表/详情返回异常标签 +- 导出流水返回异常标签列 +- 流水标签结果批量查询能力 +- 后端单元测试补充 + +## 实施说明 +- 新增 `CcdiBankStatementHitTagVO`,统一承载流水命中的异常标签编码、名称、风险等级和命中原因。 +- 在 `CcdiBankStatementListVO`、`CcdiBankStatementDetailVO` 中新增 `hitTags` 字段,作为流水列表页和详情弹窗的统一返回结构。 +- 在 `CcdiBankStatementExcel` 中新增“异常标签”导出列,导出时将命中的标签名称按顺序拼接为文本。 +- 在 `CcdiBankTagResultMapper` 中新增按 `projectId + bankStatementIds` 批量查询命中标签的方法,并在 XML 中补充对应 SQL。 +- 在 `CcdiBankStatementServiceImpl` 中补充标签挂载逻辑: + - 列表查询完成后,按当前页流水 ID 批量回查标签并分组挂载到每条记录。 + - 详情查询完成后,按当前流水 ID 回查标签并挂载到详情对象。 +- 导出查询完成后,复用同一套标签回查逻辑,将命中标签名称写入 Excel 导出对象。 +- 本次未改动流水标签判定规则,只修复“标签结果已入库但接口未返回”的数据链路缺口。 + +## 问题定位结论 +- 数据库中 `project_id = 43`、摘要为 `ATM现金存款` 的流水记录已存在 `LARGE_CASH_DEPOSIT` 等命中结果。 +- 原因是 `ccdi_bank_statement` 列表接口与详情接口此前未将 `ccdi_bank_statement_tag_result` 的命中标签组装到返回 VO 中,导致前端无法展示。 + +## 验证执行 +- 执行 `mvn -pl ccdi-project test -Dtest=CcdiBankTagResultMapperXmlTest,CcdiBankStatementHitTagsContractTest,CcdiBankStatementServiceImplTest`,结果通过。 +- 执行 `mvn -pl ccdi-project test -Dtest=CcdiBankStatementHitTagsContractTest,CcdiBankStatementServiceImplTest,CcdiBankStatementControllerTest`,结果通过。 diff --git a/docs/reports/implementation/2026-03-19-bank-statement-hit-tags-frontend-implementation.md b/docs/reports/implementation/2026-03-19-bank-statement-hit-tags-frontend-implementation.md new file mode 100644 index 00000000..04c6f4e2 --- /dev/null +++ b/docs/reports/implementation/2026-03-19-bank-statement-hit-tags-frontend-implementation.md @@ -0,0 +1,22 @@ +# 流水明细异常标签前端实施记录 + +## 修改内容 +- 列表异常标签列 +- 详情异常标签模块 +- 静态测试与构建验证 + +## 实施说明 +- 在 `DetailQuery.vue` 的列表表格中新增“异常标签”列,仅展示 `hitTags` 中的标签名称,并根据风险等级映射标签颜色。 +- 在详情弹窗中新增“命中异常标签”模块,展示标签名称、风险等级中文文案和命中原因摘要。 +- 列表接口返回值与详情接口返回值统一对 `hitTags` 做数组归一化,避免后端返回 `null` 时影响空态展示。 +- 为空标签场景补充统一空态文案,保持列表和详情展示一致。 + +## 验证执行 +- 执行 `node tests/unit/detail-query-filter-layout.test.js`,结果通过。 +- 执行 `node tests/unit/detail-query-detail-dialog.test.js`,结果通过。 +- 执行 `node tests/unit/detail-query-hit-tags-list.test.js`,结果通过。 +- 执行 `npm run build:prod`,结果通过,存在项目既有体积告警,但无新增编译错误。 + +## 接口说明 +- 本次未修改 `ruoyi-ui/src/api/ccdiProjectBankStatement.js`。 +- 原因:继续复用现有列表与详情接口,仅消费后端扩展后的响应数据结构,不新增前端请求入口。 diff --git a/docs/tests/records/2026-03-19-bank-statement-hit-tags-backend-verification.md b/docs/tests/records/2026-03-19-bank-statement-hit-tags-backend-verification.md new file mode 100644 index 00000000..740df0ba --- /dev/null +++ b/docs/tests/records/2026-03-19-bank-statement-hit-tags-backend-verification.md @@ -0,0 +1,27 @@ +# 流水明细异常标签后端验证记录 + +## 验证范围 +- 流水列表返回 `hitTags` +- 流水详情返回 `hitTags` +- 导出流水返回异常标签列 +- 标签结果 Mapper 查询能力 +- 已入库标签结果的链路核对 + +## 数据核对 +- 2026-03-19 查询 `ccdi_bank_statement`:`project_id = 43` 下存在摘要为 `ATM现金存款` 的流水记录 `bank_statement_id=51274`、`bank_statement_id=49342`。 +- 2026-03-19 查询 `ccdi_bank_statement_tag_result`:上述两条流水均存在 `LARGE_CASH_DEPOSIT`、`SINGLE_LARGE_INCOME` 命中结果。 + +## 自动验证 +- 2026-03-19 执行 `mvn -pl ccdi-project test -Dtest=CcdiBankTagResultMapperXmlTest,CcdiBankStatementHitTagsContractTest,CcdiBankStatementServiceImplTest`,结果:通过 +- 2026-03-19 执行 `mvn -pl ccdi-project test -Dtest=CcdiBankStatementHitTagsContractTest,CcdiBankStatementServiceImplTest,CcdiBankStatementControllerTest`,结果:通过 + +## 验证结果 +- [x] 列表查询结果可挂载命中异常标签 +- [x] 详情查询结果可挂载命中异常标签 +- [x] 导出对象包含“异常标签”文本列 +- [x] 标签结果 Mapper 支持按项目和流水 ID 批量回查 +- [x] `ATM现金存款` 对应流水的标签数据已在库内存在 + +## 联调建议 +- 前端刷新 `project_id=43` 的流水明细页面后,重点核对 `bank_statement_id=51274`、`bank_statement_id=49342` 是否显示“大额存现交易”等异常标签。 +- 使用相同筛选条件执行“导出流水”,核对导出文件中的“异常标签”列是否包含“大额存现交易”等标签名称。 diff --git a/docs/tests/records/2026-03-19-bank-statement-hit-tags-frontend-verification.md b/docs/tests/records/2026-03-19-bank-statement-hit-tags-frontend-verification.md new file mode 100644 index 00000000..c308b599 --- /dev/null +++ b/docs/tests/records/2026-03-19-bank-statement-hit-tags-frontend-verification.md @@ -0,0 +1,22 @@ +# 流水明细异常标签前端验证记录 + +## 验证范围 +- 列表异常标签列 +- 详情异常标签模块 +- 空态展示 +- 导出入口回归 + +## 自动验证 +- 2026-03-19 执行 `node tests/unit/detail-query-filter-layout.test.js`,结果:通过 +- 2026-03-19 执行 `node tests/unit/detail-query-detail-dialog.test.js`,结果:通过 +- 2026-03-19 执行 `node tests/unit/detail-query-hit-tags-list.test.js`,结果:通过 +- 2026-03-19 执行 `npm run build:prod`,结果:通过 + +## 验证结果 +- [x] 列表显示命中标签名称 +- [x] 详情显示名称、风险等级、命中原因摘要 +- [x] 无标签时显示空态 +- [ ] 导出入口仍可触发下载 + +## 联调待确认 +- 点击“导出流水”按钮后是否仍能正常触发下载 diff --git a/ruoyi-ui/src/views/ccdiProject/components/detail/DetailQuery.vue b/ruoyi-ui/src/views/ccdiProject/components/detail/DetailQuery.vue index 4ea4cc51..dd3ef957 100644 --- a/ruoyi-ui/src/views/ccdiProject/components/detail/DetailQuery.vue +++ b/ruoyi-ui/src/views/ccdiProject/components/detail/DetailQuery.vue @@ -227,6 +227,22 @@ + + + +
+
命中异常标签
+
+
+
+ {{ formatField(tag.ruleName) }} + + {{ formatRiskLevel(tag.riskLevel) }} + +
+
{{ formatField(tag.reasonDetail) }}
+
+
+
当前流水未命中异常标签
+