Files
junhong_cmp_fiber/internal/infrastructure/gatewaylimit/runtime.go
邹进 af8249158d
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m26s
feat: 停复机规则演练与终态自动关闭轮询
统一停复机规则转正的第一次发布:总开关仍关闭,现有停复机行为不变。

- 新增停复机规则演练:GET /stop-resume-rule/dry-run 按新规则对开机与停机卡只判断不执行,返回运营商账户 × 停机原因 × 判断结果汇总与分页明细,可假设通道实名管控配置;POST /stop-resume-rule/dry-run/export 导出明细(场景 stop_resume_rule_dry_run);仅超级管理员与平台账号
- 风险停机、已销户关闭轮询不再限于独立卡,只关该卡自身轮询,不级联设备
- 错误分类规则增加 auto_disable_polling,仅终态类别可勾选;停复机(含设备停复机)命中该规则时不重试、关闭该卡轮询、回写网关扩展状态并写审计 iot_card.terminal_disable_polling
- 迁移 000250 加列与约束,并为"已销户""仅支持风险复机"置位
2026-10-25 17:52:40 +09:00

84 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package gatewaylimit
import (
"context"
"crypto/sha1"
"encoding/hex"
"time"
"github.com/hibiken/asynq"
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/systemconfig"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
)
// Runtime 汇总一个进程内的网关限流组件,API 与 Worker 各自创建一份,限流窗口经 Redis 跨进程共享。
type Runtime struct {
Settings *SettingsProvider
Groups *GroupProvider
Classifier *Classifier
Limiter *Limiter
Guard *Guard
Recorder *StopResumeRecorder
// TerminalPolling 停复机命中"自动关闭轮询"终态规则时关闭该卡轮询。
TerminalPolling *TerminalPollingDisabler
redisClient *redis.Client
logger *zap.Logger
}
// NewRuntime 创建网关限流运行组件;配置读取使用独立注册表,与后台系统配置共享同一份数据库与缓存数据。
func NewRuntime(db *gorm.DB, redisClient *redis.Client, logger *zap.Logger) *Runtime {
if logger == nil {
logger = zap.NewNop()
}
registry := systemconfig.NewRegistry()
RegisterConfigDefinitions(registry, logger)
var cache systemconfig.Cache
if redisClient != nil {
cache = systemconfig.NewRedisCache(redisClient)
}
reader := systemconfig.NewReader(db, registry, cache, systemconfig.NewLogAlertSink(logger))
settings := NewSettingsProvider(reader, logger)
groups := NewGroupProvider(postgres.NewCarrierRateLimitGroupStore(db), logger)
classifier := NewClassifier(postgres.NewGatewayErrorClassificationStore(db), logger)
limiter := NewLimiter(redisClient, groups, settings)
guard := NewGuard(limiter, NewCarrierResolver(db, redisClient), classifier, redisClient, logger)
recorder := NewStopResumeRecorder(postgres.NewCardStopResumeRecordStore(db), classifier, logger)
terminal := NewTerminalPollingDisabler(db, classifier, logger)
return &Runtime{Settings: settings, Groups: groups, Classifier: classifier, Limiter: limiter, Guard: guard, Recorder: recorder,
TerminalPolling: terminal, redisClient: redisClient, logger: logger}
}
// TrackDeferral 记录 Asynq 任务因限流被推迟;同一任务首次被限流起累计推迟超过阈值时写一次错误告警,任务继续重试。
// 任务以"类型 + 载荷摘要"标识,记录 1 天后自动过期。
func (r *Runtime) TrackDeferral(task *asynq.Task) {
if r == nil || r.redisClient == nil || task == nil {
return
}
ctx := context.Background()
sum := sha1.Sum(task.Payload())
taskKey := task.Type() + ":" + hex.EncodeToString(sum[:])
key := constants.RedisRateLimitDeferStartKey(taskKey)
now := time.Now()
if ok, err := r.redisClient.SetNX(ctx, key, now.Unix(), 24*time.Hour).Result(); err != nil || ok {
return
}
startedUnix, err := r.redisClient.Get(ctx, key).Int64()
if err != nil {
return
}
deferred := now.Sub(time.Unix(startedUnix, 0))
if deferred < r.Settings.Current(ctx).DeferAlert {
return
}
if alerted, _ := r.redisClient.SetNX(ctx, key+":alerted", 1, 24*time.Hour).Result(); alerted {
r.logger.Error("异步任务因运营商账户网关限流累计推迟超过告警阈值,任务继续重试",
zap.String("task_type", task.Type()), zap.String("task_key", taskKey), zap.Duration("deferred", deferred))
}
}