From 5d9eca492993ce2d8fde131d92f7f19e9de06d73 Mon Sep 17 00:00:00 2001 From: ikmkj <1@qq,com> Date: Sat, 26 Jul 2025 11:05:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(singer):=20=E5=AE=9E=E7=8E=B0=E6=AD=8C?= =?UTF-8?q?=E6=89=8B=E7=83=AD=E5=BA=A6=E5=88=86=E8=AE=A1=E7=AE=97=E5=92=8C?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 HotSingerScoreUtil工具类,用于计算歌手热度分数 - 在 Singer 实体中添加 hotScore 字段,用于存储热度分数 - 在 SingerDTO 中添加 hotScore 字段,用于展示热度分数 - 修改 SingerServiceImpl 中的 getHotSinger 方法,支持从 Redis 缓存中获取热门歌手列表 - 实现定时任务和异步方法,用于定期更新和缓存热门歌手列表 --- .../com/test/musichouduan/dto/SingerDTO.java | 5 ++ .../com/test/musichouduan/entity/Singer.java | 6 ++ .../service/impl/SingerServiceImpl.java | 80 ++++++++++++++++++- .../musichouduan/util/HotSingerScoreUtil.java | 44 ++++++++++ 4 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 music-houduan/src/main/java/com/test/musichouduan/util/HotSingerScoreUtil.java diff --git a/music-houduan/src/main/java/com/test/musichouduan/dto/SingerDTO.java b/music-houduan/src/main/java/com/test/musichouduan/dto/SingerDTO.java index b146eb8..c1b245b 100644 --- a/music-houduan/src/main/java/com/test/musichouduan/dto/SingerDTO.java +++ b/music-houduan/src/main/java/com/test/musichouduan/dto/SingerDTO.java @@ -60,4 +60,9 @@ public class SingerDTO { * 关联音乐列表 */ private List musics; + + /** + * 热度分 + */ + private double hotScore; } diff --git a/music-houduan/src/main/java/com/test/musichouduan/entity/Singer.java b/music-houduan/src/main/java/com/test/musichouduan/entity/Singer.java index b646a85..6372507 100644 --- a/music-houduan/src/main/java/com/test/musichouduan/entity/Singer.java +++ b/music-houduan/src/main/java/com/test/musichouduan/entity/Singer.java @@ -84,4 +84,10 @@ public class Singer { inverseJoinColumns = @JoinColumn(name = "music_id") ) private Set musics = new HashSet<>(); + + /** + * 热度分,不持久化到数据库 + */ + @Transient + private double hotScore; } diff --git a/music-houduan/src/main/java/com/test/musichouduan/service/impl/SingerServiceImpl.java b/music-houduan/src/main/java/com/test/musichouduan/service/impl/SingerServiceImpl.java index 6d583d8..fb1a1cb 100644 --- a/music-houduan/src/main/java/com/test/musichouduan/service/impl/SingerServiceImpl.java +++ b/music-houduan/src/main/java/com/test/musichouduan/service/impl/SingerServiceImpl.java @@ -14,10 +14,20 @@ import com.test.musichouduan.repository.MusicRepository; import com.test.musichouduan.repository.SingerRepository; import com.test.musichouduan.repository.SingerTypeRepository; import com.test.musichouduan.repository.UserRepository; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import com.test.musichouduan.repository.UserSingerRepository; import com.test.musichouduan.service.FileService; import com.test.musichouduan.service.SingerService; +import com.test.musichouduan.util.HotSingerScoreUtil; import org.springframework.beans.BeanUtils; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.util.CollectionUtils; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.Comparator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -39,6 +49,16 @@ import java.util.stream.Collectors; @Service public class SingerServiceImpl implements SingerService { + private static final String HOT_SINGER_CACHE_KEY = "singer:hot_list"; + private static final long CACHE_EXPIRATION_HOURS = 2; + private static final int HOT_SINGER_LIMIT = 50; + + @Autowired + private StringRedisTemplate redisTemplate; + + @Autowired + private ObjectMapper objectMapper; + @Autowired private SingerRepository singerRepository; @@ -115,13 +135,64 @@ public class SingerServiceImpl implements SingerService { @Override public List getHotSinger(Integer limit) { - // 查询热门歌手 - List singers = singerRepository.findHotSingers(PageRequest.of(0, limit)); + String hotSingerJson = redisTemplate.opsForValue().get(HOT_SINGER_CACHE_KEY); - // 转换为DTO - return singers.stream() + if (StringUtils.hasText(hotSingerJson)) { + try { + List cachedList = objectMapper.readValue(hotSingerJson, new TypeReference>() {}); + return cachedList.stream().limit(limit).collect(Collectors.toList()); + } catch (Exception e) { + System.err.println("Failed to deserialize hot singer list from Redis: " + e.getMessage()); + } + } + + List hotList = calculateAndCacheHotSingerSync(); + return hotList.stream().limit(limit).collect(Collectors.toList()); + } + + private List calculateAndCacheHotSingerSync() { + try { + return updateHotSingerCache().get(); + } catch (Exception e) { + System.err.println("Failed to calculate hot singer list synchronously: " + e.getMessage()); + List singers = singerRepository.findHotSingers(PageRequest.of(0, HOT_SINGER_LIMIT)); + return singers.stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + } + } + + @Scheduled(cron = "0 0 * * * ?") + public void scheduleUpdateHotSinger() { + System.out.println("Scheduled task started: Updating hot singer cache..."); + updateHotSingerCache(); + } + + @Async + @Transactional(readOnly = true) + public CompletableFuture> updateHotSingerCache() { + List allSingers = singerRepository.findAll(); + + allSingers.parallelStream().forEach(singer -> { + double score = HotSingerScoreUtil.calculateHotScore(singer); + singer.setHotScore(score); + }); + + List sortedHotSingers = allSingers.stream() + .sorted(Comparator.comparing(Singer::getHotScore).reversed()) + .limit(HOT_SINGER_LIMIT) .map(this::convertToDTO) .collect(Collectors.toList()); + + try { + String jsonToCache = objectMapper.writeValueAsString(sortedHotSingers); + redisTemplate.opsForValue().set(HOT_SINGER_CACHE_KEY, jsonToCache, CACHE_EXPIRATION_HOURS, TimeUnit.HOURS); + System.out.println("Hot singer cache updated successfully with " + sortedHotSingers.size() + " items."); + } catch (Exception e) { + System.err.println("Failed to cache hot singer list to Redis: " + e.getMessage()); + } + + return CompletableFuture.completedFuture(sortedHotSingers); } @Override @@ -356,6 +427,7 @@ public class SingerServiceImpl implements SingerService { private SingerDTO convertToDTO(Singer singer) { SingerDTO singerDTO = new SingerDTO(); BeanUtils.copyProperties(singer, singerDTO); + singerDTO.setHotScore(singer.getHotScore()); // 设置类型信息 if (singer.getType() != null) { diff --git a/music-houduan/src/main/java/com/test/musichouduan/util/HotSingerScoreUtil.java b/music-houduan/src/main/java/com/test/musichouduan/util/HotSingerScoreUtil.java new file mode 100644 index 0000000..448b88b --- /dev/null +++ b/music-houduan/src/main/java/com/test/musichouduan/util/HotSingerScoreUtil.java @@ -0,0 +1,44 @@ +package com.test.musichouduan.util; + +import com.test.musichouduan.entity.Singer; +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * 歌手热度分数计算工具类 + */ +public class HotSingerScoreUtil { + + // 定义各种交互的权重 + private static final double FANS_WEIGHT = 2.0; // 粉丝数(收藏数) + private static final double COMMENT_WEIGHT = 1.5; + + // 时间衰减因子中的指数 + private static final double TIME_DECAY_EXPONENT = 1.8; + + /** + * 计算单个歌手的热度分数 + * 算法: 热度 = (交互权重和) / (时间衰减因子) + * + * @param singer 歌手实体对象 + * @return 计算出的热度分数 + */ + public static double calculateHotScore(Singer singer) { + // 1. 计算交互权重和 + long fansCount = singer.getCollectCount() != null ? singer.getCollectCount() : 0; + long commentCount = singer.getCommentCount() != null ? singer.getCommentCount() : 0; + + double interactionScore = (fansCount * FANS_WEIGHT) + + (commentCount * COMMENT_WEIGHT); + + // 2. 计算时间衰减因子 + LocalDateTime createTime = singer.getCreateTime(); + LocalDateTime now = LocalDateTime.now(); + long hoursElapsed = Duration.between(createTime, now).toHours(); + + double timeDecayFactor = Math.pow(hoursElapsed + 2, TIME_DECAY_EXPONENT); + + // 3. 计算最终热度分数 + return interactionScore / timeDecayFactor; + } +}