generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") } 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[] @@index([phone]) @@index([openid]) } // 订单 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[] @@index([userId, createdAt]) @@index([orderNo]) @@index([status]) } // 播放记录(播放的是章节的音频) model PlayRecord { id Int @id @default(autoincrement()) 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]) chapter BookChapter @relation(fields: [chapterId], references: [id]) @@unique([userId, chapterId]) @@index([userId]) @@index([chapterId]) } // 用户偏好 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]) } // 收藏(收藏的是书籍/专辑) model Favorite { id Int @id @default(autoincrement()) userId Int bookId Int // Book.id(收藏的是整本书籍) createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id]) book Book @relation(fields: [bookId], references: [id], onDelete: Cascade) @@unique([userId, bookId]) @@index([userId]) @@index([bookId]) } // 评论(评论的是章节) model Comment { id Int @id @default(autoincrement()) 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]) chapter BookChapter @relation(fields: [chapterId], references: [id]) @@index([chapterId]) @@index([userId]) } // 系统通知 model Notification { id String @id @default(uuid()) userId String title String content String @db.Text isRead Boolean @default(false) createdAt DateTime @default(now()) } // ============ 书籍/章节(核心内容) ============ // 书籍(专辑) 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[] // 收藏此书籍的用户 @@index([userId, status]) @@index([createdAt]) } // 书籍章节(内容单元) // 一个章节 = 一份内容,有3种形式:content(文本)、audioUrl(音频)、videoUrl(视频) // level=1 表示章(顶级),level=2 表示节,level=3 表示小节 // parentId = 0 表示章(顶级),parentId = 章ID 表示节,parentId = 节ID 表示小节 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[] @@unique([bookId, parentId, level, number]) @@index([bookId]) @@index([bookId, parentId]) @@index([bookId, level]) } // ============ 视频生成模块 ============ // 视频项目(关联到书籍的某个章节) model VideoProject { id Int @id @default(autoincrement()) 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]) chapter BookChapter? @relation(fields: [chapterId], references: [id]) @@index([userId, status]) @@index([createdAt]) } // ============ 搜索历史 ============ model SearchHistory { id Int @id @default(autoincrement()) userId Int // 用户ID,0表示未登录用户 keyword String // 搜索关键词 createdAt DateTime @default(now()) @@index([userId, createdAt]) @@index([userId]) } // 热门搜索词 model HotSearch { id Int @id @default(autoincrement()) keyword String // 搜索关键词 count Int @default(0) // 搜索次数 sort Int @default(0) // 排序,数字越大越靠前 createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([sort]) @@index([count]) } // 签到记录 model SignRecord { id Int @id @default(autoincrement()) userId Int createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id]) @@unique([userId, createdAt]) @@index([userId, createdAt]) } // ============ 订阅套餐系统 ============ // 套餐计划 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[] @@index([level]) @@index([isActive, sortOrder]) } // 用户订阅记录 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]) @@index([userId, status]) @@index([userId, endDate]) } // Token使用记录 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]) @@index([userId, createdAt]) @@index([userId, type]) } // Token余额 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]) @@index([userId]) } // 视频素材库(保留作为独立素材) 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 @@index([userId, type]) @@index([type, category]) } // 音频生成记录 model AudioRecord { id Int @id @default(autoincrement()) 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 @@index([userId]) @@index([audioId]) } // ============ 社交平台发布模块 ============ // 平台账号(存储各平台的登录凭证) 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 @@unique([userId, platform]) @@index([userId]) @@index([platform]) } // 发布任务记录 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 @@index([userId, platform]) @@index([status]) } // ============ 草稿模块 ============ model Draft { id Int @id @default(autoincrement()) userId Int type String // content, outline, audio title String? @db.Text content String? @db.LongText metadata String? @db.Text // JSON 存储额外信息 autoSavedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) @@index([userId, type]) @@index([userId, updatedAt]) } // ============ 播放列表模块 ============ model Playlist { id Int @id @default(autoincrement()) userId Int name String // 播放列表名称 description String? @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) items PlaylistItem[] @@index([userId]) } 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]) @@index([playlistId, order]) }