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

最近遇到一个棘手的问题,有个需求需要写一些调用外部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追踪源码是很好的一种方法,发现问题,找到问题在寻求解决问题的方式。

 

  • 2
    点赞
  • 8
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

曲奇hmb

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值