feat(api): 加C端分类频道和剧单详情接口

This commit is contained in:
Zhou Weidong authored and trae-solo-agent committed 2026-10-23 16:45:28 +08:00
1 parent 4f80a691d1
commit 45cf405285
8 files changed
+178 -4

No files matched your search

@@ -6,12 +6,15 @@ import com.fruitshorts.app.service.AppCatalogService;
import com.fruitshorts.app.vo.AppCategoryVO;
import com.fruitshorts.app.vo.AppFeedVO;
import com.fruitshorts.app.vo.AppDramaCardVO;
import com.fruitshorts.app.vo.AppPlaylistDetailVO;
import com.fruitshorts.common.pojo.CursorResult;
import com.fruitshorts.common.pojo.PageResult;
import com.fruitshorts.common.pojo.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@@ -56,4 +59,19 @@ public class AppCatalogController {
public Result<List<AppCategoryVO>> categories() {
return Result.ok(appCatalogService.categories());
}
@Operation(summary = "分类频道:按分类 ID 分页查短剧卡片")
@GetMapping("/categories/{id}/dramas")
public Result<PageResult<AppDramaCardVO>> categoryDramas(
@PathVariable Long id,
@RequestParam(defaultValue = "1") int pageIndex,
@RequestParam(defaultValue = "20") int pageSize) {
return Result.ok(appCatalogService.categoryDramas(id, pageIndex, pageSize));
}
@Operation(summary = "剧单详情:剧单基本信息 + 该剧单下已上线短剧卡片")
@GetMapping("/playlists/{id}")
public Result<AppPlaylistDetailVO> playlistDetail(@PathVariable Long id) {
return Result.ok(appCatalogService.playlistDetail(id));
}
}
@@ -8,14 +8,18 @@ import com.fruitshorts.app.vo.AppDramaCardVO;
import com.fruitshorts.app.vo.AppDramaDetailVO;
import com.fruitshorts.app.vo.AppEpisodeVO;
import com.fruitshorts.app.vo.AppFeedVO;
import com.fruitshorts.app.vo.AppPlaylistDetailVO;
import com.fruitshorts.app.vo.AppSectionVO;
import com.fruitshorts.common.pojo.CursorResult;
import com.fruitshorts.common.pojo.PageResult;
import com.fruitshorts.content.dal.entity.ContentBanner;
import com.fruitshorts.content.dal.entity.Drama;
import com.fruitshorts.content.dal.entity.DramaEpisode;
import com.fruitshorts.content.enums.BannerPosition;
import com.fruitshorts.content.service.BannerService;
import com.fruitshorts.content.service.DramaCategoryService;
import com.fruitshorts.content.service.DramaClassificationService;
import com.fruitshorts.content.service.DramaPlaylistCategoryService;
import com.fruitshorts.content.service.DramaService;
import com.fruitshorts.content.service.EpisodeService;
import com.fruitshorts.interaction.service.CommentService;
@@ -49,6 +53,8 @@ public class AppCatalogService {
private final EpisodeService episodeService;
private final BannerService bannerService;
private final DramaCategoryService dramaCategoryService;
private final DramaClassificationService dramaClassificationService;
private final DramaPlaylistCategoryService dramaPlaylistCategoryService;
private final PlaybackSigner playbackSigner;
private final CommentService commentService;
private final ThumbnailUrlResolver thumbnailUrlResolver;
@@ -69,13 +75,42 @@ public class AppCatalogService {
categorySections(DISCOVER_SECTION_SIZE, categoryNameMap()));
}
/** 分类频道:运营自定义的精选/新剧/榜单等(drama_classification)。 */
public List<AppCategoryVO> categories() {
return dramaCategoryService.listBasic().stream()
.filter(category -> category.getStatus() == null || category.getStatus() == 1)
.map(category -> new AppCategoryVO(category.getId(), category.getName()))
return dramaClassificationService.listEnabled().stream()
.map(c -> new AppCategoryVO(c.getId(), c.getName()))
.toList();
}
/** 分类频道:按分类分页查已上线短剧卡片(通过分类关联表过滤)。 */
public PageResult<AppDramaCardVO> categoryDramas(Long classificationId,
int pageIndex, int pageSize) {
dramaClassificationService.getEnabledById(classificationId); // 校验存在/启用
List<Long> allIds = dramaClassificationService.listDramaIdsByClassification(classificationId);
if (allIds.isEmpty()) {
return PageResult.empty();
}
// 只取已上线的,再按关联表原始顺序排
Map<Long, Integer> orderMap = new HashMap<>();
for (int i = 0; i < allIds.size(); i++) orderMap.put(allIds.get(i), i);
List<Drama> published = dramaService.listPublishedByIds(allIds);
List<Drama> ordered = published.stream()
.sorted((a, b) -> Integer.compare(
orderMap.getOrDefault(a.getId(), Integer.MAX_VALUE),
orderMap.getOrDefault(b.getId(), Integer.MAX_VALUE)))
.toList();
int safeIndex = Math.max(1, pageIndex);
int safeSize = Math.max(1, Math.min(pageSize, 50));
long total = ordered.size();
int fromIndex = (safeIndex - 1) * safeSize;
if (fromIndex >= ordered.size()) {
return new PageResult<>(total, List.of());
}
int toIndex = Math.min(fromIndex + safeSize, ordered.size());
List<Drama> pageRows = ordered.subList(fromIndex, toIndex);
return new PageResult<>(total, cards(pageRows, categoryNameMap()));
}
public CursorResult<AppDramaCardVO> feed(
String types, Long categoryId, Long cursor, Integer size) {
int pageSize = size == null ? DEFAULT_FEED_SIZE : Math.max(1, Math.min(size, MAX_FEED_SIZE));
@@ -137,6 +172,33 @@ public class AppCatalogService {
return detail;
}
/** C 端剧单详情:剧单基本信息 + 该剧单下所有已上线短剧卡片。 */
public AppPlaylistDetailVO playlistDetail(Long playlistId) {
var category = dramaPlaylistCategoryService.getEnabledById(playlistId);
List<Long> allIds = dramaPlaylistCategoryService.listDramaIdsByCategory(playlistId);
List<Drama> published = dramaService.listPublishedByIds(allIds);
// 保留关联表原始顺序:按 allIds 位置重新排序
Map<Long, Integer> orderMap = new HashMap<>();
for (int i = 0; i < allIds.size(); i++) {
orderMap.put(allIds.get(i), i);
}
List<Drama> ordered = published.stream()
.sorted((a, b) -> {
Integer ia = orderMap.getOrDefault(a.getId(), Integer.MAX_VALUE);
Integer ib = orderMap.getOrDefault(b.getId(), Integer.MAX_VALUE);
return Integer.compare(ia, ib);
})
.toList();
AppPlaylistDetailVO vo = new AppPlaylistDetailVO();
vo.setId(category.getId());
vo.setName(category.getName());
vo.setDescription(category.getDescription());
vo.setDramaCount(ordered.size());
vo.setList(cards(ordered, categoryNameMap()));
return vo;
}
private List<AppBannerVO> banners(String position) {
return bannerService.listActive(position).stream().map(this::toBanner).toList();
}
@@ -0,0 +1,19 @@
// Author: fruit.com.cn
package com.fruitshorts.app.vo;
import lombok.Data;
import java.util.List;
/** C 端剧单详情:基本信息 + 该剧单下已上线的短剧卡片。 */
@Data
public class AppPlaylistDetailVO {
private Long id;
private String name;
private String description;
/** 该剧单下已上线短剧的实际条数(被下架/下线的不计入) */
private Integer dramaCount;
private List<AppDramaCardVO> list;
}
@@ -8,6 +8,8 @@ import com.fruitshorts.content.dal.entity.Drama;
import com.fruitshorts.content.dal.entity.DramaEpisode;
import com.fruitshorts.content.service.BannerService;
import com.fruitshorts.content.service.DramaCategoryService;
import com.fruitshorts.content.service.DramaClassificationService;
import com.fruitshorts.content.service.DramaPlaylistCategoryService;
import com.fruitshorts.content.service.DramaService;
import com.fruitshorts.content.service.EpisodeService;
import com.fruitshorts.interaction.service.CommentService;
@@ -45,12 +47,15 @@ class AppCatalogServiceTest {
episodeService = mock(EpisodeService.class);
BannerService bannerService = mock(BannerService.class);
dramaCategoryService = mock(DramaCategoryService.class);
DramaClassificationService dramaClassificationService = mock(DramaClassificationService.class);
DramaPlaylistCategoryService dramaPlaylistCategoryService = mock(DramaPlaylistCategoryService.class);
PlaybackSigner playbackSigner = mock(PlaybackSigner.class);
thumbnailUrlResolver = mock(ThumbnailUrlResolver.class);
given(thumbnailUrlResolver.resolveAll(any())).willReturn(Map.of());
commentService = mock(CommentService.class);
catalogService = new AppCatalogService(dramaService, episodeService, bannerService,
dramaCategoryService, playbackSigner, commentService, thumbnailUrlResolver);
dramaCategoryService, dramaClassificationService, dramaPlaylistCategoryService,
playbackSigner, commentService, thumbnailUrlResolver);
}
@Test