原理基于请求头传递错误消息,利用aop和全局异常拦截机制实现。
/**
* 全局异常处理器
*/
@RestControllerAdvice
@Configuration
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class)
public Object defaultErrorHandler(Exception e) throws Exception {
log.error("系统异常:{}", e);
return ResultUtils.error("系统异常", String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()));
}
/**
* 处理运行时异常
*
* @param ex
* @return
*/
@ExceptionHandler(value = RuntimeException.class)
public Object runtimeException(RuntimeException ex) {
log.error("系统异常:{}", ex);
return ResultUtils.error("系统异常!", String.valueOf(HttpStatus.EXPECTATION_FAILED.value()));
}
@ExceptionHandler(value = DecodeException.class)
public Object decodeException(DecodeException ex) {
log.error("系统异常:{}", ex);
return ResultUtils.error(ex.getMessage(), String.valueOf(ex.status()));
}
/**
* 处理自定义业务异常
*
* @param ex
* @return
*/
@ExceptionHandler(value = BusinessException.class)
public Object exceptionHandler(BusinessException ex) {
log.error("业务异常详情:{}", ex);
return ResultUtils.error(ex.getMessage(), ex.getCode());
}
@ExceptionHandler(value = UserContextLoseException.class)
public Object notLoginException(UserContextLoseException e) {
log.error("当前用户信息不存在异常:{}", e);
return ResultUtils.notLogin();
}
/**
* 方法入参校验异常处理 content-type!=application/json
*
* @param ex 异常
* @return Object
*/
@ExceptionHandler({BindException.class})
public Object bindExceptionException(BindException ex) {
List<String> validResult = ex.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
log.warn("参数非法:{}", ex);
return ResultUtils.error(validResult.get(0));
}
/**
* 方法入参校验异常处理 content-type=application/json
*
* @param ex 异常
* @return Object
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Object methodArgumentNotValidException(MethodArgumentNotValidException ex) {
List<String> validResult = ex.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
log.warn("参数非法:{}", ex);
return ResultUtils.error(validResult.get(0));
}
/**
* 请求类型不支持
*
* @param httpRequestMethodNotSupportedException
* @return
*/
@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
public Object handleNotSupportedHttpMethodException(HttpRequestMethodNotSupportedException httpRequestMethodNotSupportedException) {
log.error("HttpRequestMethodNotSupportedException:{}", httpRequestMethodNotSupportedException);
return ResultUtils.error(httpRequestMethodNotSupportedException.getMessage(), String.valueOf(HttpStatus.EXPECTATION_FAILED.value()));
}
/**
* media type not support
*
* @param httpMediaTypeNotSupportedException
* @return
*/
@ExceptionHandler(value = HttpMediaTypeNotSupportedException.class)
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
public Object handleNotSupportedHttpMethodException(HttpMediaTypeNotSupportedException httpMediaTypeNotSupportedException) {
log.error("HttpMediaTypeNotSupportedException:{}", httpMediaTypeNotSupportedException);
return ResultUtils.error(httpMediaTypeNotSupportedException.getMessage(), String.valueOf(HttpStatus.EXPECTATION_FAILED.value()));
}
}
@Slf4j
@Aspect
@Order(value = 100)
@Component
public class FeignExceptionAspect {
@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)" +
" || @annotation(org.springframework.web.bind.annotation.GetMapping)" +
" || @annotation(org.springframework.web.bind.annotation.PostMapping)" +
" || @annotation(org.springframework.web.bind.annotation.PutMapping)" +
" || @annotation(org.springframework.web.bind.annotation.DeleteMapping)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) {
try {
Object proceed = joinPoint.proceed();
return proceed;
} catch (BusinessException e) {
log.error("feign调用异常:{}", e.getMessage());
if (null != RequestContextHolder.getRequestAttributes() && null != ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse()) {
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_CODE_KEY, e.getCode());
response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_MESSAGE_KEY, Base64.encode(e.getMessage(), "UTF-8"));
}
throw e;
} catch (UserContextLoseException e) {
log.error("用户信息缺失:{}", e);
if (null != RequestContextHolder.getRequestAttributes() && null != ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse()) {
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_CODE_KEY, e.getCode());
response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_MESSAGE_KEY, Base64.encode(e.getMessage(), "UTF-8"));
}
throw e;
} catch (FeignException e) {
// 存在未发起远程调用前抛出的FeignException异常
if (e instanceof FeignException.ServiceUnavailable) {
FeignException.ServiceUnavailable serviceUnavailable = (FeignException.ServiceUnavailable) e;
log.error(serviceUnavailable.getMessage());
throw BusinessException.createException("服务不可用");
}
throw e;
} catch (Throwable throwable) {
Throwable cause = throwable.getCause();
while (null != cause && null != cause.getCause()) {
cause = cause.getCause();
}
if (cause instanceof BusinessException) {
if (null != RequestContextHolder.getRequestAttributes() && null != ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse()) {
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_CODE_KEY, ((BusinessException) cause).getCode());
response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_MESSAGE_KEY, Base64.encode((cause).getMessage(), CommonConsts.UTF8));
}
throw (BusinessException) cause;
} else if (cause instanceof UserContextLoseException) {
if (null != RequestContextHolder.getRequestAttributes() && null != ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse()) {
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_CODE_KEY, ((UserContextLoseException) cause).getCode());
response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_MESSAGE_KEY, Base64.encode((cause).getMessage(), CommonConsts.UTF8));
}
throw (UserContextLoseException) cause;
}
log.error("接口调用异常:{}", throwable);
throw new RuntimeException("未知异常");
}
}
}
@Slf4j
public class FeignExceptionClient implements Client {
@Override
public Response execute(Request request, Request.Options options) throws IOException {
Response response = new ApacheHttpClient().execute(request, options);
RequestTemplate requestTemplate = response.request().requestTemplate();
Target<?> target = requestTemplate.feignTarget();
String serviceName = target.name();
Class<?> type = target.type();
String api = type.getName();
String url = requestTemplate.url();
Collection<String> errorCodes = response.headers().get(FEIGN_RESPONSE_HEADER_ERROR_CODE_KEY);
Collection<String> errormessage = response.headers().get(FEIGN_RESPONSE_HEADER_ERROR_MESSAGE_KEY);
String errorMessage = null;
if (null != errormessage && !errormessage.isEmpty()) {
errorMessage = (String) ((List) errormessage).get(0);
errorMessage = Base64.decodeStr(errorMessage, CommonConsts.UTF8);
}
if (CollectionUtils.isNotEmpty(errorCodes)) {
logInvokeError(serviceName, api, url);
Object errorCode = ((List) errorCodes).get(0);
if (ResultConsts.NOT_LOGIN.toString().equals(errorCode)) {
throw UserContextLoseException.createException();
}
if (String.valueOf(HttpStatus.EXPECTATION_FAILED.value()).equals(errorCode)) {
throw BusinessException.createException("系统异常");
}
if (String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()).equals(errorCode)) {
throw BusinessException.createException("系统异常");
}
if (StringUtils.isNotEmpty(errorMessage)) {
throw BusinessException.createException(errorMessage);
}
throw BusinessException.createException("系统异常");
}
if (StringUtils.isNotEmpty(errorMessage)) {
logInvokeError(serviceName, api, url);
throw BusinessException.createException(errorMessage);
}
return response;
}
private void logInvokeError(String serviceName, String api, String method) {
log.error("调用微服务[{}]-Api[{}]-Uri[{}]异常", serviceName, api, method);
}
}
public class FeignDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
String typeName = type.getTypeName();// class void
if (StringUtils.isNotEmpty(typeName)) {
Response.Body body = response.body();
String resultString = Util.toString(body.asReader(Util.UTF_8));
ResponseVO responseVO = JSONObject.parseObject(resultString, ResponseVO.class);
if (null != responseVO) {
if (null != responseVO.getCode() && !responseVO.getCode().toString().endsWith(String.valueOf(ResultConsts.SUCCESS_STATUS))) {
// 2002000100 417 not-login 100 business
throw new DecodeException(responseVO.getCode(), responseVO.getMessage(), response.request());
}
}
Class<?> responseCls;
try {
responseCls = Class.forName(typeName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
return JSONObject.parseObject(resultString, responseCls);
}
return Util.emptyValueOf(type);
}
}
public class FeignErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
return new ErrorDecoder.Default().decode(methodKey, response);
}
}
bean注入
@Bean
public Decoder decoder() {
return new FeignDecoder()::decode;
}
@Bean
public ErrorDecoder errorDecoder() {
return new FeignErrorDecoder()::decode;
}