RestTemple调用第三方API接口报错抛出异常无法返回API接口错误信息Message

  • Post author:
  • Post category:其他


最近遇到一个棘手的问题,有个需求需要写一些调用外部API接口的方法,因为没有SDK,所以我只能手动的去调用,框架采用的是springboot,Java语言编写,但是却出现了当调用异常,包括参数错误,token无效等直接抛出异常400,并没有获取到API接口的异常信息,只有我们自己抛出的异常信息。这样接口的调用方肯定不知道接口调用异常原因。

一、RestTemple实现API调用

我采用的使用springboot的restTemple来去调用接口,具体的实现是先定义一个controller接口,然后再在service层进行逻辑处理。

@Autowired
private RestTemplate restTemplate=new RestTemplate();

//service层代码
public String updateProduct(ProductParam productParam) {
    //spliceupdateProductUrl方法是拼接接口参数
    String url = spliceupdateProductUrl(productParam,"--此处填写API的接口地址--");
    //调用restTemple的get请求方法,直接返回response地址
    String response = restTemplate.getForObject(url,String.class);
    return response;
}

成功的结果:我们要求都是返回这种格式

{“message”:””,”code”:0,”data”:{}}

失败的结果:

{

“timestamp”: “2020-05-29T07:44:38.031+0000”,

“status”: 500,

“error”: “Internal Server Error”,

“message”: “400 Bad Request,

“path”: “/updateProduct”

}

直接抛出异常,message是调用接口的异常信息,这里缺少一部分说明,为什么我会知道呢?因为我试过直接在postman上调用API的时候,相同的url和参数,postman是有说明的,如下:

{“message”:”111 is an invalid id”,”code”:1000,”data”:2014}

二、追踪源代码寻找问题

我首先是通过debug追踪源码的方式去找问题所在:


第一步:

RestTemple.class


第二步:

RestTemple.class


第三步:

RestTemple.class


第四步

:RestTemple.class


第五步

:RestTemple.class


第六步

:ResponseErrorHandler.class


第七步

:ResponseErrorHandler.class的默认处理器,继续追踪


第八步

:DefaultResponseErrorHandler.class


第九步

:DefaultResponseErrorHandler.class,这里我发现一个问题,算是机缘巧合,我发现spring不同版本存在Restemple的

DefaultResponseErrorHandler源码存在差异,在下面这个方法的差异

2.2.1.RELEASE版本的DefaultResponseErrorHandler.class

2.2.6.RELEASE版本的DefaultResponseErrorHandler.class

三、尝试解决问题,不断调试

从我们不断的追踪源码发现了一些问题,这个时候我们可以升级一下spring的版本,再次运行上述代码

成功的结果:我们要求都是返回这种格式

{“message”:””,”code”:0,”data”:{}}

失败的结果:

{

“timestamp”: “2020-05-29T07:44:38.031+0000”,

“status”: 500,

“error”: “Internal Server Error”,

“message”: “400 Bad Request: [{“message”:”111 is an invalid id”,”code”:1000,”data”:2014}],

“path”: “/updateProduct”

}

这个时候我们已经可以打印出了api接口的message,但是发现返回的结果是抛出的异常信息,默认的errorhandler是通过封装接口的异常信息到自己的异常中,然后再抛出异常,因此,我们可以选择捕获异常的message。


捕获异常

@Override
public String updateProduct(ProductParam productParam) {
    String url = spliceupdateProductUrl(productParam,"--api接口url--");
    try {
        String response = restTemplate.getForObject(url,String.class);
        return response;
    }catch (Exception e){
        return e.getMessage();
    }

}


抛出异常运行结果

400 Bad Request: [{“message”:”111 is an invalid id”,”code”:1000,”data”:2014}]



这个时候我们只要json字符串怎么办?

{“message”:”111 is an invalid id”,”code”:1000,”data”:2014}

两种处理方法:

一 自定义异常处理器

就可以看到我们上面发现的自定义异常处理器了,我们可以自定义异常处理器,重写几个debug的时候走过的DefaultErrorHandle的方法,来自定义Message,不仅如此,我们也可以自定义一些其他的操作,例如编码格式转换等等。

debug找到打印信息的地方,进入getErrorMessage方法看:


第一步:写一个异常处理器

public class ReturnErrorJsonHandler extends DefaultResponseErrorHandler {

    //调用父类方法就行
    public boolean hasError(ClientHttpResponse response) throws IOException {
        return super.hasError(response);
    }
    
    //重写该方法调用重写的getErrorMessage方法
    public void handleError(ClientHttpResponse response) throws IOException {
        HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode());
        if (statusCode == null) {
            byte[] body = this.getResponseBody(response);
            String message = this.getErrorMessage( body, this.getCharset(response));
            throw new UnknownHttpStatusCodeException(message, response.getRawStatusCode(), response.getStatusText(), response.getHeaders(), body, this.getCharset(response));
        } else {
            this.handleError(response, statusCode);
        }
    }
    
    //重写该方法,让它返回我们要的格式
    private String getErrorMessage( @Nullable byte[] responseBody, @Nullable Charset charset) {
        if (ObjectUtils.isEmpty(responseBody)) {
            return "[no body]";
        } else {
            charset = charset == null ? StandardCharsets.UTF_8 : charset;
            int maxChars = 200;
            if (responseBody.length < maxChars * 2) {
                //主要是这里
                String message=  new String(responseBody, charset);
                //Unicode 转换为汉字
                return UnicodeToString(message);
            } else {
                try {
                    Reader reader = new InputStreamReader(new ByteArrayInputStream(responseBody), charset);
                    CharBuffer buffer = CharBuffer.allocate(maxChars);
                    reader.read(buffer);
                    reader.close();
                    buffer.flip();
                    return buffer.toString() + "... (" + responseBody.length + " bytes)";
                } catch (IOException var9) {
                    throw new IllegalStateException(var9);
                }
            }
        }
    }


    /**
     * 将 Unicode 转换为汉字
     * */
    public String UnicodeToString(String str) {
        Pattern pattern = Pattern.compile("(\\\\u(\\p{XDigit}{4}))");
        Matcher matcher = pattern.matcher(str);
        char ch;
        while (matcher.find()) {
            ch = (char) Integer.parseInt(matcher.group(2), 16);
            str = str.replace(matcher.group(1), ch + "");
        }
        return str;
    }

    //重写该方法调用重写的getErrorMessage方法
    protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
        String statusText = response.getStatusText();
        HttpHeaders headers = response.getHeaders();
        byte[] body = this.getResponseBody(response);
        Charset charset = this.getCharset(response);
        String message = this.getErrorMessage(body, charset);
        switch(statusCode.series()) {
            case CLIENT_ERROR:
                throw HttpClientErrorException.create(message, statusCode, statusText, headers, body, charset);
            case SERVER_ERROR:
                throw HttpServerErrorException.create(message, statusCode, statusText, headers, body, charset);
            default:
                throw new UnknownHttpStatusCodeException(message, statusCode.value(), statusText, headers, body, charset);
        }
    }


}


uincode码和uincode转换为汉字参考链接:  https://blog.csdn.net/iteye_17867/article/details/82541722

第二步:RestTemple放入自定义处理器

@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate=new RestTemplate();
    restTemplate.setErrorHandler(new ReturnErrorJsonHandler());
    return restTemplate;
}

二 捕获HttpClientErrorException

运行结果:现在即使是异常我们也可以直接抛出json数据了

{“message”:”111 is an invalid id”,”code”:1000,”data”:2014}

四、总结

每当我们解决一个问题,都需要去做一个总结,从这次解决的手段来看,如果你不想要升级springboot的版本,你可能使用第二种方法,因为旧版本的异常类是没有封装message的。同时,我们发现,会看源码真的很重要,特别有利于我们去解决很多问题,debug追踪源码是很好的一种方法,发现问题,找到问题在寻求解决问题的方式。



版权声明:本文为weixin_42736075原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。