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

如何在Grails中实现并发会话过滤器处理多登录会话场景?

多端登录冲突处理实现方案

核心设计逻辑

针对同一账号多端登录的冲突,核心是会话生命周期管理+实时状态交互,实现两种场景的处理:

  • 当原有会话超过3-4分钟无操作,自动清理并允许新登录
  • 当原有会话处于活跃状态,通过实时通知让用户确认是否授权新登录

具体实现步骤

1. 会话与活跃状态追踪

  • 每个用户登录时生成唯一sessionId,将会话信息(用户ID、最后活跃时间、设备标识、登录IP等)存储在Redis中,结构设计为:
    • 哈希表user:{userId}:sessions:存储该用户所有有效会话的sessionId与对应信息
    • 字符串session:{sessionId}:存储单个会话的详细数据,方便快速查询
  • 前端在用户有操作(页面跳转、点击事件、表单输入等)时,每隔30秒发送一次心跳请求,更新服务端对应sessionId的lastActiveTime

2. 新登录请求校验流程

当收到新的登录请求时:

  1. 查询该用户名下所有有效会话
  2. 遍历会话,判断是否处于非活跃状态:计算当前时间与lastActiveTime的差值,若超过240秒(4分钟),直接删除该会话(清理过期会话)
  3. 如果清理后仍存在活跃会话,触发实时通知流程;否则直接创建新会话,允许新用户登录

3. 实时通知与权限确认

  • 服务端通过WebSocket(或SSE)向活跃会话的前端推送登录尝试通知
  • 前端收到通知后弹出确认框,展示提示语:有用户正从其他浏览器/设备尝试登录,是否允许?
  • 前端将用户的选择(同意/拒绝)回传给服务端:
    • 同意:删除原有活跃会话,为新登录请求创建新会话,同时向原前端发送强制登出指令
    • 拒绝:直接返回错误响应给新登录请求,提示登录被当前在线用户拒绝

关键代码示例

服务端(Node.js + Redis)会话检查逻辑

const redis = require('redis');
const client = redis.createClient();

async function handleNewLogin(userId, newDeviceInfo) {
  // 获取用户所有会话
  const sessions = await client.hGetAll(`user:${userId}:sessions`);
  const now = Date.now();
  const inactiveThreshold = 240 * 1000; // 4分钟阈值
  let hasActiveSession = false;

  // 清理非活跃会话,检查是否存在活跃会话
  for (const [sessionId, sessionStr] of Object.entries(sessions)) {
    const sessionData = JSON.parse(sessionStr);
    if (now - sessionData.lastActiveTime > inactiveThreshold) {
      // 删除过期会话
      await client.hDel(`user:${userId}:sessions`, sessionId);
      await client.del(`session:${sessionId}`);
    } else {
      hasActiveSession = true;
      // 向活跃会话推送登录尝试通知
      await pushLoginAttemptNotification(sessionId);
    }
  }

  if (!hasActiveSession) {
    // 创建新会话
    const newSessionId = generateUniqueSessionId();
    const newSessionData = {
      userId,
      lastActiveTime: now,
      deviceInfo: newDeviceInfo,
      loginTime: now
    };
    await client.hSet(`user:${userId}:sessions`, newSessionId, JSON.stringify(newSessionData));
    await client.set(`session:${newSessionId}`, JSON.stringify(newSessionData));
    return { success: true, sessionId: newSessionId };
  } else {
    // 等待用户确认,返回等待状态
    return { success: false, message: '等待当前在线用户确认' };
  }
}

// 生成唯一sessionId的工具函数
function generateUniqueSessionId() {
  return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}

// 推送登录尝试通知(WebSocket实现示例)
async function pushLoginAttemptNotification(sessionId) {
  // 假设WebSocket服务维护了sessionId与连接的映射
  const socket = wsConnections.get(sessionId);
  if (socket) {
    socket.send(JSON.stringify({ type: 'LOGIN_ATTEMPT' }));
  }
}

前端心跳与WebSocket处理

// 心跳请求,每30秒更新活跃状态
setInterval(() => {
  const sessionId = localStorage.getItem('sessionId');
  if (sessionId) {
    fetch('/api/heartbeat', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sessionId })
    });
  }
}, 30000);

// WebSocket监听登录通知
const sessionId = localStorage.getItem('sessionId');
if (sessionId) {
  const socket = new WebSocket(`ws://your-domain/ws/${sessionId}`);
  socket.onmessage = (event) => {
    const data = JSON.parse(event.data);
    if (data.type === 'LOGIN_ATTEMPT') {
      const allow = confirm('有用户正从其他浏览器/设备尝试登录,是否允许?');
      fetch('/api/confirm-login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ sessionId, allow })
      }).then(res => res.json()).then(result => {
        if (result.success && allow) {
          // 被强制登出,清理本地存储并跳转登录页
          localStorage.removeItem('sessionId');
          window.location.href = '/login';
        }
      });
    }
  };

  // 处理WebSocket重连
  socket.onclose = () => {
    setTimeout(() => {
      // 重新建立连接
      window.location.reload();
    }, 5000);
  };
}

注意事项

  • 会话存储优先选择Redis这类高性能内存数据库,支持原子操作与过期时间,避免并发问题
  • 心跳请求可根据业务场景调整间隔,避免过于频繁增加服务器压力
  • WebSocket连接需处理重连逻辑,防止网络波动导致通知丢失
  • 会话操作需加分布式锁(比如Redis的SETNX),避免并发登录请求导致的状态不一致

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 04:05:22