精进代码 - 接口统一响应体

前言

  如今web项目的接口大都是 RESTful 的,响应体中包含了一些接口执行的信息,比如返回的数据(data)、响应码(code)、是否成功(success)和响应描述(message)。每个接口都需要封装成这种格式,这样每次都需要留意。
  下面来介绍一种方法,是 Spring 的 web 模块提供的功能。

统一响应体

pom依赖了web模块和lombok

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

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.6</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.61</version>
</dependency>

首先创建枚举类ResultMessage,定义一些响应码

public enum  ResultMessage {
  /** 成功 */
  SUCCESS(200, "Success"),
  /** 请求错误 */
  BAD_REQUEST(400, "Bad request"),
  /** 未经授权 */
  UNAUTHORIZED(401, "Unauthorized"),
  /** 目标不存在 */
  NOT_FOUND(404, "Not found"),
  /** 服务内部错误 */
  SERVER_ERROR(500, "Server Error");

  private int code;
  private String message;

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

  public int code() {
    return code;
  }

  public String message() {
    return message;
  }
}

创建 Result 类,代表接口的响应体

@Getter
public class Result<T> {

  /**
   * 状态码
   */
  private int code = -1;
  /**
   * 提示信息
   */
  private String message;
  /**
   * 响应数据
   */
  private T data;

  private Result(int code, String message, T data) {
    this.code = code;
    this.message = message;
    this.data = data;
  }

  public static <T> Result<T> data(T data) {
    return new Result<>(ResultMessage.SUCCESS.code(), ResultMessage.SUCCESS.message(), data);
  }

  public static <T> Result<T> err() {
    return new Result<>(ResultMessage.SERVER_ERROR.code(), ResultMessage.SERVER_ERROR.message(), null);
  }

  public static <T> Result<T> err(String message) {
    return new Result<>(ResultMessage.SERVER_ERROR.code(), message, null);
  }
}

接着创建,HelloController类

@RestController
public class HelloController {

  @GetMapping("/hello")
  public Result<String> hello() {
    return Result.data("hello");
  }
}

使用postman访问 http://localhost:8080/hello

{
    "code": 200,
    "message": "Success",
    "data": "hello"
}

创建注解 OriginalBody,代表原始响应头。

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

创建 ResultBodyAdvice 类,实现 ResponseBodyAdvice 接口,重写两个方法:

@ControllerAdvice
public class ResultBodyAdvice implements ResponseBodyAdvice {
  @Override
  public boolean supports(MethodParameter methodParameter, Class aClass) {
    Annotation[] methodAnnotations = methodParameter.getMethodAnnotations();
    for (Annotation methodAnnotation : methodAnnotations) {
      if (methodAnnotation instanceof OriginalBody) {
        return false;
      }
    }
    Annotation[] classAnnotations = methodParameter.getMethod().getDeclaringClass().getAnnotations();
    for (Annotation classAnnotation : classAnnotations) {
      if (classAnnotation instanceof OriginalBody) {
        return false;
      }
    }
    return true;
  }

  @Override
  public Object beforeBodyWrite(Object result, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse response) {
    if (result instanceof Result) {
      return result;
    }
    response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
    String type = mediaType.getType();
    if ("text".equals(type)) {
      return JSON.toJSONString(Result.data(result), SerializerFeature.PrettyFormat);
    }
    return Result.data(result);
  }
}

在 HelloController 新增两个方法,用来测试

@GetMapping("/hello2")
public String hello2() {
	return "hello2";
}

@OriginalBody
@GetMapping("/hello3")
public String hello3() {
	return "hello3";
}

使用Postman分别调用 hello2:

{
	"code": 200,
	"data": "hello2",
	"message": "Success"
}

调用 hello3 :

hello3

统一异常响应体

新增一个方法,如果接口出现异常,则响应成了酱紫:

@GetMapping("/hello4")
public String hello4(@RequestParam Integer code) {
  if (code != null && code.equals(1)) {
    throw new RuntimeException();
  }
  return "hello4";
}
{
    "code": 200,
    "message": "Success",
    "data": {
        "timestamp": "2020-04-10T11:06:45.299+0000",
        "status": 500,
        "error": "Internal Server Error",
        "message": "No message available",
        "path": "/hello4"
    }
}

这种方法使用 @ExceptionHandler 注解来解决。
创建

@ControllerAdvice
@Slf4j
@ResponseBody
public class ExceptionHandlerAdvice {
  @ExceptionHandler(Exception.class)
  public Result<Object> handleException(Exception e){
    log.error(e.getMessage(), e);
    return Result.err();
  }
}

请求结果:

{
    "code": 500,
    "message": "Server Error",
    "data": null
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章