refactor(redis): 抽出共享 Redis 客户端单例至 shared/lib/redis-client.ts
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { env } from "@/env.mjs"
|
import { env } from "@/env.mjs"
|
||||||
|
import { getRedisClient } from "@/shared/lib/redis-client"
|
||||||
|
|
||||||
import type { RateLimiter, RateLimitParams, RateLimitResult } from "./types"
|
import type { RateLimiter, RateLimitParams, RateLimitResult } from "./types"
|
||||||
|
|
||||||
@@ -77,8 +78,6 @@ interface UpstashRatelimitCtor {
|
|||||||
export class RedisRateLimiter implements RateLimiter {
|
export class RedisRateLimiter implements RateLimiter {
|
||||||
/** 按 `${limit}:${windowMs}` 缓存 Ratelimit 实例,避免重复构造 */
|
/** 按 `${limit}:${windowMs}` 缓存 Ratelimit 实例,避免重复构造 */
|
||||||
private readonly instances = new Map<string, UpstashRatelimitInstance>()
|
private readonly instances = new Map<string, UpstashRatelimitInstance>()
|
||||||
/** 共享的 Redis 客户端,避免重复连接 */
|
|
||||||
private redisClient: unknown = null
|
|
||||||
/** 加载 Ratelimit 构造器的 Promise(懒加载) */
|
/** 加载 Ratelimit 构造器的 Promise(懒加载) */
|
||||||
private ratelimitCtorPromise: Promise<UpstashRatelimitCtor> | null = null
|
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 构造器 */
|
/** 懒加载 @upstash/ratelimit 的 Ratelimit 构造器 */
|
||||||
private async getRatelimitCtor(): Promise<UpstashRatelimitCtor> {
|
private async getRatelimitCtor(): Promise<UpstashRatelimitCtor> {
|
||||||
if (this.ratelimitCtorPromise) return this.ratelimitCtorPromise
|
if (this.ratelimitCtorPromise) return this.ratelimitCtorPromise
|
||||||
@@ -124,7 +111,7 @@ export class RedisRateLimiter implements RateLimiter {
|
|||||||
|
|
||||||
const [Ratelimit, redis] = await Promise.all([
|
const [Ratelimit, redis] = await Promise.all([
|
||||||
this.getRatelimitCtor(),
|
this.getRatelimitCtor(),
|
||||||
this.getRedisClient(),
|
getRedisClient(),
|
||||||
])
|
])
|
||||||
|
|
||||||
const instance = new Ratelimit({
|
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