4.2 KiB
Services
src/services 是資料存取與 HTTP 邊界,負責封裝 ky client、hooks、token/session、錯誤處理與 API 模組。
目前資料流
component/view -> store/composable -> service module -> httpClient -> hooks
原則:
- component 不直接處理底層 HTTP client、token、hooks 或錯誤正規化。
- store 或 composable 負責協調 UI 狀態與呼叫 service。
- service 回傳資料,不持有 UI 狀態。
- service 不 import component、view 或 store。
目前檔案
client.ts:建立單一 ky instance,設定prefix、timeout、credentials 與 hooks。interceptors.ts:集中提供 ky hooks,處理 request token 注入與 response 錯誤。error.ts:提供normalizeError()與統一錯誤型別。http-error.ts:提供全域 HTTP 錯誤事件。http-toast.ts:提供 HTTP 錯誤提示相關流程。token.ts:提供 token 單一來源,並同步 localStorage。session.ts:提供 session 相關流程。modules/auth.ts:封裝登入與驗證碼 API。modules/menu.ts:封裝選單與收藏選單 API。
API 模組規則
新增 API 時,優先放在 src/services/modules/<domain>.ts。
API module 應:
- 使用
httpClient發 request。 - 匯出清楚命名的 API 物件,例如
authApi、menuApi。 - 定義與該 module 相關的 request/response 型別。
- 接收
AbortSignal等 request option,但不管理頁面 loading 或 controller 狀態。
ky 使用注意事項
本專案使用 ky,不使用 axios。新增或調整 API module 時注意:
- ky 不回傳 axios 的
{ data, status, headers }物件。需要 JSON 時使用.json<T>()。 - 若呼叫端已經依賴
{ data }形狀,請在 API module 內包回{ data: await ... },不要讓 store 或 component 混用多種 response 形狀。 - ky 的錯誤型別是
HTTPError、TimeoutError等,不是AxiosError。錯誤一律交給normalizeError(),呼叫端不要直接判斷 ky error。 - ky 基於 Fetch API,取消請求使用原生
AbortController與signal。 - token 注入、401 force logout、HTTP 錯誤導頁與 toast 都集中在 ky hooks。不要在單一 service module 裡重複實作。
- FormData 請用
body: formData;JSON payload 請用json: payload。 - 如果需求需要 upload progress、request/response transform、或其他 axios 專屬行為,先確認 ky/fetch 是否有等價做法,再決定是否擴充 service layer。
HTTP Client 設定
client.ts 的 baseURL 優先使用 VITE_API_BASE_URL,否則使用 /service/api。開發模式下,Vite proxy 會將 /service/* 轉送到後端。
template 提供 .env.example 作為環境變數範本。建立新專案時,複製成 .env 或對應 mode 的 env 檔,再填入實際 API 設定。
cp .env.example .env
目前 vite.config.mts 的 dev proxy 會將 /service/ 轉送到範例後端。正式專案若後端不同,優先調整 env 或 Vite proxy 設定,不要逐一修改 service module 裡的 endpoint。
production 不應沿用 template 內的示範後端位址,應由使用專案自己的部署環境提供 VITE_API_BASE_URL。
目前 API 呼叫範例:
authApi.getCaptcha()->/Auth/get-captchaauthApi.login()->/Auth/loginmenuApi.getMenu()->/Menu/GetMenumenuApi.getFavorite()->/Menu/GetFavorite
Token 與錯誤處理
token 由 tokenService 作為單一來源:
- store 負責登入成功後寫入 token,以及登出時清除 token。
- hooks 只讀取 token 並附加到 request。
- 401 或 HTTP 錯誤由 hooks 與錯誤事件流程集中處理。
錯誤透過 normalizeError() 轉成 UI 可理解的格式。UI 或 store 不需要直接理解 ky 的 HTTPError。
請求取消
需要取消請求時,由 store 或 composable 建立 AbortController,service module 只接收 signal。不要讓 service module 持有 controller 或 UI 狀態。
建議做法:
- 在 store/composable 以 key 管理同類請求(例如
auth/login、menu/get-menu)。 - 發新請求前先取消同 key 舊請求,避免競態與多餘流量。
- 請求結束後於
finally清理該 key;離開流程(如clear、logout)時清理全部 key。