ThirdPartyQuota.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. <?php
  2. namespace App\Module\ThirdParty\Models;
  3. use UCore\ModelCore;
  4. use App\Module\ThirdParty\Enums\QUOTA_TYPE;
  5. use Illuminate\Database\Eloquent\Relations\BelongsTo;
  6. /**
  7. * 第三方服务配额管理模型
  8. *
  9. * field start
  10. * @property int $id 主键ID
  11. * @property int $service_id 服务ID
  12. * @property string $type 配额类型
  13. * @property int $limit_value 限制值
  14. * @property int $used_value 已使用值
  15. * @property \Carbon\Carbon $reset_at 重置时间
  16. * @property \Carbon\Carbon $window_start 时间窗口开始
  17. * @property \Carbon\Carbon $window_end 时间窗口结束
  18. * @property bool $is_active 是否激活
  19. * @property float $alert_threshold 告警阈值(百分比)
  20. * @property bool $is_exceeded 是否已超限
  21. * @property \Carbon\Carbon $exceeded_at 超限时间
  22. * @property \Carbon\Carbon $created_at 创建时间
  23. * @property \Carbon\Carbon $updated_at 更新时间
  24. * field end
  25. */
  26. class ThirdPartyQuota extends ModelCore
  27. {
  28. /**
  29. * 数据表名
  30. *
  31. * @var string
  32. */
  33. protected $table = 'thirdparty_quotas';
  34. /**
  35. * 属性类型转换
  36. *
  37. * @var array
  38. */
  39. protected $casts = [
  40. 'service_id' => 'integer',
  41. 'limit_value' => 'integer',
  42. 'used_value' => 'integer',
  43. 'reset_at' => 'datetime',
  44. 'window_start' => 'datetime',
  45. 'window_end' => 'datetime',
  46. 'is_active' => 'boolean',
  47. 'alert_threshold' => 'decimal:2',
  48. 'is_exceeded' => 'boolean',
  49. 'exceeded_at' => 'datetime',
  50. 'created_at' => 'datetime',
  51. 'updated_at' => 'datetime',
  52. ];
  53. /**
  54. * 获取配额类型枚举
  55. *
  56. * @return QUOTA_TYPE
  57. */
  58. public function getQuotaTypeEnum(): QUOTA_TYPE
  59. {
  60. return QUOTA_TYPE::from($this->type);
  61. }
  62. /**
  63. * 获取配额类型标签
  64. *
  65. * @return string
  66. */
  67. public function getTypeLabel(): string
  68. {
  69. return $this->getQuotaTypeEnum()->getLabel();
  70. }
  71. /**
  72. * 获取使用百分比
  73. *
  74. * @return float
  75. */
  76. public function getUsagePercentage(): float
  77. {
  78. if ($this->limit_value <= 0) {
  79. return 0;
  80. }
  81. return min(100, ($this->used_value / $this->limit_value) * 100);
  82. }
  83. /**
  84. * 获取剩余配额
  85. *
  86. * @return int
  87. */
  88. public function getRemainingQuota(): int
  89. {
  90. return max(0, $this->limit_value - $this->used_value);
  91. }
  92. /**
  93. * 检查是否已超限
  94. *
  95. * @return bool
  96. */
  97. public function isExceeded(): bool
  98. {
  99. return $this->is_exceeded || $this->used_value >= $this->limit_value;
  100. }
  101. /**
  102. * 检查是否接近告警阈值
  103. *
  104. * @return bool
  105. */
  106. public function isNearThreshold(): bool
  107. {
  108. return $this->getUsagePercentage() >= $this->alert_threshold;
  109. }
  110. /**
  111. * 检查是否需要重置
  112. *
  113. * @return bool
  114. */
  115. public function needsReset(): bool
  116. {
  117. $quotaType = $this->getQuotaTypeEnum();
  118. // 总计类型不需要重置
  119. if ($quotaType === QUOTA_TYPE::TOTAL) {
  120. return false;
  121. }
  122. // 检查是否超过了当前时间窗口
  123. if ($this->window_end && now()->gt($this->window_end)) {
  124. return true;
  125. }
  126. // 检查重置时间
  127. if ($this->reset_at && now()->gte($this->reset_at)) {
  128. return true;
  129. }
  130. return false;
  131. }
  132. /**
  133. * 重置配额
  134. *
  135. * @return bool
  136. */
  137. public function resetQuota(): bool
  138. {
  139. $quotaType = $this->getQuotaTypeEnum();
  140. // 计算新的时间窗口
  141. $windowStart = $quotaType->getCurrentWindowStart();
  142. $windowEnd = $quotaType->getCurrentWindowEnd();
  143. // 计算下次重置时间
  144. $resetAt = $quotaType === QUOTA_TYPE::TOTAL ? null : $windowEnd;
  145. return $this->update([
  146. 'used_value' => 0,
  147. 'window_start' => $windowStart,
  148. 'window_end' => $windowEnd,
  149. 'reset_at' => $resetAt,
  150. 'is_exceeded' => false,
  151. 'exceeded_at' => null,
  152. ]);
  153. }
  154. /**
  155. * 增加使用量
  156. *
  157. * @param int $amount
  158. * @return bool
  159. */
  160. public function incrementUsage(int $amount = 1): bool
  161. {
  162. // 检查是否需要重置
  163. if ($this->needsReset()) {
  164. $this->resetQuota();
  165. $this->refresh();
  166. }
  167. $newUsedValue = $this->used_value + $amount;
  168. $updateData = ['used_value' => $newUsedValue];
  169. // 检查是否超限
  170. if ($newUsedValue >= $this->limit_value && !$this->is_exceeded) {
  171. $updateData['is_exceeded'] = true;
  172. $updateData['exceeded_at'] = now();
  173. }
  174. return $this->update($updateData);
  175. }
  176. /**
  177. * 检查是否可以使用指定数量的配额
  178. *
  179. * @param int $amount
  180. * @return bool
  181. */
  182. public function canUse(int $amount = 1): bool
  183. {
  184. if (!$this->is_active) {
  185. return false;
  186. }
  187. // 检查是否需要重置
  188. if ($this->needsReset()) {
  189. return $amount <= $this->limit_value;
  190. }
  191. return ($this->used_value + $amount) <= $this->limit_value;
  192. }
  193. /**
  194. * 获取配额状态
  195. *
  196. * @return string
  197. */
  198. public function getStatus(): string
  199. {
  200. if (!$this->is_active) {
  201. return 'inactive';
  202. }
  203. if ($this->isExceeded()) {
  204. return 'exceeded';
  205. }
  206. if ($this->isNearThreshold()) {
  207. return 'warning';
  208. }
  209. return 'normal';
  210. }
  211. /**
  212. * 获取配额状态颜色
  213. *
  214. * @return string
  215. */
  216. public function getStatusColor(): string
  217. {
  218. return match ($this->getStatus()) {
  219. 'exceeded' => 'danger',
  220. 'warning' => 'warning',
  221. 'normal' => 'success',
  222. 'inactive' => 'secondary',
  223. };
  224. }
  225. /**
  226. * 获取配额状态标签
  227. *
  228. * @return string
  229. */
  230. public function getStatusLabel(): string
  231. {
  232. return match ($this->getStatus()) {
  233. 'exceeded' => '已超限',
  234. 'warning' => '接近限制',
  235. 'normal' => '正常',
  236. 'inactive' => '未激活',
  237. };
  238. }
  239. /**
  240. * 获取格式化的配额信息
  241. *
  242. * @return string
  243. */
  244. public function getFormattedQuota(): string
  245. {
  246. return number_format($this->used_value) . ' / ' . number_format($this->limit_value);
  247. }
  248. /**
  249. * 关联第三方服务
  250. *
  251. * @return BelongsTo
  252. */
  253. public function service(): BelongsTo
  254. {
  255. return $this->belongsTo(ThirdPartyService::class, 'service_id');
  256. }
  257. /**
  258. * 创建配额记录
  259. *
  260. * @param array $data
  261. * @return static
  262. */
  263. public static function createQuota(array $data): static
  264. {
  265. $quotaType = QUOTA_TYPE::from($data['type']);
  266. // 设置时间窗口
  267. if ($quotaType->isTimeWindow()) {
  268. $data['window_start'] = $quotaType->getCurrentWindowStart();
  269. $data['window_end'] = $quotaType->getCurrentWindowEnd();
  270. $data['reset_at'] = $data['window_end'];
  271. }
  272. // 设置默认值
  273. $data['used_value'] = $data['used_value'] ?? 0;
  274. $data['is_active'] = $data['is_active'] ?? true;
  275. $data['alert_threshold'] = $data['alert_threshold'] ?? 80.0;
  276. return static::create($data);
  277. }
  278. /**
  279. * 按服务查询
  280. *
  281. * @param \Illuminate\Database\Eloquent\Builder $query
  282. * @param int $serviceId
  283. * @return \Illuminate\Database\Eloquent\Builder
  284. */
  285. public function scopeByService($query, int $serviceId)
  286. {
  287. return $query->where('service_id', $serviceId);
  288. }
  289. /**
  290. * 查询激活的配额
  291. *
  292. * @param \Illuminate\Database\Eloquent\Builder $query
  293. * @return \Illuminate\Database\Eloquent\Builder
  294. */
  295. public function scopeActive($query)
  296. {
  297. return $query->where('is_active', true);
  298. }
  299. /**
  300. * 查询已超限的配额
  301. *
  302. * @param \Illuminate\Database\Eloquent\Builder $query
  303. * @return \Illuminate\Database\Eloquent\Builder
  304. */
  305. public function scopeExceeded($query)
  306. {
  307. return $query->where('is_exceeded', true);
  308. }
  309. /**
  310. * 查询接近阈值的配额
  311. *
  312. * @param \Illuminate\Database\Eloquent\Builder $query
  313. * @return \Illuminate\Database\Eloquent\Builder
  314. */
  315. public function scopeNearThreshold($query)
  316. {
  317. return $query->whereRaw('(used_value / limit_value * 100) >= alert_threshold');
  318. }
  319. }