1. 统一用户登录权限验证
之前我们在做的登录校验:
- 在每个方法里面获取 session 和 session 中的用户信息,如果存在用户,那么就认为登录成功,否则就登陆失败了.
- 提供了统一的方法,在每个需要验证的方法中调用统一的用户登录身份校验方法来判断.
- 使用 Spring AOP 来使用统一的用户登录校验.
(没有办法得到 HttpSession 和 Request 对象,实际拦截规则很复杂,使用简单 aspectj 表达式无法满足拦截的需求).
Spring 拦截器
来实现用户的统一登录校验.
针对第 4 点步骤:
- 实现自定义拦截器,实现
Spring
中的HandlerInterceptor
接口中的preHandle(执⾏具体⽅法之前的预处理)
方法. - 将自定义拦截器加入到框架的配置中,并且设置拦截规则.
a) 给当前的类添加 @Configuration 注解
b) 实现WebMvcConfigurer 接口
c) 重写 addInterceptors 方法
1.1 自定义拦截
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@Component
public class LoginIntercept implements HandlerInterceptor {
/**
* 返回 true 表示拦截通过,可以访问后面的接口
* 返回 false 表示拦截未通过,直接返回结果给前端
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@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;
}
}
1.2 将自定义拦截器添加系统配置中,并设置拦截的规则
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Resource
private LoginIntercept loginIntercept;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(new LoginIntercept());
registry.addInterceptor(loginIntercept).addPathPatterns("/**") //拦截所有
.excludePathPatterns("/user1/login2") //不拦截登录接口
.excludePathPatterns("/user1/reg")
.excludePathPatterns("/login.html")
.excludePathPatterns("/reg.html")
.excludePathPatterns("/**/*.js")
.excludePathPatterns("/**/*.css")
.excludePathPatterns("/**/*.png")
.excludePathPatterns("/**/*.jpg");
}
}
addPathPatterns:
表示需要拦截的 URL,“**”
表示拦截任意⽅法(也就是所有⽅法)。
excludePathPatterns:
表示需要排除的 URL
1.3 测试
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@RestController
@RequestMapping("/user1")
public class UserController2 {
@RequestMapping("/login2")
public boolean login(HttpServletRequest request,String username, String password){
boolean result = false;
if(StringUtils.hasLength(username) && StringUtils.hasLength(password)){
if(username.equals("admin") && password.equals("admin")){
HttpSession session = request.getSession();
session.setAttribute("userinfo","userinfo");
return true;
}
}
return result;
}
@RequestMapping("/index")
public String index() {
return "Hello index";
}
}
响应码是200,但并没有返回结果.
我们在写两个前端页面验证一下:
测试:
我们登录没有拦截:
我们在进行登录是往往就是要进行校验,密码不对就要重新返回登录页面:
加上这一段:
我们输入正确密码:
登录成功后再访问其他页面:
当登录成功写⼊ session 之后,拦截的⻚⾯可正常访问
1.4 拦截器实现原理
正常情况下的调⽤顺序:
然⽽有了拦截器之后,会在调⽤ Controller 之前进⾏相应的业务处理,执⾏的流程如下图所示:
大体流程:
1.5 扩展: 统一访问前缀添加
所有请求地址添加 api 前缀:
其中第⼆个参数是⼀个表达式,设置为 true 表示启动前缀。
2. 统一异常处理
- 给当前的类加上
@ControllerAdvice/@RestControllerAdvice
- 给方法上添加
@ExceptionHandler(xxx.class)
,添加异常返回的业务代码
2.1 代码
编写测试代码:
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
//@ControllerAdvice
@RestControllerAdvice // 当前是针对 controller 的通知类(增强类)
public class MyExceptionAdvice {
@ExceptionHandler(ArithmeticException.class)
public HashMap<String, Object> arithmeticExceptionAdvice(ArithmeticException e) {
HashMap<String, Object> result = new HashMap<>();
result.put("state", -1);
result.put("data", null);
result.put("msg", "算术异常:" + e.getMessage());
return result;
}
@ExceptionHandler(NullPointerException.class)
public HashMap<String, Object> nullPointerExceptionAdvice(NullPointerException e) {
HashMap<String, Object> result = new HashMap<>();
result.put("state", -1);
result.put("data", null);
result.put("msg", "空指针异常:" + e.getMessage());
return result;
}
@ExceptionHandler(Exception.class)
public HashMap<String, Object> exceptionAdvice(Exception e) {
HashMap<String, Object> result = new HashMap<>();
result.put("state", -1);
result.put("data", null);
result.put("msg", "异常:" + e.getMessage());
return result;
}
}
@RequestMapping("/index")
public String index() {
int num = 10 / 0;
return "Hello,Index.";
}
@RequestMapping("/index2")
public String index2() {
Object obj = null;
System.out.println(obj.hashCode());
return "Hello,Index.";
}
@RequestMapping("/index3")
public String index3() {
String str = "java";
System.out.println(Integer.valueOf(str));
return "Hello,Index.";
}
2.2 测试
测试结果:
对前端就很友好了,他就不会报 500 的错误信息了,返回的是一个 200
3. 统一数据格式封装
- 给当前类添加
@ControllerAdvice
- 实现
ResponseBodyAdvice
重写其方法
3.1 代码
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.HashMap;
@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<String, Object> result = new HashMap<>();
result.put("state", 1);
result.put("data", body);
result.put("msg", "");
return result;
}
}
3.2 测试
写个注册:
这样统一的好处:
- ⽅便前端程序员更好的接收和解析后端数据接⼝返回的数据。
- 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就⾏了,因为所有接⼝都是这样返回的。
- 有利于项⽬统⼀数据的维护和修改。
- 有利于后端技术部⻔的统⼀规范的标准制定,不会出现稀奇古怪的返回内容
缺点就是全统一了,格式就固定了,就不能返回自己想要的了.(相当于就是个性的牺牲)