Welcome to my blog. You can post whatever you like!

The blog post is using markdown, and here is a simple guide. Markdown Guide

LOGIN TO POST A BLOG

消息提醒系统的实现

Satori

Author: Satori

Posted: 2017-08-22 10:54:10

Category: django 技术 消息提醒

Views: 541 Comments: 4

## 一、背景 消息系统是困扰我比较长时间的一个功能。试想了许多种方法,但是都不理想。最后经过一段时间的思考,总算找到了相对较好的解决方法。 首先需要知道什么是消息,消息就是用户感兴趣的内容有更新时的一个通知。用户感兴趣的内容目前看就两大类,一个是用户订阅的某作者发表了新的文章时,需要通知用户。另一个是当有人回复某用户时,包括回复用户的博客以及评论,通知用户。 可见消息和某一类信息源直接相关,另外用户希望知道哪些是未知消息,哪些是已读消息。有了这些需求之后,就可以设计数据结构来存储消息了。 ## 二、数据模型设计 对于消息而言,首先需要知道这条消息是给谁的,因此需要有一个外键指向用户。其次,要判断用户是否已经读过这条消息,因此需要一个布尔域记录这条消息是否被读过。然后需要知道这条消息的内容。这里选择使用一个generic foreign key来指向任意对象,同时需要额外的域来记录那个对象的类型以及id,因此整个消息的数据模型定义如下。 ``` class Notification(models.Model): unread = models.BooleanField(default=True) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') def __str__(self): return str(self.content_object) ``` ## 三、消息机制的实现 ### 1. 消息的生成 消息的生成是通过用户发博客和回复来触发的。当某人发了一条博客之后,需要查询谁关注了这名博主,然后向这些用户每人增添一条未读消息。同理,当某人回复某条博客或者评论的时候,需要给博客的作者或者评论的作者增添一条未读消息。 为了不改变原有逻辑,使用signal进行触发,将以上逻辑包装进处理函数中,再把对象和处理函数连接到一起,即可完成触发。 增添的逻辑如下。 首先是当有新回复的处理函数 ``` def post_save_comment_receiver(sender, instance, created, *args, **kwargs): if created: # for comment post to a blog if instance.parent is None and instance.blog.author != instance.author: Notification.objects.create(content_object=instance, user=instance.blog.author) # else if comment post to another comment elif instance.parent is not None and instance.parent.author != instance.author: Notification.objects.create(content_object=instance, user=instance.parent.author) ``` 然后是有新博客的处理函数 ``` def post_save_blog_receiver(sender, instance, created, *args, **kwargs): if created: # send notification to all followers author = instance.author relations = author.fan.all() fans = [relation.fan for relation in relations] for fan in fans: Notification.objects.create(content_object=instance, user=fan) ``` 然后将信号与对象连到一起 ``` post_save.connect(post_save_comment_receiver, sender=Comment) post_save.connect(post_save_blog_receiver, sender=Blog) ``` ### 2. 消息的阅读 当消息被阅读时将消息标记为已读,方法为发送ajax请求,触发条件为点击get detail,ajax请求如下。 ``` $(".read-reply").bind('click', function(event){ var notification = $(this).attr("id"); var notification_id = notification.split("_")[1]; $.ajax({ type: "GET", url: "{% url 'read_notification' %}", data: {"notification_id": notification_id}, success: function(result) { ajax_unread_replies_count(); get_notification_count(); ajax_comment_received(); $(this).click(); } }); }); ``` 最后收到响应后需要跳转到指定页面。 ### 3. 消息的显示 即显示有多少条消息,有两个地方需要显示,一个是导航栏,显示一共有几条消息,另一个是用户面板,显示回复的条数和订阅新博客的条数,也是通过发ajax请求得到数目,请求如下。 首先是未读回复消息数的ajax请求。 ``` function ajax_unread_replies_count() { $.ajax({ type: "GET", url: "{% url 'unread_comment_count' %}", success: function(result) { var count = result['count']; if (count > 0) { $('#unread-replies').html(count); } else { $('#unread-replies').html(""); } } }); } ``` 然后是新订阅博客消息数的ajax请求。 ``` function ajax_unread_blogs_count() { $.ajax({ type: "GET", url: "{% url 'unread_blog_count' %}", success: function(result) { var count = result['count']; if (count > 0) { $('#unread-blogs').html(count); } else { $('#unread-blogs').html(""); } } }); } ``` 具体的条目也是通过ajax请求来发送,收到的响应为具体的html代码。 先是收到回复内容的ajax请求。 ``` function ajax_comment_received() { $.ajax({ type: "GET", url: "{% url 'user_comment_received' %}", data: {"user_id": {{ request.user.id }} }, success: function(result) { $('#comment-received').html(result); }, error: function(jqXHR, exception) { $('#comment-received').html(exception); } }); } ``` 然后是新订阅博客内容的ajax请求。 ``` function ajax_subscribe_blogs_content() { $.ajax({ type: "GET", url: "{% url 'unread_blogs' %}", data: {"user_id": {{ request.user.id }} }, success: function(result) { $('#blog-subscriptions-div').html(result); update_marked_truncate(); $('pre code').each(function(i, block) { hljs.highlightBlock(block); }); }, error: function(jqXHR, exception) { $('#blog-subscriptions-div').html(exception); } }); } ``` ### 4. 消息的删除 虽然现在博客并不提供删除博客和评论操作(本来就没多少,要是能删除区不是更少QAQ), 但是以防万一还是需要处理博客或者评论被删除的情况。原理还是一样, 通过博客或者评论的id来筛选消息,然后将这些消息全部删除。删除需要用content_type, 可以使用动态的方法获得type, 再根据消息源的id就可以删除了,最后用信号将模型和函数进行连接,代码如下。 ``` def post_delete_notification_receiver(sender, instance, *args, **kwargs): ctype = ContentType.objects.get_for_model(instance) Notification.objects.filter(content_type=ctype).filter(object_id=instance.id).delete() post_delete.connect(post_delete_notification_receiver, sender=Comment) post_delete.connect(post_delete_notification_receiver, sender=Blog) ``` ## 四、消息处理的方法 前一节已经讲到了发送请求的部分,这一节讲如何实现ajax请求的url。 ### 1. 未读消息数目的实现 这个比较简单,主要是通过用户找到对应的消息,然后对消息进行filter, 找出未读的消息就行了。对于新订阅的博客,只需再filter一下,指定类型为博客,对于评论,则指定类型为评论就行了。 先是总未读消息的处理。 ``` @login_required def get_unread_notification_count(request): if request.is_ajax(): count = request.user.notification_set.filter(unread=True).count() return JsonResponse({"count": count}) else: raise Http404("Page not found!") ``` 然后是未读评论总数的处理。 ``` @login_required def get_unread_comment_count(request): if request.is_ajax(): count = request.user.notification_set.filter(unread=True).filter(content_type__model='comment').count() return JsonResponse({"count": count}) else: raise Http404("Page not found!") ``` 最后是未读订阅博客数量的处理。 ``` @login_required def get_unread_blog_count(request): if request.is_ajax(): count = request.user.notification_set.filter(unread=True).filter(content_type__model='blog').count() return JsonResponse({"count": count}) else: raise Http404("Page not found!") ``` ### 2. 标记消息为已读的实现 这个实现也比较简单,主要是通过消息id找到对应消息,然后将unread置为false就行了,不过需要验证请求用户是否是消息用户,不然就可以读别人的未读消息了,代码如下。 ``` @login_required def read_notification(request): if request.is_ajax(): notification_id = request.GET.get("notification_id") notification = get_object_or_404(Notification, id=notification_id) if request.user == notification.user: notification.unread = False notification.save() return JsonResponse({"success": True}) return JsonResponse({"success": False}) else: raise Http404("Page not found!") ``` ## 五、总结 消息系统是博客重要的组成部分。我通过具体的数据模型存储消息,并通过signal触发方式生成消息,通过ajax请求发送得到消息以及标记消息。这个解决方案的瓶颈在于当数据量足够大的时候,得到响应的速度会很慢,不过目前只能想到这个解决方法,以后如果想到更好的方法而且有足够动力的话会继续更新(多半不会)。

Read More