feat(admin): 剧集列表接口支持分页
This commit is contained in:
1 parent
5227762aeb
commit
0143fa036c
5 files changed
+322
-26
No files matched your search
+30
-5
@@ -2,10 +2,14 @@
|
||||
|
||||
package com.fruitshorts.content.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fruitshorts.common.exception.ErrorCode;
|
||||
import com.fruitshorts.common.exception.ServiceException;
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.content.dal.entity.DramaEpisode;
|
||||
import com.fruitshorts.content.dal.mapper.DramaEpisodeMapper;
|
||||
import com.fruitshorts.content.enums.ContentStatus;
|
||||
@@ -14,6 +18,7 @@ import com.fruitshorts.content.vo.EpisodeSaveReqVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
@@ -26,19 +31,39 @@ import java.util.Objects;
|
||||
@RequiredArgsConstructor
|
||||
public class EpisodeService {
|
||||
|
||||
private static final int DEFAULT_PAGE_INDEX = 1;
|
||||
private static final int DEFAULT_PAGE_SIZE = 100;
|
||||
private static final int MAX_PAGE_SIZE = 200;
|
||||
|
||||
private final DramaEpisodeMapper dramaEpisodeMapper;
|
||||
private final DramaService dramaService;
|
||||
|
||||
/** 后台:按短剧分页查询分集,status 在 SQL 层过滤;排序稳定(sort、ep_no)。 */
|
||||
public PageResult<EpisodeRespVO> pageByDrama(Long dramaId, String status,
|
||||
Integer pageIndex, Integer pageSize) {
|
||||
dramaService.getExisting(dramaId);
|
||||
int safeIndex = pageIndex == null || pageIndex < 1 ? DEFAULT_PAGE_INDEX : pageIndex;
|
||||
int safeSize = pageSize == null || pageSize < 1
|
||||
? DEFAULT_PAGE_SIZE : Math.min(pageSize, MAX_PAGE_SIZE);
|
||||
IPage<DramaEpisode> result = dramaEpisodeMapper.selectPage(
|
||||
new Page<>(safeIndex, safeSize), buildListQuery(dramaId, status));
|
||||
return new PageResult<>(result.getTotal(), result.getRecords().stream().map(this::toResp).toList());
|
||||
}
|
||||
|
||||
public List<EpisodeRespVO> listByDrama(Long dramaId, String status) {
|
||||
dramaService.getExisting(dramaId);
|
||||
return dramaEpisodeMapper.selectList(Wrappers.<DramaEpisode>lambdaQuery()
|
||||
.eq(DramaEpisode::getDramaId, dramaId)
|
||||
.eq(status != null && !status.isBlank(), DramaEpisode::getStatus, status)
|
||||
.orderByAsc(DramaEpisode::getSort)
|
||||
.orderByAsc(DramaEpisode::getEpNo))
|
||||
return dramaEpisodeMapper.selectList(buildListQuery(dramaId, status))
|
||||
.stream().map(this::toResp).toList();
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<DramaEpisode> buildListQuery(Long dramaId, String status) {
|
||||
return Wrappers.<DramaEpisode>lambdaQuery()
|
||||
.eq(DramaEpisode::getDramaId, dramaId)
|
||||
.eq(StringUtils.hasText(status), DramaEpisode::getStatus, status)
|
||||
.orderByAsc(DramaEpisode::getSort)
|
||||
.orderByAsc(DramaEpisode::getEpNo);
|
||||
}
|
||||
|
||||
/** C 端:只返回已上线剧集,按播放顺序 */
|
||||
public List<DramaEpisode> listPublishedByDrama(Long dramaId) {
|
||||
return dramaEpisodeMapper.selectList(Wrappers.<DramaEpisode>lambdaQuery()
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.content.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.content.dal.entity.Drama;
|
||||
import com.fruitshorts.content.dal.entity.DramaEpisode;
|
||||
import com.fruitshorts.content.dal.mapper.DramaEpisodeMapper;
|
||||
import com.fruitshorts.content.vo.EpisodeRespVO;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* 剧集后台分页:默认每页 100、上限 200、越界空列表、stable 排序走 DB selectPage。
|
||||
*/
|
||||
class EpisodeServicePageTest {
|
||||
|
||||
private static final long DRAMA_ID = 2001L;
|
||||
|
||||
private DramaEpisodeMapper dramaEpisodeMapper;
|
||||
private DramaService dramaService;
|
||||
private EpisodeService episodeService;
|
||||
|
||||
@BeforeAll
|
||||
static void initTableInfoCache() {
|
||||
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
|
||||
TableInfoHelper.initTableInfo(assistant, DramaEpisode.class);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
dramaEpisodeMapper = mock(DramaEpisodeMapper.class);
|
||||
dramaService = mock(DramaService.class);
|
||||
episodeService = new EpisodeService(dramaEpisodeMapper, dramaService);
|
||||
given(dramaService.getExisting(DRAMA_ID)).willReturn(new Drama());
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_use_default_page_size_100_when_page_size_omitted() {
|
||||
stubSelectPage(0L, List.of());
|
||||
|
||||
episodeService.pageByDrama(DRAMA_ID, null, null, null);
|
||||
|
||||
Page<?> page = capturePageArg();
|
||||
assertThat(page.getCurrent()).isEqualTo(1L);
|
||||
assertThat(page.getSize()).isEqualTo(100L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_cap_page_size_at_200() {
|
||||
stubSelectPage(0L, List.of());
|
||||
|
||||
episodeService.pageByDrama(DRAMA_ID, null, 1, 500);
|
||||
|
||||
Page<?> page = capturePageArg();
|
||||
assertThat(page.getSize()).isEqualTo(200L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_return_second_page_records_without_overlap_with_first() {
|
||||
DramaEpisode first = episode(1L, 1, 1);
|
||||
DramaEpisode second = episode(2L, 2, 2);
|
||||
DramaEpisode third = episode(3L, 3, 3);
|
||||
DramaEpisode fourth = episode(4L, 4, 4);
|
||||
given(dramaEpisodeMapper.selectPage(any(Page.class), any())).willAnswer(invocation -> {
|
||||
Page<DramaEpisode> page = invocation.getArgument(0);
|
||||
Page<DramaEpisode> result = new Page<>(page.getCurrent(), page.getSize(), 4L);
|
||||
if (page.getCurrent() == 1L) {
|
||||
result.setRecords(List.of(first, second));
|
||||
} else if (page.getCurrent() == 2L) {
|
||||
result.setRecords(List.of(third, fourth));
|
||||
} else {
|
||||
result.setRecords(List.of());
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
PageResult<EpisodeRespVO> page1 = episodeService.pageByDrama(DRAMA_ID, null, 1, 2);
|
||||
PageResult<EpisodeRespVO> page2 = episodeService.pageByDrama(DRAMA_ID, null, 2, 2);
|
||||
|
||||
assertThat(page1.getTotal()).isEqualTo(4L);
|
||||
assertThat(page2.getTotal()).isEqualTo(4L);
|
||||
assertThat(page1.getList()).extracting(EpisodeRespVO::getId).containsExactly(1L, 2L);
|
||||
assertThat(page2.getList()).extracting(EpisodeRespVO::getId).containsExactly(3L, 4L);
|
||||
assertThat(page1.getList()).extracting(EpisodeRespVO::getId)
|
||||
.doesNotContainAnyElementsOf(page2.getList().stream().map(EpisodeRespVO::getId).toList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_return_empty_list_with_correct_total_when_page_out_of_range() {
|
||||
given(dramaEpisodeMapper.selectPage(any(Page.class), any())).willAnswer(invocation -> {
|
||||
Page<DramaEpisode> page = invocation.getArgument(0);
|
||||
Page<DramaEpisode> result = new Page<>(page.getCurrent(), page.getSize(), 5L);
|
||||
result.setRecords(List.of());
|
||||
return result;
|
||||
});
|
||||
|
||||
PageResult<EpisodeRespVO> page = episodeService.pageByDrama(DRAMA_ID, "published", 99, 100);
|
||||
|
||||
assertThat(page.getTotal()).isEqualTo(5L);
|
||||
assertThat(page.getList()).isEmpty();
|
||||
verify(dramaEpisodeMapper).selectPage(any(Page.class), any());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void stubSelectPage(long total, List<DramaEpisode> records) {
|
||||
given(dramaEpisodeMapper.selectPage(any(Page.class), any())).willAnswer(invocation -> {
|
||||
Page<DramaEpisode> page = invocation.getArgument(0);
|
||||
Page<DramaEpisode> result = new Page<>(page.getCurrent(), page.getSize(), total);
|
||||
result.setRecords(records);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Page<?> capturePageArg() {
|
||||
ArgumentCaptor<Page> pageCaptor = ArgumentCaptor.forClass(Page.class);
|
||||
verify(dramaEpisodeMapper).selectPage(pageCaptor.capture(), any());
|
||||
return pageCaptor.getValue();
|
||||
}
|
||||
|
||||
private DramaEpisode episode(Long id, int sort, int epNo) {
|
||||
DramaEpisode episode = new DramaEpisode();
|
||||
episode.setId(id);
|
||||
episode.setDramaId(DRAMA_ID);
|
||||
episode.setSort(sort);
|
||||
episode.setEpNo(epNo);
|
||||
episode.setTitle("ep-" + epNo);
|
||||
episode.setStatus("published");
|
||||
episode.setIsFree(1);
|
||||
episode.setUnlockCoin(0);
|
||||
episode.setDurationSec(60);
|
||||
episode.setPlayCount(0L);
|
||||
episode.setLikeCount(0L);
|
||||
return episode;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user