D05 计量与计费域 · 设计文档

本域总览见《账号计费中心 · 结构大纲》。本文为该域完整设计:定位与边界 / 角色与依赖 / 数据模型 / 核心流程 / 接口契约 / 关键约束与验收标准。


1. 定位与边界

定位:用量与金额的事实底座。回答四个问题——「一次调用产生了多少用量」(量)、「这些用量归谁」(归属维度)、「这些用量应付多少钱」(金额)、「上报的量与实扣的钱对得上吗」(对账)。

本域是唯一的用量落库方与对账判定方:所有通道的用量汇聚到同一张日聚合底座,所有对账结论由本域给出。按「先事实结构、再运行路径、后对外投影」的次序组织:第 3 节给出底座结构,第 4 节给出结构之上的写入/查询/对账路径,第 5 节给出这些路径的对外投影。

核心口径

口径 内容
量价分离 应付金额由计量方按自身计价规则即算即传;本侧只入账不计价,不读计价规则、不做二次换算
覆盖式 同一维度组合同一统计日重复上报 = 覆盖,不是累加;量可重算,对账重跑不双计
归属六元组 组织 / 组 / 成员 / 模型 / 密钥展示前缀 / 统计日期
日粒度 按统计日期聚合的日粒度底座,非实时明细;日期一律按 Asia/Shanghai 归日
金额口径 元、6 位小数、字符串传输;列级精度与资金侧口径对齐
对账容差 默认 0,即精确比对(两侧同源同值)
差异方向 差异 = 计费额 − 计量额:为正即多扣(计费额大于计量额),为负即缺扣
债务口径 对账差异只落处置台账与告警,不构成欠费判定依据(欠费只认现金余额为负,判定在钱包与账务域)

边界(不管什么)

本域在多域中的位置


2. 角色与依赖

参与角色

角色 身份 在本域的能力
实例计量侧 服务间调用方 按窗口聚合后推送用量行(覆盖式),随行带应付金额
数据面网关计量侧 服务间调用方 推送账户密钥通道的用量行(密钥展示前缀与实例侧不同源)
计费路由计量侧 服务间调用方 推送中转通道的用量行;组标识与成员标识可能来自另一命名空间
运营面 运营人员(持运营范围令牌) 查看对账差异报表、手动触发对账扫描
组织管理者 工作区 Owner / Admin 查看组织透视用量(任意维度组合、导出)
个人用户 门户用户 查看本人透视用量、账期账单

能力矩阵

操作 实例 / 网关 / 路由计量侧 运营面 组织管理者 个人用户
推送用量行 ✓ ✗ ✗ ✗
账期汇总读取 ✗ ✓ ✗ ✗
对账差异报表 ✗ ✓ ✗ ✗
手动触发对账扫描 ✗ ✓ ✗ ✗
组织透视用量 ✗ ✗ ✓(Owner / Admin) ✗
本人透视用量 / 账期账单 ✗ ✗ ✗ ✓

上游依赖

下游被引用(谁引用本域)

引用方 引用内容 语义
钱包与账务域 用量日聚合 账期账单的用量侧数据源
发票域 账期汇总 开票数据源(按周期聚合后的用量与金额)
风控与合规域 用量日聚合 用量突增的阈值与环比判定输入;台账表由该域拥有
通知与触达域 站内信记录 对账异常与量级突增的送达(本域只借写站内信行)
用户自助与门户 聚合函数与口径 门户用量页、组织透视页、账单页复用同一聚合入口与分页口径

访问边界


3. 数据模型

3.1 域内关联总览

工作区维度(弱引用,无外键)组维度(弱引用,无外键)成员维度(弱引用,可归哨兵)

organizations

usage_daily

bigint

id

PK

uuid

organization_id

工作区维度,必填

uuid

group_id

组维度,必填

uuid

user_id

成员维度,可空;写入归一哨兵

string

model

模型名,不可空

string

key_display

密钥展示前缀,不做跨域外键

date

stat_date

统计日期,Asia/Shanghai 归日

bigint

requests

请求次数

bigint

input_tokens

输入 token 数

bigint

output_tokens

输出 token 数

numeric

amount

应付金额,元 6 位小数

numeric

quoted_amount

原应有费用,元 6 位小数

timestamp

updated_at

最后一次覆盖时刻

groups

users

图 1

三条弱引用线均为非同一域内的归属关系:本域刻意不为它们建外键(见 3.4 第 4 条)。

3.2 域间引用

弱引用:仅归属列,无外键弱引用:仅归属列,无外键

usage_daily
用量日聚合底座

钱包与账务域
customer_transactions

租户与组织域
organizations、groups、users

订购与支付域
账户访问密钥展示前缀

风控与合规域
risk_events

通知与触达域
notifications

图 2

3.3 表清单(自有 1 表 + 借写 2 表)

表 归属 职责 关键字段
usage_daily 本域自有 维度化用量日聚合底座(覆盖式) organization_id / group_id / user_id / model / key_display / stat_date / requests / input_tokens / output_tokens / amount / quoted_amount / updated_at
risk_events 风控与合规域,本域借写 对账差异与量级突增的处置台账 rule_id / organization_id / group_id / customer_id / trigger_tokens / threshold_tokens / action / triggered_at
notifications 通知与触达域,本域借写 对账异常与量级突增的站内信送达 customer_id / type / group_id / title / content / read_at

3.4 设计约束

  1. 六元组唯一:uq_usage_daily_dims 覆盖(工作区、组、成员、模型、密钥展示前缀、统计日期);一切写入路径都必须命中该约束做覆盖,不得追加。
  2. 成员维度可空但不写空:user_id 列可空,但写入方在无法归因到人时一律归一为哨兵标识——空值在唯一约束下互不相等,直接写空会破坏覆盖式语义,使同一维度同一天分裂成多行。
  3. 密钥展示前缀不做跨域外键:key_display 只是可读标识,仅承载展示与归属切分;实例通道与账户通道的前缀来源不同,天然分属不同唯一行,从而两写者互斥、不双计。
  4. 归属一律弱引用:工作区、组、成员三列不建外键——用量是高频写入事实表,外键约束会拖住写入路径,且归属主体可能先于用量行被关闭。
  5. 金额列可空、精度固定:amount 与 quoted_amount 均为元、固定 6 位小数;未计价时为空,覆盖写入按「新值优先、空值不清旧值」处理。
  6. 日期窗口受限:stat_date 只接受 [今天 − N, 今天](Asia/Shanghai),N 由 usage_stat_date_backfill_days 给定、默认 90 天;越界整批拒绝。
  7. 只增不改不删:usage_daily 不提供删除路径,覆盖是唯一更新方式;updated_at 记录最后一次覆盖时刻,可据此判断窗口是否被重推。
  8. 查询索引:ix_usage_daily_stat_date 支撑按日期区间的全量聚合;ix_usage_daily_group_date 支撑对账的按组 × 统计日聚合。
  9. 借写最小化:本域只向台账与站内信写入必要列,不扩列、不改其结构;金额类信息在台账中以既有列类型可承载的形式落(见 6.3 开放项 2)。

4. 核心流程

4.1 用量上报:覆盖式 UPSERT

用量底座账号计费中心计量方用量底座账号计费中心计量方alt[命中组但工作区不一致][未命中组(跨命名空间)]新值覆盖旧值;金额传空不清旧值alt[日期越界][日期通过]按窗口聚合本批维度行(含应付金额)推送用量行(单批 1..500 行)批内按六元组去重:后者覆盖前者校验统计日期窗口 [今天−N, 今天]422 整批拒绝(stat_date_out_of_range)组归属校验:命中本域组则比对工作区422 整批拒绝(group_org_mismatch)放行 + 结构化 WARN 告警(usage_bulk_group_unverified)覆盖式 UPSERT(唯一键 = 六元组)200,返回实际落库行数
图 3

4.2 写入侧校验与告警

否是否是命中且工作区不一致命中且一致未命中

收到批量用量行

行数是否在 1..500

422 校验失败

批内按六元组去重(后写者胜)

统计日期是否在 [今天−N, 今天]

422 stat_date_out_of_range(整批)

组标识是否命中本域组

422 group_org_mismatch(整批)

放行

放行 + 结构化 WARN 告警

覆盖式写入并返回落库行数

图 4

4.3 归属维度与不可归因处理

能不能实例通道账户密钥通道

一条用量事实

能否定位成员

写真实成员标识

归一为哨兵标识,保持覆盖式语义

密钥展示前缀来自哪条通道

写实例前缀,独立唯一行

写账户前缀,独立唯一行

同维度同一天重复上报 = 覆盖

组织透视展示时哨兵行成员维度置空

图 5

4.4 用量查询:维度聚合、分页与汇总口径

是否

查询请求(日期区间 + 过滤条件)

统一聚合入口:按维度列分组求和

是否导出

取全量聚合行,生成同源 CSV

SQL 侧分页 LIMIT / OFFSET

分组行数作为总数(子查询外层计数)

全量汇总(不分组):过滤范围内全部行

返回 列表 + 总数 + 分页参数

图 6

4.5 对账:量与钱逐组逐日精确比对

扣减流水用量底座账号计费中心运营面扣减流水用量底座账号计费中心运营面只取有组归属的流水(客户级入账无组可对)alt[计费额 > 计量额][计费额 < 计量额][差值在容差内]请求周期对账报表(日期区间 + 可选工作区 / 组过滤)量侧:按(工作区, 组, 统计日)聚合上报应付金额钱侧:按(工作区, 组, 同日)聚合扣减流水金额逐行比对:差异 = 计费额 − 计量额多扣行 excess_deduct(钱不可重放,最严重)缺扣行 missing_deduct(可补扣)一致 ok返回逐行结果 + 汇总(量、钱、异常行数)
图 7

4.6 对账异常扫描:周期循环、幂等与多副本安全

否是是否

周期循环触发

扫描窗口:昨天起往前 N 天(已完结窗口)

按组汇总窗口内的量与钱

差异是否超容差且量钱不同时为零

跳过(一致或纯入账)

同组是否在去重窗口内已命中

跳过(幂等)

落处置台账(含差异方向与金额)

落站内信告知工作区

返回本次新建台账数

图 8

4.7 抵消类流水(免计费留痕)量级突增扫描

否(倍数为 0 或下限为 0)是否是是否

扫描触发

突增倍数与下限是否都已启用

能力关闭,直接返回

取当前窗口免计费留痕计数(按 工作区 × 组)

取前一同长窗口的计数

当前计数 ≥ 下限 且 > 前窗计数 × 倍数

无突增

同组是否在去重窗口内已命中

跳过(幂等)

落处置台账 + 站内信(疑误免 / 漏定价)

图 9

4.8 量价分离的边界

不做二次换算

计量方:产生用量 + 按自身计价规则算出应付金额

用量日聚合底座:量与金额同期覆盖落库

扣减请求:同一取整值的金额 + 幂等键

钱包与账务域:赠金优先到现金兜底,落扣减流水

对账:计费额(流水) − 计量额(底座)

本域不读计价规则、不反向拉取、不重算金额

图 10

5. 接口契约

5.1 用量写入(/internal/usage)

方法 路径 语义
POST /internal/usage/bulk 批量推送用量行(1..500 行):覆盖式 UPSERT,返回实际落库行数;日期越界返回 stat_date_out_of_range,命中本域组但工作区不一致返回 group_org_mismatch

5.2 账期汇总与对账(/internal/usage、/internal/reconcile)

方法 路径 语义
GET /internal/usage/summary 账期汇总:按(组, 统计日)聚合的用量 + 流水按类型/组的聚合(发票与账单数据源);结束日期早于开始日期返回 end_date_before_start
GET /internal/reconcile/check 对账差异报表:周期内每(组 × 统计日)的量/钱 + 交叉校验结果 + 汇总
POST /internal/reconcile/scan 手动触发对账异常检测(幂等):量钱差异与免计费量级突增一并扫描,返回新建台账数

5.3 运营面对账(/api/account/ops)

方法 路径 语义
GET /api/account/ops/reconcile/check 对账差异报表(运营面入口,与内部实现共用同一实现,不做双轨)
POST /api/account/ops/reconcile/scan 手动触发对账扫描(幂等),并写运营操作留痕

5.4 门户用量查询(/api/account)

方法 路径 语义
GET /api/account/usage 本人透视用量:按(统计日期、模型、密钥展示前缀)聚合分页,默认最近 30 天;返回总数与分页参数
GET /api/account/org/usage 组织透视用量:维度组合可配(默认组 × 日期),返回列表 + 全量汇总 + 总数;导出时返回同源 CSV
GET /api/account/bills 账期账单:用量日汇总 + 按模型聚合 + 流水按类型聚合

6. 关键约束与验收标准

6.1 约束

  1. 覆盖式是唯一写入语义:任何通道、任何重推,同一六元组窗口在底座里恒为一行;不存在累加路径,也不存在删除路径。
  2. 维度完备且非空:工作区、组、模型、密钥展示前缀、统计日期必填;成员维度可空,但写入时归一为哨兵,保证唯一约束恒可命中。
  3. 写入即校验、校验即整批:批内去重、日期窗口、组归属三类校验任一不通过则整批拒绝,不产生部分写入。
  4. 归属弱引用:所有归属列都不建跨域外键,避免约束拖住高频写入路径,也避免归属主体关闭时拖累已落库的用量事实。
  5. 金额字符串口径:对外一律元、6 位小数字符串;列级精度固定,禁止浮点表示。
  6. 对账容差为 0:量与钱同源同值;差异只有多扣与缺扣两个方向,判定以「计费额 − 计量额」符号为准。
  7. 差异落地即留痕:每一处超容差差异都落处置台账并送达站内信,不静默吞掉;台账行不改变任何资金状态。
  8. 扫描幂等且多副本安全:周期扫描与手动触发共用同一实现;以「同规则 × 组织 × 组 × 去重窗口」判重,重复运行不产生重复台账。
  9. 债务口径外置:对账异常与免计费突增都不构成欠费判定依据;欠费判定只认现金余额为负,在钱包与账务域。
  10. 同一聚合入口:页面、导出、账单、对账只走一个聚合实现,差异只体现在分组维度与过滤条件上。
  11. 访问范围互斥:计量写链路与运营读链路分属不同令牌范围,一个范围不可执行另一范围的端点;内部路径不经公网入口。

6.2 验收标准

# 验收口径
1 同一六元组连续推送两次且量不同:底座只有一行,取后一次的值(覆盖而非累加)
2 同一批内含重复维度:请求成功,落库行数等于去重后的行数,不出现整批失败
3 成员维度为空的上报:写入归一为哨兵,重复上报仍覆盖同一行(不分裂成多行)
4 实例通道与账户密钥通道各推一行、其余维度相同:底座两行并存、互不覆盖,合计等于两行之和
5 统计日期超出 [今天 − N, 今天]:整批拒绝并返回越界原因,底座无任何新增行
6 组标识命中本域组但工作区不一致:整批拒绝;组标识未命中:放行且产生一条结构化告警
7 覆盖写入时金额传空:原有金额保留(不被抹成空);首次上报即带金额:正常落值
8 个人透视查询:只返回本人成员维度的行,总数与分页正确,翻页不改变汇总数字
9 组织透视任意维度组合:总数等于聚合后分组行数,汇总等于过滤范围内全量合计(不随当前页变化)
10 量与钱完全一致时报表全行为一致;仅钱多时标为多扣、仅钱少时标为缺扣,差异额等于两者之差
11 无组归属的入账流水不出现在对账报表行内;纯入账日不产生核对行
12 连续两次扫描同一窗口:第二次不产生新台账;多副本同时扫描亦不产生重复台账
13 免计费留痕金额恒为 0 且不计入量钱等式;当前窗计数相对前一窗口超过倍数且不低于下限时产生台账与站内信,低于下限时不产生
14 突增倍数设为 0 或下限设为 0 时,免计费突增扫描不产生任何台账
15 对账异常落台账后,工作区余额与账户状态均不发生任何变化
16 内部写入与汇总端点无内部令牌不可访问;运营面对账端点无运营范围不可访问

6.3 待后续设计细化的开放项

  1. 跨命名空间组标识的映射收敛:中转通道推送的组标识与本域组标识不同源,现为放行加告警;是否由对端改推本域组标识、或引入映射表,需与对端一并定案。
  2. 台账金额列的精度收敛:处置台账的异常金额现以最小货币单位的整数落(受既有列类型约束),随台账结构重写收敛为元小数口径。
  3. 迟到重推与已封账账期的边界:回溯窗口目前由参数给定;是否引入按账期封账的硬边界,需与账期结算流程一并设计。
  4. 减免额的可核对性:原应有费用已可承载「原价 − 实收」求差;减免原因的分类统计与运营可核对口径待细化。
  5. 用量突增与对账差异的联动:用量突增由风控与合规域扫描、对账差异由本域扫描,两者在台账层是否合并成一类处置线索待定。
  6. 口径迁移边界日的例外:两侧同源同值、容差为 0;历史上因计量单位切换产生的存量行是否需要一次性容差窗口,待与资金侧确认后收口。