Appearance
只给需要改的代码片段;复杂逻辑我都在上一行加了中文注释。以下改动基于你贴的这份
http封装。
1) 请求可“集中取消”& 退出后不再飞行
位置:请求拦截器内(_axios.interceptors.request.use)头部插入
ts
// 复杂逻辑:为每个请求创建可集中取消的 AbortController,并桥接外部 signal
const ac = new AbortController();
if (req.signal) {
const ext = req.signal as AbortSignal;
if (ext.aborted) ac.abort();
else ext.addEventListener?.("abort", () => ac.abort());
}
req.signal = ac.signal;
abortPool.add(ac);
// 复杂逻辑:把本次请求的 controller 暂存到 config 上,方便响应阶段释放
(req as any).__ac = ac;位置:响应成功拦截器(res 分支)里,return res 之前插入
ts
// 复杂逻辑:请求完成后,从取消池中释放 controller
const ac = (res.config as any).__ac as AbortController | undefined;
if (ac) abortPool.delete(ac);位置:响应失败拦截器(err 分支)里,return Promise.reject(err) 之前插入
ts
// 复杂逻辑:请求失败同样释放 controller,避免池子泄漏
const ac = (err?.config as any)?.__ac as AbortController | undefined;
if (ac) abortPool.delete(ac);这样就和你现有的
cancelAll()打通了:登出时执行cancelAll(),所有在途请求立刻中止。
2) “未登录硬拦”鉴权请求(默认需要鉴权)
位置:请求拦截器内,给你现有的加 Token 逻辑改为如下**
ts
// 复杂逻辑:统一鉴权开关——默认需要鉴权,白名单或显式 { auth:false } 不需要
const needAuth =
(req as any).auth !== false && !noAuthPaths.includes(req.url || "");
// 复杂逻辑:无 Token 且需要鉴权时,直接阻断请求,避免未登录拉数据
const at = storage.get(ACCESS_TOKEN);
const tt = storage.get(TOKEN_TYPE) || "Bearer";
if (needAuth && !at) {
// 同时中止本次请求,确保不会真正发出
((req as any).__ac as AbortController | undefined)?.abort();
return Promise.reject(new Error("UNAUTHENTICATED"));
}
// 复杂逻辑:有 Token 再注入 Authorization 头
if (needAuth && at) {
req.headers = { ...req.headers, Authorization: `${tt} ${at}` };
}使用方式:需要鉴权的 API 不用写任何额外配置(默认会拦),公开 API(或登录/注册)明确加上
{ auth: false }即可。
3) 统一 401 处理(去掉 useRouter 的非法使用)
位置:你的 codeResponseParser 内,将 401 分支替换为:
ts
// 复杂逻辑:统一未授权处理(不在解析器里用 useRouter)
if (code === 401) {
storage.remove(ACCESS_TOKEN);
storage.remove(TOKEN_TYPE);
// 复杂逻辑:避免组合式 API 环境限制,使用硬跳转到登录页
if (window.location.pathname !== LOGIN_ROUTE.path) {
window.location.replace(LOGIN_ROUTE.path);
}
throw new Error("未授权,请重新登录");
}位置:响应失败拦截器(err 分支)里,加入 HTTP 401 fallback
ts
// 复杂逻辑:后端直接返回 HTTP 401 时的兜底处理
if (err?.response?.status === 401) {
storage.remove(ACCESS_TOKEN);
storage.remove(TOKEN_TYPE);
if (window.location.pathname !== LOGIN_ROUTE.path) {
window.location.replace(LOGIN_ROUTE.path);
}
}说明:
useRouter()不能在解析器(组件外)调用,上面用window.location.replace做了上下文无关的重定向。
4) 实例方法:供登出调用(清鉴权头 & 集中取消)
位置:createAxiosHttp 末尾、return _axios as unknown as AxiosInstance 前插入
ts
// 复杂逻辑:向实例挂载工具方法,供外部优雅下线使用
(_axios as any).cancelAll = cancelAll;
(_axios as any).setAuthToken = (token?: string | null, type = "Bearer") => {
if (token) {
_axios.defaults.headers.common.Authorization = `${type} ${token}`;
storage.set(ACCESS_TOKEN, token);
storage.set(TOKEN_TYPE, type);
} else {
delete _axios.defaults.headers.common.Authorization;
storage.remove(ACCESS_TOKEN);
storage.remove(TOKEN_TYPE);
}
};这样在
logout()里可以写:http.cancelAll?.(); http.setAuthToken?.(null)。
5) (可选)请求方法支持 formdata 与 auth 透传
位置:你重写的 (_axios as any).request = async function ... 的函数签名与组装 cfg部分替换为:
ts
// 复杂逻辑:扩展 contentType,支持 formdata;透传 { auth?: boolean, signal?: AbortSignal }
(_axios as any).request = async function <T, P = RequestParams>(
url: string,
method: Method = "GET",
params?: P,
reqCfg?: AxiosRequestConfig & { auth?: boolean; signal?: AbortSignal },
contentType: "json" | "urlencoded" | "formdata" = "json"
): Promise<T> {
const cfg: AxiosRequestConfig & { auth?: boolean } = {
url,
method,
...reqCfg,
headers: { ...reqCfg?.headers },
};
if (method === "GET" || method === "HEAD") {
cfg.params = params;
cfg.paramsSerializer = (d) =>
qs.stringify(d, { indices: false, skipNulls: true });
} else {
// 复杂逻辑:根据 contentType 选择编码方式
if (contentType === "formdata") cfg.data = toFormData(params as any);
else if (contentType === "urlencoded")
cfg.data = toUrlencoded(params ?? ({} as any));
else cfg.data = params;
if (contentType === "urlencoded")
cfg.headers = {
...cfg.headers,
"Content-Type": "application/x-www-form-urlencoded",
};
if (contentType === "json")
cfg.headers = { ...cfg.headers, "Content-Type": "application/json" };
// formdata 让浏览器自动带 boundary,不手动设 Content-Type
}
return nativeRequest(cfg) as Promise<T>;
};有了
{ auth?: boolean },你在公开接口(如/api/pub/get_feednews)调用时可以:
ts
http.request("/api/pub/get_feednews", "GET", undefined, { auth: false });6) (配合改动)登出时的两行调用(在你的 authStore.logout() 里加)
ts
// 复杂逻辑:登出时立即中止所有在途请求 + 清鉴权头
http.cancelAll?.();
http.setAuthToken?.(null);7) (可选)类型补充,便于在项目里显式写 auth
新建:types/axios.d.ts
ts
// 复杂逻辑:为 AxiosRequestConfig 增加自定义字段 auth
import "axios";
declare module "axios" {
interface AxiosRequestConfig {
auth?: boolean;
}
}验证 Checklist
- 退出登录瞬间 Network 面板无新的业务请求(只有静态资源预取不算)。
- 任何标记
{ auth: true | 默认 }的请求在无 Token时不会发出。 - 后端返回 HTTP 401 或 业务码 401,都能统一清理并跳到登录页。
cancelAll()能把“已发起未完成”的请求立即中断。
以上改完,你截图里“跳转后还发请求”的现象就会消失;即使有极短时序触发,也会被 AbortController 和 未登录硬拦兜住。