Home » Code » 网易&cnBeta的嵌套评论实现

网易&cnBeta的嵌套评论实现

无评论,不新闻,这年头看完新闻不“喷”几下就混身不自在。近年来国内门户网站网易、腾讯、新浪、搜狐等都先后推出了评论功能,但做得最好最出名的无疑是冠以“有态度”的网易,采用盖楼的形式,长长的几十层,好有气势。以互联网&科技新闻为主的cnBeta也是采用这种形式,大大活跃了气氛,有时候新闻内容是什么并不重要,咱看的只是评论。

目前对评论的表现形式主要有这两种:1、类似网易&cnBeta的盖楼形式,每个回复都形成一栋,目标楼层之前的都重复显示一遍;2、类似腾讯新浪搜狐以及大部分国外网站都采用的只追加评论、不新开一栋楼的形式。前者的优点很明显,非常清楚从头开始展现整个对话,谁回复谁,哪句回复哪句非常清晰,当然缺点就是重复内容较多,比如一栋楼有20层,我要回复15层那个,那么前面15层就会复制一次追加到我的评论前面新形成一栋。假如这时又有另外一个网友也是回复15层,那么他形成的楼栋跟我的楼栋的区别就只有最后一层了。

comment_163
而后一种,优点当然就是第一种的缺点,没有重复的内容,一定程序上看起来比较简洁。但它的缺点是整个对话脉络不清淅,尤其是子评论多深度深的时候。看下面腾讯的例子:

comment_qq

箭头指向的是二级评论,下边的四级评论比较多,估计是限制了最深四级,都是aaa回复bbb的头部,多了起来都不知道是回复的bbb哪条评论,在一层bbb有很多条评论啊。而这时如果有另外一位网友要回复箭头指向那一条评论,无论是给追加到后面形成三级还是放到四级里边去都不能很清楚的看到到底回复的是哪一句评论了。因此,我还是欣赏网易那种,虽然重复的较多,但整个对话从头到尾,非常清晰。这里,讨论的就是实现第一种。

网上也有很多人问这么个盖楼如何设计数据库,大多是回答说只需要一个表,id-parent-content这么个存储方式,一级评论parentid为0。又追问如何展现楼层效果,一层一层往上找父评论,不得递归?又有说添加一个路径字段,那取的时候如何控制一级评论的数量?比如我要取前面10楼(一级评论)及它所有的子评论,如何取?好吧,感觉问题比较棘手。

我的方案则是,再添加一个楼栋表,只存楼栋的信息,关键字段一个楼层数,也就是这一栋楼在整个评论中是几楼,另一个是这一楼栋所有的楼层id,当它是一级评论时楼层id就只有一个。楼栋由楼层组成,所有的内容支持数等都是楼层的。楼层表则存储每一条回复,字段跟网上说的基本一致,路径法,将父评论的id由/连接起来存储。取的时候,先取楼栋比如要10楼,取出前10条楼栋记录。组合这10条楼栋的路径字段再将这10条楼栋里边的楼层id组合查出所有楼层信息。最后难点就是将所有的楼层信息通过楼栋信息组合成盖楼形状了。

再详细一点,每一个回复都是一条楼层记录,也都是一条楼栋记录,两个表的记录数是一致的。楼层的字段有用户名、地址、添加时间、支持数、反对数、举报数等常规字段外,还有位置position字段,这是一栋楼内部楼层的层数,它是它的父楼层的值加1,另外一个字段就是路径path,这存的是它的父楼层的id列表。楼栋的字段则有位置position,这是楼栋在整个评论里边的位置,就是几楼,另一个就是这一栋楼的所有楼层id列表floors,逗号分割的字符串。

comment_column
取列表时,取出楼栋buildingList,组合它们的floors,再取出所有楼层信息floorList,并用它们的id作为它们的键方便输出。循环输出buildingList前,将buildList的floors转换为数组,出栈或队列的方式输出楼栋的楼层。按照这么个步骤,假如得到的楼栋信息如下:

comment_buildingList
可以看到,共有4栋楼,每栋楼的位置和楼层都有了。所有的楼层信息则如下:

comment_floorList
看到这里,估计都懂了。只有一个任务,那就是循环楼栋buildingList,输出它们的楼层floors,楼层信息都在floorList中了。到这里就得看一下html结构了。一栋楼的主楼层也就是最末层先定好,在主楼层的内容输出的前边有个div放它的父楼层,里边一层套一层的blockquote输出父楼层。结构不上了,查看demo源代码都一清二楚。来看这个楼栋的循环输出:

<?php foreach ($buildingList as $building):?>
    <?php $floorId = array_pop($building['floors'])?>
    <div class="row comment-item" data-building-id="<?php echo $building['id']?>">
        <div class="col-md-12 comment-main-header">
            <h5 class="">
                <span class="comment-username"><?php echo $building['position']?>楼</span>
                <span class="comment-username"><?php echo $floorList[$floorId]['user_name']?></span>
                <span class="comment-address text-muted">来自[<?php echo $floorList[$floorId]['address']?>]</span>
                <span class="comment-time pull-right text-muted">发表于<?php echo date('Y-m-d H:i:s', $floorList[$floorId]['add_time'])?></span>
            </h5>
        </div>

        <div class="col-md-1 comment-avatar">
            <a href="#"><img src="/Public/images/1.jpg" width="80" /></a>
        </div>
        <div class="col-md-11 comment-body">
            <div class="comment-quote-wrap">
                <?php echo !empty($building['floors']) ? renderQuote2($building['floors'], $floorList) : ''?>
            </div>
            <div class="comment-text lead"><?php echo $floorList[$floorId]['content']?></div>
            <div class="comment-action clearfix" data-floor-id="<?php echo $floorList[$floorId]['id']?>">
                <div class="pull-right">
                    <a class="comment-up" href="#">支持(<span class="comment-up-count"><?php echo $floorList[$floorId]['up_count']?></span>)</a>
                    <a class="comment-down" href="#">反对(<span class="comment-down-count"><?php echo $floorList[$floorId]['down_count']?></span>)</a>
                    <a href="#" class="comment-report">举报</a>
                    <a href="#" class="comment-reply">回复</a>
                </div>
            </div>
        </div>
    </div>
    <?php endForeach?>

这里,就只剩下renderQuote2这个函数了。要输出blockquote的嵌套,可以使用递归:

function renderQuote($floorIdArr, array $floorInfoArr)
{
	$HTML = '<blockquote class="comment-quote">';
	$floorId = array_pop($floorIdArr);
	if (!empty($floorIdArr))
	{
		$HTML .= renderQuote($floorIdArr, $floorInfoArr);
	}
	$HTML .= '<div class="comment-info clearfix"><h5 class="comment-header text-muted">';
	$HTML .= '<span class="comment-username">'.$floorInfoArr[$floorId]['user_name'].'</span>';
	$HTML .= '<span class="comment-address">来自['.$floorInfoArr[$floorId]['address'].']</span>';
	$HTML .= '<span class="comment-floor pull-right">#'.$floorInfoArr[$floorId]['position'].'</span>';
	$HTML .= '</h5><div class="comment-text lead">'.$floorInfoArr[$floorId]['content'].'</div><div class="comment-action clearfix" data-floor-id="'.$floorId.'"><div class="pull-right">';
	$HTML .= '<a class="comment-up" href="#">支持(<span class="comment-up-count">'.$floorInfoArr[$floorId]['up_count'].'</span>)</a>';
	$HTML .= '<a class="comment-down" href="#">反对(<span class="comment-down-count">'.$floorInfoArr[$floorId]['down_count'].'</span>)</a>';
	$HTML .= '<a href="#" class="comment-report">举报</a><a href="#" class="comment-reply">回复</a>';
	$HTML .= '</div></div></div></blockquote>';
	return $HTML;
}

但是一般来说,使用递归都是被认为是不推荐的事,如果楼层有大大几十上百层,效率可能不是很高。因此,也可以使用下面的方法来拼凑这个嵌套父楼层:

function renderQuote2($floorIdArr, array $floorInfoArr)
{
	$floorId = array_shift($floorIdArr);

	$HTML = '<blockquote class="comment-quote">';
	$HTML .= '<div class="comment-info clearfix"><h5 class="comment-header text-muted">';
	$HTML .= '<span class="comment-username">'.$floorInfoArr[$floorId]['user_name'].'</span>';
	$HTML .= '<span class="comment-address">来自['.$floorInfoArr[$floorId]['address'].']</span>';
	$HTML .= '<span class="comment-floor pull-right">#'.$floorInfoArr[$floorId]['position'].'</span>';
	$HTML .= '</h5><div class="comment-text lead">'.$floorInfoArr[$floorId]['content'].'</div><div class="comment-action clearfix" data-floor-id="'.$floorId.'"><div class="pull-right">';
	$HTML .= '<a class="comment-up" href="#">支持(<span class="comment-up-count">'.$floorInfoArr[$floorId]['up_count'].'</span>)</a>';
	$HTML .= '<a class="comment-down" href="#">反对(<span class="comment-down-count">'.$floorInfoArr[$floorId]['down_count'].'</span>)</a>';
	$HTML .= '<a href="#" class="comment-report">举报</a><a href="#" class="comment-reply">回复</a>';
	$HTML .= '</div></div></div></blockquote>';
	while(!empty($floorIdArr))
	{
		$floorId = array_shift($floorIdArr);
		$HTML = '<blockquote class="comment-quote">'.$HTML;
		$HTML .= '<div class="comment-info clearfix"><h5 class="comment-header text-muted">';
		$HTML .= '<span class="comment-username">'.$floorInfoArr[$floorId]['user_name'].'</span>';
		$HTML .= '<span class="comment-address">来自['.$floorInfoArr[$floorId]['address'].']</span>';
		$HTML .= '<span class="comment-floor pull-right">#'.$floorInfoArr[$floorId]['position'].'</span>';
		$HTML .= '</h5><div class="comment-text lead">'.$floorInfoArr[$floorId]['content'].'</div><div class="comment-action clearfix" data-floor-id="'.$floorId.'"><div class="pull-right">';
		$HTML .= '<a class="comment-up" href="#">支持(<span class="comment-up-count">'.$floorInfoArr[$floorId]['up_count'].'</span>)</a>';
		$HTML .= '<a class="comment-down" href="#">反对(<span class="comment-down-count">'.$floorInfoArr[$floorId]['down_count'].'</span>)</a>';
		$HTML .= '<a href="#" class="comment-report">举报</a><a href="#" class="comment-reply">回复</a>';
		$HTML .= '</div></div></div></blockquote>';
	}
	return $HTML;
}

二者的区别是,前者递归是从外往内拼,后者则是从内往外拼。后者没有递归,在楼层较深时估计会好上一些。

以上,就是个人做的仿网易&cnBeta嵌套评论的方案。他们实际的做法或许是更高效的,但我目前也只想到这个了,数据库查询单表两次,另外就是一些循环,看着也没有什么明显的瓶颈。另外更新支持数反对数之类也比较简单,所有楼层是唯一的,在这一栋被点支持,另一栋里面看到的也是变的。看到的数据有很多重复,但存储的是只有一份的。【完】

DEMO

后记:

大学四年都混的cnBeta。少壮不努力,老大混CB。怀念当初的昆明程序员,他在昆明一间小公司编程,经常说他们公司的矿泉水又换了,问候网友们今天过得开心吗等等。还有安卓sh1t哥,现在都不来喷安卓了,魅族论坛换电池哥,都不用魅族了吧。曾经网友们啊,估计都已经走向人生巅峰了,不会再去那里“开喷”了。。。

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Time limit is exhausted. Please reload CAPTCHA.