package packagepkg import ( "context" "time" "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" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/redis/go-redis/v9" "go.uber.org/zap" "gorm.io/gorm" ) type ResetService struct { db *gorm.DB redis *redis.Client packageUsageStore *postgres.PackageUsageStore logger *zap.Logger } func NewResetService( db *gorm.DB, redis *redis.Client, packageUsageStore *postgres.PackageUsageStore, logger *zap.Logger, ) *ResetService { return &ResetService{ db: db, redis: redis, packageUsageStore: packageUsageStore, logger: logger, } } // ResetDailyUsage 任务 11.2-11.3: 重置日流量 func (s *ResetService) ResetDailyUsage(ctx context.Context) error { return s.resetDailyUsageWithDB(ctx, s.db) } // resetDailyUsageWithDB 内部方法,支持传入 DB/TX func (s *ResetService) resetDailyUsageWithDB(ctx context.Context, db *gorm.DB) error { now := time.Now() return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // 查询需要重置的套餐 var packages []*model.PackageUsage err := tx.Where("data_reset_cycle = ?", constants.PackageDataResetDaily). Where("next_reset_at <= ?", now). Where("status IN ?", []int{constants.PackageUsageStatusActive, constants.PackageUsageStatusDepleted}). Find(&packages).Error if err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "查询待重置套餐失败") } if len(packages) == 0 { s.logger.Info("没有需要重置的日流量套餐") return nil } // 批量重置 packageIDs := make([]uint, len(packages)) for i, pkg := range packages { packageIDs[i] = pkg.ID } // 计算下次重置时间(明天 00:00:00) nextReset := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location()) // 批量更新 updates := map[string]interface{}{ "data_usage_mb": 0, "last_reset_at": now, "next_reset_at": nextReset, "status": constants.PackageUsageStatusActive, // 重置后恢复为生效中 } if err := tx.Model(&model.PackageUsage{}). Where("id IN ?", packageIDs). Updates(updates).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "批量重置日流量失败") } s.logger.Info("日流量重置完成", zap.Int("count", len(packages)), zap.Time("next_reset_at", nextReset)) return nil }) } // ResetMonthlyUsage 任务 11.4-11.5: 重置月流量 func (s *ResetService) ResetMonthlyUsage(ctx context.Context) error { return s.resetMonthlyUsageWithDB(ctx, s.db) } // resetMonthlyUsageWithDB 内部方法,支持传入 DB/TX func (s *ResetService) resetMonthlyUsageWithDB(ctx context.Context, db *gorm.DB) error { now := time.Now() return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // 查询需要重置的套餐 var packages []*model.PackageUsage err := tx.Where("data_reset_cycle = ?", constants.PackageDataResetMonthly). Where("next_reset_at <= ?", now). Where("status IN ?", []int{constants.PackageUsageStatusActive, constants.PackageUsageStatusDepleted}). Find(&packages).Error if err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "查询待重置套餐失败") } if len(packages) == 0 { s.logger.Info("没有需要重置的月流量套餐") return nil } // 按套餐分组处理(因为需要区分联通27号 vs 其他1号) for _, pkg := range packages { // 查询运营商信息以确定计费日 // 只有单卡套餐才根据运营商判断,设备级套餐统一使用1号计费 billingDay := 1 if pkg.IotCardID != 0 { var card model.IotCard if err := tx.First(&card, pkg.IotCardID).Error; err == nil { var carrier model.Carrier if err := tx.First(&carrier, card.CarrierID).Error; err == nil { if carrier.CarrierType == "CUCC" { billingDay = 27 } } } } // 设备级套餐默认使用1号计费(已在 billingDay := 1 初始化) // 计算下次重置时间 nextReset := calculateNextMonthlyResetTime(now, billingDay) // 更新套餐 updates := map[string]interface{}{ "data_usage_mb": 0, "last_reset_at": now, "next_reset_at": nextReset, "status": constants.PackageUsageStatusActive, // 重置后恢复为生效中 } if err := tx.Model(pkg).Updates(updates).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "重置月流量失败") } s.logger.Info("月流量已重置", zap.Uint("usage_id", pkg.ID), zap.Int("billing_day", billingDay), zap.Time("next_reset_at", nextReset)) } return nil }) } // ResetYearlyUsage 任务 11.6-11.7: 重置年流量 func (s *ResetService) ResetYearlyUsage(ctx context.Context) error { return s.resetYearlyUsageWithDB(ctx, s.db) } // resetYearlyUsageWithDB 内部方法,支持传入 DB/TX func (s *ResetService) resetYearlyUsageWithDB(ctx context.Context, db *gorm.DB) error { now := time.Now() return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // 查询需要重置的套餐 var packages []*model.PackageUsage err := tx.Where("data_reset_cycle = ?", constants.PackageDataResetYearly). Where("next_reset_at <= ?", now). Where("status IN ?", []int{constants.PackageUsageStatusActive, constants.PackageUsageStatusDepleted}). Find(&packages).Error if err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "查询待重置套餐失败") } if len(packages) == 0 { s.logger.Info("没有需要重置的年流量套餐") return nil } // 批量重置 packageIDs := make([]uint, len(packages)) for i, pkg := range packages { packageIDs[i] = pkg.ID } // 计算下次重置时间(明年 1月1日 00:00:00) nextReset := time.Date(now.Year()+1, 1, 1, 0, 0, 0, 0, now.Location()) // 批量更新 updates := map[string]interface{}{ "data_usage_mb": 0, "last_reset_at": now, "next_reset_at": nextReset, "status": constants.PackageUsageStatusActive, // 重置后恢复为生效中 } if err := tx.Model(&model.PackageUsage{}). Where("id IN ?", packageIDs). Updates(updates).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "批量重置年流量失败") } s.logger.Info("年流量重置完成", zap.Int("count", len(packages)), zap.Time("next_reset_at", nextReset)) return nil }) } // calculateNextMonthlyResetTime 计算下次月重置时间 func calculateNextMonthlyResetTime(now time.Time, billingDay int) time.Time { currentDay := now.Day() targetMonth := now.Month() targetYear := now.Year() // 如果当前日期 >= 计费日,下次重置是下月计费日 if currentDay >= billingDay { targetMonth++ if targetMonth > 12 { targetMonth = 1 targetYear++ } } // 处理月末天数不足的情况(例如2月没有27日) maxDay := time.Date(targetYear, targetMonth+1, 0, 0, 0, 0, 0, now.Location()).Day() if billingDay > maxDay { billingDay = maxDay } return time.Date(targetYear, targetMonth, billingDay, 0, 0, 0, 0, now.Location()) }