赞
踩
开发中,A服务调用B服务时,B服务中参数校验未通过抛出了自定义异常,错误码是10001,错误消息是“XXXXX不能为空”,返回到A服务时,A服务的feign异常拦截无法获取到错误码10001。
OpenFeign的FeignException返回的异常信息默认status为500。导致10001code丢失。
每个服务的自定义异常可能不一样,不过无所谓,主要是异常时获取code和message并透传到前端。
@Data public class BizException extends RuntimeException implements Serializable { private static final long serialVersionUID = 1L; private String code; private String message; public BizException(){} /** * 抛出时,需指定错误码和错误消息 * @param code * @param message */ public BizException(String code, String message) { super(); this.code = code; this.message = message; } }
这是发生异常时,统一返回的数据实体。放在common包下,每个服务公用。
@Data public class ExceptionInfo { /** * 异常时间 */ private String timestamp; /** * 自定义异常码 */ private String code; /** * 自定义异常消息 */ private String message; /** * 异常url */ private String path; }
它也是Http,和处理controller全局异常一样。
假设现在有个包叫feignimpl,所有的feign接口实现都在这个包下。
这个是"被调用方"处理本服务如果发生了异常,返回ExceptionInfo实体的json数据。
上面的例子B服务就是被调用方。
@Slf4j @RestControllerAdvice({"com.demo.center.feignimpl"}) public class FeignExceptionHandler { //抛出的异常可能是自定义异常,也可能是其他运行时异常 @ResponseBody @ExceptionHandler(value = {Exception.class}) public ExceptionInfo handleFeignStatusException(Exception e, HttpServletRequest request, HttpServletResponse response) { log.warn(e.getMessage(), e); //必须要设置response的status。不是200就行。 //比如统一约定服务间调用异常为555错误码 response.setStatus(555); //如果是自定义业务异常 if (e instanceof BizException) { BizException bize = (BizException) e; //构建返回实体 ExceptionInfo ei = new ExceptionInfo(); //异常时间 ei.setTimestamp("时间随便想怎么写就怎么写"); //自定义的错误码 ei.setCode(bize.getCode()); //自定义的错误消息提示 ei.setMessage(bize.getMessage()); //请求的URI ei.setPath(request.getRequestURI()); return ei; } else if (e instanceof UserException){ //如果有其他的自定义异常,在这里添加即可 } //如果是其他的运行时异常,可以统一返回"系统异常,请稍后重试" //或者报警、邮件等其他处理方式 ExceptionInfo ei = new ExceptionInfo(); ei.setTimestamp("时间随便想怎么写就怎么写"); ei.setCode("111111"); ei.setMessage("系统异常,请稍后重试"); ei.setPath(request.getRequestURI()); return ei; } }
这个是调用方的异常拦截,上面的例子A服务时调用方。
@Slf4j @Configuration public class ExceptionErrorDecoder implements ErrorDecoder { @Override public Exception decode(String s, Response response){ try { if (response.body() != null) { //会把异常信息转换成字符串,注意断点不要打在这一行,会报IO异常 //断点可以打在它的下一行 String body = Util.toString(response.body().asReader(Charset.defaultCharset())); //将字符串转换为自定义的异常信息 ExceptionInfo ei = GsonUtil.jsonStrToObj(body, ExceptionInfo.class); //返回异常信息,随便返回哪个异常都行,主要是将code和message透传到前端 return new BizException(ei.getCode(), ei.getMessage()); } } catch (Exception ex){ //异常记录日志 log.warn("Feign异常处理错误:", ex); } //默认返回"系统异常,请稍后重试" return new BizException("500", "系统异常,请稍后重试"); } }
这个主要是拦截controller层的异常了。
@Slf4j @RestControllerAdvice({"com.demo.center.controller"}) public class GlobalJsonExceptionController { /** * ResponseBody 的controller 统一处理异常 自定义异常 * @param e * @return */ @ResponseBody @ExceptionHandler(value = {Exception.class}) public Response exception(Exception e) { log.warn(e.getMessage(), e); if (e instanceof IllegalArgumentException) { return Response.buildFailed(ResultCode.ILLEGAL_PARAM.getCode(), ResultCode.ILLEGAL_PARAM.getDesc()); } else if (e instanceof BizException) { return Response.buildFailed(((BizException) e).getCode(), e.getMessage()); } else if (e instanceof MethodArgumentNotValidException) { BindingResult bindingResult = ((MethodArgumentNotValidException) e).getBindingResult(); List<FieldError> errors = bindingResult.getFieldErrors(); //拼接message StringJoiner sj = new StringJoiner(","); for (FieldError error : errors) { sj.add(error.getDefaultMessage()); } return Response.buildFailed("400", sj.toString()); } else { return Response.buildFailed("500", "系统异常,请稍后重试"); } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。