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

Next.js中统计用户总会话时长的最佳实现方案

存储用户总登录时长的实现方案

最佳实现方案(精度高、覆盖场景全)

这个方案通过前后端配合,精准记录用户的登录时段,覆盖主动登出、页面关闭/刷新、异常退出等所有场景:

1. 后端(Laravel Sanctum)准备

  • 给用户表添加两个字段:
    // 生成迁移文件
    php artisan make:migration add_login_duration_fields_to_users_table
    
    // 迁移文件内容
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->unsignedInteger('total_login_duration')->default(0); // 总时长,单位秒
            $table->timestamp('last_login_start')->nullable(); // 上次登录开始时间
        });
    }
    
  • 登录接口改造:用户成功通过Sanctum验证后,记录登录开始时间
    // 在登录逻辑中添加
    auth()->user()->update([
        'last_login_start' => now(),
    ]);
    
  • 新增时长更新接口:接收前端传来的本次登录时长,累加到总时长
    public function updateLoginDuration(Request $request)
    {
        $user = auth()->user();
        $duration = $request->input('duration'); // 单位秒
    
        if ($duration > 0) {
            $user->increment('total_login_duration', $duration);
        }
        // 重置登录开始时间
        $user->update(['last_login_start' => null]);
    
        return response()->json(['success' => true]);
    }
    
  • 定时任务兜底:处理用户未主动触发登出的情况(比如直接关浏览器)
    // 在App\Console\Kernel中添加定时任务
    protected function schedule(Schedule $schedule)
    {
        $schedule->call(function () {
            User::whereNotNull('last_login_start')->each(function ($user) {
                $duration = now()->diffInSeconds($user->last_login_start);
                if ($duration > 0) {
                    $user->increment('total_login_duration', $duration);
                }
                $user->update(['last_login_start' => null]);
            });
        })->hourly(); // 每小时执行一次,可根据需求调整频率
    }
    

2. 前端(Next.js + Next Auth)处理

  • 登录成功时记录登录开始时间到本地存储
    // 在Next Auth的signIn回调中添加
    import { signIn } from 'next-auth/react';
    
    const handleLogin = async () => {
        const result = await signIn('credentials', {
            // 你的登录参数
            redirect: false,
        });
        if (result.ok) {
            localStorage.setItem('loginStartTime', Date.now().toString());
        }
    };
    
  • 监听登出事件,计算时长并发送给后端
    import { signOut } from 'next-auth/react';
    
    const handleLogout = async () => {
        const loginStartTime = localStorage.getItem('loginStartTime');
        if (loginStartTime) {
            const duration = Math.floor((Date.now() - parseInt(loginStartTime)) / 1000);
            if (duration > 0) {
                // 发送请求到Laravel接口
                await fetch('/api/update-login-duration', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'X-XSRF-TOKEN': getCookie('XSRF-TOKEN'), // 处理Sanctum的CSRF
                    },
                    body: JSON.stringify({ duration }),
                });
            }
            localStorage.removeItem('loginStartTime');
        }
        await signOut();
    };
    
  • 监听页面关闭/刷新事件,兜底发送时长数据
    useEffect(() => {
        const handleBeforeUnload = async (e) => {
            const loginStartTime = localStorage.getItem('loginStartTime');
            if (loginStartTime) {
                const duration = Math.floor((Date.now() - parseInt(loginStartTime)) / 1000);
                if (duration > 0) {
                    // 使用navigator.sendBeacon确保页面关闭时请求能发送成功
                    const formData = new FormData();
                    formData.append('duration', duration);
                    formData.append('_token', getCookie('XSRF-TOKEN'));
                    navigator.sendBeacon('/api/update-login-duration', formData);
                }
            }
        };
    
        window.addEventListener('beforeunload', handleBeforeUnload);
        return () => window.removeEventListener('beforeunload', handleBeforeUnload);
    }, []);
    

简便实现方案(快速落地、精度稍低)

如果对时长精度要求不高,比如只需要统计大致的活跃时长,可以用更简单的方式:

1. 后端准备

只需要给用户表添加total_login_duration字段(和上面的迁移一致),新增一个接收时长增量的接口即可。

2. 前端处理

  • 每次页面加载或路由切换时,记录当前时间,和上次记录的时间对比,计算间隔并发送给后端
    useEffect(() => {
        const lastActiveTime = localStorage.getItem('lastActiveTime');
        const currentTime = Date.now();
        
        if (lastActiveTime) {
            const duration = Math.floor((currentTime - parseInt(lastActiveTime)) / 1000);
            if (duration > 30) { // 过滤短时间切换,比如小于30秒的不统计
                fetch('/api/update-login-duration', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'X-XSRF-TOKEN': getCookie('XSRF-TOKEN'),
                    },
                    body: JSON.stringify({ duration }),
                });
            }
        }
        
        localStorage.setItem('lastActiveTime', currentTime.toString());
    }, [router.asPath]); // 监听路由变化
    
  • 这种方式不需要精确记录登录登出,只要用户在站点内活跃,就会累加时长,适合快速实现。

内容的提问来源于stack exchange,提问作者A. Atal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 15:53:16