16. Spring Boot 统一功能处理

目录

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 {@Overridepublic 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 {@Autowiredprivate LoginInterceptor loginInterceptor;// 添加拦截器@Overridepublic 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 {@Overridepublic 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 {@Autowiredprivate LoginInterceptor loginInterceptor;// 添加拦截器@Overridepublic 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 {@Autowiredprivate LoginInterceptor loginInterceptor;// 添加拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**")// 拦截所有接口.excludePathPatterns("/api/user/login").excludePathPatterns("/api/user/reg");// 排除接口}@Overridepublic 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 {@ExceptionHandlerpublic 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@ExceptionHandlerpublic 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@ExceptionHandlerpublic Object error(Exception e){HashMap<String,Object> result = new HashMap<>();result.put("success",0);result.put("code",-1);result.put("msg","内部异常");return result;}@ResponseBody@ExceptionHandlerpublic 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@ExceptionHandlerpublic 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@ExceptionHandlerpublic 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@ExceptionHandlerpublic 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@ExceptionHandlerpublic 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 {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true; // 允许对结果进行统一处理}@Overridepublic 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 {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true; // 允许对结果进行统一处理}@SneakyThrows@Overridepublic 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.mzph.cn/news/24449.shtml

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

相关文章

C++设计模式行为型之观察者模式

一、概述 观察者模式定义对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。 二、示例代码 #include <list> class Subject; class Observer { public: virtual ~Observer(); virt…

心跳跟随的心形灯(STM32(HAL)+WS2812+MAX30102)

文章目录 前言介绍系统框架原项目地址本项目开发开源地址硬件PCB软件功能 详细内容硬件外壳制作WS2812级联及控制MAX30102血氧传感器0.96OLEDFreeRTOS 效果视频总结 前言 在好几年前&#xff0c;我好像就看到了焊武帝 jiripraus在纪念结婚五周年时&#xff0c;制作的一个心跳跟…

R语言中数据重塑(长宽表的转化)

学习笔记&#xff0c;仅供学习使用。 目录 1-什么是整洁的数据&#xff1f; 2-宽表变成长表 示例1&#xff1a; 示例2&#xff1a; 示例3&#xff1a; 3-长表变宽表 示例1&#xff1a; 示例2&#xff1a; 1-什么是整洁的数据&#xff1f; 按照Hadley的表述&#xf…

【redis】redis的认识和安装

目录 1.redis是什么2.Redis的特点3.安装redis4.设置远程连接4.1 开启隧道4.2 可视化客户端连接4.3 开启防火墙 5.redis常见数据类型5.1 redis的一些全局命令5.2 数据结构 6. redis的典型应用---缓存&#xff08;cache&#xff09;6.1 使用redis做缓存6.2 缓存穿透&#xff0c;缓…

华为VRP 系统基础配置

1.flash 相当于电脑的 硬盘 2.NVRAM 拿来专门存放系统配置文件 3.RAM 运行内存 4.ROM 系统引导 1.修改系统名[Huawei]sysname SWL1 2.配置登入信息 [SWL1]header shell information "Welocom to the learning" [SWL1]q <SWL1>q User interface con0 …

Excel·VBA表格横向、纵向相互转换

如图&#xff1a;对图中区域 A1:M6 横向表格&#xff0c;转换成区域 A1:C20 纵向表格&#xff0c;即 B:M 列转换成每2列一组按行写入&#xff0c;并删除空行。同理&#xff0c;反向操作就是纵向表格转换成横向表格 目录 横向转纵向实现方法1转换结果 实现方法2转换结果 纵向转横…

《吐血整理》高级系列教程-吃透Fiddler抓包教程(30)-Fiddler如何抓Android7.0以上的Https包-番外篇

1.简介 通过宏哥前边几篇文章的讲解和介绍想必大家都知道android7.0以上&#xff0c;有android的机制不在信任用户证书&#xff0c;导致https协议无法抓包。除非把证书装在系统信任的证书里&#xff0c;此时手机需要root权限。但是大家都知道root手机是非常繁琐的且不安全&…

HDFS中的sequence file

sequence file序列化文件 介绍优缺点格式未压缩格式基于record压缩格式基于block压缩格式 介绍 sequence file是hadoop提供的一种二进制文件存储格式一条数据称之为record&#xff08;记录&#xff09;&#xff0c;底层直接以<key, value>键值对形式序列化到文件中 优…

Android随笔-VPN判断

Android中判断当前网络是否为VPN /*** 判断当前网络是否为VPN* param context* return*/public static boolean hasVPN(Context context) {// 查询网络状态&#xff0c;被动监听网络状态变化ConnectivityManager cm (ConnectivityManager) context.getSystemService(Context.C…

动态规划(一)

一、背包问题 1.1 01背包问题 特点:每件物品最多只用于一次 属性包括:最大值(Max)、最小值(Min)、数量 #include<iostream> #include<algorithm>using namespace std;const int N 1010;int n,m; int v[N],w[N]; int f[N][N];int main() {cin>>n>>m;…

bash shell 中的特殊字符详解

bash shell 中的特殊字符详解 [sharp] # 井号 井号常用作注释符号 1.注释示例 # This line is a comment.2.某命令后注释&#xff0c;#号前需要添加一个空格 echo "A comment will follow." # Comment here. # ^ Note whitespace bef…

hive中时间戳与时间字符串相互转换的方法教程

时间戳是数据库常用的存放日期的形式之一&#xff0c;表示从 UTC 时间’1970-01-01 00:00:00’开始到现在的秒数&#xff0c;与常规时间格式如 ‘2018-01-01 00:00:00’可以相互转换&#xff0c;方法如下。 一、unix_timestamp 函数用法 1、unix_timestamp() 返回当前时间戳。…

Java通过实例调用getClass()方法、类名.class操作、通过运行时类获取其它信息

说明 Java Object类的getClass()函数&#xff0c;是通过对象调用的&#xff0c;是一个实例方法&#xff0c;该方法返回当前对象的运行时类。 通过类名.class可以获得和通过实例调用getClass()函数一样的信息。 获得运行时类以后&#xff0c;可以进一步获取其它信息。 代码示例…

MyCat概述

1.MyCat概述 MyCat是阿里巴巴的产品&#xff0c;他是开源的、基于Java语言编写的MySQL数据库中间件。可以像使用mysql一样来使用mycat&#xff0c;对于开发人员来说根本感觉不到mycat的存在。 MyCat下载地址&#xff1a;http://dl.mycat.org.cn/ MyCat官网&#xff1a;http:/…

CuratorFramework接口的作用和使用

CuratorFramework接口是Apache Curator库中的核心接口之一&#xff0c;用于与ZooKeeper集群进行交互。它提供了一组丰富的方法和功能&#xff0c;用于简化与ZooKeeper的交互操作&#xff0c;包括创建、删除、读取和更新节点等。 CuratorFramework接口的主要作用是封装了底层与…

【C语言进阶篇】模拟实现通讯录 (内附源码)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《C语言初阶篇》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 &#x1f4cb; 前言一 、 通讯录的简介1.1 联系人的类型定义1.2 通讯录的定义1.3 通讯录要实现的功能 二 、 如何…

计算机网络的定义和分类

计算机网络的定义和分类 计算机网络的定义 计算机网络的精确定义并未统一计算机网络最简单的定义是&#xff1a;一些互相连接的、自治的计算机的集合 互连:指计算机之间可以通过有线或无线的方式进行数据通信自治:是指独立的计算机&#xff0c;它有自己的硬件和软件&#xff…

Python语法:... for ... in ... if ...

Python中&#xff0c;for...in...[if]...语句是一种简洁的构建List的方法&#xff0c;从for给定的List中选择出满足if条件的元素组成新的List&#xff0c;其中if是可以省略的。下面举几个简单的例子进行说明 [for in ]: ...for ....in..... 语句. 实例如下&#xff1a; (1) …

Ubuntu出现内部错误解决办法

使用的Ubuntu版本是18.04&#xff0c;使用的时候弹出对话框说出现了内部错误&#xff0c;好奇是哪里出现了错误&#xff0c;查找了一下解决的办法&#xff0c;记录一下。 参考解决方案&#xff1a;ubantu出现了内部错误 一旦程序崩溃过一次&#xff0c;就会生成一个.crash文件…

PHP实现首字母头像

<?php $name"哈哈"; $logoletter_avatar($name);echo <img src".$logo." style" border-radius: 50%;">;function letter_avatar($text) {$total unpack(L, hash(adler32, $text, true))[1];$hue $total % 360;list($r, $g, $b) hs…