Files
junhong_cmp_fiber/.opencode/skills/openspec-api-contract/SKILL.md
huang 434a8b0349
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m46s
feat: 轮询系统重构(分片队列 + 停复机统一 + Handler 拆分)
【核心变更】

1. 停复机逻辑统一(StopResumeService)
   - 新增 EvaluateAndAct 统一入口,封装三条件停复机判断
   - 停机条件:无套餐(no_package) / 流量耗尽(traffic_exhausted) / 未实名(not_realname)
   - 复机条件:stop_reason 合规 + 有套餐且未耗尽 + 已实名或行业卡
   - 修复设备套餐 Bug:hasValidPackage 按 device_id 查套餐,而非仅 iot_card_id
   - 设备维度停复机加幂等锁(Redis SetNX,TTL 30s),防止多卡并发重复调 Gateway

2. Redis 分片队列(PollingQueueManager)
   - 新建 queue_manager.go,封装所有轮询 Redis 操作
   - 16 分片 Sorted Set,Key 格式:polling:shard:{shardID}:queue:{taskType}
   - Lua 脚本原子出队(ZRANGEBYSCORE + 分批 ZREM),消除竞态窗口
   - 新增背压检测:队列深度超 50 万时 Scheduler 跳过该分片
   - RemoveFromAllQueues 覆盖 4 种任务类型(含 protect)

3. Handler 拆分(polling_handler.go 1360行 → 5个专注文件)
   - polling_base.go:共享基类(并发控制/卡缓存/重入队)
   - polling_realname_handler.go:实名采集,实名 0→1 时立即触发复机
   - polling_carddata_handler.go:流量采集,保留跨月边界检测逻辑
   - polling_package_handler.go:套餐采集,委托 EvaluateAndAct 决策
   - polling_protect_handler.go:保护期一致性检查,保护期内强制修正

4. 配置管理(PollingConfigManager)
   - 新建 config_manager.go,从 scheduler.go 提取配置职责
   - 内存缓存 + 5 分钟定时刷新,刷新失败保留原缓存
   - 修复 getCardCondition:停机卡返回 suspended,不再错配 activated 配置

5. 渐进式初始化(CardInitializer)
   - 新建 initializer.go,分批加载(每批 10 万),批次间 sleep 500ms
   - 过滤 enable_polling=false 的卡,初始化完成前 Scheduler 不出队

6. 卡生命周期服务(PollingLifecycleService)
   - 新建 lifecycle_service.go,替代已删除的 callbacks.go 和 api_callback.go
   - OnCardCreated/OnCardEnabled/OnCardStatusChanged 入队前检查 enable_polling

7. Scheduler 精简(1000+行 → 227行)
   - 保留纯调度循环:scheduleLoop + processShardSchedule + enqueueBatch
   - 保留每 10 秒触发套餐过期检测和流量重置
   - 移除所有 DB 操作、配置加载、卡初始化逻辑

8. 轮询管控 API(enable_polling)
   - 新增 PUT /api/admin/assets/:id/polling-status 接口
   - 支持对设备/卡维度开关轮询,关闭后从所有分片队列移除

9. 数据库迁移
   - 000103:tb_device 新增 enable_polling 字段(boolean, NOT NULL, DEFAULT true)
   - 000104:新增 suspended 轮询配置,为 activated 配置补全 protect_check_interval

【文件统计】
- 新增:19 个文件(handler × 5、polling 组件 × 4、迁移 × 3 等)
- 修改:20 个文件(bootstrap 注入、store 接口、monitoring 适配分片等)
- 删除:3 个文件(polling_handler.go、callbacks.go、api_callback.go)

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-07 12:27:04 +08:00

3.5 KiB
Raw Blame History

name, description
name description
openspec-api-contract OpenSpec API 契约规范。创建涉及接口的 OpenSpec 提案时使用。探索阶段提供业务与契约引导清单,提案文档要求 API 契约设计、错误码与完成标准等必填章节。

OpenSpec API 契约规范

适用场景:创建涉及 API/接口的 OpenSpec 提案时,探索和提案两个阶段均须遵守本规范。


一、探索阶段引导清单

openspec-explore 阶段,当内容涉及接口时,讨论必须覆盖以下所有维度。

业务与契约确认

输入与输出

  • 请求方是谁用户类型SuperAdmin/Platform/Agent/Enterprise/Personal
  • 输入参数:必填/选填字段、格式约束、参数来源(路径/查询/Body
  • 输出结构:哪些字段必须返回?是否需要分页?

业务规则

  • 核心业务规则与边界条件?
  • 是否涉及状态流转?状态机的完整定义?
  • 依赖外部服务时,外部异常的降级行为?

权限与资源所有权

  • 哪些用户类型可以访问?
  • 是否涉及跨用户/跨店铺/跨企业的资源访问?(需三层越权防护)
  • 资源所有权校验方式:CanManageShop / CanManageEnterprise / 自有资源?

幂等性

  • 操作类型:查询(天然幂等)/ 创建 / 更新 / 删除?
  • 写操作幂等策略:状态条件更新 / Redis 业务键防重 + 分布式锁 / 乐观锁version
  • 异步任务是否需要任务锁?

数据模型变更

  • 是否需要新建表、修改现有表或数据回填?
  • 迁移策略:上线顺序、兼容旧数据的方式?
  • 是否影响 GORM Callback 自动数据权限过滤?

错误码与异常语义

  • 预期错误场景及对应错误码?
  • 错误响应是否泄露敏感信息?(参数校验失败统一返回 CodeInvalidParam

二、提案文档必填章节

openspec-propose 生成的提案(proposal.md / design.md)中,涉及接口时以下内容不可缺失

API 契约设计

接口定义

内容
Endpoint METHOD /api/{scope}/{resource}[/:id]
请求参数 字段名、类型、必填/选填、说明
响应结构 data 字段的完整结构定义
鉴权要求 允许的用户类型
资源所有权 所有权校验方式

列表接口额外要求

  • 分页:page + page_size(默认 20最大 100
  • 排序:默认排序字段与方向
  • 过滤:支持的过滤条件列表

错误码清单

场景 错误码 说明
参数校验失败 CodeInvalidParam 统一返回,不泄露细节
资源不存在/越权 CodeForbidden 不区分两者,防止信息泄露
(业务错误场景... (对应错误码) (说明)

完成标准

最小验证步骤(按顺序列出可操作的验证步骤)

  1. (例:调用创建接口,验证返回 code=0
  2. (例:查询接口确认数据存在且字段正确)
  3. PostgreSQL MCP 查询确认数据库记录符合预期)

影响范围说明

  • 新增/修改的表:
  • 影响的现有接口:
  • 影响的权限与数据过滤范围:

约束(必须遵守)

  • 优先复用现有架构与库:不引入新依赖,错误码优先复用已有定义
  • 不做顺手重构:提案范围严格限定在目标功能;发现可优化点,记录到 backlog 但不执行
  • 数据库设计:禁止外键约束,禁止 GORM 关联标签,关联通过 ID 字段手动维护