refactor(comment): 优化评论功能实现并改进热门音乐缓存更新机制
- 移除CommentController中多余的权限注解导入 - 在CommentController中注入MusicService用于缓存更新 - 添加评论和删除评论时触发热门音乐缓存更新 - 提取评论数更新逻辑到独立方法updateTargetCommentCount - 删除评论时在最后同步评论数避免统计偏大 - 优化HotScoreUtil中播放量权重算法采用对数缩放 - 在MusicController的播放、收藏、点赞操作后更新热门音乐缓存 - 改进MusicRepository查询排序避免数据重复 - 重构MusicServiceImpl中热门音乐排序逻辑增强稳定性
This commit is contained in:
@@ -1,13 +1,12 @@
|
||||
package com.test.musichouduan.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.annotation.SaCheckRole;
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.test.musichouduan.dto.CommentDTO;
|
||||
import com.test.musichouduan.dto.PageResult;
|
||||
import com.test.musichouduan.dto.Result;
|
||||
import com.test.musichouduan.service.CommentService;
|
||||
import com.test.musichouduan.service.MusicService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -21,6 +20,9 @@ public class CommentController {
|
||||
@Autowired
|
||||
private CommentService commentService;
|
||||
|
||||
@Autowired
|
||||
private MusicService musicService;
|
||||
|
||||
/**
|
||||
* 根据ID获取评论
|
||||
*
|
||||
@@ -62,6 +64,9 @@ public class CommentController {
|
||||
@PostMapping("/add")
|
||||
public Result<CommentDTO> addComment(@RequestBody CommentDTO commentDTO) {
|
||||
CommentDTO result = commentService.addComment(commentDTO);
|
||||
if (commentDTO.getTargetType() != null && commentDTO.getTargetType() == 1) {
|
||||
musicService.updateHotMusicCache();
|
||||
}
|
||||
return Result.success("评论成功", result);
|
||||
}
|
||||
|
||||
@@ -74,7 +79,11 @@ public class CommentController {
|
||||
@SaCheckLogin
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Boolean> deleteComment(@PathVariable Long id) {
|
||||
CommentDTO commentDTO = commentService.getCommentById(id);
|
||||
boolean result = commentService.deleteComment(id);
|
||||
if (commentDTO.getTargetType() != null && commentDTO.getTargetType() == 1) {
|
||||
musicService.updateHotMusicCache();
|
||||
}
|
||||
return Result.success("删除成功", result);
|
||||
}
|
||||
|
||||
|
||||
@@ -166,6 +166,7 @@ public class MusicController {
|
||||
@RequestPart(value = "cover", required = false) MultipartFile cover,
|
||||
@RequestPart(value = "lyric", required = false) MultipartFile lyric) {
|
||||
MusicDTO result = musicService.updateMusic(id, musicDTO, file, cover, lyric);
|
||||
musicService.updateHotMusicCache();
|
||||
return Result.success("更新成功", result);
|
||||
}
|
||||
|
||||
@@ -195,6 +196,7 @@ public class MusicController {
|
||||
// 获取当前登录用户ID
|
||||
Long userId = StpUtil.getLoginIdAsLong();
|
||||
boolean isCollected = musicService.toggleCollectMusic(id, userId);
|
||||
musicService.updateHotMusicCache();
|
||||
String message = isCollected ? "收藏成功" : "取消收藏成功";
|
||||
return Result.success(message, isCollected);
|
||||
}
|
||||
@@ -227,6 +229,9 @@ public class MusicController {
|
||||
@PostMapping("/{id}/play")
|
||||
public Result<MusicDTO> playMusic(@PathVariable Long id) {
|
||||
MusicDTO musicDTO = musicService.playMusic(id);
|
||||
if (musicDTO != null) {
|
||||
musicService.updateHotMusicCache();
|
||||
}
|
||||
return Result.success(musicDTO);
|
||||
}
|
||||
|
||||
@@ -242,6 +247,7 @@ public class MusicController {
|
||||
// 获取当前登录用户ID
|
||||
Long userId = StpUtil.getLoginIdAsLong();
|
||||
boolean isLiked = musicService.toggleLikeMusic(id, userId);
|
||||
musicService.updateHotMusicCache();
|
||||
String message = isLiked ? "点赞成功" : "取消点赞成功";
|
||||
return Result.success(message, isLiked);
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public interface MusicRepository extends JpaRepository<Music, Long>, JpaSpecific
|
||||
* @param pageable 分页
|
||||
* @return 音乐列表
|
||||
*/
|
||||
@Query("SELECT m FROM Music m ORDER BY m.createTime DESC")
|
||||
@Query("SELECT m FROM Music m ORDER BY m.createTime DESC, m.id DESC")
|
||||
List<Music> findLatest(Pageable pageable);
|
||||
|
||||
/**
|
||||
@@ -87,7 +87,7 @@ public interface MusicRepository extends JpaRepository<Music, Long>, JpaSpecific
|
||||
* @param pageable 分页
|
||||
* @return 音乐列表
|
||||
*/
|
||||
@Query("SELECT m FROM Music m ORDER BY m.playCount DESC")
|
||||
@Query("SELECT m FROM Music m ORDER BY m.playCount DESC, m.createTime DESC, m.id DESC")
|
||||
List<Music> findMostPlayed(Pageable pageable);
|
||||
|
||||
/**
|
||||
|
||||
@@ -136,33 +136,7 @@ public class CommentServiceImpl implements CommentService {
|
||||
Comment savedComment = commentRepository.save(comment);
|
||||
|
||||
|
||||
// 如果评论的是音乐 (targetType=1, 这个值需要与前端约定好)
|
||||
// 通常可以定义一个枚举或常量类来管理这些类型值
|
||||
if (savedComment.getTargetType() == 1) {
|
||||
musicRepository.findById(savedComment.getTargetId()).ifPresent(music -> {
|
||||
// 首先判断类型是否等于1
|
||||
if (savedComment.getTargetType() == 1) {
|
||||
// 查询评论表,使用COUNT(id)统计该音乐的评论数量
|
||||
long commentCount = commentRepository.countByTargetIdAndTargetType(savedComment.getTargetId(), 1);
|
||||
// 设置到音乐中
|
||||
music.setCommentCount((int) commentCount);
|
||||
// 保存音乐实体
|
||||
musicRepository.save(music);
|
||||
}
|
||||
});
|
||||
} else if (savedComment.getTargetType() == 2) { // 2 代表歌单
|
||||
playlistRepository.findById(savedComment.getTargetId()).ifPresent(playlist -> {
|
||||
long commentCount = commentRepository.countByTargetIdAndTargetType(savedComment.getTargetId(), 2);
|
||||
playlist.setCommentCount((int) commentCount);
|
||||
playlistRepository.save(playlist);
|
||||
});
|
||||
} else if (savedComment.getTargetType() == 3) { // 3 代表歌手
|
||||
singerRepository.findById(savedComment.getTargetId()).ifPresent(singer -> {
|
||||
long commentCount = commentRepository.countByTargetIdAndTargetType(savedComment.getTargetId(), 3);
|
||||
singer.setCommentCount((int) commentCount);
|
||||
singerRepository.save(singer);
|
||||
});
|
||||
}
|
||||
updateTargetCommentCount(savedComment.getTargetType(), savedComment.getTargetId());
|
||||
|
||||
// 将新评论转换为DTO
|
||||
CommentDTO savedCommentDTO = convertToDTO(savedComment);
|
||||
@@ -194,27 +168,6 @@ public class CommentServiceImpl implements CommentService {
|
||||
// 删除评论
|
||||
commentRepository.delete(comment);
|
||||
|
||||
// 更新相关实体的评论数
|
||||
if (comment.getTargetType() == 1) {
|
||||
musicRepository.findById(comment.getTargetId()).ifPresent(music -> {
|
||||
long commentCount = commentRepository.countByTargetIdAndTargetType(comment.getTargetId(), 1);
|
||||
music.setCommentCount((int) commentCount);
|
||||
musicRepository.save(music);
|
||||
});
|
||||
} else if (comment.getTargetType() == 2) {
|
||||
playlistRepository.findById(comment.getTargetId()).ifPresent(playlist -> {
|
||||
long commentCount = commentRepository.countByTargetIdAndTargetType(comment.getTargetId(), 2);
|
||||
playlist.setCommentCount((int) commentCount);
|
||||
playlistRepository.save(playlist);
|
||||
});
|
||||
} else if (comment.getTargetType() == 3) {
|
||||
singerRepository.findById(comment.getTargetId()).ifPresent(singer -> {
|
||||
long commentCount = commentRepository.countByTargetIdAndTargetType(comment.getTargetId(), 3);
|
||||
singer.setCommentCount((int) commentCount);
|
||||
singerRepository.save(singer);
|
||||
});
|
||||
}
|
||||
|
||||
// 如果是父评论,同时删除子评论
|
||||
if (comment.getParentId() == null) {
|
||||
List<Comment> children = commentRepository.findByParentIdOrderByCreateTimeAsc(id);
|
||||
@@ -223,6 +176,9 @@ public class CommentServiceImpl implements CommentService {
|
||||
}
|
||||
}
|
||||
|
||||
// 删除完成后再同步评论数,避免父评论删除后子评论残留导致统计偏大
|
||||
updateTargetCommentCount(comment.getTargetType(), comment.getTargetId());
|
||||
|
||||
// 通过 WebSocket 广播删除事件
|
||||
String destination = String.format("/topic/comments/%d/%d", comment.getTargetType(), comment.getTargetId());
|
||||
Map<String, Object> deleteEvent = new HashMap<>();
|
||||
@@ -234,6 +190,31 @@ public class CommentServiceImpl implements CommentService {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateTargetCommentCount(Integer targetType, Long targetId) {
|
||||
if (targetType == null || targetId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
long commentCount = commentRepository.countByTargetIdAndTargetType(targetId, targetType);
|
||||
|
||||
if (targetType == 1) {
|
||||
musicRepository.findById(targetId).ifPresent(music -> {
|
||||
music.setCommentCount((int) commentCount);
|
||||
musicRepository.save(music);
|
||||
});
|
||||
} else if (targetType == 2) {
|
||||
playlistRepository.findById(targetId).ifPresent(playlist -> {
|
||||
playlist.setCommentCount((int) commentCount);
|
||||
playlistRepository.save(playlist);
|
||||
});
|
||||
} else if (targetType == 3) {
|
||||
singerRepository.findById(targetId).ifPresent(singer -> {
|
||||
singer.setCommentCount((int) commentCount);
|
||||
singerRepository.save(singer);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<CommentDTO> getUserCommentList(Long userId, Integer page, Integer size) {
|
||||
// 获取用户
|
||||
|
||||
@@ -243,6 +243,14 @@ public class MusicServiceImpl implements MusicService {
|
||||
public CompletableFuture<List<MusicDTO>> updateHotMusicCache() {
|
||||
// 使用 JOIN FETCH 一次性加载所有音乐及其关联的歌手信息
|
||||
List<Music> allMusic = musicRepository.findAllWithSingers();
|
||||
Comparator<Music> hotMusicComparator = Comparator
|
||||
.comparing((Music music) -> music.getHotScore() != null ? music.getHotScore() : 0D, Comparator.reverseOrder())
|
||||
.thenComparing(music -> music.getPlayCount() != null ? music.getPlayCount() : 0L, Comparator.reverseOrder())
|
||||
.thenComparing(music -> music.getCollectCount() != null ? music.getCollectCount() : 0, Comparator.reverseOrder())
|
||||
.thenComparing(music -> music.getLikeCount() != null ? music.getLikeCount() : 0, Comparator.reverseOrder())
|
||||
.thenComparing(music -> music.getCommentCount() != null ? music.getCommentCount() : 0, Comparator.reverseOrder())
|
||||
.thenComparing(Music::getCreateTime, Comparator.reverseOrder())
|
||||
.thenComparing(Music::getId, Comparator.reverseOrder());
|
||||
|
||||
// 使用并行流来加速计算过程
|
||||
allMusic.parallelStream().forEach(music -> {
|
||||
@@ -252,7 +260,7 @@ public class MusicServiceImpl implements MusicService {
|
||||
|
||||
// 对所有音乐按计算出的热度分进行降序排序
|
||||
List<MusicDTO> sortedHotMusic = allMusic.stream()
|
||||
.sorted(Comparator.comparing(Music::getHotScore).reversed())
|
||||
.sorted(hotMusicComparator)
|
||||
.limit(HOT_MUSIC_LIMIT) // 取热度最高的前50
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@@ -9,6 +9,8 @@ import java.time.LocalDateTime;
|
||||
*/
|
||||
public class HotScoreUtil {
|
||||
|
||||
// 播放量采用对数缩放,避免高播放量对热门榜产生碾压
|
||||
private static final double PLAY_WEIGHT = 1.2;
|
||||
// 定义各种交互的权重
|
||||
private static final double LIKE_WEIGHT = 1.0;
|
||||
private static final double COLLECT_WEIGHT = 2.0;
|
||||
@@ -27,17 +29,24 @@ public class HotScoreUtil {
|
||||
public static double calculateHotScore(Music music) {
|
||||
// 1. 计算交互权重和
|
||||
// 如果交互数据为 null,则默认为 0
|
||||
long playCount = music.getPlayCount() != null ? music.getPlayCount() : 0;
|
||||
long likeCount = music.getLikeCount() != null ? music.getLikeCount() : 0;
|
||||
long collectCount = music.getCollectCount() != null ? music.getCollectCount() : 0;
|
||||
long commentCount = music.getCommentCount() != null ? music.getCommentCount() : 0;
|
||||
|
||||
double interactionScore = (likeCount * LIKE_WEIGHT) +
|
||||
double playScore = Math.log1p(playCount) * PLAY_WEIGHT;
|
||||
|
||||
double interactionScore = playScore +
|
||||
(likeCount * LIKE_WEIGHT) +
|
||||
(collectCount * COLLECT_WEIGHT) +
|
||||
(commentCount * COMMENT_WEIGHT);
|
||||
|
||||
// 2. 计算时间衰减因子
|
||||
// (当前时间 - 创建时间)的小时数
|
||||
LocalDateTime createTime = music.getCreateTime();
|
||||
if (createTime == null) {
|
||||
createTime = LocalDateTime.now();
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
long hoursElapsed = Duration.between(createTime, now).toHours();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user