feat: add warmup images command
This commit is contained in:
@@ -6,6 +6,7 @@ use Console\Commands\PulseSendEventsCommand;
|
||||
use Console\Commands\ScheduleListCommand;
|
||||
use Console\Commands\ScheduleRunCommand;
|
||||
use Console\Commands\VersionCommand;
|
||||
use Console\Commands\WarmupImagesCacheCommand;
|
||||
use Monolog\Handler\RotatingFileHandler;
|
||||
use Monolog\Logger;
|
||||
use Openguru\OpenCartFramework\QueryBuilder\Connections\MySqlConnection;
|
||||
@@ -64,7 +65,7 @@ $items = Arr::mergeArraysRecursively($json, [
|
||||
],
|
||||
'store' => [
|
||||
'oc_store_id' => 0,
|
||||
'oc_default_currency' => 1,
|
||||
'oc_default_currency' => 'RUB',
|
||||
'oc_config_tax' => false,
|
||||
],
|
||||
'orders' => [
|
||||
@@ -94,4 +95,5 @@ $console->add($app->get(VersionCommand::class));
|
||||
$console->add($app->get(ScheduleRunCommand::class));
|
||||
$console->add($app->get(ScheduleListCommand::class));
|
||||
$console->add($app->get(PulseSendEventsCommand::class));
|
||||
$console->add($app->get(WarmupImagesCacheCommand::class));
|
||||
$console->run();
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\ServiceProviders\AppServiceProvider;
|
||||
use App\ServiceProviders\SettingsServiceProvider;
|
||||
use Openguru\OpenCartFramework\Application;
|
||||
use Openguru\OpenCartFramework\Cache\CacheServiceProvider;
|
||||
use Openguru\OpenCartFramework\ImageTool\ImageToolServiceProvider;
|
||||
use Openguru\OpenCartFramework\QueryBuilder\QueryBuilderServiceProvider;
|
||||
use Openguru\OpenCartFramework\Scheduler\SchedulerServiceProvider;
|
||||
use Openguru\OpenCartFramework\Support\Arr;
|
||||
@@ -29,6 +30,7 @@ class ApplicationFactory
|
||||
TelegramServiceProvider::class,
|
||||
TeleCartPulseServiceProvider::class,
|
||||
SchedulerServiceProvider::class,
|
||||
ImageToolServiceProvider::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
|
||||
namespace Console\Commands;
|
||||
|
||||
use App\Services\SettingsService;
|
||||
use Exception;
|
||||
use Openguru\OpenCartFramework\ImageTool\ImageFactory;
|
||||
use Openguru\OpenCartFramework\ImageTool\ImageUtils;
|
||||
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
||||
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
|
||||
use Openguru\OpenCartFramework\Support\Arr;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class WarmupImagesCacheCommand extends TeleCartCommand
|
||||
{
|
||||
protected static $defaultName = 'images:warmup';
|
||||
protected static $defaultDescription = 'Прогрев кеша изображений товаров';
|
||||
|
||||
private Builder $queryBuilder;
|
||||
private ImageFactory $image;
|
||||
private SettingsService $settings;
|
||||
private LoggerInterface $logger;
|
||||
|
||||
public function __construct(
|
||||
Builder $queryBuilder,
|
||||
ImageFactory $image,
|
||||
SettingsService $settings,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->queryBuilder = $queryBuilder;
|
||||
$this->image = $image;
|
||||
$this->settings = $settings;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->addArgument(
|
||||
'product_id',
|
||||
InputArgument::OPTIONAL,
|
||||
'ID товара для прогрева кеша (если не указан, прогреваются все товары)'
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$productId = $input->getArgument('product_id');
|
||||
|
||||
$io->title('Прогрев кеша изображений товаров');
|
||||
|
||||
// Получаем настройки
|
||||
$aspectRatio = $this->settings->get('app.image_aspect_ratio', '1:1');
|
||||
$cropAlgorithm = $this->settings->get('app.image_crop_algorithm', 'cover');
|
||||
[$imageWidth, $imageHeight] = ImageUtils::aspectRatioToSize($aspectRatio);
|
||||
$languageId = $this->settings->config()->getApp()->getLanguageId();
|
||||
|
||||
$io->section('Настройки');
|
||||
$io->listing([
|
||||
"Соотношение сторон: {$aspectRatio}",
|
||||
"Алгоритм обрезки: {$cropAlgorithm}",
|
||||
"Размер изображения: {$imageWidth}x{$imageHeight}",
|
||||
"Размер миниатюры: 500x500",
|
||||
"Размер большого изображения: 1000x1000",
|
||||
]);
|
||||
|
||||
// Получаем список товаров
|
||||
$products = $this->getProducts($productId, $languageId);
|
||||
|
||||
if (empty($products)) {
|
||||
$io->warning('Товары не найдены');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$totalProducts = count($products);
|
||||
$io->section("Найдено товаров: {$totalProducts}");
|
||||
|
||||
$stats = [
|
||||
'products' => 0,
|
||||
'main_images' => 0,
|
||||
'additional_images' => 0,
|
||||
'thumbnails' => 0,
|
||||
'large_images' => 0,
|
||||
'errors' => 0,
|
||||
];
|
||||
|
||||
$progressBar = $io->createProgressBar($totalProducts);
|
||||
$progressBar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %message%');
|
||||
$progressBar->setMessage('Обработка товаров...');
|
||||
$progressBar->start();
|
||||
|
||||
foreach ($products as $product) {
|
||||
$productId = $product['product_id'];
|
||||
$productName = $product['product_name'] ?? "ID: {$productId}";
|
||||
$progressBar->setMessage("Товар: {$productName}");
|
||||
|
||||
try {
|
||||
// Прогреваем основное изображение товара
|
||||
if (!empty($product['product_image'])) {
|
||||
try {
|
||||
$this->image->make($product['product_image'])
|
||||
->crop($cropAlgorithm, $imageWidth, $imageHeight)
|
||||
->url();
|
||||
$stats['main_images']++;
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error("Ошибка при прогреве основного изображения товара {$productId}: " . $e->getMessage());
|
||||
$stats['errors']++;
|
||||
}
|
||||
}
|
||||
|
||||
// Получаем дополнительные изображения товара
|
||||
$additionalImages = $this->getProductAdditionalImages($productId);
|
||||
$processedAdditional = 0;
|
||||
foreach ($additionalImages as $imagePath) {
|
||||
if ($processedAdditional >= 2) {
|
||||
break; // Ограничиваем до 2 дополнительных изображений, как в ProductsService
|
||||
}
|
||||
try {
|
||||
$this->image->make($imagePath)
|
||||
->crop($cropAlgorithm, $imageWidth, $imageHeight)
|
||||
->url();
|
||||
$stats['additional_images']++;
|
||||
$processedAdditional++;
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error("Ошибка при прогреве дополнительного изображения товара {$productId}: " . $e->getMessage());
|
||||
$stats['errors']++;
|
||||
}
|
||||
}
|
||||
|
||||
// Прогреваем изображения для детальной страницы (миниатюры и большие)
|
||||
$allImages = [];
|
||||
if (!empty($product['product_image'])) {
|
||||
$allImages[] = $product['product_image'];
|
||||
}
|
||||
$allImages = array_merge($allImages, $additionalImages);
|
||||
|
||||
foreach ($allImages as $imagePath) {
|
||||
try {
|
||||
// Миниатюра
|
||||
$this->image->make($imagePath)
|
||||
->contain(500, 500)
|
||||
->url();
|
||||
$stats['thumbnails']++;
|
||||
|
||||
// Большое изображение
|
||||
$this->image->make($imagePath)
|
||||
->resize(1000, 1000)
|
||||
->url();
|
||||
$stats['large_images']++;
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error("Ошибка при прогреве изображений для детальной страницы товара {$productId}: " . $e->getMessage());
|
||||
$stats['errors']++;
|
||||
}
|
||||
}
|
||||
|
||||
$stats['products']++;
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error("Ошибка при обработке товара {$productId}: " . $e->getMessage());
|
||||
$stats['errors']++;
|
||||
}
|
||||
|
||||
$progressBar->advance();
|
||||
}
|
||||
|
||||
$progressBar->setMessage('Завершено');
|
||||
$progressBar->finish();
|
||||
$io->newLine(2);
|
||||
|
||||
// Выводим статистику
|
||||
$io->section('Статистика');
|
||||
$io->table(
|
||||
['Метрика', 'Значение'],
|
||||
[
|
||||
['Обработано товаров', $stats['products']],
|
||||
['Основных изображений', $stats['main_images']],
|
||||
['Дополнительных изображений', $stats['additional_images']],
|
||||
['Миниатюр (500x500)', $stats['thumbnails']],
|
||||
['Больших изображений (1000x1000)', $stats['large_images']],
|
||||
['Ошибок', $stats['errors']],
|
||||
]
|
||||
);
|
||||
|
||||
if ($stats['errors'] > 0) {
|
||||
$io->warning("Обнаружено {$stats['errors']} ошибок. Проверьте логи для подробностей.");
|
||||
} else {
|
||||
$io->success('Кеш изображений успешно прогрет!');
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает список товаров для прогрева кеша
|
||||
*/
|
||||
private function getProducts(?string $productId, int $languageId): array
|
||||
{
|
||||
$query = $this->queryBuilder->newQuery()
|
||||
->select([
|
||||
'products.product_id' => 'product_id',
|
||||
'products.image' => 'product_image',
|
||||
'product_description.name' => 'product_name',
|
||||
])
|
||||
->from(db_table('product'), 'products')
|
||||
->join(
|
||||
db_table('product_description') . ' AS product_description',
|
||||
function (JoinClause $join) use ($languageId) {
|
||||
$join->on('products.product_id', '=', 'product_description.product_id')
|
||||
->where('product_description.language_id', '=', $languageId);
|
||||
}
|
||||
)
|
||||
->where('products.status', '=', 1)
|
||||
->whereRaw('products.date_available < NOW()');
|
||||
|
||||
if ($productId !== null) {
|
||||
$query->where('products.product_id', '=', (int) $productId);
|
||||
}
|
||||
|
||||
return $query->orderBy('products.product_id', 'ASC')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает дополнительные изображения товара
|
||||
*/
|
||||
private function getProductAdditionalImages(int $productId): array
|
||||
{
|
||||
$images = $this->queryBuilder->newQuery()
|
||||
->select(['products_images.image' => 'image'])
|
||||
->from(db_table('product_image'), 'products_images')
|
||||
->where('products_images.product_id', '=', $productId)
|
||||
->orderBy('products_images.sort_order')
|
||||
->get();
|
||||
|
||||
return Arr::pluck($images, 'image');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ class ImageFactory
|
||||
*/
|
||||
public function make(?string $path = null): self
|
||||
{
|
||||
$this->modifications = [];
|
||||
$this->path = $path;
|
||||
$this->fullPath = $this->imageDir . '/' . $this->path;
|
||||
|
||||
@@ -178,7 +179,7 @@ class ImageFactory
|
||||
$filename .= '_' . substr(hash('SHA256', json_encode($this->modifications)), 0, 12);
|
||||
}
|
||||
|
||||
return $this->imageDir . '/cache/' . $imagePath . $filename . '.' . $format;
|
||||
return $this->imageDir . '/cache/telecart/' . $imagePath . $filename . '.' . $format;
|
||||
}
|
||||
|
||||
private function applyModifications(): void
|
||||
|
||||
Reference in New Issue
Block a user