文章目录
参考牛客网高级项目教程
功能需求及处理策略
-
1.在帖子详情页面中,显示关于这条帖子的评论交流动态
- 即,要显示评论这条帖子的评论,并显示对应评论者的基本信息
- 要分页展现
- 并按照评论时间先后分楼层展现
- 还要,显示针对每条评论的回复,即展现评论者们的交流动态
- 为明确谁回复谁,要显示指明对象
- 即,要显示评论这条帖子的评论,并显示对应评论者的基本信息
-
2.因此,帖子评论是有级别的,为了区分处理不同级别的评论,
- 不是采用递归策略,这样比较消耗内存和性能
- 而是采用指针指向上级评论或帖子id的策略,方便管理维护各级评论
2. Dao层处理数据
1)设计评论数据表
分级策略实现-处理评论和一级回复
为方便描述区分:现设定:
-
评论:指的是对帖子的评论,只有一级
-
回复:指的是对评论的评论
- 一级回复:指的是对评论的回复
- 非一级回复:指的是指向另一条回复的回复
-
entity_type:评论的目标类型-即上级类型:今后可以扩展,比如对题目、视频等的评论等等
- 帖子:1
- 评论:2
-
entity_id:评论的目标id-即上级id
- 这样,通过上级的类型和id,可以查询到下级所有相关的评论
- 下级的评论也可以明确指向上级的哪一条评论
分级策略实现-处理非一级回复
-
非一级回复,只回复target_id指向的回复,
- 因此不严格规定级别,只严格规定target_id指向,维护这个指针即可
-
target_id,回复目标的作者用户的id
-
0,不设置默认为0,无效的指针,指的是评论和一级回复的目标指向,
- 因评论目标指向已经用entity_type=1,entity_id维护
- 一级回复的目标指向已经用entity_type=2,entity_id维护,故无需再用target_id维护
-
其他非0值:即目标回复的id
-
用status表示评论是否删除的状态
- status,评论是否有效的状态
- 0, 表示有效状态,需要显示出来
- 1,表示无效状态,无需显示出来
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 AUTO_INCREMENT=232 DEFAULT CHARSET=utf8
2)接口定义、sql编写
接口中方法声明
- 为分页显示评论列表信息
- 根据实体目标类型id分页查询所有评论列表
- 为分页需要知道要分多少页,需要知道指定目标下的评论总数
- 查询实体目标类型id下的评论数量
@Mapper
public interface CommentMapper {
/** 根据实体目标类型id分页查询所有评论列表 */
List<Comment> selectCommentList(int entityType, int entityId, int offset, int limit);
/** 查询实体目标类型id下的评论数量 */
int selectComments(int entityType, int entityId);
}
sql编写
- 注意过滤条件,即status要有效
- 排序规定,默认按照回复的时间正序排列,最先评论的在1楼
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nowcoder.community.dao.CommentMapper">
<!-- 定义复用代码块-->
<sql id="selectFields">
select id, user_id, entity_type, entity_id, target_id, content, status, create_time from comment
</sql>
<sql id="insertFields">
insert into comment(user_id, entity_type, entity_id, target_id, content, status, create_time)
values (#{userId}, #{entityType}, #{entityId}, #{targetId}, #{content}, #{status}, #{createTime})
</sql>
<!-- 查询指定目标类型id的评论列表-->
<select id="selectCommentList" resultType="comment">
<include refid="selectFields"></include>
where status = 0
and entity_type = #{entityType}
and entity_id = #{entityId}
order by create_time asc
limit #{offset}, #{limit}
</select>
<!-- 查询目标类型id一共有多少条评论-->
<!-- 返回类型必须要写-->
<select id="selectComments" resultType="int">
select count(id) from comment
where status = 0
and entity_type = #{entityType}
and entity_id = #{entityId}
</select>
</mapper>
2. service层处理分页查询业务
@Service
public class CommentService {
@Autowired
CommentMapper commentMapper;
/** 根据实体目标类型id分页查询所有评论列表 */
public List<Comment> selectCommentList(int entityType, int entityId, int offset, int limit) {
return commentMapper.selectCommentList(entityType, entityId, offset, limit);
}
/** 查询实体目标类型id下的评论数量 */
public int selectComments(int entityType, int entityId) {
return commentMapper.selectComments(entityType, entityId);
}
}
3. Controller层处理查询请求
处理页面设置
// 处理分页信息-设置页面
page.setLimit(5);
page.setPath("/discuss/detail/" + postId);
page.setRows(discussPost.getCommentCount()); // 直接从帖子表中拿,减少查询次数
分页查询帖子下的所有评论信息
// 查询帖子的评论分页信息-将关联用户信息一并封装
List<Comment> commentList = commentService.selectCommentList(
ENTITY_TYPE_POST, discussPost.getId(), page.getOffset(), page.getLimit());
List<Map<String, Object>> commentVoList = new ArrayList<>();
if(commentList != null) { // 有可能没有评论,注意边界条件
for(Comment comment : commentList) {
Map<String, Object> commentVoMap = new HashMap<>();
commentVoMap.put("comment", comment);
commentVoMap.put("user", userService.findUserById(comment.getUserId()));
commentVoList.add(commentVoMap);
}
}
model.addAttribute("comments", commentVoList);
显示每条评论下的所有回复信息
// 根据每条评论的id,循环查询每条评论下的所有回复信息
List<Comment> replyList = commentService.selectCommentList(
ENTITY_TYPE_COMMENT, comment.getId(), 0, Integer.MAX_VALUE);
List<Map<String, Object>> replyVoList = new ArrayList<>(); // 封装list
if(replyList != null) {
for(Comment reply : replyList) {
Map<String, Object> replyVoMap = new HashMap<>();
replyVoMap.put("reply", reply);
replyVoMap.put("user", userService.findUserById(reply.getUserId()));
// 回复中需要用到targetId
// 找到回复目标用户作者-注意,如果targetId=0,表示无效,无需查询
User targetUser = reply.getTargetId() == 0 ? null :
userService.findUserById(reply.getTargetId());
replyVoMap.put("targetUser", targetUser);
replyVoList.add(replyVoMap);
}
}
commentVoMap.put("replys",replyVoList); // 将当前评论的所有回复封装数据放进评论map中
// 每条评论的回复数量统计
int replyCount = commentService.selectComments(ENTITY_TYPE_COMMENT, comment.getId());
commentVoMap.put("replyCount", replyCount);
整体代码如下:
// 处理分页信息-设置页面
page.setLimit(5);
page.setPath("/discuss/detail/" + postId);
page.setRows(discussPost.getCommentCount()); // 直接从帖子表中拿,减少查询次数
// 查询帖子的评论分页信息-将关联用户信息一并封装
List<Comment> commentList = commentService.selectCommentList(
ENTITY_TYPE_POST, discussPost.getId(), page.getOffset(), page.getLimit());
List<Map<String, Object>> commentVoList = new ArrayList<>();
if(commentList != null) { // 有可能没有评论,注意边界条件
for(Comment comment : commentList) {
Map<String, Object> commentVoMap = new HashMap<>();
commentVoMap.put("comment", comment);
commentVoMap.put("user", userService.findUserById(comment.getUserId()));
// 每条评论的回复数量统计
int replyCount = commentService.selectComments(ENTITY_TYPE_COMMENT, comment.getId());
commentVoMap.put("replyCount", replyCount);
// 根据每条评论的id,循环查询每条评论下的所有回复信息
List<Comment> replyList = commentService.selectCommentList(
ENTITY_TYPE_COMMENT, comment.getId(), 0, Integer.MAX_VALUE);
List<Map<String, Object>> replyVoList = new ArrayList<>(); // 封装list
if(replyList != null) {
for(Comment reply : replyList) {
Map<String, Object> replyVoMap = new HashMap<>();
replyVoMap.put("reply", reply);
replyVoMap.put("user", userService.findUserById(reply.getUserId()));
// 回复中需要用到targetId
// 找到回复目标用户作者-注意,如果targetId=0,表示无效,无需查询
User targetUser = reply.getTargetId() == 0 ? null :
userService.findUserById(reply.getTargetId());
replyVoMap.put("targetUser", targetUser);
replyVoList.add(replyVoMap);
}
}
commentVoMap.put("replys",replyVoList); // 将当前评论的所有回复封装数据放进评论map中
commentVoList.add(commentVoMap);
}
}
model.addAttribute("comments", commentVoList);
4. 处理View视图模板
主页中,每条帖子列表动态显示回帖数量
<li class="d-inline ml-2">回帖 <span th:text="${map.post.commentCount}">7</span></li>
帖子详情页面处理
帖子的内容栏,也要显示回帖数量
<li class="d-inline ml-2"><a href="#replyform" class="text-primary">回帖 <span th:text="${post.commentCount}">7</span></a></li
回帖显示栏
总数目显示
<h6><b class="square"></b> <i th:text="${post.commentCount}">30</i>条回帖</h6>
回帖列表分页显示
cvoStat.count
- cvoStat.count:相当于for循环里的i,记录循环的次数
- cvoStat,cvo是自定义的循环集合,后拼接Stat.count,记录循环次数
<!-- 分页遍历显示回帖 -->
<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>发布于
<b th:text="${#dates.format(cvo.comment.createTime,'yyyy-MM-dd HH:mm:ss')}">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">回复(<i th:text="${cvo.replyCount}">2</i>)</a></li>
</ul>
</div>
回复列表显示
<!-- 回复列表 -->
<ul class="list-unstyled mt-4 bg-gray p-3 font-size-12 text-muted">
<!-- 循环遍历每条评论的所有回复 -->
<li class="pb-3 pt-3 mb-3 border-bottom" th:each="rvo:${cvo.replys}">
<div>
<span th:if="${rvo.targetUser==null}">
<b class="text-info" th:text="${rvo.user.username}">寒江雪</b>:
</span>
<span th:if="${rvo.targetUser!=null}">
<i class="text-info" th:text="${rvo.user.username}">Sissi</i> 回复
<b class="text-info" th:text="${rvo.targetUser.username}">寒江雪</b>:
</span>
<span th:utext="${rvo.reply.content}">这个是直播时间哈,觉得晚的话可以直接看之前的完整录播的~</span>
</div>
<div class="mt-3">
<span th:text="${#dates.format(rvo.reply.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</span>
分页显示
- 直接复用即可
<ul class="pagination justify-content-center" th:replace="index::pagination">