From 5c2e7ae2657d2473d9111c43875ce340ba556a0f Mon Sep 17 00:00:00 2001 From: shaw Date: Sat, 20 Dec 2025 11:33:06 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=B0=83=E6=95=B4=E8=AE=A2=E9=98=85?= =?UTF-8?q?=E8=AE=A1=E8=B4=B9=E6=97=B6=E9=97=B4=E7=AA=97=E5=8F=A3=E4=B8=BA?= =?UTF-8?q?=E6=AF=8F=E6=97=A50=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 窗口激活/重置时使用当天零点而非精确时间 - 使用服务器本地时区计算零点(支持 UTC+8 等时区) - 窗口重置时失效 Redis 缓存,避免数据不一致 --- .../internal/service/subscription_service.go | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/backend/internal/service/subscription_service.go b/backend/internal/service/subscription_service.go index 590b09e8..d348b5f3 100644 --- a/backend/internal/service/subscription_service.go +++ b/backend/internal/service/subscription_service.go @@ -354,45 +354,61 @@ func (s *SubscriptionService) List(ctx context.Context, page, pageSize int, user 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 检查并激活窗口(首次使用时) func (s *SubscriptionService) CheckAndActivateWindow(ctx context.Context, sub *model.UserSubscription) error { if sub.IsWindowActivated() { 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 检查并重置过期的窗口 func (s *SubscriptionService) CheckAndResetWindows(ctx context.Context, sub *model.UserSubscription) error { - now := time.Now() + // 使用当天零点作为新窗口起始时间 + windowStart := startOfDay(time.Now()) + needsInvalidateCache := false // 日窗口重置(24小时) 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 } - sub.DailyWindowStart = &now + sub.DailyWindowStart = &windowStart sub.DailyUsageUSD = 0 + needsInvalidateCache = true } // 周窗口重置(7天) 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 } - sub.WeeklyWindowStart = &now + sub.WeeklyWindowStart = &windowStart sub.WeeklyUsageUSD = 0 + needsInvalidateCache = true } // 月窗口重置(30天) 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 } - sub.MonthlyWindowStart = &now + sub.MonthlyWindowStart = &windowStart sub.MonthlyUsageUSD = 0 + needsInvalidateCache = true + } + + // 如果有窗口被重置,失效 Redis 缓存以保持一致性 + if needsInvalidateCache && s.billingCacheService != nil { + _ = s.billingCacheService.InvalidateSubscription(ctx, sub.UserID, sub.GroupID) } return nil