feat(admin): 新增观看记录分页与评论分集信息

This commit is contained in:
Zhou Weidong committed 2026-10-21 16:35:26 +08:00
1 parent d52b805adf
commit 1d316791b1
14 files changed
+953 -7

No files matched your search

@@ -0,0 +1,123 @@
// Author: fruit.com.cn
package com.fruitshorts.admin.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.service.DramaService;
import com.fruitshorts.content.service.EpisodeService;
import com.fruitshorts.interaction.config.InteractionProperties;
import com.fruitshorts.interaction.dal.entity.ContentComment;
import com.fruitshorts.interaction.dal.mapper.ContentAuditRecordMapper;
import com.fruitshorts.interaction.dal.mapper.ContentCommentMapper;
import com.fruitshorts.interaction.dal.mapper.ContentLikeMapper;
import com.fruitshorts.interaction.service.AuditRecorder;
import com.fruitshorts.interaction.service.CommentService;
import com.fruitshorts.interaction.service.SensitiveWordService;
import com.fruitshorts.interaction.vo.CommentQueryReqVO;
import com.fruitshorts.interaction.vo.CommentRespVO;
import com.fruitshorts.user.dal.entity.AppUser;
import com.fruitshorts.user.dal.mapper.AppUserMapper;
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 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;
/** 后台评论页批量补充短剧与分集展示字段。 */
class CommentServiceTest {
private ContentCommentMapper commentMapper;
private AppUserMapper appUserMapper;
private DramaService dramaService;
private EpisodeService episodeService;
private CommentService commentService;
@BeforeAll
static void initTableInfoCache() {
TableInfoHelper.initTableInfo(
new MapperBuilderAssistant(new MybatisConfiguration(), ""),
ContentComment.class);
}
@BeforeEach
void setUp() {
commentMapper = mock(ContentCommentMapper.class);
appUserMapper = mock(AppUserMapper.class);
dramaService = mock(DramaService.class);
episodeService = mock(EpisodeService.class);
commentService = new CommentService(
commentMapper,
mock(ContentLikeMapper.class),
mock(ContentAuditRecordMapper.class),
appUserMapper,
dramaService,
episodeService,
mock(SensitiveWordService.class),
mock(AuditRecorder.class),
mock(InteractionProperties.class));
}
@Test
void should_fill_drama_and_episode_metadata_when_admin_page_is_loaded() {
ContentComment withEpisode = comment(1L, 5L, 11L);
ContentComment legacy = comment(2L, 5L, 0L);
Page<ContentComment> page = new Page<>(1, 10);
page.setTotal(2L);
page.setRecords(List.of(withEpisode, legacy));
given(commentMapper.selectPage(any(), any())).willReturn(page);
AppUser user = new AppUser();
user.setId(7L);
user.setUid("uid-7");
user.setNickname("Viewer");
given(appUserMapper.selectBatchIds(any())).willReturn(List.of(user));
Drama drama = new Drama();
drama.setId(5L);
drama.setTitle("Signal");
given(dramaService.listByIds(any())).willReturn(List.of(drama));
DramaEpisode episode = new DramaEpisode();
episode.setId(11L);
episode.setEpNo(3);
episode.setTitle("The Call");
given(episodeService.listByIds(any())).willReturn(List.of(episode));
PageResult<CommentRespVO> result = commentService.adminPage(new CommentQueryReqVO());
assertThat(result.getList()).hasSize(2);
assertThat(result.getList().get(0).getDramaTitle()).isEqualTo("Signal");
assertThat(result.getList().get(0).getEpisodeNo()).isEqualTo(3);
assertThat(result.getList().get(0).getEpisodeTitle()).isEqualTo("The Call");
assertThat(result.getList().get(1).getDramaTitle()).isEqualTo("Signal");
assertThat(result.getList().get(1).getEpisodeNo()).isNull();
verify(dramaService).listByIds(List.of(5L));
verify(episodeService).listByIds(List.of(11L));
}
private ContentComment comment(Long id, Long dramaId, Long episodeId) {
ContentComment comment = new ContentComment();
comment.setId(id);
comment.setDramaId(dramaId);
comment.setEpisodeId(episodeId);
comment.setParentId(0L);
comment.setRootId(0L);
comment.setUserId(7L);
comment.setContent("comment");
comment.setStatus("published");
comment.setAuditReason("");
comment.setLikeCount(0);
comment.setReplyCount(0);
comment.setReportCount(0);
return comment;
}
}
@@ -0,0 +1,260 @@
// Author: fruit.com.cn
package com.fruitshorts.admin.service;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fruitshorts.admin.vo.AdminWatchHistoryVO;
import com.fruitshorts.admin.vo.WatchHistoryQueryReqVO;
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.dal.mapper.DramaMapper;
import com.fruitshorts.playback.dal.entity.WatchHistory;
import com.fruitshorts.playback.dal.mapper.WatchHistoryMapper;
import com.fruitshorts.user.dal.entity.AppUser;
import com.fruitshorts.user.dal.mapper.AppUserMapper;
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.time.LocalDateTime;
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.never;
import static org.mockito.Mockito.verify;
/** 后台观看记录分页条件与批量装配。 */
class WatchHistoryAdminServiceTest {
private WatchHistoryMapper watchHistoryMapper;
private AppUserMapper appUserMapper;
private DramaMapper dramaMapper;
private DramaEpisodeMapper dramaEpisodeMapper;
private WatchHistoryAdminService service;
@BeforeAll
static void initTableInfoCache() {
MybatisConfiguration configuration = new MybatisConfiguration();
for (Class<?> type : List.of(AppUser.class, Drama.class, DramaEpisode.class,
WatchHistory.class)) {
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(configuration, ""), type);
}
}
@BeforeEach
void setUp() {
watchHistoryMapper = mock(WatchHistoryMapper.class);
appUserMapper = mock(AppUserMapper.class);
dramaMapper = mock(DramaMapper.class);
dramaEpisodeMapper = mock(DramaEpisodeMapper.class);
service = new WatchHistoryAdminService(watchHistoryMapper, appUserMapper, dramaMapper,
dramaEpisodeMapper);
}
@Test
void should_use_default_page_and_newest_first_when_no_filters_are_given() {
WatchHistory row = history(100L, 7L, 5L, 11L);
stubPage(row);
stubBatchData();
PageResult<AdminWatchHistoryVO> result = service.page(new WatchHistoryQueryReqVO());
ArgumentCaptor<Page<WatchHistory>> pageCaptor = pageCaptor();
ArgumentCaptor<LambdaQueryWrapper<WatchHistory>> queryCaptor = queryCaptor();
verify(watchHistoryMapper).selectPage(pageCaptor.capture(), queryCaptor.capture());
assertThat(pageCaptor.getValue().getCurrent()).isEqualTo(1);
assertThat(pageCaptor.getValue().getSize()).isEqualTo(10);
assertThat(queryCaptor.getValue().getSqlSegment())
.contains("watched_at DESC")
.contains("id DESC");
assertThat(result.getTotal()).isEqualTo(1L);
assertThat(result.getList().get(0).getUid()).isEqualTo("user-7");
assertThat(result.getList().get(0).getDramaTitle()).isEqualTo("Signal");
assertThat(result.getList().get(0).getEpisodeNo()).isEqualTo(1);
}
@Test
void should_filter_by_matched_user_ids_when_keyword_is_present() {
WatchHistory row = history(100L, 7L, 5L, 11L);
given(appUserMapper.selectList(any())).willReturn(List.of(user(7L, "Alice")));
stubPage(row);
stubBatchData();
WatchHistoryQueryReqVO req = new WatchHistoryQueryReqVO();
req.setKeyword("Ali");
service.page(req);
LambdaQueryWrapper<WatchHistory> query = capturedQuery();
assertThat(query.getSqlSegment()).contains("user_id IN");
assertThat(query.getParamNameValuePairs().containsValue(7L)).isTrue();
}
@Test
void should_filter_by_matched_drama_ids_when_title_is_present() {
WatchHistory row = history(100L, 7L, 5L, 11L);
given(dramaMapper.selectList(any())).willReturn(List.of(drama(5L, "Signal")));
stubPage(row);
stubBatchData();
WatchHistoryQueryReqVO req = new WatchHistoryQueryReqVO();
req.setDramaTitle("Sig");
service.page(req);
LambdaQueryWrapper<WatchHistory> query = capturedQuery();
assertThat(query.getSqlSegment()).contains("drama_id IN");
assertThat(query.getParamNameValuePairs().containsValue(5L)).isTrue();
}
@Test
void should_return_empty_page_without_querying_history_when_keyword_has_no_user() {
given(appUserMapper.selectList(any())).willReturn(List.of());
WatchHistoryQueryReqVO req = new WatchHistoryQueryReqVO();
req.setKeyword("missing");
PageResult<AdminWatchHistoryVO> result = service.page(req);
assertThat(result.getTotal()).isZero();
assertThat(result.getList()).isEmpty();
verify(watchHistoryMapper, never()).selectPage(any(), any());
}
@Test
void should_apply_true_finished_condition_in_sql() {
WatchHistory row = history(100L, 7L, 5L, 11L);
row.setPositionMs(59000L);
row.setDurationMs(60000L);
stubPage(row);
stubBatchData();
WatchHistoryQueryReqVO req = new WatchHistoryQueryReqVO();
req.setFinished(true);
PageResult<AdminWatchHistoryVO> result = service.page(req);
LambdaQueryWrapper<WatchHistory> query = capturedQuery();
assertThat(query.getSqlSegment())
.contains("duration_ms >")
.contains("position_ms +");
assertThat(query.getParamNameValuePairs().containsValue(1000L)).isTrue();
assertThat(result.getList().get(0).getFinished()).isTrue();
}
@Test
void should_apply_false_finished_condition_in_sql() {
WatchHistory row = history(100L, 7L, 5L, 11L);
row.setPositionMs(1000L);
row.setDurationMs(60000L);
stubPage(row);
stubBatchData();
WatchHistoryQueryReqVO req = new WatchHistoryQueryReqVO();
req.setFinished(false);
PageResult<AdminWatchHistoryVO> result = service.page(req);
LambdaQueryWrapper<WatchHistory> query = capturedQuery();
assertThat(query.getSqlSegment())
.contains("duration_ms =")
.contains("OR")
.contains("position_ms +");
assertThat(result.getList().get(0).getFinished()).isFalse();
}
@Test
void should_apply_closed_time_range_and_clamp_page_size() {
WatchHistory row = history(100L, 7L, 5L, 11L);
stubPage(row);
stubBatchData();
LocalDateTime start = LocalDateTime.of(2026, 10, 1, 0, 0);
LocalDateTime end = LocalDateTime.of(2026, 10, 21, 23, 59, 59);
WatchHistoryQueryReqVO req = new WatchHistoryQueryReqVO();
req.setPageSize(500);
req.setStartTime(start);
req.setEndTime(end);
service.page(req);
ArgumentCaptor<Page<WatchHistory>> pageCaptor = pageCaptor();
ArgumentCaptor<LambdaQueryWrapper<WatchHistory>> queryCaptor = queryCaptor();
verify(watchHistoryMapper).selectPage(pageCaptor.capture(), queryCaptor.capture());
assertThat(pageCaptor.getValue().getSize()).isEqualTo(100);
assertThat(queryCaptor.getValue().getSqlSegment())
.contains("watched_at >=")
.contains("watched_at <=");
}
private void stubPage(WatchHistory row) {
Page<WatchHistory> page = new Page<>(1, 10);
page.setTotal(1L);
page.setRecords(List.of(row));
given(watchHistoryMapper.selectPage(any(Page.class), any())).willReturn(page);
}
private void stubBatchData() {
given(appUserMapper.selectBatchIds(any())).willReturn(List.of(user(7L, "Alice")));
given(dramaMapper.selectBatchIds(any())).willReturn(List.of(drama(5L, "Signal")));
given(dramaEpisodeMapper.selectBatchIds(any())).willReturn(List.of(episode(11L)));
}
@SuppressWarnings("unchecked")
private ArgumentCaptor<Page<WatchHistory>> pageCaptor() {
return ArgumentCaptor.forClass(Page.class);
}
@SuppressWarnings("unchecked")
private ArgumentCaptor<LambdaQueryWrapper<WatchHistory>> queryCaptor() {
return ArgumentCaptor.forClass(LambdaQueryWrapper.class);
}
private LambdaQueryWrapper<WatchHistory> capturedQuery() {
ArgumentCaptor<LambdaQueryWrapper<WatchHistory>> captor = queryCaptor();
verify(watchHistoryMapper).selectPage(any(), captor.capture());
return captor.getValue();
}
private WatchHistory history(Long id, Long userId, Long dramaId, Long episodeId) {
WatchHistory row = new WatchHistory();
row.setId(id);
row.setUserId(userId);
row.setDramaId(dramaId);
row.setEpisodeId(episodeId);
row.setPositionMs(1000L);
row.setDurationMs(60000L);
row.setSource("HOME");
row.setDeviceId("device-1");
row.setWatchedAt(LocalDateTime.of(2026, 10, 21, 12, 0));
return row;
}
private AppUser user(Long id, String nickname) {
AppUser user = new AppUser();
user.setId(id);
user.setUid("user-" + id);
user.setNickname(nickname);
return user;
}
private Drama drama(Long id, String title) {
Drama drama = new Drama();
drama.setId(id);
drama.setTitle(title);
drama.setStatus("published");
return drama;
}
private DramaEpisode episode(Long id) {
DramaEpisode episode = new DramaEpisode();
episode.setId(id);
episode.setEpNo(1);
episode.setTitle("Episode 1");
return episode;
}
}