封装的作用
-
简化 API 调用:提供 get、post、put、delete 等便捷方法。
-
统一错误处理:集中处理各种 HTTP 状态码。
-
自动处理认证:统一添加 token 到请求头。
-
请求超时控制:防止请求无限等待。
-
灵活的网关配置:支持多个 API 网关。
-
智能内容类型处理:自动处理不同类型的请求和响应数据。
1. 认证 Token 管理
let globalCustomToken: string | undefined;
const getAuthToken = () => {
//开发环境
if (process.env.NODE_ENV === 'development') {
return globalCustomToken || '';
}
const token = localStorage.getItem('k2_portal_token');
return token;
};
2. 请求头处理
// 设置请求头token
const setRequestHeaders = (requestOptions: any) => {
const {headers, data} = requestOptions;
const contentType = DEFAULT_TYPE;
const token = getAuthToken();
const obj = {
'Content-Type': contentType,
...headers,
Authorization: token || 'noToken'
}
// 检查是否有data属性,并且是FormData类型
if (data instanceof FormData) {
// FormData类型不需要设置Content-Type,浏览器会自动设置
if (obj['Content-Type']) {
delete obj['Content-Type'];
}
}
return obj;
};
3. 响应处理
/**
* 处理API响应
* @param response 响应对象
* @returns 处理后的数据
*/
const handleApiResponse = async (response: Response) => {
// 获取响应状态
const { status } = response;
// 处理非JSON响应
const contentType = response.headers.get('content-type');
let responseData;
if (contentType && contentType.includes(DEFAULT_TYPE)) {
responseData = await response.json();
} else {
// 对于非JSON响应,返回文本内容
responseData = { text: await response.text() };
}
const {body, message, data} = responseData;
// 处理成功状态
if (status >= 200 && status < 300) {
return Promise.resolve({
code: 200,
data: body || data || 'noData',
message: message || 'noMessage'
});
}
// 错误处理
const errorText = message || '请求错误';
// 使用switch处理不同状态码
switch (status) {
case 400:
console.error({
message: `请求错误 ${status}`,
description: errorText,
});
break;
case 401:
console.error({
message: '登录已过期',
description: '您的登录已过期,请重新登录',
duration: 3,
});
// 延迟登出,给用户看到提示的时间
setTimeout(() => {
//TODO 登出
}, 1500);
break;
case 500:
console.error({
message: `请求错误 ${status}`,
description: errorText,
});
break;
default:
// 可以在这里处理其他状态码
console.error({
message: `请求错误 ${status}`,
description: errorText,
});
break;
}
// 即使是错误状态,也返回数据,让调用者可以处理
return Promise.reject({
code: status,
data: body || 'noData',
message: message || 'noMessage'
});
};
4. 请求数据处理
/**
* 处理data数据
* @param options 请求配置
* @returns 处理后的data数据
*/
const handleData = (options: any) => {
const {data, headers} = options;
const type = headers['Content-Type'] || DEFAULT_TYPE
// 如果没有data,返回undefined
if (!data) {
return undefined;
}
// 如果是FormData,直接返回
if (data instanceof FormData) {
return data;
}
if(type === DEFAULT_TYPE){
return JSON.stringify(data);
}else{
return data
}
};
5. 核心请求函数
/**
* 封装的fetch请求
* @param url 请求地址
* @param options 请求配置
* @param gatewayUrl 基础网关URL
* @returns Promise 响应结果
*/
const request = async (url: string, options: RequestInit & { data?: any } = {}) => {
const { method, ...rest } = options;
// 设置请求头
const headers = setRequestHeaders(options);
// 处理data数据
const body = handleData({...options, headers});
try {
// 添加超时控制
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30秒超时
// 发起请求
const response = await fetch(url, {
...rest,
method,
headers,
body,
signal: controller.signal,
});
clearTimeout(timeoutId);
// 使用提取出的响应处理函数
return await handleApiResponse(response);
} catch (error) {
// 添加错误日志以便调试
console.error('Request failed:', url, error);
return Promise.reject(error);
}
};
6. API 方法创建
/**
* 创建API方法集合
* @param gatewayUrl 基础网关URL
*/
const createApiMethods = (gatewayUrl: string) => {
return {
/**
* GET请求
* @param url 请求地址
* @param options 其他配置
*/
get: (url: string, options?: RequestInit) => {
return request(`${gatewayUrl}${url}`, { method: 'GET', ...options });
},
/**
* POST请求
* @param url 请求地址
* @param data 请求体数据
* @param options 其他配置
*/
post: (url: string, data?: any, options?: any) => {
return request(`${gatewayUrl}${url}`, {
data,
...options,
method: 'POST',
});
},
/**
* PUT请求
* @param url 请求地址
* @param data 请求体数据
* @param options 其他配置
*/
put: (url: string, data?: any, options?: any) => {
return request(`${gatewayUrl}${url}`, {
data,
...options,
method: 'PUT',
});
},
/**
* DELETE请求
* @param url 请求地址
* @param options 其他配置
*/
delete: (url: string, options?: any) => {
return request(`${gatewayUrl}${url}`, { ...options ,method: 'DELETE',});
},
};
};
7. API 代理创建
/**
* 创建API代理
* @param getways 网关
* @param customToken 开发环境传入的token
* @returns 代理后的API对象
*/
export function createApiProxy(getways = {}, customToken: string = '') {
// 如果提供了自定义token,更新全局变量
if (customToken) {
globalCustomToken = customToken;
}
// 创建API对象
const apiBase: any = {
getways: {
gateway: '/bcf/api',
...getways,
},
};
// 创建代理处理器
const apiHandler = {
get: (target: any, prop: string) => {
// 如果属性已经存在,则返回它
if (prop in target) {
return target[prop];
}
// 否则,检查是否有对应的基础URL
if (prop in target.getways) {
// 为服务端点创建API方法并缓存,传入自定义的目标服务器地址
target[prop] = createApiMethods(target.getways[prop]);
return target[prop];
}
return undefined;
},
};
// 使用代理创建API对象
return new Proxy(apiBase, apiHandler);
}
8. 使用方式
// 默认网关使用
api.gateway.get('/users');
api.gateway.post('/users', { name: 'John', age: 30 });
api.gateway.put('/users/1', { name: 'Updated Name' });
api.gateway.delete('/users/1');
// 使用自定义网关
const customApi = createApiProxy({
auth: '/auth/api',
data: '/data/api'
});
customApi.auth.post('/login', { username: 'user', password: 'pass' });
customApi.data.get('/items');
// 开发环境使用自定义 token
const devApi = createApiProxy({}, 'dev-token-123');
devApi.gateway.get('/protected-resource');
9.Proxy 在这里的具体作用
JavaScript 的 Proxy 对象允许你创建一个对象的代理,拦截并自定义对该对象的基本操作(如属性查找、赋值、枚举、函数调用等)。
1. 延迟初始化(懒加载):
-
API 方法集合只有在首次访问特定网关时才会被创建。
-
这种按需创建的方式提高了性能,避免了不必要的资源消耗。
2. 动态属性访问:
-
允许通过 api.gateway.get() 这样的方式访问 API 方法。
-
即使 gateway 属性最初并不存在于 api 对象上,Proxy 也能拦截这个访问并动态创建它。
3.属性查找拦截:
-
当代码尝试访问 api.gateway 时,Proxy 的 get 陷阱会被触发。
-
它会检查 gateway 是否是已知的网关名称(在 getways 对象中定义)。
-
如果是,它会为该网关创建一套 API 方法(get、post、put、delete)。
4.结果缓存:
-
一旦为特定网关创建了 API 方法集,它会被存储在原始对象中。
-
这意味着下次访问同一网关时不需要重新创建方法集。
5. 使用示例解析
当你执行以下代码时:
api.gateway.get('/users');
-
代码尝试访问 api.gateway。
-
Proxy 的 get 陷阱被触发,检查 gateway 是否存在。
-
发现 gateway 在 getways 对象中定义为 /bcf/api。
-
调用 createApiMethods('/bcf/api') 创建一套 API 方法。
-
将这套方法存储在 api.gateway 中并返回。
-
然后代码访问返回对象的 get 方法并调用它。
优势
-
简洁的 API 设计:使用起来非常直观,如 api.gateway.post('/login', data)。
-
灵活的网关配置:可以轻松添加和使用多个 API 网关。
-
按需初始化:只有实际使用的网关才会创建对应的方法集。
-
可扩展性:可以轻松添加新的网关或修改现有网关的 URL。
这种设计模式非常适合处理多服务架构的前端应用,让不同服务的 API 调用保持一致的接口同时又能清晰地区分。
10.总结
这个 Fetch 封装实现了以下核心功能:
-
统一的请求/响应处理
-
自动的认证管理
-
灵活的错误处理
-
请求超时控制
-
多网关支持
-
便捷的 HTTP 方法
-
智能的内容类型处理
这种封装大大简化了 API 调用,提高了代码可维护性,并确保了请求处理的一致性。