diff --git a/internal/service/package/reset_service.go b/internal/service/package/reset_service.go index f1eadeb..ee87a8d 100644 --- a/internal/service/package/reset_service.go +++ b/internal/service/package/reset_service.go @@ -141,6 +141,10 @@ func (s *ResetService) resetMonthlyUsageWithDB(ctx context.Context, db *gorm.DB) var resetPackages []*model.PackageUsage err := db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + if err := s.backfillMissingMonthlyResetAt(tx, now); err != nil { + return err + } + var packages []*model.PackageUsage err := tx.Where("data_reset_cycle = ?", constants.PackageDataResetMonthly). Where("next_reset_at <= ?", now). @@ -228,6 +232,66 @@ func (s *ResetService) resetMonthlyUsageWithDB(ctx context.Context, db *gorm.DB) return nil } +type monthlyResetBackfillCandidate struct { + ID uint + ActivatedAt *time.Time + ExpiresAt *time.Time + CalendarType string + DurationMonths int + DurationDays int +} + +// backfillMissingMonthlyResetAt 为迁移套餐补齐缺失的下次流量重置时间。 +func (s *ResetService) backfillMissingMonthlyResetAt(tx *gorm.DB, now time.Time) error { + var candidates []monthlyResetBackfillCandidate + if err := tx.Table("tb_package_usage AS usage"). + Select("usage.id, usage.activated_at, usage.expires_at, pkg.calendar_type, pkg.duration_months, pkg.duration_days"). + Joins("JOIN tb_package AS pkg ON pkg.id = usage.package_id AND pkg.deleted_at IS NULL"). + Where("usage.data_reset_cycle = ?", constants.PackageDataResetMonthly). + Where("usage.next_reset_at IS NULL"). + Where("usage.status IN ?", []int{constants.PackageUsageStatusActive, constants.PackageUsageStatusDepleted}). + Find(&candidates).Error; err != nil { + return errors.Wrap(errors.CodeDatabaseError, err, "查询待补齐重置时间的套餐失败") + } + + for _, candidate := range candidates { + nextResetAt := calculateMissingMonthlyResetAt(candidate, now) + if nextResetAt == nil { + s.logger.Warn("缺少套餐计时信息,无法补齐月流量重置时间", zap.Uint("usage_id", candidate.ID)) + continue + } + + result := tx.Model(&model.PackageUsage{}). + Where("id = ? AND next_reset_at IS NULL", candidate.ID). + Update("next_reset_at", *nextResetAt) + if result.Error != nil { + return errors.Wrap(errors.CodeDatabaseError, result.Error, "补齐套餐月流量重置时间失败") + } + if result.RowsAffected > 0 { + s.logger.Info("已补齐套餐月流量重置时间", + zap.Uint("usage_id", candidate.ID), + zap.Time("next_reset_at", *nextResetAt)) + } + } + + return nil +} + +func calculateMissingMonthlyResetAt(candidate monthlyResetBackfillCandidate, now time.Time) *time.Time { + activatedAt := now + if candidate.ActivatedAt != nil && !candidate.ActivatedAt.IsZero() { + activatedAt = *candidate.ActivatedAt + } else if candidate.CalendarType == constants.PackageCalendarTypeByDay { + validityDays := calculateByDayValidityDays(candidate.DurationMonths, candidate.DurationDays) + if candidate.ExpiresAt == nil || candidate.ExpiresAt.IsZero() || validityDays <= 0 { + return nil + } + activatedAt = candidate.ExpiresAt.AddDate(0, 0, -validityDays) + } + + return CalculateNextResetTime(constants.PackageDataResetMonthly, candidate.CalendarType, now, activatedAt) +} + // ResetYearlyUsage 任务 11.6-11.7: 重置年流量 func (s *ResetService) ResetYearlyUsage(ctx context.Context) error { return s.resetYearlyUsageWithDB(ctx, s.db)