# 用户关联关系
**本文引用的文件**
- [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)