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

Next.js + NextAuth用户会话追踪方案咨询(含DAU统计需求)

解决方案:用户会话追踪与DAU计算(Next.js + Prisma + NextAuth)

1. 数据库模型设计(Prisma Schema)

首先扩展Prisma模型,新增会话追踪表关联用户与NextAuth会话,记录核心信息:

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  sessions      Session[] // NextAuth默认会话关联
  sessionTracks SessionTrack[] // 新增会话追踪记录关联
}

model SessionTrack {
  id             String   @id @default(cuid())
  userId         String
  user           User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  sessionId      String   // 关联NextAuth的Session.id
  startTime      DateTime @default(now())
  endTime        DateTime?
  lastActiveTime DateTime @default(now()) // 可选:记录用户最后活跃时间
  ipAddress      String?  // 可选:记录访问IP
  userAgent      String?  // 可选:记录客户端信息

  @@unique([userId, sessionId]) // 确保一个用户的单个NextAuth会话对应一条追踪记录
}

2. 会话追踪逻辑实现

2.1 登录时初始化会话记录

在NextAuth的signIn回调中,用户登录成功后自动创建会话追踪记录:

import NextAuth from "next-auth";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import prisma from "@/lib/prisma";

export const authOptions = {
  adapter: PrismaAdapter(prisma),
  callbacks: {
    async signIn({ user, session, req }) {
      await prisma.sessionTrack.create({
        data: {
          userId: user.id,
          sessionId: session.id,
          ipAddress: req.ip ?? null,
          userAgent: req.headers["user-agent"] ?? null,
        },
      });
      return true;
    },
  },
  // 其他NextAuth配置(Providers、session策略等)
};

export default NextAuth(authOptions);

2.2 处理会话结束

主动登出时标记结束

在signOut回调中更新会话结束时间:

callbacks: {
  // ...其他回调
  async signOut({ session }) {
    await prisma.sessionTrack.updateMany({
      where: {
        sessionId: session.id,
        endTime: null,
      },
      data: { endTime: new Date() },
    });
    return true;
  },
},

自动过期会话处理

通过定时任务清理过期会话并标记结束(可使用node-cron实现):

import cron from "node-cron";
import prisma from "@/lib/prisma";

// 每天凌晨1点执行
cron.schedule("0 1 * * *", async () => {
  // 找出所有已过期的NextAuth会话
  const expiredSessions = await prisma.session.findMany({
    where: { expires: { lt: new Date() } },
    select: { id: true },
  });

  // 标记对应会话追踪记录为结束
  await prisma.sessionTrack.updateMany({
    where: {
      sessionId: { in: expiredSessions.map(s => s.id) },
      endTime: null,
    },
    data: { endTime: new Date() },
  });
});

前端关闭页面时标记结束(可选)

监听前端beforeunload事件,发送请求标记会话结束:

// 全局脚本或布局组件中
window.addEventListener('beforeunload', async () => {
  try {
    await fetch('/api/end-session', {
      method: 'POST',
      credentials: 'include',
    });
  } catch (e) {
    // 忽略请求失败,beforeunload可能中断请求
  }
});

对应API路由pages/api/end-session.ts:

import { getServerSession } from "next-auth/next";
import { authOptions } from "../auth/[...nextauth]";
import prisma from "@/lib/prisma";

export default async function handler(req, res) {
  if (req.method !== 'POST') return res.status(405).end();

  const session = await getServerSession(req, res, authOptions);
  if (!session) return res.status(401).end();

  await prisma.sessionTrack.updateMany({
    where: { sessionId: session.id, endTime: null },
    data: { endTime: new Date() },
  });

  res.status(200).json({ success: true });
}

2.3 更新用户活跃时间(可选)

在受保护路由或Middleware中,定期更新用户最后活跃时间(避免频繁更新,可设置5分钟间隔):

// Middleware示例
import { getServerSession } from "next-auth/next";
import { authOptions } from "./auth/[...nextauth]";
import prisma from "@/lib/prisma";

export async function middleware(req, res) {
  const session = await getServerSession(req, res, authOptions);
  if (session) {
    const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
    await prisma.sessionTrack.updateMany({
      where: {
        sessionId: session.id,
        endTime: null,
        lastActiveTime: { lt: fiveMinutesAgo },
      },
      data: { lastActiveTime: new Date() },
    });
  }
  return NextResponse.next();
}

3. DAU计算逻辑

DAU定义为当日有活跃行为的用户数,可根据业务需求选择查询方式:

3.1 基础DAU(当日启动新会话的用户)

async function getDAU() {
  const today = new Date();
  const startOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate());
  const endOfDay = new Date(startOfDay);
  endOfDay.setDate(endOfDay.getDate() + 1);

  const activeUsers = await prisma.sessionTrack.groupBy({
    by: ['userId'],
    where: { startTime: { gte: startOfDay, lt: endOfDay } },
  });

  return activeUsers.length;
}

3.2 精确DAU(当日有任何活跃的用户)

包含当日启动新会话或已有会话在当日活跃的用户:

async function getDAU() {
  const today = new Date();
  const startOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate());
  const endOfDay = new Date(startOfDay);
  endOfDay.setDate(endOfDay.getDate() + 1);

  const activeUsers = await prisma.sessionTrack.groupBy({
    by: ['userId'],
    where: {
      OR: [
        { startTime: { gte: startOfDay, lt: endOfDay } },
        { lastActiveTime: { gte: startOfDay, lt: endOfDay } },
      ],
    },
  });

  return activeUsers.length;
}

4. 注意事项

  • 性能优化:更新活跃时间时设置时间间隔,避免高频数据库操作;对会话追踪表的userId、startTime、lastActiveTime字段建立索引。
  • 隐私合规:若记录IP、UserAgent等信息,需符合GDPR等隐私法规,提前告知用户并获取同意。
  • 定时任务部署:Vercel等无服务器环境需用Serverless Functions或第三方定时服务(如Cron-Job.org)执行定时任务。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 05:57:40