542b7d2d22
Р6 — задание, брошенное «в работе», держало очередь для всех клиентов навсегда. Причина сбоя обрезается до 900 знаков, иначе портал отвергал отчёт о самом частом виде поломки. Провал отчёта больше не глотается молча — едет в письмо человеку. Приём отчёта ловит любую ошибку и закрывает задание сбойным вместо 500 роботу. Новый сторож creative-jobs:reap каждые 10 минут разбирает пробку. Р7 — обрыв на докладе «готово» объявлял провалом уже сделанную работу. Доклад вынесен из-под обработчика сбоя, повторяется трижды, при неудаче только письмо. Осечка ухода со страницы после «Создать» больше не считается провалом загрузки. Р8 — пустой список размеров считался успехом, и робот заливал одни и те же файлы по кругу, оставляя мусор в живом кабинете. Теперь громкий отказ до похода в Яндекс. Портал 274/274, робот 41/41. Все защиты проверены вырезанием. Выходов снятия заморозки денег по-прежнему четыре.
70 lines
3.4 KiB
PHP
70 lines
3.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Advertising;
|
|
|
|
use App\Exceptions\Advertising\CreativeMatchFailedException;
|
|
|
|
/**
|
|
* Опознание креативов, загруженных роботом в веб-кабинет Яндекса.
|
|
*
|
|
* Имя опознать не помогает: всем загруженным креативам Яндекс сам ставит имя
|
|
* «Новый креатив» (проверено живым запросом 27.07.2026). Поэтому берём разницу
|
|
* слепков `creatives.get` «до» и «после» загрузки и раскладываем новые креативы
|
|
* по размеру. Чистая функция — без БД и без сети.
|
|
*/
|
|
final class CreativeIdMatcher
|
|
{
|
|
/**
|
|
* @param array<int, array{0: int, 1: int}> $before слепок до загрузки: номер → [ш, в]
|
|
* @param array<int, array{0: int, 1: int}> $after слепок после загрузки
|
|
* @param list<array{0: int, 1: int}> $expectedSizes размеры, которые робот должен был залить
|
|
* @return array<string, int> «300x250» → номер креатива
|
|
*
|
|
* @throws CreativeMatchFailedException
|
|
*/
|
|
public function match(array $before, array $after, array $expectedSizes): array
|
|
{
|
|
// Пустой список ожидаемых размеров — не «всё сошлось», а тихий ноль: цикл ниже
|
|
// просто не выполнится и вернёт пустоту как успех. Задание пометится «готово»
|
|
// с нулём проставленных номеров, запуск снова увидит баннеры без креативов и
|
|
// поставит новое задание — робот будет заливать те же файлы по кругу, оставляя
|
|
// каждый раз пачку мусорных креативов в живом кабинете. Ловим это громко.
|
|
if ($expectedSizes === []) {
|
|
throw new CreativeMatchFailedException(
|
|
'Опознавать нечего: список ожидаемых размеров пуст. Так бывает, если баннеры кампании не видны '
|
|
.'служебной роли — проверьте, что после выката перезапущен db/03_service_bypass_policies.sql.'
|
|
);
|
|
}
|
|
|
|
$newIds = array_diff_key($after, $before);
|
|
|
|
$bySize = [];
|
|
foreach ($newIds as $id => [$w, $h]) {
|
|
$bySize[$w.'x'.$h][] = (int) $id;
|
|
}
|
|
|
|
$out = [];
|
|
foreach ($expectedSizes as [$w, $h]) {
|
|
$key = $w.'x'.$h;
|
|
$found = $bySize[$key] ?? [];
|
|
|
|
if (count($found) === 0) {
|
|
throw new CreativeMatchFailedException(
|
|
"После загрузки не появился креатив размера {$key}."
|
|
);
|
|
}
|
|
if (count($found) > 1) {
|
|
throw new CreativeMatchFailedException(
|
|
"После загрузки появилось несколько креативов размера {$key} — какой чей, не определить."
|
|
);
|
|
}
|
|
|
|
$out[$key] = $found[0];
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
}
|