perf(cmpp): reduce inbound database round trips
This commit is contained in:
@@ -397,6 +397,7 @@ function createService(
|
||||
);
|
||||
service['postGatewayControl'] = jest.fn().mockResolvedValue({ delivered: true });
|
||||
service['publishGatewaySubmitCommand'] = jest.fn().mockResolvedValue(undefined);
|
||||
service['getSendQueue'] = jest.fn().mockReturnValue({ add: jest.fn().mockResolvedValue(undefined) });
|
||||
return { service, prisma, billing, riskReview, phoneFrequency };
|
||||
}
|
||||
|
||||
@@ -1627,6 +1628,7 @@ describe('SendChainService', () => {
|
||||
})).resolves.toEqual(expect.objectContaining({ accepted: true, phoneCount: 2 }));
|
||||
|
||||
expect(prisma.smsMessageRecord.create).toHaveBeenCalledTimes(2);
|
||||
expect(prisma.smsApplication.findFirst).toHaveBeenCalledTimes(1);
|
||||
expect(prisma.smsMessageRecord.update).toHaveBeenCalledWith({
|
||||
where: { id: 'record-2' },
|
||||
data: expect.objectContaining({
|
||||
@@ -1766,7 +1768,10 @@ describe('SendChainService', () => {
|
||||
templateId: 'tpl-code',
|
||||
variables: { code: '715021' },
|
||||
}));
|
||||
expect(service.enqueueBatchTask).toHaveBeenCalledWith('task-1');
|
||||
expect(service.enqueueBatchTask).toHaveBeenCalledWith('task-1', {
|
||||
messageRecordId: 'record-1',
|
||||
queuePriority: 'normal',
|
||||
});
|
||||
expect(prisma.smsReceiptRecord.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -1814,7 +1819,10 @@ describe('SendChainService', () => {
|
||||
where: { id: 'record-1' },
|
||||
data: { status: 'queued', signatureId: 'sig-1' },
|
||||
});
|
||||
expect(service.enqueueBatchTask).toHaveBeenCalledWith('task-1');
|
||||
expect(service.enqueueBatchTask).toHaveBeenCalledWith('task-1', {
|
||||
messageRecordId: 'record-1',
|
||||
queuePriority: 'normal',
|
||||
});
|
||||
expect(prisma.smsReceiptRecord.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -2090,6 +2098,26 @@ describe('SendChainService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('enqueues a freshly persisted inbound message without querying the task and message again', async () => {
|
||||
const { service, prisma } = createService();
|
||||
const add = jest.fn().mockResolvedValue(undefined);
|
||||
service['getSendQueue'] = jest.fn().mockReturnValue({ add });
|
||||
|
||||
await expect(service.enqueueBatchTask('task-1', {
|
||||
messageRecordId: 'record-1',
|
||||
queuePriority: 'priority',
|
||||
})).resolves.toEqual({ taskId: 'task-1', enqueued: 1 });
|
||||
|
||||
expect(prisma.smsBatchTask.findUnique).not.toHaveBeenCalled();
|
||||
expect(prisma.smsMessageRecord.findMany).not.toHaveBeenCalled();
|
||||
expect(add).toHaveBeenCalledWith('send-message', { messageRecordId: 'record-1' }, {
|
||||
jobId: 'record-1',
|
||||
attempts: 3,
|
||||
priority: 1,
|
||||
});
|
||||
expect(prisma.smsBatchTask.update).toHaveBeenCalledWith({ where: { id: 'task-1' }, data: { status: 'queued' } });
|
||||
});
|
||||
|
||||
it('reuses persisted carrier and province without querying routing dictionaries again', async () => {
|
||||
const { service, prisma } = createService();
|
||||
service['identifyCarrier'] = jest.fn();
|
||||
|
||||
@@ -352,8 +352,8 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
|
||||
return this.submission.confirmImport(data);
|
||||
}
|
||||
|
||||
async enqueueBatchTask(taskId: string) {
|
||||
return this.submission.enqueueBatchTask(taskId);
|
||||
async enqueueBatchTask(taskId: string, preparedMessage?: { messageRecordId: string; queuePriority?: string | null }) {
|
||||
return this.submission.enqueueBatchTask(taskId, preparedMessage);
|
||||
}
|
||||
|
||||
async cancelBatchTask(taskId: string, tenantId?: string, sourceType = 'client') {
|
||||
@@ -599,10 +599,11 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
|
||||
data: GatewayInboundSubmitDto & { phoneNumber: string },
|
||||
messageId: string,
|
||||
submitGroupMessageId: string,
|
||||
application: NonNullable<Awaited<ReturnType<SendChainService['findInboundApplication']>>>,
|
||||
synchronousRejection?: { code: string; reason: string },
|
||||
receiptRejection?: { code: string; reason: string },
|
||||
) {
|
||||
return this.submission.submitInboundSingleMessage(data, messageId, submitGroupMessageId, synchronousRejection, receiptRejection);
|
||||
return this.submission.submitInboundSingleMessage(data, messageId, submitGroupMessageId, application, synchronousRejection, receiptRejection);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -67,7 +67,19 @@ export class SendGatewaySubmitService {
|
||||
}
|
||||
|
||||
|
||||
async enqueueBatchTask(taskId: string) {
|
||||
async enqueueBatchTask(taskId: string, preparedMessage?: { messageRecordId: string; queuePriority?: string | null }) {
|
||||
if (preparedMessage) {
|
||||
// CMPP内部任务在当前请求内刚完成持久化且不暴露取消入口,可安全复用已知ID;
|
||||
// 普通批量任务仍走下方查询路径,以保留取消检查和多消息枚举语义。
|
||||
const queuePriority = normalizeQueuePriority(preparedMessage.queuePriority);
|
||||
await this.facade.getSendQueue().add('send-message', { messageRecordId: preparedMessage.messageRecordId }, {
|
||||
jobId: preparedMessage.messageRecordId,
|
||||
attempts: 3,
|
||||
priority: BULLMQ_PRIORITY[queuePriority],
|
||||
});
|
||||
await this.prisma.smsBatchTask.update({ where: { id: taskId }, data: { status: 'queued' } });
|
||||
return { taskId, enqueued: 1 };
|
||||
}
|
||||
const task = await this.prisma.smsBatchTask.findUnique({ where: { id: taskId } });
|
||||
if (!task) {
|
||||
throw new NotFoundException('SMS batch task not found');
|
||||
|
||||
@@ -356,7 +356,7 @@ async submitCompleteInboundMessage(
|
||||
...data,
|
||||
phoneNumber: submission.phoneNumber,
|
||||
phoneNumbers: undefined,
|
||||
}, submission.messageId, submitGroupMessageId, submission.receiptRejection ? undefined : dailyLimitRejection, submission.receiptRejection))));
|
||||
}, submission.messageId, submitGroupMessageId, application, submission.receiptRejection ? undefined : dailyLimitRejection, submission.receiptRejection))));
|
||||
}
|
||||
const first = results[0];
|
||||
return {
|
||||
@@ -550,16 +550,11 @@ async submitInboundSingleMessage(
|
||||
data: GatewayInboundSubmitDto & { phoneNumber: string },
|
||||
messageId: string,
|
||||
submitGroupMessageId: string,
|
||||
application: NonNullable<Awaited<ReturnType<SendSubmissionService['findInboundApplication']>>>,
|
||||
synchronousRejection?: { code: string; reason: string },
|
||||
receiptRejection?: { code: string; reason: string },
|
||||
) {
|
||||
const application = await this.measureInboundStage(
|
||||
'application_lookup',
|
||||
() => this.facade.findInboundApplication(data.account),
|
||||
);
|
||||
if (!application) {
|
||||
throw new BadRequestException('CMPP account is invalid');
|
||||
}
|
||||
// 入口已按账号取得并校验同一个应用快照;复用它可避免每个目标号码再次查询应用、企业和IP白名单。
|
||||
if (data.remoteIp && !isIpAllowed(data.remoteIp, application.ipAllowlist.map((item) => item.ipCidr))) {
|
||||
throw new BadRequestException('CMPP source IP is not in application allowlist');
|
||||
}
|
||||
@@ -715,7 +710,10 @@ async submitInboundSingleMessage(
|
||||
where: { id: task.id },
|
||||
data: { status: 'ready', riskTaskId: risk.task?.id, auditStatus: 'approved' },
|
||||
});
|
||||
await this.facade.enqueueBatchTask(task.id);
|
||||
await this.facade.enqueueBatchTask(task.id, {
|
||||
messageRecordId: message.id,
|
||||
queuePriority,
|
||||
});
|
||||
});
|
||||
};
|
||||
if (receiptRejection) {
|
||||
|
||||
@@ -164,10 +164,11 @@ async submitInboundSingleMessage(
|
||||
data: GatewayInboundSubmitDto & { phoneNumber: string },
|
||||
messageId: string,
|
||||
submitGroupMessageId: string,
|
||||
application: NonNullable<Awaited<ReturnType<SendSubmissionService['findInboundApplication']>>>,
|
||||
synchronousRejection?: { code: string; reason: string },
|
||||
receiptRejection?: { code: string; reason: string },
|
||||
) {
|
||||
return this.inboundEntry.submitInboundSingleMessage(data, messageId, submitGroupMessageId, synchronousRejection, receiptRejection);
|
||||
return this.inboundEntry.submitInboundSingleMessage(data, messageId, submitGroupMessageId, application, synchronousRejection, receiptRejection);
|
||||
}
|
||||
|
||||
async evaluateRiskWithPhoneFrequency(input: {
|
||||
@@ -214,8 +215,8 @@ async runScheduledDispatchScan() {
|
||||
return this.scheduledDispatch.runScheduledDispatchScan();
|
||||
}
|
||||
|
||||
async enqueueBatchTask(taskId: string) {
|
||||
return this.gatewaySubmit.enqueueBatchTask(taskId);
|
||||
async enqueueBatchTask(taskId: string, preparedMessage?: { messageRecordId: string; queuePriority?: string | null }) {
|
||||
return this.gatewaySubmit.enqueueBatchTask(taskId, preparedMessage);
|
||||
}
|
||||
|
||||
startWorker() {
|
||||
|
||||
Reference in New Issue
Block a user