从零开始搭建企业管理系统(五):统一响应结果和全局异常处理

统一响应结果和全局异常处理

    • 前言
    • 统一响应结果
      • 定义响应结构
      • 定义响应对象
      • 定义响应状态对象
      • 统一返回响应对象
        • 定义 Controller 拦截类
    • 全局异常处理

前言

做个功能之前我们想一下为什么要做统一响应结果和全局异常处理呢?

这是因为我们的项目采用的是前后端分离开发,并且使用 Restful 风格的接口形式,返回的业务数据以及异常都是以 JSON 的格式返回,但是目前我们返回的数据格式都是不一致的,比如如下的代码:

@GetMapping("/{id}")
@Operation(summary = "根据用户ID查询用户")
public UserEntity get(@PathVariable Long id) {return userService.get(id);
}@GetMapping("/list")
@Operation(summary = "查询全部")
public List<UserEntity> lists() {return userService.lists();
}@GetMapping("/page")
@Operation(summary = "分页查询")
public Page<UserEntity> page(int page, int size) {return userService.page(PageRequest.of(page - 1, size));
}@PostMapping
@Operation(summary = "新增用户")
public void save(@RequestBody UserEntity user) {userService.save(user);
}

由上面的接口返回值可以看出,我们4个接口就有4中返回数据格式,这时前端工作人员在对接的时候就接口她们就会很难受,需要写很多的处理来处理不同的返回数据,非常的不方便,而且我们自己使用起来也不好管理,所以在实际开发中,为了降低开发人员之间的沟通成本,一般返回结果会定义成一个统一格式,具体的格式根据实际开发业务不同有所区别。

统一响应结果

定义响应结构

从前端的角度思考,调用后端接口后如果成功了则拿到想要的数据或者对应的信息提示,如果不成功也应该有相应的信息提示。从这个角度出发,我们将响应结果设计为:

code:响应状态码,根据状态码判断是否成功

message:提示信息,根据调用的接口自定义

body:返回的数据

响应的结构如下:

{"code": 200,"message": "成功","body": [{"createUser": "admin","updateUser": "admin","createTime": "2023-12-09 14:14:20","updateTime": "2023-12-09 14:14:20","id": 2,"name": "金克斯","nickname": "爆爆","age": 18,"email": null,"password": "666"}]
}

定义响应对象

Spring 官方源码里面也有一个类似的对象,类名为 ResponseEntity,如果你想直接用这个类也行的,但是这个类我使用起来有点不合我的习惯,所以还是自定义一个好点。

@Data
public class Response<T> implements Serializable {@Schema(description = "响应状态")private Integer code;@Schema(description = "响应信息")private String message;@Schema(description = "响应数据")private T body;/*** 私有化构造函数*/private Response() {}/*** 构建 Response<T> 对象** @param body 响应数据* @param code 响应状态对象*/public static <T> Response<T> build(@Nullable T body, ResponseCode code) {return build(body, code.getCode(), code.getMessage());}/*** 构建 Response<T> 对象** @param body    响应数据* @param code    响应状态* @param message 响应信息*/public static <T> Response<T> build(@Nullable T body, Integer code, String message) {Response<T> response = new Response<>();response.setCode(code);response.setMessage(message);response.setBody(body);return response;}/*** 请求成功返回不带数据*/public static <T> Response<T> ok() {return build(null, ResponseCode.OK);}/*** 请求成功返回带数据*/public static <T> Response<T> ok(T body) {return build(body, ResponseCode.OK);}/*** 请求失败返回500*/public static <T> Response<T> error() {return build(null, ResponseCode.ERROR);}/*** 请求失败状态码*/public static <T> Response<T> error(ResponseCode code) {return build(null, code);}/*** 自定义请求失败信息*/public static <T> Response<T> error(Integer code, String message) {return build(null, code, message);}}

定义响应状态对象

public enum ResponseCode {/*** 成功*/OK(200, "OK"),/*** 失败*/ERROR(500,"系统异常,请稍后重试");private final int code;private final String message;ResponseCode(int code, String message) {this.code = code;this.message = message;}public int getCode() {return code;}public String getMessage() {return message;}
}

统一返回响应对象

这里有两种方式可以做到全局返回我们定义好的 Response 对象,第一种就是在定义方法的时候返回值都定义为 Resopnse 对象,这是可以完成的,但是我们还有更高级一点的用法,使用 Spring ResponseBodyAdvice类即可,ResponseBodyAdvice的作用就是拦截Controller方法的返回值,统一处理返回值/响应体,一般用来统一返回格式,加解密,签名等等。

我们这里采用第二种方式来完成统一响应结果。

定义 Controller 拦截类
@RestControllerAdvice(basePackages = {"com.xm.module"})
public class ResponseAdviceHandler implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {// 响应都需要被拦截return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if (body instanceof String) {// String 类型不能封装成 Response 返回return JSONUtil.toJsonStr(Response.ok(body));} else if (body instanceof Response) {// 如果是 Response 就直接返回了return body;} else {// 统一响应对象return Response.ok(body);}}
}

@RestControllerAdvice@RestController注解的增强,可以实现三个方面的功能:

  1. 全局异常处理
  2. 全局数据绑定
  3. 全局数据预处理

basePackages 表示是只拦截 com.xm.module 下的响应

JSONUtil.toJsonStr(Response.ok(body));这个 JSONUtil 我是用的 hutool 工具包里面的,好用滴很。有需要的自己导入。Hutool 官网

启动程序测试一下

在这里插入图片描述

这里报了一个异常,而且我们发现异常信息的响应结果和我们预想的不一致,这个我们在下面全局异常拦截的时候在进行处理,先把这个接口异常给解决一下。

异常的大致意思是不能初始化代理,没有 session,这是因为我们使用了懒加载的方法,修改一下这个接口方法。

@Override
public UserEntity get(Long id) {return userRepository.findById(id).orElse(null);
}

在这里插入图片描述

ok,接口请求成功,并且响应的结构和我们预想的一致,nice。

全局异常处理

刚才我们看到,在接口抛出异常的时候,我们的响应结构和我们预想的不一致,我们希望在抛出异常的时候也能返回我们的 Response 对象,怎么实现呢,还是老办法,上面我们提到了 @RestControllerAdvice@RestController注解的增强,可以实现全局异常处理,那我们就在使用这个注解进行全局异常的拦截处理吧。

@RestControllerAdvice
public class GlobalExceptionHandler {/*** 全局异常处理。* ExceptionHandler(Exception.class) 表示拦截 Exception 异常,这是所有异常都拦截* 我们还可以配置 ExceptionHandler(XXException.class) 等等进行更加精细的拦截操作** @param e 异常* @return Response*/@ExceptionHandler(Exception.class)public Response<String> exception(Exception e) {return Response.error(ResponseCode.ERROR);}}

编写好了这个之后,我们再来测试一下异常的情况下返回值是什么样的,

在这里插入图片描述

ok,异常拦截成功,关于更多的异常,可以自己定义拦截即可,本节内容就到这里了,bye~

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

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

相关文章

LVGL | Demo实例使用说明

LVGL | Demo实例使用说明 时间&#xff1a;2023年12月10日21:51:17 文章目录 LVGL | Demo实例使用说明Demos for LVGLAdd the examples to your projectsDemosWidgetsMusic playerKeypad and encoderBenchmarkStress Contributing Demos for LVGL Add the examples to your p…

基于SSM的酒店管理旅店系统(Java毕业设计)

大家好&#xff0c;我是DeBug&#xff0c;很高兴你能来阅读&#xff01;作为一名热爱编程的程序员&#xff0c;我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里&#xff0c;我将会结合实际项目经验&#xff0c;分享编程技巧、最佳实践以及解决问题的方法。无论你是…

【软件测试】年薪30万跟年薪15万的面试有些什么区别?

1、什么是兼容性测试&#xff1f;兼容性测试侧重哪些方面&#xff1f; 参考答案&#xff1a; 兼容测试主要是检查软件在不同的硬件平台、软件平台上是否可以正常的运行&#xff0c;即是通常说的软件的可移植性。 兼容的类型&#xff0c;如果细分的话&#xff0c;有平台的兼容…

Numpy数组中数据的排序【sort(),argsort()与 lexsort()】 (第13讲)

Numpy数组中数据的排序【sort(),argsort()与 lexsort()】 (第13讲)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔…

【C++ 程序设计入门基础】- 第3节-循环结构02

目录 while 语句 案例 while 循环 输入一个整数 n &#xff0c;输出 1~n 的所有整数。 查看运行结果&#xff1a; while 语句结构解析 do while 语句 案例 do while 循环 输入一个整数n&#xff0c;输出1&#xff5e;n的所有整数。 查看运行结果 while、do while的区别 …

SpringSecurity6 | 登录成功后的JSON处理

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; Java从入门到精通 ✨特色专栏&#xf…

Android:java.lang.SecurityException: Provider must not be exporte

java.lang.SecurityException: Provider must not be exporte 解决方案 首先在AndroidManifest.xml中添加provider android:authorities&#xff1a; 是用来标识provider的唯一标识&#xff0c;在同一部手机上一个"authority"串只能被一个app使用&#xff0c;冲突的话…

架构LNMP

目录 1.安装Nginx服务 2.安装 MySQL 服务 3.安装配置 PHP 解析环境 4.部署 Discuz&#xff01;社区论坛 Web 应用 1.安装Nginx服务 实验准备 systemctl stop firewalld systemctl disable firewalld setenforce 0 安装依赖包 yum -y install pcre-devel zlib-devel gcc…

Jmeter测试实践:文件下载接口

一 Jmeter步骤 1.打开jmeter4.0&#xff0c;新建测试计划&#xff0c;添加线程组。根据实际情况配置线程属性。 2.添加HTTP请求。根据接口文档进行配置。 Basic部分修改如下&#xff0c;Advanced部分保持默认。这里的参数id是文件的id&#xff0c;我进行了参数化&#xff0c…

Redis哨兵(sentinel)

文章目录 简介搭建框架具体步骤主要文件参数开始配置 案例分析原有的master挂了 哨兵运行流程和选举原理主观下线客观下线(Objectively Down)选举出领导者哨兵(哨兵中选出兵王) 选新的master使用建议 简介 将某一个从库转换为新主库&#xff0c;继续对外服务将某一个从库转换为…

Java - HashMap

数组和链表 数组&#xff1a; 存储区间是连续&#xff0c;且占用内存严重&#xff0c;空间复杂也很大&#xff0c;时间复杂为O&#xff08;1&#xff09; 优点&#xff1a;是随机读取效率很高&#xff0c;原因数组是连续&#xff08;随机访问性强&#xff0c;查找速度快&#x…

Qt6.5类库实例大全:Qt Creator快速入门

哈喽大家好&#xff0c;我是20YC编程小二&#xff01;扫码关注公众号&#xff0c;现在可免费领取《C程序员》在线视频教程哦&#xff01;#下面开始今天内容# 1. Qt Creator介绍 Qt Creator是一个轻量级的跨平台集成开发环境(IDE)&#xff0c;专为使用Qt框架进行应用程序开发而…

NAND闪存市场2023年Q3增长2.9%,Q4有望激增20%

TrendForce报告显示&#xff0c;NAND闪存市场在2023年第三季度出现了关键转折&#xff0c;主要由三星的战略性减产决定驱动。最初&#xff0c;市场对终端用户需求的不确定性以及对平淡旺季的担忧导致买家采取保守的方法&#xff0c;库存低、采购慢。然而&#xff0c;随着三星等…

华为新款笔记本搭载5nm麒麟芯片,来源成谜,可能让大家失望了~

近日&#xff0c;华为公司悄悄推出了一款基于国产技术打造的全新商用笔记本——华为擎云L540。目前&#xff0c;华为擎云L540在京东平台悄然上线的&#xff0c;尚未在华为官方渠道公开售卖。华为擎云L540搭载了麒麟9006C处理器&#xff0c;采用先进的5nm制程工艺&#xff0c;8 …

openGauss学习笔记-150 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_backup

文章目录 openGauss学习笔记-150 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_backup150.1 背景信息150.2 前提条件150.3 语法150.4 参数说明150.5 示例 openGauss学习笔记-150 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_backup 150.1 背景信息 openGaus…

错题总结(四)

1.【一维数组】输入10个整数&#xff0c;求平均值 编写一个程序&#xff0c;从用户输入中读取10个整数并存储在一个数组中。然后&#xff0c;计算并输出这些整数的平均值。 int main() {int arr[10];int sum 0;for (int n 0; n < 10; n){scanf("%d", &arr…

[完美解决]Accelerate设置单卡训练报错,成功设置单卡训练

报错内容 ValueError: Less than two GPU ids were configured and tried to run on on multiple GPUs. Please ensure at least two are specified for --gpu_ids, or use --gpu_idsall. ValueError:配置了少于两个GPU id&#xff0c;并试图在多个GPU上运行。请确保为——gpu…

小黑子——springBoot基础

springBoot简单学习 一、SpringBoot简介1.1 springBoot快速入门1.1.1 开发步骤1.1.2 对比1.1.3 官网构建工程1.1.3 SpringBoot工程快速启动 1.2 springBoot概述1.2.1 起步依赖I. 探索父工程II. 探索依赖III. 小结 1.2.2 程序启动1.2.3 切换web服务器-jetty 二、配置文件2.1 配置…

scala变量与变量类型

1.6 变量与类型&#xff08;重点&#xff09;1.6.1 变量推断1.6.2 多变量定义1.6.3 var和val的区别 1.6.3.1 是否可变 1.6.3.2 延迟加载 1.6 变量与类型&#xff08;重点&#xff09; val修饰的变量&#xff0c;相当于Java中final修饰的变量; // 定义常量s1&#xff0c;使用…

[每周一更]-(第76期):Go源码阅读与分析的方式

读源码可以深层理解Go的编写方式&#xff0c;理解作者们的思维方式&#xff1b;也有助于对Go语法用法深刻的理解&#xff0c;我们从这一篇说一下如何读源码&#xff0c;从哪些源码着手&#xff0c;从 简单到深入的方式学习源码&#xff1b; 学习源码也是一个修炼过程&#xff0…