Django:如何检测会话超时?解决超时未清理登录用户表问题
这个问题我之前也碰到过——手动登出能正常删记录,但会话超时就不行,因为Django的会话过期清理是后台静默执行的,不会触发我们写的登出视图或者user_logged_out信号。不过有几种靠谱的方案能解决这个问题,给你梳理一下:
方法一:利用Session模型的post_delete信号(最推荐)
核心思路是:当Django的clearsessions命令删除过期会话时,会触发Session模型的post_delete信号,我们在这个信号里同步删除对应的在线用户记录。
步骤1:给在线用户表添加session_key字段
首先建议给你的在线用户表(比如叫OnlineUser)加一个session_key字段,用来关联Django的会话表(django_session),这样能精准定位到每个会话对应的用户,避免误删同一用户其他设备的登录记录:
# models.py from django.db import models class OnlineUser(models.Model): username = models.CharField(max_length=150, unique=True) session_key = models.CharField(max_length=40, unique=True) # 关联会话的session_key def __str__(self): return self.username
步骤2:登录时关联会话
在用户登录时,不仅要把用户名存入OnlineUser,还要把当前会话的session_key也存进去。可以用user_logged_in信号来实现:
# signals.py from django.contrib.auth.signals import user_logged_in from django.dispatch import receiver from .models import OnlineUser @receiver(user_logged_in) def add_online_user_on_login(sender, request, user, **kwargs): # 先删除该用户可能存在的旧记录(比如同一用户之前的登录会话) OnlineUser.objects.filter(username=user.username).delete() # 创建新的在线用户记录,关联当前会话 OnlineUser.objects.create( username=user.username, session_key=request.session.session_key )
步骤3:监听Session删除信号
当过期会话被clearsessions命令删除时,触发post_delete信号,同步删除对应的在线用户记录:
# signals.py 继续添加 from django.db.models.signals import post_delete from django.contrib.sessions.models import Session @receiver(post_delete, sender=Session) def remove_online_user_on_session_delete(sender, instance, **kwargs): # 根据被删除的会话key,清理在线用户表 OnlineUser.objects.filter(session_key=instance.session_key).delete()
步骤4:注册信号
别忘了在app的apps.py里注册信号,确保项目启动时信号能被加载:
# apps.py from django.apps import AppConfig class YourAppConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'your_app_name' def ready(self): import your_app_name.signals # 导入信号模块
步骤5:确保clearsessions定时运行
Django默认不会自动运行clearsessions,你需要设置定时任务:
- 如果用Linux,可以加个cron任务,每天执行一次:
0 0 * * * /path/to/your/python /path/to/your/manage.py clearsessions - 如果用Django的第三方定时任务库(比如
django-crontab),可以在settings里配置:
CRONJOBS = [ ('0 0 * * *', 'django.core.management.call_command', ['clearsessions']), ]
方法二:自定义会话后端
如果需要更精细化的会话过期控制,可以自定义SessionStore,重写过期逻辑:
# backends.py from django.contrib.sessions.backends.db import SessionStore from .models import OnlineUser class CustomSessionStore(SessionStore): def expire(self): # 先执行父类的过期逻辑 super().expire() # 清理对应的在线用户记录 OnlineUser.objects.filter(session_key=self.session_key).delete()
然后在settings.py里指定自定义的会话引擎:
SESSION_ENGINE = 'your_app_name.backends.CustomSessionStore'
这个方法适合需要在会话过期时做额外操作的场景,但相对复杂一点。
方法三:定时任务直接清理过期用户
如果上面的信号方案不适用,也可以写一个定时任务,定期扫描过期会话并清理对应的在线用户:
步骤1:编写自定义管理命令
# your_app/management/commands/clean_expired_online_users.py from django.core.management.base import BaseCommand from django.contrib.sessions.models import Session from django.utils import timezone from .models import OnlineUser class Command(BaseCommand): help = '清理会话已过期的在线用户记录' def handle(self, *args, **options): # 获取所有过期的会话 expired_sessions = Session.objects.filter(expire_date__lt=timezone.now()) # 提取这些会话的key列表 expired_session_keys = expired_sessions.values_list('session_key', flat=True) # 删除对应的在线用户记录 deleted_count, _ = OnlineUser.objects.filter(session_key__in=expired_session_keys).delete() self.stdout.write(self.style.SUCCESS(f"成功清理{deleted_count}条过期在线用户记录"))
步骤2:设置定时任务
和clearsessions一样,把这个命令加入定时任务,比如每天执行一次,就能自动清理过期的在线用户记录了。
内容的提问来源于stack exchange,提问作者bSr

