user_platform_quota_port.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. package service
  2. import (
  3. "context"
  4. "errors"
  5. "time"
  6. )
  7. // ErrUserPlatformQuotaNotFound service 层 sentinel:quota 记录不存在。
  8. // adapter 将 repository.ErrUserPlatformQuotaNotFound 包装为此错误,
  9. // handler 只需引用 service 包,无需直接依赖 repository 包。
  10. var ErrUserPlatformQuotaNotFound = errors.New("user platform quota not found")
  11. // ErrUserPlatformQuotaFKViolation service 层 sentinel:批量 snapshot UPSERT 时存在
  12. // user_id 不在 users 表的记录(外键违反)。adapter 负责将 repository 层同名 sentinel 包装为此错误。
  13. var ErrUserPlatformQuotaFKViolation = errors.New("user platform quota snapshot FK violation")
  14. // UserPlatformQuotaSnapshot 是 service 层 flusher 向 DB 写入快照时使用的传输结构。
  15. // 字段语义与 repository.UserPlatformQuotaSnapshot 完全对应,由 adapter 负责转换。
  16. type UserPlatformQuotaSnapshot struct {
  17. UserID int64
  18. Platform string
  19. DailyUsageUSD float64
  20. WeeklyUsageUSD float64
  21. MonthlyUsageUSD float64
  22. DailyWindowStart time.Time
  23. WeeklyWindowStart time.Time
  24. MonthlyWindowStart time.Time
  25. }
  26. // UserPlatformQuotaRecord service 层传输结构体(与 repository 层解耦)。
  27. type UserPlatformQuotaRecord struct {
  28. UserID int64
  29. Platform string
  30. DailyLimitUSD *float64
  31. WeeklyLimitUSD *float64
  32. MonthlyLimitUSD *float64
  33. DailyUsageUSD float64
  34. WeeklyUsageUSD float64
  35. MonthlyUsageUSD float64
  36. // 窗口起始时间(可选,用于未来 reset 校验)
  37. DailyWindowStart *time.Time
  38. WeeklyWindowStart *time.Time
  39. MonthlyWindowStart *time.Time
  40. }
  41. // UserPlatformQuotaRepository 定义 service 层所需的 user × platform quota 数据访问端口。
  42. // repository 包的 userPlatformQuotaRepository 实现此接口。
  43. type UserPlatformQuotaRepository interface {
  44. // GetByUserPlatform 查询单条配额记录,未找到时返回 (nil, nil)。
  45. GetByUserPlatform(ctx context.Context, userID int64, platform string) (*UserPlatformQuotaRecord, error)
  46. // BulkInsertInitial 幂等批量插入初始配额记录(ON CONFLICT DO NOTHING)。
  47. BulkInsertInitial(ctx context.Context, records []UserPlatformQuotaRecord) error
  48. // IncrementUsageWithReset 原子地累加用量,若窗口已过期则先重置再累加。
  49. IncrementUsageWithReset(ctx context.Context, userID int64, platform string, cost float64, now time.Time) error
  50. // ListByUser 查询用户的所有平台配额记录。
  51. ListByUser(ctx context.Context, userID int64) ([]UserPlatformQuotaRecord, error)
  52. // UpsertForUser 全量替换该用户所有平台限额配置(事务内):
  53. // 1. 软删除未在 records 中出现的所有 active 行
  54. // 2. 对 records 中每条:UPDATE 已存在的(含重新激活软删行);UPDATE 未命中时 INSERT
  55. // 仅改 *_limit_usd + deleted_at + updated_at,保留 *_usage_usd / *_window_start。
  56. // records 为空时仅执行步骤 1。
  57. UpsertForUser(ctx context.Context, userID int64, records []UserPlatformQuotaRecord) error
  58. // ResetExpiredWindow 重置指定窗口("daily"|"weekly"|"monthly")的用量与起始时间。
  59. // 未命中活跃记录时返回(service-side wrapper of repository.ErrUserPlatformQuotaNotFound)。
  60. ResetExpiredWindow(ctx context.Context, userID int64, platform string, window string, newStart time.Time) error
  61. // BatchSnapshotUsage 绝对值覆盖写入整批 usage 快照。FK 违反返回 ErrUserPlatformQuotaFKViolation。
  62. BatchSnapshotUsage(ctx context.Context, snapshots []UserPlatformQuotaSnapshot, now time.Time) error
  63. }