16. Spring Boot 统一功能处理

news2024/12/27 13:33:46

目录

1. 用户登录权限校验

1.1 最初用户登录验证

1.2 Spring AOP 用户统一登陆验证

1.3 Spring 拦截器

1.3.1 创建自定义拦截器

1.3.2 将自定义拦截器加入系统配置

1.4 练习:登录拦截器 

1.5 拦截器实现原理

1.6 统一访问前缀添加

2. 统一异常处理 

3. 统一数据返回格式

3.1 为什么需要统⼀数据返回格式?

3.2 统一数据返回格式的实现 


学习了 Spring 的框架后,接下来,我们来学习 Spring Boot 的统一功能处理模块,也是 AOP 的实战环节。我们主要学习以下三个部分:

  1. 统一用户登录验证
  2. 统一数据格式返回
  3. 统一异常处理

接下来,我们逐一来看。

1. 用户登录权限校验

用户登录权限的发展从之前每个方法中自己验证用户登录权限,到现在统⼀的用户登录验证处理,它是⼀个逐渐完善和逐渐优化的过程。

1.1 最初用户登录验证

我们先来看一下最初的用户登录验证是如何实现的。

@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
    @RequestMapping("method1")
    public Object method1(HttpServletRequest request){
        // 有session就获取,没有session不会创建
        HttpSession session = request.getSession(false);
        if(session != null && session.getAttribute("userinfo") != null){
            // 说明已经登录,业务处理
            return true;
        }else {
            // 未登录
            return false;
        }
    }
    @RequestMapping("method2")
    public Object method2(HttpServletRequest request){
        // 有session就获取,没有session不会创建
        HttpSession session = request.getSession(false);
        if(session != null && session.getAttribute("userinfo") != null){
            // 说明已经登录,业务处理
            return true;
        }else {
            // 未登录
            return false;
        }
    }
   // 其他方法
}

运行结果如下: 

从上述代码可以看出,每个方法中都有相同的用户登录验证权限,它的缺点是:
  1. 每个方法中都要单独写用户登录验证的方法,即使封装成公共方法,也⼀样要传参调用和在方法中进行判断。
  2. 添加控制器越多,调用用户登录验证的方法也越多,这样就增加了后期的修改成本和维护成本。
  3. 这些用户登录验证的方法和接下来要实现的业务几乎没有任何关联,但每个方法中都要写⼀遍。
因此,我们需要提供⼀个公共的 AOP 方法来进行统⼀的用户登录权限验证。

1.2 Spring AOP 用户统一登陆验证

首先,我们可以通过 Spring AOP 前置通知或环绕通知来实现

@Slf4j
@Component
@Aspect
public class LoginAspect {
    @Pointcut("execution(* com.example.demo.controller.UserController.* (..))")
    public void pointcut(){}

    @Before("pointcut()")
    public void doBefore(){
        log.info("do berore...");
    }
  
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint){
        Object oj = null;
        log.info("环绕通知执行之前...");
        try {
            oj = joinPoint.proceed(); // 调用目标方法
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        log.info("环绕通知执行之后...");
        return oj;
    }
如果要在以上 Spring AOP 的切面中实现用户登录权限效验的功能,有以下两个问题:
  1. 没办法获取到 HttpSession 对象
  2. 要对⼀部分方法进行拦截,而另⼀部分方法不拦截,如注册方法和登录方法是不拦截的,这样的话排除方法的规则很难定义,甚至没办法定义。

1.3 Spring 拦截器

对于以上问题 Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步骤:
  1. 创建自定义拦截器,实现 HandlerInterceptor 接口的 preHandle(执行具体方法之前的预处理)方法。
  2. 将自定义拦截器加入 WebMvcConfigurer 的 addInterceptors 方法中。

1.3.1 创建自定义拦截器

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 判断是否登录
        HttpSession session = request.getSession(false);
        if(session != null && session.getAttribute("userinfo") != null){
            // 通过,不进行拦截
            return true;
        }
        response.setStatus(401);
        return false;
    }
}

1.3.2 将自定义拦截器加入系统配置

@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    // 添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")// 拦截所有接口
                .excludePathPatterns("/login")
                .excludePathPatterns("/reg");// 排除接口
    }
}

其中: 

  • addPathPatterns:表示需要拦截的 URL,“**”表示拦截任意方法(也就是所有方法)
  • excludePathPatterns:表示需要排除的 URL

1.4 练习:登录拦截器 

  1.   登录、注册页面不拦截,其他页面都拦截。
  2.  当登录成功写入 session 之后,拦截的页面可正常访问。
/**
 * 自定义拦截器
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 判断是否登录
        HttpSession session = request.getSession(false);// 不存在时不创建
        if(session != null && session.getAttribute("username") != null){
            // 通过,不进行拦截
            return true; // 返回 true 表示不拦截,继续执行后续代码
        }
        response.setStatus(401);
        return false;
    }
}
/**
 * 添加拦截器
 */
@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    // 添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")// 拦截所有接口
                .excludePathPatterns("/user/login")
                .excludePathPatterns("/user/reg");// 排除接口
    }
}
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
    // 获取用户信息
    @RequestMapping("/getInfo")
    public String getInfo(){
        log.info("get info...");
        return "get info...";
    }
    // 注册
    @RequestMapping("/reg")
    public String reg(){
        log.info("reg...");
        return "reg...";
    }
    // Login
    @RequestMapping("/login")
    public boolean login(HttpServletRequest request,String username,String password){
        // 判断 username && password 是否为空
        if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)){
            // 未登录
            return false;
        }
        if(!"admin".equals(username) || !"admin".equals(password)){
            return false;
        }
        HttpSession session = request.getSession(true);// 不存在则创建
        session.setAttribute("username",username);
        return true;
    }
}

运行以上代码后,可以看到 http://127.0.0.1:8080/user/reg  可以正常访问:

http://127.0.0.1:8080/user/info 界面被拦截,且状态码为:401 

http://127.0.0.1:8080/user/login 界面在没有输入用户信息之前返回 true: 

在成功写入 session 之后,界面返回 true,表示成功登录:

 

在成功登录后,再访问  http://127.0.0.1:8080/user/info 界面时,可以看到能够正常访问了:

1.5 拦截器实现原理

1.6 统一访问前缀添加

所有请求地址添加 api 前缀:

@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    // 添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")// 拦截所有接口
                .excludePathPatterns("/api/user/login")
                .excludePathPatterns("/api/user/reg");// 排除接口
    }

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.addPathPrefix("api",c -> true);
    }
}

此时可以看到没有 api 前缀则无法访问:

加上 api 前缀后,则可以访问: 

其中第二个参数是⼀个表达式,设置为 true 表示启动前缀。 

2. 统一异常处理 

将发生的异常进行统一处理。

统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的 ,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知, 也就是执行某个方法事件,具体实现代码如下:
@RestController
public class ExceptionController {
    @RequestMapping("/test1")
    public boolean test1(){
        int a = 10/0;
        return true;
    }
    @RequestMapping("/test2")
    public boolean test2(){
        String str = null;
        System.out.println(str.length());
        return true;
    }
    @RequestMapping("/test3")
    public String test3(){
        throw new RuntimeException("test3手动创建异常");
    }
}
@ControllerAdvice
public class ErrorHandler {
    @ExceptionHandler
    public Object error(Exception e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("success",0);
        result.put("code",-1);
        result.put("msg","内部异常");
        return result;
    }
}

运行后,可以看到出现错误且状态码均为500:

此时,我们需要加上注解:@ResponseBody

@ControllerAdvice
public class ErrorHandler {
    @ResponseBody
    @ExceptionHandler
    public Object error(Exception e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("success",0);
        result.put("code",-1);
        result.put("msg","内部异常");
        return result;
    }
}

 此时,我们可以看到均正常返回:

 还可以将异常信息分的更详细:

@ControllerAdvice
public class ErrorHandler {
    @ResponseBody
    @ExceptionHandler
    public Object error(Exception e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("success",0);
        result.put("code",-1);
        result.put("msg","内部异常");
        return result;
    }
    @ResponseBody
    @ExceptionHandler
    public Object error(ArithmeticException e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("success",0);
        result.put("code",-2);
        result.put("msg","ArithmeticException 异常");
        return result;
    }
    @ResponseBody
    @ExceptionHandler
    public Object error(NullPointerException e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("success",0);
        result.put("code",-3);
        result.put("msg","NullPointerException 异常");
        return result;
    }
}

运行结果如下: 

3. 统一数据返回格式

3.1 为什么需要统⼀数据返回格式?

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

3.2 统一数据返回格式的实现 

@Slf4j
@ControllerAdvice
public class ErrorHandler {
    @ResponseBody
    @ExceptionHandler
    public Object error(Exception e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("success",0);
        result.put("code",-1);
        result.put("msg","内部异常");
        log.info("Exception:",e);
        return result;
    }
    @ResponseBody
    @ExceptionHandler
    public Object error(ArithmeticException e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("success",0);
        result.put("code",-2);
        result.put("msg","ArithmeticException 异常");
        log.info("ArithmeticException:",e);
        return result;
    }
    @ResponseBody
    @ExceptionHandler
    public Object error(NullPointerException e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("success",0);
        result.put("code",-3);
        result.put("msg","NullPointerException 异常");
        log.info("NullPointerException:",e);
        return result;
    }
}
@ControllerAdvice
public class ResponseHandller 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) {
        // 在向 Body 写之前要进行的操作
        HashMap<String,Object> result = new HashMap<>();
        result.put("success",1);
        result.put("data",body);
        result.put("errMsg","");
        return result;
    }
}

可以看到在没有进行统一数据返回格式之前,运行结果如下图所示: 

在进行统一数据返回格式之后,运行结果如下图所示:  

但是需要注意的是:如果方法返回的结果类型是 String,会出现以下错误:

java.lang.ClassCastException: java.util.HashMap cannot be cast to java.lang.String 

 解决方法如下:

@ControllerAdvice
public class ResponseHandller implements ResponseBodyAdvice {
    @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) {
        // 在向 ResponseBody 写之前要进行的操作
        HashMap<String,Object> result = new HashMap<>();
        result.put("success",1);
        result.put("data",body);
        result.put("errMsg","");
        // 如果返回的数据类型是字符串类型
        if(body instanceof String){
            ObjectMapper mapper = new ObjectMapper();
            return mapper.writeValueAsString(result);
        }
        return result;
    }
}

 此时,我们可以看到运行结果如下:

查看 class 文件可以发现,@SneakyThrows 注解会自动生成 try catch 语句:


本介绍了统一用户登录权限的效验使用 WebMvcConfigurer+ HandlerInterceptor来实现,统⼀异常处理使用 @ControllerAdvice + @ExceptionHandler 来实现,统一返回值处理使用@ControllerAdvice + ResponseBodyAdvice 来处理。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/820670.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis篇

文章目录 Redis-使用场景1、缓存穿透2、缓存击穿3、缓存雪崩4、双写一致5、Redis持久化6、数据过期策略7、数据淘汰策略 Redis-分布式锁1、redis分布式锁&#xff0c;是如何实现的&#xff1f;2、redisson实现的分布式锁执行流程3、redisson实现的分布式锁-可重入4、redisson实…

AMEYA详解松下Panasonic HF SSOP 1 Form A AQY PhotoMOS继电器

Panasonic HF SSOP 1 Form A AQY PhotoMOS继电器采用微型SSOP封装&#xff0c;具有600V的负载电压和1500Vrms 的I/O隔离电压 这些继电器具有8Ω的低导通电阻和高速运行的特点&#xff0c;SSOP封装旨在实现高密度安装。Panasonic HF SSOP AQY PhotoMOS继电器适用于从测试和测量设…

【python】冒泡法--详细讲解(python实现)

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

简单工厂模式(Simple Factory)

简单工厂模式&#xff0c;又称为静态工厂方法(Static Factory Method)模式。在简单工厂模式中&#xff0c;可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例&#xff0c;被创建的实例通常都具有共同的父类。简单工厂模式不属于GoF的23个…

iPhone 7透明屏的显示效果怎么样?

iPhone 7是苹果公司于2016年推出的一款智能手机&#xff0c;它采用了4.7英寸的Retina HD显示屏&#xff0c;分辨率为1334x750像素。 虽然iPhone 7的屏幕并不是透明的&#xff0c;但是苹果公司在设计上采用了一些技术&#xff0c;使得用户在使用iPhone 7时可以有一种透明的感觉…

虚拟个家用服务器集群(3):更换 PVE 软件源

风无痕 July 31,2023 前言 很多人想建个人博客类的网站&#xff0c;这就需要网站服务器&#xff1b;需要管理手机、电脑中积累的照片&#xff0c;每张照片可都是人生一个片段的记录&#xff0c;需要管理微信中收发的各种文档等等&#xff0c;这就需要一台 NAS 即 Network Att…

教师工作量管理系统Springmvc+Spring+Mybatis课程工作量教室java源代码mysql

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 教师工作量管理系统SpringmvcSpringMybatis 系统有1权…

800V电驱动产品和技术汇总

文章来源&#xff1a; 赵老师——国汽战略院 汽车电动化研究中心 副主任研究员 需要样件请联&#xff1a;shbinzer 拆车邦 德国采埃孚 采埃孚于2022年量产800V电驱系统&#xff0c;采埃孚电驱传动技术事业部亚太区研发副总裁王岳在《采埃孚新一代超紧凑电驱动系统》报告中展…

【入门SpringCloud(一)】什么是SpringCloud?

一、概述 集群&#xff08;Cluster&#xff09;&#xff1a;同一种软件服务的多个服务节点共同为系统提供服务过程&#xff0c;称之为该软件服务集群。 分布式&#xff08;Distribute&#xff09;&#xff1a;分布式是一种系统架构&#xff0c;是将系统中的不同组件分布在不同…

计算机网络期末复习简答题、综合题、实验题答案整理汇总详细(持续更新中)

文章目录 简答题一、第一章&#xff1a;计算机网络概述1. TCP/IP 与 OSI 相结合的五层体系结构将计算机网络划分成哪几个层次&#xff1f;各层的主要功能是什么 二、第二章&#xff1a;物理层1. 交换机、路由器、网卡、网桥、集线器、中继器分别工作在哪一层2. 简述交换机、集线…

10.类型声明文件

类型声明文件的作用是 为已存在的JS库提供类型信息 目录 1 axios中的类型声明文件 2 类型声明文件与普通ts文件的区别 3 vscode中内置的类型声明文件 4 第三方库内置的类型声明文件 5 DefinitelyTyped 提供类型声明文件 6 自定义类型声明文件 6.1 创建给ts用的类…

同为科技(TOWE)带热插拔功能机柜PDU插座的应用

所谓热插拔&#xff08;hot-plugging或Hot Swap&#xff09;&#xff0c;即带电插拔&#xff0c;指的是在不关闭系统电源的情况下&#xff0c;将模块、板卡插入或拔出系统而不影响系统的正常工作&#xff0c;从而提高了系统的可靠性、快速维修性、冗余性和对灾难的及时恢复能力…

JMeter 的使用

文章目录 1. JMeter下载2. JMeter的使用2.1 JMeter中文设置2.2 JMeter的使用2.2.1 创建线程组2.2.2 HTTP请求2.2.3 监听器 1. JMeter下载 官网地址 https://jmeter.apache.org/download_jmeter.cgi https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.6.2.zip 下载解…

Vue2 第十二节 Vue组件化编程 (二)

1. VueComponent 2. 单文件组件 一. VueComponent 组件本质上是一个名为VueComponent的构造函数&#xff0c;不是程序员定义的&#xff0c;是Vue.extend生成的只需要写<school/>或者<school><school/>&#xff0c;Vue解析时&#xff0c;会帮我们创建schoo…

ThinkPHP 6 添加跳转提示扩展 liliuwei/thinkphp-jump

liliuwei/thinkphp-jump 是 TP5 中经典跳转提示&#xff0c;在 TP6 中已经取消&#xff0c;通过 composer 下载该扩展可以在 TP6 中使用 TP5 的跳转提示操作。 安装扩展 在应用根目录执行: composer require liliuwei/thinkphp-jump引入扩展 在全局配置目录生成 jump.php 文件…

Activity的生存期

以下内容摘自郭霖《第一行代码》第三版 Activity的生存期 Activity类中定义了7个回调方法&#xff0c;覆盖了Activity生命周期的每一个环节&#xff1a; onCreate()。这个方法你已经看到过很多次了&#xff0c;我们在每个Activity中都重写了这个方法&#xff0c;它会在Activit…

安科瑞电动机保护器产品在污水处理厂的应用-安科瑞黄安南

应用场景 功能 1&#xff09;排污泵经常会出现过载、缺相等问题&#xff0c;导致电机烧坏&#xff1b; 2&#xff09;为电动机提供完善的保护&#xff0c;并具备多种事件记录追忆功能&#xff1b; 3&#xff09;全电参量测量&#xff0c;包括但不限于三相电流、三相电压、有…

11-矩阵(matrix)_方阵_对称阵_单位阵_对角阵

矩阵及其运算 [ a 11 ⋯ a 1 n ⋯ ⋯ ⋯ a m 1 ⋯ a m n ] \begin{bmatrix} a_{11} & \cdots & a_{1n} \\ \cdots & \cdots & \cdots \\ a_{m1} & \cdots & a_{mn} \\ \end{bmatrix} ​a11​⋯am1​​⋯⋯⋯​a1n​⋯amn​​ ​ 矩阵就是二维数组&…

模板(下)

文章目录 非类型模板参数 模板的特化 模板分离编译 一、非类型模板参数 模板参数分为类型形参与非类型形参。 类型形参即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的参数类型名称。 非类型形参&#xff1a;就是用一个常量作为类(函数)模板的…

【笔试强训选择题】Day33.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;&#xff…