这是我参与 11 月更文挑战的第 4 天,活动详情查看:2021 最后一次更文挑战
文章评论树
文章的评论功能如何实现?应该怎么的展现?
评论表
最近,因为一些原因,在考虑这些问题,也做了一个简易的实现。首先,先设计一张评论表 Comment,它具备如下的基础字段
- commentId:评论 ID(评论的唯一 ID)
- articleId:文章 ID(评论所属的文章,最好通过文章 Title)
- commentName:评论人(发表该条评论的人)
- commentContent:评论内容(评论的内容)
- replyId:父级评论 ID(子评论所依赖的父级评论 ID(commentId), 即被回复评论)
- replyName:父级评论人(父级评论的评论人(commentName),即被回复人)
- replyComment:父级评论内容(当前评论所回复的目标评论的评论内容)
- child:二级回复(父级评论下的子级评论列表 (Comment),当前评论下的所有子级评论)
对于可能存在评论人头像、身份、时间等信息,可以选择性加入,具体的操作代码不对这些做操作,无影响
为了方便起见,引入了如下的两个依赖库
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
复制代码
实体类
接着,新建实体类 Comment,属性与数据库字段保持一致即可,由于代码较长,无用注释已删除
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Comment {
private String commentId;
private String articleId;
private String commentName;
private String commentContent;
private String replyId;
private String replyName;
private String replyComment;
private List<Comment> child;
}
复制代码
转换实现
最后,就是具体的代码操作。值得一说的是,为了清晰和便捷,我将数据库 SQL 提取出来,模拟插入
另外,replyName、replyComment、child 在数据库中可以置空(强烈建议),这些字段数据可以根据 replyId 拿到
import com.google.gson.Gson;
import java.util.*;
public class Test {
public static void main(String[] args) {
/* 模拟数据 */
Comment comment1 = new Comment("1", "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来", "优弧", "你们掘金的活动真的太多了,太讨厌了。。。", null, null, null, null);
Comment comment2 = new Comment("2", "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来", "王中阳 Go", "这么说算自言自语么~", "1", null, null, null);
Comment comment3 = new Comment("3", "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来", "西瓜 waterm", "这是在凡尔赛吗", "1", null, null, null);
Comment comment4 = new Comment("4", "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来", "荣顶", "这么说算自言自语么~", "1", null, null, null);
Comment comment5 = new Comment("5", "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来", "阿 Q 说代码", "哈哈 来者不惧", "1", null, null, null);
Comment comment6 = new Comment("6", "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来", "用户 356807", "范德萨发撒的发", "3", null, null, null);
Comment comment7 = new Comment("7", "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来", "三掌柜", "我要参与 2021 最后一次更文挑战", null, null, null, null);
Comment comment8 = new Comment("8", "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来", "三掌柜", "11 月更文挑战第一天:juejin.cn", "7", null, null, null);
Comment comment9 = new Comment("9", "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来", "三掌柜", "11 月更文挑战第二天:juejin.cn", "7", null, null, null);
Comment comment10 = new Comment("10", "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来", "三掌柜", "11 月更文挑战第三天:juejin.cn", "7", null, null, null);
Comment comment11 = new Comment("11", "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来", "xbhog", "已经没有东西了可以榨干了", null, null, null, null);
Comment comment12 = new Comment("12", "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来", "StartFromThird", "代码文字比不得超过 70% 是指 代码字数 / (文章总字数: 即 代码字数 + 其他内容字数 ) ≤ 70 / 100 吗?", null, null, null, null);
Comment comment13 = new Comment("13", "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来", "六脉神剑", "去年的时候 根本没几个活动 今年真的活动多", null, null, null, null);
/* 当前文章下的所有评论,以 HashMap 形式存储,键为评论 ID(getCommentId) */
HashMap<String, Comment> commentMap = new HashMap<>();
commentMap.put(comment1.getCommentId(), comment1);
commentMap.put(comment2.getCommentId(), comment2);
commentMap.put(comment3.getCommentId(), comment3);
commentMap.put(comment4.getCommentId(), comment4);
commentMap.put(comment5.getCommentId(), comment5);
commentMap.put(comment6.getCommentId(), comment6);
commentMap.put(comment7.getCommentId(), comment7);
commentMap.put(comment8.getCommentId(), comment8);
commentMap.put(comment9.getCommentId(), comment9);
commentMap.put(comment10.getCommentId(), comment10);
commentMap.put(comment11.getCommentId(), comment11);
commentMap.put(comment12.getCommentId(), comment12);
commentMap.put(comment13.getCommentId(), comment13);
/* Map key 排序(升序) */
//HashMap<String, Comment> sortMapByKeyAscend = sortMapByKeyAscend(commentMap);
/* 获取级联形式的评论列表 */
List<Comment> commentList = new ArrayList<>();
/* 遍历所有评论 */
for (String commendId : commentMap.keySet()) {
/* 获取评论对象 */
Comment commentEntity = commentMap.get(commendId);
/* 评论对象的父级评论是否为空(replyId),为空则作为顶级的评论存在 */
if (commentEntity.getReplyId() == null) {
/* 获取当前顶级评论下的,所有二级评论 */
List<Comment> child = getChild(commendId, commentMap);
/* 二级评论填入当前顶级评论的 child 字段 */
commentEntity.setChild(child);
/* 已整合的顶级评论,填入文章评论列表中 */
commentList.add(commentEntity);
}
}
/* List 升序排序 */
commentList.sort(Comparator.comparing(Comment::getCommentId));
System.out.println(commentList);
Gson gson = new Gson();
/* 将得到的级联形式的评论列表转换为 JSON 对象 */
System.out.println(gson.toJson(commentList));
}
/**
* 获取当前顶级评论下的,二级子评论
*
* @param id:当前顶级评论的评论 ID
* @param commentMap:文章评论,所有
*/
public static List<Comment> getChild(String id, HashMap<String, Comment> commentMap) {
/* 二级子评论列表 */
List<Comment> commentList = new ArrayList<>();
/* 当前,已经记录的二级子评论的评论 ID */
List<String> commentIdList = new ArrayList<>();
Gson gson = new Gson();
/* 遍历每一条评论 */
for (String commendId : commentMap.keySet()) {
/* 获取评论的实体对象 */
Comment commentEntity = commentMap.get(commendId);
/* 获取二级子评论的唯一父评论实体对象 */
Comment commentTop = gson.fromJson(gson.toJson(commentMap.get(commentEntity.getReplyId())), Comment.class);
/* 当前遍历的评论,其父级评论 ID(replyId)是否与当前的父评论 ID(commentId)一致 */
if (Objects.equals(commentEntity.getReplyId(), id)) {
/* 记录当前二级评论的被回复人,可以是父评论人,也可以是同为二级的回复人 */
commentEntity.setReplyName(commentTop.getCommentName());
commentEntity.setReplyComment(commentTop.getCommentContent());
/* 加入二级列表 */
commentList.add(commentEntity);
/* 加入已记录评论的评论 ID */
commentIdList.add(commentEntity.getCommentId());
/* 之后遍历的评论,其父级评论 ID 是否已经被收录 (commentIdList) */
} else if (commentIdList.contains(commentEntity.getReplyId())) {
commentEntity.setReplyName(commentTop.getCommentName());
commentEntity.setReplyComment(commentTop.getCommentContent());
commentList.add(commentEntity);
/* 必须存在!!! 1:14 */
commentIdList.add(commentEntity.getCommentId());
}
}
// System.out.println(commentIdList);
return commentList;
}
public static <K extends Comparable<? super K>, V> HashMap<K, V> sortMapByKeyAscend(HashMap<K, V> map) {
ArrayList<Map.Entry<K, V>> entries = new ArrayList<>(map.entrySet());
entries.sort(Map.Entry.comparingByKey());
HashMap<K, V> sortedMap = new LinkedHashMap<>();
for (Map.Entry<K, V> entry : entries) {
sortedMap.put(entry.getKey(), entry.getValue());
}
return sortedMap;
}
}
复制代码
上述的代码,粘贴即可运行,实际转换的是一个 JSON 树,转换效果如下,数据来源网址
[
{
"commentId": "1",
"articleId": "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来",
"commentName": "优弧",
"commentContent": "你们掘金的活动真的太多了,太讨厌了。。。",
"child": [
{
"commentId": "2",
"articleId": "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来",
"commentName": "王中阳 Go",
"commentContent": "这么说算自言自语么~",
"replyId": "1",
"replyName": "优弧",
"replyComment": "你们掘金的活动真的太多了,太讨厌了。。。"
},
{
"commentId": "3",
"articleId": "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来",
"commentName": "西瓜 waterm",
"commentContent": "这是在凡尔赛吗",
"replyId": "1",
"replyName": "优弧",
"replyComment": "你们掘金的活动真的太多了,太讨厌了。。。"
},
{
"commentId": "4",
"articleId": "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来",
"commentName": "荣顶",
"commentContent": "这么说算自言自语么~",
"replyId": "1",
"replyName": "优弧",
"replyComment": "你们掘金的活动真的太多了,太讨厌了。。。"
},
{
"commentId": "5",
"articleId": "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来",
"commentName": "阿 Q 说代码",
"commentContent": "哈哈 来者不惧",
"replyId": "1",
"replyName": "优弧",
"replyComment": "你们掘金的活动真的太多了,太讨厌了。。。"
},
{
"commentId": "6",
"articleId": "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来",
"commentName": "用户 356807",
"commentContent": "范德萨发撒的发",
"replyId": "3",
"replyName": "西瓜 waterm",
"replyComment": "这是在凡尔赛吗"
}
]
},
{
"commentId": "11",
"articleId": "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来",
"commentName": "xbhog",
"commentContent": "已经没有东西了可以榨干了",
"child": []
},
{
"commentId": "12",
"articleId": "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来",
"commentName": "StartFromThird",
"commentContent": "代码文字比不得超过 70% 是指 代码字数 / (文章总字数: 即 代码字数 + 其他内容字数 ) ≤ 70 / 100 吗?",
"child": []
},
{
"commentId": "13",
"articleId": "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来",
"commentName": "六脉神剑",
"commentContent": "去年的时候 根本没几个活动 今年真的活动多",
"child": []
},
{
"commentId": "7",
"articleId": "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来",
"commentName": "三掌柜",
"commentContent": "我要参与 2021 最后一次更文挑战",
"child": [
{
"commentId": "8",
"articleId": "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来",
"commentName": "三掌柜",
"commentContent": "11 月更文挑战第一天:juejin.cn",
"replyId": "7",
"replyName": "三掌柜",
"replyComment": "我要参与 2021 最后一次更文挑战"
},
{
"commentId": "9",
"articleId": "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来",
"commentName": "三掌柜",
"commentContent": "11 月更文挑战第二天:juejin.cn",
"replyId": "7",
"replyName": "三掌柜",
"replyComment": "我要参与 2021 最后一次更文挑战"
},
{
"commentId": "10",
"articleId": "2021 最后一次更文挑战,玩法升级奖品多,全新体验等你来",
"commentName": "三掌柜",
"commentContent": "11 月更文挑战第三天:juejin.cn",
"replyId": "7",
"replyName": "三掌柜",
"replyComment": "我要参与 2021 最后一次更文挑战"
}
]
}
]
复制代码
具体的转换代码,注释很详细,需要的自取。今天周五,月入一千八,开机混底薪!YES!




近期评论