操作日志
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m10s

This commit is contained in:
2026-04-27 12:16:38 +08:00
parent 95104100c9
commit cb75b5668b
33 changed files with 3860 additions and 112 deletions

View File

@@ -12,6 +12,7 @@ import (
"github.com/break/junhong_cmp_fiber/internal/gateway"
"github.com/break/junhong_cmp_fiber/internal/model"
assetAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_audit"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
@@ -36,6 +37,7 @@ type StopResumeService struct {
deviceSimBindingStore *postgres.DeviceSimBindingStore
gatewayClient *gateway.Client
logger *zap.Logger
assetAuditService AssetAuditService
maxRetries int
retryInterval time.Duration
@@ -49,6 +51,7 @@ func NewStopResumeService(
deviceSimBindingStore *postgres.DeviceSimBindingStore,
gatewayClient *gateway.Client,
logger *zap.Logger,
assetAuditService AssetAuditService,
) *StopResumeService {
return &StopResumeService{
redis: redis,
@@ -57,6 +60,7 @@ func NewStopResumeService(
deviceSimBindingStore: deviceSimBindingStore,
gatewayClient: gatewayClient,
logger: logger,
assetAuditService: assetAuditService,
maxRetries: 3,
retryInterval: 2 * time.Second,
}
@@ -363,6 +367,16 @@ func (s *StopResumeService) ResumeCardIfStopped(ctx context.Context, carrierType
func (s *StopResumeService) resumeSingleCard(ctx context.Context, cardID uint) error {
card, err := s.iotCardStore.GetByID(ctx, cardID)
if err != nil {
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
Operator: assetAuditSvc.SystemOperator("系统任务"),
OperationType: constants.AssetAuditOpCardAutoStart,
OperationDesc: "自动复机执行失败",
ResultStatus: constants.AssetAuditResultFailed,
ErrorCode: errorCode,
ErrorMsg: errorMsg,
AssetID: cardID,
})
return err
}
@@ -407,6 +421,21 @@ func (s *StopResumeService) resumeSingleCard(ctx context.Context, cardID uint) e
zap.Uint("card_id", cardID),
zap.String("iccid", card.ICCID),
zap.Error(err))
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
Operator: assetAuditSvc.SystemOperator("系统任务"),
OperationType: constants.AssetAuditOpCardAutoStart,
OperationDesc: "自动复机执行失败",
ResultStatus: constants.AssetAuditResultFailed,
ErrorCode: errorCode,
ErrorMsg: errorMsg,
AssetID: card.ID,
AssetIdentifier: card.ICCID,
BeforeData: map[string]any{
"network_status": card.NetworkStatus,
"stop_reason": card.StopReason,
},
})
return err
}
@@ -420,6 +449,25 @@ func (s *StopResumeService) resumeSingleCard(ctx context.Context, cardID uint) e
zap.Uint("card_id", cardID),
zap.String("iccid", card.ICCID),
zap.Error(err))
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
Operator: assetAuditSvc.SystemOperator("系统任务"),
OperationType: constants.AssetAuditOpCardAutoStart,
OperationDesc: "自动复机执行失败",
ResultStatus: constants.AssetAuditResultFailed,
ErrorCode: errorCode,
ErrorMsg: errorMsg,
AssetID: card.ID,
AssetIdentifier: card.ICCID,
BeforeData: map[string]any{
"network_status": constants.NetworkStatusOffline,
"stop_reason": card.StopReason,
},
AfterData: map[string]any{
"network_status": constants.NetworkStatusOnline,
"stop_reason": "",
},
})
return nil
}
@@ -429,13 +477,52 @@ func (s *StopResumeService) resumeSingleCard(ctx context.Context, cardID uint) e
zap.Uint("card_id", cardID),
zap.String("iccid", card.ICCID))
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
Operator: assetAuditSvc.SystemOperator("系统任务"),
OperationType: constants.AssetAuditOpCardAutoStart,
OperationDesc: "自动复机",
ResultStatus: constants.AssetAuditResultSuccess,
AssetID: card.ID,
AssetIdentifier: card.ICCID,
BeforeData: map[string]any{
"network_status": constants.NetworkStatusOffline,
"stop_reason": card.StopReason,
},
AfterData: map[string]any{
"network_status": constants.NetworkStatusOnline,
"stop_reason": "",
},
})
return nil
}
// stopCardWithRetry 调用运营商停机接口(带重试机制),并更新 DB 停机原因
func (s *StopResumeService) stopCardWithRetry(ctx context.Context, card *model.IotCard, stopReason string) error {
operator := assetAuditSvc.SystemOperator("系统任务")
operationType := constants.AssetAuditOpCardAutoStop
operationDesc := "自动停卡"
if stopReason == constants.StopReasonManual {
operator = assetAuditSvc.OperatorFromContext(ctx)
operationType = constants.AssetAuditOpCardManualStop
operationDesc = "手动停卡"
}
if s.gatewayClient == nil {
return errors.New(errors.CodeInternalError, "Gateway 未配置,停复机操作不可用")
failErr := errors.New(errors.CodeInternalError, "Gateway 未配置,停复机操作不可用")
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(failErr)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
Operator: operator,
OperationType: operationType,
OperationDesc: operationDesc + "执行失败",
ResultStatus: constants.AssetAuditResultFailed,
ErrorCode: errorCode,
ErrorMsg: errorMsg,
AssetID: card.ID,
AssetIdentifier: card.ICCID,
BeforeData: cardSnapshot(card),
})
return failErr
}
s.logger.Info("调用网关停机",
@@ -470,9 +557,44 @@ func (s *StopResumeService) stopCardWithRetry(ctx context.Context, card *model.I
zap.Uint("card_id", card.ID),
zap.String("iccid", card.ICCID),
zap.Error(updateErr))
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(updateErr)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
Operator: operator,
OperationType: operationType,
OperationDesc: operationDesc + "执行失败",
ResultStatus: constants.AssetAuditResultFailed,
ErrorCode: errorCode,
ErrorMsg: errorMsg,
AssetID: card.ID,
AssetIdentifier: card.ICCID,
BeforeData: map[string]any{
"network_status": card.NetworkStatus,
"stop_reason": card.StopReason,
},
AfterData: map[string]any{
"network_status": constants.NetworkStatusOffline,
"stop_reason": stopReason,
},
})
}
s.invalidatePollingCache(ctx, card.ID)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
Operator: operator,
OperationType: operationType,
OperationDesc: operationDesc,
ResultStatus: constants.AssetAuditResultSuccess,
AssetID: card.ID,
AssetIdentifier: card.ICCID,
BeforeData: map[string]any{
"network_status": card.NetworkStatus,
"stop_reason": card.StopReason,
},
AfterData: map[string]any{
"network_status": constants.NetworkStatusOffline,
"stop_reason": stopReason,
},
})
return nil
}
@@ -483,6 +605,26 @@ func (s *StopResumeService) stopCardWithRetry(ctx context.Context, card *model.I
zap.Error(err))
}
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(lastErr)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
Operator: operator,
OperationType: operationType,
OperationDesc: operationDesc + "执行失败",
ResultStatus: constants.AssetAuditResultFailed,
ErrorCode: errorCode,
ErrorMsg: errorMsg,
AssetID: card.ID,
AssetIdentifier: card.ICCID,
BeforeData: map[string]any{
"network_status": card.NetworkStatus,
"stop_reason": card.StopReason,
},
AfterData: map[string]any{
"network_status": constants.NetworkStatusOffline,
"stop_reason": stopReason,
},
})
return lastErr
}
@@ -529,11 +671,33 @@ func (s *StopResumeService) resumeCardWithRetry(ctx context.Context, card *model
func (s *StopResumeService) ManualStopCard(ctx context.Context, iccid string) error {
card, err := s.iotCardStore.GetByICCID(ctx, iccid)
if err != nil {
return errors.New(errors.CodeNotFound, "卡不存在")
denyErr := errors.New(errors.CodeNotFound, "卡不存在")
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
OperationType: constants.AssetAuditOpCardManualStop,
OperationDesc: "手动停卡被拒绝",
ResultStatus: constants.AssetAuditResultDenied,
ErrorCode: errorCode,
ErrorMsg: errorMsg,
AssetIdentifier: iccid,
})
return denyErr
}
if card.RealNameStatus != constants.RealNameStatusVerified {
return errors.New(errors.CodeForbidden, "卡未实名,无法操作")
denyErr := errors.New(errors.CodeForbidden, "卡未实名,无法操作")
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
OperationType: constants.AssetAuditOpCardManualStop,
OperationDesc: "手动停卡被拒绝",
ResultStatus: constants.AssetAuditResultDenied,
ErrorCode: errorCode,
ErrorMsg: errorMsg,
AssetID: card.ID,
AssetIdentifier: card.ICCID,
BeforeData: cardSnapshot(card),
})
return denyErr
}
// 检查绑定设备是否在复机保护期
@@ -542,10 +706,37 @@ func (s *StopResumeService) ManualStopCard(ctx context.Context, iccid string) er
if bindErr == nil && binding != nil {
exists, _ := s.redis.Exists(ctx, constants.RedisDeviceProtectKey(binding.DeviceID, "start")).Result()
if exists > 0 {
return errors.New(errors.CodeForbidden, "设备复机保护期内,禁止停机")
denyErr := errors.New(errors.CodeForbidden, "设备复机保护期内,禁止停机")
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
OperationType: constants.AssetAuditOpCardManualStop,
OperationDesc: "手动停卡被拒绝",
ResultStatus: constants.AssetAuditResultDenied,
ErrorCode: errorCode,
ErrorMsg: errorMsg,
AssetID: card.ID,
AssetIdentifier: card.ICCID,
BeforeData: cardSnapshot(card),
AfterData: map[string]any{
"device_id": binding.DeviceID,
},
})
return denyErr
}
} else if bindErr != nil && !stderrors.Is(bindErr, gorm.ErrRecordNotFound) {
return errors.Wrap(errors.CodeInternalError, bindErr, "查询卡绑定关系失败")
wrapErr := errors.Wrap(errors.CodeInternalError, bindErr, "查询卡绑定关系失败")
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(wrapErr)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
OperationType: constants.AssetAuditOpCardManualStop,
OperationDesc: "手动停卡执行失败",
ResultStatus: constants.AssetAuditResultFailed,
ErrorCode: errorCode,
ErrorMsg: errorMsg,
AssetID: card.ID,
AssetIdentifier: card.ICCID,
BeforeData: cardSnapshot(card),
})
return wrapErr
}
}
@@ -560,11 +751,33 @@ func (s *StopResumeService) ManualStopCard(ctx context.Context, iccid string) er
func (s *StopResumeService) ManualStartCard(ctx context.Context, iccid string) error {
card, err := s.iotCardStore.GetByICCID(ctx, iccid)
if err != nil {
return errors.New(errors.CodeNotFound, "卡不存在")
denyErr := errors.New(errors.CodeNotFound, "卡不存在")
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
OperationType: constants.AssetAuditOpCardManualStart,
OperationDesc: "手动复机被拒绝",
ResultStatus: constants.AssetAuditResultDenied,
ErrorCode: errorCode,
ErrorMsg: errorMsg,
AssetIdentifier: iccid,
})
return denyErr
}
if card.RealNameStatus != constants.RealNameStatusVerified {
return errors.New(errors.CodeForbidden, "卡未实名,无法操作")
denyErr := errors.New(errors.CodeForbidden, "卡未实名,无法操作")
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
OperationType: constants.AssetAuditOpCardManualStart,
OperationDesc: "手动复机被拒绝",
ResultStatus: constants.AssetAuditResultDenied,
ErrorCode: errorCode,
ErrorMsg: errorMsg,
AssetID: card.ID,
AssetIdentifier: card.ICCID,
BeforeData: cardSnapshot(card),
})
return denyErr
}
// 检查绑定设备是否在停机保护期
@@ -573,15 +786,54 @@ func (s *StopResumeService) ManualStartCard(ctx context.Context, iccid string) e
if bindErr == nil && binding != nil {
exists, _ := s.redis.Exists(ctx, constants.RedisDeviceProtectKey(binding.DeviceID, "stop")).Result()
if exists > 0 {
return errors.New(errors.CodeForbidden, "设备停机保护期内,禁止复机")
denyErr := errors.New(errors.CodeForbidden, "设备停机保护期内,禁止复机")
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
OperationType: constants.AssetAuditOpCardManualStart,
OperationDesc: "手动复机被拒绝",
ResultStatus: constants.AssetAuditResultDenied,
ErrorCode: errorCode,
ErrorMsg: errorMsg,
AssetID: card.ID,
AssetIdentifier: card.ICCID,
BeforeData: cardSnapshot(card),
AfterData: map[string]any{
"device_id": binding.DeviceID,
},
})
return denyErr
}
} else if bindErr != nil && !stderrors.Is(bindErr, gorm.ErrRecordNotFound) {
return errors.Wrap(errors.CodeInternalError, bindErr, "查询卡绑定关系失败")
wrapErr := errors.Wrap(errors.CodeInternalError, bindErr, "查询卡绑定关系失败")
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(wrapErr)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
OperationType: constants.AssetAuditOpCardManualStart,
OperationDesc: "手动复机执行失败",
ResultStatus: constants.AssetAuditResultFailed,
ErrorCode: errorCode,
ErrorMsg: errorMsg,
AssetID: card.ID,
AssetIdentifier: card.ICCID,
BeforeData: cardSnapshot(card),
})
return wrapErr
}
}
if err := s.resumeCardWithRetry(ctx, card); err != nil {
return errors.Wrap(errors.CodeGatewayError, err, "调用运营商复机失败,请稍后重试")
wrapErr := errors.Wrap(errors.CodeGatewayError, err, "调用运营商复机失败,请稍后重试")
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(wrapErr)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
OperationType: constants.AssetAuditOpCardManualStart,
OperationDesc: "手动复机执行失败",
ResultStatus: constants.AssetAuditResultFailed,
ErrorCode: errorCode,
ErrorMsg: errorMsg,
AssetID: card.ID,
AssetIdentifier: card.ICCID,
BeforeData: cardSnapshot(card),
})
return wrapErr
}
now := time.Now()
@@ -590,11 +842,46 @@ func (s *StopResumeService) ManualStartCard(ctx context.Context, iccid string) e
"resumed_at": now,
"stop_reason": "",
}); err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "更新卡状态失败")
wrapErr := errors.Wrap(errors.CodeDatabaseError, err, "更新卡状态失败")
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(wrapErr)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
OperationType: constants.AssetAuditOpCardManualStart,
OperationDesc: "手动复机执行失败",
ResultStatus: constants.AssetAuditResultFailed,
ErrorCode: errorCode,
ErrorMsg: errorMsg,
AssetID: card.ID,
AssetIdentifier: card.ICCID,
BeforeData: map[string]any{
"network_status": card.NetworkStatus,
"stop_reason": card.StopReason,
},
AfterData: map[string]any{
"network_status": constants.NetworkStatusOnline,
"stop_reason": "",
},
})
return wrapErr
}
s.invalidatePollingCache(ctx, card.ID)
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
OperationType: constants.AssetAuditOpCardManualStart,
OperationDesc: "手动复机",
ResultStatus: constants.AssetAuditResultSuccess,
AssetID: card.ID,
AssetIdentifier: card.ICCID,
BeforeData: map[string]any{
"network_status": card.NetworkStatus,
"stop_reason": card.StopReason,
},
AfterData: map[string]any{
"network_status": constants.NetworkStatusOnline,
"stop_reason": "",
},
})
return nil
}