Files
junhong_cmp_fiber/openspec/changes/add-carrier-channel-traffic-thresholds/tasks.md
break 33826c3443
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 13m13s
feat(套餐真流量预警): AUG26-004 真流量预警规则、达量扫描通知与导出
新增 000226 迁移:规则表 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:07:34 +08:00

8.5 KiB
Raw Blame History

1. 迁移与模型

  • 1.1 成对迁移:tb_carrier 扩列 traffic_threshold_enabled(默认 falsetraffic_threshold_value(正数 numerictraffic_threshold_unitMB/GB新增周期阈值停机锁表(carrier_id, card_id, period_start) 唯一键软删感知、任务状态组stop/resumepending/submitted/confirmed/failed/unknown、提交认领字段stop_submitted_at/resume_submitted_at)、anomaly_flagfailure_reason
  • 1.2 锁表模型与 Store含唯一键冲突 23505 识别、条件更新认领方法)。

2. 阈值配置与权限

  • 2.1 carrier 创建/更新 DTO 增阈值字段与校验(正数、单位 MB/GB、周期复用 data_reset_day)。
  • 2.2 字段级平台守卫Create/Update 仅 SuperAdmin/Platform 可写阈值字段非平台提交即拒绝List/Get 对非平台账号不返回阈值字段;不改 carrier 其余字段既有可见性。
  • 2.3 阈值新增、修改、启用、停用并入既有 carrier 更新审计前后值快照;同步 cmd/gendocs 文档。

3. 达量判定

  • 3.1 period_start 计算(显式 Asia/ShanghairesetDay 取卡 carrier 的 data_reset_day)与 GB→MB 换算常量1 GB = 1024 MB
  • 3.2 ApplyTrafficObservation 事务内达量判定:ReadingAccepted == true 且 carrier 阈值启用时,以 last_gateway_reading_mb 换算比较;达量写锁 + 停机 Outbox 事件同事务。
  • 3.3 锁唯一键 23505 单独捕获为「该周期已处理」跳过,与流量基线 CAS 冲突分离。

4. 可靠停复机消费者

  • 4.1 停机消费者:提交认领(stop_submitted_at IS NULL 条件更新);卡已 offline 跳过 Gateway 直接确认;否则复用 stopCardWithRetry;成功后写 network_status=offlinestop_reason=channel_thresholdstopped_at;失败/未知保留锁与任务状态unknown 的 Integration Log 必带 RecoveryStrategy。
  • 4.2 复机消费者:结构同停机(resume_submitted_at 认领、复用 resumeCardWithRetry、成功后写 online/清停因/resumed_at新增 2 个 Outbox 事件类型与 worker 消费者注册、装配。

5. 周期处理与恢复 cron

  • 5.1 周期处理 cron@every 1m + asynq.Unique(10m) + 无 payload扫描 period_start 过期持锁锁行(按锁行自身 carrier 的 data_reset_day 判断),条件更新认领解锁,逐卡评估(有效主套餐 + 流量未耗尽 + 实名 OK + 非风险 extend + 无其他停因),全满足写复机事件,否则只解锁。
  • 5.2 恢复扫描 cron同形态扫描未决子任务domain.UnresolvedTaskStatuses() = {submitted, unknown, failed},见 8.1)的锁,只查询网关状态回填、绝不重复发起停复机;确认成功补写卡状态;自提交起超 30 分钟(常量)仍不可查 → anomaly_flag + 安全失败原因转人工,不自动删除锁。
  • 5.3 worker 注册两个 cron 与装配;周期处理与恢复 Handler 审计上下文固定 ActorKind=AuditActorScheduledJobSource=AuditSourceScheduler

6. 停因与复机拒绝

  • 6.1 新增 StopReasonChannelThreshold = "channel_threshold" 常量;确认不纳入 isPollingStopReasonisDeviceScopeReason
  • 6.2 持锁拒绝覆盖四个入口(resumeSingleCardManualStartCardForceStartCardStartMachineSeparatedCard):拒绝 + 审计 denied + 锁保留。

7. 验证

  • 7.1 隔离库验证(验证口径与限制见 8.7:停复机执行以 stub 端点驱动,真实网关成功路径未端到端验证):平台配置阈值(含 GB 换算)成功 + 审计前后值,代理/企业写被拒且读不到字段;达量触发停机、同周期重复判定唯一冲突不重复锁/不重复停机;持锁期四入口复机拒绝且锁保留;停机 unknown 保留 + RecoveryStrategy + 恢复扫描确认补写卡状态、超期 anomaly 转人工新周期解锁、条件满足才复机、其他停因stop_reason 非空非本停因或风险 extend只解锁停用后新卡不再锁、已持锁卡新周期照常解锁复机换运营商旧锁保留、新周期按新 carrier 重置日;第 12 项套餐预警与既有 3 种停因停复机回归不变;迁移 up/down/up。
  • 7.2 运行 gofmt -wgo build ./cmd/api ./cmd/workergo run cmd/gendocs/main.goopenspec validate add-carrier-channel-traffic-thresholds --strictopenspec doctor --json;自动化测试按项目决策为 N/A。

8. 审查修复

  • 8.1B1停机/复机调用明确失败或结果未知的子任务不退出恢复扫描:扫描谓词与超期判定改用未决集合 {submitted, unknown, failed}markTaskConfirmed 的 expected 改为未决集合confirmed 仍只写一次、部分索引谓词同步为未决集合000227已确认生产库未应用
  • 8.2B2锁行引用的运营商缺失或 data_reset_day 非法时给出出路:周期处理按跨期语义认领解锁并同事务标记 anomaly_flag=1 + 安全原因 + Warn 日志转人工,不写复机事件、不调运营商、不静默跳过;ActiveLock 维持配置缺失时不放开复机的 fail-closed 语义。
  • 8.3R1恢复 stopCardWithRetry/resumeCardWithRetry 在 Integration Log 写入失败分支的「未完成」审计,保持既有审计覆盖与返回语义不变。
  • 8.4R2按 ENG-AUDIT-001 显式登记锁行状态迁移(unlockedanomaly_flag=1)的审计决定(不新建独立审计动作 + 承载方式),并在 design.md 补「审计决定」小节。
  • 8.5R4specs/package-lifecycle/spec.md MODIFIED delta 建模「持通道阈值锁时拒绝复机」对既有套餐生命周期自动复机路径的行为变更,并更新 proposal 的 Modified Capabilities。
  • 8.6 修正 design.md 与实现不符的表述(失败/未知与不可判定 carrier 的收敛路径、四入口拒绝顺序与返回、认领谓词)。
  • 8.7 回归验证口径已按实际验证方式写明B1unknown/failed 扫描与收敛、超期 anomaly、B2carrier 缺失/重置日非法的解锁与异常)与 B3连接池自锁用生产等价句柄(Service.s.db = 根连接池)+ 已提交 fixture 建删实测——这是唯一能暴露连接池自锁的写法(回滚事务式写法会让 txs.db 落到同一连接而掩蔽该缺陷R1审计写入用回滚事务实测。验证范围与限制:本轮驱动的停复机「运营商侧」输入是本机 stub Gateway 端点B2 段更是只注入会报错的空执行端口,用于证明不可判定分支不外呼),验证的是本 Change 的达量判定、锁状态机、扫描谓词与窗口逻辑;恢复扫描段用的是真实 StopResumeService + 真实 CardCommander 适配器(真实 Integration Log、真实卡状态回写与网关状态映射仅 HTTP 端点被替换。真实测试环境网关的成功路径未端到端验证:测试环境网关对 stop_card 在本轮时间窗2026-09-16 16:0417:04 CST前 60 分钟的响应为 failed=2913 / success=14(几乎全不可用),无法可靠构造「运营商已停机/已复机」的真实成功回执,因此 network_status/stop_reason 回写与 Integration Log 终态只在 stub 端点回执下验证过。共享环境干扰:验证期间未停用共享测试环境已部署 worker 的 cycle/recovery cronasynq.Unique(10m) 是共享 Redis 键),存在并发处理同一批 fixture 行的可能;以亚秒级耗时(ProcessDueLocks 839ms/66ms、RecoverSubmitted 1.438s/780ms与运行时间戳排除干扰并在末尾连续清理两遍后核对锁表/事件/carrier/fixture 卡/审计/集成日志全 0。证据留存harness 原始 stdout 未入库(仅与会话记录同在仓外,临时程序按交付前清理要求已删除);如需第三方复核,后续可在仓外保存完整 stdout或经确认后决定是否作为 Change 证据文件入库。静态检查gofmt/go build/go vet/openspec validate --strict/context-health 全通过。
  • 8.8B3修复连接池自锁新增事务句柄版 markAnomalyInTxmarkAnomaly 以自身连接池委托同一实现),processUnresolvableLock 在事务闭包内用 tx 标记异常,使解锁与 anomaly 同事务提交(否则解锁已持行锁、另一条连接上的 anomaly 更新会等待该行锁直到语句超时,表现为不可判定锁行停在 locked 且周期处理 cron 每分钟空转);并完成同类自锁全面排查(结论:本 Change 仅此一处)。