文章目录
- 前言
- 一.用户登录权限校验
- 1.1 spring 拦截器
- 1.2 传统的用户登录权限验证
- 1.3 使用拦截器的方式
- 1.4 案例
- 1.5 拦截器实现原理
- 三.统一异常处理
- 3.1 什么是统一异常处理
- 3.2 具体步骤
- 四.统⼀数据返回格式
- 4.1 为什么需要统一的数据返回
- 4.2 统一返回数据的格式
- 4.3 统一移除处理在遇到 String 返回返回时报错的问题
前言
前面一篇文章,我们具体学习了,springAOP相关概念,我们再具体的回忆一下,什么是springAOP其实就是统一功能的实现.下面我们将利用AOP的思想,去实现三个功能.
一.用户登录权限校验
⽤户登录权限的发展从之前每个⽅法中⾃⼰验证⽤户登录权限,到现在统⼀的⽤户登录验证处理,它是⼀个逐渐完善和逐渐优化的过程。
我们不使用AOP的思想,我们之前做登录验证校验的过程是:
1.1 spring 拦截器
在介绍用户登录的权限验证之前,我们需要介绍一下,spring的拦截器的实现步骤.
对于以上问题Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步骤∶
- 创建自定义拦截器,实现 HandlerInterceptor 接口的preHandle (执行具体方法之前的预处理)方法。
- 将自定义拦截器加入 WebMvcConfigurer的 addInterceptors方法中。
具体实现如下。
我下面给出一个简单的demo
1.创建自定义拦截器,实现HandlerInterceptor接口
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle方法被调用");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle方法被调用");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion方法被调用");
}
}
2.将自定义拦截器加入 WebMvcConfigurer的 addInterceptors方法中。
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Autowired
private MyInterceptor myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor); // 将拦截器注册到 Spring 容器中
}
}
1.2 传统的用户登录权限验证
具体代码入下:
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 某⽅法 1
*/
@RequestMapping("/m1")
public Object method(HttpServletRequest request) {
// 有 session 就获取,没有不会创建
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
// 说明已经登录,业务处理
return true;
} else {
// 未登录
return false;
}
}
@RequestMapping("/m2")
public Object method2(HttpServletRequest request) {
// 有 session 就获取,没有不会创建
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
// 说明已经登录,业务处理
return true;
} else {
// 未登录
return false;
}
}
// 其他⽅法...
}
从上述代码可以看出,每个⽅法中都有相同的⽤户登录验证权限,它的缺点是:
- 每个⽅法中都要单独写⽤户登录验证的⽅法,即使封装成公共⽅法,也⼀样要传参调⽤和在⽅法中
进⾏判断。 - 添加控制器越多,调⽤⽤户登录验证的⽅法也越多,这样就增加了后期的修改成本和维护成本。
- 这些⽤户登录验证的⽅法和接下来要实现的业务⼏何没有任何关联,但每个⽅法中都要写⼀遍。
所以提供⼀个公共的 AOP ⽅法来进⾏统⼀的⽤户登录权限验证迫在眉睫。
1.3 使用拦截器的方式
具体步骤如下:
- 创建自定义拦截器,实现 HandlerInterceptor 接口的preHandle (执行具体方法之前的预处理)方法。
/*
拦截器的自定义实现
*/
@Component
public class LoginInterceptor implements HandlerInterceptor {
//调用目标方法之前执行的方法
//此方法返回boolean 类型的值,如果返回true 表示拦截成功,继续执行目标方法
//如果返回false,表示拦截执行失败,检验未通过,目标方法不执行.
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//用户登录判断业务
HttpSession session=request.getSession(false);
if (session !=null && session.getAttribute("seesion_userinfo") !=null){
return true;
}
response.setContentType("application/json;charset=utf8");
response.getWriter().println("{\"code\":-1,\"msg\":\"登录失败\",\"data\":\"\"}");
return false;
}
}
- 将自定义拦截器加入 WebMvcConfigurer的 addInterceptors方法中。
@Configuration
public class MyConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/user/login")
}
}
1.4 案例
知道了具体的拦截器练习之后,我们接下来会进行一个案例的练习
登录拦截器
具体的效果如下
1.登录、注册页面不拦截,其他页面都拦截。
2.当登录成功写入 session 之后,拦截的页面可正常访问
- 先准备一个前端登录页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="/user/login" method="get">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required><br>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required><br>
<button type="submit">Login</button>
</form>
</body>
</html>
- 准备UserMapper.xml文件和接口类
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<select id="login" resultType="com.example.demo.entity.UserInfo">
select * from userinfo where username=#{username}
</select>
</mapper>
@Mapper
public interface UserMapper {
UserInfo login(@Param("username") String username);
}
- 准备Service层代码
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public UserInfo login(String username){
return userMapper.login(username);
}
}
- 编写Controller层代码
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/index")
public String index(){
return "index";
}
@RequestMapping("/reg")
public String reg(){
return "login";
}
@RequestMapping("/login")
public String login(String username, String password , HttpServletRequest request) {
//1.进行非空校验
if ( !StringUtils.hasLength(password) ||
!StringUtils.hasLength(username)){
return "参数有误";
}
UserInfo userInfo=userService.login(username);
if (userInfo == null || userInfo.getId() <= 0)
return "用户名或密码输入错误!";
if (!password.equals(userInfo.getPassword()))
return "用户名或密码输入错误!";
HttpSession session=request.getSession();
session.setAttribute("userinfo",userInfo);
return "{\"code\":\"200\",\"message\":\"登录成功\"}";
}
}
- 编写实体类
@Data
public class UserInfo {
private int id;
private String username;
private String password;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private int state;
}
最后启动springboot项目就可以了
1.5 拦截器实现原理
我们先来看程序正常的执行流程.
这边是有了拦截器之后的流程
看完了上面的具体流程以后,我们来具体的说明一下流程.
在SpringMVC中,拦截器可以通过实现HandlerInterceptor接口来实现。当请求到达时,SpringMVC会先通过HandlerMapping找到对应的Controller,然后再通过HandlerAdapter找到对应的处理方法。在这个过程中,如果有拦截器实现了HandlerInterceptor接口并重写了preHandle()方法,那么这个方法会在Controller和处理方法之前被调用。
三.统一异常处理
3.1 什么是统一异常处理
统⼀异常处理使⽤的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表
示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执⾏某个通知,也就是执⾏某个⽅法事件
3.2 具体步骤
1.创建一个异常处理类
@ControllerAdvice
public class MyExceptionAdvice {
}
2.创建异常检测的类和处理业务方法
@ExceptionHandler(NullPointerException.class)
public HashMap<String,Object> NullPointerException(NullPointerException e){
HashMap<String, Object> result=new HashMap<>();
result.put("code",-1);
result.put("msg","空指针"+e.getMessage());
result.put("data",null);
return result;
}
具体的发送请求
@RequestMapping("/login2")
public int login2(){
int num=10 / 0;
return 2;
}
显示结果:
一旦项目出现了空指针异常之后,就会统一异常处理.
四.统⼀数据返回格式
4.1 为什么需要统一的数据返回
统一数据返回格式的优点有很多,比如以下几个:
1.方便前端程序员更好的接收和解析后端数据接口返回的数据
2.降低前端程序员和后端程序员的沟通成本,按照某个格式实现就行了,因为所有接口都是这样返回的。
3.有利于项目统一数据的维护和修改。
4.有利于后端技术部门的统一规范的标准制定,不会出现稀奇古怪的返回内容
4.2 统一返回数据的格式
统一的数据返回格式可以使用 @ControllerAdvice + ResponseBodyAdvice 的方式实现,具体的代码如下:
/**
* 统一的格式处理
*/
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
private ObjectMapper objectMapper;
/**
* 是否执行beforeBody
* @param returnType
* @param converterType
* @return
*/
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
/**
*
* @param body 原始返回值
* @param returnType
* @param selectedContentType
* @param selectedConverterType
* @param request
* @param response
* @return
*/
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//返回的标准 HashMap<String,Object> ->code,msg,data
if (body instanceof HashMap) {
return body;
}
//重写返回结果,让其中返回一个统一的数据格式
HashMap<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("data", body);
result.put("msg", "");
return result;
}
}
4.3 统一移除处理在遇到 String 返回返回时报错的问题
为什么会出现这样的错误呢?
现看具体的流程
1.方法返回的是 String
2.统一数据返回之前处理 -> String ConvertHashMap
3.将 HashMap 转换成 application/json宁符串给前端(接口)
具体的错误是出在第三步的,
因为走到第三步之后
就会判断原 Body 的类型 ->
1.是 String ->StringllttpMessageConverter 进行类型转换
2.非 StringHttpMessageConverter 进行类型转换.
所以我们就可以给出具体的解决方案
1.将 StringHttpMessageConverter 去掉
2.在统一数据重写时,单独处理 String 类型,让其返回一个 String 宁符串,而非 HashMap