fix: 调整订阅计费时间窗口为每日0点
- 窗口激活/重置时使用当天零点而非精确时间 - 使用服务器本地时区计算零点(支持 UTC+8 等时区) - 窗口重置时失效 Redis 缓存,避免数据不一致
This commit is contained in:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user