This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/break/junhong_cmp_fiber/internal/gateway"
|
||||
"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/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
@@ -45,6 +46,7 @@ type Service struct {
|
||||
iotCardService IotCardRefresher
|
||||
gatewayClient *gateway.Client
|
||||
assetIdentifierStore *postgres.AssetIdentifierStore
|
||||
assetAuditService assetAuditSvc.OperationLogger
|
||||
}
|
||||
|
||||
// New 创建资产服务实例
|
||||
@@ -64,6 +66,7 @@ func New(
|
||||
orderStore *postgres.OrderStore,
|
||||
orderItemStore *postgres.OrderItemStore,
|
||||
exchangeOrderStore *postgres.ExchangeOrderStore,
|
||||
assetAuditService assetAuditSvc.OperationLogger,
|
||||
) *Service {
|
||||
return &Service{
|
||||
db: db,
|
||||
@@ -81,6 +84,7 @@ func New(
|
||||
iotCardService: iotCardService,
|
||||
gatewayClient: gatewayClient,
|
||||
assetIdentifierStore: assetIdentifierStore,
|
||||
assetAuditService: assetAuditService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -834,49 +838,102 @@ func (s *Service) GetCurrentPackage(ctx context.Context, assetType string, id ui
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询当前生效套餐失败")
|
||||
}
|
||||
|
||||
pkg, pkgErr := s.packageStore.GetByID(ctx, usage.PackageID)
|
||||
metrics := usage.BuildTrafficMetrics()
|
||||
pkgName := usage.PackageName
|
||||
pkgType := ""
|
||||
expiryBase := ""
|
||||
if pkgErr == nil && pkg != nil {
|
||||
if pkgName == "" {
|
||||
pkgName = pkg.PackageName
|
||||
}
|
||||
pkgType = pkg.PackageType
|
||||
expiryBase = pkg.ExpiryBase
|
||||
return s.buildAssetPackageResponse(ctx, usage, callerAccountType), nil
|
||||
}
|
||||
|
||||
// UpdatePackageExpiresAt 修改资产套餐过期时间。
|
||||
func (s *Service) UpdatePackageExpiresAt(ctx context.Context, assetType string, assetID uint, assetIdentifier string, packageUsageID uint, expiresAt time.Time) (*dto.AssetPackageResponse, error) {
|
||||
carrierType, err := normalizeAssetCarrierType(assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var paidAmount *int64
|
||||
if callerAccountType == constants.OwnerTypePlatform {
|
||||
paidAmount = usage.PaidAmount
|
||||
|
||||
before, err := s.packageUsageStore.GetByIDForCarrier(ctx, packageUsageID, carrierType, assetID)
|
||||
if err != nil {
|
||||
return nil, wrapPackageUsageNotFound(err)
|
||||
}
|
||||
return &dto.AssetPackageResponse{
|
||||
PackageUsageID: usage.ID,
|
||||
PackageID: usage.PackageID,
|
||||
PackageName: pkgName,
|
||||
PackageType: pkgType,
|
||||
ExpiryBase: expiryBase,
|
||||
OrderID: usage.OrderID,
|
||||
OrderNo: usage.OrderNo,
|
||||
RefundID: usage.RefundID,
|
||||
RefundNo: usage.RefundNo,
|
||||
UsageType: usage.UsageType,
|
||||
Status: usage.Status,
|
||||
StatusName: packageStatusName(usage.Status),
|
||||
RealTotalMB: metrics.RealTotalMB,
|
||||
RealUsedMB: metrics.RealUsedMB,
|
||||
VirtualTotalMB: metrics.VirtualTotalMB,
|
||||
VirtualUsedMB: metrics.VirtualUsedMB,
|
||||
ReductionPct: metrics.ReductionPct,
|
||||
EnableVirtualData: usage.EnableVirtualDataSnapshot,
|
||||
ActivatedAt: usage.ActivatedAt,
|
||||
ExpiresAt: usage.ExpiresAt,
|
||||
MasterUsageID: usage.MasterUsageID,
|
||||
Priority: usage.Priority,
|
||||
PaidAmount: paidAmount,
|
||||
RetailAmount: usage.RetailAmount,
|
||||
CreatedAt: usage.CreatedAt,
|
||||
}, nil
|
||||
|
||||
beforeData := packageUsageExpiresAtAuditData(before)
|
||||
rows, err := s.packageUsageStore.UpdateExpiresAtForCarrier(ctx, packageUsageID, carrierType, assetID, expiresAt)
|
||||
if err != nil {
|
||||
appErr := errors.Wrap(errors.CodeDatabaseError, err, "修改套餐过期时间失败")
|
||||
s.logPackageAdjustmentAudit(ctx, constants.AssetAuditOpAssetPackageExpiresAt, "修改资产套餐过期时间失败", constants.AssetAuditResultFailed, assetType, assetID, assetIdentifier, beforeData, map[string]any{
|
||||
"package_usage_id": packageUsageID,
|
||||
"expires_at": expiresAt,
|
||||
}, appErr)
|
||||
return nil, appErr
|
||||
}
|
||||
if rows == 0 {
|
||||
appErr := errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
|
||||
s.logPackageAdjustmentAudit(ctx, constants.AssetAuditOpAssetPackageExpiresAt, "修改资产套餐过期时间失败", constants.AssetAuditResultFailed, assetType, assetID, assetIdentifier, beforeData, map[string]any{
|
||||
"package_usage_id": packageUsageID,
|
||||
"expires_at": expiresAt,
|
||||
}, appErr)
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
updated, err := s.packageUsageStore.GetByIDForCarrier(ctx, packageUsageID, carrierType, assetID)
|
||||
if err != nil {
|
||||
appErr := errors.Wrap(errors.CodeDatabaseError, err, "查询套餐使用记录失败")
|
||||
s.logPackageAdjustmentAudit(ctx, constants.AssetAuditOpAssetPackageExpiresAt, "修改资产套餐过期时间失败", constants.AssetAuditResultFailed, assetType, assetID, assetIdentifier, beforeData, map[string]any{
|
||||
"package_usage_id": packageUsageID,
|
||||
"expires_at": expiresAt,
|
||||
}, appErr)
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
s.logPackageAdjustmentAudit(ctx, constants.AssetAuditOpAssetPackageExpiresAt, "修改资产套餐过期时间", constants.AssetAuditResultSuccess, assetType, assetID, assetIdentifier, beforeData, packageUsageExpiresAtAuditData(updated), nil)
|
||||
return s.buildAssetPackageResponse(ctx, updated, constants.OwnerTypePlatform), nil
|
||||
}
|
||||
|
||||
// UpdatePackageUsage 修改资产套餐已用量。
|
||||
func (s *Service) UpdatePackageUsage(ctx context.Context, assetType string, assetID uint, assetIdentifier string, packageUsageID uint, dataUsageMB int64) (*dto.AssetPackageResponse, error) {
|
||||
if dataUsageMB < 0 {
|
||||
return nil, errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
carrierType, err := normalizeAssetCarrierType(assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
before, err := s.packageUsageStore.GetByIDForCarrier(ctx, packageUsageID, carrierType, assetID)
|
||||
if err != nil {
|
||||
return nil, wrapPackageUsageNotFound(err)
|
||||
}
|
||||
|
||||
beforeData := packageUsageTrafficAuditData(before)
|
||||
nextStatus := statusForManualDataUsage(before, dataUsageMB)
|
||||
rows, err := s.packageUsageStore.UpdateDataUsageForCarrier(ctx, packageUsageID, carrierType, assetID, dataUsageMB, nextStatus)
|
||||
if err != nil {
|
||||
appErr := errors.Wrap(errors.CodeDatabaseError, err, "修改套餐已用量失败")
|
||||
s.logPackageAdjustmentAudit(ctx, constants.AssetAuditOpAssetPackageUsage, "修改资产套餐已用量失败", constants.AssetAuditResultFailed, assetType, assetID, assetIdentifier, beforeData, map[string]any{
|
||||
"package_usage_id": packageUsageID,
|
||||
"data_usage_mb": dataUsageMB,
|
||||
}, appErr)
|
||||
return nil, appErr
|
||||
}
|
||||
if rows == 0 {
|
||||
appErr := errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
|
||||
s.logPackageAdjustmentAudit(ctx, constants.AssetAuditOpAssetPackageUsage, "修改资产套餐已用量失败", constants.AssetAuditResultFailed, assetType, assetID, assetIdentifier, beforeData, map[string]any{
|
||||
"package_usage_id": packageUsageID,
|
||||
"data_usage_mb": dataUsageMB,
|
||||
}, appErr)
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
updated, err := s.packageUsageStore.GetByIDForCarrier(ctx, packageUsageID, carrierType, assetID)
|
||||
if err != nil {
|
||||
appErr := errors.Wrap(errors.CodeDatabaseError, err, "查询套餐使用记录失败")
|
||||
s.logPackageAdjustmentAudit(ctx, constants.AssetAuditOpAssetPackageUsage, "修改资产套餐已用量失败", constants.AssetAuditResultFailed, assetType, assetID, assetIdentifier, beforeData, map[string]any{
|
||||
"package_usage_id": packageUsageID,
|
||||
"data_usage_mb": dataUsageMB,
|
||||
}, appErr)
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
s.logPackageAdjustmentAudit(ctx, constants.AssetAuditOpAssetPackageUsage, "修改资产套餐已用量", constants.AssetAuditResultSuccess, assetType, assetID, assetIdentifier, beforeData, packageUsageTrafficAuditData(updated), nil)
|
||||
return s.buildAssetPackageResponse(ctx, updated, constants.OwnerTypePlatform), nil
|
||||
}
|
||||
|
||||
// getDeviceProtectStatus 查询设备保护期状态
|
||||
@@ -973,6 +1030,153 @@ func (s *Service) resolveAssetGeneration(ctx context.Context, assetType string,
|
||||
return 1
|
||||
}
|
||||
|
||||
func (s *Service) buildAssetPackageResponse(ctx context.Context, usage *model.PackageUsage, callerAccountType string) *dto.AssetPackageResponse {
|
||||
if usage == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
pkg, pkgErr := s.packageStore.GetByID(ctx, usage.PackageID)
|
||||
metrics := usage.BuildTrafficMetrics()
|
||||
pkgName := usage.PackageName
|
||||
pkgType := ""
|
||||
expiryBase := ""
|
||||
if pkgErr == nil && pkg != nil {
|
||||
if pkgName == "" {
|
||||
pkgName = pkg.PackageName
|
||||
}
|
||||
pkgType = pkg.PackageType
|
||||
expiryBase = pkg.ExpiryBase
|
||||
}
|
||||
var paidAmount *int64
|
||||
if callerAccountType == constants.OwnerTypePlatform {
|
||||
paidAmount = usage.PaidAmount
|
||||
}
|
||||
|
||||
return &dto.AssetPackageResponse{
|
||||
PackageUsageID: usage.ID,
|
||||
PackageID: usage.PackageID,
|
||||
PackageName: pkgName,
|
||||
PackageType: pkgType,
|
||||
ExpiryBase: expiryBase,
|
||||
OrderID: usage.OrderID,
|
||||
OrderNo: usage.OrderNo,
|
||||
RefundID: usage.RefundID,
|
||||
RefundNo: usage.RefundNo,
|
||||
UsageType: usage.UsageType,
|
||||
Status: usage.Status,
|
||||
StatusName: packageStatusName(usage.Status),
|
||||
RealTotalMB: metrics.RealTotalMB,
|
||||
RealUsedMB: metrics.RealUsedMB,
|
||||
VirtualTotalMB: metrics.VirtualTotalMB,
|
||||
VirtualUsedMB: metrics.VirtualUsedMB,
|
||||
ReductionPct: metrics.ReductionPct,
|
||||
EnableVirtualData: usage.EnableVirtualDataSnapshot,
|
||||
ActivatedAt: usage.ActivatedAt,
|
||||
ExpiresAt: usage.ExpiresAt,
|
||||
MasterUsageID: usage.MasterUsageID,
|
||||
Priority: usage.Priority,
|
||||
PaidAmount: paidAmount,
|
||||
RetailAmount: usage.RetailAmount,
|
||||
CreatedAt: usage.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeAssetCarrierType(assetType string) (string, error) {
|
||||
switch assetType {
|
||||
case "card", constants.AssetTypeIotCard:
|
||||
return constants.AssetTypeIotCard, nil
|
||||
case constants.AssetTypeDevice:
|
||||
return constants.AssetTypeDevice, nil
|
||||
default:
|
||||
return "", errors.New(errors.CodeInvalidParam, "不支持的资产类型,仅支持 card 或 device")
|
||||
}
|
||||
}
|
||||
|
||||
func wrapPackageUsageNotFound(err error) error {
|
||||
if stderrors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
|
||||
}
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询套餐使用记录失败")
|
||||
}
|
||||
|
||||
func statusForManualDataUsage(usage *model.PackageUsage, dataUsageMB int64) *int {
|
||||
if usage == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
threshold := usage.EffectiveThresholdMB()
|
||||
if threshold <= 0 {
|
||||
return nil
|
||||
}
|
||||
switch usage.Status {
|
||||
case constants.PackageUsageStatusActive:
|
||||
if dataUsageMB >= threshold {
|
||||
status := constants.PackageUsageStatusDepleted
|
||||
return &status
|
||||
}
|
||||
case constants.PackageUsageStatusDepleted:
|
||||
if dataUsageMB < threshold {
|
||||
status := constants.PackageUsageStatusActive
|
||||
return &status
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func packageUsageExpiresAtAuditData(usage *model.PackageUsage) map[string]any {
|
||||
if usage == nil {
|
||||
return nil
|
||||
}
|
||||
return map[string]any{
|
||||
"package_usage_id": usage.ID,
|
||||
"expires_at": usage.ExpiresAt,
|
||||
"status": usage.Status,
|
||||
"status_name": packageStatusName(usage.Status),
|
||||
}
|
||||
}
|
||||
|
||||
func packageUsageTrafficAuditData(usage *model.PackageUsage) map[string]any {
|
||||
if usage == nil {
|
||||
return nil
|
||||
}
|
||||
metrics := usage.BuildTrafficMetrics()
|
||||
return map[string]any{
|
||||
"package_usage_id": usage.ID,
|
||||
"data_usage_mb": usage.DataUsageMB,
|
||||
"virtual_used_mb": metrics.VirtualUsedMB,
|
||||
"real_total_mb": metrics.RealTotalMB,
|
||||
"virtual_total_mb": metrics.VirtualTotalMB,
|
||||
"status": usage.Status,
|
||||
"status_name": packageStatusName(usage.Status),
|
||||
"enable_virtual_data": usage.EnableVirtualDataSnapshot,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) logPackageAdjustmentAudit(ctx context.Context, operationType, operationDesc, resultStatus, assetType string, assetID uint, assetIdentifier string, beforeData, afterData map[string]any, err error) {
|
||||
if s == nil || s.assetAuditService == nil {
|
||||
return
|
||||
}
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
wrappedBefore, wrappedAfter := assetAuditSvc.WrapOperationContent(beforeData, afterData, map[string]any{
|
||||
"asset_type": assetAuditSvc.NormalizeAssetType(assetType),
|
||||
"asset_id": assetID,
|
||||
"asset_identifier": assetIdentifier,
|
||||
})
|
||||
s.assetAuditService.LogOperation(ctx, assetAuditSvc.BuildLog(ctx, assetAuditSvc.BuildLogParams{
|
||||
Operator: assetAuditSvc.OperatorFromContext(ctx),
|
||||
AssetType: assetType,
|
||||
AssetID: assetID,
|
||||
AssetIdentifier: assetIdentifier,
|
||||
OperationType: operationType,
|
||||
OperationDesc: operationDesc,
|
||||
BeforeData: wrappedBefore,
|
||||
AfterData: wrappedAfter,
|
||||
ResultStatus: resultStatus,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
}))
|
||||
}
|
||||
|
||||
// tracePreviousGenerations 通过换货链逆向追溯前代资产的订单,最多追溯10代
|
||||
func (s *Service) tracePreviousGenerations(ctx context.Context, assetType string, assetID uint) ([]*dto.PreviousGenerationOrders, bool) {
|
||||
const maxDepth = 10
|
||||
|
||||
@@ -339,6 +339,24 @@ func getOperationFieldDesc(operationType string) map[string]string {
|
||||
return map[string]string{
|
||||
"realname_policy": "实名策略(none/before_order/after_order)",
|
||||
}
|
||||
case "asset_package_expires_at":
|
||||
return map[string]string{
|
||||
"package_usage_id": "套餐使用记录ID",
|
||||
"expires_at": "套餐过期时间",
|
||||
"status": "套餐状态",
|
||||
"status_name": "套餐状态名称",
|
||||
}
|
||||
case "asset_package_usage":
|
||||
return map[string]string{
|
||||
"package_usage_id": "套餐使用记录ID",
|
||||
"data_usage_mb": "套餐真实已用量(MB)",
|
||||
"virtual_used_mb": "套餐展示已用量(MB)",
|
||||
"real_total_mb": "套餐真实总量(MB)",
|
||||
"virtual_total_mb": "套餐业务停机阈值(MB)",
|
||||
"status": "套餐状态",
|
||||
"status_name": "套餐状态名称",
|
||||
"enable_virtual_data": "是否启用虚流量",
|
||||
}
|
||||
case "card_realname_status":
|
||||
return map[string]string{
|
||||
"real_name_status": "实名状态(0未实名/1已实名)",
|
||||
|
||||
Reference in New Issue
Block a user