# 数据访问层
**本文引用的文件**
- [schema.prisma](file://server/prisma/schema.prisma)
- [models/index.ts](file://server/src/models/index.ts)
- [book-generator.store.ts](file://server/src/modules/book-generator/book-generator.store.ts)
- [book-generator.types.ts](file://server/src/modules/book-generator/book-generator.types.ts)
- [tts.service.ts](file://server/src/modules/tts/tts.service.ts)
- [database-structure.md](file://docs/database-structure.md)
- [migrate-genstage.ts](file://server/prisma/migrate-genstage.ts)
- [sync-genstage.ts](file://server/prisma/sync-genstage.ts)
- [config/index.ts](file://server/src/config/index.ts)
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖分析](#依赖分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件面向AI有声书生成平台的数据访问层,系统性梳理Prisma ORM在本项目中的使用方式与数据模型设计,覆盖实体关系映射、查询构建、事务管理、迁移策略、版本管理与数据同步机制,并给出查询优化、索引设计、性能调优、数据验证与完整性保障、最佳实践(批量操作、分页查询、缓存策略)以及数据库设计指南。
## 项目结构
数据访问层主要由以下部分构成:
- Prisma Schema:定义数据模型、索引、关系与约束
- Prisma Client:在应用中初始化与连接数据库
- 业务模块存储层:以类封装对Prisma的读写操作,提供领域语义的接口
- TTS服务:与音频生成流程深度耦合的数据持久化
- 迁移与同步脚本:历史字段到新阶段字段的迁移与一致性同步
```mermaid
graph TB
subgraph "Prisma"
S["schema.prisma
数据模型与关系"]
C["Prisma Client 初始化
models/index.ts"]
end
subgraph "业务模块"
BGS["BookGeneratorStore
book-generator.store.ts"]
TYPES["类型定义
book-generator.types.ts"]
TTS["TTS服务
tts.service.ts"]
end
subgraph "迁移与同步"
MIG["迁移脚本
migrate-genstage.ts"]
SYNC["同步脚本
sync-genstage.ts"]
end
S --> C
C --> BGS
C --> TTS
BGS --> TYPES
MIG --> S
SYNC --> S
```
**图表来源**
- [schema.prisma](file://server/prisma/schema.prisma)
- [models/index.ts](file://server/src/models/index.ts)
- [book-generator.store.ts](file://server/src/modules/book-generator/book-generator.store.ts)
- [book-generator.types.ts](file://server/src/modules/book-generator/book-generator.types.ts)
- [tts.service.ts](file://server/src/modules/tts/tts.service.ts)
- [migrate-genstage.ts](file://server/prisma/migrate-genstage.ts)
- [sync-genstage.ts](file://server/prisma/sync-genstage.ts)
**章节来源**
- [schema.prisma](file://server/prisma/schema.prisma)
- [models/index.ts](file://server/src/models/index.ts)
- [book-generator.store.ts](file://server/src/modules/book-generator/book-generator.store.ts)
- [book-generator.types.ts](file://server/src/modules/book-generator/book-generator.types.ts)
- [tts.service.ts](file://server/src/modules/tts/tts.service.ts)
- [migrate-genstage.ts](file://server/prisma/migrate-genstage.ts)
- [sync-genstage.ts](file://server/prisma/sync-genstage.ts)
## 核心组件
- Prisma Client:集中初始化与连接,提供统一的数据库访问入口
- BookGeneratorStore:围绕“书籍-章节”生命周期的CRUD与复杂查询,封装事务与状态推进
- TTS服务:音频生成过程中的记录创建、状态更新与章节绑定
- 迁移与同步脚本:将历史状态字段映射到统一的genStage,保证数据一致性
**章节来源**
- [models/index.ts](file://server/src/models/index.ts)
- [book-generator.store.ts](file://server/src/modules/book-generator/book-generator.store.ts)
- [tts.service.ts](file://server/src/modules/tts/tts.service.ts)
- [migrate-genstage.ts](file://server/prisma/migrate-genstage.ts)
- [sync-genstage.ts](file://server/prisma/sync-genstage.ts)
## 架构总览
数据访问层遵循“模型即服务”的思想:Prisma负责模型与SQL抽象,业务模块通过类方法暴露领域语义的操作,避免直接散布SQL与查询逻辑。
```mermaid
classDiagram
class PrismaClient {
+connect()
+disconnect()
}
class BookStore {
+create(data)
+getById(id, filterPublic, userId)
+getAllByUser(userId, includePublic)
+getPublicBooks()
+update(id, data)
+delete(id)
+createChapter(data)
+createChapters(chapters)
+createChapterItem(bookId, item, parentId, level)
+updateChapter(bookId, number, data)
+updateChapterById(id, data)
+getChapters(bookId)
+getChapterTree(bookId)
+countCompletedChapters(bookId)
+publishAlbum(bookId)
+unpublishAlbum(bookId)
+togglePublish(bookId)
+generateChapterAudio(bookId, chapterNumber, userId)
+generateChapterAudioById(chapterId, userId)
+generateSingleChapterContent(bookId, chapterId)
}
class TTS {
+generateAudio(userId, text, voiceId, voiceParams, onComplete, options)
+getAudioStatus(audioId)
}
PrismaClient <.. BookStore : "注入"
PrismaClient <.. TTS : "注入"
BookStore --> TTS : "调用音频生成"
```
**图表来源**
- [models/index.ts](file://server/src/models/index.ts)
- [book-generator.store.ts](file://server/src/modules/book-generator/book-generator.store.ts)
- [tts.service.ts](file://server/src/modules/tts/tts.service.ts)
## 详细组件分析
### 数据模型与实体关系映射
- 用户(User):与多种资源关联(评论、草稿、收藏、订单、播放记录、歌单、签到、订阅、偏好、令牌余额与使用)
- 订单(Order):与用户、订阅套餐关联,支持按用户与时间维度查询
- 订阅(Subscription):与用户、套餐关联,支持状态与到期日索引
- 书籍(Book):与章节、视频项目关联,支持用户与状态、创建时间索引
- 章节(BookChapter):树形结构(level、parentId、number),支持唯一约束(bookId,parentId,level,number),并维护音频/视频元数据
- 其他:播放记录、收藏、评论、通知、搜索历史、热门搜索、签到、令牌余额/使用、视频素材、视频项目、发布任务、草稿、歌单、歌单项、反馈等
```mermaid
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
}
ORDER {
int id PK
int userId FK
string orderNo UK
int planId FK
string productType
decimal amount
string status
string paymentMethod
string paymentId
datetime paidAt
datetime createdAt
datetime updatedAt
}
SUBSCRIPTION_PLAN {
int id PK
string name
int level
decimal priceMonthly
decimal priceYearly
string description
string 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
}
BOOK {
int id PK
int userId FK
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
varchar failedStage
string genStage
string status
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
string status
}
PLAY_RECORD {
int id PK
int userId FK
int chapterId FK
float progress
float duration
datetime updatedAt
datetime createdAt
}
FAVORITE {
int id PK
int userId FK
int bookId FK
datetime createdAt
}
COMMENT {
int id PK
int userId FK
int chapterId FK
text content
int rating
datetime createdAt
}
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 FK
text description
datetime createdAt
}
AUDIO_RECORD {
int id PK
int userId FK
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
int bookId FK
}
VIDEO_PROJECT {
int id PK
int userId FK
string title
text description
string coverUrl
longtext configJson
string outputUrl
int duration
int fileSize
int bookId FK
int chapterId FK
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 updatedAt
datetime createdAt
}
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 FK
string audioId
int order
}
FEEDBACK {
string id PK
string type
text title
text content
text contact
text screenshotUrls
string status
datetime createdAt
datetime updatedAt
}
USER ||--o{ ORDER : "拥有"
USER ||--o{ SUBSCRIPTION : "拥有"
USER ||--o{ BOOK : "拥有"
USER ||--o{ COMMENT : "拥有"
USER ||--o{ FAVORITE : "拥有"
USER ||--o{ PLAY_RECORD : "拥有"
USER ||--o{ PLAYLIST : "拥有"
USER ||--o{ SIGN_RECORD : "拥有"
USER ||--o{ DRAFT : "拥有"
USER ||--o{ TOKEN_USAGE : "产生"
USER ||--o{ AUDIO_RECORD : "拥有"
USER ||--o{ PLATFORM_ACCOUNT : "绑定"
USER ||--o{ VIDEO_PROJECT : "创建"
ORDER }o--|| SUBSCRIPTION_PLAN : "购买"
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--o{ PLAYLIST_ITEM : "包含"
```
**图表来源**
- [schema.prisma](file://server/prisma/schema.prisma)
**章节来源**
- [schema.prisma](file://server/prisma/schema.prisma)
### 查询构建与事务管理
- 单表查询:通过Prisma Client的findUnique/findMany/update等方法实现,结合include/orderBy/where进行关联与排序
- 复杂查询:如“书籍公开列表且仅含带音频的章节”,使用嵌套where与some条件
- 事务管理:使用$transaction包裹多表联动更新(如发布/取消发布书籍时同时更新书籍与章节公开状态)
```mermaid
sequenceDiagram
participant Svc as "业务服务"
participant Store as "BookStore"
participant Prisma as "Prisma Client"
participant DB as "数据库"
Svc->>Store : "发布书籍"
Store->>Prisma : "$transaction([更新书籍, 更新章节公开])"
Prisma->>DB : "BEGIN"
DB-->>Prisma : "OK"
Prisma->>DB : "UPDATE book SET isPublished=true"
DB-->>Prisma : "OK"
Prisma->>DB : "UPDATE bookChapter SET isPublic=true WHERE bookId=?"
DB-->>Prisma : "OK"
Prisma->>DB : "COMMIT"
DB-->>Prisma : "OK"
Prisma-->>Store : "事务完成"
Store-->>Svc : "完成"
```
**图表来源**
- [book-generator.store.ts](file://server/src/modules/book-generator/book-generator.store.ts)
**章节来源**
- [book-generator.store.ts](file://server/src/modules/book-generator/book-generator.store.ts)
### 数据模型定义与约束
- 唯一性:用户手机号/微信标识、订单号、令牌余额唯一用户、音频记录唯一ID、播放记录(userId,chapterId)唯一
- 外键与级联:章节删除时可级联影响收藏、评论、播放记录、歌单项;书籍删除时级联影响章节与视频项目
- 索引:按用户+时间、用户+状态、书本+层级+编号等建立复合索引,提升查询效率
- 字段类型:使用LongText/Text适配大文本,Decimal精确金额,DateTime统一时间戳
**章节来源**
- [schema.prisma](file://server/prisma/schema.prisma)
### 数据库迁移策略、版本管理与数据同步
- 迁移脚本:将历史状态字段映射到统一的genStage,确保数据一致性
- 同步脚本:使用安全的状态推进函数,避免并发冲突导致的竞态
- 版本管理:Prisma迁移锁定文件与版本化SQL脚本配合,确保团队协作一致
```mermaid
flowchart TD
Start(["开始"]) --> LoadOld["读取历史状态字段"]
LoadOld --> ComputeNew["计算新 genStage"]
ComputeNew --> ApplyBook["应用到 Book.genStage"]
ApplyBook --> ApplyChapter["应用到 BookChapter.genStage"]
ApplyChapter --> SafeTrans["使用安全推进函数同步"]
SafeTrans --> Done(["完成"])
```
**图表来源**
- [migrate-genstage.ts](file://server/prisma/migrate-genstage.ts)
- [sync-genstage.ts](file://server/prisma/sync-genstage.ts)
**章节来源**
- [migrate-genstage.ts](file://server/prisma/migrate-genstage.ts)
- [sync-genstage.ts](file://server/prisma/sync-genstage.ts)
### 查询优化、索引设计与性能调优
- 索引策略:用户维度(用户+时间、用户+状态)、书籍维度(书本+层级+编号、书本+父节点、书本+层级)、订单/订阅/播放记录等高频查询字段建立复合索引
- 查询优化:使用select投影减少字段传输;使用include按需加载关联;分页查询使用skip/take或游标分页
- 性能调优:批量插入使用upsert避免重复;长文本分段生成音频;并发控制与重试策略;合理使用事务边界
**章节来源**
- [schema.prisma](file://server/prisma/schema.prisma)
- [book-generator.store.ts](file://server/src/modules/book-generator/book-generator.store.ts)
- [tts.service.ts](file://server/src/modules/tts/tts.service.ts)
### 数据验证规则、业务规则与数据完整性
- 业务规则:章节内容生成完成后才允许音频生成;失败状态回退到上一步并可重试;发布书籍时自动公开有音频的章节
- 数据完整性:唯一约束、外键约束、枚举值(状态、质量等级等);事务保证跨表一致性
- 输入校验:类型定义与Prisma模型共同约束字段类型与长度
**章节来源**
- [book-generator.types.ts](file://server/src/modules/book-generator/book-generator.types.ts)
- [book-generator.store.ts](file://server/src/modules/book-generator/book-generator.store.ts)
- [schema.prisma](file://server/prisma/schema.prisma)
### 数据访问最佳实践
- 批量操作:使用upsert批量创建章节,避免重复;批量合并音频文件
- 分页查询:列表接口使用分页参数,避免一次性加载过多数据
- 缓存策略:对热点数据(如公开书籍列表)可引入Redis缓存,降低数据库压力
- 事务边界:将强一致性的多表更新放入事务,确保原子性
- 错误处理:对并发冲突进行捕获与重试;对额度限制等可恢复错误进行降级与切换
**章节来源**
- [book-generator.store.ts](file://server/src/modules/book-generator/book-generator.store.ts)
- [tts.service.ts](file://server/src/modules/tts/tts.service.ts)
### 数据库设计指南
- 采用“书籍-章节-音频”三层结构,章节支持树形层级(level、parentId、number)
- 使用genStage统一管理生成阶段,便于状态推进与可视化
- 对大文本字段使用合适的数据类型(Text/LongText),避免超长VARCHAR
- 为高频查询字段建立复合索引,平衡写入与读取性能
**章节来源**
- [database-structure.md](file://docs/database-structure.md)
- [schema.prisma](file://server/prisma/schema.prisma)
## 依赖分析
- Prisma Client依赖环境变量DATABASE_URL
- 业务模块依赖Prisma Client进行数据访问
- TTS服务依赖Prisma进行音频记录与章节更新
- 迁移与同步脚本依赖Prisma Client与状态推进工具
```mermaid
graph LR
ENV["环境变量 DATABASE_URL"] --> PRISMA["Prisma Client"]
PRISMA --> STORE["BookGeneratorStore"]
PRISMA --> TTS["TTS服务"]
PRISMA --> MIG["迁移脚本"]
PRISMA --> SYNC["同步脚本"]
```
**图表来源**
- [config/index.ts](file://server/src/config/index.ts)
- [models/index.ts](file://server/src/models/index.ts)
- [book-generator.store.ts](file://server/src/modules/book-generator/book-generator.store.ts)
- [tts.service.ts](file://server/src/modules/tts/tts.service.ts)
- [migrate-genstage.ts](file://server/prisma/migrate-genstage.ts)
- [sync-genstage.ts](file://server/prisma/sync-genstage.ts)
**章节来源**
- [config/index.ts](file://server/src/config/index.ts)
- [models/index.ts](file://server/src/models/index.ts)
- [book-generator.store.ts](file://server/src/modules/book-generator/book-generator.store.ts)
- [tts.service.ts](file://server/src/modules/tts/tts.service.ts)
- [migrate-genstage.ts](file://server/prisma/migrate-genstage.ts)
- [sync-genstage.ts](file://server/prisma/sync-genstage.ts)
## 性能考量
- 查询性能:为高频查询字段建立索引;使用投影与分页;避免N+1查询
- 写入性能:批量upsert;事务合并多次更新;长文本分段处理
- 并发控制:使用安全推进函数与乐观锁;对冲突进行重试
- 存储与网络:音频文件上传至OSS,本地仅保留临时文件;LRC歌词生成与时长计算分离
[本节为通用指导,无需具体文件分析]
## 故障排查指南
- 连接失败:检查DATABASE_URL与数据库连通性
- 并发冲突:状态推进失败时重试或检查冲突原因
- 额度限制:切换TTS提供商或等待配额恢复
- 僵尸任务:检测长时间无输出的生成目录并标记失败
**章节来源**
- [models/index.ts](file://server/src/models/index.ts)
- [tts.service.ts](file://server/src/modules/tts/tts.service.ts)
## 结论
本数据访问层以Prisma为核心,结合业务模块的存储类与服务层,实现了清晰的领域建模与稳定的事务一致性。通过迁移与同步脚本,历史数据得到平滑过渡;通过索引与查询优化,兼顾了读写性能。建议持续完善缓存策略与监控告警,进一步提升系统稳定性与可维护性。
[本节为总结,无需具体文件分析]
## 附录
- 常用查询示例与索引设计参考数据库结构文档
- 状态字段说明与状态流转参考数据库结构文档
**章节来源**
- [database-structure.md](file://docs/database-structure.md)