feat(playlist): 实现热门歌单缓存更新功能
- 添加定时任务每小时更新热门歌单缓存 - 实现分页查询所有歌单并计算热门分数- 使用并行流提升歌单评分计算性能 - 按照热门分数排序并限制返回数量 - 将热门歌单列表序列化后存入Redis缓存 - 添加详细的JavaDoc注释说明方法用途 -优化查询逻辑中的条件组合说明- 增强错误处理和日志记录机制
This commit is contained in:
@@ -83,38 +83,54 @@ public class PlaylistServiceImpl implements PlaylistService {
|
||||
private FileService fileService;
|
||||
|
||||
@Override
|
||||
/**
|
||||
* 根据歌单ID获取歌单信息
|
||||
* @param id 歌单的唯一标识符
|
||||
* @return PlaylistDTO 歌单的数据传输对象,包含歌单的详细信息
|
||||
* @throws BusinessException 当指定的歌单不存在时抛出业务异常
|
||||
*/
|
||||
public PlaylistDTO getPlaylistById(Long id) {
|
||||
// 从数据库中查找指定ID的歌单,如果不存在则抛出业务异常
|
||||
Playlist playlist = playlistRepository.findById(id)
|
||||
.orElseThrow(() -> new BusinessException("歌单不存在"));
|
||||
// 将实体对象转换为DTO对象并返回
|
||||
return convertToDTO(playlist);
|
||||
}
|
||||
|
||||
@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) {
|
||||
// 构建查询条件
|
||||
Specification<Playlist> spec = (root, query, cb) -> {
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
query.distinct(true); // 添加 distinct 查询
|
||||
query.distinct(true); // 添加 distinct 查询,避免重复结果
|
||||
|
||||
// 关键字查询
|
||||
if (StringUtils.hasText(keyword)) {
|
||||
// 搜索歌单名、描述、创建者用户名以及歌单内的音乐名
|
||||
Predicate p1 = cb.like(root.get("name"), "%" + keyword + "%");
|
||||
Predicate p2 = cb.like(root.get("description"), "%" + 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 + "%");
|
||||
predicates.add(cb.or(p1, p2, p3, p4));
|
||||
Predicate p1 = cb.like(root.get("name"), "%" + keyword + "%"); // 匹配歌单名
|
||||
Predicate p2 = cb.like(root.get("description"), "%" + 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 + "%"); // 匹配歌单内的音乐名
|
||||
predicates.add(cb.or(p1, p2, p3, p4)); // 将多个查询条件组合为OR关系
|
||||
}
|
||||
|
||||
// 类型查询
|
||||
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(
|
||||
spec,
|
||||
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 * * * ?")
|
||||
public void scheduleUpdateHotPlaylist() {
|
||||
// 打印任务开始日志
|
||||
System.out.println("Scheduled task started: Updating hot playlist cache...");
|
||||
// 调用更新热门歌单缓存的方法
|
||||
updateHotPlaylistCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新热门歌单缓存的方法
|
||||
* 使用异步方式和只读事务注解
|
||||
* @return 返回包含热门歌单DTO列表的CompletableFuture
|
||||
*/
|
||||
@Async
|
||||
@Transactional(readOnly = true)
|
||||
public CompletableFuture<List<PlaylistDTO>> updateHotPlaylistCache() {
|
||||
// 用于存储所有歌单的列表
|
||||
List<Playlist> allPlaylists = new ArrayList<>();
|
||||
// 分页查询结果对象
|
||||
Page<Playlist> playlistPage;
|
||||
// 当前页码,从0开始
|
||||
int pageNum = 0;
|
||||
// 每页大小,设置为500
|
||||
int pageSize = 500;
|
||||
|
||||
// 分页查询所有歌单,直到没有更多数据
|
||||
do {
|
||||
// 创建分页对象
|
||||
Pageable pageable = PageRequest.of(pageNum, pageSize);
|
||||
// 执行分页查询
|
||||
playlistPage = playlistRepository.findAll(pageable);
|
||||
// 将当前页的歌单添加到总列表中
|
||||
allPlaylists.addAll(playlistPage.getContent());
|
||||
// 页码递增
|
||||
pageNum++;
|
||||
} while (playlistPage.hasNext());
|
||||
|
||||
// 使用并行流为每个歌单计算热门分数
|
||||
allPlaylists.parallelStream().forEach(playlist -> {
|
||||
// 计算热门歌单分数
|
||||
double score = HotPlaylistScoreUtil.calculateHotScore(playlist);
|
||||
// 设置歌单的热门分数
|
||||
playlist.setHotScore(score);
|
||||
});
|
||||
|
||||
// 将歌单按热门分数降序排序,并限制热门歌单数量
|
||||
List<PlaylistDTO> sortedHotPlaylists = allPlaylists.stream()
|
||||
// 按热门分数降序排序
|
||||
.sorted(Comparator.comparing(Playlist::getHotScore).reversed())
|
||||
// 限制热门歌单数量
|
||||
.limit(HOT_PLAYLIST_LIMIT)
|
||||
// 将歌单实体转换为DTO对象
|
||||
.map(this::convertToDTO)
|
||||
// 收集为列表
|
||||
.collect(Collectors.toList());
|
||||
|
||||
try {
|
||||
// 将热门歌单列表转换为JSON字符串
|
||||
String jsonToCache = objectMapper.writeValueAsString(sortedHotPlaylists);
|
||||
// 将JSON字符串存入Redis缓存,设置过期时间
|
||||
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.");
|
||||
} catch (Exception e) {
|
||||
// 打印更新失败的错误信息
|
||||
System.err.println("Failed to cache hot playlist list to Redis: " + e.getMessage());
|
||||
}
|
||||
|
||||
// 返回包含热门歌单DTO列表的CompletableFuture
|
||||
return CompletableFuture.completedFuture(sortedHotPlaylists);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user