Commit 8f679730 by Евгений

Улучшение #19452

Учет баллов при работе с АПИ
1 parent 478a9c4a
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
namespace App\Jobs; namespace App\Jobs;
use App\Service\AdsHandler; use App\Service\AdsHandler;
use App\Service\API;
use App\Service\Contract\APIRequest; use App\Service\Contract\APIRequest;
use App\Service\HeaderLimits; use App\Service\HeaderLimits;
use App\Service\Limits; use App\Service\Limits;
...@@ -41,9 +40,17 @@ class ProcessCallAPI implements ShouldQueue ...@@ -41,9 +40,17 @@ class ProcessCallAPI implements ShouldQueue
try{ try{
$api = API::getInstance($this->api); $api = API::getInstance($this->api);
//считаем на сколько объектов зарезервировано получение данных
//только их и запрашиваем
$limit = \App\Models\Limits::find($this->limitId);
//те на которые не хватило баллов помещаем в очередь
if ($api = $this->api->chunk($limit->spent)){
dispatch( new ProcessCallLimitedAPI($api));
}
$response = $api->execute(); $response = $api->execute();
$limits->acceptRezerv($this->limitId, new HeaderLimits($response->headers()) ); $limits->acceptRezerv($this->limitId, new HeaderLimits($response->headers()) );
//TODO: обработать результат //TODO: обработать результат.
// если не хватило баллов на все что хотели запросиь, то в очередь отправляем новый запрос на получение новых данных
AdsHandler::getInstance($this->api)->handle($response); AdsHandler::getInstance($this->api)->handle($response);
}catch(\Exception $e ){ }catch(\Exception $e ){
//TODO: надо отдельно выделить ошибки вызовов, за которые списываются баллы //TODO: надо отдельно выделить ошибки вызовов, за которые списываются баллы
......
...@@ -11,10 +11,13 @@ use Illuminate\Foundation\Bus\Dispatchable; ...@@ -11,10 +11,13 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class ProcessCallLimitedAPI implements ShouldQueue, ShouldBeUnique class ProcessCallLimitedAPI implements ShouldQueue//, ShouldBeUnique
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var APIRequest
*/
private $api; private $api;
/** /**
...@@ -52,30 +55,37 @@ class ProcessCallLimitedAPI implements ShouldQueue, ShouldBeUnique ...@@ -52,30 +55,37 @@ class ProcessCallLimitedAPI implements ShouldQueue, ShouldBeUnique
return; return;
} }
//вызов АПИ разбиваем на несколько //TODO: Вызов нао будет делать после обработки результатов.
if ($api = $this->api->chunk($objects)){ // в том случае, если окажется что были получены не все запрошенные объекты
//вызываем для остальных объектов вызов АПИ // т.к. мы не знам сколько там на самом деел объектов, может там их 10, при это лимит стоит 2000
self::dispatch( new ProcessCallLimitedAPI($this->api->getToken(), $api)); // нам разрешено получить 100. Мы запрапшиваем только 100, получаем 10, но если уже сейчас поставим сразу вторую заадчу на получение еще 100
} // то выйдет что зря потратим баллы на еще один азпрос
// //вызов АПИ разбиваем на несколько
// if ($api = $this->api->chunk($objects)){
// //вызываем для остальных объектов вызов АПИ
// dispatch( new ProcessCallLimitedAPI($api));
// }
//вызываем очередь получения данных от АПИ //вызываем очередь получения данных от АПИ
//туда передаем сохраненный лимит //туда передаем сохраненный лимит
//там уже либо будет удален, если не удастся выполнить запрос //там уже либо будет удален, если не удастся выполнить запрос
//либо обновятся данные //либо обновятся данные
self::dispatch(new ProcessCallAPI($limitId, $this->api)); dispatch(new ProcessCallAPI($limitId, $this->api));
} }
/** // /**
* The unique ID of the job. // * The unique ID of the job.
* // *
* @return string // * @return string
*/ // */
public function uniqueId() // public function uniqueId()
{ // {
return $this->api->getToken()->id; // return $this->api->getToken()->id;
} // }
private function reRunHour(){ private function reRunHour(){
self::dispatch( new ProcessCallLimitedAPI($this->api))->delay(now()->addMinutes(60-date("i"))); $process = new ProcessCallLimitedAPI($this->api);
dispatch( $process )
->delay(now()->addMinutes(60-date("i")));
} }
} }
...@@ -5,13 +5,22 @@ use App\Service\Contract\APIRequest; ...@@ -5,13 +5,22 @@ use App\Service\Contract\APIRequest;
class AdsHandler{ class AdsHandler{
protected static $_instance; protected static $_instance;
protected $request;
protected function __constructor(APIRequest $request = null){ protected function __constructor(APIRequest $request = null){
$this->request = $request;
} }
public static function getInstance(APIRequest $request = null){ public static function getInstance(APIRequest $request = null){
self::$_instance = new self($request); self::$_instance = new self($request);
return self::$_instance; return self::$_instance;
} }
public function handle($response){
//постраничная выбрка
if ($response->limited()){
$this->request->next($response->limited());
dispatch( new ProcessCallLimitedAPI($this->request));
}
}
} }
...@@ -61,9 +61,8 @@ class Limits implements \App\Service\Contract\Limits { ...@@ -61,9 +61,8 @@ class Limits implements \App\Service\Contract\Limits {
} }
/** /**
* @param string $service * @param APIRequest $request
* @param string $method * @param int $objects
* @param int $limit
* @return int * @return int
* @throws \Exception * @throws \Exception
* предполагается что класс работает в очереди. * предполагается что класс работает в очереди.
...@@ -84,6 +83,7 @@ class Limits implements \App\Service\Contract\Limits { ...@@ -84,6 +83,7 @@ class Limits implements \App\Service\Contract\Limits {
$rezerv->service = $request->getService(); $rezerv->service = $request->getService();
$rezerv->method = $request->getMethod(); $rezerv->method = $request->getMethod();
$rezerv->spent = $limit; $rezerv->spent = $limit;
$rezerv->day = 0;
$rezerv->current = $this->token->limit; $rezerv->current = $this->token->limit;
$rezerv->reserved = 1; $rezerv->reserved = 1;
$rezerv->save(); $rezerv->save();
...@@ -146,6 +146,8 @@ class Limits implements \App\Service\Contract\Limits { ...@@ -146,6 +146,8 @@ class Limits implements \App\Service\Contract\Limits {
$limit = new \App\Models\Limits(); $limit = new \App\Models\Limits();
$limit->token = $this->token->id; $limit->token = $this->token->id;
$limit->service = '';
$limit->method = '';
$limit->spent = $limits->getSpentLimit(); $limit->spent = $limits->getSpentLimit();
$limit->current = $limits->getCurrentLimit(); $limit->current = $limits->getCurrentLimit();
$limit->day = $limits->getDayLimit(); $limit->day = $limits->getDayLimit();
......
...@@ -71,4 +71,9 @@ class APIRequest implements \App\Service\Contract\APIRequest { ...@@ -71,4 +71,9 @@ class APIRequest implements \App\Service\Contract\APIRequest {
{ {
throw new Exception('Я не знаю формата запрсов, чтобы его разбить'); throw new Exception('Я не знаю формата запрсов, чтобы его разбить');
} }
function next($count)
{
throw new Exception('Я не знаю формата запрсов, чтобы его разбить');
}
} }
...@@ -3,21 +3,51 @@ namespace App\Service\Requests; ...@@ -3,21 +3,51 @@ namespace App\Service\Requests;
use App\Models\Tokens; use App\Models\Tokens;
/**
* Class DirectRequest
* @package App\Service\Requests
*
*
*/
class DirectRequest extends APIRequest { class DirectRequest extends APIRequest {
CONST MAX_COUNT = 10000;
function chunk($count): ?\App\Service\Contract\APIRequest function chunk($count): ?\App\Service\Contract\APIRequest
{ {
if ($this->params['Page']['Limit'] <= $count) { //для запросов get ничего не разбиваем, т.к. заранее не знаем сколько чего будет
return null; //для них только после выполнения запроса будет известно есть ли кроме $count еще данные
//а текущий запрос просто устанавливается в $count
//а вот для запросов мутатрово будем разбивать. Но тут уже будет зависеть от запроса
//будем под каждый реализовывать делитель
if (!isset($this->params['Page'])){
$this->params['Page'] = [];
} }
$nextCount = $this->params['Page']['Limit'] - $count;
$this->params['Page']['Limit'] = $count; $this->params['Page']['Limit'] = $count;
return null;
// if (!isset($this->params['Page'])){
// $this->params['Page']['Limit'] = $count;
// $this->params['Page']['Offset'] = 0;
// return null;
// }
// if ($this->params['Page']['Limit'] <= $count) {
// return null;
// }
//
// $this->params['Page']['Limit'] = $count;
//
// $new = clone $this;
// $params = $this->params;
// $params['Page']['Offset'] += $count;
// $new->setParams($params);
// return $new;
}
$new = clone $this; function next($offset){
$params = $this->params; if (!isset($this->params['Page'])){
$params['Page']['Limit'] = $nextCount; $this->params['Page'] = [];
$new->setParams($params); }
return $new; $this->params['Page']['Limit'] = self::MAX_COUNT;
$this->params['Page']['Offset'] = $offset;
} }
} }
php artisan queue:work & php artisan queue:work --queue=limits &
php artisan queue:work & php artisan queue:work --queue=api &
php artisan queue:work & php artisan queue:work --queue=api &
php artisan queue:work & php artisan queue:work --queue=api &
php artisan queue:work & php artisan queue:work --queue=api &
php artisan queue:work --queue=api &
php artisan schedule:work & php artisan schedule:work &
...@@ -61,7 +61,7 @@ class ApiRequestTest extends TestCase ...@@ -61,7 +61,7 @@ class ApiRequestTest extends TestCase
] ]
]); ]);
$request = $this->request->chunk(3); $request = $this->request->chunk(3);
$this->assertEquals($request->getParams()['Page']['Limit'], 7); $this->assertEquals($request->getParams()['Page']['Offset'], 3);
$this->assertEquals($this->request->getParams()['Page']['Limit'], 3); $this->assertEquals($this->request->getParams()['Page']['Limit'], 3);
$this->request->setParams([ $this->request->setParams([
......
<?php
namespace Tests\Unit;
use App\Jobs\ProcessCallAPI;
use App\Jobs\ProcessCallLimitedAPI;
use App\Models\Account;
use App\Models\Tokens;
use App\Models\User;
use App\Service\Contract\API;
use App\Service\HeaderLimits;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;
class CallLimitedApiTest extends TestCase
{
use RefreshDatabase;
private $request;
private $token;
protected function setUp(): void
{
parent::setUp();
$this->request = \App\Service\Requests\APIRequest::getInstance(API::YANDEX);
$account = Account::create(['name' => 'Acme Corporation']);
$this->user = factory(User::class)->create([
'account_id' => $account->id,
'first_name' => 'John',
'last_name' => 'Doe',
'email' => 'johndoe@example.com',
'owner' => true,
]);
$this->token = factory(Tokens::class)->create([
'created_by' => $this->user->id
]);
}
public function testCall(){
Queue::fake();
Queue::assertNothingPushed();
$this->request->setToken($this->token);
$process = new ProcessCallLimitedAPI($this->request);
dispatch($process)->onQueue('limits');
Queue::assertPushed(ProcessCallLimitedAPI::class);
$process = new ProcessCallLimitedAPI($this->request);
dispatch($process)->onQueue('limits');
Queue::assertPushed(ProcessCallLimitedAPI::class, 2);
$this->token->limit=14;
$this->request->setService('AdGroups');
$this->request->setMethod('get');
$process->handle();
Queue::assertPushed(ProcessCallLimitedAPI::class, 3);
$this->token->limit=16;
$this->request->setService('AdGroups');
$this->request->setMethod('get');
$process->handle();
Queue::assertPushed(ProcessCallAPI::class);
$this->assertEquals($this->token->limit, 0);
}
}
...@@ -6,6 +6,7 @@ use App\Models\Account; ...@@ -6,6 +6,7 @@ use App\Models\Account;
use App\Models\Tokens; use App\Models\Tokens;
use App\Models\User; use App\Models\User;
use App\Service\Contract\API; use App\Service\Contract\API;
use App\Service\HeaderLimits;
use App\Service\Limits; use App\Service\Limits;
use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Date; use Illuminate\Support\Facades\Date;
...@@ -143,9 +144,32 @@ class LimitsTest extends TestCase ...@@ -143,9 +144,32 @@ class LimitsTest extends TestCase
$this->assertEquals($objects, 4000); $this->assertEquals($objects, 4000);
} }
public function testDoRezervNoLimit()
{
$request = \App\Service\Requests\APIRequest::getInstance(API::YANDEX);
$request->setService('AdExtensions');
$request->setMethod('add');
$this->expectException(\Exception::class);
$this->limitService->doRezerv($request, $this->token->limit);
}
public function testDoRezerv() public function testDoRezerv()
{ {
$this->markTestIncomplete(); $request = \App\Service\Requests\APIRequest::getInstance(API::YANDEX);
$request->setService('AdExtensions');
$request->setMethod('add');
$this->token->limit = 10;
$objects = $this->token->limit-5;
$last = $this->token->limit;
$id = $this->limitService->doRezerv($request, $objects);
$this->assertEquals($this->token->limit, 0);
$limit = \App\Models\Limits::find($id);
$this->assertEquals($limit->spent, $last);
$this->assertEquals(1, $limit->reserved);
$this->assertEquals($last, $limit->current);
} }
public function testRemoveRezerv() public function testRemoveRezerv()
...@@ -162,10 +186,33 @@ class LimitsTest extends TestCase ...@@ -162,10 +186,33 @@ class LimitsTest extends TestCase
} }
public function testAcceptRezerv(){ public function testAcceptRezerv(){
$this->markTestIncomplete(); $limit = $this->token->limits()->save(factory(\App\Models\Limits::class)->make([
'reserved' => 1,
'spent' => 10
]));
$this->limitService->acceptRezerv(
$limit->id,
new HeaderLimits(['Units' => "1/2/3"])
);
$this->assertEquals($this->token->limit, 2);
$limit = \App\Models\Limits::find($limit->id);
$this->assertEquals($limit->spent, 1);
$this->assertEquals($limit->current, 2);
$this->assertEquals($limit->day, 3);
$this->assertEquals($limit->reserved, 0);
} }
public function testUpdateLimits(){ public function testUpdateLimits(){
$this->markTestIncomplete(); $this->limitService->updateLimits(
new HeaderLimits(['Units' => "1/2/3"])
);
$this->assertEquals($this->token->limit, 2);
$limit = $this->token->limits()->complited()->firstOrFail();
$this->assertEquals($limit->spent, 1);
$this->assertEquals($limit->current, 2);
$this->assertEquals($limit->day, 3);
$this->assertEquals($limit->reserved, 0);
} }
} }
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!