本人从事和流程优化的工作,和计算机本无多大关系。后来阴差阳错渐渐自学编程,倒也找到了很多乐趣,当然也有很多坑。
这篇文章将作为记录我开发博客系统遇到的大大小小的坑中的首篇——也是卡住我最多时间思考的地方。
如何制作一个类似网易盖楼的评论系统?
问题就分解成了:
- 如何设计数据库的评论表
- 如何在视图中展示出嵌套的样式
在网上查了一些资料,
- 评论表有的是回复和评论功用一张表,但是必须要指定每一条评论的id以及它的回复对象pid,若是第一条回复,它的回复对象则是null。
- 也有方案是以回复和评论以两张表分开的形式存储
从降低耦合的角度出发,第二种方案更好,在第一种方案中假如需要删除评论,也许就会其他评论造成断档的问题。由于水平有限,我还是采取了第一种方案,理由是建表简单。
但是考虑到上述问题,我也设计了一个评论必须经过审核的逻辑,没有经过审核的评论是无法展示的,也就避免了展示后被删除的可能。
CREATE TABLE "Comment" (
id INTEGER NOT NULL,
uid VARCHAR(50) NOT NULL,
rdr_name VARCHAR(20) NOT NULL,
rdr_mail VARCHAR(20) NOT NULL,
rdr_message VARCHAR(200) NOT NULL,
reply_id VARCHAR(50) NOT NULL,
reply_to_id VARCHAR(50),
message_date DATETIME NOT NULL,
approved BOOLEAN NOT NULL,
PRIMARY KEY (id),
UNIQUE (id),
UNIQUE (reply_id),
CHECK (approved IN (0, 1))
);
其中uid指向文章编号,reply_id会在每一次插入自动生成,reply_to_id就是它回复的评论的id。
有了这样的数据库,接下来就是要考虑如何提取数据的问题了。在一个文章地下,会有数十条评论,每一条评论下面还有层层嵌套的评论。查询的开销非常大,幸运的是,这样的递归查询,已经有了解决方案了——就是使用CTE(Common Table Expression),在我所用的sqlite3中版本号需要 >3.8.3。
首先遍历出所有评论,对每一条评论递归查询出它所有的回复。
with recursive
cte(id, reply_id, reply_to_id, rdr_message, rdr_name, message_date) as (
select id, reply_id, reply_to_id, rdr_message, rdr_name ,message_date from Comment where reply_id = 'fa102480-dd21-11e6-b1ae-f4066974556c' and approved = 1 and uid = '3928f38e-d702-11e6-94'
union all
select Comment.id, Comment.reply_id, Comment.reply_to_id, Comment.rdr_message, Comment.rdr_name, Comment.message_date from Comment join cte on Comment.reply_id = cte.reply_to_id
)
select * from cte
这样就获得了一个自下而上的的评论列表。这样结构的数据很难直接看出相互的继承关系,所以需要进一步处理使其结构嵌套起来,在python后端完成。
def nest(lst):
"""
aim to turn flatten list (which fetched from sql) to nested structure
:param lst: list
:return: nested list
"""
if not lst:
return None
first = lst[0]
del lst[0]
return {"pid": first, "id": nest(lst)}
这个递归函数会形成一个嵌套的字典表示层级关系提供给jinja模板进行递归渲染。
{% macro render_comment(comment, show_btn=True) %}
<li class="comment">
{{ media(comment["pid"], btn=show_btn) }}
{% if comment["id"] %}
<ul class="comment-ul">{{ render_comment(comment["id"], False) }}</ul>
{% endif %}
</li>
{% endmacro %}
其中的media也是一个宏,在我的界面里面我利用了bootstrap的media列表来展示评论,具体细节就不展示了,可以替换为任意的样式。