feat: 优化异常处理
parent
09c74d5338
commit
7e13a4080b
|
|
@ -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;
|
||||
}
|
||||
|
||||
private void validateTimestamp(HttpRequest request, OriginalResponse response) {
|
||||
try {
|
||||
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)));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(HttpRequest request, OriginalResponse response, Channel channel) {
|
||||
// 校验应答状态码
|
||||
isInvalidHttpCode(response);
|
||||
|
||||
// 校验时间戳
|
||||
validateTimestamp(request, response);
|
||||
|
||||
if (!headerContainsSecretKey(response)) {
|
||||
return;
|
||||
// 校验应答签名
|
||||
validateResponseSignature(request, response, channel);
|
||||
}
|
||||
|
||||
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]不存在, 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, HttpResponseBody=%s", TIMESTAMP,
|
||||
timestamp, response.getHttpHeaders().getHeader(REQUEST_ID), Strings.toStr(response.getBody())));
|
||||
}
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
String signature = response.getHttpHeaders().getHeader(SIGNATURE);
|
||||
if (signature == null || signature.isEmpty()) {
|
||||
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)));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ValidationException(e.getMessage(), e);
|
||||
// 签名证书编号
|
||||
String serialNumber = response.getHttpHeaders().getHeader(BANK_CERTIFICATE_SERIAL);
|
||||
if (!verifier.verify(serialNumber, message, signature)) {
|
||||
throw new ValidationException(String.format("校验失败, 签名[%s=%s]校验未通过, Request-Id=%s, HttpResponseBody=%s", SIGNATURE,
|
||||
signature, response.getHttpHeaders().getHeader(REQUEST_ID), Strings.toStr(response.getBody())));
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 "";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,9 +41,13 @@ public class ApacheHttpclient extends AbstractApiClient {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected OriginalResponse doRemoteExecute(HttpRequest httpRequest) throws IOException {
|
||||
ClassicHttpRequest request = buildHttpRequest(httpRequest);
|
||||
return httpClient.execute(request, response -> assembleOriginalResponse(httpRequest, response));
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 文件下载
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public class EntPayAgrtQueryListResponse implements ApiResponse {
|
|||
*/
|
||||
@SerializedName("sys_serial_no")
|
||||
private String sysSerialNo;
|
||||
|
||||
|
||||
/**
|
||||
* 受托支付协议列表
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 短信服务
|
||||
|
|
|
|||
Loading…
Reference in New Issue