Commit 478a9c4a by Евгений

Улучшение #19452

Учет баллов при работе с АПИ
1 parent 0191d305
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:V0x95Ad1Sqtee/P0fEoVjlir4U9wNc92qhckgU2NV8o=
APP_DEBUG=true
APP_URL=http://localhost
LOG_CHANNEL=stack
DB_CONNECTION=sqlite
DB_DATABASE=:memory:
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\Tokens;
use App\Service\Limits;
use Illuminate\Console\Command; use Illuminate\Console\Command;
class refreshLimits extends Command class refreshLimits extends Command
...@@ -11,14 +13,14 @@ class refreshLimits extends Command ...@@ -11,14 +13,14 @@ class refreshLimits extends Command
* *
* @var string * @var string
*/ */
protected $signature = 'command:name'; protected $signature = 'refresh:limits';
/** /**
* The console command description. * The console command description.
* *
* @var string * @var string
*/ */
protected $description = 'Command description'; protected $description = 'Обновление количетсво баллов по токенам';
/** /**
* Create a new command instance. * Create a new command instance.
...@@ -37,6 +39,9 @@ class refreshLimits extends Command ...@@ -37,6 +39,9 @@ class refreshLimits extends Command
*/ */
public function handle() public function handle()
{ {
return 0; $tokens = Tokens::whereNotNull('type')->get();
foreach ($tokens as $token){
Limits::getInstance($token)->refreshCurrentLimit();
}
} }
} }
...@@ -52,7 +52,7 @@ class ProcessCallLimitedAPI implements ShouldQueue, ShouldBeUnique ...@@ -52,7 +52,7 @@ class ProcessCallLimitedAPI implements ShouldQueue, ShouldBeUnique
return; return;
} }
//вызов АПИ разбиваем на несколько, если коненчо не хватает баллов одним запросом все получить //вызов АПИ разбиваем на несколько
if ($api = $this->api->chunk($objects)){ if ($api = $this->api->chunk($objects)){
//вызываем для остальных объектов вызов АПИ //вызываем для остальных объектов вызов АПИ
self::dispatch( new ProcessCallLimitedAPI($this->api->getToken(), $api)); self::dispatch( new ProcessCallLimitedAPI($this->api->getToken(), $api));
......
...@@ -8,4 +8,9 @@ use Illuminate\Database\Eloquent\Model; ...@@ -8,4 +8,9 @@ use Illuminate\Database\Eloquent\Model;
class Limits extends Model class Limits extends Model
{ {
use HasFactory; use HasFactory;
public function scopeComplited($query)
{
$query->where('reserved', 0);
}
} }
<?php <?php
namespace App\Service; namespace App\Service\API;
use App\Service\Contract\APIRequest; use App\Service\Contract\APIRequest;
use Illuminate\Http\Client\Response; use Illuminate\Http\Client\Response;
...@@ -15,7 +15,7 @@ class API implements \App\Service\Contract\API { ...@@ -15,7 +15,7 @@ class API implements \App\Service\Contract\API {
static function getInstance(APIRequest $request = null){ static function getInstance(APIRequest $request = null){
switch($request->getApi()){ switch($request->getApi()){
case 'yd': case API::YANDEX:
return new YandexDirect($request); return new YandexDirect($request);
break; break;
} }
......
<?php <?php
namespace App\Service; namespace App\Service\API;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
......
...@@ -12,5 +12,6 @@ class AdsHandler{ ...@@ -12,5 +12,6 @@ class AdsHandler{
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;
} }
} }
...@@ -4,6 +4,8 @@ namespace App\Service\Contract; ...@@ -4,6 +4,8 @@ namespace App\Service\Contract;
use Illuminate\Http\Client\Response; use Illuminate\Http\Client\Response;
interface API{ interface API{
CONST YANDEX = 'yd';
function getAuthLink(); function getAuthLink();
function getToken($code); function getToken($code);
......
...@@ -10,10 +10,9 @@ interface APIRequest{ ...@@ -10,10 +10,9 @@ interface APIRequest{
function getMethod(); function getMethod();
function setParams(array $params); function setParams(array $params);
function getParams(); function getParams();
function setToken(int $token); function setToken(Tokens $token);
function getToken(): Tokens; function getToken(): Tokens;
function setApi(string $api);
function getApi(): string; function getApi(): string;
function chunk($objects): APIRequest; function chunk($objects): ?APIRequest;
} }
...@@ -6,7 +6,7 @@ interface Limits{ ...@@ -6,7 +6,7 @@ interface Limits{
function DayLimit(): int; function DayLimit(): int;
function countObjectsLimit(APIRequest $request): int; function countObjectsLimit(APIRequest $request): int;
function doRezerv(APIRequest $request, int $limit): int; function doRezerv(APIRequest $request, int $limit): int;
function removeRezerv(int $id): int; function removeRezerv(int $id);
function updateLimits(HeaderLimits $limits); function updateLimits(HeaderLimits $limits);
function acceptRezerv($id, HeaderLimits $limits); function acceptRezerv($id, HeaderLimits $limits);
} }
...@@ -7,10 +7,10 @@ class HeaderLimits implements \App\Service\Contract\HeaderLimits{ ...@@ -7,10 +7,10 @@ class HeaderLimits implements \App\Service\Contract\HeaderLimits{
private $currentLimit = 0; private $currentLimit = 0;
private $spentLimit = 0; private $spentLimit = 0;
function __constructor(Array $headers) function __construct(Array $headers)
{ {
if (!isset($headers['Units'])){ if (!isset($headers['Units'])){
throw new Exception('Не найден заголовок с баллами'); throw new \Exception('Не найден заголовок с баллами');
} }
list($this->spentLimit, $this->currentLimit, $this->dayLimit) = explode("/", $headers['Units']); list($this->spentLimit, $this->currentLimit, $this->dayLimit) = explode("/", $headers['Units']);
} }
......
...@@ -3,22 +3,22 @@ namespace App\Service; ...@@ -3,22 +3,22 @@ namespace App\Service;
use App\Models\Tokens; use App\Models\Tokens;
use App\Service\Contract\HeaderLimits; use App\Service\Contract\HeaderLimits;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class Limits implements \App\Service\Contract\Limits { class Limits implements \App\Service\Contract\Limits {
private static $_instance;
private $token; private $token;
private $limitCosts; private $limitCosts;
private function __constructor(Tokens $token){ private function __construct(Tokens $token){
$this->token = $token; $this->token = $token;
$this->limitCosts = new Costs(); $this->limitCosts = new Costs();
} }
public static function getInstance($token): Limits public static function getInstance(Tokens $token): Limits
{ {
self::$_instance = new self($token); return new self($token);
} }
function current(): int function current(): int
...@@ -26,9 +26,29 @@ class Limits implements \App\Service\Contract\Limits { ...@@ -26,9 +26,29 @@ class Limits implements \App\Service\Contract\Limits {
return $this->token->limit; return $this->token->limit;
} }
function DayLimit(): int function dayLimit(): int
{ {
$this->token->limits->first()->day; return $this->token->limits()->complited()->firstOrFail()->day;
}
/**
* Если несколько дней не обновляется и дневные лимиты там менялись то будет ошибка
* но мы ей пренебрегаем, т.к. вообще должно каждый час запускаться это дело и толко при каком то сбое может быть иначе
* Еще может быть ошибка когда первый запус в день происходит, если изменилось количетсво балло на день,
* но это после первого же сама будет исправлено, так что тоже ничгео страшнго нет
*/
function refreshCurrentLimit(){
//последние лимиты по баллам из АПИ
$limit = $this->token->limits()->complited()->first();
//сколько часов прошло после последнего запуска
$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 countObjectsLimit(\App\Service\Contract\APIRequest $request): int function countObjectsLimit(\App\Service\Contract\APIRequest $request): int
...@@ -81,7 +101,7 @@ class Limits implements \App\Service\Contract\Limits { ...@@ -81,7 +101,7 @@ class Limits implements \App\Service\Contract\Limits {
return $rezerv->id; return $rezerv->id;
} }
function removeRezerv(int $id): int function removeRezerv(int $id)
{ {
DB::beginTransaction(); DB::beginTransaction();
try{ try{
......
<?php <?php
namespace App\Service; namespace App\Service\Requests;
use App\Models\Tokens; use App\Models\Tokens;
use App\Service\API\API;
class APIRequest implements \App\Service\Contract\APIRequest { class APIRequest implements \App\Service\Contract\APIRequest {
protected $api;
protected $service;
protected $method;
protected $params;
protected $token;
private function __construct($api){
$this->api = $api;
}
static function getInstance($type){
switch($type){
case API::YANDEX:
return new DirectRequest($type);
}
}
function getApi(): string
{
return $this->api;
}
function setService(string $service) function setService(string $service)
{ {
// TODO: Implement setService() method. $this->service = $service;
} }
function getService() function getService()
{ {
// TODO: Implement getService() method. return $this->service;
} }
function setMethod(string $method) function setMethod(string $method)
{ {
// TODO: Implement setMethod() method. $this->method = $method;
} }
function getMethod() function getMethod()
{ {
// TODO: Implement getMethod() method. return $this->method;
} }
function setParams(array $params) function setParams(array $params)
{ {
// TODO: Implement setParams() method. $this->params = $params;
} }
function getParams() function getParams()
{ {
// TODO: Implement getParams() method. return $this->params;
} }
function setToken(int $token) function setToken(Tokens $token)
{ {
// TODO: Implement setToken() method. $this->token = $token;
} }
function getToken(): Tokens function getToken(): Tokens
{ {
// TODO: Implement getToken() method. return $this->token;
}
function setApi(string $api)
{
// TODO: Implement setApi() method.
}
function getApi(): string
{
// TODO: Implement getApi() method.
} }
function chunk($objects): \App\Service\Contract\APIRequest function chunk($count): ?\App\Service\Contract\APIRequest
{ {
// TODO: Implement chunk() method. throw new Exception('Я не знаю формата запрсов, чтобы его разбить');
} }
} }
<?php
namespace App\Service\Requests;
use App\Models\Tokens;
class DirectRequest extends APIRequest {
function chunk($count): ?\App\Service\Contract\APIRequest
{
if ($this->params['Page']['Limit'] <= $count) {
return null;
}
$nextCount = $this->params['Page']['Limit'] - $count;
$this->params['Page']['Limit'] = $count;
$new = clone $this;
$params = $this->params;
$params['Page']['Limit'] = $nextCount;
$new->setParams($params);
return $new;
}
}
<?php
use Faker\Generator as Faker;
use Illuminate\Support\Str;
/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| This directory should contain each of the model factory definitions for
| your application. Factories provide a convenient way to generate new
| model instances for testing / seeding your application's database.
|
*/
$factory->define(App\Models\Limits::class, function (Faker $faker) {
return [
'service' => $faker->word,
'method' => $faker->word,
'spent' => $faker->randomDigit,
'current' => $faker->randomDigit,
'day' => $faker->randomDigit,
'reserved' => $faker->numberBetween(0, 1),
];
});
<?php
use Faker\Generator as Faker;
use Illuminate\Support\Str;
/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| This directory should contain each of the model factory definitions for
| your application. Factories provide a convenient way to generate new
| model instances for testing / seeding your application's database.
|
*/
$factory->define(App\Models\Tokens::class, function (Faker $faker) {
return [
'token' => $faker->uuid,
'login' => $faker->userName,
'api' => 'yd',
'type' => 'goal',
'created_by' => 1,
'limit' => $faker->randomDigit,
];
});
...@@ -28,7 +28,7 @@ class CreateLimitsTable extends Migration ...@@ -28,7 +28,7 @@ class CreateLimitsTable extends Migration
}); });
Schema::table('tokens', function (Blueprint $table) { Schema::table('tokens', function (Blueprint $table) {
$table->integer('limit'); $table->integer('limit')->default(0);
}); });
} }
......
<?php
namespace Tests\Unit;
use App\Models\Account;
use App\Models\Tokens;
use App\Models\User;
use App\Service\APIRequest;
use App\Service\Contract\API;
use App\Service\Limits;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Date;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
class ApiRequestTest extends TestCase
{
use RefreshDatabase;
private $request;
protected function setUp(): void
{
parent::setUp();
$this->request = \App\Service\Requests\APIRequest::getInstance(API::YANDEX);
}
public function testApi(){
$this->assertEquals($this->request->getApi(), API::YANDEX);
}
public function testTokenType(){
$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
]);
$this->request->setToken( $this->token );
$this->assertEquals($this->token, $this->request->getToken());
$this->expectException(\TypeError::class);
$this->request->setToken( 1 );
}
public function testChunk(){
$this->request->setParams([
'Page' => [
"Limit" => 10,
"Offset" => 0
]
]);
$request = $this->request->chunk(3);
$this->assertEquals($request->getParams()['Page']['Limit'], 7);
$this->assertEquals($this->request->getParams()['Page']['Limit'], 3);
$this->request->setParams([
'Page' => [
"Limit" => 10,
"Offset" => 0
]
]);
$request = $this->request->chunk(20);
$this->assertNull($request);
$this->assertEquals($this->request->getParams()['Page']['Limit'], 10);
}
}
<?php
namespace Tests\Unit;
use App\Models\Account;
use App\Models\Tokens;
use App\Models\User;
use App\Service\Contract\API;
use App\Service\Costs;
use App\Service\Limits;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Date;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
class CostsTest extends TestCase
{
use RefreshDatabase;
private $costs;
private $request;
protected function setUp(): void
{
parent::setUp();
$this->costs = new Costs();
$this->request = \App\Service\Requests\APIRequest::getInstance(API::YANDEX);
}
public function testGetCostCall(){
$this->request->setService('AdExtensions');
$this->request->setMethod('add');
$limit = $this->costs->getCostCall($this->request);
$this->assertEquals($limit, 5);
$this->request->setService('AdGroups');
$this->request->setMethod('update');
$limit = $this->costs->getCostCall($this->request);
$this->assertEquals($limit, 20);
$this->request->setService('Keywords');
$this->request->setMethod('get');
$limit = $this->costs->getCostCall($this->request);
$this->assertEquals($limit, 15);
}
public function testGetCostObject(){
$this->request->setService('Keywords');
$this->request->setMethod('get');
$this->request->setParams(['FieldNames' => []]);
$limit = $this->costs->getCostObject($this->request);
$this->assertEquals($limit, 1/2000);
$this->request->setService('Keywords');
$this->request->setMethod('get');
$this->request->setParams(['FieldNames' => ['Productivity']]);
$limit = $this->costs->getCostObject($this->request);
$this->assertEquals($limit, 3/2000);
$this->request->setService('SmartAdTargets');
$this->request->setMethod('get');
$limit = $this->costs->getCostObject($this->request);
$this->assertEquals($limit, 1);
}
}
<?php
namespace Tests\Unit;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function testBasicTest()
{
$this->assertTrue(true);
}
}
<?php
namespace Tests\Unit;
use App\Service\HeaderLimits;
use Tests\TestCase;
class HeaderLimitsTest extends TestCase
{
public function testParse(){
$limits = new HeaderLimits(['Units' => "0/0/0"]);
$this->assertEquals($limits->getDayLimit(), 0);
$this->assertEquals($limits->getCurrentLimit(), 0);
$this->assertEquals($limits->getSpentLimit(), 0);
$limits = new HeaderLimits(['Units' => "1/2/3"]);
$this->assertEquals($limits->getDayLimit(), 3);
$this->assertEquals($limits->getCurrentLimit(), 2);
$this->assertEquals($limits->getSpentLimit(), 1);
}
function testException(){
$this->expectException(\Exception::class);
$limits = new HeaderLimits([]);
}
}
<?php
namespace Tests\Unit;
use App\Models\Account;
use App\Models\Tokens;
use App\Models\User;
use App\Service\Contract\API;
use App\Service\Limits;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Date;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
class LimitsTest extends TestCase
{
use RefreshDatabase;
private $token;
private $limitService;
protected function setUp(): void
{
parent::setUp();
$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
]);
$this->limitService = Limits::getInstance($this->token);
}
public function testCurrentLimit()
{
$this->assertEquals($this->token->limit, $this->limitService->current());
}
public function testDayLimitException(){
$limit = $this->token->limits()->save(factory(\App\Models\Limits::class)->make([
'reserved' => 1
]));
$this->expectException(ModelNotFoundException::class);
$this->limitService->dayLimit();
}
public function testCurrentDayLimit()
{
$limit = $this->token->limits()->save(factory(\App\Models\Limits::class)->make([
'reserved' => 0
]));
$this->assertEquals($limit->day, $this->limitService->dayLimit());
sleep(1);
$limit = $this->token->limits()->save(factory(\App\Models\Limits::class)->make([
'reserved' => 0
]));
$this->assertEquals($limit->day, $this->limitService->dayLimit());
}
public function testRefreshCurrentLimit(){
$limit = $this->token->limits()->save(factory(\App\Models\Limits::class)->make([
'reserved' => 0,
'updated_at' => Date::now()->subHours(24),
'current' => 2,
'day' => 48
]));
$this->limitService->refreshCurrentLimit();
$this->assertEquals($this->token->limit, 48);
$limit = $this->token->limits()->save(factory(\App\Models\Limits::class)->make([
'reserved' => 0,
'updated_at' => Date::now()->subHours(3),
'current' => 2,
'day' => 48
]));
$this->limitService->refreshCurrentLimit();
$this->assertEquals($this->token->limit, 8);
$limit = $this->token->limits()->save(factory(\App\Models\Limits::class)->make([
'reserved' => 0,
'updated_at' => Date::now()->subHours(1),
'current' => 2,
'day' => 48
]));
$this->limitService->refreshCurrentLimit();
$this->assertEquals($this->token->limit, 4);
$limit = $this->token->limits()->save(factory(\App\Models\Limits::class)->make([
'reserved' => 0,
'current' => 0,
'day' => 24
]));
$this->limitService->refreshCurrentLimit();
$this->assertEquals($this->token->limit, 0);
$limit = $this->token->limits()->save(factory(\App\Models\Limits::class)->make([
'reserved' => 0,
'updated_at' => Date::now()->subHours(1),
'current' => 2,
'day' => 48
]));
$this->limitService->refreshCurrentLimit();
$this->assertEquals($this->token->limit, 0);
}
public function testCountObjectsLimit()
{
$request = \App\Service\Requests\APIRequest::getInstance(API::YANDEX);
$request->setService('AdExtensions');
$request->setMethod('add');
$this->token->limit = 6;
$objects = $this->limitService->countObjectsLimit($request);
$this->assertEquals($objects, 1);
$this->token->limit = 12;
$objects = $this->limitService->countObjectsLimit($request);
$this->assertEquals($objects, 7);
$request->setService('Bids');
$request->setMethod('get');
$this->token->limit = 18;
$objects = $this->limitService->countObjectsLimit($request);
$this->assertEquals($objects, 2000);
$this->token->limit = 21;
$objects = $this->limitService->countObjectsLimit($request);
$this->assertEquals($objects, 4000);
}
public function testDoRezerv()
{
$this->markTestIncomplete();
}
public function testRemoveRezerv()
{
$limit = $this->token->limits()->save(factory(\App\Models\Limits::class)->make([
'reserved' => 1,
'spent' => 10
]));
$spent = $this->token->limit;
$this->limitService->removeRezerv($limit->id);
$this->assertEquals($this->token->limit, $spent+10);
$this->expectException(ModelNotFoundException::class);
$this->limitService->dayLimit();
}
public function testAcceptRezerv(){
$this->markTestIncomplete();
}
public function testUpdateLimits(){
$this->markTestIncomplete();
}
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!