feat(admin): 新增观看记录分页与评论分集信息
This commit is contained in:
1 parent
d52b805adf
commit
1d316791b1
14 files changed
+953
-7
No files matched your search
+123
@@ -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;
|
||||
}
|
||||
}
|
||||
+260
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user