在我们写SpringBoot项目的时候,有些功能模块几乎每个项目都是一样,这些功能模块有:
1.统一用户登录权限验证;
2.统一数据格式返回;
3.统一异常处理;
在我们利用Spring AOP来做统一登录验证的时候会遇到两个问题:
1.没有办法获取到HTTPSession对象;
2.很难做到只对某一部分拦截,对某一部分不拦截;
对于上面的问题可以使用Spring拦截器来解决,Spring拦截器HandlerInterceptor的实现分为一下两个步骤:
1. 创建自定义拦截器(判断用户是否登陆):该拦截器必须实现HandlerInterceptor并重写preHandle(执行具体方法之前的预处理)方法。
/*
* 登录拦截器
* */
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
HttpSession session = request.getSession(false);
if(session!=null&&session.getAttribute("userinfo")!=null){
//说明已经登录
return true;
}
response.setStatus(401);
return false;
}
}
2.配置拦截器和拦截规则
/*
* 全局配置文件
* */
@Configuration
public class AppConfig implements WebMvcConfigurer {
//配置拦截器和拦截规则
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")//拦截所有请求
.excludePathPatterns("/**/*.html")//排除所有的html文件
.excludePathPatterns("/**/*.css")//排除所有的css文件
.excludePathPatterns("/**/*.js")//排除所有的js文件
.excludePathPatterns("/user/login");//排除登录接口
}
}
结果:
因为拦截了.jpg文件,随意页面中的图片加载不出来,下面是我排除.jpg文件的接口以后的效果:
下面看一个具体的用户登录权限验证的操作:
首先用户是通过输入网址到达某个页面,这个页面可能是系统内部的某个页面(不是登录页面),这个时候因为没有登陆,所以就不能访问:
一:创建两个前端页面
Document
index 页面
Document
登录页面
二:写一个拦截器,拦截index页面,不拦截login页面
public class LoginIntercept implements HandlerInterceptor {
/*
* 返回true表示拦截判断通过,可以访问后面的接口
* 如果返回false表示拦截未通过,直接返回结果给前端
* */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.得到HttpSession对象
HttpSession session = request.getSession(false);
if(session!=null&&session.getAttribute("userinfo")!=null){
//表示已经登录
return true;
}
//执行到此行代码表示未登录,未登录就跳转到登录页面
return false;
}
}
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginIntercept())
.addPathPatterns("/**")//拦截所有的url
.excludePathPatterns("/user/login")//不拦截登录接口
.excludePathPatterns("/user/reg")//不拦截注册接口
.excludePathPatterns("/login.html")//不拦截登录页面
.excludePathPatterns("/reg.html")//不拦截注册页面
.excludePathPatterns("/**/*.js")//不拦截所有的js信息
.excludePathPatterns("/**/*.css")//不拦截所有的css信息
.excludePathPatterns("/**/*.jpg");//不拦截所有的图片信息
}
}
三:验证
四:让未登录的用户跳转到登录页面
输入http://localhost:8080/index.html以后因为没有登陆,它会自动的跳转到登录页面:
五:在url中输入session信息(输入以后相当于就登录了)
没有输入session信息之前的状态(访问):
我在浏览器输入框中输入localhost:8080/user/index,如果是登录状态,它就会显示Hello,index,但是现在我是未登录状态(也就是浏览器中没有我的登录信息——session) ,因为之前重定向到了登录页面,所以它会让我先登录:
在这个页面中,会让我输入登录信息,验证通过后浏览器就会自动的保存我的session,然后我就可以访问其他页面了 ,下面是我没有输入正确session的状态:
下面是我输入正确session之后的状态:
然后我在访问其他页面:
当我的登录信息正确以后,拦截器就不拦了,上面整个过程就是用户统一登录的验证。
之前我们没有设置拦截器的时候,用户请求数据的流程是下面这样的:
添加拦截器以后,用户请求数据的流程:
也就是说在我们请求数据的时候,所有的请求否要通过拦截器这个关卡,如果拦截器返回false,请求就不能通过,返回true才能访问到要访问的数据。
在我们使用拦截器拦截目标文件的时候,可以在相应的url地址前面加上前缀:
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
//在所有的请求地址前面加上api前缀
configurer.addPathPrefix("api",c->true);
}
代码里面的c表示所有的controller
例如:在所有的请求地址前面加上api前缀
没加之前:
加了之后:
在我们写程序的时候,难免会遇到异常,这个异常可能是我们自己导致的也有可能是用户提交的数据导致的,虽然异常还可以try-catch一下处理一下,但是异常多了以后不可能每一个地方都去处理,而且在Spring事务中try-catch会导致事务不能回滚。为了解决这一普遍现象造成的问题,这里就可以Spring里面用统一异常处理机制来处理。
统一异常处理机制:
1.给异常处理类加上@controllerAdervice注解或者@RestControllerAdervice注解,@controllerAdervice注解这个注解表示控制器通知类,而@RestControllerAdervice是一个组合注解,它相当于@controllerAdervice+@ResponseBody,使用了这个注解以后,可以不再类上面加@ResponseBody注解。
@RestControllerAdvice//当前针对controller的通知类
public class MyExceptionAdervice {
}
2.在方法上面加上@ExceptionHandler(XXX.class),添加异常返回的业务逻辑。
在controller里面写一个错误逻辑:
执行结果:
返回统一数据格式的好处:
返回统一数据格式的实现也是分为两步:
1.在返回统一数据格式的类上面加上@ControllerAdvice注解;
2.让这个类实现ResponseBodyAdvice接口,并重写它里面的两个方法
@ControllerAdvice
public class MyResponseAdvice implements ResponseBodyAdvice {
/*
* 返回一个boolean值,true表示返回数据之前对数据进行重写,也就是会进入beforeBodyWrite方法,再返回
* 如果返回false表示对结果不进行任何处理,直接返回
* */
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
HashMap result = new HashMap<>();
result.put("state",1);
result.put("data",body);
result.put("msg","");
return result;
}
}
结果: