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

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 来处理。

 

 

相关文章:

16. Spring Boot 统一功能处理

目录 1. 用户登录权限校验 1.1 最初用户登录验证 1.2 Spring AOP 用户统一登陆验证 1.3 Spring 拦截器 1.3.1 创建自定义拦截器 1.3.2 将自定义拦截器加入系统配置 1.4 练习&#xff1a;登录拦截器 1.5 拦截器实现原理 1.6 统一访问前缀添加 2. 统一异常处理 3. 统…...

PostgreSQL-数据库命令

PostgreSQL-数据库命令 介绍 一个数据库是一个或多个模式的集合,而模式包含表、函数等。因此,完整的逻辑组织结构层次是服务器实例(PostgreSQL Server)、数据库(Database)、模式(Schema)、表(Table),以及某些其他对象(如函数)。一个PostgreSQL服务器实例可以管理…...

面试题:说说JavaScript中内存泄漏的几种情况?垃圾回收机制

内存泄漏 一、是什么&#xff1f;二、垃圾回收机制&#xff1f;2.1、标记清除法2.2、引用计数法 三、常见内存泄露情况 一、是什么&#xff1f; 由于疏忽或错误造成程序未能释放已经不再使用的内存&#xff1b;并非指内存在物理上的消失&#xff0c;而是应用程序分配某段内存后…...

HTML基础介绍1

HTML是什么 1.HTML&#xff08;HyperText Mark-up Language&#xff09;即超文本标签语言&#xff08;可以展示的内容类型很多&#xff09; 2.HTML文本是由HTML标签组成的文本&#xff0c;可以包括文字、图形、动画、声音、表格、连接等 3.HTML的结构包括头部&#xff08;He…...

【腾讯云 Cloud Studio 实战训练营】Redisgo_task 分布式锁实现

文章目录 前言问题场景腾讯云 Cloud Studio Redisgo_task长短类型分布式场景介绍Redisgo_task实现原理SetNx(valueexpire)原子性子协程Done()时间点子协程中的Ticker Redisgo_task唯一外部依赖Redisgo_task Lock结构Redisgo_task架构健壮性设计Redisgo_task可扩展性Redisgo_tas…...

Linux CentOS系统怎么下载软件

Linux CenOS系统想要下载软件可以在Linux内置的应用商店&#xff0c;并通过Yum 包管理器来下载&#xff08;直接使用yum命令下载软件&#xff09; 在Linux系统中&#xff0c;Yum&#xff08;Yellowdog Updater, Modified&#xff09;是用于管理RPM软件包的一个包管理器。 安装…...

SNAT和DNAT原理与应用

iptables的备份和还原 1.写在命令行当中的都是临时配置。 2.把我们的规则配置在 备份&#xff08;导出&#xff09;&#xff1a;iptables-save > /opt/iptables.bak 默认配置文件&#xff1a;/etc/sysconfig/iptables 永久配置&#xff1a;cat /opt/iptables.bak > /etc…...

Java8实战-总结11

Java8实战-总结11 Lambda表达式方法引用管中窥豹如何构建方法引用 构造函数引用 Lambda表达式 方法引用 方法引用让你可以重复使用现有的方法定义&#xff0c;并像Lambda一样传递它们。在一些情况下&#xff0c;比起使用Lambda表达式&#xff0c;它们似乎更易读&#xff0c;感…...

2023爱分析·低代码厂商全景报告|爱分析报告

关键发现 低代码开始向甲方核心场景渗透&#xff0c;呈现两个显著特征&#xff1a;“更深入”、“更垂直”。更深入&#xff0c;即甲方愈发注重低代码在复杂业务场景的应用开发能力&#xff1b;更垂直&#xff0c;即甲方需要使用低代码开发行业垂直应用&#xff0c;因此对行业或…...

视频两侧有黑边怎么处理?教你裁切视频黑边方法

现在的大多数电视是16:9的宽屏&#xff0c;而大多数视频都是4:3的标清或是16:9的高清。当你看一个标清或高清视频时&#xff0c;如果它的比例与你的电视屏幕比例不同&#xff0c;视频两侧就会出现黑边。这些黑边会对视频的质量或观看体验产生影响&#xff0c;那么怎么处理呢&am…...

如何设计一个Android端高性能日志监控系统

开发中客户端经常遇到一些线上问题, 无法复现, 但是又的的确确存在; 当线上反馈的时候无从下手; 主要是因为并不知道用户所处的环境,以及所做的操作顺序或者程序运行的顺序; 在排查问题和复现问题上占用了很大的成本; 如果debug时的log日志如果线上也能查看就好了; 基于此, 我们…...

maven下载按照及初次使用相关配置

maven下载按照及初次使用相关配置 一、下载 与安装 依赖Java&#xff0c;需要配置JAVA_HOME设置MAVEN自身的运行环境&#xff0c;需要配置MAVEN_HOME测试环境配置结果 MVN测试成功&#xff01;&#xff01;&#xff01; 二、本地仓库配置 Maven启动后&#xff0c;会自动保…...

opencv05-掩膜

参考&#xff1a; https://blog.csdn.net/shuiyixin/article/details/88825549 #include <iostream> #include <opencv2/highgui/highgui.hpp> #include <opencv2/opencv.hpp> #include <vector> #include <array> #include <algorithm>u…...

通讯软件013——分分钟学会Kepware OPC AE Server仿真配置

本文介绍如何使用Kepware软件仿真OPC AE Server配置。相关软件可登录网信智汇&#xff08;wangxinzhihui&#xff09;下载。 1、创建1个数据源&#xff1a;本案例采用“Graybox.Simulator.1”作为数据源。连接OPC Server数据源“Graybox.Simulator.1”。 右键点击“连通性”&am…...

Windows下安装Hive(包安装成功)

Windows下安装Hive Hive与Hadoop的版本选择很关键&#xff0c;千万不能选错&#xff0c;否则各种报错。一、Hive下载1.1、官网下载Hive1.2、网盘下载Hive 二、解压安装包&#xff0c;配置Hive环境变量2.1、环境变量新增&#xff1a;HIVE_HOME2.2、修改Path环境变量&#xff0c;…...

count(列名) ,count(1)与count(*) 有何区别?

Mysql版本&#xff1a;8.0.26 可视化客户端&#xff1a;sql yog 文章目录 一、Mysql之count函数简介二、count(列名) &#xff0c;count(常量)与count(*) 有何区别&#xff1f;2.1 统计字段上的区别2.2 执行效率上的区别 一、Mysql之count函数简介 &#x1f449;表达式 COUNT(…...

node.js判断元素是否包括

在Node.js中&#xff0c;可以使用Array.prototype.some()方法来判断数组中是否包含某个元素。下面是一个示例代码&#xff1a; const arr [ { ‘_android:name’: ‘com.eg.android.AlipayGphone’ }, { ‘_android:name’: ‘com.eg.android.AlipayGphoneRC’ }, { ‘_andro…...

基于SpringBoot+Vue的地方废物回收机构管理系统设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…...

【SAP MII学习】Day01--Overview, Security Services, and Workbench

1. Module 1: Overview 1.1 问题存在的原因 上图展示的是在工厂中IT的架构图&#xff0c;主要分为一下的三个层次&#xff1a; Shop Floor Automation and Control Systems (SFAC):collect data from the PLCs and sensors that are connected to the machinery on the facto…...

枚举类常见用法,A Guide to Java Enums

目录 啥是枚举类Custom Enum MethodsComparing Enum Types Using “” OperatorUsing Enum Types in Switch StatementsFields, Methods and Constructors in EnumsEnumSetEnumMapStrategy PatternSingleton PatternJava 8 and EnumsJSON Representation of EnumRead More Java…...

Vue Baidu Map--vue引入百度地图

1.安装 npm方式安装 $ npm install vue-baidu-map --save2.局部注册 <template> <div class"map-content" v-if"iscollegeRole"><baidu-map class"bm-view map":ak"mapAK" :scroll-wheel-zoom"true" :cen…...

使用Express部署Vue项目

使用Express部署Vue项目 目录 1. 背景 2. 配置Vue CLI 1.1 安装nodejs 1.2 创建vue-cli 1.3 创建vue项目 1.4 构建vue项目3. 配置Express 2.1 安装express 2.2 创建项目4. 使用express部署vue项目 1&#xff0c;背景 我们想要做一个前后端分离的课程项目&#xff0c;前端…...

344.翻转字符串+387.字符串中的第一个唯一字符

目录 一、翻转字符串 二、字符串中的第一个唯一字符 一、翻转字符串 344. 反转字符串 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:void reverseString(vector<char>& s) {int start0;int end s.size()-1;while(start < end){swap(s[sta…...

安装mmcv

安装MMCV 创建虚拟环境gupao ,并激活nvcc -V 查看cuda版本 打开当前项目文件主页查看环境配置Prerequisites — MMPretrain 1.0.1 documentation 4. 安装合适的torch版本&#xff0c;原来的版本会自动卸载 pip install torch1.13.1cu117 torchvision0.14.1cu117 torch…...

什么是服务网格?

背景&#xff1a; 服务网格这个概念出来很久了&#xff0c;从 2017 年被提出来&#xff0c;到 2018 年正式爆发&#xff0c;很多云厂商和互联网企业都在纷纷向服务网格靠拢。像蚂蚁集团、美团、百度、网易等一线互联 网公司&#xff0c;都有服务网格的落地应用。服务网格是微服…...

8.1作业

文件IO函数实现拷贝文件。子进程先拷贝后半部分&#xff0c;父进程再拷贝前半部分&#xff0c;允许使用sleep函数 #include<stdio.h> #include<string.h> #include<stdlib.h> #include<head.h> int main(int argc, const char *argv[]) {pid_t cpidfo…...

linux-安全技术

文章目录 安全机制墨菲定理信息安全防护的目标安全防护环节常见的安全攻击STRIDE 安全机制 墨菲定理 摘自百度百科 墨菲定律是一种心理学效应&#xff0c;1949年由美国的一名工程师爱德华墨菲&#xff08;Edward A. Murphy&#xff09;提出的&#xff0c;亦称墨菲法则、墨菲…...

如何在免费版 pycharm 中使用 github copilot (chatGPT)?

起因 在 vscode 中使用了 github copilot 以后&#xff0c;感觉这个人工智能还不错。 但 vscode 对于 python 项目调试并不是特别方便&#xff0c;所以想在 Pycharm 中也能使用同一个 github 账号&#xff0c;用上 copilot 的功能。 不需要等待&#xff0c;安装即用&#xff…...

SSD202D-UBOOT-FDT-获取DTB

因为一些需求,我们决定给uboot添加一个功能,在boot阶段识别获取出dtb,然后获取dts参数 DTS引脚是这样设置的 /* * infinity2m-ssc011a-s01a-padmux-display.dtsi- Sigmastar * * Copyright (c) [2019~2020] SigmaStar Technology. * * * This software is licensed under the …...

【Maven】Setting文件分享

<?xml version"1.0" encoding"UTF-8"?><!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding …...