概述
在用Vue进行开发的时候,官方推荐的前后端通信插件是Axios。 Axios是一个基于Promise的HTTP库,可以用在浏览器和Node.js中。
Github开源地址:
文档:
axios框架,有很多优点,支持Promise,支持拦截请求和响应
在ajax中,请求成功之后,会调用success中的函数处理请求来的数据。那在我们axios中是怎么处理的呢?
axios()本身会返回一个Promise对象,当请求成功的时候,会默认调用resolve()函数,从而你可以把请求成功处理data 的代码放到then方法中执行。同理请求失败的时候,会默认调用reject函数传入错误信息。然后默认调用catch方法
axios源码解析,推荐这一篇文章:
zhuanlan.zhihu.com/p/156862881
使用
安装与导入
npm install axios --save import axios from "axios"
执行 GET 请求
// 为给定 ID 的 user 创建请求
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
// 可选地,上面的请求可以这样做
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
执行 POST 请求
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
创建实例
var instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
跨域请求
由于浏览器的安全性限制,不允许AJAX访问协议不同、域名不同、端口号不同的数据接口,浏览器认为这种访问不安全。
实现跨域最常用的几种方式:
1.JSONP的实现原理
可以通过动态创建script标签的形式,把script标签的src属性,指向数据接口的地址,因为script标签不存在跨域限制,这种数据获取方式,称作JSONP
2.代理
axios支持代理配置,我们可以通过设置代理来防止跨域问题。
通常在代码中对axios进行全局代理配置,当我们最终把前端代码发布到生产服务器的时候,再通过Nginx等代理服务器来进行请求转发,这样我们的前端代码和后端接口就可以部署在不同的服务器上,也不会产生跨域问题。
在dev开发模式下可以下使用 webpack 的 proxy
以实际项目的配置为例,在vue.config.js文件中的devServer中配置proxy代理请求地址
devServer: {
// overlay: {
// warnings: true,
// errors: true
// },
// hot: true,//自动保存
open: true,
// host: "localhost",
port: port,
https: false,
hot:true,//自动保存
hotOnly: true,
disableHostCheck: true,
proxy: {
'/api': {
timeout: 30000, // 请求超时时间
// target: 'http://122.112.193.171:8086/arcamel-admin',
pathRewrite:{
'^/api': ''
},
// target : 'http://boot.jeecg.org',
ws: false, // 是否启用websockets
// secure: false,
changeOrigin: true, // 开启代理,在本地创建一个虚拟服务端
headers: {
// 'Content-Type': 'application/json'
},
}
}
},
在生产环境中需要使用 nginx 进行反向代理。nginx.conf文件里中配置
location /api {
proxy_pass http://122.112.193.171:8084/arcamel-admin;
}
不管是 proxy 和 nginx 的原理都是一样的,通过搭建一个中转服务器来转发请求规避跨域的问题。
3.后端接口跨域支持
后端接口支持跨域,这个也很简单,就是后端程序员(编写接口的),通过过滤器对接口请求进行配置,从而准许接口能够被跨域访问。这样一来,我们前端程序员啥也不用管,直接就可以调用。
Axios封装与拦截器
方便统一处理,简单封装示例:
import axios from "axios"
export function instance1 (config,success,failure){
let instance = axios.create({});
instance(config).then(res=>success(res)).catch(err=>failure(err))
}
import axios from "axios"
// export function instance1 (config,success,failure){
// let instance = axios.create({});
// instance(config).then(res=>success(res)).catch(err=>failure(err))
// }
export function instance1 (config){
let instance = axios.create({});
return instance(config);
}
拦截器
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
请求时使用application/x-www-form-urlencode
//可以使用qs库来编码数据。示例代码如下:
const qs = require('qs');
axios.post('/foo', qs.stringify({ 'bar': 123 }));
import qs from 'qs';
const data = { 'bar': 123};
const options = {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
data: qs.stringify(data),
url,
};
axios(options);
以实际项目为例:远程档案预约系统客户端,展示axios的封装和拦截请求的配置
main.js同级目录下,新建http文件夹 main.js文件中引入
import api from './http/index'
Vue.use(api)
http文件夹中新建如下文件: axios.js文件
import axios from 'axios';
import config from './config';
import qs from 'qs';
import { setItem, getItem, removeItem } from "@/utils/store";
import { getToken } from "@/utils/auth";
import { Message } from 'element-ui';
import router from '@/router'
import store from '@/store'
// 使用vuex做全局loading时使用
// import store from '@/store'
export default function $axios(options) {
return new Promise((resolve, reject) => {
const instance = axios.create({
baseURL: config.baseURL,
headers: {
'X-Access-Token': getToken()
},
transformResponse: [function (data) {
}]
})
// request 拦截器
instance.interceptors.request.use(
config => {
let token = getItem('Token')
// 1. 请求开始的时候可以结合 vuex 开启全屏 loading 动画
// console.log(store.state.loading)
// console.log('准备发送请求...')
// 2. 带上token
if (token) {
config.headers.accessToken = token
}
return config
},
error => {
// 请求错误时
console.log('request:', error)
// 1. 判断请求超时
if (error.code === 'ECONNABORTED' && error.message.indexOf('timeout') !== -1) {
console.log('timeout请求超时')
// return service.request(originalRequest);// 再重复请求一次
}
// 2. 需要重定向到错误页面
const errorInfo = error.response
console.log(errorInfo)
if (errorInfo) {
error = errorInfo.data // 页面那边catch的时候就能拿到详细的错误信息,看最下边的Promise.reject
const errorStatus = errorInfo.status; // 404 403 500 ...
router.push({
path: `/error/${errorStatus}`
})
}
return Promise.reject(error) // 在调用的那边可以拿到(catch)你想返回的错误信息
}
)
// response 拦截器
instance.interceptors.response.use(
response => {
let data;
// IE9时response.data是undefined,因此需要使用response.request.responseText(Stringify后的字符串)
if (response.data == undefined) {
data = JSON.parse(response.request.responseText)
} else {
data = response.data
}
// 根据返回的code值来做不同的处理
switch (data.rc) {
case 1:
console.log(data.desc)
break;
case 0:
store.commit('changeState')
// console.log('登录成功')
default:
}
// 若不是正确的返回code,且已经登录,就抛出错误
// const err = new Error(data.desc)
// err.data = data
// err.response = response
// throw err
return data
},
err => {
if (err && err.response) {
switch (err.response.status) {
case 400:
err.message = '请求错误'
break
case 401:
err.message = '未授权,请登录'
break
case 403:
err.message = '拒绝访问'
break
case 404:
err.message = `请求地址出错: ${err.response.config.url}`
break
case 408:
err.message = '请求超时'
break
case 500:
// err.message = '服务器内部错误'
let res = JSON.parse(err.response.request.response);
debugger
if(res.error == 'Internal Server Error'){
msgInvalidLogin(res.message);
}
err.message = '服务器内部错误'
break
case 501:
err.message = '服务未实现'
break
case 502:
err.message = '网关错误'
break
case 503:
err.message = '服务不可用'
break
case 504:
err.message = '网关超时'
break
case 505:
err.message = 'HTTP版本不受支持'
break
default:
}
}
console.error(err)
return Promise.reject(err) // 返回接口返回的错误信息
}
)
// 请求处理
instance(options).then(res => {
resolve(res)
return false
}).catch(error => {
reject(error)
})
})
}
function msgInvalidLogin(msg){
Message.error(msg);
let second = 3;
const timer = setInterval(() => {
second--;
if (second) {
} else {
clearInterval(timer);
store.dispatch('LogOut').then(() =>{
location.reload()// In order to re-instantiate the vue-router object to avoid bugs
})
}
}, 1000);
}
config.js文件,配置请求头
import { getToken } from "@/utils/auth";
export default {
method: 'get',
// 基础url前缀
// baseURL: 'http://localhost:8080/',
baseURL: window._CONFIG['BASE_API'] ,
// 请求头信息
headers: {
'Content-Type': 'application/json;charset=UTF-8',
'X-Access-Token': getToken()
},
// 参数
data: {},
// 设置超时时间
timeout: 10000,
// 携带凭证
withCredentials: true,
// 返回数据类型
responseType: 'json'
}
interface.js文件,封装请求接口
import axios from './axios'
/*
* 将所有接口统一起来便于维护
* 如果项目很大可以将 url 独立成文件,接口分成不同的模块
*/
export const loginByUsername = (username, password, captcha, checkKey) => {
const data = {
username,
password,
captcha,
checkKey
}
// console.log(data)
return axios({
url: '/sys/login',
method: 'post',
data
})
}
export const logout = () => {
return axios({
url: '/sys/logout',
method: 'post'
})
}
export const getUserInfo = (token) => {
return axios({
url: '/sys/permission/getUserPermissionByToken',
method: 'get',
params: { token }
})
}
//post
export const postAction = (url, parameter) => {
return axios({
url: url,
method: 'post',
data: parameter
})
}
//post method= {post | put}
export const httpAction = (url, parameter, method) => {
return axios({
url: url,
contentType: "application/json; charset=utf-8",
dateType: "json",
method: method,
data: parameter
})
}
//post method= {post | put}
export const httpActionByQuery = (url, query, parameter, method) => {
return axios({
url: url,
method: method,
params: query,
data: parameter
})
}
//put
export const putAction = (url, parameter) => {
return axios({
url: url,
method: 'put',
data: parameter
})
}
//get
export const getAction = (url, parameter) => {
return axios({
url: url,
method: 'get',
params: parameter
})
}
/**
* 获取字典
* @param {*} data
*/
export function getDictItems(data) {
return axios({
url: 'sys/dict/getDictItems/'+ data,
method: "get"
});
}
//deleteAction
export const deleteAction = (url, parameter) => {
return axios({
url: url,
method: 'delete',
params: parameter
})
}
// 默认全部导出
export default {
loginByUsername,
logout,
getUserInfo,
postAction,
httpAction,
httpActionByQuery,
putAction,
getAction,
getDictItems,
deleteAction
}
在index.js文件中,导入所有接口后,挂载到Vue 原型的 $api 对象上,可以直接调用 getAction 接口
this.$api.getAction(url, params).then((res) => {
if (res.success) {
}
});
index.js文件代码:
// 导入所有接口
import apis from './interface'
const install = Vue => {
if (install.installed)
return;
install.installed = true;
Object.defineProperties(Vue.prototype, {
// 注意,此处挂载在 Vue 原型的 $api 对象上
$api: {
get() {
return apis
}
}
})
}
export default install
除了将接口挂载到Vue 原型的 $api 对象上,还可以在需要的地方直接引入,譬如login.js文件的接口方法引入:
import { loginByUsername } from '@/http/login'
loginByUsername(username, userInfo.password, userInfo.captcha, userInfo.checkKey).then(response => {
if (response.result) {
}
}).catch(error => {
})
login.js 文件代码:
import axios from './axios'
export function loginByUsername(username, password, captcha, checkKey) {
const data = {
username,
password,
captcha,
checkKey
}
// console.log(data)
return axios({
url: '/sys/login',
method: 'post',
data
})
}
export function getUserInfo(token) {
return axios({
url: '/sys/permission/getUserPermissionByToken',
method: 'get',
params: { token }
})
}
参考书籍:
Vue.js入门与商城开发实战 -- 黄菊华