All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m26s
统一停复机规则转正的第一次发布:总开关仍关闭,现有停复机行为不变。 - 新增停复机规则演练: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 加列与约束,并为"已销户""仅支持风险复机"置位
84 lines
3.3 KiB
Go
84 lines
3.3 KiB
Go
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))
|
||
}
|
||
}
|