Django - Как отслеживать, находится ли пользователь в режиме онлайн/офлайн в режиме реального времени?

Я рассматриваю возможность использования django-уведомлений и веб-сокетов для отправки уведомлений в реальном времени в iOS/Android и веб-приложения. Поэтому я, вероятно, буду использовать Django Channels.

Могу ли я использовать каналы Django для отслеживания состояния онлайн-пользователя в режиме реального времени? Если да, то как я могу достичь этого без постоянного опроса сервера?

Я ищу наилучшую практику, так как я не смог найти правильное решение.

ОБНОВЛЕНИЕ:

То, что я пробовал до сих пор, заключается в следующем: используя 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)

ПРИМЕЧАНИЕ.

Мое текущее рабочее решение использует Django REST Framework с конечной точкой API, чтобы клиентские приложения отправляли HTTP-запрос POST с текущим статусом. Например, веб - приложение отслеживает события мыши и постоянно по месту службы online Статус каждые Х секунд, когда нет больше событий мыши не POST на away состояние, когда вкладка/окно собирается быть закрыты, приложение отправляет запрос POST со статусом offline. Это рабочее решение, в зависимости от браузера у меня возникают проблемы при отправке offline состояния, но он работает.

То, что я ищу, - лучшее решение, которое не требует постоянного опроса сервера.

Ответы

Ответ 1

Использование 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)

Ответ 2

Лучший подход - использование 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. Они просто используют набор параметров браузера для генерации хэш-ключа. Это лучше, чем использовать только идентификатор сеанса, потому что он изменяется менее откровенно.

Отслеживание статуса

После каждого действия вы можете просто обновить last_echo. Таким образом, вы можете понять, кто подключен или ушел, и с какого устройства.


Преимущество: В случае сбоя, перезагрузки и т.д. Статус отслеживания может быть восстановлен в любое время.