开课吧WEB前端高级工程师17期完结无密内附资料文档
download:zxit666 3w
JAVA轻量级错误码设计最佳理论
设计目的
先谈公司现状,我们目前就是经过抛出Java异常,将异常信息Message返回给其他效劳或者前端,这样有什么问题?
- 不好交流沟通,原本只需经过一个明白的错误码就能沟通分明,用异常信息会带来含糊性。
- 只要异常音讯,很难定位详细是哪个效劳的缘由,详细缘由是什么,由于异常音讯比拟灵敏,可能是不同效劳、同效劳不同模块都有类似的。
因而,我们需求设计一套错误码机制,那一个优秀的错误码设计应该到达什么目的或者有什么准绳呢?
我们能够参考下阿里巴巴《Java 开发手册》- 异常日志-错误码的内容。
错误码的制定准绳:快速溯源、简单易记、沟通规范化。
阐明:错误码想得过于圆满和复杂,就像康熙字典中的生僻字一样,用词似乎精准,但是字典不容易随身携带并且简单易懂。
正例:错误码答复的问题是谁的错?错在哪?
1)错误码必需可以快速知晓错误来源,可快速判别是谁的问题。
2)错误码易于记忆和比对(代码中容易 equals)。
3)错误码可以脱离文档和系统平台到达线下轻量化地自在沟通的目的。
错误码设计
错误码设计,主要思索下面几个点:
- 错误码的格式该是什么样?
- 错误码在java中该如何表示,枚举,常量?
- 错误码怎样保证独一性,开发可能不晓得曾经被占用了,该如何防止?
- 错误码该如何返回?
错误码格式
错误码格式一方面请求精简,同时也要表现出它的效劳组件和模块信息。
错误码的数据类型能够是字符串,比方"SDM_USER_001", 也能够用数值表示10203等,他们能够利害,用字符串可能比拟直观,用数字比拟精简,但是运用纯数字来停止错误码编排不利于理性记忆和分类。
错误码在项目中表示
错误码在项目中该如何呈现呢?
- 错误码统一经过枚举类表现,能够轻松的表示枚举的代码和对应的含义,易于维护。
- 依照项目+模块粒度定义成多个错误码枚举类,也就是一个模块一个错误码的枚举类。
- 定义出公共的错误码的接口,让各个效劳组件、模块完成,统一错误码的参数。
- 别离进项目模块编码,增加模块编码枚举的复用性。
模块接口代码如下:
public interface ProjectModule {
/**
* 项目编码
*/
int getProjectCode();
/**
* 模块编码
*/
int getModuleCode();
/**
* 项目称号
*/
String getProjectName();
/**
* 模块称号
*/
String getModuleName();
}
复制代码
详细的模块:
@Getter
@AllArgsConstructor
public enum UserProjectCodes implements ProjectModule {
/**
* 登录模块
*/
LOGIN(1, 1, "用户中心", "登录模块"),
/**
* 用户管理模块
*/
USER(1, 2, "用户中心", "用户模块");
private int projectCode;
private int moduleCode;
private String projectName;
private String moduleName;
}
复制代码
错误码接口代码如下:
public interface ErrorCode {
/**
* 最细粒度code,不包含project、module信息
*/
int getNodeNum();
/**
* 异常信息 英文
*/
String getMsg();
/**
* 拼接project、module、node后的完好的错误码
*/
default int getCode() {
return ErrorManager.genCode(this);
}
default ProjectModule projectModule(){
return ErrorManager.projectModule(this);
}
}
复制代码
详细错误码枚举:
@Getter
public enum UserErrorCodes implements ErrorCode {
/**
* 用户不存在
*/
USER_NOT_EXIST(0, "用户名不存在"),
/**
* 密码错误
*/
PASSWORD_ERROR(1, "密码错误");
private final int nodeNum;
private final String msg;
UserErrorCodes(int nodeNum, String msg) {
this.nodeNum = nodeNum;
this.msg = msg;
// 注册错误码,也就是绑定这个错误码属于哪个模块的
ErrorManager.register(UserProjectCodes.USER, this);
}
}
复制代码
错误码防重校验
错误码请求全局独一,不能存在反复抵触,那么怎样经过程序来做一个校验。
我们在上面定义错误码枚举的结构函数中,有了一个注册错误码的操作,我们其实能够在这里做反复校验的逻辑。
ErrorManager.register(UserProjectCodes.USER, this);
复制代码
public static void register(ProjectModule projectModule, ErrorCode errorCode) {
Preconditions.checkNotNull(projectModule);
Preconditions.checkArgument(projectModule.getProjectCode() >= 0);
Preconditions.checkArgument(projectModule.getModuleCode() >= 0);
Preconditions.checkArgument(errorCode.getNodeNum() >= 0);
int code = genCode(projectModule, errorCode);
// 假如存在反复,抛出异常
Preconditions.checkArgument(!GLOBAL_ERROR_CODE_MAP.containsKey(code), "错误码反复:" + code);
GLOBAL_ERROR_CODE_MAP.put(code, errorCode);
ERROR_PROJECT_MODULE_MAP.put(errorCode, projectModule);
}
复制代码
这样就会抛出异常,提早晓得存在反复的错误码。
返回错误码信息
接口调用后,该如何返回错误码信息呢? 普通我们的项目中都会定义统一的返回对象,该对象普通会有code, msg信息,如下:
public class Result extends ErrorInfo {
private int code;
private String msg;
private T data;
.....
}
错误码和异常
错误码和异常常常是分不开的,能够经过设计良好的异常体系,将错误码文雅的返回给调用方:
- 自定义异常, 自定义异常中包含了错误信息的属性字段
- 假如程序逻辑中出错,能够设置对应的错误码信息,抛出自定义异常
- 统一拦截自定义异常,后去其中的异常信息,返回给恳求方
异常体系:
笼统出异常的统一接口:
public interface IErrorCodeException {
/**
* 错误信息,获取异常的错误信息
*/
ErrorInfo getErrorInfo();
/**
* 模块信息,获取异常属于哪个模块的
*/
ProjectModule projectModule();
}
复制代码
public class ErrorInfo {
static final Map<Integer, ErrorInfo> NO_PARAM_CODES_MAP = new ConcurrentHashMap<>();
static final Map<String, ErrorInfo> ERROR_MSG_CODES_MAP = new ConcurrentHashMap<>();
/**
* 错误码
*/
@Getter
private final int code;
/**
* 返回错误信息 英文
*/
@Getter
private final String msg;
.......
}
复制代码
自定义运转时异常笼统基类:
@Getter
public abstract class BaseRuntimeException extends RuntimeException implements IErrorCodeException{
final ErrorInfo errorInfo;
protected BaseRuntimeException(String message) {
super(message);
this.errorInfo = ErrorInfo.parse(message);
}
protected BaseRuntimeException(String message, Throwable cause) {
super(message, cause);
this.errorInfo = ErrorInfo.parse(message);
}
protected BaseRuntimeException(Throwable cause) {
super(cause);
this.errorInfo = ErrorInfo.parse(cause.getMessage());
}
protected BaseRuntimeException(ErrorInfo errorInfo) {
super(errorInfo.toString());
this.errorInfo = errorInfo;
}
protected BaseRuntimeException(ErrorCode errorCode) {
this(ErrorInfo.parse(errorCode));
ProjectModule.check(projectModule(), errorCode.projectModule());
}
protected BaseRuntimeException(ErrorCode errorCode, Object... args) {
this(ErrorInfo.parse(errorCode, args));
ProjectModule.check(projectModule(), errorCode.projectModule());
}
@Override
public ErrorInfo getErrorInfo() {
return errorInfo;
}
}
复制代码
详细的模块异常:
public class UserException extends BaseRuntimeException {
protected UserException(String message) {
super(message);
}
protected UserException(String message, Throwable cause) {
super(message, cause);
}
protected UserException(Throwable cause) {
super(cause);
}
protected UserException(ErrorInfo errorInfo) {
super(errorInfo);
}
protected UserException(ErrorCode errorCode) {
super(errorCode);
}
protected UserException(ErrorCode errorCode, Object... args) {
super(errorCode, args);
}
@Override
public ProjectModule projectModule() {
return UserProjectCodes.USER;
}
}
复制代码
这里的异常体系中绑定了属于哪个模块,主要是从严谨性的角度动身,由于错误码自身是绑定了模块的,这时分再将错误码设置到异常中,能够做一个校验,能否是属于同一个模块。
统一拦截异常返回
经过spring提供的异常拦截注解 @ControllerAdvice
,完成对异常的统一处置。
@ResponseBody
@ExceptionHandler(value = Throwable.class)
public ResponseEntity<Result<?>> processException(HttpServletRequest request, Exception e) {
Pair<Throwable, String> pair = getExceptionMessage(e);
// 假如是自定义异常
if (e instanceof IErrorCodeException) {
if (e.getCause() != null) {
log.error("error, request: {}", parseParam(request), e.getCause());
} else {
log.error("error: {}, request: {}", pair.getRight(), parseParam(request));
}
ErrorInfo errorInfo = ((IErrorCodeException) e).getErrorInfo();
Result<?> apiResult;
if (errorInfo == null) {
apiResult = Result.error(SystemErrorCodes.SYSTEM_ERROR.getCode(), pair.getRight());
} else {
apiResult = Result.error(errorInfo.getCode(), errorInfo.getMsg());
}
return new ResponseEntity<>(apiResult, HttpStatus.OK);
}
log.error("error, request: {}", parseParam(request), e);
// 返回系统异常
Result errorResult = Result.error(SystemErrorCodes.SYSTEM_ERROR.getCode(), pair.getLeft().getClass().getSimpleName() + ": " + pair.getRight());
return new ResponseEntity<>(errorResult, HttpStatus.OK);
}
复制代码
系统错误码处置
由于不同模块会存在一些公共的系统异常,针对这种,我们需求内置定义一个系统的错误码:
public enum SystemErrorCodes implements ErrorCode {
SUCCESS(0, "ok"),
SYSTEM_ERROR(1, "system error");
private final int nodeNum;
private final String msg;
SystemErrorCodes(int nodeNum, String msg) {
this.nodeNum = nodeNum;
this.msg = msg;
ErrorManager.register(SystemProjectModule.INSTANCE, this);
}
}
复制代码
便当查看错误码表
最后这个也很关键,常常我们需求看下系统中全量的错误码,这时分我们能够开放一个接口获取系统中全量错误码,或者将他们展现到前端页面中。
public static List getAllErrorCodes() {
return ERROR_PROJECT_MODULE_MAP.entrySet().stream()
.sorted((it1, it2) -> ERROR_CODE_COMPARATOR.compare(it1.getKey(), it2.getKey()))
.collect(Collectors.groupingBy(Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, Collectors.toList())))
.entrySet()
.stream()
.sorted((it1, it2) -> PROJECT_MODULE_COMPARATOR.compare(it1.getKey(), it2.getKey()))
.collect(Collectors.groupingBy(
e -> new TreeNode(e.getKey().getProjectCode(), e.getKey().getProjectName()),
Collectors.groupingBy(
it -> new TreeNode(it.getKey().getModuleCode(), it.getKey().getModuleName()),
Collectors.mapping(Map.Entry::getValue, Collectors.toList())
)
)
)
.entrySet()
.stream()
.map(e -> {
TreeNode top = e.getKey();
List middleNode = e.getValue()
.entrySet()
.stream()
.map(e1 -> {
TreeNode key = e1.getKey();
List leftNode = e1.getValue().stream()
.flatMap(Collection::stream)
.map(errorCode -> new TreeNode(errorCode.getCode(), errorCode.getMsg()))
.collect(Collectors.toList());
key.setNodes(leftNode);
return key;
})
.collect(Collectors.toList());
top.setNodes(middleNode);
return top;
})
.collect(Collectors.toList());
}
总结
本文主要解说了错误码设计的计划,希望对大家有协助,假如有更好的计划设计,也能够留言,一同生长进步。