From ff64500ffdb932b3b3f8ba1e2880f0ee9ddff298 Mon Sep 17 00:00:00 2001 From: Tim <135014430+nagisa77@users.noreply.github.com> Date: Mon, 30 Jun 2025 19:46:57 +0800 Subject: [PATCH] Add comment module --- README.md | 1 + .../com/openisle/config/SecurityConfig.java | 1 + .../controller/CommentController.java | 75 +++++++++++++++++++ src/main/java/com/openisle/model/Comment.java | 42 +++++++++++ .../repository/CommentRepository.java | 12 +++ .../com/openisle/service/CommentService.java | 57 ++++++++++++++ 6 files changed, 188 insertions(+) create mode 100644 src/main/java/com/openisle/controller/CommentController.java create mode 100644 src/main/java/com/openisle/model/Comment.java create mode 100644 src/main/java/com/openisle/repository/CommentRepository.java create mode 100644 src/main/java/com/openisle/service/CommentService.java diff --git a/README.md b/README.md index 741fe2d0e..37510ffa5 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ OpenIsle 是一个基于 Spring Boot 的社区后端平台示例,提供注册 - **邮件通知**:示例通过 Resend API 发送欢迎邮件,可根据需要修改。 - **灵活配置**:数据库地址、账户密码、Resend API Key 等均可通过环境变量或 `application.properties` 配置。 - **角色权限**:内置 `ADMIN` 和 `USER` 两种角色,`/api/admin/**` 接口仅管理员可访问。 +- **文章/评论**:支持发表文章并在文章下发布评论,评论可多级回复。 ## 快速开始 diff --git a/src/main/java/com/openisle/config/SecurityConfig.java b/src/main/java/com/openisle/config/SecurityConfig.java index a0d9f93cf..d5ca99684 100644 --- a/src/main/java/com/openisle/config/SecurityConfig.java +++ b/src/main/java/com/openisle/config/SecurityConfig.java @@ -64,6 +64,7 @@ public class SecurityConfig { .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.POST, "/api/auth/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/posts/**").permitAll() + .requestMatchers(HttpMethod.GET, "/api/comments/**").permitAll() .requestMatchers("/api/admin/**").hasAuthority("ADMIN") .anyRequest().authenticated() ) diff --git a/src/main/java/com/openisle/controller/CommentController.java b/src/main/java/com/openisle/controller/CommentController.java new file mode 100644 index 000000000..a2d59f7fd --- /dev/null +++ b/src/main/java/com/openisle/controller/CommentController.java @@ -0,0 +1,75 @@ +package com.openisle.controller; + +import com.openisle.model.Comment; +import com.openisle.service.CommentService; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class CommentController { + private final CommentService commentService; + + @PostMapping("/posts/{postId}/comments") + public ResponseEntity createComment(@PathVariable Long postId, + @RequestBody CommentRequest req, + Authentication auth) { + Comment comment = commentService.addComment(auth.getName(), postId, req.getContent()); + return ResponseEntity.ok(toDto(comment)); + } + + @PostMapping("/comments/{commentId}/replies") + public ResponseEntity replyComment(@PathVariable Long commentId, + @RequestBody CommentRequest req, + Authentication auth) { + Comment comment = commentService.addReply(auth.getName(), commentId, req.getContent()); + return ResponseEntity.ok(toDto(comment)); + } + + @GetMapping("/posts/{postId}/comments") + public List listComments(@PathVariable Long postId) { + return commentService.getCommentsForPost(postId).stream() + .map(this::toDtoWithReplies) + .collect(Collectors.toList()); + } + + private CommentDto toDtoWithReplies(Comment comment) { + CommentDto dto = toDto(comment); + List replies = commentService.getReplies(comment.getId()).stream() + .map(this::toDtoWithReplies) + .collect(Collectors.toList()); + dto.setReplies(replies); + return dto; + } + + private CommentDto toDto(Comment comment) { + CommentDto dto = new CommentDto(); + dto.setId(comment.getId()); + dto.setContent(comment.getContent()); + dto.setCreatedAt(comment.getCreatedAt()); + dto.setAuthor(comment.getAuthor().getUsername()); + return dto; + } + + @Data + private static class CommentRequest { + private String content; + } + + @Data + private static class CommentDto { + private Long id; + private String content; + private LocalDateTime createdAt; + private String author; + private List replies; + } +} diff --git a/src/main/java/com/openisle/model/Comment.java b/src/main/java/com/openisle/model/Comment.java new file mode 100644 index 000000000..fe72ab446 --- /dev/null +++ b/src/main/java/com/openisle/model/Comment.java @@ -0,0 +1,42 @@ +package com.openisle.model; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@Table(name = "comments") +public class Comment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, columnDefinition = "TEXT") + private String content; + + @Column(nullable = false) + private LocalDateTime createdAt; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "author_id") + private User author; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "post_id") + private Post post; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id") + private Comment parent; + + @PrePersist + protected void onCreate() { + this.createdAt = LocalDateTime.now(); + } +} diff --git a/src/main/java/com/openisle/repository/CommentRepository.java b/src/main/java/com/openisle/repository/CommentRepository.java new file mode 100644 index 000000000..ee7f3e23c --- /dev/null +++ b/src/main/java/com/openisle/repository/CommentRepository.java @@ -0,0 +1,12 @@ +package com.openisle.repository; + +import com.openisle.model.Comment; +import com.openisle.model.Post; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface CommentRepository extends JpaRepository { + List findByPostAndParentIsNullOrderByCreatedAtAsc(Post post); + List findByParentOrderByCreatedAtAsc(Comment parent); +} diff --git a/src/main/java/com/openisle/service/CommentService.java b/src/main/java/com/openisle/service/CommentService.java new file mode 100644 index 000000000..8b50c5d5a --- /dev/null +++ b/src/main/java/com/openisle/service/CommentService.java @@ -0,0 +1,57 @@ +package com.openisle.service; + +import com.openisle.model.Comment; +import com.openisle.model.Post; +import com.openisle.model.User; +import com.openisle.repository.CommentRepository; +import com.openisle.repository.PostRepository; +import com.openisle.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class CommentService { + private final CommentRepository commentRepository; + private final PostRepository postRepository; + private final UserRepository userRepository; + + public Comment addComment(String username, Long postId, String content) { + User author = userRepository.findByUsername(username) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + Post post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("Post not found")); + Comment comment = new Comment(); + comment.setAuthor(author); + comment.setPost(post); + comment.setContent(content); + return commentRepository.save(comment); + } + + public Comment addReply(String username, Long parentId, String content) { + User author = userRepository.findByUsername(username) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + Comment parent = commentRepository.findById(parentId) + .orElseThrow(() -> new IllegalArgumentException("Comment not found")); + Comment comment = new Comment(); + comment.setAuthor(author); + comment.setPost(parent.getPost()); + comment.setParent(parent); + comment.setContent(content); + return commentRepository.save(comment); + } + + public List getCommentsForPost(Long postId) { + Post post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("Post not found")); + return commentRepository.findByPostAndParentIsNullOrderByCreatedAtAsc(post); + } + + public List getReplies(Long parentId) { + Comment parent = commentRepository.findById(parentId) + .orElseThrow(() -> new IllegalArgumentException("Comment not found")); + return commentRepository.findByParentOrderByCreatedAtAsc(parent); + } +}