feat: 轮询系统重构(分片队列 + 停复机统一 + Handler 拆分)
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m46s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m46s
【核心变更】
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>
This commit is contained in:
139
.opencode/skills/comment-standards/SKILL.md
Normal file
139
.opencode/skills/comment-standards/SKILL.md
Normal file
@@ -0,0 +1,139 @@
|
||||
---
|
||||
name: comment-standards
|
||||
description: Go 注释规范。编写 Go 代码注释、文档注释时使用。包含包注释、结构体注释、接口注释、函数注释、内联注释的完整规范与示例。
|
||||
---
|
||||
|
||||
# Go 注释规范
|
||||
|
||||
**基本原则**:
|
||||
- **所有注释使用中文**
|
||||
- **导出符号必须有文档注释**(包、函数、方法、类型、接口、常量、变量)
|
||||
- **复杂逻辑必须有实现注释**(解释"为什么",而不是"做了什么")
|
||||
- **禁止废话注释**(不要用注释复述代码本身)
|
||||
- **修改代码时必须同步更新注释**
|
||||
|
||||
---
|
||||
|
||||
## 包注释
|
||||
|
||||
每个包的入口文件(通常是主文件或 `doc.go`)必须有包注释:
|
||||
|
||||
```go
|
||||
// Package account 提供账号管理的业务逻辑服务
|
||||
// 包含账号创建、修改、删除、权限分配等功能
|
||||
package account
|
||||
```
|
||||
|
||||
## 结构体注释
|
||||
|
||||
所有导出结构体必须有文档注释,说明该结构体代表什么:
|
||||
|
||||
```go
|
||||
// Service 账号业务服务
|
||||
// 负责账号的 CRUD、角色分配、密码管理等业务逻辑
|
||||
type Service struct {
|
||||
store *Store
|
||||
auditService AuditServiceInterface
|
||||
}
|
||||
```
|
||||
|
||||
## 接口注释
|
||||
|
||||
导出接口必须注释接口用途,每个方法必须说明契约:
|
||||
|
||||
```go
|
||||
// PermissionChecker 权限检查器接口
|
||||
// 用于查询用户的权限列表
|
||||
type PermissionChecker interface {
|
||||
// CheckPermission 检查用户是否拥有指定权限
|
||||
// userID: 用户ID
|
||||
// permCode: 权限编码(格式: module:action)
|
||||
// platform: 端口类型 (all/web/h5)
|
||||
CheckPermission(ctx context.Context, userID uint, permCode string, platform string) (bool, error)
|
||||
}
|
||||
```
|
||||
|
||||
## 函数和方法注释
|
||||
|
||||
**导出函数/方法**必须以函数名开头,说明功能:
|
||||
|
||||
```go
|
||||
// Create 创建账号
|
||||
// POST /api/admin/accounts
|
||||
func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
||||
```
|
||||
|
||||
**复杂方法**(超过 30 行或包含复杂业务逻辑)必须额外说明实现思路:
|
||||
|
||||
```go
|
||||
// ActivateByRealname 首次实名激活套餐
|
||||
// 当用户完成实名认证后,自动激活处于"囤货待实名"状态的套餐:
|
||||
// 1. 查找该卡所有 status=3(待实名激活)的套餐
|
||||
// 2. 按创建时间排序,第一个主套餐立即激活(status=1)
|
||||
// 3. 其余主套餐进入排队状态(status=4)
|
||||
// 4. 加油包如果绑定了已激活的主套餐则一并激活
|
||||
func (s *UsageService) ActivateByRealname(ctx context.Context, cardID uint) error {
|
||||
```
|
||||
|
||||
**未导出函数/方法**:
|
||||
- 简单逻辑(< 15 行):可以不加注释
|
||||
- 复杂逻辑(≥ 15 行)或非显而易见的算法:必须加注释
|
||||
|
||||
```go
|
||||
// buildPermissionTree 递归构建权限树
|
||||
// 采用 map 索引 + 单次遍历算法,时间复杂度 O(n)
|
||||
func (s *Service) buildPermissionTree(permissions []*model.Permission) []*dto.PermissionTreeNode {
|
||||
```
|
||||
|
||||
## 常量和枚举注释
|
||||
|
||||
分组常量必须有组注释,每个值必须有行内注释:
|
||||
|
||||
```go
|
||||
// 用户类型常量
|
||||
const (
|
||||
UserTypeSuperAdmin = 1 // 超级管理员
|
||||
UserTypePlatform = 2 // 平台用户
|
||||
UserTypeAgent = 3 // 代理账号
|
||||
UserTypeEnterprise = 4 // 企业账号
|
||||
)
|
||||
```
|
||||
|
||||
## 内联注释规范
|
||||
|
||||
**必须添加内联注释的场景**:
|
||||
|
||||
| 场景 | 要求 |
|
||||
|------|------|
|
||||
| 复杂条件判断 | 解释判断的业务含义 |
|
||||
| 多步骤业务流程 | 用编号注释标明每一步 |
|
||||
| 非显而易见的设计决策 | 解释"为什么这样做"而不是"做了什么" |
|
||||
| 缓存/事务/并发处理 | 说明策略和原因 |
|
||||
| 临时方案/兼容逻辑 | 标注 TODO 或说明背景 |
|
||||
|
||||
**✅ 好的内联注释(解释为什么)**:
|
||||
|
||||
```go
|
||||
// 使用 Redis 分布式锁防止并发重复创建,锁超时 10 秒
|
||||
if !s.acquireLock(ctx, lockKey, 10*time.Second) {
|
||||
return errors.New(errors.CodeTooManyRequests, "操作过于频繁,请稍后重试")
|
||||
}
|
||||
|
||||
// 先冻结佣金再扣款,保证资金安全(失败时佣金自动解冻)
|
||||
if err := s.freezeCommission(ctx, tx, orderID); err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
**❌ 废话注释(禁止)**:
|
||||
|
||||
```go
|
||||
// 获取用户ID ← 禁止:代码本身已经很清楚
|
||||
userID := middleware.GetUserIDFromContext(ctx)
|
||||
|
||||
// 创建账号 ← 禁止:变量名已说明意图
|
||||
account := &model.Account{}
|
||||
|
||||
// 返回错误 ← 禁止:return err 不需要注释
|
||||
return err
|
||||
```
|
||||
Reference in New Issue
Block a user