聚合统一,SpringBoot实现全局响应和全局异常处理

news2024/11/16 20:48:20

目录

前言

全局响应

数据规范

状态码(错误码)

全局响应类

使用

优化

全局异常处理

为什么需要全局异常处理

业务异常类

全局捕获

使用

优化

总结


前言

        在悦享校园1.0版本中的数据返回采用了以Map对象返回的方式,虽然较为便捷但也带来一些问题。一是在Controller中所有方法均需要实例化一个Map对象。二是当返回数据较多时使用put方式添加信息会容易出现遗漏的问题。在异常处理方面,虽然该版本中对所有异常通过继承RuntimeException的方式来进行封装,但业务异常较多时这一操作就显得冗余,且需要使用上述提到的Map对象包装异常信息。对于以上问题在2.0版本中通过结合SpringBoot来进行优雅的解决。

全局响应

数据规范

一般来讲我们提供给前端接口调用的返回值为如下的JSON格式,其包含结果状态,状态码,响应信息和响应数据。通常会使用@ResponseBody注解配合一个响应类来实现这一功能。但需要注意的是,当方法返回值为String类型时,@ResponseBody注解并不会将其转为JSON格式,需要手动进行转换。

{
    "success": true,
    "code": 0,
    "message": "操作成功",
    "data": "Hello"
}

状态码(错误码)

通过第一步数据规范可知,当接口被调用后会返回对应信息,若调用成功时返回固定的状态码即可,但调用失败时则需要不同的状态码来标识。为解决这个问题这里使用枚举的方式来定义出现异常时的错误信息。(此处的枚举对象名称可以自定义)

 块的错误。(此处的枚举对象名称可以自定义)

@AllArgsConstructor
@Getter
public enum ExceptionCodeEnum {
    // 操作成功
    EC0(0,"操作成功"),
    // 通用模块错误
    EC10000(10000,"系统内部错误"),
    EC10001(10001,"参数错误"),
    EC10002(10002,"资源不存在"),
    
    // 用户模块错误
    EC20000(20000,"用户名已被占用"),
    EC20001(20001,"用户不存在"),
    EC20002(20002,"用户名或密码错误"),
    
    // 其它模块....

    /**
     * 异常代码
     */
    private Integer code;
    /**
     * 描述信息
     */
    private String message;
}

全局响应类

此处创建一个泛型类来实现全局返回信息的格式统一,并且提供返回结果不同状态下的构造方法。

@Data
@Builder
@AllArgsConstructor
public class ResultDataVO<T> {
    /**
     * 调用结果状态
     */
    private Boolean success;
    /**
     * 响应代码
     */
    private Integer code;
    /**
     * 详细信息
     */
    private String message;
    /**
     * 返回数据,数据为空则不返回
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private T data;


    /**
     * 操作成功时返回的数据
     * @param result
     * @param <T>
     * @return
     */
    public static <T> ResultDataVO<T> success(T result) {

        return ResultDataVO.<T>builder()
                .success(true)
                .code(ExceptionCodeEnum.EC0.getCode())
                .message(ExceptionCodeEnum.EC0.getMessage())
                .data(result)
                .build();
    }

    /**
     * 操作失败
     * @param <T>
     * @param exceptionCodeEnum 错误类型枚举
     * @return
     */
    public static <T> ResultDataVO<T> failure(ExceptionCodeEnum exceptionCodeEnum){

        return ResultDataVO.<T>builder()
                .success(false)
                .code(exceptionCodeEnum.getCode())
                .message(exceptionCodeEnum.getMessage())
                .data(null)
                .build();
    }

    /**
     * 操作失败,返回信息
     * @param exceptionCodeEnum 错误信息列表
     * @param result 对应失败信息对象
     * @param <T>
     * @return
     */
    public static <T> ResultDataVO<T> failure(ExceptionCodeEnum exceptionCodeEnum, T result){

        return ResultDataVO.<T>builder()
                .success(false)
                .code(exceptionCodeEnum.getCode())
                .message(exceptionCodeEnum.getMessage())
                .data(result)
                .build();
    }
}

使用

通过以上操作已经实现了一个基础的全局数据响应处理,可以通过如下方式来使用。

@GetMapping("/{id}")
    public ResultDataVO getMsg(@RequestParam(required = false) String name,
                               @Max(value = 10,message = "最大值不能超过10")
                               @PathVariable(name = "id") int uid) {
        String result = "Hello,"+name+" id "+uid;

        return ResultDataVO.success(result);
    }

优化

虽然到这里我们已经基本实现了全局响应,但如果不想要在每个方法中调用ResultDataVO的success方法,可以通过如下方式解决。这里新建处理类实现了ResponseBodyAdvice接口,该接口包含三个方法,supports、beforeBodyWrite、handleEmptyBody。

supports用于指明方法是否需要对进入的方法进行后续包装处理,默认返回true,即对所有方法处理。

beforeBodyWrite用于在控制器方法返回结果后,但在响应体写入之前调用。可以在此方法中修改body对象,如包装、添加元数据等。在该方法中将使用ResultDataVO的success方法进行包装,由此将可以省去在Controller方法中重复调用success方法。

handleEmptyBody用于处理null值,由于ResultDataVO类中已经对null值进行了处理,因此无需重写该方法。

注意:即使在全异处理添加了@RestControllerAdvice注解后,仍需要在Controller类上添加@ResponseBody注解,或者直接使用@RestController注解。因为方法返回值经过invokeAndHandle处理后已经确定,若没有以上注解则默认会按照String类型进行路径映射查找视图,进而返回404错误,而@RestControllerAdvice的相关方法会在invokeAndHandle处理后再进行调用。

@RestControllerAdvice
@Slf4j
public class GlobalExceptionAdvice implements ResponseBodyAdvice<Object> {


    /**
     * json格式化操作
     */
    @Resource
    private ObjectMapper objectMapper;

    /**
    * 是否开启对所有方法的处理,可以在此方法中添加条件使其支持对特定方法的处理。
    */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }
    
    /**
    * 用于在控制器方法返回结果后,但在响应体写入之前调用。可以此处对数据进行包装等操作
    */
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        /**
         * 未被捕获的错误进行拦截
         */
        if(body == null){
            log.error("未处理的异常信息,请检查错误日志");
            return ResultDataVO.failure(ExceptionCodeEnum.EC10000);
        }

        /**
         * 返回类型为String则需要手动序列化
         */
        if (body instanceof String) {
            return objectMapper.writeValueAsString(ResultDataVO.success(body));
        }
        /**
         * 已被包装为全局VO对象直接返回
         */
        if (body instanceof ResultDataVO) {
            return body;
        }
        /**
         * 判断是否为404,500等错误类型
         */
        if (body instanceof LinkedHashMap) {
            LinkedHashMap<String, Object> httpErrorCode = (LinkedHashMap<String, Object>) body;
            Integer code = (Integer) httpErrorCode.get("status");
            String message = (String) httpErrorCode.get("error");
            return new ResultDataVO(false, code, message, null);

        }

        return ResultDataVO.success(body);
    }
}

全局异常处理

为什么需要全局异常处理

使用全局异常处理更加灵活和规范化, 所有错误信息会被封装后返回给前端,避免暴露业务细节。

业务异常类

由于代码在运行过程中会出现异常,通常我们会使用 try...catch 方式来捕获并处理,在此之后我们需要返回错误信息知调用者当前状况。由于我们处理的异常多为RuntimeException的子类,因此可以通过编写一个业务异常类来实现总的异常信息处理,与以往不同在这里并不会为所有的业务异常创建具体的异常类,将使用前文中的错误码来配合使用。

@Getter
public class BusinessException extends RuntimeException {

    /**
     * 错误对象枚举
     */
    private ExceptionCodeEnum codeEnum;
    /**
     * 根据传入的异常枚举解析异常相关信息。
     * @param codeEnum
     */
    public BusinessException(ExceptionCodeEnum codeEnum){
        this.codeEnum = codeEnum;
    }
}

全局捕获

由于已经定义了总的异常处理类,因此在使用时只需要通过抛出 BusinessException 对象即可。但我们需要在代码中写入大量的try-catch语句来捕获处理异常。并且对于错误信息的返回需要符合在全局响应中的数据规范,也就是说需要像全局响应一样统一调用ResultDataVO的failure方法。

在前文创建的GlobalExceptionAdvice类上有一个@RestControllerAdvice注解,该注解将使所有的异常都进入到此处被处理同时也可以用于全局的数据绑定、格式化等。

既然所有的异常都进入该类处理,那么如何处理呢?这里使用@ExceptionHandler注解,使用它可以指定当前方法处理哪种类型的异常,示例代码如下。通过在方法体内调ResultDataVO的failure方法来完成返回数据格式的规范,这里仅列举了三个异常处理,可自行添加更多的异常类。

    /**
     * 数据格式转换错误
     */
    @ExceptionHandler(DataFormatException.class)
    @ResponseBody
    public ResultDataVO dataFormatExceptionHandler(DataFormatException e) {
        log.error("捕获数据格式转换错误异常", e);
        return ResultDataVO.failure(ExceptionCodeEnum.EC10001);
    }

    /**
     * 业务异常捕获
     *
     * @param businessException
     * @return
     */
    @ExceptionHandler(value = BusinessException.class)
    public ResultDataVO handleBusinessException(BusinessException businessException) {
        log.error("捕获业务异常", businessException);
        return ResultDataVO.failure(businessException.getCodeEnum());
    }
    
    /**
     * 系统级异常
     *
     * @param throwable
     */
    @ExceptionHandler(value = Throwable.class)
    public ResultDataVO handleThrowable(Throwable throwable) {
        log.error("捕获系统级异常", throwable);
        return ResultDataVO.failure(ExceptionCodeEnum.EC10000);
    }

使用

通过上述操作可以实现一定程度上对try-catch的消除,示例代码如下

    /**
     * 统一异常处理
     * @return
     */
    @GetMapping("/error")
    public ResultDataVO getError(){
        // try...catch
        int res = 1 / 0;
        return ResultDataVO.success(res);
    }

优化

虽然采用上述方式已经实现了对异常的统一拦截处理并返回,但若异常产生的源头并非Crontroller中出现而是在系统内部时则有可能导致返回结果出现问题,该问题已经在GlobalExceptionAdvice类beforeBodyWrite方法中做了处理,当body对象为空时则仍然会调用ResultDataVO的failure方法。

        // 用于处理未被正常捕获的异常
        if(body == null){
            log.error("未处理的异常信息,请检查错误日志");
            return ResultDataVO.failure(ExceptionCodeEnum.EC10000);
        }
        
        // ......

总结

通过使用@RestControllerAdvice和@ExceptionHandler注解和错误码以及对应处理/响应类即可实现全局异常的统一处理,其中GlobalExceptionAdvice类对全局响应和异常处理做了合并,可按照业务需求自行拆分。

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

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

相关文章

机器人制作开源方案 | 货物输送小车

作者&#xff1a;周展鹏 黄万森 彭军铭 吕会权 聂文俊 单位&#xff1a;柳州工学院 指导老师&#xff1a;蔡洪炜 王一波 1. 场景调研 目前货物输送已成为人们生活中必不可少的部分&#xff0c;加之国内近年来有因快递配送导致疫情迅速传播的事件常有发生&#xff0c;因此在疫…

Mybatis3详解 之 全局配置文件详解

1、全局配置文件 前面我们看到的Mybatis全局文件并没有全部列举出来&#xff0c;所以这一章我们来详细的介绍一遍&#xff0c;Mybatis的全局配置文件并不是很复杂&#xff0c;它的所有元素和代码如下所示&#xff1a; <?xml version"1.0" encoding"UTF-8&…

精通Linux系列第二章:虚拟机安装Linux系统环境教程

文章目录 一、前言二、VMware Fusion安装教程2.1 说说安装虚拟机的好处2.2 安装VMware Fusion 三、环境搭建3.1 各种Linux发行版介绍与iso下载链接3.2 VMware Fusion安装Linux3.2.1 Ubuntu桌面版安装3.2.2 Debian桌面版安装3.2.3 Fedora桌面版安装3.2.4 CentOS桌面版安装3.2.5 …

RHCA礼品领取步骤

RHCA礼品领取步骤 1. 进入领取页面 考过RHCA的5门课程后会收到5份单科1份RHCA电子证书.其实还有一份玻璃证书笔记本A面贴纸红帽ID号短袖T恤可以领取. 领取地址如下: http://redhat.brandfuel.com 在第一框内填写红帽ID就是考试时填写的9位id号,每3位用-分割 第二个框填写姓,就…

图扑软件受邀亮相 IOTE 2023 国际物联网展

IOTE 2023 国际物联网展&#xff0c;作为全球物联网领域的盛会&#xff0c;于 9 月 20 日 - 22 日在中国深圳拉开帷幕。本届展会以“IoT构建数字经济底座”为主题&#xff0c;由深圳市物联网产业协会主办&#xff0c;打造当前物联网最新科技大秀。促进物联网与各行业深度融合&a…

Android studio “Layout Inspector“工具在Android14 userdebug设备无法正常使用

背景描述 做rom开发的都知道&#xff0c;“Layout Inspector”和“Attach Debugger to Android Process”是studio里很好用的工具&#xff0c;可以用来查看布局、调试系统进程&#xff08;比如setting、launcher、systemui&#xff09;。 问题描述 最进刚开始一个Android 14…

海贝造音强势登陆深圳 助力本土原创音乐升阶

海贝负责人&#xff1a;萧弘天先生 据媒体报导&#xff0c;全球音乐产业收入已恢复至疫情前水平&#xff0c;甚至比往年高出16%&#xff0c;值此佳机&#xff0c;在大湾区深耕娱乐行业30年之久的一众行业先锋&#xff0c;港澳资深传媒人在深圳成立了海贝造音。 深圳&#xff…

RFID资产管理系统应用助力企业实现高效资产运营管理

在企业运营中&#xff0c;资产以各种形式存在&#xff0c;包括生产物资、设备、车辆、办公桌、电脑、电缆等等&#xff0c;这些资产都具有价值高、流动性强、安全管理难等特点&#xff0c;而固定资产数量多、种类繁多、价值高、使用周期长、使用地点分散等特点使得其管理变得非…

增值税发票的Python代码快速识别

# 导入potencent这个库&#xff0c;下载命令&#xff1a;pip install potencent import potencent # 调用增值税识别的功能 potencent.ocr.VatInvoiceOCR(img_pathrC:\vx_CoderWanFeng\your_img.jpg)

你的游戏项目有这些问题吗?

在移动游戏对高品质画面的要求不断增加的背景下&#xff0c;我们一直专注于移动设备GPU性能的优化&#xff0c;以确保您的游戏体验得以最佳展现。然而&#xff0c;不同GPU芯片之间的性能差异以及由此可能引发的GPU瓶颈问题使得优化工作更加具有挑战性。 因此&#xff0c;在不久…

GM(1,1)应用案例1

北方某城市1986-1992年道路交通平均噪声级数数据如表&#xff08;1&#xff09;建立GM(1,1)模型。 表&#xff08;1&#xff09;城市交通平均噪声级数数据/db(A) 编号 1 2 3 4 5 6 7 年份 1986 1987 1988 1989 1990 1991 1992 Leq 71.1 72.4 72.4 72.1 7…

安卓:解决AndroidStudio导出Unity的Apk(APP)出现2个显示图标

用AndroidStudio打开该项目 实现只保留1个app图标 AndroidManifest.xml的改法如下&#xff1a; <?xml version"1.0" encoding"utf-8"?> <manifest xmlns:android"http://schemas.android.com/apk/res/android" package"com.fru…

26055-2022 再生碳化钨粉 思维导图

声明 本文是学习GB-T 26055-2022 再生碳化钨粉. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本文件规定了再生碳化钨粉的分类、技术要求、试验方法、检验规则、标志、包装、运输、贮存、随行文 件及订货单内容。 本文件适用于以回收的硬…

C# 多态性

简单来讲&#xff0c;多态&#xff0c;就是派生类的对象可以隐式转化为基类对象。在派生类中可以重写基类中定义并实现的虚方法。 可以用基类声明&#xff0c;用派生类实例化&#xff0c;这样的变量调用方法时会调用运行时方法&#xff08;即派生类重写的方法&#xff09;。 …

基于Vue和Element UI实现前后端分离和交互

目录 前言 一、Element UI简介 1.Element UI是什么 2.Element UI的特点 二、项目搭建 1.创建一个SPA项目 2.安装 Element-UI 3.导入组件 4.创建登陆注册界面 登录组件---Login.vue 注册组件---Register.vue 定义组件与路由的对应关系 效果演示&#xff1a; 三、前…

激活函数总结(四十六):激活函数补充(Nipuna、StarReLU)

激活函数总结&#xff08;四十六&#xff09;&#xff1a;激活函数补充 1 引言2 激活函数2.1 Nipuna激活函数2.2 StarReLU激活函数 3. 总结 1 引言 在前面的文章中已经介绍了介绍了一系列激活函数 (Sigmoid、Tanh、ReLU、Leaky ReLU、PReLU、Swish、ELU、SELU、GELU、Softmax、…

Ros2 学习01-Ros2 VS Ros1

ROS最早的设计目标就是开发这样一款PR2家庭服务机器人&#xff0c;这款机器人绝大部分时间都是独立工作&#xff0c;为了让他具备充足的能力&#xff1a; 它搭载了工作站级别的计算平台和各种先进的通信设备&#xff0c;不用担忧算力不够&#xff0c;有足够的实力支持各种复杂…

【VUE复习·7】样式绑定:静态样式绑定、动态样式绑定(明亮模式 / 暗黑模式 切换的效果如何实现)

总览 1.静态样式绑定 2.动态样式绑定 一、静态样式绑定 1.正常写即可 <div><div class"basic" click"changeName">{{name}}</div> </div><style>.basic{...} </style>二、动态样式绑定 1.示例 这么写&#xff0…

K8s Kubelet 垃圾回收机制

前言 Kubelet 垃圾回收(Garbage Collection)是一个非常有用的功能,它负责自动清理节点上的无用镜像和容器。Kubelet 每隔 1 分钟进行一次容器清理,每隔 5 分钟进行一次镜像清理(截止到 v1.15 版本,垃圾回收间隔时间还都是在源码中固化的,不可自定义配置)。如果节点上已…

2023经典好用的图床网站推荐(站长必备)

图床一般是指储存图片的服务器&#xff0c;有国内和国外之分&#xff0c;国外的图床由于有空间距离等因素决定访问速度很慢影响图片显示速度。国内也分为单线空间、多线空间和cdn加速三种。同时允许你把图片对外连接的网上空间&#xff0c;图床有免费的&#xff0c;也有收费的。…