Files
lpt-be/src/main/java/com/guo/learningprogresstracker/entity/StudySessionsEntity.java
T
cat-shark 591dc89e53 feat: 有效学习时间不足10分钟不计入总学习时间
- StudySessionsEntity: 新增 MIN_EFFECTIVE_TIME_SECONDS 常量,
  endedStudySession() 中检查 effectiveTime,不足则清零
- StudySessionsService: endedStudySession 返回类型从 void 改为 String
2026-05-30 17:36:50 +08:00

227 lines
8.1 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}