SpringBoot同一功能处理

news/2025/2/27 8:35:18

一、拦截器

1.1 什么是拦截器?

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

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

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

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

java">@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()指定不拦截哪些请求.

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

java">@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.

java">@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 注册配置拦截器

java">@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);//设置拦截器排除拦截的路径
    }
}
java">@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、登录的情况下再次进行图书列表的查看

java">@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.对后端返回数据进行封装,告知前端处理的结果

回顾
后端统⼀返回结果
java">@Data
public class Result<T> {
 private int status;
 private String errorMessage;
 private T data;
}
后端逻辑处理
java">@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 注解

java">@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类型时才有这种错误发生.
解决方案:
添加如下代码:
java">@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是异常处理器,两个结合表
示当出现异常的时候执行某个通知,也就是执行某个方法事件
具体代码如下:

java">@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())
java">public static Result fail(String msg) {
 Result result = new Result();
 result.setStatus(ResultStatus.FAIL);
 result.setErrorMessage(msg);
 result.setData("");
 return result;
}
我们可以针对不同的异常, 返回不同的结果
java">@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 异常");
    }
}
模拟制造异常:
java">@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.niftyadmin.cn/n/5869811.html

相关文章

java后端开发day21--面向对象进阶(二)--继承进阶

&#xff08;以下内容全部来自上述课程&#xff09; 1.继承 1.子类到底能继承父类中的哪些内容&#xff1f; 构造方法&#xff08;纯不能&#xff09; 非私有&#xff1a;不能 private&#xff1a;不能 比如&#xff1a;把构造变量看成自己&#xff0c;爹就是爹&#xff0c…

Oracle 12c Docker安装问题排查 sga_target 1536M is too small

一、问题描述 在虚拟机环境&#xff08;4核16GB内存&#xff09;上部署 truevoly/oracle-12c 容器镜像时&#xff0c;一切运行正常。然而&#xff0c;当在一台 128 核 CPU 和 512GB 内存的物理服务器上运行时&#xff0c;容器启动时出现了 ORA-00821 等错误&#xff0c;提示 S…

应用的负载均衡

概述 负载均衡&#xff08;Load Balancing&#xff09; 调度后方的多台机器&#xff0c;以统一的接口对外提供服务&#xff0c;承担此职责的技术组件被称为“负载均衡”。 负载均衡器将传入的请求分发到应用服务器和数据库等计算资源。负载均衡是计算机网络中一种用于优化资源利…

大模型最新面试题系列:深度学习基础(一)

1. 反向传播中梯度消失的本质原因是什么&#xff1f;如何量化梯度消失的程度&#xff1f; 本质原因 在神经网络的反向传播过程中&#xff0c;梯度是通过链式法则来计算的。假设一个简单的多层神经网络&#xff0c;每一层的输出 y i y_i yi​ 是由前一层的输出 x i x_i xi​…

Vite vs Webpack

1. Vite 比 Webpack 快在哪里&#xff1f; 开发模式的差异 Webpack&#xff1a;在开发环境中&#xff0c;Webpack 是先打包再启动开发服务器。这意味着所有的模块都需要在开发前进行打包&#xff0c;这会增加启动时间和构建时间。 Vite&#xff1a;Vite 则是直接启动开发服务器…

Java设计模式 —— 【行为型模式】中介者模式(Mediator Pattern)详解

文章目录 概述结构优缺点及适用场景案例实现 概述 中介者模式又叫调停模式&#xff0c;是一种行为模式&#xff0c;它定义一个中介角色来封装一系列对象之间的交互&#xff0c;使原有对象之间的耦合松散&#xff0c;且可以独立地改变它们之间的交互。 中介者模式是迪米特原则的…

angular使用IndexedDb实现增删改查sql

说明&#xff1a;我听说前端有一款数据库&#xff0c;叫IndexedDb数据库&#xff0c;可以存储超大的文件和数据&#xff0c;大约有250M&#xff0c;有了这个&#xff0c;就可以在浏览器里面&#xff0c;存储超大的数据&#xff0c; 事实上IndexedDb存储的数据&#xff0c;存在浏…

【热力图 Heatmap】——1

🌟 解锁数据可视化的魔法钥匙 —— pyecharts实战指南 🌟 在这个数据为王的时代,每一次点击、每一次交易、每一份报告背后都隐藏着无尽的故事与洞察。但你是否曾苦恼于如何将这些冰冷的数据转化为直观、吸引人的视觉盛宴? 🔥 欢迎来到《pyecharts图形绘制大师班》 �…