parent
e683171570
commit
b006059b41
@ -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 自定义首页帖子算法
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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<Post> 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<Post> 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<Comment> 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<Comment> 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<Post> postQueryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
postQueryWrapper.le(Post::getCreateTime, expiredTime);
|
||||||
|
List<Post> expiredPosts = postMapper.selectList(postQueryWrapper);
|
||||||
|
expiredPosts.forEach(post -> {
|
||||||
|
redisUtil.zRemove("post:hot:", post.getId());
|
||||||
|
redisUtil.zRemove("post:time:", post.getId());
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清理过期的评论缓存
|
||||||
|
LambdaQueryWrapper<Comment> commentQueryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
commentQueryWrapper.le(Comment::getCreateTime, expiredTime);
|
||||||
|
List<Comment> expiredComments = commentMapper.selectList(commentQueryWrapper);
|
||||||
|
expiredComments.forEach(comment -> {
|
||||||
|
// 评论相关缓存清理
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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:
|
lj:
|
||||||
db:
|
db:
|
||||||
host: 192.168.125.128
|
host: 192.168.59.129
|
||||||
password: MySQL@5678
|
password: Forely123!
|
||||||
redis:
|
redis:
|
||||||
host: 192.168.125.128
|
host: 192.168.59.129
|
||||||
port: 6379
|
port: 6379
|
||||||
password: Redis@9012
|
password: Forely123!
|
||||||
rabbitmq:
|
rabbitmq:
|
||||||
host: 192.168.125.128
|
host: 192.168.59.129
|
||||||
port: 5672
|
port: 5672
|
||||||
username: rabbit_admin
|
username: admin
|
||||||
password: Rabbit@3456
|
password: Forely123!
|
||||||
minio:
|
minio:
|
||||||
endpoint: http://192.168.125.128:9000
|
endpoint: http://192.168.59.129:9000
|
||||||
accessKey: minio_admin
|
accessKey: forely
|
||||||
secretKey: Minio@1234
|
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
|
||||||
|
Loading…
Reference in new issue