####异常码的设计和使用
为了更快的确定异常原因,从而快速定位出错地点并解决异常。可以使用异常码,每个异常码都对应一个特定的错误。本项目参考阿里巴巴开发手册,将出错原因大体上分为3类:用户端出错,服务器端出错以及远端第三方组件出错,分别用大写英文字母A、B、C标识,每个类别下使用6位数字来表示出错原因,因而总共可以表示30万种错误。每个大类中根据内容详细程度不同,分为3个级别,级别越高内容越详细。
异常码设计
1 2 3 4
| public Interface IErrorCode{ String code; String message; }
|
基础异常码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
|
public enum BaseErrorCode implements IErrorCode {
CLIENT_ERROR("A000001", "用户端错误"),
USER_REGISTER_ERROR("A000100", "用户注册错误"), USER_NAME_VERIFY_ERROR("A000110", "用户名校验失败"), USER_NAME_EXIST_ERROR("A000111", "用户名已存在"), USER_NAME_SENSITIVE_ERROR("A000112", "用户名包含敏感词"), USER_NAME_SPECIAL_CHARACTER_ERROR("A000113", "用户名包含特殊字符"), PASSWORD_VERIFY_ERROR("A000120", "密码校验失败"), PASSWORD_SHORT_ERROR("A000121", "密码长度不够"), PHONE_VERIFY_ERROR("A000151", "手机格式校验失败"),
IDEMPOTENT_TOKEN_NULL_ERROR("A000200", "幂等Token为空"), IDEMPOTENT_TOKEN_DELETE_ERROR("A000201", "幂等Token已被使用或失效"),
SERVICE_ERROR("B000001", "系统执行出错"), SERVICE_TIMEOUT_ERROR("B000100", "系统执行超时"),
REMOTE_ERROR("C000001", "调用第三方服务出错");
private final String code;
private final String message;
BaseErrorCode(String code, String message) { this.code = code; this.message = message; }
@Override public String code() { return code; }
@Override public String message() { return message; } }
|
异常的设计和使用
根据异常码的分类,因此产生的异常也分为3类.
异常类设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import com.nageoffer.shortlink.admin.common.convention.errorcode.IErrorCode; import lombok.Getter; import org.springframework.util.StringUtils;
import java.util.Optional;
@Getter public abstract class AbstractException extends RuntimeException {
public final String errorCode;
public final String errorMessage;
public AbstractException(String message, Throwable throwable, IErrorCode errorCode) { super(message, throwable); this.errorCode = errorCode.code(); this.errorMessage = Optional.ofNullable(StringUtils.hasLength(message) ? message : null).orElse(errorCode.message()); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import com.nageoffer.shortlink.admin.common.convention.errorcode.BaseErrorCode; import com.nageoffer.shortlink.admin.common.convention.errorcode.IErrorCode;
public class ClientException extends AbstractException {
public ClientException(IErrorCode errorCode) { this(null, null, errorCode); }
public ClientException(String message) { this(message, null, BaseErrorCode.CLIENT_ERROR); }
public ClientException(String message, IErrorCode errorCode) { this(message, null, errorCode); }
public ClientException(String message, Throwable throwable, IErrorCode errorCode) { super(message, throwable, errorCode); }
@Override public String toString() { return "ClientException{" + "code='" + errorCode + "'," + "message='" + errorMessage + "'" + '}'; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import com.nageoffer.shortlink.admin.common.convention.errorcode.BaseErrorCode; import com.nageoffer.shortlink.admin.common.convention.errorcode.IErrorCode;
public class RemoteException extends AbstractException {
public RemoteException(String message) { this(message, null, BaseErrorCode.REMOTE_ERROR); }
public RemoteException(String message, IErrorCode errorCode) { this(message, null, errorCode); }
public RemoteException(String message, Throwable throwable, IErrorCode errorCode) { super(message, throwable, errorCode); }
@Override public String toString() { return "RemoteException{" + "code='" + errorCode + "'," + "message='" + errorMessage + "'" + '}'; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import com.nageoffer.shortlink.admin.common.convention.errorcode.BaseErrorCode; import com.nageoffer.shortlink.admin.common.convention.errorcode.IErrorCode;
import java.util.Optional;
public class ServiceException extends AbstractException {
public ServiceException(String message) { this(message, null, BaseErrorCode.SERVICE_ERROR); }
public ServiceException(IErrorCode errorCode) { this(null, errorCode); }
public ServiceException(String message, IErrorCode errorCode) { this(message, null, errorCode); }
public ServiceException(String message, Throwable throwable, IErrorCode errorCode) { super(Optional.ofNullable(message).orElse(errorCode.message()), throwable, errorCode); }
@Override public String toString() { return "ServiceException{" + "code='" + errorCode + "'," + "message='" + errorMessage + "'" + '}'; } }
|
在处理异常上,设置全局异常拦截器,统一处理产生的所有异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.StrUtil; import com.nageoffer.shortlink.admin.common.convention.errorcode.BaseErrorCode; import com.nageoffer.shortlink.admin.common.convention.exception.AbstractException; import com.nageoffer.shortlink.admin.common.convention.result.Result; import com.nageoffer.shortlink.admin.common.convention.result.Results; import jakarta.servlet.http.HttpServletRequest; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Optional;
@Component @Slf4j @RestControllerAdvice public class GlobalExceptionHandler {
@SneakyThrows @ExceptionHandler(value = MethodArgumentNotValidException.class) public Result validExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException ex) { BindingResult bindingResult = ex.getBindingResult(); FieldError firstFieldError = CollectionUtil.getFirst(bindingResult.getFieldErrors()); String exceptionStr = Optional.ofNullable(firstFieldError) .map(FieldError::getDefaultMessage) .orElse(StrUtil.EMPTY); log.error("[{}] {} [ex] {}", request.getMethod(), getUrl(request), exceptionStr); return Results.failure(BaseErrorCode.CLIENT_ERROR.code(), exceptionStr); }
@ExceptionHandler(value = {AbstractException.class}) public Result abstractException(HttpServletRequest request, AbstractException ex) { if (ex.getCause() != null) { log.error("[{}] {} [ex] {}", request.getMethod(), request.getRequestURL().toString(), ex.toString(), ex.getCause()); return Results.failure(ex); } log.error("[{}] {} [ex] {}", request.getMethod(), request.getRequestURL().toString(), ex.toString()); return Results.failure(ex); }
@ExceptionHandler(value = Throwable.class) public Result defaultErrorHandler(HttpServletRequest request, Throwable throwable) { log.error("[{}] {} ", request.getMethod(), getUrl(request), throwable); return Results.failure(); }
private String getUrl(HttpServletRequest request) { if (StringUtils.isEmpty(request.getQueryString())) { return request.getRequestURL().toString(); } return request.getRequestURL().toString() + "?" + request.getQueryString(); } }
|
@SneakyThrows注解:可作用于方法和构造函数上,可以帮助捕获异常,简化代码,比如
1 2 3 4 5 6 7 8
| public void utf8ToString(byte[] bytes) { try { return new String(bytes, "UTF-8"); } catch (Exception e) { throw Lombok.sneakyThrow(e); } }
|
使用该注解后,可以使代码更加简洁。该注解位于Lombok中
1 2 3 4 5
| @SneakyThrows public void utf8ToString(byte[] bytes) { return new String(bytes, "UTF-8"); }
|
通过本节需要掌握
- 异常码的设计和使用
- 异常类的设计和使用
- 全局异常处理器的设计
- @SneakyThrows注解的使用