D06 账户状态与准入域 · 设计文档

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


1. 定位与边界

定位:全仓「这个客户此刻能不能用」的唯一判定者与唯一下发者。本域回答三件事——客户处于五态中的哪一态;状态键里放什么(载荷契约);读侧怎么判、何时放行。它是全仓契约最敏感的一个域:键名、载荷、版本语义与读侧规则是跨服务契约,写侧改了字段而读侧不认,会以「放行欠费客户」或「误封已充值客户」两种相反形式暴露。

核心概念

概念 承载 语义
状态键 状态存储中的客户级键 对外实时准入媒介,唯一生产者为本域、消费方只读;键缺失或过期一律放行
派生态 state active / grace / overdue / banned / closed 五值,判定生命周期优先
封锁位 blocked 与 block_reason 拒绝语义的载体:quota 按额度拒绝,status 按状态拒绝
宽限窗 grace_until 仅 grace 态透出;窗是否走完由消费方按自身时钟判定
准入真相 available 唯一公式 = 现金 + Σ(在营未过期赠金剩余) + 信用额度;大于 0 即放行
状态版本 state_version 客户级单调版本,库层在行锁内取全局序列赋值;写侧去旧专用
欠费标记 overdue_since 派生量,供报表、清单与欠款分级使用;不是准入真相

关键口径

边界


2. 角色与依赖

能力矩阵

能力 本域 计费路由 数据面网关 运营面 门户用户
判定派生态 ✓(唯一实现) ✗ ✗ ✗ ✗
写状态键 ✓(唯一生产者) ✗ ✗ ✗ ✗
读状态键 ✗ ✓ ✓ 仅投影 仅投影
状态查询端点 ✓(提供) ✓ ✓ ✓ ✗
状态标记与宽限设置 语义在本域 ✗ ✗ ✓ ✗
派生态展示 ✗ ✗ ✗ ✓ ✓(本人)

上游依赖

上游 依赖内容 语义
钱包与账务域 现金余额、赠金剩余、信用额度、宽限截止 可用额度公式的唯一输入
钱包与账务域 欠费标记重算结果 派生量,供清单与账龄分级
账号与身份域 客户标识与工作区归属 状态键的键主体
订购与支付域 客户创建点 创建即写键的触发来源

下游被引用

引用方 引用内容 语义
数据面网关 状态键与封锁语义 每请求前置准入,按原因分流到额度拒绝或状态拒绝
计费路由 状态查询端点 扣费前判定整批拒绝,在途损失入台账
门户与自助 派生态与宽限截止 欠费告知与还款入口的展示口径(详见 D12)
风控与合规域 欠款账龄与封锁分级 提醒 / 催收 / 冻结的事件输入
通知与触达域 变更事件 欠费与解封告知的触发面(投递不在本域)
运营管理 状态与宽限的只读投影 客户台账与审计索引

3. 数据模型

本域无自有表。事实载体有两个:状态存储中的客户级状态键(键名、载荷与存活期共同构成对外契约)与客户表上的状态与版本列(status / grace_until / grace_granted_at / overdue_since / credit_limit / state_version)。前者是派生态的对外投影,后者是判定输入与版本序的落点;两者都不由本域独立拥有,本节只描述读取关系与派生依赖。

3.1 域内关联总览

赠金授权行,变更即改变可用额度只增资金流水,扣费类写入抬高版本派生投影,每客户一键写键落地成功即发出变更事件

customers

uuid

id

PK

string

status

active / banned / closed

decimal

cash_balance

现金余额,元 6 位小数,可为负

decimal

credit_limit

准入缓冲,默认 0

timestamp

overdue_since

欠费派生标记,非准入真相

timestamp

grace_until

宽限截止时刻

timestamp

grace_granted_at

上次自动授限时刻

bigint

state_version

客户级单调版本,库层触发器赋值

timestamp

created_at

customer_grants

uuid

id

PK

uuid

customer_id

FK

decimal

amount

赠金金额,元 6 位小数

timestamp

expires_at

过期时刻,扣减按升序

string

status

active / revoked

customer_transactions

uuid

id

PK

uuid

customer_id

FK

string

type

扣费类为唯一抬高版本的流水类型

decimal

amount

uuid

grant_id

被扣赠金,空即现金行

timestamp

created_at

state_key_payload

uuid

customer_id

PK

键名内嵌的客户标识

string

state

active / grace / overdue / banned / closed

boolean

blocked

封锁位

string

block_reason

quota / status

timestamp

grace_until

仅 grace 态非空

string

available

可用额度,元字符串

string

cash_balance

现金余额,元字符串

timestamp

evaluated_at

评估时刻

bigint

state_version

载荷内嵌的客户级版本

state_changed_event

uuid

customer_id

PK

string

from_state

写键前的旧派生态

string

to_state

本次派生态

string

reason

recharge / grant / deduct / adjustment / grace_change / status_change / transfer / register / sweep

图 1

3.2 域间引用

变更抬高版本语句级抬高版本每请求前置准入扣费前判定派生态投影欠费与解封告知欠款账龄与封锁分级输入只读投影

customers
状态与版本列

状态键
客户级派生态载荷

customer_grants
赠金行

customer_transactions
扣费类流水

状态查询端点
派生态唯一出口

数据面网关
额度拒绝 / 状态拒绝

计费路由

门户与自助
欠费展示与还款入口

变更事件通道

通知与触达域

风控与合规域
提醒 / 催收 / 冻结

运营管理
客户台账与审计索引

图 2

3.3 表清单(本域无自有表,引用 3 表)

表 职责(本域视角) 关键字段
customers 状态与版本的落点:判定输入的来源,版本序的载体 status / cash_balance / credit_limit / overdue_since / grace_until / grace_granted_at / state_version / created_at
customer_grants 赠金剩余参与可用额度;授权行变更抬高客户版本 customer_id / amount / expires_at / status
customer_transactions 扣费类流水的写入抬高客户版本;赠金已用量聚合 customer_id / type / amount / grant_id / created_at

状态存储中的状态键与变更事件通道不是关系库表,故不入本表清单,其键名与载荷见 4.2 与 4.3。

3.4 设计约束

  1. 键名唯一形态:键名形态为 STATE_KEY_PREFIX:{customer_id}:state——STATE_KEY_PREFIX 是部署内共享的客户状态命名空间常量,取值为两段(平台共享前缀 + 固定客户段 cust);中段为客户标识,尾段为固定后缀 state。变更事件通道常量 STATE_CHANNEL 同属该命名空间(尾段 state-changed)。两者均不得按环境改写,否则消费方读到的是另一片命名空间。
  2. 载荷字段固定九项:customer_id、state、blocked、block_reason、grace_until、available、cash_balance、evaluated_at、state_version;金额一律元字符串、6 位小数。
  3. 宽限截止的透出条件:grace_until 仅当派生态为 grace 时非空,其余态一律为空——消费方据此把「窗内服务」写成一次时钟比较。
  4. 写入必须原子比对:写键走「读旧值 → 比对版本 → 写键」三步的原子脚本,在状态存储的单线程执行模型内完成、无并发窗口;等价的「先读后写」会被并发发布插队,去旧形同虚设。
  5. 版本由库层赋值:state_version 由客户表上的触发器在行锁内取全局序列(customer_state_version_seq)赋值,应用层只读不写;行锁把同客户的并发写串行化,后取到锁的事务必然取到更大序列值,故版本序 = 提交序。
  6. 版本采样先于内容采样:派生必须「先版本、后内容」,使「版本不大于内容新鲜度」恒成立;反序在已提交读下会产出「旧内容 + 新版本」并被当最新值接受,修复失效。
  7. 该次序由构造保证:状态派生的唯一入口不接受调用方传入的可用额度,两值一律由函数内部按固定次序采样——调用方无从「先算好内容再进来读版本」。
  8. 键的存活期双档:放行态用短存活期 state_key_ttl_seconds(默认 600 秒),封锁态用长存活期 state_blocked_key_ttl_seconds(默认 7200 秒);后者不得低于一个加固周期 state_sweep_interval_seconds(默认 300 秒)。
  9. 命名空间唯一生产者:本域是该命名空间唯一写者;键已存在但内容不是合法对象时属「外力写入」,写入照旧(fail-safe)但必须落告警。
  10. 状态值域封闭:派生态仅取五个值,客户经营状态仅取 active / banned / closed 三值;closed 为终态,无退出路径。
  11. 不落任何自有数据:本域不建表、不建列,全部事实读自客户行与赠金行、流水行;可用额度公式只在钱包与账务域一处维护,本域只调用不复制。

4. 核心流程

4.1 状态派生与判定(生命周期优先)

closedbannedactive 且可用额度大于 0active 且可用额度不大于 0是否

读客户行:先取客户级状态版本,再取可用额度与宽限截止

生命周期状态

closed:封锁位为真,原因为 status

banned:封锁位为真,原因为 status

active:封锁位为假

宽限截止是否晚于当前时刻

grace:封锁位为假,透出宽限截止

overdue:封锁位为真,原因为 quota

按九项字段组装载荷,金额取元字符串

图 3

4.2 发布、版本比对与去旧

消费方状态存储关系库账户状态与准入域钱包与账务域消费方状态存储关系库账户状态与准入域钱包与账务域次序不可反:反序会产出旧内容加新版本,去旧逻辑将误接受alt[本次版本小于键内版本][本次版本不小于键内版本,或键内无版本字段]alt[状态存储未启用][执行原子比对写]事务提交成功后请求发布,携带原因枚举第一步读客户级状态版本第二步读可用额度与宽限截止返回未落地,全链路降级为幂等空操作读键内旧值并与本次版本比对丢弃:不写键、不发事件写键并按封锁位选择存活期档位发布变更事件:旧态、新态、原因、时间戳返回已落地
图 4

4.3 消费方读侧判定

否是是quotastatus否是否

消费方读取状态键

键是否存在且未过期

放行:敞口限于竞态窗口内已放行的在途费用加信用额度

封锁位是否为真

封锁原因

按额度拒绝

按状态拒绝

宽限截止是否非空且当前时刻已不早于它

按额度拒绝:宽限已走完

放行

图 5

4.4 双档存活期与误封上界

真假

决定键的存活期

本次载荷的封锁位

长存活期:封锁态键

短存活期:放行态键

下界:不得短于一个加固周期,否则真实封锁人群的键会在两轮加固之间过期,数据面按缺键放行放走欠费客户,这是安全侧敞口,比误封更严重

上界:由加固周期乘固定倍数得出,远大于一个周期,留足加固余量

恢复发布丢失时,陈旧封锁键至多存活一个长存活期,误封时长有确定上界

短存活期:缺失即放行,误判方向是多服务一会儿

图 6

4.5 解封路径的同步写键

数据面网关状态存储账户状态与准入域钱包与账务域数据面网关状态存储账户状态与准入域钱包与账务域不阻断业务响应:退回长存活期过期与加固轮转兜底alt[写键成功][写键失败或超时]充值入账 / 运营调整 / 状态改回在营 / 宽限 / 退款冲销 / 赠金入账 / 转账两侧 同事务提交同一请求内请求同步发布自开短会话重读提交后事实并派生写键,调用侧设超时上界已落地下一请求即见放行态返回未落地,仅告警
图 7

4.6 加固轮转的两段扫描

有是否无

每轮加固开始,设总预算

第一段:优先人群全量刷新

封锁与欠费标记人群:经营状态非在营,或欠费标记非空

近 state_sweep_recent_hours 新建客户:创建即写键的兜底

赠金近到期与刚过期窗口:被动衰减的补偿

预算是否还有剩余

第二段:按客户标识升序取剩余预算

是否已到表尾

回绕补头段,游标推进到已扫过的末行标识,不从表头重扫

游标推进到本批末行标识

逐行重导出并写键

图 8

4.7 创建即写键

数据面网关账户状态与准入域钱包与账务域注册或建企业入口数据面网关账户状态与准入域钱包与账务域注册或建企业入口加固轮转按近新建客户兜底alt[客户行已提交可见][事务回滚或多次重读仍不可见]注册个人账户或创建企业钱包建客户行与钱包行,余额从零创建点调度发布短退避重读客户行,最多数次写键,新账号出生即被判定静默放弃
图 9

4.8 欠费派生标记的重算与宽限授限

是且尚无标记否且已有标记无变化

任一钱包变更事务末尾

按唯一公式重算可用额度

可用额度是否不大于 0

置欠费标记;策略开启则自动授宽限并受冷却期约束

清标记并清宽限窗

不动标记

同一事务提交,随后按路径选择同步或异步发布

图 10

4.9 欠款生命周期分级与运营杠杆

超过 30 天超过 60 天超过 90 天

欠费持续计时

账龄区间

分级提醒

分级催收

分级冻结

写入风险事件台账(风控与合规域)

运营杠杆:自动授限策略、冷却期、手动宽限

核销走运营调整流水,现金回正后由重算规则自然恢复

图 11

4.10 状态存储不可用时的降级

否是否是

发布或查询路径

状态存储是否启用且可达

写:返回未落地,不抛异常;读:视为键缺失

一切异常仅告警,不改变业务事务成败

兜底:键过期回落放行与加固轮转收敛

键内内容是否为合法对象

覆盖写入并告警:命名空间被本域之外的进程写入

按版本比对决定写入或丢弃

图 12

5. 接口契约

5.1 准入状态查询(/internal,计量链令牌)

方法 路径 语义
GET /internal/customers/{customer_id}/state 派生态唯一出口:返回 state / blocked / block_reason / grace_until / available / cash_balance,并附经营状态与两个旧字段 allowed 与 reason(封锁位取反,原因取 customer_disabled 或 insufficient_available)

5.2 状态发布所依附的写入口(语义归属见对应域)

方法 路径 语义
POST /internal/customers/{customer_id}/deduct-batch 批量扣费,提交后异步发布(详见 D04)
POST /internal/customers/{customer_id}/deduct 单笔扣费兼容入口,同上(详见 D04)
POST /internal/customers/{customer_id}/credit 赠金入账,解封路径同步写键(详见 D04)
PATCH /internal/customers/{customer_id}/status 状态标记,同步写键(详见 D04)
PATCH /api/account/ops/customers/{customer_id}/status 运营面状态标记,同步写键(索引见 D11)
PATCH /api/account/ops/customers/{customer_id}/credit-limit 信用额度配置,抬高额度可解封,同步写键(详见 D04)
POST /api/account/ops/customers/{customer_id}/adjustment 运营调整含坏账核销,同步写键(详见 D04)
PUT /api/account/ops/customers/{customer_id}/grace 宽限设置,同步写键(详见 D04)

6. 关键约束与验收标准

6.1 约束

  1. 状态键单一生产者:命名空间内只有本域写键;写键失败不阻断业务,读侧一律只读。
  2. 判定唯一实现:五态判定只有一处实现,状态键与状态查询端点共用同一产出;调用方不得自算可用额度或自行推断派生态。
  3. 生命周期优先:closed / banned 直判封锁,closed 为终态;账务态只在经营状态为在营时参与。
  4. 版本序即提交序:版本由库层触发器在行锁内取全局序列赋值,应用层只读不写;绕过库层赋值即令去旧失效。
  5. 写侧去旧三分支:新版本小于键内版本丢弃且不发事件;相等允许;键内无版本字段视为 0 允许覆盖(滚动升级兼容)。
  6. 采样次序先版本后内容:派生入口不接受调用方传入可用额度,次序由构造保证,新增调用点无法破坏它。
  7. 读侧规则封闭:封锁位为真即拒(按原因分流);封锁位为假且宽限截止已过按额度拒;键缺失或过期一律放行。消费方不感知版本字段。
  8. 宽限截止仅 grace 态透出:其余四态该字段为空,「窗内放行」只能由读侧时钟判定完成。
  9. 存活期双向夹逼:放行态短存活期(默认 600 秒),封锁态长存活期(默认 7200 秒);后者不低于一个加固周期,上界取周期固定倍数。
  10. 解封路径同步写键:充值、运营调整、状态改回在营、宽限、退款冲销、赠金入账、转账两侧须在同一请求内完成写键;失败或超时仅告警。
  11. 创建即写键:注册与建企业须在创建点调度发布,杜绝新账号因缺键被放行一次消费。
  12. 加固轮转两段且游标不回退到表头:优先人群每周期全量刷新,剩余预算按标识升序推进并回绕;周期 = ⌈客户数 ÷ 每轮预算⌉ × 加固间隔。
  13. 标记是派生量:唯一置位与清除点在钱包变更事务末尾;准入与生命周期出口都不读它。
  14. 降级为放行且敞口有界:状态存储未启用或不可达按缺键处理(放行),敞口 = 在途未落账费用 + 信用额度,由存活期与加固轮转收敛。

6.2 验收标准

# 验收口径
1 经营状态为封禁或注销时,无论可用额度多高,派生态均为对应封锁态且封锁原因为状态
2 可用额度恰为 0 且宽限关闭时判定为欠费态、原因为额度;最小正数时为在营态
3 宽限窗内为宽限态、封锁位假、截止非空;窗走完后同一客户转为欠费态
4 两笔并发扣费后键内 available 与最后提交那笔一致,不存在旧快照覆盖新值
5 伪造比键内版本更小的发布:键与事件通道均无变化
6 与键内版本相等的重复发布:键被重写(续期)且不报错
7 键内无版本字段的存量键:任何新版本都能覆盖
8 键内被外部写入非合法内容:发布照常覆盖并产生安全告警
9 扣费仅命中赠金、现金差额为 0 时客户版本仍被抬高(语句级生效)
10 入账与赠金撞唯一约束时收敛为幂等,不因版本写入变成序列化错误,不返回 5xx
11 充值解封后同一请求返回时封锁位已为假;同步写键失败后键至多存活一个长存活期即回落放行
12 长存活期低于一个加固周期时被验收拒绝(封锁人群会在加固间隙失效)
13 跑满一个轮转周期后全库每个客户的键都被刷新过至少一次;到表尾后不出现头段重复刷新而中段被跳过
14 新注册且无赠金的账号:创建后立即读键为欠费态,无被放行一次消费的窗口
15 宽限窗内客户在加固轮转中被按当下时钟重新求值:未到期续宽限、已到期固化为欠费态
16 赠金自然过期(无任何钱包事务)后,一个加固周期内键内 available 与派生态被重新导出
17 状态存储连接配置为空时,扣费与充值接口行为不变(发布为幂等空操作),无异常抛出
18 状态查询端点与状态键读出的 state / blocked / available 完全一致(同源判定)
19 消费方仅凭封锁位、宽限截止与键存在性即可完成判定,忽略版本字段不影响结果

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

  1. 载荷的显式契约版本化:现以「新增字段向后兼容 + 消费方忽略」替代协议版本号;若出现破坏性变更,需要引入键名或字段层面的版本协商。
  2. 准入收紧档(可用额度低于单笔均值若干倍即拒)与单笔上限的默认值:现默认关闭,仅在敞口需收紧时启用。
  3. 高敏租户收紧开关:默认放行语义写死,收紧为断路的口径与生效范围待定。
  4. 每轮预算与加固间隔的容量模型:客户量级增长后两者的相对关系需重新标定,以维持「优先人群间隔不大于一个存活期」。
  5. 变更事件的订阅方清单与消费方式(谁订阅、如何幂等去重)由各消费方自定,尚未收敛为统一约定。
  6. 账龄分级的事件落库形态与提醒触达的唯一化口径(避免同一账龄段重复提醒)。