ac0154c1fb
- TaskInfo DTO 新增 materialUrl 字段 - StudySessionResponse 新增 materialUrl 和 taskId 字段 - StudySessionsServiceImpl 在学习会话响应中填充 materialUrl 和 taskId - TasksServiceImpl 更新任务时保留原有 taskNum - 修复 TaskInfoResponse 注释格式错误
266 lines
14 KiB
Java
266 lines
14 KiB
Java
package com.guo.learningprogresstracker.service.impl;
|
|
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
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.StudySessionResponse;
|
|
import com.guo.learningprogresstracker.entity.StudyReportFragmentsEntity;
|
|
import com.guo.learningprogresstracker.entity.StudyReportsEntity;
|
|
import com.guo.learningprogresstracker.entity.StudySessionsEntity;
|
|
import com.guo.learningprogresstracker.entity.TaskEntity;
|
|
import com.guo.learningprogresstracker.enums.StudySessionStateEnum;
|
|
import com.guo.learningprogresstracker.exception.ErrorParameterException;
|
|
import com.guo.learningprogresstracker.exception.NotFindEntitiesException;
|
|
import com.guo.learningprogresstracker.mapStruct.StudySessionConvert;
|
|
import com.guo.learningprogresstracker.mapper.StudyReportFragmentsMapper;
|
|
import com.guo.learningprogresstracker.mapper.StudyReportsMapper;
|
|
import com.guo.learningprogresstracker.mapper.StudySessionsMapper;
|
|
import com.guo.learningprogresstracker.service.StudyExpectationsService;
|
|
import com.guo.learningprogresstracker.service.StudySessionsService;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
import java.time.LocalDateTime;
|
|
import java.util.ArrayList;
|
|
import java.util.Optional;
|
|
import java.util.stream.Collectors;
|
|
|
|
/**
|
|
* 针对表【study_sessions(记录每次学习会话的具体数据)】的数据库操作Service实现
|
|
*/
|
|
@Slf4j
|
|
@Service
|
|
@RequiredArgsConstructor
|
|
public class StudySessionsServiceImpl extends ServiceImpl<StudySessionsMapper, StudySessionsEntity>
|
|
implements StudySessionsService {
|
|
|
|
public static final int WORK_DURATION = 25;
|
|
|
|
private final TasksServiceImpl tasksServiceImpl;
|
|
private final StudyReportFragmentsServiceImpl studyReportFragmentsServiceImpl;
|
|
private final StudyReportFragmentsMapper studyReportFragmentsMapper;
|
|
private final StudyReportsMapper studyReportsMapper;
|
|
private final AiServiceClient aiServiceClient;
|
|
private final StudyExpectationsService studyExpectationsService;
|
|
|
|
public StudySessionResponse startOrContinueStudySession(String taskNum) throws NotFindEntitiesException, ServiceException {
|
|
TaskEntity taskEntity = tasksServiceImpl.getOneOpt(
|
|
Wrappers.lambdaQuery(TaskEntity.class).eq(TaskEntity::getTaskNum, taskNum))
|
|
.orElseThrow(() -> new NotFindEntitiesException(String.format("[%s]不存在", taskNum)));
|
|
|
|
StudySessionsEntity session = this.getOneOpt(Wrappers.<StudySessionsEntity>lambdaQuery()
|
|
.eq(StudySessionsEntity::getTaskNum, taskNum)
|
|
.ne(StudySessionsEntity::getSessionState, StudySessionStateEnum.ENDED.name()))
|
|
.orElseGet(() -> {
|
|
// 如果没有则创建新会话
|
|
StudySessionsEntity newSession = StudySessionsEntity.initNewSession();
|
|
newSession.setTaskNum(taskNum);
|
|
newSession.setLastStartTime(LocalDateTime.now());
|
|
this.save(newSession);
|
|
return newSession;
|
|
});
|
|
|
|
session.calculatePointerPosition();
|
|
|
|
if (session.isOverTime()) {
|
|
session.pausedStudySession(session.getLastStartTime().plusMinutes(WORK_DURATION));
|
|
this.updateById(session);
|
|
}
|
|
|
|
StudySessionResponse response = StudySessionConvert.MAPPER.toStudySessionResponse(session);
|
|
response.setTaskName(taskEntity.getTaskName());
|
|
response.setMaterialUrl(taskEntity.getMaterialUrl());
|
|
response.setTaskId(taskEntity.getId());
|
|
|
|
if (session.isOverTime()) {
|
|
response.setSystemMessage("上段学习任务已经超时25分钟,将仅计算为25分钟的有效学习时间,请注意休息!");
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
|
|
@Override
|
|
public void pauseStudySession(String sessionNum, LocalDateTime endTime) throws ErrorParameterException, ServiceException {
|
|
StudySessionsEntity studySessionsEntity = this.getOneOpt(Wrappers.lambdaQuery(StudySessionsEntity.class)
|
|
.eq(StudySessionsEntity::getSessionNum, sessionNum))
|
|
.orElseThrow(() -> new ErrorParameterException("会话[" + sessionNum + "]不存在"));
|
|
if (StudySessionStateEnum.PAUSED.name().equals(studySessionsEntity.getSessionState())) {
|
|
log.error("会话[" + studySessionsEntity.getSessionNum() + "]重复暂停");
|
|
throw new ErrorParameterException("会话[" + sessionNum + "]已经暂停,请勿重复暂停!");
|
|
}
|
|
studySessionsEntity.calculatePointerPosition();
|
|
studySessionsEntity.pausedStudySession(endTime);
|
|
this.updateById(studySessionsEntity);
|
|
}
|
|
|
|
|
|
@Override
|
|
@Transactional
|
|
public String endedStudySession(String sessionNum, String content) throws ErrorParameterException {
|
|
StudySessionsEntity studySessionsEntity = this.getOneOpt(Wrappers.lambdaQuery(StudySessionsEntity.class)
|
|
.eq(StudySessionsEntity::getSessionNum, sessionNum))
|
|
.orElseThrow(() -> new ErrorParameterException("会话[" + sessionNum + "]不存在"));
|
|
boolean wasOngoing = StudySessionStateEnum.ONGOING.name().equals(studySessionsEntity.getSessionState());
|
|
studySessionsEntity.endedStudySession();
|
|
this.updateById(studySessionsEntity);
|
|
StudyReportsEntity studyReportsEntity = new StudyReportsEntity();
|
|
studyReportsEntity.setSessionNum(sessionNum);
|
|
studyReportsEntity.setContent(content);
|
|
studyReportsMapper.insert(studyReportsEntity);
|
|
if (wasOngoing && studySessionsEntity.getEffectiveTime() == 0) {
|
|
return "本次有效学习时间不足10分钟,不计入总学习时间";
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public StudySessionResponse getNotEndedStudySessionByTaskNum(String taskNum) throws ErrorParameterException {
|
|
TaskEntity task = tasksServiceImpl.getOneOpt(Wrappers.lambdaQuery(TaskEntity.class)
|
|
.eq(TaskEntity::getTaskNum, taskNum))
|
|
.orElseThrow(() -> new ErrorParameterException("任务[" + taskNum + "]不存在"));
|
|
StudySessionsDto dto = Optional.ofNullable(studyReportsMapper.getNotEndedStudySessionDtoByTaskNum(taskNum))
|
|
.orElseThrow(() -> new ErrorParameterException("任务[" + taskNum + "]不存在进行中或暂停中的会话"));
|
|
return StudySessionConvert.MAPPER.toStudySessionResponse(dto);
|
|
}
|
|
|
|
@Override
|
|
public ArrayList<String> getAllFragments(String sessionNum) throws ErrorParameterException {
|
|
if (this.exists(Wrappers.lambdaQuery(StudySessionsEntity.class)
|
|
.eq(StudySessionsEntity::getSessionNum, sessionNum))) {
|
|
return studyReportFragmentsMapper.selectList(Wrappers.lambdaQuery(StudyReportFragmentsEntity.class)
|
|
.eq(StudyReportFragmentsEntity::getSessionNum, sessionNum))
|
|
.stream().map(StudyReportFragmentsEntity::getContent).collect(Collectors.toCollection(ArrayList::new));
|
|
} else {
|
|
throw new ErrorParameterException("会话[" + sessionNum + "]不存在");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
@Transactional
|
|
public void continueStudySession(String sessionNum) throws ErrorParameterException, ServiceException {
|
|
StudySessionsEntity studySessionsEntity = this.getOneOpt(Wrappers.lambdaQuery(StudySessionsEntity.class)
|
|
.eq(StudySessionsEntity::getSessionNum, sessionNum))
|
|
.orElseThrow(() -> new ErrorParameterException("会话[" + sessionNum + "]不存在"));
|
|
if (StudySessionStateEnum.ONGOING.name().equals(studySessionsEntity.getSessionState())) {
|
|
log.error("会话[" + studySessionsEntity.getSessionNum() + "]重复开始");
|
|
throw new ErrorParameterException("会话[" + sessionNum + "]已经开始,请勿重复开始!");
|
|
} else if (StudySessionStateEnum.ENDED.name().equals(studySessionsEntity.getSessionState())) {
|
|
log.error("会话[" + studySessionsEntity.getSessionNum() + "]处于结束状态,不可开始该会话!");
|
|
throw new ErrorParameterException("会话[" + sessionNum + "]处于结束状态,不可开始该会话!");
|
|
}
|
|
studySessionsEntity.continueStudySession();
|
|
this.updateById(studySessionsEntity);
|
|
}
|
|
|
|
/**
|
|
* 通过 sessionNum 获取学习会话
|
|
*/
|
|
public StudySessionResponse getStudySessionBySessionNum(String sessionNum) throws ErrorParameterException {
|
|
StudySessionsEntity session = this.getOneOpt(Wrappers.lambdaQuery(StudySessionsEntity.class)
|
|
.eq(StudySessionsEntity::getSessionNum, sessionNum))
|
|
.orElseThrow(() -> new ErrorParameterException("会话[" + sessionNum + "]不存在"));
|
|
return StudySessionConvert.MAPPER.toStudySessionResponse(session);
|
|
}
|
|
|
|
/**
|
|
* 生成学习报告草稿:优先调用 lpt-ai 聚合残片,服务不可用时降级为按序拼接。
|
|
* 草稿仅作为编辑起点返回,不落库——最终报告由用户确认后经 endedStudySession 保存。
|
|
*/
|
|
public String generateReportDraft(String sessionNum) throws ErrorParameterException {
|
|
StudySessionsEntity session = this.getOneOpt(Wrappers.lambdaQuery(StudySessionsEntity.class)
|
|
.eq(StudySessionsEntity::getSessionNum, sessionNum))
|
|
.orElseThrow(() -> new ErrorParameterException("会话[" + sessionNum + "]不存在"));
|
|
|
|
ArrayList<String> fragments = getAllFragments(sessionNum);
|
|
if (fragments.isEmpty()) {
|
|
return "";
|
|
}
|
|
|
|
String taskName = tasksServiceImpl.getOneOpt(
|
|
Wrappers.lambdaQuery(TaskEntity.class).eq(TaskEntity::getTaskNum, session.getTaskNum()))
|
|
.map(TaskEntity::getTaskName)
|
|
.orElse("学习任务");
|
|
String expectation = Optional.ofNullable(studyExpectationsService.getBySessionNum(sessionNum))
|
|
.map(e -> e.getDescription())
|
|
.orElse(null);
|
|
|
|
return aiServiceClient.aggregateReport(taskName, fragments, expectation)
|
|
.orElseGet(() -> String.join("\n", fragments));
|
|
}
|
|
|
|
// ============ 分页查询任务的历史残片/报告 ============
|
|
|
|
@Override
|
|
public Page<StudyReportFragmentsEntity> getTaskFragments(String taskNum, int page, int size, String keyword) {
|
|
Page<StudyReportFragmentsEntity> pg = new Page<>(page, size);
|
|
var wrapper = Wrappers.lambdaQuery(StudyReportFragmentsEntity.class)
|
|
.apply("session_num in (select session_num from study_sessions where task_num = {0})", taskNum)
|
|
.orderByDesc(StudyReportFragmentsEntity::getCreatedTime);
|
|
if (keyword != null && !keyword.isBlank()) {
|
|
wrapper.like(StudyReportFragmentsEntity::getContent, keyword);
|
|
}
|
|
return studyReportFragmentsMapper.selectPage(pg, wrapper);
|
|
}
|
|
|
|
@Override
|
|
public Page<StudyReportsEntity> getTaskReports(String taskNum, int page, int size, String keyword) {
|
|
Page<StudyReportsEntity> pg = new Page<>(page, size);
|
|
var wrapper = Wrappers.lambdaQuery(StudyReportsEntity.class)
|
|
.apply("session_num in (select session_num from study_sessions where task_num = {0})", taskNum)
|
|
.orderByDesc(StudyReportsEntity::getCreatedTime);
|
|
if (keyword != null && !keyword.isBlank()) {
|
|
wrapper.like(StudyReportsEntity::getContent, keyword);
|
|
}
|
|
Page<StudyReportsEntity> result = studyReportsMapper.selectPage(pg, wrapper);
|
|
// 填充每个报告对应会话的预期目标
|
|
for (StudyReportsEntity report : result.getRecords()) {
|
|
if (report.getSessionNum() != null) {
|
|
Optional.ofNullable(studyExpectationsService.getBySessionNum(report.getSessionNum()))
|
|
.ifPresent(e -> report.setSessionExpectation(e.getDescription()));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// ============ 活跃会话查询 ============
|
|
|
|
/**
|
|
* 查询当前用户是否有活跃会话(ONGOING 或 PAUSED)。
|
|
*
|
|
* @param excludeTaskNum 可选,排除指定任务(同一任务继续学习时不会视为冲突)
|
|
* @return 活跃会话响应;无活跃会话时返回 null
|
|
*/
|
|
@Override
|
|
public StudySessionResponse getActiveSession(String excludeTaskNum) {
|
|
var wrapper = Wrappers.lambdaQuery(StudySessionsEntity.class)
|
|
.in(StudySessionsEntity::getSessionState,
|
|
StudySessionStateEnum.ONGOING.name(),
|
|
StudySessionStateEnum.PAUSED.name())
|
|
.orderByDesc(StudySessionsEntity::getCreatedTime)
|
|
.last("LIMIT 1");
|
|
|
|
StudySessionsEntity session = this.getOne(wrapper);
|
|
if (session == null) return null;
|
|
|
|
// 如果活跃会话属于被排除的任务,视为无冲突
|
|
if (excludeTaskNum != null && excludeTaskNum.equals(session.getTaskNum())) {
|
|
return null;
|
|
}
|
|
|
|
String taskName = tasksServiceImpl.getOneOpt(
|
|
Wrappers.lambdaQuery(TaskEntity.class).eq(TaskEntity::getTaskNum, session.getTaskNum()))
|
|
.map(TaskEntity::getTaskName)
|
|
.orElse(null);
|
|
|
|
StudySessionResponse response = StudySessionConvert.MAPPER.toStudySessionResponse(session);
|
|
response.setTaskName(taskName);
|
|
return response;
|
|
}
|
|
|
|
}
|