From cc58a0f37ac1d7594cd04b9d87e83f557c67eaaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Sat, 21 Mar 2026 10:35:12 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20[bending]=20Canvas=20=ED=8E=B8=EC=A7=91?= =?UTF-8?q?=EA=B8=B0=EC=9A=A9=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=ED=94=84?= =?UTF-8?q?=EB=A1=9D=EC=8B=9C=20=EB=9D=BC=EC=9A=B0=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(R2=20CORS=20=EC=9A=B0=ED=9A=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/FileViewController.php | 54 +++++++++++++++++++ resources/views/bending/base/form.blade.php | 2 +- .../views/bending/products/form.blade.php | 2 +- routes/web.php | 1 + 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/FileViewController.php b/app/Http/Controllers/FileViewController.php index 1e15781a..a605ab68 100644 --- a/app/Http/Controllers/FileViewController.php +++ b/app/Http/Controllers/FileViewController.php @@ -54,4 +54,58 @@ public function show(int $id) return redirect($url); } + + /** + * 이미지 프록시 (Canvas 편집기용) + * + * R2에서 이미지를 서버 사이드로 다운로드하여 같은 도메인에서 스트리밍. + * crossOrigin 요청 없이 Canvas에서 taint 없이 사용 가능. + */ + public function proxy(int $id) + { + $cacheKey = "file_presigned_url:{$id}"; + + $url = Cache::remember($cacheKey, now()->addMinutes(5), function () use ($id) { + $baseUrl = config('services.api.base_url', 'https://api.sam.kr'); + $internalUrl = config('services.api.internal_url'); + $apiKey = config('services.api.key'); + $token = session('api_access_token', ''); + + $headers = [ + 'X-API-KEY' => $apiKey, + 'X-TENANT-ID' => session('selected_tenant_id', 1), + ]; + + if ($internalUrl) { + $headers['Host'] = parse_url($baseUrl, PHP_URL_HOST) ?: 'api.sam.kr'; + $baseUrl = $internalUrl; + } + + $response = Http::baseUrl($baseUrl) + ->withoutVerifying() + ->withHeaders($headers) + ->withToken($token) + ->timeout(10) + ->get("/api/v1/files/{$id}/presigned-url"); + + return $response->successful() ? $response->json('data.url') : null; + }); + + if (! $url) { + Cache::forget($cacheKey); + abort(404); + } + + $imageResponse = Http::withoutVerifying()->timeout(15)->get($url); + + if (! $imageResponse->successful()) { + abort(404); + } + + $contentType = $imageResponse->header('Content-Type') ?: 'image/png'; + + return response($imageResponse->body(), 200) + ->header('Content-Type', $contentType) + ->header('Cache-Control', 'public, max-age=300'); + } } diff --git a/resources/views/bending/base/form.blade.php b/resources/views/bending/base/form.blade.php index 253f74d6..8e47860d 100644 --- a/resources/views/bending/base/form.blade.php +++ b/resources/views/bending/base/form.blade.php @@ -247,7 +247,7 @@ class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'b

형상 이미지

@if(!empty($imageFile)) - 전개도 + 전개도 @else 이미지 없음 @endif diff --git a/resources/views/bending/products/form.blade.php b/resources/views/bending/products/form.blade.php index f5a53677..0747740e 100644 --- a/resources/views/bending/products/form.blade.php +++ b/resources/views/bending/products/form.blade.php @@ -436,7 +436,7 @@ class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'b

결합형태 이미지

@if(!empty($imageFile)) - 결합형태 + 결합형태 @else 이미지 없음 @endif diff --git a/routes/web.php b/routes/web.php index 637cd830..051cb8bd 100644 --- a/routes/web.php +++ b/routes/web.php @@ -498,6 +498,7 @@ // 파일 뷰어 (API R2 이미지 프록시) Route::get('/files/{id}/view', [\App\Http\Controllers\FileViewController::class, 'show'])->whereNumber('id')->name('files.view'); + Route::get('/files/{id}/proxy', [\App\Http\Controllers\FileViewController::class, 'proxy'])->whereNumber('id')->name('files.proxy'); // 절곡품 기초관리 Route::prefix('bending')->name('bending.')->group(function () {