feat(复习):标准思维导图生成与回忆对比

- 新增 review_standard_mind_maps / review_recall_records 数据表
- BuiltinMindMapGenerator:从报告/残片/应用场景规则生成标准导图
- MindMapAiClient 接口 + RemoteAiMindMapClient 预留
- StandardMindMapService:生成/编辑/重新生成/回忆对比
- 对比算法:标题归一化 + Bigram Jaccard 模糊匹配
- ReviewController 新增 6 个端点
- MindMapTreeTool / MindMapNode / CompareResult 工具类
- 完整单元测试(10 个,全部通过)
- 更新 docs/review-module-design.md
This commit is contained in:
2026-07-03 08:53:51 +08:00
parent 5f3f35c53b
commit 32b247526b
34 changed files with 1890 additions and 153 deletions
@@ -2,16 +2,19 @@ package com.guo.learningprogresstracker.controller;
import com.guo.learningprogresstracker.dto.ReviewFeedItem;
import com.guo.learningprogresstracker.dto.ReviewTaskStats;
import com.guo.learningprogresstracker.dto.request.CreateReviewApplicationRequest;
import com.guo.learningprogresstracker.dto.request.UpdateReviewApplicationRequest;
import com.guo.learningprogresstracker.dto.request.RecallCompareRequest;
import com.guo.learningprogresstracker.dto.request.UpdateStandardMindMapRequest;
import com.guo.learningprogresstracker.dto.request.UpsertReviewMindMapRequest;
import com.guo.learningprogresstracker.entity.CommonResult;
import com.guo.learningprogresstracker.entity.ReviewApplicationEntity;
import com.guo.learningprogresstracker.entity.ReviewMindMapEntity;
import com.guo.learningprogresstracker.entity.ReviewRecallRecordEntity;
import com.guo.learningprogresstracker.entity.ReviewStandardMindMapEntity;
import com.guo.learningprogresstracker.entity.StudyReportFragmentsEntity;
import com.guo.learningprogresstracker.entity.StudyReportsEntity;
import com.guo.learningprogresstracker.exception.NotFindEntitiesException;
import com.guo.learningprogresstracker.exception.OperationFailedException;
import com.guo.learningprogresstracker.service.ReviewService;
import com.guo.learningprogresstracker.service.StandardMindMapService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
@@ -21,6 +24,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
import java.util.List;
/**
* 复习模块控制层 — 复习内容滚动展示与交互
@@ -33,6 +37,8 @@ public class ReviewController {
private final ReviewService reviewService;
private final StandardMindMapService standardMindMapService;
/**
* 获取复习 feed,合并报告和残片按时间倒序
*/
@@ -83,42 +89,6 @@ public class ReviewController {
return CommonResult.success(reviewService.getFragmentDetail(id));
}
/**
* 获取指定任务的应用场景列表
*/
@GetMapping("/applications/{taskNum}")
public CommonResult<List<ReviewApplicationEntity>> getApplications(@PathVariable String taskNum) throws NotFindEntitiesException {
return CommonResult.success(reviewService.getApplications(taskNum));
}
/**
* 创建应用场景
*/
@PostMapping("/applications")
public CommonResult<ReviewApplicationEntity> createApplication(
@Valid @RequestBody CreateReviewApplicationRequest request) throws NotFindEntitiesException {
return CommonResult.success(reviewService.createApplication(request));
}
/**
* 更新应用场景
*/
@PutMapping("/applications/{id}")
public CommonResult<ReviewApplicationEntity> updateApplication(
@PathVariable Integer id,
@Valid @RequestBody UpdateReviewApplicationRequest request) throws NotFindEntitiesException {
return CommonResult.success(reviewService.updateApplication(id, request));
}
/**
* 删除应用场景
*/
@DeleteMapping("/applications/{id}")
public CommonResult<Void> deleteApplication(@PathVariable Integer id) throws NotFindEntitiesException {
reviewService.deleteApplication(id);
return CommonResult.success();
}
/**
* 获取指定任务的思维导图
*/
@@ -146,4 +116,62 @@ public class ReviewController {
@RequestParam("file") MultipartFile file) throws NotFindEntitiesException, IOException {
return CommonResult.success(reviewService.uploadMindMap(taskNum, file));
}
// ============ 标准思维导图与回忆对比 ============
/**
* 获取或自动生成任务的标准思维导图
*/
@GetMapping("/standard-mind-map/{taskNum}")
public CommonResult<ReviewStandardMindMapEntity> getStandardMindMap(
@PathVariable String taskNum) throws NotFindEntitiesException, OperationFailedException {
return CommonResult.success(standardMindMapService.getOrGenerate(taskNum));
}
/**
* 强制重新生成任务的标准思维导图
*/
@PostMapping("/standard-mind-map/{taskNum}/regenerate")
public CommonResult<ReviewStandardMindMapEntity> regenerateStandardMindMap(
@PathVariable String taskNum) throws NotFindEntitiesException, OperationFailedException {
return CommonResult.success(standardMindMapService.regenerate(taskNum));
}
/**
* 用户编辑标准思维导图(大纲文本形式)
*/
@PutMapping("/standard-mind-map/{taskNum}")
public CommonResult<ReviewStandardMindMapEntity> updateStandardMindMap(
@PathVariable String taskNum,
@Valid @RequestBody UpdateStandardMindMapRequest request) throws NotFindEntitiesException {
return CommonResult.success(standardMindMapService.updateByOutline(taskNum, request.getOutline()));
}
/**
* 用户提交回忆大纲,与标准导图对比
*/
@PostMapping("/standard-mind-map/{taskNum}/recall")
public CommonResult<ReviewStandardMindMapEntity> recallCompare(
@PathVariable String taskNum,
@Valid @RequestBody RecallCompareRequest request) throws NotFindEntitiesException, OperationFailedException {
return CommonResult.success(standardMindMapService.recallCompare(taskNum, request.getRecallOutline()));
}
/**
* 获取该任务的所有回忆对比记录
*/
@GetMapping("/standard-mind-map/{taskNum}/recall-records")
public CommonResult<List<ReviewRecallRecordEntity>> listRecallRecords(
@PathVariable String taskNum) throws NotFindEntitiesException {
return CommonResult.success(standardMindMapService.listRecallRecords(taskNum));
}
/**
* 获取单条回忆对比记录详情
*/
@GetMapping("/standard-mind-map/recall-records/{recordId}")
public CommonResult<ReviewRecallRecordEntity> getRecallRecord(
@PathVariable Integer recordId) throws NotFindEntitiesException {
return CommonResult.success(standardMindMapService.getRecallRecord(recordId));
}
}
@@ -4,15 +4,19 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.protobuf.ServiceException;
import com.guo.learningprogresstracker.common.Ops;
import com.guo.learningprogresstracker.dto.TaskInfo;
import com.guo.learningprogresstracker.dto.request.CreateTaskApplicationRequest;
import com.guo.learningprogresstracker.dto.response.StudySessionResponse;
import com.guo.learningprogresstracker.dto.response.TaskInfoResponse;
import com.guo.learningprogresstracker.entity.CommonResult;
import com.guo.learningprogresstracker.dto.request.TaskRequest;
import com.guo.learningprogresstracker.dto.request.UpdateTaskApplicationRequest;
import com.guo.learningprogresstracker.entity.TaskApplicationEntity;
import com.guo.learningprogresstracker.exception.ErrorParameterException;
import com.guo.learningprogresstracker.exception.NotFindEntitiesException;
import com.guo.learningprogresstracker.service.TasksService;
import com.guo.learningprogresstracker.service.impl.StudySessionsServiceImpl;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
@@ -27,6 +31,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.rmi.ServerException;
import java.util.List;
/**
* 任务控制层
@@ -69,6 +74,36 @@ public class TaskController {
return CommonResult.success();
}
@GetMapping("/{taskNum}/applications")
@Operation(summary = "获取任务应用场景列表")
public CommonResult<List<TaskApplicationEntity>> getApplications(@PathVariable String taskNum) throws NotFindEntitiesException {
return CommonResult.success(tasksService.getApplications(taskNum));
}
@PostMapping("/{taskNum}/applications")
@Operation(summary = "创建任务应用场景")
public CommonResult<TaskApplicationEntity> createApplication(
@PathVariable String taskNum,
@Valid @RequestBody CreateTaskApplicationRequest request) throws NotFindEntitiesException {
request.setTaskNum(taskNum);
return CommonResult.success(tasksService.createApplication(request));
}
@PutMapping("/applications/{id}")
@Operation(summary = "更新任务应用场景")
public CommonResult<TaskApplicationEntity> updateApplication(
@PathVariable Integer id,
@Valid @RequestBody UpdateTaskApplicationRequest request) throws NotFindEntitiesException {
return CommonResult.success(tasksService.updateApplication(id, request));
}
@DeleteMapping("/applications/{id}")
@Operation(summary = "删除任务应用场景")
public CommonResult<Void> deleteApplication(@PathVariable Integer id) throws NotFindEntitiesException {
tasksService.deleteApplication(id);
return CommonResult.success();
}
@GetMapping
@Operation(summary = "获取所有任务列表")
public CommonResult<Page<TaskInfo>> tasksList(@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,