3. 计费核心数据模型

多租户SaaS计费系统的数据模型是计费逻辑的基石。本章将详细拆解六张核心表:租户表、产品表、定价计划表、订阅表、用量记录表和账单表。理解它们之间的关联关系与字段设计,是后续实现计费接口与鉴权拦截链路的前提。

3.1 核心表结构设计

每张表都围绕“多租户隔离”与“计费原子化”两个原则设计。租户ID(tenant_id)作为全局分片键,确保数据安全隔离;计费相关字段采用精度可控的数值类型(如 DECIMAL(18,6)),避免浮点误差。

表名 核心字段(示例) 主键/索引 业务说明
租户表 (tenant) tenant_id, tenant_name, status, tier, created_at PK: tenant_id 标识每个租户及其服务等级(免费/专业/企业)
产品表 (product) product_id, product_name, product_type, unit, tenant_id PK: product_id; UK: (tenant_id, product_name) 定义可计费的资源或服务(如API调用次数、存储空间)
定价计划表 (pricing_plan) plan_id, plan_name, billing_cycle, price, tier, tenant_id PK: plan_id; UK: (tenant_id, plan_name) 定义具体的定价策略(如按月订阅、按量计费单价)
订阅表 (subscription) sub_id, tenant_id, plan_id, start_date, end_date, status PK: sub_id; IDX: (tenant_id, status) 记录租户当前生效的定价计划及时间范围
用量记录表 (usage_record) usage_id, tenant_id, product_id, quantity, recorded_at PK: usage_id; IDX: (tenant_id, product_id, recorded_at) 存储每次资源消耗的明细数据,用于按量计费
账单表 (invoice) invoice_id, tenant_id, sub_id, amount, due_date, status PK: invoice_id; IDX: (tenant_id, status, due_date) 最终生成的计费单据,包含应缴金额与支付状态
设计要点: 所有涉及金额或数量的字段均使用 DECIMAL 类型,避免 FLOAT/DOUBLE 带来的精度丢失。例如 price DECIMAL(18,6) 可支持到小数点后6位,满足微计费场景。

3.2 核心实体关系(ER图)

下图展示了六张表之间的主外键关联与业务流向。租户是顶层实体,产品与定价计划独立于订阅存在,订阅关联租户与定价计划,用量记录关联租户与产品,账单最终关联订阅与租户。

多租户计费核心ER图 多租户计费核心数据模型 ER 图 租户表 (tenant) tenant_id (PK) tenant_name, tier 产品表 (product) product_id (PK) product_name, unit 定价计划表 plan_id (PK) plan_name, price 订阅表 (subscription) sub_id (PK) start_date, end_date 用量记录表 usage_id (PK) quantity, recorded_at 账单表 (invoice) invoice_id (PK) amount, due_date 1:N 1:N 1:N 1:N 1:N 1:N 1:N 1:N 图例说明: 租户 产品 定价计划 订阅 用量记录 账单 外键关联

3.3 关键字段与索引设计

以下列出每张表中容易忽略但至关重要的字段与索引策略,这些设计直接影响计费接口的查询性能与数据一致性。

3.3.1 租户表 (tenant)

  • tier:枚举类型(FREE, PRO, ENTERPRISE),用于鉴权拦截时判断功能可用性。
  • status:ACTIVE / SUSPENDED / CLOSED,计费接口需先校验此字段。
  • 索引:idx_tenant_status (status) 用于批量查询活跃租户。

3.3.2 产品表 (product)

  • product_type:UNIT(按量计费)或 SUBSCRIPTION(订阅制),决定计费引擎的分支逻辑。
  • unit:计量单位(如 次、GB、小时),用于用量记录聚合。
  • 唯一约束:uk_tenant_product (tenant_id, product_name) 防止同一租户下产品名重复。

3.3.3 定价计划表 (pricing_plan)

  • billing_cycle:MONTHLY / YEARLY / ONE_TIME,影响账单生成周期。
  • price:DECIMAL(18,6),支持阶梯定价时需配合 min_quantity / max_quantity 字段(扩展设计)。
  • 索引:idx_plan_tenant (tenant_id) 加速租户内计划查询。

3.3.4 订阅表 (subscription)

  • status:ACTIVE / EXPIRED / CANCELLED,计费鉴权时需校验订阅是否在有效期内。
  • start_date / end_date:DATETIME 类型,用于判断当前时间是否在订阅区间。
  • 复合索引:idx_sub_tenant_status (tenant_id, status) 是计费接口最频繁的查询路径。

3.3.5 用量记录表 (usage_record)

  • quantity:DECIMAL(18,6),记录单次消耗量。
  • recorded_at:DATETIME(3),精确到毫秒,用于去重与时间窗口聚合。
  • 复合索引:idx_usage_tenant_product_time (tenant_id, product_id, recorded_at) 是按量计费查询的核心索引。

3.3.6 账单表 (invoice)

  • amount:DECIMAL(18,6),最终应缴金额。
  • due_date:DATE,用于逾期判断与催收。
  • status:PENDING / PAID / OVERDUE / CANCELLED。
  • 索引:idx_invoice_tenant_due (tenant_id, due_date, status) 支持账单列表分页与逾期筛选。
实战建议: 在用量记录表与账单表上,务必添加 created_at 与 updated_at 审计字段,并设置 DEFAULT CURRENT_TIMESTAMP。这有助于排查计费数据不一致问题,也便于后续对账系统使用。

3.4 数据模型与鉴权拦截的关联

计费数据模型不仅是存储结构,更是鉴权拦截链路的决策依据。以下流程图展示了在API请求中,如何利用上述数据模型进行多租户计费鉴权。

计费鉴权拦截链路流程图 计费鉴权拦截链路流程图 API 请求到达 校验 tenant.status 校验 subscription 有效期 匹配 product & pricing_plan 写入 usage_record / 生成 invoice 拒绝请求 拒绝请求 拒绝请求 说明:每一步校验失败都会直接拒绝请求,并记录审计日志。只有全部通过才允许写入用量或生成账单。
注意: 在鉴权拦截链路中,subscription 表的 end_date 字段必须与当前时间进行比对。如果订阅已过期,即使租户状态为 ACTIVE,也应拒绝计费操作,防止产生无效账单。

3.5 数据模型扩展建议

在实际生产环境中,上述六张表通常需要配合以下扩展设计来应对复杂计费场景:

  • 阶梯定价表:当定价计划支持阶梯价格时,可拆出 pricing_tier 表,包含 plan_id、min_quantity、max_quantity、unit_price 字段。
  • 折扣与优惠券表:与账单表关联,支持按比例或固定金额减免。
  • 对账表:记录每次计费引擎的执行批次与结果摘要,便于财务对账与异常排查。
  • 审计日志表:记录所有计费相关数据的变更历史,满足合规要求。