新增项目归档前后端实施计划
This commit is contained in:
@@ -0,0 +1,311 @@
|
||||
# Project Archive 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.
|
||||
|
||||
**Goal:** 为项目管理列表补齐真实归档后端链路,并在服务层封住“已归档项目仍可上传数据或修改参数”的写入口。
|
||||
|
||||
**Architecture:** 后端采用最短路径实现,在现有 `CcdiProjectController -> ICcdiProjectService -> CcdiProjectServiceImpl` 链路中新增专用归档动作,不复用通用更新接口。归档成功后统一写入 `status=2`、`isArchived=1`,同时将“已归档不可写”收敛到项目服务校验层,再复用到上传与参数保存服务,保证页面限制和接口限制一致。
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, JUnit 5, Mockito, Maven
|
||||
|
||||
## 后端验收清单
|
||||
|
||||
- `POST /ccdi/project/{projectId}/archive` 可成功归档已完成项目
|
||||
- 归档后项目写入 `status = "2"` 且 `isArchived = 1`
|
||||
- 非 `已完成` 状态项目调用归档接口会被拒绝
|
||||
- 已归档项目调用上传或参数保存相关后端入口会被拒绝
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 先用测试锁定归档状态流转和写保护规则
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImplTest.java`
|
||||
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImplTest.java`
|
||||
|
||||
- [ ] **Step 1: Write the failing service test for archive success**
|
||||
|
||||
在 `CcdiProjectServiceImplTest.java` 中新增归档成功用例,至少锁定以下结果:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void shouldArchiveCompletedProject() {
|
||||
CcdiProject project = new CcdiProject();
|
||||
project.setProjectId(40L);
|
||||
project.setProjectName("专案A");
|
||||
project.setStatus("1");
|
||||
project.setIsArchived(0);
|
||||
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||
|
||||
service.archiveProject(40L, "tester");
|
||||
|
||||
assertEquals("2", project.getStatus());
|
||||
assertEquals(1, project.getIsArchived());
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Extend the failing test for rejected states**
|
||||
|
||||
继续补两个失败场景:
|
||||
|
||||
```java
|
||||
assertThrows(ServiceException.class, () -> service.archiveProject(41L, "tester"));
|
||||
assertThrows(ServiceException.class, () -> service.ensureProjectNotArchived(42L, "已归档项目暂不允许修改参数"));
|
||||
```
|
||||
|
||||
覆盖目标:
|
||||
|
||||
- 进行中 / 打标中 / 已归档项目不允许归档
|
||||
- 已归档项目会被项目服务层写保护拦截
|
||||
|
||||
- [ ] **Step 3: Run service tests to verify they fail first**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn -pl ccdi-project -Dtest=CcdiProjectServiceImplTest,CcdiFileUploadServiceImplTest,CcdiModelParamServiceImplTest test
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因包括 `archiveProject` / `ensureProjectNotArchived` 尚未实现,或现有测试断言不满足
|
||||
|
||||
- [ ] **Step 4: Add minimal upload and param guard expectations**
|
||||
|
||||
在 `CcdiFileUploadServiceImplTest.java` 和 `CcdiModelParamServiceImplTest.java` 中补充 mock 校验,锁定归档保护会被调用,例如:
|
||||
|
||||
```java
|
||||
verify(projectService).ensureProjectNotArchived(PROJECT_ID, "已归档项目暂不允许上传或拉取数据");
|
||||
verify(projectService).ensureProjectNotArchived(40L, "已归档项目暂不允许修改参数");
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImplTest.java
|
||||
git commit -m "补充项目归档后端测试约束"
|
||||
```
|
||||
|
||||
### Task 2: 实现项目归档服务与接口
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectService.java`
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java`
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectController.java`
|
||||
|
||||
- [ ] **Step 1: Add the failing controller contract test**
|
||||
|
||||
新建或补充控制器测试,锁定归档接口契约:
|
||||
|
||||
```java
|
||||
Method method = CcdiProjectController.class.getMethod("archiveProject", Long.class);
|
||||
PostMapping postMapping = method.getAnnotation(PostMapping.class);
|
||||
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
||||
|
||||
assertEquals("/{projectId}/archive", postMapping.value()[0]);
|
||||
assertEquals("@ss.hasPermi('ccdi:project:edit')", preAuthorize.value());
|
||||
```
|
||||
|
||||
建议新增文件:
|
||||
|
||||
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectControllerTest.java`
|
||||
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectControllerContractTest.java`
|
||||
|
||||
- [ ] **Step 2: Run controller tests to verify they fail**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn -pl ccdi-project -Dtest=CcdiProjectControllerTest,CcdiProjectControllerContractTest test
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因是当前 `CcdiProjectController` 尚无归档方法
|
||||
|
||||
- [ ] **Step 3: Implement the minimal archive action**
|
||||
|
||||
在 `ICcdiProjectService.java` 增加:
|
||||
|
||||
```java
|
||||
void archiveProject(Long projectId, String operator);
|
||||
void ensureProjectNotArchived(Long projectId, String message);
|
||||
```
|
||||
|
||||
在 `CcdiProjectServiceImpl.java` 实现:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public void archiveProject(Long projectId, String operator) {
|
||||
CcdiProject project = getRequiredProject(projectId);
|
||||
if (!CcdiProjectStatusConstants.COMPLETED.equals(project.getStatus())) {
|
||||
throw new ServiceException("仅已完成项目允许归档");
|
||||
}
|
||||
project.setStatus(CcdiProjectStatusConstants.ARCHIVED);
|
||||
project.setIsArchived(1);
|
||||
project.setUpdateBy(operator);
|
||||
project.setUpdateTime(new Date());
|
||||
projectMapper.updateById(project);
|
||||
}
|
||||
```
|
||||
|
||||
并在 `CcdiProjectController.java` 增加:
|
||||
|
||||
```java
|
||||
@PostMapping("/{projectId}/archive")
|
||||
@Operation(summary = "归档项目")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
|
||||
public AjaxResult archiveProject(@PathVariable Long projectId) {
|
||||
projectService.archiveProject(projectId, SecurityUtils.getUsername());
|
||||
return AjaxResult.success("项目归档成功");
|
||||
}
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- 不新增 DTO
|
||||
- 不在控制器里直接写数据库更新逻辑
|
||||
- 不引入“取消归档”“恢复归档”分支
|
||||
|
||||
- [ ] **Step 4: Run tests to verify the archive chain passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn -pl ccdi-project -Dtest=CcdiProjectServiceImplTest,CcdiProjectControllerTest,CcdiProjectControllerContractTest test
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
- 能证明归档接口、服务状态流转和权限注解均已到位
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectService.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectController.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectControllerTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectControllerContractTest.java
|
||||
git commit -m "实现项目归档后端接口"
|
||||
```
|
||||
|
||||
### Task 3: 将归档写保护接入上传与参数保存链路
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java`
|
||||
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImplTest.java`
|
||||
|
||||
- [ ] **Step 1: Add failing assertions for archive guard calls**
|
||||
|
||||
先在两组测试里锁定新增校验调用顺序:
|
||||
|
||||
```java
|
||||
verify(projectService).ensureProjectNotArchived(PROJECT_ID, "已归档项目暂不允许上传或拉取数据");
|
||||
verify(projectService).ensureProjectWritable(PROJECT_ID, "当前项目正在进行银行流水打标,暂不允许上传或拉取数据");
|
||||
```
|
||||
|
||||
以及:
|
||||
|
||||
```java
|
||||
verify(projectService).ensureProjectNotArchived(40L, "已归档项目暂不允许修改参数");
|
||||
verify(projectService).ensureProjectWritable(40L, "当前项目正在进行银行流水打标,暂不允许修改参数");
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the targeted tests to verify failure**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest,CcdiModelParamServiceImplTest test
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因是现有实现尚未调用 `ensureProjectNotArchived`
|
||||
|
||||
- [ ] **Step 3: Implement the minimal service guard**
|
||||
|
||||
在 `CcdiFileUploadServiceImpl.java` 和 `CcdiModelParamServiceImpl.java` 中,在现有 `ensureProjectWritable(...)` 前增加:
|
||||
|
||||
```java
|
||||
projectService.ensureProjectNotArchived(projectId, "已归档项目暂不允许上传或拉取数据");
|
||||
projectService.ensureProjectNotArchived(projectId, "已归档项目暂不允许修改参数");
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- 不改现有业务主流程
|
||||
- 不改上传、拉取、参数保存的入参与返回结构
|
||||
- 只补充状态校验
|
||||
|
||||
- [ ] **Step 4: Run the full backend regression set**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn -pl ccdi-project -Dtest=CcdiProjectServiceImplTest,CcdiProjectControllerTest,CcdiProjectControllerContractTest,CcdiFileUploadServiceImplTest,CcdiModelParamServiceImplTest test
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
- 能证明归档接口和写保护闭环同时成立
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImplTest.java
|
||||
git commit -m "补充项目归档后端写保护"
|
||||
```
|
||||
|
||||
### Task 4: 沉淀后端实施与验证记录
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/tests/records/2026-03-24-project-archive-backend-verification.md`
|
||||
- Create: `docs/reports/implementation/2026-03-24-project-archive-backend-record.md`
|
||||
|
||||
- [ ] **Step 1: Write the verification skeleton**
|
||||
|
||||
在验证记录中至少写明:
|
||||
|
||||
```markdown
|
||||
# 项目归档后端验证记录
|
||||
|
||||
## 验证范围
|
||||
- 归档接口仅允许已完成项目调用
|
||||
- 归档成功后状态和 isArchived 正确落库
|
||||
- 已归档项目后端禁止上传或拉取数据
|
||||
- 已归档项目后端禁止修改参数
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Record exact commands and results**
|
||||
|
||||
把实际执行命令与结果写入文档:
|
||||
|
||||
```bash
|
||||
mvn -pl ccdi-project -Dtest=CcdiProjectServiceImplTest,CcdiProjectControllerTest,CcdiProjectControllerContractTest,CcdiFileUploadServiceImplTest,CcdiModelParamServiceImplTest test
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 记录通过结果,或失败时记录具体原因
|
||||
|
||||
- [ ] **Step 3: Write the backend implementation record**
|
||||
|
||||
在实施记录中说明:
|
||||
|
||||
- 为什么采用专用归档接口而不是复用更新接口
|
||||
- 为什么需要把“已归档不可写”下沉到服务层
|
||||
- 本次改动影响了哪些后端入口
|
||||
- 已完成哪些测试验证
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/tests/records/2026-03-24-project-archive-backend-verification.md docs/reports/implementation/2026-03-24-project-archive-backend-record.md
|
||||
git commit -m "补充项目归档后端实施记录"
|
||||
```
|
||||
Reference in New Issue
Block a user