refactor(redis): 抽出共享 Redis 客户端单例至 shared/lib/redis-client.ts
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
import { env } from "@/env.mjs"
|
||||
import { getRedisClient } from "@/shared/lib/redis-client"
|
||||
|
||||
import type { RateLimiter, RateLimitParams, RateLimitResult } from "./types"
|
||||
|
||||
@@ -77,8 +78,6 @@ interface UpstashRatelimitCtor {
|
||||
export class RedisRateLimiter implements RateLimiter {
|
||||
/** 按 `${limit}:${windowMs}` 缓存 Ratelimit 实例,避免重复构造 */
|
||||
private readonly instances = new Map<string, UpstashRatelimitInstance>()
|
||||
/** 共享的 Redis 客户端,避免重复连接 */
|
||||
private redisClient: unknown = null
|
||||
/** 加载 Ratelimit 构造器的 Promise(懒加载) */
|
||||
private ratelimitCtorPromise: Promise<UpstashRatelimitCtor> | null = null
|
||||
|
||||
@@ -91,18 +90,6 @@ export class RedisRateLimiter implements RateLimiter {
|
||||
}
|
||||
}
|
||||
|
||||
/** 懒加载 @upstash/redis 的 Redis 客户端 */
|
||||
private async getRedisClient(): Promise<unknown> {
|
||||
if (this.redisClient) return this.redisClient
|
||||
// webpackIgnore: 让 @upstash/redis 成为运行时可选依赖,未启用 redis 驱动时不要求安装
|
||||
const { Redis } = await import(/* webpackIgnore: true */ "@upstash/redis")
|
||||
this.redisClient = new Redis({
|
||||
url: env.UPSTASH_REDIS_REST_URL!,
|
||||
token: env.UPSTASH_REDIS_REST_TOKEN!,
|
||||
})
|
||||
return this.redisClient
|
||||
}
|
||||
|
||||
/** 懒加载 @upstash/ratelimit 的 Ratelimit 构造器 */
|
||||
private async getRatelimitCtor(): Promise<UpstashRatelimitCtor> {
|
||||
if (this.ratelimitCtorPromise) return this.ratelimitCtorPromise
|
||||
@@ -124,7 +111,7 @@ export class RedisRateLimiter implements RateLimiter {
|
||||
|
||||
const [Ratelimit, redis] = await Promise.all([
|
||||
this.getRatelimitCtor(),
|
||||
this.getRedisClient(),
|
||||
getRedisClient(),
|
||||
])
|
||||
|
||||
const instance = new Ratelimit({
|
||||
|
||||
56
src/shared/lib/redis-client.ts
Normal file
56
src/shared/lib/redis-client.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import "server-only"
|
||||
|
||||
import { env } from "@/env.mjs"
|
||||
|
||||
/**
|
||||
* 共享 Redis 客户端单例(rate-limit + cache 共用)。
|
||||
*
|
||||
* 复用 env.UPSTASH_REDIS_REST_URL / TOKEN。
|
||||
* 动态 import + webpackIgnore,与原 redis-limiter.ts 同模式。
|
||||
*
|
||||
* 故障降级:返回 null,调用方走 fail-open。
|
||||
*/
|
||||
let singleton: unknown | null = null
|
||||
let loadPromise: Promise<unknown> | null = null
|
||||
|
||||
/**
|
||||
* 获取共享 Redis 客户端。
|
||||
*
|
||||
* - 首次调用时动态 import @upstash/redis 并构造 Redis 实例
|
||||
* - 后续调用复用单例
|
||||
* - 若 env 未配置 UPSTASH_REDIS_REST_URL/TOKEN,返回 null
|
||||
* - 若动态 import 抛错(包未安装),返回 null
|
||||
*
|
||||
* 调用方需自行处理 null(fail-open 降级)。
|
||||
*/
|
||||
export async function getRedisClient(): Promise<unknown | null> {
|
||||
if (singleton) return singleton
|
||||
if (loadPromise) return loadPromise
|
||||
|
||||
if (!env.UPSTASH_REDIS_REST_URL || !env.UPSTASH_REDIS_REST_TOKEN) {
|
||||
return null
|
||||
}
|
||||
|
||||
loadPromise = (async () => {
|
||||
try {
|
||||
// webpackIgnore: 让 @upstash/redis 成为运行时可选依赖
|
||||
const { Redis } = await import(/* webpackIgnore: true */ "@upstash/redis")
|
||||
const client = new Redis({
|
||||
url: env.UPSTASH_REDIS_REST_URL!,
|
||||
token: env.UPSTASH_REDIS_REST_TOKEN!,
|
||||
})
|
||||
singleton = client
|
||||
return client
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[redis-client] Failed to load @upstash/redis:",
|
||||
error instanceof Error ? error.message : String(error),
|
||||
)
|
||||
return null
|
||||
} finally {
|
||||
loadPromise = null
|
||||
}
|
||||
})()
|
||||
|
||||
return loadPromise
|
||||
}
|
||||
Reference in New Issue
Block a user