|
|
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<void> {
|
|
|
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<T = unknown>(
|
|
|
requestId: string,
|
|
|
): Promise<T | null> {
|
|
|
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<boolean> {
|
|
|
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<void> {
|
|
|
await getRedis().set(
|
|
|
cacheKeys.l1(requestId),
|
|
|
JSON.stringify(data),
|
|
|
"EX",
|
|
|
L1_TTL_SECONDS,
|
|
|
);
|
|
|
}
|
|
|
|
|
|
export async function getL2<T = unknown>(
|
|
|
cargoHash: string,
|
|
|
): Promise<T | null> {
|
|
|
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<number> {
|
|
|
const ttl = l2TtlWithJitter();
|
|
|
await getRedis().set(
|
|
|
cacheKeys.l2(cargoHash),
|
|
|
JSON.stringify(data),
|
|
|
"EX",
|
|
|
ttl,
|
|
|
);
|
|
|
return ttl;
|
|
|
}
|
|
|
|
|
|
export async function getL3<T = unknown>(
|
|
|
cargoHash: string,
|
|
|
): Promise<T | null> {
|
|
|
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<void> {
|
|
|
await getRedis().set(
|
|
|
cacheKeys.l3(cargoHash),
|
|
|
JSON.stringify(data),
|
|
|
"EX",
|
|
|
L3_TTL_SECONDS,
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/** SETNX 获取击穿锁,TTL 5s */
|
|
|
export async function acquireLock(cargoHash: string): Promise<boolean> {
|
|
|
const result = await getRedis().set(
|
|
|
cacheKeys.lock(cargoHash),
|
|
|
"1",
|
|
|
"EX",
|
|
|
LOCK_TTL_SECONDS,
|
|
|
"NX",
|
|
|
);
|
|
|
return result === "OK";
|
|
|
}
|
|
|
|
|
|
export async function releaseLock(cargoHash: string): Promise<void> {
|
|
|
await getRedis().del(cacheKeys.lock(cargoHash));
|
|
|
}
|
|
|
|
|
|
/** 未获锁时等待 2s 后读 L2 */
|
|
|
export async function waitAndGetL2<T = unknown>(
|
|
|
cargoHash: string,
|
|
|
): Promise<T | null> {
|
|
|
await sleep(LOCK_WAIT_MS);
|
|
|
return getL2<T>(cargoHash);
|
|
|
}
|
|
|
|
|
|
export async function getAddressCandidatesCache<T = unknown>(
|
|
|
hash: string,
|
|
|
): Promise<T | null> {
|
|
|
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<void> {
|
|
|
await getRedis().set(
|
|
|
cacheKeys.addrCand(hash),
|
|
|
JSON.stringify(data),
|
|
|
"EX",
|
|
|
ADDRESS_CANDIDATES_CACHE_TTL_SECONDS,
|
|
|
);
|
|
|
}
|