Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

用户通知系统设计 #6

Open
codetalks-new opened this issue Feb 20, 2019 · 1 comment
Open

用户通知系统设计 #6

codetalks-new opened this issue Feb 20, 2019 · 1 comment

Comments

@codetalks-new
Copy link
Owner

codetalks-new commented Feb 20, 2019

在用户创建的主题回收回复,收到感谢,或者在一些回复中被 @ 了。需要通知用户。

数据模型设计

当前的通知类型有如下一些:

  • 收到回复
  • 收到@
  • 收到感谢
  • 创建主题被收藏
  • 关注的用户有了新动态
  • 关注的节点有了新主题

通知消息类别与位操作

这些用户又有不同的重要程度,用户应该可以忽略不重要的消息。
由于这些消息种类不是特别多。可以使用位运算方便后面操作与查询 。
使用 64 位的整型存储可以支持最多64种不同类型的通知。对于此类系统来说完全足够了。
一个用户要面对 64 个开关也是够了。

当前针对上面的通知类型,细化声明如下:

class NotificationCategory(IntFlag, EnumChoiceMixin):
  RECEIVE_REPLY = 1 << 0  # 收到回复
  RECEIVE_MENTION = 1 << 1  # 收到 @
  RECEIVE_REPLY_LIKE = 1 << 2  # 收到回复感谢
  RECEIVE_POST_LIKE = 1 << 3  # 收到主题感谢
  RECEIVE_REPLY_FAV = 1 << 4  # 收到回复被收藏
  RECEIVE_POST_FAV = 1 << 5  # 收到主题被收藏
  FOLLOWED_USER_NEW_REPLY = 1 << 6  # 关注用户有了新回复
  FOLLOWED_USER_NEW_POST = 1 << 7  # 关注用户创建了新主题
  FOLLOWED_POST_NEW_REPLY = 1 << 8  # 关注的主题有了新回复
  FOLLOWED_NODE_NEW_POST = 1 << 9  # 关注的节点有了新主题

  @property
  def label(self):
    return _notification_category_to_text[self]


_notification_category_to_text = {
  NotificationCategory.RECEIVE_REPLY: "收到回复",
  NotificationCategory.RECEIVE_MENTION: "收到@",
  NotificationCategory.RECEIVE_REPLY_LIKE: "收到回复感谢",
  NotificationCategory.RECEIVE_POST_LIKE: "收到主题感谢",
  NotificationCategory.RECEIVE_REPLY_FAV: "收到回复收藏",
  NotificationCategory.RECEIVE_POST_FAV: "收到主题收藏",
  NotificationCategory.FOLLOWED_USER_NEW_REPLY: "关注用户有了新回复",
  NotificationCategory.FOLLOWED_USER_NEW_POST: "关注用户创建了新主题",
  NotificationCategory.FOLLOWED_POST_NEW_REPLY: "关注的主题有了新回复",
  NotificationCategory.FOLLOWED_NODE_NEW_POST: "关注的节点有了新主题",

}
@codetalks-new
Copy link
Owner Author

通知消息的数据模型设计思路

通知的数据模型参考 django-notifications. 其主要思路,以几个通知消息作为实例分析如下:

  1. 消息1: 您关注的用户张三在 Django 节点创建了一个新的主题《Django 进阶指南》
    在这条消息中,可以分解如下:
  • 事件发起者: "张三“
  • 事件动作: 创建主题
  • 事件动作结果:创建了新的主题
  • 事件目标范围:Django 节点
  1. 消息2: 您关注的主题 《Django 进阶指南》发表了新的回复:"不错,赞👍🏻"
    这个消息,直接一看没有事件的发起人,但是仔细一想却也是有的,因为一条回复总是由某一个人创建的。
    所以在消息通知中把事件发起人也写进来可以写成这样:
    李四在您关注的主题《Django 进阶指南》发表了新的回复:"不错,赞👍🏻"
    此时消息分解如下:
  • 事件发起人: 李四
  • 事件动作: 发表回复
  • 事件动作结果: 发表了回复
  • 事件目标范围: 主题《Django 进阶指南》
  1. 消息3: 您发表的主题 《Django 进阶指南》被系统自动评选为本日热门
    在前两种消息示例中,事件发起人有对应的实体,事件动作结果也有对应的实体。
    在这条消息中,可以认为并没有事件发起人,事件动作结果也没有对应的实体。
    这条消息示例要说明的是,事件发起人,和事件动作结果都可能为空。

  2. 消息4:为庆祝建站1周年,系统向您赠送了100铜币
    在这条消息中,可以认为事件目标范围也没有对应的实体。

Django 中泛型外键

在上面的示例中,我们事件动作结果对应的是不同实体的。一个对应“主题",一个对应 "回复"
针对这种外键关联,Django 的 contenttypes 框架提供了 GenericForeignKey
我们用 action_object 表示 事件动作结果。那关联事件动作结果时,需要这样写:

  action_object_content_type = models.ForeignKey(ContentType, blank=True, null=True,
                                                 related_name='notify_action_object', on_delete=models.CASCADE)
  action_object_object_id = models.CharField(max_length=255, blank=True, null=True)
  action_object = GenericForeignKey('action_object_content_type', 'action_object_object_id')

content_type 对应实体类型。 object_id 对应实体的 ID。

数据模型的基本实现

命名对应如下:

  • 事件发起者: actor
  • 事件动作: 通过 category 区别。
  • 事件动作结果: action_object
  • 事件动作目标范围: target
class Notification(BaseModel):
  """通知 模型设计参考自 django-notifications"""
  recipient = models.ForeignKey(WepostUser, on_delete=models.CASCADE, verbose_name="接收人")
  # django-notifications 中的 verb 一内容就体现在 category 中.
  category = models.BigIntegerField("类别", choices=NotificationCategory.choices())
  level = models.PositiveSmallIntegerField("级别", choices=NotificationLevel.choices(), default=NotificationLevel.INFO)
  unread = models.BooleanField("未读", db_index=True,default=True)
  # 配置泛型 actor 外键关联
  actor_content_type = models.ForeignKey(ContentType, blank=True, null=True, related_name="notify_actor",
                                         on_delete=models.CASCADE)
  actor_object_id = models.CharField(max_length=255, blank=True, null=True)
  actor = GenericForeignKey('actor_content_type', 'actor_object_id')

  # 配置事件发生范围,如果是指发帖子的话,可以认为是关联到对应节点.
  target_content_type = models.ForeignKey(
    ContentType,
    related_name='notify_target',
    blank=True,
    null=True,
    on_delete=models.CASCADE
  )
  target_object_id = models.CharField(max_length=255, blank=True, null=True)
  target = GenericForeignKey('target_content_type', 'target_object_id')

  # 配置事件发生主体,以发贴来说,可以当作关联到具体的帖子
  action_object_content_type = models.ForeignKey(ContentType, blank=True, null=True,
                                                 related_name='notify_action_object', on_delete=models.CASCADE)
  action_object_object_id = models.CharField(max_length=255, blank=True, null=True)
  action_object = GenericForeignKey('action_object_content_type', 'action_object_object_id')

  memo = models.TextField("备注", blank=True, null=True, help_text="可以用来保存简短的回复等内容")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant