feat: add HTTP API and complete client workflows

This commit is contained in:
hectorzhao
2026-07-16 11:34:06 +08:00
parent 4f07b331e5
commit dcb6162dcf
40 changed files with 2548 additions and 365 deletions
+2 -1
View File
@@ -3,13 +3,14 @@ import { BillingModule } from '../billing/billing.module';
import { PrismaModule } from '../prisma/prisma.module';
import { RiskReviewModule } from '../risk-review/risk-review.module';
import { SmsConfigModule } from '../sms-config/sms-config.module';
import { OpenApiModule } from '../open-api/open-api.module';
import { AdminSendChainController } from './admin-send-chain.controller';
import { ClientSendChainController } from './client-send-chain.controller';
import { GatewayEventsController } from './gateway-events.controller';
import { SendChainService } from './send-chain.service';
@Module({
imports: [PrismaModule, BillingModule, forwardRef(() => RiskReviewModule), SmsConfigModule],
imports: [PrismaModule, BillingModule, forwardRef(() => RiskReviewModule), SmsConfigModule, forwardRef(() => OpenApiModule)],
controllers: [AdminSendChainController, ClientSendChainController, GatewayEventsController],
providers: [SendChainService],
exports: [SendChainService],
+26 -2
View File
@@ -1,4 +1,4 @@
import { BadRequestException, Injectable, Logger, NotFoundException, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { BadRequestException, forwardRef, Inject, Injectable, Logger, NotFoundException, OnModuleDestroy, OnModuleInit, Optional } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { Queue, Worker } from 'bullmq';
import IORedis from 'ioredis';
@@ -9,6 +9,7 @@ import { setTimeout as sleep } from 'node:timers/promises';
import { BillingService } from '../billing/billing.service';
import { PrismaService } from '../prisma/prisma.service';
import { RiskReviewService } from '../risk-review/risk-review.service';
import { OpenApiService } from '../open-api/open-api.service';
export interface CreateBatchTaskDto {
tenantId: string;
@@ -24,6 +25,7 @@ export interface CreateBatchTaskDto {
sourceIp?: string;
userAgent?: string;
sourceType?: 'client' | 'api' | 'cmpp';
clientMessageId?: string;
}
export interface GatewayInboundAuthDto {
@@ -258,6 +260,7 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
private readonly prisma: PrismaService,
private readonly billing: BillingService,
private readonly riskReview: RiskReviewService,
@Optional() @Inject(forwardRef(() => OpenApiService)) private readonly openApi?: OpenApiService,
) {}
onModuleInit() {
@@ -379,6 +382,7 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
signatureId: messageClassification.signatureId,
drainageInfoId: messageClassification.drainageInfoId,
messageId: `MSG-${randomUUID()}`,
clientMessageId: data.clientMessageId,
phoneNumber: phone,
content: data.content,
billingUnits: billing.billingUnitsPerMessage,
@@ -965,6 +969,7 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
destId: data.destId,
content: data.content,
receivedAt: record.receivedAt.toISOString(),
uplinkMessageId: record.id,
},
});
}
@@ -1571,8 +1576,27 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
}
const application = await this.prisma.smsApplication.findUnique({
where: { id: data.applicationId },
select: { cmppAccount: true, downstreamReceiptRetryEnabled: true, downstreamUplinkRetryEnabled: true },
select: { cmppAccount: true, downstreamReceiptRetryEnabled: true, downstreamUplinkRetryEnabled: true, httpConfig: true },
});
try {
await this.openApi?.queueWebhookEvent({
tenantId: data.tenantId,
applicationId: data.applicationId,
messageRecordId: data.messageRecordId,
messageId: data.messageId,
uplinkMessageId: typeof data.payload.uplinkMessageId === 'string' ? data.payload.uplinkMessageId : undefined,
eventType: data.deliveryType,
payload: data.payload,
});
} catch (error) {
this.logger.error(`HTTP webhook queue failed for ${data.deliveryType}/${data.messageId ?? '-'}: ${error instanceof Error ? error.message : String(error)}`);
}
const deliveryMode = data.deliveryType === 'receipt'
? application?.httpConfig?.receiptDeliveryMode ?? 'cmpp'
: application?.httpConfig?.uplinkDeliveryMode ?? 'cmpp';
if (!['cmpp', 'both'].includes(deliveryMode)) {
return null;
}
const payload = { account: application?.cmppAccount, applicationId: data.applicationId, ...data.payload };
const delivery = await this.prisma.cmppDownstreamDelivery.create({
data: {