【Spring Boot】Spring Boot 统一功能处理

Spring拦截器

为何需要Spring拦截器?

在之前Servlet开发中,对登陆校验有两种方法:

  1. 在每个方法中都获取到session中的用户信息,然后判断用户是否存在,如果用户存在则登陆,如果用户不存在则未登陆
  2. 提供统一的方法验证用户是否登陆,在需要验证用户登陆的地方调用该方法进行判断

从上述可以看出这样做比较复杂,因为如果有许多地方需要进行登陆验证的话,就需要写重复的校验代码或者重复调用判断登陆的方法

对此我们提供Spring拦截器来实现用户的统一登陆验证,拦截器的实现分为以下两个步骤:

  1. 创建自定义拦截器类,实现HandlerInterceptor接口并重写preHandle方法
  2. 创建配置类,将自定义的拦截器添加到该配置类中,并添加拦截规则
    • 给配置类添加@Configuration注解
    • 实现WebMvcConfigurer接口
    • 重写addInterceptors方法

注意:一个项目中可以配置多个拦截器

自定义拦截器

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        if(session != null && session.getAttribute("user") != null){
            return true;
        }
        return false;
    }
}

preHandle方法的返回值为boolean类型:

  • 如果返回值为true,表示通过拦截,可以访问后续接口
  • 如果返回值为false,表示拦截未通过,直接返回结果给前端

如果返回为false,前端展示的为空白,对于登陆校验,当拦截未通过时,我们希望返回到登陆界面,所以可以在返回false之前重定向到登陆页面

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        if(session != null && session.getAttribute("user") != null){
            return true;
        }
        //当代码进行到这一步时,说明拦截未通过,防止前端显示空白,重定向到登陆页面
        response.sendRedirect("/login.html");
        return false;
    }
}

加入系统配置并配置拦截规则

@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).
                addPathPatterns("/**"). //拦截所有url
                excludePathPatterns("/user/login"). //排除登陆接口
                excludePathPatterns("/user/register"). //排除注册接口
                //排除所有的静态页面
                excludePathPatterns("/login.html").
                excludePathPatterns("/register.html").
                excludePathPatterns("/**/*.js").
                excludePathPatterns("/**/*.css").
                excludePathPatterns("/**/*.jpg");
    }
}

说明:addPathPatterns表示需要拦截的URL,excludePathPatterns表示需要排除的URL

验证登陆拦截器

  1. 先创建login.html和content.html页面,然后创建后端接口
    @RequestMapping("/login")
    public String login(String username, String password, HttpServletRequest req){
        if(username==null || password==null){
            return "请输入用户名和密码";
        }
        if(username.equals("abc") && password.equals("123")){
            HttpSession session = req.getSession(true);
            session.setAttribute("user","user");
            return "登陆成功";
        }
        //登录失败也要返回前端失败的数据,否则前端会展示空白
        response.setContentType("application/json; charset=utf8");
        Map<String,Object> result = new HashMap<>();
        result.put("code",-1);
        result.put("msg","未登录不允许访问");
        result.put("data",null);
        response.getWriter().write(objectMapper.writeValueAsString(result));
        return "登陆失败";
    }

    @RequestMapping("/content")
    public String content(){
        return "已经登陆成功";
    }
  1. 创建拦截器(与上述相同,去掉重定向的那行代码),配置拦截规则:我们排除对登陆页面和登陆接口的拦截
@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).
                addPathPatterns("/**"). //拦截所有url
                excludePathPatterns("/user/login"). //排除登陆接口
                excludePathPatterns("/login.html");
    }
}
  1. 启动程序访问登陆页面,登陆页面正常访问
    在这里插入图片描述
  2. 访问登陆接口,正常访问,此时未登陆
    在这里插入图片描述
  3. 在未登录的情况下访问content.html,可以看到显示空白说明拦截器拦截成功
    在这里插入图片描述
  4. 在未登录的情况下访问content接口,可以看到显示空白,说明拦截成功
    在这里插入图片描述
  5. 我们进行登陆后再访问content页面和content接口
    在这里插入图片描述
  6. 登陆成功后,访问content页面成功
    在这里插入图片描述
  7. 登陆成功后,访问content接口成功
    在这里插入图片描述

经过上述步骤,我们的登陆拦截器验证成功,满足我们需求

添加统一访问前缀

给所有的请求url添加前缀

  1. 添加方式一:在配置拦截规则的配置类中重写configurePathMatch方法
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        //c -> true意思是给所有的controller都添加api前缀
        configurer.addPathPrefix("api",c -> true);
    }
  1. 添加方式二:在application配置文件中添加,该方式是给全局添加访问前缀,包括访问html静态文件
server:
  servlet:
    context-path: /api

验证统一前缀是否添加成功

启动程序,用之前的url访问登陆接口,发现找不到资源

在这里插入图片描述

添加api前缀访问,访问成功

在这里插入图片描述

注意:如果添加了拦截规则,也必须给拦截规则添加前缀,否则会被拦截器拦截到,前端将显示空白

 excludePathPatterns("/api/user/login")

统一异常处理

统一异常的步骤:

  1. 创建一个类,添加@ControllerAdvice注解
  2. 创建一个方法,方法上添加@ExceptionHandler注解

@ControllerAdvice表示当前类是针对controller的通知类(增强类),@ExceptionHandler是异常处理器,两个结合表示当出现异常的时候执行某个通知

//返回json格式数据,将@Controller与@ResponseBody注解合成一个注解@RestController
@RestControllerAdvice
public class MyException {

    @ExceptionHandler(Exception.class)
    public Object handler(Exception e){
        Map<String,Object> map = new HashMap<>();
        map.put("state",-1);
        map.put("data",null);
        map.put("msg",e.getMessage());
        return map;
    }
}

对异常进行统一处理后,当服务器出现500错误时,会给前端返回自定义的错误信息,不会再直接返回给前端500状态码了

在这里插入图片描述

统一数据返回格式

统一数据返回格式步骤:

  1. 创建一个类,添加@ControllerAdvice注解,实现ResponseBodyAdvice接口
  2. 重写supports和beforeBodyWrite方法
@RestControllerAdvice
public class MyResponse implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        //返回true表示返回数据之前对数据进行重写,也就是会进入beforeBodyWrite方法再返回
        //返回false表示对返回数据不进行任何处理,不会进入beforeBodyWrite方法,直接返回
        return true;
    }

    @Override //controller返回结果之前,进行格式重写
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    	//假设标准返回格式为hashmap
    	if(body instanceof HashMap){
    		return body; //如果格式正确,直接返回
    	}
    	//如果格式不正确,进行格式重写
        Map<String,Object> result = new HashMap<>();
        result.put("state",1);
        result.put("data",body);
        result.put("message","");
        return request;
    }
}

上述是保底策略,也就是当程序返回的格式不正确时,才会进行数据格式重写,我们在写代码的时候,往往都是返回自定义数据格式

如果程序在controller中返回的类型为String,此时,程序会出现类型转换异常

在这里插入图片描述

所以我们可以在重写格式里,对String类单独判断一下

if(body instanceof String){
    return objectMapper.writeValueAsString(body);
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

X_H学Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值