package iot_card import ( "context" stderrors "errors" "strconv" "time" "github.com/break/junhong_cmp_fiber/internal/gateway" "github.com/break/junhong_cmp_fiber/internal/infrastructure/integrationlog" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/pkg/constants" pkgerrors "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/break/junhong_cmp_fiber/pkg/middleware" "go.uber.org/zap" "gorm.io/gorm" ) type speedTierIntegrationLog interface { Start(ctx context.Context, input integrationlog.Attempt) (*model.IntegrationLog, error) Complete(ctx context.Context, integrationID string, completion integrationlog.Completion) (*model.IntegrationLog, error) } // SetSpeedTier 为有权限的 IoT 卡设置或恢复固定限速档位。 func (s *Service) SetSpeedTier(ctx context.Context, iccid string, code *int) (*dto.SetIotCardSpeedTierResponse, error) { if !canManageCardSpeedTier(ctx) { return nil, pkgerrors.New(pkgerrors.CodeForbidden, "仅平台和代理后台账号可设置卡限速档位") } if code == nil || !constants.IsGatewaySpeedTier(*code) { return nil, pkgerrors.New(pkgerrors.CodeInvalidParam, "固定限速档位不合法") } if s == nil || s.iotCardStore == nil || s.gatewayClient == nil || s.speedTierIntegration == nil || s.db == nil || s.auditWriter == nil { return nil, pkgerrors.New(pkgerrors.CodeServiceUnavailable, "卡限速服务未完整配置") } card, err := s.iotCardStore.GetByICCID(ctx, iccid) if err != nil || card == nil || card.ICCID == "" { return nil, pkgerrors.New(pkgerrors.CodeForbidden, "无权限操作该资源或资源不存在") } tierName := constants.GetGatewaySpeedTierName(*code) resourceID := strconv.FormatUint(uint64(card.ID), 10) requestID := middleware.GetRequestIDFromContext(ctx) attempt, err := s.speedTierIntegration.Start(ctx, integrationlog.Attempt{ Provider: constants.IntegrationProviderGateway, Direction: constants.IntegrationDirectionOutbound, Operation: constants.IntegrationOperationGatewaySpeedTier, ExternalID: &card.ICCID, ResourceType: constants.AssetTypeIotCard, ResourceID: &resourceID, ResourceKey: &card.ICCID, RequestID: requestID, CorrelationID: requestID, TriggerSeries: requestID, RequestSummary: map[string]any{ "iot_card_id": card.ID, "iccid": card.ICCID, "tier_code": *code, "tier_name": tierName, }, }) if err != nil { s.recordSpeedTierAudit(ctx, card, *code, "", false, constants.AuditResultFailed, err) return nil, err } startedAt := time.Now() gatewayErr := s.gatewayClient.SetCardSpeedTier(ctx, &gateway.CardSpeedTierReq{ CardNo: card.ICCID, Code: strconv.Itoa(*code), }) if gatewayErr != nil && s.logger != nil { s.logger.Warn("Gateway 卡限速请求失败", zap.Uint("iot_card_id", card.ID), zap.String("integration_id", attempt.IntegrationID), zap.Error(gatewayErr), ) } completion := speedTierCompletion(gatewayErr, time.Since(startedAt)) if _, completeErr := s.speedTierIntegration.Complete(ctx, attempt.IntegrationID, completion); completeErr != nil { if s.logger != nil { s.logger.Error("终结卡限速 Integration Log 失败", zap.Uint("iot_card_id", card.ID), zap.String("integration_id", attempt.IntegrationID), zap.Error(completeErr), ) } auditErr := gatewayErr if auditErr == nil { auditErr = completeErr } s.recordSpeedTierAudit(ctx, card, *code, attempt.IntegrationID, false, speedTierAuditResult(gatewayErr), auditErr) return nil, pkgerrors.Wrap(pkgerrors.CodeDatabaseError, completeErr, "终结卡限速外部交互记录失败") } if gatewayErr != nil { s.recordSpeedTierAudit(ctx, card, *code, attempt.IntegrationID, true, speedTierAuditResult(gatewayErr), gatewayErr) if isGatewayTimeout(gatewayErr) { return nil, pkgerrors.New(pkgerrors.CodeGatewayTimeout, "Gateway 卡限速请求结果未知,请核对实际档位后再操作") } return nil, gatewayErr } s.recordSpeedTierAudit(ctx, card, *code, attempt.IntegrationID, true, constants.AuditResultSuccess, nil) return &dto.SetIotCardSpeedTierResponse{ IotCardID: card.ID, ICCID: card.ICCID, Code: *code, SpeedTierName: tierName, IntegrationID: attempt.IntegrationID, }, nil } func canManageCardSpeedTier(ctx context.Context) bool { switch middleware.GetUserTypeFromContext(ctx) { case constants.UserTypeSuperAdmin, constants.UserTypePlatform, constants.UserTypeAgent: return middleware.GetUserIDFromContext(ctx) > 0 default: return false } } func speedTierCompletion(err error, duration time.Duration) integrationlog.Completion { completion := integrationlog.Completion{ Result: constants.IntegrationResultSuccess, DurationMS: duration.Milliseconds(), StateChanged: false, ResponseSummary: map[string]any{ "result": "success", }, } if err == nil { return completion } completion.Result = constants.IntegrationResultFailed completion.StateChanged = false completion.SafeProviderMessage = "Gateway 卡限速请求失败" completion.ResponseSummary = map[string]any{"result": "failed"} if isGatewayTimeout(err) { completion.Result = constants.IntegrationResultUnknown completion.SafeProviderMessage = "Gateway 卡限速请求结果未知" completion.ResponseSummary = map[string]any{"result": "unknown"} completion.RecoveryStrategy = constants.GatewaySpeedTierUnknownRecoveryStrategy } return completion } func isGatewayTimeout(err error) bool { var appErr *pkgerrors.AppError return stderrors.As(err, &appErr) && appErr != nil && appErr.Code == pkgerrors.CodeGatewayTimeout } func speedTierAuditResult(err error) string { if err == nil { return constants.AuditResultSuccess } if isGatewayTimeout(err) { return constants.AuditResultUnknown } return constants.AuditResultFailed } func (s *Service) recordSpeedTierAudit(ctx context.Context, card *model.IotCard, code int, integrationID string, integrationLogCompleted bool, result string, businessErr error) { afterData := map[string]any{ "requested_tier_code": code, "requested_tier_name": constants.GetGatewaySpeedTierName(code), "integration_id": integrationID, "integration_log_completed": integrationLogCompleted, } if s.db == nil || s.auditWriter == nil { recordCardAuditSecondaryFailure(ctx, constants.AuditActionIotCardSpeedTierSet, card.ID, businessErr, pkgerrors.New(pkgerrors.CodeInvalidStatus, "IoT 卡统一审计接缝未配置")) return } if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { return s.appendCardLifecycleAudit(ctx, tx, constants.AuditActionIotCardSpeedTierSet, "设置 IoT 卡固定限速档位为"+constants.GetGatewaySpeedTierName(code), result, card, nil, afterData, businessErr) }); err != nil { recordCardAuditSecondaryFailure(ctx, constants.AuditActionIotCardSpeedTierSet, card.ID, businessErr, err) } } var _ speedTierIntegrationLog = (*integrationlog.Repository)(nil)