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 @@
namespace App\Console\Commands;
use App\Models\Tokens;
use App\Service\Limits;
use Illuminate\Console\Command;
class refreshLimits extends Command
......@@ -11,14 +13,14 @@ class refreshLimits extends Command
*
* @var string
*/
protected $signature = 'command:name';
protected $signature = 'refresh:limits';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
protected $description = 'Обновление количетсво баллов по токенам';
/**
* Create a new command instance.
......@@ -37,6 +39,9 @@ class refreshLimits extends Command
*/
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
return;
}
//вызов АПИ разбиваем на несколько, если коненчо не хватает баллов одним запросом все получить
//вызов АПИ разбиваем на несколько
if ($api = $this->api->chunk($objects)){
//вызываем для остальных объектов вызов АПИ
self::dispatch( new ProcessCallLimitedAPI($this->api->getToken(), $api));
......
......@@ -8,4 +8,9 @@ use Illuminate\Database\Eloquent\Model;
class Limits extends Model
{
use HasFactory;
public function scopeComplited($query)
{
$query->where('reserved', 0);
}
}
<?php
namespace App\Service;
namespace App\Service\API;
use App\Service\Contract\APIRequest;
use Illuminate\Http\Client\Response;
......@@ -15,7 +15,7 @@ class API implements \App\Service\Contract\API {
static function getInstance(APIRequest $request = null){
switch($request->getApi()){
case 'yd':
case API::YANDEX:
return new YandexDirect($request);
break;
}
......
<?php
namespace App\Service;
namespace App\Service\API;
use Illuminate\Support\Facades\Http;
......
......@@ -12,5 +12,6 @@ class AdsHandler{
public static function getInstance(APIRequest $request = null){
self::$_instance = new self($request);
return self::$_instance;
}
}
......@@ -4,6 +4,8 @@ namespace App\Service\Contract;
use Illuminate\Http\Client\Response;
interface API{
CONST YANDEX = 'yd';
function getAuthLink();
function getToken($code);
......
......@@ -10,10 +10,9 @@ interface APIRequest{
function getMethod();
function setParams(array $params);
function getParams();
function setToken(int $token);
function setToken(Tokens $token);
function getToken(): Tokens;
function setApi(string $api);
function getApi(): string;
function chunk($objects): APIRequest;
function chunk($objects): ?APIRequest;
}
......@@ -6,7 +6,7 @@ interface Limits{
function DayLimit(): int;
function countObjectsLimit(APIRequest $request): int;
function doRezerv(APIRequest $request, int $limit): int;
function removeRezerv(int $id): int;
function removeRezerv(int $id);
function updateLimits(HeaderLimits $limits);
function acceptRezerv($id, HeaderLimits $limits);
}
......@@ -7,10 +7,10 @@ class HeaderLimits implements \App\Service\Contract\HeaderLimits{
private $currentLimit = 0;
private $spentLimit = 0;
function __constructor(Array $headers)
function __construct(Array $headers)
{
if (!isset($headers['Units'])){
throw new Exception('Не найден заголовок с баллами');
throw new \Exception('Не найден заголовок с баллами');
}
list($this->spentLimit, $this->currentLimit, $this->dayLimit) = explode("/", $headers['Units']);
}
......
......@@ -3,22 +3,22 @@ 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 static $_instance;
private $token;
private $limitCosts;
private function __constructor(Tokens $token){
private function __construct(Tokens $token){
$this->token = $token;
$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
......@@ -26,9 +26,29 @@ class Limits implements \App\Service\Contract\Limits {
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
......@@ -81,7 +101,7 @@ class Limits implements \App\Service\Contract\Limits {
return $rezerv->id;
}
function removeRezerv(int $id): int
function removeRezerv(int $id)
{
DB::beginTransaction();
try{
......
<?php
namespace App\Service;
namespace App\Service\Requests;
use App\Models\Tokens;
use App\Service\API\API;
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)
{
// TODO: Implement setService() method.
$this->service = $service;
}
function getService()
{
// TODO: Implement getService() method.
return $this->service;
}
function setMethod(string $method)
{
// TODO: Implement setMethod() method.
$this->method = $method;
}
function getMethod()
{
// TODO: Implement getMethod() method.
return $this->method;
}
function setParams(array $params)
{
// TODO: Implement setParams() method.
$this->params = $params;
}
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
{
// TODO: Implement getToken() method.
}
function setApi(string $api)
{
// TODO: Implement setApi() method.
}
function getApi(): string
{
// TODO: Implement getApi() method.
return $this->token;
}
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
});
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!