对于第三方接口调用日志这个功能,笔者在工作中曾见过以下两种方式:
Restemplate+装饰者模式+MQ实现
网关监控 + Feign拦截器 + 观察者模式实现
其中观察者模式的实现是我最为佩服的设计,个人认为以上两种实现都显得略过臃肿,应该简化设计,让异步记录的实现更加简洁优雅,因此产生了这样的构思。
为什么选择HttpClient而不是RestTemplate?
其一是因为RestTemplate默认情况下是基于Java原生HTTP的支持,在性能上对比HttpClient略显不足。
其二是RestTemplate在一些版本较老的SpringBoot版本存在较多bug,不够稳定。
其三是HttpClient拥有更广泛的适用性。
异步线程池的实现可参考我的另一篇博客
/**
* DecoratedHttpClient
* 特性:异步记录第三方接口调用日志
*/
@Slf4j
public class DecoratedHttpClient {
private final HttpClient httpClient;
/**
* 默认的HttpClient
* 跳过SSL证书验证
*/
public DecoratedHttpClient() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException{
this.httpClient = HttpClients.custom()
.setSSLSocketFactory(
new SSLConnectionSocketFactory(SSLContextBuilder.create().loadTrustMaterial(TrustAllStrategy.INSTANCE).build()),
NoopHostnameVerifier.INSTANCE)
)
.build()
}
/**
* 自定义的HttpClient
*/
public DecoratedHttpClient(HttpClient httpClient){
this.httpClient = httpClient;
}
public String callApi(HttpUriRequest httpUriRequest) throws Exception{
try {
HttpResponse httpResponse = httpClient.execute(httpUriRequest);
String apiResponse = getResponse(httpResponse);
ThreadPoolSington.getInstance().submitTask(new LogRecordAsyncTask(httpUriRequest, apiResponse, null));
return apiResponse;
} catch(IOException e){
ThreadPoolSington.getInstance().submitTask(new LogRecordAsyncTask(httpUriRequest, null, e));
throw e;
}
}
private String getResponse(HttpResponse httpResponse) throws IOException{
StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent()))){
String line;
while((line = reader.readLine()) != null){
stringBuilder.append(line);
}
}
return stringBuilder.toString();
}
}
/**
* 异步日志任务
*/
@Slf4j
public class LogRecordAsyncTask implents Runnable {
private final HttpUriRequest httpUriRequest;
private final String apiResponse;
private final Exception exception;
public LogRecordAsyncTask(HttpUriRequest httpUriRequest, String apiResponse, Exception exception){
this.httpUriRequest = httpUriRequest;
this.apiResponse = apiResponse;
this.exception = exception;
}
@Override
public String toString() {
// TODO 代码均为手敲,顶不住了...
}
@Override
public void run() {
// TODO 持久化日志
log.info("第三方接口调用日志 ---> {}", this.toString());
}
}
从代码中可以看到我的设计,记录了HttpUriRequest,第三方接口的原始返回body,异常调用的信息,可以更好的监控第三方接口的调用情况。
在run方法中你甚至还可以拓展实时报警机制,可参考我的另一篇博客