Как определяется ошибка в API, ошибка возникает при запросе или ошибка при обработке запроса. API не может разобрать переданные данные, у самого API много проблем, и даже правильно сформированные запросы не выполняются. В обоих случаях требуется анализ для выяснения причины.
Вероятно, самый полезный диагностический элемент в мире API, коды ошибок очень полезны, будь то ошибка в виде кода или простой ответ на ошибку. Коды ошибок на этапе ответа API — это основной способ, с помощью которого разработчики могут сообщать пользователям об ошибках.
напишите хороший код ошибки
Хороший код ошибки должен соответствовать трем основным критериям, чтобы быть действительно функциональным. Хороший код ошибки должен включать:
- Идентификация бизнес-домена, чтобы можно было легко определить основную причину и область проблемы;
- Идентификатор внутренней ссылки для символов ошибок, характерных для документа. В некоторых случаях коды состояния HTTP можно заменить, если внутренняя справочная таблица содержит схему кодов состояния HTTP или аналогичную ссылку.
- Удобочитаемое сообщение с описанием контекста, причины и общего решения текущей ошибки.
Основной метод обработки в промышленности
curl https://graph.facebook.com/v2.9/me?fields=id%2Cname%2Cpicture%2C%20picture&access_token=xxxxxxxxxxx
{
- error: {
message: "An active access token must be used to query information about the current user.",
type: "OAuthException",
code: 2500,
fbtrace_id: "ABdaipBGDyGFOyVCgrBfL56"
}
}
curl https://api.twitter.com/1.1/statuses/mentions_timeline.json
{
- errors: [
- {
code: 215,
message: "Bad Authentication data."
}
]
}
Определение кодов ошибок
- Во время запроса произошла ошибка, и логика обработки не была введена.
{
"domain":"pay",
"code":10501002,
"message":"参数错误",
"errors":[
- {
"name":"bankNo",
"message":"银行卡号不符合规范"
}
]
}
- Ошибка обработки запроса
{
"domain":"order",
"code":111501002,
"message":"支付通道网络异常"
}
{
"domain":"user",
"code":100501001,
"message":"对应的用户不存在!"
}
Сведения о коде ошибки:
- domain определяет домен, который удобен для определения источника ошибки.
- code определяет кодировку внутренней ошибки
- сообщение описывает причину ошибки
- error подробно описывает некоторые конкретные ошибки
Код Дополнительное описание: Описание кода исключения состоит из 8 цифр, первые три — системные идентификаторы (начиная со 100), средние две — идентификаторы модуля (бизнес-подразделения), а последние три — идентификаторы исключений (конкретные исключения) Дополнительное описание ошибки: если сообщение не может точно описать причину ошибки, и каждое описание ошибки необходимо уточнить, рассмотрите возможность использования поля ошибки для дополнения элемента ошибки. домен Дополнительное примечание: базовая структура инкапсулирует некоторую обработку исключений, например, ошибки проверки параметров, которые должны использоваться всей системой без идентификации системы. В результате по коду невозможно определить, в какой системе ошибка, а при длинной ссылке сложно выяснить, в чем проблема, поэтому идентификатор бизнес-домена текущего приложения динамически получается во время обработка ошибок.
Обработка ошибок — Spring Boot
Определите модель ответа
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* Result
*
* @author Weichao Li (liweichao0102@gmail.com)
* @since 2019-08-11
*/
@Data
@AllArgsConstructor
@ApiModel("统一 Response 返回值")
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
public static final long SUCCESS_CODE = 200L;
public static final String DEFAULT_SUCCESS_MESSAGE = "success";
@ApiModelProperty(name = "业务域或应用标识", notes = "仅当产生错误时会赋值该字段")
private String domain;
@ApiModelProperty(name = "结果码", notes = "正确响应时该值为 Result#SUCCESS_CODE,错误响应时为错误代码")
private long code;
@ApiModelProperty(name = "人工可读的消息", notes = "正确响应时该值为 Result#DEFAULT_SUCCESS_MESSAGE,错误响应时为错误信息")
private String msg;
@ApiModelProperty(name = "响应体", notes = "正确响应时该值会被使用")
private T data;
/**
* 当验证错误时,各项具体的错误信息
*/
@ApiModelProperty("错误信息")
private List<Error> errors;
public Result(T data) {
this.setData(data);
this.setCode(SUCCESS_CODE);
this.setMsg(DEFAULT_SUCCESS_MESSAGE);
}
public Result() {
this.setCode(SUCCESS_CODE);
this.setMsg(DEFAULT_SUCCESS_MESSAGE);
}
public void addError(String name, String message) {
if (this.errors == null) {
this.errors = new ArrayList<>();
}
this.errors.add(new Error(name, message));
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("统一 Response 返回值中错误信息的模型")
public class Error {
@ApiModelProperty(name = "错误项", notes = "错误的具体项")
private String name;
@ApiModelProperty(name = "错误项说明", notes = "错误的具体项说明")
private String message;
}
}
Обработка перехватчика исключений
Проект Spring Boot уже обрабатывал определенные исключения, но обобщение недостаточно уточнено, поэтому базовая структура должна единообразно захватывать и обрабатывать эти исключения. В Spring Boot есть один@RestControllerAdvice Аннотация , указывающая, что захват глобальных исключений включен, нам нужно только использовать аннотацию ExceptionHandler в пользовательском методе, а затем определить тип захваченного исключения для единообразной обработки этих захваченных исключений. Определить базовый класс исключения
import com.github.hicolors.best.practices.pojo.Result;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* 扩展异常
*
* @author Weichao Li (liweichao0102@gmail.com)
* @since 2019-08-11
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ExtensionException extends RuntimeException {
/**
* 业务域
*/
private String domain;
/**
* 业务异常码 ( 详情参加文档说明 )
*/
private Long code;
/**
* 业务异常信息
*/
private String message;
/**
* 额外数据,可支持扩展
*/
private Object data;
/**
* cause
*/
private Throwable cause;
/**
* 业务域标识自动取当前服务
*
* @param code code
* @param message message
*/
public ExtensionException(Long code, String message) {
this.code = code;
this.message = message;
}
/**
* 指定业务域标识
*
* @param domain domain
* @param code code
* @param message message
*/
public ExtensionException(String domain, Long code, String message) {
this.domain = domain;
this.code = code;
this.message = message;
}
public ExtensionException(Result result) {
this.domain = result.getDomain();
this.code = result.getCode();
this.message = result.getMsg();
this.data = result.getData();
}
}
Глобальный обработчик исключений — перечисление информации
import lombok.Getter;
/**
* WebMvc 模块异常码定义
* <p>
* 系统标识:100
* 模块标识:02
*
* @author Weichao Li (liweichao0102@gmail.com)
* @since 2019-08-11
*/
@Getter
public enum EnumExceptionMessageWebMvc {
// 非预期异常
UNEXPECTED_ERROR(10002000L, "服务发生非预期异常,请联系管理员!"),
PARAM_VALIDATED_UN_PASS(10002001L, "参数校验(JSR303)不通过,请检查参数或联系管理员!"),
NO_HANDLER_FOUND_ERROR(10002002L, "未找到对应的处理器,请检查 API 或联系管理员!"),
HTTP_REQUEST_METHOD_NOT_SUPPORTED_ERROR(10002003L, "不支持的请求方法,请检查 API 或联系管理员!"),
HTTP_MEDIA_TYPE_NOT_SUPPORTED_ERROR(10002004L, "不支持的互联网媒体类型,请检查 API 或联系管理员"),
;
private final Long code;
private final String message;
EnumExceptionMessageWebMvc(Long code, String message) {
this.code = code;
this.message = message;
}
}
глобальный обработчик исключений
import com.github.hicolors.best.practices.exception.ExtensionException;
import com.github.hicolors.best.practices.pojo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.text.MessageFormat;
import java.util.List;
import java.util.Objects;
/**
* ExceptionHandlerAdvice
*
* @author Weichao Li (liweichao0102@gmail.com)
* @since 2019/11/25
*/
@RestControllerAdvice
@Slf4j
public class ExceptionHandlerAdvice {
@Value("${spring.application.domain:${spring.application.name:unknown-spring-boot}}")
private String domain;
/**
* 针对业务异常的处理
*
* @param exception 业务异常
* @param request http request
* @param response http response
* @return 异常处理结果
*/
@ExceptionHandler(value = ExtensionException.class)
@SuppressWarnings("unchecked")
public Result extensionException(ExtensionException exception,
HttpServletRequest request, HttpServletResponse response) {
log.warn("请求发生了预期异常,出错的 url [{}],出错的描述为 [{}]",
request.getRequestURL().toString(), exception.getMessage());
Result result = new Result();
result.setDomain(StringUtils.isEmpty(exception.getDomain()) ? domain : exception.getDomain());
result.setCode(exception.getCode());
result.setMsg(exception.getMessage());
Object data = exception.getData();
if (Objects.nonNull(data) && data instanceof List) {
if (((List) data).size() > 0 && (((List) data).get(0) instanceof Result.Error)) {
result.setErrors((List<Result.Error>) data);
}
}
return result;
}
/**
* 针对参数校验失败异常的处理
*
* @param exception 参数校验异常
* @param request http request
* @param response http response
* @return 异常处理结果
*/
@ExceptionHandler(value = {BindException.class, MethodArgumentNotValidException.class, ConstraintViolationException.class})
public Result databindException(Exception exception, HttpServletRequest request, HttpServletResponse response) {
log.error(MessageFormat.format("请求发生了非预期异常,出错的 url [{0}],出错的描述为 [{1}]",
request.getRequestURL().toString(), exception.getMessage()), exception);
Result result = new Result();
result.setDomain(domain);
result.setCode(EnumExceptionMessageWebMvc.PARAM_VALIDATED_UN_PASS.getCode());
result.setMsg(EnumExceptionMessageWebMvc.PARAM_VALIDATED_UN_PASS.getMessage());
if (exception instanceof BindException) {
for (FieldError fieldError : ((BindException) exception).getBindingResult().getFieldErrors()) {
result.addError(fieldError.getField(), fieldError.getDefaultMessage());
}
} else if (exception instanceof MethodArgumentNotValidException) {
for (FieldError fieldError : ((MethodArgumentNotValidException) exception).getBindingResult().getFieldErrors()) {
result.addError(fieldError.getField(), fieldError.getDefaultMessage());
}
} else if (exception instanceof ConstraintViolationException) {
for (ConstraintViolation cv : ((ConstraintViolationException) exception).getConstraintViolations()) {
result.addError(cv.getPropertyPath().toString(), cv.getMessage());
}
}
return result;
}
/**
* 针对spring web 中的异常的处理
*
* @param exception Spring Web 异常
* @param request http request
* @param response http response
* @return 异常处理结果
*/
@ExceptionHandler(value = {
NoHandlerFoundException.class,
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class
})
public Result springWebExceptionHandler(Exception exception, HttpServletRequest request, HttpServletResponse response) {
log.error(MessageFormat.format("请求发生了非预期异常,出错的 url [{0}],出错的描述为 [{1}]",
request.getRequestURL().toString(), exception.getMessage()), exception);
Result result = new Result();
result.setDomain(domain);
if (exception instanceof NoHandlerFoundException) {
result.setCode(EnumExceptionMessageWebMvc.NO_HANDLER_FOUND_ERROR.getCode());
result.setMsg(EnumExceptionMessageWebMvc.NO_HANDLER_FOUND_ERROR.getMessage());
} else if (exception instanceof HttpRequestMethodNotSupportedException) {
result.setCode(EnumExceptionMessageWebMvc.HTTP_REQUEST_METHOD_NOT_SUPPORTED_ERROR.getCode());
result.setMsg(EnumExceptionMessageWebMvc.HTTP_REQUEST_METHOD_NOT_SUPPORTED_ERROR.getMessage());
} else if (exception instanceof HttpMediaTypeNotSupportedException) {
result.setCode(EnumExceptionMessageWebMvc.HTTP_MEDIA_TYPE_NOT_SUPPORTED_ERROR.getCode());
result.setMsg(EnumExceptionMessageWebMvc.HTTP_MEDIA_TYPE_NOT_SUPPORTED_ERROR.getMessage());
} else {
result.setCode(EnumExceptionMessageWebMvc.UNEXPECTED_ERROR.getCode());
result.setMsg(EnumExceptionMessageWebMvc.UNEXPECTED_ERROR.getMessage());
}
return result;
}
/**
* 针对全局异常的处理
*
* @param exception 全局异常
* @param request http request
* @param response http response
* @return 异常处理结果
*/
@ExceptionHandler(value = Throwable.class)
public Result throwableHandler(Exception exception, HttpServletRequest request, HttpServletResponse response) {
log.error(MessageFormat.format("请求发生了非预期异常,出错的 url [{0}],出错的描述为 [{1}]",
request.getRequestURL().toString(), exception.getMessage()), exception);
Result result = new Result();
result.setDomain(domain);
result.setCode(EnumExceptionMessageWebMvc.UNEXPECTED_ERROR.getCode());
result.setMsg(EnumExceptionMessageWebMvc.UNEXPECTED_ERROR.getMessage());
return result;
}
}
использовать
- Ненормальное использование развития бизнеса
// 此处只是简单演示,逻辑处理应该抽象在 mvc 分层中,业务开发过程中只需要抛异常即可。
@GetMapping
public String get() {
throw new ExtensionException(105001001L, "simple 资源不存在");
}
- Ненормальное использование базовой структуры
model
@Data
public class ValidatedModel {
@NotNull(message = "id 不能为空")
@Min(value = 10, message = "id 不能小于 10")
private Long id;
@NotBlank(message = "name 不能为空")
@Length(max = 5, message = "name 长度不能超过 5")
private String name;
}
controller
// 此处只是简单演示
@PostMapping("/test/validated")
public String getx(@Validated @RequestBody ValidatedModel model) {
return model.getName();
}
ссылка на код
Набор персонала
Инфраструктура единорога индустрии платформы электронной коммерции (яд APP) ищет инженеров / архитекторов Java / Golang / Kubernetes R & D, Base Shanghai Yangpu Internet Treasure, приглашаем заинтересованных студентов отправлять свои резюме на liweichao0102@gmail.com.