当前位置: 首页 > article >正文

SpringBoot同一功能处理

一、拦截器

1.1 什么是拦截器?

拦截器是Spring框架提供的核心功能之一,主要用来拦截用户的请求,在指定方法前后,根据业务需要执行预先设定的代码.

也就是说,允许开发人员提前预定义一些逻辑,在用户的请求响应前后执行.也可以在用户请求前阻止其执行.在拦截器当中,开发人员可以在应用程序中做一些通用性的操作,比如通过拦截器来拦截前端发来的请求,判断Session中是否有登录用户的信息.如果有就可以放行,如果没有就进行拦截.

拦截器的使用分为两步:
1.定义拦截器
2.   注册配置拦截器

自定义拦截器:实现HandlerInterceptor接口,并重写其所有方法

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        log.info("LoginInterceptor目标方法执行前执行..");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler,
                           ModelAndView modelAndView) throws Exception {
        log.info("LoginInterceptor 目标方法执行后执行");
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) throws Exception {
        log.info("LoginInterceptor视图渲染完毕后执行,最后执行");
    }
}

启动服务,访问登录请求,观察后端日志 

可以看到preHandle方法执行之后就放行了,开始执行目标方法,目标方法执行完成之后执行
postHandle和afterCompletion方法.

将拦截器中preHandle方法的返回值改为false,再观察运行结果

运行程序 观察日志,拦截器拦截了请求,没有进行响应. 

1.2 拦截路径

我们在注册配置拦截器的时候,通过addPathPatterns()方法指定要拦截哪些请求.也可以通过
excludePathPatterns()指定不拦截哪些请求.

如用户登录校验,我们希望可以对除了登录之外所有的路径生效.

@Configuration
public class WebConfig implements WebMvcConfigurer {


    @Autowired
    // 自定义拦截器对象
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册自定义拦截器对象
        registry.addInterceptor(loginInterceptor)
                //拦截所有请求   (/** 拦截所有请求)
                .addPathPatterns("/**")
                .excludePathPatterns("/user/login"); //(/user/login不拦截)
    }
}

在拦截器中除了可以设置/**拦截所有资源外,还有一些常见拦截路径设置:

拦截路径含义举例
/*一级路径能匹配/user,/book,/login,不能匹配/user/login
/**任意级路径能匹配/user,/user/login,/user/reg
/book/*/book下的一级路径能匹配/book/addBook,不能匹配/book/addBook/1,book
/book/**/book下的任意级路径能配/book,/book/addBook,/book/addBook/2,不能匹配/user/login

1.3 拦截器执行流程 

普通调用顺序:

 添加拦截器后的调用顺序:

1添加拦截器后,执行Controller的方法之前请求会先被拦截器拦截住.执行preHandle()方法
这个方法需要返回一个布尔类型的值.如果返回true,就表示放行本次操作,继续访问controller中的方法.如果返回false,则不会放行(controller中的方法也不会执行).


2.controller当中的方法执行完毕后再回过来执行postHandle()和afterCompletion()方法执行完毕之后,最终给浏览器响应数据.

1.4 定义拦截器

从session中获取用户信息,如果session中不存在,则返回false,并设置http状态码为401,否则返回true.

@Slf4j
@Component
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(Constants.SESSION_USER_KEY) != null) {
            return true;
        }
        response.setStatus(401);
        return false;
    }
}

http状态码401:Unauthorized 未经过认证.指示⾝份验证是必需的,没有提供⾝份验证或⾝份验证失败.如果请求已经包 含授权凭据,那么401状态码表示不接受这些凭据。

1.5 注册配置拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {


    //⾃定义的拦截器对象
    @Autowired
    private LoginInterceptor loginInterceptor;
    private List<String> excludePaths = Arrays.asList(
            "/user/login",
            "/**/*.js",
            "/**/*.css",
            "/**/*.png",
            "/**/*.html"
    );

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册⾃定义拦截器对象

        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")// 设置拦截器拦截的请求路径 (/**表⽰拦截所有请求) 
                .excludePathPatterns(excludePaths);//设置拦截器排除拦截的路径
    }
}
@Slf4j
@RequestMapping("/book")
@RestController
public class BookController {

    @Autowired
    private BookService bookService;

    @RequestMapping("/getBookListByPage")
    public Result getBookListByPage(PageRequest pageRequest, HttpSession session){
        log.info("查询翻页信息, pageRequest:{}",pageRequest);
        //校验成功
        if (pageRequest.getPageSize()<0 || pageRequest.getCurrentPage()<1){
            return Result.fail("参数校验失败");
        }
        PageResult<BookInfo> bookInfoPageResult = null;
        try {
            bookInfoPageResult = bookService.selectBookInfoByPage(pageRequest);
            return Result.success(bookInfoPageResult);
        }catch (Exception e){
            log.error("查询翻页信息错误,e:{}",e);
            return Result.fail(e.getMessage());
        }
    }
}

运行程序,通过Postman进行测试

1、未登录的情况下进行图书列表的查看

http://127.0.0.1:8080/book/getListByPage

2、登录的情况下再次进行图书列表的查看

@Slf4j
@RequestMapping("/book")
@RestController
public class BookController {

    @Autowired
    private BookService bookService;

    @RequestMapping("/getBookListByPage")
    public Result getBookListByPage(PageRequest pageRequest, HttpSession session){
        log.info("查询翻页信息, pageRequest:{}",pageRequest);
        //用户登录校验
        UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);
        if (userInfo==null|| userInfo.getId()<=0 || "".equals(userInfo.getUserName())){
            //用户未登录
            return Result.unlogin();
        }
        //校验成功
        if (pageRequest.getPageSize()<0 || pageRequest.getCurrentPage()<1){
            return Result.fail("参数校验失败");
        }
        PageResult<BookInfo> bookInfoPageResult = null;
        try {
            bookInfoPageResult = bookService.selectBookInfoByPage(pageRequest);
            return Result.success(bookInfoPageResult);
        }catch (Exception e){
            log.error("查询翻页信息错误,e:{}",e);
            return Result.fail(e.getMessage());
        }
    }
}

启动程序,在Postman中进行登录

​​​​​​http://127.0.0.1:8080/user/login?name=admin&password=admin

Postman返回正确的数据

 二、统一数据返回格式

强制登录案例中,我们共做了两部分工作
1.通过Session来判断用户是否登录
2.对后端返回数据进行封装,告知前端处理的结果

回顾
后端统⼀返回结果
@Data
public class Result<T> {
 private int status;
 private String errorMessage;
 private T data;
}
后端逻辑处理
@RequestMapping("/getListByPage")
public Result getListByPage(PageRequest pageRequest) {
 log.info("获取图书列表, pageRequest:{}", pageRequest);
 //⽤⼾登录, 返回图书列表
 PageResult<BookInfo> pageResult = 
bookService.getBookListByPage(pageRequest);
 log.info("获取图书列表222, pageRequest:{}", pageResult);
 return Result.success(pageResult);
}
Result.success(pageResult) 就是对返回数据进行了封装

2.1 快速入门

统一的数据返回格式使用@ControllerAdviceResponseBodyAdvice的方式实现
@ControllerAdvice表示控制器通知类
添加类 ResponseAdvice,实现 ResponseBodyAdvice接口,并在类上添加
@ControllerAdvice 注解

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
 @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) {
 return Result.success(body);
 }
}

supports方法:判断是否要执行beforeBodyWrite方法.true为执行,false不执行.通过该方法可以
选择哪些类或哪些方法的response要进行处理,其他的不进行处理.

beforeBodyWrite方法: 对response⽅法进⾏具体操作处理
测试
测试接⼝: http://127.0.0.1:8080/book/queryBookById?bookId=1
添加统⼀数据返回格式之前:
添加统⼀数据返回格式之后:
我们继续测试修改图书的接⼝: http://127.0.0.1:8080/book/updateBook
结果显⽰, 发⽣内部错误
查看数据库, 发现数据操作成功
查看日志
最终发现 只有返回结果为String类型时才有这种错误发生.
解决方案:
添加如下代码:
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    private ObjectMapper objectMapper;
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof Result<?>){
            return body;
        }
        if (body instanceof String){
            return objectMapper.writeValueAsString(Result.success(body));
        }
        return Result.success(body);
    }
}

再次运行返回正常结果:

2.2  使用统一数据返回格式的优点

1. 方便前端程序员更好的接收和解析后端数据接口返回的数据
2. 降低前端程序员和后端程序员的沟通成本, 按照某个格式实现就可以了, 因为所有接⼝都是这样        返回的.
3. 有利于项⽬统⼀数据的维护和修改.
4. 有利于后端技术部⻔的统⼀规范的标准制定, 不会出现稀奇古怪的返回内容.

 、统一异常处理

统一异常处理使用的是@ControllerAdvice+@ExceptionHandler来实现的
@ControllerAdvice表示控制器通知类,@ExceptionHandler是异常处理器,两个结合表
示当出现异常的时候执行某个通知,也就是执行某个方法事件
具体代码如下:

@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
@ExceptionHandler
public Object handler(Exception e) {
return Result.fail(e.getMessage());
  }
}
类名,方法名和返回值可以自定义,重要的是注解
接口返回为数据时,需要加@ResponseBody注解
以上代码表示,如果代码出现Exception异常(包括Exception的子类),就返回一个Result的对象,Result对象的设置参考Result.fail(e.getMessage())
public static Result fail(String msg) {
 Result result = new Result();
 result.setStatus(ResultStatus.FAIL);
 result.setErrorMessage(msg);
 result.setData("");
 return result;
}
我们可以针对不同的异常, 返回不同的结果
@Slf4j
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {
    @ExceptionHandler
    public Object handler(Exception e){
        log.error("发生异常, e: ", e);
        return Result.fail();
    }

//    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler
    public Object handler(ArrayIndexOutOfBoundsException e){
        log.error("发生异常, e:{} ", e.getMessage());
        return Result.fail("发生数组越界异常");
    }
    @ExceptionHandler
    public Object handler(NullPointerException e){
        log.error("发生异常, e: ", e);
        return Result.fail("发生NullPointerException 异常");
    }

    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler
    public Object handler(NoResourceFoundException e){
        log.error("发生异常, e: {}, path:{}", e.getDetailMessageCode(), e.getResourcePath());
        return Result.fail("发生NoResourceFoundException 异常");
    }
}
模拟制造异常:
@Slf4j
@RequestMapping("/test")
@RestController
public class TestController {

    @RequestMapping("/t1")
    public String t1(){
        int a = 10/0;
        log.info("执行T1方法");
        return "t1";
    }

    @RequestMapping("/t2")
    public int t2(){
        String aa = "abcdef";
        log.info("执行T2方法");
        return aa.length();
    }

    @RequestMapping("/t3")
    public int t3(){
        int[] a = {1,2,3};
        int val = a[4];
        log.info("执行T3方法");
        return val;
    }

}

访问:http://127.0.0.1:8080/test/t1

异常

访问:http://127.0.0.1:8080/test/t2

正常返回

访问:http://127.0.0.1:8080/test/t3

异常


http://www.kler.cn/a/567994.html

相关文章:

  • 【Web 大语言模型攻击简介】
  • Java Web应用中获取客户端的真实IP地址
  • MATLAB实现四阶龙格库塔法求解常微分方程组
  • 软件工程----内聚与耦合
  • MySQL 中如何解决深度分页的问题?什么是 MySQL 的主从同步机制?它是如何实现的?如何处理 MySQL 的主从同步延迟?
  • 大白话前端性能优化方法的分类与具体实现
  • 【Linux网络编程】高效I/O--select/poll服务器
  • Android AsyncLayoutInflater异步加载xml布局文件,Kotlin
  • 支持向量机(Support Vector Machine,SVM)详细解释(带示例)
  • 关于手眼标定的数学模型及标定流程
  • Seaborn知识总结
  • 【基于PHP的CMS动态网站的渗透测试流程】
  • android studio gradle 阿里镜像
  • Spark内存迭代计算
  • 电脑软件:推荐一款非常实用的PDF合并分割工具PDFsam
  • 第十三届蓝桥杯大赛软件赛决赛C/C++ 大学 B 组
  • PySpark实现Snowflake数据导出到Amazon Redshift
  • Vue进阶之Vue2源码解析
  • python 使用 venv 创建虚拟环境 (VSCode)
  • 网络运维学习笔记(DeepSeek优化版)009网工初级(HCIA-Datacom与CCNA-EI)路由理论基础与静态路由