feat: 优化异常处理

main
13009 2024-05-20 16:02:36 +08:00
parent 09c74d5338
commit 7e13a4080b
11 changed files with 80 additions and 100 deletions

View File

@ -4,9 +4,7 @@ import com.czcb.scfs.api.core.Channel;
import com.czcb.scfs.api.core.Profile; import com.czcb.scfs.api.core.Profile;
import com.czcb.scfs.api.core.exception.TimestampException; import com.czcb.scfs.api.core.exception.TimestampException;
import com.czcb.scfs.api.core.exception.ValidationException; import com.czcb.scfs.api.core.exception.ValidationException;
import com.czcb.scfs.api.core.http.HttpLogger;
import com.czcb.scfs.api.core.http.HttpRequest; import com.czcb.scfs.api.core.http.HttpRequest;
import com.czcb.scfs.api.core.http.LogLevel;
import com.czcb.scfs.api.core.http.OriginalResponse; import com.czcb.scfs.api.core.http.OriginalResponse;
import com.czcb.scfs.api.core.util.Strings; import com.czcb.scfs.api.core.util.Strings;
@ -25,72 +23,61 @@ public final class DefaultValidator implements Validator {
// 时间戳过期时间 // 时间戳过期时间
private static final int RESPONSE_EXPIRED_MINUTES = 10; private static final int RESPONSE_EXPIRED_MINUTES = 10;
private final Verifier verifier; private final Verifier verifier;
private final HttpLogger httpLogger;
private final Profile profile; private final Profile profile;
public DefaultValidator(Profile profile) { public DefaultValidator(Profile profile) {
this.profile = profile; this.profile = profile;
this.verifier = profile.getSignature().getVerifier(); this.verifier = profile.getSignature().getVerifier();
this.httpLogger = new HttpLogger(profile.getHttpProfile() == null ? LogLevel.basic : profile.getHttpProfile().logLevel());
} }
private boolean isInvalidHttpCode(int httpCode) { @Override
return httpCode < HTTP_OK || httpCode >= HTTP_MULT_CHOICE; public void validate(HttpRequest request, OriginalResponse response, Channel channel) {
// 校验应答状态码
isInvalidHttpCode(response);
// 校验时间戳
validateTimestamp(request, response);
// 校验应答签名
validateResponseSignature(request, response, channel);
} }
private void validateTimestamp(HttpRequest request, OriginalResponse response) { private void isInvalidHttpCode(OriginalResponse response) {
try { if (response.getStatusCode() < HTTP_OK || response.getStatusCode() >= HTTP_MULT_CHOICE) {
throw new ValidationException(String.format("校验失败, HttpStatusCode=%s, Request-Id=%s, HttpResponseBody=%s",
response.getStatusCode(), response.getHttpHeaders().getHeader(REQUEST_ID), Strings.toStr(response.getBody())));
}
}
public void validateTimestamp(HttpRequest request, OriginalResponse response) {
String timestamp = response.getHttpHeaders().getHeader(TIMESTAMP); String timestamp = response.getHttpHeaders().getHeader(TIMESTAMP);
if (Strings.isEmpty(timestamp)) { if (Strings.isEmpty(timestamp)) {
throw new TimestampException(String.format("响应头时间戳校验失败, 响应头[%s=%s]不存在, Request-Id=%s", TIMESTAMP, throw new TimestampException(String.format("校验失败, 时间戳[%s]不存在, Request-Id=%s, HttpResponseBody=%s", TIMESTAMP,
timestamp, response.getHttpHeaders().getHeader(REQUEST_ID))); response.getHttpHeaders().getHeader(REQUEST_ID), Strings.toStr(response.getBody())));
} }
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp)); Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
// 拒绝过期请求 // 拒绝过期请求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) { if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
throw new TimestampException(String.format("响应头时间戳校验失败, 响应头[%s=%s]已过期, Request-Id=%s", TIMESTAMP, throw new TimestampException(String.format("校验失败, 时间戳[%s=%s]已过期, Request-Id=%s, HttpResponseBody=%s", TIMESTAMP,
timestamp, response.getHttpHeaders().getHeader(REQUEST_ID))); timestamp, response.getHttpHeaders().getHeader(REQUEST_ID), Strings.toStr(response.getBody())));
}
} catch (Exception e) {
httpLogger.logResponseError(request, response);
throw new TimestampException(e.getMessage(), e);
} }
} }
@Override public void validateResponseSignature(HttpRequest request, OriginalResponse response, Channel channel) {
public void validate(HttpRequest request, OriginalResponse response, Channel channel) {
// 校验时间戳
validateTimestamp(request, response);
if (!headerContainsSecretKey(response)) {
return;
}
// 待签名串 // 待签名串
String message = profile.getSignature().getCredential().buildResponseMessage(response, channel); String message = profile.getSignature().getCredential().buildResponseMessage(response, channel);
try {
String signature = response.getHttpHeaders().getHeader(SIGNATURE); String signature = response.getHttpHeaders().getHeader(SIGNATURE);
if (signature == null || signature.isEmpty()) { if (signature == null || signature.isEmpty()) {
throw new ValidationException(String.format("响应校验失败, 签名不存在, Request-Id=%s", throw new ValidationException(String.format("校验失败, 签名[%s]不存在, Request-Id=%s, HttpResponseBody=%s", SIGNATURE,
response.getHttpHeaders().getHeader(REQUEST_ID))); response.getHttpHeaders().getHeader(REQUEST_ID), Strings.toStr(response.getBody())));
} }
// 签名证书编号 // 签名证书编号
String serialNumber = response.getHttpHeaders().getHeader(BANK_CERTIFICATE_SERIAL); String serialNumber = response.getHttpHeaders().getHeader(BANK_CERTIFICATE_SERIAL);
if (!verifier.verify(serialNumber, message, signature)) { if (!verifier.verify(serialNumber, message, signature)) {
httpLogger.logResponseError(request, response); throw new ValidationException(String.format("校验失败, 签名[%s=%s]校验未通过, Request-Id=%s, HttpResponseBody=%s", SIGNATURE,
throw new ValidationException(String.format("响应校验失败, 签名校验未通过, Request-Id=%s", signature, response.getHttpHeaders().getHeader(REQUEST_ID), Strings.toStr(response.getBody())));
response.getHttpHeaders().getHeader(REQUEST_ID)));
} }
} catch (Exception e) {
throw new ValidationException(e.getMessage(), e);
}
}
private boolean headerContainsSecretKey(OriginalResponse response) {
return Strings.isNotEmpty(response.getHttpHeaders().getHeader(SECRET_KEY))
&& Strings.isNotEmpty(response.getHttpHeaders().getHeader(CHANNEL_CERTIFICATE_SERIAL))
&& Strings.isNotEmpty(response.getHttpHeaders().getHeader(BANK_CERTIFICATE_SERIAL));
} }
} }

View File

@ -4,8 +4,6 @@ import com.czcb.scfs.api.core.ApiClient;
import com.czcb.scfs.api.core.Channel; import com.czcb.scfs.api.core.Channel;
import com.czcb.scfs.api.core.Profile; import com.czcb.scfs.api.core.Profile;
import com.czcb.scfs.api.core.cipher.*; import com.czcb.scfs.api.core.cipher.*;
import com.czcb.scfs.api.core.exception.ApiClientException;
import com.czcb.scfs.api.core.util.StopWatch;
import com.czcb.scfs.api.core.util.Strings; import com.czcb.scfs.api.core.util.Strings;
import java.util.Objects; import java.util.Objects;
@ -58,18 +56,8 @@ public abstract class AbstractApiClient implements ApiClient {
// 打印请求数据 // 打印请求数据
httpLogger.logRequest(newRequest, getHttpVersion()); httpLogger.logRequest(newRequest, getHttpVersion());
// 启动计时器 // 真实调用
StopWatch watch = new StopWatch("scfs-request").start(); OriginalResponse originalResponse = doRemoteExecute(newRequest);
OriginalResponse originalResponse;
try {
// 实际调用
originalResponse = doRemoteExecute(newRequest);
} catch (Exception e) {
httpLogger.logDoRemoteExecuteError(newRequest, e);
throw new ApiClientException(e);
} finally {
watch.stop();
}
// 校验响应 // 校验响应
OriginalResponse resultfulResponse = validateResponse(newRequest, originalResponse); OriginalResponse resultfulResponse = validateResponse(newRequest, originalResponse);
@ -77,7 +65,7 @@ public abstract class AbstractApiClient implements ApiClient {
// 解析返回数据 // 解析返回数据
HttpResponse<T> httpResponse = assembleHttpResponse(resultfulResponse, responseClass); HttpResponse<T> httpResponse = assembleHttpResponse(resultfulResponse, responseClass);
// 打印响应结果 // 打印响应结果
httpLogger.logResponse(newRequest, originalResponse, httpResponse, watch.getLastTaskTimeMillis()); httpLogger.logResponse(newRequest, originalResponse, httpResponse);
return httpResponse; return httpResponse;
} }

View File

@ -59,10 +59,9 @@ public class HttpLogger {
* *
* @param originalResponse * @param originalResponse
* @param response * @param response
* @param duration
* @param <T> * @param <T>
*/ */
protected <T> void logResponse(HttpRequest request, OriginalResponse originalResponse, HttpResponse<T> response, long duration) { protected <T> void logResponse(HttpRequest request, OriginalResponse originalResponse, HttpResponse<T> response) {
String logPrefixText = logPrefix(request); String logPrefixText = logPrefix(request);
if (isFull(logLevel)) { if (isFull(logLevel)) {
String reasonPhrase = httpReasonPhrase(originalResponse); String reasonPhrase = httpReasonPhrase(originalResponse);
@ -79,10 +78,6 @@ public class HttpLogger {
String text = Json.toJson(response.getServiceResponse()); String text = Json.toJson(response.getServiceResponse());
logger.info("{}应答报文:{}", logPrefixText, text); logger.info("{}应答报文:{}", logPrefixText, text);
if (isFull(logLevel)) {
logger.info("{}请求-应答耗时(毫秒):{}", logPrefixText, duration);
}
} }
/** /**
@ -90,15 +85,14 @@ public class HttpLogger {
* *
* @param originalResponse * @param originalResponse
*/ */
public void logResponseError(HttpRequest request, OriginalResponse originalResponse) { public void logResponseError(HttpRequest request, OriginalResponse originalResponse, String errorMessage) {
String logPrefixText = logPrefix(request); String logPrefixText = logPrefix(request);
logger.error("{}响应:{} {}", logPrefixText, originalResponse.getVersion(), originalResponse.getStatusCode());
if (isFull(logLevel)) { if (isFull(logLevel)) {
originalResponse.getHttpHeaders().getHeaders().forEach((k, v) -> logger.info("{}请求头:{}:{}", logPrefixText, k, v)); originalResponse.getHttpHeaders().getHeaders().forEach((k, v) -> logger.info("{}应答头:{}:{}", logPrefixText, k, v));
String body = Strings.toStr(originalResponse.getBody());
logger.error("{}响应原始报文:{}", logPrefixText, body);
} }
String body = Strings.toStr(originalResponse.getBody() == null ? new byte[]{} : originalResponse.getBody());
logger.error("{}{}, 应答原始报文:{}", logPrefixText, errorMessage, body);
} }
public String httpReasonPhrase(OriginalResponse response) { public String httpReasonPhrase(OriginalResponse response) {
@ -110,11 +104,6 @@ public class HttpLogger {
return status.getReasonPhrase(); return status.getReasonPhrase();
} }
public void logRemoteError(HttpRequest newRequest, String message) {
String logPrefixText = logPrefix(newRequest);
logger.error("{}远程调用异常:{}", logPrefixText, message);
}
private String logPrefix(HttpRequest request) { private String logPrefix(HttpRequest request) {
if (logPrefix) { if (logPrefix) {
return String.format("[%s] ", request.getId()); return String.format("[%s] ", request.getId());
@ -122,4 +111,5 @@ public class HttpLogger {
return ""; return "";
} }
} }

View File

@ -41,9 +41,13 @@ public class ApacheHttpclient extends AbstractApiClient {
} }
@Override @Override
protected OriginalResponse doRemoteExecute(HttpRequest httpRequest) throws IOException { protected OriginalResponse doRemoteExecute(HttpRequest httpRequest) {
try {
ClassicHttpRequest request = buildHttpRequest(httpRequest); ClassicHttpRequest request = buildHttpRequest(httpRequest);
return httpClient.execute(request, response -> assembleOriginalResponse(httpRequest, response)); return httpClient.execute(request, response -> assembleOriginalResponse(httpRequest, response));
} catch (Exception e) {
throw new ApiClientException(String.format("服务调用异常: %s", e.getMessage()), e);
}
} }
@Override @Override

View File

@ -1,8 +1,14 @@
//package com.czcb.scfs.api.core.cipher; package com.czcb.scfs.api.core.cipher;
//
//class DefaultValidatorTest { import org.junit.jupiter.api.Test;
////
//// @Test class DefaultValidatorTest {
//// void validate() {
//// } @Test
//} void validate() {
}
@Test
void responseHeaderContainsSecretKey() {
}
}

View File

@ -5,7 +5,8 @@ import com.czcb.scfs.api.core.http.HttpHeaders;
import com.czcb.scfs.api.core.http.HttpResponse; import com.czcb.scfs.api.core.http.HttpResponse;
import com.czcb.scfs.api.service.v2.bmd.model.*; import com.czcb.scfs.api.service.v2.bmd.model.*;
import static com.czcb.scfs.api.core.Constants.*; import static com.czcb.scfs.api.core.Constants.API_VERSION;
import static com.czcb.scfs.api.core.Constants.V_2;
/** /**
* @author wangwei * @author wangwei

View File

@ -6,7 +6,8 @@ import com.czcb.scfs.api.core.http.HttpResponse;
import com.czcb.scfs.api.service.v2.face.model.FaceFileRequest; import com.czcb.scfs.api.service.v2.face.model.FaceFileRequest;
import com.czcb.scfs.api.service.v2.face.model.FaceFileResponse; import com.czcb.scfs.api.service.v2.face.model.FaceFileResponse;
import static com.czcb.scfs.api.core.Constants.*; import static com.czcb.scfs.api.core.Constants.API_VERSION;
import static com.czcb.scfs.api.core.Constants.V_2;
/** /**
* @author wangwei * @author wangwei

View File

@ -8,7 +8,8 @@ import com.czcb.scfs.api.service.v2.file.model.DownloadFileResponse;
import com.czcb.scfs.api.service.v2.file.model.UploadFileRequest; import com.czcb.scfs.api.service.v2.file.model.UploadFileRequest;
import com.czcb.scfs.api.service.v2.file.model.UploadFileResponse; import com.czcb.scfs.api.service.v2.file.model.UploadFileResponse;
import static com.czcb.scfs.api.core.Constants.*; import static com.czcb.scfs.api.core.Constants.API_VERSION;
import static com.czcb.scfs.api.core.Constants.V_2;
/** /**
* *

View File

@ -6,7 +6,8 @@ import com.czcb.scfs.api.core.http.HttpResponse;
import com.czcb.scfs.api.service.v2.ocr.model.OcrFileRequest; import com.czcb.scfs.api.service.v2.ocr.model.OcrFileRequest;
import com.czcb.scfs.api.service.v2.ocr.model.OcrFileResponse; import com.czcb.scfs.api.service.v2.ocr.model.OcrFileResponse;
import static com.czcb.scfs.api.core.Constants.*; import static com.czcb.scfs.api.core.Constants.API_VERSION;
import static com.czcb.scfs.api.core.Constants.V_2;
/** /**
* @author wangwei * @author wangwei

View File

@ -6,7 +6,8 @@ import com.czcb.scfs.api.core.http.HttpResponse;
import com.czcb.scfs.api.service.v2.sms.model.SendVerifySignRequest; import com.czcb.scfs.api.service.v2.sms.model.SendVerifySignRequest;
import com.czcb.scfs.api.service.v2.sms.model.SendVerifySignResponse; import com.czcb.scfs.api.service.v2.sms.model.SendVerifySignResponse;
import static com.czcb.scfs.api.core.Constants.*; import static com.czcb.scfs.api.core.Constants.API_VERSION;
import static com.czcb.scfs.api.core.Constants.V_2;
/** /**
* *