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.

12 KiB

教学班实训任务恢复默认内容 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)。

  • Step 1: 写失败的服务测试

在 TrainingTaskServiceImplTest 新增如下测试。测试类创建 class-1TEACHINGcreatedBy 为 teacher-1和 class-2createdBy 为 teacher-a

@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);
}
  • Step 2: 运行失败测试

Run: mvn -q -Dtest=TrainingTaskServiceImplTest test

Expected: 编译失败,缺少恢复服务或 Mapper 方法。

  • Step 3: 添加 Mapper 契约与 SQL
int deleteByTeachingClassAndTaskKey(@Param("teachingClassId") String teachingClassId,
                                   @Param("taskKey") String taskKey);
int deleteByTeachingClass(@Param("teachingClassId") String teachingClassId);
<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>
  • Step 4: 实现服务
@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));
}
  • Step 5: 验证并提交

Run: mvn -q -Dtest=TrainingTaskServiceImplTest test

Expected: PASS。

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
  • Test: src/test/java/com/sztzjy/linkCommerce/service/impl/TrainingTaskServiceImplTest.java

Interfaces:

  • Consumes: Task 1 服务方法。

  • Produces: DELETE /api/training-tasks/classes/{teachingClassId}/{taskKey}/override 和 DELETE /api/training-tasks/classes/{teachingClassId}/overrides。

  • Step 1: 写失败的源文件契约测试

在现有测试中读取控制器源码,添加:

assertTrue(source.contains("@DeleteMapping(\"/classes/{teachingClassId}/{taskKey}/override\")"));
assertTrue(source.contains("trainingTaskService.restoreClassTaskDefault"));
assertTrue(source.contains("@DeleteMapping(\"/classes/{teachingClassId}/overrides\")"));
assertTrue(source.contains("trainingTaskService.restoreClassTasksDefaults"));
  • Step 2: 运行失败测试

Run: mvn -q -Dtest=TrainingTaskServiceImplTest test

Expected: FAIL两个路由尚不存在。

  • Step 3: 实现两个 DELETE 控制器方法

每个方法均使用 requireTeacher(request)。成功返回 new ResultEntity(HttpStatus.OK, "Restore success");捕获 IllegalArgumentException 后返回 new ResultEntity(HttpStatus.BAD_REQUEST, e.getMessage())。单任务方法调用:

trainingTaskService.restoreClassTaskDefault(teachingClassId, taskKey, user.getUserId());

全班方法调用:

trainingTaskService.restoreClassTasksDefaults(teachingClassId, user.getUserId());
  • Step 4: 验证并提交

Run: mvn -q -DforkCount=0 test

Expected: PASS。

git add src/main/java/com/sztzjy/linkCommerce/controller/stu/TrainingTaskController.java src/test/java/com/sztzjy/linkCommerce/service/impl/TrainingTaskServiceImplTest.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以及二次确认的页面操作。

  • Step 1: 写失败的静态契约测试

创建测试文件,读取 API 和页面源码,断言存在两个恢复 API、两个中文按钮、confirm 调用和 selectedTeachingClass 条件:

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));
  • Step 2: 运行失败测试

Run: node tests/training-task-restore.static.test.cjs

Expected: FAIL恢复函数和按钮不存在。

  • Step 3: 添加 API 函数
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",
  });
}
  • Step 4: 实现恢复交互

导入两个 API 并新增 restoring 状态。教学班筛选区仅当 selectedTeachingClass 存在时显示“恢复本班全部默认任务”;编辑抽屉 footer 仅在非只读时显示“恢复此任务默认内容”。

两个处理方法都先调用 proxy.$modal.confirm文案明确为“仅恢复当前教学班的实训任务内容不影响学生已提交数据、成绩、进度及其他班级。是否继续”。确认后分别调用 API成功提示单任务关闭抽屉两个操作均 await queryTasks();取消确认不提示失败。

  • 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但无新增编译错误。

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: 可复现验证记录。

  • Step 1: 完整后端回归

Run: mvn -q -DforkCount=0 test

Expected: PASS。

  • 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 班学生端内容同步默认,且既有完成记录、成绩、进度保留。

  • Step 4: 提交验证记录
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。