跳到主要内容

API 请求

BBC 8.0 通过 @shop-tnt/api@shop-tnt/utils/request 统一处理 token、刷新重试、请求取消、Loading、错误提示与响应解包。页面不得自行创建另一套 Axios 实例绕过这些能力。

分层与放置位置

位置职责是否放请求函数
packages/api/request.tsapiRequestapiGetapiPostapiPutapiDel 等请求辅助是,仅基础设施
packages/api/base.ts站点、主题、验证码、地区等跨端基础接口
packages/api/upload.ts跨端上传能力
packages/api/models/*.ts从后端 Java VO/DTO/DO 生成的 TypeScript 类型
apps/pc/src/api买家 PC 端业务接口
apps/seller/src/api商家端业务接口
apps/admin/src/api平台管理端业务接口
danger

packages/api/models 是生成类型目录,只通过 type-only 方式导出。不要把运行时请求函数写入该目录,否则重新生成类型时会覆盖业务代码,也不会形成稳定的运行时导出入口。

类型统一从包根入口导入:

import type { GoodsDO, TNTApiPromise, TNTPageResponse } from "@shop-tnt/api";

不要假设 @shop-tnt/api/models 是公开子路径;当前 packages/api/package.json 没有导出该路径。

新增单端接口

下面以 Admin 列表接口为例,在 apps/admin/src/api/example.ts 中声明:

import { apiRequest } from "@shop-tnt/api";
import type {
TNTApiPromise,
TNTPageQuery,
TNTPageResponse,
} from "@shop-tnt/api";

export interface ExampleListItem {
id: string;
name: string;
}

export function getExampleList(
params: TNTPageQuery,
): TNTApiPromise<TNTPageResponse<ExampleListItem>> {
return apiRequest<TNTPageResponse<ExampleListItem>>({
url: "admin/examples",
method: "get",
loading: false,
params,
});
}

后端 Long 或雪花 ID 可能超过 JavaScript 安全整数范围,应按字符串接收和回传,不要为了方便强制转为 Number。

Admin 和 Seller 的 API 根入口采用命名空间导出。在 apps/admin/src/api/index.ts 增加:

export * as AdminExampleApi from "./example";

页面使用:

import { AdminExampleApi } from "@/api";

const result = await AdminExampleApi.getExampleList({
page_no: 1,
page_size: 20,
});

Seller 使用 SellerXxxApi 命名空间。PC 当前没有统一 src/api/index.ts,按已有模式直接从业务模块导入:

import { getAddressList } from "@/api/address";

跨端运行时接口

只有两个以上应用确实复用同一请求语义时,才将运行时接口提升到 packages/api。新增时需要:

  1. 创建职责明确的模块,不能写入 models
  2. packages/api/index.ts 明确导出。
  3. 如果调用方需要子路径导入,同时维护 packages/api/package.jsonexports
  4. 执行严格 API 类型检查和所有受影响应用构建。

当前公共基础接口通过命名空间使用:

import { BaseApi } from "@shop-tnt/api";

const regions = await BaseApi.getRegionChildren(0);

请求方法

包根入口提供:

  • apiRequest(options):传入完整 Axios 和 ShopTNT 运行时配置。
  • apiGet(url, params, options)
  • apiPost(url, data, options)
  • apiPut(url, data, options)
  • apiDel(url, options)
  • jsonHeaders():返回 JSON Content-Type。
  • normalizeIds(ids):将 ID 数组转换为逗号分隔路径参数。

复杂请求使用 apiRequest,简单请求可以使用方法助手:

import { apiGet, apiPost, jsonHeaders } from "@shop-tnt/api";

export function getExample(id: string) {
return apiGet(`admin/examples/${id}`, undefined, { loading: false });
}

export function createExample(data: Record<string, unknown>) {
return apiPost("admin/examples", data, {
headers: jsonHeaders(),
});
}

设置 application/json 时,请求运行时会执行 JSON 序列化;未设置且不是 FormData 时,POST/PUT 默认按表单规则序列化。请求格式必须与后端契约一致,不能通过反复尝试猜测。

运行时选项

选项默认行为适用场景
needToken默认需要完整登录会话登录、验证码、公开接口显式传 false
loading默认显示统一 Loading静默刷新、局部骨架屏等显式传 false
message默认显示统一错误消息页面完全接管反馈时传 false
cancelScopeGET 为 route,上传和非 GET 为 none需要全局取消时用 global
cancelKey默认不去重搜索联想等新请求应替换旧请求时设置稳定 key

cancelScope 可选值:

  • route:路由切换时取消,适合页面查询。
  • global:账号切换或退出时统一取消。
  • none:不登记取消,适合提交、上传等不能被路由切断的请求。
export function searchGoods(keyword: string) {
return apiRequest({
url: "admin/goods",
method: "get",
loading: false,
cancelScope: "route",
cancelKey: "goods-search",
params: { keyword },
});
}

token 与错误处理

请求运行时默认执行以下行为:

  1. 请求前检查当前应用隔离的 user、access token 和 refresh token。
  2. access token 缺失但 refresh token 可用时尝试刷新。
  3. 并发刷新复用同一个 Promise,避免刷新风暴。
  4. 命中会话失效条件的请求最多自动重试一次。
  5. 刷新失败时清理半登录状态并交给当前应用处理登录跳转。
  6. 未关闭 message 时显示统一错误提示。

调用方仍应使用 try/finally 清理页面局部状态。不要用空 catch 隐藏失败:

loading.value = true;
try {
rows.value = (await AdminExampleApi.getExampleList(query)).data;
} finally {
loading.value = false;
}

只有页面需要补充上下文或提供重试操作时才捕获异常;如果设置 message: false,页面必须给用户明确反馈,并保留足够的排查上下文。

上传与长请求

上传优先使用 @shop-tnt/api/upload 或项目现有上传组件。请求运行时会识别 /uploaders,取消默认超时并避免在路由切换时中断。不要把访问密钥写进浏览器环境变量,也不要在页面直接初始化一套长期凭证。

验证

修改端内 API 时执行对应构建;修改共享请求、API 类型或 packages/api 时至少执行:

pnpm typecheck
pnpm build:dev

接口验收不能只看构建通过,还要验证:

  • URL、method、query、body 和 Content-Type 与后端契约一致。
  • 公开接口没有错误要求 token,受保护接口不能绕过会话。
  • Long ID 没有被转成不安全 Number。
  • 路由切换取消查询,但不会中断提交和上传。
  • 401/403、超时、业务错误和刷新失败都有明确反馈与可排查上下文。