您好,登录后才能下订单哦!
在Web应用中,评论系统是一个常见的功能模块。无论是博客、新闻网站还是社交平台,用户都可以通过评论功能表达自己的观点。而评论回复功能则是评论系统的进一步扩展,允许用户对某条评论进行回复,形成多层次的讨论。
本文将详细介绍如何在Java中实现一个单表的评论回复功能。我们将从需求分析、数据库设计、实体类设计、数据访问层设计、业务逻辑层设计、控制器层设计、前端实现、测试与验证等方面进行详细讲解。
在实现评论回复功能之前,我们需要明确系统的需求。以下是评论回复功能的基本需求:
为了实现评论回复功能,我们需要设计一个数据库表来存储评论和回复信息。由于我们采用单表设计,所有评论和回复都将存储在同一个表中。
我们设计一个名为comment的表,表结构如下:
CREATE TABLE comment (
    id BIGINT PRIMARY KEY AUTO_INCREMENT, -- 评论ID
    content TEXT NOT NULL, -- 评论内容
    user_id BIGINT NOT NULL, -- 用户ID
    parent_id BIGINT DEFAULT NULL, -- 父评论ID,NULL表示顶级评论
    create_time DATETIME NOT NULL, -- 创建时间
    update_time DATETIME NOT NULL, -- 更新时间
    FOREIGN KEY (parent_id) REFERENCES comment(id) -- 外键约束
);
id:评论的唯一标识,自增主键。content:评论的内容。user_id:发布评论的用户ID。parent_id:父评论的ID。如果该字段为NULL,则表示这是一条顶级评论;否则,表示这是一条回复。create_time:评论的创建时间。update_time:评论的更新时间。为了提高查询效率,我们可以为parent_id字段添加索引:
CREATE INDEX idx_parent_id ON comment(parent_id);
在Java中,我们需要定义一个实体类来映射数据库中的comment表。以下是Comment实体类的设计:
import javax.persistence.*;
import java.util.Date;
@Entity
@Table(name = "comment")
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false, columnDefinition = "TEXT")
    private String content;
    @Column(name = "user_id", nullable = false)
    private Long userId;
    @Column(name = "parent_id")
    private Long parentId;
    @Column(name = "create_time", nullable = false)
    @Temporal(TemporalType.TIMESTAMP)
    private Date createTime;
    @Column(name = "update_time", nullable = false)
    @Temporal(TemporalType.TIMESTAMP)
    private Date updateTime;
    // Getters and Setters
}
id:对应数据库中的id字段,自增主键。content:对应数据库中的content字段,存储评论内容。userId:对应数据库中的user_id字段,存储发布评论的用户ID。parentId:对应数据库中的parent_id字段,存储父评论的ID。createTime:对应数据库中的create_time字段,存储评论的创建时间。updateTime:对应数据库中的update_time字段,存储评论的更新时间。由于我们采用单表设计,所有评论和回复都存储在同一个表中。因此,Comment实体类不需要定义与其他实体类的关系。
数据访问层(DAO层)负责与数据库进行交互。我们将使用Spring Data JPA来实现数据访问层。
我们定义一个CommentRepository接口,继承自JpaRepository:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface CommentRepository extends JpaRepository<Comment, Long> {
    List<Comment> findByParentId(Long parentId);
    List<Comment> findByParentIdIsNullOrderByCreateTimeDesc();
    List<Comment> findByParentIdIsNullOrderByCreateTimeAsc();
}
findByParentId(Long parentId):根据父评论ID查询所有回复。findByParentIdIsNullOrderByCreateTimeDesc():查询所有顶级评论,并按创建时间降序排序。findByParentIdIsNullOrderByCreateTimeAsc():查询所有顶级评论,并按创建时间升序排序。业务逻辑层(Service层)负责处理业务逻辑。我们将定义一个CommentService接口及其实现类CommentServiceImpl。
import java.util.List;
public interface CommentService {
    Comment addComment(Comment comment);
    void deleteComment(Long id);
    List<Comment> getTopLevelComments(String sort);
    List<Comment> getReplies(Long parentId);
}
addComment(Comment comment):添加一条评论或回复。deleteComment(Long id):删除一条评论或回复。getTopLevelComments(String sort):获取所有顶级评论,并根据排序参数进行排序。getReplies(Long parentId):获取某条评论的所有回复。import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
@Service
public class CommentServiceImpl implements CommentService {
    @Autowired
    private CommentRepository commentRepository;
    @Override
    public Comment addComment(Comment comment) {
        comment.setCreateTime(new Date());
        comment.setUpdateTime(new Date());
        return commentRepository.save(comment);
    }
    @Override
    public void deleteComment(Long id) {
        commentRepository.deleteById(id);
    }
    @Override
    public List<Comment> getTopLevelComments(String sort) {
        if ("asc".equalsIgnoreCase(sort)) {
            return commentRepository.findByParentIdIsNullOrderByCreateTimeAsc();
        } else {
            return commentRepository.findByParentIdIsNullOrderByCreateTimeDesc();
        }
    }
    @Override
    public List<Comment> getReplies(Long parentId) {
        return commentRepository.findByParentId(parentId);
    }
}
addComment(Comment comment):设置评论的创建时间和更新时间,并保存到数据库。deleteComment(Long id):根据ID删除评论。getTopLevelComments(String sort):根据排序参数获取所有顶级评论。getReplies(Long parentId):根据父评论ID获取所有回复。控制器层(Controller层)负责处理HTTP请求,并调用业务逻辑层的方法。我们将定义一个CommentController类来处理评论相关的请求。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/comments")
public class CommentController {
    @Autowired
    private CommentService commentService;
    @PostMapping
    public Comment addComment(@RequestBody Comment comment) {
        return commentService.addComment(comment);
    }
    @DeleteMapping("/{id}")
    public void deleteComment(@PathVariable Long id) {
        commentService.deleteComment(id);
    }
    @GetMapping
    public List<Comment> getTopLevelComments(@RequestParam(defaultValue = "desc") String sort) {
        return commentService.getTopLevelComments(sort);
    }
    @GetMapping("/{parentId}/replies")
    public List<Comment> getReplies(@PathVariable Long parentId) {
        return commentService.getReplies(parentId);
    }
}
addComment(@RequestBody Comment comment):处理添加评论的POST请求。deleteComment(@PathVariable Long id):处理删除评论的DELETE请求。getTopLevelComments(@RequestParam(defaultValue = "desc") String sort):处理获取顶级评论的GET请求,支持排序参数。getReplies(@PathVariable Long parentId):处理获取某条评论的回复的GET请求。前端部分主要负责展示评论列表、发布评论、回复评论等功能。我们将使用HTML、CSS和JavaScript来实现前端页面。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>评论系统</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="container">
        <h1>评论系统</h1>
        <div id="comment-form">
            <textarea id="comment-content" placeholder="请输入评论内容"></textarea>
            <button id="submit-comment">提交评论</button>
        </div>
        <div id="comment-list"></div>
    </div>
    <script src="script.js"></script>
</body>
</html>
body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 0;
    background-color: #f4f4f4;
}
.container {
    width: 80%;
    margin: 0 auto;
    padding: 20px;
    background-color: #fff;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
#comment-form {
    margin-bottom: 20px;
}
#comment-content {
    width: 100%;
    height: 100px;
    padding: 10px;
    margin-bottom: 10px;
    border: 1px solid #ccc;
    border-radius: 4px;
}
#submit-comment {
    padding: 10px 20px;
    background-color: #007bff;
    color: #fff;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}
#submit-comment:hover {
    background-color: #0056b3;
}
.comment {
    margin-bottom: 20px;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 4px;
    background-color: #f9f9f9;
}
.comment .content {
    margin-bottom: 10px;
}
.comment .reply-form {
    margin-top: 10px;
}
.comment .reply-form textarea {
    width: 100%;
    height: 50px;
    padding: 5px;
    margin-bottom: 5px;
    border: 1px solid #ccc;
    border-radius: 4px;
}
.comment .reply-form button {
    padding: 5px 10px;
    background-color: #28a745;
    color: #fff;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}
.comment .reply-form button:hover {
    background-color: #218838;
}
.reply {
    margin-left: 20px;
    margin-top: 10px;
    padding: 10px;
    border: 1px solid #eee;
    border-radius: 4px;
    background-color: #f1f1f1;
}
document.addEventListener('DOMContentLoaded', function () {
    const commentList = document.getElementById('comment-list');
    const commentForm = document.getElementById('comment-form');
    const commentContent = document.getElementById('comment-content');
    const submitComment = document.getElementById('submit-comment');
    // 加载评论列表
    loadComments();
    // 提交评论
    submitComment.addEventListener('click', function () {
        const content = commentContent.value.trim();
        if (content) {
            addComment(content);
            commentContent.value = '';
        }
    });
    // 加载评论列表
    function loadComments() {
        fetch('/comments?sort=desc')
            .then(response => response.json())
            .then(comments => {
                commentList.innerHTML = '';
                comments.forEach(comment => {
                    renderComment(comment);
                });
            });
    }
    // 渲染评论
    function renderComment(comment) {
        const commentDiv = document.createElement('div');
        commentDiv.className = 'comment';
        commentDiv.innerHTML = `
            <div class="content">${comment.content}</div>
            <div class="reply-form">
                <textarea placeholder="请输入回复内容"></textarea>
                <button onclick="addReply(${comment.id}, this)">回复</button>
            </div>
        `;
        commentList.appendChild(commentDiv);
        // 加载回复
        fetch(`/comments/${comment.id}/replies`)
            .then(response => response.json())
            .then(replies => {
                replies.forEach(reply => {
                    renderReply(reply, commentDiv);
                });
            });
    }
    // 渲染回复
    function renderReply(reply, parentComment) {
        const replyDiv = document.createElement('div');
        replyDiv.className = 'reply';
        replyDiv.innerHTML = `
            <div class="content">${reply.content}</div>
        `;
        parentComment.appendChild(replyDiv);
    }
    // 添加评论
    function addComment(content) {
        const comment = {
            content: content,
            userId: 1, // 假设用户ID为1
            parentId: null
        };
        fetch('/comments', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(comment)
        })
            .then(response => response.json())
            .then(newComment => {
                renderComment(newComment);
            });
    }
    // 添加回复
    window.addReply = function (parentId, button) {
        const replyContent = button.previousElementSibling.value.trim();
        if (replyContent) {
            const reply = {
                content: replyContent,
                userId: 1, // 假设用户ID为1
                parentId: parentId
            };
            fetch('/comments', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(reply)
            })
                .then(response => response.json())
                .then(newReply => {
                    const parentComment = button.closest('.comment');
                    renderReply(newReply, parentComment);
                    button.previousElementSibling.value = '';
                });
        }
    };
});
fetch请求获取所有顶级评论,并调用renderComment函数渲染评论。addComment函数,将评论内容发送到后端。renderComment函数负责渲染单条评论,并加载该评论的所有回复。renderReply函数负责渲染单条回复。addReply函数,将回复内容发送到后端。在完成代码编写后,我们需要对系统进行测试,确保功能正常。
通过以上测试步骤,我们可以验证评论回复功能的正确性。如果所有功能都能正常工作,说明我们的实现是成功的。
本文详细介绍了如何在Java中实现一个单表的评论回复功能。我们从需求分析、数据库设计、实体类设计、数据访问层设计、业务逻辑层设计、控制器层设计、前端实现、测试与验证等方面进行了详细讲解。通过本文的学习,读者可以掌握如何在Java中实现一个简单的评论回复系统,并可以根据实际需求进行扩展和优化。
希望本文对您有所帮助,感谢阅读!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。