【业务功能篇48】后端接口开发的统一规范

业务背景:日常工作中,我们开发接口时,一般都会涉及到参数校验、异常处理、封装结果返回等处理。而我们项目有时为了快速迭代,在这方面上有所疏忽,后续导致代码维护比较难,不同的开发人员的不同习惯,各式各样的接口返回,所以专门进行了一次业务整改,统一规范专项

如果每个后端开发在参数校验、异常处理等都是各写各的,没有统一处理的话,代码就不优雅,也不容易维护。所以,作为一名合格的后端开发工程师,我们需要统一校验参数,统一异常处理、统一结果返回,让代码更加规范、可读性更强、更容易维护。

接口统一响应对象返回

作为后端开发,我们项目的响应结果,需要统一标准的返回格式。一般一个标准的响应报文对象,有一下几个属性

  • code :响应状态码
  • message :响应结果描述
  • data:返回的数据

1)响应状态码一般用枚举表示:

包结构: com.xxx.common.core.domain.ResultCode

/**
 * 响应状态码
 **/
public enum ResultCode {

    /**操作成功**/
    SUCCESS("200","操作成功"),

    /**操作失败**/
    ERROR("500","操作失败"),;

    /**
     * 自定义状态码
     **/
    private String code;
    
    
    /**自定义描述**/
    private String message;

    ResultCode(String code, String message) {
        this.code = code;
        this.message = message;
    }

    public String getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

2)因为返回的数据类型不是确定的,我们可以使用泛型,如下:

包结构:com.xxx.common.core.domain.BaseResponse

/**
 * 响应结果封装对象
 **/
public class BaseResponse<T> implements Serializable {

    private static final long serialVersionUID = 1901152752394073986L;

    /**
     * 响应状态码
     */
    private String code;


    /**
     * 响应结果描述
     */
    private String message;

    /**
     * 返回的数据
     */
    private T data;


    /**
     * 成功返回
     * @param data
     * @return: com.msb.hjycommunity.common.core.domain.BaseResponse<T>
     */
    public static <T> BaseResponse<T> success(T data){

        BaseResponse<T> response = new BaseResponse<>();
        response.setCode(ResultCode.SUCCESS.getCode());
        response.setMessage(ResultCode.SUCCESS.getMessage());
        response.setData(data);

        return response;
    }


    /**
     * 失败返回
     * @param message
     * @return: com.msb.hjycommunity.common.core.domain.BaseResponse<T>
     */
    public static <T> BaseResponse<T> fail(String message){

        BaseResponse<T> response = new BaseResponse<>();
        response.setCode(ResultCode.ERROR.getCode());
        response.setMessage(message);

        return response;
    }

    /**
     * 失败返回
     * @param message
     * @return: com.msb.hjycommunity.common.core.domain.BaseResponse<T>
     */
    public static <T> BaseResponse<T> fail(String code, String message){

        BaseResponse<T> response = new BaseResponse<>();
        response.setCode(code);
        response.setMessage(message);

        return response;
    }


    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;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

3)测试

  • 创建user实体类
public class User {

    private String userId;

    private String username;
    
    public User() {
    
    }
    
    public User(String userId, String username) {
        this.userId = userId;
        this.username = username;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

 

  • 创建UserController
@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/queryUserById")
    public BaseResponse<User> queryUserById(String userId){

        if(userId != null){
           return BaseResponse.success(new User(userId,"spike"));
        }else{
            return BaseResponse.fail("查询用户信息失败!");
        }
    }
}

 

  • 测试
http://localhost:8888/user/queryUserById?userId=1

//output
{
  "code": "200",
  "message": "操作成功",
  "data": {
    "userId": "1",
    "username": "tom"
  }
}

http://localhost:8888/user/queryUserById

//output
{
  "code": "500",
  "message": "查询用户信息失败!",
  "data": null
}

使用注解,统一参数校验

我们在实际的开发过程中经常会遇到需要对参数进行校验的情况,比如在需要用户输入手机号的时候他是不是真的输入了一个合法的手机号,在需要用户输入一个邮箱的时候他是不是真的输入了一个合法的邮箱,用户输入的内容是不是超出了长度限制等等。

当一个表单的数据较多的时候,单纯的数据校验代码就会占到很大的幅度,所以简化基础数据的校验可以省去我们很多的工作,让我们能更专注于功能的实现。

接下来我们一起看一下 springboot中参数校验(validation)的使用,首先导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Validator可以非常方便的制定校验规则,并自动帮你完成校验。首先在入参里需要校验的字段加上注解,每个注解对应不同的校验规则,并可制定校验失败后的信息:


public class User {
    
    private String userId;

    @NotNull(message = "username 不能为空")
    private String username;
}

 

校验规则和错误提示信息配置完毕后,接下来只需要在接口需要校验的参数上加上@Validated注解,并添加BindResult参数即可方便完成验证:


@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/addUser")
    public BaseResponse addUser(@Validated User user, BindingResult bindingResult){

        List<FieldError> fieldErrors = bindingResult.getFieldErrors();

        //如果参数校验失败,会将错误信息封装成对象组装在 BindingResult
        if(!fieldErrors.isEmpty()){
            return BaseResponse.fail(fieldErrors.get(0).getDefaultMessage());
        }

        return BaseResponse.success("OK");
    }
}

 

测试

http://localhost:8888/user/addUser?username="tom"

{
  "code": "500",
  "message": "userId 不能为空",
  "data": null
}

http://localhost:8888/user/addUser?username="tom"&userId=1

{
  "code": "200",
  "message": "操作成功",
  "data": "OK"
}

内置的校验注解有很多,罗列如下:

统一异常处理

日常开发中,我们一般都是自定义统一的异常类

  • 自定义异常可以携带更多的信息。
  • 项目开发中经常是很多人负责不同的模块,使用自定义异常可以统一了对外异常展示的方式。
  • 自定义异常语义更加清晰明了,一看就知道是项目中手动抛出的异常。

自定义异常类, 所在包结构: com.xxx.common.core.exception.BaseException


/**
 * 基础异常
 **/
public class BaseException extends RuntimeException{


    /**
     * 错误码
     */
    private String code;


    /**
     * 错误消息
     */
    private String defaultMessage;

    public BaseException() {
    }

    public BaseException(String code, String defaultMessage) {
        this.code = code;
        this.defaultMessage = defaultMessage;
    }

    public String getCode() {
        return code;
    }

    public String getDefaultMessage() {
        return defaultMessage;
    }
}

 

在controller 层,我们来使用这个自定义异常:


@RequestMapping("/queryUser")
public BaseResponse queryUser(User user){

    //模拟查询失败抛出异常
    throw new BaseException("500","测试异常类!");
}

 

这块代码,没什么问题,但是如果在很多地方都抛出这个异常,我们处理起来就会比较麻烦。

这时我们可以借助注解@RestControllerAdvice,让代码更优雅。@RestControllerAdvice是一个应用于Controller层的切面注解,它一般配合@ExceptionHandler注解一起使用,作为项目的全局异常处理。示例代码如下:


/**
 * 全局异常处理器
 **/
@RestControllerAdvice
public class GlobalExceptionHandler {


}

我们有想要拦截的异常类型,比如想拦截BaseException类型,就新增一个方法,使用@ExceptionHandler注解修饰,并指定你想处理的异常类型,接着在方法内编写对该异常的操作逻辑,就完成了对该异常的全局处理! 


@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 基础异常
     * @param e
     * @return: com.xxx.common.core.domain.BaseResponse
     */
    @ExceptionHandler(BaseException.class)
    @ResponseBody
    public BaseResponse baseExceptionHandler(BaseException e){
        
        return BaseResponse.fail(e.getDefaultMessage());
    }

}

 

测试

http://localhost:8888/hejiayun/user/queryUser

{
  "code": "500",
  "message": "测试异常类!",
  "data": null
}

总结:整改的几个维度,但这里需要注意的是,全局异常设计,在个人看来并不一定是有效的,有时会封装过简单,还不易于排查代码问题。

  • 通过Validation 完成了方便的参数校验
  • 通过全局异常处理 + 自定义异常完成了异常操作的规范
  • 通过数据统一响应完成了响应数据的规范
  • 多个方面组装非常优雅的完成了后端接口的协调,让开发人员有更多的经历注重业务逻辑代码,轻松构建后端接口。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

整合spring cloud云服务架构 - 企业分布式微服务云架构构建

1. 介绍 Commonservice-system是一个大型分布式、微服务、面向企业的JavaEE体系快速研发平台&#xff0c;基于模块化、服务化、原子化、热插拔的设计思想&#xff0c;使用成熟领先的无商业限制的主流开源技术构建。采用服务化的组件开发模式&#xff0c;可实现复杂的业务功能。…

玩转ChatGPT:Custom instructions (vol. 1)

一、写在前面 据说GPT-4又被削了&#xff0c;前几天让TA改代码&#xff0c;来来回回好几次才成功。 可以看到之前3小时25条的限制&#xff0c;现在改成了3小时50条&#xff0c;可不可以理解为&#xff1a;以前一个指令能完成的任务&#xff0c;现在得两条指令&#xff1f; 可…

leetcode743. 网络延迟时间 DJ

https://leetcode.cn/problems/network-delay-time/ 有 n 个网络节点&#xff0c;标记为 1 到 n。 给你一个列表 times&#xff0c;表示信号经过 有向 边的传递时间。 times[i] (ui, vi, wi)&#xff0c;其中 ui 是源节点&#xff0c;vi 是目标节点&#xff0c; wi 是一个信…

ip、域名、DNS、CDN概念

1、概念 ip地址 在网络世界里, 一台服务器或者说一台网络设备对应着一个ip地址, 如果我们需要访问指定的网络设备的资源, 那么我们就需要知道这个ip地址, 然后才能去访问它. 这就好像, 我想去朋友家里, 我必须先知道他家的住址, 才能去拜访它. 在互联网世界中, 所有的通信都是…

react+redux异步操作数据

reactredux异步操作数据 redux中操作异步方法&#xff0c;主要是&#xff1a; 1、借助createAsyncThunk()封装异步方法&#xff1b;2、通过extraReducers处理异步方法触发后的具体逻辑&#xff0c;操作派生的state 1、异步操作的slice import { createSlice, createAsyncThunk…

Spring MVC -- 返回数据(静态页面+非静态页面+JSON对象+请求转发与请求重定向)

目录 1. 返回静态页面 2. 返回非静态页面 2.1 ResponseBody 返回页面内容 2.2 RestController ResponseBody Controller 2.3 示例:实现简单计算的功能 3. 返回JSON对象 3.1 实现登录功能&#xff0c;返回 JSON 对象 4. 请求转发(forward)或请求重定向(redirect) 4.1 请…

计讯物联5G千兆网关TG463赋能无人船应用方案,开启自动巡检的智能模式

方案背景 水电站、水库、堤坝等水利工程水下构筑物常年处于水下&#xff0c;并在复杂的水流环境下运行&#xff0c;难免会出现磨蚀、露筋等损伤&#xff0c;而传统的安全监测方式一般是通过潜水员检查上层水柱或通过降低水位进行人工巡查&#xff0c;不仅成本高&#xff0c;效…

STM32(HAL库)驱动AD8232心率传感器

目录 1、简介 2、CubeMX初始化配置 2.1 基础配置 2.1.1 SYS配置 2.1.2 RCC配置 2.2 ADC外设配置 2.3 串口外设配置 2.4 GPIO配置 2.5 项目生成 3、KEIL端程序整合 3.1 串口重映射 3.2 ADC数据采集 3.3 主函数代码整合 4 硬件连接 5 效果展示 1、简介 本文通过STM32…

Vue3 Vite electron 开发桌面程序

Electron是一个跨平台的桌面应用程序开发框架&#xff0c;它允许开发人员使用Web技术&#xff08;如HTML、CSS和JavaScript&#xff09;构建桌面应用程序&#xff0c;这些应用程序可以在Windows、macOS和Linux等操作系统上运行。 Electron的核心是Chromium浏览器内核和Node.js…

【C++基础(五)】类和对象(上)

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C初阶之路⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; 类和对象-上 1. 前言2. 类的引入3. 类的定义4. 类的…

Obsidian同步到Notion

插件介绍 将Obsidian的内容同步到Notion需要使用一个第三方插件"Obsidian shared to Notion"EasyChris/obsidian-to-notion: Share obsidian markdown file to notion and generate notion share link 同步obsdian文件到notion&#xff0c;并生成notion分享链接&am…

[SSM]手写Spring框架

目录 十一、手写Spring框架 第一步&#xff1a;创建模块myspring 第二步&#xff1a;准备好要管理的Bean 第三步&#xff1a;准备myspring.xml配置文件 第四步&#xff1a;核心接口实现 第五步&#xff1a;实例化Bean 第六步&#xff1a;给Bean属性赋值 第七步&#xff…

JVM运行时数据区——方法区、堆、栈的关系

方法区存储加载的字节码文件内的相关信息和运行时常量池&#xff0c;方法区可以看作是独立于Java堆的内存空间&#xff0c;方法区是在JVM启动时创建的&#xff0c;其内存的大小可以调整&#xff0c;是线程共享的&#xff0c;并且也会出现内存溢出的情况&#xff0c;也可存在垃圾…

Android - 集成三方模组原厂WiFi Hal库问题

Android - 集成三方模组原厂WiFi Hal库问题 最近Android 11产品平台上需要集成三方WiFi/AP模组厂商提供的hal静态库时遇到一个问题&#xff1a;将三方的库代码集成进系统&#xff0c;并正确配置、编译出lib_driver_cmd_xxx.a(xxx一般是厂商的名字缩写&#xff0c;仅仅是个后缀用…

在英特尔 CPU 上微调 Stable Diffusion 模型

扩散模型能够根据文本提示生成逼真的图像&#xff0c;这种能力促进了生成式人工智能的普及。人们已经开始把这些模型用在包括数据合成及内容创建在内的多个应用领域。Hugging Face Hub 包含超过 5 千个预训练的文生图 模型。这些模型与 Diffusers 库 结合使用&#xff0c;使得构…

【算法基础:搜索与图论】3.4 求最短路算法(Dijkstrabellman-fordspfaFloyd)

文章目录 求最短路算法总览Dijkstra朴素 Dijkstra 算法&#xff08;⭐原理讲解&#xff01;⭐重要&#xff01;&#xff09;&#xff08;用于稠密图&#xff09;例题&#xff1a;849. Dijkstra求最短路 I代码1——使用邻接表代码2——使用邻接矩阵 补充&#xff1a;稠密图和稀疏…

WPF实战项目十(API篇):引入工作单元UnitOfWork

1、通过github地址&#xff1a;https://github.com/arch/UnitOfWork&#xff0c;下载UnitOfWork的代码&#xff0c;将工作单元部分的代码引用到自己的项目&#xff0c;新增UnitOfWork文件夹。 2、在UnitOfWork文件夹下引用UnitOfWork下的IPagedList.cs、PagedList.cs类&#xf…

iOS--编译连接的过程_2

文章目录 iOS编译&#xff08;一&#xff09;编译器前端 编译器后端执行一次XCode build的流程 IPA包的内容二进制文件的内容iOS Link Map File文件说明1. Link Map File 是什么2. Link Map File 有什么用3. 生成 Link Map File查看Link Map File1&#xff09;路径部分计算机系…

Docker 核心概念深度解析:探索容器、镜像和仓库在Docker生态系统中的重要作用和 应用

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

机器学习算法基础-覃秉丰 笔记版

文章目录 笔记回归sklearn-LASSOsklearn-一元线性回归sklearn-多元线性回归sklearn-岭回归sklearn-弹性网ElasticNetsklearn-逻辑回归sklearn-非线性逻辑回归标准方程法-岭回归梯度下降法-一元线性回归梯度下降法-多元线性回归梯度下降法-逻辑回归梯度下降法-非线性逻辑回归线性…