api_key.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. package service
  2. import (
  3. "time"
  4. "github.com/Wei-Shaw/sub2api/internal/pkg/ip"
  5. )
  6. // API Key status constants
  7. const (
  8. StatusAPIKeyActive = "active"
  9. StatusAPIKeyDisabled = "disabled"
  10. StatusAPIKeyQuotaExhausted = "quota_exhausted"
  11. StatusAPIKeyExpired = "expired"
  12. )
  13. // Rate limit window durations
  14. const (
  15. RateLimitWindow5h = 5 * time.Hour
  16. RateLimitWindow1d = 24 * time.Hour
  17. RateLimitWindow7d = 7 * 24 * time.Hour
  18. )
  19. // IsWindowExpired returns true if the window starting at windowStart has exceeded the given duration.
  20. // A nil windowStart is treated as expired — no initialized window means any accumulated usage is stale.
  21. func IsWindowExpired(windowStart *time.Time, duration time.Duration) bool {
  22. return windowStart == nil || time.Since(*windowStart) >= duration
  23. }
  24. type APIKey struct {
  25. ID int64
  26. UserID int64
  27. Key string
  28. KeyHash string // Historical hash-only credentials, never returned by DTOs.
  29. KeyPrefix string
  30. Name string
  31. GroupID *int64
  32. Status string
  33. IPWhitelist []string
  34. IPBlacklist []string
  35. // 预编译的 IP 规则,用于认证热路径避免重复 ParseIP/ParseCIDR。
  36. CompiledIPWhitelist *ip.CompiledIPRules `json:"-"`
  37. CompiledIPBlacklist *ip.CompiledIPRules `json:"-"`
  38. LastUsedAt *time.Time
  39. LastUsedIP *string
  40. CreatedAt time.Time
  41. UpdatedAt time.Time
  42. User *User
  43. Group *Group
  44. CurrentConcurrency int
  45. // Quota fields
  46. Quota float64 // Quota limit in USD (0 = unlimited)
  47. QuotaUsed float64 // Used quota amount
  48. ExpiresAt *time.Time // Expiration time (nil = never expires)
  49. // Rate limit fields
  50. RateLimit5h float64 // Rate limit in USD per 5h (0 = unlimited)
  51. RateLimit1d float64 // Rate limit in USD per 1d (0 = unlimited)
  52. RateLimit7d float64 // Rate limit in USD per 7d (0 = unlimited)
  53. Usage5h float64 // Used amount in current 5h window
  54. Usage1d float64 // Used amount in current 1d window
  55. Usage7d float64 // Used amount in current 7d window
  56. Window5hStart *time.Time // Start of current 5h window
  57. Window1dStart *time.Time // Start of current 1d window
  58. Window7dStart *time.Time // Start of current 7d window
  59. }
  60. func (k *APIKey) IsActive() bool {
  61. return k.Status == StatusActive
  62. }
  63. // HasRateLimits returns true if any rate limit window is configured
  64. func (k *APIKey) HasRateLimits() bool {
  65. return k.RateLimit5h > 0 || k.RateLimit1d > 0 || k.RateLimit7d > 0
  66. }
  67. // IsExpired checks if the API key has expired
  68. func (k *APIKey) IsExpired() bool {
  69. if k.ExpiresAt == nil {
  70. return false
  71. }
  72. return time.Now().After(*k.ExpiresAt)
  73. }
  74. // IsQuotaExhausted checks if the API key quota is exhausted
  75. func (k *APIKey) IsQuotaExhausted() bool {
  76. if k.Quota <= 0 {
  77. return false // unlimited
  78. }
  79. return k.QuotaUsed >= k.Quota
  80. }
  81. // GetQuotaRemaining returns remaining quota (-1 for unlimited)
  82. func (k *APIKey) GetQuotaRemaining() float64 {
  83. if k.Quota <= 0 {
  84. return -1 // unlimited
  85. }
  86. remaining := k.Quota - k.QuotaUsed
  87. if remaining < 0 {
  88. return 0
  89. }
  90. return remaining
  91. }
  92. // GetDaysUntilExpiry returns days until expiry (-1 for never expires)
  93. func (k *APIKey) GetDaysUntilExpiry() int {
  94. if k.ExpiresAt == nil {
  95. return -1 // never expires
  96. }
  97. duration := time.Until(*k.ExpiresAt)
  98. if duration < 0 {
  99. return 0
  100. }
  101. return int(duration.Hours() / 24)
  102. }
  103. // EffectiveUsage5h returns the 5h window usage, or 0 if the window has expired.
  104. func (k *APIKey) EffectiveUsage5h() float64 {
  105. if IsWindowExpired(k.Window5hStart, RateLimitWindow5h) {
  106. return 0
  107. }
  108. return k.Usage5h
  109. }
  110. // EffectiveUsage1d returns the 1d window usage, or 0 if the window has expired.
  111. func (k *APIKey) EffectiveUsage1d() float64 {
  112. if IsWindowExpired(k.Window1dStart, RateLimitWindow1d) {
  113. return 0
  114. }
  115. return k.Usage1d
  116. }
  117. // EffectiveUsage7d returns the 7d window usage, or 0 if the window has expired.
  118. func (k *APIKey) EffectiveUsage7d() float64 {
  119. if IsWindowExpired(k.Window7dStart, RateLimitWindow7d) {
  120. return 0
  121. }
  122. return k.Usage7d
  123. }
  124. // APIKeyListFilters holds optional filtering parameters for listing API keys.
  125. type APIKeyListFilters struct {
  126. Search string
  127. Status string
  128. GroupID *int64 // nil=不筛选, 0=无分组, >0=指定分组
  129. }