问题说明

开发中,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;

}

创建Feign异常响应拦截

它也是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;
    }
}

创建Feign异常拦截

这个是调用方的异常拦截,上面的例子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", "系统异常,请稍后重试");
        }
    }
}
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐