RecommendByHot_scores
Change-Id: Icfef76f6ce21a60288c61d60ad1cd2d70045f953
diff --git a/src/main/java/com/example/g8backend/controller/PostController.java b/src/main/java/com/example/g8backend/controller/PostController.java
index 51aa519..03687c3 100644
--- a/src/main/java/com/example/g8backend/controller/PostController.java
+++ b/src/main/java/com/example/g8backend/controller/PostController.java
@@ -1,6 +1,7 @@
package com.example.g8backend.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.g8backend.dto.PostCreateDTO;
import com.example.g8backend.entity.PostView;
import com.example.g8backend.mapper.PostViewMapper;
@@ -148,4 +149,19 @@
return ResponseEntity.ok(history);
}
+
+ @GetMapping("/recommended")
+ public ResponseEntity<Page<Post>> getRecommendedPosts(
+ @RequestParam(defaultValue = "1") int page,
+ @RequestParam(defaultValue = "10") int size) {
+
+ // 获取当前用户ID
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ long userId = (long) authentication.getPrincipal();
+
+ // 调用 Service 层方法
+ Page<Post> pageResult = postService.getRecommendedPosts(page, size, userId);
+
+ return ResponseEntity.ok(pageResult);
+ }
}
diff --git a/src/main/java/com/example/g8backend/entity/Post.java b/src/main/java/com/example/g8backend/entity/Post.java
index 1a3cd25..351e24d 100644
--- a/src/main/java/com/example/g8backend/entity/Post.java
+++ b/src/main/java/com/example/g8backend/entity/Post.java
@@ -4,12 +4,13 @@
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
-
import java.sql.Timestamp;
import lombok.Data;
+import lombok.experimental.Accessors;
@Data
@TableName("posts")
+@Accessors(chain = true)
public class Post {
@TableId(type = IdType.AUTO)
private Long postId;
@@ -23,6 +24,13 @@
@TableField("view_count")
private Integer viewCount = 0;
+ // 新增热度评分字段
+ @TableField("hot_score")
+ private Double hotScore = 5.0; // 初始热度
+
+ // 新增最后计算时间字段
+ @TableField("last_calculated")
+ private Timestamp lastCalculated;
@Override
public String toString() {
@@ -33,7 +41,9 @@
", postContent='" + postContent + '\'' +
", createdAt=" + createdAt +
", postType='" + postType + '\'' +
- ", viewCount=" + viewCount + // ✅ 更新 toString 方法
+ ", viewCount=" + viewCount +
+ ", hotScore=" + hotScore + // 新增字段
+ ", lastCalculated=" + lastCalculated + // 新增字段
'}';
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/mapper/CommentMapper.java b/src/main/java/com/example/g8backend/mapper/CommentMapper.java
index b8fa1bf..8387b80 100644
--- a/src/main/java/com/example/g8backend/mapper/CommentMapper.java
+++ b/src/main/java/com/example/g8backend/mapper/CommentMapper.java
@@ -23,4 +23,7 @@
// 删除评论
@Delete("DELETE FROM comments WHERE comment_id = #{commentId}")
int deleteById(Long commentId);
+
+ @Select("SELECT COUNT(*) FROM comments WHERE post_id = #{postId}")
+ Long selectCountByPostId(@Param("postId") Long postId);
}
diff --git a/src/main/java/com/example/g8backend/mapper/PostMapper.java b/src/main/java/com/example/g8backend/mapper/PostMapper.java
index d49125e..b5a5280 100644
--- a/src/main/java/com/example/g8backend/mapper/PostMapper.java
+++ b/src/main/java/com/example/g8backend/mapper/PostMapper.java
@@ -54,4 +54,27 @@
@Update("UPDATE posts SET view_count = view_count + 1 WHERE post_id = #{postId}")
void incrementViewCount(Long postId);
+
+ @Select("SELECT COUNT(*) FROM post_likes WHERE post_id = #{postId}")
+ Long selectLikeCount(Long postId);
+
+ @Select("SELECT post_id FROM post_views WHERE user_id = #{userId}")
+ List<Long> findViewedPostIds(Long userId);
+
+ @Update({
+ "<script>",
+ "UPDATE posts",
+ "SET hot_score = CASE",
+ " <foreach collection='posts' item='post'>",
+ " WHEN post_id = #{post.postId} THEN #{post.hotScore}",
+ " </foreach>",
+ "END,",
+ "last_calculated = NOW()",
+ "WHERE post_id IN",
+ " <foreach collection='posts' item='post' open='(' separator=',' close=')'>",
+ " #{post.postId}",
+ " </foreach>",
+ "</script>"
+ })
+ int batchUpdateHotScore(@Param("posts") List<Post> posts);
}
diff --git a/src/main/java/com/example/g8backend/service/IPostService.java b/src/main/java/com/example/g8backend/service/IPostService.java
index a349b59..ca5376b 100644
--- a/src/main/java/com/example/g8backend/service/IPostService.java
+++ b/src/main/java/com/example/g8backend/service/IPostService.java
@@ -1,7 +1,9 @@
package com.example.g8backend.service;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.g8backend.entity.Post;
import com.baomidou.mybatisplus.extension.service.IService;
+import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@@ -20,4 +22,10 @@
@Transactional
void recordViewHistory(Long userId, Long postId);
+
+ @Scheduled(cron = "0 */10 * * * *") // 每10分钟执行一次
+ @Transactional
+ void calculateHotScores();
+
+ Page<Post> getRecommendedPosts(int page, int size, Long userId);
}
diff --git a/src/main/java/com/example/g8backend/service/impl/PostServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/PostServiceImpl.java
index f2bef5f..ae47d8a 100644
--- a/src/main/java/com/example/g8backend/service/impl/PostServiceImpl.java
+++ b/src/main/java/com/example/g8backend/service/impl/PostServiceImpl.java
@@ -1,20 +1,25 @@
package com.example.g8backend.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.g8backend.entity.Post;
import com.example.g8backend.entity.PostLike;
import com.example.g8backend.entity.PostTag;
import com.example.g8backend.entity.PostView;
+import com.example.g8backend.mapper.CommentMapper;
import com.example.g8backend.mapper.PostMapper;
import com.example.g8backend.mapper.PostViewMapper;
import com.example.g8backend.service.IPostService;
import com.example.g8backend.service.IPostTagService;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.List;
@Service
@@ -24,12 +29,15 @@
private final PostViewMapper postViewMapper;
+ private final CommentMapper commentMapper;
+
@Autowired
private IPostTagService postTagService;
- public PostServiceImpl(PostMapper postMapper, PostViewMapper postViewMapper) {
+ public PostServiceImpl(PostMapper postMapper, PostViewMapper postViewMapper, CommentMapper commentMapper) {
this.postMapper = postMapper;
this.postViewMapper = postViewMapper;
+ this.commentMapper = commentMapper;
this.baseMapper = postMapper; // 重要:设置 baseMapper
}
@@ -38,14 +46,15 @@
return postMapper.getPostsByUserId(userId);
}
- @Override
public void createPost(Post post) {
+ post.setHotScore(5.0); // 初始热度
post.setCreatedAt(new Timestamp(System.currentTimeMillis()));
save(post);
}
@Override
public void createPost(Post post, Long[] tagIds) {
+ post.setHotScore(5.0); // 初始热度
post.setCreatedAt(new Timestamp(System.currentTimeMillis()));
save(post);
for (long tagId : tagIds) {
@@ -110,4 +119,55 @@
postMapper.incrementViewCount(postId); // 直接调用原子操作
}
+ @Override
+ @Scheduled(cron = "0 */10 * * * *") // 每10分钟执行一次
+ @Transactional
+ public void calculateHotScores() {
+ // 1. 获取所有帖子
+ List<Post> posts = postMapper.selectList(new QueryWrapper<>());
+ Instant now = Instant.now();
+
+ // 2. 计算每个帖子的热度
+ posts.forEach(post -> {
+ // 计算时间衰减因子(以小时为单位)
+ long hoursSinceCreation = ChronoUnit.HOURS.between(
+ post.getCreatedAt().toInstant(),
+ now
+ );
+
+ // 获取互动数据(点赞数、评论数)
+ Long likeCount = postMapper.selectLikeCount(post.getPostId());
+ Long commentCount = commentMapper.selectCountByPostId(post.getPostId());
+
+ // 热度计算公式
+ double hotScore = (
+ Math.log(post.getViewCount() + 1) * 0.2 +
+ likeCount * 0.5 +
+ commentCount * 0.3
+ ) / Math.pow(hoursSinceCreation + 2, 1.5);
+
+ post.setHotScore(hotScore);
+ post.setLastCalculated(new Timestamp(System.currentTimeMillis()));
+ });
+
+ // 3. 批量更新热度(自定义SQL实现)
+ postMapper.batchUpdateHotScore(posts);
+ }
+
+ @Override
+ public Page<Post> getRecommendedPosts(int page, int size, Long userId) {
+ // 1. 获取用户已浏览的帖子ID列表
+ List<Long> viewedPostIds = postViewMapper.findViewedPostIds(userId);
+
+ // 2. 构建查询条件:排除已浏览帖子,按热度降序
+ QueryWrapper<Post> queryWrapper = new QueryWrapper<>();
+ if (!viewedPostIds.isEmpty()) {
+ queryWrapper.notIn("post_id", viewedPostIds);
+ }
+ queryWrapper.orderByDesc("hot_score");
+
+ // 3. 分页查询
+ return postMapper.selectPage(new Page<>(page, size), queryWrapper);
+ }
+
}
\ No newline at end of file
diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql
index d97701c..e5ddbb4 100644
--- a/src/main/resources/schema.sql
+++ b/src/main/resources/schema.sql
@@ -1,111 +1,122 @@
-CREATE DATABASE IF NOT EXISTS g8backend;
-USE g8backend;
-
+-- 用户表(保持不变)
CREATE TABLE IF NOT EXISTS `users` (
- user_id INT AUTO_INCREMENT PRIMARY KEY,
- user_name VARCHAR(255) NOT NULL,
- password VARCHAR(255) NOT NULL,
- email VARCHAR(255) NOT NULL UNIQUE,
- passkey VARCHAR(255) NOT NULL UNIQUE
+ `user_id` INT AUTO_INCREMENT PRIMARY KEY,
+ `user_name` VARCHAR(255) NOT NULL,
+ `password` VARCHAR(255) NOT NULL,
+ `email` VARCHAR(255) NOT NULL UNIQUE,
+ `passkey` VARCHAR(255) NOT NULL UNIQUE
);
+-- 种子表(保持不变)
CREATE TABLE IF NOT EXISTS `torrents` (
- torrent_id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL,
- torrent_name VARCHAR(255) NOT NULL,
- info_hash BINARY(20) NOT NULL,
- file_size FLOAT NOT NULL,
- FOREIGN KEY (user_id) REFERENCES users(user_id)
+ `torrent_id` INT AUTO_INCREMENT PRIMARY KEY,
+ `user_id` INT NOT NULL,
+ `torrent_name` VARCHAR(255) NOT NULL,
+ `info_hash` BINARY(20) NOT NULL,
+ `file_size` FLOAT NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`user_id`)
);
+-- Peer表(保持不变)
CREATE TABLE IF NOT EXISTS `peers` (
- passkey VARCHAR(255) NOT NULL,
- info_hash BINARY(20) NOT NULL,
- peer_id VARCHAR(20) NOT NULL,
- ip_address VARCHAR(128) NOT NULL,
- port INT NOT NULL,
- uploaded FLOAT NOT NULL,
- downloaded FLOAT NOT NULL,
- last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (passkey) REFERENCES users(passkey),
- PRIMARY KEY (passkey, info_hash, peer_id)
+ `passkey` VARCHAR(255) NOT NULL,
+ `info_hash` BINARY(20) NOT NULL,
+ `peer_id` VARCHAR(20) NOT NULL,
+ `ip_address` VARCHAR(128) NOT NULL,
+ `port` INT NOT NULL,
+ `uploaded` FLOAT NOT NULL,
+ `downloaded` FLOAT NOT NULL,
+ `last_seen` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ FOREIGN KEY (`passkey`) REFERENCES `users`(`passkey`),
+ PRIMARY KEY (`passkey`, `info_hash`, `peer_id`)
);
+-- 帖子表(新增 hot_score 和 last_calculated 字段)
CREATE TABLE IF NOT EXISTS `posts` (
- post_id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL,
- hot_score DOUBLE DEFAULT 0.0,
- view_count INT DEFAULT 0,
- post_title VARCHAR(255) NOT NULL,
- post_content TEXT NOT NULL,
- post_type ENUM('resource', 'discussion') NOT NULL,
- created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(user_id)
+ `post_id` INT AUTO_INCREMENT PRIMARY KEY,
+ `user_id` INT NOT NULL,
+ `hot_score` DOUBLE DEFAULT 5.0 COMMENT '热度评分',
+ `view_count` INT DEFAULT 0 COMMENT '浏览数',
+ `post_title` VARCHAR(255) NOT NULL,
+ `post_content` TEXT NOT NULL,
+ `post_type` ENUM('resource', 'discussion') NOT NULL,
+ `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `last_calculated` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后热度计算时间',
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`user_id`),
+ INDEX `idx_hot_score` (`hot_score`), -- 新增热度索引
+ INDEX `idx_post_type` (`post_type`) -- 新增类型索引
);
+-- 标签表(保持不变)
CREATE TABLE IF NOT EXISTS `tags`(
- tag_id INT AUTO_INCREMENT PRIMARY KEY,
- tag_name VARCHAR(255) NOT NULL UNIQUE,
- parent_id INT DEFAULT NULL,
- FOREIGN KEY (parent_id) REFERENCES tags(tag_id)
+ `tag_id` INT AUTO_INCREMENT PRIMARY KEY,
+ `tag_name` VARCHAR(255) NOT NULL UNIQUE,
+ `parent_id` INT DEFAULT NULL,
+ FOREIGN KEY (`parent_id`) REFERENCES `tags`(`tag_id`)
);
+-- 帖子标签关联表(保持不变)
CREATE TABLE IF NOT EXISTS `post_tag` (
- post_id INT NOT NULL,
- tag_id INT NOT NULL,
- FOREIGN KEY (post_id) REFERENCES posts(post_id),
- FOREIGN KEY (tag_id) REFERENCES tags(tag_id),
- PRIMARY KEY (post_id, tag_id)
+ `post_id` INT NOT NULL,
+ `tag_id` INT NOT NULL,
+ FOREIGN KEY (`post_id`) REFERENCES `posts`(`post_id`),
+ FOREIGN KEY (`tag_id`) REFERENCES `tags`(`tag_id`),
+ PRIMARY KEY (`post_id`, `tag_id`)
);
--- 关注关系表
+-- 用户关注表(保持不变)
CREATE TABLE IF NOT EXISTS `user_follows` (
- follower_id INT NOT NULL,
- followed_id INT NOT NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (follower_id) REFERENCES users(user_id),
- FOREIGN KEY (followed_id) REFERENCES users(user_id),
- PRIMARY KEY (follower_id, followed_id)
+ `follower_id` INT NOT NULL,
+ `followed_id` INT NOT NULL,
+ `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`follower_id`) REFERENCES `users`(`user_id`),
+ FOREIGN KEY (`followed_id`) REFERENCES `users`(`user_id`),
+ PRIMARY KEY (`follower_id`, `followed_id`)
);
--- 私信表
+-- 私信表(保持不变)
CREATE TABLE IF NOT EXISTS `private_messages` (
- message_id INT AUTO_INCREMENT PRIMARY KEY,
- sender_id INT NOT NULL,
- receiver_id INT NOT NULL,
- content TEXT NOT NULL,
- sent_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- is_read BOOLEAN DEFAULT false,
- FOREIGN KEY (sender_id) REFERENCES users(user_id),
- FOREIGN KEY (receiver_id) REFERENCES users(user_id)
+ `message_id` INT AUTO_INCREMENT PRIMARY KEY,
+ `sender_id` INT NOT NULL,
+ `receiver_id` INT NOT NULL,
+ `content` TEXT NOT NULL,
+ `sent_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ `is_read` BOOLEAN DEFAULT false,
+ FOREIGN KEY (`sender_id`) REFERENCES `users`(`user_id`),
+ FOREIGN KEY (`receiver_id`) REFERENCES `users`(`user_id`)
);
+-- 评论表(保持不变)
CREATE TABLE IF NOT EXISTS `comments` (
- comment_id INT AUTO_INCREMENT PRIMARY KEY, -- 评论ID
- post_id INT NOT NULL, -- 所属帖子ID
- user_id INT NOT NULL, -- 评论用户ID
- parent_comment_id INT DEFAULT NULL, -- 父评论ID, 如果是顶级评论则为NULL
- content TEXT NOT NULL, -- 评论内容
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 评论时间
- FOREIGN KEY (post_id) REFERENCES posts(post_id), -- 关联帖子
- FOREIGN KEY (user_id) REFERENCES users(user_id), -- 关联用户
- FOREIGN KEY (parent_comment_id) REFERENCES comments(comment_id) -- 关联父评论
+ `comment_id` INT AUTO_INCREMENT PRIMARY KEY,
+ `post_id` INT NOT NULL,
+ `user_id` INT NOT NULL,
+ `parent_comment_id` INT DEFAULT NULL,
+ `content` TEXT NOT NULL,
+ `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`post_id`) REFERENCES `posts`(`post_id`),
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`user_id`),
+ FOREIGN KEY (`parent_comment_id`) REFERENCES `comments`(`comment_id`),
+ INDEX `idx_post_id` (`post_id`) -- 新增评论帖子索引
);
-CREATE TABLE IF NOT EXISTS post_likes (
- user_id INT NOT NULL,
- post_id INT NOT NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY (user_id, post_id),
- CONSTRAINT fk_post FOREIGN KEY (post_id) REFERENCES posts(post_id),
- CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(user_id)
+-- 帖子点赞表(保持不变)
+CREATE TABLE IF NOT EXISTS `post_likes` (
+ `user_id` INT NOT NULL,
+ `post_id` INT NOT NULL,
+ `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`user_id`, `post_id`),
+ FOREIGN KEY (`post_id`) REFERENCES `posts`(`post_id`),
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`user_id`)
);
-CREATE TABLE IF NOT EXISTS post_views (
- view_id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL,
- post_id INT NOT NULL,
- view_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(user_id),
- FOREIGN KEY (post_id) REFERENCES posts(post_id)
-);
+-- 帖子浏览记录表(新增复合索引)
+CREATE TABLE IF NOT EXISTS `post_views` (
+ `view_id` INT AUTO_INCREMENT PRIMARY KEY,
+ `user_id` INT NOT NULL,
+ `post_id` INT NOT NULL,
+ `view_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`user_id`),
+ FOREIGN KEY (`post_id`) REFERENCES `posts`(`post_id`),
+ INDEX `idx_user_view_time` (`user_id`, `view_time` DESC) -- 新增用户浏览时间索引
+);
\ No newline at end of file