| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- import axios from 'axios';
- class RequestHttp {
- constructor(config) {
- this.instance = axios.create(config);
- this.abortControllers = new Map();
- this._setupInterceptors();
- }
- _generateRequestKey(config) {
- const { method, url, params, data } = config;
- return `${method}-${url}-${JSON.stringify(params || {})}-${JSON.stringify(data || {})}`;
- }
- _addPendingRequest(config) {
- const requestKey = this._generateRequestKey(config);
- const controller = new AbortController();
- config.signal = controller.signal;
- this.abortControllers.set(requestKey, controller);
- }
- _removePendingRequest(config, cancel = false) {
- const requestKey = this._generateRequestKey(config);
- if (this.abortControllers.has(requestKey)) {
- if (cancel) {
- this.abortControllers.get(requestKey).abort();
- }
- this.abortControllers.delete(requestKey);
- }
- }
- _setupInterceptors() {
- // 请求拦截器
- this.instance.interceptors.request.use(
- (config) => {
- // 防重复请求
- if (config.cancelDuplicate !== false) {
- this._removePendingRequest(config, true);
- this._addPendingRequest(config);
- }
- // 注入 Token
- if (config.withToken !== false) {
- const token = localStorage.getItem('token');
- if (token && config.headers) {
- config.headers.Authorization = `Bearer ${token}`;
- }
- }
- return config;
- },
- (error) => {
- return Promise.reject(error);
- }
- );
- // 响应拦截器
- this.instance.interceptors.response.use(
- (response) => {
- this._removePendingRequest(response.config);
- const { data, config } = response;
- // 业务成功:脱壳返回
- if (data.code === 200) {
- return data.data;
- }
- // 业务错误
- if (!config.skipGlobalError) {
- console.error('Business Error:', data.message);
- }
- // Token 过期:仅在非 skipGlobalError 的请求上触发整站跳登录页,
- // 否则像登录接口(凭证错也是 401)会被强制 reload,覆盖掉本地 hint。
- if (data.code === 401 && !config.skipGlobalError) {
- console.warn('Token已过期,请重新登录');
- localStorage.removeItem('token');
- window.location.href = '/login';
- }
- return Promise.reject(new Error(data.message || 'Error'));
- },
- (error) => {
- if (axios.isCancel(error)) {
- return Promise.reject('Request Canceled by Duplicate Prevention');
- }
- if (error.config) {
- this._removePendingRequest(error.config);
- }
- const status = error.response?.status;
- let errorMsg = error.message;
- switch (status) {
- case 400: errorMsg = '请求参数错误'; break;
- case 401: errorMsg = '未授权,请重新登录'; break;
- case 403: errorMsg = '拒绝访问'; break;
- case 404: errorMsg = '请求地址不存在'; break;
- case 500: errorMsg = '服务器内部错误'; break;
- case 502: errorMsg = '网关错误'; break;
- case 503: errorMsg = '服务不可用'; break;
- case 504: errorMsg = '网关超时'; break;
- default: errorMsg = '网络连接异常';
- }
- console.error('HTTP Error:', errorMsg);
- return Promise.reject(error);
- }
- );
- }
- get(url, config) {
- return this.instance.get(url, config);
- }
- post(url, data, config) {
- return this.instance.post(url, data, config);
- }
- put(url, data, config) {
- return this.instance.put(url, data, config);
- }
- delete(url, config) {
- return this.instance.delete(url, config);
- }
- }
- export const http = new RequestHttp({
- baseURL: process.env.VUE_APP_API_BASE || '',
- timeout: Number(process.env.VUE_APP_REQUEST_TIMEOUT) || 15000,
- cancelDuplicate: true,
- });
|