refactor: 首页feed仅返回残片
This commit is contained in:
@@ -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("这个任务不存在或已被删除");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-14
@@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user