161 lines
6.4 KiB
PHP
161 lines
6.4 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Console\Commands;
|
||
|
|
|
||
|
|
use App\Models\Commons\File;
|
||
|
|
use Illuminate\Console\Attributes\AsCommand;
|
||
|
|
use Illuminate\Console\Command;
|
||
|
|
use Illuminate\Support\Facades\DB;
|
||
|
|
use Illuminate\Support\Facades\Http;
|
||
|
|
use Illuminate\Support\Facades\Storage;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 가이드레일/케이스/하단마감재 모델의 부품별 이미지 임포트
|
||
|
|
*
|
||
|
|
* chandj guiderail/shutterbox/bottombar components의 imgdata →
|
||
|
|
* 5130.codebridge-x.com에서 다운로드 → R2 업로드 → components에 file_id 추가
|
||
|
|
*/
|
||
|
|
#[AsCommand(name: 'bending-model:import-images', description: '절곡품 모델 부품별 이미지 → R2 마이그레이션')]
|
||
|
|
class BendingModelImportImages extends Command
|
||
|
|
{
|
||
|
|
protected $signature = 'bending-model:import-images
|
||
|
|
{--tenant_id=287 : Target tenant ID}
|
||
|
|
{--dry-run : 실제 저장 없이 미리보기}
|
||
|
|
{--source=https://5130.codebridge-x.com/bending/img : 이미지 소스 URL}';
|
||
|
|
|
||
|
|
private int $uploaded = 0;
|
||
|
|
|
||
|
|
private int $skipped = 0;
|
||
|
|
|
||
|
|
private int $failed = 0;
|
||
|
|
|
||
|
|
public function handle(): int
|
||
|
|
{
|
||
|
|
$tenantId = (int) $this->option('tenant_id');
|
||
|
|
$dryRun = $this->option('dry-run');
|
||
|
|
$sourceBase = rtrim($this->option('source'), '/');
|
||
|
|
|
||
|
|
$this->info('=== 절곡품 모델 부품 이미지 → R2 마이그레이션 ===');
|
||
|
|
$this->info('Mode: '.($dryRun ? 'DRY-RUN' : 'LIVE'));
|
||
|
|
$this->newLine();
|
||
|
|
|
||
|
|
// chandj에서 원본 imgdata 조회
|
||
|
|
$chandjTables = [
|
||
|
|
'GUIDERAIL_MODEL' => 'guiderail',
|
||
|
|
'SHUTTERBOX_MODEL' => 'shutterbox',
|
||
|
|
'BOTTOMBAR_MODEL' => 'bottombar',
|
||
|
|
];
|
||
|
|
|
||
|
|
foreach ($chandjTables as $category => $table) {
|
||
|
|
$this->info("--- {$category} ({$table}) ---");
|
||
|
|
|
||
|
|
$chandjRows = DB::connection('chandj')->table($table)->whereNull('is_deleted')->get();
|
||
|
|
$samItems = DB::table('items')->where('tenant_id', $tenantId)
|
||
|
|
->where('item_category', $category)->whereNull('deleted_at')
|
||
|
|
->get(['id', 'code', 'options']);
|
||
|
|
|
||
|
|
// legacy_num → chandj row 매핑
|
||
|
|
$chandjMap = [];
|
||
|
|
foreach ($chandjRows as $row) {
|
||
|
|
$chandjMap[$row->num] = $row;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach ($samItems as $samItem) {
|
||
|
|
$opts = json_decode($samItem->options, true) ?? [];
|
||
|
|
$legacyNum = $opts['legacy_num'] ?? $opts['legacy_guiderail_num'] ?? null;
|
||
|
|
|
||
|
|
if (! $legacyNum || ! isset($chandjMap[$legacyNum])) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
$chandjRow = $chandjMap[$legacyNum];
|
||
|
|
$chandjComps = json_decode($chandjRow->bending_components ?? '[]', true) ?: [];
|
||
|
|
$components = $opts['components'] ?? [];
|
||
|
|
$updated = false;
|
||
|
|
|
||
|
|
foreach ($components as $idx => &$comp) {
|
||
|
|
// chandj component에서 imgdata 찾기
|
||
|
|
$chandjComp = $chandjComps[$idx] ?? null;
|
||
|
|
$imgdata = $chandjComp['imgdata'] ?? null;
|
||
|
|
|
||
|
|
if (! $imgdata || ! empty($comp['image_file_id'])) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
$imageUrl = "{$sourceBase}/{$imgdata}";
|
||
|
|
|
||
|
|
if ($dryRun) {
|
||
|
|
$this->line(" ✅ {$samItem->code} #{$idx} ← {$imgdata}");
|
||
|
|
$this->uploaded++;
|
||
|
|
$updated = true;
|
||
|
|
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
$response = Http::withoutVerifying()->timeout(15)->get($imageUrl);
|
||
|
|
|
||
|
|
if (! $response->successful()) {
|
||
|
|
$this->warn(" ❌ {$samItem->code} #{$idx}: HTTP {$response->status()}");
|
||
|
|
$this->failed++;
|
||
|
|
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
$imageContent = $response->body();
|
||
|
|
$extension = pathinfo($imgdata, PATHINFO_EXTENSION) ?: 'png';
|
||
|
|
$storedName = bin2hex(random_bytes(8)).'.'.strtolower($extension);
|
||
|
|
$directory = sprintf('%d/items/%s/%s', $tenantId, date('Y'), date('m'));
|
||
|
|
$filePath = $directory.'/'.$storedName;
|
||
|
|
|
||
|
|
Storage::disk('r2')->put($filePath, $imageContent);
|
||
|
|
|
||
|
|
$file = File::create([
|
||
|
|
'tenant_id' => $tenantId,
|
||
|
|
'display_name' => $imgdata,
|
||
|
|
'stored_name' => $storedName,
|
||
|
|
'file_path' => $filePath,
|
||
|
|
'file_size' => strlen($imageContent),
|
||
|
|
'mime_type' => $response->header('Content-Type', 'image/png'),
|
||
|
|
'file_type' => 'image',
|
||
|
|
'field_key' => 'bending_component_image',
|
||
|
|
'document_id' => $samItem->id,
|
||
|
|
'document_type' => '1',
|
||
|
|
'is_temp' => false,
|
||
|
|
'uploaded_by' => 1,
|
||
|
|
'created_by' => 1,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$comp['image_file_id'] = $file->id;
|
||
|
|
$comp['imgdata'] = $imgdata;
|
||
|
|
$updated = true;
|
||
|
|
$this->uploaded++;
|
||
|
|
$this->line(" ✅ {$samItem->code} #{$idx} {$comp['itemName']} ← {$imgdata} → file_id={$file->id}");
|
||
|
|
} catch (\Exception $e) {
|
||
|
|
$this->error(" ❌ {$samItem->code} #{$idx}: {$e->getMessage()}");
|
||
|
|
$this->failed++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
unset($comp);
|
||
|
|
|
||
|
|
// components 업데이트
|
||
|
|
if ($updated && ! $dryRun) {
|
||
|
|
$opts['components'] = $components;
|
||
|
|
DB::table('items')->where('id', $samItem->id)->update([
|
||
|
|
'options' => json_encode($opts, JSON_UNESCAPED_UNICODE),
|
||
|
|
'updated_at' => now(),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->newLine();
|
||
|
|
$this->info("업로드: {$this->uploaded}건 | 스킵: {$this->skipped}건 | 실패: {$this->failed}건");
|
||
|
|
if ($dryRun) {
|
||
|
|
$this->info('🔍 DRY-RUN 완료.');
|
||
|
|
}
|
||
|
|
|
||
|
|
return self::SUCCESS;
|
||
|
|
}
|
||
|
|
}
|