post&topic&forum IMP_Backend

Change-Id: I6510aa51e15b15c0e3232e3b8f3a16fe37ffa0c6
diff --git a/src/main/java/com/github/example/pt/controller/ForumController.java b/src/main/java/com/github/example/pt/controller/ForumController.java
new file mode 100644
index 0000000..3793e4d
--- /dev/null
+++ b/src/main/java/com/github/example/pt/controller/ForumController.java
@@ -0,0 +1,55 @@
+package com.github.example.pt.controller;
+
+import com.github.example.pt.entity.Forum;
+import com.github.example.pt.entity.Topic;
+import com.github.example.pt.service.ForumService;
+import org.hibernate.Session;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/forums")
+public class ForumController {
+
+    @Autowired
+    private ForumService forumService;
+
+    @GetMapping
+    public List<Forum> getAllForums() {
+        return forumService.getAllForums();
+    }
+
+    @GetMapping("/{id}")
+    public ResponseEntity<Forum> getForumById(@PathVariable Long id) {
+        return forumService.getForumById(id)
+                .map(ResponseEntity::ok)
+                .orElse(ResponseEntity.notFound().build());
+    }
+
+    @PostMapping
+    public Forum createForum(@RequestBody Forum forum) {
+        return forumService.createForum(forum);
+    }
+
+    @PutMapping("/{id}")
+    public ResponseEntity<Forum> updateForum(@PathVariable Long id, @RequestBody Forum forum) {
+        try {
+            return ResponseEntity.ok(forumService.updateForum(id, forum));
+        } catch (RuntimeException e) {
+            return ResponseEntity.notFound().build();
+        }
+    }
+
+    @DeleteMapping("/{id}")
+    public ResponseEntity<Void> deleteForum(@PathVariable Long id ) {
+
+        forumService.deleteForum(id);
+        return ResponseEntity.noContent().build();
+    }
+
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/controller/ForumTagController.java b/src/main/java/com/github/example/pt/controller/ForumTagController.java
new file mode 100644
index 0000000..a3a048d
--- /dev/null
+++ b/src/main/java/com/github/example/pt/controller/ForumTagController.java
@@ -0,0 +1,88 @@
+package com.github.example.pt.controller;
+
+import cn.dev33.satoken.stp.StpUtil;
+import com.github.example.pt.entity.ForumTag;
+import com.github.example.pt.entity.Topic;
+import com.github.example.pt.entity.User;
+import com.github.example.pt.service.ForumTagService;
+import com.github.example.pt.service.TopicService;
+import com.github.example.pt.service.TopicTagService;
+import com.github.example.pt.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Set;
+
+@RestController
+@RequestMapping("/api/tags")
+public class ForumTagController {
+
+    @Autowired
+    private ForumTagService tagService;
+
+    @Autowired
+    private TopicService topicTagService;
+
+    @Autowired
+    private TopicTagService TagService;
+
+    @Autowired
+    private UserService userService;
+
+    @GetMapping
+    public ResponseEntity<?> getAllTags() {
+        return ResponseEntity.ok(tagService.getAllTags());
+    }
+
+    @PostMapping
+    public ResponseEntity<?> createTag(@RequestParam String name, @RequestParam(required = false) String color) {
+        return ResponseEntity.ok(tagService.createTag(name, color));
+    }
+
+    @DeleteMapping("/{tagId}")
+    public ResponseEntity<Void> deleteTag(@PathVariable Long tagId) {
+        tagService.deleteTag(tagId);
+        return ResponseEntity.noContent().build();
+    }
+
+    @PostMapping("/topics/{topicId}/assign")
+    public ResponseEntity<Set<ForumTag>> assignTagsToTopic(
+            @PathVariable Long topicId,
+            @RequestBody Set<String> tagNames) {
+
+        Topic topic = topicTagService.getTopicById(topicId).orElse(null);
+        if (topic == null) return ResponseEntity.notFound().build();
+
+        for (String tagName : tagNames) {
+            tagService.findOrCreate(tagName, "#ccc").ifPresent(tag -> topic.getTags().add(tag));//findOrCreate(tagName, "#ccc").ifPresent(tag -> topic.getTags().add(tag));
+        }
+        topicTagService.updateTopic(topicId, topic);
+        return ResponseEntity.ok(topic.getTags());
+    }
+
+    @GetMapping("/topics/{topicId}")
+    public ResponseEntity<Set<ForumTag>> getTagsByTopic(@PathVariable Long topicId) {
+        Topic topic = topicTagService.getTopicById(topicId).orElse(null);
+        if (topic == null) return ResponseEntity.notFound().build();
+        return ResponseEntity.ok(topic.getTags());
+    }
+
+    @DeleteMapping("/topics/{topicId}/remove/{tagId}")
+    public ResponseEntity<Void> removeTagFromTopic(@PathVariable Long topicId, @PathVariable Long tagId ) {
+        User user = userService.getUser(StpUtil.getLoginIdAsLong());
+        Long userid;
+        if (user != null) {
+            userid = user.getId();
+            if(userid.equals(topicId)){
+                TagService.removeTagFromTopic(topicId, tagId);
+                return ResponseEntity.noContent().build();
+            }
+        }
+
+        return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
+
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/controller/PostController.java b/src/main/java/com/github/example/pt/controller/PostController.java
new file mode 100644
index 0000000..898a369
--- /dev/null
+++ b/src/main/java/com/github/example/pt/controller/PostController.java
@@ -0,0 +1,94 @@
+package com.github.example.pt.controller;
+
+import cn.dev33.satoken.stp.StpUtil;
+import com.github.example.pt.entity.Post;
+import com.github.example.pt.entity.Topic;
+import com.github.example.pt.entity.User;
+import com.github.example.pt.service.PostService;
+import com.github.example.pt.service.TopicService;
+import com.github.example.pt.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/posts")
+public class PostController {
+
+    @Autowired
+    private PostService postService;
+
+    @Autowired
+    private TopicService topicService;
+
+    @Autowired
+    private UserService UserService;
+
+    @GetMapping("/topic/{topicId}")
+    public ResponseEntity<List<Post>> getPostsByTopic(@PathVariable Long topicId) {
+        return topicService.getTopicById(topicId)
+                .map(topic -> ResponseEntity.ok(postService.getPostsByTopic(topic)))
+                .orElse(ResponseEntity.notFound().build());
+    }
+
+    @GetMapping("/user/{userId}")
+    public List<Post> getPostsByUser(@PathVariable Long userId) {
+        return postService.getPostsByUser(userId);
+    }
+
+    @GetMapping("/{id}")
+    public ResponseEntity<Post> getPostById(@PathVariable Long id) {
+        return postService.getPostById(id)
+                .map(ResponseEntity::ok)
+                .orElse(ResponseEntity.notFound().build());
+    }
+
+    @PostMapping
+    public Post createPost(@RequestBody Post post) {
+        return postService.createPost(post);
+    }
+
+    @PutMapping("/{id}")
+    public ResponseEntity<Post> updatePost(@PathVariable Long id, @RequestBody Post post) {
+        try {
+            return ResponseEntity.ok(postService.updatePost(id, post));
+        } catch (RuntimeException e) {
+            return ResponseEntity.notFound().build();
+        }
+    }
+
+    @DeleteMapping("/{id}")
+    public ResponseEntity<Void> softDeletePost(@PathVariable Long id ) {
+        //Topic topic = topicService.getTopicById(id).orElseThrow(() -> new RuntimeException("topic not found"));
+
+        //创建post的人才有资格删除post
+        Post post = postService.getPostById(id).orElseThrow(() -> new RuntimeException("post not found"));
+        Long postuserid = post.getUser().getId();
+        //创建该帖子的人可以删除post
+        Long topicuserid = post.getTopic().getUser().getId();
+
+        User user = UserService.getUser(StpUtil.getLoginIdAsLong());
+        if (user != null) {
+            Long UserId = user.getId();
+            if(topicuserid.equals(UserId) || postuserid.equals(UserId)){
+                postService.softDeletePost(id);
+                return ResponseEntity.noContent().build();
+            }
+
+        }
+
+
+        return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
+
+    }
+
+    @GetMapping("/topic/{topicId}/tree")
+    public List<Post> getPostTreeByTopic(@PathVariable Long topicId) {
+        List<Post> all = postService.getPostsByTopic(topicId);
+        return postService.buildPostTree(all);
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/controller/PostLikeController.java b/src/main/java/com/github/example/pt/controller/PostLikeController.java
new file mode 100644
index 0000000..217a7c1
--- /dev/null
+++ b/src/main/java/com/github/example/pt/controller/PostLikeController.java
@@ -0,0 +1,68 @@
+package com.github.example.pt.controller;
+
+import com.github.example.pt.entity.Post;
+import com.github.example.pt.entity.User;
+import com.github.example.pt.service.PostLikeService;
+import com.github.example.pt.service.PostService;
+import com.github.example.pt.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/post-likes")
+public class PostLikeController {
+
+    @Autowired
+    private PostLikeService postLikeService;
+
+    @Autowired
+    private PostService postService;
+
+    @Autowired
+    private UserService userService;
+
+    @PostMapping("/{postId}/like/{userId}")
+    public ResponseEntity<?> likePost(@PathVariable Long postId, @PathVariable Long userId) {
+        Post post = postService.getPostById(postId).orElse(null);
+        User user = userService.getUser(userId);
+
+        if (post == null || user == null) return ResponseEntity.notFound().build();
+        if (postLikeService.hasUserLikedPost(user, post)) {
+            return ResponseEntity.badRequest().body("Already liked");
+        }
+
+        return ResponseEntity.ok(postLikeService.likePost(user, post));
+    }
+
+    @DeleteMapping("/{postId}/unlike/{userId}")
+    public ResponseEntity<?> unlikePost(@PathVariable Long postId, @PathVariable Long userId) {
+        Post post = postService.getPostById(postId).orElse(null);
+        User user = userService.getUser(userId);
+
+        if (post == null || user == null) return ResponseEntity.notFound().build();
+        postLikeService.unlikePost(user, post);
+        return ResponseEntity.noContent().build();
+    }
+
+    @GetMapping("/{postId}/count")
+    public ResponseEntity<Long> countLikes(@PathVariable Long postId) {
+        return postService.getPostById(postId)
+                .map(post -> ResponseEntity.ok(postLikeService.countLikes(post)))
+                .orElse(ResponseEntity.notFound().build());
+    }
+
+    @GetMapping("/{postId}/likes/check")
+    public ResponseEntity<Map<String, Boolean>> hasLikedPost(
+            @PathVariable Long postId,
+            @RequestParam Long userId  // 最好从 token/session 中获取
+    ) {
+        boolean liked = postLikeService.hasUserLikedPost(postId, userId);
+        Map<String, Boolean> response = new HashMap<>();
+        response.put("liked", liked);
+        return ResponseEntity.ok(response);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/controller/TopicController.java b/src/main/java/com/github/example/pt/controller/TopicController.java
new file mode 100644
index 0000000..3b0d5f0
--- /dev/null
+++ b/src/main/java/com/github/example/pt/controller/TopicController.java
@@ -0,0 +1,93 @@
+package com.github.example.pt.controller;
+
+import cn.dev33.satoken.stp.StpUtil;
+import com.github.example.pt.entity.Topic;
+import com.github.example.pt.entity.User;
+import com.github.example.pt.service.ForumService;
+import com.github.example.pt.service.TopicService;
+import com.github.example.pt.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/topics")
+public class TopicController {
+
+    @Autowired
+    private TopicService topicService;
+
+    @Autowired
+    private ForumService forumService;
+
+    @Autowired
+    private UserService UserService;
+
+    @GetMapping
+    public List<Topic> getAllTopics() {
+        return topicService.getAllTopics();
+    }
+
+    @GetMapping("/forum/{forumId}")
+    public ResponseEntity<List<Topic>> getTopicsByForum(@PathVariable Long forumId) {
+        return forumService.getForumById(forumId)
+                .map(forum -> ResponseEntity.ok(topicService.getTopicsByForum(forum)))
+                .orElse(ResponseEntity.notFound().build());
+    }
+
+    @GetMapping("/{id}")
+    public ResponseEntity<Topic> getTopicById(@PathVariable Long id) {
+        return topicService.getTopicById(id)
+                .map(ResponseEntity::ok)
+                .orElse(ResponseEntity.notFound().build());
+    }
+
+    @PostMapping
+    public Topic createTopic(@RequestBody Topic topic) {
+        return topicService.createTopic(topic);
+    }
+
+    @PutMapping("/{id}")
+    public ResponseEntity<Topic> updateTopic(@PathVariable Long id, @RequestBody Topic topic) {
+        try {
+            return ResponseEntity.ok(topicService.updateTopic(id, topic));
+        } catch (RuntimeException e) {
+            return ResponseEntity.notFound().build();
+        }
+    }
+
+    @DeleteMapping("/{id}")
+    public ResponseEntity<Void> deleteTopic(@PathVariable Long id ) {
+        //User user = UserService.getUser(userId);
+        Topic topic = topicService.getTopicById(id).orElseThrow(() -> new RuntimeException("topic not found"));
+        Long topicuserid = topic.getUser().getId();
+
+        User user = UserService.getUser(StpUtil.getLoginIdAsLong());
+        Long userId = null;
+        if (user != null) {
+            userId = user.getId();
+            if(topicuserid.equals(userId)){
+                topicService.deleteTopic(id);
+                return ResponseEntity.noContent().build();
+            }
+        }
+
+        return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
+
+
+    }
+
+    @GetMapping("/search")
+    public Page<Topic> searchTopics(
+            @RequestParam String q,
+            @RequestParam(required = false) Long forumId,
+            @RequestParam(defaultValue = "0") int page,
+            @RequestParam(defaultValue = "10") int size) {
+        return topicService.searchTopics(q, forumId, page, size);
+    }
+
+}
diff --git a/src/main/java/com/github/example/pt/controller/TopicSubscriptionController.java b/src/main/java/com/github/example/pt/controller/TopicSubscriptionController.java
new file mode 100644
index 0000000..d33d256
--- /dev/null
+++ b/src/main/java/com/github/example/pt/controller/TopicSubscriptionController.java
@@ -0,0 +1,50 @@
+package com.github.example.pt.controller;
+
+import com.github.example.pt.entity.Topic;
+import com.github.example.pt.entity.User;
+import com.github.example.pt.service.TopicService;
+import com.github.example.pt.service.TopicSubscriptionService;
+import com.github.example.pt.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/subscriptions")
+public class TopicSubscriptionController {
+
+    @Autowired
+    private TopicSubscriptionService subscriptionService;
+
+    @Autowired
+    private TopicService topicService;
+
+    @Autowired
+    private UserService userService;
+
+    @PostMapping("/subscribe")
+    public ResponseEntity<?> subscribe(@RequestParam Long userId, @RequestParam Long topicId) {
+        Topic topic = topicService.getTopicById(topicId).orElse(null);
+        User user = userService.getUser(userId);
+        if (topic == null || user == null) return ResponseEntity.badRequest().build();
+
+        subscriptionService.subscribe(user, topic);
+        return ResponseEntity.ok().build();
+    }
+
+    @PostMapping("/unsubscribe")
+    public ResponseEntity<?> unsubscribe(@RequestParam Long userId, @RequestParam Long topicId) {
+        subscriptionService.unsubscribe(userId, topicId);
+        return ResponseEntity.ok().build();
+    }
+
+    @GetMapping("/check")
+    public ResponseEntity<Boolean> checkSubscription(@RequestParam Long userId, @RequestParam Long topicId) {
+        return ResponseEntity.ok(subscriptionService.isSubscribed(userId, topicId));
+    }
+
+    @GetMapping("/list")
+    public ResponseEntity<?> listSubscriptions(@RequestParam Long userId) {
+        return ResponseEntity.ok(subscriptionService.getSubscriptions(userId));
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/controller/TopicViewController.java b/src/main/java/com/github/example/pt/controller/TopicViewController.java
new file mode 100644
index 0000000..a66fb89
--- /dev/null
+++ b/src/main/java/com/github/example/pt/controller/TopicViewController.java
@@ -0,0 +1,41 @@
+package com.github.example.pt.controller;
+
+import com.github.example.pt.entity.Topic;
+import com.github.example.pt.entity.User;
+import com.github.example.pt.service.TopicService;
+import com.github.example.pt.service.TopicViewService;
+import com.github.example.pt.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/topic-views")
+public class TopicViewController {
+
+    @Autowired
+    private TopicViewService topicViewService;
+
+    @Autowired
+    private TopicService topicService;
+
+    @Autowired
+    private UserService userService;
+
+    @PostMapping("/record")
+    public ResponseEntity<?> recordView(@RequestParam Long topicId, @RequestParam Long userId) {
+        Topic topic = topicService.getTopicById(topicId).orElse(null);
+        User user = userService.getUser(userId);
+        if (topic == null || user == null) return ResponseEntity.notFound().build();
+
+        topicViewService.recordView(topic, user);
+        return ResponseEntity.ok().build();
+    }
+
+    @GetMapping
+    public ResponseEntity<?> getView(@RequestParam Long topicId, @RequestParam Long userId) {
+        return topicViewService.getViewRecord(topicId, userId)
+                .map(ResponseEntity::ok)
+                .orElse(ResponseEntity.notFound().build());
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/controller/UserForumHistoryController.java b/src/main/java/com/github/example/pt/controller/UserForumHistoryController.java
new file mode 100644
index 0000000..2c05573
--- /dev/null
+++ b/src/main/java/com/github/example/pt/controller/UserForumHistoryController.java
@@ -0,0 +1,25 @@
+package com.github.example.pt.controller;
+
+import com.github.example.pt.entity.ForumActionType;
+import com.github.example.pt.service.UserForumHistoryService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/history")
+public class UserForumHistoryController {
+
+    @Autowired
+    private UserForumHistoryService historyService;
+
+    @GetMapping("/{userId}")
+    public ResponseEntity<?> getUserHistory(@PathVariable Long userId) {
+        return ResponseEntity.ok(historyService.getAllByUser(userId));
+    }
+
+    @GetMapping("/{userId}/{type}")
+    public ResponseEntity<?> getUserHistoryByType(@PathVariable Long userId, @PathVariable ForumActionType type) {
+        return ResponseEntity.ok(historyService.getByType(userId, type));
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/entity/Forum.java b/src/main/java/com/github/example/pt/entity/Forum.java
new file mode 100644
index 0000000..174c3f0
--- /dev/null
+++ b/src/main/java/com/github/example/pt/entity/Forum.java
@@ -0,0 +1,41 @@
+package com.github.example.pt.entity;
+
+import jakarta.persistence.*;
+import lombok.*;
+
+import java.util.List;
+
+@Entity
+@Table(name = "forums")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class Forum {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @Column(nullable = false, unique = true)
+    private String slug;
+
+    @Column(nullable = false)
+    private String name;
+
+    @Column(columnDefinition = "TEXT")
+    private String description;
+
+    @ManyToOne
+    @JoinColumn(name = "parent_id")
+    private Forum parent;
+
+    @OneToMany(mappedBy = "parent")
+    private List<Forum> children;
+
+    @Column(name = "sort_order")
+    private Integer sortOrder;
+
+    @Column(name = "is_locked")
+    private Boolean isLocked = false;
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/entity/ForumActionType.java b/src/main/java/com/github/example/pt/entity/ForumActionType.java
new file mode 100644
index 0000000..619e9f7
--- /dev/null
+++ b/src/main/java/com/github/example/pt/entity/ForumActionType.java
@@ -0,0 +1,6 @@
+package com.github.example.pt.entity;
+
+public enum ForumActionType {
+    topic,
+    reply
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/entity/ForumTag.java b/src/main/java/com/github/example/pt/entity/ForumTag.java
new file mode 100644
index 0000000..5833906
--- /dev/null
+++ b/src/main/java/com/github/example/pt/entity/ForumTag.java
@@ -0,0 +1,27 @@
+package com.github.example.pt.entity;
+import jakarta.persistence.*;
+import lombok.*;
+
+import java.util.Set;
+
+@Entity
+@Table(name = "forum_tags")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class ForumTag {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @Column(nullable = false, unique = true)
+    private String name;
+
+    @Column(length = 10)
+    private String color;
+
+    @ManyToMany(mappedBy = "tags")
+    private Set<Topic> topics;
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/entity/Post.java b/src/main/java/com/github/example/pt/entity/Post.java
new file mode 100644
index 0000000..8fcfb3c
--- /dev/null
+++ b/src/main/java/com/github/example/pt/entity/Post.java
@@ -0,0 +1,48 @@
+package com.github.example.pt.entity;
+import jakarta.persistence.*;
+import lombok.*;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+@Table(name = "posts")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class Post {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "topic_id")
+    private Topic topic;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "user_id")
+    private User user;
+
+    @Column(columnDefinition = "TEXT", nullable = false)
+    private String content;
+
+    @Column(name = "created_at", nullable = false)
+    private LocalDateTime createdAt;
+
+    @Column(name = "updated_at", nullable = false)
+    private LocalDateTime updatedAt;
+
+    @Column(name = "is_deleted")
+    private Boolean isDeleted = false;
+
+    @ManyToOne
+    @JoinColumn(name = "parent_id")
+    private Post parent;
+
+    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
+    @OrderBy("createdAt ASC")
+    private List<Post> replies = new ArrayList<>();
+}
diff --git a/src/main/java/com/github/example/pt/entity/PostLike.java b/src/main/java/com/github/example/pt/entity/PostLike.java
new file mode 100644
index 0000000..63eebf6
--- /dev/null
+++ b/src/main/java/com/github/example/pt/entity/PostLike.java
@@ -0,0 +1,38 @@
+package com.github.example.pt.entity;
+
+import jakarta.persistence.*;
+import lombok.*;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "post_likes")
+@IdClass(PostLike.PostLikeId.class)
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class PostLike {
+
+    @Id
+    @ManyToOne
+    @JoinColumn(name = "user_id", nullable = false)
+    private User user;
+
+    @Id
+    @ManyToOne
+    @JoinColumn(name = "post_id", nullable = false)
+    private Post post;
+
+    @Column(name = "created_at", nullable = false)
+    private LocalDateTime createdAt;
+
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class PostLikeId implements Serializable {
+        private Long user;
+        private Long post;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/entity/Topic.java b/src/main/java/com/github/example/pt/entity/Topic.java
new file mode 100644
index 0000000..a28b070
--- /dev/null
+++ b/src/main/java/com/github/example/pt/entity/Topic.java
@@ -0,0 +1,52 @@
+package com.github.example.pt.entity;
+
+import jakarta.persistence.*;
+import lombok.*;
+
+import java.time.LocalDateTime;
+import java.util.Set;
+
+@Entity
+@Table(name = "topics")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class Topic {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "forum_id")
+    private Forum forum;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "user_id")
+    private User user;
+
+    @Column(nullable = false)
+    private String title;
+
+    @Column(name = "is_pinned")
+    private Boolean isPinned = false;
+
+    @Column(name = "is_locked")
+    private Boolean isLocked = false;
+
+    @Column(name = "created_at", nullable = false)
+    private LocalDateTime createdAt;
+
+    @Column(name = "updated_at", nullable = false)
+    private LocalDateTime updatedAt;
+
+    @ManyToMany
+    @JoinTable(
+            name = "topic_tags",
+            joinColumns = @JoinColumn(name = "topic_id"),
+            inverseJoinColumns = @JoinColumn(name = "tag_id")
+    )
+    private Set<ForumTag> tags;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/entity/TopicSubscription.java b/src/main/java/com/github/example/pt/entity/TopicSubscription.java
new file mode 100644
index 0000000..22d78df
--- /dev/null
+++ b/src/main/java/com/github/example/pt/entity/TopicSubscription.java
@@ -0,0 +1,29 @@
+package com.github.example.pt.entity;
+
+import jakarta.persistence.*;
+import lombok.*;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "topic_subscriptions")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@IdClass(TopicSubscriptionId.class)
+public class TopicSubscription implements Serializable {
+
+    @Id
+    @ManyToOne
+    @JoinColumn(name = "user_id", nullable = false)
+    private User user;
+
+    @Id
+    @ManyToOne
+    @JoinColumn(name = "topic_id", nullable = false)
+    private Topic topic;
+
+    @Column(name = "subscribed_at", nullable = false)
+    private LocalDateTime subscribedAt;
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/entity/TopicSubscriptionId.java b/src/main/java/com/github/example/pt/entity/TopicSubscriptionId.java
new file mode 100644
index 0000000..e565b7d
--- /dev/null
+++ b/src/main/java/com/github/example/pt/entity/TopicSubscriptionId.java
@@ -0,0 +1,29 @@
+package com.github.example.pt.entity;
+
+import lombok.*;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TopicSubscriptionId implements Serializable {
+
+    private Long user;
+    private Long topic;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof TopicSubscriptionId)) return false;
+        TopicSubscriptionId that = (TopicSubscriptionId) o;
+        return Objects.equals(user, that.user) &&
+                Objects.equals(topic, that.topic);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(user, topic);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/entity/TopicTag.java b/src/main/java/com/github/example/pt/entity/TopicTag.java
new file mode 100644
index 0000000..64749e4
--- /dev/null
+++ b/src/main/java/com/github/example/pt/entity/TopicTag.java
@@ -0,0 +1,28 @@
+package com.github.example.pt.entity;
+
+import jakarta.persistence.*;
+
+import lombok.*;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+@Entity
+@Table(name = "topic_tags")
+public class TopicTag {
+
+    @EmbeddedId
+    private TopicTagId id;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @MapsId("topicId")
+    @JoinColumn(name = "topic_id")
+    private Topic topic;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @MapsId("tagId")
+    @JoinColumn(name = "tag_id")
+    private ForumTag tag;
+
+    // getters and setters
+}
diff --git a/src/main/java/com/github/example/pt/entity/TopicTagId.java b/src/main/java/com/github/example/pt/entity/TopicTagId.java
new file mode 100644
index 0000000..215f3d6
--- /dev/null
+++ b/src/main/java/com/github/example/pt/entity/TopicTagId.java
@@ -0,0 +1,18 @@
+package com.github.example.pt.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Embeddable;
+
+import java.io.Serializable;
+
+@Embeddable
+public class TopicTagId implements Serializable {
+
+    @Column(name = "topic_id")
+    private Long topicId;
+
+    @Column(name = "tag_id")
+    private Long tagId;
+
+    // Default constructor, getters and setters
+}
diff --git a/src/main/java/com/github/example/pt/entity/TopicView.java b/src/main/java/com/github/example/pt/entity/TopicView.java
new file mode 100644
index 0000000..29eab77
--- /dev/null
+++ b/src/main/java/com/github/example/pt/entity/TopicView.java
@@ -0,0 +1,28 @@
+package com.github.example.pt.entity;
+
+import jakarta.persistence.*;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "topic_views")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@IdClass(TopicViewId.class)
+public class TopicView {
+
+    @Id
+    @ManyToOne
+    @JoinColumn(name = "topic_id")
+    private Topic topic;
+
+    @Id
+    @ManyToOne
+    @JoinColumn(name = "user_id")
+    private User user;
+
+    @Column(name = "last_viewed_at", nullable = false)
+    private LocalDateTime lastViewedAt;
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/entity/TopicViewId.java b/src/main/java/com/github/example/pt/entity/TopicViewId.java
new file mode 100644
index 0000000..c7ae474
--- /dev/null
+++ b/src/main/java/com/github/example/pt/entity/TopicViewId.java
@@ -0,0 +1,29 @@
+package com.github.example.pt.entity;
+
+import lombok.*;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TopicViewId implements Serializable {
+
+    private Long topic;
+    private Long user;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof TopicViewId)) return false;
+        TopicViewId that = (TopicViewId) o;
+        return Objects.equals(topic, that.topic) &&
+                Objects.equals(user, that.user);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(topic, user);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/entity/UserForumHistory.java b/src/main/java/com/github/example/pt/entity/UserForumHistory.java
new file mode 100644
index 0000000..e00f00f
--- /dev/null
+++ b/src/main/java/com/github/example/pt/entity/UserForumHistory.java
@@ -0,0 +1,36 @@
+package com.github.example.pt.entity;
+
+import jakarta.persistence.*;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "user_forum_history", indexes = {
+        @Index(name = "idx_user_id", columnList = "user_id"),
+        @Index(name = "idx_type_target", columnList = "type, target_id")
+})
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class UserForumHistory {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "user_id")
+    private User user;
+
+    @Enumerated(EnumType.STRING)
+    @Column(nullable = false)
+    private ForumActionType type; // 'topic' or 'reply'
+
+    @Column(name = "target_id", nullable = false)
+    private Long targetId;
+
+    @Column(name = "created_at", nullable = false)
+    private LocalDateTime createdAt;
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/repository/ForumRepository.java b/src/main/java/com/github/example/pt/repository/ForumRepository.java
new file mode 100644
index 0000000..2c78c2f
--- /dev/null
+++ b/src/main/java/com/github/example/pt/repository/ForumRepository.java
@@ -0,0 +1,10 @@
+package com.github.example.pt.repository;
+
+import com.github.example.pt.entity.*;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface ForumRepository extends JpaRepository<Forum, Long> {
+    Optional<Forum> findBySlug(String slug);
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/repository/ForumTagRepository.java b/src/main/java/com/github/example/pt/repository/ForumTagRepository.java
new file mode 100644
index 0000000..3e0f3cc
--- /dev/null
+++ b/src/main/java/com/github/example/pt/repository/ForumTagRepository.java
@@ -0,0 +1,11 @@
+package com.github.example.pt.repository;
+
+import com.github.example.pt.entity.ForumTag;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface ForumTagRepository extends JpaRepository<ForumTag, Long> {
+    Optional<ForumTag> findByName(String name);
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/repository/PostLikeRepository.java b/src/main/java/com/github/example/pt/repository/PostLikeRepository.java
new file mode 100644
index 0000000..ec12c6c
--- /dev/null
+++ b/src/main/java/com/github/example/pt/repository/PostLikeRepository.java
@@ -0,0 +1,17 @@
+package com.github.example.pt.repository;
+
+import com.github.example.pt.entity.Post;
+import com.github.example.pt.entity.PostLike;
+import com.github.example.pt.entity.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface PostLikeRepository extends JpaRepository<PostLike, PostLike.PostLikeId> {
+    Optional<PostLike> findByUserAndPost(User user, Post post);
+    List<PostLike> findByPost(Post post);
+    long countByPost(Post post);
+    boolean existsByPostIdAndUserId(Long postId, Long userId);
+
+}
diff --git a/src/main/java/com/github/example/pt/repository/PostRepository.java b/src/main/java/com/github/example/pt/repository/PostRepository.java
new file mode 100644
index 0000000..f168766
--- /dev/null
+++ b/src/main/java/com/github/example/pt/repository/PostRepository.java
@@ -0,0 +1,13 @@
+package com.github.example.pt.repository;
+
+import com.github.example.pt.entity.Post;
+import com.github.example.pt.entity.Topic;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+public interface PostRepository extends JpaRepository<Post, Long> {
+    List<Post> findByTopicAndIsDeletedFalse(Topic topic);
+    List<Post> findByUserIdAndIsDeletedFalse(Long userId);
+    List<Post> findByTopicIdOrderByCreatedAtAsc(Long topicId);
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/repository/TopicRepository.java b/src/main/java/com/github/example/pt/repository/TopicRepository.java
new file mode 100644
index 0000000..b396af0
--- /dev/null
+++ b/src/main/java/com/github/example/pt/repository/TopicRepository.java
@@ -0,0 +1,24 @@
+package com.github.example.pt.repository;
+
+import com.github.example.pt.entity.Forum;
+import com.github.example.pt.entity.Topic;
+import io.lettuce.core.dynamic.annotation.Param;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+
+import java.util.List;
+
+public interface TopicRepository extends JpaRepository<Topic, Long> {
+    List<Topic> findByForum(Forum forum);
+
+    @Query("SELECT t FROM Topic t WHERE " +
+            "(LOWER(t.title) LIKE LOWER(CONCAT('%', :keyword, '%')) " +
+            "OR EXISTS (SELECT p FROM Post p WHERE p.topic.id = t.id AND LOWER(p.content) LIKE LOWER(CONCAT('%', :keyword, '%')))) " +
+            "AND (:forumId IS NULL OR t.forum.id = :forumId)")
+    Page<Topic> searchByKeyword(@Param("keyword") String keyword,
+                                @Param("forumId") Long forumId,
+                                Pageable pageable);
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/repository/TopicSubscriptionRepository.java b/src/main/java/com/github/example/pt/repository/TopicSubscriptionRepository.java
new file mode 100644
index 0000000..0e13e1f
--- /dev/null
+++ b/src/main/java/com/github/example/pt/repository/TopicSubscriptionRepository.java
@@ -0,0 +1,13 @@
+package com.github.example.pt.repository;
+
+import com.github.example.pt.entity.TopicSubscription;
+import com.github.example.pt.entity.TopicSubscriptionId;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+public interface TopicSubscriptionRepository extends JpaRepository<TopicSubscription, TopicSubscriptionId> {
+    List<TopicSubscription> findByUser_Id(Long userId);
+    boolean existsByUser_IdAndTopic_Id(Long userId, Long topicId);
+    void deleteByUser_IdAndTopic_Id(Long userId, Long topicId);
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/repository/TopicTagRepository.java b/src/main/java/com/github/example/pt/repository/TopicTagRepository.java
new file mode 100644
index 0000000..322d123
--- /dev/null
+++ b/src/main/java/com/github/example/pt/repository/TopicTagRepository.java
@@ -0,0 +1,13 @@
+package com.github.example.pt.repository;
+
+import com.github.example.pt.entity.TopicTag;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface TopicTagRepository extends JpaRepository<TopicTag, Long> {
+
+    // 自定义删除方法,根据 topicId 和 tagId 删除对应的记录
+    void deleteByTopicIdAndTagId(Long topicId, Long tagId);
+
+}
diff --git a/src/main/java/com/github/example/pt/repository/TopicViewRepository.java b/src/main/java/com/github/example/pt/repository/TopicViewRepository.java
new file mode 100644
index 0000000..a88a0ac
--- /dev/null
+++ b/src/main/java/com/github/example/pt/repository/TopicViewRepository.java
@@ -0,0 +1,11 @@
+package com.github.example.pt.repository;
+
+import com.github.example.pt.entity.TopicView;
+import com.github.example.pt.entity.TopicViewId;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface TopicViewRepository extends JpaRepository<TopicView, TopicViewId> {
+    Optional<TopicView> findByTopic_IdAndUser_Id(Long topicId, Long userId);
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/repository/UserForumHistoryRepository.java b/src/main/java/com/github/example/pt/repository/UserForumHistoryRepository.java
new file mode 100644
index 0000000..ad20aef
--- /dev/null
+++ b/src/main/java/com/github/example/pt/repository/UserForumHistoryRepository.java
@@ -0,0 +1,14 @@
+package com.github.example.pt.repository;
+
+import com.github.example.pt.entity.ForumActionType;
+import com.github.example.pt.entity.UserForumHistory;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+public interface UserForumHistoryRepository extends JpaRepository<UserForumHistory, Long> {
+
+    List<UserForumHistory> findByUser_IdOrderByCreatedAtDesc(Long userId);
+
+    List<UserForumHistory> findByUser_IdAndTypeOrderByCreatedAtDesc(Long userId, ForumActionType type);
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/service/ForumService.java b/src/main/java/com/github/example/pt/service/ForumService.java
new file mode 100644
index 0000000..f86b607
--- /dev/null
+++ b/src/main/java/com/github/example/pt/service/ForumService.java
@@ -0,0 +1,52 @@
+package com.github.example.pt.service;
+
+import com.github.example.pt.entity.Forum;
+import com.github.example.pt.repository.ForumRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.crossstore.ChangeSetPersister;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public class ForumService {
+
+    @Autowired
+    private ForumRepository forumRepository;
+
+    public List<Forum> getAllForums() {
+        return forumRepository.findAll();
+    }
+
+    public Optional<Forum> getForumById(Long id) {
+        return forumRepository.findById(id);
+    }
+
+    public Forum createForum(Forum forum) {
+        return forumRepository.save(forum);
+    }
+
+    public Forum updateForum(Long id, Forum updatedForum) {
+        return forumRepository.findById(id).map(forum -> {
+            forum.setName(updatedForum.getName());
+            forum.setSlug(updatedForum.getSlug());
+            forum.setDescription(updatedForum.getDescription());
+            forum.setSortOrder(updatedForum.getSortOrder());
+            forum.setIsLocked(updatedForum.getIsLocked());
+            forum.setParent(updatedForum.getParent());
+            return forumRepository.save(forum);
+        }).orElseThrow(() -> new RuntimeException("Forum not found"));
+    }
+
+    public void deleteForum(Long id) {
+        forumRepository.deleteById(id);
+    }
+
+    public void setForumLockStatus(Long forumId, boolean locked) {
+        Forum forum = forumRepository.findById(forumId)
+                .orElseThrow(() -> new RuntimeException("Forum not found"));
+        forum.setIsLocked(locked);
+        forumRepository.save(forum);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/service/ForumTagService.java b/src/main/java/com/github/example/pt/service/ForumTagService.java
new file mode 100644
index 0000000..5a200f4
--- /dev/null
+++ b/src/main/java/com/github/example/pt/service/ForumTagService.java
@@ -0,0 +1,41 @@
+package com.github.example.pt.service;
+
+import com.github.example.pt.entity.ForumTag;
+import com.github.example.pt.entity.Topic;
+import com.github.example.pt.repository.ForumTagRepository;
+import com.github.example.pt.repository.TopicRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public class ForumTagService {
+
+    @Autowired
+    private ForumTagRepository tagRepo;
+
+    @Autowired
+    private TopicRepository toRepo;
+
+    public List<ForumTag> getAllTags() {
+        return tagRepo.findAll();
+    }
+
+    public ForumTag createTag(String name, String color) {
+        return tagRepo.save(ForumTag.builder().name(name).color(color).build());
+    }
+
+    public void deleteTag(Long tagId) {
+        tagRepo.deleteById(tagId);
+    }
+
+    public Optional<ForumTag> findOrCreate(String name, String color) {
+        return tagRepo.findByName(name)
+                .map(Optional::of)
+                .orElseGet(() -> Optional.of(tagRepo.save(ForumTag.builder().name(name).color(color).build())));
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/service/PostLikeService.java b/src/main/java/com/github/example/pt/service/PostLikeService.java
new file mode 100644
index 0000000..1f83429
--- /dev/null
+++ b/src/main/java/com/github/example/pt/service/PostLikeService.java
@@ -0,0 +1,48 @@
+package com.github.example.pt.service;
+
+import com.github.example.pt.entity.Post;
+import com.github.example.pt.entity.PostLike;
+import com.github.example.pt.entity.User;
+import com.github.example.pt.repository.PostLikeRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public class PostLikeService {
+
+    @Autowired
+    private PostLikeRepository postLikeRepository;
+
+    public boolean hasUserLikedPost(User user, Post post) {
+        return postLikeRepository.findByUserAndPost(user, post).isPresent();
+    }
+
+    public PostLike likePost(User user, Post post) {
+        return postLikeRepository.save(PostLike.builder()
+                .user(user)
+                .post(post)
+                .createdAt(LocalDateTime.now())
+                .build());
+    }
+
+    public void unlikePost(User user, Post post) {
+        postLikeRepository.findByUserAndPost(user, post).ifPresent(postLikeRepository::delete);
+    }
+
+    public long countLikes(Post post) {
+        return postLikeRepository.countByPost(post);
+    }
+
+    public List<PostLike> getLikesByPost(Post post) {
+        return postLikeRepository.findByPost(post);
+    }
+
+    public boolean hasUserLikedPost(Long postId, Long userId) {
+        return postLikeRepository.existsByPostIdAndUserId(postId, userId);
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/service/PostService.java b/src/main/java/com/github/example/pt/service/PostService.java
new file mode 100644
index 0000000..692e8de
--- /dev/null
+++ b/src/main/java/com/github/example/pt/service/PostService.java
@@ -0,0 +1,75 @@
+package com.github.example.pt.service;
+
+import com.github.example.pt.entity.Post;
+import com.github.example.pt.entity.Topic;
+import com.github.example.pt.repository.PostRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.*;
+
+@Service
+public class PostService {
+
+    @Autowired
+    private PostRepository postRepository;
+
+    public List<Post> getPostsByTopic(Topic topic) {
+        return postRepository.findByTopicAndIsDeletedFalse(topic);
+    }
+
+    public List<Post> getPostsByUser(Long userId) {
+        return postRepository.findByUserIdAndIsDeletedFalse(userId);
+    }
+
+    public Optional<Post> getPostById(Long id) {
+        return postRepository.findById(id);
+    }
+
+    public Post createPost(Post post) {
+        post.setCreatedAt(LocalDateTime.now());
+        post.setUpdatedAt(LocalDateTime.now());
+        post.setIsDeleted(false);
+        return postRepository.save(post);
+    }
+
+    public Post updatePost(Long id, Post updatedPost) {
+        return postRepository.findById(id).map(post -> {
+            post.setContent(updatedPost.getContent());
+            post.setUpdatedAt(LocalDateTime.now());
+            return postRepository.save(post);
+        }).orElseThrow(() -> new RuntimeException("Post not found"));
+    }
+
+    public void softDeletePost(Long id) {
+        postRepository.findById(id).ifPresent(post -> {
+            post.setIsDeleted(true);
+            postRepository.save(post);
+        });
+    }
+
+    public List<Post> getPostsByTopic(Long topicId) {
+        return postRepository.findByTopicIdOrderByCreatedAtAsc(topicId);
+    }
+
+    public List<Post> buildPostTree(List<Post> flatList) {
+        Map<Long, Post> map = new HashMap<>();
+        List<Post> roots = new ArrayList<>();
+
+        for (Post post : flatList) {
+            map.put(post.getId(), post);
+            post.setReplies(new ArrayList<>()); // 保证非 null
+        }
+
+        for (Post post : flatList) {
+            if (post.getParent() != null && map.containsKey(post.getParent().getId())) {
+                map.get(post.getParent().getId()).getReplies().add(post);
+            } else {
+                roots.add(post); // 顶层评论
+            }
+        }
+        return roots;
+    }
+
+}
diff --git a/src/main/java/com/github/example/pt/service/TopicService.java b/src/main/java/com/github/example/pt/service/TopicService.java
new file mode 100644
index 0000000..cf487d5
--- /dev/null
+++ b/src/main/java/com/github/example/pt/service/TopicService.java
@@ -0,0 +1,62 @@
+package com.github.example.pt.service;
+
+import com.github.example.pt.entity.Forum;
+import com.github.example.pt.entity.Topic;
+import com.github.example.pt.repository.TopicRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public class TopicService {
+
+    @Autowired
+    private TopicRepository topicRepository;
+
+    public List<Topic> getAllTopics() {
+        return topicRepository.findAll();
+    }
+
+    public List<Topic> getTopicsByForum(Forum forum) {
+        return topicRepository.findByForum(forum);
+    }
+
+    public Optional<Topic> getTopicById(Long id) {
+        return topicRepository.findById(id);
+    }
+
+    public Topic createTopic(Topic topic) {
+        topic.setCreatedAt(LocalDateTime.now());
+        topic.setUpdatedAt(LocalDateTime.now());
+        return topicRepository.save(topic);
+    }
+
+    public Topic updateTopic(Long id, Topic updatedTopic) {
+        return topicRepository.findById(id).map(topic -> {
+            topic.setTitle(updatedTopic.getTitle());
+            topic.setForum(updatedTopic.getForum());
+            topic.setUser(updatedTopic.getUser());
+            topic.setIsPinned(updatedTopic.getIsPinned());
+            topic.setIsLocked(updatedTopic.getIsLocked());
+            topic.setUpdatedAt(LocalDateTime.now());
+            return topicRepository.save(topic);
+        }).orElseThrow(() -> new RuntimeException("Topic not found"));
+    }
+
+    public void deleteTopic(Long id) {
+        topicRepository.deleteById(id);
+    }
+
+    public Page<Topic> searchTopics(String keyword, Long forumId, int page, int size) {
+        Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
+        return topicRepository.searchByKeyword(keyword, forumId, pageable);
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/service/TopicSubscriptionService.java b/src/main/java/com/github/example/pt/service/TopicSubscriptionService.java
new file mode 100644
index 0000000..71a2806
--- /dev/null
+++ b/src/main/java/com/github/example/pt/service/TopicSubscriptionService.java
@@ -0,0 +1,37 @@
+package com.github.example.pt.service;
+
+import com.github.example.pt.entity.Topic;
+import com.github.example.pt.entity.TopicSubscription;
+import com.github.example.pt.entity.User;
+import com.github.example.pt.repository.TopicSubscriptionRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Service
+public class TopicSubscriptionService {
+
+    @Autowired
+    private TopicSubscriptionRepository repository;
+
+    public void subscribe(User user, Topic topic) {
+        if (!repository.existsByUser_IdAndTopic_Id(user.getId(), topic.getId())) {
+            TopicSubscription subscription = new TopicSubscription(user, topic, LocalDateTime.now());
+            repository.save(subscription);
+        }
+    }
+
+    public void unsubscribe(Long userId, Long topicId) {
+        repository.deleteByUser_IdAndTopic_Id(userId, topicId);
+    }
+
+    public List<TopicSubscription> getSubscriptions(Long userId) {
+        return repository.findByUser_Id(userId);
+    }
+
+    public boolean isSubscribed(Long userId, Long topicId) {
+        return repository.existsByUser_IdAndTopic_Id(userId, topicId);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/example/pt/service/TopicTagService.java b/src/main/java/com/github/example/pt/service/TopicTagService.java
new file mode 100644
index 0000000..0faa430
--- /dev/null
+++ b/src/main/java/com/github/example/pt/service/TopicTagService.java
@@ -0,0 +1,43 @@
+package com.github.example.pt.service;
+import com.github.example.pt.entity.ForumTag;
+import com.github.example.pt.entity.Topic;
+import com.github.example.pt.entity.TopicSubscription;
+import com.github.example.pt.entity.User;
+import com.github.example.pt.repository.ForumTagRepository;
+import com.github.example.pt.repository.TopicRepository;
+import com.github.example.pt.repository.TopicSubscriptionRepository;
+import com.github.example.pt.repository.TopicTagRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+@Service
+public class TopicTagService {
+
+    private final TopicTagRepository topicTagRepository;
+    private final TopicRepository topicRepository;
+    private final ForumTagRepository forumTagRepository;
+
+    @Autowired
+    public TopicTagService(TopicTagRepository topicTagRepository,
+                           TopicRepository topicRepository,
+                           ForumTagRepository forumTagRepository) {
+        this.topicTagRepository = topicTagRepository;
+        this.topicRepository = topicRepository;
+        this.forumTagRepository = forumTagRepository;
+    }
+
+    public void removeTagFromTopic(Long topicId, Long tagId) {
+        // 确保topic存在
+        Topic topic = topicRepository.findById(topicId)
+                .orElseThrow(() -> new RuntimeException("Topic not found"));
+
+        // 确保tag存在
+        ForumTag tag = forumTagRepository.findById(tagId)
+                .orElseThrow(() -> new RuntimeException("Tag not found"));
+
+        // 从 topic_tags 表删除对应关系
+        topicTagRepository.deleteByTopicIdAndTagId(topicId, tagId);
+    }
+}
diff --git a/src/main/java/com/github/example/pt/service/TopicViewService.java b/src/main/java/com/github/example/pt/service/TopicViewService.java
new file mode 100644
index 0000000..a86e329
--- /dev/null
+++ b/src/main/java/com/github/example/pt/service/TopicViewService.java
@@ -0,0 +1,33 @@
+package com.github.example.pt.service;
+import com.github.example.pt.entity.Topic;
+import com.github.example.pt.entity.TopicView;
+import com.github.example.pt.entity.User;
+import com.github.example.pt.repository.TopicViewRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.Optional;
+
+@Service
+public class TopicViewService {
+
+    @Autowired
+    private TopicViewRepository topicViewRepository;
+
+    public void recordView(Topic topic, User user) {
+        Optional<TopicView> existing = topicViewRepository.findByTopic_IdAndUser_Id(topic.getId(), user.getId());
+        if (existing.isPresent()) {
+            TopicView view = existing.get();
+            view.setLastViewedAt(LocalDateTime.now());
+            topicViewRepository.save(view);
+        } else {
+            TopicView newView = new TopicView(topic, user, LocalDateTime.now());
+            topicViewRepository.save(newView);
+        }
+    }
+
+    public Optional<TopicView> getViewRecord(Long topicId, Long userId) {
+        return topicViewRepository.findByTopic_IdAndUser_Id(topicId, userId);
+    }
+}
diff --git a/src/main/java/com/github/example/pt/service/UserForumHistoryService.java b/src/main/java/com/github/example/pt/service/UserForumHistoryService.java
new file mode 100644
index 0000000..019b001
--- /dev/null
+++ b/src/main/java/com/github/example/pt/service/UserForumHistoryService.java
@@ -0,0 +1,36 @@
+package com.github.example.pt.service;
+
+import com.github.example.pt.entity.ForumActionType;
+import com.github.example.pt.entity.User;
+import com.github.example.pt.entity.UserForumHistory;
+import com.github.example.pt.repository.UserForumHistoryRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Service
+public class UserForumHistoryService {
+
+    @Autowired
+    private UserForumHistoryRepository repository;
+
+    public void recordAction(User user, ForumActionType type, Long targetId) {
+        UserForumHistory history = UserForumHistory.builder()
+                .user(user)
+                .type(type)
+                .targetId(targetId)
+                .createdAt(LocalDateTime.now())
+                .build();
+        repository.save(history);
+    }
+
+    public List<UserForumHistory> getAllByUser(Long userId) {
+        return repository.findByUser_IdOrderByCreatedAtDesc(userId);
+    }
+
+    public List<UserForumHistory> getByType(Long userId, ForumActionType type) {
+        return repository.findByUser_IdAndTypeOrderByCreatedAtDesc(userId, type);
+    }
+}
\ No newline at end of file
diff --git a/src/main/resources/ComCent_Data.sql b/src/main/resources/ComCent_Data.sql
new file mode 100644
index 0000000..555227e
--- /dev/null
+++ b/src/main/resources/ComCent_Data.sql
@@ -0,0 +1,70 @@
+-- 插入一些论坛板块数据
+INSERT INTO `forums` (`slug`, `name`, `description`, `parent_id`, `sort_order`, `is_locked`) VALUES
+                                                                                                 ('general', '综合讨论区', '这里是综合讨论区,讨论各种话题', NULL, 1, FALSE),
+                                                                                                 ('technology', '技术讨论区', '专注技术的讨论区,分享技术资源', NULL, 2, FALSE),
+                                                                                                 ('gaming', '游戏讨论区', '这里是游戏爱好者的天地', NULL, 3, FALSE),
+                                                                                                 ('movies', '电影讨论区', '讨论最新电影与经典影片', NULL, 4, FALSE);
+
+
+-- 插入一些标签数据
+INSERT INTO `forum_tags` (`name`, `color`) VALUES
+                                               ('Java', '#f44336'),
+                                               ('Spring Boot', '#4caf50'),
+                                               ('MySQL', '#ff9800'),
+                                               ('React', '#2196f3'),
+                                               ('Gaming', '#9c27b0'),
+                                               ('Movies', '#03a9f4');
+
+-- 插入一些用户数据
+INSERT INTO `users` (`email`, `password`, `username`, `group_id`, `passkey`, `create_at`, `avatar`, `custom_title`, `signature`, `language`, `download_bandwidth`, `upload_bandwidth`, `downloaded`, `uploaded`, `real_downloaded`, `real_uploaded`, `isp`, `karma`, `invite_slot`, `seeding_time`, `personal_access_token`, `privacy_level`) VALUES
+                                                                                                                                                                                                                                                                                                                                                  ('john.doe@example.com', '$2a$12$KqN2gNzfHdtXTXV1JErmuI6N5b8R75Afj6mEXZkLGRn8epbOT6bmq', 'john_doe', 1, 'c9f5e2b3-13a1-456b-92ec-32145b4a1a7e', '2025-06-01 10:00:00', 'https://www.example.com/avatar1.jpg', '论坛用户', '这个人很懒,什么都没有写', 'en-US', '100mbps', '50mbps', 12345, 67890, 12345, 67890, 'Unknown ISP', 100.00, 5, 0, 'abcdef123456', '0'),
+                                                                                                                                                                                                                                                                                                                                                  ('jane.doe@example.com', '$2a$12$EYh8kLRWxOEmjxjXb.8j2uev9wWjl1zDbb2u5ckT6eBqqZnFYvNY6', 'jane_doe', 2, 'bd9e7a7a-519e-4d3e-bf0b-d9145a61cfb7', '2025-06-01 11:00:00', 'https://www.example.com/avatar2.jpg', '高级用户', '喜欢探索各种技术,沉迷于编程', 'zh-CN', '1gbps', '200mbps', 10000, 50000, 10000, 50000, 'China Telecom', 150.00, 10, 100, 'abcdef654321', '1');
+
+-- 插入一些帖子主题数据
+INSERT INTO `topics` (`forum_id`, `user_id`, `title`, `is_pinned`, `is_locked`, `created_at`, `updated_at`) VALUES
+                                                                                                                (1, 1, '欢迎来到综合讨论区', FALSE, FALSE, '2025-06-01 12:00:00', '2025-06-01 12:30:00'),
+                                                                                                                (2, 2, 'Spring Boot 介绍与实战', TRUE, FALSE, '2025-06-02 10:00:00', '2025-06-02 10:10:00'),
+                                                                                                                (3, 1, '有哪些好玩的游戏推荐?', FALSE, FALSE, '2025-06-02 11:00:00', '2025-06-02 11:05:00'),
+                                                                                                                (4, 2, '最近看过的电影推荐', FALSE, TRUE, '2025-06-02 12:00:00', '2025-06-02 12:15:00');
+
+-- 插入一些帖子内容数据
+INSERT INTO `posts` (`topic_id`, `user_id`, `content`, `created_at`, `updated_at`) VALUES
+                                                                                       (1, 1, '欢迎大家来到综合讨论区,大家可以自由讨论各种话题。', '2025-06-01 12:10:00', '2025-06-01 12:10:00'),
+                                                                                       (2, 2, 'Spring Boot 是一个非常流行的 Java 开发框架,它大大简化了开发流程。', '2025-06-02 10:05:00', '2025-06-02 10:05:00'),
+                                                                                       (3, 1, '最近玩了很多游戏,强烈推荐《赛博朋克 2077》。', '2025-06-02 11:02:00', '2025-06-02 11:02:00'),
+                                                                                       (4, 2, '《复仇者联盟4》真是一个巨大的电影爆发,强烈推荐!', '2025-06-02 12:05:00', '2025-06-02 12:05:00');
+
+-- 插入一些帖子点赞数据
+INSERT INTO `post_likes` (`user_id`, `post_id`, `created_at`) VALUES
+                                                                  (1, 1, '2025-06-01 12:20:00'),
+                                                                  (2, 2, '2025-06-02 10:10:00'),
+                                                                  (1, 3, '2025-06-02 11:10:00'),
+                                                                  (2, 4, '2025-06-02 12:10:00');
+
+-- 插入一些标签数据
+INSERT INTO `topic_tags` (`topic_id`, `tag_id`) VALUES
+                                                    (1, 1),
+                                                    (2, 2),
+                                                    (3, 3),
+                                                    (4, 4);
+
+-- 插入一些浏览记录数据
+INSERT INTO `topic_views` (`topic_id`, `user_id`, `last_viewed_at`) VALUES
+                                                                        (1, 1, '2025-06-01 12:10:00'),
+                                                                        (2, 2, '2025-06-02 10:05:00'),
+                                                                        (3, 1, '2025-06-02 11:05:00'),
+                                                                        (4, 2, '2025-06-02 12:05:00');
+
+-- 插入一些用户关注的帖子数据
+INSERT INTO `topic_subscriptions` (`user_id`, `topic_id`, `subscribed_at`) VALUES
+                                                                               (1, 1, '2025-06-01 12:15:00'),
+                                                                               (2, 2, '2025-06-02 10:15:00'),
+                                                                               (1, 3, '2025-06-02 11:15:00'),
+                                                                               (2, 4, '2025-06-02 12:15:00');
+
+-- 插入一些用户论坛活动记录数据
+INSERT INTO `user_forum_history` (`user_id`, `type`, `target_id`, `created_at`) VALUES
+                                                                                    (1, 'topic', 1, '2025-06-01 12:00:00'),
+                                                                                    (2, 'topic', 2, '2025-06-02 10:00:00'),
+                                                                                    (1, 'reply', 1, '2025-06-01 12:10:00'),
+                                                                                    (2, 'reply', 2, '2025-06-02 10:05:00');
diff --git a/src/main/resources/CommunicationCenter.sql b/src/main/resources/CommunicationCenter.sql
new file mode 100644
index 0000000..040894a
--- /dev/null
+++ b/src/main/resources/CommunicationCenter.sql
@@ -0,0 +1,102 @@
+--forums 表 – 论坛板块
+CREATE TABLE `forums` (
+                          `id` BIGINT NOT NULL AUTO_INCREMENT,
+                          `slug` VARCHAR(255) NOT NULL UNIQUE,
+                          `name` VARCHAR(255) NOT NULL,
+                          `description` TEXT,
+                          `parent_id` BIGINT DEFAULT NULL,
+                          `sort_order` INT DEFAULT 0,
+                          `is_locked` BOOLEAN DEFAULT FALSE,
+                          PRIMARY KEY (`id`),
+                          FOREIGN KEY (`parent_id`) REFERENCES `forums`(`id`) ON DELETE SET NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+--topics 表 – 帖子主题(一个帖子)
+CREATE TABLE `topics` (
+                          `id` BIGINT NOT NULL AUTO_INCREMENT,
+                          `forum_id` BIGINT NOT NULL,
+                          `user_id` BIGINT NOT NULL,
+                          `title` VARCHAR(255) NOT NULL,
+                          `is_pinned` BOOLEAN DEFAULT FALSE,
+                          `is_locked` BOOLEAN DEFAULT FALSE,
+                          `created_at` DATETIME NOT NULL,
+                          `updated_at` DATETIME NOT NULL,
+                          PRIMARY KEY (`id`),
+                          FOREIGN KEY (`forum_id`) REFERENCES `forums`(`id`) ON DELETE CASCADE,
+                          FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
+);
+
+--posts 表 – 帖子内容(主题 + 回复)
+CREATE TABLE `posts` (
+                         `id` BIGINT NOT NULL AUTO_INCREMENT,
+                         `topic_id` BIGINT NOT NULL,
+                         `user_id` BIGINT NOT NULL,
+                         `content` TEXT NOT NULL,
+                         `created_at` DATETIME NOT NULL,
+                         `updated_at` DATETIME NOT NULL,
+                         `is_deleted` BOOLEAN DEFAULT FALSE,
+                         PRIMARY KEY (`id`),
+                         FOREIGN KEY (`topic_id`) REFERENCES `topics`(`id`) ON DELETE CASCADE,
+                         FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
+);
+
+--post_likes 表 – 帖子点赞记录
+CREATE TABLE `post_likes` (
+                              `user_id` BIGINT NOT NULL,
+                              `post_id` BIGINT NOT NULL,
+                              `created_at` DATETIME NOT NULL,
+                              PRIMARY KEY (`user_id`, `post_id`),
+                              FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
+                              FOREIGN KEY (`post_id`) REFERENCES `posts`(`id`) ON DELETE CASCADE
+);
+
+--forum_tags 表 – 标签系统
+CREATE TABLE `forum_tags` (
+                              `id` BIGINT NOT NULL AUTO_INCREMENT,
+                              `name` VARCHAR(50) NOT NULL UNIQUE,
+                              `color` VARCHAR(10) DEFAULT '#ccc',
+                              PRIMARY KEY (`id`)
+);
+
+--中间表:topic_tags
+CREATE TABLE `topic_tags` (
+                              `topic_id` BIGINT NOT NULL,
+                              `tag_id` BIGINT NOT NULL,
+                              PRIMARY KEY (`topic_id`, `tag_id`),
+                              FOREIGN KEY (`topic_id`) REFERENCES `topics`(`id`) ON DELETE CASCADE,
+                              FOREIGN KEY (`tag_id`) REFERENCES `forum_tags`(`id`) ON DELETE CASCADE
+);
+
+--topic_views 表 – 浏览记录
+CREATE TABLE `topic_views` (
+                               `topic_id` BIGINT NOT NULL,
+                               `user_id` BIGINT NOT NULL,
+                               `last_viewed_at` DATETIME NOT NULL,
+                               PRIMARY KEY (`topic_id`, `user_id`),
+                               FOREIGN KEY (`topic_id`) REFERENCES `topics`(`id`) ON DELETE CASCADE,
+                               FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
+);
+
+--topic_subscriptions 表 – 用户关注(收藏)的帖子
+CREATE TABLE `topic_subscriptions` (
+                                       `user_id` BIGINT NOT NULL,
+                                       `topic_id` BIGINT NOT NULL,
+                                       `subscribed_at` DATETIME NOT NULL,
+                                       PRIMARY KEY (`user_id`, `topic_id`),
+                                       FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
+                                       FOREIGN KEY (`topic_id`) REFERENCES `topics`(`id`) ON DELETE CASCADE
+);
+
+--用户论坛活动记录表
+CREATE TABLE `user_forum_history` (
+                                      `id` BIGINT NOT NULL AUTO_INCREMENT,
+                                      `user_id` BIGINT NOT NULL,
+                                      `type` ENUM('topic', 'reply') NOT NULL COMMENT '操作类型:发主题或回复',
+                                      `target_id` BIGINT NOT NULL COMMENT 'topic_id 或 post_id',
+                                      `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+                                      PRIMARY KEY (`id`),
+                                      INDEX `idx_user_id` (`user_id`),
+                                      INDEX `idx_type_target` (`type`, `target_id`),
+                                      CONSTRAINT `fk_ufh_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+--
\ No newline at end of file
diff --git a/src/main/resources/posts_add.sql b/src/main/resources/posts_add.sql
new file mode 100644
index 0000000..85a89a7
--- /dev/null
+++ b/src/main/resources/posts_add.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `topics`
+    MODIFY COLUMN `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE `posts`
+    MODIFY COLUMN `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
diff --git a/target/classes/ComCent_Data.sql b/target/classes/ComCent_Data.sql
new file mode 100644
index 0000000..555227e
--- /dev/null
+++ b/target/classes/ComCent_Data.sql
@@ -0,0 +1,70 @@
+-- 插入一些论坛板块数据
+INSERT INTO `forums` (`slug`, `name`, `description`, `parent_id`, `sort_order`, `is_locked`) VALUES
+                                                                                                 ('general', '综合讨论区', '这里是综合讨论区,讨论各种话题', NULL, 1, FALSE),
+                                                                                                 ('technology', '技术讨论区', '专注技术的讨论区,分享技术资源', NULL, 2, FALSE),
+                                                                                                 ('gaming', '游戏讨论区', '这里是游戏爱好者的天地', NULL, 3, FALSE),
+                                                                                                 ('movies', '电影讨论区', '讨论最新电影与经典影片', NULL, 4, FALSE);
+
+
+-- 插入一些标签数据
+INSERT INTO `forum_tags` (`name`, `color`) VALUES
+                                               ('Java', '#f44336'),
+                                               ('Spring Boot', '#4caf50'),
+                                               ('MySQL', '#ff9800'),
+                                               ('React', '#2196f3'),
+                                               ('Gaming', '#9c27b0'),
+                                               ('Movies', '#03a9f4');
+
+-- 插入一些用户数据
+INSERT INTO `users` (`email`, `password`, `username`, `group_id`, `passkey`, `create_at`, `avatar`, `custom_title`, `signature`, `language`, `download_bandwidth`, `upload_bandwidth`, `downloaded`, `uploaded`, `real_downloaded`, `real_uploaded`, `isp`, `karma`, `invite_slot`, `seeding_time`, `personal_access_token`, `privacy_level`) VALUES
+                                                                                                                                                                                                                                                                                                                                                  ('john.doe@example.com', '$2a$12$KqN2gNzfHdtXTXV1JErmuI6N5b8R75Afj6mEXZkLGRn8epbOT6bmq', 'john_doe', 1, 'c9f5e2b3-13a1-456b-92ec-32145b4a1a7e', '2025-06-01 10:00:00', 'https://www.example.com/avatar1.jpg', '论坛用户', '这个人很懒,什么都没有写', 'en-US', '100mbps', '50mbps', 12345, 67890, 12345, 67890, 'Unknown ISP', 100.00, 5, 0, 'abcdef123456', '0'),
+                                                                                                                                                                                                                                                                                                                                                  ('jane.doe@example.com', '$2a$12$EYh8kLRWxOEmjxjXb.8j2uev9wWjl1zDbb2u5ckT6eBqqZnFYvNY6', 'jane_doe', 2, 'bd9e7a7a-519e-4d3e-bf0b-d9145a61cfb7', '2025-06-01 11:00:00', 'https://www.example.com/avatar2.jpg', '高级用户', '喜欢探索各种技术,沉迷于编程', 'zh-CN', '1gbps', '200mbps', 10000, 50000, 10000, 50000, 'China Telecom', 150.00, 10, 100, 'abcdef654321', '1');
+
+-- 插入一些帖子主题数据
+INSERT INTO `topics` (`forum_id`, `user_id`, `title`, `is_pinned`, `is_locked`, `created_at`, `updated_at`) VALUES
+                                                                                                                (1, 1, '欢迎来到综合讨论区', FALSE, FALSE, '2025-06-01 12:00:00', '2025-06-01 12:30:00'),
+                                                                                                                (2, 2, 'Spring Boot 介绍与实战', TRUE, FALSE, '2025-06-02 10:00:00', '2025-06-02 10:10:00'),
+                                                                                                                (3, 1, '有哪些好玩的游戏推荐?', FALSE, FALSE, '2025-06-02 11:00:00', '2025-06-02 11:05:00'),
+                                                                                                                (4, 2, '最近看过的电影推荐', FALSE, TRUE, '2025-06-02 12:00:00', '2025-06-02 12:15:00');
+
+-- 插入一些帖子内容数据
+INSERT INTO `posts` (`topic_id`, `user_id`, `content`, `created_at`, `updated_at`) VALUES
+                                                                                       (1, 1, '欢迎大家来到综合讨论区,大家可以自由讨论各种话题。', '2025-06-01 12:10:00', '2025-06-01 12:10:00'),
+                                                                                       (2, 2, 'Spring Boot 是一个非常流行的 Java 开发框架,它大大简化了开发流程。', '2025-06-02 10:05:00', '2025-06-02 10:05:00'),
+                                                                                       (3, 1, '最近玩了很多游戏,强烈推荐《赛博朋克 2077》。', '2025-06-02 11:02:00', '2025-06-02 11:02:00'),
+                                                                                       (4, 2, '《复仇者联盟4》真是一个巨大的电影爆发,强烈推荐!', '2025-06-02 12:05:00', '2025-06-02 12:05:00');
+
+-- 插入一些帖子点赞数据
+INSERT INTO `post_likes` (`user_id`, `post_id`, `created_at`) VALUES
+                                                                  (1, 1, '2025-06-01 12:20:00'),
+                                                                  (2, 2, '2025-06-02 10:10:00'),
+                                                                  (1, 3, '2025-06-02 11:10:00'),
+                                                                  (2, 4, '2025-06-02 12:10:00');
+
+-- 插入一些标签数据
+INSERT INTO `topic_tags` (`topic_id`, `tag_id`) VALUES
+                                                    (1, 1),
+                                                    (2, 2),
+                                                    (3, 3),
+                                                    (4, 4);
+
+-- 插入一些浏览记录数据
+INSERT INTO `topic_views` (`topic_id`, `user_id`, `last_viewed_at`) VALUES
+                                                                        (1, 1, '2025-06-01 12:10:00'),
+                                                                        (2, 2, '2025-06-02 10:05:00'),
+                                                                        (3, 1, '2025-06-02 11:05:00'),
+                                                                        (4, 2, '2025-06-02 12:05:00');
+
+-- 插入一些用户关注的帖子数据
+INSERT INTO `topic_subscriptions` (`user_id`, `topic_id`, `subscribed_at`) VALUES
+                                                                               (1, 1, '2025-06-01 12:15:00'),
+                                                                               (2, 2, '2025-06-02 10:15:00'),
+                                                                               (1, 3, '2025-06-02 11:15:00'),
+                                                                               (2, 4, '2025-06-02 12:15:00');
+
+-- 插入一些用户论坛活动记录数据
+INSERT INTO `user_forum_history` (`user_id`, `type`, `target_id`, `created_at`) VALUES
+                                                                                    (1, 'topic', 1, '2025-06-01 12:00:00'),
+                                                                                    (2, 'topic', 2, '2025-06-02 10:00:00'),
+                                                                                    (1, 'reply', 1, '2025-06-01 12:10:00'),
+                                                                                    (2, 'reply', 2, '2025-06-02 10:05:00');
diff --git a/target/classes/CommunicationCenter.sql b/target/classes/CommunicationCenter.sql
new file mode 100644
index 0000000..040894a
--- /dev/null
+++ b/target/classes/CommunicationCenter.sql
@@ -0,0 +1,102 @@
+--forums 表 – 论坛板块
+CREATE TABLE `forums` (
+                          `id` BIGINT NOT NULL AUTO_INCREMENT,
+                          `slug` VARCHAR(255) NOT NULL UNIQUE,
+                          `name` VARCHAR(255) NOT NULL,
+                          `description` TEXT,
+                          `parent_id` BIGINT DEFAULT NULL,
+                          `sort_order` INT DEFAULT 0,
+                          `is_locked` BOOLEAN DEFAULT FALSE,
+                          PRIMARY KEY (`id`),
+                          FOREIGN KEY (`parent_id`) REFERENCES `forums`(`id`) ON DELETE SET NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+--topics 表 – 帖子主题(一个帖子)
+CREATE TABLE `topics` (
+                          `id` BIGINT NOT NULL AUTO_INCREMENT,
+                          `forum_id` BIGINT NOT NULL,
+                          `user_id` BIGINT NOT NULL,
+                          `title` VARCHAR(255) NOT NULL,
+                          `is_pinned` BOOLEAN DEFAULT FALSE,
+                          `is_locked` BOOLEAN DEFAULT FALSE,
+                          `created_at` DATETIME NOT NULL,
+                          `updated_at` DATETIME NOT NULL,
+                          PRIMARY KEY (`id`),
+                          FOREIGN KEY (`forum_id`) REFERENCES `forums`(`id`) ON DELETE CASCADE,
+                          FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
+);
+
+--posts 表 – 帖子内容(主题 + 回复)
+CREATE TABLE `posts` (
+                         `id` BIGINT NOT NULL AUTO_INCREMENT,
+                         `topic_id` BIGINT NOT NULL,
+                         `user_id` BIGINT NOT NULL,
+                         `content` TEXT NOT NULL,
+                         `created_at` DATETIME NOT NULL,
+                         `updated_at` DATETIME NOT NULL,
+                         `is_deleted` BOOLEAN DEFAULT FALSE,
+                         PRIMARY KEY (`id`),
+                         FOREIGN KEY (`topic_id`) REFERENCES `topics`(`id`) ON DELETE CASCADE,
+                         FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
+);
+
+--post_likes 表 – 帖子点赞记录
+CREATE TABLE `post_likes` (
+                              `user_id` BIGINT NOT NULL,
+                              `post_id` BIGINT NOT NULL,
+                              `created_at` DATETIME NOT NULL,
+                              PRIMARY KEY (`user_id`, `post_id`),
+                              FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
+                              FOREIGN KEY (`post_id`) REFERENCES `posts`(`id`) ON DELETE CASCADE
+);
+
+--forum_tags 表 – 标签系统
+CREATE TABLE `forum_tags` (
+                              `id` BIGINT NOT NULL AUTO_INCREMENT,
+                              `name` VARCHAR(50) NOT NULL UNIQUE,
+                              `color` VARCHAR(10) DEFAULT '#ccc',
+                              PRIMARY KEY (`id`)
+);
+
+--中间表:topic_tags
+CREATE TABLE `topic_tags` (
+                              `topic_id` BIGINT NOT NULL,
+                              `tag_id` BIGINT NOT NULL,
+                              PRIMARY KEY (`topic_id`, `tag_id`),
+                              FOREIGN KEY (`topic_id`) REFERENCES `topics`(`id`) ON DELETE CASCADE,
+                              FOREIGN KEY (`tag_id`) REFERENCES `forum_tags`(`id`) ON DELETE CASCADE
+);
+
+--topic_views 表 – 浏览记录
+CREATE TABLE `topic_views` (
+                               `topic_id` BIGINT NOT NULL,
+                               `user_id` BIGINT NOT NULL,
+                               `last_viewed_at` DATETIME NOT NULL,
+                               PRIMARY KEY (`topic_id`, `user_id`),
+                               FOREIGN KEY (`topic_id`) REFERENCES `topics`(`id`) ON DELETE CASCADE,
+                               FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
+);
+
+--topic_subscriptions 表 – 用户关注(收藏)的帖子
+CREATE TABLE `topic_subscriptions` (
+                                       `user_id` BIGINT NOT NULL,
+                                       `topic_id` BIGINT NOT NULL,
+                                       `subscribed_at` DATETIME NOT NULL,
+                                       PRIMARY KEY (`user_id`, `topic_id`),
+                                       FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
+                                       FOREIGN KEY (`topic_id`) REFERENCES `topics`(`id`) ON DELETE CASCADE
+);
+
+--用户论坛活动记录表
+CREATE TABLE `user_forum_history` (
+                                      `id` BIGINT NOT NULL AUTO_INCREMENT,
+                                      `user_id` BIGINT NOT NULL,
+                                      `type` ENUM('topic', 'reply') NOT NULL COMMENT '操作类型:发主题或回复',
+                                      `target_id` BIGINT NOT NULL COMMENT 'topic_id 或 post_id',
+                                      `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+                                      PRIMARY KEY (`id`),
+                                      INDEX `idx_user_id` (`user_id`),
+                                      INDEX `idx_type_target` (`type`, `target_id`),
+                                      CONSTRAINT `fk_ufh_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+--
\ No newline at end of file
diff --git a/target/classes/com/github/example/pt/controller/ForumController.class b/target/classes/com/github/example/pt/controller/ForumController.class
new file mode 100644
index 0000000..ee618ba
--- /dev/null
+++ b/target/classes/com/github/example/pt/controller/ForumController.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/controller/ForumTagController.class b/target/classes/com/github/example/pt/controller/ForumTagController.class
new file mode 100644
index 0000000..5436ff4
--- /dev/null
+++ b/target/classes/com/github/example/pt/controller/ForumTagController.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/controller/PostController.class b/target/classes/com/github/example/pt/controller/PostController.class
new file mode 100644
index 0000000..35314d2
--- /dev/null
+++ b/target/classes/com/github/example/pt/controller/PostController.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/controller/PostLikeController.class b/target/classes/com/github/example/pt/controller/PostLikeController.class
new file mode 100644
index 0000000..20a5a89
--- /dev/null
+++ b/target/classes/com/github/example/pt/controller/PostLikeController.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/controller/TopicController.class b/target/classes/com/github/example/pt/controller/TopicController.class
new file mode 100644
index 0000000..6e5e49f
--- /dev/null
+++ b/target/classes/com/github/example/pt/controller/TopicController.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/controller/TopicSubscriptionController.class b/target/classes/com/github/example/pt/controller/TopicSubscriptionController.class
new file mode 100644
index 0000000..91fa5d9
--- /dev/null
+++ b/target/classes/com/github/example/pt/controller/TopicSubscriptionController.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/controller/TopicViewController.class b/target/classes/com/github/example/pt/controller/TopicViewController.class
new file mode 100644
index 0000000..5860406
--- /dev/null
+++ b/target/classes/com/github/example/pt/controller/TopicViewController.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/controller/UserForumHistoryController.class b/target/classes/com/github/example/pt/controller/UserForumHistoryController.class
new file mode 100644
index 0000000..52851b7
--- /dev/null
+++ b/target/classes/com/github/example/pt/controller/UserForumHistoryController.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/Forum$ForumBuilder.class b/target/classes/com/github/example/pt/entity/Forum$ForumBuilder.class
new file mode 100644
index 0000000..9c8ecc3
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/Forum$ForumBuilder.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/Forum.class b/target/classes/com/github/example/pt/entity/Forum.class
new file mode 100644
index 0000000..cc02e3b
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/Forum.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/ForumActionType.class b/target/classes/com/github/example/pt/entity/ForumActionType.class
new file mode 100644
index 0000000..a934d46
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/ForumActionType.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/ForumTag$ForumTagBuilder.class b/target/classes/com/github/example/pt/entity/ForumTag$ForumTagBuilder.class
new file mode 100644
index 0000000..38321ae
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/ForumTag$ForumTagBuilder.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/ForumTag.class b/target/classes/com/github/example/pt/entity/ForumTag.class
new file mode 100644
index 0000000..e092880
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/ForumTag.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/Post$PostBuilder.class b/target/classes/com/github/example/pt/entity/Post$PostBuilder.class
new file mode 100644
index 0000000..9eaec84
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/Post$PostBuilder.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/Post.class b/target/classes/com/github/example/pt/entity/Post.class
new file mode 100644
index 0000000..23eadd3
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/Post.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/PostLike$PostLikeBuilder.class b/target/classes/com/github/example/pt/entity/PostLike$PostLikeBuilder.class
new file mode 100644
index 0000000..fe98d63
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/PostLike$PostLikeBuilder.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/PostLike$PostLikeId.class b/target/classes/com/github/example/pt/entity/PostLike$PostLikeId.class
new file mode 100644
index 0000000..2dbc24a
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/PostLike$PostLikeId.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/PostLike.class b/target/classes/com/github/example/pt/entity/PostLike.class
new file mode 100644
index 0000000..9dada7e
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/PostLike.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/Topic$TopicBuilder.class b/target/classes/com/github/example/pt/entity/Topic$TopicBuilder.class
new file mode 100644
index 0000000..ea91c3b
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/Topic$TopicBuilder.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/Topic.class b/target/classes/com/github/example/pt/entity/Topic.class
new file mode 100644
index 0000000..35f3c95
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/Topic.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/TopicSubscription.class b/target/classes/com/github/example/pt/entity/TopicSubscription.class
new file mode 100644
index 0000000..2d709d1
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/TopicSubscription.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/TopicSubscriptionId.class b/target/classes/com/github/example/pt/entity/TopicSubscriptionId.class
new file mode 100644
index 0000000..f193a93
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/TopicSubscriptionId.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/TopicTag.class b/target/classes/com/github/example/pt/entity/TopicTag.class
new file mode 100644
index 0000000..5cd4342
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/TopicTag.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/TopicTagId.class b/target/classes/com/github/example/pt/entity/TopicTagId.class
new file mode 100644
index 0000000..f0231e4
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/TopicTagId.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/TopicView.class b/target/classes/com/github/example/pt/entity/TopicView.class
new file mode 100644
index 0000000..aca8526
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/TopicView.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/TopicViewId.class b/target/classes/com/github/example/pt/entity/TopicViewId.class
new file mode 100644
index 0000000..a62b6c6
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/TopicViewId.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/UserForumHistory$UserForumHistoryBuilder.class b/target/classes/com/github/example/pt/entity/UserForumHistory$UserForumHistoryBuilder.class
new file mode 100644
index 0000000..cbf6dbd
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/UserForumHistory$UserForumHistoryBuilder.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/entity/UserForumHistory.class b/target/classes/com/github/example/pt/entity/UserForumHistory.class
new file mode 100644
index 0000000..52179f7
--- /dev/null
+++ b/target/classes/com/github/example/pt/entity/UserForumHistory.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/repository/ForumRepository.class b/target/classes/com/github/example/pt/repository/ForumRepository.class
new file mode 100644
index 0000000..2712bfa
--- /dev/null
+++ b/target/classes/com/github/example/pt/repository/ForumRepository.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/repository/ForumTagRepository.class b/target/classes/com/github/example/pt/repository/ForumTagRepository.class
new file mode 100644
index 0000000..6fe6c8a
--- /dev/null
+++ b/target/classes/com/github/example/pt/repository/ForumTagRepository.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/repository/PostLikeRepository.class b/target/classes/com/github/example/pt/repository/PostLikeRepository.class
new file mode 100644
index 0000000..94ae29a
--- /dev/null
+++ b/target/classes/com/github/example/pt/repository/PostLikeRepository.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/repository/PostRepository.class b/target/classes/com/github/example/pt/repository/PostRepository.class
new file mode 100644
index 0000000..7c1ec9c
--- /dev/null
+++ b/target/classes/com/github/example/pt/repository/PostRepository.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/repository/TopicRepository.class b/target/classes/com/github/example/pt/repository/TopicRepository.class
new file mode 100644
index 0000000..c12121c
--- /dev/null
+++ b/target/classes/com/github/example/pt/repository/TopicRepository.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/repository/TopicSubscriptionRepository.class b/target/classes/com/github/example/pt/repository/TopicSubscriptionRepository.class
new file mode 100644
index 0000000..e865343
--- /dev/null
+++ b/target/classes/com/github/example/pt/repository/TopicSubscriptionRepository.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/repository/TopicTagRepository.class b/target/classes/com/github/example/pt/repository/TopicTagRepository.class
new file mode 100644
index 0000000..5caa056
--- /dev/null
+++ b/target/classes/com/github/example/pt/repository/TopicTagRepository.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/repository/TopicViewRepository.class b/target/classes/com/github/example/pt/repository/TopicViewRepository.class
new file mode 100644
index 0000000..b5d745a
--- /dev/null
+++ b/target/classes/com/github/example/pt/repository/TopicViewRepository.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/repository/UserForumHistoryRepository.class b/target/classes/com/github/example/pt/repository/UserForumHistoryRepository.class
new file mode 100644
index 0000000..59a7ac3
--- /dev/null
+++ b/target/classes/com/github/example/pt/repository/UserForumHistoryRepository.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/service/ForumService.class b/target/classes/com/github/example/pt/service/ForumService.class
new file mode 100644
index 0000000..8e329cf
--- /dev/null
+++ b/target/classes/com/github/example/pt/service/ForumService.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/service/ForumTagService.class b/target/classes/com/github/example/pt/service/ForumTagService.class
new file mode 100644
index 0000000..3133a42
--- /dev/null
+++ b/target/classes/com/github/example/pt/service/ForumTagService.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/service/PostLikeService.class b/target/classes/com/github/example/pt/service/PostLikeService.class
new file mode 100644
index 0000000..6687516
--- /dev/null
+++ b/target/classes/com/github/example/pt/service/PostLikeService.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/service/PostService.class b/target/classes/com/github/example/pt/service/PostService.class
new file mode 100644
index 0000000..49a5000
--- /dev/null
+++ b/target/classes/com/github/example/pt/service/PostService.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/service/TopicService.class b/target/classes/com/github/example/pt/service/TopicService.class
new file mode 100644
index 0000000..2b079df
--- /dev/null
+++ b/target/classes/com/github/example/pt/service/TopicService.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/service/TopicSubscriptionService.class b/target/classes/com/github/example/pt/service/TopicSubscriptionService.class
new file mode 100644
index 0000000..e481616
--- /dev/null
+++ b/target/classes/com/github/example/pt/service/TopicSubscriptionService.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/service/TopicTagService.class b/target/classes/com/github/example/pt/service/TopicTagService.class
new file mode 100644
index 0000000..f1c110a
--- /dev/null
+++ b/target/classes/com/github/example/pt/service/TopicTagService.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/service/TopicViewService.class b/target/classes/com/github/example/pt/service/TopicViewService.class
new file mode 100644
index 0000000..16672ab
--- /dev/null
+++ b/target/classes/com/github/example/pt/service/TopicViewService.class
Binary files differ
diff --git a/target/classes/com/github/example/pt/service/UserForumHistoryService.class b/target/classes/com/github/example/pt/service/UserForumHistoryService.class
new file mode 100644
index 0000000..7a8aa6f
--- /dev/null
+++ b/target/classes/com/github/example/pt/service/UserForumHistoryService.class
Binary files differ
diff --git a/target/classes/posts_add.sql b/target/classes/posts_add.sql
new file mode 100644
index 0000000..85a89a7
--- /dev/null
+++ b/target/classes/posts_add.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `topics`
+    MODIFY COLUMN `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+ALTER TABLE `posts`
+    MODIFY COLUMN `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;