简单的场景:比如是一个客户反馈系统,客户反馈的时候,我们需要针对一个话题进行回复,话题在客服和客户之间轮转回复,但并没有强制的限制要求客户和客服只能一问一答,客户和客服都可以回复多次。
复杂的场景就是如贴吧,论坛等,可以针对某个回复进行插楼回复,这种针对一个热门话题很有用,大家可以针对自己的爱好,进行插楼回复,忽略不感兴趣的话题。
先来分析第一个简单的场景,默认不支持插楼的场景
,类似QQ聊天的场景,只有简单的二级关系,一级对应话题,所有的回复都是二级,在二级内都是平级关系,但是拥有先后关系,每一个回复是有先后顺序的。
简单的场景覆盖:
- 体现回复的内容
- 体现回复的先后顺序
- 可辨别回复人身份
- 支持消息状态标识
简单ER图
根据上述的描述,我们简单设计一下的存储细节:
上述结构中,我们存储了一下字段:
Column | Type | Description |
---|---|---|
create_time | bigint | 消息创建时间 |
status | int | 消息状态 |
user_id | vchar(16) | 用户ID |
parent_id | foreign key | 回复消息ID |
content | text | 消息内容 |
通过上述简单的设计,我们可以实现我们的需求,通过self - self
的外键关系,我们可以通过索引是否是空来辨别那些是需求,那些是回复内容(跟帖);通过外键我们也能快速查看一个需求的所有回复,最终通过create_time来快速排序;通过user_id来获取消息创建人的角色,辨别是用户还是admin。
上面这个简单的设计中,我们主要通过表的自外键连接来快速查找到一个话题的所有的回复,但是标准的做法应该是建立一张话题表,然后再来一章评论表,评论表通过外键与话题表进行关联。
这个设计上实际上和上面的并无差异,只是把话题单独抽象成一个表进行存储,适合数据量更加大的情形。
在上述设计的基础上,我来可以再来看看带有插楼功能的话题回复应该如何设计。带有插楼功能的情形是我们不仅要区分回复属于那个话题,还需要知道他的前置回复是谁,不是简单的二级关系,我们可能遭遇多级的情况,比如某一楼的回复比较有意思,那么就可能出现盖楼
的情况,存在多级的情况;另外还需要考虑在同一级楼的前后顺序。
上述设计中,只是增加了回复表的自外键参考结构,起的作用就是可以通过这个关系查看到当前回复的父级消息是谁,最终构造出复杂的多级关系,唯一的问题是,不太好输出最终的数据结构,解析困难。
Django 设计
针对第一种场景,我们做一下实例:
import time
from django.db import models
class Comments(models.Model):
"""user comments """
STATUS = (
(0, u"创建"),
(1, u"未读"),
(2, u"查看"),
(3, u"关闭")
)
objects = CommentsManager() # 对象管理器
create_time = models.BigIntegerField(default=time.time()) # 创建时间
status = models.IntegerField(default=0, choices=STATUS) # 帖子状态
user_id = models.CharField(max_length=16, null=False, blank=False) # 创建用户
parent_id = models.Foreign_key("self", default=None, null=True, related_name="comments") # 参考话题
content = models.TextField(blank=False, null=False) # 回复内容
class CommentsManager(models.Manager):
"""comments manager"""
def get_queryset(self):
"""
Get Default Queryset
:return:
"""
return super(TemplateManager, self).get_queryset()
def get_all_topics(self):
"""
Get all topics
"""
return self.get_queryset().filter(parent_id=None).values(
"status", "user_id", "status", "content")
def get_all_comments_by_tid(self, tid):
"""
Get all comments by parent id
:param tid:
"""
topic = self.get_queryset().get(pk=tid)
return topic.comments().values(
"status", "user_id", "status", "content")
上述实例只是测试了没有插楼的情形,插楼的情况下,需要我们创建两个表,如果有需要可以上述示例的基础上进行扩展。