import { ADDRESS_CANDIDATES_CACHE_TTL_SECONDS, L1_TTL_SECONDS, L2_TTL_BASE_SECONDS, L2_TTL_JITTER_MAX_SECONDS, L3_TTL_SECONDS, LOCK_TTL_SECONDS, LOCK_WAIT_MS, } from "@/lib/constants/cache"; import { getRedis } from "@/lib/redis"; export const cacheKeys = { l1: (requestId: string) => `idem:${requestId}`, l2: (cargoHash: string) => `quote:${cargoHash}`, l3: (cargoHash: string) => `stale:${cargoHash}`, lock: (cargoHash: string) => `lock:quote:${cargoHash}`, addrCand: (hash: string) => `addr-cand:${hash}`, }; function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } function l2TtlWithJitter(): number { const jitter = Math.floor(Math.random() * (L2_TTL_JITTER_MAX_SECONDS + 1)); return L2_TTL_BASE_SECONDS + jitter; } export async function getL1( requestId: string, ): Promise { const raw = await getRedis().get(cacheKeys.l1(requestId)); if (!raw) { return null; } return JSON.parse(raw) as T; } /** SETNX + TTL;已存在时不覆盖,返回 false */ export async function setL1( requestId: string, data: unknown, ): Promise { const key = cacheKeys.l1(requestId); const value = JSON.stringify(data); const result = await getRedis().set(key, value, "EX", L1_TTL_SECONDS, "NX"); return result === "OK"; } /** 覆盖写入 L1(RPA 完成后更新幂等响应) */ export async function overwriteL1( requestId: string, data: unknown, ): Promise { await getRedis().set( cacheKeys.l1(requestId), JSON.stringify(data), "EX", L1_TTL_SECONDS, ); } export async function getL2( cargoHash: string, ): Promise { const raw = await getRedis().get(cacheKeys.l2(cargoHash)); if (!raw) { return null; } return JSON.parse(raw) as T; } export async function setL2( cargoHash: string, data: unknown, ): Promise { const ttl = l2TtlWithJitter(); await getRedis().set( cacheKeys.l2(cargoHash), JSON.stringify(data), "EX", ttl, ); return ttl; } export async function getL3( cargoHash: string, ): Promise { const raw = await getRedis().get(cacheKeys.l3(cargoHash)); if (!raw) { return null; } return JSON.parse(raw) as T; } /** 仅 RPA 成功时写入;正常路径禁止读取 L3 */ export async function setL3( cargoHash: string, data: unknown, ): Promise { await getRedis().set( cacheKeys.l3(cargoHash), JSON.stringify(data), "EX", L3_TTL_SECONDS, ); } /** SETNX 获取击穿锁,TTL 5s */ export async function acquireLock(cargoHash: string): Promise { const result = await getRedis().set( cacheKeys.lock(cargoHash), "1", "EX", LOCK_TTL_SECONDS, "NX", ); return result === "OK"; } export async function releaseLock(cargoHash: string): Promise { await getRedis().del(cacheKeys.lock(cargoHash)); } /** 未获锁时等待 2s 后读 L2 */ export async function waitAndGetL2( cargoHash: string, ): Promise { await sleep(LOCK_WAIT_MS); return getL2(cargoHash); } export async function getAddressCandidatesCache( hash: string, ): Promise { const raw = await getRedis().get(cacheKeys.addrCand(hash)); if (!raw) { return null; } return JSON.parse(raw) as T; } /** 地址联想成功结果短缓存(默认 120s) */ export async function setAddressCandidatesCache( hash: string, data: unknown, ): Promise { await getRedis().set( cacheKeys.addrCand(hash), JSON.stringify(data), "EX", ADDRESS_CANDIDATES_CACHE_TTL_SECONDS, ); }