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

Django:追踪并记录用户登录/登出行为至数据库

问题描述
  • 需求:将用户登录、登出时间记录至数据库,已通过login和logout函数实现主动操作记录,需额外检测并记录用户关闭浏览器的登出行为
  • 尝试方案:使用WebSocket实现,在base.html中添加WebSocket连接代码,编写OnlineStatusConsumer处理连接与断开事件
  • 现存问题:用户在项目内切换页面时,WebSocket会断开并重连,导致被误判为登出,求可行解决办法

base.html 中的 WebSocket 代码

let wsProtocol = window.location.protocol === "https:" ? "wss" : "ws";
let url = `${wsProtocol}://${window.location.host}/ws/socket-server/`
const markSocket = new WebSocket(url)
markSocket.onopen = function(e){
    markSocket.send(JSON.stringify({
        'message':'CONNECTED'
    }))
}
markSocket.onclose = function(event) {
    console.error("WebSocket closed:", event);
};

consumer.py 代码

class OnlineStatusConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        await self.accept()
        user = self.scope['user']
        status = True
        print("CONNECTED",user)
        await self.change_online_status(user, status)

    async def disconnect(self, close_code):
        user = self.scope['user']
        status = False
        print("DISCONNECTED", user,close_code)
        await self.change_online_status(user, status)

    @database_sync_to_async
    def change_online_status(self, user, status):
        if status:
            active_user = ActiveUsers.objects.get_or_create()[0]
            if not active_user.active_users.filter(id=user.id).exists():
                active_user.active_users.add(user)
                active_user.active_user_count += 1
                active_user.save()
                # 获取用户最近的登录尝试记录
                last_login_attempt = SpecificUserLoginDiary.objects.filter(user=user).last()
                # 如果用户已有登录尝试记录且距离上次登录尝试不超过2分钟,则不创建新记录
                if last_login_attempt and (timezone.now() - last_login_attempt.login_date) < timedelta(minutes=2):
                    print("最近登录尝试在2分钟内完成。")
                else:
                    specific_user_active_log = SpecificUserLoginDiary.objects.create(
                        user=user,
                        login_date=timezone.now()
                    )
            else:
                print("用户已连接")
        else:
            active_user = ActiveUsers.objects.get_or_create()[0]
            if active_user.active_users.filter(id=user.id).exists():
                active_user.active_users.remove(user)
                active_user.active_user_count -= 1
                active_user.save()
                specific_user_active_log = SpecificUserLoginDiary.objects.filter().last()
                if specific_user_active_log:
                    specific_user_active_log.logout_date = datetime.now()
                    specific_user_active_log.save()
            else:
                print("用户已断开连接")
可行解决办法

1. 增加断开后的延迟处理逻辑

在disconnect事件触发时,不要立即更新用户登出状态,而是设置一个5-10秒的延迟任务。如果延迟时间内用户重新建立WebSocket连接,就取消任务,不记录登出;若超时未重连,再执行登出状态更新。

代码示例(修改consumer.py)

from asgiref.sync import sync_to_async
import asyncio

# 生产环境建议用Redis等分布式存储替代全局字典,避免多进程丢失数据
pending_logout_tasks = {}

class OnlineStatusConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        await self.accept()
        user = self.scope['user']
        if not user.is_authenticated:
            await self.close()
            return
        
        # 取消该用户未执行的延迟登出任务
        if user.id in pending_logout_tasks:
            pending_logout_tasks[user.id].cancel()
            del pending_logout_tasks[user.id]
            print(f"取消用户 {user} 的延迟登出任务")
        
        await self.change_online_status(user, True)
        print("CONNECTED", user)

    async def disconnect(self, close_code):
        user = self.scope['user']
        if not user.is_authenticated:
            return
        
        # 创建延迟10秒的登出任务
        task = asyncio.create_task(self.delayed_logout(user))
        pending_logout_tasks[user.id] = task
        print(f"为用户 {user} 创建延迟登出任务,等待10秒")

    async def delayed_logout(self, user):
        await asyncio.sleep(10)
        # 执行登出状态更新
        await self.change_online_status(user, False)
        print(f"执行用户 {user} 的登出记录")
        if user.id in pending_logout_tasks:
            del pending_logout_tasks[user.id]

    # 原有的change_online_status方法保持不变

2. 前端优化:减少页面切换时的连接断开

  • 若项目可改造为单页应用(SPA),使用Vue/React等框架通过路由切换页面,无需刷新浏览器,WebSocket连接可保持持久
  • 多页应用场景下,可监听前端beforeunload事件,但该事件无法区分页面刷新和关闭,仍需配合后端延迟处理逻辑使用

3. 基于心跳机制判断真实离线

后端定期向前端发送心跳包,前端收到后立即回复。若后端连续多次收不到心跳回复,再判定用户离线并记录登出时间,避免短时间断开被误判。

4. 优化登录日志关联逻辑

在用户连接WebSocket时,前端生成一个唯一会话标识(如UUID)并发送给后端,后端将该标识与当前登录日志绑定。断开时根据会话标识找到对应日志更新登出时间,避免页面切换重连时覆盖历史日志。


内容的提问来源于stack exchange,提问作者batuhanaltog

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 10:26:02