評論功能的簡單實現
- 2020 年 3 月 8 日
- 筆記
最近在寫一個問答功能,類似於評論,幾番找資料才有點感覺(主要是太菜了),為了加深印象就單獨抽出來記下筆記,然後這篇寫完就開始SpringBoot的複習了
1. 說明
網上看到有三種類型的評論,按照筆者的理解記下了過程(可能理解錯了,望大神指出),所以列出的是筆者的理解,下面以模擬博客評論的場景來說明,(這些類型是筆者形容的,並沒有這個詞),總覺得很慌理解錯了,希望大家評論指正
測試環境:SSM、JDK1.8
2. 沒有互動型
這種類型只能評論,評論之間沒有互動,類似於問答形式。提出問題,然後回答,一對多關係。這些回答之間沒有任何聯繫

從圖可以簡單看出,這種類型的評論是比較簡單的,設計一個評論表,其內部添加一個掛載的博客id字段即可
數據庫設計
CREATE TABLE `comment` ( `comment_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '評論的id', `nickname` varchar(255) DEFAULT NULL COMMENT '評論者的昵稱', `content` varchar(255) DEFAULT NULL COMMENT '評論的內容', `blog_id` int(11) DEFAULT NULL COMMENT '評論掛載的博客id', PRIMARY KEY (`comment_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
這裡主要說明評論功能的實現,表會儘可能簡單的設計,像點贊,分頁,圖像這些不再考慮範圍內,另一個blog表也沒有給出,可以自行理解
查詢語句
SELECT * FROM comment WHERE blog_id = #{blog_id}
傳入需要查詢評論的博客id即可,將查詢的內容放入其評論區完成,這種評論較為簡單,評論之間沒有互動,適用於少數場景(像筆者這次寫的問答功能,但該問答有非法關鍵詞,官方回答,鎖定,審核,等功能,也不簡單)
3. 套娃型
這種類型筆者見得比較少,因為像樹狀,評論多起來層級結構複雜,不人性化

小一評論博客,小二緊接着回復小一的評論,小三又回復小二的評論,小一又回了小三的評論,像俄羅斯套娃層層套
數據庫設計
這裡筆者用單表來實現,筆者稱評論與回復這二者為父子關係,評論為父級,回復為子級,這種關係在數據里增多一個parent_id字段來維護,默認為-1,表示沒有父級
CREATE TABLE `blog` ( `comment_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '評論的id', `nickname` varchar(255) DEFAULT NULL COMMENT '評論者的昵稱', `content` varchar(255) DEFAULT NULL COMMENT '評論的內容', `blog_id` int(11) DEFAULT NULL COMMENT '評論掛載的博客id', `parent_id` int(11) DEFAULT '-1' COMMENT '父級評論', PRIMARY KEY (`comment_id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
博客評論多起來的時,可用blog_id作為索引(不想增加與功能無關內容,假裝沒看到)
Dto、映射文件、Service層
由於使用mybatis,所以把映射文件放上來一目了然
public class CommentDTO { private int id; private String nickname; private String content; private List<CommentDTO> children; // 存放子級的回復 // getter / setter }
<?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.howl.dao.CommentDao"> <resultMap id="commentDTOMap" type="com.howl.dto.CommentDTO"> <id property="id" column="comment_id"></id> <result property="nickname" column="nickname"></result> <result property="content" column="content"></result> <!-- 這裡筆者使用分步查詢,入參使用了@Param註解,名字有稍微變化 --> <association property="children" select="com.howl.dao.CommentDao.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> </mapper>
@Service public class CommentService { @Autowired CommentDao commentDao; public List<CommentDTO> selectCommentById(int blogId) { // 默認傳入-1,即找出父級評論先 return commentDao.selectCommentById(blogId, -1); } }
這樣查詢出來的語句是層層套的,不信你看
[{ "id": 1, "nickname": "小二", "content": "不錯", "children": [{ "id": 2, "nickname": "小三", "content": "支持", "children": [{ "id": 3, "nickname": "小四", "content": "6666", "children": [] }] }] }, { "id": 4, "nickname": "小五", "content": "一般般把", "children": [] }]
4. 兩層型
即只有兩層關係,比單層多了互動功能,比套娃簡潔,看圖

這種看起來舒服多了,怎麼做到的呢? 其實和套娃型使用的是同一個表與查詢,映射文件都不用改,不同之處在於查詢出來的後期的邏輯處理,很多時候跨庫也是如此,查完數據再進行邏輯的處理
處理邏輯
由套娃型轉變成二層型
套娃的示意圖:

- 1樓和2樓同級,屬於父級評論,直接掛載的博客下
- A屬於1樓評論的子級
- B屬於A的子級
- C屬於B的子級
二層的示意圖:

A,B,C 屬於同級,直接屬於1樓評論的子級
處理邏輯代碼
業務邏輯在Service層,DTT測試
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class CommentServiceTest { @Autowired CommentDao commentDao; @Test public void selectCommentById() { // 默認傳入-1,找出父級的評論,假裝查看博客id為7的評論 List<CommentDTO> comments = commentDao.selectCommentById(7, -1); // 這裡將套娃關係處理為二層關係 System.out.println(JSON.toJSONString(findParent(comments))); } // 處理每個父級評論的子級及其嵌套子級 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": 1, "nickname": "小二", "content": "不錯", "children": [{ "id": 3, "nickname": "小四", "content": "6666", "children": [] }, { "id": 2, "nickname": "小三", "content": "支持", "children": [] }] }, { "id": 4, "nickname": "小五", "content": "一般般把", "children": [] }]
後記:後期邏輯處理部分花了大半天沒濾清關係,沒想到第二天醒來隨手兩分鐘就搞定。原因:增強for底層使用了迭代器,修改結構會有快速失敗機制、還有處理二層關係的時候,最底層往上解除套娃關係時,記得將孩子置空

