feat: complete cmpp platform phases 0-5

This commit is contained in:
hectorzhao
2026-07-01 13:22:04 +08:00
parent 824a8b334f
commit ee926fea04
86 changed files with 10490 additions and 14 deletions
+601
View File
@@ -0,0 +1,601 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
}
model Tenant {
id String @id @default(cuid())
name String
code String @unique
status String @default("active")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
users User[]
operationLogs OperationLog[]
fileObjects FileObject[]
enterpriseBlacklists EnterpriseBlacklist[]
accounts TenantAccount[]
accountTransactions AccountTransaction[]
rechargeOrders RechargeOrder[]
smsBillingRecords SmsBillingRecord[]
smsApplications SmsApplication[]
smsSignatures SmsSignature[]
smsTemplates SmsTemplate[]
auditRecords AuditRecord[]
}
model User {
id String @id @default(cuid())
tenantId String?
username String @unique
displayName String
passwordHash String
status String @default("active")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant? @relation(fields: [tenantId], references: [id])
roles UserRole[]
operationLogs OperationLog[]
auditRecords AuditRecord[]
}
model Role {
id String @id @default(cuid())
code String @unique
name String
scope String @default("platform")
description String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
users UserRole[]
permissions RolePermission[]
}
model Permission {
id String @id @default(cuid())
code String @unique
name String
description String?
createdAt DateTime @default(now())
roles RolePermission[]
}
model UserRole {
userId String
roleId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
@@id([userId, roleId])
}
model RolePermission {
roleId String
permissionId String
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
@@id([roleId, permissionId])
}
model OperationLog {
id String @id @default(cuid())
tenantId String?
userId String?
action String
resource String
resourceId String?
ipAddress String?
userAgent String?
detail Json?
createdAt DateTime @default(now())
tenant Tenant? @relation(fields: [tenantId], references: [id])
user User? @relation(fields: [userId], references: [id])
@@index([tenantId, createdAt])
@@index([userId, createdAt])
}
model FileObject {
id String @id @default(cuid())
tenantId String?
bucket String
objectKey String @unique
fileName String
contentType String
sizeBytes BigInt
checksum String?
purpose String
createdAt DateTime @default(now())
tenant Tenant? @relation(fields: [tenantId], references: [id])
}
model PhoneSegment {
id String @id @default(cuid())
prefix String @unique
carrier String
province String?
city String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model SensitiveWord {
id String @id @default(cuid())
word String @unique
level String @default("block")
status String @default("active")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model EnterpriseBlacklist {
id String @id @default(cuid())
tenantId String
phoneNumber String
reason String?
status String @default("active")
createdAt DateTime @default(now())
tenant Tenant @relation(fields: [tenantId], references: [id])
@@unique([tenantId, phoneNumber])
}
model GlobalBlacklist {
id String @id @default(cuid())
phoneNumber String @unique
reason String?
status String @default("active")
createdAt DateTime @default(now())
}
model DrainageField {
id String @id @default(cuid())
code String @unique
name String
fieldType String
required Boolean @default(false)
status String @default("active")
description String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model BillingPlan {
id String @id @default(cuid())
name String
priceCents Int
smsUnits Int
validDays Int
status String @default("active")
description String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
rechargeOrders RechargeOrder[]
}
model TenantAccount {
id String @id @default(cuid())
tenantId String
balanceCents Int @default(0)
smsUnits Int @default(0)
creditCents Int @default(0)
status String @default("active")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
@@unique([tenantId])
}
model AccountTransaction {
id String @id @default(cuid())
tenantId String
transactionType String
amountCents Int @default(0)
smsUnits Int @default(0)
balanceAfter Int @default(0)
relatedType String?
relatedId String?
remark String?
createdAt DateTime @default(now())
tenant Tenant @relation(fields: [tenantId], references: [id])
@@index([tenantId, createdAt])
@@index([relatedType, relatedId])
}
model BillingRule {
id String @id @default(cuid())
code String @unique
name String
chargeBasis String @default("submit_success")
unitPrice Int
status String @default("active")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model RechargeOrder {
id String @id @default(cuid())
tenantId String
planId String?
orderNo String @unique
amountCents Int
smsUnits Int @default(0)
status String @default("created")
payMethod String?
paidAt DateTime?
operatorId String?
remark String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
plan BillingPlan? @relation(fields: [planId], references: [id])
@@index([tenantId, createdAt])
}
model SmsBillingRecord {
id String @id @default(cuid())
tenantId String
applicationId String?
taskId String?
messageId String?
phoneNumber String?
contentLength Int
billingUnits Int
unitPrice Int
amountCents Int
billingStatus String @default("estimated")
transactionId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
@@index([tenantId, createdAt])
@@index([taskId])
@@index([messageId])
}
model SmsApplication {
id String @id @default(cuid())
tenantId String
name String
scene String?
callbackUrl String?
secretHash String
dailyLimit Int?
maxPhonesPerTask Int @default(1000000)
templateMismatchMode String @default("reject")
status String @default("active")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
ipAllowlist SmsApplicationIpAllowlist[]
signatures SmsSignature[]
templates SmsTemplate[]
@@index([tenantId, status])
}
model SmsApplicationIpAllowlist {
id String @id @default(cuid())
applicationId String
ipCidr String
remark String?
createdAt DateTime @default(now())
application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade)
@@unique([applicationId, ipCidr])
}
model SmsSignature {
id String @id @default(cuid())
tenantId String
applicationId String?
name String
purpose String?
drainageInfo Json?
auditStatus String @default("draft")
reportStatus String @default("waiting_material")
rejectReason String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
application SmsApplication? @relation(fields: [applicationId], references: [id])
materials SignatureMaterial[]
templates SmsTemplate[]
reportMaterials SignatureReportMaterial[]
reportTasks ChannelSignatureReportTask[]
@@index([tenantId, auditStatus])
@@index([tenantId, reportStatus])
}
model SignatureMaterial {
id String @id @default(cuid())
signatureId String
fileObjectId String?
materialType String
title String
description String?
createdAt DateTime @default(now())
signature SmsSignature @relation(fields: [signatureId], references: [id], onDelete: Cascade)
}
model SmsTemplate {
id String @id @default(cuid())
tenantId String
applicationId String
signatureId String?
name String
content String
category String?
auditStatus String @default("draft")
rejectReason String?
billingUnits Int @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
application SmsApplication @relation(fields: [applicationId], references: [id])
signature SmsSignature? @relation(fields: [signatureId], references: [id])
variables TemplateVariable[]
@@index([tenantId, auditStatus])
@@index([applicationId])
}
model TemplateVariable {
id String @id @default(cuid())
templateId String
name String
example String?
required Boolean @default(true)
createdAt DateTime @default(now())
template SmsTemplate @relation(fields: [templateId], references: [id], onDelete: Cascade)
@@unique([templateId, name])
}
model AuditRecord {
id String @id @default(cuid())
tenantId String?
targetType String
targetId String
action String
statusBefore String?
statusAfter String
reason String?
reviewerId String?
createdAt DateTime @default(now())
tenant Tenant? @relation(fields: [tenantId], references: [id])
reviewer User? @relation(fields: [reviewerId], references: [id])
@@index([targetType, targetId])
@@index([tenantId, createdAt])
}
model SmsChannel {
id String @id @default(cuid())
code String @unique
name String
carrier String?
protocol String @default("CMPP")
gatewayHost String
gatewayPort Int
enterpriseCode String?
account String
passwordCipher String
srcId String
cmppVersion String @default("3.0")
rateLimitPerSecond Int @default(100)
unitPrice Int @default(0)
status String @default("active")
config Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
groupItems SmsChannelGroupItem[]
routeRules ChannelRouteRule[]
healthMetrics ChannelHealthMetric[]
reportFields ChannelReportField[]
reportTasks ChannelSignatureReportTask[]
reportRecords ChannelSignatureReportRecord[]
@@index([status])
}
model SmsChannelGroup {
id String @id @default(cuid())
code String @unique
name String
description String?
status String @default("active")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
items SmsChannelGroupItem[]
routeRules ChannelRouteRule[]
}
model SmsChannelGroupItem {
id String @id @default(cuid())
groupId String
channelId String
carrier String?
province String?
priority Int @default(100)
weight Int @default(1)
isBackup Boolean @default(false)
rateLimitPerSecond Int?
createdAt DateTime @default(now())
group SmsChannelGroup @relation(fields: [groupId], references: [id], onDelete: Cascade)
channel SmsChannel @relation(fields: [channelId], references: [id])
@@unique([groupId, channelId, carrier, province])
@@index([channelId])
}
model ChannelRouteRule {
id String @id @default(cuid())
tenantId String?
applicationId String?
groupId String
channelId String?
carrier String?
province String?
priority Int @default(100)
status String @default("active")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
group SmsChannelGroup @relation(fields: [groupId], references: [id])
channel SmsChannel? @relation(fields: [channelId], references: [id])
@@index([tenantId, applicationId, status])
@@index([groupId, priority])
}
model ChannelHealthMetric {
id String @id @default(cuid())
channelId String
submitTotal Int @default(0)
submitSuccess Int @default(0)
submitFailed Int @default(0)
receiptSuccess Int @default(0)
receiptFailed Int @default(0)
unknownTotal Int @default(0)
windowStart DateTime
windowEnd DateTime
createdAt DateTime @default(now())
channel SmsChannel @relation(fields: [channelId], references: [id])
@@index([channelId, windowStart])
}
model ChannelReportField {
id String @id @default(cuid())
channelId String
code String
name String
fieldType String
required Boolean @default(false)
description String?
sortOrder Int @default(100)
status String @default("active")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
channel SmsChannel @relation(fields: [channelId], references: [id], onDelete: Cascade)
@@unique([channelId, code])
}
model SignatureReportMaterial {
id String @id @default(cuid())
signatureId String
channelId String
fieldCode String
fieldValue String?
fileObjectId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
signature SmsSignature @relation(fields: [signatureId], references: [id], onDelete: Cascade)
@@unique([signatureId, channelId, fieldCode])
@@index([channelId])
}
model ChannelSignatureReportTask {
id String @id @default(cuid())
tenantId String
signatureId String
channelId String
status String @default("pending")
reason String?
createdById String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
signature SmsSignature @relation(fields: [signatureId], references: [id])
channel SmsChannel @relation(fields: [channelId], references: [id])
records ChannelSignatureReportRecord[]
exportFiles ReportExportFile[]
receiptImports ReportReceiptImport[]
@@index([tenantId, status])
@@index([signatureId, channelId])
}
model ChannelSignatureReportRecord {
id String @id @default(cuid())
taskId String
channelId String
action String
statusBefore String?
statusAfter String
reason String?
operatorId String?
createdAt DateTime @default(now())
task ChannelSignatureReportTask @relation(fields: [taskId], references: [id], onDelete: Cascade)
channel SmsChannel @relation(fields: [channelId], references: [id])
@@index([taskId, createdAt])
@@index([channelId, createdAt])
}
model ReportExportFile {
id String @id @default(cuid())
taskId String
fileObjectId String?
fileName String
rowCount Int @default(0)
status String @default("generated")
createdAt DateTime @default(now())
task ChannelSignatureReportTask @relation(fields: [taskId], references: [id], onDelete: Cascade)
}
model ReportReceiptImport {
id String @id @default(cuid())
taskId String
fileObjectId String?
fileName String
rowCount Int @default(0)
successCount Int @default(0)
failedCount Int @default(0)
status String @default("imported")
result Json?
createdAt DateTime @default(now())
task ChannelSignatureReportTask @relation(fields: [taskId], references: [id], onDelete: Cascade)
}