diff --git a/admin/next.config.ts b/admin/next.config.ts index 8fd8f94..143cd2c 100644 --- a/admin/next.config.ts +++ b/admin/next.config.ts @@ -1,7 +1,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - serverExternalPackages: ["bcrypt"], + serverExternalPackages: ["bcrypt", "ioredis"], turbopack: { root: process.cwd(), }, diff --git a/admin/src/lib/redis.ts b/admin/src/lib/redis.ts index 6f6a91a..b379da6 100644 --- a/admin/src/lib/redis.ts +++ b/admin/src/lib/redis.ts @@ -35,16 +35,20 @@ function createClusterClient(): Redis { }); const firstUrl = new URL(REDIS_CLUSTER_URLS[0]); + const username = firstUrl.username || undefined; + const password = firstUrl.password || undefined; - // ioredis 5.x: Cluster 是 default export, redisOptions 展开到顶层, 无 clusterRetryStrategy + // ioredis 5.x: username/password 必须放在 redisOptions 里,不能放顶层 // eslint-disable-next-line @typescript-eslint/no-explicit-any const cluster = new (Cluster as any)(nodes, { lazyConnect: true, + enableReadyCheck: true, maxRetriesPerRequest: 3, - retryStrategy: (times: number) => Math.min(times * 100, 3000), - // 从第一个 URL 提取认证信息(所有节点共用相同密码) - username: firstUrl.username || undefined, - password: firstUrl.password || undefined, + redisOptions: { + username, + password, + retryStrategy: (times: number) => Math.min(times * 100, 3000), + }, }); return cluster as Redis; @@ -52,6 +56,16 @@ function createClusterClient(): Redis { export function getRedis(): Redis { if (!redis) { + const mode = REDIS_CLUSTER_URLS.length > 1 ? "cluster" : "single"; + const clusterNodes = REDIS_CLUSTER_URLS.map((url) => { + const u = new URL(url); + return `${u.hostname}:${u.port}`; + }); + console.log("[Redis] Initializing", { + mode, + clusterNodes, + singleUrl: mode === "single" ? REDIS_URL : undefined, + }); redis = REDIS_CLUSTER_URLS.length > 1 ? createClusterClient() : createSingleClient(); }