feat: 新增复习任务统计接口

This commit is contained in:
2026-06-18 00:07:43 +08:00
parent 2cf3533141
commit 81543969d8
4 changed files with 179 additions and 0 deletions
@@ -1,6 +1,7 @@
package com.guo.learningprogresstracker.controller; package com.guo.learningprogresstracker.controller;
import com.guo.learningprogresstracker.dto.ReviewFeedItem; import com.guo.learningprogresstracker.dto.ReviewFeedItem;
import com.guo.learningprogresstracker.dto.ReviewTaskStats;
import com.guo.learningprogresstracker.entity.CommonResult; import com.guo.learningprogresstracker.entity.CommonResult;
import com.guo.learningprogresstracker.entity.StudyReportFragmentsEntity; import com.guo.learningprogresstracker.entity.StudyReportFragmentsEntity;
import com.guo.learningprogresstracker.entity.StudyReportsEntity; import com.guo.learningprogresstracker.entity.StudyReportsEntity;
@@ -32,6 +33,22 @@ public class ReviewController {
return CommonResult.success(reviewService.getReviewFeed(limit)); return CommonResult.success(reviewService.getReviewFeed(limit));
} }
/**
* 获取任务维度的复习统计汇总
*/
@GetMapping("/tasks")
public CommonResult<List<ReviewTaskStats>> getReviewTaskStats() {
return CommonResult.success(reviewService.getReviewTaskStats());
}
/**
* 获取指定任务的复习统计汇总
*/
@GetMapping("/tasks/{taskNum}")
public CommonResult<ReviewTaskStats> getReviewTaskStats(@PathVariable String taskNum) {
return CommonResult.success(reviewService.getReviewTaskStats(taskNum));
}
/** /**
* 获取指定任务下的所有报告和残片 * 获取指定任务下的所有报告和残片
*/ */
@@ -0,0 +1,20 @@
package com.guo.learningprogresstracker.dto;
import lombok.Data;
/**
* 任务维度的复习统计数据。
*/
@Data
public class ReviewTaskStats {
private String taskNum;
private String taskName;
private long reportCount;
private long fragmentCount;
private double effectiveTime;
private long sessionCount;
private double todayEffectiveTime;
private double weekEffectiveTime;
private double avgEffectiveTime;
private double avgEffectivenessRatio;
}
@@ -1,6 +1,7 @@
package com.guo.learningprogresstracker.service; package com.guo.learningprogresstracker.service;
import com.guo.learningprogresstracker.dto.ReviewFeedItem; import com.guo.learningprogresstracker.dto.ReviewFeedItem;
import com.guo.learningprogresstracker.dto.ReviewTaskStats;
import com.guo.learningprogresstracker.entity.StudyReportFragmentsEntity; import com.guo.learningprogresstracker.entity.StudyReportFragmentsEntity;
import com.guo.learningprogresstracker.entity.StudyReportsEntity; import com.guo.learningprogresstracker.entity.StudyReportsEntity;
import com.guo.learningprogresstracker.exception.NotFindEntitiesException; import com.guo.learningprogresstracker.exception.NotFindEntitiesException;
@@ -17,6 +18,16 @@ public interface ReviewService {
*/ */
List<ReviewFeedItem> getReviewFeed(int limit); List<ReviewFeedItem> getReviewFeed(int limit);
/**
* 获取所有任务的复习统计数据
*/
List<ReviewTaskStats> getReviewTaskStats();
/**
* 获取指定任务的复习统计数据
*/
ReviewTaskStats getReviewTaskStats(String taskNum);
/** /**
* 获取指定任务下的所有报告和残片 * 获取指定任务下的所有报告和残片
*/ */
@@ -2,6 +2,7 @@ package com.guo.learningprogresstracker.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.guo.learningprogresstracker.dto.ReviewFeedItem; import com.guo.learningprogresstracker.dto.ReviewFeedItem;
import com.guo.learningprogresstracker.dto.ReviewTaskStats;
import com.guo.learningprogresstracker.entity.StudyReportFragmentsEntity; import com.guo.learningprogresstracker.entity.StudyReportFragmentsEntity;
import com.guo.learningprogresstracker.entity.StudyReportsEntity; import com.guo.learningprogresstracker.entity.StudyReportsEntity;
import com.guo.learningprogresstracker.entity.StudySessionsEntity; import com.guo.learningprogresstracker.entity.StudySessionsEntity;
@@ -14,7 +15,11 @@ import com.guo.learningprogresstracker.mapper.TasksMapper;
import com.guo.learningprogresstracker.service.ReviewService; import com.guo.learningprogresstracker.service.ReviewService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -46,6 +51,35 @@ public class ReviewServiceImpl implements ReviewService {
return mergeAndConvert(reports, fragments); return mergeAndConvert(reports, fragments);
} }
@Override
public List<ReviewTaskStats> getReviewTaskStats() {
List<TaskEntity> tasks = tasksMapper.selectList(
Wrappers.<TaskEntity>lambdaQuery()
.orderByDesc(TaskEntity::getCalculatedPriority));
List<ReviewTaskStats> stats = tasks.stream()
.map(this::newTaskStats)
.collect(Collectors.toList());
fillReviewTaskStats(stats);
return stats;
}
@Override
public ReviewTaskStats getReviewTaskStats(String taskNum) {
TaskEntity task = tasksMapper.selectList(
Wrappers.<TaskEntity>lambdaQuery()
.eq(TaskEntity::getTaskNum, taskNum)
.last("LIMIT 1"))
.stream()
.findFirst()
.orElse(null);
ReviewTaskStats stats = newTaskStats(taskNum, task == null ? null : task.getTaskName());
fillReviewTaskStats(Collections.singletonList(stats));
return stats;
}
@Override @Override
public List<ReviewFeedItem> getTaskReview(String taskNum) { public List<ReviewFeedItem> getTaskReview(String taskNum) {
List<String> sessionNums = studySessionsMapper.selectList( List<String> sessionNums = studySessionsMapper.selectList(
@@ -151,4 +185,101 @@ public class ReviewServiceImpl implements ReviewService {
Comparator.nullsLast(Comparator.reverseOrder()))) Comparator.nullsLast(Comparator.reverseOrder())))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private ReviewTaskStats newTaskStats(TaskEntity task) {
return newTaskStats(task.getTaskNum(), task.getTaskName());
}
private ReviewTaskStats newTaskStats(String taskNum, String taskName) {
ReviewTaskStats stats = new ReviewTaskStats();
stats.setTaskNum(taskNum);
stats.setTaskName(taskName);
return stats;
}
private void fillReviewTaskStats(List<ReviewTaskStats> statsList) {
Map<String, ReviewTaskStats> statsByTaskNum = statsList.stream()
.filter(item -> StringUtils.hasText(item.getTaskNum()))
.collect(Collectors.toMap(
ReviewTaskStats::getTaskNum,
item -> item,
(a, b) -> a,
LinkedHashMap::new));
if (statsByTaskNum.isEmpty()) {
return;
}
List<StudySessionsEntity> sessions = studySessionsMapper.selectList(
Wrappers.<StudySessionsEntity>lambdaQuery()
.in(StudySessionsEntity::getTaskNum, statsByTaskNum.keySet()));
LocalDateTime todayStart = LocalDate.now().atStartOfDay();
LocalDateTime weekStart = LocalDate.now().with(DayOfWeek.MONDAY).atStartOfDay();
Map<String, String> sessionToTaskNum = new HashMap<>();
Map<String, Double> effectivenessRatioSum = new HashMap<>();
for (StudySessionsEntity session : sessions) {
ReviewTaskStats stats = statsByTaskNum.get(session.getTaskNum());
if (stats == null) {
continue;
}
if (StringUtils.hasText(session.getSessionNum())) {
sessionToTaskNum.put(session.getSessionNum(), session.getTaskNum());
}
double effectiveTime = session.getEffectiveTime();
stats.setSessionCount(stats.getSessionCount() + 1);
stats.setEffectiveTime(stats.getEffectiveTime() + effectiveTime);
effectivenessRatioSum.merge(session.getTaskNum(), session.getEffectivenessRatio(), Double::sum);
LocalDateTime startTime = session.getStartTime();
if (startTime != null && !startTime.isBefore(todayStart)) {
stats.setTodayEffectiveTime(stats.getTodayEffectiveTime() + effectiveTime);
}
if (startTime != null && !startTime.isBefore(weekStart)) {
stats.setWeekEffectiveTime(stats.getWeekEffectiveTime() + effectiveTime);
}
}
statsByTaskNum.forEach((taskNum, stats) -> {
if (stats.getSessionCount() > 0) {
stats.setAvgEffectiveTime(stats.getEffectiveTime() / stats.getSessionCount());
stats.setAvgEffectivenessRatio(effectivenessRatioSum.getOrDefault(taskNum, 0D) / stats.getSessionCount());
}
});
fillReportAndFragmentCount(sessionToTaskNum, statsByTaskNum);
}
private void fillReportAndFragmentCount(Map<String, String> sessionToTaskNum,
Map<String, ReviewTaskStats> statsByTaskNum) {
if (sessionToTaskNum.isEmpty()) {
return;
}
Set<String> sessionNums = sessionToTaskNum.keySet();
List<StudyReportsEntity> reports = studyReportsMapper.selectList(
Wrappers.<StudyReportsEntity>lambdaQuery()
.in(StudyReportsEntity::getSessionNum, sessionNums)
.select(StudyReportsEntity::getSessionNum));
for (StudyReportsEntity report : reports) {
ReviewTaskStats stats = statsByTaskNum.get(sessionToTaskNum.get(report.getSessionNum()));
if (stats != null) {
stats.setReportCount(stats.getReportCount() + 1);
}
}
List<StudyReportFragmentsEntity> fragments = studyReportFragmentsMapper.selectList(
Wrappers.<StudyReportFragmentsEntity>lambdaQuery()
.in(StudyReportFragmentsEntity::getSessionNum, sessionNums)
.select(StudyReportFragmentsEntity::getSessionNum));
for (StudyReportFragmentsEntity fragment : fragments) {
ReviewTaskStats stats = statsByTaskNum.get(sessionToTaskNum.get(fragment.getSessionNum()));
if (stats != null) {
stats.setFragmentCount(stats.getFragmentCount() + 1);
}
}
}
} }