API 请求
BBC 8.0 通过 @shop-tnt/api 和 @shop-tnt/utils/request 统一处理 token、刷新重试、请求取消、Loading、错误提示与响应解包。页面不得自行创建另一套 Axios 实例绕过这些能力。
分层与放置位置
| 位置 | 职责 | 是否放请求函数 |
|---|---|---|
packages/api/request.ts | apiRequest、apiGet、apiPost、apiPut、apiDel 等请求辅助 | 是,仅基础设施 |
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。新增时需要:
- 创建职责明确的模块,不能写入
models。 - 从
packages/api/index.ts明确导出。 - 如果调用方需要子路径导入,同时维护
packages/api/package.json的exports。 - 执行严格 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 |
cancelScope | GET 为 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 与错误处理
请求运行时默认执行以下行为:
- 请求前检查当前应用隔离的 user、access token 和 refresh token。
- access token 缺失但 refresh token 可用时尝试刷新。
- 并发刷新复用同一个 Promise,避免刷新风暴。
- 命中会话失效条件的请求最多自动重试一次。
- 刷新失败时清理半登录状态并交给当前应用处理登录跳转。
- 未关闭
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、超时、业务错误和刷新失败都有明确反馈与可排查上下文。