前端网络访问,主流方案就是Ajax,Vue也不例外,在Vue2.0之前,网络访问较多的采用vue-resources,Vue2.0以后,官方不再建议使用vue-resourves,这个项目也停止维护,目前建议使用的方案是axios。
axios是一个第三方的网络请求库,很多人一说Ajax就想到$ .Ajax()、$ .Post()、$ .Get(),这些是Ajax没错,但这些都是jQuery中封装的Ajax,并不是最原始的写法,如果用JavaScript,不用任何外部依赖的库一样也可以发送一个Ajax请求,那个对象就是JavaScript里面有个对象叫XMLHttpRquest,它才是最原始的Ajax请求,它里面有一些回调如onsuccess、onerror等等,那么这样就会有点麻烦,所以才会有一些封装的东西($ .Ajax()、$ .Post()、$ .Get())。那么JavaScript里面既然可以封装成这些,那当然也能封装成其他的,axios就是其中之一。
axios使用步骤很简单,首先在前端项目中引入axios
1、axios引入
安装axios网络请求库(npm install axios -S),安装完成后,按理说可以直接使用了,但是,一般在生产环境中,我们都需要对网络请求进行封装,因为网络请求可能会出错,这些错误有的是代码错误,也有的是业务错误,不管是哪一种错误,都需要开发者去处理,而我们不可能在每一次发送请求时,都去枚举各种错误情况,因此我们需要对前端请求进行封装,封装完成后,将 前端错误统一处理,这样开发者只需要在每一次发送请求的地方处理请求成功的情况。失败的情况统一去处理。
2、请求封装
在axios中,我们可以使用axios自带的拦截器来实现对错误的统一处理,axios中有请求拦截器,也有响应拦截器,请求拦截器中可以统一添加公共的请求参数,例如单点登陆这种token。响应拦截器则可以实现对错误的统一处理。另外一个需要注意的则是错误的展示需要使用一种通用的方式,而不可以和页面绑定(例如,登录失败在用户名或者密码输入框后面展示错误信息,不支持这种错误展示方式),这里推荐使用ElementUI中的MessageBox来展示错误信息,这是一个跟页面无关的组件。
封装后的axios如下:
import axios from 'axios'
import {Message} from 'element-ui'
axios.interceptors.request.use(config => {
return config;
}, err => {
Message.error({message: '请求超时!'});
// return Promise.resolve(err);
})
axios.interceptors.response.use(data => {//{data:{status:200,msg"",obj:{}},status:200}
if (data.status && data.status == 200 && data.data.status == 500) {
//业务逻辑错误
Message.error({message: data.data.msg});
return;
}
if (data.data.msg) {
Message.success({message: data.data.msg});
}
return data.data;
}, err => {
if (err.response.status == 504 || err.response.status == 404) {
Message.error({message: '服务器被吃了⊙﹏⊙∥'});
} else if (err.response.status == 403) {
Message.error({message: '权限不足,请联系管理员!'});
} else if (err.response.status == 401) {
Message.error({message: err.response.data.msg});
} else {
if (err.response.data.msg) {
Message.error({message: err.response.data.msg});
}else{
Message.error({message: '未知错误!'});
}
}
// return Promise.resolve(err);
})
let base = '';
export const postKeyValueRequest = (url, params) => {
return axios({
method: 'post',
url: `${base}${url}`,
data: params,
transformRequest: [function (data) {
let ret = ''
for (let it in data) {
ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
}
return ret
}],
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
}
export const postRequest = (url, params) => {
return axios({
method: 'post',
url: `${base}${url}`,
data: params,
headers: {
'Content-Type': 'application/json'
}
});
}
export const uploadFileRequest = (url, params) => {
return axios({
method: 'post',
url: `${base}${url}`,
data: params,
headers: {
'Content-Type': 'multipart/form-data'
}
});
}
export const putRequest = (url, params) => {
return axios({
method: 'put',
url: `${base}${url}`,
data: params,
headers: {
'Content-Type': 'application/json'
}
});
}
export const deleteRequest = (url) => {
return axios({
method: 'delete',
url: `${base}${url}`
});
}
export const getRequest = (url) => {
return axios({
method: 'get',
url: `${base}${url}`
});
}
(1)、首先导入axios和message组件
message用于弹出提示消息
(2)、定义了一个请求拦截器
request请求拦截器,请求发送的时候,要不要提供一些额外的参数,例如单点登录,每次登录的时候需要给它提供额外的参数,所有的请求都要添加,但是不可能每个请求都手动添加,所以请求的时候就正常请求,然后在这里这里把它拦截下来自动的给它添加一个额外的参数,对于当前来说并没有类似的需求,可以删除,也可以保留,方便以后有需要的时候使用
(3)、定义了一个响应拦截器,这个拦截器有两个参数,第一个data表示服务端啊处理成功的响应,第二个error表示服务端处理失败的响应。对照jQuery中的Ajax,第一个相当于success回调,第二个相当于error回调
(4)、响应的data表示服务端返回的数据,data=>{}是ES6引入的新的语法,它是一个json对象,它的数据格式是{data:{status:200,msg"",obj:{}},status:200}其中data中的对象就是服务端返回的具体的JSON,第一个status就是服务器返回过来的,自定义的那个RespBean的status,第二个status表示http响应码。
(5)、首先判断http响应码为200,并且服务端返回的status为500,表示业务逻辑错误,此时直接通过message将错误信息展示出来,然后return。
(6)、如果服务端返回的字段中包含msg,则将msg显示出来,这个msg一般是成功的提示。
(7)、最后返回data.data,即将服务端返回的数据return,这个数据最终会来到请求调用的地方。
(8)、当http响应码大于等于400时,进入error
注意:response响应拦截器是对响应的拦截,这里有一个关于错误的误区,在使用ajax的时候,里面有一个success回调,还有一个error回调,那么什么时候会调用error回调呢?比如说像刚才一样,想要去登录,不管登录成功还是登录失败,它的http响应码都是200,那如果发了一个$.ajax这样的请求,那么登录失败后是去success还是去error呢?是去success,那这个error什么时候触发呢?比如发来一个请求,服务端返回一个500、服务器内部执行错误、没找到服务器等,这一类的才会到达error里面去。
3、方法封装
请求封装完成后,还需要对方法进行封装,方便调用。由于在前后端分离项目中,大多数情况下都采用restful风格来设计,所以前端主要封装get/post/put/delete方法,然后所有的请求都是JSON。
这里一开始定义了一个base变量,这是请求的前缀(假如有一天,突然给所有的后端修改了前缀,给项目加了项目名,或者给所有的接口加了前缀,这样就不用挨个的方法去改,只需要修改base变量的值就行了),方便后期维护(如果需要统一修改请求前缀)
4、制作Vue插件
封装好的方法已经可以直接使用了,但是比较麻烦,每次使用时,都需要在相关的Vue文件中引入方法,像下面这样:
但是这种操作方式太麻烦,所以我们可以将方法进一步封装成vue的插件,这样在每一个vue文件中不需要引入方法就能够直接调用方法了。插件封装可以参考vue官方文档(https://cn.vuejs.org/v2/guide/plugins.html)
官方文档给了我们5种插件的制作方式,我们这里采用第4种,具体操作就是在main.js中引入所有的封装好的方法,然后挂载到Vue.prototype
封装完成后,以后在vue文件中,直接通过this就可以获取到网络请求方法的引用了。
**注意:**then中的msg就是响应拦截器中返回的msg,这个msg如果没有值,表示请求失败,如果有值,表示请求成功!
5、配置请求转发
在前后端分离中,前端和后端在不同的端口或者地址上运行,如果前端直接向后端发送请求,这个请求是跨域的,但是在项目部署时,打包编译后,拷贝到Java项目中,和Java项目一起运行,此时就不存在跨域问题了,所以这里我们的解决思路不是解决跨域问题而是配置NodeJS的请求转发,来实现网络请求。
请求转发在vue项目的config/index.js文件中配置。
由于这个请求会发给8080端口,而不是后端接口,但是这种跨域问题并不是真正的跨域问题,所以在这里采用一个index.js做一个请求转发,当我自动的把发到8080上的请求转发到后端接口。在index.js中有一个转发的代理表格(proxyTable),在这里面去配,让它把请求转发到后端9999上去。在代理表中添加如下代码,意思是把所有以斜杠开头的请求,都转发到9999上面去,加了这个配置就可以运行了
**注意:**添加了请求转发配置后,一定要重启前端项目才会生效。
'/': {
target: 'http://localhost:9999',
changeOrigin: true,
pathRewrite: {
'^/': ''
}
}
6、发送请求
由于之前写后端接口的时候postman测试传的数据不是json格式,和前端不一样,所以要把前端控制器改成如下:
@RestController
//本项目使用前后端分离的方式,所以基本不会使用到@Controller注解,因为后端无需返回页面,只返回json
//所以基本上使用@RestController
public class LoginController {
//这样权限不足的时候就不会再返回页面,而是返回json,再applicationContext.xml中有配置
@GetMapping("/unauthorizedUrl")
public RespBean unauthorizedUrl(){
return RespBean.error("权限不足,请联系管理员!");
}
@GetMapping("/login")
public RespBean login(){
return RespBean.error("尚未登录,请登录!");
}
@PostMapping("/doLogin")
public RespBean login(@RequestBody User user){
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(user.getAccount(),user.getPassword()));
//拿到当前登录的用户信息返回,但是不返回密码
User currentUser = (User) SecurityUtils.getSubject().getSession().getAttribute("user");
currentUser.setPassword(null);
return RespBean.ok("登陆成功!",currentUser);
}catch (AuthenticationException e){
e.printStackTrace();
}
return RespBean.error("登录失败!");
}
}
@JsonIgnore注解这个注解的意思是生成json的时候,忽略这个字段,但是这里不能使用它,因为它是双向的,生成json时候忽略它,从json生成对象的时候也忽略它,这样在登录的时候传用户名和密码过来的时候,传的是一个json字符串,要把这个json字符串转成User对象,在这个转换的过程中,一样也会忽略password,这样就会导致无法登录
测试:
使用前端登录请求也能登录成功: