package service import ( "context" "errors" "time" ) // ErrUserPlatformQuotaNotFound service 层 sentinel:quota 记录不存在。 // adapter 将 repository.ErrUserPlatformQuotaNotFound 包装为此错误, // handler 只需引用 service 包,无需直接依赖 repository 包。 var ErrUserPlatformQuotaNotFound = errors.New("user platform quota not found") // UserPlatformQuotaRecord service 层传输结构体(与 repository 层解耦)。 type UserPlatformQuotaRecord struct { UserID int64 Platform string DailyLimitUSD *float64 WeeklyLimitUSD *float64 MonthlyLimitUSD *float64 DailyUsageUSD float64 WeeklyUsageUSD float64 MonthlyUsageUSD float64 // 窗口起始时间(可选,用于未来 reset 校验) DailyWindowStart *time.Time WeeklyWindowStart *time.Time MonthlyWindowStart *time.Time } // UserPlatformQuotaRepository 定义 service 层所需的 user × platform quota 数据访问端口。 // repository 包的 userPlatformQuotaRepository 实现此接口。 type UserPlatformQuotaRepository interface { // GetByUserPlatform 查询单条配额记录,未找到时返回 (nil, nil)。 GetByUserPlatform(ctx context.Context, userID int64, platform string) (*UserPlatformQuotaRecord, error) // BulkInsertInitial 幂等批量插入初始配额记录(ON CONFLICT DO NOTHING)。 BulkInsertInitial(ctx context.Context, records []UserPlatformQuotaRecord) error // IncrementUsageWithReset 原子地累加用量,若窗口已过期则先重置再累加。 IncrementUsageWithReset(ctx context.Context, userID int64, platform string, cost float64, now time.Time) error // ListByUser 查询用户的所有平台配额记录。 ListByUser(ctx context.Context, userID int64) ([]UserPlatformQuotaRecord, error) // UpsertForUser 全量替换该用户所有平台限额配置(事务内): // 1. 软删除未在 records 中出现的所有 active 行 // 2. 对 records 中每条:UPDATE 已存在的(含重新激活软删行);UPDATE 未命中时 INSERT // 仅改 *_limit_usd + deleted_at + updated_at,保留 *_usage_usd / *_window_start。 // records 为空时仅执行步骤 1。 UpsertForUser(ctx context.Context, userID int64, records []UserPlatformQuotaRecord) error // ResetExpiredWindow 重置指定窗口("daily"|"weekly"|"monthly")的用量与起始时间。 // 未命中活跃记录时返回(service-side wrapper of repository.ErrUserPlatformQuotaNotFound)。 ResetExpiredWindow(ctx context.Context, userID int64, platform string, window string, newStart time.Time) error }