refactor: 首页feed仅返回残片

This commit is contained in:
2026-08-03 23:08:14 +08:00
parent c8d0584f11
commit 5b6f7d6a2a
5 changed files with 23 additions and 32 deletions
@@ -35,7 +35,7 @@ public class ReviewController {
private final StandardMindMapService standardMindMapService; private final StandardMindMapService standardMindMapService;
/** /**
* 获取复习 feed合并报告和残片按时间倒序 * 获取复习 feed仅返回学习残片(首页滚动条使用)
*/ */
@GetMapping("/feed") @GetMapping("/feed")
public CommonResult<List<ReviewFeedItem>> getReviewFeed( public CommonResult<List<ReviewFeedItem>> getReviewFeed(
@@ -5,7 +5,7 @@ import lombok.Data;
import java.time.LocalDateTime; import java.time.LocalDateTime;
/** /**
* 复习滚动 feed 条目,合并展示学习报告和残片 * 复习滚动 feed 条目;首页仅使用残片,任务详情仍可同时包含报告和残片
*/ */
@Data @Data
public class ReviewFeedItem { public class ReviewFeedItem {
@@ -14,7 +14,7 @@ import java.util.List;
public interface ReviewService { public interface ReviewService {
/** /**
* 获取复习 feed 列表,合并报告和残片按时间倒序 * 获取复习 feed 列表,仅返回学习残片按时间倒序
*/ */
List<ReviewFeedItem> getReviewFeed(int limit, String mode); List<ReviewFeedItem> getReviewFeed(int limit, String mode);
@@ -16,6 +16,7 @@ import com.guo.learningprogresstracker.mapper.StudySessionsMapper;
import com.guo.learningprogresstracker.mapper.TasksMapper; 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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@@ -29,6 +30,7 @@ import java.util.stream.Stream;
/** /**
* 复习模块 Service 实现 * 复习模块 Service 实现
*/ */
@Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class ReviewServiceImpl implements ReviewService { public class ReviewServiceImpl implements ReviewService {
@@ -53,13 +55,6 @@ public class ReviewServiceImpl implements ReviewService {
} }
boolean random = RANDOM_MODE.equalsIgnoreCase(mode); boolean random = RANDOM_MODE.equalsIgnoreCase(mode);
List<StudyReportsEntity> reports = random
? studyReportsMapper.selectList(Wrappers.<StudyReportsEntity>lambdaQuery()
.last("ORDER BY RAND() LIMIT " + safeLimit))
: studyReportsMapper.selectList(Wrappers.<StudyReportsEntity>lambdaQuery()
.orderByDesc(StudyReportsEntity::getCreatedTime)
.last("LIMIT " + safeLimit));
List<StudyReportFragmentsEntity> fragments = random List<StudyReportFragmentsEntity> fragments = random
? studyReportFragmentsMapper.selectList(Wrappers.<StudyReportFragmentsEntity>lambdaQuery() ? studyReportFragmentsMapper.selectList(Wrappers.<StudyReportFragmentsEntity>lambdaQuery()
.last("ORDER BY RAND() LIMIT " + safeLimit)) .last("ORDER BY RAND() LIMIT " + safeLimit))
@@ -67,7 +62,7 @@ public class ReviewServiceImpl implements ReviewService {
.orderByDesc(StudyReportFragmentsEntity::getCreatedTime) .orderByDesc(StudyReportFragmentsEntity::getCreatedTime)
.last("LIMIT " + safeLimit)); .last("LIMIT " + safeLimit));
List<ReviewFeedItem> items = mergeAndConvert(reports, fragments); List<ReviewFeedItem> items = mergeAndConvert(List.of(), fragments);
if (random) { if (random) {
Collections.shuffle(items); Collections.shuffle(items);
} }
@@ -82,14 +77,11 @@ public class ReviewServiceImpl implements ReviewService {
private List<ReviewFeedItem> getSmartFeed(int safeLimit) { private List<ReviewFeedItem> getSmartFeed(int safeLimit) {
int candidateLimit = Math.min(safeLimit * SMART_CANDIDATE_MULTIPLIER, 500); int candidateLimit = Math.min(safeLimit * SMART_CANDIDATE_MULTIPLIER, 500);
List<StudyReportsEntity> reports = studyReportsMapper.selectList(
Wrappers.<StudyReportsEntity>lambdaQuery()
.last("ORDER BY RAND() LIMIT " + candidateLimit));
List<StudyReportFragmentsEntity> fragments = studyReportFragmentsMapper.selectList( List<StudyReportFragmentsEntity> fragments = studyReportFragmentsMapper.selectList(
Wrappers.<StudyReportFragmentsEntity>lambdaQuery() Wrappers.<StudyReportFragmentsEntity>lambdaQuery()
.last("ORDER BY RAND() LIMIT " + candidateLimit)); .last("ORDER BY RAND() LIMIT " + candidateLimit));
List<ReviewFeedItem> candidates = mergeAndConvert(reports, fragments); List<ReviewFeedItem> candidates = mergeAndConvert(List.of(), fragments);
if (candidates.size() <= safeLimit) { if (candidates.size() <= safeLimit) {
Collections.shuffle(candidates); Collections.shuffle(candidates);
return candidates; return candidates;
@@ -224,13 +216,19 @@ public class ReviewServiceImpl implements ReviewService {
@Override @Override
public StudyReportsEntity getReportDetail(int id) throws NotFindEntitiesException { public StudyReportsEntity getReportDetail(int id) throws NotFindEntitiesException {
return Optional.ofNullable(studyReportsMapper.selectById(id)) return Optional.ofNullable(studyReportsMapper.selectById(id))
.orElseThrow(() -> new NotFindEntitiesException("学习报告[" + id + "]不存在")); .orElseThrow(() -> {
log.warn("学习报告[{}]不存在", id);
return new NotFindEntitiesException("这份学习报告不存在或已被删除");
});
} }
@Override @Override
public StudyReportFragmentsEntity getFragmentDetail(int id) throws NotFindEntitiesException { public StudyReportFragmentsEntity getFragmentDetail(int id) throws NotFindEntitiesException {
return Optional.ofNullable(studyReportFragmentsMapper.selectById(id)) return Optional.ofNullable(studyReportFragmentsMapper.selectById(id))
.orElseThrow(() -> new NotFindEntitiesException("学习残片[" + id + "]不存在")); .orElseThrow(() -> {
log.warn("学习残片[{}]不存在", id);
return new NotFindEntitiesException("这条学习残片不存在或已被删除");
});
} }
/** /**
@@ -321,7 +319,8 @@ public class ReviewServiceImpl implements ReviewService {
private void ensureTaskExists(String taskNum) throws NotFindEntitiesException { private void ensureTaskExists(String taskNum) throws NotFindEntitiesException {
if (!StringUtils.hasText(taskNum) || !tasksMapper.exists( if (!StringUtils.hasText(taskNum) || !tasksMapper.exists(
Wrappers.<TaskEntity>lambdaQuery().eq(TaskEntity::getTaskNum, taskNum))) { Wrappers.<TaskEntity>lambdaQuery().eq(TaskEntity::getTaskNum, taskNum))) {
throw new NotFindEntitiesException("任务[" + taskNum + "]不存在"); log.warn("任务[{}]不存在", taskNum);
throw new NotFindEntitiesException("这个任务不存在或已被删除");
} }
} }
@@ -62,13 +62,13 @@ class ReviewServiceImplTest {
void getReviewFeed_smartMode_prefersLowRecallRatioContent() { void getReviewFeed_smartMode_prefersLowRecallRatioContent() {
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
StudyReportsEntity oldLowMastery = new StudyReportsEntity(); StudyReportFragmentsEntity oldLowMastery = new StudyReportFragmentsEntity();
oldLowMastery.setId(1); oldLowMastery.setId(1);
oldLowMastery.setSessionNum("S_LOW"); oldLowMastery.setSessionNum("S_LOW");
oldLowMastery.setContent("很久没复习且掌握度低"); oldLowMastery.setContent("很久没复习且掌握度低");
oldLowMastery.setCreatedTime(now.minusDays(60)); oldLowMastery.setCreatedTime(now.minusDays(60));
StudyReportsEntity freshHighMastery = new StudyReportsEntity(); StudyReportFragmentsEntity freshHighMastery = new StudyReportFragmentsEntity();
freshHighMastery.setId(2); freshHighMastery.setId(2);
freshHighMastery.setSessionNum("S_HIGH"); freshHighMastery.setSessionNum("S_HIGH");
freshHighMastery.setContent("刚学完且掌握度高"); freshHighMastery.setContent("刚学完且掌握度高");
@@ -93,8 +93,7 @@ class ReviewServiceImplTest {
highRatioRecord.setRecallRatio(1.0); highRatioRecord.setRecallRatio(1.0);
highRatioRecord.setCreatedTime(now); highRatioRecord.setCreatedTime(now);
when(studyReportsMapper.selectList(any())).thenReturn(List.of(oldLowMastery, freshHighMastery)); when(studyReportFragmentsMapper.selectList(any())).thenReturn(List.of(oldLowMastery, freshHighMastery));
when(studyReportFragmentsMapper.selectList(any())).thenReturn(List.of());
when(studySessionsMapper.selectList(any())).thenReturn(List.of(sessionLow, sessionHigh)); when(studySessionsMapper.selectList(any())).thenReturn(List.of(sessionLow, sessionHigh));
when(tasksMapper.selectList(any())).thenReturn(List.of(taskLow, taskHigh)); when(tasksMapper.selectList(any())).thenReturn(List.of(taskLow, taskHigh));
when(reviewRecallRecordMapper.selectList(any())).thenReturn(List.of(highRatioRecord)); when(reviewRecallRecordMapper.selectList(any())).thenReturn(List.of(highRatioRecord));
@@ -116,15 +115,9 @@ class ReviewServiceImplTest {
} }
@Test @Test
void getReviewFeed_shouldApplyFinalLimitAfterMerge() { void getReviewFeed_shouldReturnFragmentsOnly() {
StudyReportsEntity report = new StudyReportsEntity();
report.setId(1);
report.setSessionNum("S1");
report.setContent("report");
report.setCreatedTime(LocalDateTime.now());
StudyReportFragmentsEntity fragment = new StudyReportFragmentsEntity(); StudyReportFragmentsEntity fragment = new StudyReportFragmentsEntity();
fragment.setId(2); fragment.setId(1);
fragment.setSessionNum("S1"); fragment.setSessionNum("S1");
fragment.setContent("fragment"); fragment.setContent("fragment");
fragment.setCreatedTime(LocalDateTime.now().minusMinutes(1)); fragment.setCreatedTime(LocalDateTime.now().minusMinutes(1));
@@ -137,7 +130,6 @@ class ReviewServiceImplTest {
task.setTaskNum("T1"); task.setTaskNum("T1");
task.setTaskName("Task"); task.setTaskName("Task");
when(studyReportsMapper.selectList(any())).thenReturn(List.of(report));
when(studyReportFragmentsMapper.selectList(any())).thenReturn(List.of(fragment)); when(studyReportFragmentsMapper.selectList(any())).thenReturn(List.of(fragment));
when(studySessionsMapper.selectList(any())).thenReturn(List.of(session)); when(studySessionsMapper.selectList(any())).thenReturn(List.of(session));
when(tasksMapper.selectList(any())).thenReturn(List.of(task)); when(tasksMapper.selectList(any())).thenReturn(List.of(task));
@@ -145,7 +137,7 @@ class ReviewServiceImplTest {
List<ReviewFeedItem> items = reviewService.getReviewFeed(1, "recent"); List<ReviewFeedItem> items = reviewService.getReviewFeed(1, "recent");
assertEquals(1, items.size()); assertEquals(1, items.size());
assertEquals("REPORT", items.get(0).getSourceType()); assertEquals("FRAGMENT", items.get(0).getSourceType());
assertEquals("Task", items.get(0).getTaskName()); assertEquals("Task", items.get(0).getTaskName());
} }