Back to Airi

Data Model And State

apps/server/docs/ai-context/data-model-and-state.md

0.11.04.9 KB
Original Source

Data Model And State

真相源原则

这套服务端最关键的状态归属如下:

  • Postgres
    • 用户认证数据
    • 角色、聊天、Provider 配置
    • Flux 余额与账本
    • Stripe 业务镜像
    • LLM 请求日志
  • Redis
    • Flux 余额缓存
    • 服务配置 KV
    • 聊天跨实例广播 (Pub/Sub)
    • Sub-Flux 计量债务账本(TTS 字符等,详见 flux-meter.md
    • TTS voices 上游响应缓存

如果要判断”改哪个地方才算真的改成功”,大多数场景答案都是 Postgres。Redis Streams 已全部移除,没有”计费事件队列”这层抽象。

主要表分组

认证

  • user
  • session
  • account
  • verification

来源文件:

  • src/schemas/accounts.ts

说明:

  • better-auth 直接用这组表
  • pnpm -F @proj-airi/server auth:generate 自动产物,手改会被覆盖

角色与用户交互

  • characters
  • character_covers
  • avatar_model
  • character_capabilities
  • character_i18n
  • character_prompts
  • user_character_likes
  • user_character_bookmarks

来源文件:

  • src/schemas/characters.ts
  • src/schemas/user-character.ts

说明:

  • 角色实体采用软删除
  • 点赞与收藏通过中间表建模
  • 计数值冗余保存在 characters 表上

聊天

  • chats
  • chat_members
  • messages
  • media
  • stickers
  • sticker_packs

来源文件:

  • src/schemas/chats.ts

说明:

  • messages.seq 是会话内顺序字段
  • 写消息时通过 SELECT ... FOR UPDATE 锁 chat 以串行生成 seq
  • senderId 是宽松字段,不强制外键

Provider 配置

  • user_provider_configs
  • system_provider_configs

来源文件:

  • src/schemas/providers.ts

说明:

  • 运行时查询时会把系统配置和用户配置拼接成一个结果集
  • configjsonb

Flux / 账本

  • user_flux
  • flux_transaction

来源文件:

  • src/schemas/flux.ts
  • src/schemas/flux-transaction.ts

职责边界:

  • user_flux
    • 当前余额快照(单行/用户)
  • flux_transaction
    • append-only 账本流水(type: credit / debit / initial / promo)
    • 同时承担系统真相源和用户可见历史,/api/v1/flux/history 直接读这张表

关键约束:

  • flux_transaction(userId, requestId) WHERE requestId IS NOT NULL 有部分唯一索引
  • 用来做扣费 / 充值幂等(含 admin promo grant 的 idempotencyKey

Stripe 业务镜像

  • stripe_customer
  • stripe_checkout_session
  • stripe_subscription
  • stripe_invoice

来源文件:

  • src/schemas/stripe.ts

说明:

  • 这些表是 Stripe 状态的本地镜像
  • 真正的余额变化仍由 billingService 写入 user_flux + flux_transaction
  • fluxCredited 字段用于避免重复入账

LLM 请求日志

  • llm_request_log

来源文件:

  • src/schemas/llm-request-log.ts

说明:

  • 只做追加写入
  • 明确不加 user 外键,以避免高并发写入的额外约束成本

服务与状态写入边界

createFluxService()

负责:

  • 余额读取
  • 新用户首次读取时初始化 user_flux
  • Redis cache-aside

不负责:

  • 扣费
  • 充值
  • transaction 写入

createBillingService()

负责:

  • 所有余额写操作
  • DB 事务
  • debitFlux / credit 方法:事务内 lock → check → update user_flux → insert flux_transaction ledger
  • 事务提交后 best-effort redis.set 更新 Flux 余额缓存

这是所有 Flux 写路径应收敛到的中心。

createStripeService()

负责:

  • Stripe 实体 upsert

不负责:

  • 最终 Flux 入账

真正入账通过 billingService.creditFluxFromStripeCheckout() 或相关 credit 方法完成。

Redis 中的数据类型

Flux 缓存

  • key: flux:<userId>
  • value: 字符串化整数

写入来源:

  • fluxService.getFlux() cache miss 后回填
  • billingService 余额事务提交后 best-effort redis.set 直接更新(API 进程内同步)

配置 KV

  • key: config:<CONFIG_NAME>

config-kv.ts 管理,支持:

  • 数值
  • 字符串
  • FLUX_PACKAGES JSON

聊天跨实例广播

  • channel: chat:broadcast:<userId>

幂等与并发控制

余额并发

billingService 在事务中:

  1. (可选)按 (userId, requestId) 命中 ledger → 命中即返回,跳过余下步骤
  2. SELECT user_flux FOR UPDATE
  3. 计算新余额
  4. user_flux + 写 flux_transaction ledger
  5. 事务提交后 best-effort redis.set

这保证同一用户余额更新是串行化的,并且 ledger 行与余额变更在同一原子提交里。

Stripe 幂等

主要依赖:

  • stripe_checkout_session.fluxCredited
  • stripe_invoice.fluxCredited
  • flux_transaction(userId, requestId) 唯一约束

现有代码中的结构信号

  • src/schemas/flux-grant-batch.ts 是已废弃的旧 admin batch 设计 schema,没有被 app.ts 装配也没有 migration 在用,是 dead code,改这块前直接删除。当前 admin 发 FLUX 走 /api/admin/flux-grants 同步路径,不写新表。