《程序猿入职必会(6) · 返回结果统一封装》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

  • 写在前面的话
  • 返回结果统一封装
      • 定义一个返回值VO类
      • 处理返回值的几种方案
      • HandlerMethodReturnValueHandler
  • 总结陈词

CSDN.gif

写在前面的话

本系列博文已连载到第六篇,通过前五篇博文,我们已完成了教师信息的基础增删改查功能,在介绍其他知识专栏之前,先来谈一谈CURD页面的规范问题。
前后端分离的开发模式中,后端程序猿有必要与前端程序猿约定一个相对于规范的返回格式,如果仅仅返回数据,有点像裸奔。因此,后端项目需要对返回结果进行统一封装返回,前端也需要封装请求后置拦截器对返回结果处理。
按业内约定俗成的规范,返回结果至少包含:code 状态码、data 数据、msg 消息内容、error 错误内容。
上述只是基础部分,实际开发中,可能还包含:timestamp 时间戳、requestId 日志ID等等。
加油,程序猿,保持住Tempo,开干,玩的就是真实!

关联文章:
《程序猿入职必会(1) · 搭建拥有数据交互的 SpringBoot 》
《程序猿入职必会(2) · 搭建具备前端展示效果的 Vue》
《程序猿入职必会(3) · SpringBoot 各层功能完善 》
《程序猿入职必会(4) · Vue 完成 CURD 案例 》
《程序猿入职必会(5) · CURD 页面细节规范 》


返回结果统一封装

定义一个返回值VO类

这个是考虑统一封装的第一步,很简单,仅提供参考。

@Data
public class ResultModel<T> {/*** 成功编码*/public static final String SUCCESS_CODE = ResponseCodeEnum.SUCCESS.getCode();/*** 异常编码*/public static final String ERROR_CODE = ResponseCodeEnum.EX_ERROR.getCode();/*** 响应编码*/private String code = SUCCESS_CODE;/*** 响应数据*/private T data;/*** 响应信息*/private String message = "";/*** 异常详细信息*/private String error = "";/*** 返回成功* @param data* @param <T>* @return*/public static <T> ResultModel<T> success(T data) {return success(data, "");}/*** 返回成功* @param data* @param message* @param <T>* @return*/public static <T> ResultModel<T> success(T data, String message) {return new ResultModel(SUCCESS_CODE, data, message);}/*** 返回失败* @param code* @param message* @param error* @return*/public static ResultModel fail(String code, String message, String error) {return new ResultModel(code, null, message, error);}public static ResultModel fail(ResponseCodeEnum code) {return new ResultModel(code.getCode(), null, code.getMessage(), code.getMessage());}public static ResultModel fail(ResponseCodeEnum code, String error) {return new ResultModel(code.getCode(), null, code.getMessage(), error);}public static ResultModel fail(String error) {return new ResultModel(ResponseCodeEnum.EX_ERROR.getCode(), null, error, error);}public boolean isSuccess() {return Objects.equals(this.code, ResponseCodeEnum.SUCCESS.getCode());}public ResultModel() {}public ResultModel(String code, T data, String message) {this.code = code;this.data = data;this.message = message;}public ResultModel(String code, T data, String message, String error) {this.code = code;this.data = data;this.message = message;this.error = error;}
}

也可以定义一个状态枚举类,非必须:

public enum ResponseCodeEnum {/*** 调用成功*/SUCCESS("00000", "调用成功"),/*** 系统异常*/EX_ERROR("EX00000", "系统异常"),/*** 参数不合法*/EX_PARAM("EX00001", "参数不合法"),/*** 接口调用异常*/EX_REQUEST("EX00002", "接口调用异常"),/*** 接口返回错误*/EX_RESULT("EX00003", "接口返回错误"),/*** 微信接口异常*/EX_WECHAT("EX00004", "微信接口异常"),/*** 令牌为空*/EX_TOKEN_EMPTY("EX00005", "令牌为空"),/*** 令牌无效*/EX_TOKEN_INVALID("EX00006", "令牌无效"),/*** 网站来源无效*/EX_REFERER_INVALID("EX00007", "网站来源无效"),/*** 404*/EX_PAGE_404("EX404", "页面地址无效");private String code;private String message;ResponseCodeEnum(String code, String message) {this.code = code;this.message = message;}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}@Overridepublic String toString() {return "[" + this.code + "]" + this.message;}
}

处理返回值的几种方案

SpringBoot 针对 返回值处理有多种方案,相关关键词诸如 ResponseBodyAdvice、MessageConverters、 HandlerMethodReturnValueHandler。

【三者比较】
1、ResponseBodyAdvice(响应拦截器):
作用:ResponseBodyAdvice 允许你在将响应体写入 HTTP 响应之前拦截和修改它。它提供了一种全局定制响应处理逻辑的方式,适用于 Spring MVC 或 Spring WebFlux 应用程序。
工作原理:ResponseBodyAdvice 接口定义了在响应体写入之前将被调用的方法,你可以在这些方法中检查或修改响应体、方法返回类型、请求和其他上下文信息。这使得你可以根据应用程序的需求对响应进行定制化处理。
示例:你可以使用 ResponseBodyAdvice 添加全局的响应头信息、对返回数据进行统一的格式化等。
2、MessageConverters(消息转换器):
作用:MessageConverters 负责将 Controller 方法的返回值转换为 HTTP 响应的内容,以及将请求的内容转换为 Controller 方法的参数。
工作原理:消息转换器负责将 Java 对象与特定的媒体类型之间进行转换,例如 JSON、XML、HTML 等。它可以根据请求的 Content-Type 头信息和方法的返回值类型,选择适当的转换器来进行转换。
示例:你可以使用 MappingJackson2HttpMessageConverter 将 Java 对象转换为 JSON 格式的响应体,或将请求体中的 JSON 数据转换为 Java 对象。
3、HandlerMethodReturnValueHandler(返回值处理器):
作用:HandlerMethodReturnValueHandler 用于处理方法的返回值,将其转换为合适的响应内容。它负责将方法的返回值转换为 HTTP 响应体的内容。
工作原理:HandlerMethodReturnValueHandler 负责将方法的返回值转换为特定的响应内容,例如对象、字符串、视图等。它可以根据返回值的类型和请求的信息来选择适当的处理方式。
示例:你可以使用 ViewMethodReturnValueHandler 将返回值转换为视图,HttpEntityMethodProcessor 将返回的 HttpEntity 对象转换为 HTTP 响应。
总的来说,ResponseBodyAdvice 允许你在响应体写入之前对其进行全局性的处理,MessageConverters 负责将 Java 对象与特定的媒体类型之间进行转换,而 HandlerMethodReturnValueHandler 用于根据方法的返回值类型和请求信息将其转换为合适的响应内容。
关于顺序,HandlerMethodReturnValueHandler 负责处理方法的返回值,ResponseBodyAdvice 在写入响应体之前提供额外的处理机会,而 MessageConverters 则负责将处理过的结果转换为特定的媒体类型。因此,它们的执行顺序是:先执行 HandlerMethodReturnValueHandler,然后是 ResponseBodyAdvice,最后是 MessageConverters。

【方案点评】
三种处理方案各有千秋,本文选用 HandlerMethodReturnValueHandler 展开介绍,顺便可以介绍一下自定义注解的结合使用。
当然,博主所在公司进行框架封装时,采用 ResponseBodyAdvice,并未采用 HandlerMethodReturnValueHandler,原因是,自定义 HandlerMethodReturnValueHandler 意味着要替换 RequestResponseBodyMethodProcessor, SpringMVC 的若干默认定制功能就消失了,可能导致非意料的情况,具体后续再专栏介绍。


HandlerMethodReturnValueHandler

废话不多说,直接上代码。

Step1、定义两个自定义注解,放着备用
后续需要进行返回值封装处理的控制器,就使用@ResultController 注解即可。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResultModelAnnotation {
}@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RestController
@ResultModelAnnotation
public @interface ResultController {
}

Step2、自定义 HandlerMethodReturnValueHandler
实现 HandlerMethodReturnValueHandler 接口,实现 supportsReturnType 和 handleReturnValue 方法。
supportsReturnType 代表生效时机,下方意思是当类或者方法包含 ResultModelAnnotation 注解的时生效。
handleReturnValue 代表返回值处理逻辑,其实就是封装成 ResultModel 格式,再 response 出去。

public class ResultModelHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {@Overridepublic boolean supportsReturnType(MethodParameter returnType) {return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResultModelAnnotation.class) || returnType.hasMethodAnnotation(ResultModelAnnotation.class));}@Overridepublic void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {ResultModel<Object> resultModel;ApiOperation methodAnnotation = returnType.getMethodAnnotation(ApiOperation.class);String message = "";if (methodAnnotation != null) {message = methodAnnotation.value() + "成功";}if (returnValue instanceof ResultModel) {resultModel = (ResultModel<Object>) returnValue;if (!resultModel.isSuccess()) {resultModel.setMessage(message + "error");}} else {resultModel = ResultModel.success(returnValue, message);}mavContainer.setRequestHandled(true);HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);// 设置状态码response.setStatus(HttpStatus.OK.value());response.setHeader("result-model", "true");// 设置ContentTyperesponse.setContentType(MediaType.APPLICATION_JSON_VALUE);// 避免乱码response.setCharacterEncoding("UTF-8");PrintWriter writer = null;try {writer = response.getWriter();writer.write(JSON.toJSONString(resultModel, SerializerFeature.WriteMapNullValue));writer.flush();} catch (IOException ex) {ex.printStackTrace();} finally {if (writer != null) {writer.close();}}}
}

Step3、自定义RequestMappingHandlerAdapter
继承 RequestMappingHandlerAdapter,重写 afterPropertiesSet 方法。
逻辑就是将前面自定义的 ResultModelHandlerMethodReturnValueHandler,放到第一位,首发选手。

public class ResultRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {@Overridepublic void afterPropertiesSet() {super.afterPropertiesSet();List<HandlerMethodReturnValueHandler> returnValueHandlers = super.getReturnValueHandlers();ResultModelHandlerMethodReturnValueHandler handler = new ResultModelHandlerMethodReturnValueHandler();List<HandlerMethodReturnValueHandler> list = new ArrayList<>();list.add(handler);list.addAll(returnValueHandlers);super.setReturnValueHandlers(list);}
}

Step4、控制类添加自定义注解
直接用前面博文提到的教师信息控制器,将 @RestController 注解修改为 @ResultController

Tips:若部分接口不需要按这个格式返回,则不需要修改注解。

@ResultController
@Api(value = "ZyTeacherInfoController", tags = {"教师信息表服务"})
@RequestMapping(value = "/zyTeacherInfo")
public class ZyTeacherInfoController extends BaseController {}

Step5、万事俱备,测试一下
启动服务,访问单个教师的接口:http://localhost:8083/zyTeacherInfo/2
输出信息如下,可以看到其格式了,搞定收工!

{"code": "00000","data": {"createdTime": "2024-05-16 20:07:21","modifiedTime": null,"sortNo": null,"stuItem": null,"teaCode": "2","teaConfig": null,"teaImg": null,"teaName": "李老师","teaPhone": null,"teaType": null,"validFlag": "1"},"error": "","message": "获取教师信息表详细信息成功","success": true
}

总结陈词

此篇文章介绍了前后端分离项目中,关于统一返回结果的封装,仅供学习参考。
下一篇文章介绍前端 Axios 插件封装思路,以及对于这一返回封装结果的接受处理。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

CSDN_END.gif

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

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

相关文章

Profinet从站转TCP/IP协议转化网关(功能与配置)

如何将Profinet和TCP/IP网络连接通讯起来呢?近来几天有几个朋友问到这个问题&#xff0c;那么作者在这里统一说明一下。其实有一个不错的设备产品可以很轻易地解决这个问题&#xff0c;名为JM-DNT-PN。接下来作者就从该设备的功能及配置详细说明一下。 一&#xff0c;设备主要…

el-table合计行更新问题

说明&#xff1a;在使用el-table自带的底部合计功能时&#xff0c;初始界面不会显示合计内容 解决方案&#xff1a;使用 doLayout()方法 updated() {this.$nextTick(() > {this.$refs[inventorySumTable].doLayout();});},完整代码&#xff1a; // show-summary&#xff1a…

Bugku的web题目get,post

1.web基础$_GET http://114.67.175.224:17587/ OK明显的代码审计题目。 让我们看看代码&#xff0c;先get获取what参数变量&#xff0c;如果what变量‘flag’&#xff0c;输出flag。 该题为GET传参&#xff0c;可直接在url后面加参数 在url后加上?whatflag 即可获得flag 2…

科普文:科普文:springcloud之-Hystrix服务容错

Hystrix概念 Hystrix 服务容错保护 的概念和说明 这就是大名鼎鼎的&#xff1a;豪猪 豪猪的英文就是&#xff1a;Hystrix&#xff0c;国外一些大牛的程序员在给自己的架构起名字的时候&#xff0c;往往就这么特别。哪天咱们中国人自己也能写出些架构&#xff0c;咱们就按照中…

2024后端开发面试题总结

一、前言 上一篇离职贴发布之后仿佛登上了热门&#xff0c;就连曾经阿里的师兄都看到了我的分享&#xff0c;这波流量真是受宠若惊&#xff01; 回到正题&#xff0c;文章火之后&#xff0c;一些同学急切想要让我分享一下面试内容&#xff0c;回忆了几个晚上顺便总结一下&#…

【VS2019安装+QT配置】

【VS2019安装QT配置】 1. 前言2. 下载visual studio20193. visual studio2019安装4. 环境配置4.1 系统环境变量配置4.2 qt插件开发 5. Visual Studio导入QT项目6. 总结 1. 前言 前期安装了qt&#xff0c;发现creator编辑器并不好用&#xff0c;一点都不时髦。在李大师的指导下&…

C++画蜡烛图

GPT-4o (OpenAI) 在 C 中绘制蜡烛图通常不像在高级语言&#xff08;如 Python&#xff09;中那么简单&#xff0c;因为 C 并没有内置的图形绘制库。然而&#xff0c;您可以使用一些第三方库来完成这项任务&#xff0c;比如使用 Qt 或者 SFML 等图形库。这里我们以 Qt 库为例&a…

PM2 快速上手指南

PM2是 Node.js 的优秀运行时管理工具&#xff0c;专为简化和优化 Node.js 应用程序的生产部署与运行而设计。 PM2 官网链接: https://pm2.keymetrics.io/ 1.PM2 的优势 持续运行&#xff1a;即使应用出错或崩溃&#xff0c;也能自动重启。负载均衡&#xff1a;智能地自动分…

Linux shell编程学习笔记67: tracepath命令 追踪数据包的路由信息

0 前言 网络信息是电脑网络信息安全检查中的一块重要内容&#xff0c;Linux和基于Linux的操作系统&#xff0c;提供了很多的网络命令&#xff0c;今天我们研究tracepath命令。 Tracepath 在大多数 Linux 发行版中都是可用的。如果在你的系统中没有预装&#xff0c;请根据你的…

WordPress插件介绍页源码单页Html

源码介绍 WordPress插件介绍页源码单页Html源码&#xff0c;这是一款产品介绍使用页面&#xff0c;也可以用来做其他软件或者应用介绍下载页&#xff0c;界面简约美观&#xff0c;源码由HTMLCSSJS组成&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器…

合作伙伴中心Partner Center中添加了Copilot预览版

目录 一、引言 二、Copilot 功能概述 2.1 Copilot 简介 2.2 Copilot 的核心功能 2.3 Copilot 的访问和使用 三、Copilot 的使用方法 3.1 Copilot 功能区域 3.2 Copilot 使用示例 3.2.1 编写有效提示 3.2.2 使用反馈循环 四、负责任的人工智能 4.1 Copilot 结果的可…

UE4如何直接调试Game

某些功能在编辑器里不好调试&#xff0c;例如Pak&#xff0c;就需要直接调试 Game&#xff0c;做法是选择 Game&#xff0c;不要选择Client&#xff0c;加断点&#xff0c;然后点击 Debug 就好了。 断点调试成功&#xff1a; 同时看到界面&#xff1a;

PCIe总线-Linux内核PCIe软件框架分析(十一)

1.简介 Linux内核PCIe软件框架如下图所示&#xff0c;按照PCIe的模式&#xff0c;可分为RC和EP软件框架。RC的软件框架分为五层&#xff0c;第一层为RC Controller Driver&#xff0c;和RC Controller硬件直接交互&#xff0c;不同的RC Controller&#xff0c;其驱动实现也不相…

【React】详解 React Hooks 使用规则

文章目录 一、Hooks 的基本原则1. 只在最顶层调用 Hooks2. 只在 React 函数组件和自定义 Hooks 中调用 Hooks 二、常见 Hooks 及其使用规则1. useState2. useEffect3. useContext4. useReducer5. useMemo6. useCallback 三、常见错误及其解决方案1. 在条件语句中调用 Hooks2. 在…

RK3568 Linux 平台开发系列讲解(内核入门篇):从内核的角度看外设芯片的驱动

在嵌入式 Linux 开发中,外设芯片的驱动是实现操作系统与硬件之间交互的关键环节。对于 RK3568 这样的处理器平台,理解如何从内核的角度构建和管理外设芯片的驱动程序至关重要。 1. 外设驱动的基础概念 外设驱动(Device Driver)是操作系统与硬件设备之间的桥梁。它负责控…

机器学习(二十一):错误分析、创造数据和迁移学习

一、错误分析 假设交叉验证集一共有500个数据点&#xff0c;模型拟合结果中&#xff0c;有100个数据点有误。 错误分析就是&#xff0c;手动地分析这100个错误数据&#xff08;或随机选择一些错误数据&#xff09;&#xff0c;根据它们的共同属性、共同特征分类&#xff0c;然…

在QT中使用多线程并发服务器(C++)

什么是多线程并发服务器&#xff1f;在QT里如何使用多线程并发服务器呢&#xff1f; 多线程并发服务器是一种网络服务器设计&#xff0c;它能够同时处理多个客户端的请求。在多线程服务器中&#xff0c;主线程负责监听和接受来自客户端的连接请求&#xff0c;每当有一个新的连…

C++(week13): C++基础: 标准模板库 STL

文章目录 零、标准模板库 STL一、容器 (Container)1.序列式容器(1)vector2.五种遍历10.vector的迭代器失效问题 (2)deque(3)list 2.关联式容器(1)set4.set的查找(2)find() 8.set中存储自定义类型&#xff1a;三种方法 (2)multiset7.multiset的特殊操作&#xff1a;bound系列函数…

【前端 15】Vue生命周期

Vue生命周期 在Vue.js中&#xff0c;了解组件的生命周期对于开发者来说是至关重要的。Vue的生命周期指的是Vue实例从创建到销毁的一系列过程&#xff0c;每个阶段都对应着特定的生命周期钩子&#xff08;或称为生命周期方法&#xff09;&#xff0c;允许我们在不同的时间点加入…

【网络安全】AWS S3 Bucket配置错误导致敏感信息泄露

未经许可&#xff0c;不得转载。 文章目录 前言技术分析正文 前言 AWS&#xff08;Amazon Web Services&#xff09;是亚马逊公司提供的一个安全的云服务平台&#xff0c;旨在为个人、公司和政府机构提供计算能力、存储解决方案、内容交付和其他功能。作为全球领先的云服务提供…