591dc89e53
- StudySessionsEntity: 新增 MIN_EFFECTIVE_TIME_SECONDS 常量, endedStudySession() 中检查 effectiveTime,不足则清零 - StudySessionsService: endedStudySession 返回类型从 void 改为 String
227 lines
8.1 KiB
Java
227 lines
8.1 KiB
Java
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;
|
||
}
|
||
}
|