package com.guo.learningprogresstracker.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.google.protobuf.ServiceException; import com.guo.learningprogresstracker.enums.StudySessionStateEnum; import com.guo.learningprogresstracker.utils.GenerateNumTool; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.util.ObjectUtils; import java.io.Serializable; import java.time.Duration; import java.time.LocalDateTime; import static com.guo.learningprogresstracker.service.impl.StudySessionsServiceImpl.WORK_DURATION; /** * 记录每次学习会话的具体数据 * @TableName study_sessions */ @TableName(value ="study_sessions") @Slf4j @Data public class StudySessionsEntity extends BaseEntity implements Serializable { @TableId(type = IdType.AUTO) private Integer id; @TableField(value = "session_num") private String sessionNum; /** * 任务编号 */ @TableField(value = "task_num") private String taskNum; /** * 学习开始时间 */ @TableField(value = "start_time") private LocalDateTime startTime; @TableField(value = "last_start_time") private LocalDateTime lastStartTime; /** * 学习结束时间 */ @TableField(value = "end_time") private LocalDateTime endTime; /** * 实际使用时间(秒) */ @TableField(value = "actual_time") private double actualTime; /** * 有效学习时间(秒) */ @TableField(value = "effective_time") private double effectiveTime; /** * 有效时间比 */ @TableField(value = "effectiveness_ratio") private double effectivenessRatio; /** * 学习会话的状态(进行中、暂停、已结束) */ @TableField(value = "session_state") private String sessionState; /** * 计时器指针位置,时间戳形式存储(毫秒) */ @TableField(value = "pointer_position") private Long pointerPosition; @TableField(exist = false) private static final long serialVersionUID = 1L; /** * 初始化新会话 * @return */ public static StudySessionsEntity initNewSession(){ StudySessionsEntity studySessionsEntity = new StudySessionsEntity(); studySessionsEntity.setSessionNum(GenerateNumTool.generateNum("SESSION")); studySessionsEntity.setStartTime(LocalDateTime.now()); studySessionsEntity.setLastStartTime(LocalDateTime.now()); studySessionsEntity.setPointerPosition(WORK_DURATION * 60 * 1000L); studySessionsEntity.setLastModifiedTime(LocalDateTime.now()); studySessionsEntity.setSessionState(StudySessionStateEnum.ONGOING.name()); return studySessionsEntity; } /** * 继续会话 */ public void continueStudySession() throws ServiceException { if (this.getSessionState().equals(StudySessionStateEnum.PAUSED.name())){ this.setSessionState(StudySessionStateEnum.ONGOING.name()); this.setLastStartTime(LocalDateTime.now()); this.calculatePointerPosition(); } else if (this.getSessionState().equals(StudySessionStateEnum.ONGOING.name())) { log.info("进行中的学习会话({})被重新开始",this.getSessionNum()); } else{ log.warn("不应出现的情况:开始了一个状态为【{}】的学习会话({})",this.getSessionState(),this.getSessionNum()); } } /** * 暂停会话 */ public void pausedStudySession(LocalDateTime endTime){ if (this.getSessionState().equals(StudySessionStateEnum.PAUSED.name())) { log.warn("不应出现的情况:暂停了一个状态为【{}】的学习会话({})", this.getSessionState(), this.getSessionNum()); } else { this.setEndTime(ObjectUtils.isEmpty(endTime) ? LocalDateTime.now() : endTime); this.setActualTime(Duration.between(this.startTime, this.endTime).toSeconds()); this.setEffectiveTime(this.getEffectiveTime() + Duration.between(this.lastStartTime, this.endTime).toSeconds()); this.setEffectivenessRatio(this.effectiveTime / this.actualTime); this.setSessionState(StudySessionStateEnum.PAUSED.name()); } } /** * 有效学习时间的最小阈值(秒),低于此值不计入总学习时间 */ private static final double MIN_EFFECTIVE_TIME_SECONDS = 10 * 60; /** * 结束会话 */ public void endedStudySession() { if (this.getSessionState().equals(StudySessionStateEnum.ENDED.name())){ log.warn("不应出现的情况:结束了一个状态为【{}】的学习会话",this.getSessionState()); }else { if (this.getSessionState().equals(StudySessionStateEnum.ONGOING.name())) { this.pausedStudySession(LocalDateTime.now()); } if (this.getEffectiveTime() < MIN_EFFECTIVE_TIME_SECONDS) { log.info("会话[{}]有效学习时间({}秒)不足10分钟,不计入总学习时间", this.getSessionNum(), this.getEffectiveTime()); this.setEffectiveTime(0); this.setEffectivenessRatio(0); } this.setSessionState(StudySessionStateEnum.ENDED.name()); } } /** * 第一次运行 * @return */ public boolean isFirstRun() { return ObjectUtils.isEmpty(this.pointerPosition); } /** * 计算当前倒计时指针的位置(单位:毫秒) * * 规则说明: * - 如果是第一次运行(lastStartTime为null),则倒计时从25分钟开始 * - 如果是暂停状态,返回上次保存的pointerPosition * - 如果是进行中状态,返回 pointerPosition -(当前时间 - lastStartTime) * 即从上次开始后经历的时间,从之前保存的pointerPosition中扣除 * * @return 指针当前位置,单位为毫秒 */ public void calculatePointerPosition() throws ServiceException { if (isFirstRun()) { this.pointerPosition = WORK_DURATION * 60 * 1000L; // 毫秒 return; } try { StudySessionStateEnum state = StudySessionStateEnum.valueOf(sessionState); switch (state) { case PAUSED: // 暂停状态,不修改 pointerPosition return; case ONGOING: long elapsedMillis = Duration.between(lastStartTime, LocalDateTime.now()).toMillis(); long basePointerPosition = ObjectUtils.isEmpty(pointerPosition) ? WORK_DURATION * 60 * 1000L : pointerPosition; long newPointerPosition = basePointerPosition - elapsedMillis; if (newPointerPosition <= 0) { // 如果循环已经结束,则需要重置指针位置 this.pointerPosition = WORK_DURATION * 60 * 1000L; } else { this.pointerPosition = newPointerPosition; } return; case ENDED: throw new ServiceException("已经结束的任务不应再计算倒计时起始位置"); default: log.error("未知学习会话状态: {}", this.sessionState); throw new RuntimeException("未知学习会话状态,无法计算倒计时起始位置"); } } catch (IllegalArgumentException e) { log.error("无效的学习会话状态: {}", sessionState, e); throw new ServiceException("无效的学习会话状态: " + sessionState, e); } } public boolean isOverTime() { LocalDateTime now = LocalDateTime.now(); long minutes = Duration.between(ObjectUtils.isEmpty(lastStartTime) ? LocalDateTime.now() : lastStartTime, now).toMinutes(); return minutes >= WORK_DURATION * 2; } }