'integer', 'limit_value' => 'integer', 'used_value' => 'integer', 'reset_at' => 'datetime', 'window_start' => 'datetime', 'window_end' => 'datetime', 'is_active' => 'boolean', 'alert_threshold' => 'decimal:2', 'is_exceeded' => 'boolean', 'exceeded_at' => 'datetime', 'created_at' => 'datetime', 'updated_at' => 'datetime', ]; /** * 获取配额类型枚举 * * @return QUOTA_TYPE */ public function getQuotaTypeEnum(): QUOTA_TYPE { return QUOTA_TYPE::from($this->type); } /** * 获取配额类型标签 * * @return string */ public function getTypeLabel(): string { return $this->getQuotaTypeEnum()->getLabel(); } /** * 获取使用百分比 * * @return float */ public function getUsagePercentage(): float { if ($this->limit_value <= 0) { return 0; } return min(100, ($this->used_value / $this->limit_value) * 100); } /** * 获取剩余配额 * * @return int */ public function getRemainingQuota(): int { return max(0, $this->limit_value - $this->used_value); } /** * 检查是否已超限 * * @return bool */ public function isExceeded(): bool { return $this->is_exceeded || $this->used_value >= $this->limit_value; } /** * 检查是否接近告警阈值 * * @return bool */ public function isNearThreshold(): bool { return $this->getUsagePercentage() >= $this->alert_threshold; } /** * 检查是否需要重置 * * @return bool */ public function needsReset(): bool { $quotaType = $this->getQuotaTypeEnum(); // 总计类型不需要重置 if ($quotaType === QUOTA_TYPE::TOTAL) { return false; } // 检查是否超过了当前时间窗口 if ($this->window_end && now()->gt($this->window_end)) { return true; } // 检查重置时间 if ($this->reset_at && now()->gte($this->reset_at)) { return true; } return false; } /** * 重置配额 * * @return bool */ public function resetQuota(): bool { $quotaType = $this->getQuotaTypeEnum(); // 计算新的时间窗口 $windowStart = $quotaType->getCurrentWindowStart(); $windowEnd = $quotaType->getCurrentWindowEnd(); // 计算下次重置时间 $resetAt = $quotaType === QUOTA_TYPE::TOTAL ? null : $windowEnd; return $this->update([ 'used_value' => 0, 'window_start' => $windowStart, 'window_end' => $windowEnd, 'reset_at' => $resetAt, 'is_exceeded' => false, 'exceeded_at' => null, ]); } /** * 增加使用量 * * @param int $amount * @return bool */ public function incrementUsage(int $amount = 1): bool { // 检查是否需要重置 if ($this->needsReset()) { $this->resetQuota(); $this->refresh(); } $newUsedValue = $this->used_value + $amount; $updateData = ['used_value' => $newUsedValue]; // 检查是否超限 if ($newUsedValue >= $this->limit_value && !$this->is_exceeded) { $updateData['is_exceeded'] = true; $updateData['exceeded_at'] = now(); } return $this->update($updateData); } /** * 检查是否可以使用指定数量的配额 * * @param int $amount * @return bool */ public function canUse(int $amount = 1): bool { if (!$this->is_active) { return false; } // 检查是否需要重置 if ($this->needsReset()) { return $amount <= $this->limit_value; } return ($this->used_value + $amount) <= $this->limit_value; } /** * 获取配额状态 * * @return string */ public function getStatus(): string { if (!$this->is_active) { return 'inactive'; } if ($this->isExceeded()) { return 'exceeded'; } if ($this->isNearThreshold()) { return 'warning'; } return 'normal'; } /** * 获取配额状态颜色 * * @return string */ public function getStatusColor(): string { return match ($this->getStatus()) { 'exceeded' => 'danger', 'warning' => 'warning', 'normal' => 'success', 'inactive' => 'secondary', }; } /** * 获取配额状态标签 * * @return string */ public function getStatusLabel(): string { return match ($this->getStatus()) { 'exceeded' => '已超限', 'warning' => '接近限制', 'normal' => '正常', 'inactive' => '未激活', }; } /** * 获取格式化的配额信息 * * @return string */ public function getFormattedQuota(): string { return number_format($this->used_value) . ' / ' . number_format($this->limit_value); } /** * 关联第三方服务 * * @return BelongsTo */ public function service(): BelongsTo { return $this->belongsTo(ThirdPartyService::class, 'service_id'); } /** * 创建配额记录 * * @param array $data * @return static */ public static function createQuota(array $data): static { $quotaType = QUOTA_TYPE::from($data['type']); // 设置时间窗口 if ($quotaType->isTimeWindow()) { $data['window_start'] = $quotaType->getCurrentWindowStart(); $data['window_end'] = $quotaType->getCurrentWindowEnd(); $data['reset_at'] = $data['window_end']; } // 设置默认值 $data['used_value'] = $data['used_value'] ?? 0; $data['is_active'] = $data['is_active'] ?? true; $data['alert_threshold'] = $data['alert_threshold'] ?? 80.0; return static::create($data); } /** * 按服务查询 * * @param \Illuminate\Database\Eloquent\Builder $query * @param int $serviceId * @return \Illuminate\Database\Eloquent\Builder */ public function scopeByService($query, int $serviceId) { return $query->where('service_id', $serviceId); } /** * 查询激活的配额 * * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ public function scopeActive($query) { return $query->where('is_active', true); } /** * 查询已超限的配额 * * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ public function scopeExceeded($query) { return $query->where('is_exceeded', true); } /** * 查询接近阈值的配额 * * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ public function scopeNearThreshold($query) { return $query->whereRaw('(used_value / limit_value * 100) >= alert_threshold'); } }