前言:
這篇文章主要介紹了博主在學習過程中的一些思路、以及這個評論模塊的大致的實現過程,可能並不是常規的做法(全都是自己YY出來的),希望能提供一些思路。
一、實現一個什么樣的評論模塊?
最開始接觸到評論的時候,是在自己做一個個人博客的時候遇到的,當時自己想的很簡單,別人評論,然后博主回復。類似於這樣:
這種評論很簡單,數據庫里只需要把回復作為一個字段存進去就可以了,但是后來看到了 暢言 和 多說 ,兩個免費的評論系統,只需要調用他們的API就可以把評論模塊托管到第三方平台,比較方便。類似這種比較完善的評論模塊,是可以評論別人的評論,然后又可以回復其他的評論,類似於這樣:
二、數據庫該怎么設計?
剛開始是想設計成兩個表,一個表是所有的評論,一個表存放所有的評論的子評論,然后子評論中多一個字段,存放父評論的ID,這樣把兩個表關聯起來,但是如果再往下增加子節點,就要判斷是往哪個數據表中插入,這就比較復雜了。其實仔細想一下這兩張表的結構,使用ID關聯起來的,完全可以合成一張表,表結構如下:
最上層的評論的pid是0,子評論的pid就是父評論的id,好像這樣的表結構就可以把所有的評論放到一個表中,但是又有一個問題了,像這樣表結構存貯的數據取出來的結果是默認按id排序的:
這樣的表結構取出來的數據默認的排序規則是按照id排序的,在前台數據展示的時候,怎么把一個子評論放到其應有的位置呢? 例如:上表中的id值為105的這條數據,父評論是id為102的這條數據,所以,105的位置應該在102這條數據的后面,而這種默認的排序規則是不滿足要求的;剛開始想的是怎么把這個數據表怎么填充到一棵樹的節點上,因為這個評論系統的結構可以模擬成一棵樹,但是怎么遍歷這棵樹讓數據按照正確的規則顯示到前台的頁面上呢,數據結構和算法學得不是太好,思考未果。。。於是就想,數據都是正確的,只是順序不對,只需要將它排序就行了。
三、進行數據模擬
數據模擬的思路很簡單,就是把數據庫中的每一條記錄都當成是一個對象實體,把整個表的數據放到一個對象數組里面,然后針對這個對象數組進行排序;首先肯定要遍歷這個數組,每次循環只需要找到該條評論的子評論就行了,通過pid作為參數進行判斷,每一次循環都是做同樣的事,其實就是一種遞歸的思想,遞歸的深度就取決於子評論的深度;具體代碼如下
package commet.sort.test; import commet.sort.entity.Comment; public class Test { public static void main(String[] args) { Comment[] comments = { new Comment(1001, 0, "a", "a's comment", 1), new Comment(1002, 0, "b", "b's comment", 1), new Comment(1003, 0, "c", "c's comment", 1), new Comment(1004, 0, "d", "d's comment", 1), new Comment(1005, 1002, "e", "e's comment", 2), new Comment(1006, 1002, "f", "f's comment", 2), new Comment(1007, 1005, "g", "g's comment", 3), new Comment(1008, 0, "h", "h's comment", 1), new Comment(1009, 0, "i", "i's comment", 1), new Comment(10010, 1003, "j", "j's comment", 1), new Comment(10011, 1010, "k", "k's comment", 1), new Comment(10012, 1007, "l", "l's comment", 1), new Comment(10013, 1006, "m", "m's comment", 1), new Comment(10014, 0, "n", "n's comment", 1), }; sortByArray(comments, 0); } static int num = 0; // 通過對象數組進行排序 public static void sortByArray(Comment[] comments, int pid) { for (int i = 0; i < comments.length; i++) { if (comments[i].getPid() == pid) { System.out.println(comments[i]); sortByArray(comments, comments[i].getId()); } num++; } } }
上述代碼中,新建對象數組是按照id的順序來排列的,把排序的結果打印,相當於模擬前台的數據輸出。控制台的輸出結果如下:
Comment [id=1001, pid=0, content=a, userName=a's comment, level=1]
Comment [id=1002, pid=0, content=b, userName=b's comment, level=1]
Comment [id=1005, pid=1002, content=e, userName=e's comment, level=2]
Comment [id=1007, pid=1005, content=g, userName=g's comment, level=3]
Comment [id=10012, pid=1007, content=l, userName=l's comment, level=1]
Comment [id=1006, pid=1002, content=f, userName=f's comment, level=2]
Comment [id=10013, pid=1006, content=m, userName=m's comment, level=1]
Comment [id=1003, pid=0, content=c, userName=c's comment, level=1]
Comment [id=10010, pid=1003, content=j, userName=j's comment, level=1]
Comment [id=1004, pid=0, content=d, userName=d's comment, level=1]
Comment [id=1008, pid=0, content=h, userName=h's comment, level=1]
Comment [id=1009, pid=0, content=i, userName=i's comment, level=1]
Comment [id=10014, pid=0, content=n, userName=n's comment, level=1]
從控制台輸出的結果來看,每條數據的pid都等於上一條數據的id,按照這樣的排序規則輸出到前台頁面就滿足需求,但是還有一個問題:如何去控制縮進(從上面的示例圖片可以看出,如果一條評論是子評論,該條評論相對於父評論是有一些縮進的),剛剛在講數據庫設計的時候,可以看到表結構中有一個屬性:level,這個值就是來控制每一條子評論的縮進的,把level值作為div的類名,通過樣式來控制每個div的縮進。
.level1 { margin-left: 0px; } .level2 { margin-left: 40px; border-left: 3px solid #339BD5; padding-left: 10px; } .level3 { margin-left: 80px; border-left: 3px solid #339BD5; padding-left: 10px; }
其實這個level值也是控制遞歸的深度的限制值,由於后台用了遞歸的排序,所以當數據量較大、遞歸的深度較深時,執行效率將會大打折扣,而且這個深度也必須受到控制,不可能無限地遞歸下去;其實一般的評論都會做分頁,這樣每次都是取較少量的數據進行排序,這樣就不用太去擔心遞歸的效率問題。
四、簡單的實現
剛剛的排序的算法只是一個並不可能在實際中使用,由於用到了springmvc+mybatis,在后台service層進行排序的時候把存儲數據的list作為一個成員變量,由於框架機制的原因,每一個service都是單例的,service中的成員變量會存在線程安全問題;因此放棄了在后台排序的念頭,改成在頁面加載時,后台將數據轉成json,交由前台進行排序:
前台使用了bootstrap中的媒體對象,這種排版看起來類似於一個評論:http://v3.bootcss.com/components/#media
以下是前台頁面:
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3個meta標簽*必須*放在最前面,任何其他內容都*必須*跟隨其后! --> <meta name="description" content=""> <meta name="author" content=""> <title>Home</title> <link href="${pageContext.request.contextPath}/sourceFile/offcanvas.css" rel="stylesheet"> <link rel="stylesheet" href="${pageContext.request.contextPath}/sourceFile/bootstrap.min.css"> <script src="${pageContext.request.contextPath}/sourceFile/jquery.2.1.1.js"></script> <script src="${pageContext.request.contextPath}/sourceFile/bootstrap.min.js"></script> </head> <style> .level1 { margin-left: 0px; } .level2 { margin-left: 40px; border-left: 3px solid #339BD5; padding-left: 10px; } .level3 { margin-left: 80px; border-left: 3px solid #339BD5; padding-left: 10px; } </style> <script> $(function() { //alert("${comments}"); var comments = ${comments}; getComment(comments, 0); var reply="<div><a>回復</a></div>" $(".level1").append(reply); $(".level2").append(reply); })
//對json數組進行排序,並且按照排序結果動態添加dom節點 function getComment(comments, pid) { $.each(comments, function(n, value) { if (value.pid == pid) { $(".media-list").append("<hr/><li class='media'><div class='level"+value.level+"'> <div class='media-body'> <p class='media-heading'>id和pid:"+value.id +"___"+ value.pid+"</p>內容是:"+value.content+"</div></div></li>");
//遞歸
getComment(comments,value.id); } }); } </script> <body> <div class="container"> <footer> <p>© create by YaoQi</p> <ul class="media-list"> <li class="media"> <div class="media-left"> <a href="#"> <img class="media-object" src="${pageContext.request.contextPath}/sourceFile/images/2.ico" alt="..."> </a> </div> <div class="media-body"> <h4 class="media-heading"></h4> </div> </li> </ul> </footer> </div> <!--/.container--> </body> </html>
測試最終的結果: