Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展 Confidence: medium Scope-risk: broad Directive: 后续修改需保持审计事件与业务事务边界一致 Tested: git diff --cached --check Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
181 lines
6.9 KiB
Go
181 lines
6.9 KiB
Go
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)
|