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

NextJS中启动时设置的Map在其他函数中无法读取的问题排查

问题原因与解决方案

核心原因

1. Serverless环境的实例隔离特性

在Vercel等Serverless平台部署NextJS时,每个请求可能触发全新的函数实例。启动时执行loadAllUserPools的实例与处理登录请求的实例完全独立,内存中的userPoolMap不会在实例间共享。新实例处理请求时,userPoolMap是未初始化的空Map,自然无法取到值。

2. 模块重复加载导致实例不共享

NextJS的模块解析机制在部分场景下(比如不同路由/API路由的隔离加载),会导致包含userPoolMap的模块被多次加载,生成多个独立的Map实例。启动时的loadAllUserPools操作的是其中一个实例,而登录时调用getCognitoUserPoolBySubdomain访问的是另一个未初始化的实例。

解决方案

方案1:改用分布式缓存存储(推荐)

放弃内存存储,使用Redis等分布式缓存统一存储用户池数据,确保所有实例都能访问同一套数据:

import { createClient } from 'redis';

// 初始化Redis客户端
const redisClient = createClient({
  // 填写你的Redis配置信息
  url: process.env.REDIS_URL
});
await redisClient.connect();

export async function loadAllUserPools() {
  // 从数据库获取数据(示例用模拟数据)
  const dbUserPoolData = [
    { cognitoUserPoolId: 'user-pool-1111111', cognitoClientId: '11111111111', accessUrl: 'tenant1' },
    { cognitoUserPoolId: 'user-pool-2222222', cognitoClientId: '222222222222', accessUrl: 'tenant2' },
  ];

  // 将数据存入Redis
  for (const item of dbUserPoolData) {
    await redisClient.set(
      `userpool:${item.accessUrl}`,
      JSON.stringify({ userPoolId: item.cognitoUserPoolId, clientId: item.cognitoClientId })
    );
  }
}

export async function getCognitoUserPoolBySubdomain(subdomain: string) {
  const dataStr = await redisClient.get(`userpool:${subdomain}`);
  return dataStr ? JSON.parse(dataStr) : undefined;
}

方案2:确保模块单例与初始化时机

如果坚持使用内存存储,需通过单例模式保证userPoolMap唯一,并在处理请求前完成初始化:

  1. 封装单例管理类:
// userPoolManager.ts
class UserPoolManager {
  private static instance: UserPoolManager;
  private userPoolMap: Map<string, { userPoolId: string; clientId: string }>;

  private constructor() {
    this.userPoolMap = new Map();
  }

  public static getInstance(): UserPoolManager {
    if (!UserPoolManager.instance) {
      UserPoolManager.instance = new UserPoolManager();
    }
    return UserPoolManager.instance;
  }

  public async loadAllUserPools() {
    const dbUserPoolData = [
      { cognitoUserPoolId: 'user-pool-1111111', cognitoClientId: '11111111111', accessUrl: 'tenant1' },
      { cognitoUserPoolId: 'user-pool-2222222', cognitoClientId: '222222222222', accessUrl: 'tenant2' },
    ];
    this.userPoolMap.clear();
    dbUserPoolData.forEach(item => {
      this.userPoolMap.set(item.accessUrl, {
        userPoolId: item.cognitoUserPoolId,
        clientId: item.cognitoClientId,
      });
    });
  }

  public getCognitoUserPoolBySubdomain(subdomain: string) {
    return this.userPoolMap.get(subdomain);
  }
}

export const userPoolManager = UserPoolManager.getInstance();
  1. 在入口处确保初始化完成(以middleware.ts为例):
// middleware.ts
import { userPoolManager } from './userPoolManager';

let isInitialized = false;

export async function middleware(request: NextRequest) {
  // 仅初始化一次
  if (!isInitialized) {
    await userPoolManager.loadAllUserPools();
    isInitialized = true;
  }
  // 后续请求处理逻辑
}

方案3:请求时懒加载数据

在getCognitoUserPoolBySubdomain中检查Map状态,为空则重新加载(需处理并发重复加载问题):

let isLoading = false;
let userPoolMap = new Map();

export async function loadAllUserPools() {
  if (isLoading) return;
  isLoading = true;
  try {
    const dbUserPoolData = [
      { cognitoUserPoolId: 'user-pool-1111111', cognitoClientId: '11111111111', accessUrl: 'tenant1' },
      { cognitoUserPoolId: 'user-pool-2222222', cognitoClientId: '222222222222', accessUrl: 'tenant2' },
    ];
    userPoolMap.clear();
    dbUserPoolData.forEach(item => {
      userPoolMap.set(item.accessUrl, {
        userPoolId: item.cognitoUserPoolId,
        clientId: item.cognitoClientId,
      });
    });
  } finally {
    isLoading = false;
  }
}

export async function getCognitoUserPoolBySubdomain(subdomain: string) {
  if (userPoolMap.size === 0) {
    await loadAllUserPools();
  }
  return userPoolMap.get(subdomain);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 09:50:06