feat(auth): 实现认证API和token自动刷新功能

添加认证服务API模块,包括登录、token刷新和验证功能
在axios拦截器中实现token自动刷新机制,处理401错误
更新tsconfig配置以支持ES2020特性
重构API导入路径以使用新的auth模块结构
This commit is contained in:
2026-01-06 22:17:31 +08:00
parent 05669a6570
commit 8e5558d9a2
6 changed files with 92 additions and 11 deletions

View File

@@ -3,7 +3,18 @@ import axios, {
type InternalAxiosRequestConfig,
} from "axios";
import { ENV } from "../config";
import { getAccessToken } from "@/utils/token";
import {
getAccessToken,
getRefreshToken,
setTokens,
clearTokens,
} from "@/utils/token";
import { Message } from "@arco-design/web-vue";
// 是否正在刷新 token
let isRefreshing = false;
// 存储因为 token 过期而挂起的请求
let requests: ((token: string) => void)[] = [];
const request: AxiosInstance = axios.create({
baseURL: ENV.API_BASE_URL,
@@ -32,17 +43,83 @@ request.interceptors.request.use(
// =======================
request.interceptors.response.use(
(response) => {
console.log("响应: ", response);
// console.log("响应: ", response);
const data = response.data;
/**TODO: 增加响应码处理 */
return data;
},
(error) => {
async (error) => {
const originalRequest = error.config;
// 处理 401 未授权情况 (Token 过期)
// 确保不是刷新 token 的请求本身 (避免死循环)
if (
error.response?.status === 401 &&
!originalRequest._retry &&
!originalRequest.url.includes("/auth/refresh")
) {
// 如果正在刷新,将当前请求加入队列等待
if (isRefreshing) {
return new Promise((resolve) => {
requests.push((token: string) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
resolve(request(originalRequest));
});
});
}
originalRequest._retry = true;
isRefreshing = true;
try {
const refreshToken = getRefreshToken();
if (!refreshToken) {
throw new Error("No refresh token available");
}
// 使用原生 axios 发送刷新请求,避免拦截器循环
const response = await axios.post(
`${ENV.API_BASE_URL}/v1/auth/refresh`,
null,
{
params: { refreshToken },
}
);
if (response.data?.success) {
const { accessToken, refreshToken: newRefreshToken } =
response.data.data;
// 更新本地存储
setTokens(accessToken, newRefreshToken);
// 执行队列中的请求
requests.forEach((cb) => cb(accessToken));
requests = [];
// 重试当前请求
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
return request(originalRequest);
} else {
throw new Error("Refresh token failed");
}
} catch (refreshError) {
console.error("Token 刷新失败:", refreshError);
// 清除过期 token
clearTokens();
Message.error("登录已过期,请重新登录");
// 这里可以选择跳转到登录页,例如 window.location.href = '/user/login'
// 建议让路由守卫或者页面自行处理未登录状态
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
}
}
/**
* Promise.reject(error) 用来 返回一个状态为 rejected 的 Promise相当于“主动抛出错误”
*
*
* 让调用者能够在 .catch() 或 try/catch 中捕获这个错误。
它在 异步流程、拦截器、错误处理 中非常常见。
* 它在 异步流程、拦截器、错误处理 中非常常见。
*/
console.error("❌ 网络错误:", error);
return Promise.reject(error);