简介
模块采用 spring boot 下 spring mvc 的 ErrorController 或者 spring webflux 的 ErrorWebExceptionHandler ,进行的包装处理。
响应体结构
所有错误处理最终返回 ResponseVo 对象,结构如下:
{
"success": false,
"error": {
"code": 100109,
"message": "验证码错误",
"longMessage": null,
"source": null,
"details": null
},
"data": null,
"requestId": null
}
ErrorResponseVo 字段说明:
字段 |
说明 |
code |
应用级错误码 |
message |
错误消息 |
longMessage |
错误消息扩展版本(可选) |
source |
错误来源信息(可选) |
details |
错误详情列表,包含字段级错误信息 |
ErrorDetail 字段说明:
字段 |
说明 |
field |
字段名称(可选) |
code |
错误码 |
message |
错误消息 |
错误码定义
ErrorCode
实现 com.luamas.thinkbridge.common.base.exception.ErrorCode 接口
ErrorCode 接口定义了以下方法:
方法 |
说明 |
getValue() |
获取错误码数值 |
getName() |
获取错误码名称(用于国际化消息查找的key) |
getInfo() |
获取错误码描述信息 |
所有字段名需以 public static final 修饰,如:public static final ResultCode OK = new ResultCode(200, "base.OK", "OK");
|
package com.luamas.thinkbridge.common.base.exception;
/**
* 基础错误类型,各个模块需要实现此接口,要求所有的字段名,以`public static final`修饰,如:<br/>
* public static final BaseResultCode OK = new BaseResultCode(200, "base.OK", "OK");<br/>
* public static final BaseResultCode UNAUTHORIZED = new BaseResultCode(401, "base.UNAUTHORIZED", "未授权: {0}");<br/>
*
* @author luamas
*/
public interface ErrorCode {
int getValue();
String getName();
String getInfo();
}
示例
package com.luamas.thinkbridge.integration.demo;
import com.luamas.thinkbridge.common.auto.processor.AutoErrorCode;
import com.luamas.thinkbridge.common.base.exception.ErrorCode;
/**
* 错误码范围100000~200000
*
* @author luamas
*/
@AutoErrorCode
public class ResultCode implements ErrorCode {
public static final ResultCode CAPTCHA_ERROR = new ResultCode(100109, "integrationDemo.CAPTCHA_ERROR", "info");
public static final ResultCode USER_NOT_EXIST = new ResultCode(100105, "【{0}】原生描述", "info");
public static final ResultCode ERROR_DETAILS = new ResultCode(100106, "多个错误", "info");
public final int value;
private final String name;
private final String info;
private ResultCode(int value, String name, String info) {
this.value = value;
this.name = name;
this.info = info;
}
@Override
public int getValue() {
return value;
}
@Override
public String getName() {
return name;
}
@Override
public String getInfo() {
return info;
}
}
@AutoErrorCode 注解
使用 @AutoErrorCode 注解标注错误码类后,配合 thinkbridge-common-auto-processor 注解处理器,会在 class 路径下生成 META-INF/thinkbridge/error_code.factories 文件,用于收集所有错误码。
@AutoErrorCode
public class ResultCode implements ErrorCode {
public static final ResultCode CAPTCHA_ERROR = new ResultCode(100109, "integrationDemo.CAPTCHA_ERROR", "info");
public static final ResultCode USER_NOT_EXIST = new ResultCode(100105, "integrationDemo.USER_NOT_EXIST", "info");
}
获取所有错误码
Gradle 配置
这里必须使用thinkbridge的注解处理器,否则无法从 ErrorCodeService 获取到错误码
|
annotationProcessor("com.luamas.thinkbridge:thinkbridge-common-auto-processor")
compileOnly("com.luamas.thinkbridge:thinkbridge-common-auto-processor")
示例
定义错误码,并实现 ErrorCode 接口
package com.luamas.thinkbridge.integration.demo;
import com.luamas.thinkbridge.common.auto.processor.AutoErrorCode;
import com.luamas.thinkbridge.common.base.exception.ErrorCode;
/**
* 错误码范围100000~200000
*
* @author luamas
*/
@AutoErrorCode
public class ResultCode implements ErrorCode {
public static final ResultCode CAPTCHA_ERROR = new ResultCode(100109, "integrationDemo.CAPTCHA_ERROR", "info");
public static final ResultCode USER_NOT_EXIST = new ResultCode(100105, "【{0}】原生描述", "info");
public static final ResultCode ERROR_DETAILS = new ResultCode(100106, "多个错误", "info");
public final int value;
private final String name;
private final String info;
private ResultCode(int value, String name, String info) {
this.value = value;
this.name = name;
this.info = info;
}
@Override
public int getValue() {
return value;
}
@Override
public String getName() {
return name;
}
@Override
public String getInfo() {
return info;
}
}
所有实现 ErrorCode 接口,并被 @AutoErrorCode 进行注解,将会在class路径下产生 META-INF/thinkbridge/error_code.factories 文件来描述所有子类。
|
使用 ErrorCodeService 获取错误码
package com.luamas.thinkbridge.integration.demo.errorprocess;
import com.luamas.thinkbridge.common.base.exception.ErrorCode;
import com.luamas.thinkbridge.common.error.ErrorCodeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@RestController
@Tag(name = "错误码查询", description = "查询当前模块定义的所有错误码及国际化消息")
@RequestMapping("errorCode")
@RequiredArgsConstructor
public class ErrorCodeController {
private final ErrorCodeService errorCodeService;
@Operation(summary = "获取所有错误码", description = "按语言获取模块内注册的所有错误码定义")
@Parameter(name = "Accept-Language", description = "en或者zh_CN,默认使用浏览器自带的语言", in = ParameterIn.HEADER)
@GetMapping
public Map<String, List<ErrorCode>> test(Locale locale) {
return errorCodeService.getI18nErrorCodeObjects(locale);
}
}
错误消息国际化
错误处理支持消息国际化,默认会查找 i18n/error_message.properties 文件。
工作原理
-
ErrorCode实现类的getMsgCode()返回消息码 -
错误处理模块通过
I18nMessageSourceFactory查找对应的消息 -
消息文件路径为
i18n/error_message前缀的 properties 文件 -
支持
{0},{1}等占位符,通过ErrorCode.getArgs()传入参数
错误码定义
package com.luamas.thinkbridge.integration.demo;
import com.luamas.thinkbridge.common.auto.processor.AutoErrorCode;
import com.luamas.thinkbridge.common.base.exception.ErrorCode;
/**
* 错误码范围100000~200000
*
* @author luamas
*/
@AutoErrorCode
public class ResultCode implements ErrorCode {
public static final ResultCode CAPTCHA_ERROR = new ResultCode(100109, "integrationDemo.CAPTCHA_ERROR", "info");
public static final ResultCode USER_NOT_EXIST = new ResultCode(100105, "【{0}】原生描述", "info");
public static final ResultCode ERROR_DETAILS = new ResultCode(100106, "多个错误", "info");
public final int value;
private final String name;
private final String info;
private ResultCode(int value, String name, String info) {
this.value = value;
this.name = name;
this.info = info;
}
@Override
public int getValue() {
return value;
}
@Override
public String getName() {
return name;
}
@Override
public String getInfo() {
return info;
}
}
属性配置
消息查找规则
查找键 |
说明 |
{msgCode} |
优先使用 |
error.NOTFOUND |
页面未找到的默认消息 |
error.ERROR |
通用错误的默认消息 |
error.BEAUTIFY_MESSAGE |
未知异常的美化消息 |
控制器示例
package com.luamas.thinkbridge.integration.demo.errorprocess;
import com.luamas.thinkbridge.common.base.exception.BusinessException;
import com.luamas.thinkbridge.common.base.vo.ErrorDetail;
import com.luamas.thinkbridge.common.base.vo.ResponseVo;
import com.luamas.thinkbridge.integration.demo.ResultCode;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@RestController
@Tag(name = "统一错误处理")
@RequestMapping("errorprocess")
public class ErrorProcessController {
@GetMapping("test1")
@ResponseBody
@Parameter(name = "Accept-Language", description = "en或者zh_CN,默认使用浏览器自带的语言", in = ParameterIn.HEADER)
@Operation(summary = "国际化错误信息")
public ResponseVo<String> test1(@RequestParam(value = "userId", required = false) String userId) {
throw BusinessException.of(ResultCode.CAPTCHA_ERROR, userId);
}
@GetMapping("test2")
@ResponseBody
@Parameter(name = "Accept-Language", description = "en或者zh_CN,默认使用浏览器自带的语言", in = ParameterIn.HEADER)
@Operation(summary = "非国际化信息,全部统一返回中文信息,不建议")
public ResponseVo<String> test2(@RequestParam(value = "userId", required = false) String userId) {
throw BusinessException.of(ResultCode.USER_NOT_EXIST, userId);
}
@GetMapping("test3")
@ResponseBody
@Operation(summary = "未知异常,这里会触发一个告警")
public ResponseVo<String> test3() {
throw new RuntimeException("未捕获异常,会抛出一个默认值");
}
@PostMapping("test4")
@ResponseBody
@Operation(summary = "对象校验")
public ResponseVo<String> test4(@Valid User user) {
return ResponseVo.success();
}
@GetMapping("test5")
@ResponseBody
@Operation(summary = "自定义多错误异常")
public ResponseVo<String> test5() {
List<ErrorDetail> errorDetails = new ArrayList<>();
errorDetails.add(ErrorDetail.of(1, "发生错误1"));
errorDetails.add(ErrorDetail.of(2, "发生错误2"));
errorDetails.add(ErrorDetail.of(3, "发生错误3"));
throw BusinessException.ofDetails(ResultCode.ERROR_DETAILS, errorDetails);
}
@Getter
@Setter
static class User {
@NotBlank
private String username;
}
}
消息码的命名建议使用 模块名.错误名 的格式,如 integrationDemo.CAPTCHA_ERROR,避免不同模块间的消息码冲突。
|
未知错误处理
当异常没有被任何 ErrorExceptionHandler 处理时,将进入未知错误处理流程。
默认输出为堆栈信息,生产环境中可能暴露敏感信息。
自定义处理
实现 com.luamas.thinkbridge.common.error.UnknownErrorResponseHandler 接口,将未知异常消息设置为固定信息,避免展示堆栈信息:
package com.luamas.thinkbridge.integration.demo.errorprocess;
import com.luamas.thinkbridge.common.error.ErrorResponseHandler;
import com.luamas.thinkbridge.common.error.ErrorResult;
import com.luamas.thinkbridge.common.error.UnknownErrorResponseHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Locale;
@Profile("errorprocess")
//@Component
@RequiredArgsConstructor
public class MyUnknownErrorResponseHandler implements UnknownErrorResponseHandler {
private final ErrorResponseHandler<?> errorResponseHandler;
@Override
public ErrorResult<?> handle(Throwable throwable, HttpStatus status, Locale locale) {
// 定义一个统一的异常码
int code = 50000000;
return errorResponseHandler.error("未知异常,debug可以打印此异常", null, code, status.value(), null, new HashMap<>(), locale);
}
}
自定义异常
如果不想用ThinkBridge定义好的 com.luamas.thinkbridge.common.base.exception 下的异常 ,只需要继承 com.luamas.thinkbridge.common.base.exception.AbstractException
并实现 com.luamas.thinkbridge.common.error.ErrorExceptionHandler。
ThinkBridgeException 接口定义了以下方法:getCode()、getMsgCode()、getArgs()、getMessage()、getHttpStatus()、getData()、getDetails()、getExtend()、isAlarmEnabled()。自定义异常时需确保正确实现这些方法。
|
ErrorExceptionHandler 接口
ErrorExceptionHandler 接口方法如下
方法 |
说明 |
supports(Throwable) |
判断是否支持处理该异常 |
handle(Throwable, HttpStatus, Locale) |
处理异常并返回 ErrorResult |
package com.luamas.thinkbridge.common.error;
import org.springframework.http.HttpStatus;
import java.util.Locale;
public interface ErrorExceptionHandler {
int DEFAULT_ERROR_CODE = 1500;
/
* 是否支持
*
* @param exception
* @return
*/
boolean supports(Throwable exception);
/
* 进行处理
*
* @param exception
* @param status
* @return
*/
ErrorResult<?> handle(Throwable exception, HttpStatus status, Locale locale);
}
BaseErrorExceptionHandler 默认实现
BaseErrorExceptionHandler 是默认的异常处理器,处理流程如下:
-
判断异常是否为
TransmitException,如果是则直接透传原始响应 -
判断异常是否为
ThinkBridgeException,提取 code、msgCode、data、details、extend 等信息 -
将 details 放入 extend 中(key 为
THINK-BRIDGE-ERROR-DETAILS) -
调用
ErrorResponseHandler生成最终错误响应
package com.luamas.thinkbridge.common.error;
import com.luamas.thinkbridge.common.base.exception.ThinkBridgeException;
import com.luamas.thinkbridge.common.base.exception.TransmitException;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
@RequiredArgsConstructor
public class BaseErrorExceptionHandler implements ErrorExceptionHandler {
private final int defaultCode;
private final ErrorResponseHandler<?> errorResponseHandler;
@Override
public boolean supports(Throwable exception) {
return exception instanceof ThinkBridgeException;
}
@Override
public ErrorResult<?> handle(Throwable exception, HttpStatus status, Locale locale) {
if (exception instanceof TransmitException transmitException) {
return new ErrorResult<>(transmitException, HttpStatus.resolve(transmitException.getHttpStatus()));
}
if (exception instanceof ThinkBridgeException thinkBridgeException) {
return handleThinkBridgeException(thinkBridgeException, status, locale);
}
return null;
}
private ErrorResult<?> handleThinkBridgeException(ThinkBridgeException ex, HttpStatus status, Locale locale) {
int httpStatus = ex.getHttpStatus();
int code = ex.getCode() != 0 ? ex.getCode() : defaultCode;
String message = ex.getMsgCode();
Object data = ex.getData();
Map<String, Object> extend = new HashMap<>(ex.getExtend());
if (!ex.getDetails().isEmpty()) {
extend.put(ErrorConst.ERROR_DETAILS, ex.getDetails());
}
return errorResponseHandler.error(message, ex.getArgs(), code, httpStatus, data, extend, locale);
}
}
自定义异常示例
// 1. 自定义异常类
public class MyException extends AbstractException {
protected MyException(int code, String msgCode, Object... args) {
super(code, msgCode, args);
}
public static MyException of(ErrorCode errorCode, Object... args) {
return new MyException(errorCode.getValue(), errorCode.getName(), args);
}
}
// 2. 自定义异常处理器
@Component
public class MyExceptionHandler implements ErrorExceptionHandler {
@Override
public boolean supports(Throwable exception) {
return exception instanceof MyException;
}
@Override
public ErrorResult<?> handle(Throwable exception, HttpStatus status, Locale locale) {
MyException ex = (MyException) exception;
// 自定义处理逻辑
return new ErrorResult<>(ResponseVo.error(ex.getCode(), ex.getMessage()), HttpStatus.resolve(ex.getHttpStatus()));
}
}
自定义返回体
如果不想用ThinkBridge定义好的 com.luamas.thinkbridge.common.base.vo.ResponseVo ,只需要实现 com.luamas.thinkbridge.common.error.ErrorResponseHandler 泛型接口即可。
ErrorResponseHandler 接口
ErrorResponseHandler 接口方法如下
方法 |
说明 |
success(Object data) |
构建成功响应 |
error(message, args, errorCode, statusCode, data, extend, locale) |
构建错误响应(推荐使用) |
旧版 error(String message, Object[] args, Integer errorCode, Integer statusCode, Object data, Locale locale) 方法已标记为 @Deprecated,请使用带 extend 参数的新方法。
|
package com.luamas.thinkbridge.common.error;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
public interface ErrorResponseHandler<T> {
ErrorResult<T> success(Object data);
/
* 返回ResponseVoResponseEntity
*
* @param message 消息
* @param args 消息参数
* @param errorCode 错误码
* @param statusCode 状态码
* @param data 数据,默认可以为空
* @param locale 本地化
* @return
*/
@Deprecated
default ErrorResult<T> error(String message, Object[] args, Integer errorCode, Integer statusCode, Object data, Locale locale) {
return error(message, args, errorCode, statusCode, data, new HashMap<>(), locale);
}
/
* 返回ResponseVoResponseEntity
*
* @param message 消息
* @param args 消息参数
* @param errorCode 错误码
* @param statusCode 状态码
* @param data 数据,默认可以为空
* @param locale 本地化
* @return
*/
ErrorResult<T> error(String message, Object[] args, Integer errorCode, Integer statusCode, Object data, Map<String, Object> extend, Locale locale);
}
ThinkBridgeErrorResponseHandler 默认实现
默认实现 ThinkBridgeErrorResponseHandler 的处理逻辑:
-
通过
ErrorMessageSourceUtils对消息进行国际化处理 -
如果
compatibility为 true,对无法国际化的消息尝试使用String.format格式化 -
从
extend中提取details错误详情列表 -
构建
ResponseVo.error()响应体 -
如果 HTTP 状态码为 500,自动改为 200(避免触发前端异常处理)
package com.luamas.thinkbridge.common.error;
import com.luamas.thinkbridge.common.base.vo.ErrorDetail;
import com.luamas.thinkbridge.common.base.vo.ResponseVo;
import com.luamas.thinkbridge.common.spring.utils.ErrorMessageSourceUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@Slf4j
@RequiredArgsConstructor
public class ThinkBridgeErrorResponseHandler implements ErrorResponseHandler<ResponseVo<?>> {
/
* 是否兼容处理
*/
private final boolean compatibility;
@Override
public ErrorResult<ResponseVo<?>> success(Object data) {
ResponseVo<?> responseVo = ResponseVo.success(data);
return new ErrorResult<>(responseVo, HttpStatus.OK);
}
/
* 返回ResponseVoResponseEntity
*
* @param message 消息
* @param args 消息参数
* @param errorCode 错误码
* @param statusCode 状态码
* @param data 数据,默认可以为空
* @return
*/
public ErrorResult<ResponseVo<?>> error(String message, Object[] args, Integer errorCode, Integer statusCode, Object data, Map<String, Object> extend, Locale locale) {
HttpStatus status;
ResponseVo<?> responseVo;
try {
message = ErrorMessageSourceUtils.getMessage(message, locale, args);
} catch (Exception ex) {
if (compatibility) {
if (message.contains("%s")) {
try {
message = String.format(message, args);
} catch (Exception e) {
log.warn("Failed to format error message with String.format: {}", message, e);
}
}
}
}
if (compatibility) {
if (message.contains("%s")) {
try {
message = String.format(message, args);
} catch (Exception e) {
log.warn("Failed to format error message with String.format: {}", message, e);
}
}
}
List<ErrorDetail> details = null;
if (extend.containsKey(ErrorConst.ERROR_DETAILS)) {
details = (List<ErrorDetail>) extend.get(ErrorConst.ERROR_DETAILS);
}
responseVo = ResponseVo.error(errorCode, message, data, details);
if (500 == statusCode) {
statusCode = 200;
}
status = HttpStatus.valueOf(statusCode);
return new ErrorResult<>(responseVo, status);
}
}
自定义返回体示例
@Component
public class MyErrorResponseHandler implements ErrorResponseHandler<MyResponseVo> {
@Override
public ErrorResult<MyResponseVo> success(Object data) {
return new ErrorResult<>(MyResponseVo.success(data), HttpStatus.OK);
}
@Override
public ErrorResult<MyResponseVo> error(String message, Object[] args, Integer errorCode, Integer statusCode, Object data, Map<String, Object> extend, Locale locale) {
MyResponseVo responseVo = MyResponseVo.error(errorCode, message);
return new ErrorResult<>(responseVo, HttpStatus.valueOf(statusCode));
}
}
自定义默认错误码
如果不想用ThinkBridge定义好的 com.luamas.thinkbridge.common.error.DefaultExceptionConfigUtils 默认的异常错误码规则
ConfigExceptionClassData 属性
属性 |
说明 |
status |
HTTP 状态码,不配置则使用传递来的 status |
code |
错误码,为空则使用全局默认错误码 |
message |
错误消息,为空则默认使用 exception 的消息 |
extend |
扩展数据,传递给下层使用,默认空 |
默认异常映射表
DefaultExceptionConfigUtils 内置了以下异常映射:
Security 相关
异常类 |
HTTP 状态码 |
错误码 |
AuthenticationException |
401 |
1200 |
AccessDeniedException |
403 |
1205 |
OAuth2AuthenticationException |
401 |
1403 |
Spring MVC 相关
异常类 |
HTTP 状态码 |
错误码 |
HttpRequestMethodNotSupportedException |
- |
1001 |
HttpMediaTypeNotSupportedException |
- |
1003 |
HttpMediaTypeNotAcceptableException |
- |
1004 |
MissingPathVariableException |
- |
1005 |
MissingServletRequestParameterException |
- |
1006 |
ServletRequestBindingException |
- |
1007 |
ConversionNotSupportedException |
- |
1008 |
TypeMismatchException |
- |
1009 |
HttpMessageNotReadableException |
- |
1010 |
HttpMessageNotWritableException |
- |
1011 |
MethodArgumentNotValidException |
200 |
1012 |
MissingServletRequestPartException |
- |
1013 |
BindException |
200 |
1014 |
NoHandlerFoundException |
404 |
1015 |
NoResourceFoundException |
404 |
1016 |
DefaultExceptionConfigUtils 类如下
package com.luamas.thinkbridge.common.error;
import java.util.HashMap;
import java.util.Map;
/**
* @author : luamas
* @date : 2023/1/6 15:35
*/
public class DefaultExceptionConfigUtils {
public static Map<String, ConfigExceptionClassData> getConfigExceptions() {
Map<String, ConfigExceptionClassData> map = new HashMap<>();
// security
map.put("org.springframework.security.core.AuthenticationException", new ConfigExceptionClassData(401, 1200, null, new HashMap<>()));
map.put("org.springframework.security.access.AccessDeniedException", new ConfigExceptionClassData(403, 1205, null, new HashMap<>()));
// oauth2
map.put("org.springframework.security.oauth2.core.OAuth2AuthenticationException", new ConfigExceptionClassData(401, 1403, null, new HashMap<>()));
// spring
map.put("org.springframework.web.HttpRequestMethodNotSupportedException", new ConfigExceptionClassData(null, 1001, null, new HashMap<>()));
map.put("org.springframework.web.HttpMediaTypeNotSupportedException", new ConfigExceptionClassData(null, 1003, null, new HashMap<>()));
map.put("org.springframework.web.HttpMediaTypeNotAcceptableException", new ConfigExceptionClassData(null, 1004, null, new HashMap<>()));
map.put("org.springframework.web.bind.MissingPathVariableException", new ConfigExceptionClassData(null, 1005, null, new HashMap<>()));
map.put("org.springframework.web.bind.MissingServletRequestParameterException", new ConfigExceptionClassData(null, 1006, null, new HashMap<>()));
map.put("org.springframework.web.bind.ServletRequestBindingException", new ConfigExceptionClassData(null, 1007, null, new HashMap<>()));
map.put("org.springframework.beans.ConversionNotSupportedException", new ConfigExceptionClassData(null, 1008, null, new HashMap<>()));
map.put("org.springframework.beans.TypeMismatchException", new ConfigExceptionClassData(null, 1009, null, new HashMap<>()));
map.put("org.springframework.http.converter.HttpMessageNotReadableException", new ConfigExceptionClassData(null, 1010, null, new HashMap<>()));
map.put("org.springframework.http.converter.HttpMessageNotWritableException", new ConfigExceptionClassData(null, 1011, null, new HashMap<>()));
map.put("org.springframework.web.bind.MethodArgumentNotValidException", new ConfigExceptionClassData(200, 1012, "error.FAILVERIFY", new HashMap<>()));
map.put("org.springframework.web.multipart.support.MissingServletRequestPartException", new ConfigExceptionClassData(null, 1013, null, new HashMap<>()));
map.put("org.springframework.validation.BindException", new ConfigExceptionClassData(200, 1014, "error.FAILVERIFY", new HashMap<>()));
// spring mvc
map.put("org.springframework.web.servlet.NoHandlerFoundException", new ConfigExceptionClassData(404, 1015, null, new HashMap<>()));
map.put("org.springframework.web.servlet.resource.NoResourceFoundException", new ConfigExceptionClassData(404, 1016, null, new HashMap<>()));
return map;
}
}
替换默认规则
使用 default-config-exceptions 会完全覆盖默认配置,使用 custom-config-exceptions 则是在默认配置基础上追加。
|
# 覆盖默认配置
think:
error:
default-config-exceptions:
org.springframework.security.core.AuthenticationException:
status: 401
code: 1200
# 追加自定义配置(不影响默认配置)
think:
error:
custom-config-exceptions:
java.sql.SQLException:
status: 500
code: 1500
message: "数据库错误"
自定义错误码
未知异常配置
对未知异常可以进行以下配置
配置属性
属性 |
默认值 |
说明 |
unknownEnableBeautifyMessage |
false |
未知异常是否启用美化消息,生产建议启用 |
unknownCode |
500 |
未知异常的错误码 |
unknownBeautifyMessage |
error.BEAUTIFY_MESSAGE |
未知异常的美化消息,支持国际化配置 |
defaultCode |
1500 |
默认错误码 |
notfound |
1504 |
页面未找到的错误码 |
defaultConfigExceptions |
(内置) |
默认异常映射配置,完全覆盖 |
customConfigExceptions |
空 |
自定义异常映射配置,追加到默认配置 |
package com.luamas.thinkbridge.bootstarter.error.configuration;
import com.luamas.thinkbridge.common.error.ConfigExceptionClassData;
import com.luamas.thinkbridge.common.error.DefaultExceptionConfigUtils;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.HashMap;
import java.util.Map;
/
* @author : luamas
* @date : 2023/1/6 14:51
*/
@ConfigurationProperties(prefix = "think.error")
@Getter
@Setter
public class ThinkBridgeErrorProperties {
/
* 未知异常启用美化消息,生产建议启用,默认打印堆栈异常信息
*/
private boolean unknownEnableBeautifyMessage = false;
/
* 未知异常的错误码,默认为500
*/
private int unknownCode = 500;
/
* 未知异常的美化消息,支持国际化配置
*/
private String unknownBeautifyMessage = "error.BEAUTIFY_MESSAGE";
/
* 默认错误码,默认为1500
*/
private int defaultCode = 1500;
/
* 页面未找到的错误码,默认为1504
*/
private int notfound = 1504;
/
* 异常相关错误码等配置
*/
private Map<String, ConfigExceptionClassData> defaultConfigExceptions = DefaultExceptionConfigUtils.getConfigExceptions();
/
* 异常相关错误码等配置
*/
private Map<String, ConfigExceptionClassData> customConfigExceptions = new HashMap<>();
public Map<String, ConfigExceptionClassData> getConfigExceptions() {
Map<String, ConfigExceptionClassData> map = new HashMap<>();
map.putAll(defaultConfigExceptions);
map.putAll(customConfigExceptions);
return map;
}
}
配置示例
# 下面演示了对所有SQL错误的拦截
think:
error:
unknown-enable-beautify-message: false # 未知异常启用美化消息,生产建议启用,默认打印堆栈异常信息
unknown-code: 500 # 未知异常的错误码,默认为500
unknown-beautify-message: "error.BEAUTIFY_MESSAGE" # 未知异常的美化消息,支持国际化配置,默认:"error.BEAUTIFY_MESSAGE"
default-code: 1500 # 默认错误码,默认为1500
notfound: 1504 # 页面未找到的错误码,默认为1504
think:
error:
# 启用未知异常美化消息
unknown-enable-beautify-message: true
# 未知异常错误码
unknown-code: 500
# 默认错误码
default-code: 1500
# 页面未找到错误码
notfound: 1504
异常使用
Thinkbridge中大部分异常全部实现了 com.luamas.thinkbridge.common.base.exception.ThinkBridgeException 接口。
ThinkBridgeException 接口定义了以下方法:
方法 |
说明 |
getCode() |
异常码 |
getMsgCode() |
异常消息码(用于国际化查找) |
getArgs() |
异常消息参数 |
getMessage() |
异常消息(已国际化处理) |
getHttpStatus() |
HTTP 状态码,默认 200 |
getData() |
业务数据,默认 null |
getDetails() |
错误详情列表,默认空列表 |
getExtend() |
扩展数据,默认空 Map |
isAlarmEnabled() |
是否触发告警,默认 true |
目前包含了以下常用异常:
BusinessException
com.luamas.thinkbridge.common.base.exception.BusinessException
统一的业务异常类,通过链式 API 控制行为,替代了旧版多个分散的异常类。
基本用法
// 基本业务异常
throw BusinessException.of(ResultCode.CAPTCHA_ERROR, userId);
// 带业务数据
throw BusinessException.ofData(ResultCode.XXX, dataObj);
// 带错误详情列表
throw BusinessException.ofDetails(ResultCode.ERROR_DETAILS, errorDetails);
链式配置
// 关闭告警
throw BusinessException.of(ResultCode.XXX, args).noAlarm();
// 自定义 HTTP 状态码
throw BusinessException.of(ResultCode.XXX, args).httpStatus(400);
// 设置扩展数据
throw BusinessException.of(ResultCode.XXX, args)
.extend(Map.of("key1", "value1"));
// 添加单条扩展数据
throw BusinessException.of(ResultCode.XXX, args)
.putExtend("key1", "value1");
// 组合使用
throw BusinessException.of(ResultCode.XXX, args)
.noAlarm()
.httpStatus(400)
.putExtend("key1", "value1");
属性说明
属性 |
说明 |
httpStatus |
HTTP 状态码,默认 200 |
data |
业务数据,通过 ofData() 设置 |
details |
错误详情列表,通过 ofDetails() 设置 |
alarmEnabled |
是否触发告警,默认 true,通过 noAlarm() 关闭 |
extend |
扩展数据,通过 putExtend() 设置 |
旧版异常迁移对照
旧版 |
新版 |
BusinessException |
|
BusinessDataException |
|
BusinessErrorDetailsException |
|
NoRollbackException |
由事务管理器根据异常类型判断,不再需要 |
NoRollbackDataException |
由事务管理器根据异常类型判断,不再需要 |
NoneAlarmException |
|
IdempotentException |
|
SuccessException |
使用正常的返回值替代,不再支持 |
ErrorDetail
com.luamas.thinkbridge.common.base.vo.ErrorDetail
错误详情对象,用于在异常中携带多个字段级错误信息。
基本用法
// 使用错误码和消息创建
ErrorDetail detail1 = ErrorDetail.of(1, "发生错误1");
// 使用字段名、错误码和消息创建
ErrorDetail detail2 = ErrorDetail.of("fieldName", 2, "字段错误");
// 使用 ErrorCode 创建(支持国际化)
ErrorDetail detail3 = ErrorDetail.of(ResultCode.CAPTCHA_ERROR, userId);
配合 BusinessException 使用
List<ErrorDetail> errorDetails = new ArrayList<>();
errorDetails.add(ErrorDetail.of(1, "发生错误1"));
errorDetails.add(ErrorDetail.of(2, "发生错误2"));
throw BusinessException.ofDetails(ResultCode.ERROR_DETAILS, errorDetails);
代码如下:
package com.luamas.thinkbridge.common.base.exception;
import com.luamas.thinkbridge.common.base.vo.ErrorDetail;
import java.util.List;
import java.util.Map;
/
* 统一业务异常类,通过属性控制行为:
* <ul>
* <li>httpStatus: HTTP 状态码,默认 200</li>
* <li>data: 业务数据,null 表示无数据</li>
* <li>details: 错误详情列表,null 表示无详情</li>
* <li>alarmEnabled: 是否触发告警,默认 true</li>
* <li>extend: 扩展数据</li>
* </ul>
*
* @author luamas
/
public class BusinessException extends AbstractException {
protected BusinessException(int code, String msgCode, Object... args) {
super(code, msgCode, args);
}
protected BusinessException(Throwable cause, int code, String msgCode, Object... args) {
super(cause, code, msgCode, args);
}
/
* 创建业务异常
*/
public static BusinessException of(ErrorCode errorCode, Object... args) {
return new BusinessException(errorCode.getValue(), errorCode.getName(), args);
}
public static BusinessException of(Throwable cause, ErrorCode errorCode, Object... args) {
return new BusinessException(cause, errorCode.getValue(), errorCode.getName(), args);
}
public static BusinessException of(int code, String msgCode, Object... args) {
return new BusinessException(code, msgCode, args);
}
/
* 创建带业务数据的业务异常
*/
public static BusinessException ofData(ErrorCode errorCode, Object data, Object... args) {
BusinessException ex = new BusinessException(errorCode.getValue(), errorCode.getName(), args);
ex.data = data;
return ex;
}
/
* 创建带错误详情的业务异常
*/
public static BusinessException ofDetails(ErrorCode errorCode, List<? extends ErrorDetail> details, Object... args) {
BusinessException ex = new BusinessException(errorCode.getValue(), errorCode.getName(), args);
ex.details = details;
return ex;
}
/
* 关闭告警
*/
public BusinessException noAlarm() {
this.alarmEnabled = false;
return this;
}
/
* 设置 HTTP 状态码
*/
public BusinessException httpStatus(int httpStatus) {
this.httpStatus = httpStatus;
return this;
}
/
* 设置扩展数据
*/
public BusinessException extend(Map<String, Object> extend) {
this.extend = extend;
return this;
}
/
* 添加扩展数据
*/
public BusinessException putExtend(String key, Object value) {
this.extend.put(key, value);
return this;
}
// ========== 兼容旧版 API (不推荐使用) ==========
/
* @deprecated 使用 {@link #of(ErrorCode, Object...)} 替代
*/
@Deprecated
public static BusinessException newInstance(ErrorCode errorCode, Object... args) {
return of(errorCode, args);
}
/
* @deprecated 使用 {@link #of(Throwable, ErrorCode, Object...)} 替代
*/
@Deprecated
public static BusinessException newInstance(Throwable cause, ErrorCode errorCode, Object... args) {
return of(cause, errorCode, args);
}
/*
* @deprecated 使用 {@link #of(int, String, Object...)} 替代
*/
@Deprecated
public static BusinessException newInstance(int code, String msgCode, Object... args) {
return of(code, msgCode, args);
}
}
UnauthorizedException
com.luamas.thinkbridge.common.base.exception.UnauthorizedException
返回 HTTP 状态码 401,使用 of() 创建实例
throw UnauthorizedException.of(ResultCode.XXX, args);
代码如下:
package com.luamas.thinkbridge.common.base.exception;
/
* 没有授权,此异常会抛出401
*
* @author luamas
*/
public class UnauthorizedException extends AbstractException {
protected UnauthorizedException(int code, String msgCode, Object... args) {
super(code, msgCode, args);
this.httpStatus = 401;
}
protected UnauthorizedException(Throwable cause, int code, String msgCode, Object... args) {
super(cause, code, msgCode, args);
this.httpStatus = 401;
}
public static UnauthorizedException of(ErrorCode errorCode, Object... args) {
return new UnauthorizedException(errorCode.getValue(), errorCode.getName(), args);
}
public static UnauthorizedException of(Throwable cause, ErrorCode errorCode, Object... args) {
return new UnauthorizedException(cause, errorCode.getValue(), errorCode.getName(), args);
}
public static UnauthorizedException of(int code, String msgCode, Object... args) {
return new UnauthorizedException(code, msgCode, args);
}
/
* @deprecated 使用 {@link #of(ErrorCode, Object...)} 替代
*/
@Deprecated
public static UnauthorizedException newInstance(ErrorCode errorCode, Object... args) {
return of(errorCode, args);
}
/
* @deprecated 使用 {@link #of(Throwable, ErrorCode, Object...)} 替代
*/
@Deprecated
public static UnauthorizedException newInstance(Throwable cause, ErrorCode errorCode, Object... args) {
return of(cause, errorCode, args);
}
/
* @deprecated 使用 {@link #of(int, String, Object...)} 替代
*/
@Deprecated
public static UnauthorizedException newInstance(int code, String msgCode, Object... args) {
return of(code, msgCode, args);
}
}
ForbiddenException
com.luamas.thinkbridge.common.base.exception.ForbiddenException
返回 HTTP 状态码 403,使用 of() 创建实例
throw ForbiddenException.of(ResultCode.XXX, args);
代码如下:
package com.luamas.thinkbridge.common.base.exception;
/
* 没有授权,此异常会抛出403
*
* @author luamas
/
public class ForbiddenException extends AbstractException {
protected ForbiddenException(int code, String msgCode, Object... args) {
super(code, msgCode, args);
this.httpStatus = 403;
}
protected ForbiddenException(Throwable cause, int code, String msgCode, Object... args) {
super(cause, code, msgCode, args);
this.httpStatus = 403;
}
public static ForbiddenException of(ErrorCode errorCode, Object... args) {
return new ForbiddenException(errorCode.getValue(), errorCode.getName(), args);
}
public static ForbiddenException of(Throwable cause, ErrorCode errorCode, Object... args) {
return new ForbiddenException(cause, errorCode.getValue(), errorCode.getName(), args);
}
/
* @deprecated 使用 {@link #of(ErrorCode, Object...)} 替代
*/
@Deprecated
public static ForbiddenException newInstance(ErrorCode errorCode, Object... args) {
return of(errorCode, args);
}
/*
* @deprecated 使用 {@link #of(Throwable, ErrorCode, Object...)} 替代
*/
@Deprecated
public static ForbiddenException newInstance(Throwable cause, ErrorCode errorCode, Object... args) {
return of(cause, errorCode, args);
}
}
TransmitException
com.luamas.thinkbridge.common.base.exception.TransmitException
用来转义其他的 HTTP 错误为 TransmitException 异常,进行统一处理(如 feign 即可按照原始的 http 值进行转义抛出)。
TransmitException 不实现 ThinkBridgeException 接口,它直接继承 RuntimeException,用于透传原始的 ResponseVo 响应体。
|
基本用法
// 转发异常,保留原始 HTTP 状态码和响应体
throw TransmitException.of(httpStatus, responseVo);
// 仅传入 ResponseVo,默认 HTTP 状态码 200
throw TransmitException.of(responseVo);
// 自定义消息
throw TransmitException.of("自定义消息", httpStatus, responseVo);
代码如下:
package com.luamas.thinkbridge.common.base.exception;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.luamas.thinkbridge.common.base.vo.ResponseVo;
import lombok.Getter;
import lombok.Setter;
/
* 异常转发
*
* @author luamas
*/
@Getter
@Setter
public class TransmitException extends RuntimeException {
@JsonIgnore
private Integer httpStatus;
@JsonUnwrapped
private ResponseVo<?> response;
protected TransmitException(String message, Integer httpStatus, ResponseVo<?> response) {
super(message);
this.httpStatus = httpStatus;
this.response = response;
}
public static TransmitException of(String message, Integer httpStatus, ResponseVo<?> response) {
return new TransmitException(message, httpStatus, response);
}
public static TransmitException of(Integer httpStatus, ResponseVo<?> response) {
return of(response.getError().getMessage(), httpStatus, response);
}
public static TransmitException of(ResponseVo<?> response) {
return of(200, response);
}
/
* @deprecated 使用 {@link #of(String, Integer, ResponseVo)} 替代
*/
@Deprecated
public static TransmitException newInstance(String message, Integer httpStatus, ResponseVo<?> response) {
return of(message, httpStatus, response);
}
/
* @deprecated 使用 {@link #of(Integer, ResponseVo)} 替代
*/
@Deprecated
public static TransmitException newInstance(Integer httpStatus, ResponseVo<?> response) {
return of(httpStatus, response);
}
/
* @deprecated 使用 {@link #of(ResponseVo)} 替代
*/
@Deprecated
public static TransmitException newInstance(ResponseVo<?> response) {
return of(response);
}
}
InternalException
com.luamas.thinkbridge.common.base.exception.InternalException
内部错误异常,返回 HTTP 状态码 500。适用于服务端内部逻辑错误,如数据库异常、第三方服务调用失败等场景。
基本用法
// 使用 ErrorCode 创建
throw InternalException.of(ResultCode.XXX, args);
// 带原始异常创建
throw InternalException.of(cause, ResultCode.XXX, args);
代码如下:
package com.luamas.thinkbridge.common.base.exception;
/
* 内部错误异常,所有业务异常都必须继承于此异常
*
* @author luamas
/
public class InternalException extends AbstractException {
/
* 建议使用of(int code, String msgCode, Object... args)
* @param code 异常码
* @param msgCode 异常消息码
* @param args 异常消息参数
*/
protected InternalException(int code, String msgCode, Object... args) {
super(code, msgCode, args);
this.httpStatus = 500;
}
/
* 建议使用of(Throwable cause, int code, String msgCode, Object... args)
* @param cause 异常原因
* @param code 异常码
* @param msgCode 异常消息码
* @param args 异常消息参数
*/
protected InternalException(Throwable cause, int code, String msgCode, Object... args) {
super(cause, code, msgCode, args);
this.httpStatus = 500;
}
public static InternalException of(ErrorCode errorCode, Object... args) {
return new InternalException(errorCode.getValue(), errorCode.getName(), args);
}
public static InternalException of(Throwable cause, ErrorCode errorCode, Object... args) {
return new InternalException(cause, errorCode.getValue(), errorCode.getName(), args);
}
/
* @deprecated 使用 {@link #of(ErrorCode, Object...)} 替代
*/
@Deprecated
public static InternalException newInstance(ErrorCode errorCode, Object... args) {
return of(errorCode, args);
}
/*
* @deprecated 使用 {@link #of(Throwable, ErrorCode, Object...)} 替代
*/
@Deprecated
public static InternalException newInstance(Throwable cause, ErrorCode errorCode, Object... args) {
return of(cause, errorCode, args);
}
}