接口返回响应,统一封装(ResponseBodyAdvice + Result)(SpringBoot)
需求
接口的返回响应,封装成统一的数据格式,再返回给前端。
依赖
对于SpringBoot项目,接口层基于 SpringWeb,也就是 SpringMVC。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
说明
为了使接口的返回结果数据更加规范化,便于接口测试和前端处理,需要以统一的格式来返回数据;
为了不在每一个接口里面,都写一段返回数据封装的代码,将数据封装的逻辑提取出来,使用切面(AOP)原理,统一对数据进行封装。
如上,涉及到两个问题:
- 定义:响应实体的数据结构;
- 响应数据统一封装;
下面,我们分别来介绍这两个问题如何处理。
响应实体的数据结构
数据结构
返回响应,统一封装实体,数据结构如下:

代码
package com.example.core.model;import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;/*** 返回响应,统一封装实体** @param <T> 数据实体泛型*/
@Getter
@ToString
@EqualsAndHashCode
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Schema(name = "返回响应", description = "返回响应,统一封装实体")
public class Result<T> {@Schema(description = "用户提示", example = "操作成功!")private String userMessage;/*** 错误码<br>* 调用成功时,为 null。<br>* 示例:A0211*/@Schema(description = "错误码")private String errorCode;/*** 错误信息<br>* 调用成功时,为 null。<br>* 示例:"用户输入密码错误次数超限"*/@Schema(description = "错误信息")private String errorMessage;/*** 数据实体(泛型)<br>* 当接口没有返回数据时,为 null。*/@Schema(description = "数据实体(泛型)")private T data;public static <T> Result<T> success(T data) {return new Result<>("操作成功!", null, null, data);}public static <T> Result<T> fail(String userMessage, String errorCode, String errorMessage) {return new Result<>(userMessage, errorCode, errorMessage, null);}}
特别说明:不需要表示成功或失败的字段
在本处的数据结构中,没有一个专门用来表示接口请求成功或失败的字段(比如:success 或 code)。
推荐的做法是:使用 HTTP状态码表示请求是否成功;最简单的模型是,当状态码为200时,表示成功;当状态码为 3xx,4xx,5xx 时,代表请求失败。
HTTP的状态码,已经清晰的描述了请求的响应状态(成功/失败)。
复杂模型中,http状态码还包含请求成功的类型和失败的原因。
复杂模型中,成功的类型:
| HTTP状态码 | 含义 |
|---|---|
| 200 OK | 请求成功 |
| 201 Created | 新增成功 |
| 202 Accepted | 成功,异步任务已经接收 |
成功的类型:
| HTTP状态码 | 含义 |
|---|---|
| 400 Bad Request | 失败,客户端请求错误(比如,参数传递错误) |
| 401 Unauthorized | 失败,未登录 |
| 403 Forbidden | 失败,未授权 |
响应统一封装
响应统一封装:基于 ResponseBodyAdvice 。
基于面相切面编程(AOP)原理,每个接口方法调用成功后,在返回给客户端前,会进行指定的处理,这里是响应数据统一封装成指定的格式;其实也可以做其他的事情,比如 加密。
代码
package com.example.core.advice;import com.example.core.model.Result;
import com.example.core.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;/*** 响应统一封装* <p>* 将响应数据,封装成统一的数据格式。* <p>* 通过本处理器,将接口方法返回的数据,统一封装到 Result 的 data 字段中,如果接口方法返回为 void,则 data 字段的值为 null。*/
@Slf4j
@RestControllerAdvice(basePackages = "com.example.web")
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {/*** 此组件是否支持给定的控制器方法返回类型和选定的 {@code HttpMessageConverter} 类型。** @return 如果应该调用 {@link #beforeBodyWrite} ,则为 {@code true};否则为false。*/@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {// 返回类型不为Result,才需要封装return returnType.getParameterType() != Result.class;}/*** 统一封装返回响应数据*/@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,ServerHttpResponse response) {// 数据封装为Result:将接口方法返回的数据,封装到 Result.data 字段中。Result<Object> result = Result.success(body);// 返回类型不是 String:直接返回if (returnType.getParameterType() != String.class) {return result;}// 返回类型是 String:不能直接返回,需要进行额外处理// 1. 将 Content-Type 设为 application/json ;返回类型是String时,默认 Content-Type = text/plainHttpHeaders headers = response.getHeaders();headers.setContentType(MediaType.APPLICATION_JSON);// 2. 将 Result 转为 Json字符串 再返回// (否则会报错 java.lang.ClassCastException: com.example.core.model.Result cannot be cast to java.lang.String)return JsonUtil.toJson(result);}}
补充说明
需要注意两点:
- 返回类型不为 Result,才需要封装;
- 返回类型是 String,需要进行额外处理,不能直接返回,否则会报错。
如果返回类型是 Result 也封装,就会使得接口返回中多一层 Result 嵌套;
SpringWeb的接口如果返回值为String类型,默认 Content-Type = text/plain,需要手动设置为 application/json;
返回类型为String时,接口必须返回String,否则会报错 ClassCastException,所以需要将封装好的Result 转换成JSON字符串后返回;
测试
代码
package com.example.web.exception.controller;import com.example.core.log.annotation.ApiLog;
import com.example.core.model.PageQuery;
import com.example.web.exception.query.UserQuery;
import com.example.web.model.vo.UserVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;@Slf4j
@RestController
@RequestMapping("exception")
@Tag(name = "异常统一处理")
public class ExceptionController {@ApiLog@GetMapping(path = "users")@Operation(summary = "查询用户列表", description = "测试:BindException。参数校验异常:Get请求,Query参数,以对象的形式接收。")public List<UserVO> listUsers(@Valid UserQuery userQuery, PageQuery pageQuery,HttpServletRequest request, HttpServletResponse response, HttpSession session) {log.info("查询用户列表。userQuery={},pageQuery={}", userQuery, pageQuery);String queryName = userQuery.getName();String queryPhone = userQuery.getMobilePhone();return listMockUsers().stream().filter(user -> {boolean isName = true;boolean isPhone = true;if (StringUtils.hasText(queryName)) {isName = user.getName().contains(queryName);}if (StringUtils.hasText(queryPhone)) {isPhone = user.getMobilePhone().contains(queryPhone);}return isName && isPhone;}).collect(Collectors.toList());}private List<UserVO> listMockUsers() {List<UserVO> list = new ArrayList<>();UserVO vo = new UserVO();vo.setId("1234567890123456789");vo.setName("张三");vo.setMobilePhone("18612345678");vo.setEmail("zhangsan@qq.com");vo.setBeginTime(new Date());vo.setEndTime(new Date());vo.setBeginDate(new Date());vo.setEndDate(new Date());list.add(vo);UserVO vo2 = new UserVO();vo2.setId("1234567890123456781");vo2.setName("李四");vo2.setMobilePhone("13412345678");vo2.setEmail("lisi@example.com");vo2.setBeginTime(new Date());vo2.setEndTime(new Date());vo2.setBeginDate(new Date());vo2.setEndDate(new Date());list.add(vo2);return list;}}
效果

相关文章:
接口返回响应,统一封装(ResponseBodyAdvice + Result)(SpringBoot)
需求 接口的返回响应,封装成统一的数据格式,再返回给前端。 依赖 对于SpringBoot项目,接口层基于 SpringWeb,也就是 SpringMVC。 <dependency><groupId>org.springframework.boot</groupId><artifactId&g…...
苹果cms模板MXone V10.7魔改版源码 全开源
苹果cms模板MXone V10.7魔改版源码 全开源 苹果cms模板MXone魔改版短视大气海报样式 安装模板教程说明: 1、将模板压缩包上传到苹果CMS程序/template下解压 2、网站模板选择mxone 模板目录填写html 3、网站模板选择好之后一定要先访问前台,然后再进…...
ArcGIS笔记13_利用ArcGIS制作岸线与水深地形数据?建立水动力模型之前的数据收集与处理?
本文目录 前言Step 1 岸线数据Step 2 水深地形数据Step 3 其他数据及资料 前言 在利用MIKE建立水动力模型(详见【MIKE水动力笔记】系列)之前,需要收集、处理和制作诸多数据和资料,主要有岸线数据、水深地形数据、开边界潮位驱动数…...
一些k8s集群操作命令
参考: 【K8S系列】Pod重启策略及重启可能原因_k8s查看pod重启原因-CSDN博客 #查看加入集群命令 kubeadm token create --print-join-command #kubeadm重置k8s kubeadm reset -f ipvsadm --clear systemctl stop kubelet rm -rf /etc/kubernetes/* reboot …...
pycharm运行R语言脚本(win10环境下安装)
文章目录 简介1. pycharm安装插件2. 安装R语言解释器2.1下载安装包2.2具体安装过程 3.编辑环境变量4 检验是否安装成功:5.安装需要的library6.pycharm中配置安装好的R语言解释器 简介 pycharm 安装 R language for Intellij R language for Intellij 是一个插件&am…...
Java进击框架:Spring-Test(六)
Java进击框架:Spring-Test(六) 前言单元测试模拟对象 集成测试上下文管理和缓存事务管理集成测试的支持类执行SQL脚本WebTestClientMockMvc JDBC测试支持其它注释 前言 Spring团队提倡测试驱动开发(TDD)。Spring团队发现,控制反转…...
微软:Octo Tempest是最危险的金融黑客组织之一
导语 最近,微软发布了一份关于金融黑客组织Octo Tempest的详细报告。这个组织以其高级社交工程能力而闻名,专门针对从事数据勒索和勒索软件攻击的企业。Octo Tempest的攻击手段不断演变,目标范围也不断扩大,成为了电缆电信、电子邮…...
JS加密/解密之逻辑运算符加密进阶篇
前言 前篇给大家介绍了运算符不为人知的基础知识。他们的各种表达形式,今天我们从这个基础上,继续进一步告诉大家,如何对字符串进行加密处理。还是那句话,技术人不废话,直接晒代码。 示例源代码 // 字符串加密示…...
【ROS入门】机器人系统仿真——URDF集成Gazebo
文章结构 URDF与Gazebo基本集成流程创建功能包编写URDF或Xacro文件启动 Gazebo 并显示机器人模型 URDF集成Gazebo相关设置collisioninertial颜色设置 URDF集成Gazebo实操编写封装惯性矩阵算法的 xacro 文件复制相关 xacro 文件,并设置 collision inertial 以及 colo…...
互联多区域电网的负荷频率控制研究
摘要 电力行业的发展程度是衡量国民经济水平以及国家安全保障的一项重要指标。多区域负荷频率控制系统作为现代电力系统发展的重要趋势,在可靠性、经济性和稳定性上都具备一定的优势。保证系统稳定和输出电能的质量是电网运行的关键。电力系统输出电能质量的优劣取决…...
【java学习—九】模板方法(TemplateMethod)设计模式(4)
文章目录 1. 在java中什么是模板2. 模板方法设计解决了什么问题?3. 代码化理解 1. 在java中什么是模板 抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留…...
【MyBatis Plus】初识 MyBatis Plus,在 Spring Boot 项目中集成 MyBatis Plus,理解常用注解以及常见配置
文章目录 一、初识 MyBatis Plus1.1 MyBatis Plus 是什么1.2 MyBatis Plus 和 MyBatis 的区别 二、在 Spring Boot 项目中集成 MyBatis Plus2.1 环境准备2.2 引入 MyBatis Plus 依赖2.3 定义 Mapper2.4 测试 MyBatis Plus 的使用 三、MyBatis Plus 常用注解3.1 为什么需要注解3…...
Centos7 安装和配置 Redis 5 教程
在Centos上安装Redis 5,如果是 Centos8,那么 yum 仓库中默认的 redis 版本就是 5,直接 yum install 即可。但如果是 Centos7,yum 仓库中默认的 redis 版本是 3 系列,比较老: 通过 yum list | grep redis 命…...
使用 RAG、Langchain 和 Streamlit 制作用于文档问答的 AI 聊天机器人
在这篇文章中,我们将探索创建一个简单但有效的聊天机器人,该机器人根据上传的 PDF 或文本文件的内容响应查询。该聊天机器人使用 Langchain、FAISS 和 OpenAI 的 GPT-4 构建,将为文档查询提供友好的界面,同时保持对话上下文完整。…...
论文阅读——RoBERTa
一、LM效果好但是各种方法之间细致比较有挑战性,因为训练耗费资源多、并且在私有的不同大小的数据集上训练,不同超参数选择对结果影响很大。使用复制研究的方法对BERT预训练的超参数和数据集的影响细致研究,发现BERT训练不够,提出…...
springboot项目打jar包,运行时提示jar中没有主清单属性
可能性一: 没有在pom中加入maven插件 在pom中加入下方代码即可。 <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</ve…...
【Codeforces】 CF79D Password
题目链接 CF方向 Luogu方向 题目解法 看到区间异或,一个经典的套路是做差分,我们即在 l l l 处异或一次,在 r 1 r1 r1 处异或一次,然后前缀和起来 于是我们可以将问题转化成:有一个序列初始全 0 0 0,…...
叛乱沙漠风暴server安装 ubuntu 22.04
最新版沙暴已经不支持centos了,还是使用ubuntu比较顺利 官方文档: https://sandstorm-support.newworldinteractive.com/hc/en-us/articles/360049211072-Server-Admin-Guide // 安装steamcmd依赖 sudo add-apt-repository multiverse sudo apt inst…...
ES6中的新增属性——解构赋值
首先我们要创建一个假数据,我们现在要取出user中的id和名称,如下: let user JSON.parse(sessionStorage.getItem(userInfo)) let id user.id; let name user.name; 非常的麻烦,我们需要一项一项的获取,这个时候可…...
行业追踪,2023-10-27
自动复盘 2023-10-27 凡所有相,皆是虚妄。若见诸相非相,即见如来。 k 线图是最好的老师,每天持续发布板块的rps排名,追踪板块,板块来开仓,板块去清仓,丢弃自以为是的想法,板块去留让…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
