You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 06:37:34