//src/lib/httpAxios.js
/**
* Axios 实例封装
* 1. 创建 Axios 实例
* 2. 请求拦截器:添加 token,并使用 "Bearer " 前缀
* 3. 响应拦截器:统一处理 HTTP 错误和业务错误
* 4. 提取 Axios 错误的友好提示信息
* 5. 导出 Axios 实例
*/
import axios from "axios";
import Cookies from "universal-cookie";
const cookies = new Cookies();
import { refreshToken } from "@/lib/authService"; // 注意引入路径
/**
* 创建 axios 实例
* 可以根据需要配置 baseURL、timeout、headers 等
*/
const httpAxios = axios.create({
baseURL:
process.env.NEXT_PUBLIC_API_BASE_URL || "https://www.test.com",
timeout: 10000,
// withCredentials: true,
});
/**
* 提取 Axios 错误的友好提示信息
* @param {Error} error - Axios 捕获的错误对象
* @returns {string} - 提取出的错误信息
*/
function extractErrorMessage(error) {
// 如果有响应数据
if (error.response) {
const { status, data } = error.response;
// 根据后端的返回结构进行解析
if (data) {
// 如果 data 是对象 (object),则尝试解析 message 字段
if (typeof data === "object") {
//根据实际后端返回的数据结构进行解析
if (data.data?.message) {
return data.data.message || JSON.stringify(data.data);
}
return data.message || JSON.stringify(data);
}
// 如果 data 不是对象,则直接返回
return data;
}
return `HTTP Error: ${status}`;
} else if (error.request) {
// 请求发出后未收到响应
return "No response received. Please check your network.";
}
// 其他错误直接返回 error.message
return error.message;
}
/**
* 请求拦截器:添加 token,并使用 "Bearer " 前缀
* 如果需要支持取消请求,可使用 AbortController,参考: https://axios-http.com/docs/cancellation
* @param {object} config - Axios 请求配置
* @returns {object} - 处理后的请求配置
* @throws {Error} - 请求错误
* @see https://axios-http.com/docs/req_config
*/
httpAxios.interceptors.request.use(
(config) => {
const token = cookies.get("jwtToken");
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`;
}
// 如果需要支持取消请求,可使用 AbortController,参考:
// const controller = new AbortController();
// config.signal = controller.signal;
return config;
},
(error) => {
console.error("Request error:", error);
return Promise.reject(error);
}
);
/**
* 响应拦截器:统一处理 HTTP 错误和业务错误
* 在响应拦截器中,对后端返回的业务状态码进行判断,并将错误标准化后再抛出,便于在组件中统一捕获和处理错误
*/
httpAxios.interceptors.response.use(
(response) => {
/**
* 响应拦截器:直接返回响应数据
* 如果需要对响应数据进行统一处理,可在此处添加代码
* 例如,对响应数据进行格式化、统一处理错误等
* 注意:如果需要对错误进行统一处理,应该在响应拦截器中添加处理逻辑
*/
return response;
},
async (error) => {
let errorMsg = extractErrorMessage(error);
const originalRequest = error.config;
if (error.response) {
const { status } = error.response;
// 检查是否为 401 错误,并且该请求尚未重试过
if (status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
// 调用全局的刷新 token 函数
const newToken = await refreshToken();
// 更新请求头并重新发起请求
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return httpAxios(originalRequest);
} catch (refreshError) {
// 刷新 token 失败时,可进行统一处理,如清理 token 并跳转登录页面
return Promise.reject(refreshError);
}
}
}
return Promise.reject(new Error(errorMsg));
}
);
export default httpAxios;
这里我们通过 Axios 的拦截器实现了请求拦截和响应拦截,实现了 token 的自动添加和刷新,以及错误的统一处理。这样我们就可以在组件中直接使用 httpAxios 实例来发起请求,而不用关心 token 的添加和刷新,以及错误的处理。
//src/lib/authService.js
import httpAxios from "@/lib/httpAxios";
import Cookies from "universal-cookie";
const cookies = new Cookies();
/**
* token 刷新通常属于全局状态管理或认证逻辑,这里抽离成一个独立的函数,而不依赖于 React hook。
* 在需要刷新 token 的地方,直接调用该函数即可。
*/
export async function refreshToken() {
try {
const token = cookies.get("jwtToken");
// 直接调用 axios 实例的请求方法,不通过 hook
const response = await httpAxios.request({
url: "/jwt-login/v1/refresh",
method: "POST",
params: {
// 通过 params 传递 token
JWT: token,
},
});
// 返回格式为 { "success": true, data: { jwt: "new-token" } }
const newToken = response.data?.data?.jwt;
if (newToken) {
return newToken;
}
throw new Error("刷新 token 失败");
} catch (err) {
throw err;
}
}