|
|
import { IDEMPOTENCY_TTL_MS } from "@/lib/constants/quote";
|
|
|
import { prisma } from "@/lib/prisma";
|
|
|
import { getL1, setL1 } from "@/modules/cache/redis-cache";
|
|
|
|
|
|
export type IdempotencyHit = {
|
|
|
hit: true;
|
|
|
quoteId: string;
|
|
|
response: unknown;
|
|
|
};
|
|
|
|
|
|
export type IdempotencyMiss = {
|
|
|
hit: false;
|
|
|
};
|
|
|
|
|
|
export type IdempotencyResult = IdempotencyHit | IdempotencyMiss;
|
|
|
|
|
|
/** 查 L1 Redis → DB 双保险,24h 内同 request_id 返回同一 quote_id */
|
|
|
export async function checkIdempotency(
|
|
|
requestId: string,
|
|
|
): Promise<IdempotencyResult> {
|
|
|
const cached = await getL1<{ quoteId: string; response: unknown }>(
|
|
|
requestId,
|
|
|
);
|
|
|
if (cached?.quoteId) {
|
|
|
return {
|
|
|
hit: true,
|
|
|
quoteId: cached.quoteId,
|
|
|
response: cached.response,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
const record = await prisma.idempotencyRecord.findUnique({
|
|
|
where: { requestId },
|
|
|
select: { quoteId: true },
|
|
|
});
|
|
|
|
|
|
if (!record) {
|
|
|
return { hit: false };
|
|
|
}
|
|
|
|
|
|
const quote = await prisma.quoteRecord.findUnique({
|
|
|
where: { quoteId: record.quoteId },
|
|
|
select: {
|
|
|
quoteId: true,
|
|
|
status: true,
|
|
|
sourceType: true,
|
|
|
isRealtime: true,
|
|
|
},
|
|
|
});
|
|
|
|
|
|
const response = {
|
|
|
quote_id: record.quoteId,
|
|
|
status: quote?.status ?? "done",
|
|
|
};
|
|
|
|
|
|
await setL1(requestId, { quoteId: record.quoteId, response });
|
|
|
|
|
|
return {
|
|
|
hit: true,
|
|
|
quoteId: record.quoteId,
|
|
|
response,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
/** 写入 DB + Redis L1,处理唯一键冲突 */
|
|
|
export async function saveIdempotency(
|
|
|
requestId: string,
|
|
|
customerId: string,
|
|
|
quoteId: string,
|
|
|
response: unknown,
|
|
|
): Promise<void> {
|
|
|
const expireAt = new Date(Date.now() + IDEMPOTENCY_TTL_MS);
|
|
|
|
|
|
try {
|
|
|
await prisma.idempotencyRecord.create({
|
|
|
data: {
|
|
|
requestId,
|
|
|
customerId,
|
|
|
quoteId,
|
|
|
expireAt,
|
|
|
},
|
|
|
});
|
|
|
} catch (error: unknown) {
|
|
|
const prismaError = error as { code?: string };
|
|
|
if (prismaError.code === "P2002") {
|
|
|
const existing = await prisma.idempotencyRecord.findUnique({
|
|
|
where: { requestId },
|
|
|
select: { quoteId: true },
|
|
|
});
|
|
|
if (existing) {
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
throw error;
|
|
|
}
|
|
|
|
|
|
await setL1(requestId, { quoteId, response });
|
|
|
}
|