All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m33s
- 新增网关限流:按本系统运营商账户(卡 carrier_id)→ 限流组 → 操作限制每秒请求数,每次 HTTP 尝试计量,默认限流、仅后台人工点击/通道阈值/自动续费/手动刷新豁免;支持只统计与拦截两种模式,默认只统计 - Asynq 遇限流不计失败、固定短延迟重试;轮询任务与停复机业务重试遇限流短延迟重排 - 停机、复机不再使用网关内部重试,只发一次请求 - 网关返回结构化错误;新增上游错误分类规则表与测试接口 - 新增按月分区的停复机记录表:系统自动停复机只写记录,不再写统一审计与集成日志;普通轮询查询不再写集成日志 - 新增限流组、错误分类、停复机记录、按账户调用统计与枚举后台接口 - 迁移 000243~000246;同步 OpenSpec 提案(含后续 6 个 change)与技术方案、工程约束
81 lines
3.3 KiB
Go
81 lines
3.3 KiB
Go
package task
|
||
|
||
import (
|
||
"context"
|
||
"strconv"
|
||
"time"
|
||
|
||
"github.com/google/uuid"
|
||
|
||
"github.com/break/junhong_cmp_fiber/internal/infrastructure/integrationlog"
|
||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||
)
|
||
|
||
// newGatewayAttempt 只在内存中准备稳定关联标识;成功无变化的轮询不落库。
|
||
func newGatewayAttempt(cardID uint, operation, scene string) integrationlog.Attempt {
|
||
resourceID := strconv.FormatUint(uint64(cardID), 10)
|
||
integrationID := uuid.NewString()
|
||
triggerSource := constants.CardObservationSourcePolling
|
||
triggerScene := scene
|
||
return integrationlog.Attempt{IntegrationID: integrationID,
|
||
Provider: constants.IntegrationProviderGateway, Direction: constants.IntegrationDirectionOutbound,
|
||
Operation: operation, ResourceType: constants.AssetTypeIotCard, ResourceID: &resourceID,
|
||
TriggerSource: &triggerSource, TriggerScene: &triggerScene, TriggerSeries: &integrationID,
|
||
CorrelationID: &integrationID, RequestSummary: map[string]any{"card_id": cardID},
|
||
Metadata: map[string]any{"scene": scene}}
|
||
}
|
||
|
||
// recordGatewayAttempt 在结果明确后才写入外部交互日志。
|
||
func recordGatewayAttempt(ctx context.Context, repository *integrationlog.Repository, attempt integrationlog.Attempt, result string, changed bool, summary string) error {
|
||
if repository == nil {
|
||
return errors.New(errors.CodeInternalError, "Gateway Integration Log 未配置")
|
||
}
|
||
attempt.InitialResult = result
|
||
attempt.StateChanged = changed
|
||
attempt.Metadata = map[string]any{"scene": attempt.TriggerScene, "summary": summary}
|
||
_, err := repository.Start(ctx, attempt)
|
||
return err
|
||
}
|
||
|
||
// recordPriorityGatewayAttempt 只在本轮同时是一次优先执行时写外部交互日志,并把日志标识回写优先项。
|
||
// 普通轮询查询(无优先项在途)不再写集成日志:失败原因与调用量改由网关调用分钟计数与卡最近错误保留
|
||
// (add-carrier-gateway-rate-limit);优先执行仍按优先轮询可追溯要求保留日志引用。
|
||
func recordPriorityGatewayAttempt(
|
||
ctx context.Context,
|
||
repository *integrationlog.Repository,
|
||
attempt integrationlog.Attempt,
|
||
result string,
|
||
changed bool,
|
||
summary string,
|
||
round *pollingPriorityRound,
|
||
) error {
|
||
if round == nil {
|
||
return nil
|
||
}
|
||
if err := recordGatewayAttempt(ctx, repository, attempt, result, changed, summary); err != nil {
|
||
return err
|
||
}
|
||
round.RecordIntegrationLog(attempt.IntegrationID)
|
||
return nil
|
||
}
|
||
|
||
// completeGatewayAttempt 记录 Gateway 查询成功或明确失败,不把响应正文写入日志。
|
||
func completeGatewayAttempt(ctx context.Context, repository *integrationlog.Repository, attempt *model.IntegrationLog, success bool, startedAt time.Time) error {
|
||
if repository == nil || attempt == nil {
|
||
return errors.New(errors.CodeInternalError, "Gateway Integration Log 尝试不存在")
|
||
}
|
||
completionCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 5*time.Second)
|
||
defer cancel()
|
||
result := constants.IntegrationResultFailed
|
||
if success {
|
||
result = constants.IntegrationResultSuccess
|
||
}
|
||
_, err := repository.Complete(completionCtx, attempt.IntegrationID, integrationlog.Completion{
|
||
Result: result, DurationMS: time.Since(startedAt).Milliseconds(), StateChanged: false,
|
||
ResponseSummary: map[string]any{"success": success},
|
||
})
|
||
return err
|
||
}
|