Limits.php 5.87 KB
<?php
namespace App\Service;

use App\Models\Tokens;
use App\Service\Contract\HeaderLimits;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;

class Limits implements \App\Service\Contract\Limits {

    private $token;
    private $limitCosts;

    private function __construct(Tokens $token){
        $this->token = $token;
        $this->limitCosts = new Costs();
    }

    public static function getInstance(Tokens $token): Limits
    {
        return new self($token);
    }

    function current(): int
    {
        return $this->token->limit;
    }

    function dayLimit(): int
    {
        return $this->token->limits()->complited()->firstOrFail()->day;
    }

    /**
     * Если несколько дней не обновляется и дневные лимиты там менялись то будет ошибка
     * но мы ей пренебрегаем, т.к. вообще должно каждый час запускаться это дело и толко при каком то сбое может быть иначе
     * Еще может быть ошибка когда первый запус в день происходит, если изменилось количетсво балло на день,
     * но это после первого же сама будет исправлено, так что тоже ничгео страшнго нет
     */
    function refreshCurrentLimit(){
        //последние лимиты по баллам из АПИ
        $limit = $this->token->limits()->complited()->first();
        if (!$limit)
            return;

        //сколько часов прошло после последнего запуска
        $hours = Date::now()->diffInHours($limit->updated_at);
        //новый лимит это послдений доступный + по 1/24 дневного лимита за каждый час без запросов.
        // Но не блее чем за 23 предыдущих часа
        $hours  = $hours > 23 ? 23 : $hours;
        $current = $limit->current +  $hours * $limit->day/24;
        $this->token->limit = $current;
        $this->token->save();
    }

    function countObjects(\App\Service\Contract\APIRequest $request): int
    {
        return $request->getCountObjects();
    }

    function countObjectsLimit(\App\Service\Contract\APIRequest $request): int
    {
        $cost = $this->limitCosts->getCostObject($request);
        $costCall = $this->limitCosts->getCostCall($request);

        if ($costCall > $this->current()) {
            return 0;
        }

        return $cost > 0 ? floor($this->current() - $this->getSpent($this->countObjects($request), $request)) : -1;
    }

    /**
     * @param APIRequest $request
     * @param int $objects
     * @return int
     * @throws \Exception
     * предполагается что класс работает в очереди.
     * Иначе может быть что одновременно будет два резервирования с одним и тем же остатком.
     */
    function doRezerv(\App\Service\Contract\APIRequest $request, int $objects): int
    {
        $limit = $this->getSpent($objects, $request);
        if ($this->token->limit<$limit){
            throw new \Exception('Недостаточно баллов');
        }

        DB::beginTransaction();

        try{
            $rezerv = new \App\Models\Limits();
            $rezerv->token = $this->token->id;
            $rezerv->service = $request->getService();
            $rezerv->method = $request->getMethod();
            $rezerv->spent = $limit;
            $rezerv->day = 0;
            $rezerv->current = $this->token->limit;
            $rezerv->reserved = 1;
            $rezerv->save();

            $this->token->limit -= $limit;

            $this->token->save();

            DB::commit();

        }catch(\Exception $e){
            DB::rollBack();
            throw $e;
        }

        return $rezerv->id;
    }

    function removeRezerv(int $id)
    {
        DB::beginTransaction();
        try{
            $limit = \App\Models\Limits::findOrFail($id);
            $this->token->limit += $limit->spent;
            $this->token->save();
            $limit->delete();

            DB::commit();
        }catch(\Exception $e){
            DB::rollBack();
            throw $e;
        }
    }

    function acceptRezerv($id, HeaderLimits $limits){
        DB::beginTransaction();
        try{
            $this->token->limit = $limits->getCurrentLimit();
            $this->token->save();

            $limit = \App\Models\Limits::findOrFail($id);
            $limit->spent = $limits->getSpentLimit();
            $limit->current = $limits->getCurrentLimit();
            $limit->day = $limits->getDayLimit();
            $limit->reserved = 0;
            $limit->save();

            DB::commit();
        }catch(\Exception $e){
            DB::rollBack();
            throw $e;
        }
    }

    function updateLimits(HeaderLimits $limits)
    {
        DB::beginTransaction();
        try{
            $this->token->limit = $limits->getCurrentLimit();
            $this->token->save();

            $limit = new \App\Models\Limits();
            $limit->token = $this->token->id;
            $limit->service = '';
            $limit->method = '';
            $limit->spent = $limits->getSpentLimit();
            $limit->current = $limits->getCurrentLimit();
            $limit->day = $limits->getDayLimit();
            $limit->reserved = 0;
            $limit->save();

            DB::commit();
        }catch(\Exception $e){
            DB::rollBack();
            throw $e;
        }
    }

    private function getSpent($objects, \App\Service\Contract\APIRequest $request): int
    {
        $cost = $this->limitCosts->getCostObject($request);
        return $objects * $cost + $this->limitCosts->getCostCall($request);
    }
}