Improve image import logging and move HTTP downloads out of DB transaction
Deploy / deploy (push) Successful in 16s
Deploy / deploy (push) Successful in 16s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
02c17d6984
commit
822790bfc0
3 files changed
+63
-10
No files matched your search
@@ -24,19 +24,29 @@ class ShopifyImages extends Command
|
||||
}
|
||||
|
||||
// Deduplizieren: pro Produkttitel nur einmal
|
||||
$products = collect($variants)
|
||||
$shopifyProducts = collect($variants)
|
||||
->filter(fn($v) => ! empty($v['image_urls']))
|
||||
->keyBy('product_title');
|
||||
|
||||
$this->info("Produkte mit Bildern in Shopify: {$products->count()}");
|
||||
$totalInShopify = collect($variants)->pluck('product_title')->unique()->count();
|
||||
$this->info("Shopify-Produkte gesamt: {$totalInShopify}");
|
||||
$this->info("Davon mit Bild-URLs: {$shopifyProducts->count()}");
|
||||
|
||||
if ($shopifyProducts->isEmpty()) {
|
||||
$this->warn('Kein Shopify-Produkt hat Bild-URLs — Import nicht möglich.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$downloaded = 0;
|
||||
$skipped = 0;
|
||||
$notFound = 0;
|
||||
|
||||
foreach ($products as $title => $variant) {
|
||||
foreach ($shopifyProducts as $title => $variant) {
|
||||
$product = Product::where('title', $title)->first();
|
||||
|
||||
if (! $product) {
|
||||
$this->warn(" Nicht lokal gefunden: \"{$title}\"");
|
||||
$notFound++;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -47,15 +57,27 @@ class ShopifyImages extends Command
|
||||
|
||||
$paths = [];
|
||||
foreach ($variant['image_urls'] as $url) {
|
||||
$response = Http::get($url);
|
||||
try {
|
||||
$response = Http::timeout(30)->get($url);
|
||||
} catch (\Exception $e) {
|
||||
$this->warn(" Verbindungsfehler für {$url}: {$e->getMessage()}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($response->failed()) {
|
||||
$this->warn(" Bild konnte nicht geladen werden: {$url}");
|
||||
$this->warn(" HTTP {$response->status()} für: {$url}");
|
||||
continue;
|
||||
}
|
||||
|
||||
$body = $response->body();
|
||||
if (empty($body)) {
|
||||
$this->warn(" Leere Antwort für: {$url}");
|
||||
continue;
|
||||
}
|
||||
|
||||
$ext = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION) ?: 'jpg';
|
||||
$filename = 'product-images/' . Str::uuid() . '.' . $ext;
|
||||
Storage::disk('public')->put($filename, $response->body());
|
||||
Storage::disk('public')->put($filename, $body);
|
||||
$paths[] = $filename;
|
||||
}
|
||||
|
||||
@@ -63,6 +85,8 @@ class ShopifyImages extends Command
|
||||
$product->update(['images' => $paths]);
|
||||
$this->line(" ✓ {$title} (" . count($paths) . " Bild(er))");
|
||||
$downloaded++;
|
||||
} else {
|
||||
$this->warn(" Keine Bilder gespeichert für: \"{$title}\"");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +94,9 @@ class ShopifyImages extends Command
|
||||
if ($skipped) {
|
||||
$this->line(" {$skipped} übersprungen (haben bereits Bilder — nutze --force zum Überschreiben)");
|
||||
}
|
||||
if ($notFound) {
|
||||
$this->warn(" {$notFound} Shopify-Produkte lokal nicht gefunden (Titelabweichung?)");
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,9 @@ class ShopifyImport extends Command
|
||||
|
||||
$stats = ['products' => 0, 'sizes' => 0, 'inventory' => 0, 'skipped' => 0];
|
||||
|
||||
DB::transaction(function () use ($variants, $dryRun, &$stats) {
|
||||
$newProducts = [];
|
||||
|
||||
DB::transaction(function () use ($variants, $dryRun, &$stats, &$newProducts) {
|
||||
foreach ($variants as $variant) {
|
||||
$packingRaw = $variant['option1'];
|
||||
|
||||
@@ -60,7 +62,11 @@ class ShopifyImport extends Command
|
||||
);
|
||||
if ($product->wasRecentlyCreated) {
|
||||
$stats['products']++;
|
||||
$this->downloadImages($product, $variant['image_urls']);
|
||||
// Collect for image download after transaction
|
||||
$newProducts[$product->id] = [
|
||||
'product' => $product,
|
||||
'image_urls' => $variant['image_urls'],
|
||||
];
|
||||
}
|
||||
|
||||
$size = ProductSize::firstOrCreate(
|
||||
@@ -93,6 +99,11 @@ class ShopifyImport extends Command
|
||||
}
|
||||
});
|
||||
|
||||
// Download images outside the transaction to avoid holding DB locks during HTTP calls
|
||||
foreach ($newProducts as ['product' => $product, 'image_urls' => $urls]) {
|
||||
$this->downloadImages($product, $urls);
|
||||
}
|
||||
|
||||
if (! $dryRun) {
|
||||
$this->info("✓ {$stats['products']} Produkte erstellt");
|
||||
$this->info("✓ {$stats['sizes']} Varianten erstellt");
|
||||
@@ -114,19 +125,33 @@ class ShopifyImport extends Command
|
||||
|
||||
$paths = [];
|
||||
foreach ($urls as $url) {
|
||||
$response = Http::get($url);
|
||||
try {
|
||||
$response = Http::timeout(30)->get($url);
|
||||
} catch (\Exception $e) {
|
||||
$this->warn(" Bild-Download fehlgeschlagen ({$product->title}): {$e->getMessage()}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($response->failed()) {
|
||||
$this->warn(" HTTP {$response->status()} beim Bild-Download ({$product->title}): {$url}");
|
||||
continue;
|
||||
}
|
||||
|
||||
$body = $response->body();
|
||||
if (empty($body)) {
|
||||
$this->warn(" Leere Bild-Antwort ({$product->title}): {$url}");
|
||||
continue;
|
||||
}
|
||||
|
||||
$ext = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION) ?: 'jpg';
|
||||
$filename = 'product-images/' . Str::uuid() . '.' . $ext;
|
||||
Storage::disk('public')->put($filename, $response->body());
|
||||
Storage::disk('public')->put($filename, $body);
|
||||
$paths[] = $filename;
|
||||
}
|
||||
|
||||
if (! empty($paths)) {
|
||||
$product->update(['images' => $paths]);
|
||||
$this->line(" ✓ Bilder gespeichert: {$product->title} (" . count($paths) . ')');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,7 @@ class ProductResource extends Resource {
|
||||
->columns([
|
||||
Tables\Columns\ImageColumn::make('images')
|
||||
->label('Bild')
|
||||
->disk('public')
|
||||
->limit(1),
|
||||
Tables\Columns\TextColumn::make('title')
|
||||
->label('Name')
|
||||
|
||||
Reference in new issue
Block a user