Files
moeka-project/apps/server/docs/ai-context/data-model-and-state.md
T

238 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 同步路径,不写新表。