Skip to content

结论(一句话)

不要维护 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 分层,把鉴权语义固化到“调用入口”,彻底杜绝分散判断。

本页访问 (不蒜子统计;全站数据可在百度统计 / Umami 后台查看)

Last updated:

本站总访问