feat: WebClient module 확장 적용

dev
gitea-관리자 1 year ago
parent 98764f06eb
commit 1b55a58e39

@ -13,7 +13,6 @@ import javax.validation.Validator;
import kr.xit.biz.ens.model.kakao.KkopayDocAttrDTO.DocumentBinderUuid;
import kr.xit.biz.ens.model.kakao.KkopayDocAttrDTO.Receiver;
import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO;
import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.BulkSendRequests;
import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.BulkSendResponses;
import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.BulkStatusRequests;
@ -24,6 +23,7 @@ import kr.xit.biz.ens.model.kakao.KkopayDocDTO.SendRequest;
import kr.xit.biz.ens.model.kakao.KkopayDocDTO.SendResponse;
import kr.xit.biz.ens.model.kakao.KkopayDocDTO.ValidTokenRequest;
import kr.xit.biz.ens.model.kakao.KkopayDocDTO.ValidTokenResponse;
import kr.xit.core.spring.util.ApiWebClientUtil;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
@ -33,7 +33,6 @@ import org.springframework.stereotype.Component;
import kr.xit.core.exception.BizRuntimeException;
import kr.xit.core.model.ApiResponseDTO;
import kr.xit.core.spring.annotation.TraceLogging;
import kr.xit.core.spring.util.ApiWebClient;
import kr.xit.core.support.utils.Checks;
import kr.xit.core.support.utils.JsonUtils;
import lombok.RequiredArgsConstructor;
@ -59,22 +58,22 @@ import lombok.extern.slf4j.Slf4j;
@Component
public class AsyncKkopayEltrcDocService extends EgovAbstractServiceImpl implements IAsyncKkopayEltrcDocService {
@Value("${contract.provider.kakao.host}")
@Value("${contract.kakao.host}")
private String HOST;
@Value("#{'${contract.provider.kakao.api.send}'.split(';')}")
@Value("#{'${contract.kakao.api.send}'.split(';')}")
private String[] API_SEND;
@Value("#{'${contract.provider.kakao.api.validToken}'.split(';')}")
@Value("#{'${contract.kakao.api.validToken}'.split(';')}")
private String[] API_VALID_TOKEN;
@Value("#{'${contract.provider.kakao.api.modifyStatus}'.split(';')}")
@Value("#{'${contract.kakao.api.modifyStatus}'.split(';')}")
private String[] API_MODIFY_STATUS;
@Value("#{'${contract.provider.kakao.api.findStatus}'.split(';')}")
@Value("#{'${contract.kakao.api.findStatus}'.split(';')}")
private String[] API_STATUS;
@Value("#{'${contract.provider.kakao.api.bulksend}'.split(';')}")
@Value("#{'${contract.kakao.api.bulksend}'.split(';')}")
private String[] API_BULKSEND;
@Value("#{'${contract.provider.kakao.api.bulkstatus}'.split(';')}")
@Value("#{'${contract.kakao.api.bulkstatus}'.split(';')}")
private String[] API_BULKSTATUS;
private final ApiWebClient webClient;
private final ApiWebClientUtil webClient;
private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
private static final CharSequence DOCUMENT_BINDER_UUID = "{document_binder_uuid}";
@ -111,7 +110,7 @@ public class AsyncKkopayEltrcDocService extends EgovAbstractServiceImpl implemen
if(Objects.requireNonNull(errors).size() > 0) throw BizRuntimeException.create(errors.toString());
return CompletableFuture.supplyAsync(() ->
webClient.exchange(HOST + API_SEND[0], HttpMethod.valueOf(API_SEND[1]), JsonUtils.toJson(reqDTO), SendResponse.class))
webClient.exchangeKko(HOST + API_SEND[0], HttpMethod.valueOf(API_SEND[1]), JsonUtils.toJson(reqDTO), SendResponse.class))
.handle((r, e) -> {
if(e != null){
return webClient.sendError(e);
@ -138,7 +137,7 @@ public class AsyncKkopayEltrcDocService extends EgovAbstractServiceImpl implemen
.replace("{tokens}", reqDTO.getToken());
return CompletableFuture.supplyAsync(() ->
webClient.exchange(url, HttpMethod.valueOf(API_VALID_TOKEN[1]), null, ValidTokenResponse.class))
webClient.exchangeKko(url, HttpMethod.valueOf(API_VALID_TOKEN[1]), null, ValidTokenResponse.class))
.handle((r, e) -> {
if(e != null){
return webClient.sendError(e);
@ -168,7 +167,7 @@ public class AsyncKkopayEltrcDocService extends EgovAbstractServiceImpl implemen
String url = HOST + API_MODIFY_STATUS[0].replace(DOCUMENT_BINDER_UUID, reqDTO.getDocument_binder_uuid());
return CompletableFuture.supplyAsync(() ->
webClient.exchange(url, HttpMethod.valueOf(API_MODIFY_STATUS[1]), body, Void.class))
webClient.exchangeKko(url, HttpMethod.valueOf(API_MODIFY_STATUS[1]), body, Void.class))
.handle((r, e) -> {
if(e != null){
return webClient.sendError(e);
@ -197,7 +196,7 @@ public class AsyncKkopayEltrcDocService extends EgovAbstractServiceImpl implemen
String url = HOST + API_STATUS[0].replace(DOCUMENT_BINDER_UUID, reqDTO.getDocument_binder_uuid());
return CompletableFuture.supplyAsync(() ->
webClient.exchange(url, HttpMethod.valueOf(API_STATUS[1]), null, DocStatusResponse.class))
webClient.exchangeKko(url, HttpMethod.valueOf(API_STATUS[1]), null, DocStatusResponse.class))
.handle((r, e) -> {
if(e != null){
return webClient.sendError(e);
@ -219,7 +218,7 @@ public class AsyncKkopayEltrcDocService extends EgovAbstractServiceImpl implemen
@Async("asyncExecutor")
public CompletableFuture<ApiResponseDTO<BulkSendResponses>> requestSendBulk(final BulkSendRequests reqDTO) {
return CompletableFuture.supplyAsync(() ->
webClient.exchange(HOST + API_BULKSEND[0], HttpMethod.valueOf(API_BULKSEND[1]), JsonUtils.toJson(reqDTO), BulkSendResponses.class))
webClient.exchangeKko(HOST + API_BULKSEND[0], HttpMethod.valueOf(API_BULKSEND[1]), JsonUtils.toJson(reqDTO), BulkSendResponses.class))
.handle((r, e) -> {
if(e != null){
return webClient.sendError(e);
@ -258,7 +257,7 @@ public class AsyncKkopayEltrcDocService extends EgovAbstractServiceImpl implemen
}
return CompletableFuture.supplyAsync(() ->
webClient.exchange(HOST + API_BULKSTATUS[0], HttpMethod.valueOf(API_BULKSTATUS[1]), JsonUtils.toJson(reqDTO), BulkStatusResponses.class))
webClient.exchangeKko(HOST + API_BULKSTATUS[0], HttpMethod.valueOf(API_BULKSTATUS[1]), JsonUtils.toJson(reqDTO), BulkStatusResponses.class))
.handle((r, e) -> {
if(e != null){
return webClient.sendError(e);

@ -8,7 +8,6 @@ import java.util.stream.Collectors;
import kr.xit.biz.ens.model.kakao.KkopayDocAttrDTO.DocumentBinderUuid;
import kr.xit.biz.ens.model.kakao.KkopayDocAttrDTO.Receiver;
import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO;
import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.BulkSendReq;
import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.BulkSendRequests;
import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.BulkSendResponses;
@ -26,7 +25,7 @@ import kr.xit.core.exception.BizRuntimeException;
import kr.xit.core.model.ApiResponseDTO;
import kr.xit.core.model.ErrorDTO;
import kr.xit.core.spring.annotation.TraceLogging;
import kr.xit.core.spring.util.ApiWebClient;
import kr.xit.core.spring.util.ApiWebClientUtil;
import kr.xit.core.support.utils.Checks;
import kr.xit.core.support.utils.JsonUtils;
@ -62,22 +61,22 @@ import javax.validation.Validator;
@Component
public class KkopayEltrcDocService extends EgovAbstractServiceImpl implements IKkopayEltrcDocService {
@Value("${contract.provider.kakao.host}")
@Value("${contract.kakao.host}")
private String HOST;
@Value("#{'${contract.provider.kakao.api.send}'.split(';')}")
@Value("#{'${contract.kakao.api.send}'.split(';')}")
private String[] API_SEND;
@Value("#{'${contract.provider.kakao.api.validToken}'.split(';')}")
@Value("#{'${contract.kakao.api.validToken}'.split(';')}")
private String[] API_VALID_TOKEN;
@Value("#{'${contract.provider.kakao.api.modifyStatus}'.split(';')}")
@Value("#{'${contract.kakao.api.modifyStatus}'.split(';')}")
private String[] API_MODIFY_STATUS;
@Value("#{'${contract.provider.kakao.api.findStatus}'.split(';')}")
@Value("#{'${contract.kakao.api.findStatus}'.split(';')}")
private String[] API_STATUS;
@Value("#{'${contract.provider.kakao.api.bulksend}'.split(';')}")
@Value("#{'${contract.kakao.api.bulksend}'.split(';')}")
private String[] API_BULKSEND;
@Value("#{'${contract.provider.kakao.api.bulkstatus}'.split(';')}")
@Value("#{'${contract.kakao.api.bulkstatus}'.split(';')}")
private String[] API_BULKSTATUS;
private final ApiWebClient webClient;
private final ApiWebClientUtil webClient;
private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
private static final CharSequence DOCUMENT_BINDER_UUID = "{document_binder_uuid}";
@ -111,7 +110,7 @@ public class KkopayEltrcDocService extends EgovAbstractServiceImpl implements IK
if(Checks.isEmpty(receiver.getBirthday())) Objects.requireNonNull(errors).add("receiver.birthday=받는이 생년월일은 필수입니다.");
}
if(Objects.requireNonNull(errors).size() > 0) throw BizRuntimeException.create(errors.toString());
return webClient.exchange(HOST + API_SEND[0], HttpMethod.valueOf(API_SEND[1]), JsonUtils.toJson(reqDTO), SendResponse.class);
return webClient.exchangeKko(HOST + API_SEND[0], HttpMethod.valueOf(API_SEND[1]), JsonUtils.toJson(reqDTO), SendResponse.class);
}
/**
@ -129,7 +128,7 @@ public class KkopayEltrcDocService extends EgovAbstractServiceImpl implements IK
String url = HOST
+ API_VALID_TOKEN[0].replace(DOCUMENT_BINDER_UUID, reqDTO.getDocument_binder_uuid())
.replace("{tokens}", reqDTO.getToken());
return webClient.exchange(url, HttpMethod.valueOf(API_VALID_TOKEN[1]), null, ValidTokenResponse.class);
return webClient.exchangeKko(url, HttpMethod.valueOf(API_VALID_TOKEN[1]), null, ValidTokenResponse.class);
}
/**
@ -150,7 +149,7 @@ public class KkopayEltrcDocService extends EgovAbstractServiceImpl implements IK
String body = "{\"document\": {\"is_detail_read\": true} }";
String url = HOST + API_MODIFY_STATUS[0].replace(DOCUMENT_BINDER_UUID, reqDTO.getDocument_binder_uuid());
webClient.exchange(url, HttpMethod.valueOf(API_MODIFY_STATUS[1]), body, Void.class);
webClient.exchangeKko(url, HttpMethod.valueOf(API_MODIFY_STATUS[1]), body, Void.class);
}
/**
@ -171,7 +170,7 @@ public class KkopayEltrcDocService extends EgovAbstractServiceImpl implements IK
validate(reqDTO, null);
String url = HOST + API_STATUS[0].replace(DOCUMENT_BINDER_UUID, reqDTO.getDocument_binder_uuid());
return webClient.exchange(url, HttpMethod.valueOf(API_STATUS[1]), null, DocStatusResponse.class);
return webClient.exchangeKko(url, HttpMethod.valueOf(API_STATUS[1]), null, DocStatusResponse.class);
}
/**
@ -229,7 +228,7 @@ public class KkopayEltrcDocService extends EgovAbstractServiceImpl implements IK
throw BizRuntimeException.create(errors.toString());
}
return webClient.exchange(HOST + API_BULKSEND[0], HttpMethod.valueOf(API_BULKSEND[1]), JsonUtils.toJson(reqDTO), BulkSendResponses.class);
return webClient.exchangeKko(HOST + API_BULKSEND[0], HttpMethod.valueOf(API_BULKSEND[1]), JsonUtils.toJson(reqDTO), BulkSendResponses.class);
}
/**
@ -259,13 +258,9 @@ public class KkopayEltrcDocService extends EgovAbstractServiceImpl implements IK
if(errors.size() > 0) {
throw BizRuntimeException.create(errors.toString());
}
return webClient.exchange(HOST + API_BULKSTATUS[0], HttpMethod.valueOf(API_BULKSTATUS[1]), JsonUtils.toJson(reqDTO), BulkStatusResponses.class);
return webClient.exchangeKko(HOST + API_BULKSTATUS[0], HttpMethod.valueOf(API_BULKSTATUS[1]), JsonUtils.toJson(reqDTO), BulkStatusResponses.class);
}
@Override
public ApiResponseDTO<KkopayDocDTO.ValidTokenResponse> findMyDocReadyAndMblPage(OneTimeToken reqDTO) {
@ -273,7 +268,7 @@ public class KkopayEltrcDocService extends EgovAbstractServiceImpl implements IK
.replace("{tokens}", reqDTO.getToken());
// 유효성 검증
ValidTokenResponse validTokenRes = webClient.exchange(url, HttpMethod.valueOf(API_VALID_TOKEN[1]), null,
ValidTokenResponse validTokenRes = webClient.exchangeKko(url, HttpMethod.valueOf(API_VALID_TOKEN[1]), null,
ValidTokenResponse.class);
if(!"USED".equals(validTokenRes.getToken_status())){
@ -286,7 +281,7 @@ public class KkopayEltrcDocService extends EgovAbstractServiceImpl implements IK
// 정상 : HttpStatus.NO_CONTENT(204) return
// error : body에 error_code, error_message return
ErrorDTO errorDTO = webClient.exchange(url2, HttpMethod.valueOf(API_MODIFY_STATUS[1]), body, ErrorDTO.class);
ErrorDTO errorDTO = webClient.exchangeKko(url2, HttpMethod.valueOf(API_MODIFY_STATUS[1]), body, ErrorDTO.class);
if(errorDTO != null){
return ApiResponseDTO.error(errorDTO.getErrorCode(), errorDTO.getErrorMessage());
}

@ -39,7 +39,7 @@ import lombok.extern.slf4j.Slf4j;
@RestController
@RequestMapping(value = "/api/kakaopay/v1")
public class KkopayEltrcDocController {
@Value("${contract.provider.kakao.token}")
@Value("${contract.kakao.token}")
private String accessToken;
private final IKkopayEltrcDocService service;

@ -2,26 +2,36 @@
# application 설정
#-----------------------------------------------------------------------
contract:
provider:
# milisecond
connection:
timeout: 30000
readTimeout: 30000
thread:
# 동시 실행 스레드 개수
corePoolSize: 5
# 스레드 풀에서 사용할 수 있는 최대 개수
maxPoolSize: 10
kakao:
bulk-max-cnt: 10
host: https://docs-gw.kakaopay.com
#host: https://dummy.restapiexample.com
token: dd394da7f66211eb9cbe46e139ceffc2
uuid: CON-41ef0535f67211ebbdedd2e6ed332381
api:
send: /v1/documents;POST
validToken: /v1/{document_binder_uuid}/tokens/{tokens};GET
modifyStatus: /v1/documents/{document_binder_uuid};POST
findStatus: /v1/documents/{document_binder_uuid}/status;GET
bulksend: /v1/documents/bulk;POST
bulkstatus: /v1/documents/bulk/status;POST
# milisecond
connection:
timeout: 30000
readTimeout: 30000
thread:
# 동시 실행 스레드 개수
corePoolSize: 5
# 스레드 풀에서 사용할 수 있는 최대 개수
maxPoolSize: 10
kakao:
bulk-max-cnt: 10
host: https://docs-gw.kakaopay.com
#host: https://dummy.restapiexample.com
token: dd394da7f66211eb9cbe46e139ceffc2
uuid: CON-41ef0535f67211ebbdedd2e6ed332381
api:
send: /v1/documents;POST
validToken: /v1/{document_binder_uuid}/tokens/{tokens};GET
modifyStatus: /v1/documents/{document_binder_uuid};POST
findStatus: /v1/documents/{document_binder_uuid}/status;GET
bulksend: /v1/documents/bulk;POST
bulkstatus: /v1/documents/bulk/status;POST
nice:
#host: http://parking.cheonan.go.kr
host: https://svc.niceapi.co.kr:22001
client-id: "6c3eb1ff-530d-458a-9a6e-e02e3346f679"
client-secret: "960f204ec45bb312b7ad2d6b54b984d9c353b8"
api:
generate-token: /digital/niceid/oauth/oauth/token
revoke-token: /digital/niceid/oauth/oauth/token/revokeById
publickey: /digital/niceid/api/v1.0/common/crypto/publickey
symmetrickey: /digital/niceid/api/v1.0/common/crypto/symmetrickey
ci: /digital/niceid/cert/v1.0/ipin/addinfo/ci

@ -2,7 +2,6 @@ package kr.xit.biz.ens.service;
import java.io.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import egovframework.com.cmm.util.EgovDateUtil;
@ -17,7 +16,7 @@ import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.BulkStatusRequests;
import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.BulkStatusResponses;
import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.PropertyBulk;
import kr.xit.biz.sms.service.ISmsMessageService;
import kr.xit.core.spring.util.ApiWebClient;
import kr.xit.core.spring.util.ApiWebClientUtil;
import kr.xit.core.support.utils.DateUtils;
import kr.xit.core.support.utils.JsonUtils;
import org.apache.commons.collections4.ListUtils;
@ -62,14 +61,14 @@ import javax.validation.Validator;
@RequiredArgsConstructor
@Service
public class EnsBatchService extends EgovAbstractServiceImpl implements IEnsBatchService {
@Value("${contract.provider.kakao.host}")
@Value("${contract.kakao.host}")
private String apiHost;
@Value("${contract.provider.kakao.api.bulksend}")
@Value("${contract.kakao.api.bulksend}")
private String apiBulkSend;
@Value("${contract.provider.kakao.api.bulkstatus}")
@Value("${contract.kakao.api.bulkstatus}")
private String apiBulkStatus;
private final ApiWebClient apiWebClient;
private final ApiWebClientUtil apiWebClient;
private final IEnsBatchMapper mapper;
private final EgovPasswordEncoder encryptor;
private final ISmsMessageService smsService;
@ -77,7 +76,7 @@ public class EnsBatchService extends EgovAbstractServiceImpl implements IEnsBatc
private static final Validator validator = Validation.buildDefaultValidatorFactory()
.getValidator();
@Value("${contract.provider.kakao.bulk-max-cnt}")
@Value("${contract.kakao.bulk-max-cnt}")
private int bulkMaxCnt;
@Value("${file.cmm.upload.root}")
@ -463,7 +462,7 @@ public class EnsBatchService extends EgovAbstractServiceImpl implements IEnsBatc
List<ApiResponseDTO<BulkSendResponses>> apiResults = partitions.stream()
.map(bulkSendList ->
ApiResponseDTO.success(
apiWebClient.exchange(
apiWebClient.exchangeKko(
url.toString(),
HttpMethod.POST,
JsonUtils.toJson(BulkSendRequests.builder()
@ -862,7 +861,7 @@ public class EnsBatchService extends EgovAbstractServiceImpl implements IEnsBatc
List<ApiResponseDTO<KkopayDocBulkDTO.BulkStatusResponses>> apiResults = partitions.stream()
.map(uuids ->
ApiResponseDTO.success(
apiWebClient.exchange(
apiWebClient.exchangeKko(
url.toString(),
HttpMethod.POST,
JsonUtils.toJson(BulkStatusRequests.builder()

@ -4,15 +4,12 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.Map;
import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.BulkSendRequests;
import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.BulkSendResponses;
import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.BulkStatusRequests;
import kr.xit.biz.ens.model.kakao.KkopayDocDTO.SendRequest;
import kr.xit.biz.ens.model.kakao.KkopayDocDTO.SendResponse;
import kr.xit.biz.ens.service.IEnsBatchService;
import kr.xit.core.model.ApiResponseDTO;
import kr.xit.core.spring.util.ApiWebClient;
import kr.xit.core.spring.util.ApiWebClientUtil;
import kr.xit.core.support.utils.JsonUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
@ -43,14 +40,14 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/batch/ens/test/v1")
public class ApiCallTestController {
@Value("${contract.provider.kakao.host}")
@Value("${contract.kakao.host}")
private String apiHost;
@Value("${contract.provider.kakao.api.bulksend}")
@Value("${contract.kakao.api.bulksend}")
private String apiBulkSend;
@Value("${contract.provider.kakao.api.bulkstatus}")
@Value("${contract.kakao.api.bulkstatus}")
private String apiBulkStatus;
private final ApiWebClient apiWebClient;
private final ApiWebClientUtil apiWebClient;
private static final String SNDNG_PROCESS_STTUS = "sndngProcessSttus";
private final IEnsBatchService service;
@ -102,7 +99,7 @@ public class ApiCallTestController {
.append("/api/kakaopay/v1/documents");
return
apiWebClient.exchange(url.toString(),
apiWebClient.exchangeKko(url.toString(),
HttpMethod.POST,
JsonUtils.toJson(reqDTO),
ApiResponseDTO.class);
@ -156,7 +153,7 @@ public class ApiCallTestController {
) {
final String url = apiHost + apiBulkSend;
return apiWebClient.exchange(
return apiWebClient.exchangeKko(
url,
HttpMethod.POST,
JsonUtils.toJson(reqDTO),
@ -190,7 +187,7 @@ public class ApiCallTestController {
) {
final String url = apiHost + apiBulkStatus;;
return apiWebClient.exchange(
return apiWebClient.exchangeKko(
url,
HttpMethod.POST,
JsonUtils.toJson(reqDTO),

@ -6,12 +6,12 @@ package kr.xit.biz.common;
*
* packageName : kr.xit.ens.support.common
* fileName : KakaoConstants
* author : xitdev
* author : limju
* date : 2023-05-04
* ======================================================================
*
* ----------------------------------------------------------------------
* 2023-05-04 xitdev
* 2023-05-04 limju
*
* </pre>
*/

@ -19,12 +19,12 @@ import lombok.experimental.SuperBuilder;
*
* packageName : kr.xit.biz.ens.model.kakao
* fileName : KkopayDocAttrDTO
* author : xitdev
* author : limju
* date : 2023-05-03
* ======================================================================
*
* ----------------------------------------------------------------------
* 2023-05-03 xitdev
* 2023-05-03 limju
*
* </pre>
*/

@ -20,12 +20,12 @@ import org.hibernate.validator.constraints.Length;
*
* packageName : kr.xit.ens.model.kakao
* fileName : KkopayDocDTO
* author : xitdev
* author : limju
* date : 2023-05-03
* ======================================================================
*
* ----------------------------------------------------------------------
* 2023-05-03 xitdev
* 2023-05-03 limju
*
* </pre>
*/

@ -1,6 +1,7 @@
package kr.xit.core.consts;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.springframework.http.HttpHeaders;
/**
* <pre>
@ -102,7 +103,7 @@ public class Constants {
*
*/
public enum HeaderName {
TOKEN("Authorization"), //Token
TOKEN(HttpHeaders.AUTHORIZATION), //Token
UUID("Contract-Uuid"), //Contract-Uuid
; // TOKEN

@ -35,10 +35,10 @@ import kr.xit.core.spring.config.support.CustomAsyncExceptionHandler;
@EnableAsync
public class AsyncExecutorConfig extends AsyncConfigurerSupport {
@Value("${contract.provider.connection.thread.corePoolSize:5}")
@Value("${contract.connection.thread.corePoolSize:5}")
private int corePoolSize;
@Value("${contract.provider.connection.thread.maxPoolSize:10}")
@Value("${contract.connection.thread.maxPoolSize:10}")
private int maxPoolSize;
/**

@ -17,12 +17,12 @@ import lombok.extern.slf4j.Slf4j;
*
* packageName : kr.xit.core.spring.config.support
* fileName : PropertiesConfig
* author : xitdev
* author : limju
* date : 2023-05-01
* ======================================================================
*
* ----------------------------------------------------------------------
* 2023-05-01 xitdev
* 2023-05-01 limju
*
* </pre>
*/

@ -0,0 +1,138 @@
package kr.xit.core.spring.config.support;
import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.LoggingCodecSupport;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.DefaultUriBuilderFactory;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
/**
* <pre>
* description : WebClient configuration
*
* packageName : kr.xit.core.spring.config.support
* fileName : WebClientConfig
* author : julim
* date : 2023-09-06
* ======================================================================
*
* ----------------------------------------------------------------------
* 2023-09-06 julim
*
* </pre>
*/
@Slf4j
@Configuration
public class WebClientConfig {
@Value("${contract.connection.timeout:5000}")
private int connectTimeout;
@Value("${contract.connection.readTimeout:5000}")
private int readTimeout;
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
// HttpClient 총 연결 시간
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout)
.responseTimeout(Duration.ofMillis(this.connectTimeout))
.doOnConnected(conn ->
conn.addHandlerLast(new ReadTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS))
.addHandlerLast(new WriteTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS)));
/**
* setEncodingMode : GET URI 릿
* @return
*/
@Bean
public WebClient webClient() {
// 256KB 보다 큰 HTTP 메시지를 처리 시도 → DataBufferLimitException 에러 발생 방어
ExchangeStrategies es = ExchangeStrategies.builder()
.codecs(configurer -> {
configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024);
//configurer.customCodecs().register(new Jackson2JsonDecoder());
//configurer.customCodecs().register(new Jackson2JsonEncoder());
})
.build();
//FIXME::rest call async 로깅
// ExchangeStrategies를 통해 setEnableLoggingRequestDetails(true)로 설정
// boot에서 로깅 org.springframework.web.reactive.function.client.ExchangeFunctions: DEBUG 하여 활성
es.messageWriters()
.stream()
.filter(LoggingCodecSupport .class::isInstance)
.forEach(writer -> ((LoggingCodecSupport)writer).setEnableLoggingRequestDetails(true));
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
return WebClient.builder()
.uriBuilderFactory(factory)
.clientConnector(new ReactorClientHttpConnector(httpClient))
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
.exchangeStrategies(es)
.filters(exchangeFilterFunctions -> {
exchangeFilterFunctions.add(logRequest());
exchangeFilterFunctions.add(logResponse());
//TODO::에러발생시 점검필요
//exchangeFilterFunctions.add(errorHandler());
})
.build();
}
/**
* maxConnections : connection pool
* pendingAcquireTimeout :
* pendingAcquireMaxCount : (-1: no limit)
* maxIdleTime : idle
* @return
*/
@Bean
public ConnectionProvider connectionProvider() {
return ConnectionProvider.builder("http-pool")
.maxConnections(100)
.pendingAcquireTimeout(Duration.ofMillis(0))
.pendingAcquireMaxCount(-1)
.maxIdleTime(Duration.ofMillis(1000L))
.build();
}
private ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
if (log.isDebugEnabled()) {
StringBuilder sb = new StringBuilder("\n>>>>>>>>>> Http Rest Request <<<<<<<<<<<<<\n");
clientRequest
.headers()
.forEach((name, values) -> values.forEach(value -> sb.append(name).append(": ").append(value).append("\n")));
log.debug(sb.toString());
}
return Mono.just(clientRequest);
});
}
private ExchangeFilterFunction logResponse() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
StringBuilder sb = new StringBuilder("\n>>>>>>>>>> Http Rest Response <<<<<<<<<<<<<\n");
clientResponse.headers()
.asHttpHeaders()
.forEach((name, values) -> values.forEach(value -> sb.append(name).append(": ").append(value).append("\n")));
log.debug(sb.toString());
return Mono.just(clientResponse);
});
}
}

@ -1,262 +0,0 @@
package kr.xit.core.spring.util;
import java.time.Duration;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.LoggingCodecSupport;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientRequestException;
import io.netty.channel.ChannelOption;
import io.netty.channel.ConnectTimeoutException;
import io.netty.handler.timeout.ReadTimeoutException;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import kr.xit.core.consts.Constants;
import kr.xit.core.exception.BizRuntimeException;
import kr.xit.core.model.ApiResponseDTO;
import kr.xit.core.model.ErrorDTO;
import kr.xit.core.spring.util.error.ClientError;
import kr.xit.core.spring.util.error.ErrorParse;
import kr.xit.core.spring.util.error.ServerError;
import kr.xit.core.support.utils.Checks;
import kr.xit.core.support.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
/**
* <pre>
* description : react Restfull Util
*
* packageName : kr.xit.core.spring.util
* fileName : ApiWebClient
* author : julim
* date : 2023-04-28
* ======================================================================
*
* ----------------------------------------------------------------------
* 2023-04-28 julim
*
* </pre>
* @see kr.xit.core.spring.config.AsyncExecutorConfig
* @see ClientError
* @see ServerError
*/
@Slf4j
@Component
public class ApiWebClient {
private final WebClient webClient;
public ApiWebClient(
@Value("${contract.provider.kakao.token:}")
String accessToken,
@Value("${contract.provider.kakao.uuid:}")
String contractUuid,
@Value("${contract.provider.connection.timeout:5000}")
int connectionTimeout,
@Value("${contract.provider.connection.readTimeout:5000}")
int readTimeout
) {
ExchangeStrategies es = ExchangeStrategies.builder()
.codecs(configurer -> {
configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024);
//configurer.customCodecs().register(new Jackson2JsonDecoder());
//configurer.customCodecs().register(new Jackson2JsonEncoder());
})
.build();
//FIXME::rest call async 로깅
// ExchangeStrategies를 통해 setEnableLoggingRequestDetails(true)로 설정
// boot에서 로깅 org.springframework.web.reactive.function.client.ExchangeFunctions: DEBUG 하여 활성
es.messageWriters()
.stream()
.filter(LoggingCodecSupport.class::isInstance)
.forEach(writer -> ((LoggingCodecSupport)writer).setEnableLoggingRequestDetails(true));
this.webClient = WebClient.builder()
.clientConnector(httpClient(connectionTimeout, readTimeout))
.baseUrl("http://localhost:8081") // (1) 외부 API Base URl
.defaultHeaders((headers)->{
headers.setContentType(new MediaType(MediaType.APPLICATION_JSON, Constants.CHARSET_UTF8));
headers.set(Constants.HeaderName.TOKEN.getCode(), String.format("%s %s", Constants.JwtToken.GRANT_TYPE.getCode(), accessToken));
headers.set(Constants.HeaderName.UUID.getCode(), contractUuid);
})
.exchangeStrategies(es)
.filters(exchangeFilterFunctions -> {
exchangeFilterFunctions.add(logRequest());
exchangeFilterFunctions.add(logResponse());
//TODO::에러발생시 점검필요
//exchangeFilterFunctions.add(errorHandler());
})
.build();
}
//TODO:: ApiResponseDTO로 return 하도록 해야할 듯
// CompletableFuture.supplyAsync().handle() / exceptionally() 사용을 위해
/**
* <pre>
* Async call - Response Entity()
* @param url call url
* @param method POST|GET
* @param body JSON String type
* @param rtnClzz rtnClzz return type class
* (ex: new KkopayDocDTO.DocStatusResponse().getClass())
* @return T rtnClzz return DTO
* </pre>
*/
public <T> T exchange(String url, HttpMethod method, Object body, Class<T> rtnClzz) {
return webClient.method(method)
.uri(url)
.bodyValue(Objects.requireNonNullElse(body, ""))
.exchangeToMono(res -> res.bodyToMono(rtnClzz))
.block();
}
public <T> T exchange2(String url, HttpMethod method, Object body, Class<T> rtnClzz) {
return webClient.method(method)
.uri(url)
.bodyValue(Objects.requireNonNullElse(body, ""))
.exchangeToMono(res -> res.bodyToMono(rtnClzz))
.block();
}
public <T> ApiResponseDTO<T> sendError(Throwable e) {
Map<String,Object> map = ErrorParse.extractError(e.getCause());
return ApiResponseDTO.error(String.valueOf(map.get("code")), String.valueOf(map.get("message")), (HttpStatus)map.get("httpStatus"));
}
/**
* <pre>
* Async call - body
* @param url call url
* @param method POST|GET
* @param body JSON String type
* @param rtnClzz rtnClzz return type class
* (ex: new KkopayDocDTO.DocStatusResponse().getClass())
* @return T rtnClzz return DTO
* </pre>
*/
public <T> T retrive(String url, HttpMethod method, Object body, Class<T> rtnClzz) {
return webClient.mutate()
.build()
.method(method)
.uri(url)
.bodyValue(Objects.requireNonNullElse(body, ""))
.retrieve()
.onStatus(
status -> status.is4xxClientError() || status.is5xxServerError(),
res -> res.bodyToMono(String.class).map(BizRuntimeException::create))
.bodyToMono(rtnClzz)
.block();
}
private ReactorClientHttpConnector httpClient(int connectTimeout, int readTimeout){
return new ReactorClientHttpConnector(
HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout)
.responseTimeout(Duration.ofMillis(connectTimeout))
.doOnConnected(conn ->
conn.addHandlerLast(new ReadTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS))
.addHandlerLast(new WriteTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS)))
);
}
ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
if (log.isDebugEnabled()) {
StringBuilder sb = new StringBuilder("\n>>>>>>>>>> Http Rest Request <<<<<<<<<<<<<\n");
clientRequest
.headers()
.forEach((name, values) -> values.forEach(value -> sb.append(name).append(": ").append(value).append("\n")));
log.debug(sb.toString());
}
return Mono.just(clientRequest);
});
}
ExchangeFilterFunction logResponse() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
StringBuilder sb = new StringBuilder("\n>>>>>>>>>> Http Rest Response <<<<<<<<<<<<<\n");
clientResponse.headers()
.asHttpHeaders()
.forEach((name, values) -> values.forEach(value -> sb.append(name).append(": ").append(value).append("\n")));
log.debug(sb.toString());
return Mono.just(clientResponse);
});
}
ExchangeFilterFunction errorHandler() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
HttpStatus status = clientResponse.statusCode();
if (clientResponse.statusCode().is4xxClientError()) {
return clientResponse.bodyToMono(String.class)
.flatMap(errorBody -> Mono.error(new ClientError(status, errorBody)));
} else if (clientResponse.statusCode().is5xxServerError()) {
return clientResponse.bodyToMono(String.class)
.flatMap(errorBody -> Mono.error(new ServerError(status, errorBody)));
}
return Mono.just(clientResponse);
});
}
public <T> T exchange3(String url, HttpMethod method, Object body, Class<T> rtnClzz) {
CountDownLatch cdl = new CountDownLatch(1);
AtomicReference<T> result = new AtomicReference<>();
webClient.mutate()
.build()
.method(method)
.uri(url)
.bodyValue(body)
.exchangeToMono(res -> res.bodyToMono(rtnClzz))
.onErrorContinue((e, i) -> {
log.error("{}", e);
})
.doOnTerminate(() -> cdl.countDown())
.subscribe(data -> result.set(data));
try {
cdl.await();
} catch (InterruptedException e) {
// thread pool에 에러상태 set
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
return result.get();
}
}

@ -0,0 +1,145 @@
package kr.xit.core.spring.util;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import kr.xit.core.consts.Constants;
import kr.xit.core.exception.BizRuntimeException;
import kr.xit.core.model.ApiResponseDTO;
import kr.xit.core.spring.config.support.WebClientConfig;
import kr.xit.core.spring.util.error.ClientError;
import kr.xit.core.spring.util.error.ErrorParse;
import kr.xit.core.spring.util.error.ServerError;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
/**
* <pre>
* description : react Restfull Util
*
* packageName : kr.xit.core.spring.util
* fileName : ApiWebClientUtil
* author : julim
* date : 2023-09-06
* ======================================================================
*
* ----------------------------------------------------------------------
* 2023-09-06 julim
*
* </pre>
* @see kr.xit.core.spring.config.AsyncExecutorConfig
* @see ClientError
* @see ServerError
*/
@Component
@RequiredArgsConstructor
public class ApiWebClientUtil {
@Value("${contract.kakao.token:}")
private String kkoAccessToken;
@Value("${contract.kakao.uuid:}")
private String kkoContractUuid;
private final WebClientConfig webClientConfig;
public <T> T get(final String url, final Class<T> responseDtoClass, Map<String, String> headerMap) {
return webClientConfig.webClient().method(HttpMethod.GET)
.uri(url)
.headers(httpHeaders -> getHeaders(httpHeaders, headerMap))
.retrieve()
.onStatus(
status -> status.is4xxClientError() || status.is5xxServerError(),
res -> res.bodyToMono(String.class).map(BizRuntimeException::create))
.bodyToMono(responseDtoClass)
.block();
}
public <T, V> T post(final String url, final V requestDto, final Class<T> responseDtoClass, Map<String, String> headerMap) {
return webClientConfig.webClient().method(HttpMethod.POST)
.uri(url)
.headers(httpHeaders -> getHeaders(httpHeaders, headerMap))
.bodyValue(requestDto)
.retrieve()
.onStatus(
status -> status.is4xxClientError() || status.is5xxServerError(),
res -> res.bodyToMono(String.class).map(BizRuntimeException::create))
.bodyToMono(responseDtoClass)
.block();
}
public <T> T exchangeKko(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz) {
Map<String, String> map = new HashMap<>();
map.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
map.put(HttpHeaders.AUTHORIZATION,
String.format("%s %s", Constants.JwtToken.GRANT_TYPE.getCode(), kkoAccessToken));
map.put(Constants.HeaderName.UUID.getCode(), kkoContractUuid);
return exchange(url, method, body, rtnClzz, map);
}
/**
* <pre>
* Async call - Response Entity()
* @param url call url
* @param method POST|GET
* @param body JSON String type
* @param rtnClzz rtnClzz return type class
* (ex: new KkopayDocDTO.DocStatusResponse().getClass())
* @return T rtnClzz return DTO
* </pre>
*/
public <T> T exchange(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, final Map<String,String> headerMap) {
return webClientConfig.webClient()
.method(method)
.uri(url)
.headers(httpHeaders -> getHeaders(httpHeaders, headerMap))
.bodyValue(Objects.requireNonNullElse(body, ""))
.exchangeToMono(res -> res.bodyToMono(rtnClzz))
.block();
}
public <T> ApiResponseDTO<T> sendError(final Throwable e) {
Map<String,Object> map = ErrorParse.extractError(e.getCause());
return ApiResponseDTO.error(String.valueOf(map.get("code")), String.valueOf(map.get("message")), (HttpStatus)map.get("httpStatus"));
}
private URI createUrl(final String endPoint, final String... value) {
return UriComponentsBuilder.fromUriString(endPoint)
.build(value);
}
private HttpHeaders getHeaders(final HttpHeaders headers, final Map<String, String> map) {
for(Map.Entry<String, String> e : map.entrySet()){
headers.add(e.getKey(), e.getValue());
}
return headers;
}
ExchangeFilterFunction errorHandler() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
HttpStatus status = clientResponse.statusCode();
if (clientResponse.statusCode().is4xxClientError()) {
return clientResponse.bodyToMono(String.class)
.flatMap(errorBody -> Mono.error(new ClientError(status, errorBody)));
} else if (clientResponse.statusCode().is5xxServerError()) {
return clientResponse.bodyToMono(String.class)
.flatMap(errorBody -> Mono.error(new ServerError(status, errorBody)));
}
return Mono.just(clientResponse);
});
}
}

@ -76,6 +76,7 @@
</plugin>
<!-- proguard -->
<!--
<plugin>
<groupId>com.github.wvengen</groupId>
<artifactId>proguard-maven-plugin</artifactId>
@ -100,7 +101,6 @@
<option>-dontshrink</option>
<option>-dontnote</option>
<option>-dontwarn</option>
<!-- <option>-printseeds</option>-->
<option>-keepparameternames</option>
<option>-renamesourcefileattribute SourceFile</option>
<option>-keepattributes Signature,Exceptions,*Annotation*,
@ -133,16 +133,9 @@
</options>
<libs>
<lib>${java.home}/jmods/java.base.jmod</lib>
<!-- <lib>${java.home}/jmods/java.datatransfer.jmod</lib>-->
<!-- <lib>${java.home}/jmods/java.prefs.jmod</lib>-->
<!-- <lib>${java.home}/jmods/java.xml.jmod</lib>-->
<!-- <lib>${java.home}/jmods/java.desktop.jmod</lib>-->
<!-- <lib>${java.home}/jmods/java.rmi.jmod</lib>-->
<!-- <lib>${java.home}/lib/rt.jar</lib>-->
<!-- <lib>${java.home}/lib/jce.jar</lib>-->
</libs>
</configuration>
</plugin>
</plugin-->
</plugins>
</build>

Loading…
Cancel
Save