前端动态 API 生成与封装:前端 API 调用的一种思路
引子
在前端开发中,排除部分使用graphql的场景,一般还是需要调用大量的后端接口。传统的做法是手动编写每个 API 调用函数,或者使用像 Swagger Codegen 这样的工具生成客户端代码。但这两种方式都有各自的问题:
手动编写的问题:
- 工作量大,接口多了容易出错
- 后端接口变更后,前端需要同步修改
- 代码重复,每个接口都是相似的 fetch/axios 调用
- 难以统一管理(错误处理、认证、日志等)
代码生成工具的问题:
- 需要额外的构建步骤
- 生成的代码通常很冗长
- 更新接口需要重新生成和提交大量代码
- 难以自定义生成逻辑
思考:能不能让前端在运行时动态生成 API 调用函数?
这篇文章分享一下我的思路
核心思路
传统方式
javascript
// api/customer.js - 需要手动编写每个函数
export async function getCustomerList(params) {
const response = await fetch('/api/customer/list', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}`
},
body: JSON.stringify(params)
});
return response.json();
}
// ... 还有几百个类似的函数
主流方式
后端写 Swagger / OpenAPI 文档,或api配置表
前端使用运行脚本根据配置表生成 SDK
生成出类似这样的标准文件:
javascript
import { request } from '@/utils/request';
export const Api = {
getCustomerList(params) {
return request.post('/api/customer/list', params);
},
getCustomerDetail(id) {
return request.get(`/api/customer/detail/${id}`);
},
updateCustomer(id, data) {
return request.post(`/api/customer/update/${id}`, data);
}
}
动态生成方式
前提:
- 后端使用了BFF层
- 确定的前端url约定,可以通过url推断出对应后端BFF层的API集合
- 后端提供接口返回 API 元数据(可以根据url返回当前url页面可以调用的API集合)
javascript
//
// 后端接口返回 API 元数据
const apiMetadata = {
"apis": [
{
"method": "getCustomerList",
"path": "/customer/list",
"requestMethod": "POST",
"params": { "status": "String", "name": "String" },
"returnType": "json"
},
{
"method": "getCustomerDetail",
"path": "/customer/detail/{id}",
"requestMethod": "GET",
"params": { "id": "Long" },
"returnType": "json"
},
{
"method": "updateCustomer",
"path": "/customer/update/{id}",
"requestMethod": "POST",
"params": { "id": "Long", "data": "Object" },
"requestBodyParam": "data",
"returnType": "json"
}
]
};
// 前端根据元数据自动生成所有 API 函数
generateApis(apiMetadata);
// 页面逻辑直接使用,不需要先定义,配置,或手动编写
window.api.getCustomerList({ status: 'active' }, (response) => {
console.log(response);
});
// 也支持 Promise
const response = await window.api.getCustomerDetail.getPromise(123);
好处:
- 前端不需要编写任何 API 调用代码
- 后端接口变更,前端自动同步
- 可以实现统一的错误处理、认证、日志等Aspect
- 后端返回的API元数据甚至可以带一些蜜罐接口,前端代码永远不会调用,但是看起来很好被恶意利用的,而且可以定期变更,前端代码无需修改
因为接口是根据动态返回的元数据自动生成的,为了让前端同学拿到最新的接口文档,可以前端写一个函数,可以通过console调用格式化数据接口文档,类似swagger。另外后端如何定义,识别接口元数据呢,我主要是通过OpenApi的注解识别(一般方法上都已经加过了),只需要单独定义一个注解来标识哪些可以返回给前端。
生成Api部分逻辑实现设计
javascript
[业务代码]
↓
[uniends.api - 中间件层]
↓
┌─────────────────────┐
│ 数据分析追踪 │ → 发送行为数据
│ 性能指标采集 │ → 上报到 APM
│ 错误日志 │ → 发送到 Sentry
│ 缓存检查 │ → 查询 Redis 或内存
│ 请求参数加工 │ → 自动补齐 headers
│ A/B 测试 │ → 路由到不同后端 API
│ 自动生成traceid │ → 生成唯一请求 ID
└─────────────────────┘
↓
[实际后端 API 调用]
↓
[响应处理器]
↓
┌─────────────────────┐
│ 成功/失败统计 │ → 上报性能指标
│ 缓存写入 │
│ 错误追踪 │
│ 用户行为分析 │
└─────────────────────┘
↓
[业务回调函数]
根据上方设计可以随时新增Aspect, 页面所有API调用都会经过这些中间件处理。
javascript
日志记录 ─┐
埋点追踪 ─┤
缓存 ─┤
错误追踪 ─┤------→ → [后端]
性能监控 ─┤
重试/断路器 ─┤
鉴权 ─┘
实践中的经验
好处
1. 开发效率提升
之前: 每次后端新增接口,前端都要:
- 查看接口文档
- 手动编写调用函数
- 测试
- 提交代码
现在: 后端新增接口后,前端直接用,不需要任何额外工作。
2. 接口变更自动同步
后端修改接口url,参数或路径,前端:
- 之前: 需要找到所有调用的地方,逐个修改或者需要更新接口定义配置表
- 现在: 自动同步,不需要改任何代码,新增的参数不是必须的可以自动忽略,url变更对前端无感,因为前端是通过固定名称的函数调用。
3. 统一的最佳实践
所有 API 调用自动包含:
- 认证 token
- 错误处理
- 请求日志
- 性能监控
- 重试逻辑
- 超时控制
可以随时新增中间件处理逻辑
4. 更容易的重构
接口具体调用方式为统一封装的,更容易重构
-
你的项目中如何管理 API 调用?
- 手写每个函数?
- 使用代码生成工具?
- 还是其他方案?
-
你会考虑使用动态生成吗?
- 如果会,你的顾虑是什么?
- 如果不会,原因是什么?
总结
以上思路已经有了基本实现,有需要可以交流