Django - 如何实时跟踪用户是否 online/offline?
Django - How to track if a user is online/offline in realtime?
我正在考虑使用 django-notifications 和 Web 套接字向 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 应用程序跟踪鼠标事件并每隔 X 秒持续 POST online
状态,当没有更多鼠标事件时 POST away
状态,当 tab/window 即将关闭,应用程序发送一个 POST 状态为 offline
的请求。
这是一个有效的解决方案,具体取决于浏览器我在发送 offline
状态时遇到问题,但它有效。
我正在寻找一个更好的解决方案,不需要不断地轮询服务器。
使用 WebSockets 绝对是更好的方法。
您可以计算连接数,而不是二进制 "online"/"offline" 状态:当新的 WebSocket 连接时,将 "online" 计数器加一,当 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)
最好的方法是使用 Websockets。
但我认为您不仅应该存储状态,还应该存储 会话密钥 或 设备标识 。如果您只使用计数器,您将丢失有价值的信息,例如,用户在特定时刻连接的是什么设备。这在某些项目中很关键。此外,如果发生错误(断开连接、服务器崩溃等),您将无法跟踪与每个设备相关的计数器,并且可能需要在最后重置计数器。
我建议您将此信息存储在另一个相关的 table:
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 那样做。他们只是使用一组浏览器参数来生成哈希键。比单独使用 session id 好,因为它变化不频繁。
跟踪离开状态
在每个 操作后 ,您可以简单地更新 last_echo
。通过这种方式,您可以了解谁已连接或离开,以及来自什么设备。
优点:万一出现死机、重启等情况,可以随时重新建立跟踪状态
我的回答是根据C14L的回答。计算连接数的想法非常聪明。我只是做了一些改进,至少在我的情况下。比较乱和复杂,但是我觉得很有必要
有时,WebSocket 连接的次数多于断开的次数,例如,当它出现错误时。这使得连接不断增加。我的方法不是在 WebSocket 打开时增加连接,而是在用户访问页面之前增加它。当 WebSocket 断开连接时,我减少连接
在views.py
def homePageView(request):
updateOnlineStatusi_goIn(request)
# continue normal code
...
def updateOnlineStatusi_goIn(request):
useri = request.user
if OnlineStatus.objects.filter(user=useri).exists() == False:
dct = {
'online': False,
'connections': 0,
'user': useri
}
onlineStatusi = OnlineStatus.objects.create(**dct)
else:
onlineStatusi = OnlineStatus.objects.get(user=useri)
onlineStatusi.connections += 1
onlineStatusi.online = True
onlineStatusi.save()
dct = {
'action': 'updateOnlineStatus',
'online': onlineStatusi.online,
'userId': useri.id,
}
async_to_sync(get_channel_layer().group_send)(
'commonRoom', {'type': 'sendd', 'dct': dct})
在models.py
class OnlineStatus(models.Model):
online = models.BooleanField(null=True, blank=True)
connections = models.BigIntegerField(null=True, blank=True)
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True)
在consummers.py
class Consumer (AsyncWebsocketConsumer):
async def sendd(self, e): await self.send(json.dumps(e["dct"]))
async def connect(self):
await self.accept()
await self.channel_layer.group_add('commonRoom', self.channel_name)
async def disconnect(self, _):
await self.channel_layer.group_discard('commonRoom', self.channel_name)
dct = await updateOnlineStatusi_goOut(self)
await self.channel_layer.group_send(channelRoom, {"type": "sendd", "dct": dct})
@database_sync_to_async
def updateOnlineStatusi_goOut(self):
useri = self.scope["user"]
onlineStatusi = OnlineStatus.objects.get(user=useri)
onlineStatusi.connections -= 1
if onlineStatusi.connections <= 0:
onlineStatusi.connections = 0
onlineStatusi.online = False
else:
onlineStatusi.online = True
onlineStatusi.save()
dct = {
'action': 'updateOnlineStatus',
'online': onlineStatusi.online,
'userId': useri.id,
}
return dct
我正在考虑使用 django-notifications 和 Web 套接字向 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 应用程序跟踪鼠标事件并每隔 X 秒持续 POST online
状态,当没有更多鼠标事件时 POST away
状态,当 tab/window 即将关闭,应用程序发送一个 POST 状态为 offline
的请求。
这是一个有效的解决方案,具体取决于浏览器我在发送 offline
状态时遇到问题,但它有效。
我正在寻找一个更好的解决方案,不需要不断地轮询服务器。
使用 WebSockets 绝对是更好的方法。
您可以计算连接数,而不是二进制 "online"/"offline" 状态:当新的 WebSocket 连接时,将 "online" 计数器加一,当 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)
最好的方法是使用 Websockets。
但我认为您不仅应该存储状态,还应该存储 会话密钥 或 设备标识 。如果您只使用计数器,您将丢失有价值的信息,例如,用户在特定时刻连接的是什么设备。这在某些项目中很关键。此外,如果发生错误(断开连接、服务器崩溃等),您将无法跟踪与每个设备相关的计数器,并且可能需要在最后重置计数器。
我建议您将此信息存储在另一个相关的 table:
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 那样做。他们只是使用一组浏览器参数来生成哈希键。比单独使用 session id 好,因为它变化不频繁。
跟踪离开状态
在每个 操作后 ,您可以简单地更新 last_echo
。通过这种方式,您可以了解谁已连接或离开,以及来自什么设备。
优点:万一出现死机、重启等情况,可以随时重新建立跟踪状态
我的回答是根据C14L的回答。计算连接数的想法非常聪明。我只是做了一些改进,至少在我的情况下。比较乱和复杂,但是我觉得很有必要
有时,WebSocket 连接的次数多于断开的次数,例如,当它出现错误时。这使得连接不断增加。我的方法不是在 WebSocket 打开时增加连接,而是在用户访问页面之前增加它。当 WebSocket 断开连接时,我减少连接
在views.py
def homePageView(request):
updateOnlineStatusi_goIn(request)
# continue normal code
...
def updateOnlineStatusi_goIn(request):
useri = request.user
if OnlineStatus.objects.filter(user=useri).exists() == False:
dct = {
'online': False,
'connections': 0,
'user': useri
}
onlineStatusi = OnlineStatus.objects.create(**dct)
else:
onlineStatusi = OnlineStatus.objects.get(user=useri)
onlineStatusi.connections += 1
onlineStatusi.online = True
onlineStatusi.save()
dct = {
'action': 'updateOnlineStatus',
'online': onlineStatusi.online,
'userId': useri.id,
}
async_to_sync(get_channel_layer().group_send)(
'commonRoom', {'type': 'sendd', 'dct': dct})
在models.py
class OnlineStatus(models.Model):
online = models.BooleanField(null=True, blank=True)
connections = models.BigIntegerField(null=True, blank=True)
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True)
在consummers.py
class Consumer (AsyncWebsocketConsumer):
async def sendd(self, e): await self.send(json.dumps(e["dct"]))
async def connect(self):
await self.accept()
await self.channel_layer.group_add('commonRoom', self.channel_name)
async def disconnect(self, _):
await self.channel_layer.group_discard('commonRoom', self.channel_name)
dct = await updateOnlineStatusi_goOut(self)
await self.channel_layer.group_send(channelRoom, {"type": "sendd", "dct": dct})
@database_sync_to_async
def updateOnlineStatusi_goOut(self):
useri = self.scope["user"]
onlineStatusi = OnlineStatus.objects.get(user=useri)
onlineStatusi.connections -= 1
if onlineStatusi.connections <= 0:
onlineStatusi.connections = 0
onlineStatusi.online = False
else:
onlineStatusi.online = True
onlineStatusi.save()
dct = {
'action': 'updateOnlineStatus',
'online': onlineStatusi.online,
'userId': useri.id,
}
return dct