feat(复习): 支持会话维度复习 + 标准导图按会话过滤(后端)

- MindMapNode 新增 sessionNum 字段
- BuiltinMindMapGenerator 生成节点时填充 sessionNum
- MindMapTreeTool.filterBySession() 按会话过滤标准导图
- recallCompare 接受 sessionNum 参数
- ReviewRecallRecordEntity 新增 sessionNum 字段
- Flyway V20260705_1: review_recall_records 加 session_num 列
- 新增 GET /study-sessions/tasks/{taskNum}/sessions 端点
This commit is contained in:
2026-07-05 14:40:38 +08:00
parent 22cc5dc871
commit a636e516d1
13 changed files with 144 additions and 3 deletions
@@ -32,9 +32,10 @@ public interface StandardMindMapService {
* 用户提交回忆大纲,与标准导图对比并返回结果。
*
* @param recallOutline 用户回忆的缩进大纲文本
* @param sessionNum 可选会话编码,指定时仅对比该会话相关的标准导图节点
* @return 对比记录实体(compareResult 字段包含对比树 JSON
*/
ReviewStandardMindMapEntity recallCompare(String taskNum, String recallOutline) throws NotFindEntitiesException, OperationFailedException;
ReviewStandardMindMapEntity recallCompare(String taskNum, String recallOutline, String sessionNum) throws NotFindEntitiesException, OperationFailedException;
/**
* 获取该任务的所有回忆对比记录。
@@ -11,7 +11,9 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.guo.learningprogresstracker.exception.ErrorParameterException;
import java.time.LocalDateTime;
import com.guo.learningprogresstracker.dto.response.SessionBriefVO;
import java.util.ArrayList;
import java.util.List;
/**
* @author guo
@@ -35,4 +37,7 @@ public interface StudySessionsService extends IService<StudySessionsEntity> {
Page<StudyReportsEntity> getTaskReports(String taskNum, int page, int size, String keyword);
StudySessionResponse getActiveSession(String excludeTaskNum);
/** 获取任务的所有会话简要信息(复习维度选择用) */
List<SessionBriefVO> getTaskSessions(String taskNum);
}
@@ -84,6 +84,7 @@ public class BuiltinMindMapGenerator implements MindMapAiClient {
}
MindMapNode sessionNode = new MindMapNode(sessionTitle);
sessionNode.setSessionNum(sessionNum);
// 报告作为子节点
for (StudyReportsEntity report : sessReports) {
@@ -93,6 +94,7 @@ public class BuiltinMindMapGenerator implements MindMapAiClient {
reportNode.setNotes(content);
reportNode.setSourceType("REPORT");
reportNode.setSourceId(report.getId());
reportNode.setSessionNum(sessionNum);
sessionNode.getChildren().add(reportNode);
}
@@ -104,6 +106,7 @@ public class BuiltinMindMapGenerator implements MindMapAiClient {
fragNode.setNotes(content);
fragNode.setSourceType("FRAGMENT");
fragNode.setSourceId(frag.getId());
fragNode.setSessionNum(sessionNum);
sessionNode.getChildren().add(fragNode);
}
@@ -96,7 +96,7 @@ public class StandardMindMapServiceImpl implements StandardMindMapService {
@Override
@Transactional
public ReviewStandardMindMapEntity recallCompare(String taskNum, String recallOutline)
public ReviewStandardMindMapEntity recallCompare(String taskNum, String recallOutline, String sessionNum)
throws NotFindEntitiesException, OperationFailedException {
ensureTaskExists(taskNum);
@@ -107,6 +107,11 @@ public class StandardMindMapServiceImpl implements StandardMindMapService {
MindMapNode standardRoot = MindMapTreeTool.fromJson(standard.getContent(), objectMapper);
MindMapNode recallRoot = MindMapTreeTool.parseOutline(recallOutline);
// 2b. 若指定了会话维度,过滤标准导图只保留该会话相关节点
if (sessionNum != null && !sessionNum.isBlank()) {
standardRoot = MindMapTreeTool.filterBySession(standardRoot, sessionNum);
}
// 3. 执行对比(优先 AI 语义对比,失败时降级为内置算法)
CompareResult result;
if (aiServiceClient.isConfigured()) {
@@ -142,6 +147,7 @@ public class StandardMindMapServiceImpl implements StandardMindMapService {
ReviewRecallRecordEntity record = new ReviewRecallRecordEntity();
record.setTaskNum(taskNum);
record.setStandardMapId(standard.getId());
record.setSessionNum(sessionNum != null && !sessionNum.isBlank() ? sessionNum : null);
record.setRecallContent(recallOutline);
record.setCompareResult(resultJson);
record.setRecallRatio(result.getRecallRatio());
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.protobuf.ServiceException;
import com.guo.learningprogresstracker.dto.StudySessionsDto;
import com.guo.learningprogresstracker.dto.response.SessionBriefVO;
import com.guo.learningprogresstracker.dto.response.StudySessionResponse;
import com.guo.learningprogresstracker.entity.StudyReportFragmentsEntity;
import com.guo.learningprogresstracker.entity.StudyReportsEntity;
@@ -26,6 +27,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -225,6 +227,34 @@ public class StudySessionsServiceImpl extends ServiceImpl<StudySessionsMapper, S
return result;
}
// ============ 获取任务会话列表(复习维度选择用) ============
@Override
public List<SessionBriefVO> getTaskSessions(String taskNum) {
var sessions = this.list(Wrappers.lambdaQuery(StudySessionsEntity.class)
.eq(StudySessionsEntity::getTaskNum, taskNum)
.orderByDesc(StudySessionsEntity::getStartTime));
return sessions.stream().map(s -> {
SessionBriefVO vo = new SessionBriefVO();
vo.setSessionNum(s.getSessionNum());
vo.setStartTime(s.getStartTime() != null ? s.getStartTime().toString() : null);
// 查询该会话的预期
Optional.ofNullable(studyExpectationsService.getBySessionNum(s.getSessionNum()))
.ifPresent(e -> vo.setExpectation(e.getDescription()));
// 统计报告和残片数量
long reportCount = studyReportsMapper.selectCount(
Wrappers.lambdaQuery(StudyReportsEntity.class)
.eq(StudyReportsEntity::getSessionNum, s.getSessionNum()));
long fragmentCount = studyReportFragmentsMapper.selectCount(
Wrappers.lambdaQuery(StudyReportFragmentsEntity.class)
.eq(StudyReportFragmentsEntity::getSessionNum, s.getSessionNum()));
vo.setReportCount((int) reportCount);
vo.setFragmentCount((int) fragmentCount);
return vo;
}).collect(Collectors.toList());
}
// ============ 活跃会话查询 ============
/**