Files
ccdi/docs/plans/backend/2026-03-29-project-detail-risk-overview-risk-people-pagination-backend-implementation.md

374 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Project Detail Risk Overview Risk People Pagination Backend Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
>
> **Repo note:** 本仓库 `AGENTS.md` 明确禁止开启 subagent执行本计划时请在当前会话使用 `superpowers:executing-plans`。
**Goal:**`GET /ccdi/project/overview/risk-people` 改造成标准分页接口,固定默认每页 5 条,保证项目详情“风险总览”员工列表按数据库真分页返回 `rows + total + pageNum + pageSize`
**Architecture:** 后端继续沿用 `CcdiProjectOverviewController -> ICcdiProjectOverviewService -> CcdiProjectOverviewServiceImpl -> CcdiProjectOverviewMapper.xml` 这一条结果总览链路,不新增补丁接口。接口路径保持不变,只引入独立分页查询 DTO、分页 VO 和 MyBatis Plus `Page` 查询;数据来源继续使用 `ccdi_project_overview_employee_result`,排序规则保持现状,不新增筛选口径。
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus `Page`, MyBatis XML, Maven, JUnit 5, Mockito, Swagger/OpenAPI
---
### Task 1: 建立 `risk-people` 分页接口契约
**Files:**
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiProjectRiskPeopleQueryDTO.java`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectRiskPeopleOverviewVO.java`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectOverviewService.java`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewController.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/CcdiProjectOverviewServiceStructureTest.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerTest.java`
- [ ] **Step 1: 先写失败测试,锁定 DTO、VO 字段和 Controller / Service 新签名**
```java
assertNotNull(clazz.getMethod(
"getRiskPeopleOverview",
Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO")
));
```
```java
Method method = CcdiProjectOverviewController.class.getMethod(
"getRiskPeople",
CcdiProjectRiskPeopleQueryDTO.class
);
assertEquals("/risk-people", method.getAnnotation(GetMapping.class).value()[0]);
```
```java
CcdiProjectRiskPeopleOverviewVO overview = new CcdiProjectRiskPeopleOverviewVO();
overview.setRows(List.of(item));
overview.setTotal(1L);
overview.setPageNum(1L);
overview.setPageSize(5L);
```
- [ ] **Step 2: 运行结构测试与控制器测试,确认当前仓库仍是旧全量契约**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewServiceStructureTest,CcdiProjectOverviewControllerTest test
```
Expected:
- `CcdiProjectOverviewServiceStructureTest` 因缺少 DTO 签名而失败
- `CcdiProjectOverviewControllerTest` 仍断言 `getRiskPeople(Long projectId)`,与新契约不匹配
- [ ] **Step 3: 写最小 DTO / VO / Controller / Service 契约**
```java
@Data
public class CcdiProjectRiskPeopleQueryDTO {
private Long projectId;
private Integer pageNum;
private Integer pageSize;
}
```
```java
@GetMapping("/risk-people")
public AjaxResult getRiskPeople(CcdiProjectRiskPeopleQueryDTO queryDTO) {
CcdiProjectRiskPeopleOverviewVO overview = overviewService.getRiskPeopleOverview(queryDTO);
return AjaxResult.success(overview);
}
```
```java
@Data
public class CcdiProjectRiskPeopleOverviewVO {
private List<CcdiProjectRiskPeopleOverviewItemVO> rows;
private Long total;
private Long pageNum;
private Long pageSize;
}
```
- [ ] **Step 4: 回跑契约测试,确认分页接口外壳已建立**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewServiceStructureTest,CcdiProjectOverviewControllerTest test
```
Expected:
- `PASS`
- 控制器测试明确返回 `rows + total + pageNum + pageSize`
- [ ] **Step 5: 提交本任务**
```bash
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiProjectRiskPeopleQueryDTO.java \
ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectRiskPeopleOverviewVO.java \
ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectOverviewService.java \
ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewController.java \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/CcdiProjectOverviewServiceStructureTest.java \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerTest.java
git commit -m "改造风险总览员工列表分页接口契约"
```
### Task 2: 将风险人员查询改为数据库分页
**Files:**
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapper.java`
- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapperSqlTest.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImplTest.java`
- [ ] **Step 1: 先写失败测试,锁定分页查询方法名、排序和分页参数默认值**
```java
String riskPeopleSql = extractSelect(xml, "selectRiskPeopleOverviewPage");
assertTrue(riskPeopleSql.contains("from ccdi_project_overview_employee_result"), riskPeopleSql);
assertTrue(riskPeopleSql.contains("result.project_id = #{query.projectId}"), riskPeopleSql);
assertTrue(
riskPeopleSql.contains("order by risk_level_sort asc, result.model_count desc, result.rule_count desc, result.staff_id_card asc"),
riskPeopleSql
);
assertFalse(riskPeopleSql.contains("limit 10"), riskPeopleSql);
```
```java
verify(overviewMapper).selectRiskPeopleOverviewPage(
argThat(page -> page.getCurrent() == 1L && page.getSize() == 5L),
argThat(query -> query.getProjectId().equals(40L))
);
```
- [ ] **Step 2: 运行 SQL 与 Service 测试,确认当前实现仍是全量查询**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewMapperSqlTest,CcdiProjectOverviewServiceImplTest test
```
Expected:
- SQL 测试提示缺少 `selectRiskPeopleOverviewPage`
- Service 测试因仍调用 `selectRiskPeopleOverviewByProjectId` 而失败
- [ ] **Step 3: 写最小分页 SQL 与服务实现**
```java
Page<CcdiProjectEmployeeRiskAggregateVO> page = new Page<>(
defaultPageNum(queryDTO.getPageNum()),
defaultPageSize(queryDTO.getPageSize())
);
Page<CcdiProjectEmployeeRiskAggregateVO> resultPage =
overviewMapper.selectRiskPeopleOverviewPage(page, queryDTO);
```
```java
overview.setRows(defaultList(resultPage == null ? null : resultPage.getRecords())
.stream()
.map(aggregate -> buildRiskPeopleItem(queryDTO.getProjectId(), aggregate))
.toList());
overview.setTotal(resultPage == null ? 0L : resultPage.getTotal());
overview.setPageNum(page.getCurrent());
overview.setPageSize(page.getSize());
```
```xml
<select id="selectRiskPeopleOverviewPage" resultMap="EmployeeRiskAggregateResultMap">
select
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 = #{query.projectId}
order by risk_level_sort asc, result.model_count desc, result.rule_count desc, result.staff_id_card asc
</select>
```
- [ ] **Step 4: 回跑分页相关测试,确认改成真分页且字段映射不变**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewMapperSqlTest,CcdiProjectOverviewServiceImplTest test
```
Expected:
- `PASS`
- `shouldMapRiskPeopleOverviewRows` 改为断言 `rows`
- 新增断言 `total/pageNum/pageSize`
- `riskLevel/riskLevelType/modelCount/riskPointTagList` 保持原语义
- [ ] **Step 5: 提交本任务**
```bash
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapper.java \
ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml \
ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapperSqlTest.java \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImplTest.java
git commit -m "实现风险总览员工列表后端分页查询"
```
### Task 3: 补齐分页默认值与项目不存在回归
**Files:**
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java`
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImplTest.java`
- [ ] **Step 1: 先写失败测试,锁定 `pageNum/pageSize` 缺省回落为 `1/5`**
```java
CcdiProjectRiskPeopleQueryDTO queryDTO = new CcdiProjectRiskPeopleQueryDTO();
queryDTO.setProjectId(40L);
service.getRiskPeopleOverview(queryDTO);
verify(overviewMapper).selectRiskPeopleOverviewPage(
argThat(page -> page.getCurrent() == 1L && page.getSize() == 5L),
any(CcdiProjectRiskPeopleQueryDTO.class)
);
```
```java
CcdiProjectRiskPeopleQueryDTO invalidQuery = new CcdiProjectRiskPeopleQueryDTO();
invalidQuery.setProjectId(40L);
invalidQuery.setPageNum(0);
invalidQuery.setPageSize(-1);
```
- [ ] **Step 2: 运行 Service 测试,确认当前没有锁死默认值**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewServiceImplTest test
```
Expected:
- 新增默认值断言失败
- [ ] **Step 3: 在 Service 中复用现有默认分页工具方法或补充最小默认值逻辑**
```java
private long defaultRiskPeoplePageNum(Integer pageNum) {
return pageNum == null || pageNum <= 0 ? 1L : pageNum.longValue();
}
private long defaultRiskPeoplePageSize(Integer pageSize) {
return pageSize == null || pageSize <= 0 ? 5L : pageSize.longValue();
}
```
- [ ] **Step 4: 回跑 Service 测试**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewServiceImplTest test
```
Expected:
- `PASS`
- `项目不存在` 的旧异常断言仍通过
- [ ] **Step 5: 提交本任务**
```bash
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImplTest.java
git commit -m "补齐风险总览员工列表分页默认值"
```
### Task 4: 补齐后端验证记录与实施记录
**Files:**
- Create: `docs/tests/records/2026-03-29-project-detail-risk-overview-risk-people-pagination-backend-verification.md`
- Create: `docs/reports/implementation/2026-03-29-project-detail-risk-overview-risk-people-pagination-backend-implementation.md`
- Verify: `docs/design/2026-03-29-project-detail-risk-overview-risk-people-pagination-design.md`
- [ ] **Step 1: 运行本需求后端最小回归**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewServiceStructureTest,CcdiProjectOverviewControllerTest,CcdiProjectOverviewMapperSqlTest,CcdiProjectOverviewServiceImplTest test
```
Expected:
- `PASS`
- `risk-people` 分页契约、SQL 和服务映射全部通过
- [ ] **Step 2: 写后端验证记录**
`docs/tests/records/2026-03-29-project-detail-risk-overview-risk-people-pagination-backend-verification.md` 记录:
- 执行命令
- 执行日期
- 测试结果
- 结论:`risk-people` 已改为标准分页接口,默认每页 5 条
- [ ] **Step 3: 写后端实施记录**
`docs/reports/implementation/2026-03-29-project-detail-risk-overview-risk-people-pagination-backend-implementation.md` 记录:
- 改造的 Controller / Service / Mapper / SQL / 测试文件
- 返回结构从 `overviewList` 迁移到 `rows`
- 排序规则保持不变
- 本次未新增补丁接口
- [ ] **Step 4: 检查暂存区仅包含本任务文件**
Run:
```bash
git status --short
git diff --cached --name-only
```
Expected:
- 暂存区只出现本任务相关后端源码、测试和文档
- [ ] **Step 5: 提交本任务**
```bash
git add docs/tests/records/2026-03-29-project-detail-risk-overview-risk-people-pagination-backend-verification.md \
docs/reports/implementation/2026-03-29-project-detail-risk-overview-risk-people-pagination-backend-implementation.md
git commit -m "补充风险总览员工列表后端分页实施记录"
```
## Done When
- `GET /ccdi/project/overview/risk-people` 已改为接收 `projectId + pageNum + pageSize`
- 接口返回 `rows + total + pageNum + pageSize`
- SQL 基于 `ccdi_project_overview_employee_result` 实现真分页
- 默认分页固定回落为 `1 / 5`
- 后端验证记录与实施记录已补齐