Django非活动中间件无法自动登出用户问题求助
问题描述
我希望用户在10分钟无活动后自动登出并更新后端的isCurrentlyActive字段,但当前的实现仅在页面刷新或向服务器发送请求时才生效。以下是我的中间件代码及settings.py配置,请问如何实现无需请求即可自动触发登出与后端字段更新?
中间件代码
from datetime import datetime, timedelta from django.contrib.auth import logout from codera_main.models import UserDetail class InactivityMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): # 检查用户是否已认证 if request.user.is_authenticated: # 检查会话是否已过期 if request.session.get_expiry_age() <= 0: # 登出用户并标记为非活跃 user_detail = UserDetail.objects.get(user=request.user) if user_detail.isCurrentlyActive: user_detail.isCurrentlyActive = False user_detail.save() print(f"User {user_detail.user} is now inactive and logged out.") logout(request) else: # 从会话中获取最后活动时间戳 last_activity = request.session.get('last_activity') # 检查最后活动时间戳是否存在且有效 if last_activity: last_activity = datetime.fromisoformat(last_activity) time_since_last_activity = datetime.now() - last_activity print(f"User {request.user} last activity: {last_activity}") print(f"Time since last activity: {time_since_last_activity}") # 检查用户是否超过10分钟无活动 if time_since_last_activity > timedelta(minutes=10): # 登出用户并标记为非活跃 user_detail = UserDetail.objects.get(user=request.user) if user_detail.isCurrentlyActive: user_detail.isCurrentlyActive = False user_detail.save() print(f"User {user_detail.user} is now inactive and logged out.") logout(request) else: # 更新isCurrentlyActive字段为True user_detail = UserDetail.objects.get(user=request.user) if not user_detail.isCurrentlyActive: user_detail.isCurrentlyActive = True user_detail.save() print(f"User {user_detail.user} is still active.") else: # 更新isCurrentlyActive字段为True user_detail = UserDetail.objects.get(user=request.user) if not user_detail.isCurrentlyActive: user_detail.isCurrentlyActive = True user_detail.save() print(f"User {user_detail.user} is now active.") # 更新会话中的最后活动时间戳 request.session['last_activity'] = datetime.now().isoformat() print(f"User {request.user} last activity updated to {request.session['last_activity']}") response = self.get_response(request) return response
settings.py配置
SESSION_COOKIE_AGE = 1800 # 30分钟(秒为单位),默认是1209600(2周) SESSION_EXPIRE_SECONDS = 600 SESSION_SAVE_EVERY_REQUEST = True # 默认是False
解决方案
Django是请求驱动的后端框架,无请求时不会主动触发后端逻辑。要实现无需请求自动登出并更新字段,需要前端主动触发+后端兜底方案结合:
一、前端主动触发登出请求
通过监听用户页面活动(鼠标移动、键盘输入等),超时后主动调用后端登出接口,这是最直接的实时方案:
1. 前端核心逻辑(JavaScript)
let inactivityTimer; // 重置超时计时器 function resetTimer() { clearTimeout(inactivityTimer); // 设置10分钟后触发登出 inactivityTimer = setTimeout(logoutUser, 10 * 60 * 1000); } // 调用后端登出接口 function logoutUser() { fetch('/api/logout/', { method: 'POST', headers: { 'X-CSRFToken': getCookie('csrftoken'), 'Content-Type': 'application/json' } }).then(response => { if (response.ok) { window.location.href = '/login/'; // 跳转到登录页 } }); } // 监听用户活动事件 document.addEventListener('mousemove', resetTimer); document.addEventListener('keydown', resetTimer); document.addEventListener('click', resetTimer); // 页面加载时启动计时器 window.onload = resetTimer; // 辅助函数:获取Django CSRF令牌 function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; }
2. 后端配合:新增登出API
在views.py中添加处理登出的视图,同时更新用户活跃状态:
from django.http import JsonResponse from django.contrib.auth import logout from codera_main.models import UserDetail from django.views.decorators.csrf import csrf_exempt from django.contrib.auth.decorators import login_required @login_required @csrf_exempt def api_logout(request): if request.method == 'POST': user_detail = UserDetail.objects.get(user=request.user) if user_detail.isCurrentlyActive: user_detail.isCurrentlyActive = False user_detail.save() logout(request) return JsonResponse({'status': 'success'}, status=200) return JsonResponse({'status': 'error'}, status=400)
在urls.py配置路由:
path('api/logout/', views.api_logout, name='api_logout'),
二、后端定时任务兜底
针对用户关闭页面、浏览器崩溃等前端无法触发的场景,用定时任务批量清理超时用户:
1. 用Celery实现定时任务(推荐)
如果项目已使用Celery,添加每分钟执行一次的检查任务:
# tasks.py from datetime import datetime, timedelta from celery import shared_task from codera_main.models import UserDetail @shared_task def check_inactive_users(): ten_minutes_ago = datetime.now() - timedelta(minutes=10) # 查询所有活跃且超过10分钟无活动的用户 # 建议把last_activity字段移到UserDetail模型中,替代会话存储 inactive_users = UserDetail.objects.filter( isCurrentlyActive=True, last_activity__lt=ten_minutes_ago ) inactive_users.update(isCurrentlyActive=False)
在Celery配置中设置定时任务:
# celery.py from celery.schedules import crontab app.conf.beat_schedule = { 'check-inactive-users-every-minute': { 'task': 'yourapp.tasks.check_inactive_users', 'schedule': crontab(minute='*/1'), }, }
2. 简化模型设计
建议修改UserDetail模型,把last_activity直接存入数据库,方便后端查询:
# models.py from django.db import models from django.contrib.auth.models import User class UserDetail(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) isCurrentlyActive = models.BooleanField(default=False) last_activity = models.DateTimeField(auto_now=True) # 每次保存自动更新时间
三、优化现有中间件
保留现有中间件作为兜底(比如用户刷新页面时触发),同时调整会话配置保持超时一致:
# settings.py SESSION_COOKIE_AGE = 600 # 10分钟,和无活动超时时间对齐 SESSION_EXPIRE_SECONDS = 600 SESSION_SAVE_EVERY_REQUEST = True
内容的提问来源于stack exchange,提问作者jahantaila
相关产品推荐
相关产品推荐

