收口审计治理与套餐任务进展

Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展
Confidence: medium
Scope-risk: broad
Directive: 后续修改需保持审计事件与业务事务边界一致
Tested: git diff --cached --check
Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
This commit is contained in:
2026-08-05 14:30:54 +08:00
parent b3499adfca
commit 5e552d99bc
178 changed files with 16797 additions and 5674 deletions

View File

@@ -4,6 +4,7 @@ import (
"context"
"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"
@@ -11,6 +12,7 @@ import (
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type ResetService struct {
@@ -19,6 +21,12 @@ type ResetService struct {
packageUsageStore *postgres.PackageUsageStore
logger *zap.Logger
resumeCallback ResumeCallback
auditWriter *audit.Writer
}
// SetLifecycleAudit 注入套餐权益流量重置统一审计 Writer。
func (s *ResetService) SetLifecycleAudit(writer *audit.Writer) {
s.auditWriter = writer
}
func NewResetService(
@@ -55,6 +63,7 @@ func (s *ResetService) resetDailyUsageWithDB(ctx context.Context, db *gorm.DB) e
err := tx.Where("data_reset_cycle = ?", constants.PackageDataResetDaily).
Where("next_reset_at <= ?", now).
Where("status IN ?", []int{constants.PackageUsageStatusActive, constants.PackageUsageStatusDepleted}).
Clauses(clause.Locking{Strength: "UPDATE"}).
Find(&packages).Error
if err != nil {
@@ -80,21 +89,40 @@ func (s *ResetService) resetDailyUsageWithDB(ctx context.Context, db *gorm.DB) e
"status": constants.PackageUsageStatusActive,
}
if err := tx.Model(&model.PackageUsage{}).
Where("id IN ?", packageIDs).
Updates(updates).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "批量重置日流量失败")
result := tx.Model(&model.PackageUsage{}).
Where("id IN ? AND next_reset_at <= ? AND status IN ?", packageIDs, now, []int{constants.PackageUsageStatusActive, constants.PackageUsageStatusDepleted}).
Updates(updates)
if result.Error != nil {
return errors.Wrap(errors.CodeDatabaseError, result.Error, "批量重置日流量失败")
}
if result.RowsAffected != int64(len(packages)) {
return errors.New(errors.CodeConflict, "日流量重置目标状态已变化")
}
changes := make([]packageUsageAuditChange, 0, len(packages))
for _, usage := range packages {
beforeData := packageUsageStateData(usage)
usage.DataUsageMB = 0
usage.LastResetAt = &now
usage.NextResetAt = &nextReset
usage.Status = constants.PackageUsageStatusActive
changes = append(changes, packageUsageAuditChange{Usage: usage, BeforeData: beforeData, AfterData: packageUsageStateData(usage)})
}
resetPackages = packages
if err := appendPackageUsageAudit(ctx, tx, s.auditWriter, constants.AuditActionPackageUsageTrafficReset, "重置套餐权益日流量", changes, nil, map[string]any{"reset_cycle": constants.PackageDataResetDaily}); err != nil {
return err
}
s.logger.Info("日流量重置完成",
zap.Int("count", len(packages)),
zap.Time("next_reset_at", nextReset))
resetPackages = packages
return nil
})
if err != nil {
if len(resetPackages) > 0 {
recordPackageUsageFailure(ctx, s.db, s.auditWriter, constants.AuditActionPackageUsageTrafficReset, "重置套餐权益日流量失败", resetPackages[0], err)
}
return err
}
@@ -117,6 +145,7 @@ func (s *ResetService) resetMonthlyUsageWithDB(ctx context.Context, db *gorm.DB)
err := tx.Where("data_reset_cycle = ?", constants.PackageDataResetMonthly).
Where("next_reset_at <= ?", now).
Where("status IN ?", []int{constants.PackageUsageStatusActive, constants.PackageUsageStatusDepleted}).
Clauses(clause.Locking{Strength: "UPDATE"}).
Find(&packages).Error
if err != nil {
@@ -128,6 +157,7 @@ func (s *ResetService) resetMonthlyUsageWithDB(ctx context.Context, db *gorm.DB)
return nil
}
changes := make([]packageUsageAuditChange, 0, len(packages))
for _, usage := range packages {
var pkg model.Package
if err := tx.First(&pkg, usage.PackageID).Error; err != nil {
@@ -156,9 +186,22 @@ func (s *ResetService) resetMonthlyUsageWithDB(ctx context.Context, db *gorm.DB)
"status": constants.PackageUsageStatusActive,
}
if err := tx.Model(usage).Updates(updates).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "重置月流量失败")
beforeData := packageUsageStateData(usage)
result := tx.Model(usage).
Where("next_reset_at <= ? AND status IN ?", now, []int{constants.PackageUsageStatusActive, constants.PackageUsageStatusDepleted}).
Updates(updates)
if result.Error != nil {
return errors.Wrap(errors.CodeDatabaseError, result.Error, "重置月流量失败")
}
if result.RowsAffected == 0 {
continue
}
usage.DataUsageMB = 0
usage.LastResetAt = &now
usage.NextResetAt = nextResetAt
usage.Status = constants.PackageUsageStatusActive
changes = append(changes, packageUsageAuditChange{Usage: usage, BeforeData: beforeData, AfterData: packageUsageStateData(usage)})
resetPackages = append(resetPackages, usage)
s.logger.Info("月流量已重置",
zap.Uint("usage_id", usage.ID),
@@ -166,11 +209,18 @@ func (s *ResetService) resetMonthlyUsageWithDB(ctx context.Context, db *gorm.DB)
zap.Time("next_reset_at", *nextResetAt))
}
resetPackages = packages
if len(changes) > 0 {
if err := appendPackageUsageAudit(ctx, tx, s.auditWriter, constants.AuditActionPackageUsageTrafficReset, "重置套餐权益月流量", changes, nil, map[string]any{"reset_cycle": constants.PackageDataResetMonthly}); err != nil {
return err
}
}
return nil
})
if err != nil {
if len(resetPackages) > 0 {
recordPackageUsageFailure(ctx, s.db, s.auditWriter, constants.AuditActionPackageUsageTrafficReset, "重置套餐权益月流量失败", resetPackages[0], err)
}
return err
}
@@ -193,6 +243,7 @@ func (s *ResetService) resetYearlyUsageWithDB(ctx context.Context, db *gorm.DB)
err := tx.Where("data_reset_cycle = ?", constants.PackageDataResetYearly).
Where("next_reset_at <= ?", now).
Where("status IN ?", []int{constants.PackageUsageStatusActive, constants.PackageUsageStatusDepleted}).
Clauses(clause.Locking{Strength: "UPDATE"}).
Find(&packages).Error
if err != nil {
@@ -218,21 +269,40 @@ func (s *ResetService) resetYearlyUsageWithDB(ctx context.Context, db *gorm.DB)
"status": constants.PackageUsageStatusActive,
}
if err := tx.Model(&model.PackageUsage{}).
Where("id IN ?", packageIDs).
Updates(updates).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "批量重置年流量失败")
result := tx.Model(&model.PackageUsage{}).
Where("id IN ? AND next_reset_at <= ? AND status IN ?", packageIDs, now, []int{constants.PackageUsageStatusActive, constants.PackageUsageStatusDepleted}).
Updates(updates)
if result.Error != nil {
return errors.Wrap(errors.CodeDatabaseError, result.Error, "批量重置年流量失败")
}
if result.RowsAffected != int64(len(packages)) {
return errors.New(errors.CodeConflict, "年流量重置目标状态已变化")
}
changes := make([]packageUsageAuditChange, 0, len(packages))
for _, usage := range packages {
beforeData := packageUsageStateData(usage)
usage.DataUsageMB = 0
usage.LastResetAt = &now
usage.NextResetAt = &nextReset
usage.Status = constants.PackageUsageStatusActive
changes = append(changes, packageUsageAuditChange{Usage: usage, BeforeData: beforeData, AfterData: packageUsageStateData(usage)})
}
resetPackages = packages
if err := appendPackageUsageAudit(ctx, tx, s.auditWriter, constants.AuditActionPackageUsageTrafficReset, "重置套餐权益年流量", changes, nil, map[string]any{"reset_cycle": constants.PackageDataResetYearly}); err != nil {
return err
}
s.logger.Info("年流量重置完成",
zap.Int("count", len(packages)),
zap.Time("next_reset_at", nextReset))
resetPackages = packages
return nil
})
if err != nil {
if len(resetPackages) > 0 {
recordPackageUsageFailure(ctx, s.db, s.auditWriter, constants.AuditActionPackageUsageTrafficReset, "重置套餐权益年流量失败", resetPackages[0], err)
}
return err
}