Возврат единого формата RestFul API + глобальная обработка исключений

Java
Возврат единого формата RestFul API + глобальная обработка исключений

1. Предпосылки

Сегодня, когда преобладают распределенные и микросервисы, в большинстве проектов используется среда микросервисов, которая разделяет интерфейс и серверную часть. Фронтенд и бэкенд взаимодействуют, а фронтенд запрашивает в соответствии с соглашениемURLPath и передать соответствующие параметры, внутренний сервер получает запрос, выполняет бизнес-обработку и возвращает данные внешнему интерфейсу.

Поэтому очень важно унифицировать возвращаемое значение интерфейса и обеспечить идемпотентность возвращаемого значения интерфейса.Эта статья в основном знакомит с набором результатов, используемым в настоящее время блогерами.

2. Единый дизайн формата

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Код состояния, возвращаемый запросом.

диапазон кодов тип значение
1** 100-199 Информация Сервер получает запрос и требует от запрашивающей стороны продолжить операцию.
2** 200-299 успех Запрос успешно принят и обработан
3** 300-399 перенаправить Для выполнения запроса необходимы дальнейшие действия.
4** 400-499 ошибка клиента Запрос содержит синтаксическую ошибку или запрос не может быть выполнен
5** 500-599 Ошибка сервера Произошла ошибка при обработке сервера

ОбщийHTTPкод состояния:

  1. 200- запрос выполнен успешно;
  2. 301- ресурсы (веб-страницы и т.д.) постоянно перемещаются в другиеURL;
  3. 404- запрашиваемый ресурс (веб-страница и т.п.) не существует;
  4. 500- Внутренняя Ошибка Сервера.
  • message: сообщение об ошибке

Когда возникает ошибка, как дать дружескую подсказку?

  1. в соответствии сcodeУкажите соответствующее местоположение кода ошибки;
  2. записать описание ошибки вmessage, что удобно вызывающей стороне интерфейса для более подробного понимания ошибки.

2.3 Единый класс результатов

public class HttpResult <T> 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 + '\'' +
                '}';
    }
}

инструкция:

  1. Конструктор является закрытым и не может быть создан непосредственно внешним миром;
  2. Только статический метод унифицированного возвращаемого класса может быть вызван для возврата объекта;
  3. successЯвляетсяBooleanзначение, через это значение вы можете напрямую наблюдать, успешен ли запрос;
  4. dataУказывает данные ответа, которые используются для возврата данных, требуемых клиентом после успешного выполнения запроса.

3. Тест и резюме

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 Идеи дизайна:

  1. Настройте класс исключения (например:TokenVerificationException), ловить исключения, характерные для проекта или бизнеса;
  2. использовать@ExceptionHandlerАннотации фиксируют пользовательские исключения и общие исключения;
  3. использовать@ControllerAdviceинтегрированный@ExceptionHandlerметод в класс;
  4. Информация об аномальном объекте добавляется к унифицированному перечислению результатов;

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;
        }
    }
}
  • инструкция
  1. я использую@RestControllerAdvice, Эквивалентно@ControllerAdvice + @ResponseBody
  2. Класс перечисления ошибок здесь опущен, см. подробностиКод на гитхабе.

V. Тест и резюме

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. Резюме

Нет решения, подходящего для различных ситуаций, таких как пейджинг, и можно добавить статическое решение для возврата результатов пейджинга Конкретная реализация здесь не будет показана. Поэтому для вас очень хорошо иметь определенную читабельность.Вы можете высказывать мнения и предложения от старших братьев с разными мнениями.

6.1 Пример кода

Пример кода на GitHub

6.2 Технический обмен

  1. Пыль Блог
  2. Блог Пыли - Самородки
  3. Пыль Блог - Блог Парк
  4. Github
  5. публика
    风尘博客