You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

296 lines
13 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 教学班实训任务恢复默认内容 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.
**Goal:** 教师可将当前教学班的单个或全部实训任务内容恢复为平台默认内容,且不影响任务分配、学生数据或其他班级。
**Architecture:** 当前每个教学班在 training_task_class_config 中维护完整任务副本。恢复仅删除当前班的一个或全部副本;下一次任务读取会沿用现有 ensureClassTasksInitialized以 training_task 中的平台默认内容自动补回缺失副本。控制权限保持 requireOwnedTeachingClass 的现有边界。
**Tech Stack:** Spring Boot、MyBatis XML Mapper、JUnit 5 + Mockito、Vue 3、Element Plus、Node assert。
## Global Constraints
- 不新增数据库表或迁移。
- 仅教学班创建者可恢复内容。
- 不修改 task_allocation也不修改学生完成、成绩、进度与其他班级数据。
- 删除不存在的副本也成功返回,以支持幂等重试。
---
## 文件结构
| 文件 | 责任 |
| --- | --- |
| src/main/java/com/sztzjy/linkCommerce/mapper/TrainingTaskClassConfigMapper.java | 声明两个删除副本方法。 |
| src/main/resources/mappers/TrainingTaskClassConfigMapper.xml | 实现按班级、按班级和任务键的删除 SQL。 |
| src/main/java/com/sztzjy/linkCommerce/service/TrainingTaskService.java | 声明恢复服务接口。 |
| src/main/java/com/sztzjy/linkCommerce/service/impl/TrainingTaskServiceImpl.java | 权限校验后删除当前班级副本。 |
| src/main/java/com/sztzjy/linkCommerce/controller/stu/TrainingTaskController.java | 暴露两个教师端 DELETE 接口。 |
| src/test/java/com/sztzjy/linkCommerce/service/impl/TrainingTaskServiceImplTest.java | 覆盖范围、权限和幂等删除调用。 |
| src/api/trainingTask.js | 封装两个 DELETE 请求。 |
| src/views/teacherEnd/trainingTask/index.vue | 提供单任务与全班恢复、二次确认和刷新。 |
| tests/training-task-restore.static.test.cjs | 锁定前端恢复契约。 |
### Task 1: 后端副本恢复服务
**Files:**
- Modify: src/main/java/com/sztzjy/linkCommerce/mapper/TrainingTaskClassConfigMapper.java
- Modify: src/main/resources/mappers/TrainingTaskClassConfigMapper.xml
- Modify: src/main/java/com/sztzjy/linkCommerce/service/TrainingTaskService.java
- Modify: src/main/java/com/sztzjy/linkCommerce/service/impl/TrainingTaskServiceImpl.java
- Test: src/test/java/com/sztzjy/linkCommerce/service/impl/TrainingTaskServiceImplTest.java
**Interfaces:**
- Consumes: requireOwnedTeachingClass(String, String)。
- Produces: restoreClassTaskDefault(String, String, String) 和 restoreClassTasksDefaults(String, String)。
- [x] **Step 1: 写失败的服务测试**
在 TrainingTaskServiceImplTest 新增如下测试。测试类创建 class-1TEACHINGcreatedBy 为 teacher-1和 class-2createdBy 为 teacher-a
~~~java
@Test
void restoreClassTaskDefaultDeletesOnlySelectedClassTaskCopy() {
service.restoreClassTaskDefault("class-1", "new-product-survey", "teacher-1");
verify(service.trainingTaskClassConfigMapper)
.deleteByTeachingClassAndTaskKey("class-1", "new-product-survey");
verify(service.trainingTaskClassConfigMapper, never()).deleteByTeachingClass(anyString());
}
@Test
void restoreClassTasksDefaultsDeletesOnlySelectedClassCopies() {
service.restoreClassTasksDefaults("class-1", "teacher-1");
verify(service.trainingTaskClassConfigMapper).deleteByTeachingClass("class-1");
}
@Test
void teacherCannotRestoreAnotherTeachersClass() {
assertThrows(IllegalArgumentException.class,
() -> service.restoreClassTasksDefaults("class-2", "teacher-b"));
verifyNoInteractions(service.trainingTaskClassConfigMapper);
}
~~~
- [x] **Step 2: 运行失败测试**
Run: mvn -q -Dtest=TrainingTaskServiceImplTest test
Expected: 编译失败,缺少恢复服务或 Mapper 方法。
- [x] **Step 3: 添加 Mapper 契约与 SQL**
~~~java
int deleteByTeachingClassAndTaskKey(@Param("teachingClassId") String teachingClassId,
@Param("taskKey") String taskKey);
int deleteByTeachingClass(@Param("teachingClassId") String teachingClassId);
~~~
~~~xml
<delete id="deleteByTeachingClassAndTaskKey">
delete from training_task_class_config
where teaching_class_id = #{teachingClassId,jdbcType=VARCHAR}
and task_key = #{taskKey,jdbcType=VARCHAR}
</delete>
<delete id="deleteByTeachingClass">
delete from training_task_class_config
where teaching_class_id = #{teachingClassId,jdbcType=VARCHAR}
</delete>
~~~
- [x] **Step 4: 实现服务**
~~~java
@Override
@Transactional(rollbackFor = Exception.class)
public void restoreClassTaskDefault(String teachingClassId, String taskKey, String operatorId) {
requireOwnedTeachingClass(teachingClassId, operatorId);
trainingTaskClassConfigMapper.deleteByTeachingClassAndTaskKey(
StringUtils.trimToEmpty(teachingClassId), StringUtils.trimToEmpty(taskKey));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void restoreClassTasksDefaults(String teachingClassId, String operatorId) {
requireOwnedTeachingClass(teachingClassId, operatorId);
trainingTaskClassConfigMapper.deleteByTeachingClass(StringUtils.trimToEmpty(teachingClassId));
}
~~~
- [x] **Step 5: 验证并提交**
Run: mvn -q -Dtest=TrainingTaskServiceImplTest test
Expected: PASS。
~~~powershell
git add src/main/java/com/sztzjy/linkCommerce/mapper/TrainingTaskClassConfigMapper.java src/main/resources/mappers/TrainingTaskClassConfigMapper.xml src/main/java/com/sztzjy/linkCommerce/service/TrainingTaskService.java src/main/java/com/sztzjy/linkCommerce/service/impl/TrainingTaskServiceImpl.java src/test/java/com/sztzjy/linkCommerce/service/impl/TrainingTaskServiceImplTest.java
git commit -m "feat: restore class training task defaults"
~~~
### Task 2: 教师端 REST 接口
**Files:**
- Modify: src/main/java/com/sztzjy/linkCommerce/controller/stu/TrainingTaskController.java
- Create: src/test/java/com/sztzjy/linkCommerce/controller/stu/TrainingTaskControllerRestoreDefaultTest.java
**Interfaces:**
- Consumes: Task 1 服务方法。
- Produces: DELETE /api/training-tasks/classes/{teachingClassId}/{taskKey}/override 和 DELETE /api/training-tasks/classes/{teachingClassId}/overrides。
- [x] **Step 1: 写失败的控制器调用测试**
创建 TrainingTaskControllerRestoreDefaultTest向控制器注入 mock TrainingTaskService并用带 roleId=3 的 JWT 请求调用两个端点:
~~~java
ResultEntity result = controller.restoreClassTaskDefault(
"class-1", "new-product-survey", requestFor("teacher-1"));
assertEquals(HttpStatus.OK, result.getStatusCode());
verify(service).restoreClassTaskDefault("class-1", "new-product-survey", "teacher-1");
~~~
全班测试调用 controller.restoreClassTasksDefaults("class-1", requestFor("teacher-1")),并验证服务收到 restoreClassTasksDefaults("class-1", "teacher-1")。
- [x] **Step 2: 运行失败测试**
Run: mvn -q -Dtest=TrainingTaskControllerRestoreDefaultTest test
Expected: 编译失败,两个控制器方法尚不存在。
- [x] **Step 3: 实现两个 DELETE 控制器方法**
每个方法均使用 requireTeacher(request)。成功返回 new ResultEntity(HttpStatus.OK, "Restore success");捕获 IllegalArgumentException 后返回 new ResultEntity(HttpStatus.BAD_REQUEST, e.getMessage())。单任务方法调用:
~~~java
trainingTaskService.restoreClassTaskDefault(teachingClassId, taskKey, user.getUserId());
~~~
全班方法调用:
~~~java
trainingTaskService.restoreClassTasksDefaults(teachingClassId, user.getUserId());
~~~
- [x] **Step 4: 验证并提交**
Run: mvn -q -DforkCount=0 test
Expected: PASS。
~~~powershell
git add src/main/java/com/sztzjy/linkCommerce/controller/stu/TrainingTaskController.java src/test/java/com/sztzjy/linkCommerce/controller/stu/TrainingTaskControllerRestoreDefaultTest.java
git commit -m "feat: expose training task default restore APIs"
~~~
### Task 3: 教师端操作与静态测试
**Files:**
- Modify: src/api/trainingTask.js
- Modify: src/views/teacherEnd/trainingTask/index.vue
- Create: tests/training-task-restore.static.test.cjs
**Interfaces:**
- Consumes: Task 2 的 DELETE 路由。
- Produces: restoreClassTrainingTaskDefault、restoreClassTrainingTasksDefaults以及二次确认的页面操作。
- [x] **Step 1: 写失败的静态契约测试**
创建测试文件,读取 API 和页面源码,断言存在两个恢复 API、两个中文按钮、confirm 调用和 selectedTeachingClass 条件:
~~~javascript
assert(/restoreClassTrainingTaskDefault/.test(api));
assert(/restoreClassTrainingTasksDefaults/.test(api));
assert(/恢复此任务默认内容/.test(page));
assert(/恢复本班全部默认任务/.test(page));
assert(/proxy\?\.\$modal\?\.confirm/.test(page));
assert(/v-if="selectedTeachingClass"/.test(page));
~~~
- [x] **Step 2: 运行失败测试**
Run: node tests/training-task-restore.static.test.cjs
Expected: FAIL恢复函数和按钮不存在。
- [x] **Step 3: 添加 API 函数**
~~~javascript
export function restoreClassTrainingTaskDefault(teachingClassId, taskKey) {
return request({
url: "/api/training-tasks/classes/" + teachingClassId + "/" + taskKey + "/override",
method: "delete",
});
}
export function restoreClassTrainingTasksDefaults(teachingClassId) {
return request({
url: "/api/training-tasks/classes/" + teachingClassId + "/overrides",
method: "delete",
});
}
~~~
- [x] **Step 4: 实现恢复交互**
导入两个 API 并新增 restoring 状态。教学班筛选区仅当 selectedTeachingClass 存在时显示“恢复本班全部默认任务”;编辑抽屉 footer 仅在非只读时显示“恢复此任务默认内容”。
两个处理方法都先调用 proxy.$modal.confirm文案明确为“仅恢复当前教学班的实训任务内容不影响学生已提交数据、成绩、进度及其他班级。是否继续”。确认后分别调用 API成功提示单任务关闭抽屉两个操作均 await queryTasks();取消确认不提示失败。
- [x] **Step 5: 验证并提交**
Run: node tests/training-task-restore.static.test.cjs; node tests/school-default-task.static.test.cjs; npm run build:prod
Expected: 全部成功,构建可保留已有 warning但无新增编译错误。
~~~powershell
git add src/api/trainingTask.js src/views/teacherEnd/trainingTask/index.vue tests/training-task-restore.static.test.cjs
git commit -m "feat: restore teaching class task defaults"
~~~
### Task 4: 完整回归与交付记录
**Files:**
- Modify: docs/superpowers/plans/2026-07-30-training-task-restore-default.md
**Interfaces:**
- Consumes: Task 13 完成功能。
- Produces: 可复现验证记录。
- [x] **Step 1: 完整后端回归**
Run: mvn -q -DforkCount=0 test
Expected: PASS。
- [x] **Step 2: 完整前端回归**
Run: node tests/training-task-restore.static.test.cjs; node tests/school-default-task.static.test.cjs; npm run build:prod
Expected: PASS。
- [ ] **Step 3: 人工验收**
以教师账号编辑 A 班一个任务后刷新,确认 A 显示编辑内容;切换 B 班,确认 B 不变。恢复 A 的单任务,确认仅该任务默认化;恢复 A 的全部任务,确认全部默认化。检查 A 班学生端内容同步默认,且既有完成记录、成绩、进度保留。
- [x] **Step 4: 提交验证记录**
~~~powershell
git add docs/superpowers/plans/2026-07-30-training-task-restore-default.md
git commit -m "docs: verify training task default restore"
~~~
## 自检
- Spec coverageTask 12 覆盖范围、权限、幂等Task 3 覆盖两处入口和二次确认Task 4 覆盖默认重建、班级隔离及学生数据不变。
- Placeholder scan无 TBD、TODO 或“类似 Task N”等占位说明。
- Type consistency后端统一使用 restoreClassTaskDefault / restoreClassTasksDefaults前端统一使用 restoreClassTrainingTaskDefault / restoreClassTrainingTasksDefaults。
## 执行记录2026-07-30
- 后端服务测试先因恢复方法与 Mapper 删除方法不存在而编译失败,最小实现后转绿。
- 控制器使用 `TrainingTaskControllerRestoreDefaultTest` 做实际教师 JWT 调用测试,替代原计划的源码文本断言;该测试先因端点方法不存在而编译失败,新增端点后转绿。
- 最终自动验证:`mvn -q -DforkCount=0 test`、`node tests/training-task-restore.static.test.cjs`、`node tests/school-default-task.static.test.cjs`、`npm run build:prod` 均以退出码 0 结束。
- 人工验收仍待在运行中的教师端完成:编辑班级 A 的任务、单任务恢复、全班恢复,并核验班级 B 与学生的既有数据未受影响。