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([