新增 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。
92 lines
7.8 KiB
Markdown
92 lines
7.8 KiB
Markdown
## Context
|
||
|
||
套餐商品 `tb_package.real_data_mb` 是可变配置值(后台可编辑)。套餐使用记录 `tb_package_usage` 以 `data_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_mb`、`current_month_usage_mb`)。
|
||
|
||
### 有效套餐集合与主套餐
|
||
|
||
- 按资产(`iot_card_id` 或 `device_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_id`、`threshold_percent`(大于等于 1、小于等于 100,允许小数)、`enabled`、`remark`(最多 500 字符)。套餐必须存在且 `real_data_mb > 0`(配置合法性校验);同套餐已有规则返回“套餐已存在真流量预警规则”。
|
||
- `PUT /package-traffic-alert-rules/:id`:允许修改阈值、启停、备注;不修改已产生预警快照。停用后扫描不建新预警;启用或降低阈值后不主动回填,仅由下一次扫描按当前有效套餐判断。
|
||
- `GET /package-traffic-alert-rules` 返回套餐、真流量总额度、阈值、启用状态、备注和更新时间。所有成功写操作记录操作者、前后值和时间。
|
||
|
||
### 扫描与幂等
|
||
|
||
- Worker 只读取当前有效套餐使用记录(见上集合),按资产聚合真已用量与真总量快照。
|
||
- 调度形态照抄既有 `package_expiry_reminder`:`asynq.Scheduler` 以 `CRON_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。
|