From b006059b4125c33ef48eb9f45a3ca142c9f60aba Mon Sep 17 00:00:00 2001 From: forely <1605769034@qq.com> Date: Mon, 2 Jun 2025 12:26:45 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AF=84=E8=AE=BA=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E6=A8=A1=E5=9E=8B=EF=BC=8C=E6=B3=A8=E6=84=8F=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E8=A1=A8=E5=8F=98=E6=9B=B4=EF=BC=8C=E9=9C=80?= =?UTF-8?q?=E8=A6=81=E9=87=8D=E6=96=B0=E8=BF=90=E8=A1=8Csql=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E3=80=82=E5=AE=8C=E6=88=90=E8=AF=84=E8=AE=BA=E5=92=8C?= =?UTF-8?q?=E5=B8=96=E5=AD=90=E7=9A=84=E8=87=AA=E5=AE=9A=E4=B9=89=E7=83=AD?= =?UTF-8?q?=E5=BA=A6=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/utils/RedisUtil.java | 4 +- .../LuojiaChannelApplication.java | 2 + .../interact/controller/ChatController.java | 4 +- .../modules/post/algorithm/PostSelector.java | 86 ++++++++++++++++++ .../post/controller/CommentController.java | 4 +- .../post/controller/PostController.java | 4 +- .../post/dto/req/CommentPageQueryDTO.java | 5 +- .../post/dto/req/PostPageQueryDTO.java | 3 + .../modules/post/dto/resp/CommentInfoDTO.java | 2 + .../modules/post/entity/Comment.java | 1 + .../post/service/impl/CommentServiceImpl.java | 75 ++++++++++----- .../post/service/impl/PostServiceImpl.java | 17 +++- .../modules/post/task/UpdateScoreTask.java | 91 +++++++++++++++++++ .../src/main/resources/application-local.yaml | 58 ++++++------ .../src/main/resources/db/luojia_channel.sql | 1 + 15 files changed, 289 insertions(+), 68 deletions(-) create mode 100644 珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/algorithm/PostSelector.java create mode 100644 珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/task/UpdateScoreTask.java diff --git a/珞珈岛-项目相关文件/luojia-island/common/src/main/java/com/luojia_channel/common/utils/RedisUtil.java b/珞珈岛-项目相关文件/luojia-island/common/src/main/java/com/luojia_channel/common/utils/RedisUtil.java index 2e8da6a..73dd746 100644 --- a/珞珈岛-项目相关文件/luojia-island/common/src/main/java/com/luojia_channel/common/utils/RedisUtil.java +++ b/珞珈岛-项目相关文件/luojia-island/common/src/main/java/com/luojia_channel/common/utils/RedisUtil.java @@ -85,7 +85,7 @@ public class RedisUtil { if(typedTuples == null || typedTuples.isEmpty()){ return ScrollPageResponse.builder().build(); } - // 获取返回的offset与minTime + // 获取返回的offset与minval List ids = new ArrayList<>(); int returnOffset = 1; long min = 0; @@ -109,6 +109,8 @@ public class RedisUtil { .build(); } + + public void set(String key, Object value) { redisTemplate.opsForValue().set(key, value, DEFAULT_TIMEOUT, DEFAULT_TIME_UNIT); } diff --git a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/LuojiaChannelApplication.java b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/LuojiaChannelApplication.java index 07f28a6..0b136a5 100644 --- a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/LuojiaChannelApplication.java +++ b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/LuojiaChannelApplication.java @@ -2,8 +2,10 @@ package com.luojia_channel; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableScheduling public class LuojiaChannelApplication { public static void main(String[] args) { diff --git a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/interact/controller/ChatController.java b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/interact/controller/ChatController.java index 236337f..e2e766e 100644 --- a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/interact/controller/ChatController.java +++ b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/interact/controller/ChatController.java @@ -32,7 +32,7 @@ public class ChatController { @ApiResponse(responseCode = "500", description = "获取失败,请稍后重试") }) @GetMapping("/chat-list") - public Result> getChatList(@RequestBody ChatPageQueryDTO chatPageQueryDTO) { + public Result> getChatList(ChatPageQueryDTO chatPageQueryDTO) { return Result.success(chatService.getChatList(chatPageQueryDTO)); } @@ -46,7 +46,7 @@ public class ChatController { @ApiResponse(responseCode = "500", description = "获取失败,请稍后重试") }) @GetMapping("/history") - public Result> getChatHistory(@RequestBody ChatPageQueryDTO chatPageQueryDTO) { + public Result> getChatHistory(ChatPageQueryDTO chatPageQueryDTO) { return Result.success(chatService.getChatHistory(chatPageQueryDTO)); } } \ No newline at end of file diff --git a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/algorithm/PostSelector.java b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/algorithm/PostSelector.java new file mode 100644 index 0000000..0fef6aa --- /dev/null +++ b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/algorithm/PostSelector.java @@ -0,0 +1,86 @@ +package com.luojia_channel.modules.post.algorithm; + +import cn.hutool.core.bean.BeanUtil; +import com.luojia_channel.common.utils.RedisUtil; +import com.luojia_channel.modules.post.dto.resp.PostBasicInfoDTO; +import com.luojia_channel.modules.post.entity.Comment; +import com.luojia_channel.modules.post.entity.Post; +import com.luojia_channel.modules.post.mapper.CommentMapper; +import com.luojia_channel.modules.post.mapper.PostMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.concurrent.TimeUnit; + + +@Component +@RequiredArgsConstructor +public class PostSelector { + + private final PostMapper postMapper; + private final CommentMapper commentMapper; + private final RedisUtil redisUtil; + + public void calculatePostScore(Post post){ + long hours = calculateHoursSince(post.getCreateTime()); + + double likes = post.getLikeCount(); + double comments = post.getCommentCount(); + double favorites = post.getFavoriteCount(); + + double newScore = (1 + likes + comments*0.7 + favorites*0.5) + / Math.pow(1 + hours/24.0, 1.8); + + redisUtil.zAdd("post:hot:", post.getId(), newScore); + } + + public void calculateCommentScore(Comment comment){ + long hours = calculateHoursSince(comment.getCreateTime()); + + double likes = comment.getLikeCount(); + double replies = comment.getReplyCount(); + double newScore = (1 + likes + replies*0.7) + / Math.pow(1 + hours/24.0, 1.8); + + if(comment.getId().equals(comment.getTopId())) { + redisUtil.zAdd("post:comment_by_hot:" + comment.getPostId(), comment.getId(), newScore); + } else { + redisUtil.zAdd("comment:reply_by_hot:" + comment.getTopId(), comment.getId(), newScore); + } + } + + // 更新帖子热度分数 + public void updatePostScore(Long postId) { + Post post = postMapper.selectById(postId); + if (post == null) return; + calculatePostScore(post); + } + + // 更新评论热度分数 + public void updateCommentScore(Long commentId) { + Comment comment = commentMapper.selectById(commentId); + if(comment == null) return; + calculateCommentScore(comment); + } + + // 计算从指定时间到现在经过的小时数 + public long calculateHoursSince(LocalDateTime startTime) { + if (startTime == null) { + return 0; + } + + Instant startInstant = startTime.atZone(ZoneId.systemDefault()).toInstant(); + Instant nowInstant = Instant.now(); + + long millisDiff = Duration.between(startInstant, nowInstant).toMillis(); + return TimeUnit.HOURS.convert(millisDiff, TimeUnit.MILLISECONDS); + } + + // TODO 自定义首页帖子算法 + +} + diff --git a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/controller/CommentController.java b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/controller/CommentController.java index a37f304..705baf7 100644 --- a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/controller/CommentController.java +++ b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/controller/CommentController.java @@ -82,7 +82,7 @@ public class CommentController { @ApiResponse(responseCode = "200", description = "获取成功"), @ApiResponse(responseCode = "500", description = "获取失败,帖子ID不合法") }) - public Result> getCommentsByPostId(@RequestBody CommentPageQueryDTO commentPageQueryDTO) { + public Result> getCommentsByPostId(CommentPageQueryDTO commentPageQueryDTO) { ScrollPageResponse commentList = commentService.getCommentsByPostId(commentPageQueryDTO); return Result.success(commentList); } @@ -98,7 +98,7 @@ public class CommentController { @ApiResponse(responseCode = "200", description = "获取成功"), @ApiResponse(responseCode = "500", description = "获取失败,评论ID不合法") }) - public Result> getReplyById(@RequestBody CommentPageQueryDTO commentPageQueryDTO) { + public Result> getReplyById(CommentPageQueryDTO commentPageQueryDTO) { ScrollPageResponse commentInfoDTOList = commentService.getReplyById(commentPageQueryDTO); return Result.success(commentInfoDTOList); } diff --git a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/controller/PostController.java b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/controller/PostController.java index ea1c787..8661772 100644 --- a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/controller/PostController.java +++ b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/controller/PostController.java @@ -107,7 +107,7 @@ public class PostController { @ApiResponse(responseCode = "200", description = "获取成功"), @ApiResponse(responseCode = "500", description = "获取失败,请稍后重试") }) - public Result> pagePost(@RequestBody PostPageQueryDTO postPageQueryDTO) { + public Result> pagePost(PostPageQueryDTO postPageQueryDTO) { return Result.success(postService.pagePost(postPageQueryDTO)); } @@ -121,7 +121,7 @@ public class PostController { @ApiResponse(responseCode = "200", description = "获取成功"), @ApiResponse(responseCode = "500", description = "获取失败,请稍后重试") }) - public Result> pagePostOfUser(@RequestBody PostPageQueryDTO postPageQueryDTO) { + public Result> pagePostOfUser(PostPageQueryDTO postPageQueryDTO) { return Result.success(postService.pagePostOfUser(postPageQueryDTO)); } diff --git a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/dto/req/CommentPageQueryDTO.java b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/dto/req/CommentPageQueryDTO.java index df85dae..cecb0bc 100644 --- a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/dto/req/CommentPageQueryDTO.java +++ b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/dto/req/CommentPageQueryDTO.java @@ -14,7 +14,6 @@ public class CommentPageQueryDTO extends ScrollPageRequest { @Schema(title = "评论ID") private Long parentCommentId; - private Boolean orderByTime = true; - - private Boolean orderByHot = false; + @Schema(title = "排序类型,0表示按时间,1表示按热度") + private Integer type = 0; } diff --git a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/dto/req/PostPageQueryDTO.java b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/dto/req/PostPageQueryDTO.java index d2616d9..e2ef571 100644 --- a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/dto/req/PostPageQueryDTO.java +++ b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/dto/req/PostPageQueryDTO.java @@ -11,4 +11,7 @@ public class PostPageQueryDTO extends ScrollPageRequest { description = "想要查看的用户的id,输入空时为自己的id" ) private Long userId; + + @Schema(title = "排序类型,0表示按时间,1表示按热度,2表示自定义的推荐算法(暂未实现)") + private Integer type = 0; } diff --git a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/dto/resp/CommentInfoDTO.java b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/dto/resp/CommentInfoDTO.java index e63aeb9..70126bd 100644 --- a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/dto/resp/CommentInfoDTO.java +++ b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/dto/resp/CommentInfoDTO.java @@ -74,6 +74,8 @@ public class CommentInfoDTO { private String userName; private String userAvatar; + private String replyUserName; + @Schema( description = "子评论列表" ) diff --git a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/entity/Comment.java b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/entity/Comment.java index a00ac09..5bc0192 100644 --- a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/entity/Comment.java +++ b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/entity/Comment.java @@ -21,6 +21,7 @@ public class Comment { private Long userId; private Long postId; private Long parentCommentId; + private Long replyUserId; private Long topId; private LocalDateTime createTime; private LocalDateTime updateTime; diff --git a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/service/impl/CommentServiceImpl.java b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/service/impl/CommentServiceImpl.java index 1248d1b..764d485 100644 --- a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/service/impl/CommentServiceImpl.java +++ b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/service/impl/CommentServiceImpl.java @@ -14,6 +14,7 @@ import com.luojia_channel.common.utils.PageUtil; import com.luojia_channel.common.utils.RedisUtil; import com.luojia_channel.common.utils.UserContext; import com.luojia_channel.modules.message.mq.domain.NotificationMessage; +import com.luojia_channel.modules.post.algorithm.PostSelector; import com.luojia_channel.modules.post.dto.req.CommentPageQueryDTO; import com.luojia_channel.modules.post.dto.req.CommentSaveDTO; import com.luojia_channel.modules.post.dto.resp.CommentInfoDTO; @@ -35,10 +36,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; @Service @@ -52,6 +50,7 @@ public class CommentServiceImpl extends ServiceImpl impl private final NotificationProducer notificationProducer; private final RedisUtil redisUtil; private final PostMapper postMapper; + private final PostSelector postSelector; @Override @Transactional(rollbackFor = Exception.class) @@ -66,6 +65,9 @@ public class CommentServiceImpl extends ServiceImpl impl comment.setUserId(userId); comment.setCreateTime(LocalDateTime.now()); comment.setUpdateTime(LocalDateTime.now()); + comment.setReplyCount(0L); + comment.setLikeCount(0L); + if (!save(comment)) { throw new PostException("创建评论失败"); } @@ -74,6 +76,7 @@ public class CommentServiceImpl extends ServiceImpl impl if (comment.getParentCommentId() == null) { comment.setTopId(comment.getId()); comment.setParentCommentId(0L); + comment.setReplyUserId(0L); if (!updateById(comment)) { throw new PostException("更新根级评论 topId 失败"); } @@ -81,8 +84,8 @@ public class CommentServiceImpl extends ServiceImpl impl String key = "post:comment_by_time:" + comment.getPostId(); redisUtil.zAdd(key, comment.getId(), System.currentTimeMillis()); } - // 帖子的回复数加一 + // 帖子的回复数加一 Long postId = comment.getPostId(); Post post = postMapper.selectById(postId); if (post == null) { @@ -119,15 +122,15 @@ public class CommentServiceImpl extends ServiceImpl impl } // 设置顶级评论id comment.setTopId(parentComment.getTopId()); + comment.setReplyUserId(parentComment.getUserId()); updateById(comment); // 更新评论的zset回复列表 - String buildKey = String.format("%d_%d", comment.getPostId(), comment.getParentCommentId()); - String key = "comment:reply_by_time:" + buildKey; + String key = "comment:reply_by_time:" + comment.getTopId(); redisUtil.zAdd(key, comment.getId(), System.currentTimeMillis()); - // 更新顶级评论回复数,当顶级评论与回复评论不同时 + // 更新顶级评论回复数 LambdaUpdateWrapper updateWrapper = Wrappers.lambdaUpdate(Comment.class) .eq(Comment::getId, comment.getTopId()) .setSql("reply_count = reply_count + 1"); @@ -136,20 +139,32 @@ public class CommentServiceImpl extends ServiceImpl impl throw new PostException("回复顶级评论失败"); } + // 更新父评论回复数 + if(!parentComment.getTopId().equals(parentComment.getId())){ + parentComment.setReplyCount(parentComment.getReplyCount()+1); + updateById(parentComment); + postSelector.calculateCommentScore(parentComment); + } + postSelector.updateCommentScore(comment.getTopId()); + // 消息通知,回复评论 - String content = String.format("%s 回复了你的评论: %s", - UserContext.getUsername(), - StringUtils.abbreviate(commentSaveDTO.getContent(), 20)); - NotificationMessage notificationMessage = NotificationMessage.builder() - .senderId(userId) - .senderName(UserContext.getUsername()) - .senderAvatar(UserContext.getAvatar()) - .receiverId(parentComment.getUserId()) - .content(content) - .messageType(2) - .build(); - notificationProducer.sendMessage(notificationMessage); + if(!userId.equals(comment.getReplyUserId())){ + String content = String.format("%s 回复了你的评论: %s", + UserContext.getUsername(), + StringUtils.abbreviate(commentSaveDTO.getContent(), 20)); + NotificationMessage notificationMessage = NotificationMessage.builder() + .senderId(userId) + .senderName(UserContext.getUsername()) + .senderAvatar(UserContext.getAvatar()) + .receiverId(parentComment.getUserId()) + .content(content) + .messageType(2) + .build(); + notificationProducer.sendMessage(notificationMessage); + } } + postSelector.calculateCommentScore(comment); + postSelector.calculatePostScore(post); redisUtil.delete("post:detail:" + comment.getPostId()); return comment.getId(); } @@ -179,9 +194,12 @@ public class CommentServiceImpl extends ServiceImpl impl Comment comment = commentMapper.selectById(id); if(comment.getId().equals(comment.getTopId())) { redisUtil.zRemove("post:comment_by_time:" + comment.getPostId(), comment.getId()); + redisUtil.zRemove("post:comment_by_hot:" + comment.getPostId(), comment.getId()); redisUtil.delete("comment:reply_by_time:" + comment.getTopId()); + redisUtil.delete("comment:reply_by_hot:" + comment.getTopId()); }else{ redisUtil.zRemove("comment:reply_by_time:" + comment.getTopId(), comment.getId()); + redisUtil.zRemove("comment:reply_by_hot:" + comment.getTopId(), comment.getId()); } // TODO 如果根评论删除,那么其他评论怎么办,目前做法是删除其下所有的子评论 } @@ -199,11 +217,12 @@ public class CommentServiceImpl extends ServiceImpl impl .orderByDesc(Comment::getCreateTime); return getCommentInfoDTOPageResponse(commentPageQueryDTO, queryWrapper); */ - String key = "post:comment_by_time:" + commentPageQueryDTO.getPostId(); + String condition = commentPageQueryDTO.getType().equals(0) ? "time:" : "hot:"; + String key = "post:comment_by_" + condition + commentPageQueryDTO.getPostId(); return redisUtil.scrollPageQuery(key, CommentInfoDTO.class, commentPageQueryDTO, (commentIds) -> { List comments = commentMapper.selectByIdsOrderByField(commentIds); - List userIds = new ArrayList<>(); + Set userIds = new HashSet<>(); comments.forEach(comment -> userIds.add(comment.getUserId())); List users = userMapper.selectBatchIds(userIds); Map userMap = users.stream() @@ -232,13 +251,17 @@ public class CommentServiceImpl extends ServiceImpl impl .orderByDesc(Comment::getCreateTime); return getCommentInfoDTOPageResponse(commentPageQueryDTO, queryWrapper); */ - String buildKey = String.format("%d_%d", commentPageQueryDTO.getPostId(), commentPageQueryDTO.getParentCommentId()); - String key = "comment:reply_by_time:" + buildKey; + String condition = commentPageQueryDTO.getType().equals(0) ? "time:" : "hot:"; + String key = "comment:reply_by_" + condition + commentPageQueryDTO.getParentCommentId(); return redisUtil.scrollPageQuery(key, CommentInfoDTO.class, commentPageQueryDTO, (commentIds) -> { List comments = commentMapper.selectByIdsOrderByField(commentIds); - List userIds = new ArrayList<>(); + Set userIds = new HashSet<>(); comments.forEach(comment -> userIds.add(comment.getUserId())); + for(Comment comment : comments){ + userIds.add(comment.getUserId()); + userIds.add(comment.getReplyUserId()); + } List users = userMapper.selectBatchIds(userIds); Map userMap = users.stream() .collect(Collectors.toMap(User::getId, user -> user)); @@ -249,6 +272,8 @@ public class CommentServiceImpl extends ServiceImpl impl commentInfoDTO.setUserAvatar(user.getAvatar()); commentInfoDTO.setUserName(user.getUsername()); commentInfoDTO.setIsLike(isLikedComment(comment.getId())); + User replyUser = userMap.getOrDefault(comment.getReplyUserId(), new User()); + commentInfoDTO.setReplyUserName(replyUser.getUsername()); commentInfoDTOS.add(commentInfoDTO); } return commentInfoDTOS; diff --git a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/service/impl/PostServiceImpl.java b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/service/impl/PostServiceImpl.java index f2e4456..6e5a71b 100644 --- a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/service/impl/PostServiceImpl.java +++ b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/service/impl/PostServiceImpl.java @@ -17,6 +17,7 @@ import com.luojia_channel.modules.interact.entity.Follow; import com.luojia_channel.modules.interact.mapper.FollowMapper; import com.luojia_channel.modules.interact.service.impl.FollowServiceImpl; import com.luojia_channel.modules.message.mq.domain.NotificationMessage; +import com.luojia_channel.modules.post.algorithm.PostSelector; import com.luojia_channel.modules.post.dto.req.PostSaveDTO; import com.luojia_channel.modules.post.dto.req.PostPageQueryDTO; import com.luojia_channel.modules.post.dto.resp.PostBasicInfoDTO; @@ -49,10 +50,11 @@ public class PostServiceImpl extends ServiceImpl implements Po private final ValidatePostUtil validatePostUtil; private final RedisUtil redisUtil; private final UserMapper userMapper; + private final PostSelector postSelector; // 匿名用户名与匿名头像 - private static final String ANONYMOUS_NAME = "匿名用户"; - private static final String ANONYMOUS_AVATAR = ""; + public static final String ANONYMOUS_NAME = "匿名用户"; + public static final String ANONYMOUS_AVATAR = ""; private final FollowMapper followMapper; private final FollowServiceImpl followService; @@ -68,6 +70,10 @@ public class PostServiceImpl extends ServiceImpl implements Po post.setUserId(UserContext.getUserId()); post.setCreateTime(LocalDateTime.now()); post.setUpdateTime(LocalDateTime.now()); + post.setCommentCount(0); + post.setLikeCount(0); + post.setViewCount(0); + post.setFavoriteCount(0); if(!save(post)){ throw new PostException("创建帖子失败"); } @@ -84,6 +90,7 @@ public class PostServiceImpl extends ServiceImpl implements Po } // TODO 消息通知? } + postSelector.calculatePostScore(post); redisUtil.zAdd("post:time:", post.getId(), System.currentTimeMillis()); redisUtil.zAdd("post:user:" + userId, post.getId(), System.currentTimeMillis()); return post.getId(); @@ -120,6 +127,7 @@ public class PostServiceImpl extends ServiceImpl implements Po // redisUtil.delete("post:of:user:" + UserContext.getUserId()); redisUtil.delete("post:detail:" + id); redisUtil.zRemove("post:time:", id); + redisUtil.zRemove("post:hot:", id); redisUtil.zRemove("post:user:" + userId, id); } @@ -135,6 +143,7 @@ public class PostServiceImpl extends ServiceImpl implements Po if(post == null){ throw new PostException("帖子不存在或被删除"); } + postSelector.calculatePostScore(post); PostInfoDTO postInfoDTO = BeanUtil.copyProperties(post, PostInfoDTO.class); User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class) .eq(User::getId, post.getUserId())); @@ -152,7 +161,7 @@ public class PostServiceImpl extends ServiceImpl implements Po @Override public ScrollPageResponse pagePost(PostPageQueryDTO postPageQueryDTO) { - // TODO 目前分页查询直接按照创建时间顺序排序了,未来考虑加入多种规则 + // TODO 未来考虑加入多种规则 /* 旧版分页逻辑 LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(Post.class) .orderByDesc(Post::getCreateTime); @@ -175,7 +184,7 @@ public class PostServiceImpl extends ServiceImpl implements Po return postBasicInfoDTO; }); */ - String key = "post:time:"; + String key = postPageQueryDTO.getType().equals(0) ? "post:time:" : "post:hot:"; return redisUtil.scrollPageQuery(key, PostBasicInfoDTO.class, postPageQueryDTO, (postIds) -> { List userIds = new ArrayList<>(); diff --git a/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/task/UpdateScoreTask.java b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/task/UpdateScoreTask.java new file mode 100644 index 0000000..07875b1 --- /dev/null +++ b/珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/task/UpdateScoreTask.java @@ -0,0 +1,91 @@ +package com.luojia_channel.modules.post.task; + +import com.luojia_channel.common.utils.RedisUtil; +import com.luojia_channel.modules.post.algorithm.PostSelector; +import com.luojia_channel.modules.post.entity.Comment; +import com.luojia_channel.modules.post.entity.Post; +import com.luojia_channel.modules.post.mapper.CommentMapper; +import com.luojia_channel.modules.post.mapper.PostMapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; + +@Component +@RequiredArgsConstructor +public class UpdateScoreTask { + + private static final int BATCH_SIZE = 100; + // 每半小时更新热度 + private static final long POST_UPDATE_INTERVAL = 30 * 60; + private static final long COMMENT_UPDATE_INTERVAL = 30 * 60; + + private final PostSelector postSelector; + private final PostMapper postMapper; + private final CommentMapper commentMapper; + private final RedisUtil redisUtil; + + // 定时更新帖子热度分数 + @Scheduled(fixedRate = POST_UPDATE_INTERVAL * 1000) + public void updatePostScores() { + // 扩大更新范围到最近30天的帖子 + LocalDateTime startTime = LocalDateTime.now().minusDays(30); + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.ge(Post::getCreateTime, startTime); + + long totalPosts = postMapper.selectCount(queryWrapper); + long batches = (totalPosts + BATCH_SIZE - 1) / BATCH_SIZE; + + for (int i = 0; i < batches; i++) { + queryWrapper.last("LIMIT " + i * BATCH_SIZE + ", " + BATCH_SIZE); + List posts = postMapper.selectList(queryWrapper); + posts.forEach(post -> postSelector.updatePostScore(post.getId())); + } + } + + // 定时更新评论热度分数 + @Scheduled(fixedRate = COMMENT_UPDATE_INTERVAL * 1000) + public void updateCommentScores() { + // 扩大更新范围到最近15天的评论 + LocalDateTime startTime = LocalDateTime.now().minusDays(15); + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.ge(Comment::getCreateTime, startTime); + + long totalComments = commentMapper.selectCount(queryWrapper); + long batches = (totalComments + BATCH_SIZE - 1) / BATCH_SIZE; + + for (int i = 0; i < batches; i++) { + queryWrapper.last("LIMIT " + i * BATCH_SIZE + ", " + BATCH_SIZE); + List comments = commentMapper.selectList(queryWrapper); + comments.forEach(comment -> postSelector.updateCommentScore(comment.getId())); + } + } + + // 定期清理过期的缓存数据 + @Scheduled(cron = "0 0 4 * * ?") // 每天凌晨4点执行 + public void cleanExpiredData() { + LocalDateTime expiredTime = LocalDateTime.now().minusDays(60); + + // 清理过期的帖子缓存 + LambdaQueryWrapper postQueryWrapper = new LambdaQueryWrapper<>(); + postQueryWrapper.le(Post::getCreateTime, expiredTime); + List expiredPosts = postMapper.selectList(postQueryWrapper); + expiredPosts.forEach(post -> { + redisUtil.zRemove("post:hot:", post.getId()); + redisUtil.zRemove("post:time:", post.getId()); + }); + + // 清理过期的评论缓存 + LambdaQueryWrapper commentQueryWrapper = new LambdaQueryWrapper<>(); + commentQueryWrapper.le(Comment::getCreateTime, expiredTime); + List expiredComments = commentMapper.selectList(commentQueryWrapper); + expiredComments.forEach(comment -> { + // 评论相关缓存清理 + }); + } +} diff --git a/珞珈岛-项目相关文件/luojia-island/service/src/main/resources/application-local.yaml b/珞珈岛-项目相关文件/luojia-island/service/src/main/resources/application-local.yaml index 2e3c7da..8f563c9 100644 --- a/珞珈岛-项目相关文件/luojia-island/service/src/main/resources/application-local.yaml +++ b/珞珈岛-项目相关文件/luojia-island/service/src/main/resources/application-local.yaml @@ -1,36 +1,36 @@ -###本地开发环境 -# lj: -# db: -# host: localhost -# password: 123456 -# redis: -# host: localhost -# port: 6379 -# password: 123456 -# rabbitmq: -# host: localhost -# port: 5672 -# username: root -# password: 123456 -# minio: -# endpoint: http://localhost:9000 -# accessKey: root -# secretKey: 12345678 - +##本地开发环境 lj: db: - host: 192.168.125.128 - password: MySQL@5678 + host: 192.168.59.129 + password: Forely123! redis: - host: 192.168.125.128 + host: 192.168.59.129 port: 6379 - password: Redis@9012 + password: Forely123! rabbitmq: - host: 192.168.125.128 + host: 192.168.59.129 port: 5672 - username: rabbit_admin - password: Rabbit@3456 + username: admin + password: Forely123! minio: - endpoint: http://192.168.125.128:9000 - accessKey: minio_admin - secretKey: Minio@1234 + endpoint: http://192.168.59.129:9000 + accessKey: forely + secretKey: Forely123! + +#lj: +# db: +# host: 192.168.125.128 +# password: MySQL@5678 +# redis: +# host: 192.168.125.128 +# port: 6379 +# password: Redis@9012 +# rabbitmq: +# host: 192.168.125.128 +# port: 5672 +# username: rabbit_admin +# password: Rabbit@3456 +# minio: +# endpoint: http://192.168.125.128:9000 +# accessKey: minio_admin +# secretKey: Minio@1234 diff --git a/珞珈岛-项目相关文件/luojia-island/service/src/main/resources/db/luojia_channel.sql b/珞珈岛-项目相关文件/luojia-island/service/src/main/resources/db/luojia_channel.sql index 180d95b..90b40ec 100644 --- a/珞珈岛-项目相关文件/luojia-island/service/src/main/resources/db/luojia_channel.sql +++ b/珞珈岛-项目相关文件/luojia-island/service/src/main/resources/db/luojia_channel.sql @@ -106,6 +106,7 @@ CREATE TABLE `comment` ( `user_id` BIGINT NOT NULL COMMENT '评论用户ID', `post_id` BIGINT NOT NULL COMMENT '关联的帖子ID', `parent_comment_id` BIGINT DEFAULT 0 COMMENT '父评论ID', + `reply_user_id` BIGINT DEFAULT 0 COMMENT '回复用户ID', `top_id` BIGINT COMMENT '顶层评论ID', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',