|
@@ -8,131 +8,116 @@ datasource db {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
model User {
|
|
model User {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
- phone String? @unique
|
|
|
|
|
- openid String? @unique
|
|
|
|
|
- nickname String @default("用户")
|
|
|
|
|
- avatar String @default("")
|
|
|
|
|
- memberLevel Int @default(0)
|
|
|
|
|
- memberExpireAt DateTime?
|
|
|
|
|
- dailyUsage Int @default(0)
|
|
|
|
|
- lastUsageDate String @default("")
|
|
|
|
|
- createdAt DateTime @default(now())
|
|
|
|
|
- updatedAt DateTime @updatedAt
|
|
|
|
|
-
|
|
|
|
|
- // 音频时长配额(2026-04-12 新增)
|
|
|
|
|
- usedAudioMinutes Int @default(0) // 本月已使用音频分钟数
|
|
|
|
|
- subscriptionResetDate DateTime? // 配额重置日期
|
|
|
|
|
-
|
|
|
|
|
- orders Order[]
|
|
|
|
|
- playRecords PlayRecord[]
|
|
|
|
|
- preferences UserPreference?
|
|
|
|
|
- favorites Favorite[]
|
|
|
|
|
- comments Comment[]
|
|
|
|
|
- signRecords SignRecord[]
|
|
|
|
|
- subscriptions Subscription[]
|
|
|
|
|
- tokenUsages TokenUsage[]
|
|
|
|
|
- tokenBalance TokenBalance?
|
|
|
|
|
- drafts Draft[]
|
|
|
|
|
- playlists Playlist[]
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
|
|
+ phone String? @unique
|
|
|
|
|
+ openid String? @unique
|
|
|
|
|
+ nickname String @default("用户")
|
|
|
|
|
+ avatar String @default("")
|
|
|
|
|
+ memberLevel Int @default(0)
|
|
|
|
|
+ memberExpireAt DateTime?
|
|
|
|
|
+ dailyUsage Int @default(0)
|
|
|
|
|
+ lastUsageDate String @default("")
|
|
|
|
|
+ createdAt DateTime @default(now())
|
|
|
|
|
+ updatedAt DateTime @updatedAt
|
|
|
|
|
+ usedAudioMinutes Int @default(0)
|
|
|
|
|
+ subscriptionResetDate DateTime?
|
|
|
|
|
+ comments Comment[]
|
|
|
|
|
+ drafts Draft[]
|
|
|
|
|
+ favorites Favorite[]
|
|
|
|
|
+ orders Order[]
|
|
|
|
|
+ playRecords PlayRecord[]
|
|
|
|
|
+ playlists Playlist[]
|
|
|
|
|
+ signRecords SignRecord[]
|
|
|
|
|
+ subscriptions Subscription[]
|
|
|
|
|
+ tokenBalance TokenBalance?
|
|
|
|
|
+ tokenUsages TokenUsage[]
|
|
|
|
|
+ preferences UserPreference?
|
|
|
|
|
|
|
|
@@index([phone])
|
|
@@index([phone])
|
|
|
@@index([openid])
|
|
@@index([openid])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 订单
|
|
|
|
|
model Order {
|
|
model Order {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
- userId Int
|
|
|
|
|
- orderNo String @unique
|
|
|
|
|
- planId Int? // 关联的套餐ID
|
|
|
|
|
- productType String // monthly 或 yearly
|
|
|
|
|
- amount Decimal @db.Decimal(10, 2)
|
|
|
|
|
- status String @default("pending") // pending, paid, failed, refunded
|
|
|
|
|
- paymentMethod String? // alipay, wechat, mock
|
|
|
|
|
- paymentId String? // 第三方支付单号
|
|
|
|
|
- paidAt DateTime?
|
|
|
|
|
- createdAt DateTime @default(now())
|
|
|
|
|
- updatedAt DateTime @updatedAt
|
|
|
|
|
-
|
|
|
|
|
- user User @relation(fields: [userId], references: [id])
|
|
|
|
|
- plan SubscriptionPlan? @relation(fields: [planId], references: [id])
|
|
|
|
|
- tokenUsages TokenUsage[]
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
|
|
+ userId Int
|
|
|
|
|
+ orderNo String @unique
|
|
|
|
|
+ planId Int?
|
|
|
|
|
+ productType String
|
|
|
|
|
+ amount Decimal @db.Decimal(10, 2)
|
|
|
|
|
+ status String @default("pending")
|
|
|
|
|
+ paymentMethod String?
|
|
|
|
|
+ paymentId String?
|
|
|
|
|
+ paidAt DateTime?
|
|
|
|
|
+ createdAt DateTime @default(now())
|
|
|
|
|
+ updatedAt DateTime @updatedAt
|
|
|
|
|
+ plan SubscriptionPlan? @relation(fields: [planId], references: [id])
|
|
|
|
|
+ user User @relation(fields: [userId], references: [id])
|
|
|
|
|
+ tokenUsages TokenUsage[]
|
|
|
|
|
|
|
|
@@index([userId, createdAt])
|
|
@@index([userId, createdAt])
|
|
|
@@index([orderNo])
|
|
@@index([orderNo])
|
|
|
@@index([status])
|
|
@@index([status])
|
|
|
|
|
+ @@index([planId], map: "Order_planId_fkey")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 播放记录(播放的是章节的音频)
|
|
|
|
|
model PlayRecord {
|
|
model PlayRecord {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
userId Int
|
|
userId Int
|
|
|
- chapterId Int // BookChapter.id(章节ID)
|
|
|
|
|
- progress Float @default(0) // 播放进度(秒)
|
|
|
|
|
- duration Float @default(0) // 总时长
|
|
|
|
|
- updatedAt DateTime @updatedAt
|
|
|
|
|
- createdAt DateTime @default(now())
|
|
|
|
|
-
|
|
|
|
|
- user User @relation(fields: [userId], references: [id])
|
|
|
|
|
|
|
+ chapterId Int
|
|
|
|
|
+ progress Float @default(0)
|
|
|
|
|
+ duration Float @default(0)
|
|
|
|
|
+ updatedAt DateTime @updatedAt
|
|
|
|
|
+ createdAt DateTime @default(now())
|
|
|
chapter BookChapter @relation(fields: [chapterId], references: [id])
|
|
chapter BookChapter @relation(fields: [chapterId], references: [id])
|
|
|
|
|
+ user User @relation(fields: [userId], references: [id])
|
|
|
|
|
|
|
|
@@unique([userId, chapterId])
|
|
@@unique([userId, chapterId])
|
|
|
@@index([userId])
|
|
@@index([userId])
|
|
|
@@index([chapterId])
|
|
@@index([chapterId])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 用户偏好
|
|
|
|
|
model UserPreference {
|
|
model UserPreference {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
- userId Int @unique
|
|
|
|
|
- playSpeed Float @default(1.0)
|
|
|
|
|
- quality String @default("standard") // standard, high
|
|
|
|
|
- theme String @default("light")
|
|
|
|
|
- // 新增字段 - 用户偏好设置增强
|
|
|
|
|
- defaultVoiceId String? @default("cherry") // 默认音色ID
|
|
|
|
|
- defaultVolume Int @default(50) // 默认音量 0-100
|
|
|
|
|
- autoPlayNext Boolean @default(true) // 自动播放下一首
|
|
|
|
|
- wifiOnlyDownload Boolean @default(false) // 仅WiFi下载
|
|
|
|
|
- updatedAt DateTime @updatedAt
|
|
|
|
|
- createdAt DateTime @default(now())
|
|
|
|
|
-
|
|
|
|
|
- user User @relation(fields: [userId], references: [id])
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
|
|
+ userId Int @unique
|
|
|
|
|
+ playSpeed Float @default(1)
|
|
|
|
|
+ quality String @default("standard")
|
|
|
|
|
+ theme String @default("light")
|
|
|
|
|
+ defaultVoiceId String? @default("cherry")
|
|
|
|
|
+ defaultVolume Int @default(50)
|
|
|
|
|
+ autoPlayNext Boolean @default(true)
|
|
|
|
|
+ wifiOnlyDownload Boolean @default(false)
|
|
|
|
|
+ updatedAt DateTime @updatedAt
|
|
|
|
|
+ createdAt DateTime @default(now())
|
|
|
|
|
+ user User @relation(fields: [userId], references: [id])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 收藏(收藏的是书籍/专辑)
|
|
|
|
|
model Favorite {
|
|
model Favorite {
|
|
|
id Int @id @default(autoincrement())
|
|
id Int @id @default(autoincrement())
|
|
|
userId Int
|
|
userId Int
|
|
|
- bookId Int // Book.id(收藏的是整本书籍)
|
|
|
|
|
|
|
+ bookId Int
|
|
|
createdAt DateTime @default(now())
|
|
createdAt DateTime @default(now())
|
|
|
-
|
|
|
|
|
- user User @relation(fields: [userId], references: [id])
|
|
|
|
|
book Book @relation(fields: [bookId], references: [id], onDelete: Cascade)
|
|
book Book @relation(fields: [bookId], references: [id], onDelete: Cascade)
|
|
|
|
|
+ user User @relation(fields: [userId], references: [id])
|
|
|
|
|
|
|
|
@@unique([userId, bookId])
|
|
@@unique([userId, bookId])
|
|
|
@@index([userId])
|
|
@@index([userId])
|
|
|
@@index([bookId])
|
|
@@index([bookId])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-
|
|
|
|
|
-// 评论(评论的是章节)
|
|
|
|
|
model Comment {
|
|
model Comment {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
userId Int
|
|
userId Int
|
|
|
- chapterId Int // BookChapter.id
|
|
|
|
|
- content String @db.Text
|
|
|
|
|
- rating Int // 1-5星
|
|
|
|
|
- createdAt DateTime @default(now())
|
|
|
|
|
-
|
|
|
|
|
- user User @relation(fields: [userId], references: [id])
|
|
|
|
|
|
|
+ chapterId Int
|
|
|
|
|
+ content String @db.Text
|
|
|
|
|
+ rating Int
|
|
|
|
|
+ createdAt DateTime @default(now())
|
|
|
chapter BookChapter @relation(fields: [chapterId], references: [id])
|
|
chapter BookChapter @relation(fields: [chapterId], references: [id])
|
|
|
|
|
+ user User @relation(fields: [userId], references: [id])
|
|
|
|
|
|
|
|
@@index([chapterId])
|
|
@@index([chapterId])
|
|
|
@@index([userId])
|
|
@@index([userId])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 系统通知
|
|
|
|
|
model Notification {
|
|
model Notification {
|
|
|
id String @id @default(uuid())
|
|
id String @id @default(uuid())
|
|
|
userId String
|
|
userId String
|
|
@@ -142,89 +127,64 @@ model Notification {
|
|
|
createdAt DateTime @default(now())
|
|
createdAt DateTime @default(now())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-
|
|
|
|
|
-// ============ 书籍/章节(核心内容) ============
|
|
|
|
|
-
|
|
|
|
|
-// 书籍(专辑)
|
|
|
|
|
model Book {
|
|
model Book {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
- userId Int?
|
|
|
|
|
- title String // 书名
|
|
|
|
|
- subtitle String? // 副标题
|
|
|
|
|
- description String @db.Text // 书籍描述/用户输入
|
|
|
|
|
- coverUrl String? // 封面图
|
|
|
|
|
- targetAudience String @default("通用") // 目标受众
|
|
|
|
|
- style String @default("专业严谨") // 写作风格
|
|
|
|
|
- bookScale String @default("标准教程") // 书籍规模:800, 2000, 5000, 小册子, 标准教程, 系统教材
|
|
|
|
|
- totalChapters Int @default(10) // 总章节数
|
|
|
|
|
- estimatedWords Int @default(0) // 预估总字数
|
|
|
|
|
- status String @default("draft") // draft, planning, generating, completed, failed, interrupted
|
|
|
|
|
- progress Int @default(0) // 生成进度 0-100
|
|
|
|
|
- isPublished Boolean @default(false) // 是否已发布
|
|
|
|
|
-
|
|
|
|
|
- // 大纲(JSON 存储)
|
|
|
|
|
- outlineJson String? @db.LongText // BookOutline JSON
|
|
|
|
|
-
|
|
|
|
|
- // 前言/后记
|
|
|
|
|
- foreword String? @db.Text
|
|
|
|
|
- afterword String? @db.Text
|
|
|
|
|
-
|
|
|
|
|
- // 错误信息
|
|
|
|
|
- errorMsg String? @db.Text
|
|
|
|
|
-
|
|
|
|
|
- createdAt DateTime @default(now())
|
|
|
|
|
- updatedAt DateTime @updatedAt
|
|
|
|
|
-
|
|
|
|
|
- chapters BookChapter[]
|
|
|
|
|
- videoProjects VideoProject[] // 关联的视频项目
|
|
|
|
|
- favorites Favorite[] // 收藏此书籍的用户
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
|
|
+ userId Int?
|
|
|
|
|
+ title String
|
|
|
|
|
+ subtitle String?
|
|
|
|
|
+ description String @db.Text
|
|
|
|
|
+ coverUrl String?
|
|
|
|
|
+ targetAudience String @default("通用")
|
|
|
|
|
+ style String @default("专业严谨")
|
|
|
|
|
+ bookScale String @default("标准教程")
|
|
|
|
|
+ totalChapters Int @default(10)
|
|
|
|
|
+ estimatedWords Int @default(0)
|
|
|
|
|
+ progress Int @default(0)
|
|
|
|
|
+ isPublished Boolean @default(false)
|
|
|
|
|
+ outlineJson String? @db.LongText
|
|
|
|
|
+ foreword String? @db.Text
|
|
|
|
|
+ afterword String? @db.Text
|
|
|
|
|
+ errorMsg String? @db.Text
|
|
|
|
|
+ createdAt DateTime @default(now())
|
|
|
|
|
+ updatedAt DateTime @updatedAt
|
|
|
|
|
+ failedStage String? @db.VarChar(30)
|
|
|
|
|
+ genStage String @default("draft")
|
|
|
|
|
+ status String @default("draft")
|
|
|
|
|
+ chapters BookChapter[]
|
|
|
|
|
+ favorites Favorite[]
|
|
|
|
|
+ videoProjects VideoProject[]
|
|
|
|
|
|
|
|
@@index([userId, status])
|
|
@@index([userId, status])
|
|
|
@@index([createdAt])
|
|
@@index([createdAt])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 书籍章节(内容单元)
|
|
|
|
|
-// 一个章节 = 一份内容,有3种形式:content(文本)、audioUrl(音频)、videoUrl(视频)
|
|
|
|
|
-// level=1 表示章(顶级),level=2 表示节,level=3 表示小节
|
|
|
|
|
-// parentId = 0 表示章(顶级),parentId = 章ID 表示节,parentId = 节ID 表示小节
|
|
|
|
|
model BookChapter {
|
|
model BookChapter {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
- bookId Int
|
|
|
|
|
- parentId Int @default(0) // 父节点ID(0 = 章这一级)
|
|
|
|
|
- level Int @default(1) // 层级:1=章, 2=节, 3=小节
|
|
|
|
|
- number Int // 同级排序序号
|
|
|
|
|
- title String // 章节标题
|
|
|
|
|
- summary String? @db.Text // 章节概述
|
|
|
|
|
- keyPoints String? @db.Text // 核心知识点(JSON数组)
|
|
|
|
|
- estimatedWords Int @default(1000) // 预估字数
|
|
|
|
|
- content String? @db.LongText // 正文内容(原始文本)
|
|
|
|
|
- wordCount Int @default(0) // 实际字数
|
|
|
|
|
-
|
|
|
|
|
- // 大纲状态(title, summary, keyPoints)
|
|
|
|
|
- status String @default("pending") // pending, completed, failed
|
|
|
|
|
-
|
|
|
|
|
- // 内容生成状态(content, wordCount)
|
|
|
|
|
- contentStatus String? @db.VarChar(20) // null=未开始, pending=待生成, generating=生成中, completed=已完成, failed=失败
|
|
|
|
|
- contentError String? @db.Text // 内容生成错误信息
|
|
|
|
|
-
|
|
|
|
|
- generatedAt DateTime?
|
|
|
|
|
-
|
|
|
|
|
- // 3种表现形式
|
|
|
|
|
- audioUrl String? @db.Text // 音频URL(由 content 生成)
|
|
|
|
|
- audioDuration Int @default(0) // 音频时长(秒)
|
|
|
|
|
- videoUrl String? @db.Text // 视频URL(由 content/audio 生成)
|
|
|
|
|
- videoDuration Int? // 视频时长(秒)
|
|
|
|
|
-
|
|
|
|
|
- // 公开状态(2026-04-14 新增)
|
|
|
|
|
- isPublic Boolean @default(false) // 是否公开到首页
|
|
|
|
|
-
|
|
|
|
|
- book Book @relation(fields: [bookId], references: [id], onDelete: Cascade)
|
|
|
|
|
- // parent BookChapter? @relation("ChapterChildren", fields: [parentId], references: [id], onDelete: Cascade)
|
|
|
|
|
- // children BookChapter[] @relation("ChapterChildren")
|
|
|
|
|
- videoProjects VideoProject[]
|
|
|
|
|
- playRecords PlayRecord[]
|
|
|
|
|
- comments Comment[]
|
|
|
|
|
- playlistItems PlaylistItem[]
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
|
|
+ bookId Int
|
|
|
|
|
+ parentId Int @default(0)
|
|
|
|
|
+ level Int @default(1)
|
|
|
|
|
+ number Int
|
|
|
|
|
+ title String
|
|
|
|
|
+ summary String? @db.Text
|
|
|
|
|
+ keyPoints String? @db.Text
|
|
|
|
|
+ estimatedWords Int @default(1000)
|
|
|
|
|
+ content String? @db.LongText
|
|
|
|
|
+ wordCount Int @default(0)
|
|
|
|
|
+ contentError String? @db.Text
|
|
|
|
|
+ generatedAt DateTime?
|
|
|
|
|
+ audioUrl String? @db.Text
|
|
|
|
|
+ audioDuration Int @default(0)
|
|
|
|
|
+ videoUrl String? @db.Text
|
|
|
|
|
+ videoDuration Int?
|
|
|
|
|
+ isPublic Boolean @default(false)
|
|
|
|
|
+ genStage String @default("idle")
|
|
|
|
|
+ lrcLyrics String? @db.Text
|
|
|
|
|
+ status String @default("pending")
|
|
|
|
|
+ book Book @relation(fields: [bookId], references: [id], onDelete: Cascade)
|
|
|
|
|
+ comments Comment[]
|
|
|
|
|
+ playRecords PlayRecord[]
|
|
|
|
|
+ playlistItems PlaylistItem[]
|
|
|
|
|
+ videoProjects VideoProject[]
|
|
|
|
|
|
|
|
@@unique([bookId, parentId, level, number])
|
|
@@unique([bookId, parentId, level, number])
|
|
|
@@index([bookId])
|
|
@@index([bookId])
|
|
@@ -232,61 +192,47 @@ model BookChapter {
|
|
|
@@index([bookId, level])
|
|
@@index([bookId, level])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-
|
|
|
|
|
-// ============ 视频生成模块 ============
|
|
|
|
|
-
|
|
|
|
|
-// 视频项目(关联到书籍的某个章节)
|
|
|
|
|
model VideoProject {
|
|
model VideoProject {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
userId Int?
|
|
userId Int?
|
|
|
- title String // 项目标题
|
|
|
|
|
- description String? // 描述
|
|
|
|
|
- coverUrl String? // 封面图
|
|
|
|
|
-
|
|
|
|
|
- // 素材配置(JSON存储)
|
|
|
|
|
- configJson String? @db.LongText // VideoConfig JSON
|
|
|
|
|
-
|
|
|
|
|
- // 输出视频
|
|
|
|
|
- outputUrl String? // 生成后的视频URL
|
|
|
|
|
- duration Int? // 视频时长(秒)
|
|
|
|
|
- fileSize Int? // 文件大小(字节)
|
|
|
|
|
-
|
|
|
|
|
- // 关联:直接关联到章节
|
|
|
|
|
- bookId Int? // 关联的书籍ID(可选)
|
|
|
|
|
- chapterId Int? // 关联的章节ID(直接关联章节获取 content/audioUrl)
|
|
|
|
|
-
|
|
|
|
|
- status String @default("draft") // draft, processing, completed, failed
|
|
|
|
|
- progress Int @default(0) // 0-100
|
|
|
|
|
- errorMsg String? @db.Text
|
|
|
|
|
-
|
|
|
|
|
- createdAt DateTime @default(now())
|
|
|
|
|
- updatedAt DateTime @updatedAt
|
|
|
|
|
-
|
|
|
|
|
- book Book? @relation(fields: [bookId], references: [id])
|
|
|
|
|
|
|
+ title String
|
|
|
|
|
+ description String?
|
|
|
|
|
+ coverUrl String?
|
|
|
|
|
+ configJson String? @db.LongText
|
|
|
|
|
+ outputUrl String?
|
|
|
|
|
+ duration Int?
|
|
|
|
|
+ fileSize Int?
|
|
|
|
|
+ bookId Int?
|
|
|
|
|
+ chapterId Int?
|
|
|
|
|
+ status String @default("draft")
|
|
|
|
|
+ progress Int @default(0)
|
|
|
|
|
+ errorMsg String? @db.Text
|
|
|
|
|
+ createdAt DateTime @default(now())
|
|
|
|
|
+ updatedAt DateTime @updatedAt
|
|
|
|
|
+ book Book? @relation(fields: [bookId], references: [id])
|
|
|
chapter BookChapter? @relation(fields: [chapterId], references: [id])
|
|
chapter BookChapter? @relation(fields: [chapterId], references: [id])
|
|
|
|
|
|
|
|
@@index([userId, status])
|
|
@@index([userId, status])
|
|
|
@@index([createdAt])
|
|
@@index([createdAt])
|
|
|
|
|
+ @@index([bookId], map: "VideoProject_bookId_fkey")
|
|
|
|
|
+ @@index([chapterId], map: "VideoProject_chapterId_fkey")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// ============ 搜索历史 ============
|
|
|
|
|
-
|
|
|
|
|
model SearchHistory {
|
|
model SearchHistory {
|
|
|
id Int @id @default(autoincrement())
|
|
id Int @id @default(autoincrement())
|
|
|
- userId Int // 用户ID,0表示未登录用户
|
|
|
|
|
- keyword String // 搜索关键词
|
|
|
|
|
|
|
+ userId Int
|
|
|
|
|
+ keyword String
|
|
|
createdAt DateTime @default(now())
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
|
|
@@index([userId, createdAt])
|
|
@@index([userId, createdAt])
|
|
|
@@index([userId])
|
|
@@index([userId])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 热门搜索词
|
|
|
|
|
model HotSearch {
|
|
model HotSearch {
|
|
|
id Int @id @default(autoincrement())
|
|
id Int @id @default(autoincrement())
|
|
|
- keyword String // 搜索关键词
|
|
|
|
|
- count Int @default(0) // 搜索次数
|
|
|
|
|
- sort Int @default(0) // 排序,数字越大越靠前
|
|
|
|
|
|
|
+ keyword String
|
|
|
|
|
+ count Int @default(0)
|
|
|
|
|
+ sort Int @default(0)
|
|
|
createdAt DateTime @default(now())
|
|
createdAt DateTime @default(now())
|
|
|
updatedAt DateTime @updatedAt
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
@@ -294,238 +240,231 @@ model HotSearch {
|
|
|
@@index([count])
|
|
@@index([count])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 签到记录
|
|
|
|
|
model SignRecord {
|
|
model SignRecord {
|
|
|
id Int @id @default(autoincrement())
|
|
id Int @id @default(autoincrement())
|
|
|
userId Int
|
|
userId Int
|
|
|
createdAt DateTime @default(now())
|
|
createdAt DateTime @default(now())
|
|
|
-
|
|
|
|
|
user User @relation(fields: [userId], references: [id])
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
|
|
|
|
@@unique([userId, createdAt])
|
|
@@unique([userId, createdAt])
|
|
|
@@index([userId, createdAt])
|
|
@@index([userId, createdAt])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// ============ 订阅套餐系统 ============
|
|
|
|
|
-
|
|
|
|
|
-// 套餐计划
|
|
|
|
|
model SubscriptionPlan {
|
|
model SubscriptionPlan {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
- name String // 套餐名称
|
|
|
|
|
- level Int @default(0) // 套餐等级:0免费 1基础 2专业 3旗舰
|
|
|
|
|
- priceMonthly Decimal @db.Decimal(10, 2) @default(0) // 月付价格
|
|
|
|
|
- priceYearly Decimal @db.Decimal(10, 2) @default(0) // 年付价格
|
|
|
|
|
- description String? @db.Text // 套餐描述
|
|
|
|
|
- features String? @db.Text // 功能列表(JSON数组)
|
|
|
|
|
- isRecommended Boolean @default(false) // 是否推荐
|
|
|
|
|
- isActive Boolean @default(true) // 是否上架
|
|
|
|
|
- sortOrder Int @default(0) // 排序
|
|
|
|
|
-
|
|
|
|
|
- // 限制配置
|
|
|
|
|
- dailyGenerations Int @default(3) // 每日生成次数,-1表示无限制
|
|
|
|
|
- perGenerationLimit Int @default(2000) // 单次生成限制字数
|
|
|
|
|
- monthlyTokens Int @default(10000) // 每月token配额,-1表示无限制
|
|
|
|
|
- monthlyMinutes Int @default(30) // 每月音频时长配额(分钟)
|
|
|
|
|
- yearlyTokens Int? // 每年token配额(旗舰版用)
|
|
|
|
|
- voiceOptions Int @default(5) // 可用音色数,-1表示全部
|
|
|
|
|
- audioQuality String @default("standard") // standard, high, lossless
|
|
|
|
|
-
|
|
|
|
|
- // 高级功能
|
|
|
|
|
- apiAccess Boolean @default(false) // API访问权限
|
|
|
|
|
- batchProcessing Boolean @default(false) // 批量处理权限
|
|
|
|
|
- teamManagement Boolean @default(false) // 团队管理权限
|
|
|
|
|
- overageEnabled Boolean @default(false) // 是否允许超出配额
|
|
|
|
|
- overagePrice Decimal @db.Decimal(10, 2) @default(0) // 超出配额价格(元/分钟)
|
|
|
|
|
-
|
|
|
|
|
- createdAt DateTime @default(now())
|
|
|
|
|
- updatedAt DateTime @updatedAt
|
|
|
|
|
-
|
|
|
|
|
- subscriptions Subscription[]
|
|
|
|
|
- orders Order[]
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
|
|
+ name String
|
|
|
|
|
+ level Int @default(0)
|
|
|
|
|
+ priceMonthly Decimal @default(0.00) @db.Decimal(10, 2)
|
|
|
|
|
+ priceYearly Decimal @default(0.00) @db.Decimal(10, 2)
|
|
|
|
|
+ description String? @db.Text
|
|
|
|
|
+ features String? @db.Text
|
|
|
|
|
+ isRecommended Boolean @default(false)
|
|
|
|
|
+ isActive Boolean @default(true)
|
|
|
|
|
+ sortOrder Int @default(0)
|
|
|
|
|
+ dailyGenerations Int @default(3)
|
|
|
|
|
+ perGenerationLimit Int @default(2000)
|
|
|
|
|
+ monthlyTokens Int @default(10000)
|
|
|
|
|
+ monthlyMinutes Int @default(30)
|
|
|
|
|
+ yearlyTokens Int?
|
|
|
|
|
+ voiceOptions Int @default(5)
|
|
|
|
|
+ audioQuality String @default("standard")
|
|
|
|
|
+ apiAccess Boolean @default(false)
|
|
|
|
|
+ batchProcessing Boolean @default(false)
|
|
|
|
|
+ teamManagement Boolean @default(false)
|
|
|
|
|
+ overageEnabled Boolean @default(false)
|
|
|
|
|
+ overagePrice Decimal @default(0.00) @db.Decimal(10, 2)
|
|
|
|
|
+ createdAt DateTime @default(now())
|
|
|
|
|
+ updatedAt DateTime @updatedAt
|
|
|
|
|
+ orders Order[]
|
|
|
|
|
+ subscriptions Subscription[]
|
|
|
|
|
|
|
|
@@index([level])
|
|
@@index([level])
|
|
|
@@index([isActive, sortOrder])
|
|
@@index([isActive, sortOrder])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 用户订阅记录
|
|
|
|
|
model Subscription {
|
|
model Subscription {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
- userId Int
|
|
|
|
|
- planId Int
|
|
|
|
|
- startDate DateTime
|
|
|
|
|
- endDate DateTime
|
|
|
|
|
- status String @default("active") // active, expired, cancelled
|
|
|
|
|
- autoRenew Boolean @default(false) // 自动续费
|
|
|
|
|
- createdAt DateTime @default(now())
|
|
|
|
|
- updatedAt DateTime @updatedAt
|
|
|
|
|
-
|
|
|
|
|
- user User @relation(fields: [userId], references: [id])
|
|
|
|
|
- plan SubscriptionPlan @relation(fields: [planId], references: [id])
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
|
|
+ userId Int
|
|
|
|
|
+ planId Int
|
|
|
|
|
+ startDate DateTime
|
|
|
|
|
+ endDate DateTime
|
|
|
|
|
+ status String @default("active")
|
|
|
|
|
+ autoRenew Boolean @default(false)
|
|
|
|
|
+ createdAt DateTime @default(now())
|
|
|
|
|
+ updatedAt DateTime @updatedAt
|
|
|
|
|
+ plan SubscriptionPlan @relation(fields: [planId], references: [id])
|
|
|
|
|
+ user User @relation(fields: [userId], references: [id])
|
|
|
|
|
|
|
|
@@index([userId, status])
|
|
@@index([userId, status])
|
|
|
@@index([userId, endDate])
|
|
@@index([userId, endDate])
|
|
|
|
|
+ @@index([planId], map: "Subscription_planId_fkey")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Token使用记录
|
|
|
|
|
model TokenUsage {
|
|
model TokenUsage {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
- userId Int
|
|
|
|
|
- type String // text_to_speech, api_call, batch_process
|
|
|
|
|
- amount Int @default(0) // 消耗token数量
|
|
|
|
|
- contentLength Int @default(0) // 内容长度(字数)
|
|
|
|
|
- orderId Int? // 关联的订单ID
|
|
|
|
|
- description String? @db.Text // 消耗描述
|
|
|
|
|
- createdAt DateTime @default(now())
|
|
|
|
|
-
|
|
|
|
|
- user User @relation(fields: [userId], references: [id])
|
|
|
|
|
- order Order? @relation(fields: [orderId], references: [id])
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
|
|
+ userId Int
|
|
|
|
|
+ type String
|
|
|
|
|
+ amount Int @default(0)
|
|
|
|
|
+ contentLength Int @default(0)
|
|
|
|
|
+ orderId Int?
|
|
|
|
|
+ description String? @db.Text
|
|
|
|
|
+ createdAt DateTime @default(now())
|
|
|
|
|
+ order Order? @relation(fields: [orderId], references: [id])
|
|
|
|
|
+ user User @relation(fields: [userId], references: [id])
|
|
|
|
|
|
|
|
@@index([userId, createdAt])
|
|
@@index([userId, createdAt])
|
|
|
@@index([userId, type])
|
|
@@index([userId, type])
|
|
|
|
|
+ @@index([orderId], map: "TokenUsage_orderId_fkey")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Token余额
|
|
|
|
|
model TokenBalance {
|
|
model TokenBalance {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
- userId Int @unique
|
|
|
|
|
- totalTokens Int @default(0) // 总token配额
|
|
|
|
|
- usedTokens Int @default(0) // 已使用token
|
|
|
|
|
- resetDate DateTime? // 重置日期(月/年)
|
|
|
|
|
- createdAt DateTime @default(now())
|
|
|
|
|
- updatedAt DateTime @updatedAt
|
|
|
|
|
-
|
|
|
|
|
- user User @relation(fields: [userId], references: [id])
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
|
|
+ userId Int @unique
|
|
|
|
|
+ totalTokens Int @default(0)
|
|
|
|
|
+ usedTokens Int @default(0)
|
|
|
|
|
+ resetDate DateTime?
|
|
|
|
|
+ createdAt DateTime @default(now())
|
|
|
|
|
+ updatedAt DateTime @updatedAt
|
|
|
|
|
+ user User @relation(fields: [userId], references: [id])
|
|
|
|
|
|
|
|
@@index([userId])
|
|
@@index([userId])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 视频素材库(保留作为独立素材)
|
|
|
|
|
model VideoMaterial {
|
|
model VideoMaterial {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
- userId Int? // null 表示公共素材
|
|
|
|
|
- type String // image, audio, template
|
|
|
|
|
- name String // 素材名称
|
|
|
|
|
- url String @db.Text // 素材URL
|
|
|
|
|
- thumbnail String? // 缩略图URL
|
|
|
|
|
- tags String? @db.Text // 标签(JSON数组)
|
|
|
|
|
- category String? // 分类:nature, abstract, business, music等
|
|
|
|
|
- duration Int? // 音频时长(秒)
|
|
|
|
|
- size Int? // 文件大小(字节)
|
|
|
|
|
- width Int? // 图片宽度
|
|
|
|
|
- height Int? // 图片高度
|
|
|
|
|
- createdAt DateTime @default(now())
|
|
|
|
|
- updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
|
|
+ userId Int?
|
|
|
|
|
+ type String
|
|
|
|
|
+ name String
|
|
|
|
|
+ url String @db.Text
|
|
|
|
|
+ thumbnail String?
|
|
|
|
|
+ tags String? @db.Text
|
|
|
|
|
+ category String?
|
|
|
|
|
+ duration Int?
|
|
|
|
|
+ size Int?
|
|
|
|
|
+ width Int?
|
|
|
|
|
+ height Int?
|
|
|
|
|
+ createdAt DateTime @default(now())
|
|
|
|
|
+ updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
@@index([userId, type])
|
|
@@index([userId, type])
|
|
|
@@index([type, category])
|
|
@@index([type, category])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 音频生成记录
|
|
|
|
|
model AudioRecord {
|
|
model AudioRecord {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
userId Int?
|
|
userId Int?
|
|
|
- audioId String @unique // uuid,对应磁盘上的文件夹名
|
|
|
|
|
- title String @default("未命名音频")
|
|
|
|
|
- text String? @db.LongText
|
|
|
|
|
- wordCount Int @default(0)
|
|
|
|
|
- voiceId String @default("cherry")
|
|
|
|
|
- voiceParams String? // JSON: {speed, pitch, volume}
|
|
|
|
|
- audioUrl String? @db.Text
|
|
|
|
|
- audioDuration Int @default(0) // 秒
|
|
|
|
|
- audioSize Int @default(0) // 字节
|
|
|
|
|
- status String @default("processing") // processing, completed, failed
|
|
|
|
|
- errorMsg String? @db.Text
|
|
|
|
|
- createdAt DateTime @default(now())
|
|
|
|
|
- updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
+ audioId String @unique
|
|
|
|
|
+ title String @default("未命名音频")
|
|
|
|
|
+ text String? @db.LongText
|
|
|
|
|
+ wordCount Int @default(0)
|
|
|
|
|
+ voiceId String @default("cherry")
|
|
|
|
|
+ voiceParams String?
|
|
|
|
|
+ audioUrl String? @db.Text
|
|
|
|
|
+ audioDuration Int @default(0)
|
|
|
|
|
+ audioSize Int @default(0)
|
|
|
|
|
+ status String @default("processing")
|
|
|
|
|
+ errorMsg String? @db.Text
|
|
|
|
|
+ createdAt DateTime @default(now())
|
|
|
|
|
+ updatedAt DateTime @updatedAt
|
|
|
|
|
+ bookId Int?
|
|
|
|
|
|
|
|
@@index([userId])
|
|
@@index([userId])
|
|
|
@@index([audioId])
|
|
@@index([audioId])
|
|
|
|
|
+ @@index([bookId])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// ============ 社交平台发布模块 ============
|
|
|
|
|
-
|
|
|
|
|
-// 平台账号(存储各平台的登录凭证)
|
|
|
|
|
model PlatformAccount {
|
|
model PlatformAccount {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
- userId Int
|
|
|
|
|
- platform String // douyin, kuaishou, bilibili
|
|
|
|
|
- nickname String @default("")
|
|
|
|
|
- avatar String @default("")
|
|
|
|
|
- cookies String @db.Text // 登录 Cookie
|
|
|
|
|
- headers String? @db.Text // 关键请求头(JSON)
|
|
|
|
|
- isValid Boolean @default(true)
|
|
|
|
|
- expireTime DateTime? // 凭证过期时间
|
|
|
|
|
- createdAt DateTime @default(now())
|
|
|
|
|
- updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
|
|
+ userId Int
|
|
|
|
|
+ platform String
|
|
|
|
|
+ nickname String @default("")
|
|
|
|
|
+ avatar String @default("")
|
|
|
|
|
+ cookies String @db.Text
|
|
|
|
|
+ headers String? @db.Text
|
|
|
|
|
+ isValid Boolean @default(true)
|
|
|
|
|
+ expireTime DateTime?
|
|
|
|
|
+ createdAt DateTime @default(now())
|
|
|
|
|
+ updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
@@unique([userId, platform])
|
|
@@unique([userId, platform])
|
|
|
@@index([userId])
|
|
@@index([userId])
|
|
|
@@index([platform])
|
|
@@index([platform])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 发布任务记录
|
|
|
|
|
model PublishTask {
|
|
model PublishTask {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
- userId Int
|
|
|
|
|
- videoProjectId Int
|
|
|
|
|
- platform String // douyin, kuaishou, bilibili
|
|
|
|
|
- title String @db.Text
|
|
|
|
|
- description String? @db.Text
|
|
|
|
|
- tags String? @db.Text // JSON 数组
|
|
|
|
|
- coverUrl String? @db.Text
|
|
|
|
|
- videoUrl String? @db.Text // 本地视频路径
|
|
|
|
|
- status String @default("pending") // pending, uploading, success, failed
|
|
|
|
|
- errorMsg String? @db.Text
|
|
|
|
|
- publishedUrl String? @db.Text // 发布后的作品链接
|
|
|
|
|
- createdAt DateTime @default(now())
|
|
|
|
|
- updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
|
|
+ userId Int
|
|
|
|
|
+ videoProjectId Int
|
|
|
|
|
+ platform String
|
|
|
|
|
+ title String @db.Text
|
|
|
|
|
+ description String? @db.Text
|
|
|
|
|
+ tags String? @db.Text
|
|
|
|
|
+ coverUrl String? @db.Text
|
|
|
|
|
+ videoUrl String? @db.Text
|
|
|
|
|
+ status String @default("pending")
|
|
|
|
|
+ errorMsg String? @db.Text
|
|
|
|
|
+ publishedUrl String? @db.Text
|
|
|
|
|
+ createdAt DateTime @default(now())
|
|
|
|
|
+ updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
@@index([userId, platform])
|
|
@@index([userId, platform])
|
|
|
@@index([status])
|
|
@@index([status])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// ============ 草稿模块 ============
|
|
|
|
|
model Draft {
|
|
model Draft {
|
|
|
id Int @id @default(autoincrement())
|
|
id Int @id @default(autoincrement())
|
|
|
userId Int
|
|
userId Int
|
|
|
- type String // content, outline, audio
|
|
|
|
|
|
|
+ type String
|
|
|
title String? @db.Text
|
|
title String? @db.Text
|
|
|
content String? @db.LongText
|
|
content String? @db.LongText
|
|
|
- metadata String? @db.Text // JSON 存储额外信息
|
|
|
|
|
|
|
+ metadata String? @db.Text
|
|
|
autoSavedAt DateTime?
|
|
autoSavedAt DateTime?
|
|
|
createdAt DateTime @default(now())
|
|
createdAt DateTime @default(now())
|
|
|
updatedAt DateTime @updatedAt
|
|
updatedAt DateTime @updatedAt
|
|
|
-
|
|
|
|
|
user User @relation(fields: [userId], references: [id])
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
|
|
|
|
@@index([userId, type])
|
|
@@index([userId, type])
|
|
|
@@index([userId, updatedAt])
|
|
@@index([userId, updatedAt])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// ============ 播放列表模块 ============
|
|
|
|
|
model Playlist {
|
|
model Playlist {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
userId Int
|
|
userId Int
|
|
|
- name String // 播放列表名称
|
|
|
|
|
- description String? @db.Text
|
|
|
|
|
- createdAt DateTime @default(now())
|
|
|
|
|
- updatedAt DateTime @updatedAt
|
|
|
|
|
-
|
|
|
|
|
- user User @relation(fields: [userId], references: [id])
|
|
|
|
|
|
|
+ name String
|
|
|
|
|
+ description String? @db.Text
|
|
|
|
|
+ createdAt DateTime @default(now())
|
|
|
|
|
+ updatedAt DateTime @updatedAt
|
|
|
|
|
+ user User @relation(fields: [userId], references: [id])
|
|
|
items PlaylistItem[]
|
|
items PlaylistItem[]
|
|
|
|
|
|
|
|
@@index([userId])
|
|
@@index([userId])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
model PlaylistItem {
|
|
model PlaylistItem {
|
|
|
- id Int @id @default(autoincrement())
|
|
|
|
|
- playlistId Int
|
|
|
|
|
- chapterId Int? // 关联的章节ID
|
|
|
|
|
- audioId String? // 关联的音频ID(独立音频)
|
|
|
|
|
- order Int @default(0) // 排序
|
|
|
|
|
-
|
|
|
|
|
- playlist Playlist @relation(fields: [playlistId], references: [id], onDelete: Cascade)
|
|
|
|
|
- chapter BookChapter? @relation(fields: [chapterId], references: [id])
|
|
|
|
|
|
|
+ id Int @id @default(autoincrement())
|
|
|
|
|
+ playlistId Int
|
|
|
|
|
+ chapterId Int?
|
|
|
|
|
+ audioId String?
|
|
|
|
|
+ order Int @default(0)
|
|
|
|
|
+ chapter BookChapter? @relation(fields: [chapterId], references: [id])
|
|
|
|
|
+ playlist Playlist @relation(fields: [playlistId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
|
|
@@index([playlistId, order])
|
|
@@index([playlistId, order])
|
|
|
|
|
+ @@index([chapterId], map: "PlaylistItem_chapterId_fkey")
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+model Feedback {
|
|
|
|
|
+ id String @id @default(uuid())
|
|
|
|
|
+ type String
|
|
|
|
|
+ title String @db.Text
|
|
|
|
|
+ content String @db.Text
|
|
|
|
|
+ contact String @db.Text
|
|
|
|
|
+ screenshotUrls String @db.Text
|
|
|
|
|
+ status String @default("submitted")
|
|
|
|
|
+ createdAt DateTime @default(now())
|
|
|
|
|
+ updatedAt DateTime @updatedAt
|
|
|
|
|
+
|
|
|
|
|
+ @@index([type])
|
|
|
|
|
+ @@index([status])
|
|
|
|
|
+ @@index([createdAt])
|
|
|
}
|
|
}
|