【问题标题】:Django - How to track if a user is online/offline in realtime?Django - 如何实时跟踪用户是否在线/离线?
【发布时间】:2019-01-26 14:25:02
【问题描述】:

我正在考虑使用 django-notifications 和 Web Sockets 向 iOS/Android 和 Web 应用程序发送实时通知。所以我可能会使用 Django Channels

我可以使用 Django Channels 来实时跟踪用户的在线状态吗?如果是,那么我如何在不不断轮询服务器的情况下实现这一点?

我正在寻找最佳做法,因为我找不到任何合适的解决方案。

更新

到目前为止,我尝试过的是以下方法: 使用 Django Channels,我实现了一个 WebSocket 消费者,它在连接时会将用户状态设置为'online',而当套接字断开连接时,用户状态将设置为'offline'。 最初我想包含'away' 状态,但我的方法无法提供那种信息。 此外,当用户从多个设备使用应用程序时,我的实现将无法正常工作,因为连接可以在一个设备上关闭,但仍然在另一个设备上打开;即使用户有另一个打开的连接,状态也会设置为'offline'

class MyConsumer(AsyncConsumer):

    async def websocket_connect(self, event):
        # Called when a new websocket connection is established
        print("connected", event)
        user = self.scope['user']
        self.update_user_status(user, 'online')

    async def websocket_receive(self, event):
        # Called when a message is received from the websocket
        # Method NOT used
        print("received", event)

    async def websocket_disconnect(self, event):
        # Called when a websocket is disconnected
        print("disconnected", event)
        user = self.scope['user']
        self.update_user_status(user, 'offline')

    @database_sync_to_async
    def update_user_status(self, user, status):
        """
        Updates the user `status.
        `status` can be one of the following status: 'online', 'offline' or 'away'
        """
        return UserProfile.objects.filter(pk=user.pk).update(status=status)

注意

我当前的工作解决方案是使用带有 API 端点的 Django REST 框架,让客户端应用程序发送具有当前状态的 HTTP POST 请求。 例如,Web应用程序跟踪鼠标事件并在online status oder x秒后,当没有更多的鼠标事件发布away status时,当标签/窗口即将关闭时,该应用程序会发送a状态为 offline 的 POST 请求。 这是一个可行的解决方案,具体取决于浏览器我在发送offline 状态时遇到问题,但它可以工作。

我正在寻找一种更好的解决方案,不需要不断地轮询服务器。

【问题讨论】:

    标签: django django-channels django-notification


    【解决方案1】:

    最好的方法是使用 Websockets。

    但我认为您不仅应该存储状态,还应该存储会话密钥设备标识。如果您只使用计数器,则会丢失有价值的信息,例如,用户在特定时刻连接的设备是什么。这是一些项目的关键。此外,如果发生错误(断开连接、服务器崩溃等),您将无法跟踪与每个设备相关的计数器,并且可能需要在最后重置计数器。

    我建议您将此信息存储在另一个相关表中:

    from django.db import models
    from django.conf import settings
    
    
    class ConnectionHistory(models.Model):
        ONLINE = 'online'
        OFFLINE = 'offline'
        STATUS = (
            (ONLINE, 'On-line'),
            (OFFLINE, 'Off-line'),
        )
        user = models.ForeignKey(
            settings.AUTH_USER_MODEL,
            on_delete=models.CASCADE
        )
        device_id = models.CharField(max_lenght=100)
        status = models.CharField(
            max_lenght=10, choices=STATUS,
            default=ONLINE
        )
        first_login = models.DatetimeField(auto_now_add=True)
        last_echo = models.DatetimeField(auto_now=True)
    
        class Meta:
            unique_together = (("user", "device_id"),)
    

    通过这种方式,您可以为每台设备创建一条记录,以跟踪其状态以及一些其他信息,例如 ip 地址、地理位置等。然后您可以执行以下操作(根据您的代码):

    @database_sync_to_async
    def update_user_status(self, user, device_id, status):
        return ConnectionHistory.objects.get_or_create(
            user=user, device_id=device_id,
        ).update(status=status)
    

    如何获取设备标识

    有很多图书馆都像https://www.npmjs.com/package/device-uuid 这样。他们只是使用一组浏览器参数来生成哈希键。它比单独使用会话 id 更好,因为它的变化不那么频繁。

    跟踪离开状态

    在每个操作之后,您可以简单地更新last_echo。通过这种方式,您可以了解谁连接或离开了哪些设备。


    优点:如果发生崩溃、重启等情况,可以随时重新建立跟踪状态。

    【讨论】:

    • device_id 是如何确定的?我的意思是从scope 变量我们可以得到session cookie 但不是设备ID。
    • 有一些库可以实现这个(客户端),比如npmjs.com/package/device-uuid。他们通常使用一组浏览器参数(user_agent 等)来构建哈希。但正如我所说,您也可以使用session_id 而不是device_id 甚至两者都使用。每个设备的会话 id 已经是唯一的,但与 device_id 不同的是,每次重新登录后都会更改,因此如果您使用 session_id 而不是 device_id,您的 ConnectionHistory 可能会变得更大,并且您可能需要偶尔删除旧记录。
    【解决方案2】:

    使用 WebSockets 绝对是更好的方法。

    您可以计算连接数,而不是具有二进制“在线”/“离线”状态:当一个新的 WebSocket 连接时,将“在线”计数器加一,当一个 WebSocket 断开连接时,减少它。因此,当它是 0 时,用户在所有设备上都处于离线状态。

    类似的东西

    @database_sync_to_async
    def update_user_incr(self, user):
        UserProfile.objects.filter(pk=user.pk).update(online=F('online') + 1)
    
    @database_sync_to_async
    def update_user_decr(self, user):
        UserProfile.objects.filter(pk=user.pk).update(online=F('online') - 1)
    

    【讨论】:

    • 是的,您的解决方案有效。我如何跟踪“离开”状态?
    • 如果您将“离开”定义为“用户在过去 5 分钟内没有在任何连接的设备上执行任何操作”,您需要记住最后一次操作的时间使用的设备。因此,只需在其中放置最后一个“动作”的时间戳的第二个 UserProfile 属性。当然,这也取决于您所说的“用户操作”。发文?或者移动鼠标(可以使用 JavaScript 通过打开的 WebSocket 将操作传达给后端)?
    • 我会等赏金到期后再分配你+50让其他用户分享其他答案
    • 您可以在 Django 应用程序启动时重置所有用户的计数器。
    • 连接状态计数是否应该存储在数据库中?我认为没有必要坚持下去。计数不能存储在会话变量或内存缓存中吗?
    猜你喜欢
    • 2012-01-17
    • 2017-05-03
    • 1970-01-01
    • 1970-01-01
    • 2010-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多