📢 本文章按照仿牛客网里显示评论功能记录下里面的这个主要逻辑。
👤 公众号:恩故事还在继续
1️⃣ 效果展示

2️⃣ 功能说明
如上图所示,在实际的应用场景中,我们需要对某个帖子或者文字评论,我们不仅能对该帖子评论而且其他用户也可以对我们自己的评论进行回复。
比如: 有A、B、C三个用户,A 对 帖子进行评论
然后B可以对A的评论进行回复,C也可以对B的评论进行回复,A也可以对自己回复。
3️⃣ 数据库设计
CREATE TABLE `comment` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`entity_type` int(11) DEFAULT NULL,
`entity_id` int(11) DEFAULT NULL,
`target_id` int(11) DEFAULT NULL,
`content` text,
`status` int(11) DEFAULT NULL,
`create_time` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_user_id` (`user_id`) /*!80000 INVISIBLE */,
KEY `index_entity_id` (`entity_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
4️⃣ 实体类
package org.example.community.entity;
import java.util.Date;
public class Comment {
private int id;
private int userId; // 用户id
private int entityType; // 评论的类型(评论目标类),比如:1-帖子、2-评论、3-用户、4-课程、5-视频
private int entityId; // 具体的评论id,比如具体是哪个id的帖子
private int targetId; // 指向评论id,比如我评论了帖子,我当前这条评论可被其他人评论 我是A,其他人是B B->A
private String content; // 评论内容
private int status; // 评论状态 0-正常 1-禁言
private Date createTime; // 回帖时间
// 后面省略 get set 方法
}
✏️ 实体属性解读
对于上面的 entityType 属性, 这个属性代表的是我们评论的类型,比如: 帖子、视频、用户等
然后我们标记 1 - 帖子、 2 - 用户
entityType = 1 则为对帖子的评论
entityType = 2 代表的是对用户的评论、或者是用户对用户的回复
然后我们接着说一下 entityId 这个代表的是帖子ID 也就是用户对哪个帖子的评论
最后就是 targetId 这个代表的是回复人的ID
如下图所示:

5️⃣ 后端代码实现
@RequestMapping(path = "/detail/{discussPostId}", method = RequestMethod.GET)
public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page) {
// 帖子
DiscussPost post = discussPostService.findDisPostById(discussPostId);
model.addAttribute("post", post);
// 用户
User user = userService.findUserById(post.getUserId());
model.addAttribute("user", user);
// 评论分页信息
page.setLimit(5); // 每页显示五条评论
page.setPath("/discuss/detail/" + discussPostId);
page.setRows(post.getCommentCount());
// 评论列表: 给帖子的评论
List<Comment> commentList = commentService.findCommentsByEntity(ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit());
// 回复: 给评论的评论
List<Map<String, Object>> commentVoList = new ArrayList<>();
if(commentList != null){
for(Comment comment : commentList){
// 存放评论视图对象
Map<String, Object> commentVo = new HashMap<>();
// 一条评论
commentVo.put("comment", comment);
// 用户
commentVo.put("user", userService.findUserById(comment.getUserId()));
// 回复列表
List<Comment> replyList = commentService.findCommentsByEntity(ENTITY_TYPE_COMMENT, comment.getId(),0, Integer.MAX_VALUE);
// 回复Vo列表
List<Map<String, Object>> replyVoList = new ArrayList<>();
if(replyList != null){
for(Comment replay : replyList){
Map<String, Object> replayVo = new HashMap<>();
// 回复
replayVo.put("replay", replay);
// 作者
replayVo.put("user", userService.findUserById(replay.getUserId()));
// 回复目标
User target = userService.findUserById(replay.getTargetId());
replayVo.put("target", target);
replyVoList.add(replayVo);
}
}
commentVo.put("replays", replyVoList);
// 获取回复数量
int replayCount = commentService.findCommentCount(ENTITY_TYPE_COMMENT, comment.getId());
commentVo.put("replayCount", replayCount);
commentVoList.add(commentVo);
}
}
model.addAttribute("comments", commentVoList);
return "/site/discuss-detail";
}
📝 思维导向
上面的代码我们主要看评论那一部分,这一部分我也是理解了很久, 所以画了个图方面直观理解,如下图所示:
1. 获取所有评论: commentList
2. 将每条评论存储到视图对象中,主要与前端交互: commentVoList
3. 通过遍历 commentList 然后将每条评论存入到: commentVo
4. 首先遍历得到的是帖子对应的评论,可以获得的属性是: user、comment 然后添加到 commentVo
5. 其次,我们需要获取每条评论下的回复; replayList
6. 将每条评论下的回复存放到视图视图对象中: replayVoList
7. 通过遍历 replayList 获取每条回复下的 replay、 target、 user
8. 最后用 replayVoList 将 replay、 target、user属性存放 => replyVoList.add(replayVo)
9. 遍历完成每条评论之后直接添加到 commentVo.put("replays", replyVoList)
10. 然后再获取每条帖子下的评论数: commentVo.put("replayCount", replayCount)
11. 最后直接添加到:commentVoList.add(commentVo)
✏️ 下面这个图是我们渲染给前端的返回值, 可以通过下面这个图直接来取值

6️⃣ 前端渲染
<!-- 回帖列表 -->
<ul class="list-unstyled mt-4">
<!-- 第1条回帖 -->
<li class="media pb-3 pt-3 mb-3 border-bottom" th:each="cvo:${comments}">
<a href="profile.html">
<img th:src="${cvo.user.headerUrl}" class="align-self-start mr-4 rounded-circle user-header" alt="用户头像" >
</a>
<div class="media-body">
<div class="mt-0">
<span class="font-size-12 text-success" th:utext="${cvo.user.username}">掉脑袋切切</span>
<span class="badge badge-secondary float-right floor">
<i th:text="${page.offset + cvoStat.count}">1</i>#
</span>
</div>
<div class="mt-2" th:text="${cvo.comment.content}">
这开课时间是不是有点晚啊。。。
</div>
<div class="mt-4 text-muted font-size-12">
<span th:text="${#dates.format(cvo.comment.createTime,'yyyy-MM-dd HH:mm:ss')}">发布于 <b>2019-04-15 15:32:18</b></span>
<ul class="d-inline float-right">
<li class="d-inline ml-2"><a href="#" class="text-primary">赞(1)</a></li>
<li class="d-inline ml-2">|</li>
<li class="d-inline ml-2"><a href="#" class="text-primary">回复(<span th:text="${cvo.replayCount}">2</span>)</a></li>
</ul>
</div>
<!-- 回复列表 -->
<ul class="list-unstyled mt-4 bg-gray p-3 font-size-12 text-muted" >
<!-- 第1条回复 -->
<li class="pb-3 pt-3 mb-3 border-bottom" th:each="rvo:${cvo.replays}">
<div>
<span th:if="${rvo.target == null}">
<b class="text-info" th:text="${rvo.user.username}">寒江雪</b>:
</span>
<span th:if="${rvo.target != null}">
<i class="text-info" th:text="${rvo.user.username}">Sissi</i> 回复
<b class="text-info" th:utext="${rvo.target.username}">:寒江雪</b>
</span>
<span th:text="${rvo.replay.content}">这个是直播时间哈,觉得晚的话可以直接看之前的完整录播的~</span>
</div>
<div class="mt-3">
<span th:text="${#dates.format(rvo.replay.createTime, 'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</span>
<ul class="d-inline float-right">
<li class="d-inline ml-2"><a href="#" class="text-primary">赞(1)</a></li>
<li class="d-inline ml-2">|</li>
<li class="d-inline ml-2"><a th:href="|#huifu-${rvoStat.count}|" data-toggle="collapse" class="text-primary">回复</a></li>
</ul>
<div th:id="|huifu-${rvoStat.count}|" class="mt-4 collapse">
<div>
<input type="text" class="input-size" placeholder="回复寒江雪"/>
</div>
<div class="text-right mt-2">
<button type="button" class="btn btn-primary btn-sm" onclick=""> 回 复 </button>
</div>
</div>
</div>
</li>
<!-- 回复输入框 -->
<li class="pb-3 pt-3">
<div>
<input type="text" class="input-size" placeholder="请输入你的观点"/>
</div>
<div class="text-right mt-2">
<button type="button" class="btn btn-primary btn-sm" onclick=""> 回 复 </button>
</div>
</li>
</ul>
</div>
</li>
</ul>
> :seven: 📞 联系 👨

1176

被折叠的 条评论
为什么被折叠?



