238 lines
4.9 KiB
Markdown
238 lines
4.9 KiB
Markdown
# 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`
|
||
|
||
说明:
|
||
|
||
- 运行时查询时会把系统配置和用户配置拼接成一个结果集
|
||
- `config` 是 `jsonb`
|
||
|
||
### 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` 同步路径,不写新表。
|