# 数据访问层 **本文引用的文件** - [schema.prisma](file://server/prisma/schema.prisma) - [models/index.ts](file://server/src/models/index.ts) - [subscription.service.ts](file://server/src/modules/subscription/subscription.service.ts) - [subscription.controller.ts](file://server/src/modules/subscription/subscription.controller.ts) - [book-generator.service.ts](file://server/src/modules/book-generator/book-generator.service.ts) - [book-generator.controller.ts](file://server/src/modules/book-generator/book-generator.controller.ts) - [tts.service.ts](file://server/src/modules/tts/tts.service.ts) - [tts.controller.ts](file://server/src/modules/tts/tts.controller.ts) - [player.service.ts](file://server/src/modules/player/player.service.ts) - [migrate-genstage.ts](file://server/prisma/migrate-genstage.ts) - [migration_lock.toml](file://server/prisma/migrations/migration_lock.toml) ## 目录 1. [简介](#简介) 2. [项目结构](#项目结构) 3. [核心组件](#核心组件) 4. [架构总览](#架构总览) 5. [详细组件分析](#详细组件分析) 6. [依赖分析](#依赖分析) 7. [性能考虑](#性能考虑) 8. [故障排查指南](#故障排查指南) 9. [结论](#结论) 10. [附录](#附录) ## 简介 本文件面向AI有声书生成平台的数据访问层,围绕Prisma ORM的数据模型设计与DAO模式实现展开,覆盖用户、书籍、章节、音频、订阅等核心实体及其关系映射;同时阐述CRUD封装、事务与连接池、迁移策略、查询优化、索引设计、缓存策略,并提供数据模型ER图与最佳实践建议。 ## 项目结构 数据访问层主要由以下部分组成: - 数据模型定义:基于Prisma Schema的实体与关系 - DAO与服务层:封装CRUD、业务逻辑与配额校验 - 控制器层:对外暴露REST接口 - 迁移与同步:历史数据迁移脚本与迁移锁文件 - 连接管理:Prisma客户端连接与生命周期 ```mermaid graph TB subgraph "数据访问层" PRISMA["Prisma 客户端
连接/查询"] MODELS["数据模型
schema.prisma"] DAO_SUB["订阅服务
subscription.service.ts"] DAO_TTS["TTS服务
tts.service.ts"] DAO_PLAYER["播放服务
player.service.ts"] DAO_BOOK["书籍生成服务
book-generator.service.ts"] end subgraph "控制器层" CTRL_SUB["订阅控制器
subscription.controller.ts"] CTRL_TTS["TTS控制器
tts.controller.ts"] CTRL_BOOK["书籍生成控制器
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 ``` 图表来源 - [schema.prisma](file://server/prisma/schema.prisma) - [models/index.ts](file://server/src/models/index.ts) - [subscription.service.ts](file://server/src/modules/subscription/subscription.service.ts) - [tts.service.ts](file://server/src/modules/tts/tts.service.ts) - [player.service.ts](file://server/src/modules/player/player.service.ts) - [book-generator.service.ts](file://server/src/modules/book-generator/book-generator.service.ts) - [subscription.controller.ts](file://server/src/modules/subscription/subscription.controller.ts) - [tts.controller.ts](file://server/src/modules/tts/tts.controller.ts) - [book-generator.controller.ts](file://server/src/modules/book-generator/book-generator.controller.ts) 章节来源 - [schema.prisma](file://server/prisma/schema.prisma) - [models/index.ts](file://server/src/models/index.ts) ## 核心组件 - Prisma 客户端与连接管理:集中导出PrismaClient实例与连接方法,确保全局唯一连接与统一生命周期管理。 - 订阅与配额服务:封装套餐查询、用户订阅状态、Token余额与使用记录、音频时长配额与计费、书籍生成配额等。 - TTS 服务:封装音频生成流程、状态查询、LRC歌词生成、章节音频更新、音色与提供商选择。 - 播放服务:封装播放进度读写、章节音频合并(自动聚合子小节音频)、最近播放记录。 - 书籍生成服务:批量生成编排器,按步骤推进内容生成、音频生成、音频合并、视频生成与合并,并支持取消与进度推送。 - 控制器:REST接口封装,参数校验、鉴权中间件、调用服务层并返回标准化响应。 章节来源 - [models/index.ts](file://server/src/models/index.ts) - [subscription.service.ts](file://server/src/modules/subscription/subscription.service.ts) - [tts.service.ts](file://server/src/modules/tts/tts.service.ts) - [player.service.ts](file://server/src/modules/player/player.service.ts) - [book-generator.service.ts](file://server/src/modules/book-generator/book-generator.service.ts) - [subscription.controller.ts](file://server/src/modules/subscription/subscription.controller.ts) - [tts.controller.ts](file://server/src/modules/tts/tts.controller.ts) - [book-generator.controller.ts](file://server/src/modules/book-generator/book-generator.controller.ts) ## 架构总览 数据访问层采用“控制器-服务-DAO-Prisma”的分层架构,服务层负责业务规则与数据一致性,DAO层负责具体数据操作,Prisma负责ORM映射与SQL生成。 ```mermaid 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 响应" ``` 图表来源 - [subscription.controller.ts](file://server/src/modules/subscription/subscription.controller.ts) - [subscription.service.ts](file://server/src/modules/subscription/subscription.service.ts) - [tts.controller.ts](file://server/src/modules/tts/tts.controller.ts) - [tts.service.ts](file://server/src/modules/tts/tts.service.ts) - [book-generator.controller.ts](file://server/src/modules/book-generator/book-generator.controller.ts) - [book-generator.service.ts](file://server/src/modules/book-generator/book-generator.service.ts) - [player.service.ts](file://server/src/modules/player/player.service.ts) - [schema.prisma](file://server/prisma/schema.prisma) ## 详细组件分析 ### 数据模型与关系映射(ER图) 下图为基于Prisma Schema的核心实体与关系的ER图,涵盖一对一、一对多、多对多关系及关键索引。 ```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 } 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 : "包含" ``` 图表来源 - [schema.prisma](file://server/prisma/schema.prisma) 章节来源 - [schema.prisma](file://server/prisma/schema.prisma) ### DAO模式与CRUD封装 - 订阅与配额:提供套餐查询、用户订阅查询、Token余额与使用记录、音频时长配额检查与扣减、书籍生成配额评估等。 - TTS:提供音频生成、状态查询、LRC歌词生成、章节音频更新、音色与提供商选择。 - 播放:提供播放进度读取与更新、章节音频合并(自动聚合子小节音频)、最近播放记录。 - 书籍生成:提供批量生成编排器,按步骤推进并支持取消与进度推送。 章节来源 - [subscription.service.ts](file://server/src/modules/subscription/subscription.service.ts) - [tts.service.ts](file://server/src/modules/tts/tts.service.ts) - [player.service.ts](file://server/src/modules/player/player.service.ts) - [book-generator.service.ts](file://server/src/modules/book-generator/book-generator.service.ts) ### 事务管理与连接池 - Prisma客户端默认启用连接池与并发请求处理,通过统一的PrismaClient实例管理连接生命周期。 - 在服务层中,涉及多步写入的场景(如消费Token并记录使用)建议在单事务中执行,避免并发竞态导致的余额不一致。 章节来源 - [models/index.ts](file://server/src/models/index.ts) - [subscription.service.ts](file://server/src/modules/subscription/subscription.service.ts) ### 数据库迁移策略 - 迁移锁:使用migration_lock.toml锁定迁移执行,防止并发迁移。 - 历史数据迁移:提供从旧字段到新genStage字段的迁移脚本,自动推断书籍与章节的生成阶段。 - 版本管理:Prisma通过schema.prisma与migrations目录维护版本演进,建议每次变更均生成新迁移并审阅SQL。 章节来源 - [migration_lock.toml](file://server/prisma/migrations/migration_lock.toml) - [migrate-genstage.ts](file://server/prisma/migrate-genstage.ts) - [schema.prisma](file://server/prisma/schema.prisma) ### 查询优化与索引设计 - 常用查询路径与索引: - 用户:phone、openid、id - 订单:userId、orderNo、status、planId - 播放记录:userId、chapterId、唯一组合(userId, chapterId) - 书籍:userId、createdAt - 章节:bookId、bookId+parentId、bookId+level、唯一组合(bookId, parentId, level, number) - Token使用:userId、type、orderId - 视频项目:userId、status、bookId、chapterId - 搜索:keyword、sort、count - 订阅:userId、status、endDate - 音频:userId、audioId、bookId - 建议: - 为高频过滤字段建立复合索引,避免全表扫描。 - 对范围查询(createdAt、endDate)与排序(orderBy)字段建立合适索引。 - 对唯一约束字段(如orderNo、audioId、openid)保持唯一性以提升查询效率。 章节来源 - [schema.prisma](file://server/prisma/schema.prisma) ### 缓存策略 - 读多写少的静态数据(如套餐配置、音色列表)可在应用层缓存,减少数据库压力。 - 对热点查询(如用户配额、Token余额)可引入Redis缓存,设置合理TTL与失效策略。 - 对大字段(如longtext)建议延迟加载或分表存储,避免影响主表查询性能。 [本节为通用建议,不直接分析具体文件] ### API工作流示例(TTS生成) ```mermaid 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 : "任务已创建" ``` 图表来源 - [tts.controller.ts](file://server/src/modules/tts/tts.controller.ts) - [tts.service.ts](file://server/src/modules/tts/tts.service.ts) - [subscription.service.ts](file://server/src/modules/subscription/subscription.service.ts) - [schema.prisma](file://server/prisma/schema.prisma) ## 依赖分析 - 控制器依赖服务层,服务层依赖Prisma DAO,DAO依赖Prisma客户端。 - 订阅服务与TTS服务存在跨模块调用(配额检查与扣减)。 - 书籍生成服务与播放服务、视频生成服务存在间接耦合(章节音频合并、视频项目关联)。 ```mermaid 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 ``` 图表来源 - [subscription.controller.ts](file://server/src/modules/subscription/subscription.controller.ts) - [subscription.service.ts](file://server/src/modules/subscription/subscription.service.ts) - [tts.controller.ts](file://server/src/modules/tts/tts.controller.ts) - [tts.service.ts](file://server/src/modules/tts/tts.service.ts) - [book-generator.controller.ts](file://server/src/modules/book-generator/book-generator.controller.ts) - [book-generator.service.ts](file://server/src/modules/book-generator/book-generator.service.ts) - [models/index.ts](file://server/src/models/index.ts) 章节来源 - [subscription.controller.ts](file://server/src/modules/subscription/subscription.controller.ts) - [tts.controller.ts](file://server/src/modules/tts/tts.controller.ts) - [book-generator.controller.ts](file://server/src/modules/book-generator/book-generator.controller.ts) - [subscription.service.ts](file://server/src/modules/subscription/subscription.service.ts) - [tts.service.ts](file://server/src/modules/tts/tts.service.ts) - [book-generator.service.ts](file://server/src/modules/book-generator/book-generator.service.ts) - [models/index.ts](file://server/src/models/index.ts) ## 性能考虑 - 连接池:合理配置Prisma连接池大小,避免高并发下的连接争用。 - 查询优化:为高频查询字段建立索引,避免SELECT *,使用投影查询。 - 写入优化:批量插入/更新时使用事务,减少往返次数。 - 缓存:热点数据缓存、读写分离、CDN加速静态资源。 - IO优化:音频/视频上传使用对象存储,本地仅保留必要元数据。 [本节为通用建议,不直接分析具体文件] ## 故障排查指南 - 连接失败:检查DATABASE_URL环境变量与网络连通性,确认Prisma连接方法调用。 - 额度不足:检查用户订阅状态与Token余额,确认配额计算逻辑与重置日期。 - 生成失败:检查TTS提供商配置、分段策略、存储上传失败回退逻辑。 - 进度异常:检查播放记录唯一键与upsert逻辑,确认章节音频合并条件。 章节来源 - [models/index.ts](file://server/src/models/index.ts) - [subscription.service.ts](file://server/src/modules/subscription/subscription.service.ts) - [tts.service.ts](file://server/src/modules/tts/tts.service.ts) - [player.service.ts](file://server/src/modules/player/player.service.ts) ## 结论 本数据访问层以Prisma为核心,结合服务层封装与控制器接口,实现了从用户、书籍、章节、音频到订阅与配额的全链路数据管理。通过合理的索引设计、迁移策略与缓存策略,能够满足高并发与复杂业务场景的需求。建议持续完善事务边界、监控与告警体系,保障数据一致性与系统稳定性。 ## 附录 - 数据模型字段与索引参考:见“数据模型与关系映射”章节。 - 迁移脚本与锁文件:见“数据库迁移策略”章节。 - API接口与调用关系:见“架构总览”与“详细组件分析”。