From 81543969d8b3b902016ee04183165cc6885ec751 Mon Sep 17 00:00:00 2001 From: cat_shark <1716967236@qq.com> Date: Thu, 18 Jun 2026 00:07:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=A4=8D=E4=B9=A0?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E7=BB=9F=E8=AE=A1=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReviewController.java | 17 +++ .../dto/ReviewTaskStats.java | 20 +++ .../service/ReviewService.java | 11 ++ .../service/impl/ReviewServiceImpl.java | 131 ++++++++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 src/main/java/com/guo/learningprogresstracker/dto/ReviewTaskStats.java diff --git a/src/main/java/com/guo/learningprogresstracker/controller/ReviewController.java b/src/main/java/com/guo/learningprogresstracker/controller/ReviewController.java index 3c512fc..c177c92 100644 --- a/src/main/java/com/guo/learningprogresstracker/controller/ReviewController.java +++ b/src/main/java/com/guo/learningprogresstracker/controller/ReviewController.java @@ -1,6 +1,7 @@ package com.guo.learningprogresstracker.controller; import com.guo.learningprogresstracker.dto.ReviewFeedItem; +import com.guo.learningprogresstracker.dto.ReviewTaskStats; import com.guo.learningprogresstracker.entity.CommonResult; import com.guo.learningprogresstracker.entity.StudyReportFragmentsEntity; import com.guo.learningprogresstracker.entity.StudyReportsEntity; @@ -32,6 +33,22 @@ public class ReviewController { return CommonResult.success(reviewService.getReviewFeed(limit)); } + /** + * 获取任务维度的复习统计汇总 + */ + @GetMapping("/tasks") + public CommonResult> getReviewTaskStats() { + return CommonResult.success(reviewService.getReviewTaskStats()); + } + + /** + * 获取指定任务的复习统计汇总 + */ + @GetMapping("/tasks/{taskNum}") + public CommonResult getReviewTaskStats(@PathVariable String taskNum) { + return CommonResult.success(reviewService.getReviewTaskStats(taskNum)); + } + /** * 获取指定任务下的所有报告和残片 */ diff --git a/src/main/java/com/guo/learningprogresstracker/dto/ReviewTaskStats.java b/src/main/java/com/guo/learningprogresstracker/dto/ReviewTaskStats.java new file mode 100644 index 0000000..21a9c23 --- /dev/null +++ b/src/main/java/com/guo/learningprogresstracker/dto/ReviewTaskStats.java @@ -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; +} diff --git a/src/main/java/com/guo/learningprogresstracker/service/ReviewService.java b/src/main/java/com/guo/learningprogresstracker/service/ReviewService.java index 052ffb2..4a4e1af 100644 --- a/src/main/java/com/guo/learningprogresstracker/service/ReviewService.java +++ b/src/main/java/com/guo/learningprogresstracker/service/ReviewService.java @@ -1,6 +1,7 @@ package com.guo.learningprogresstracker.service; import com.guo.learningprogresstracker.dto.ReviewFeedItem; +import com.guo.learningprogresstracker.dto.ReviewTaskStats; import com.guo.learningprogresstracker.entity.StudyReportFragmentsEntity; import com.guo.learningprogresstracker.entity.StudyReportsEntity; import com.guo.learningprogresstracker.exception.NotFindEntitiesException; @@ -17,6 +18,16 @@ public interface ReviewService { */ List getReviewFeed(int limit); + /** + * 获取所有任务的复习统计数据 + */ + List getReviewTaskStats(); + + /** + * 获取指定任务的复习统计数据 + */ + ReviewTaskStats getReviewTaskStats(String taskNum); + /** * 获取指定任务下的所有报告和残片 */ diff --git a/src/main/java/com/guo/learningprogresstracker/service/impl/ReviewServiceImpl.java b/src/main/java/com/guo/learningprogresstracker/service/impl/ReviewServiceImpl.java index 263b3f2..af70420 100644 --- a/src/main/java/com/guo/learningprogresstracker/service/impl/ReviewServiceImpl.java +++ b/src/main/java/com/guo/learningprogresstracker/service/impl/ReviewServiceImpl.java @@ -2,6 +2,7 @@ package com.guo.learningprogresstracker.service.impl; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.guo.learningprogresstracker.dto.ReviewFeedItem; +import com.guo.learningprogresstracker.dto.ReviewTaskStats; import com.guo.learningprogresstracker.entity.StudyReportFragmentsEntity; import com.guo.learningprogresstracker.entity.StudyReportsEntity; import com.guo.learningprogresstracker.entity.StudySessionsEntity; @@ -14,7 +15,11 @@ import com.guo.learningprogresstracker.mapper.TasksMapper; import com.guo.learningprogresstracker.service.ReviewService; import lombok.RequiredArgsConstructor; 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.stream.Collectors; import java.util.stream.Stream; @@ -46,6 +51,35 @@ public class ReviewServiceImpl implements ReviewService { return mergeAndConvert(reports, fragments); } + @Override + public List getReviewTaskStats() { + List tasks = tasksMapper.selectList( + Wrappers.lambdaQuery() + .orderByDesc(TaskEntity::getCalculatedPriority)); + + List stats = tasks.stream() + .map(this::newTaskStats) + .collect(Collectors.toList()); + + fillReviewTaskStats(stats); + return stats; + } + + @Override + public ReviewTaskStats getReviewTaskStats(String taskNum) { + TaskEntity task = tasksMapper.selectList( + Wrappers.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 public List getTaskReview(String taskNum) { List sessionNums = studySessionsMapper.selectList( @@ -151,4 +185,101 @@ public class ReviewServiceImpl implements ReviewService { Comparator.nullsLast(Comparator.reverseOrder()))) .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 statsList) { + Map 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 sessions = studySessionsMapper.selectList( + Wrappers.lambdaQuery() + .in(StudySessionsEntity::getTaskNum, statsByTaskNum.keySet())); + + LocalDateTime todayStart = LocalDate.now().atStartOfDay(); + LocalDateTime weekStart = LocalDate.now().with(DayOfWeek.MONDAY).atStartOfDay(); + Map sessionToTaskNum = new HashMap<>(); + Map 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 sessionToTaskNum, + Map statsByTaskNum) { + if (sessionToTaskNum.isEmpty()) { + return; + } + + Set sessionNums = sessionToTaskNum.keySet(); + List reports = studyReportsMapper.selectList( + Wrappers.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 fragments = studyReportFragmentsMapper.selectList( + Wrappers.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); + } + } + } }