purchases 表字段设计
数据模型第三张表:购买记录——每笔支付事件(订阅购买 / 充值)一行,是「实付人民币」这一唯一只有作者知道的信息的落点,也是折算链第二层(额度美元 → 实付人民币)的事实来源。本文记录字段清单、语义与决策理由。
| 项目 |
内容 |
| 状态 |
已实现(2026-09-15) |
| 关联 |
docs/design/qpu_history表字段设计.md(折算链第一条:quota → 额度美元)· docs/design/API契约.md §2.3(登记 / 编辑端点)· docs/upstream/NewAPI-sk-key接口文档.md(other.subscription_* 字段依据) |
| 范围 |
列结构 + 语义 + 写入时机;登记入口见 API 契约 §2.3 |
| 字段数 |
9 = 1 主键 + 8 人工录入(登记 4 · 期间 2 · 订阅 2) |
1. 表定位
- 一行 = 一笔支付事件(订阅购买 / 充值)——同一模型兼容两种形态;实现先做订阅路径(实测当前用量 100% 为订阅)
- 折算链全貌:
日志.quota ÷ quota_per_unit = 额度美元 → × 购买汇率 = 实付人民币;两条链的系数都取该日志所属购买的购买时刻(整笔订阅一把尺,见 qpu_history表字段设计.md §3.3)。购买比率 = paid_amount ÷ (quota_granted ÷ quota_per_unit)——只存事实、不存比率(见 3.2)
- 与日志的分流:日志
other.billing_source = "subscription" → 按本表匹配(优先 subscription_id,同 ID 跨期时按时间区间);"wallet"(充值)→ 池子近似(后议)
- 数据规模:年个位数行(订阅按周期购买)——无需索引设计
2. 字段清单
2.1 主键与登记字段(5 个)
| 列 |
类型 |
值来源 |
说明 |
id |
bigserial PK |
自增 |
|
kind |
text |
人工选择 |
subscription(订阅)/ topup(充值) |
paid_at |
timestamptz |
人工填写 |
支付时间 |
paid_amount |
numeric |
人工必填 |
实付人民币——唯一「只有作者知道」的信息 |
note |
text |
人工填写 |
备注 |
2.2 期间与订阅字段(4 个)
| 列 |
类型 |
值来源 |
说明 |
period_start |
timestamptz? |
人工录入(钱包页面) |
仅订阅:该笔覆盖的用量期间起点;左闭 |
period_end |
timestamptz? |
人工录入(钱包页面) |
期间终点;左开;未结束可空 |
subscription_id |
bigint? |
人工录入(钱包页面「订阅 #N」) |
上游订阅实例 ID;多订阅并存时用于精确分流(与日志 other.subscription_id 匹配) |
quota_granted |
bigint? |
人工录入(钱包页面「总额度」) |
本笔额度(订阅:钱包页面总额度;充值:充值记录) |
3. 决策记录(含理由)
3.1 登记 = 手工抄录(来源只有两处)
购买记录全部由作者手工录入,来源仅两处:支付记录(kind / paid_at / paid_amount)与钱包页面(订阅 ID / 期间 / 总额度)。订阅关键信息只有作者可见(没有免 auth 接口可查)——系统不做回填与推断,日志观测仅用于核对提示(见 §4);必填 / 可空的校验细节归技术稿。
3.2 折算比率不存储——存事实、不存派生数
paid_amount 是唯一不可替代的人工信息;比率是派生数——「记实付」比「记汇率」更省心(不用心算)且可事后核对;只存派生比率会丢失核对依据
- 日志同样不加汇率列(
usage_logs 零 schema 改动):入库有滞后(1000 条窗口内可能几天后才补回)→「入库时的配置值」会错标给「更早消费的日志」,且无从发现;半准确的数字比没有数字更危险
- 折算全部发生在展示 / 聚合层;关联线索(
billing_source / subscription_id / 金额分解)本就在日志 other 里,零 schema 改动即可用
3.3 为什么额度也手工录入(不采用日志感知)
日志逐条自带 subscription_total(实测),但回填它需要「订阅 ID → 该 ID 日志 total」的关联逻辑;钱包页面已直接给出总额度——手工抄录一步到位。系统对购买记录零回填、零推断,观测仅用于核对(见 §4)。
3.4 命名与类型
- 金额列用
numeric(人民币金额不用浮点);时间列统一 timestamptz
- 期间左闭右开(
[period_start, period_end))——与全项目期间惯例一致
- 订阅实例 ID 与日志同名
subscription_id——同一体系、同一含义(分流匹配即用此列)
4. 写入时机与维护
| 时机 |
动作 |
| 人工登记(管理通道) |
INSERT:录入支付信息(时间 / 实付)与订阅信息(ID / 期间 / 总额度,抄自钱包页面)→ 确认 |
| 系统观测(仅核对) |
日志观测到的订阅 ID / subscription_total 仅作核对提示(如「观测到 #N 未对应任何购买记录」),不参与回填 |
| 编辑 / 删除 |
数据为人工登记,改错即修(见 API 契约 §2.3) |
5. 明确不存的(已否决字段与理由)
| 字段 |
否决理由 |
| 折算比率(购买汇率) |
派生数——存事实(实付 + 额度)即可事后核对,只存比率会丢失核对依据(见 3.2) |
6. 展示层边界(界面设计的输入)
- 明细应展示「订阅归属」(数据源 = 日志
other):订阅名称、额度实况、(本表登记后)期间范围——给所有使用者可见
- 查看面金额口径(2026-09-13 修订):卡片 / 明细的「消费」类金额 = 折算实付人民币(按购买期间分段,每段
quota 占比 × 该笔实付);原「天然不含实付金额」的边界就此放开。仍不上查看面:实付总额、购买比率、购买记录(含 note)本身——管理面独占
- 已知代价(接受):明细含 quota 列、QPU 公开(500000 可查),使用者理论上可由两者反推购买比率——本工具是成本分担场景、非对抗关系
- 「按订阅维度统计」(本期用量/占比)数据天然支持——待评估,暂不列入
- 管理页相关见
docs/design/交互设计.md §3.3 与 docs/design/API契约.md §2.3
7. 待定事项(关联后续环节)
| 事项 |
归属 |
充值(topup)路径的折算近似算法 |
池子加权平均等方案,后议 |
| 多订阅并存的日志行为 |
按源码结构推理(每笔标所属 ID);实现期以真实场景实测验证 |
| 期间与订阅 ID 的匹配细节 |
技术稿细化(ID 优先、同 ID 跨期按时间) |
| 核对提示(观测到的订阅 ID ↔ 已录入记录比对)的交互 |
交互设计 |