Vue/Vuex中使用axios全局处理HTTP响应错误

8
我正在尝试修复VueJS单页应用程序中出现的不良行为,即出现临时状态。应用程序不知道JWT已经过期,因此呈现给用户的界面仍然显示为登录状态。例如,在休眠后这种情况可能会发生。
这些用户可以继续向API发送任何请求,但最终会收到401响应(这是正确的)。
我想为401响应编写一个全局处理程序。(即:“从vuex中清除所有与用户相关的内容,并像访客一样呈现页面,包括登录表单弹窗等。”)否则,我就必须为每个请求编写401处理程序。
我可以将响应拦截器添加到axios中,它们可以正常工作。但这些拦截器无法访问Vuex(或Vue)。
每当我尝试将Vuex或Vue导入到我的Axios中时,就会出现循环依赖关系(当然),一切都会崩溃。
如果我只是抛出/返回错误,则仍然必须在每个请求上单独处理它。如何在axios拦截器中调用this.$store中的方法?
Axios文件包含一个默认导出类API,该类在main.js中全局添加到Vue中。
import api from 'Api/api'
// ...
Vue.prototype.$http = api

我曾经认为可以通过全局实例方法访问Vue,因此必须有一种方法可以从$http中访问它。但是我似乎错了?

代码

main.js

// ...
import api from 'Api/api'
// ...
Vue.prototype.$http = api

new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App },
  vuetify: new Vuetify(opts),
});

api.js

import Client from './ApiClient'

const apiClient = new Client({ basePath: process.env.VUE_APP_API_URL })

const api = {
  get(url) {
    return apiClient._get(`${basePath}/${url}`)
  },
  post(url, data) {
    return apiClient._post(`${basePath}/${url}`, data)
  },
  // ...
}
export default api

ApiClient.js

const axios = require('axios')

const errorHandler = (error) => {
  if (error.response.status === 401) {
    store.dispatch('user/logout') // here is the problem
  }
  return Promise.reject({ ...error })
}


export default class API {
  constructor(options) {
    this.options = Object.assign({ basePath: '' }, options)
    this.axios = axios.create({ timeout: 60000 })
    this.axios.interceptors.response.use(
      response => response,
      error => errorHandler(error)
    )
  }
  // ...
}

ApiClient.js 中导入 store 导致依赖循环:我猜测这是因为在其中导入了 Vue?

store.js

import Vue from 'vue'
import Vuex from 'vuex'
import PersistedState from 'vuex-persistedstate'
import CreateMutationsSharer from 'vuex-shared-mutations';
import SecureLS from 'secure-ls';
// import modules

Vue.use(Vuex);
const ls = new SecureLS({ encodingType: 'aes' });

export default new Vuex.Store({
  // options
})

客户端将无法知道令牌是否仍然有效。 - devman
4个回答

3
conf
import Axios from 'axios'
import IdentityProxy from './IdentityProxy.js'
import UsuariosProxi from './UsuariosProxi'
import ZonasProxi from './ZonasProxi'

//axios
Axios.defaults.headers.common.Accept='application/json'
//Axios.defaults.headers.common['Access-Control-Allow-Origin'] = '*';

Axios.interceptors.request.use(
    config => {
        let token = localStorage.getItem('access_token');

        if(token){
            config.headers= {
                'x-access-token': `${token}`
            }
        }
        return config;
    },
    error => Promise.reject(error)
);
Axios.interceptors.response.use(
    response => response,
    error => {
      if (error.response.status===403||error.response.status===401) {
        localStorage.removeItem('access_token');
        window.location.reload(true);
      }
   
      return Promise.reject(error);
    }
  );
let url=null

if(localStorage.getItem("config")!==null){
    let config = JSON.parse(localStorage.getItem("config"))
    url = config
}

console.log(url)
export default{
    identityProxy: new IdentityProxy(Axios, url),
    _usuarioProxi: new UsuariosProxi(Axios, url),
    _zonasProxi: new ZonasProxi(Axios, url),
}
//
export default class IdentityProxy{

    constructor(axios,url){
    this.axios = axios;
    this.url =url;
    }

    register(params){
        return this.axios.post(this.url+'/identity/register',params)
    }

    login(params){
        
        return this.axios.post(this.url+'/auth/signin',params)
    }
}
//
export default class UsuariosProxi{
    constructor(axios,url){
    this.axios = axios;
    this.url =url;
    }

    /* getAll(){
        return this.axios.get(this.url+'/users')
    } */
    getAll(page, take) {
        return this.axios.get(this.url + `/users?page=${page}&take=${take}`);
    }
    create(params) {
        return this.axios.post(this.url + '/auth/signup', params);
    }

    get(id) {
        return this.axios.get(this.url + `/users/${id}`);
    }
    update(id, params) {
        return this.axios.put(this.url + `/users/${id}`, params);
    }

    remove(id) {
        return this.axios.delete(this.url + `/users/${id}`);
    }
    //-----APARTE SOLO TRAE LISTA DE ROLES--------
    getRoles() {
        return this.axios.get(this.url + '/users/newrol');
    }
}
//st
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const state = {
    user:null
}
export default new Vuex.Store({
    state
});

2

main.js:

import store from './store';

const Instance = new Vue({
  store,
  ...
})

export const { $store } = Instance;

现在您可以在任何地方导入{ $store } from '@/main.js'。它将是您在应用程序中安装的相同实例,而不是一个新的Vuex.Store({})(这是每次您在其他地方导入./store时导出的内容)。
您可以以相同的方式导出任何您想要在服务、测试、帮助程序等中使用的内容...例如:
export const { $store, $http, $bus, $t } = Instance;

这种方法是否也会创建一个依赖循环,但这次是从main.js文件开始的? - Bordalo
@mutsa,通常情况下不会出现这种情况。如果您遇到这样的情况并且找不到解决方法,请将其作为一个不同的问题发布。 - tao
如果我正确理解了您的建议,那么它应该将main.js导入到ApiClient.js中以在其上使用store,这将创建另一个“依赖循环”,因为main.js-> api.js-> ApiClient.js-> main.js。 我用另一种方法成功解决了这个特定的问题,我会发布出来。 @tao感谢您的时间。 - Bordalo

1

基于这些线程,我能够满足我的需求并找到了解决方案:

main.js

import api, {apiConfig} from 'Api/api'
apiConfig({ store: $store });

ApiClient.js

let configs = {
  store: undefined,
};
const apiConfig = ({ store }) => {
  configs = { ...configs, store };
};
export default api;
export { apiConfig };

这样,api.js文件将需要一个配置,以后可以扩展。

1

您是否考虑直接将您的商店导入到ApiClient.js中?类似以下方式:

const axios = require('axios')
import store from 'path/to/store'

const errorHandler = (error) => {
if (error.response.status === 401) {
  store.dispatch('user/logout') // now store should be accessible
}
  return Promise.reject({ ...error })
}


export default class API {
  constructor(options) {
    this.options = Object.assign({ basePath: '' }, options)
    this.axios = axios.create({ timeout: 60000 })
    this.axios.interceptors.response.use(
      response => response,
      error => errorHandler(error)
    )
  }
  // ...
}

这导致了依赖循环和溢出,但我不太确定原因。我已经在store.js中导入了Vue,这可能是问题吗?我将把store.js添加到我的问题中。 - devman
你用什么工具来构建应用程序?我有相同的存储文件(已导入Vue),并且它可以在我的api.js中导入而没有任何问题,你能否请展示一下你遇到了哪个错误? - strelok2010
我想我找到了问题,多次检查后,你说它对你有效,我才发现。 我有一个无限循环的请求,但这些似乎来自于“用户/注销”操作,该操作向API发送另一个请求,告诉它丢弃JWT。当然,如果您没有登录,这是行不通的:D 我想就是这样。我会深入挖掘并确保。如果我在不久的将来没有发布任何更新,那么它将起作用,并且您的答案将被接受,谢谢:) - devman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接