RestFul API 统一格式返回 + 全局异常处理

简介:

RestFul API 统一格式返回 + 全局异常处理
一、背景
在分布式、微服务盛行的今天,绝大部分项目都采用的微服务框架,前后端分离方式。前端和后端进行交互,前端按照约定请求URL路径,并传入相关参数,后端服务器接收请求,进行业务处理,返回数据给前端。

所以统一接口的返回值,保证接口返回值的幂等性很重要,本文主要介绍博主当前使用的结果集。

二、统一格式设计
2.1 统一结果的一般形式
示例:
{

# 是否响应成功
success: true,
# 响应状态码
code: 200,        
# 响应数据
data: Object
# 返回错误信息
message: "",

}
2.2 结果类枚举
public enum ResultCodeEnum {

/*** 通用部分 100 - 599***/
// 成功请求
SUCCESS(200, "successful"),
// 重定向
REDIRECT(301, "redirect"),
// 资源未找到
NOT_FOUND(404, "not found"),
// 服务器错误
SERVER_ERROR(500,"server error"),

/*** 这里可以根据不同模块用不同的区级分开错误码,例如:  ***/

// 1000~1999 区间表示用户模块错误
// 2000~2999 区间表示订单模块错误
// 3000~3999 区间表示商品模块错误
// 。。。

;
/**
 * 响应状态码
 */
private Integer code;
/**
 * 响应信息
 */
private String message;

ResultCodeEnum(Integer code, String msg) {
    this.code = code;
    this.message = msg;
}

public Integer getCode() {
    return code;
}

public String getMessage() {
    return message;
}

}
code:响应状态码
一般小伙伴们是在开发的时候需要什么,就添加什么。但是,为了规范,我们应当参考HTTP请求返回的状态码。

code区间 类型 含义
1** 100-199 信息 服务器接收到请求,需要请求者继续执行操作
2** 200-299 成功 请求被成功接收并处理
3** 300-399 重定向 需要进一步的操作以完成请求
4** 400-499 客户端错误 请求包含语法错误或无法完成请求
5** 500-599 服务器错误 服务器在处理的时候发生错误
常见的HTTP状态码:

200 - 请求成功;
301 - 资源(网页等)被永久转移到其它URL;
404 - 请求的资源(网页等)不存在;
500 - 内部服务器错误。
message:错误信息
在发生错误时,如何友好的进行提示?

根据code 给予对应的错误码定位;
把错误描述记录到message中,便于接口调用者更详细的了解错误。
2.3 统一结果类
public class HttpResult implements Serializable {

/**
 * 是否响应成功
 */
private Boolean success;
/**
 * 响应状态码
 */
private Integer code;
/**
 * 响应数据
 */
private T data;
/**
 * 错误信息
 */
private String message;

// 构造器开始
/**
 * 无参构造器(构造器私有,外部不可以直接创建)
 */
private HttpResult() {
    this.code = 200;
    this.success = true;
}
/**
 * 有参构造器
 * @param obj
 */
private HttpResult(T obj) {
    this.code = 200;
    this.data = obj;
    this.success = true;
}

/**
 * 有参构造器
 * @param resultCode
 */
private HttpResult(ResultCodeEnum resultCode) {
    this.success = false;
    this.code = resultCode.getCode();
    this.message = resultCode.getMessage();
}
// 构造器结束

/**
 * 通用返回成功(没有返回结果)
 * @param <T>
 * @return
 */
public static<T> HttpResult<T> success(){
    return new HttpResult();
}

/**
 * 返回成功(有返回结果)
 * @param data
 * @param <T>
 * @return
 */
public static<T> HttpResult<T> success(T data){
    return new HttpResult<T>(data);
}

/**
 * 通用返回失败
 * @param resultCode
 * @param <T>
 * @return
 */
public static<T> HttpResult<T> failure(ResultCodeEnum resultCode){
    return  new HttpResult<T>(resultCode);
}

public Boolean getSuccess() {
    return success;
}

public void setSuccess(Boolean success) {
    this.success = success;
}

public Integer getCode() {
    return code;
}

public void setCode(Integer code) {
    this.code = code;
}

public T getData() {
    return data;
}

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

public String getMessage() {
    return message;
}

public void setMessage(String message) {
    this.message = message;
}

@Override
public String toString() {
    return "HttpResult{" +
            "success=" + success +
            ", code=" + code +
            ", data=" + data +
            ", message='" + message + '\'' +
            '}';
}

}
说明:

构造器私有,外部不可以直接创建;
只可以调用统一返回类的静态方法返回对象;
success 是一个Boolean 值,通过这个值,可以直接观察到该次请求是否成功;
data 表示响应数据,用于请求成功后,返回客户端需要的数据。
三、测试及总结
3.1 简单的接口测试
@RestController
@RequestMapping("/httpRest")
@Api(tags = "统一结果测试")
public class HttpRestController {

@ApiOperation(value = "通用返回成功(没有返回结果)", httpMethod = "GET")
@GetMapping("/success")
public HttpResult success(){
    return HttpResult.success();
}

@ApiOperation(value = "返回成功(有返回结果)", httpMethod = "GET")
@GetMapping("/successWithData")
public HttpResult successWithData(){
    return HttpResult.success("风尘博客");
}

@ApiOperation(value = "通用返回失败", httpMethod = "GET")
@GetMapping("/failure")
public HttpResult failure(){
    return HttpResult.failure(ResultCodeEnum.NOT_FOUND);
}

}
这里 Swagger以及SpringMVC的配置就没贴出来了,详见Github 示例代码。

3.2 返回结果
http://localhost:8080/swagger-ui.html#/

{
"code": 200,
"success": true
}
{
"code": 200,
"data": "风尘博客",
"success": true
}
{
"code": 404,
"message": "not found",
"success": false
}
四、全局异常处理
使用统一返回结果时,还有一种情况,就是程序的报错是由于运行时异常导致的结果,有些异常是我们在业务中抛出的,有些是无法提前预知。

因此,我们需要定义一个统一的全局异常,在Controller捕获所有异常,并且做适当处理,并作为一种结果返回。

4.1 设计思路:
自定一个异常类(如:TokenVerificationException),捕获针对项目或业务的异常;
使用@ExceptionHandler注解捕获自定义异常和通用异常;
使用@ControllerAdvice集成@ExceptionHandler的方法到一个类中;
异常的对象信息补充到统一结果枚举中;
4.2 自定义异常
public class TokenVerificationException extends RuntimeException {

/**
 * 错误码
 */
protected Integer code;

protected String msg;

public Integer getCode() {
    return code;
}

public String getMsg() {
    return msg;
}

public void setMsg(String msg) {
    this.msg = msg;
}

/**
 * 有参构造器,返回码在枚举类中,这里可以指定错误信息
 * @param msg
 */
public TokenVerificationException(String msg) {
    super(msg);
}

}
4.3 统一异常处理器
@ControllerAdvice注解是一种作用于控制层的切面通知(Advice),能够将通用的@ExceptionHandler、@InitBinder和@ModelAttributes方法收集到一个类型,并应用到所有控制器上。

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

/**
 * 异常捕获
 * @param e 捕获的异常
 * @return 封装的返回对象
 **/
@ExceptionHandler(Exception.class)
public HttpResult handlerException(Exception e) {
    ResultCodeEnum resultCodeEnum;
    // 自定义异常
    if (e instanceof TokenVerificationException) {
        resultCodeEnum = ResultCodeEnum.TOKEN_VERIFICATION_ERROR;
        resultCodeEnum.setMessage(getConstraintViolationErrMsg(e));
        log.error("tokenVerificationException:{}", resultCodeEnum.getMessage());
    }else {
        // 其他异常,当我们定义了多个异常时,这里可以增加判断和记录
        resultCodeEnum = ResultCodeEnum.SERVER_ERROR;
        resultCodeEnum.setMessage(e.getMessage());
        log.error("common exception:{}", JSON.toJSONString(e));
    }
    return HttpResult.failure(resultCodeEnum);
}

/**
 * 获取错误信息
 * @param ex
 * @return
 */
private String getConstraintViolationErrMsg(Exception ex) {
    // validTest1.id: id必须为正数
    // validTest1.id: id必须为正数, validTest1.name: 长度必须在有效范围内
    String message = ex.getMessage();
    try {
        int startIdx = message.indexOf(": ");
        if (startIdx < 0) {
            startIdx = 0;
        }
        int endIdx = message.indexOf(", ");
        if (endIdx < 0) {
            endIdx = message.length();
        }
        message = message.substring(startIdx, endIdx);
        return message;
    } catch (Throwable throwable) {
        log.info("ex caught", throwable);
        return message;
    }
}

}
说明
我使用的是@RestControllerAdvice ,等同于@ControllerAdvice + @ResponseBody
错误枚举类这里省略了,详见Github代码。
五、测试及总结
5.1 测试接口
@RestController
@RequestMapping("/exception")
@Api(tags = "异常测试接口")
public class ExceptionRestController {

@ApiOperation(value = "业务异常(token 异常)", httpMethod = "GET")
@GetMapping("/token")
public HttpResult token() {
    // 模拟业务层抛出 token 异常
    throw new TokenVerificationException("token 已经过期");
}
@ApiOperation(value = "其他异常", httpMethod = "GET")
@GetMapping("/errorException")
public HttpResult errorException() {
    //这里故意造成一个其他异常,并且不进行处理
    Integer.parseInt("abc123");
    return HttpResult.success();
}

}
5.2 返回结果
http://localhost:8080/swagger-ui.html#/

{
"code": 500,
"message": "For input string: "abc123"",
"success": false
}
{
"code": 4000,
"message": "token 已经过期",
"success": false
}
5.3 小结
@RestControllerAdvice和@ExceptionHandler会捕获所有Rest接口的异常并封装成我们定义的HttpResult的结果集返回,但是:处理不了拦截器里的异常

六、总结
没有哪一种方案是适用于各种情况的,如:分页情况,还可以增加返回分页结果的静态方案,具体实现,这里就不展示了。所以,适合自己的,具有一定可读性都是很好的,欢迎持不同意见的大佬给出意见建议。

6.1 示例代码
Github 示例代码

6.2 技术交流
风尘博客
风尘博客-掘金
风尘博客-博客园
Github
原文地址https://www.cnblogs.com/vandusty/p/12557551.html

相关文章
|
10天前
|
JSON 前端开发 搜索推荐
关于商品详情 API 接口 JSON 格式返回数据解析的示例
本文介绍商品详情API接口返回的JSON数据解析。最外层为`product`对象,包含商品基本信息(如id、name、price)、分类信息(category)、图片(images)、属性(attributes)、用户评价(reviews)、库存(stock)和卖家信息(seller)。每个字段详细描述了商品的不同方面,帮助开发者准确提取和展示数据。具体结构和字段含义需结合实际业务需求和API文档理解。
|
2月前
|
JSON 缓存 JavaScript
深入浅出:使用Node.js构建RESTful API
在这个数字时代,API已成为软件开发的基石之一。本文旨在引导初学者通过Node.js和Express框架快速搭建一个功能完备的RESTful API。我们将从零开始,逐步深入,不仅涉及代码编写,还包括设计原则、最佳实践及调试技巧。无论你是初探后端开发,还是希望扩展你的技术栈,这篇文章都将是你的理想指南。
|
1月前
|
JSON JavaScript 前端开发
深入浅出Node.js:从零开始构建RESTful API
在数字化时代的浪潮中,后端开发作为连接用户与数据的桥梁,扮演着至关重要的角色。本文将引导您步入Node.js的奇妙世界,通过实践操作,掌握如何使用这一强大的JavaScript运行时环境构建高效、可扩展的RESTful API。我们将一同探索Express框架的使用,学习如何设计API端点,处理数据请求,并实现身份验证机制,最终部署我们的成果到云服务器上。无论您是初学者还是有一定基础的开发者,这篇文章都将为您打开一扇通往后端开发深层知识的大门。
57 12
|
1月前
|
JSON API 数据格式
获取商品详情API的请求格式是什么
获取商品详情API的请求格式通常依赖于特定的电商平台或服务提供商,但一般遵循类似的结构。以下是一个概括性的说明,以及针对几个主流电商平台的示例:
|
2月前
|
XML JSON 缓存
深入理解RESTful API设计原则与实践
在现代软件开发中,构建高效、可扩展的应用程序接口(API)是至关重要的。本文旨在探讨RESTful API的核心设计理念,包括其基于HTTP协议的特性,以及如何在实际应用中遵循这些原则来优化API设计。我们将通过具体示例和最佳实践,展示如何创建易于理解、维护且性能优良的RESTful服务,从而提升前后端分离架构下的开发效率和用户体验。
|
2月前
|
监控 安全 API
深入浅出:构建高效RESTful API的最佳实践
在数字化时代,API已成为连接不同软件和服务的桥梁。本文将带你深入了解如何设计和维护一个高效、可扩展且安全的RESTful API。我们将从基础概念出发,逐步深入到高级技巧,让你能够掌握创建优质API的关键要素。无论你是初学者还是有经验的开发者,这篇文章都将为你提供实用的指导和启示。让我们一起探索API设计的奥秘,打造出色的后端服务吧!
|
2月前
|
JSON 缓存 测试技术
构建高效RESTful API的后端实践指南####
本文将深入探讨如何设计并实现一个高效、可扩展且易于维护的RESTful API。不同于传统的摘要概述,本节将直接以行动指南的形式,列出构建RESTful API时必须遵循的核心原则与最佳实践,旨在为开发者提供一套直接可行的实施框架,快速提升API设计与开发能力。 ####
|
2月前
|
JavaScript NoSQL API
深入浅出Node.js:从零开始构建RESTful API
在数字化时代的浪潮中,后端开发如同一座灯塔,指引着数据的海洋。本文将带你航行在Node.js的海域,探索如何从一张白纸到完成一个功能完备的RESTful API。我们将一起学习如何搭建开发环境、设计API结构、处理数据请求与响应,以及实现数据库交互。准备好了吗?启航吧!
|
2月前
|
JSON API 开发者
深入理解RESTful API设计原则
在数字化时代,API已成为连接不同软件应用的桥梁。本文旨在探讨RESTful API设计的基本原则和最佳实践,帮助开发者构建高效、可扩展的网络服务接口。通过解析REST架构风格的核心概念,我们将了解如何设计易于理解和使用的API,同时保证其性能和安全性。
|
2月前
|
存储 缓存 API
深入理解RESTful API设计原则
在现代软件开发中,RESTful API已成为前后端分离架构下不可或缺的通信桥梁。本文旨在探讨RESTful API的核心设计原则,包括资源导向、无状态、统一接口、以及可缓存性等,并通过实例解析如何在实际应用中遵循这些原则来构建高效、可维护的API接口。我们将深入分析每个原则背后的设计理念,提供最佳实践指导,帮助开发者优化API设计,提升系统整体性能和用户体验。
46 0

热门文章

最新文章