本文引用的文件
本文件面向AI有声书生成平台的数据访问层,围绕Prisma ORM的数据模型设计与DAO模式实现展开,覆盖用户、书籍、章节、音频、订阅等核心实体及其关系映射;同时阐述CRUD封装、事务与连接池、迁移策略、查询优化、索引设计、缓存策略,并提供数据模型ER图与最佳实践建议。
数据访问层主要由以下部分组成:
连接管理:Prisma客户端连接与生命周期
graph TB
subgraph "数据访问层"
PRISMA["Prisma 客户端<br/>连接/查询"]
MODELS["数据模型<br/>schema.prisma"]
DAO_SUB["订阅服务<br/>subscription.service.ts"]
DAO_TTS["TTS服务<br/>tts.service.ts"]
DAO_PLAYER["播放服务<br/>player.service.ts"]
DAO_BOOK["书籍生成服务<br/>book-generator.service.ts"]
end
subgraph "控制器层"
CTRL_SUB["订阅控制器<br/>subscription.controller.ts"]
CTRL_TTS["TTS控制器<br/>tts.controller.ts"]
CTRL_BOOK["书籍生成控制器<br/>book-generator.controller.ts"]
end
MODELS --> PRISMA
DAO_SUB --> PRISMA
DAO_TTS --> PRISMA
DAO_PLAYER --> PRISMA
DAO_BOOK --> PRISMA
CTRL_SUB --> DAO_SUB
CTRL_TTS --> DAO_TTS
CTRL_BOOK --> DAO_BOOK
图表来源
章节来源
章节来源
数据访问层采用“控制器-服务-DAO-Prisma”的分层架构,服务层负责业务规则与数据一致性,DAO层负责具体数据操作,Prisma负责ORM映射与SQL生成。
sequenceDiagram
participant C as "客户端"
participant Ctrl as "控制器"
participant Svc as "服务层"
participant Dao as "DAO/Prisma"
participant DB as "MySQL"
C->>Ctrl : "HTTP 请求"
Ctrl->>Svc : "调用业务方法"
Svc->>Dao : "执行数据操作"
Dao->>DB : "执行查询/更新"
DB-->>Dao : "返回结果"
Dao-->>Svc : "返回实体/统计"
Svc-->>Ctrl : "业务结果"
Ctrl-->>C : "JSON 响应"
图表来源
下图为基于Prisma Schema的核心实体与关系的ER图,涵盖一对一、一对多、多对多关系及关键索引。
erDiagram
USER {
int id PK
string phone UK
string openid UK
string nickname
string avatar
int memberLevel
datetime memberExpireAt
int dailyUsage
string lastUsageDate
datetime createdAt
datetime updatedAt
int usedAudioMinutes
datetime subscriptionResetDate
}
SUBSCRIPTION_PLAN {
int id PK
string name
int level
decimal priceMonthly
decimal priceYearly
text description
text features
boolean isRecommended
boolean isActive
int sortOrder
int dailyGenerations
int perGenerationLimit
int monthlyTokens
int monthlyMinutes
int yearlyTokens
int voiceOptions
string audioQuality
boolean apiAccess
boolean batchProcessing
boolean teamManagement
boolean overageEnabled
decimal overagePrice
datetime createdAt
datetime updatedAt
}
SUBSCRIPTION {
int id PK
int userId FK
int planId FK
datetime startDate
datetime endDate
string status
boolean autoRenew
datetime createdAt
datetime updatedAt
}
TOKEN_BALANCE {
int id PK
int userId UK
int totalTokens
int usedTokens
datetime resetDate
datetime createdAt
datetime updatedAt
}
TOKEN_USAGE {
int id PK
int userId FK
string type
int amount
int contentLength
int orderId
text description
datetime createdAt
}
ORDER {
int id PK
int userId FK
string orderNo UK
int planId
string productType
decimal amount
string status
string paymentMethod
string paymentId
datetime paidAt
datetime createdAt
datetime updatedAt
}
BOOK {
int id PK
int userId
string title
string subtitle
text description
string coverUrl
string targetAudience
string style
string bookScale
int totalChapters
int estimatedWords
int progress
boolean isPublished
longtext outlineJson
text foreword
text afterword
text errorMsg
datetime createdAt
datetime updatedAt
string failedStage
string genStage
text bookAnalysis
}
BOOK_CHAPTER {
int id PK
int bookId FK
int parentId
int level
int number
string title
text summary
text keyPoints
int estimatedWords
longtext content
int wordCount
text contentError
datetime generatedAt
text audioUrl
int audioDuration
text videoUrl
int videoDuration
boolean isPublic
string genStage
text lrcLyrics
}
PLAY_RECORD {
int id PK
int userId FK
int chapterId FK
float progress
float duration
datetime createdAt
datetime updatedAt
}
COMMENT {
int id PK
int userId FK
int chapterId FK
text content
int rating
datetime createdAt
}
FAVORITE {
int id PK
int userId FK
int bookId FK
datetime createdAt
}
AUDIO_RECORD {
int id PK
int userId
string audioId UK
string title
longtext text
int wordCount
string voiceId
string voiceParams
text audioUrl
int audioDuration
int audioSize
string status
text errorMsg
datetime createdAt
datetime updatedAt
}
VIDEO_PROJECT {
int id PK
int userId
string title
text description
string coverUrl
longtext configJson
string outputUrl
int duration
int fileSize
int bookId
int chapterId
string status
int progress
text errorMsg
datetime createdAt
datetime updatedAt
}
USER_PREFERENCE {
int id PK
int userId UK
float playSpeed
string quality
string theme
string defaultVoiceId
int defaultVolume
boolean autoPlayNext
boolean wifiOnlyDownload
datetime createdAt
datetime updatedAt
}
PLAYLIST {
int id PK
int userId FK
string name
text description
datetime createdAt
datetime updatedAt
}
PLAYLIST_ITEM {
int id PK
int playlistId FK
int chapterId
string audioId
int order
}
SIGN_RECORD {
int id PK
int userId FK
datetime createdAt
}
DRAFT {
int id PK
int userId FK
string type
text title
longtext content
text metadata
datetime autoSavedAt
datetime createdAt
datetime updatedAt
}
NOTIFICATION {
string id PK
int userId
string title
text content
boolean isRead
datetime createdAt
}
HOT_SEARCH {
int id PK
string keyword
int count
int sort
datetime createdAt
datetime updatedAt
}
SEARCH_HISTORY {
int id PK
int userId FK
string keyword
datetime createdAt
}
FEEDBACK {
string id PK
string type
text title
text content
text contact
text screenshotUrls
string status
datetime createdAt
datetime updatedAt
}
PLATFORM_ACCOUNT {
int id PK
int userId FK
string platform
string nickname
string avatar
text cookies
text headers
boolean isValid
datetime expireTime
datetime createdAt
datetime updatedAt
}
PUBLISH_TASK {
int id PK
int userId FK
int videoProjectId FK
string platform
text title
text description
text tags
text coverUrl
text videoUrl
string status
text errorMsg
text publishedUrl
datetime createdAt
datetime updatedAt
}
VIDEO_MATERIAL {
int id PK
int userId
string type
string name
text url
string thumbnail
text tags
string category
int duration
int size
int width
int height
datetime createdAt
datetime updatedAt
}
USER ||--o{ ORDER : "拥有"
USER ||--o{ SUBSCRIPTION : "拥有"
USER ||--o{ TOKEN_BALANCE : "拥有"
USER ||--o{ TOKEN_USAGE : "产生"
USER ||--o{ BOOK : "创作"
USER ||--o{ COMMENT : "发表"
USER ||--o{ FAVORITE : "收藏"
USER ||--o{ PLAY_RECORD : "播放"
USER ||--o{ AUDIO_RECORD : "生成"
USER ||--o{ VIDEO_PROJECT : "创建"
USER ||--o{ PLAYLIST : "创建"
USER ||--o{ SIGN_RECORD : "签到"
USER ||--o{ DRAFT : "保存"
USER ||--o{ PLATFORM_ACCOUNT : "绑定"
USER ||--o{ PUBLISH_TASK : "发布"
SUBSCRIPTION_PLAN ||--o{ SUBSCRIPTION : "被订阅"
SUBSCRIPTION ||--o{ ORDER : "购买"
BOOK ||--o{ BOOK_CHAPTER : "包含"
BOOK ||--o{ VIDEO_PROJECT : "关联"
BOOK_CHAPTER ||--o{ COMMENT : "被评论"
BOOK_CHAPTER ||--o{ PLAY_RECORD : "被播放"
BOOK_CHAPTER ||--o{ PLAYLIST_ITEM : "被加入"
BOOK_CHAPTER ||--o{ VIDEO_PROJECT : "关联"
PLAYLIST ||--o{ PLAYLIST_ITEM : "包含"
图表来源
章节来源
章节来源
章节来源
章节来源
章节来源
[本节为通用建议,不直接分析具体文件]
sequenceDiagram
participant Client as "客户端"
participant Ctrl as "TTS控制器"
participant Svc as "TTS服务"
participant SubSvc as "订阅服务"
participant Prisma as "Prisma"
participant Storage as "存储服务"
Client->>Ctrl : "POST /tts/generate"
Ctrl->>SubSvc : "checkAudioQuota(userId, textLength)"
SubSvc->>Prisma : "查询用户与配额"
Prisma-->>SubSvc : "返回配额信息"
SubSvc-->>Ctrl : "允许/拒绝"
Ctrl->>Svc : "generateAudio(userId, text, voiceId, params)"
Svc->>Prisma : "创建AudioRecord"
Svc->>Storage : "上传音频"
Storage-->>Svc : "返回URL"
Svc->>Prisma : "更新章节音频/记录"
Svc-->>Ctrl : "返回audioId与URL"
Ctrl-->>Client : "任务已创建"
图表来源
书籍生成服务与播放服务、视频生成服务存在间接耦合(章节音频合并、视频项目关联)。
graph LR
CTRL_SUB["subscription.controller.ts"] --> SVC_SUB["subscription.service.ts"]
CTRL_TTS["tts.controller.ts"] --> SVC_TTS["tts.service.ts"]
CTRL_BOOK["book-generator.controller.ts"] --> SVC_BOOK["book-generator.service.ts"]
SVC_SUB --> PRISMA["Prisma Client"]
SVC_TTS --> PRISMA
SVC_BOOK --> PRISMA
SVC_TTS --> SVC_SUB
图表来源
章节来源
[本节为通用建议,不直接分析具体文件]
章节来源
本数据访问层以Prisma为核心,结合服务层封装与控制器接口,实现了从用户、书籍、章节、音频到订阅与配额的全链路数据管理。通过合理的索引设计、迁移策略与缓存策略,能够满足高并发与复杂业务场景的需求。建议持续完善事务边界、监控与告警体系,保障数据一致性与系统稳定性。