From b67e1665d9e89b6d1a44abdc5a8d60f6ea2313bf Mon Sep 17 00:00:00 2001 From: Haebin Date: Tue, 26 Nov 2024 15:15:05 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=EB=B9=84=EC=A6=88=EB=8B=88?= =?UTF-8?q?=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=ED=8D=BC=EC=82=AC=EB=93=9C=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EB=B6=84=EB=A6=AC=20&=20=EB=A1=9C?= =?UTF-8?q?=EA=B9=85=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC=20(#38)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update .gitignore * feat: reqCertification 로직에 CF-12872, CF-03002 응답일 때, warn 로깅 추가 * refactor: 불필요 함수형 인터페이스 제거 * refactor: 실행기 / 요청기 객체 성격에 맞게 분리 * refactor: EasyCodef 객체 Facade Pattern 분리 * refactor: 로깅부 로직 분리 * chore: 로깅부 개선 * release: v2.0.0-beta-004 --- .gitignore | 1 + README.md | 2 +- build.gradle.kts | 2 +- .../io/codef/api/CodefExecutorManager.java | 65 ++++ src/main/java/io/codef/api/EasyCodef.java | 289 +++--------------- .../java/io/codef/api/EasyCodefConnector.java | 46 ++- .../java/io/codef/api/EasyCodefToken.java | 2 +- .../java/io/codef/api/ResponseHandler.java | 108 +++---- .../api/constants/CodefResponseCode.java | 1 + .../api/dto/CodefTransactionIdResponse.java | 4 - .../io/codef/api/dto/EasyCodefRequest.java | 9 - .../api/dto/EasyCodefRequestBuilder.java | 8 +- .../codef/api/facade/MultipleReqFacade.java | 76 +++++ .../api/facade/SimpleAuthCertFacade.java | 155 ++++++++++ .../io/codef/api/facade/SingleReqFacade.java | 41 +++ .../java/io/codef/api/logger/JsonLogUtil.java | 19 -- .../api/storage/MultipleRequestStorage.java | 4 +- .../codef/api/storage/SimpleAuthStorage.java | 2 +- .../io/codef/api/util/AuthorizationUtil.java | 14 + src/main/java/io/codef/api/util/JsonUtil.java | 15 + .../api/{dto => vo}/CodefSimpleAuth.java | 5 +- 21 files changed, 493 insertions(+), 375 deletions(-) create mode 100644 src/main/java/io/codef/api/CodefExecutorManager.java create mode 100644 src/main/java/io/codef/api/facade/MultipleReqFacade.java create mode 100644 src/main/java/io/codef/api/facade/SimpleAuthCertFacade.java create mode 100644 src/main/java/io/codef/api/facade/SingleReqFacade.java delete mode 100644 src/main/java/io/codef/api/logger/JsonLogUtil.java create mode 100644 src/main/java/io/codef/api/util/AuthorizationUtil.java create mode 100644 src/main/java/io/codef/api/util/JsonUtil.java rename src/main/java/io/codef/api/{dto => vo}/CodefSimpleAuth.java (52%) diff --git a/.gitignore b/.gitignore index 9931ea6..e85d8a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/src/main/resources/logback-test.xml *.java.hsp *.sonarj *.sw* diff --git a/README.md b/README.md index b0c6cbe..339d904 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@

Build Status Last Commit - Maven Central + Maven Central



diff --git a/build.gradle.kts b/build.gradle.kts index 0316f1e..4a243cb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { group = "io.codef.api" -version = "2.0.0-beta-003" +version = "2.0.0-beta-004" signing { useInMemoryPgpKeys( diff --git a/src/main/java/io/codef/api/CodefExecutorManager.java b/src/main/java/io/codef/api/CodefExecutorManager.java new file mode 100644 index 0000000..5ac77ce --- /dev/null +++ b/src/main/java/io/codef/api/CodefExecutorManager.java @@ -0,0 +1,65 @@ +package io.codef.api; + +import io.codef.api.dto.EasyCodefRequest; +import io.codef.api.dto.EasyCodefResponse; +import io.codef.api.facade.SingleReqFacade; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +// 실행기 관리를 위한 클래스 +public class CodefExecutorManager implements AutoCloseable { + private final ScheduledExecutorService scheduler; + private final Executor virtualThreadExecutor; + + private CodefExecutorManager(ScheduledExecutorService scheduler, Executor virtualThreadExecutor) { + this.scheduler = scheduler; + this.virtualThreadExecutor = virtualThreadExecutor; + } + + public static CodefExecutorManager create() { + return new CodefExecutorManager( + Executors.newScheduledThreadPool(1), + Executors.newThreadPerTaskExecutor(Thread.ofVirtual().factory()) + ); + } + + public CompletableFuture scheduleRequest( + EasyCodefRequest request, + long delayMs, + SingleReqFacade facade + ) { + return scheduleDelayedExecution(delayMs) + .thenComposeAsync( + ignored -> executeRequest(request, facade), + virtualThreadExecutor + ); + } + + private CompletableFuture scheduleDelayedExecution(long delayMs) { + CompletableFuture future = new CompletableFuture<>(); + scheduler.schedule( + () -> future.complete(null), + delayMs, + TimeUnit.MILLISECONDS + ); + return future; + } + + private CompletableFuture executeRequest( + EasyCodefRequest request, + SingleReqFacade facade + ) { + return CompletableFuture.supplyAsync( + () -> facade.requestProduct(request), + virtualThreadExecutor + ); + } + + @Override + public void close() { + scheduler.shutdown(); + } +} \ No newline at end of file diff --git a/src/main/java/io/codef/api/EasyCodef.java b/src/main/java/io/codef/api/EasyCodef.java index 9b9bdab..ebda865 100644 --- a/src/main/java/io/codef/api/EasyCodef.java +++ b/src/main/java/io/codef/api/EasyCodef.java @@ -1,289 +1,90 @@ package io.codef.api; -import static io.codef.api.dto.EasyCodefRequest.SSO_ID; -import static io.codef.api.dto.EasyCodefRequest.TRUE; - -import io.codef.api.constants.CodefClientType; -import io.codef.api.constants.CodefResponseCode; -import io.codef.api.dto.CodefSimpleAuth; import io.codef.api.dto.EasyCodefRequest; import io.codef.api.dto.EasyCodefResponse; -import io.codef.api.error.CodefError; import io.codef.api.error.CodefException; +import io.codef.api.facade.MultipleReqFacade; +import io.codef.api.facade.SimpleAuthCertFacade; +import io.codef.api.facade.SingleReqFacade; import io.codef.api.storage.MultipleRequestStorage; import io.codef.api.storage.SimpleAuthStorage; import io.codef.api.util.RsaUtil; import java.security.PublicKey; -import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.stream.IntStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class EasyCodef { - private static final long REQUEST_DELAY_MS = 700L; private static final Logger log = LoggerFactory.getLogger(EasyCodef.class); - private final SimpleAuthStorage simpleAuthStorage; - private final MultipleRequestStorage multipleRequestStorage; + private final SingleReqFacade singleReqFacade; + private final MultipleReqFacade multipleReqFacade; + private final SimpleAuthCertFacade simpleAuthCertFacade; + private final PublicKey publicKey; - private final CodefClientType clientType; - private final EasyCodefToken easyCodefToken; protected EasyCodef(EasyCodefBuilder builder) { - this.publicKey = RsaUtil.generatePublicKey(builder.getPublicKey()); - log.info("EasyCodef RSA public key successfully initialized."); - - this.clientType = builder.getClientType(); - log.info("Your Codef clientType {} is successfully initialized.", clientType); - - this.easyCodefToken = new EasyCodefToken(builder); - this.simpleAuthStorage = new SimpleAuthStorage(); - this.multipleRequestStorage = new MultipleRequestStorage(); - - log.info("=================================================="); - log.info("Your EasyCodef Entity is successfully initialized!"); - log.info("==================================================\n"); - } - - private List returnFirstErrorResponse( - EasyCodefResponse firstResponse, - String transactionId - ) { - log.info("Result Status Code : {}", firstResponse.code()); - - return List.of(firstResponse); - } - - /** - * 단일 상품 요청 - */ - public EasyCodefResponse requestProduct(EasyCodefRequest request) throws CodefException { - String requestUrl = buildRequestUrl(request); - EasyCodefToken validToken = easyCodefToken.validateAndRefreshToken(); - - EasyCodefResponse response = - EasyCodefConnector.requestProduct(request, validToken, requestUrl); - - simpleAuthStorage.storeIfRequired(request, response, requestUrl); - return response; - } - /** - * 다중 상품 요청 - */ - public EasyCodefResponse requestMultipleProduct(List requests) - throws CodefException { - validateRequests(requests); - easyCodefToken.validateAndRefreshToken(); - assignSsoId(requests, UUID.randomUUID().toString()); + EasyCodefToken easyCodefToken = new EasyCodefToken(builder); + SimpleAuthStorage simpleAuthStorage = new SimpleAuthStorage(); + MultipleRequestStorage multipleRequestStorage = new MultipleRequestStorage(); + CodefExecutorManager executorManager = CodefExecutorManager.create(); - var executors = createExecutors(); - - try { - return processMultipleRequests(requests, executors); - } finally { - cleanupExecutors(executors); - } - } - - /** - * 단건 간편인증 완료 요청 - */ - public EasyCodefResponse requestSimpleAuthCertification(String transactionId) - throws CodefException { - CodefSimpleAuth simpleAuth = simpleAuthStorage.get(transactionId); - easyCodefToken.validateAndRefreshToken(); - - EasyCodefRequest enrichedRequest = enrichRequestWithTwoWayInfo(simpleAuth); - EasyCodefResponse response = executeSimpleAuthRequest(enrichedRequest, - simpleAuth.requestUrl()); - - simpleAuthStorage.updateIfRequired( - simpleAuth.requestUrl(), - enrichedRequest, - response, - transactionId + this.singleReqFacade = new SingleReqFacade( + easyCodefToken, + simpleAuthStorage, + builder.getClientType() ); - log.info("Result Status Code : {}", response.code()); - - return response; - } - - /** - * 다건 간편인증 완료 요청 - */ - public List requestMultipleSimpleAuthCertification(String transactionId) - throws CodefException { - CodefSimpleAuth simpleAuth = simpleAuthStorage.get(transactionId); - easyCodefToken.validateAndRefreshToken(); - - EasyCodefRequest enrichedRequest = enrichRequestWithTwoWayInfo(simpleAuth); - EasyCodefResponse firstResponse = - executeSimpleAuthRequest(enrichedRequest, simpleAuth.requestUrl()); - - simpleAuthStorage.updateIfRequired( - simpleAuth.requestUrl(), - enrichedRequest, - firstResponse, - transactionId + this.multipleReqFacade = new MultipleReqFacade( + singleReqFacade, + multipleRequestStorage, + executorManager ); - return isSuccessful(firstResponse) - ? combineWithRemainingResponses(firstResponse, transactionId) // Case CF-00000 - : returnFirstErrorResponse(firstResponse, transactionId); - } - - // Private helper methods - - private String buildRequestUrl(EasyCodefRequest request) { - return clientType.getHost() + request.path(); - } - - private EasyCodefRequest enrichRequestWithTwoWayInfo(CodefSimpleAuth simpleAuth) { - EasyCodefRequest request = simpleAuth.request(); - Map body = request.requestBody(); - - body.put(EasyCodefRequest.IS_TWO_WAY, true); - body.put(EasyCodefRequest.SIMPLE_AUTH, TRUE); - body.put(EasyCodefRequest.TWO_WAY_INFO, simpleAuth.response().data()); - - return request; - } - - private EasyCodefResponse executeSimpleAuthRequest( - EasyCodefRequest request, - String requestUrl - ) throws CodefException { - EasyCodefToken validToken = easyCodefToken.validateAndRefreshToken(); - return EasyCodefConnector.requestProduct(request, validToken, requestUrl); - } - - private boolean isSuccessful(EasyCodefResponse response) { - return CodefResponseCode.CF_00000.equals(response.code()); - } - - private List combineWithRemainingResponses( - EasyCodefResponse firstResponse, - String transactionId - ) throws CodefException { - - log.info("remainingResponses called By transactionId `{}`", transactionId); - - List remainingResponses = - multipleRequestStorage.getRemainingResponses(transactionId); - - log.info("Await Response Count = {}", remainingResponses.size()); - List allResponses = new ArrayList<>(remainingResponses); - allResponses.add(firstResponse); - - log.info("Total Response Count = {}", allResponses.size()); - log.info("Result Status Codes : {}", - allResponses.stream().map(EasyCodefResponse::code).toList()); - - return allResponses; - } - - private void validateRequests(List requests) { - requests.forEach( - request -> CodefValidator.requireNonNullElseThrow(request, CodefError.REQUEST_NULL)); - } - - private void assignSsoId(List requests, String uuid) { - requests.forEach(request -> request.requestBody().put(SSO_ID, uuid)); - } - - private CodefExecutors createExecutors() { - return new CodefExecutors( - Executors.newScheduledThreadPool(1), - Executors.newThreadPerTaskExecutor(Thread.ofVirtual().factory()) + this.simpleAuthCertFacade = new SimpleAuthCertFacade( + singleReqFacade, + simpleAuthStorage, + multipleRequestStorage ); - } - - private EasyCodefResponse processMultipleRequests( - List requests, - CodefExecutors codefExecutors - ) throws CodefException { - List> futures = - scheduleRequests(requests, codefExecutors); - - CompletableFuture firstCompleted = - CompletableFuture.anyOf(futures.toArray(new CompletableFuture[0])) - .thenApply(result -> (EasyCodefResponse) result); - EasyCodefResponse result = firstCompleted.join(); - multipleRequestStorage.store(result.transactionId(), futures); - - return result; + logInitializeSuccessfully(); } - private List> scheduleRequests( - List requests, - CodefExecutors codefExecutors - ) { - return IntStream.range(0, requests.size()) - .mapToObj(i -> scheduleRequest(requests.get(i), i * REQUEST_DELAY_MS, codefExecutors)) - .toList(); + public EasyCodefResponse requestProduct(EasyCodefRequest request) throws CodefException { + return singleReqFacade.requestProduct(request); } - private CompletableFuture scheduleRequest( - EasyCodefRequest request, - long delayMs, - CodefExecutors codefExecutors - ) { - CompletableFuture future = new CompletableFuture<>(); - - codefExecutors.scheduler.schedule( - () -> executeRequest(request, codefExecutors.virtualThreadExecutor, future), - delayMs, - TimeUnit.MILLISECONDS - ); - - return future; + public EasyCodefResponse requestMultipleProduct(List requests) throws CodefException { + return multipleReqFacade.requestMultipleProduct(requests); } - private void executeRequest( - EasyCodefRequest request, - Executor executor, - CompletableFuture future - ) { - CompletableFuture.supplyAsync(() -> { - try { - return requestProduct(request); - } catch (Exception e) { - throw new RuntimeException(e); - } - }, executor).whenComplete((response, ex) -> { - if (ex != null) { - future.completeExceptionally(ex); - } else { - future.complete(response); - } - }); + public EasyCodefResponse requestSimpleAuthCertification(String transactionId) throws CodefException { + return simpleAuthCertFacade.requestSimpleAuthCertification(transactionId); } - private void cleanupExecutors(CodefExecutors codefExecutors) { - codefExecutors.scheduler.shutdown(); + public List requestMultipleSimpleAuthCertification(String transactionId) throws CodefException { + return simpleAuthCertFacade.requestMultipleSimpleAuthCertification(transactionId); } public PublicKey getPublicKey() { return publicKey; } - private record CodefExecutors( - ScheduledExecutorService scheduler, - Executor virtualThreadExecutor - ) { - + private void logInitializeSuccessfully() { + log.info(""" + + + ------. ,-----. ,--. ,---.\s + | .---' ,--,--. ,---.,--. ,--.' .--./ ,---. ,-| |,---. / .-'\s + | `--, ' ,-. |( .-' \\ ' / | | | .-. |' .-. | .-. :| `-,\s + | `---.\\ '-' |.-' `) \\ ' ' '--'\\' '-' '\\ `-' \\ --.| .-'\s + `------' `--`--'`----'.-' / `-----' `---' `---' `----'`--' \s + + > EasyCodef v2.0.0-beta-004 Successfully Initialized! Hello worlds! + """ + ); } } \ No newline at end of file diff --git a/src/main/java/io/codef/api/EasyCodefConnector.java b/src/main/java/io/codef/api/EasyCodefConnector.java index 250a824..82d9d41 100644 --- a/src/main/java/io/codef/api/EasyCodefConnector.java +++ b/src/main/java/io/codef/api/EasyCodefConnector.java @@ -1,7 +1,5 @@ package io.codef.api; -import static io.codef.api.dto.EasyCodefRequest.BASIC_TOKEN_FORMAT; -import static io.codef.api.dto.EasyCodefRequest.BEARER_TOKEN_FORMAT; import static org.apache.hc.core5.http.HttpHeaders.AUTHORIZATION; import com.alibaba.fastjson2.JSON; @@ -11,11 +9,13 @@ import io.codef.api.dto.EasyCodefResponse; import io.codef.api.error.CodefError; import io.codef.api.error.CodefException; -import io.codef.api.logger.JsonLogUtil; +import io.codef.api.util.AuthorizationUtil; import io.codef.api.util.HttpClientUtil; +import io.codef.api.util.JsonUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.io.entity.StringEntity; import org.slf4j.Logger; @@ -54,7 +54,8 @@ public static String requestToken( private static HttpPost createTokenRequest(String codefOAuthToken) { HttpPost httpPost = new HttpPost(CodefHost.CODEF_OAUTH_SERVER + CodefPath.ISSUE_TOKEN); - httpPost.addHeader(AUTHORIZATION, String.format(BASIC_TOKEN_FORMAT, codefOAuthToken)); + String basicToken = AuthorizationUtil.createBasicAuth(codefOAuthToken); + httpPost.addHeader(AUTHORIZATION, basicToken); return httpPost; } @@ -64,8 +65,8 @@ private static HttpPost createProductRequest( String requestUrl ) { HttpPost httpPost = new HttpPost(requestUrl); - httpPost.addHeader(AUTHORIZATION, - String.format(BEARER_TOKEN_FORMAT, token.getAccessToken())); + String accessToken = AuthorizationUtil.createBearerAuth(token.getAccessToken()); + httpPost.addHeader(AUTHORIZATION, accessToken); String rawRequest = JSON.toJSONString(request.requestBody()); httpPost.setEntity(new StringEntity(rawRequest, StandardCharsets.UTF_8)); @@ -76,19 +77,13 @@ private static HttpPost createProductRequest( private static T executeRequest( HttpPost request, ResponseProcessor processor - ) { - try (var httpClient = HttpClientUtil.createClient()) { - log.info("[{}] Codef API Request", request.hashCode()); - log.info("> Request Host : {}://{}", request.getScheme(), - request.getAuthority().toString()); - log.info("> Requset Uri : {}\n", request.getRequestUri()); + ) throws CodefException { + logRequest(request); + try (CloseableHttpClient httpClient = HttpClientUtil.createClient()) { return httpClient.execute(request, response -> { - log.info("[{}] Codef API Response", request.hashCode()); - log.info("> Response Status : {}", response.getCode()); T result = processor.process(response); - log.info("> Response →\n{}\n", JsonLogUtil.toPrettyJson(result)); - + logResponse(request.hashCode(), response, result); return result; }); } catch (IOException exception) { @@ -96,6 +91,25 @@ private static T executeRequest( } } + private static void logRequest(HttpPost request) { + log.info("[{}] Codef API Request", request.hashCode()); + log.info("> Request Host: {}://{}", + request.getScheme(), + request.getAuthority() + ); + log.info("> Request URI: {}\n", request.getRequestUri()); + } + + private static void logResponse( + int requestHashCode, + ClassicHttpResponse response, + Object result + ) { + log.info("[{}] Codef API Response", requestHashCode); + log.info("> Response Status: {}", response.getCode()); + log.info("> Response → \n{}\n", JsonUtil.toPrettyJson(result)); + } + @FunctionalInterface private interface ResponseProcessor { diff --git a/src/main/java/io/codef/api/EasyCodefToken.java b/src/main/java/io/codef/api/EasyCodefToken.java index 6eeec8a..13c2d76 100644 --- a/src/main/java/io/codef/api/EasyCodefToken.java +++ b/src/main/java/io/codef/api/EasyCodefToken.java @@ -32,7 +32,7 @@ protected EasyCodefToken(EasyCodefBuilder builder) { this.expiresAt = LocalDateTime.now().plusDays(VALIDITY_PERIOD_DAYS); log.info( - "Codef API AccessToken expiry at {} but, EasyCodef will handle automatic renewal", + "Codef API AccessToken expiry at {}. Also, EasyCodef will handle automatic renewal.", expiresAt ); log.info("Codef API AccessToken successfully initialized.\n"); diff --git a/src/main/java/io/codef/api/ResponseHandler.java b/src/main/java/io/codef/api/ResponseHandler.java index 87c407e..a65fec0 100644 --- a/src/main/java/io/codef/api/ResponseHandler.java +++ b/src/main/java/io/codef/api/ResponseHandler.java @@ -13,45 +13,39 @@ import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.Optional; +import java.util.function.Function; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.http.io.entity.EntityUtils; public class ResponseHandler { - private static final String UTF_8 = StandardCharsets.UTF_8.toString(); - public ResponseHandler() { - } - /** * 토큰 응답 처리 */ public String handleTokenResponse(ClassicHttpResponse response) throws CodefException { - HttpStatusHandler handler = new HttpStatusHandler<>( - response.getCode(), + return handleHttpResponse( + response, + this::parseAccessToken, CodefError.OAUTH_UNAUTHORIZED, CodefError.OAUTH_INTERNAL_ERROR, - this::parseAccessToken + false ); - - return handleHttpResponse(response, handler, false); } /** * 상품 응답 처리 */ - public EasyCodefResponse handleProductResponse(ClassicHttpResponse response) - throws CodefException { - HttpStatusHandler handler = new HttpStatusHandler<>( - response.getCode(), + public EasyCodefResponse handleProductResponse(ClassicHttpResponse response) throws CodefException { + return handleHttpResponse( + response, + this::parseProductResponse, CodefError.CODEF_API_UNAUTHORIZED, CodefError.CODEF_API_SERVER_ERROR, - this::parseProductResponse + true ); - - return handleHttpResponse(response, handler, true); } /** @@ -59,27 +53,31 @@ public EasyCodefResponse handleProductResponse(ClassicHttpResponse response) */ private T handleHttpResponse( ClassicHttpResponse response, - HttpStatusHandler handler, + Function parser, + CodefError unauthorizedError, + CodefError defaultError, boolean requireUrlDecoding ) throws CodefException { String responseBody = extractResponseBody(response, requireUrlDecoding); - return handleStatusCode(responseBody, handler); + + return switch (response.getCode()) { + case HttpStatus.SC_OK -> parser.apply(responseBody); + case HttpStatus.SC_UNAUTHORIZED -> throw CodefException.of(unauthorizedError, responseBody); + default -> throw CodefException.of(defaultError, responseBody); + }; } /** * HTTP 응답 본문 추출 */ - private String extractResponseBody( - ClassicHttpResponse response, - boolean requiresDecoding - ) { + private String extractResponseBody(ClassicHttpResponse response, boolean requiresDecoding) throws CodefException { try { String responseBody = EntityUtils.toString(response.getEntity()); return requiresDecoding ? URLDecoder.decode(responseBody, UTF_8) : responseBody; - } catch (IOException ioException) { - throw CodefException.of(CodefError.IO_ERROR, ioException); - } catch (ParseException parseException) { - throw CodefException.of(CodefError.PARSE_ERROR, parseException); + } catch (IOException e) { + throw CodefException.of(CodefError.IO_ERROR, e); + } catch (ParseException e) { + throw CodefException.of(CodefError.PARSE_ERROR, e); } } @@ -89,8 +87,8 @@ private String extractResponseBody( private String parseAccessToken(String responseBody) throws CodefException { try { return JSON.parseObject(responseBody).getString(ACCESS_TOKEN); - } catch (Exception exception) { - throw CodefException.of(CodefError.PARSE_ERROR, exception); + } catch (Exception e) { + throw CodefException.of(CodefError.PARSE_ERROR, e); } } @@ -98,50 +96,20 @@ private String parseAccessToken(String responseBody) throws CodefException { * 상품 응답 파싱 */ private EasyCodefResponse parseProductResponse(String responseBody) throws CodefException { - JSONObject jsonResponse = JSON.parseObject(responseBody); - - EasyCodefResponse.Result result = Optional.ofNullable(jsonResponse.getJSONObject(RESULT)) - .map(object -> object.to(EasyCodefResponse.Result.class)) - .orElseThrow(() -> CodefException.from(CodefError.PARSE_ERROR)); - - Object data = Optional.ofNullable(jsonResponse.getJSONObject(DATA)) - .map(obj -> obj.to(Object.class)) - .orElseThrow(() -> CodefException.from(CodefError.PARSE_ERROR)); - - return new EasyCodefResponse(result, data); - } - - /** - * HTTP 상태 코드에 따른 처리 - */ - private T handleStatusCode(String responseBody, HttpStatusHandler handler) - throws CodefException { - return switch (handler.statusCode) { - case HttpStatus.SC_OK -> handler.successHandler.parse(responseBody); - case HttpStatus.SC_UNAUTHORIZED -> - throw CodefException.of(handler.unauthorizedError, responseBody); - default -> throw CodefException.of(handler.defaultError, responseBody); - }; - } - - /** - * HTTP 응답 처리를 위한 공통 인터페이스 - */ - private interface ResponseParser { - - T parse(String responseBody) throws CodefException; + try { + JSONObject jsonResponse = JSON.parseObject(responseBody); - } + EasyCodefResponse.Result result = Optional.ofNullable(jsonResponse.getJSONObject(RESULT)) + .map(object -> object.to(EasyCodefResponse.Result.class)) + .orElseThrow(() -> CodefException.from(CodefError.PARSE_ERROR)); - /** - * HTTP 응답 상태 코드에 따른 처리를 위한 레코드 - */ - private record HttpStatusHandler( - int statusCode, - CodefError unauthorizedError, - CodefError defaultError, - ResponseParser successHandler - ) { + Object data = Optional.ofNullable(jsonResponse.getJSONObject(DATA)) + .map(obj -> obj.to(Object.class)) + .orElseThrow(() -> CodefException.from(CodefError.PARSE_ERROR)); + return new EasyCodefResponse(result, data); + } catch (Exception e) { + throw CodefException.of(CodefError.PARSE_ERROR, e); + } } } \ No newline at end of file diff --git a/src/main/java/io/codef/api/constants/CodefResponseCode.java b/src/main/java/io/codef/api/constants/CodefResponseCode.java index c370d9d..97fd50f 100644 --- a/src/main/java/io/codef/api/constants/CodefResponseCode.java +++ b/src/main/java/io/codef/api/constants/CodefResponseCode.java @@ -4,4 +4,5 @@ public class CodefResponseCode { public static final String CF_00000 = "CF-00000"; public static final String CF_03002 = "CF-03002"; + public static final String CF_12872 = "CF-12872"; } diff --git a/src/main/java/io/codef/api/dto/CodefTransactionIdResponse.java b/src/main/java/io/codef/api/dto/CodefTransactionIdResponse.java index d86c9c3..42d2124 100644 --- a/src/main/java/io/codef/api/dto/CodefTransactionIdResponse.java +++ b/src/main/java/io/codef/api/dto/CodefTransactionIdResponse.java @@ -3,8 +3,4 @@ public record CodefTransactionIdResponse( String transactionId ) { - - public static CodefTransactionIdResponse from(String transactionId) { - return new CodefTransactionIdResponse(transactionId); - } } diff --git a/src/main/java/io/codef/api/dto/EasyCodefRequest.java b/src/main/java/io/codef/api/dto/EasyCodefRequest.java index 17c7753..2486fb3 100644 --- a/src/main/java/io/codef/api/dto/EasyCodefRequest.java +++ b/src/main/java/io/codef/api/dto/EasyCodefRequest.java @@ -1,8 +1,5 @@ package io.codef.api.dto; -import static org.apache.hc.client5.http.auth.StandardAuthScheme.BASIC; -import static org.apache.hc.client5.http.auth.StandardAuthScheme.BEARER; - import java.util.HashMap; public record EasyCodefRequest( @@ -10,12 +7,6 @@ public record EasyCodefRequest( HashMap requestBody ) { - /** - * Header Format Constants - */ - public static final String BEARER_TOKEN_FORMAT = BEARER + " %s"; - public static final String BASIC_TOKEN_FORMAT = BASIC + " %s"; - /** * Header Format Constants */ diff --git a/src/main/java/io/codef/api/dto/EasyCodefRequestBuilder.java b/src/main/java/io/codef/api/dto/EasyCodefRequestBuilder.java index 5d3a6aa..24e3d52 100644 --- a/src/main/java/io/codef/api/dto/EasyCodefRequestBuilder.java +++ b/src/main/java/io/codef/api/dto/EasyCodefRequestBuilder.java @@ -84,13 +84,7 @@ public EasyCodefRequest build() { this.requestBody(EASY_CODEF_JAVA_FLAG, true); this.generalRequestBody.putAll(secureRequestBody); - EasyCodefRequest easyCodefRequest = new EasyCodefRequest(path, generalRequestBody); - -// log.info("[EasyCodef] request object has been successfully built [ {} ]"); -// log.info(">> path = {}", path); -// log.info(">> requestBody = {}", generalRequestBody); - - return easyCodefRequest; + return new EasyCodefRequest(path, generalRequestBody); } private void encryptSecureRequestBody() { diff --git a/src/main/java/io/codef/api/facade/MultipleReqFacade.java b/src/main/java/io/codef/api/facade/MultipleReqFacade.java new file mode 100644 index 0000000..91ad38d --- /dev/null +++ b/src/main/java/io/codef/api/facade/MultipleReqFacade.java @@ -0,0 +1,76 @@ +package io.codef.api.facade; + +import static io.codef.api.dto.EasyCodefRequest.SSO_ID; + +import io.codef.api.CodefExecutorManager; +import io.codef.api.CodefValidator; +import io.codef.api.dto.EasyCodefRequest; +import io.codef.api.dto.EasyCodefResponse; +import io.codef.api.error.CodefError; +import io.codef.api.error.CodefException; +import io.codef.api.storage.MultipleRequestStorage; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.stream.IntStream; + +// 다중 요청 처리기 +public class MultipleReqFacade { + private static final long REQUEST_DELAY_MS = 700L; + + private final SingleReqFacade singleReqFacade; + private final MultipleRequestStorage multipleRequestStorage; + private final CodefExecutorManager executorManager; + + public MultipleReqFacade( + SingleReqFacade singleReqFacade, + MultipleRequestStorage multipleRequestStorage, + CodefExecutorManager executorManager + ) { + this.singleReqFacade = singleReqFacade; + this.multipleRequestStorage = multipleRequestStorage; + this.executorManager = executorManager; + } + + public EasyCodefResponse requestMultipleProduct(List requests) + throws CodefException { + validateRequests(requests); + assignSsoId(requests, UUID.randomUUID().toString()); + + try { + List> futures = scheduleRequests(requests); + + CompletableFuture firstCompleted = + CompletableFuture.anyOf(futures.toArray(new CompletableFuture[0])) + .thenApply(result -> (EasyCodefResponse) result); + + EasyCodefResponse result = firstCompleted.join(); + multipleRequestStorage.store(result.transactionId(), futures); + + return result; + } finally { + executorManager.close(); + } + } + + private void validateRequests(List requests) { + requests.forEach( + request -> CodefValidator.requireNonNullElseThrow(request, CodefError.REQUEST_NULL)); + } + + private void assignSsoId(List requests, String uuid) { + requests.forEach(request -> request.requestBody().put(SSO_ID, uuid)); + } + + private List> scheduleRequests( + List requests + ) { + return IntStream.range(0, requests.size()) + .mapToObj(i -> executorManager.scheduleRequest( + requests.get(i), + i * REQUEST_DELAY_MS, + singleReqFacade + )) + .toList(); + } +} diff --git a/src/main/java/io/codef/api/facade/SimpleAuthCertFacade.java b/src/main/java/io/codef/api/facade/SimpleAuthCertFacade.java new file mode 100644 index 0000000..e4e6253 --- /dev/null +++ b/src/main/java/io/codef/api/facade/SimpleAuthCertFacade.java @@ -0,0 +1,155 @@ +package io.codef.api.facade; + +import static io.codef.api.constants.CodefResponseCode.CF_03002; +import static io.codef.api.constants.CodefResponseCode.CF_12872; +import static io.codef.api.dto.EasyCodefRequest.TRUE; + +import com.alibaba.fastjson2.JSON; +import io.codef.api.constants.CodefResponseCode; +import io.codef.api.dto.EasyCodefRequest; +import io.codef.api.dto.EasyCodefResponse; +import io.codef.api.error.CodefException; +import io.codef.api.storage.MultipleRequestStorage; +import io.codef.api.storage.SimpleAuthStorage; +import io.codef.api.vo.CodefSimpleAuth; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SimpleAuthCertFacade { + + private static final Logger log = LoggerFactory.getLogger(SimpleAuthCertFacade.class); + + private final SingleReqFacade singleReqFacade; + private final SimpleAuthStorage simpleAuthStorage; + private final MultipleRequestStorage multipleRequestStorage; + + public SimpleAuthCertFacade( + SingleReqFacade singleReqFacade, + SimpleAuthStorage simpleAuthStorage, + MultipleRequestStorage multipleRequestStorage + ) { + this.singleReqFacade = singleReqFacade; + this.simpleAuthStorage = simpleAuthStorage; + this.multipleRequestStorage = multipleRequestStorage; + } + + public EasyCodefResponse requestSimpleAuthCertification(String transactionId) throws CodefException { + CodefSimpleAuth simpleAuth = simpleAuthStorage.get(transactionId); + EasyCodefRequest enrichedRequest = enrichRequestWithTwoWayInfo(simpleAuth); + EasyCodefResponse response = singleReqFacade.requestProduct(enrichedRequest); + + simpleAuthStorage.updateIfRequired( + simpleAuth.requestUrl(), + enrichedRequest, + response, + transactionId + ); + + logResponseStatus(response, transactionId); + return response; + } + + public List requestMultipleSimpleAuthCertification( + String transactionId + ) throws CodefException { + EasyCodefResponse firstResponse = requestSimpleAuthCertification(transactionId); + + return isSuccessResponse(firstResponse) + ? combineWithRemainingResponses(firstResponse, transactionId) + : returnFirstResponse(firstResponse); + } + + private void logAddAuthResponseStatus( + EasyCodefResponse response, + String transactionId, + String resultStatusCode + ) { + if (resultStatusCode.equals(CF_03002)) { + Object data = response.data(); + String addAuthMethod = JSON.parseObject(data.toString()).getString("method"); + + log.warn("Additional authentication required | method : {}\n", addAuthMethod); + } else if (resultStatusCode.equals(CF_12872)) { + log.warn( + "Retry limit for additional authentication exceeded. " + + "Please restart the process from the initial request.\n" + ); + } + } + + private void logDefaultResponseStatus(String transactionId, String resultStatusCode) { + log.info("Result Status Code : {}", resultStatusCode); + log.info("Transaction Id : {}", transactionId); + } + + private void logResponseStatus( + EasyCodefResponse response, + String transactionId + ) { + String resultStatusCode = response.code(); + logDefaultResponseStatus(transactionId, resultStatusCode); + + logAddAuthResponseStatus(response, transactionId, resultStatusCode); + } + + private List returnFirstResponse(EasyCodefResponse firstErrorResponse) { + return List.of(firstErrorResponse); + } + + private boolean isSuccessResponse(EasyCodefResponse response) { + return CodefResponseCode.CF_00000.equals(response.code()); + } + + private boolean isAddAuthResponse(EasyCodefResponse response) { + return CodefResponseCode.CF_03002.equals(response.code()); + } + + private boolean isFailureResponse(EasyCodefResponse response) { + return !isSuccessResponse(response) && !isAddAuthResponse(response); + } + + private EasyCodefRequest enrichRequestWithTwoWayInfo(CodefSimpleAuth simpleAuth) { + EasyCodefRequest request = simpleAuth.request(); + Map body = request.requestBody(); + + body.put(EasyCodefRequest.IS_TWO_WAY, true); + body.put(EasyCodefRequest.SIMPLE_AUTH, TRUE); + body.put(EasyCodefRequest.TWO_WAY_INFO, simpleAuth.response().data()); + + return request; + } + + private List combineWithRemainingResponses(EasyCodefResponse firstResponse, + String transactionId) throws CodefException { + + log.info("Await Responses called By transactionId `{}`", transactionId); + List responses = multipleRequestStorage.getRemainingResponses( + transactionId); + log.info("Await Responses Count = {}", responses.size()); + + responses.add(firstResponse); + List allResponseCodes = responses.stream().map(EasyCodefResponse::code).toList(); + log.info("Total Responses Count = {}\n", responses.size()); + + long successCount = responses.stream().filter(this::isSuccessResponse).count(); + long addAuthCount = responses.stream().filter(this::isAddAuthResponse).count(); + long failureCount = responses.stream().filter(this::isFailureResponse).count(); + + log.info("Success Response Status [CF-00000] Count : {}", successCount); + log.info("AddAuth Response Status [CF-03002] Count : {}", addAuthCount); + log.warn("Failure Response Status [ Else ] Count : {}", failureCount); + + if (failureCount > 0) { + responses.stream() + .filter(this::isFailureResponse) + .map(EasyCodefResponse::code) + .collect(Collectors.groupingBy(code -> code, Collectors.counting())) + .forEach((code, count) -> log.warn("> Error code : {}, Count: {}", code, count)); + } + + return responses; + } +} \ No newline at end of file diff --git a/src/main/java/io/codef/api/facade/SingleReqFacade.java b/src/main/java/io/codef/api/facade/SingleReqFacade.java new file mode 100644 index 0000000..c0d1ebf --- /dev/null +++ b/src/main/java/io/codef/api/facade/SingleReqFacade.java @@ -0,0 +1,41 @@ +package io.codef.api.facade; + +import io.codef.api.EasyCodefConnector; +import io.codef.api.EasyCodefToken; +import io.codef.api.constants.CodefClientType; +import io.codef.api.dto.EasyCodefRequest; +import io.codef.api.dto.EasyCodefResponse; +import io.codef.api.error.CodefException; +import io.codef.api.storage.SimpleAuthStorage; + +// 단일 요청 처리기 +public class SingleReqFacade { + private final EasyCodefToken easyCodefToken; + private final SimpleAuthStorage simpleAuthStorage; + private final CodefClientType clientType; + + public SingleReqFacade( + EasyCodefToken easyCodefToken, + SimpleAuthStorage simpleAuthStorage, + CodefClientType clientType + ) { + this.easyCodefToken = easyCodefToken; + this.simpleAuthStorage = simpleAuthStorage; + this.clientType = clientType; + } + + public EasyCodefResponse requestProduct(EasyCodefRequest request) throws CodefException { + String requestUrl = buildRequestUrl(request); + EasyCodefToken validToken = easyCodefToken.validateAndRefreshToken(); + + EasyCodefResponse response = + EasyCodefConnector.requestProduct(request, validToken, requestUrl); + + simpleAuthStorage.storeIfRequired(request, response, requestUrl); + return response; + } + + private String buildRequestUrl(EasyCodefRequest request) { + return clientType.getHost() + request.path(); + } +} diff --git a/src/main/java/io/codef/api/logger/JsonLogUtil.java b/src/main/java/io/codef/api/logger/JsonLogUtil.java deleted file mode 100644 index 08b36ff..0000000 --- a/src/main/java/io/codef/api/logger/JsonLogUtil.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.codef.api.logger; - -import com.alibaba.fastjson2.JSON; -import com.alibaba.fastjson2.JSONWriter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class JsonLogUtil { - - private static final Logger log = LoggerFactory.getLogger(JsonLogUtil.class); - - public static String toPrettyJson(Object obj) { - return JSON.toJSONString(obj, - JSONWriter.Feature.PrettyFormat, - JSONWriter.Feature.WriteMapNullValue, - JSONWriter.Feature.WriteNullListAsEmpty - ); - } -} \ No newline at end of file diff --git a/src/main/java/io/codef/api/storage/MultipleRequestStorage.java b/src/main/java/io/codef/api/storage/MultipleRequestStorage.java index 5baeb46..069b14b 100644 --- a/src/main/java/io/codef/api/storage/MultipleRequestStorage.java +++ b/src/main/java/io/codef/api/storage/MultipleRequestStorage.java @@ -4,11 +4,13 @@ import io.codef.api.dto.EasyCodefResponse; import io.codef.api.error.CodefError; import io.codef.api.error.CodefException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; public class MultipleRequestStorage { @@ -30,7 +32,7 @@ public List getRemainingResponses( .map(this::safeJoin) .filter(Objects::nonNull) .filter(response -> !Objects.equals(response.transactionId(), transactionId)) - .toList(); + .collect(Collectors.toCollection(ArrayList::new)); storage.remove(transactionId); return results; diff --git a/src/main/java/io/codef/api/storage/SimpleAuthStorage.java b/src/main/java/io/codef/api/storage/SimpleAuthStorage.java index 0be9371..edaaf9e 100644 --- a/src/main/java/io/codef/api/storage/SimpleAuthStorage.java +++ b/src/main/java/io/codef/api/storage/SimpleAuthStorage.java @@ -1,11 +1,11 @@ package io.codef.api.storage; import io.codef.api.constants.CodefResponseCode; -import io.codef.api.dto.CodefSimpleAuth; import io.codef.api.dto.EasyCodefRequest; import io.codef.api.dto.EasyCodefResponse; import io.codef.api.error.CodefError; import io.codef.api.error.CodefException; +import io.codef.api.vo.CodefSimpleAuth; import java.util.HashMap; import java.util.Map; import java.util.Optional; diff --git a/src/main/java/io/codef/api/util/AuthorizationUtil.java b/src/main/java/io/codef/api/util/AuthorizationUtil.java new file mode 100644 index 0000000..1ffe625 --- /dev/null +++ b/src/main/java/io/codef/api/util/AuthorizationUtil.java @@ -0,0 +1,14 @@ +package io.codef.api.util; + +public class AuthorizationUtil { + private static final String BASIC_FORMAT = "Basic %s"; + private static final String BEARER_FORMAT = "Bearer %s"; + + public static String createBasicAuth(String token) { + return String.format(BASIC_FORMAT, token); + } + + public static String createBearerAuth(String token) { + return String.format(BEARER_FORMAT, token); + } +} diff --git a/src/main/java/io/codef/api/util/JsonUtil.java b/src/main/java/io/codef/api/util/JsonUtil.java new file mode 100644 index 0000000..e59796e --- /dev/null +++ b/src/main/java/io/codef/api/util/JsonUtil.java @@ -0,0 +1,15 @@ +package io.codef.api.util; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONWriter; + +public class JsonUtil { + + public static String toPrettyJson(Object object) { + return JSON.toJSONString(object, + JSONWriter.Feature.PrettyFormat, + JSONWriter.Feature.WriteMapNullValue, + JSONWriter.Feature.WriteNullListAsEmpty + ); + } +} \ No newline at end of file diff --git a/src/main/java/io/codef/api/dto/CodefSimpleAuth.java b/src/main/java/io/codef/api/vo/CodefSimpleAuth.java similarity index 52% rename from src/main/java/io/codef/api/dto/CodefSimpleAuth.java rename to src/main/java/io/codef/api/vo/CodefSimpleAuth.java index 3000e92..bac6e45 100644 --- a/src/main/java/io/codef/api/dto/CodefSimpleAuth.java +++ b/src/main/java/io/codef/api/vo/CodefSimpleAuth.java @@ -1,4 +1,7 @@ -package io.codef.api.dto; +package io.codef.api.vo; + +import io.codef.api.dto.EasyCodefRequest; +import io.codef.api.dto.EasyCodefResponse; public record CodefSimpleAuth( String requestUrl,