自我从事网站开发以来,有好几个项目里都有用到评论这个功能,所以我就想把评论这一块,单独拿出来,做成一个组件化的模块。既节约了开发的工作时间,还能让自己对这个模块的功能有更进一步的理解。

因为目前我主要是用ThinkPHP框架在做开发,所以以下相关实例会以TP框架的语法来呈现。但具体方法细节我个人是觉得有所不足的,没有把关联模型的功能给利用起来。

这里参考“多说”和“百度贴吧”等评论系统,自己使用PHP+MSQL实现了一个简单的楼中楼评论与回复效果。并记录了两种方式(递归方式和入栈迭代出栈方式)来实现的过程,并分析两种方式的优缺点,至于前端如何实现没有展现。

首先设计数据库如下:

CREATE TABLE `shop_comments` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`uid` int(10) DEFAULT ‘0’ COMMENT ‘评论用户ID’,
`shop_id` int(10) DEFAULT ‘0’ COMMENT ‘shop_id(商家id)’,
`reply_id` int(10) DEFAULT ‘0’ COMMENT ‘被评论记录ID’,
`content` varchar(150) DEFAULT ” COMMENT ‘评论内容’,
`grade` float(10,0) DEFAULT ‘0’ COMMENT ‘星级评分’,
`image_group` text COMMENT ‘图片组,以逗号分割’,
`status` tinyint(2) DEFAULT ‘0’ COMMENT ‘审核状态:0=未审核;1=已审核通过’,
`create_at` int(10) DEFAULT ‘0’ COMMENT ‘评论时间’,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT;

创建测试数据如下:

具体实现方案如下(在ThinkPHP框架上实现):

1、递归方式
优点:实现代码简单,而且如果评论的层级固定在5个层次一下的话,建议使用该种方法,这样前端通过这种数据结果实现简单。
缺点:如果评论的层级没有固定的话,前端将无法展示评论信息了,而且如果层级太多的话,将会极大的消耗内存,更要命的是每次递归都得查询数据库,性能将大大的降低。

public static function commentsList($shopId, $reply_id=0, &$result=array()) {
    $_where = "shop_id = {$shopId} AND status=1 AND reply_id = {$reply_id}"; //$reply_id默认0为主评论
    $shopModel = new ShopCommentsModel();
    $res = $shopModel->where($_where)->order('create_at DESC')->select();
    if (empty($res)) {
        return array();
    }
    foreach ($res as $com) {
        $thisArr = &$result[]; //引用,指向同一内存变量
        $com["_child"] = self::commentsList($shopId, $com['id'], $thisArr);//实现递归方法(比较耗内存)
        $thisArr = $com;
    }
    return $result;
}

部分数据展示如下:(点击图片放大查看)

2、非递归方式(堆栈方式实现)
优点:只查询一次数据库,性能较好。可以实现n层级的评论,前端也能很好的展示
缺点:代码稍微复杂,对于固定的层级评论,前端展示楼中楼评论效果较为复杂。

public function getCommlist() {
    $shop_id = $this->request->param('shop_id');
    if(empty($shop_id))  return $this->json_error('所属店铺不存在');
    $shopModel = new ShopCommentsModel();
    $result = $shopModel->where(['shop_id'=>$shop_id, 'status'=>1])->order('create_at ASC')->select();
    $commList = $stack = array();
    if ($result) {
        foreach ($result AS $k=>$v) { //先将评论的数据进行入库(即:reply_id=0)
            if ($v['reply_id'] == 0) {
                $v['_level'] = 0; //设置层级数
                $v['_root'] = $v['id']; //标识评论id
                array_push($stack,$v); //入栈
                unset($result[$k]);
            }
        }
        while(!empty($stack)) { //迭代
            $node = array_pop($stack); //出栈
            $commList[] = $node;
            foreach ($result as $_k=>$_v) {
                if ($_v['reply_id'] == $node['id']) {
                    $_v['_level'] = $node['_level'] + 1; //设置下级层级数
                    $_v['_root'] = $node['_root']; //标识评论id
                    array_push($stack,$_v); //入栈
                    unset($result[$_k]);
                }
            }
        }
    }
    return json_encode($commList);
}

数据展示效果如下:(点击图片放大查看)

最近,知乎也对它的评论系统进行了一次改版,不过不是改版成楼中楼,而是在每个有对话评论的后面加上一个弹窗链接 查看对话,点击链接后弹窗能看到这两个人之间互动的所有评论。

采用时间顺序倒序或者正序平铺的方式展示评论,这种方式实现起来简单,但是阅读困难;采用楼中楼的方式展示评论,对用户的阅读习惯比较友好,但是实现起来可能比较困难。