user_platform_quota_service_adapter.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. package repository
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "time"
  7. "github.com/Wei-Shaw/sub2api/internal/service"
  8. )
  9. // userPlatformQuotaServiceAdapter 将 repository 层的 userPlatformQuotaRepository
  10. // 适配为 service.UserPlatformQuotaRepository 接口(返回 *service.UserPlatformQuotaRecord)。
  11. type userPlatformQuotaServiceAdapter struct {
  12. inner *userPlatformQuotaRepository
  13. }
  14. // NewUserPlatformQuotaServiceAdapter 将 UserPlatformQuotaRepository 实现包装为
  15. // 满足 service.UserPlatformQuotaRepository 接口的适配器。
  16. func NewUserPlatformQuotaServiceAdapter(repo UserPlatformQuotaRepository) service.UserPlatformQuotaRepository {
  17. impl, ok := repo.(*userPlatformQuotaRepository)
  18. if !ok {
  19. // 非标准实现(如测试 fake),通过通用适配器包装
  20. return &genericUserPlatformQuotaAdapter{inner: repo}
  21. }
  22. return &userPlatformQuotaServiceAdapter{inner: impl}
  23. }
  24. func (a *userPlatformQuotaServiceAdapter) GetByUserPlatform(ctx context.Context, userID int64, platform string) (*service.UserPlatformQuotaRecord, error) {
  25. rec, err := a.inner.GetByUserPlatform(ctx, userID, platform)
  26. if err != nil || rec == nil {
  27. return nil, err
  28. }
  29. return toServiceRecord(rec), nil
  30. }
  31. // IncrementUsageWithReset 原子累加 cost 到 (user, platform) 三个窗口的用量。
  32. func (a *userPlatformQuotaServiceAdapter) IncrementUsageWithReset(ctx context.Context, userID int64, platform string, cost float64, now time.Time) error {
  33. return a.inner.IncrementUsageWithReset(ctx, userID, platform, cost, now)
  34. }
  35. // ListByUser 查询用户的所有平台配额记录。
  36. func (a *userPlatformQuotaServiceAdapter) ListByUser(ctx context.Context, userID int64) ([]service.UserPlatformQuotaRecord, error) {
  37. rows, err := a.inner.ListByUser(ctx, userID)
  38. if err != nil {
  39. return nil, err
  40. }
  41. out := make([]service.UserPlatformQuotaRecord, len(rows))
  42. for i, r := range rows {
  43. out[i] = service.UserPlatformQuotaRecord{
  44. UserID: r.UserID,
  45. Platform: r.Platform,
  46. DailyLimitUSD: r.DailyLimitUSD,
  47. WeeklyLimitUSD: r.WeeklyLimitUSD,
  48. MonthlyLimitUSD: r.MonthlyLimitUSD,
  49. DailyUsageUSD: r.DailyUsageUSD,
  50. WeeklyUsageUSD: r.WeeklyUsageUSD,
  51. MonthlyUsageUSD: r.MonthlyUsageUSD,
  52. DailyWindowStart: r.DailyWindowStart,
  53. WeeklyWindowStart: r.WeeklyWindowStart,
  54. MonthlyWindowStart: r.MonthlyWindowStart,
  55. }
  56. }
  57. return out, nil
  58. }
  59. // BulkInsertInitial 将 service.UserPlatformQuotaRecord 切片转换后调用底层 repo。
  60. func (a *userPlatformQuotaServiceAdapter) BulkInsertInitial(ctx context.Context, records []service.UserPlatformQuotaRecord) error {
  61. repoRecords := make([]UserPlatformQuotaRecord, len(records))
  62. for i, r := range records {
  63. repoRecords[i] = UserPlatformQuotaRecord{
  64. UserID: r.UserID,
  65. Platform: r.Platform,
  66. DailyLimitUSD: r.DailyLimitUSD,
  67. WeeklyLimitUSD: r.WeeklyLimitUSD,
  68. MonthlyLimitUSD: r.MonthlyLimitUSD,
  69. }
  70. }
  71. return a.inner.BulkInsertInitial(ctx, repoRecords)
  72. }
  73. // UpsertForUser 全量替换该用户所有平台限额。
  74. func (a *userPlatformQuotaServiceAdapter) UpsertForUser(ctx context.Context, userID int64, records []service.UserPlatformQuotaRecord) error {
  75. repoRecords := toRepoRecords(records)
  76. return a.inner.UpsertForUser(ctx, userID, repoRecords)
  77. }
  78. // ResetExpiredWindow 转发至 repository.ResetExpiredWindow,并将 repository sentinel 包装为 service sentinel。
  79. func (a *userPlatformQuotaServiceAdapter) ResetExpiredWindow(ctx context.Context, userID int64, platform string, window string, newStart time.Time) error {
  80. err := a.inner.ResetExpiredWindow(ctx, userID, platform, window, newStart)
  81. if errors.Is(err, ErrUserPlatformQuotaNotFound) {
  82. return fmt.Errorf("%w: %w", service.ErrUserPlatformQuotaNotFound, err)
  83. }
  84. return err
  85. }
  86. // BatchSnapshotUsage 转换 []service.UserPlatformQuotaSnapshot → []UserPlatformQuotaSnapshot,
  87. // 调底层 repo,并将 repository FK sentinel 包装为 service sentinel。
  88. func (a *userPlatformQuotaServiceAdapter) BatchSnapshotUsage(ctx context.Context, snapshots []service.UserPlatformQuotaSnapshot, now time.Time) error {
  89. repoSnaps := make([]UserPlatformQuotaSnapshot, len(snapshots))
  90. for i, s := range snapshots {
  91. repoSnaps[i] = UserPlatformQuotaSnapshot{
  92. UserID: s.UserID,
  93. Platform: s.Platform,
  94. DailyUsageUSD: s.DailyUsageUSD,
  95. WeeklyUsageUSD: s.WeeklyUsageUSD,
  96. MonthlyUsageUSD: s.MonthlyUsageUSD,
  97. DailyWindowStart: s.DailyWindowStart,
  98. WeeklyWindowStart: s.WeeklyWindowStart,
  99. MonthlyWindowStart: s.MonthlyWindowStart,
  100. }
  101. }
  102. err := a.inner.BatchSnapshotUsage(ctx, repoSnaps, now)
  103. if errors.Is(err, ErrUserPlatformQuotaFKViolation) {
  104. return fmt.Errorf("%w: %v", service.ErrUserPlatformQuotaFKViolation, err)
  105. }
  106. return err
  107. }
  108. // genericUserPlatformQuotaAdapter 通过通用接口适配(用于测试 fake 或非标准实现)。
  109. type genericUserPlatformQuotaAdapter struct {
  110. inner UserPlatformQuotaRepository
  111. }
  112. func (a *genericUserPlatformQuotaAdapter) GetByUserPlatform(ctx context.Context, userID int64, platform string) (*service.UserPlatformQuotaRecord, error) {
  113. rec, err := a.inner.GetByUserPlatform(ctx, userID, platform)
  114. if err != nil || rec == nil {
  115. return nil, err
  116. }
  117. return toServiceRecord(rec), nil
  118. }
  119. // IncrementUsageWithReset 原子累加 cost(通用 adapter 实现)。
  120. func (a *genericUserPlatformQuotaAdapter) IncrementUsageWithReset(ctx context.Context, userID int64, platform string, cost float64, now time.Time) error {
  121. return a.inner.IncrementUsageWithReset(ctx, userID, platform, cost, now)
  122. }
  123. // ListByUser 查询用户的所有平台配额记录(通用 adapter 实现)。
  124. func (a *genericUserPlatformQuotaAdapter) ListByUser(ctx context.Context, userID int64) ([]service.UserPlatformQuotaRecord, error) {
  125. rows, err := a.inner.ListByUser(ctx, userID)
  126. if err != nil {
  127. return nil, err
  128. }
  129. out := make([]service.UserPlatformQuotaRecord, len(rows))
  130. for i, r := range rows {
  131. out[i] = service.UserPlatformQuotaRecord{
  132. UserID: r.UserID,
  133. Platform: r.Platform,
  134. DailyLimitUSD: r.DailyLimitUSD,
  135. WeeklyLimitUSD: r.WeeklyLimitUSD,
  136. MonthlyLimitUSD: r.MonthlyLimitUSD,
  137. DailyUsageUSD: r.DailyUsageUSD,
  138. WeeklyUsageUSD: r.WeeklyUsageUSD,
  139. MonthlyUsageUSD: r.MonthlyUsageUSD,
  140. DailyWindowStart: r.DailyWindowStart,
  141. WeeklyWindowStart: r.WeeklyWindowStart,
  142. MonthlyWindowStart: r.MonthlyWindowStart,
  143. }
  144. }
  145. return out, nil
  146. }
  147. // BulkInsertInitial 将 service.UserPlatformQuotaRecord 切片转换后调用底层 generic repo。
  148. func (a *genericUserPlatformQuotaAdapter) BulkInsertInitial(ctx context.Context, records []service.UserPlatformQuotaRecord) error {
  149. repoRecords := make([]UserPlatformQuotaRecord, len(records))
  150. for i, r := range records {
  151. repoRecords[i] = UserPlatformQuotaRecord{
  152. UserID: r.UserID,
  153. Platform: r.Platform,
  154. DailyLimitUSD: r.DailyLimitUSD,
  155. WeeklyLimitUSD: r.WeeklyLimitUSD,
  156. MonthlyLimitUSD: r.MonthlyLimitUSD,
  157. }
  158. }
  159. return a.inner.BulkInsertInitial(ctx, repoRecords)
  160. }
  161. // UpsertForUser 全量替换(通用 adapter 实现)。
  162. func (a *genericUserPlatformQuotaAdapter) UpsertForUser(ctx context.Context, userID int64, records []service.UserPlatformQuotaRecord) error {
  163. repoRecords := toRepoRecords(records)
  164. return a.inner.UpsertForUser(ctx, userID, repoRecords)
  165. }
  166. // ResetExpiredWindow 转发至 repository.ResetExpiredWindow(通用 adapter),并包装 sentinel。
  167. func (a *genericUserPlatformQuotaAdapter) ResetExpiredWindow(ctx context.Context, userID int64, platform string, window string, newStart time.Time) error {
  168. err := a.inner.ResetExpiredWindow(ctx, userID, platform, window, newStart)
  169. if errors.Is(err, ErrUserPlatformQuotaNotFound) {
  170. return fmt.Errorf("%w: %w", service.ErrUserPlatformQuotaNotFound, err)
  171. }
  172. return err
  173. }
  174. // BatchSnapshotUsage 转换 []service.UserPlatformQuotaSnapshot → []UserPlatformQuotaSnapshot(通用 adapter),
  175. // 并将 repository FK sentinel 包装为 service sentinel。
  176. func (a *genericUserPlatformQuotaAdapter) BatchSnapshotUsage(ctx context.Context, snapshots []service.UserPlatformQuotaSnapshot, now time.Time) error {
  177. repoSnaps := make([]UserPlatformQuotaSnapshot, len(snapshots))
  178. for i, s := range snapshots {
  179. repoSnaps[i] = UserPlatformQuotaSnapshot{
  180. UserID: s.UserID,
  181. Platform: s.Platform,
  182. DailyUsageUSD: s.DailyUsageUSD,
  183. WeeklyUsageUSD: s.WeeklyUsageUSD,
  184. MonthlyUsageUSD: s.MonthlyUsageUSD,
  185. DailyWindowStart: s.DailyWindowStart,
  186. WeeklyWindowStart: s.WeeklyWindowStart,
  187. MonthlyWindowStart: s.MonthlyWindowStart,
  188. }
  189. }
  190. err := a.inner.BatchSnapshotUsage(ctx, repoSnaps, now)
  191. if errors.Is(err, ErrUserPlatformQuotaFKViolation) {
  192. return fmt.Errorf("%w: %v", service.ErrUserPlatformQuotaFKViolation, err)
  193. }
  194. return err
  195. }
  196. // toServiceRecord 将 repository.UserPlatformQuotaRecord 转换为 service.UserPlatformQuotaRecord。
  197. func toServiceRecord(rec *UserPlatformQuotaRecord) *service.UserPlatformQuotaRecord {
  198. return &service.UserPlatformQuotaRecord{
  199. UserID: rec.UserID,
  200. Platform: rec.Platform,
  201. DailyLimitUSD: rec.DailyLimitUSD,
  202. WeeklyLimitUSD: rec.WeeklyLimitUSD,
  203. MonthlyLimitUSD: rec.MonthlyLimitUSD,
  204. DailyUsageUSD: rec.DailyUsageUSD,
  205. WeeklyUsageUSD: rec.WeeklyUsageUSD,
  206. MonthlyUsageUSD: rec.MonthlyUsageUSD,
  207. DailyWindowStart: rec.DailyWindowStart,
  208. WeeklyWindowStart: rec.WeeklyWindowStart,
  209. MonthlyWindowStart: rec.MonthlyWindowStart,
  210. }
  211. }
  212. // toRepoRecords 将 service.UserPlatformQuotaRecord 切片转换为 repository.UserPlatformQuotaRecord(含 limit 字段,含 usage/window_start)。
  213. func toRepoRecords(records []service.UserPlatformQuotaRecord) []UserPlatformQuotaRecord {
  214. out := make([]UserPlatformQuotaRecord, len(records))
  215. for i, r := range records {
  216. out[i] = UserPlatformQuotaRecord{
  217. UserID: r.UserID,
  218. Platform: r.Platform,
  219. DailyLimitUSD: r.DailyLimitUSD,
  220. WeeklyLimitUSD: r.WeeklyLimitUSD,
  221. MonthlyLimitUSD: r.MonthlyLimitUSD,
  222. DailyUsageUSD: r.DailyUsageUSD,
  223. WeeklyUsageUSD: r.WeeklyUsageUSD,
  224. MonthlyUsageUSD: r.MonthlyUsageUSD,
  225. DailyWindowStart: r.DailyWindowStart,
  226. WeeklyWindowStart: r.WeeklyWindowStart,
  227. MonthlyWindowStart: r.MonthlyWindowStart,
  228. }
  229. }
  230. return out
  231. }