为什么会写这篇,主要是对以前的知识点回顾,以及给不清楚的人稍微梳理一下前后端对权限,网络请求公共模块的简单写法
java后端
前后端分离的好处之一就是可以更加的专精所长,但是随之带来的是两端在数据交互方面都需要进行自己的协调
1.跨域问题
No 'Access-Control-Allow-Origin' header is present on the requested resource
跨域问题我认为是需要让后端来做,毕竟做个cors也不是多么麻烦的事儿.
如果前端需要配置:
-
1.开发阶段一层devServer转发
注释起来的部分就是,这里还涉及到部分转发,就是访问的时候指定哪个路径下需要转发,路径重写,指定目标,反正也是需要一些的理解
- 2.发布阶段nginx配置
一般这一步是到发布的时候,连上公司服务器才能进行,下面这个我没试过,应该是能行的,这个多长时间下就可以了.nginx配置一般都需要学一下,也不说哪些配置优化,就基础的配置,比如搭建,转发什么的.
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
if ($request_method = 'OPTIONS') {
return 204;
}
}
如果后端来配置:
就是一部到位的事情,直接配置一个cors,直接解决
注释部分可以增加更细化的配置,不过开发阶段我就没去管那么多
一个小注意点
顺嘴说一句,如果是src图片加载出现跨域问题,就是有的图片地址出现,只需要在index.html添加一个meta,防止别人的资源对你的资源进行refer防盗链劫持
<!-- 防止img加载403-->
<meta name="referrer" content="no-referrer" />
2.网络请求封装
-
1.java后端
这里的封装其实就是对请求的拦截处理,token过期处理,有几个点得注意下:
配置拦截器劫持
拦截器拦截检验
@Configuration
public class XqInterceptor implements HandlerInterceptor {
@Autowired
UserAuthModelMapper userAuthModelMapper;
@Autowired
UserModelMapper userModelMapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("进入了一次预处理" + request.getRequestURI());
String url = request.getRequestURI();
if (url.contains("/login")) {
return true;
}
// 这里检验token是否过期
{
String token = request.getHeader("token");
if (token == null) {
// throw new XQException(RespEnum.user_token_error);
write(request, response, Resp.error(RespEnum.user_token_null));
return false;
}
UserAuthModel userAuthModel = userAuthModelMapper.findUserByToken(token);
if (userAuthModel == null) {
// throw new XQException(RespEnum.user_token_error);
write(request, response, Resp.error(RespEnum.user_token_error));
return false;
}
Date updateTime = userAuthModel.getUpdateTime();
Long expireTimeInterval = updateTime.getTime() + 1000 * 60 * 60 * 24;;
Long nowInterval = new Date().getTime();
if (expireTimeInterval < nowInterval) {
// 已经过期
userAuthModel.setToken(UUID.randomUUID().toString());
userAuthModel.setUpdateTime(null);
userAuthModelMapper.updateByPrimaryKeySelective(userAuthModel);
write(request, response, Resp.error(RespEnum.user_token_error));
return false;
}
return true;
}
}
private <T> void write(HttpServletRequest request, HttpServletResponse response, T content)
throws IOException {
response.setCharacterEncoding("UTF-8");
String origin = request.getHeader("Origin");
response.setHeader("Access-Control-Allow-Origin", origin);
//告诉浏览器允许跨域访问的方法
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
//告诉浏览器允许带有Content-Type,header1,header2头的请求访问
response.addHeader("Access-Control-Allow-Headers", "*");
//允许带有cookie访问
response.addHeader("Access-Control-Allow-Credentials", "true");
String contentStr = new ObjectMapper().writeValueAsString(content);
response.getWriter().write(contentStr);
}
}
拦截器抛出的异常在自定义的异常处理器中是捕获不到的,必须要直接对response进行返回
这里的逻辑我并没有进行很细化的编写(其实前端传值的时候,header里面应该添加token和id的,我一开始嫌麻烦没做,所以就只用了个token)
- 2.vue前端
假设大家基本用的都是axios(其他的框架我也没听过,毕竟只是基础地学了下前端的vue)
基本都回去封装一个http.js模块做axios的前后置处理
简单地说,就是new一个axios实例,然后对request,和response处理,然后export抛出即可
-
第一个注意点:
这里是一个js文件,有时候我们可能想在这里进行一个toast弹窗之类的操作,在elementui中.这个需要一个vue的实例进行router进行push什么的操作
其实,我们可以对main.js进行抛出即可
然后引入这个mian.js,就可以做任何想要的操作
-
第二个注意点:
request,response的处理,前置处理header,后置处理通用全局错误码,不过我目前写的都还是比较简单地
-
第三个:
使用的话,建议大家可以封装一个api文件夹
其实这里如果想更少侵入的话,甚至可以将user.js等模块统一起来,然后放大Vue.prototype.$api上,但是那样写的话,编辑器就没有提示,所以我还是做这个引入的操作
-
第四个:
这里最后说一个点,在上图user.js模块中,我测试了很多很多网络请求的传参,最后还是用了qs的stringfy() 才能正常的在java中使用@RequestParam拿到参数,这里面其实坑特别特别多,因为axios用的不是x-www-form-urlencoded格式的参数,用的是body体传参
// 设置post请求头
// instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
上面这个也不好使的,反正坑是很多的,需要大家注意下
最后把http.js模块丢出来给大家参考下吧,去去糟粕
/**
* axios封装
* 请求拦截、响应拦截、错误统一处理
* https://juejin.im/post/5b55c118f265da0f6f1aa354
*/
import axios from 'axios'
import router from '../router'
import store from '../store'
import cookies from 'js-cookie'
import mainVue from '../main'
/**
* 提示函数
* 禁止点击蒙层、显示一秒后关闭
*/
const tip = msg => {
mainVue.$message(msg);
}
/**
* 跳转登录页
* 携带当前页面路由,以期在登录页面完成登录后返回当前页面
*/
const toLogin = () => {
mainVue.$router.replace({
path: '/login',
query: {
redirect: mainVue.$router.currentRoute.fullPath
}
})
}
/**
* 请求失败后的错误统一处理
* @param {Number} status 请求失败的状态码
*/
const errorHandle = (code, other) => {
// 状态码判断
switch (code) {
// 401: 未登录状态,跳转登录页
case 204:
case 205:
case 206:
case 207:
// 403 token过期
// 清除token并跳转登录页
cookies.remove("token")
tip('登陆过去,请重新登陆')
setTimeout(() => {
toLogin()
}, 1000)
break
// store.commit('loginSuccess', null);
default:
console.log(other)
}
}
// 创建axios实例
var instance = axios.create()
// g过期时间
instance.defaults.timeout = 1000 * 12
// 设置post请求头
// instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
/**
* 请求拦截器
* 每次请求前,如果存在token则在请求头中携带token
*/
instance.interceptors.request.use(
config => {
// 登录流程控制中,根据本地是否存在token判断用户的登录情况
// 但是即使token存在,也有可能token是过期的,所以在每次的请求头中携带token
// 后台根据携带的token判断用户的登录情况,并返回给我们对应的状态码
// 而后我们可以在响应拦截器中,根据状态码进行一些统一的操作。
// const token = store.state.token
const token = cookies.get("token")
token && (config.headers.token = token)
return config
},
error => Promise.error(error))
// 响应拦截器
instance.interceptors.response.use(
// 请求成功
res => {
// console.log(JSON.stringify(res))
if (res.status === 200) {
if (res.data.code === 0) {
return Promise.resolve(res)
}else {
// Promise.reject(res)
errorHandle(res.data.code, res.data.msg)
return Promise.reject(res)
}
} else {
return Promise.reject(res)
}
// res.status === 200 ? Promise.resolve(res) : Promise.reject(res)
},
// 请求失败
error => {
const { response } = error
if (response) {
// 请求已发出,但是不在2xx的范围
errorHandle(response.data.code, response.data.message)
return Promise.reject(response)
} else {
// 处理断网的情况
// eg:请求超时或断网时,更新state的network状态
// network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
// 关于断网组件中的刷新重新获取数据,会在断网组件中说明
if (!window.navigator.onLine) {
store.commit('changeNetwork', false)
} else {
return Promise.reject(error)
}
}
})
export default instance
总结:
其实东西有很多,但是写出来的很少,前后端的交互花了我很多时间去研究,尤其是前端,毕竟只是随便糊弄了几下就上手做了,毕竟只是做个分离的前后端分离管理,很多东西也不需要特别严格,成长肯定是有的,但是怕自己过段时间不写又给忘了,所以写了一些出来,给大家看看,也给自己留个醒,到时候知道在哪找这些问题
网友评论