# 用户关联关系 **本文引用的文件** - [schema.prisma](file://server/prisma/schema.prisma) - [favorites.service.ts](file://server/src/modules/favorites/favorites.service.ts) - [player.service.ts](file://server/src/modules/player/player.service.ts) - [comments.service.ts](file://server/src/modules/comments/comments.service.ts) - [drafts.controller.ts](file://server/src/modules/drafts/drafts.controller.ts) - [sign.service.ts](file://server/src/modules/sign/sign.service.ts) - [playlist.controller.ts](file://server/src/modules/player/playlist.controller.ts) - [subscription.service.ts](file://server/src/modules/subscription/subscription.service.ts) - [index.ts](file://server/src/models/index.ts) - [20260422105352_add_content_status/migration.sql](file://server/prisma/migrations/20260422105352_add_content_status/migration.sql) ## 目录 1. [简介](#简介) 2. [项目结构](#项目结构) 3. [核心组件](#核心组件) 4. [架构总览](#架构总览) 5. [详细组件分析](#详细组件分析) 6. [依赖分析](#依赖分析) 7. [性能考量](#性能考量) 8. [故障排查指南](#故障排查指南) 9. [结论](#结论) 10. [附录](#附录) ## 简介 本文件聚焦于AI有声书生成平台中的“用户”模型与其相关业务实体之间的关联关系与数据模型设计,覆盖一对一、一对多、多对多关系的映射方式,并结合Prisma Schema与实际服务层实现,给出外键约束与级联删除策略的设计考量。同时提供常见查询场景的实现思路与最佳实践,帮助开发者快速理解与扩展用户侧的数据访问模式。 ## 项目结构 围绕用户关联关系的关键文件分布如下: - 数据模型定义:Prisma Schema(包含User及其关联表) - 服务层实现:各模块服务负责具体查询与业务逻辑 - 控制器层:路由控制器负责请求参数解析与响应封装 - 数据库迁移:外键约束与索引在迁移脚本中落地 ```mermaid graph TB subgraph "数据模型(Prisma)" U["User
用户"] C["Comment
评论"] D["Draft
草稿"] F["Favorite
收藏"] O["Order
订单"] PR["PlayRecord
播放记录"] PL["Playlist
播放列表"] PSI["PlaylistItem
播放列表项"] SR["SignRecord
签到记录"] SUB["Subscription
订阅"] TP["TokenBalance
Token余额"] TU["TokenUsage
Token使用"] UP["UserPreference
用户偏好"] end U --- C U --- D U --- F U --- O U --- PR U --- PL U --- SR U --- SUB U --- TP U --- TU U --- UP O --- TU F --- B["Book
书籍"] PR --- BC["BookChapter
章节"] PL --- PSI PSI --- BC ``` 图表来源 - [schema.prisma](file://server/prisma/schema.prisma) 章节来源 - [schema.prisma](file://server/prisma/schema.prisma) ## 核心组件 - User(用户):作为所有关联关系的起点,承载手机号、微信标识、会员等级、Token余额、播放偏好等属性,并通过数组字段与多个实体建立关联。 - 评论关系:User 与 Comment 为一对多;Comment 再指向 BookChapter。 - 草稿关系:User 与 Draft 为一对多。 - 收藏关系:User 与 Favorite 为一对多;Favorite 指向 Book。 - 订单关系:User 与 Order 为一对多;Order 可能关联 SubscriptionPlan 与 TokenUsage。 - 播放记录关系:User 与 PlayRecord 为一对多;PlayRecord 指向 BookChapter。 - 播放列表关系:User 与 Playlist 为一对多;Playlist 与 PlaylistItem 为一对多;PlaylistItem 指向 BookChapter。 - 签到记录关系:User 与 SignRecord 为一对多。 - 订阅关系:User 与 Subscription 为一对多;Subscription 指向 SubscriptionPlan。 - Token余额与使用:User 与 TokenBalance 为一对一;User 与 TokenUsage 为一对多;TokenUsage 可能反向指向 Order。 - 用户偏好:User 与 UserPreference 为一对一。 章节来源 - [schema.prisma](file://server/prisma/schema.prisma) ## 架构总览 用户关联关系在Prisma Schema中以relation字段声明,在数据库层面通过外键约束与索引保障一致性与查询效率。服务层通过Prisma Client进行查询与写入,控制器层负责鉴权与参数校验。 ```mermaid sequenceDiagram participant Client as "客户端" participant Ctrl as "控制器(示例)" participant Svc as "服务层(示例)" participant Prisma as "Prisma Client" participant DB as "MySQL" Client->>Ctrl : "发起请求" Ctrl->>Svc : "调用业务方法(userId, ...)" Svc->>Prisma : "执行查询/写入" Prisma->>DB : "SQL执行" DB-->>Prisma : "结果集" Prisma-->>Svc : "对象/集合" Svc-->>Ctrl : "组装数据" Ctrl-->>Client : "响应" ``` 图表来源 - [index.ts](file://server/src/models/index.ts) - [favorites.service.ts](file://server/src/modules/favorites/favorites.service.ts) - [player.service.ts](file://server/src/modules/player/player.service.ts) ## 详细组件分析 ### User 与评论关系(一对多) - 关系映射:User.comments 为数组;Comment.userId 外键指向 User.id。 - 查询示例:根据章节ID获取该章节下的所有评论,或根据用户ID获取其评论列表。 - 外键约束:迁移脚本中为 Order.user 与 PlayRecord.user 等建立了外键约束,体现一致的约束策略。 ```mermaid classDiagram class User { +id +phone +openid +nickname +avatar +memberLevel +memberExpireAt +dailyUsage +lastUsageDate +usedAudioMinutes +subscriptionResetDate +comments +drafts +favorites +orders +playRecords +playlists +signRecords +subscriptions +tokenBalance +tokenUsages +preferences } class Comment { +id +userId +chapterId +content +rating +createdAt } User "1" <-- "many" Comment : "comments" ``` 图表来源 - [schema.prisma](file://server/prisma/schema.prisma) 章节来源 - [schema.prisma](file://server/prisma/schema.prisma) - [comments.service.ts](file://server/src/modules/comments/comments.service.ts) ### User 与草稿关系(一对多) - 关系映射:User.drafts 为数组;Draft.userId 外键指向 User.id。 - 查询示例:按类型与更新时间倒序列出用户草稿;支持按ID查询单个草稿。 ```mermaid classDiagram class User class Draft { +id +userId +type +title +content +metadata +autoSavedAt +createdAt +updatedAt } User "1" <-- "many" Draft : "drafts" ``` 图表来源 - [schema.prisma](file://server/prisma/schema.prisma) 章节来源 - [schema.prisma](file://server/prisma/schema.prisma) - [drafts.controller.ts](file://server/src/modules/drafts/drafts.controller.ts) ### User 与收藏关系(一对多) - 关系映射:User.favorites 为数组;Favorite.userId 外键指向 User.id;Favorite.bookId 指向 Book.id。 - 级联策略:Favorite.bookId 字段声明 onDelete: Cascade,表示当书籍被删除时,对应的收藏记录也会级联删除。 - 查询示例:获取用户收藏列表并包含书籍基本信息;添加/取消收藏;检查是否已收藏。 ```mermaid classDiagram class User class Favorite { +id +userId +bookId +createdAt } class Book { +id +title +subtitle +description +coverUrl +createdAt } User "1" <-- "many" Favorite : "favorites" Favorite --> Book : "bookId -> Book.id (onDelete : Cascade)" ``` 图表来源 - [schema.prisma](file://server/prisma/schema.prisma) 章节来源 - [schema.prisma](file://server/prisma/schema.prisma) - [favorites.service.ts](file://server/src/modules/favorites/favorites.service.ts) ### User 与订单关系(一对多) - 关系映射:User.orders 为数组;Order.userId 外键指向 User.id;Order.planId 可能指向 SubscriptionPlan。 - 外键约束:迁移脚本为 Order.user 与 PlayRecord.user 等建立了外键约束,体现RESTRICT策略以保护数据完整性。 - 查询示例:获取用户订单列表与分页统计;订单与TokenUsage的关联便于成本归集。 ```mermaid classDiagram class User class Order { +id +userId +orderNo +planId +productType +amount +status +paymentMethod +paymentId +paidAt +createdAt +updatedAt } class SubscriptionPlan class TokenUsage User "1" <-- "many" Order : "orders" Order --> SubscriptionPlan : "planId -> Plan.id" Order --> TokenUsage : "tokenUsages" ``` 图表来源 - [schema.prisma](file://server/prisma/schema.prisma) - [20260422105352_add_content_status/migration.sql](file://server/prisma/migrations/20260422105352_add_content_status/migration.sql) 章节来源 - [schema.prisma](file://server/prisma/schema.prisma) - [20260422105352_add_content_status/migration.sql](file://server/prisma/migrations/20260422105352_add_content_status/migration.sql) ### User 与播放记录关系(一对多) - 关系映射:User.playRecords 为数组;PlayRecord.userId 外键指向 User.id;PlayRecord.chapterId 指向 BookChapter。 - 约束与索引:唯一索引(用户, 章节)确保同一用户对同一章节仅有一条播放进度;索引覆盖查询场景。 - 查询示例:获取用户播放进度列表并包含章节信息;保存/更新播放进度;删除播放记录;获取最近播放记录。 ```mermaid classDiagram class User class PlayRecord { +id +userId +chapterId +progress +duration +createdAt +updatedAt } class BookChapter User "1" <-- "many" PlayRecord : "playRecords" PlayRecord --> BookChapter : "chapterId -> Chapter.id" ``` 图表来源 - [schema.prisma](file://server/prisma/schema.prisma) 章节来源 - [schema.prisma](file://server/prisma/schema.prisma) - [player.service.ts](file://server/src/modules/player/player.service.ts) ### User 与播放列表关系(一对多) - 关系映射:User.playlists 为数组;Playlist.userId 外键指向 User.id;Playlist.items 为 PlaylistItem 数组。 - 多对多:PlaylistItem 通过 playlistId 与 chapterId/audioId 组合,实现列表与章节/音频的多对多关联。 - 查询示例:获取用户播放列表;获取播放列表详情并按顺序包含章节;添加/删除列表项;重新排序。 ```mermaid classDiagram class User class Playlist { +id +userId +name +description +createdAt +updatedAt } class PlaylistItem { +id +playlistId +chapterId +audioId +order } class BookChapter User "1" <-- "many" Playlist : "playlists" Playlist "1" <-- "many" PlaylistItem : "items" PlaylistItem --> BookChapter : "chapterId -> Chapter.id" ``` 图表来源 - [schema.prisma](file://server/prisma/schema.prisma) 章节来源 - [schema.prisma](file://server/prisma/schema.prisma) - [playlist.controller.ts](file://server/src/modules/player/playlist.controller.ts) ### User 与签到记录关系(一对多) - 关系映射:User.signRecords 为数组;SignRecord.userId 外键指向 User.id。 - 查询示例:检查当日签到状态与连续签到天数;执行签到并调整用户免费使用次数。 ```mermaid classDiagram class User class SignRecord { +id +userId +createdAt } User "1" <-- "many" SignRecord : "signRecords" ``` 图表来源 - [schema.prisma](file://server/prisma/schema.prisma) 章节来源 - [schema.prisma](file://server/prisma/schema.prisma) - [sign.service.ts](file://server/src/modules/sign/sign.service.ts) ### User 与订阅关系(一对多) - 关系映射:User.subscriptions 为数组;Subscription.userId 外键指向 User.id;Subscription.planId 指向 SubscriptionPlan。 - 查询示例:获取用户当前有效订阅;查询套餐列表与详情;初始化默认套餐。 ```mermaid classDiagram class User class Subscription { +id +userId +planId +startDate +endDate +status +autoRenew +createdAt +updatedAt } class SubscriptionPlan User "1" <-- "many" Subscription : "subscriptions" Subscription --> SubscriptionPlan : "planId -> Plan.id" ``` 图表来源 - [schema.prisma](file://server/prisma/schema.prisma) 章节来源 - [schema.prisma](file://server/prisma/schema.prisma) - [subscription.service.ts](file://server/src/modules/subscription/subscription.service.ts) ### User 与 Token 余额/使用关系(一对一/一对多) - 关系映射:User.tokenBalance 一对一;User.tokenUsages 一对多;TokenUsage.orderId 可能指向 Order。 - 查询示例:获取用户Token余额与使用明细;消耗Token并记录使用;检查配额。 ```mermaid classDiagram class User class TokenBalance { +id +userId +totalTokens +usedTokens +resetDate +createdAt +updatedAt } class TokenUsage { +id +userId +type +amount +contentLength +orderId +description +createdAt } class Order User "1" --> "1" TokenBalance : "tokenBalance" User "1" <-- "many" TokenUsage : "tokenUsages" TokenUsage --> Order : "orderId -> Order.id" ``` 图表来源 - [schema.prisma](file://server/prisma/schema.prisma) 章节来源 - [schema.prisma](file://server/prisma/schema.prisma) - [subscription.service.ts](file://server/src/modules/subscription/subscription.service.ts) ### User 与偏好关系(一对一) - 关系映射:User.preferences 一对一;UserPreference.userId 外键指向 User.id。 - 查询示例:读取/更新用户播放速度、音质、主题、默认音色、音量、自动播放等偏好设置。 ```mermaid classDiagram class User class UserPreference { +id +userId +playSpeed +quality +theme +defaultVoiceId +defaultVolume +autoPlayNext +wifiOnlyDownload +createdAt +updatedAt } User "1" --> "1" UserPreference : "preferences" ``` 图表来源 - [schema.prisma](file://server/prisma/schema.prisma) 章节来源 - [schema.prisma](file://server/prisma/schema.prisma) ## 依赖分析 - 外键约束策略 - 订单与播放记录:迁移脚本为 Order.user 与 PlayRecord.user 建立外键约束,采用 RESTRICT 策略,防止误删用户导致订单/播放记录悬挂。 - 收藏与书籍:Favorite.bookId 声明 onDelete: Cascade,确保书籍删除后收藏失效,避免孤儿记录。 - 索引策略 - 用户表:phone、openid 唯一索引;User.id 主键。 - 订单表:userId、orderNo、status、planId 索引,提升查询与统计效率。 - 播放记录:唯一索引(用户, 章节),索引(userId)、(chapterId)。 - 播放列表:Playlist.user 与 PlaylistItem.playlistId 索引,PlaylistItem.order 辅助排序。 - TokenUsage:userId、type、orderId 索引,便于按用户与类型统计。 - 服务层依赖 - 所有服务通过 models/index.ts 导出的 Prisma Client 实例进行数据库交互,确保连接复用与生命周期管理。 ```mermaid graph LR U["User"] -- "RESTRICT" --> O["Order"] U -- "RESTRICT" --> PR["PlayRecord"] U -- "CASCADE" --> F["Favorite"] F -- "CASCADE" --> B["Book"] U -- "ONE-TO-ONE" --> TP["TokenBalance"] U -- "ONE-TO-MANY" --> TU["TokenUsage"] O --> TU U -- "ONE-TO-ONE" --> UP["UserPreference"] U -- "ONE-TO-MANY" --> PL["Playlist"] PL -- "CASCADE" --> PSI["PlaylistItem"] PSI -- "MANY-TO-ONE" --> BC["BookChapter"] ``` 图表来源 - [schema.prisma](file://server/prisma/schema.prisma) - [20260422105352_add_content_status/migration.sql](file://server/prisma/migrations/20260422105352_add_content_status/migration.sql) 章节来源 - [schema.prisma](file://server/prisma/schema.prisma) - [20260422105352_add_content_status/migration.sql](file://server/prisma/migrations/20260422105352_add_content_status/migration.sql) - [index.ts](file://server/src/models/index.ts) ## 性能考量 - 索引命中 - 在高频查询字段上建立合适索引(如订单的 userId/status、播放记录的 userId/chapterId、TokenUsage 的 userId/type)。 - 唯一约束 - 播放记录的(用户, 章节)唯一索引避免重复写入,提高去重与幂等性。 - 分页与聚合 - 列表查询使用 take/skip 并配合 count 聚合,避免一次性加载大量数据。 - 关联查询 - 使用 include/select 精准投影,减少不必要的字段传输与序列化开销。 - 级联策略 - 对于强依赖关系(如收藏-书籍)采用 CASCADE,降低清理成本;对于关键业务(如订单/播放记录)采用 RESTRICT,保障数据安全。 ## 故障排查指南 - 外键约束错误 - 若出现“外键约束失败”,检查是否尝试删除仍被引用的记录(如用户、书籍、播放列表)。遵循 RESTRICT/CASCADE 策略进行修复。 - 唯一冲突 - 播放记录或收藏的唯一索引冲突通常由重复插入引起,应先查询再决定 upsert 或跳过。 - 查询性能问题 - 检查是否缺少必要索引;确认查询条件是否命中索引;避免 N+1 查询,合理使用 include/select。 - Token/配额异常 - 核对 TokenBalance 与 TokenUsage 的累计值;检查消费流程是否正确记录;关注月度重置逻辑。 章节来源 - [schema.prisma](file://server/prisma/schema.prisma) - [subscription.service.ts](file://server/src/modules/subscription/subscription.service.ts) ## 结论 本文基于Prisma Schema与服务层实现,系统梳理了User模型与评论、草稿、收藏、订单、播放记录、播放列表、签到记录、订阅、Token余额/使用、偏好等实体的关联关系与约束策略。通过明确的一对一/一对多/多对多映射、外键约束与索引设计,以及服务层的查询与业务逻辑封装,为用户侧数据访问提供了清晰、可扩展、高性能的实现路径。 ## 附录 ### 常见查询场景与实现要点 - 获取用户所有收藏 - 通过 Favorites 服务按 userId 查询并 include book 基本信息,按创建时间倒序。 - 参考:[favorites.service.ts](file://server/src/modules/favorites/favorites.service.ts) - 查询用户播放历史 - 通过 Player 服务按 userId 查询 PlayRecord 并 include 章节信息,按更新时间倒序。 - 参考:[player.service.ts](file://server/src/modules/player/player.service.ts) - 统计用户创作数量 - 通过 Drafts 控制器按 userId 与类型过滤,统计草稿数量或按更新时间倒序获取最新草稿。 - 参考:[drafts.controller.ts](file://server/src/modules/drafts/drafts.controller.ts) - 获取用户Token使用明细 - 通过 Subscription 服务按 userId 分页查询 TokenUsage,并统计总数。 - 参考:[subscription.service.ts](file://server/src/modules/subscription/subscription.service.ts) - 获取用户播放列表与详情 - 通过 Playlist 控制器按 userId 查询列表并 include items,按 order 排序章节。 - 参考:[playlist.controller.ts](file://server/src/modules/player/playlist.controller.ts)