使用遞迴迴圈開發評論回覆功能,適用於大部分的簡單單體應用
評論功能或許是大多數的單體應用之中會用到的功能,我們會在自己所開發的專案之中進行整合該功能
大多數時候我們會將評論功能劃分成以下幾種:
- 單一型
- 巢狀型
- 兩層型
一、分類方式
1、單一型
單一型評論方式就是日常論壇之中的蓋樓的方式
使用者只能根據所在的文章或者問題進行單一回復,評論之間沒有互動
類似於問答形式。提出問題,然後回答,一對多關係。這些回答之間沒有任何聯絡
2、巢狀型
巢狀型評論方式會對有回覆的評論進行遞迴,會造成後端效能不佳,而且對於前端的展示也不是很友好
3、兩層型
兩層型評論方式就是除了一級評論之外,無論是對於該評論的回覆還是對於回覆的回覆都統一在第二層
二、實現原理
就以最常見的部落格來說,不同的分類方式實現原理不一樣
1、單一型
我們只需要在評論的資料表格中新增部落格id即可,查詢出相對應的資料直接進行展示即可
create table `comment` (
`id` int(11) not null auto_increment comment '主鍵id',
`nickname` varchar(255) default null comment '評論者暱稱',
`avatar` varchar(255) comment '評論頭像',
`content` varchar(255) default null comment '評論的內容',
`blog_id` int(11) default null comment '評論的部落格id',
primary key (`id`)
) comment '評論表';
在業務之中根據部落格id查詢出來,傳遞給前端展示出來即可
select * from comment where blog_id=#{blog_id}
2、巢狀型
巢狀型的評論方式所需要的資料結構是樹狀型的,評論多起來的話層級結構會變得很複雜,對於效能消耗也是很巨大,【不推薦】
實現原理為我們會在評論表之中新增一個【parent_id】欄位,定義評論和回覆為父子級的關係,評論為父級,回覆為子級,預設為【-1】,表示為沒有父級,
create table `comment` (
`id` int(11) not null auto_increment comment '主鍵id',
`nickname` varchar(255) default null comment '評論者暱稱',
`avatar` varchar(255) comment '評論頭像',
`content` varchar(255) default null comment '評論的內容',
`blog_id` int(11) default null comment '評論的部落格id',
`parent_id` int(11) default '-1' comment '父級評論id',
primary key (`id`)
) comment '評論表';
需要使用遞迴和連結串列進行迴圈遍歷插入回覆
設計如下:
Content.java
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "主鍵id")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Integer id;
@ApiModelProperty(value = "使用者暱稱")
@TableField("nickname")
private String nickname;
@ApiModelProperty(value = "頭像")
@TableField("avatar")
private String avatar;
@ApiModelProperty(value = "評論")
@TableField("comment")
private String comment;
@ApiModelProperty(value = "部落格id ")
@TableField("blog_id")
private Integer blogId;
@ApiModelProperty(value = "回覆評論id")
@TableField("parent_id")
private Integer parentId;
- DTO設計
ContentDTO.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@ApiModel(value = "評論模型")
@JsonIgnoreProperties(value = { "handler" })
public class ContentDTO {
private int id;
private String nickname;
private String content;
private List<ContentDTO> children;
}
使用mybatis做為持久層框架,編寫sql查詢語句進行巢狀查詢,
<resultMap id="commentDTOMap" type="com.zukxu.items.comment.entity.ContentDTO">
<id property="id" column="comment_id"></id>
<result property="nickname" column="nickname"></result>
<result property="content" column="content"></result>
<association property="children"
select="com.zukxu.items.comment.mapper.ContentMapper.selectCommentById" column="{blogId=blog_id,parentId=comment_id}"
fetchType="lazy">
</association>
</resultMap>
<select id="selectCommentById" resultMap="commentDTOMap">
SELECT comment_id,nickname,content,blog_id,parent_id FROM blog WHERE blog_id = #{blogId} AND parent_id = #{parentId}
</select>
結果如下:
[
{
"id": "1309302063977304065",
"nickname": "1",
"content": "這次該可以了吧",
"children": [
{
"id": "1309319425866698753",
"nickname": "1",
"content": "好了?",
"children": []
}
]
},
{
"id": "1309341283121154994",
"nickname": "4",
"content": "為什麼呢",
"children": [
{
"id": "1309373849414787073",
"nickname": "1",
"content": "好了?",
"children": []
},
{
"id": "1309308402422091778",
"nickname": "1",
"content": "可以了吧",
"children": []
},
{
"id": "1309373675783184385",
"nickname": "1",
"content": "好了?",
"children": [
{
"id": "1309373886580514817",
"nickname": "1",
"content": "???",
"children": []
}
]
}
]
}
]
結果會造成多重巢狀,不是很友好
3、兩層型
比單一型多了互動的功能,比巢狀型更加簡潔,方便操作管理
設計和巢狀型保持一致,只需要在查詢出來資料之後對資料進行處理即可
將巢狀型轉為兩層型結構
處理每個父級評論的子級及其巢狀子級
public List<CommentDTO> findParent(List<CommentDTO> comments) {
for (CommentDTO comment : comments) {
// 防止checkForComodification(),而建立一個新集合
ArrayList<CommentDTO> fatherChildren = new ArrayList<>();
// 遞迴處理子級的回覆,即回覆內有回覆
findChildren(comment, fatherChildren);
// 將遞迴處理後的集合放回父級的孩子中
comment.setChildren(fatherChildren);
}
return comments;
}
public void findChildren(CommentDTO parent, List<CommentDTO> fatherChildren) {
// 找出直接子級
List<CommentDTO> comments = parent.getChildren();
// 遍歷直接子級的子級
for (CommentDTO comment : comments) {
// 若非空,則還有子級,遞迴
if (!comment.getChildren().isEmpty()) {
findChildren(comment, fatherChildren);
}
// 已經到了最底層的巢狀關係,將該回復放入新建立的集合
fatherChildren.add(comment);
// 容易忽略的地方:將相對底層的子級放入新建立的集合之後
// 則表示解除了巢狀關係,對應的其父級的子級應該設為空
comment.setChildren(new ArrayList<>());
}
}
}
最後的結果如下:
[
{
"id": "1309302063977304065",
"userId": "1",
"comment": "這次該可以了吧",
"children": [
{
"id": "1309319425866698753",
"userId": "1",
"comment": "好了?",
"children": []
}
]
},
{
"id": "1309341283121154994",
"userId": "4",
"comment": "為什麼呢",
"children": [
{
"id": "1309373849414787073",
"userId": "1",
"comment": "好了?",
"children": []
},
{
"id": "1309308402422091778",
"userId": "1",
"comment": "可以了吧",
"children": []
},
{
"id": "1309373886580514817",
"userId": "1",
"comment": "???",
"children": []
},
{
"id": "1309373675783184385",
"userId": "1",
"comment": "好了?",
"children": []
}
]
}
]
絕大多數時候我們都會去使用兩層型的評論方式做評論