如何通过中间件在用户登出时移除在线用户列表记录
我来帮你解决登出时自动移除在线用户的问题!先分析下你当前代码里的几个关键问题,再给出完整的修复方案:
为什么你的信号没生效?
大概率是信号接收器没被Django正确加载,或者中间件里的类属性修改逻辑导致在线列表没有实时更新。另外你通过request.__class__绑定online_now_ids和online_now的方式有问题——这会修改所有HttpRequest实例的类属性,导致不同请求共享同一个在线列表,很容易出现数据不一致。
完整修复方案
第一步:修正中间件逻辑(绑定实例属性而非类属性)
把在线用户数据绑定到request实例上,避免全局污染,同时优化在线列表的更新逻辑:
from django.core.cache import cache from django.conf import settings from django.contrib.auth.models import User from django.utils.deprecation import MiddlewareMixin # 从settings读取配置,设置默认值 ONLINE_THRESHOLD = getattr(settings, 'ONLINE_THRESHOLD', 30) ONLINE_MAX = getattr(settings, 'ONLINE_MAX', 50) class OnlineNowMiddleware(MiddlewareMixin): """维护当前登录的在线用户列表 - request.online_now_ids: 当前在线用户ID列表 - request.online_now: 当前在线用户实例查询集 """ def process_request(self, request): # 获取缓存中的在线用户ID列表 uids = cache.get('online-now', []) # 校验每个用户的在线标记是否有效(过期则移除) online_keys = [f'online-{uid}' for uid in uids] valid_online_keys = cache.get_many(online_keys).keys() online_now_ids = [int(key.replace('online-', '')) for key in valid_online_keys] # 如果用户已认证,更新其在线状态 if request.user.is_authenticated: uid = request.user.id # 把用户移到列表末尾(标记为最新活跃) if uid in online_now_ids: online_now_ids.remove(uid) online_now_ids.append(uid) # 限制列表最大长度,避免缓存过大 if len(online_now_ids) > ONLINE_MAX: del online_now_ids[0] # 更新缓存 cache.set(f'online-{uid}', True, ONLINE_THRESHOLD) cache.set('online-now', online_now_ids, ONLINE_THRESHOLD) # 将在线用户数据绑定到request实例(关键:用实例属性而非类属性) request.online_now_ids = online_now_ids # 直接生成用户查询集,避免延迟属性的潜在问题 request.online_now = User.objects.filter(id__in=online_now_ids)
第二步:正确配置登出信号接收器
信号接收器必须被Django启动时加载才能生效,推荐用以下方式:
- 在你的app目录下创建
signals.py文件,写入接收器逻辑:
from django.core.cache import cache from django.conf import settings from django.dispatch import receiver from django.contrib.auth.signals import user_logged_out ONLINE_THRESHOLD = getattr(settings, 'ONLINE_THRESHOLD', 30) @receiver(user_logged_out) def remove_user_from_online_list(sender, request, user, **kwargs): if user is not None: # 删除用户的在线标记缓存 cache.delete(f'online-{user.pk}') # 更新在线用户ID列表,移除当前登出用户 current_online_uids = cache.get('online-now', []) if user.pk in current_online_uids: current_online_uids.remove(user.pk) cache.set('online-now', current_online_uids, ONLINE_THRESHOLD)
- 修改app的
apps.py,确保信号在Django启动时被加载:
from django.apps import AppConfig class YourAppConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = '你的app名称' def ready(self): # 导入信号模块,触发接收器注册 import 你的app名称.signals
- 在
settings.py中指定app的配置类:
INSTALLED_APPS = [ # ...其他app '你的app名称.apps.YourAppConfig', ]
关键注意事项
- 确保中间件已添加到
settings.py的MIDDLEWARE列表中 - 生产环境建议用Redis/Memcached作为缓存后端(Django默认本地缓存不适合多进程部署)
- 登出时不仅要移除用户ID列表,还要删除该用户的单独在线标记,避免下次请求时又被加回列表
内容的提问来源于stack exchange,提问作者Paddy Popeye
相关产品推荐
相关产品推荐

