Appearance
结论(一句话)
不要维护 noAuthPaths 白名单数组。 大厂常用是约定式路径前缀(如 /api/pub/** 全部公开,其余默认鉴权)+ 显式 auth:false 覆盖个别特殊接口(如登录/注册)。必要时再做双实例(authHttp/pubHttp)隔离。
你只需做的最小改动(片段)
1) 用“前缀约定”替换白名单数组
复杂逻辑:将“是否需要鉴权”收敛为实例级默认值 + URL 前缀 + 每次请求的显式覆盖。
删除:
ts
// 删除这行
const noAuthPaths = [
"/login",
"/register",
"/public-endpoint",
"/api/pub/get_feednews",
];在 createAxiosHttp 定义处新增第二个参数(不改已有调用方也能有默认值):
ts
// 复杂逻辑:通过 opts 控制默认鉴权与公开前缀
function createAxiosHttp(
config: AxiosRequestConfig,
opts: { authDefault?: boolean; publicPrefixes?: string[] } = {},
) {
const authDefault = opts.authDefault ?? true
const publicPrefixes = opts.publicPrefixes ?? ['/api/pub'] // 仅维护“前缀”,而非逐个接口
// ...在请求拦截器里替换 needAuth 判定:
ts
_axios.interceptors.request.use((req) => {
// ...(保留你已有的 AbortController 代码)
// 复杂逻辑:先按前缀判定是否公开,再由显式 req.auth 覆盖
const url = req.url || "";
const isPublicByPrefix = publicPrefixes.some((p) => url.startsWith(p));
// 复杂逻辑:优先使用 req.auth(true/false),否则用实例默认值 + 前缀约定
const needAuth = (req as any).auth ?? (authDefault && !isPublicByPrefix);
const accToken = storage.get(ACCESS_TOKEN);
const tokenType = storage.get(TOKEN_TYPE) || "Bearer";
if (needAuth && !accToken) {
((req as any).__abortController as AbortController | undefined)?.abort();
return Promise.reject(new Error("UNAUTHENTICATED"));
}
if (needAuth && accToken) {
req.headers = { ...req.headers, Authorization: `${tokenType} ${accToken}` };
}
return req;
});创建实例时传入前缀约定(一次配置,全局生效):
ts
// 复杂逻辑:默认需要鉴权,唯独 /api/pub/** 前缀走公开
const http = createAxiosHttp(
{
timeout: 10000,
baseURL: API_BASE,
withCredentials: true,
xsrfCookieName: "Authorization",
xsrfHeaderName: "Authorization",
},
{ authDefault: true, publicPrefixes: ["/api/pub"] }
);2) 显式覆盖个别公开接口(例如登录/注册)
复杂逻辑:无需继续维护数组;在调用时给
{ auth:false }。
ts
// 复杂逻辑:显式声明无需鉴权
http.request("/auth/login", "POST", body, { auth: false });
http.request("/auth/register", "POST", body, { auth: false });3) 小修 bug:统一释放键,避免内存泄露
你在请求里写的是
__abortController,但错误拦截里读取的是__ac。统一成同一个键。
在错误拦截器里替换:
ts
// 复杂逻辑:统一用 __abortController
const abortController = (err?.config as any)?.__abortController as
| AbortController
| undefined;
if (abortController) abortPool.delete(abortController);为什么这是“更像大厂”的做法
- 约定 > 配置:接口分层(
/api/pub/**公开,其他默认鉴权)是稳定、低维护的组织方式;新增公开接口只需放在该前缀下。 - 按需覆盖:极少数“路径不在公开前缀但仍需公开”的接口,用
auth:false显式声明,可读性强。 - 可扩展:未来要拆网关/多后端时,可用双实例进一步隔离(见下方“进阶”)。
- 类型友好:
AxiosRequestConfig扩展auth?: boolean,IDE 自动提示,避免遗漏。
进阶可选(按需采纳)
方案 B:双实例隔离(最常见于中大型前端)
公有与鉴权接口不同实例,互不影响,拦截器更简单。
ts
// 复杂逻辑:一个实例默认鉴权,一个实例默认公开
export const authHttp = createAxiosHttp(config, {
authDefault: true,
publicPrefixes: [],
});
export const pubHttp = createAxiosHttp(config, {
authDefault: false,
publicPrefixes: ["/api/pub"],
});
// 使用时不再传 auth:false
pubHttp.request("/api/pub/get_feednews", "GET");
authHttp.request("/api/secure/profile", "GET");方案 C:API 分层封装(类型安全)
给每个后端接口包一层函数:在函数里固定
auth:true/false,业务层不再关心auth。
ts
// 复杂逻辑:在 API 层固定鉴权语义
export const Api = {
getFeedNews: () =>
http.request<Feed[]>("/api/pub/get_feednews", "GET", undefined, {
auth: false,
}),
getProfile: () => http.request<Profile>("/api/secure/profile", "GET"), // 默认为鉴权
};小结
- 最佳实践:前缀约定 + 显式覆盖,别再维护接口白名单数组。
- 这样新增公开接口只要放到
/api/pub/**,其余默认鉴权;个别例外auth:false即可。 - 若项目更复杂,进一步采用双实例或API 分层,把鉴权语义固化到“调用入口”,彻底杜绝分散判断。