feat(playlist): 实现热门歌单缓存更新功能
- 添加定时任务每小时更新热门歌单缓存 - 实现分页查询所有歌单并计算热门分数- 使用并行流提升歌单评分计算性能 - 按照热门分数排序并限制返回数量 - 将热门歌单列表序列化后存入Redis缓存 - 添加详细的JavaDoc注释说明方法用途 -优化查询逻辑中的条件组合说明- 增强错误处理和日志记录机制
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user