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. 用户登录权限校验
用户登录权限的发展从之前每个方法中自己验证用户登录权限,到现在统⼀的用户登录验证处理,它是⼀个逐渐完善和逐渐优化的过程。
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 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;}
- 没办法获取到 HttpSession 对象
- 要对⼀部分方法进行拦截,而另⼀部分方法不拦截,如注册方法和登录方法是不拦截的,这样的话排除方法的规则很难定义,甚至没办法定义。
1.3 Spring 拦截器
- 创建自定义拦截器,实现 HandlerInterceptor 接口的 preHandle(执行具体方法之前的预处理)方法。
- 将自定义拦截器加入 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 练习:登录拦截器
- 登录、注册页面不拦截,其他页面都拦截。
- 当登录成功写入 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. 统一异常处理
将发生的异常进行统一处理。
@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 为什么需要统⼀数据返回格式?
- 方便前端程序员更好的接收和解析后端数据接口返回的数据。
- 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就可以,因为所有接口都是这样返回的。
- 有利于项目统一数据的维护和修改。
- 有利于后端技术部门的统一规范的标准制定,不会出现稀奇古怪的返回内容。
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 练习:登录拦截器 1.5 拦截器实现原理 1.6 统一访问前缀添加 2. 统一异常处理 3. 统…...
PostgreSQL-数据库命令
PostgreSQL-数据库命令 介绍 一个数据库是一个或多个模式的集合,而模式包含表、函数等。因此,完整的逻辑组织结构层次是服务器实例(PostgreSQL Server)、数据库(Database)、模式(Schema)、表(Table),以及某些其他对象(如函数)。一个PostgreSQL服务器实例可以管理…...

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

HTML基础介绍1
HTML是什么 1.HTML(HyperText Mark-up Language)即超文本标签语言(可以展示的内容类型很多) 2.HTML文本是由HTML标签组成的文本,可以包括文字、图形、动画、声音、表格、连接等 3.HTML的结构包括头部(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内置的应用商店,并通过Yum 包管理器来下载(直接使用yum命令下载软件) 在Linux系统中,Yum(Yellowdog Updater, Modified)是用于管理RPM软件包的一个包管理器。 安装…...

SNAT和DNAT原理与应用
iptables的备份和还原 1.写在命令行当中的都是临时配置。 2.把我们的规则配置在 备份(导出):iptables-save > /opt/iptables.bak 默认配置文件:/etc/sysconfig/iptables 永久配置:cat /opt/iptables.bak > /etc…...

Java8实战-总结11
Java8实战-总结11 Lambda表达式方法引用管中窥豹如何构建方法引用 构造函数引用 Lambda表达式 方法引用 方法引用让你可以重复使用现有的方法定义,并像Lambda一样传递它们。在一些情况下,比起使用Lambda表达式,它们似乎更易读,感…...
2023爱分析·低代码厂商全景报告|爱分析报告
关键发现 低代码开始向甲方核心场景渗透,呈现两个显著特征:“更深入”、“更垂直”。更深入,即甲方愈发注重低代码在复杂业务场景的应用开发能力;更垂直,即甲方需要使用低代码开发行业垂直应用,因此对行业或…...

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

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

maven下载按照及初次使用相关配置
maven下载按照及初次使用相关配置 一、下载 与安装 依赖Java,需要配置JAVA_HOME设置MAVEN自身的运行环境,需要配置MAVEN_HOME测试环境配置结果 MVN测试成功!!! 二、本地仓库配置 Maven启动后,会自动保…...
opencv05-掩膜
参考: 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配置。相关软件可登录网信智汇(wangxinzhihui)下载。 1、创建1个数据源:本案例采用“Graybox.Simulator.1”作为数据源。连接OPC Server数据源“Graybox.Simulator.1”。 右键点击“连通性”&am…...

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

count(列名) ,count(1)与count(*) 有何区别?
Mysql版本:8.0.26 可视化客户端:sql yog 文章目录 一、Mysql之count函数简介二、count(列名) ,count(常量)与count(*) 有何区别?2.1 统计字段上的区别2.2 执行效率上的区别 一、Mysql之count函数简介 👉表达式 COUNT(…...
node.js判断元素是否包括
在Node.js中,可以使用Array.prototype.some()方法来判断数组中是否包含某个元素。下面是一个示例代码: const arr [ { ‘_android:name’: ‘com.eg.android.AlipayGphone’ }, { ‘_android:name’: ‘com.eg.android.AlipayGphoneRC’ }, { ‘_andro…...

基于SpringBoot+Vue的地方废物回收机构管理系统设计与实现(源码+LW+部署文档等)
博主介绍: 大家好,我是一名在Java圈混迹十余年的程序员,精通Java编程语言,同时也熟练掌握微信小程序、Python和Android等技术,能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…...

【SAP MII学习】Day01--Overview, Security Services, and Workbench
1. Module 1: Overview 1.1 问题存在的原因 上图展示的是在工厂中IT的架构图,主要分为一下的三个层次: 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…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...

以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...