趁热记录下,给未来的自己
0. 前言
在一个项目乃乃至整个团队在所有项目的开发过程中,统一设计和规范使用错误码,是必须要落实的一个点。
错误码的设计,直接关系到后端接口的条理性和优雅性;错误码的使用,直接关系到开发人员的开发规范和开发效率。
所以,对错误码进行优雅的管理,是非常有必要的,是一个团队的基石之一。
下面,我会从如何设计,管理和多语言错误码的具体实现等三个方面一一阐述。
1. 设计
首先,需要对错误码大致分为两类:
- 通用错误码
- 特殊含义错误码
其次,错误码需要包含的要素:
- 错误码
- 中文错误信息
- 英文错误信息
1.1 通用错误码
主要包含:成功和失败(未知错误)
- SUCCESS( "10000", "成功", "ok" ),
- UNKNOWN_ERROR( "-1", "未知错误", "unknown error" )
1.2 特殊含义错误码
-
和用户相关的错误, 如: A_PARAM_VALIDATION_ERROR( "A0100", "参数校验失败", "parameter validation error" )
-
和第三方依赖相关的错误, 如: B_STORAGE_ERROR("B0100", "持久化存储错误", "storage error")
-
和当前业务系统相关的错误, 如: C_RESOURCE_OUT_ERROR( "C0100", "系统资源异常", "system resource error" )
2. 管理
由于目前没有人力投入到错误码管理平台的开发,市面上也没有比较合适的开源项目可以使用,因此,暂时使用飞书文档做错误码管理。略。
3. 具体实现
代码侧,首先要有一个枚举类,用于存放所有错误码。
错误码枚举类
public enum ReturnEnum {
/**
* 成功
*/
SUCCESS( "10000", "成功", "ok" ) ,
/**
* 未知错误
*/
UNKNOWN_ERROR( "-1", "未知错误", "unknown error" ) ,
/**
* 一级宏观错误码,和用户相关
*/
A_USER_CLIENT_ERROR( "A0000", "用户端错误", "user client error" ) ,
/**
* 一级宏观错误码,和第三方服务相关:数据库,缓存,第三方接口等
*/
B_GENERAL_3RD_ERROR( "B0000", "一般三方依赖错误", "general 3rd error" ) ,
/**
* 二级宏观错误码,持久化存储错误,主要和 mysql 相关
*/
B_STORAGE_ERROR( "B0100", "持久化存储错误", "storage error" ) ,
B_SQL_INTEGRITY_CONSTRAINT_VIOLATION_ERROR( "B0101", "违反数据库索引约束错误", "sql integrity constraint violation" ) ,
B_DUPLICATE_KEY_ERROR( "B0102", "违反数据库唯一索引约束错误", "duplicated key" ) ,
B_QUERY_NO_RESULT( "B0103", "没有查询到数据", "no data queried" ) ,
B_MINIO_UPLOAD_URL_ERROR( "B0104", "获取文件上传链接错误", "get upload file url error" ) ,
/**
* 二级宏观错误码,缓存存储错误,主要和 redis 相关
*/
B_CACHE_ERROR( "B0200", "缓存错误", "cache error" ) ,
B_CACHE_REDIS_SET_ERROR( "B0201", "缓存写入异常", "cache set error" ) ,
B_CACHE_REDIS_DEL_ERROR( "B0202", "缓存删除异常", "cache del error" ) ,
B_CACHE_REDIS_GET_ERROR( "B0203", "缓存获取异常", "cache get error" ) ,
/**
* 二级宏观错误码,和第三方接口有关
*/
B_3RD_API_ERROR( "B0300", "第三方接口异常", "3rd api error" ) ,
/**
* 一级宏观错误码,和当前系统相关:业务逻辑,程序健壮性等
*/
C_GENERAL_BUSINESS_ERROR( "B0000", "一般业务错误", "general business error" ) ,
/**
;
String msgCode;
String msgCn;
String msgEn;
}
增加一个拦截器LanguageInterceptor
public class LanguageInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle ( HttpServletRequest request, HttpServletResponse response, Object handler ) throws Exception {
String acceptLanguage = request.getHeader ( HttpHeaders.ACCEPT_LANGUAGE) ;
RequestHolderUtil.addLang( acceptLanguage ) ;
return true;
}
@Override
public void postHandle ( HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView ) throws Exception {
}
@Override
public void afterCompletion ( HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex ) throws Exception {
RequestHolderUtil.remove() ;
}
}
public class RequestHolderUtil {
private static final ThreadLocal<String> requestHolder = new ThreadLocal();
public RequestHolderUtil() {
}
public static void addLang(String language) {
requestHolder.set(language);
}
public static String getLang() {
return (String)requestHolder.get();
}
public static void remove() {
requestHolder.remove();
}
}
注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors ( InterceptorRegistry registry ) {
registry.addInterceptor ( new LanguageInterceptor ()) .addPathPatterns ( "/**" ) ;
}
}
返回包装类
@Data
@AllArgsConstructor
@Slf4j
public class ReturnBase implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 日志跟踪标识
*/
public static final String TRACE_ID = "TRACE_ID";
private String traceId;
private String msgCode;
private String msg;
private Object data;
private Integer total;
...
public static ReturnBase error ( ReturnEnum returnEnum ) {
return error( returnEnum, RequestHolderUtil.getLang()) ;
}
public static ReturnBase error(ReturnEnum returnEnum, String language) {
String msg;
if (Objects.isNull(language)) {
return new ReturnBase(MDC.get(TRACE_ID), returnEnum.getMsgCode(), returnEnum.getMsgEn(), null, 0);
}
switch(language){
case CommonConstant.SIMPLE_CHINESE:
msg = returnEnum.getMsgCn();
break;
default:
msg = returnEnum.getMsgEn();
break;
}
return new ReturnBase(MDC.get(TRACE_ID), returnEnum.getMsgCode(), msg, null, 0);
}
...
public static String MsgFormat ( String code, String msg ) {
return String.format( "%s (error: %s)", msg, code ) ;
}
}
使用示例
前端发起请求,在header
里携带HttpHeaders.``ACCEPT_LANGUAGE``=zh-Hans
;
后端LanguageInterceptor
拦截器拦截后,将ACCEPT_LANGUAGE
塞入当前请求的Threadlocal
*变量里;
后端返回错误码给前端:ReturnBase.error(``ReturnEnum``.``C_GENERAL_BUSINESS_ERROR``);
在ReturnBase.error()
方法中,会根据之前塞入到Threadlocal
里的ACCEPT_LANGUAGE
变量,自动选择返回中文还是英文的错误文案。