feat: complete cmpp gateway delivery recovery workflows

This commit is contained in:
hectorzhao
2026-07-08 16:30:06 +08:00
parent cc628d0214
commit 8144f08652
60 changed files with 8901 additions and 94 deletions
+201 -12
View File
@@ -36,8 +36,13 @@ model Tenant {
smsApiRequests SmsApiRequest[]
smsSubmitRecords SmsSubmitRecord[]
smsReceiptRecords SmsReceiptRecord[]
smsMessageSegmentAudits SmsMessageSegmentAudit[]
smsUplinkMessages SmsUplinkMessage[]
smsUplinkMatchCandidates SmsUplinkMatchCandidate[]
cmppDownstreamDeliveries CmppDownstreamDelivery[]
cmppConnectionStates CmppConnectionState[]
gatewayDownstreamRecoveryStatuses GatewayDownstreamRecoveryStatus[]
gatewaySubmitDeadLetters GatewaySubmitDeadLetter[]
}
model EnterpriseCertification {
@@ -338,6 +343,8 @@ model SmsApplication {
callbackUrl String?
cmppAccount String @unique
secretHash String
cmppMaxConnections Int @default(1)
cmppWindowSize Int @default(16)
dailyLimit Int?
customerUnitPrice Int @default(0)
queuePriority String @default("normal")
@@ -354,7 +361,12 @@ model SmsApplication {
sendTasks SmsSendTask[]
batchTasks SmsBatchTask[]
messageRecords SmsMessageRecord[]
uplinkMessages SmsUplinkMessage[]
uplinkMatchCandidates SmsUplinkMatchCandidate[]
downstreamDeliveries CmppDownstreamDelivery[]
connectionStates CmppConnectionState[]
gatewayDownstreamRecoveryStatuses GatewayDownstreamRecoveryStatus[]
gatewaySubmitDeadLetters GatewaySubmitDeadLetter[]
@@index([tenantId, status])
}
@@ -496,8 +508,10 @@ model SmsChannel {
submitSessions CmppSubmitSession[]
submitRecords SmsSubmitRecord[]
receiptRecords SmsReceiptRecord[]
segmentAudits SmsMessageSegmentAudit[]
uplinkMessages SmsUplinkMessage[]
connectionStates CmppConnectionState[]
gatewaySubmitDeadLetters GatewaySubmitDeadLetter[]
@@index([status])
}
@@ -818,6 +832,7 @@ model SmsBatchTask {
messages SmsMessageRecord[]
submitRecords SmsSubmitRecord[]
receiptRecords SmsReceiptRecord[]
segmentAudits SmsMessageSegmentAudit[]
@@index([tenantId, status, createdAt])
@@index([status, scheduledAt])
@@ -876,6 +891,10 @@ model SmsMessageRecord {
channel SmsChannel? @relation(fields: [channelId], references: [id])
submitRecords SmsSubmitRecord[]
receiptRecords SmsReceiptRecord[]
segmentAudits SmsMessageSegmentAudit[]
matchedUplinks SmsUplinkMessage[] @relation("SmsUplinkMatchedMessage")
uplinkMatchCandidates SmsUplinkMatchCandidate[]
downstreamDeliveries CmppDownstreamDelivery[]
@@index([tenantId, status, queuedAt])
@@index([batchTaskId, status])
@@ -922,12 +941,51 @@ model SmsSubmitRecord {
messageRecord SmsMessageRecord @relation(fields: [messageRecordId], references: [id], onDelete: Cascade)
channel SmsChannel @relation(fields: [channelId], references: [id])
session CmppSubmitSession? @relation(fields: [sessionId], references: [id])
segmentAudits SmsMessageSegmentAudit[]
@@index([tenantId, createdAt])
@@index([messageRecordId])
@@index([gatewayMessageId])
}
model SmsMessageSegmentAudit {
id String @id @default(cuid())
tenantId String
batchTaskId String?
messageRecordId String
submitRecordId String?
channelId String?
submitId String
attempt Int @default(0)
segmentTotal Int @default(1)
segmentIndex Int @default(1)
sequenceId Int?
gatewayMessageId String?
submitStatus String @default("queued")
receiptStatus String?
rawStatus String?
compensationType String?
errorCode String?
errorMessage String?
submittedAt DateTime?
deliveredAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
batchTask SmsBatchTask? @relation(fields: [batchTaskId], references: [id])
messageRecord SmsMessageRecord @relation(fields: [messageRecordId], references: [id], onDelete: Cascade)
submitRecord SmsSubmitRecord? @relation(fields: [submitRecordId], references: [id])
channel SmsChannel? @relation(fields: [channelId], references: [id])
@@unique([messageRecordId, submitId, segmentIndex])
@@index([tenantId, createdAt])
@@index([messageRecordId, segmentIndex])
@@index([submitRecordId])
@@index([gatewayMessageId])
@@index([submitStatus, receiptStatus])
}
model SmsReceiptRecord {
id String @id @default(cuid())
tenantId String
@@ -954,21 +1012,152 @@ model SmsReceiptRecord {
}
model SmsUplinkMessage {
id String @id @default(cuid())
tenantId String?
channelId String
messageId String?
sequenceId Int?
phoneNumber String
destId String
content String
receivedAt DateTime
createdAt DateTime @default(now())
id String @id @default(cuid())
tenantId String?
applicationId String?
channelId String
messageRecordId String?
messageId String?
sequenceId Int?
phoneNumber String
destId String
content String
matchStatus String @default("unmatched")
matchReason String?
receivedAt DateTime
createdAt DateTime @default(now())
tenant Tenant? @relation(fields: [tenantId], references: [id])
channel SmsChannel @relation(fields: [channelId], references: [id])
tenant Tenant? @relation(fields: [tenantId], references: [id])
application SmsApplication? @relation(fields: [applicationId], references: [id])
messageRecord SmsMessageRecord? @relation("SmsUplinkMatchedMessage", fields: [messageRecordId], references: [id])
channel SmsChannel @relation(fields: [channelId], references: [id])
matchCandidates SmsUplinkMatchCandidate[]
@@index([tenantId, createdAt])
@@index([applicationId, receivedAt])
@@index([channelId, receivedAt])
@@index([messageRecordId])
@@index([matchStatus])
@@index([phoneNumber])
}
model SmsUplinkMatchCandidate {
id String @id @default(cuid())
uplinkMessageId String
tenantId String
applicationId String
messageRecordId String?
matchSource String
confidence Int @default(50)
reason String?
status String @default("pending")
claimedAt DateTime?
claimedById String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
uplinkMessage SmsUplinkMessage @relation(fields: [uplinkMessageId], references: [id], onDelete: Cascade)
tenant Tenant @relation(fields: [tenantId], references: [id])
application SmsApplication @relation(fields: [applicationId], references: [id])
messageRecord SmsMessageRecord? @relation(fields: [messageRecordId], references: [id])
@@unique([uplinkMessageId, applicationId, messageRecordId])
@@index([uplinkMessageId, status])
@@index([tenantId, status, createdAt])
@@index([applicationId, status, createdAt])
@@index([messageRecordId])
}
model CmppDownstreamDelivery {
id String @id @default(cuid())
tenantId String
applicationId String
messageRecordId String?
messageId String?
deliveryType String
status String @default("pending")
payload Json
retryCount Int @default(0)
nextRetryAt DateTime?
deliveredAt DateTime?
lastError String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
application SmsApplication @relation(fields: [applicationId], references: [id])
messageRecord SmsMessageRecord? @relation(fields: [messageRecordId], references: [id])
@@index([tenantId, status, createdAt])
@@index([applicationId, status, createdAt])
@@index([messageId])
@@index([messageRecordId])
@@index([deliveryType, status])
}
model GatewaySubmitDeadLetter {
id String @id @default(cuid())
streamMessageId String @unique
tenantId String?
applicationId String?
channelId String?
traceId String?
messageId String?
submitId String?
status String @default("pending")
failureCode String
failureMessage String
attempts Int @default(1)
maxAttempts Int @default(3)
commandPayload Json?
rawPayload String?
manualRetryCount Int @default(0)
lastRetryStreamId String?
lastRetriedAt DateTime?
resolvedAt DateTime?
resolvedStatus String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant? @relation(fields: [tenantId], references: [id])
application SmsApplication? @relation(fields: [applicationId], references: [id])
channel SmsChannel? @relation(fields: [channelId], references: [id])
@@index([status, createdAt])
@@index([tenantId, status, createdAt])
@@index([applicationId, status, createdAt])
@@index([channelId, status, createdAt])
@@index([messageId])
@@index([submitId])
}
model GatewayDownstreamRecoveryStatus {
id String @id @default(cuid())
account String @unique
tenantId String?
applicationId String?
gatewayInstanceId String?
state String
lockOwner String?
lockExpiresAt DateTime?
lastAttemptAt DateTime?
lastSuccessAt DateTime?
lastFailureAt DateTime?
nextRetryAt DateTime?
attemptCount Int @default(0)
failureCategory String?
lastError String?
lastSkipReason String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant? @relation(fields: [tenantId], references: [id])
application SmsApplication? @relation(fields: [applicationId], references: [id])
@@index([tenantId, state, updatedAt])
@@index([applicationId, state, updatedAt])
@@index([failureCategory, updatedAt])
@@index([lockOwner, lockExpiresAt])
@@index([state, updatedAt])
@@index([nextRetryAt])
}