请求系统

框架对 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 缓存广播)。

延伸阅读