前端请求层设计 Skill
定位
只聚焦前端请求层设计:从 request.ts 出发,建立统一、健壮、可维护的请求体系。
本 skill 提供两层规范:
- 通用前端规范:面向 Web/H5/React/Vue 等标准前端项目,基于
fetch/axios实现。 - uniapp 适配规范:面向 uni-app 小程序/APP/H5 跨端项目,基于
uni.request实现。
二者核心思想完全一致(统一入口、响应信封、鉴权拦截、错误码映射、Token 刷新队列、SSE、上传),仅底层网络 API 不同。
本 skill 只处理请求相关逻辑,不依赖其他 skill。
前后端契约(强约定)
本 skill 的所有规范必须与
references/api-contract.md严格对齐。该契约文档是前后端桥接的唯一真理源,定义:
- 接口存放位置(vue/uniapp/react 统一
src/api/)- 登录页/管理页接口契约
- 请求头规范(Authorization / Content-Type / X-Request-ID)
- 响应信封
{ code, message, data }- 错误码契约(
code<0异常,>=0成功,-1特殊 = JWT 鉴权失效)- showError 参数语义(默认
false自动弹,true调用方自己处理)- HTTP 500 兜底(强制 swallow 用户 Toast,交给全局错误处理)
强约定:
- 后端新生成业务 → 必须更新后端
api-contract.md - 前端新加接口 → 必须读取契约,按契约定义路径/字段/code
- 契约变更必须前后端同步
解决的问题
| 痛点 | 后果 | 本技能方案 |
|---|---|---|
| 每个页面各自调原生请求 | 鉴权/错误处理重复 | 统一 request.ts |
| Token 过期无感知 | 用户操作失败 | 响应拦截器识别 401,统一交给 auth service 处理 |
| 重复点击导致重复请求 | 数据异常/资源浪费 | 防抖去重 |
| 后端接口未 ready | 前端阻塞 | Mock 机制 |
| 游客误触敏感接口 | 报错/白屏 | 请求层前置拦截 |
| 错误提示不统一 | 用户体验差 | 统一错误通知 |
| SSE/流式接口不知如何接入 | 聊天/AI 回复无法流式展示 | 跨端 SSE 封装 + 打字机效果 |
| Token 过期后并发请求全部失败 | 用户重复登录、数据丢失 | Token 刷新队列 + 失败请求自动重试 |
When to Use
- "请求封装"
- "request.ts 怎么写"
- "前端请求统一处理"
- "uniapp 请求统一处理"
- "接口拦截"
- "Token 刷新"(请求层衔接部分,详见 references/auth-patterns.md)
- "游客模式拦截"
- "Mock 数据配置"
- "接口防抖"
- "错误处理"
- "文件上传"
- "SSE 流式请求"
- "打字机效果"
- "Server-Sent Events"
- "AI 聊天流式回复"
When NOT to Use
- 需要完整登录鉴权/权限设计 → 本 skill 只提供请求层衔接,完整鉴权体系需单独设计
- 需要项目整体规范化/目录结构诊断 → 不在本 skill 范围内
- 需要跨平台兼容性审计 → 不在本 skill 范围内
两层规范速查
| 规范 | 适用场景 | 底层 API | 参考位置 |
|---|---|---|---|
| 通用前端规范 | Web / H5 / React / Vue 等 | fetch / axios | references/frontend-spec.md |
| uniapp 适配规范 | 微信小程序 / App / H5 | uni.request / uni.uploadFile | references/uniapp-spec.md |
二者仅在「底层网络 API」和「Token 存储方式」上有差异;响应信封、错误码、鉴权拦截、去重、Mock、SSE 解析逻辑完全一致。
Quick Reference
| 能力 | 关键选项 | 参考位置 |
|---|---|---|
| 前后端契约 | 接口位置 / 错误码 / JWT | references/api-contract.md |
| 统一请求 | request<T>(options) | references/frontend-spec.md、references/uniapp-spec.md |
| Token 注入 | needAuth、authMode | references/auth-patterns.md |
| 401/403 处理 | skipAuthHandler | references/auth-patterns.md |
| 防抖去重 | skipDebounce | references/request-impl.md |
| Mock 数据 | USE_MOCK | references/mock-guide.md |
| 错误提示 | showError | references/error-handling.md |
| 文件上传 | upload<T>(options) | references/error-handling.md |
| SSE 流式请求 | sse<T>(options, onMessage) | references/sse-guide.md |
| Token 自动刷新 | auth.service.ts 队列 | references/auth-patterns.md |
核心文件结构
强约定:所有前端项目(vue / uniapp / react)接口统一存放在
src/api/下。详细目录约定见 api-contract.md §2。
src/ ├── api/ # 前后端桥接层(frontend-request-skill 管理) │ ├── request.ts # 统一请求封装(核心) │ ├── upload.ts # 文件上传封装 │ ├── sse.ts # SSE 流式请求封装 │ ├── modules/ # 业务模块 API(auth / user / role / menu / ...) │ ├── _mocks_/ # Mock 数据字典 │ │ ├── index.ts # MOCK_MAP + MockEntry │ │ └── *.mock.ts # 各模块 Mock │ └── types/ # 接口请求/响应类型 ├── services/ │ └── auth.service.ts # 鉴权服务:login / logout / handleUnauthorized ├── config/ │ ├── api.config.ts # BASE_URL / 超时 / Mock / 成功码 / 鉴权失败码 / 重试 │ └── error.config.ts # 错误码映射(与 api-contract.md 错误码表对齐) ├── utils/ │ ├── auth.ts # getToken / setToken │ ├── toast.ts # 错误提示工具 │ └── error.ts # 错误信息提取 └── composables/ ├── useAuth.ts # 游客判断 Hook └── useTypewriter.ts # 打字机效果 Hook
禁止把接口散落到
pages/api/、views/api/、utils/api/、service/等位置。uniapp 项目同样使用
src/api/,H5/小程序/App 三端一致。
响应信封与错误约定
本 skill 采用统一的响应结构(响应信封),与后端接口契约严格对齐:
typescriptexport interface ApiResponse<T = any> { code: number; // 业务状态码 message: string; // 提示信息 data: T; // 业务数据 }
业务状态码约定(与 api-contract.md §7 严格对齐)
| code 范围 | 含义 | 处理方式 |
|---|---|---|
code = 0 | 业务成功 | 正常返回 data |
code < 0 | 业务异常 | 抛出 RequestError,错误码为对应的负数值 |
code = -1 | JWT 鉴权失效(未登录 / Token 无效 / 过期 / 未传递) | 触发 Token 刷新流程,等同 HTTP 401 |
code > 0 | 按项目约定(若存在) | 默认也视为业务异常 |
本项目示例默认
SUCCESS_CODES = [0]、AUTH_FAILURE_CODES = [-1]。如果你的后端约定不同,请在src/config/api.config.ts中调整。
showError 参数语义(与 api-contract.md §8 严格对齐)
核心约定:默认请求层自动弹 Toast,传
showError: true时不弹,调用方自己处理。
| 取值 | 含义 | 默认 |
|---|---|---|
false(默认) | 请求层按 ERROR_CODE_MAP 自动弹 Toast | ✅ |
true | 请求层不弹,调用方自行 try/catch 处理 | — |
设计理由:默认自动弹覆盖 80% 场景(页面直取数据);少数特殊场景(轮询、上传队列、批量提交)传
showError: true让调用方自己处理。
HTTP 500 错误兜底(与 api-contract.md §9 严格对齐)
HTTP 500 / 502 / 503 / 504 类错误前端不弹任何用户 Toast。
理由:500 是服务端问题,用户弹 Toast 也无法解决;交给全局错误处理(Sentry / 监控平台 / 后端日志)。
业务调用方仍可感知(用于降级逻辑),但用户无感。
HTTP 状态异常
HTTP 层错误与业务 code 互不干扰,统一由 statusCode 判断:
| HTTP 状态 | 错误码 | 场景 |
|---|---|---|
| 401 | UNAUTHORIZED | 登录过期,触发 Token 刷新 |
| 403 | FORBIDDEN | 权限不足 |
| 400 / 404 / 500 等 | HTTP_ERROR | 请求异常 |
| 超时 / 断网 | TIMEOUT / NETWORK_ERROR | 网络异常 |
错误码映射约定
src/config/error.config.ts 中的 ERROR_CODE_MAP 用于把错误码转成用户友好文案。本 skill 内置的映射仅为示例,你必须按自己后端的真实 code 约定替换:
typescriptexport const ERROR_CODE_MAP: Record<string, string> = { // HTTP 状态异常(请求层) UNAUTHORIZED: '登录已过期,请重新登录', FORBIDDEN: '权限不足', TIMEOUT: '请求超时,请检查网络', NETWORK_ERROR: '网络异常,请稍后重试', // 业务异常示例(按 code < 0 约定,需与后端契约保持一致) '-1001': '参数校验错误', '-1002': '未登录或 Token 无效', '-1003': '无权限', '-1004': '资源不存在', '-1005': '资源冲突', '-1006': '请求过于频繁', '-2000': '系统繁忙,请稍后再试', };
重要:以上错误码与各 init-skill 内置契约(
fastapi-init-skill、springboot-init-skill等)保持一致。接入真实项目时,请与后端确认错误码表并替换。
分页约定
前后端统一的分页请求/响应格式:
请求参数:
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
page | number | 1 | 页码,从 1 开始 |
pageSize | number | 20 | 每页条数,上限 100 |
响应结构(在 data 内):
typescriptexport interface PageResponse<T> { list: T[]; // 数据列表 total: number; // 总条数 page: number; // 当前页码 pageSize: number; // 每页条数 }
各后端 init-skill(springboot / fastapi / go-gin)的分页格式已对齐此约定。
Token 响应约定
后端登录接口返回 Token 时,统一使用以下字段:
typescriptexport interface TokenResponse { accessToken: string; // 访问令牌(Java 后端 camelCase,Python 后端 snake_case 兼容) refreshToken: string; // 刷新令牌 tokenType: string; // 固定 "Bearer" expiresIn: number; // 过期时间(秒) }
注意:Java 后端使用 camelCase,Python 后端使用 snake_case。前端必须做兼容处理(优先 camelCase,fallback snake_case),详见 api-contract.md §10。
JWT 鉴权约定(所有骨架统一)
强约定:所有后端骨架(Go / Java / Python / NodeJS)必须使用 JWT 做无状态鉴权。
请求头:
Authorization: Bearer <token>。失效标识:
HTTP 401或code === -1。完整规范见 api-contract.md §11。
设计要点
1. 统一入口
typescript// src/api/request.ts export interface RequestOptions { url: string; method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'OPTIONS' | 'HEAD'; data?: any; // 请求体:通常为 plain object header?: Record<string, string>; timeout?: number; needAuth?: boolean; // 是否需要 Token,默认 true showError?: boolean; // 是否在请求层弹出错误 Toast;默认 false(自动弹)。true 时不弹,调用方自行处理 skipDebounce?: boolean; // 是否跳过防抖,默认 false skipAuthHandler?: boolean; // 是否跳过 401 处理,默认 false prefix?: string; // API 前缀,默认 DEFAULT_PREFIX authMode?: 'bearer' | 'customer-token'; // Token 头格式 retry?: number; // 失败重试次数,默认 REQUEST_RETRY_COUNT } export interface RequestPromise<T> extends Promise<ApiResponse<T>> { __abort?: () => void; } export interface ApiResponse<T = any> { code: number; message: string; data: T; } export interface UploadOptions { url: string; file: File | string; // 通用前端用 File;uniapp 用文件路径 string name?: string; formData?: Record<string, any>; header?: Record<string, string>; timeout?: number; onProgress?: (progress: number) => void; // 0-100 } export function request<T = any>(options: RequestOptions): RequestPromise<T>; export function get<T = any>( url: string, data?: any, options?: Omit<RequestOptions, 'url' | 'method' | 'data'> ): RequestPromise<T>; export function post<T = any>( url: string, data?: any, options?: Omit<RequestOptions, 'url' | 'method' | 'data'> ): RequestPromise<T>; export function put<T = any>( url: string, data?: any, options?: Omit<RequestOptions, 'url' | 'method' | 'data'> ): RequestPromise<T>; export function del<T = any>( url: string, data?: any, options?: Omit<RequestOptions, 'url' | 'method' | 'data'> ): RequestPromise<T>; export function upload<T = any>(options: UploadOptions): Promise<T>; // 直接返回业务 data,不包 ApiResponse
完整实现见 references/frontend-spec.md(通用前端)与 references/uniapp-spec.md(uniapp 适配)。
2. 鉴权衔接
本 skill 只负责请求层与鉴权的衔接点:
- 默认请求自动注入 Token
- 支持
needAuth: false跳过(如登录接口本身) - 支持
authMode: 'bearer' | 'customer-token'切换鉴权头格式 - 401/403 响应交给
auth.service.ts统一处理 - 登录态来源可由项目自行选择:Storage 最小化方案 或 Pinia
userStore方案
重要:通用前端示例默认使用
localStorage;uniapp 示例默认使用uni.getStorageSync('token')。如果你使用 Pinia 管理登录态,请参考 references/auth-patterns.md 替换为userStore方案,请求层代码无需改动。
详细 Token 管理、401/403 处理、Token 刷新队列、登出回跳等见 references/auth-patterns.md。
3. 游客模式
请求层只做最小拦截:
typescriptimport { formatError } from '@/utils/error'; if (options.needAuth !== false && !getToken()) { return Promise.reject(formatError('NO_AUTH_TOKEN', '未登录')); }
业务层建议前置检查:
typescriptconst { checkLogin } = useAuth(); function handleLike() { if (!checkLogin()) return; post('/api/like', { id: itemId }); }
4. 防抖去重
- 同一 key 的并发请求只发一次,返回同一个 Promise
- 请求完成后清理 pending,释放内存
- 提交类接口可设置
skipDebounce: true
5. Mock 机制
- 通过全局开关
USE_MOCK控制:开启后所有请求强制走 Mock,关闭后全部走真实接口 - Mock 数据建议按接口字段契约声明类型(
MockEntry<T>) - 支持精确匹配
METHOD:/path和 REST 路径参数匹配 - 见 references/mock-guide.md
6. 错误处理
- 统一错误信息提取(
message/msg/error/detail) - 开发环境 Modal 展示完整错误,生产环境 Toast/Modal 分级提示
- 文件上传单独封装
- 见 references/error-handling.md
7. SSE 流式请求
- 封装
sse<T>(options, onMessage, onError?),支持 H5EventSource与小程序enableChunked双端 - 自动注入 Token、401 识别、手动中断
- 流式 chunk 解析 + 数据行缓存,适用于 AI 聊天、打字机效果
- 见 references/sse-guide.md
8. Token 自动刷新与失败重试
- HTTP 401 触发静默刷新
- 刷新期间新请求入队,刷新成功后自动重发
- 刷新失败统一登出,避免用户反复登录
- 见 references/auth-patterns.md
Common Mistakes
| 错误 | 后果 | 正确做法 |
|---|---|---|
| 在请求层写死鉴权跳转逻辑 | 与 auth skill 重复、难以维护 | 请求层只识别 401/403,统一交给 auth.service.ts |
| 把 401 重试/Token 刷新在每个 API 里单独实现 | 代码重复、并发刷新导致多次登录 | 使用队列式 Token 刷新,统一收口到 auth.service.ts |
| 并发请求未做去重 | 重复点击导致重复提交 | 用 Map 缓存同一 key 的 pending Promise |
JSON.stringify 直接生成请求 key | 属性顺序不同导致 key 不同,去重失效 | 递归排序 key 后序列化 |
statusCode !== 200 判断成功 | 201/204 等合法状态被误判 | 200 <= statusCode < 300 |
| Mock 数据写进生产包 | 数据泄露、行为异常 | Mock 仅由 VITE_USE_MOCK 控制,生产环境设为 false |
| 文件上传复用 request 的防抖 | 大文件/多次选择文件被错误去重 | 上传单独封装,不走 request 防抖 |
| SSE 在小程序端使用 H5 的 EventSource | 小程序无原生 EventSource,直接报错 | 使用 enableChunked + 手动解析 chunk |
| SSE 不处理连接中断/页面卸载 | 内存泄漏、重复回调 | 返回可中断的 requestTask,页面 onUnload 时调用 |
| 401 时直接重试原请求但不刷新 Token | 重试仍失败,陷入死循环 | 先刷新 Token,再重试队列中的请求 |
| Token 刷新不排队 | 并发刷新导致多次登录请求 | 使用 isRefreshing + Promise 队列 |
接口散落到 pages/api/、views/api/、service/ | 跨项目无法复用,Mock 难统一 | 强约定:所有前端项目(vue/uniapp/react)统一放 src/api/,见 api-contract.md §2 |
| 凭直觉写接口路径/字段 | 前后端不对齐,联调失败 | 强约定:新加接口必须读 api-contract.md,没有就更新 |
| HTTP 500 给用户弹"服务器繁忙" Toast | 用户看到无意义提示,无法自助解决 | 强约定:500 类错误请求层 swallow,交给全局错误处理,见 api-contract.md §9 |
把业务异常用 showError: true 隐藏 | 用户不知道发生了什么 | showError: true 只用于轮询 / 静默重试 / 自定义提示场景 |
输出
触发本 skill 时,按以下优先级输出:
- 契约对齐:先读
references/api-contract.md,确认接口位置 / 错误码 / JWT 约定 - 问题诊断:当前请求层存在的主要问题(重复代码、缺拦截器、错误处理散落等)
- 结构方案:推荐的
src/api/、src/config/、src/utils/、src/services/文件划分 - 核心代码:给出或修正
request.ts、upload.ts、错误处理工具的实现 - 衔接说明:明确哪些逻辑属于请求层、哪些应收口到
auth.service.ts - 进阶能力:按需补充 SSE 流式请求、Token 自动刷新队列、失败重试
- 参考引用:复杂实现直接引用
references/中的对应文档
职责边界
| 范畴 | 本 skill 负责 | 本 skill 不负责 |
|---|---|---|
| 请求封装 | request.ts、拦截器、去重、Mock、错误提示、上传、SSE 衔接 | — |
| 鉴权实现 | 请求层 Token 注入、401/403 识别、刷新触发点 | Token 管理、登录态、登出回跳等完整鉴权体系 |
| Token 刷新队列 | — | 推荐由 auth.service.ts 统一实现,请求层只负责触发与重试 |
| 项目规范 | — | 目录结构、命名规范等通用规范 |
| 跨平台审计 | — | 多端兼容性检查 |
与后端规范的联动
本 skill 的响应信封、错误码、JWT、请求头规范以
references/api-contract.md为准。
- 后端
EnvelopeRoute输出{ code, message, data } - 前端
request.ts按相同结构解析 ERROR_CODE_MAP直接复用契约 §7 错误码表- JWT 鉴权 +
code === -1失效标识由契约 §11 约束
后端 init-skill(fastapi-init-skill / springboot-init-skill / go-gin-init-skill / nodejs-init-skill)必须生成与 references/api-contract.md 兼容的 api-contract.md,否则前后端无法桥接。
触发词
frontend-request、前端请求层设计 Skill


