perf(cmpp): add durable inbound fast path

This commit is contained in:
hectorzhao
2026-08-20 17:34:45 +08:00
parent 26ef67fb6a
commit 0b63bcd74e
29 changed files with 1136 additions and 87 deletions
+46 -2
View File
@@ -80,6 +80,7 @@ export class PhoneFrequencyService {
phones: string[],
sourceType?: string,
requestedAt = new Date(),
reservationKey?: string,
) {
if (!applicationId) return new Map<string, PhoneFrequencyRejection>();
const normalizedPhones = [...new Set(phones.map((phone) => phone.trim()).filter(Boolean))].sort();
@@ -87,14 +88,35 @@ export class PhoneFrequencyService {
await this.riskReview.ensureDefaultRules();
const rules = await this.effectiveRules(applicationId);
if (rules.length === 0) return new Map<string, PhoneFrequencyRejection>();
const normalizedReservationKey = reservationKey?.trim();
return this.prisma.$transaction(async (tx) => {
if (normalizedReservationKey) {
// Frequency counters and the reservation result commit together. Retrying a reclaimed
// Inbox item therefore returns the original decision without incrementing either window.
await tx.$executeRaw`SELECT pg_advisory_xact_lock(hashtextextended(${'phone-frequency:' + normalizedReservationKey}, 0))`;
const existingReservation = await tx.phoneFrequencyReservation.findUnique({
where: { reservationKey: normalizedReservationKey },
});
if (existingReservation) {
if (existingReservation.tenantId !== tenantId || existingReservation.applicationId !== applicationId) {
throw new BadRequestException('号码频控幂等键已用于另一笔预留');
}
return frequencyRejectionsFromJson(existingReservation.result);
}
}
const rejected = new Map<string, PhoneFrequencyRejection>();
// 平台级白名单只截断号码频控链路;调用 reserve 之前已执行的格式、黑名单等校验不受影响。
const whitelistedPhones = await this.findActiveWhitelistedPhones(tx, normalizedPhones);
const controlledPhones = normalizedPhones.filter((phone) => !whitelistedPhones.has(phone));
if (controlledPhones.length === 0) return rejected;
if (controlledPhones.length === 0 || rules.length === 0) {
if (normalizedReservationKey) {
await tx.phoneFrequencyReservation.create({
data: { reservationKey: normalizedReservationKey, tenantId, applicationId, result: [] },
});
}
return rejected;
}
for (const rule of rules) {
const window = fixedShanghaiWindow(requestedAt, readPeriodSeconds(rule));
// 分块限制 SQL 参数数量,但两条规则的全部分块仍在同一事务中提交或回滚。
@@ -144,6 +166,16 @@ export class PhoneFrequencyService {
}
}
}
if (normalizedReservationKey) {
await tx.phoneFrequencyReservation.create({
data: {
reservationKey: normalizedReservationKey,
tenantId,
applicationId,
result: [...rejected.entries()].map(([phoneNumber, rejection]) => ({ phoneNumber, ...rejection })),
},
});
}
return rejected;
}, { isolationLevel: Prisma.TransactionIsolationLevel.ReadCommitted });
}
@@ -566,6 +598,18 @@ export class PhoneFrequencyService {
}
}
function frequencyRejectionsFromJson(value: Prisma.JsonValue) {
const result = new Map<string, PhoneFrequencyRejection>();
if (!Array.isArray(value)) return result;
for (const item of value) {
if (!item || typeof item !== 'object' || Array.isArray(item)) continue;
const phoneNumber = typeof item.phoneNumber === 'string' ? item.phoneNumber : '';
const reason = typeof item.reason === 'string' ? item.reason : '';
if (phoneNumber && reason) result.set(phoneNumber, { code: 'PHONE_FREQUENCY_LIMIT', reason });
}
return result;
}
const whitelistUserInclude = {
createdBy: { select: { id: true, username: true, displayName: true } },
updatedBy: { select: { id: true, username: true, displayName: true } },