Files
moeka-project/apps/server/docs/ai-context/data-model-and-state.md
T
RainbowBird f8d1fa7a64 refactor(server): drop redis stream + worker role (#1792)
The Redis Stream `billing-events` + `worker` Railway role +
advisory-lock poller layered together didn't actually buy us reliability
— `debitFlux` swallowed XADD failures, leaving the door open to "balance
updated, ledger row never written". Collapse the whole thing back to:
`creditFlux` and `debitFlux` write `flux_transaction` ledger rows inline
within the same DB transaction that mutates `user_flux`, and `(user_id,
request_id)` remains the partial unique index that keeps retries safe.

Concrete changes:
- Inline ledger inserts in `BillingService.{debitFlux, creditFlux,
creditFluxFromStripeCheckout, creditFluxFromInvoice}`; drop `billingMq`
and `publishEvent` plumbing entirely.
- `routes/openai/v1` writes `llm_request_log` synchronously via the
existing `requestLogService`; the duplicate `llm-request-log.ts` service
module is removed.
- `bin/run-worker.ts`, `libs/mq/*`,
`services/billing/billing-events.ts`,
`services/billing/billing-consumer-handler.ts`, and matching tests are
deleted. CLI now exposes only `api`.
- `BILLING_EVENTS_*` env vars and the `DEFAULT_BILLING_EVENTS_STREAM`
helper are dropped; `docker-compose.yml` no longer ships a worker
service.
- `docs/ai-context/{workers-and-runtime, billing-architecture,
redis-boundaries-and-pubsub, data-model-and-state,
architecture-overview, README}.md`, `CLAUDE.md`, and the existing
verification docs are updated to describe the single-process synchronous
pipeline.

Tests: 29 files / 247 cases pass. Production deployments need to drop
the worker Railway service after this lands.
2026-05-08 21:14:01 +08:00

242 lines
4.5 KiB
Markdown

# Data Model And State
## 真相源原则
这套服务端最关键的状态归属如下:
- `Postgres`
- 用户认证数据
- 角色、聊天、Provider 配置
- Flux 余额与账本
- Stripe 业务镜像
- LLM 请求日志
- `Redis`
- Flux 余额缓存
- 服务配置 KV
- 聊天跨实例广播
- 计费事件队列
如果要判断“改哪个地方才算真的改成功”,大多数场景答案都是 Postgres。
## 主要表分组
### 认证
- `user`
- `session`
- `account`
- `verification`
来源文件:
- `src/schemas/accounts.ts`
说明:
- `better-auth` 直接用这组表
- `src/schemas/auth.ts` 基本是重复副本,目前不是主要依赖入口
### 角色与用户交互
- `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`
- `flux_transaction`
来源文件:
- `src/schemas/flux.ts`
- `src/schemas/flux-transaction.ts`
- `src/schemas/flux-transaction.ts`
职责边界:
- `user_flux`
- 当前余额快照
- `flux_transaction`
- append-only 账本流水
- 偏系统真相源
- `flux_transaction`
- 用户可见历史
- 偏产品展示
关键约束:
- `flux_transaction``(userId, requestId)` 有部分唯一索引
- 用来做扣费 / 充值幂等
### 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)` 唯一约束
## 现有代码中的结构信号
- `accounts.ts``auth.ts` 是重复 schema,后续如果做整理,应先统一真实使用入口再删副本。