Files
CFDivePlatform/openspec/changes/api-performance-optimization/tasks.md
T
a620906209 efb1f22be3 feat:實作 API 效能優化 — Redis 快取、分頁、DB 索引
- 引入 Redis(predis)快取層:Admin Stats(5分鐘)、課程列表(3分鐘,tag-based 失效)、評價分布(10分鐘)
- ReviewController::publicList 改為 paginate + eager load votes,消除 N+1
- AdminReviewController::index 加入分頁(預設 20,最大 100)
- 新增 notifications / diving_offers 效能索引 migration
- 新增 docker-compose.override.yml 本機開發 port mapping 機制(不進 git)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 00:17:16 +08:00

3.3 KiB
Raw Blame History

1. 基礎設施:Redis 設定

  • 1.1 [後端] docker-compose.yml 新增 redis:alpine service,並在 cfdive-app depends_on 加入 redis
  • 1.2 [後端] .env 設定 CACHE_STORE=redisREDIS_HOST=redisREDIS_PORT=6379.env.example 同步補上
  • 1.3 [後端] config/cache.php 確認 redis connection 設定正確(REDIS_HOSTREDIS_PASSWORDREDIS_PORT
  • 1.4 [後端] 執行 docker-compose up --build,確認 redis container 啟動,php artisan cache:clear 無連線錯誤

2. 資料庫索引補充

  • 2.1 [後端] 建立 migration add_performance_indexes,為 notifications 表新增 [notifiable_type, notifiable_id, read_at] 複合索引
  • 2.2 [後端] 同一 migration 為 diving_offers 表新增 provider_id 索引
  • 2.3 [後端] 執行 php artisan migrate,確認索引建立成功(可用 SHOW INDEX FROM notifications 驗證)

3. 查詢優化:ReviewController::publicList

  • 3.1 [後端] ReviewController::publicList() 加入分頁:->paginate(min($perPage, 50))per_page 預設 20,從 request 取得
  • 3.2 [後端] 評價查詢改為 Review::with('votes')->where(...) eager load votes,移除獨立的 ReviewVote::where()->pluck() SQL
  • 3.3 [後端] has_voted 判斷改從 eager loaded $review->votes collection 計算($review->votes->contains('member_id', $memberId)
  • 3.4 [後端] 評價分布統計加上 Cache::remember("offer_review_distribution_{$offerId}", 600, fn() => ...) 快取(TTL 10 分鐘)
  • 3.5 [後端] Member 新增/修改/刪除評價的 service 方法中,加入 Cache::forget("offer_review_distribution_{$offerId}") 快取失效
  • 3.6 [後端] Response 加入 Laravel 分頁 metacurrent_pagelast_pageper_pagetotal

4. 查詢優化:AdminReviewController

  • 4.1 [後端] AdminReviewController::index().get() 改為 .paginate(min($perPage, 100))per_page 預設 20

5. 快取層:AdminStatsController

  • 5.1 [後端] AdminStatsController 的三個 count() 查詢,用 Cache::remember('admin_stats', 300, fn() => [...]) 包裹(TTL 5 分鐘)

6. 快取層:課程列表

  • 6.1 [後端] DivingOfferController::index() 對搜尋結果加入 Cache::remember() 快取,Cache key 為 diving_offers_ + md5(serialize($request->all()))TTL 180 秒
  • 6.2 [後端] DivingOfferController::store()update()destroy() 加入 Cache::flush()Cache::forget() 清除對應快取 key

7. 前端相容性調整

  • 7.1 [前端] CourseDetailView.vue 的 reviews API 呼叫確認 response 結構相容(data.data 現在含分頁 meta,原本是陣列)
  • 7.2 [前端] ReviewsView.vueCoach Portal)確認 API response 結構相容

8. 手動驗證

  • 8.1 [整合測試] 開啟 GET /api/diving-offers/{id}/reviews,確認回傳包含 meta.totalmeta.current_page 等分頁欄位
  • 8.2 [整合測試] 呼叫 GET /api/admin/stats 兩次,確認第二次無 DB 查詢(可用 Laravel Telescope 或 query log 確認)
  • 8.3 [整合測試] Provider 修改課程後,再次請求課程列表確認資料已更新(快取失效正常)
  • 8.4 [整合測試] 執行 SHOW INDEX FROM notificationsSHOW INDEX FROM diving_offers,確認索引存在