收口审计治理与套餐任务进展
Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展 Confidence: medium Scope-risk: broad Directive: 后续修改需保持审计事件与业务事务边界一致 Tested: git diff --cached --check Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/infrastructure/audit"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
@@ -38,6 +39,12 @@ type UsageService struct {
|
||||
deviceSimBindingStore *postgres.DeviceSimBindingStore
|
||||
logger *zap.Logger
|
||||
stopResumeCallback StopResumeCallback // 停复机回调,可选
|
||||
auditWriter *audit.Writer
|
||||
}
|
||||
|
||||
// SetLifecycleAudit 注入套餐权益流量扣减统一审计 Writer。
|
||||
func (s *UsageService) SetLifecycleAudit(writer *audit.Writer) {
|
||||
s.auditWriter = writer
|
||||
}
|
||||
|
||||
func NewUsageService(
|
||||
@@ -81,6 +88,7 @@ func (s *UsageService) DeductDataUsage(ctx context.Context, carrierType string,
|
||||
shouldSuspend := false
|
||||
suspendCarrierType := ""
|
||||
var suspendCarrierID uint
|
||||
var auditChanges []packageUsageAuditChange
|
||||
|
||||
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
targetCarrierType, targetCarrierID, packages, err := s.resolveActivePackages(ctx, tx, carrierType, carrierID)
|
||||
@@ -116,9 +124,12 @@ func (s *UsageService) DeductDataUsage(ctx context.Context, carrierType string,
|
||||
isLastPackage := index == len(packages)-1
|
||||
if remainingQuota <= 0 && !isLastPackage {
|
||||
// 套餐已用完,标记为已用完
|
||||
beforeData := packageUsageStateData(pkg)
|
||||
if err := tx.Model(pkg).Update("status", constants.PackageUsageStatusDepleted).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "更新套餐状态失败")
|
||||
}
|
||||
pkg.Status = constants.PackageUsageStatusDepleted
|
||||
auditChanges = append(auditChanges, packageUsageAuditChange{Usage: pkg, BeforeData: beforeData, AfterData: packageUsageStateData(pkg)})
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -132,9 +143,12 @@ func (s *UsageService) DeductDataUsage(ctx context.Context, carrierType string,
|
||||
deductFromPkg = remainingQuota
|
||||
}
|
||||
if deductFromPkg <= 0 {
|
||||
beforeData := packageUsageStateData(pkg)
|
||||
if err := tx.Model(pkg).Update("status", constants.PackageUsageStatusDepleted).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "更新套餐状态失败")
|
||||
}
|
||||
pkg.Status = constants.PackageUsageStatusDepleted
|
||||
auditChanges = append(auditChanges, packageUsageAuditChange{Usage: pkg, BeforeData: beforeData, AfterData: packageUsageStateData(pkg)})
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -149,14 +163,20 @@ func (s *UsageService) DeductDataUsage(ctx context.Context, carrierType string,
|
||||
updates["status"] = constants.PackageUsageStatusDepleted
|
||||
}
|
||||
|
||||
beforeData := packageUsageStateData(pkg)
|
||||
if err := tx.Model(pkg).Updates(updates).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "更新套餐使用量失败")
|
||||
}
|
||||
pkg.DataUsageMB = newUsage
|
||||
if status, ok := updates["status"].(int); ok {
|
||||
pkg.Status = status
|
||||
}
|
||||
|
||||
// 任务 10.6: 写入日记录
|
||||
if err := s.updateDailyRecord(ctx, tx, pkg.ID, today, deductFromPkg, newUsage); err != nil {
|
||||
return err
|
||||
}
|
||||
auditChanges = append(auditChanges, packageUsageAuditChange{Usage: pkg, BeforeData: beforeData, AfterData: packageUsageStateData(pkg)})
|
||||
|
||||
remainingUsage -= deductFromPkg
|
||||
|
||||
@@ -171,17 +191,28 @@ func (s *UsageService) DeductDataUsage(ctx context.Context, carrierType string,
|
||||
}
|
||||
|
||||
// 任务 10.5: 检查是否所有套餐都用完(触发停机)
|
||||
shouldSuspendCurrent, err := s.checkAndTriggerSuspension(ctx, tx, targetCarrierType, targetCarrierID)
|
||||
shouldSuspendCurrent, suspensionChanges, err := s.checkAndTriggerSuspension(ctx, tx, targetCarrierType, targetCarrierID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
auditChanges = append(auditChanges, suspensionChanges...)
|
||||
shouldSuspend = shouldSuspendCurrent
|
||||
suspendCarrierType = targetCarrierType
|
||||
suspendCarrierID = targetCarrierID
|
||||
if len(auditChanges) > 0 {
|
||||
if err := appendPackageUsageAudit(ctx, tx, s.auditWriter, constants.AuditActionPackageUsageTrafficDeducted, "扣减套餐权益流量", normalizePackageUsageAuditChanges(auditChanges), nil, map[string]any{
|
||||
"carrier_type": targetCarrierType, "carrier_id": targetCarrierID, "usage_mb": deductUsageMB,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
if len(auditChanges) > 0 {
|
||||
recordPackageUsageFailure(ctx, s.db, s.auditWriter, constants.AuditActionPackageUsageTrafficDeducted, "扣减套餐权益流量失败", auditChanges[0].Usage, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -360,7 +391,7 @@ func (s *UsageService) updateDailyRecord(ctx context.Context, tx *gorm.DB, packa
|
||||
}
|
||||
|
||||
// checkAndTriggerSuspension 任务 10.5: 检查停机条件
|
||||
func (s *UsageService) checkAndTriggerSuspension(ctx context.Context, tx *gorm.DB, carrierType string, carrierID uint) (bool, error) {
|
||||
func (s *UsageService) checkAndTriggerSuspension(ctx context.Context, tx *gorm.DB, carrierType string, carrierID uint) (bool, []packageUsageAuditChange, error) {
|
||||
query := tx.Model(&model.PackageUsage{}).
|
||||
Where("status IN ?", []int{constants.PackageUsageStatusActive, constants.PackageUsageStatusDepleted})
|
||||
|
||||
@@ -369,24 +400,28 @@ func (s *UsageService) checkAndTriggerSuspension(ctx context.Context, tx *gorm.D
|
||||
} else if carrierType == constants.AssetTypeDevice {
|
||||
query = query.Where("device_id = ?", carrierID)
|
||||
} else {
|
||||
return false, errors.New(errors.CodeInvalidParam, "无效的载体类型")
|
||||
return false, nil, errors.New(errors.CodeInvalidParam, "无效的载体类型")
|
||||
}
|
||||
|
||||
var packages []*model.PackageUsage
|
||||
if err := query.Find(&packages).Error; err != nil {
|
||||
return false, errors.Wrap(errors.CodeDatabaseError, err, "查询套餐状态失败")
|
||||
return false, nil, errors.Wrap(errors.CodeDatabaseError, err, "查询套餐状态失败")
|
||||
}
|
||||
|
||||
hasAvailablePackage := false
|
||||
var changes []packageUsageAuditChange
|
||||
for _, pkg := range packages {
|
||||
if pkg == nil {
|
||||
continue
|
||||
}
|
||||
if pkg.IsTrafficDepleted() {
|
||||
if pkg.Status != constants.PackageUsageStatusDepleted {
|
||||
beforeData := packageUsageStateData(pkg)
|
||||
if err := tx.Model(pkg).Update("status", constants.PackageUsageStatusDepleted).Error; err != nil {
|
||||
return false, errors.Wrap(errors.CodeDatabaseError, err, "更新套餐耗尽状态失败")
|
||||
return false, nil, errors.Wrap(errors.CodeDatabaseError, err, "更新套餐耗尽状态失败")
|
||||
}
|
||||
pkg.Status = constants.PackageUsageStatusDepleted
|
||||
changes = append(changes, packageUsageAuditChange{Usage: pkg, BeforeData: beforeData, AfterData: packageUsageStateData(pkg)})
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -398,10 +433,10 @@ func (s *UsageService) checkAndTriggerSuspension(ctx context.Context, tx *gorm.D
|
||||
s.logger.Warn("所有套餐已用完,触发停机",
|
||||
zap.String("carrier_type", carrierType),
|
||||
zap.Uint("carrier_id", carrierID))
|
||||
return true, nil
|
||||
return true, changes, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
return false, changes, nil
|
||||
}
|
||||
|
||||
// triggerSuspensionAfterCommit 在事务提交后触发停机检查,避免回调读到未提交状态。
|
||||
|
||||
Reference in New Issue
Block a user