# 用户关联关系

<cite>
**本文引用的文件**
- [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)
</cite>

## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖分析](#依赖分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)

## 简介
本文件聚焦于AI有声书生成平台中的“用户”模型与其相关业务实体之间的关联关系与数据模型设计，覆盖一对一、一对多、多对多关系的映射方式，并结合Prisma Schema与实际服务层实现，给出外键约束与级联删除策略的设计考量。同时提供常见查询场景的实现思路与最佳实践，帮助开发者快速理解与扩展用户侧的数据访问模式。

## 项目结构
围绕用户关联关系的关键文件分布如下：
- 数据模型定义：Prisma Schema（包含User及其关联表）
- 服务层实现：各模块服务负责具体查询与业务逻辑
- 控制器层：路由控制器负责请求参数解析与响应封装
- 数据库迁移：外键约束与索引在迁移脚本中落地

```mermaid
graph TB
subgraph "数据模型(Prisma)"
U["User<br/>用户"]
C["Comment<br/>评论"]
D["Draft<br/>草稿"]
F["Favorite<br/>收藏"]
O["Order<br/>订单"]
PR["PlayRecord<br/>播放记录"]
PL["Playlist<br/>播放列表"]
PSI["PlaylistItem<br/>播放列表项"]
SR["SignRecord<br/>签到记录"]
SUB["Subscription<br/>订阅"]
TP["TokenBalance<br/>Token余额"]
TU["TokenUsage<br/>Token使用"]
UP["UserPreference<br/>用户偏好"]
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<br/>书籍"]
PR --- BC["BookChapter<br/>章节"]
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)