Files
junhong_cmp_fiber/openspec/changes/archive/2026-09-16-add-package-real-usage-alerts/design.md
break d5bcda94fe feat(套餐真流量预警): AUG26-004 真流量预警规则、达量扫描通知与导出
新增 000228 迁移:规则表 tb_package_traffic_alert_rule(每套餐商品至多一条,无软删除,package_id
非部分唯一约束)、达量预警快照表 tb_package_traffic_alert(以主套餐使用记录 + 阈值快照为唯一键,
触发时冻结用量、额度、比例、阈值、到期时间、归属与资产快照),并为 tb_package_usage 新增扫描
范围部分索引 idx_package_usage_alert_scope;down 在预警表存在数据时阻断回滚。

新增规则维护接口 GET/POST/PUT /api/admin/package-traffic-alert-rules(仅超级管理员与平台账号):
创建校验套餐存在且真流量额度大于零,阈值为 1%~100% 的两位小数;修改只影响后续扫描,不回填也
不改写既有预警快照;全部写操作记录操作者、前后值与时间。

新增每日 06:00(Asia/Shanghai)扫描任务 package:traffic:alert:scan,与套餐临期扫描共用 data_cleanup
队列:按资产汇总当前有效套餐的真流量,分子取使用记录真已用量、分母取使用记录真总量快照,命中
主套餐规则阈值时在同一事务创建预警与可靠通知事件;重复执行以唯一冲突视为已处理,不重复投递,
不建停机锁、不调用运营商。

新增预警列表、详情与异步导出 GET /api/admin/package-traffic-alerts、GET /api/admin/package-traffic-alerts/:id、
POST /api/admin/package-traffic-alerts/export,列表与详情一律读冻结快照;新增通知类型
package.traffic.alert 与受控目标 package_traffic_alert_detail,目标解析仅对超级管理员与平台账号
返回可跳转,越权与不存在统一按资源不可见处理。

同步 OpenAPI(cmd/gendocs、cmd/api/docs.go、pkg/openapi/handlers.go)、审计动作与资源注册、上下文
健康检查证据;归档变更并同步 package-traffic-alert 主 Spec。
2026-09-16 17:05:55 +08:00

7.8 KiB
Raw Blame History

Context

套餐商品 tb_package.real_data_mb 是可变配置值(后台可编辑)。套餐使用记录 tb_package_usagedata_usage_mb(真已用)与 data_limit_mb(真总量快照,购买时写入、全仓无更新路径)承载真实用量;既有 H5、后台资产详情、套餐历史、客户视图、代理开放接口与卡导出一律以使用记录快照展示真额度。现有通知以事件投递并按接收人隔离后台通知无类型白名单但新增通知类型必须先注册模板定义否则渲染失败、通知写不进库。资产数据范围对超级管理员与平台用户为空操作。预警必须是套餐级观测不能复用运营商通道阈值停机锁。

Goals / Non-Goals

Goals:

  • 建立套餐真流量规则、预警事实、通知与可追溯导出。

Non-Goals:

  • 不建停机锁、不调用运营商、不做停复机、不读取通道计费周期与通道累计流量。

Decisions

额度与用量口径

  • 分子取 tb_package_usage.data_usage_mb(真已用),由卡网关读数增量经既有扣减链路写入。
  • 分母取 tb_package_usage.data_limit_mb(真总量快照)。该字段与 H5、后台资产详情、套餐历史、客户视图、代理开放接口与卡导出的既有展示一致使用记录快照不可变历史可复现。
  • 商品 real_data_mb 仅作为规则启用时的配置合法性校验(必须大于零),不作为汇总分母。
  • 禁止读取虚流量(virtual_total_mb_snapshot / enable_virtual_data_snapshot)、展示量(display_gain_ratio_snapshot 放大结果)、卡级累计(tb_iot_card.data_usage_mb)与运营商通道累计值(last_gateway_reading_mbcurrent_month_usage_mb)。

有效套餐集合与主套餐

  • 按资产(iot_card_iddevice_id,二者互斥非零)汇总当前有效套餐使用记录,含加油包:status IN (1,2) AND refund_id IS NULL
  • 排除 0-待生效、3-已过期、4-已失效。
  • 判定只依据 status,不得仅用 expires_at 判断过期(过期与失效由异步任务改状态,expires_at 在生效中状态下可能已到期)。
  • 主套餐(阈值来源)为 master_usage_id IS NULL;多条时按 priority ASC, activated_at ASC, id ASC 取第一条,与既有当前主套餐查询一致。
  • 主套餐无有效规则、总额度不大于零或比例未达阈值时跳过。

汇总、去重与补建

  • 唯一键为 (package_usage_id, threshold_percent_snapshot)
  • 降阈值后同一使用记录产生新阈值快照的预警属预期补建,不是重复;升阈值或停用不修改既有预警快照。
  • 唯一冲突视为已处理,不重复投递通知。
  • 实际消耗流量的使用记录所关联资产是权威归属;插拔卡同时展示卡与当前关联设备,不汇总多张卡。

规则维护

  • POST /package-traffic-alert-rules:仅超级管理员、平台用户;请求 package_idthreshold_percent(大于等于 1、小于等于 100允许小数enabledremark(最多 500 字符)。套餐必须存在且 real_data_mb > 0(配置合法性校验);同套餐已有规则返回“套餐已存在真流量预警规则”。
  • PUT /package-traffic-alert-rules/:id:允许修改阈值、启停、备注;不修改已产生预警快照。停用后扫描不建新预警;启用或降低阈值后不主动回填,仅由下一次扫描按当前有效套餐判断。
  • GET /package-traffic-alert-rules 返回套餐、真流量总额度、阈值、启用状态、备注和更新时间。所有成功写操作记录操作者、前后值和时间。

扫描与幂等

  • Worker 只读取当前有效套餐使用记录(见上集合),按资产聚合真已用量与真总量快照。
  • 调度形态照抄既有 package_expiry_reminderasynq.SchedulerCRON_TZ=Asia/Shanghai 注册,调度器仅在单例 Worker 角色创建。既有临期扫描不使用 asynq.Unique,也无启动补偿;本项同样不新增手工触发扫描入口。
  • 幂等由事实唯一键((package_usage_id, threshold_percent_snapshot))与 Outbox 幂等追加(OnConflict(event_id) DoNothing)保证。
  • 事实与事件在同一事务写入:预警、通知事件与审计同事务提交;唯一冲突视为已处理,不重复投递通知。
  • 通知失败进入既有可靠投递恢复,不能删除预警或重新计算快照。

通知

  • 类别沿用 expiry,不新增类别;新增通知类型 package.traffic.alert
  • 必须注册注册表 Definition,否则渲染失败、通知写不进库。
  • 接收人新建「仅业务员」解析路径:business_owner_account_id 指向启用且 user_type 为平台的账号;不得复用返回「店铺 agent 账号 业务员」的既有 resolver。
  • 无有效业务员时只保存预警,不补发给未来业务员。
  • 新增受控 ref_type 并在通知目标定义中注册,指向预警详情,不返回 URL。
  • 幂等键内嵌使用记录与阈值快照。

列表、详情与导出

  • GET /package-traffic-alerts:仅超级管理员、平台用户,先应用既有资产数据范围;支持套餐、店铺、业务员、资产/卡标识、阈值、触发时间和通知投递状态筛选、分页。返回冻结快照与通知结果,当前归属变化不得改写预警事实。
  • GET /package-traffic-alerts/:id:同一数据范围校验后返回完整预警快照和通知投递历史;越权与不存在统一按既有资源不可见处理。
  • POST /package-traffic-alerts/export:仅超级管理员、平台用户。复用既有异步导出任务机制(新增导出场景并复用导出任务创建服务),场景需在全部五处注册点落地(场景常量、DataSource、注册中心实例化、场景白名单、导出 DTO oneof 两处);创建时冻结操作者、筛选、时间范围与可见资产范围。既有通用导出入口不做场景级角色校验,故本项以受控入口暴露该场景。
  • 数据范围使用导出侧的范围过滤(空范围拒绝),不得使用请求上下文版过滤(空范围语义相反)。
  • 导出列:资产类型、资产标识、对应标识符、卡标识、设备类型、设备型号、套餐名称、真流量已用量、真流量额度、比例、阈值快照、到期时间、剩余天数、触发时间、店铺、业务员、用户组、通知投递结果;不含运营商通道列。
  • 归属口径按 PRD §2.16 写死:导出中套餐、用量、总量、阈值与到期时间使用触发快照;店铺、业务员、用户组按导出执行时当前归属补充。预警行仍冻结店铺与业务员快照,供列表、详情与追溯;两者口径不同且不得混用。用户组不得写入店铺表,按既有实时推导。

数据范围与权限

  • 列表、详情与导出仅超级管理员与平台用户。
  • 先应用既有资产数据范围;当前对超管或平台无实际过滤,该要求保留为冻结语义与未来放开的前置。
  • 越权与不存在统一按资源不可见处理。
  • 导出范围在创建时冻结,执行期间归属变化不得扩大范围。

与通道阈值严格分离

  • 不建停机锁、不调用运营商、不做停复机、不读取通道计费周期与通道累计流量,不触碰既有停复机服务与网关停机调用。

Risks / Trade-offs

  • 流量数据延迟 → 下次扫描补建,不回写已冻结预警。
  • 多卡设备 → 以使用记录资产归属聚合,不从当前设备反推流量。
  • 扫描并发 → 唯一索引处理同一命中重复创建。
  • 分母取快照 → 商品改价后重跑,比例与预警不因商品当前值变化;代价是商品价与预警分母可能不同,属预期。

Migration Plan

新增成对迁移和索引;隔离环境验证规则启停/降阈值、有效套餐汇总、去重、无业务员、权限导出和 up/down/up。