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.exception.TimestampException;
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.LogLevel;
import com.czcb.scfs.api.core.http.OriginalResponse;
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 final Verifier verifier;
private final HttpLogger httpLogger;
private final Profile profile;
public DefaultValidator(Profile profile) {
this.profile = profile;
this.verifier = profile.getSignature().getVerifier();
this.httpLogger = new HttpLogger(profile.getHttpProfile() == null ? LogLevel.basic : profile.getHttpProfile().logLevel());
}
private boolean isInvalidHttpCode(int httpCode) {
return httpCode < HTTP_OK || httpCode >= HTTP_MULT_CHOICE;
@Override
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) {
try {
private void isInvalidHttpCode(OriginalResponse response) {
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);
if (Strings.isEmpty(timestamp)) {
throw new TimestampException(String.format("响应头时间戳校验失败, 响应头[%s=%s]不存在, Request-Id=%s", TIMESTAMP,
timestamp, response.getHttpHeaders().getHeader(REQUEST_ID)));
throw new TimestampException(String.format("校验失败, 时间戳[%s]不存在, Request-Id=%s, HttpResponseBody=%s", TIMESTAMP,
response.getHttpHeaders().getHeader(REQUEST_ID), Strings.toStr(response.getBody())));
}
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
// 拒绝过期请求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
throw new TimestampException(String.format("响应头时间戳校验失败, 响应头[%s=%s]已过期, Request-Id=%s", TIMESTAMP,
timestamp, response.getHttpHeaders().getHeader(REQUEST_ID)));
}
} catch (Exception e) {
httpLogger.logResponseError(request, response);
throw new TimestampException(e.getMessage(), e);
throw new TimestampException(String.format("校验失败, 时间戳[%s=%s]已过期, Request-Id=%s, HttpResponseBody=%s", TIMESTAMP,
timestamp, response.getHttpHeaders().getHeader(REQUEST_ID), Strings.toStr(response.getBody())));
}
}
@Override
public void validate(HttpRequest request, OriginalResponse response, Channel channel) {
// 校验时间戳
validateTimestamp(request, response);
if (!headerContainsSecretKey(response)) {
return;
}
public void validateResponseSignature(HttpRequest request, OriginalResponse response, Channel channel) {
// 待签名串
String message = profile.getSignature().getCredential().buildResponseMessage(response, channel);
try {
String signature = response.getHttpHeaders().getHeader(SIGNATURE);
if (signature == null || signature.isEmpty()) {
throw new ValidationException(String.format("响应校验失败, 签名不存在, Request-Id=%s",
response.getHttpHeaders().getHeader(REQUEST_ID)));
throw new ValidationException(String.format("校验失败, 签名[%s]不存在, Request-Id=%s, HttpResponseBody=%s", SIGNATURE,
response.getHttpHeaders().getHeader(REQUEST_ID), Strings.toStr(response.getBody())));
}
// 签名证书编号
String serialNumber = response.getHttpHeaders().getHeader(BANK_CERTIFICATE_SERIAL);
if (!verifier.verify(serialNumber, message, signature)) {
httpLogger.logResponseError(request, response);
throw new ValidationException(String.format("响应校验失败, 签名校验未通过, Request-Id=%s",
response.getHttpHeaders().getHeader(REQUEST_ID)));
throw new ValidationException(String.format("校验失败, 签名[%s=%s]校验未通过, Request-Id=%s, HttpResponseBody=%s", SIGNATURE,
signature, response.getHttpHeaders().getHeader(REQUEST_ID), Strings.toStr(response.getBody())));
}
} 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.Profile;
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 java.util.Objects;
@ -58,18 +56,8 @@ public abstract class AbstractApiClient implements ApiClient {
// 打印请求数据
httpLogger.logRequest(newRequest, getHttpVersion());
// 启动计时器
StopWatch watch = new StopWatch("scfs-request").start();
OriginalResponse originalResponse;
try {
// 实际调用
originalResponse = doRemoteExecute(newRequest);
} catch (Exception e) {
httpLogger.logDoRemoteExecuteError(newRequest, e);
throw new ApiClientException(e);
} finally {
watch.stop();
}
// 真实调用
OriginalResponse originalResponse = doRemoteExecute(newRequest);
// 校验响应
OriginalResponse resultfulResponse = validateResponse(newRequest, originalResponse);
@ -77,7 +65,7 @@ public abstract class AbstractApiClient implements ApiClient {
// 解析返回数据
HttpResponse<T> httpResponse = assembleHttpResponse(resultfulResponse, responseClass);
// 打印响应结果
httpLogger.logResponse(newRequest, originalResponse, httpResponse, watch.getLastTaskTimeMillis());
httpLogger.logResponse(newRequest, originalResponse, httpResponse);
return httpResponse;
}

View File

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

View File

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

View File

@ -1,8 +1,14 @@
//package com.czcb.scfs.api.core.cipher;
//
//class DefaultValidatorTest {
////
//// @Test
//// void validate() {
//// }
//}
package com.czcb.scfs.api.core.cipher;
import org.junit.jupiter.api.Test;
class DefaultValidatorTest {
@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.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

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.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

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.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.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

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.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;
/**
*