feat(playlist): 实现热门歌单缓存更新功能

- 添加定时任务每小时更新热门歌单缓存
- 实现分页查询所有歌单并计算热门分数- 使用并行流提升歌单评分计算性能
- 按照热门分数排序并限制返回数量
- 将热门歌单列表序列化后存入Redis缓存
- 添加详细的JavaDoc注释说明方法用途
-优化查询逻辑中的条件组合说明- 增强错误处理和日志记录机制
This commit is contained in:
ikmkj
2025-10-10 22:35:55 +08:00
parent 4a379bed83
commit b46a7f8b1b

View File

@@ -83,38 +83,54 @@ public class PlaylistServiceImpl implements PlaylistService {
private FileService fileService; private FileService fileService;
@Override @Override
/**
* 根据歌单ID获取歌单信息
* @param id 歌单的唯一标识符
* @return PlaylistDTO 歌单的数据传输对象,包含歌单的详细信息
* @throws BusinessException 当指定的歌单不存在时抛出业务异常
*/
public PlaylistDTO getPlaylistById(Long id) { public PlaylistDTO getPlaylistById(Long id) {
// 从数据库中查找指定ID的歌单如果不存在则抛出业务异常
Playlist playlist = playlistRepository.findById(id) Playlist playlist = playlistRepository.findById(id)
.orElseThrow(() -> new BusinessException("歌单不存在")); .orElseThrow(() -> new BusinessException("歌单不存在"));
// 将实体对象转换为DTO对象并返回
return convertToDTO(playlist); return convertToDTO(playlist);
} }
@Override @Override
/**
* 获取歌单列表
* @param page 当前页码
* @param size 每页大小
* @param keyword 搜索关键词
* @param typeId 歌单类型ID
* @return PageResult<PlaylistDTO> 歌单列表分页结果
*/
public PageResult<PlaylistDTO> getPlaylistList(Integer page, Integer size, String keyword, Long typeId) { public PageResult<PlaylistDTO> getPlaylistList(Integer page, Integer size, String keyword, Long typeId) {
// 构建查询条件 // 构建查询条件
Specification<Playlist> spec = (root, query, cb) -> { Specification<Playlist> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>(); List<Predicate> predicates = new ArrayList<>();
query.distinct(true); // 添加 distinct 查询 query.distinct(true); // 添加 distinct 查询,避免重复结果
// 关键字查询 // 关键字查询
if (StringUtils.hasText(keyword)) { if (StringUtils.hasText(keyword)) {
// 搜索歌单名、描述、创建者用户名以及歌单内的音乐名 // 搜索歌单名、描述、创建者用户名以及歌单内的音乐名
Predicate p1 = cb.like(root.get("name"), "%" + keyword + "%"); Predicate p1 = cb.like(root.get("name"), "%" + keyword + "%"); // 匹配歌单名
Predicate p2 = cb.like(root.get("description"), "%" + keyword + "%"); Predicate p2 = cb.like(root.get("description"), "%" + keyword + "%"); // 匹配歌单描述
Predicate p3 = cb.like(root.join("creator").get("username"), "%" + keyword + "%"); Predicate p3 = cb.like(root.join("creator").get("username"), "%" + keyword + "%"); // 匹配创建者用户名
Predicate p4 = cb.like(root.join("musics", jakarta.persistence.criteria.JoinType.LEFT).get("name"), "%" + keyword + "%"); Predicate p4 = cb.like(root.join("musics", jakarta.persistence.criteria.JoinType.LEFT).get("name"), "%" + keyword + "%"); // 匹配歌单内的音乐名
predicates.add(cb.or(p1, p2, p3, p4)); predicates.add(cb.or(p1, p2, p3, p4)); // 将多个查询条件组合为OR关系
} }
// 类型查询 // 类型查询
if (typeId != null) { if (typeId != null) {
predicates.add(cb.equal(root.get("type").get("id"), typeId)); predicates.add(cb.equal(root.get("type").get("id"), typeId)); // 添加类型ID查询条件
} }
return cb.and(predicates.toArray(new Predicate[0])); return cb.and(predicates.toArray(new Predicate[0])); // 将所有查询条件组合为AND关系
}; };
// 执行查询 // 执行查询,按创建时间降序排列
Page<Playlist> playlistPage = playlistRepository.findAll( Page<Playlist> playlistPage = playlistRepository.findAll(
spec, spec,
PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createTime")) PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createTime"))
@@ -173,46 +189,80 @@ public class PlaylistServiceImpl implements PlaylistService {
} }
} }
/**
* 定时任务方法:用于更新热门歌单缓存
* 使用@Scheduled注解配置定时任务cron表达式设置为"0 0 * * * ?"
* 表示每小时整点执行一次任务即每小时的0分0秒触发
*/
@Scheduled(cron = "0 0 * * * ?") @Scheduled(cron = "0 0 * * * ?")
public void scheduleUpdateHotPlaylist() { public void scheduleUpdateHotPlaylist() {
// 打印任务开始日志
System.out.println("Scheduled task started: Updating hot playlist cache..."); System.out.println("Scheduled task started: Updating hot playlist cache...");
// 调用更新热门歌单缓存的方法
updateHotPlaylistCache(); updateHotPlaylistCache();
} }
/**
* 更新热门歌单缓存的方法
* 使用异步方式和只读事务注解
* @return 返回包含热门歌单DTO列表的CompletableFuture
*/
@Async @Async
@Transactional(readOnly = true) @Transactional(readOnly = true)
public CompletableFuture<List<PlaylistDTO>> updateHotPlaylistCache() { public CompletableFuture<List<PlaylistDTO>> updateHotPlaylistCache() {
// 用于存储所有歌单的列表
List<Playlist> allPlaylists = new ArrayList<>(); List<Playlist> allPlaylists = new ArrayList<>();
// 分页查询结果对象
Page<Playlist> playlistPage; Page<Playlist> playlistPage;
// 当前页码从0开始
int pageNum = 0; int pageNum = 0;
// 每页大小设置为500
int pageSize = 500; int pageSize = 500;
// 分页查询所有歌单,直到没有更多数据
do { do {
// 创建分页对象
Pageable pageable = PageRequest.of(pageNum, pageSize); Pageable pageable = PageRequest.of(pageNum, pageSize);
// 执行分页查询
playlistPage = playlistRepository.findAll(pageable); playlistPage = playlistRepository.findAll(pageable);
// 将当前页的歌单添加到总列表中
allPlaylists.addAll(playlistPage.getContent()); allPlaylists.addAll(playlistPage.getContent());
// 页码递增
pageNum++; pageNum++;
} while (playlistPage.hasNext()); } while (playlistPage.hasNext());
// 使用并行流为每个歌单计算热门分数
allPlaylists.parallelStream().forEach(playlist -> { allPlaylists.parallelStream().forEach(playlist -> {
// 计算热门歌单分数
double score = HotPlaylistScoreUtil.calculateHotScore(playlist); double score = HotPlaylistScoreUtil.calculateHotScore(playlist);
// 设置歌单的热门分数
playlist.setHotScore(score); playlist.setHotScore(score);
}); });
// 将歌单按热门分数降序排序,并限制热门歌单数量
List<PlaylistDTO> sortedHotPlaylists = allPlaylists.stream() List<PlaylistDTO> sortedHotPlaylists = allPlaylists.stream()
// 按热门分数降序排序
.sorted(Comparator.comparing(Playlist::getHotScore).reversed()) .sorted(Comparator.comparing(Playlist::getHotScore).reversed())
// 限制热门歌单数量
.limit(HOT_PLAYLIST_LIMIT) .limit(HOT_PLAYLIST_LIMIT)
// 将歌单实体转换为DTO对象
.map(this::convertToDTO) .map(this::convertToDTO)
// 收集为列表
.collect(Collectors.toList()); .collect(Collectors.toList());
try { try {
// 将热门歌单列表转换为JSON字符串
String jsonToCache = objectMapper.writeValueAsString(sortedHotPlaylists); String jsonToCache = objectMapper.writeValueAsString(sortedHotPlaylists);
// 将JSON字符串存入Redis缓存设置过期时间
redisTemplate.opsForValue().set(HOT_PLAYLIST_CACHE_KEY, jsonToCache, CACHE_EXPIRATION_HOURS, TimeUnit.HOURS); redisTemplate.opsForValue().set(HOT_PLAYLIST_CACHE_KEY, jsonToCache, CACHE_EXPIRATION_HOURS, TimeUnit.HOURS);
// 打印更新成功的日志
System.out.println("Hot playlist cache updated successfully with " + sortedHotPlaylists.size() + " items."); System.out.println("Hot playlist cache updated successfully with " + sortedHotPlaylists.size() + " items.");
} catch (Exception e) { } catch (Exception e) {
// 打印更新失败的错误信息
System.err.println("Failed to cache hot playlist list to Redis: " + e.getMessage()); System.err.println("Failed to cache hot playlist list to Redis: " + e.getMessage());
} }
// 返回包含热门歌单DTO列表的CompletableFuture
return CompletableFuture.completedFuture(sortedHotPlaylists); return CompletableFuture.completedFuture(sortedHotPlaylists);
} }