feat(images): 課程圖片伺服器端壓縮——scaleDown 2048 + JPEG 85,上限放寬至 10MB
Run Tests / test (pull_request) Successful in 2m0s

實作 openspec change course-image-compression(O3.1 體感優化):
- 新增 App\Traits\CompressesImages:與聊天圖片管線參數一致的共用壓縮邏輯
- CourseImageController 封面/相簿套用壓縮,上傳上限 2MB→10MB(容納手機原圖)
- 前端課程卡片與相簿縮圖加 loading=lazy(詳情頁首屏封面刻意不加)
- CourseImageTest +4 案例(10MB 邊界、轉存 .jpg、>2048 縮小、小圖不放大)
- course-image-upload 主規格同步(新增伺服器端壓縮 requirement)

容器內全套件 155 passed / 400 assertions。
聊天端改用 trait 留待後續(該處無測試覆蓋,不在本 change 重構)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 23:43:24 +08:00
parent 87a4f70e65
commit 6414222a18
11 changed files with 255 additions and 11 deletions
@@ -5,15 +5,19 @@ namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Models\CourseImage;
use App\Models\DivingOffer;
use App\Traits\CompressesImages;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class CourseImageController extends Controller
{
use CompressesImages;
private function validateImage(Request $request): void
{
$request->validate([
'image' => 'required|image|mimes:jpg,jpeg,png,webp|max:2048',
// 10MB:伺服器端會壓縮(compressToJpeg),放寬以容納手機原圖
'image' => 'required|image|mimes:jpg,jpeg,png,webp|max:10240',
]);
}
@@ -34,7 +38,7 @@ class CourseImageController extends Controller
Storage::disk('public')->delete($offer->cover_image);
}
$path = $request->file('image')->store("offers/{$offerId}/cover", 'public');
$path = $this->compressToJpeg($request->file('image'), "offers/{$offerId}/cover");
$offer->update(['cover_image' => $path]);
return response()->json([
@@ -67,7 +71,7 @@ class CourseImageController extends Controller
return response()->json(['status' => false, 'message' => '相簿最多 3 張圖片'], 422);
}
$path = $request->file('image')->store("offers/{$offerId}/gallery", 'public');
$path = $this->compressToJpeg($request->file('image'), "offers/{$offerId}/gallery");
$sortOrder = ($offer->courseImages()->max('sort_order') ?? 0) + 1;
$image = CourseImage::create([
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace App\Traits;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Intervention\Image\Drivers\Gd\Driver;
use Intervention\Image\ImageManager;
trait CompressesImages
{
/**
* 壓縮上傳圖片並存入 public disk,回傳相對路徑。
* 參數與聊天圖片管線一致(scaleDown 2048 + JPEG quality 85),
* 確保平台內影像處理行為單一來源。
*/
private function compressToJpeg(UploadedFile $file, string $directory): string
{
$manager = new ImageManager(new Driver());
$image = $manager->read($file);
if ($image->width() > 2048 || $image->height() > 2048) {
$image->scaleDown(width: 2048, height: 2048);
}
$path = trim($directory, '/') . '/' . Str::uuid() . '.jpg';
Storage::disk('public')->put($path, $image->toJpeg(quality: 85));
return $path;
}
}