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

news2024/11/27 3:56:39

📢 大家好,我是 【战神刘玉栋】,有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;
    }

    @Override
    public 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 {

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResultModelAnnotation.class) || returnType.hasMethodAnnotation(ResultModelAnnotation.class));
    }

    @Override
    public 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");
        // 设置ContentType
        response.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 {

    @Override
    public 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.coloradmin.cn/o/1952310.html

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

相关文章

SenseVoice 实测,阿里开源语音大模型,识别效果和效率优于 Whisper,居然还能检测掌声、笑声!5分钟带你部署体验

前段时间&#xff0c;带着大家捏了一个对话机器人&#xff1a; 手把手带你搭建一个语音对话机器人&#xff0c;5分钟定制个人AI小助手&#xff08;新手入门篇&#xff09; 其中语音识别&#xff08;ASR&#xff09;方案&#xff0c;采用的是阿里开源的 FunASR&#xff0c;这刚…

【Python机器学习】朴素贝叶斯——条件概率

条件概率 假设现在有一个装了7块石头的罐子&#xff08;3块灰色&#xff0c;4块黑色&#xff09;&#xff0c;如果从中随机取出一块&#xff0c;灰色的可能性就是3/7&#xff0c;黑色的可能性是4/7。我们使用p(gray)来表示取到灰色石头的概率&#xff0c;其概率值可以通过灰色…

Radxa ROCK 5B+开发板基本配置和上手测试

目录 1.ROCK 5B Plus开发板是什么&#xff1f;2.烧录官方系统3.设置ROOT用户4.开发板温度情况5.VNC远程桌面配置6.WIFI模块测速7.M2接口使用注意8.总结 1.ROCK 5B Plus开发板是什么&#xff1f; ROCK 5B&#xff08;即ROCK 5B Plus&#xff0c;本文用ROCK 5B指代&#xff09; …

数据结构-----对列

前言 Hello, 小伙伴们&#xff0c;你们的作者菌又来了&#xff0c;前不久&#xff0c;我们学习了一种数据结构----栈&#xff0c;他特殊的性质使得他在一些数据管理的问题上被广泛的使用&#xff0c;那今天&#xff0c;我们就来学习另一种十分重要的数据结构--对列。 在开始之…

Spring Boot中如何实现全链路调用日志跟踪?

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 引言 在Spring Boot中实现全链路调用日志跟踪&#xff0c;主要依赖于Mapped Diagnostic Context&#xff08;MDC&#xff09;功能。MDC是一种用于在多线程条件下记录日志的功能&#xff0c;它可以看作是与当…

C++ | Leetcode C++题解之第283题移动零

题目&#xff1a; 题解&#xff1a; class Solution { public:void moveZeroes(vector<int>& nums) {int n nums.size(), left 0, right 0;while (right < n) {if (nums[right]) {swap(nums[left], nums[right]);left;}right;}} };

[Python][列表和元组]详细讲解

目录 0.是什么&#xff1f;1.列表1.创建列表2.访问下标3.切片操作4.遍历列表元素5.新增元素6.查找元素7.删除元素8.连接列表 2.关于元组∞.积累 0.是什么&#xff1f; 列表和元组类似C/C中的数组列表&#xff1a;一种在代码中批量表示/保存数据的方式 代码中需要表示的数据特别…

093、Python操作Excel生成统计图表

在Excel里做统计表是我们经常会做的一件事情。我们也可以通过编程的方式操作Excel生成统计图表。 下面是官方的一个很有参考价值的案例&#xff1a; from openpyxl import Workbook from openpyxl.chart import BarChart, Reference from copy import deepcopywb Workbook(w…

C# 使用pythonnet 迁入 python 初始化错误解决办法

pythonnet 从 3.0 版本开始&#xff0c;必须设置Runtime.PythonDLL属性或环境变量 例如&#xff1a; string pathToVirtualEnv ".\\envs\\pythonnetTest"; Runtime.PythonDLL Path.Combine(pathToVirtualEnv, "python39.dll"); PythonEngine.PythonHom…

vscode 调试web后端

1、调试环境配置 一、安装python环境管理器 其中要先在vscode选择对应的python环境&#xff0c;最方便的是按照环境管理器后从中选择。其中在【externsions】里面安装python即可。 如下&#xff1a; 二、编写launch.json文件 其中如下&#xff1a; {// Use IntelliSense …

GraphHopper-map-navi_路径规划、导航(web前端页面版)

文章目录 一、项目地址二、踩坑环境三、问题记录3.1、graphhopper中地图问题3.1.1. getOpacity不存在的问题3.1.2. dispatchEvent不存在的问题3.1.3. vectorLayer.set(background-maplibre-layer, true)不存在set方法3.1.4. maplibre-gl.js.map不存在的问题3.1.5. Uncaught Ref…

AWS-Lambda的使用

介绍 Lambda 是一种无服务器(Serverless), 而且设计成事件驱动的计算服务器. 简单来说, 你可以将你的 code 上传, 当有事件产生(例如cronjob , 或者S3有新的文件被上传上來) , 你的code 就会在瞬间(零点几秒以內)被叫起來执行. 由于你不用管 Server如何维护, 或者自动扩展之类…

数据结构第二讲:顺序表

数据结构第二讲&#xff1a;顺序表 1.线性表2.什么是顺序表3. 静态顺序表4.动态顺序表4.1顺序表基础4.2顺序表的初始化4.3顺序表的销毁4.4顺序表的尾插4.5顺序表的头插4.6顺序表的尾删4.7顺序表的头删4.8顺序表在指定位置之前插入数据4.9顺序表删除指定位置的数据4.10顺序表查找…

ubuntu22.04 安装 NVIDIA 驱动以及CUDA

目录 1、事前问题解决 2、安装 nvidia 驱动 3、卸载 nvidia 驱动方法 4、安装 CUDA 5、安装 Anaconda 6、安装 PyTorch 1、事前问题解决 在安装完ubuntu之后&#xff0c;如果进入ubuntu出现黑屏情况&#xff0c;一般就是nvidia驱动与linux自带的不兼容&#xff0c;可以通…

AMQP-核心概念-4

本文参考以下链接摘录翻译&#xff1a; https://www.rabbitmq.com/tutorials/amqp-concepts 绑定 (Bindings) 绑定是交换机用来将消息路由到队列的规则。为了让一个交换机E将消息路由到队列Q&#xff0c;Q必须绑定到E。绑定可以有一个可选属性routing key&#xff0c;有一些类…

uart开发调试

1. Uart基本框架 1.1概念 通信系统有两种方式&#xff0c;同步通信和异步通信. 同步通信的典型特征&#xff1a;通信双方公用同一个时钟&#xff0c;发送/接受速率完全一致&#xff0c;通信时需要带时钟信号传输. 异步通信的典型特征&#xff1a;通信双方各自具有独立的时钟…

电脑为什么会出现“找不到msvcr120.dll无法执行代码”?如何解决msvcr120.dll丢失错误

在使用电脑的过程中不知带大家有没有遇到过“找不到msvcr120.dll无法执行代码”的错误提示的情况&#xff0c;出现这样的情况大家都有什么解决办法可以解决&#xff1f;有什么办法能够帮助大家修复丢失的msvcr120.dll文件。接下来这篇文章就将教大家修复“找不到msvcr120.dll无…

Vue3-拉开序幕的setup

Vue3 中的 setup 是一个新的配置项&#xff0c;值是一个函数。 export default {name: App,setup: function () {} } </script> 和 Vue2 中的 data 一样&#xff0c;我也可以将 setup 简写成为 export default {name: App,setup() {} } setup函数的使用 与 Vue2 不一样…

刷题计划 day4 【双指针、快慢指针、环形链表】链表下

⚡刷题计划day4继续&#xff0c;可以点个免费的赞哦~ 下一期将会开启哈希表刷题专题&#xff0c;往期可看专栏&#xff0c;关注不迷路&#xff0c; 您的支持是我的最大动力&#x1f339;~ 目录 ⚡刷题计划day4继续&#xff0c;可以点个免费的赞哦~ 下一期将会开启哈希表刷题…

十一、【Python】基础教程-【Python全掌握】六大基础数据类型:布尔类型的终极指南

目录 一、基础类型“布尔型”处理方法 1. 直接赋值和使用 2. 布尔值的逻辑运算 3. 条件语句中的布尔值 4. 布尔值转换 5. 短路逻辑 6. 在循环和迭代中的使用 一、基础类型“布尔型”处理方法 在Python中&#xff0c;布尔类型是一种基本的数据类型&#xff0c;用于表示逻…