七月迭代短暂完结,还有很多后端的关键东西没有弄,这是一版赶时间做的东西
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
This commit is contained in:
83
internal/service/iot_card/realname_policy_batch.go
Normal file
83
internal/service/iot_card/realname_policy_batch.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package iot_card
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
assetAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_audit"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// BatchUpdateRealnamePolicy 批量更新卡实名认证策略,整批校验后在单事务内全成全败。
|
||||
func (s *Service) BatchUpdateRealnamePolicy(ctx context.Context, req *dto.BatchUpdateAssetRealnamePolicyRequest) (*dto.BatchUpdateAssetRealnamePolicyResponse, error) {
|
||||
if middleware.GetUserTypeFromContext(ctx) == constants.UserTypeEnterprise {
|
||||
return nil, errors.New(errors.CodeForbidden, "企业账号无权修改卡实名认证策略")
|
||||
}
|
||||
ids, err := validateBatchRealnamePolicyRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var cards []model.IotCard
|
||||
query := middleware.ApplyShopFilter(ctx, tx.Model(&model.IotCard{})).Clauses(clause.Locking{Strength: "UPDATE"})
|
||||
if err := query.Where("id IN ?", ids).Find(&cards).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询批量卡资产失败")
|
||||
}
|
||||
if len(cards) != len(ids) {
|
||||
return errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
|
||||
}
|
||||
result := tx.Model(&model.IotCard{}).Where("id IN ?", ids).Update("realname_policy", req.RealnamePolicy)
|
||||
if result.Error != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, result.Error, "批量更新卡实名认证策略失败")
|
||||
}
|
||||
if result.RowsAffected != int64(len(ids)) {
|
||||
return errors.New(errors.CodeConflict, "卡资产状态已变化,请刷新后重试")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardRealnamePolicy,
|
||||
OperationDesc: "批量更新卡实名认证策略",
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
BatchTotal: len(ids),
|
||||
SuccessCount: len(ids),
|
||||
AfterData: map[string]any{
|
||||
"asset_ids": ids,
|
||||
"realname_policy": req.RealnamePolicy,
|
||||
},
|
||||
})
|
||||
return &dto.BatchUpdateAssetRealnamePolicyResponse{SuccessCount: len(ids), RealnamePolicy: req.RealnamePolicy}, nil
|
||||
}
|
||||
|
||||
func validateBatchRealnamePolicyRequest(req *dto.BatchUpdateAssetRealnamePolicyRequest) ([]uint, error) {
|
||||
if req == nil || len(req.AssetIDs) == 0 || len(req.AssetIDs) > 500 || !isValidRealnamePolicy(req.RealnamePolicy) {
|
||||
return nil, errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
seen := make(map[uint]struct{}, len(req.AssetIDs))
|
||||
ids := make([]uint, 0, len(req.AssetIDs))
|
||||
for _, id := range req.AssetIDs {
|
||||
if id == 0 {
|
||||
return nil, errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
if _, exists := seen[id]; exists {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "资产ID不能重复")
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func isValidRealnamePolicy(policy string) bool {
|
||||
return policy == constants.RealnamePolicyNone ||
|
||||
policy == constants.RealnamePolicyBeforeOrder ||
|
||||
policy == constants.RealnamePolicyAfterOrder
|
||||
}
|
||||
@@ -72,6 +72,7 @@ type Service struct {
|
||||
cardObservation *cardapp.Service
|
||||
observationSeries cardapp.BestEffortSeriesDispatcher
|
||||
trafficLock *cardtrafficlock.Lock
|
||||
speedTierIntegration speedTierIntegrationLog
|
||||
}
|
||||
|
||||
// SetObservationSeriesDispatcher 注入获取实名链接后的后台观测端口。
|
||||
@@ -84,6 +85,11 @@ func (s *Service) SetCardObservationService(service *cardapp.Service) {
|
||||
s.cardObservation = service
|
||||
}
|
||||
|
||||
// SetSpeedTierIntegrationLog 注入卡固定限速的 Integration Log 接缝。
|
||||
func (s *Service) SetSpeedTierIntegrationLog(integration speedTierIntegrationLog) {
|
||||
s.speedTierIntegration = integration
|
||||
}
|
||||
|
||||
// SetPackageExpiryQuery 注入套餐最终到期查询,供列表使用批量投影。
|
||||
func (s *Service) SetPackageExpiryQuery(query *packageexpiry.Query) {
|
||||
s.packageExpiryQuery = query
|
||||
@@ -250,6 +256,9 @@ func (s *Service) ListStandalone(ctx context.Context, req *dto.ListStandaloneIot
|
||||
if req.NetworkStatus != nil {
|
||||
filters["network_status"] = *req.NetworkStatus
|
||||
}
|
||||
if req.RealNameStatus != nil {
|
||||
filters["real_name_status"] = *req.RealNameStatus
|
||||
}
|
||||
if req.AuthorizedEnterpriseID != nil {
|
||||
filters["authorized_enterprise_id"] = *req.AuthorizedEnterpriseID
|
||||
}
|
||||
|
||||
158
internal/service/iot_card/speed_tier.go
Normal file
158
internal/service/iot_card/speed_tier.go
Normal file
@@ -0,0 +1,158 @@
|
||||
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"
|
||||
assetAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_audit"
|
||||
"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"
|
||||
)
|
||||
|
||||
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 {
|
||||
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,
|
||||
RequestSummary: map[string]any{
|
||||
"iot_card_id": card.ID,
|
||||
"iccid": card.ICCID,
|
||||
"tier_code": *code,
|
||||
"tier_name": tierName,
|
||||
"operator_id": middleware.GetUserIDFromContext(ctx),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
s.logSpeedTierAudit(ctx, card, *code, "", constants.AssetAuditResultFailed, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
startedAt := time.Now()
|
||||
gatewayErr := s.gatewayClient.SetCardSpeedTier(ctx, &gateway.CardSpeedTierReq{
|
||||
CardNo: card.ICCID,
|
||||
Code: strconv.Itoa(*code),
|
||||
})
|
||||
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),
|
||||
)
|
||||
}
|
||||
s.logSpeedTierAudit(ctx, card, *code, attempt.IntegrationID, constants.AssetAuditResultFailed, completeErr)
|
||||
return nil, pkgerrors.Wrap(pkgerrors.CodeDatabaseError, completeErr, "终结卡限速外部交互记录失败")
|
||||
}
|
||||
if gatewayErr != nil {
|
||||
s.logSpeedTierAudit(ctx, card, *code, attempt.IntegrationID, constants.AssetAuditResultFailed, gatewayErr)
|
||||
if isGatewayTimeout(gatewayErr) {
|
||||
return nil, pkgerrors.New(pkgerrors.CodeGatewayTimeout, "Gateway 卡限速请求结果未知,请核对实际档位后再操作")
|
||||
}
|
||||
return nil, gatewayErr
|
||||
}
|
||||
|
||||
s.logSpeedTierAudit(ctx, card, *code, attempt.IntegrationID, constants.AssetAuditResultSuccess, 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: true,
|
||||
ResponseSummary: map[string]any{
|
||||
"result": "success",
|
||||
},
|
||||
}
|
||||
if err == nil {
|
||||
return completion
|
||||
}
|
||||
completion.Result = constants.IntegrationResultFailed
|
||||
completion.StateChanged = false
|
||||
completion.ProviderMessage = err.Error()
|
||||
completion.ResponseSummary = map[string]any{"result": "failed"}
|
||||
if isGatewayTimeout(err) {
|
||||
completion.Result = constants.IntegrationResultUnknown
|
||||
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 (s *Service) logSpeedTierAudit(ctx context.Context, card *model.IotCard, code int, integrationID, result string, err error) {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
OperationType: constants.AssetAuditOpCardSpeedTier,
|
||||
OperationDesc: "设置 IoT 卡固定限速档位",
|
||||
BeforeData: map[string]any{"card": cardSnapshot(card)},
|
||||
AfterData: map[string]any{
|
||||
"tier_code": code,
|
||||
"tier_name": constants.GetGatewaySpeedTierName(code),
|
||||
"integration_id": integrationID,
|
||||
},
|
||||
ResultStatus: result,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
})
|
||||
}
|
||||
|
||||
var _ speedTierIntegrationLog = (*integrationlog.Repository)(nil)
|
||||
Reference in New Issue
Block a user