評論功能的簡單實現


最近在寫一個問答功能,類似於評論,幾番找資料才有點感覺(主要是太菜了),為了加深印象就單獨抽出來記下筆記,然後這篇寫完就開始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底層使用了迭代器,修改結構會有快速失敗機制、還有處理二層關係的時候,最底層往上解除套娃關係時,記得將孩子置空