fix: 调整订阅计费时间窗口为每日0点

- 窗口激活/重置时使用当天零点而非精确时间
- 使用服务器本地时区计算零点(支持 UTC+8 等时区)
- 窗口重置时失效 Redis 缓存,避免数据不一致
This commit is contained in:
shaw
2025-12-20 11:33:06 +08:00
parent 420bedd615
commit 5c2e7ae265

View File

@@ -354,45 +354,61 @@ func (s *SubscriptionService) List(ctx context.Context, page, pageSize int, user
return s.userSubRepo.List(ctx, params, userID, groupID, status) return s.userSubRepo.List(ctx, params, userID, groupID, status)
} }
// startOfDay 返回给定时间所在日期的零点(保持原时区)
func startOfDay(t time.Time) time.Time {
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
}
// CheckAndActivateWindow 检查并激活窗口(首次使用时) // CheckAndActivateWindow 检查并激活窗口(首次使用时)
func (s *SubscriptionService) CheckAndActivateWindow(ctx context.Context, sub *model.UserSubscription) error { func (s *SubscriptionService) CheckAndActivateWindow(ctx context.Context, sub *model.UserSubscription) error {
if sub.IsWindowActivated() { if sub.IsWindowActivated() {
return nil return nil
} }
now := time.Now() // 使用当天零点作为窗口起始时间
return s.userSubRepo.ActivateWindows(ctx, sub.ID, now) windowStart := startOfDay(time.Now())
return s.userSubRepo.ActivateWindows(ctx, sub.ID, windowStart)
} }
// CheckAndResetWindows 检查并重置过期的窗口 // CheckAndResetWindows 检查并重置过期的窗口
func (s *SubscriptionService) CheckAndResetWindows(ctx context.Context, sub *model.UserSubscription) error { func (s *SubscriptionService) CheckAndResetWindows(ctx context.Context, sub *model.UserSubscription) error {
now := time.Now() // 使用当天零点作为新窗口起始时间
windowStart := startOfDay(time.Now())
needsInvalidateCache := false
// 日窗口重置24小时 // 日窗口重置24小时
if sub.NeedsDailyReset() { if sub.NeedsDailyReset() {
if err := s.userSubRepo.ResetDailyUsage(ctx, sub.ID, now); err != nil { if err := s.userSubRepo.ResetDailyUsage(ctx, sub.ID, windowStart); err != nil {
return err return err
} }
sub.DailyWindowStart = &now sub.DailyWindowStart = &windowStart
sub.DailyUsageUSD = 0 sub.DailyUsageUSD = 0
needsInvalidateCache = true
} }
// 周窗口重置7天 // 周窗口重置7天
if sub.NeedsWeeklyReset() { if sub.NeedsWeeklyReset() {
if err := s.userSubRepo.ResetWeeklyUsage(ctx, sub.ID, now); err != nil { if err := s.userSubRepo.ResetWeeklyUsage(ctx, sub.ID, windowStart); err != nil {
return err return err
} }
sub.WeeklyWindowStart = &now sub.WeeklyWindowStart = &windowStart
sub.WeeklyUsageUSD = 0 sub.WeeklyUsageUSD = 0
needsInvalidateCache = true
} }
// 月窗口重置30天 // 月窗口重置30天
if sub.NeedsMonthlyReset() { if sub.NeedsMonthlyReset() {
if err := s.userSubRepo.ResetMonthlyUsage(ctx, sub.ID, now); err != nil { if err := s.userSubRepo.ResetMonthlyUsage(ctx, sub.ID, windowStart); err != nil {
return err return err
} }
sub.MonthlyWindowStart = &now sub.MonthlyWindowStart = &windowStart
sub.MonthlyUsageUSD = 0 sub.MonthlyUsageUSD = 0
needsInvalidateCache = true
}
// 如果有窗口被重置,失效 Redis 缓存以保持一致性
if needsInvalidateCache && s.billingCacheService != nil {
_ = s.billingCacheService.InvalidateSubscription(ctx, sub.UserID, sub.GroupID)
} }
return nil return nil