【Java系列】SpringCloudAlibaba统一返回体及全局异常捕获实现

本文将以实际代码展示如何实现SpringCloudAlibaba的统一返回体及全局异常捕获。

作者:后端小肥肠

1. 前言

在构建微服务应用时,统一返回体和异常捕获机制的设计对于保持代码的整洁性和提高服务的可维护性至关重要。特别是在使用 Spring Boot 和 Spring Cloud Alibaba这样的现代开发框架时,这一点显得尤为重要。本文将重点介绍如何在Spring Cloud Alibaba环境中实现统一的响应体和异常处理策略。通过这种方式,无论是在单体应用还是在复杂的微服务架构中,开发者都能保证返回信息的一致性和异常的有效管理。

2. 开发环境搭建

2.1. 所需版本依赖

依赖版本
Spring Boot2.6.3
Spring Cloud

2021.0.1

java1.8以上
Spring Cloud Alibaba2021.0.1.0

2.2. pom依赖  

    <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.3</version><relativePath/></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><spring-cloud.version>2021.0.1</spring-cloud.version><spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version></properties><dependencies><!-- springCloud --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${spring-cloud-alibaba.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.jetbrains</groupId><artifactId>annotations</artifactId><version>17.0.0</version></dependency></dependencies>

3. Spring Cloud统一返回体和异常捕获实现

3.1. Spring Cloud统一返回体实现

微服务项目一般由网关加下游微服务组成,我的习惯是分成网关模块,api模块(存放远程调用方法及公共实体类),common模块(存放一些公共Bean及工具类)及各类业务模块,通常返回体是放在common类中:

SpringCloud项目结构示例

在common中新建response包,将统一返回相关类放入其中即可。

3.1.1. 编写响应状态码枚举
public enum ResponseStatusCodeEnum implements IResponseStatusCode {//服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。SUCCESS(200, "OK"),//用户新建或修改数据成功。UPDATE_RETURN_201(201, "CREATED"),//表示一个请求已经进入后台排队(异步任务)ALL_RETURN_202(202, "Accepted"),//用户删除数据成功。DELETE_RETURN_204(204, "NO CONTENT"),//用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。UPDATE_RETURN_400(400, "INVALID REQUEST"),//用户发出的请求参数有误,服务器没有找到对应资源 这是新加的WRONG_PARAMETER_NOT_FIND_400(400,"请求参数有误,资源不存在"),//401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。ALL_RETURN_401(401, "Unauthorized"),TOKEN_PAST(1401, "身份过期,请求重新登录!"),//403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。ALL_RETURN_403(403, "Forbidden"),//404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。ALL_RETURN_404(404, "NOT FOUND"),//406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。GET_RETURN_406(406, "Not Acceptable"),//410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。GET_RETURN_410(410, "Gone"),//422 Unprocessable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。UPDATE_RETURN_422(422, "Unprocessable entity"),//500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。*/GET_RETURN_500(500, "INTERNAL SERVER ERROR"),CONFLICT_RETURN_409(409,"CONFLICT");private final Integer code;private final String message;ResponseStatusCodeEnum(Integer code, String message) {this.code = code;this.message = message;}@Overridepublic Integer getCode() {return this.code;}@Overridepublic String getMessage() {return this.message;}
}​
3.1.2. 编写响应状态码接口
public interface IResponseStatusCode {/*** 获取响应状态码** @return 响应状态码*/Integer getCode();/*** 获取响应消息** @return 响应消息*/String getMessage();
}
3.1.3. 编写统一返回结构体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResponseStructure<T>{private Integer code;private String status;private String message;private T data;private static final String SUCCESS = "success";private static final String FAIL = "fail";public static <T> ResponseStructure<T> success(T data) {return new ResponseStructure<>(ResponseStatusCodeEnum.SUCCESS.getCode(),SUCCESS,ResponseStatusCodeEnum.SUCCESS.getMessage(),data);}public static <T> ResponseStructure<T> created(String message) {return new ResponseStructure<>(ResponseStatusCodeEnum.UPDATE_RETURN_201.getCode(),SUCCESS,message,null);}public static <T> ResponseStructure<T> unauthorized(String message) {return new ResponseStructure<>(ResponseStatusCodeEnum.ALL_RETURN_403.getCode(),FAIL,message,null);}public static <T> ResponseStructure<T> unauthenticated(String message) {return new ResponseStructure<>(ResponseStatusCodeEnum.ALL_RETURN_401.getCode(),FAIL,message,null);}public static <T> ResponseStructure<T> success(String message, T data) {return new ResponseStructure<>(ResponseStatusCodeEnum.SUCCESS.getCode(),SUCCESS,message,data);}public static <T> ResponseStructure<T> success(Integer code, String message) {return new ResponseStructure<>(code, SUCCESS, message, null);}public static <T> ResponseStructure<T> success(Integer code, String message, T data) {return new ResponseStructure<>(code, SUCCESS, message, data);}public static ResponseStructure<Object> failed() {return new ResponseStructure<>(ResponseStatusCodeEnum.GET_RETURN_500.getCode(),FAIL,ResponseStatusCodeEnum.GET_RETURN_500.getMessage(),null);}public static ResponseStructure<String> failed(String message) {return new ResponseStructure<>(ResponseStatusCodeEnum.GET_RETURN_500.getCode(),FAIL,message,null);}public static ResponseStructure<Object> failed(IResponseStatusCode errorResult) {return new ResponseStructure<>(errorResult.getCode(),FAIL,errorResult.getMessage(),null);}public static ResponseStructure<Object> conflict(String message) {return new ResponseStructure<>(ResponseStatusCodeEnum.CONFLICT_RETURN_409.getCode(),FAIL,message,null);}public static <T> ResponseStructure<T> instance(Integer code, String message, T data) {ResponseStructure<T> responseStructure = new ResponseStructure<>();responseStructure.setCode(code);responseStructure.setMessage(message);responseStructure.setData(data);if (code >= 300) {responseStructure.setStatus(FAIL);} else {responseStructure.setStatus(SUCCESS);}return responseStructure;}public static <T> ResponseStructure<T> instance(Integer code, String message) {ResponseStructure<T> responseStructure = new ResponseStructure<>();responseStructure.setCode(code);responseStructure.setMessage(message);responseStructure.setData(null);if (code >= 300) {responseStructure.setStatus(FAIL);} else {responseStructure.setStatus(SUCCESS);}return responseStructure;}public static <T> ResponseStructure<T> instance(IResponseStatusCode code) {ResponseStructure<T> responseStructure = new ResponseStructure<>();responseStructure.setCode(code.getCode());responseStructure.setMessage(code.getMessage());responseStructure.setData(null);if (code.getCode() >= 300) {responseStructure.setStatus(FAIL);} else {responseStructure.setStatus(SUCCESS);}return responseStructure;}
}
3.1.4. 编写GlobalResponseBodyAdvice
@RestControllerAdvice
public class GlobalResponseBodyAdvice implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(@NotNull MethodParameter returnType,@NotNull Class<? extends HttpMessageConverter<?>> converterType) {GlobalResponse globalResponse = returnType.getMethodAnnotation(GlobalResponse.class);return globalResponse == null || globalResponse.format();}@Overridepublic Object beforeBodyWrite(Object body,@NotNull MethodParameter returnType,@NotNull MediaType selectedContentType,@NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType,@NotNull ServerHttpRequest request,@NotNull ServerHttpResponse response) {// 如果是 actuator 请求,直接返回if (isActuatorRequest(request)) {return body;}/* 如果是 Feign 请求,直接返回*/if (Objects.requireNonNull(request.getHeaders().get("user-agent")).get(0).startsWith("Java")) {return body;}// 以下代码主要解决和 Swagger 的冲突if (body instanceof ResponseStructure || body instanceof Json || body instanceof UiConfiguration ||(body instanceof ArrayList && !((ArrayList<?>) body).isEmpty() &&((ArrayList<?>) body).get(0) instanceof SwaggerResource)) {return body;}ResponseStructure<Object> responseStructure;// 如果是 POST 请求,业务状态码统一设置为 201if ("POST".equals(((ServletServerHttpRequest) request).getServletRequest().getMethod())) {responseStructure = ResponseStructure.created("OK");} else {responseStructure = ResponseStructure.success(null);}// 如果返回值是字符串类型,则用其替换 messageif (body instanceof String) {responseStructure.setData(body);return JSON.toJSONString(responseStructure);}if (body instanceof byte[]) {return body;}responseStructure.setData(body);return responseStructure;}private boolean isActuatorRequest(ServerHttpRequest request) {return ((ServletServerHttpRequest) request).getServletRequest().getRequestURI().endsWith("/actuator");}
}

这个类的主要作用是为应用提供一个统一的响应结构,帮助前端开发者和最终用户更好地理解和处理 API 的响应。通过拦截所有非特殊请求的响应体,并将它们包装成统一的格式,这个实现可以极大地增强 API 的一致性和可维护性。同时,它也处理了一些特殊情况,如避免改变对于特定工具或请求的原始响应(Swagger UI 或 Feign 客户端请求)。

3.1.5. 编写GlobalResponse
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GlobalResponse {boolean format() default true;
}
3.1.6. 效果测试 

1. controller层方法编写,只需要返回实际数据结构即可

2. 返回结构测试

3.2 Spring Cloud异常捕获实现

在common中新建expection包,将异常捕获类类放入其中即可。

3.2.1. 编写ExceptionAdvice
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {@ExceptionHandler(BindException.class)public Object bindException(BindException bindException) {bindException.printStackTrace();String message = Objects.requireNonNull(bindException.getBindingResult().getFieldError()).getDefaultMessage();return ResponseStructure.conflict(message);}@ResponseStatus(value = HttpStatus.CONFLICT)@ExceptionHandler({ValidationException.class})public ResponseStructure<Object> handleValidationException(ValidationException validationException) {validationException.printStackTrace();return (ResponseStructure<Object>) ResponseStructure.conflict(validationException.getMessage());}@ResponseStatus(value = HttpStatus.CONFLICT)@ExceptionHandler({MaxUploadSizeExceededException.class})public ResponseStructure<Object> handleMaxUploadSizeException(MaxUploadSizeExceededException maxUploadSizeExceededException) {maxUploadSizeExceededException.printStackTrace();return (ResponseStructure<Object>) ResponseStructure.conflict("当前文件大小已超过限制大小,请重新上传文件");}/*** 顶级异常捕获,当其他异常无法处理时选择使用*/@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)@ExceptionHandler({Exception.class})public ResponseStructure<String> handle(Exception exception) {exception.printStackTrace();return (ResponseStructure<String>) ResponseStructure.failed(exception.getMessage());}/*** 认证异常捕获*/@ResponseStatus(value = HttpStatus.UNAUTHORIZED)@ExceptionHandler({AuthenticationException.class})public ResponseStructure<String> handleUnAhthorized(AuthenticationException exception) {exception.printStackTrace();return ResponseStructure.instance(ALL_RETURN_401.getCode(), exception.getMessage());}
}

ExceptionAdvice 类通过定义一系列异常处理器,使得应用能够在抛出异常时提供友好的用户反馈,而不是让用户面对不友好的原始错误信息或空白页面。这样的处理机制不仅提高了应用的可用性和可维护性,还能通过日志记录帮助开发者快速定位和解决问题。此外,通过统一异常处理和响应格式,开发者可以更容易地保持前后端的一致性和同步。 

3.2.2. 效果测试

1. 编写异常测试controller层方法

2. 返回结构测试

5. 结语

本文以代码实例展示了如何在SpringCloudAlibaba中实现统一返回及全部异常捕获,如您有更好观点欢迎在评论区留言探讨~

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

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

相关文章

DataX案例,MongoDB数据导入HDFS与MySQL

【尚硅谷】Alibaba开源数据同步工具DataX技术教程_哔哩哔哩_bilibili 目录 1、MongoDB 1.1、MongoDB介绍 1.2、MongoDB基本概念解析 1.3、MongoDB中的数据存储结构 1.4、MongoDB启动服务 1.5、MongoDB小案例 2、DataX导入导出案例 2.1、读取MongoDB的数据导入到HDFS 2…

Re65:读论文 GPT-3 Language Models are Few-Shot Learners

诸神缄默不语-个人CSDN博文目录 诸神缄默不语的论文阅读笔记和分类 论文全名&#xff1a;Language Models are Few-Shot Learners ArXiv网址&#xff1a;https://arxiv.org/abs/2005.14165 2020 NeurIPS&#xff1a;https://papers.nips.cc/paper/2020/hash/1457c0d6bfcb49674…

UVa1313/LA2693 Ghost Busters

UVa1313/LA2693 Ghost Busters 题目链接题意分析AC代码 题目链接 本题是2002年ICPC欧洲区域赛东北欧赛区的G题 题意 有 N ( N ≤ 100 ) N(N≤100) N(N≤100)个鬼&#xff0c;每个鬼是中心在 ( X i , Y i , Z i ) ( 1 ≤ X i , Y i , Z i ≤ 10000 ) (X_i,Y_i,Z_i) (1 ≤ X_i,Y…

Python中的回调函数和C中函数指针什么关系?

你好&#xff0c;我是安然无虞。 Python 回调 在Python中&#xff0c;‘回调函数’ (callback) 是指一个作为参数传递给其它代码的函数。 目的是在后者完成某些操作后调用这个传递进来的函数。 回调允许在执行异步操作或处理事件时通知调用者代码。 回调函数通常用于&#…

【数据结构】-- 单链表 vs 双向链表

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;python从入门到精通&#xff0c;魔法指针&#xff0c;进阶C&#xff0c;C语言&#xff0c;C语言题集&#xff0c;C语言实现游戏&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文…

react query 学习笔记

文章目录 react query 学习笔记查询客户端 QueryClient获取查询客户端 useQueryClient异步重新请求数据 queryClient.fetchQuery /使查询失效 queryClient.invalidateQueries 与 重新请求数据queryClient.refetchQueries 查询 QueriesuseQuery查询配置对象查询的键值 Query Key…

力扣 | 24. 两两交换链表中的节点

两两交换链表中的节点 给定一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后的链表。 你不能只是单纯的改变节点内部的值&#xff0c;而是需要实际的进行节点交换。 输入&#xff1a;head 1->2->3->4->5->NULL 输出&#xff1a;2->1-&g…

互联网轻量级框架整合之MyBatis核心组件

在看本篇内容之前&#xff0c;最好先理解一下Hibernate和MyBatis的本质区别&#xff0c;这篇Hibernate和MyBatis使用对比实例做了实际的代码级对比&#xff0c;而MyBatis作为更适合互联网产品的持久层首选必定有必然的原因 MyBatis核心组件 MyBatis能够成为数据持久层首选框&a…

【博客710】victoriametrics数据写入的pull和push模式以及优缺点

victoriametrics数据写入的pull和push模式以及优缺点 example&#xff1a; curl -d ‘{“metric”:{“name”:“foo”,“job”:“node_exporter”},“values”:[0,1,2],“timestamps”:[1549891472010,1549891487724,1549891503438]}’ -X POST ‘http://localhost:8428/api/v1…

:app debug:armeabi-v7a failed to configure C/C++

报错信息 由于刚换电脑不久&#xff0c;新建native c工程时&#xff0c;出现报错如下&#xff1a; :app debug:armeabi-v7a failed to configure C/C null java.lang.NullPointerExceptionat com.android.build.gradle.tasks.CmakeQueryMetadataGenerator.getProcessBuilder(…

成功转行Python工程师,年薪30W+,经验总结都在这!

都说郎怕入错行&#xff0c;行业对职场人的影响不言而喻。我们身边有很多和自己起点差不多的人&#xff0c;读了差不多的高中&#xff0c;差不多的大学&#xff0c;但是有的人突然一飞冲天&#xff0c;大House、移民、海外置业、全球旅行成了最常见的话题&#xff0c;出入私立医…

多模态 ——LLaVA 集成先进图像理解与自然语言交互GPT-4的大模型

概述 提出了一种大型模型 LLaVA&#xff0c;它使用 GPT-4 生成多模态语言图像指令跟随数据&#xff0c;并利用该数据将视觉和语言理解融为一体。初步实验表明&#xff0c;LLaVA 展示了出色的多模态聊天能力&#xff0c;在合成多模态指令上的表现优于 GPT-4。 在科学质量保证中…