請求系統

框架對 Nuxt 內建 $fetch 的封裝,提供 AES 加密傳輸、Token 自動注入與續期、統一回應格式解包(status/data/error/errorCode)和錯誤攔截能力。

基本用法

框架透過全域自動導入暴露 request() 函式:

// 基礎請求(預設 POST)
const data = await request('/api/user/info')

// 帶 body 的請求(自動序列化)
const result = await request('/api/login', {
  body: { username: 'admin', password: '123456' },
})

// 帶查詢參數
const list = await request('/api/products', {
  params: { page: 1, pageSize: 20, keyword: '搜尋詞' },
})

公開 plain JSON 中繼資料例外

OpenAPI operation 明確標記 x-sw-protocol: plain-json 時,generate-api 會略過該操作,不產生 request() 的加密客戶端包裝;此類中繼資料 GET 由 server 原生 $fetch 按後端契約存取,明確傳固定租戶識別,禁止轉發訪客 cookie、Authorization 或簽名參數。x-sw-visibility: public 只描述存取資格,不能取代傳輸協定標記。未標記的既有業務介面繼續走加密協定;未知 x-sw-protocol 會讓生成失敗。

本節新增 API 對應目前本地待發布改動;尚未發布。消費方採用前須核驗實際框架版本。

回應格式約定

框架約定後端介面回傳統一格式(回應體解密後),request() 自動解包 data 欄位:

// 後端回傳(解密後)
{ "status": "y", "data": { "id": 1, "name": "張三" } }

// request() 回傳值直接是 data 部分
const user = await request('/api/user/info')
// user = { id: 1, name: "張三" }

當 status === 'n' 時,request() 拋出 Error(message 為後端 error 欄位),不會自動顯示 toast —— 由呼叫方 try/catch 並給使用者回饋。

明文失敗信封:舊後端是混合協定——多數回應加密,但部分業務失敗(如登入介面的「帳號或密碼錯誤」4002)以明文 JSON 回傳,且 Content-Type 與加密回應相同。request() 會在解密前識別這類明文失敗信封並同樣拋 ApiError,業務側不必區分。只放行 status: 'n' 的明文;明文 status: 'y' 或任意 JSON 視為協定錯誤。解密失敗(金鑰 / transport 錯配、後端回傳非協定報文)統一拋以 [swift-web] 响应报文无法解密 開頭的 Error,不再冒出 Invalid typed array length 這類底層例外。

HTTP 200 但 body 為空

極少數情況下後端會回傳 HTTP 200 + 空 body。此時 request() 回傳 undefined(型別簽章上仍是 Promise<T>),不拋錯 —— 這是舊協定的既定行為,舊前端同樣原樣放行。

問題在於消費方拿到 undefined 會渲染成空狀態,與「後端真的沒資料」不可區分,出問題時排查無線索。所以 framework 在開發期會為此打一條告警(每個端點只出一次,生產建置不含該程式碼):

[swift-web] POST /movie/home 返回 HTTP 200 但 body 为空 → request() 返回 undefined。

看到這條告警時:先確認該端點是否本就允許回傳空。若不該為空,問題在後端,不要在前端加兜底把它掩蓋掉。

自動 Token 注入

request() 自動攜帶身分憑證:請求體統一包裝為 { data, token, deviceId }(token 取自 useUserStore().token),gzip + AES-CBC 加密後作為 text/plain 發送 —— 無需手動傳 headers。

上面以 text/plain 發送的是預設的 base64 傳輸編碼;對接裸二進位加密協定的後端時改配 request.transport: 'binary'(收發均為裸位元組流),見 Module Options · request。

配置

環境相關項放 .env(建置期由 nuxi 載入、烤入產物):

# .env —— 每個環境不同的值
NUXT_PUBLIC_API_BASE=https://api.example.com   # 後端 API 基礎路徑

apiPrefix / apiVersion / apiKey 是專案常數(換環境也不變),直接寫在 nuxt.config.ts 的 runtimeConfig.public 實值裡隨程式碼走;執行期同名 NUXT_PUBLIC_* 程序環境變數仍可臨時覆蓋(Nuxt 原生機制)。

錯誤處理

try {
  const data = await request('/api/user/sensitive-info')
} catch (e) {
  // request() 失敗拋 Error(message 為後端 error 欄位或網路錯誤訊息)
  // token 失效(errorCode 2002)已在內部靜默重登重試,業務無需單獨處理
  showToast(e instanceof Error ? e.message : '請求失敗,請重試')
}

useFetch 與 request() 的選擇

場景推薦方式
頁面初始資料(SSR)useFetch() 或 useAsyncData()
使用者操作觸發的請求request()(無響應式包裝,更簡潔)
需要取消 / 重新請求useFetch()(自帶 abort 和 refresh)
伺服器端 API route$fetch()(Nitro 內建,無需 Token)

Token 過期自動續期

framework 的 request() 在回應解包階段攔截 errorCode === 2002(token 失效),自動呼叫 silentLogin() 重新建立身份後帶 _retry=true 重發原請求一次,業務側零感知。再失敗拋 登入失效,請重新整理頁面 供上層兜底。

注意:該機制僅在 client 端生效。SSR 期 server 端遇到 2002 會按未登入處理(不重試),原因是 server 端無法重新建立設備身份(重新登入會寫 Set-Cookie 到回應頭,被 ISR 快取廣播)。

延伸閱讀