bug_fix+historyview

Change-Id: I6f446c1b660a4322865cfcf5502c88cb772ca0a1
diff --git a/src/main/java/com/example/g8backend/controller/PostController.java b/src/main/java/com/example/g8backend/controller/PostController.java
index a47734a..51aa519 100644
--- a/src/main/java/com/example/g8backend/controller/PostController.java
+++ b/src/main/java/com/example/g8backend/controller/PostController.java
@@ -1,6 +1,9 @@
 package com.example.g8backend.controller;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.example.g8backend.dto.PostCreateDTO;
+import com.example.g8backend.entity.PostView;
+import com.example.g8backend.mapper.PostViewMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
 import org.springframework.security.core.Authentication;
@@ -16,6 +19,8 @@
 public class PostController {
     @Autowired
     private IPostService postService;
+    @Autowired  // ✅ 新增注入
+    private PostViewMapper postViewMapper;
 
     @PostMapping("")
     public ResponseEntity<?> createPost(@RequestBody PostCreateDTO postCreateDTO) {
@@ -34,7 +39,15 @@
     }
 
     @GetMapping("/{postId}")
-    public Post getPost(@PathVariable("postId") Long postId) {
+    public Post getPost(@PathVariable Long postId) {
+        // 获取当前用户ID
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        long userId = (long) authentication.getPrincipal();
+
+        // 记录浏览行为
+        postService.recordViewHistory(userId, postId);
+
+        // 返回帖子详情
         return postService.getById(postId);
     }
 
@@ -119,4 +132,20 @@
 
         return postService.searchPosts(keyword, tags, author);
     }
+
+    @GetMapping("/history")
+    public ResponseEntity<List<PostView>> getViewHistory() {
+        // 获取当前用户ID
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        long userId = (long) authentication.getPrincipal();
+
+        // 查询历史记录(按时间倒序)
+        List<PostView> history = postViewMapper.selectList(
+                new QueryWrapper<PostView>()
+                        .eq("user_id", userId)
+                        .orderByDesc("view_time")
+        );
+
+        return ResponseEntity.ok(history);
+    }
 }
diff --git a/src/main/java/com/example/g8backend/entity/Post.java b/src/main/java/com/example/g8backend/entity/Post.java
index ac7ff20..1a3cd25 100644
--- a/src/main/java/com/example/g8backend/entity/Post.java
+++ b/src/main/java/com/example/g8backend/entity/Post.java
@@ -1,6 +1,7 @@
 package com.example.g8backend.entity;
 
 import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 
@@ -19,6 +20,10 @@
     private Timestamp createdAt;
     private String postType;
 
+    @TableField("view_count")
+    private Integer viewCount = 0;
+
+
     @Override
     public String toString() {
         return "Post{" +
@@ -28,6 +33,7 @@
                 ", postContent='" + postContent + '\'' +
                 ", createdAt=" + createdAt +
                 ", postType='" + postType + '\'' +
+                ", viewCount=" + viewCount +  // ✅ 更新 toString 方法
                 '}';
     }
 }
diff --git a/src/main/java/com/example/g8backend/entity/PostView.java b/src/main/java/com/example/g8backend/entity/PostView.java
new file mode 100644
index 0000000..72de7ca
--- /dev/null
+++ b/src/main/java/com/example/g8backend/entity/PostView.java
@@ -0,0 +1,20 @@
+package com.example.g8backend.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.IdType;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.time.LocalDateTime;
+
+@Data
+@Accessors(chain = true)
+@TableName("post_views") // 指定数据库表名
+public class PostView {
+        @TableId(value = "view_id", type = IdType.AUTO) // 主键映射为 view_id,自增
+        private Long viewId;
+        private Long userId;    // 对应 user_id 字段(自动驼峰转下划线)
+        private Long postId;    // 对应 post_id 字段(自动驼峰转下划线)
+        private LocalDateTime viewTime; // 对应 view_time 字段
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/mapper/PostMapper.java b/src/main/java/com/example/g8backend/mapper/PostMapper.java
index 8f7e029..d49125e 100644
--- a/src/main/java/com/example/g8backend/mapper/PostMapper.java
+++ b/src/main/java/com/example/g8backend/mapper/PostMapper.java
@@ -4,11 +4,7 @@
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.example.g8backend.entity.Post;
 import com.example.g8backend.entity.PostLike;
-import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Param;
-import org.apache.ibatis.annotations.Select;
-import org.apache.ibatis.annotations.Delete;
-import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.*;
 
 import java.util.List;
 
@@ -55,4 +51,7 @@
     // 获取某个帖子点赞数
     @Select("SELECT COUNT(*) FROM post_likes WHERE post_id = #{postId}")
     Long selectCount(@Param("postId") Long postId);
+
+    @Update("UPDATE posts SET view_count = view_count + 1 WHERE post_id = #{postId}")
+    void incrementViewCount(Long postId);
 }
diff --git a/src/main/java/com/example/g8backend/mapper/PostViewMapper.java b/src/main/java/com/example/g8backend/mapper/PostViewMapper.java
new file mode 100644
index 0000000..7423418
--- /dev/null
+++ b/src/main/java/com/example/g8backend/mapper/PostViewMapper.java
@@ -0,0 +1,21 @@
+package com.example.g8backend.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.example.g8backend.entity.PostView;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+@Mapper
+public interface PostViewMapper extends BaseMapper<PostView> {
+
+
+    @Select("SELECT post_id FROM post_views WHERE user_id = #{userId}")
+    List<Long> findViewedPostIds(@Param("userId") Long userId);
+
+
+    @Select("SELECT * FROM post_views WHERE user_id = #{userId} ORDER BY view_time DESC LIMIT #{limit}")
+    List<PostView> selectRecentViews(@Param("userId") Long userId, @Param("limit") int limit);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/service/IPostService.java b/src/main/java/com/example/g8backend/service/IPostService.java
index 82c792e..a349b59 100644
--- a/src/main/java/com/example/g8backend/service/IPostService.java
+++ b/src/main/java/com/example/g8backend/service/IPostService.java
@@ -2,6 +2,7 @@
 
 import com.example.g8backend.entity.Post;
 import com.baomidou.mybatisplus.extension.service.IService;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
 
@@ -16,4 +17,7 @@
     void unlikePost(Long userId, Long postId);
 
     List<Post> searchPosts(String keyword, List<Long> tagIds, String author); // 更新为支持多个标签
+
+    @Transactional
+    void recordViewHistory(Long userId, Long postId);
 }
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 647095d..f2bef5f 100644
--- a/src/main/java/com/example/g8backend/service/impl/PostServiceImpl.java
+++ b/src/main/java/com/example/g8backend/service/impl/PostServiceImpl.java
@@ -5,11 +5,15 @@
 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.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.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
 import java.sql.Timestamp;
 import java.util.List;
 
@@ -18,11 +22,14 @@
 
     private final PostMapper postMapper;
 
+    private final PostViewMapper postViewMapper;
+
     @Autowired
     private IPostTagService postTagService;
 
-    public PostServiceImpl(PostMapper postMapper) {
+    public PostServiceImpl(PostMapper postMapper, PostViewMapper postViewMapper) {
         this.postMapper = postMapper;
+        this.postViewMapper = postViewMapper;
         this.baseMapper = postMapper; // 重要:设置 baseMapper
     }
 
@@ -87,4 +94,20 @@
     public List<Post> searchPosts(String keyword, List<Long> tagIds, String author) {
         return postMapper.searchPosts(keyword, tagIds, author); // 调用mapper的搜索方法
     }
+
+
+    @Override
+    @Transactional
+    public void recordViewHistory(Long userId, Long postId) {
+        // 1. 插入浏览记录
+        PostView view = new PostView()
+                .setUserId(userId)
+                .setPostId(postId)
+                .setViewTime(new Timestamp(System.currentTimeMillis()).toLocalDateTime());
+        postViewMapper.insert(view);
+
+        // 2. 原子更新浏览数
+        postMapper.incrementViewCount(postId); // 直接调用原子操作
+    }
+
 }
\ No newline at end of file
diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql
index 355602c..d97701c 100644
--- a/src/main/resources/schema.sql
+++ b/src/main/resources/schema.sql
@@ -34,6 +34,8 @@
 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,
@@ -56,14 +58,6 @@
     PRIMARY KEY (post_id, tag_id)
 );
 
-CREATE TABLE IF NOT EXISTS `post_likes` (
-    user_id INT NOT NULL,
-    post_id INT NOT NULL,
-    FOREIGN KEY (user_id) REFERENCES users(user_id),
-    FOREIGN KEY (post_id) REFERENCES posts(post_id),
-    PRIMARY KEY (user_id, post_id)
-);
-
 -- 关注关系表
 CREATE TABLE IF NOT EXISTS `user_follows` (
     follower_id INT NOT NULL,
@@ -98,11 +92,20 @@
     FOREIGN KEY (parent_comment_id) REFERENCES comments(comment_id) -- 关联父评论
 );
 
-CREATE TABLE post_likes (
-    user_id BIGINT NOT NULL,
-    post_id BIGINT NOT NULL,
+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_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)
+);
diff --git a/src/test/java/com/example/g8backend/service/PostHistoryServiceTest.java b/src/test/java/com/example/g8backend/service/PostHistoryServiceTest.java
new file mode 100644
index 0000000..143422d
--- /dev/null
+++ b/src/test/java/com/example/g8backend/service/PostHistoryServiceTest.java
@@ -0,0 +1,75 @@
+package com.example.g8backend.service;
+
+import com.example.g8backend.entity.PostView;
+import com.example.g8backend.mapper.PostMapper;
+import com.example.g8backend.mapper.PostViewMapper;
+import com.example.g8backend.service.impl.PostServiceImpl;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+@Transactional
+public class PostHistoryServiceTest {
+
+    @Mock
+    private PostViewMapper postViewMapper;
+
+    @Mock
+    private PostMapper postMapper;
+
+    @InjectMocks
+    private PostServiceImpl postService;
+
+    @Test
+    public void testRecordViewHistory_NormalCase() {
+        // 测试数据
+        Long userId = 1L;
+        Long postId = 100L;
+
+        // 调用方法
+        postService.recordViewHistory(userId, postId);
+
+        // 验证行为
+        verify(postViewMapper, times(1)).insert(any(PostView.class));
+        verify(postMapper, times(1)).incrementViewCount(eq(postId));
+    }
+
+    @Test
+    public void testRecordViewHistory_CheckDataIntegrity() {
+        Long userId = 2L;
+        Long postId = 200L;
+
+        postService.recordViewHistory(userId, postId);
+
+        // 显式指定参数类型为 PostView
+        verify(postViewMapper).insert(argThat(new ArgumentMatcher<PostView>() {
+            @Override
+            public boolean matches(PostView view) {
+                return view.getUserId().equals(userId) &&
+                        view.getPostId().equals(postId) &&
+                        view.getViewTime() != null;
+            }
+        }));
+    }
+    @Test
+    public void testRecordViewHistory_MultipleCalls() {
+        // 模拟多次调用
+        Long postId = 300L;
+        postService.recordViewHistory(1L, postId);
+        postService.recordViewHistory(2L, postId);
+
+        // 验证浏览数更新次数
+        verify(postMapper, times(2)).incrementViewCount(postId);
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/example/g8backend/service/PostServiceTest.java b/src/test/java/com/example/g8backend/service/PostServiceTest.java
index 0161229..9f8fb14 100644
--- a/src/test/java/com/example/g8backend/service/PostServiceTest.java
+++ b/src/test/java/com/example/g8backend/service/PostServiceTest.java
@@ -2,6 +2,7 @@
 
 import com.example.g8backend.entity.Post;
 import com.example.g8backend.mapper.PostMapper;
+import com.example.g8backend.mapper.PostViewMapper;
 import com.example.g8backend.service.impl.PostServiceImpl;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
@@ -26,6 +27,9 @@
     @Mock
     private PostMapper postMapper;
 
+    @Mock
+    private PostViewMapper postViewMapper;
+
     private PostServiceImpl postService;
 
     private Post testPost;
@@ -33,7 +37,7 @@
     @BeforeEach
     void setUp() {
         MockitoAnnotations.openMocks(this);
-        postService = new PostServiceImpl(postMapper);
+        postService = new PostServiceImpl(postMapper, postViewMapper);
         testPost = createTestPost();
     }