用户关联关系.md 18 KB

用户关联关系

本文引用的文件

  • schema.prisma
  • favorites.service.ts
  • player.service.ts
  • comments.service.ts
  • drafts.controller.ts
  • sign.service.ts
  • playlist.controller.ts
  • subscription.service.ts
  • index.ts
  • 20260422105352_add_content_status/migration.sql

目录

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

简介

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

项目结构

围绕用户关联关系的关键文件分布如下:

  • 数据模型定义:Prisma Schema(包含User及其关联表)
  • 服务层实现:各模块服务负责具体查询与业务逻辑
  • 控制器层:路由控制器负责请求参数解析与响应封装
  • 数据库迁移:外键约束与索引在迁移脚本中落地

    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

章节来源

  • 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

架构总览

用户关联关系在Prisma Schema中以relation字段声明,在数据库层面通过外键约束与索引保障一致性与查询效率。服务层通过Prisma Client进行查询与写入,控制器层负责鉴权与参数校验。

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
  • favorites.service.ts
  • player.service.ts

详细组件分析

User 与评论关系(一对多)

  • 关系映射:User.comments 为数组;Comment.userId 外键指向 User.id。
  • 查询示例:根据章节ID获取该章节下的所有评论,或根据用户ID获取其评论列表。
  • 外键约束:迁移脚本中为 Order.user 与 PlayRecord.user 等建立了外键约束,体现一致的约束策略。

    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

章节来源

  • schema.prisma
  • comments.service.ts

User 与草稿关系(一对多)

  • 关系映射:User.drafts 为数组;Draft.userId 外键指向 User.id。
  • 查询示例:按类型与更新时间倒序列出用户草稿;支持按ID查询单个草稿。

    classDiagram
    class User
    class Draft {
    +id
    +userId
    +type
    +title
    +content
    +metadata
    +autoSavedAt
    +createdAt
    +updatedAt
    }
    User "1" <-- "many" Draft : "drafts"
    

图表来源

  • schema.prisma

章节来源

  • schema.prisma
  • drafts.controller.ts

User 与收藏关系(一对多)

  • 关系映射:User.favorites 为数组;Favorite.userId 外键指向 User.id;Favorite.bookId 指向 Book.id。
  • 级联策略:Favorite.bookId 字段声明 onDelete: Cascade,表示当书籍被删除时,对应的收藏记录也会级联删除。
  • 查询示例:获取用户收藏列表并包含书籍基本信息;添加/取消收藏;检查是否已收藏。

    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

章节来源

  • schema.prisma
  • favorites.service.ts

User 与订单关系(一对多)

  • 关系映射:User.orders 为数组;Order.userId 外键指向 User.id;Order.planId 可能指向 SubscriptionPlan。
  • 外键约束:迁移脚本为 Order.user 与 PlayRecord.user 等建立了外键约束,体现RESTRICT策略以保护数据完整性。
  • 查询示例:获取用户订单列表与分页统计;订单与TokenUsage的关联便于成本归集。

    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
  • 20260422105352_add_content_status/migration.sql

章节来源

  • schema.prisma
  • 20260422105352_add_content_status/migration.sql

User 与播放记录关系(一对多)

  • 关系映射:User.playRecords 为数组;PlayRecord.userId 外键指向 User.id;PlayRecord.chapterId 指向 BookChapter。
  • 约束与索引:唯一索引(用户, 章节)确保同一用户对同一章节仅有一条播放进度;索引覆盖查询场景。
  • 查询示例:获取用户播放进度列表并包含章节信息;保存/更新播放进度;删除播放记录;获取最近播放记录。

    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

章节来源

  • schema.prisma
  • player.service.ts

User 与播放列表关系(一对多)

  • 关系映射:User.playlists 为数组;Playlist.userId 外键指向 User.id;Playlist.items 为 PlaylistItem 数组。
  • 多对多:PlaylistItem 通过 playlistId 与 chapterId/audioId 组合,实现列表与章节/音频的多对多关联。
  • 查询示例:获取用户播放列表;获取播放列表详情并按顺序包含章节;添加/删除列表项;重新排序。

    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

章节来源

  • schema.prisma
  • playlist.controller.ts

User 与签到记录关系(一对多)

  • 关系映射:User.signRecords 为数组;SignRecord.userId 外键指向 User.id。
  • 查询示例:检查当日签到状态与连续签到天数;执行签到并调整用户免费使用次数。

    classDiagram
    class User
    class SignRecord {
    +id
    +userId
    +createdAt
    }
    User "1" <-- "many" SignRecord : "signRecords"
    

图表来源

  • schema.prisma

章节来源

  • schema.prisma
  • sign.service.ts

User 与订阅关系(一对多)

  • 关系映射:User.subscriptions 为数组;Subscription.userId 外键指向 User.id;Subscription.planId 指向 SubscriptionPlan。
  • 查询示例:获取用户当前有效订阅;查询套餐列表与详情;初始化默认套餐。

    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

章节来源

  • schema.prisma
  • subscription.service.ts

User 与 Token 余额/使用关系(一对一/一对多)

  • 关系映射:User.tokenBalance 一对一;User.tokenUsages 一对多;TokenUsage.orderId 可能指向 Order。
  • 查询示例:获取用户Token余额与使用明细;消耗Token并记录使用;检查配额。

    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

章节来源

  • schema.prisma
  • subscription.service.ts

User 与偏好关系(一对一)

  • 关系映射:User.preferences 一对一;UserPreference.userId 外键指向 User.id。
  • 查询示例:读取/更新用户播放速度、音质、主题、默认音色、音量、自动播放等偏好设置。

    classDiagram
    class User
    class UserPreference {
    +id
    +userId
    +playSpeed
    +quality
    +theme
    +defaultVoiceId
    +defaultVolume
    +autoPlayNext
    +wifiOnlyDownload
    +createdAt
    +updatedAt
    }
    User "1" --> "1" UserPreference : "preferences"
    

图表来源

  • schema.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 实例进行数据库交互,确保连接复用与生命周期管理。

      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
  • 20260422105352_add_content_status/migration.sql

章节来源

  • schema.prisma
  • 20260422105352_add_content_status/migration.sql
  • 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
  • subscription.service.ts

结论

本文基于Prisma Schema与服务层实现,系统梳理了User模型与评论、草稿、收藏、订单、播放记录、播放列表、签到记录、订阅、Token余额/使用、偏好等实体的关联关系与约束策略。通过明确的一对一/一对多/多对多映射、外键约束与索引设计,以及服务层的查询与业务逻辑封装,为用户侧数据访问提供了清晰、可扩展、高性能的实现路径。

附录

常见查询场景与实现要点

  • 获取用户所有收藏
    • 通过 Favorites 服务按 userId 查询并 include book 基本信息,按创建时间倒序。
    • 参考:favorites.service.ts
  • 查询用户播放历史
    • 通过 Player 服务按 userId 查询 PlayRecord 并 include 章节信息,按更新时间倒序。
    • 参考:player.service.ts
  • 统计用户创作数量
    • 通过 Drafts 控制器按 userId 与类型过滤,统计草稿数量或按更新时间倒序获取最新草稿。
    • 参考:drafts.controller.ts
  • 获取用户Token使用明细
    • 通过 Subscription 服务按 userId 分页查询 TokenUsage,并统计总数。
    • 参考:subscription.service.ts
  • 获取用户播放列表与详情
    • 通过 Playlist 控制器按 userId 查询列表并 include items,按 order 排序章节。
    • 参考:playlist.controller.ts