Compare commits
43 Commits
main
...
main-restf
@ -0,0 +1,258 @@
|
||||
package kr.xit.ens.kakao.pay.service;
|
||||
|
||||
import kr.xit.biz.common.*;
|
||||
import kr.xit.biz.ens.model.kakao.pay.*;
|
||||
import kr.xit.core.exception.BizRuntimeException;
|
||||
import kr.xit.core.spring.annotation.TraceLogging;
|
||||
import kr.xit.core.spring.util.*;
|
||||
import kr.xit.core.support.utils.Checks;
|
||||
import kr.xit.core.support.utils.JsonUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : 카카오 페이 전자 문서 API dummy 테스트
|
||||
* packageName : kr.xit.ens.kakao.pay.service
|
||||
* fileName : KkopayApiDummyTestService
|
||||
* author : julim
|
||||
* date : 2023-04-28
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-04-28 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class KkopayApiDummyTestService extends EgovAbstractServiceImpl {
|
||||
|
||||
// FIXME:: API 호출 모듈 선택 - ApiWebClientUtil | ApiRestTemplateUtil | ApiHttpClientUtil
|
||||
private final ApiHttpClientUtil webClient;
|
||||
|
||||
public KkopayDocAttrDTO.DocumentBinderUuid requestSend(final KkopayDocDTO.SendRequest reqDTO) {
|
||||
List<String> errors = new ArrayList<>();
|
||||
|
||||
final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||
// 실제 데이타만 검증
|
||||
Set<ConstraintViolation<KkopayDocDTO.RequestSend>> list = validator.validate(reqDTO.getDocument());
|
||||
if (list.size() > 0) {
|
||||
|
||||
errors = list.stream()
|
||||
.map(row -> String.format("%s=%s", row.getPropertyPath(), row.getMessageTemplate()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
KkopayDocDTO.RequestSend reqSendDTO = reqDTO.getDocument();
|
||||
if(reqSendDTO.getRead_expired_at() != null && reqSendDTO.getRead_expired_sec() != null){
|
||||
errors.add("처리마감시간(절대시간 또는 상대시간)은 하나만 지정해야 합니다.");
|
||||
}
|
||||
if(reqSendDTO.getRead_expired_at() == null && reqSendDTO.getRead_expired_sec() == null){
|
||||
errors.add("처리마감시간(절대시간 또는 상대시간)을 지정해야 합니다.");
|
||||
}
|
||||
|
||||
KkopayDocAttrDTO.Receiver receiver = reqSendDTO.getReceiver();
|
||||
if(Checks.isEmpty(receiver.getCi())){
|
||||
if(Checks.isEmpty(receiver.getName())) errors.add("받는이 이름은 필수입니다.");
|
||||
if(Checks.isEmpty(receiver.getPhone_number())) errors.add("받는이 전화번호는 필수입니다.");
|
||||
if(Checks.isEmpty(receiver.getBirthday())) errors.add("받는이 생년월일은 필수입니다.");
|
||||
}
|
||||
if(errors.size() > 0){
|
||||
throw BizRuntimeException.create(errors.toString());
|
||||
}
|
||||
|
||||
return KkopayDocAttrDTO.DocumentBinderUuid
|
||||
.builder()
|
||||
.document_binder_uuid("BIN-eue7e73uw737377eeeee")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 토큰 유효성 검증(Redirect URL 접속 허용/불허)
|
||||
* </pre>
|
||||
* @param reqDTO KkoPayEltrDocDTO.RequestSendReq
|
||||
* @return ResponseEntity<KkopayDocDTO.ValidTokenRes></KkopayDocDTO.ValidTokenRes>
|
||||
*/
|
||||
public KkopayDocDTO.ValidTokenResponse validToken(final KkopayDocDTO.ValidTokenRequest reqDTO) {
|
||||
final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||
// 실제 데이타만 검증
|
||||
Set<ConstraintViolation<KkopayDocDTO.ValidTokenRequest>> list = validator.validate(reqDTO);
|
||||
if (list.size() > 0) {
|
||||
|
||||
List<String> errors = list.stream()
|
||||
.map(row -> String.format("%s=%s", row.getPropertyPath(), row.getMessageTemplate()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
throw BizRuntimeException.create(errors.toString());
|
||||
}
|
||||
|
||||
return KkopayDocDTO.ValidTokenResponse
|
||||
.builder()
|
||||
.token_status("USED")
|
||||
.token_expires_at(1624344762L)
|
||||
.payload("payload 파라미터 입니다.")
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 모바일웹 연계 문서발송 요청
|
||||
* -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
|
||||
* </pre>
|
||||
* @param reqDTO KkoPayEltrDocDTO.RequestSendReq
|
||||
* @return
|
||||
*/
|
||||
public KkopayDocBulkDTO.BulkSendResponses requestSendBulk(final KkopayDocBulkDTO.BulkSendRequests reqDTO) {
|
||||
List<String> errors = new ArrayList<>();
|
||||
final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||
|
||||
//TODO :: Collection validation
|
||||
List<KkopayDocBulkDTO.BulkSendReq> dtos = reqDTO.getDocuments();
|
||||
for(int idx = 0; idx < dtos.size(); idx++) {
|
||||
Set<ConstraintViolation<KkopayDocBulkDTO.BulkSendReq>> list = validator.validate(dtos.get(idx));
|
||||
if (list.size() > 0) {
|
||||
|
||||
int finalIdx = idx;
|
||||
errors.addAll(list.stream()
|
||||
.map(row -> String.format("%s[%d]=%s", row.getPropertyPath(), finalIdx +1, row.getMessageTemplate()))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for(int idx = 0; idx < dtos.size(); idx++) {
|
||||
|
||||
if(dtos.get(idx).getRead_expired_at() != null && dtos.get(idx).getRead_expired_sec() != null){
|
||||
errors.add("처리마감시간(절대시간 또는 상대시간)은 하나만 지정해야 합니다.");
|
||||
}
|
||||
if(dtos.get(idx).getRead_expired_at() == null && dtos.get(idx).getRead_expired_sec() == null){
|
||||
errors.add("처리마감시간(절대시간 또는 상대시간)을 지정해야 합니다.");
|
||||
}
|
||||
|
||||
KkopayDocAttrDTO.Receiver receiver = dtos.get(idx).getReceiver();
|
||||
if (Checks.isEmpty(receiver.getCi())) {
|
||||
if (Checks.isEmpty(receiver.getName())) errors.add(String.format("받는이 이름은 필수입니다([%d] 번째 오류)", idx+1));
|
||||
if (Checks.isEmpty(receiver.getPhone_number())) errors.add(String.format("받는이 전화번호는 필수입니다([%d] 번째 오류)", idx+1));
|
||||
if (Checks.isEmpty(receiver.getBirthday())) errors.add(String.format("받는이 생년월일은 필수입니다([%d] 번째 오류)", idx+1));
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder()
|
||||
.append(StringUtils.defaultString(receiver.getName(), StringUtils.EMPTY))
|
||||
.append(StringUtils.defaultString(receiver.getPhone_number(), StringUtils.EMPTY))
|
||||
.append(StringUtils.defaultString(receiver.getBirthday(), StringUtils.EMPTY));
|
||||
|
||||
if(Checks.isNotEmpty(sb.toString())){
|
||||
errors.add(String.format("CI가 지정 되었습니다(받는이 정보 불필요:[%d] 번째 오류) .", idx+1));
|
||||
}
|
||||
}
|
||||
}
|
||||
if(errors.size() > 0){
|
||||
throw BizRuntimeException.create(errors.toString());
|
||||
}
|
||||
|
||||
List<KkopayDocBulkDTO.BulkSendRes> resDTO = new ArrayList<>();
|
||||
resDTO.add(KkopayDocBulkDTO.BulkSendRes.builder()
|
||||
.external_document_uuid(reqDTO.getDocuments().get(0).getProperty().getExternal_document_uuid())
|
||||
.document_binder_uuid("BIN-127de7hdcyeuudfeuuewe8e8e")
|
||||
.build()
|
||||
);
|
||||
resDTO.add(KkopayDocBulkDTO.BulkSendRes.builder()
|
||||
.external_document_uuid(reqDTO.getDocuments().get(0).getProperty().getExternal_document_uuid())
|
||||
.error_code(ApiConstants.Error.NOT_FOUND.getCode())
|
||||
.error_message("요청 정보를 찾을 수 없습니다. documentBinder를 찾을수 없습니다.")
|
||||
.build()
|
||||
);
|
||||
|
||||
KkopayDocBulkDTO.BulkSendResponses res = KkopayDocBulkDTO.BulkSendResponses.builder()
|
||||
.documents(resDTO)
|
||||
.build();
|
||||
|
||||
KkopayDocBulkDTO.BulkSendResponses object = JsonUtils.toObject(JsonUtils.toJson(res),
|
||||
KkopayDocBulkDTO.BulkSendResponses.class);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 대량(bulk) 문서 상태 조회 API
|
||||
* -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
|
||||
* : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
|
||||
* : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
|
||||
* -.doc_box_status 상태변경순서
|
||||
* : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
|
||||
* </pre>
|
||||
* @param reqDTO
|
||||
* @return KkopayDocBulkDTO.BulkStatusResponse
|
||||
*/
|
||||
public KkopayDocBulkDTO.BulkStatusResponses findBulkStatus(final KkopayDocBulkDTO.BulkStatusRequests reqDTO) {
|
||||
List<String> errors = new ArrayList<>();
|
||||
|
||||
List<String> dtos = reqDTO.getDocument_binder_uuids();
|
||||
for(int idx = 0; idx < dtos.size(); idx++) {
|
||||
String binderUuid = dtos.get(idx);
|
||||
if (Checks.isEmpty(binderUuid) || binderUuid.length() > 40) {
|
||||
errors.add(String.format("문서 식별 번호는 40자를 넘을 수 없습니다[%d번째]", idx+1));
|
||||
}
|
||||
}
|
||||
if(errors.size() > 0) {
|
||||
throw BizRuntimeException.create(errors.toString());
|
||||
}
|
||||
|
||||
List<KkopayDocBulkDTO.BulkStatus> resDTO = new ArrayList<>();
|
||||
|
||||
resDTO.add(KkopayDocBulkDTO.BulkStatus.builder()
|
||||
.document_binder_uuid(reqDTO.getDocument_binder_uuids().get(0))
|
||||
.error_code(ApiConstants.Error.NOT_FOUND.getCode())
|
||||
.error_message("요청 정보를 찾을 수 없습니다. documentBinder를 찾을수 없습니다.")
|
||||
.build()
|
||||
);
|
||||
|
||||
resDTO.add(KkopayDocBulkDTO.BulkStatus.builder()
|
||||
.document_binder_uuid(reqDTO.getDocument_binder_uuids().get(1))
|
||||
.status_data(KkopayDocAttrDTO.DocStatus.builder()
|
||||
.doc_box_status(ApiConstants.KkopayDocStatus.RECEIVED)
|
||||
.doc_box_sent_at(1443456743L)
|
||||
.doc_box_received_at(1443456743L)
|
||||
.user_notified_at(1443456743L)
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
resDTO.add(KkopayDocBulkDTO.BulkStatus.builder()
|
||||
.document_binder_uuid(reqDTO.getDocument_binder_uuids().get(2))
|
||||
.status_data(KkopayDocAttrDTO.DocStatus.builder()
|
||||
.doc_box_status(ApiConstants.KkopayDocStatus.READ)
|
||||
.doc_box_sent_at(1443456743L)
|
||||
.doc_box_received_at(1443456743L)
|
||||
.doc_box_read_at(1443456743L)
|
||||
.authenticated_at(1443456743L)
|
||||
.token_used_at(1443456743L)
|
||||
.user_notified_at(1443456743L)
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
KkopayDocBulkDTO.BulkStatusResponses res = KkopayDocBulkDTO.BulkStatusResponses.builder()
|
||||
.documents(resDTO)
|
||||
.build();
|
||||
|
||||
KkopayDocBulkDTO.BulkStatusResponses object = JsonUtils.toObject(JsonUtils.toJson(res),
|
||||
KkopayDocBulkDTO.BulkStatusResponses.class);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,151 @@
|
||||
package kr.xit.ens.kakao.pay.web;
|
||||
|
||||
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 kr.xit.biz.common.*;
|
||||
import kr.xit.biz.ens.model.kakao.pay.*;
|
||||
import kr.xit.core.model.ApiResponseDTO;
|
||||
import kr.xit.core.exception.BizRuntimeException;
|
||||
import kr.xit.core.support.utils.Checks;
|
||||
import kr.xit.ens.kakao.pay.service.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : 카카오페이 전자문서 API dummy 테스트 controller
|
||||
* packageName : kr.xit.ens.kakao.pay.controller
|
||||
* fileName : KkopayApiDummyTestController
|
||||
* author : julim
|
||||
* date : 2023-04-28
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-04-28 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Tag(name = "KkopayApiDummyTestController", description = "카카오페이 전자문서 API dummy 테스트")
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping(value = "/api/kakao/pay/test")
|
||||
public class KkopayApiDummyTestController {
|
||||
|
||||
private final KkopayApiDummyTestService service;
|
||||
|
||||
@Operation(summary = "문서발송 요청", description = "카카오페이 전자문서 서버로 문서발송 처리를 요청")
|
||||
@PostMapping(value = "/documents", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ApiResponseDTO<?> requestSend(
|
||||
@RequestBody final KkopayDocDTO.SendRequest reqDTO
|
||||
) {
|
||||
return ApiResponseDTO.success(service.requestSend(reqDTO));
|
||||
}
|
||||
|
||||
@Operation(summary = "토큰 유효성 검증", description = "Redirect URL 접속 허용/불허")
|
||||
@PostMapping(value = "/validToken", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ApiResponseDTO<?> validToken(
|
||||
@RequestBody final KkopayDocDTO.ValidTokenRequest reqDTO
|
||||
) {
|
||||
return ApiResponseDTO.success(service.validToken(reqDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서 상태 변경 API
|
||||
* -.문서에 대해서 열람 상태로 변경. 사용자가 문서열람 시(OTT 검증 완료 후 페이지 로딩 완료 시점) 반드시 문서 열람 상태 변경 API를 호출해야 함.
|
||||
* -.미 호출 시 아래와 같은 문제 발생
|
||||
* 1)유통증명시스템을 사용하는 경우 해당 API를 호출한 시점으로 열람정보가 등록되어 미 호출 시 열람정보가 등록 되지 않음.
|
||||
* 2)문서상태조회 API(/v1/documents/{document_binder_uuid}/status) 호출 시 read_at최초 열람시간) 데이터가 내려가지 않음.
|
||||
* </pre>
|
||||
* @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid
|
||||
* @return ApiResponseDTO
|
||||
*/
|
||||
@Operation(summary = "문서 상태 변경", description = "문서 상태 변경")
|
||||
@PostMapping(value = "/modifyStatus", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ApiResponseDTO<?> modifyStatus(
|
||||
@RequestBody final KkopayDocAttrDTO.DocumentBinderUuid reqDTO
|
||||
) {
|
||||
return ApiResponseDTO.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서 상태 조회 API
|
||||
* -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
|
||||
* : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
|
||||
* : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
|
||||
* -.doc_box_status 상태변경순서
|
||||
* : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
|
||||
* </pre>
|
||||
* @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid
|
||||
* @return ApiResponseDTO
|
||||
*/
|
||||
@Operation(summary = "문서 상태 조회", description = "문서 상태 조회")
|
||||
@PostMapping(value = "/findStatus", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ApiResponseDTO<?> findStatus(
|
||||
@RequestBody final KkopayDocAttrDTO.DocumentBinderUuid reqDTO
|
||||
) {
|
||||
|
||||
SecureRandom random = new SecureRandom(); // Compliant for security-sensitive use cases
|
||||
int bound = 1000000000;
|
||||
|
||||
return ApiResponseDTO.success(KkopayDocDTO.DocStatusResponse
|
||||
.builder()
|
||||
.payload(reqDTO.getDocument_binder_uuid())
|
||||
.doc_box_status(ApiConstants.KkopayDocStatus.SENT)
|
||||
.doc_box_sent_at((long)(random.nextInt(bound)) + 1000000000)
|
||||
.doc_box_received_at((long)(random.nextInt(bound)) + 1000000000)
|
||||
//.payload("payload 파라미터 입니다.")
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 모바일웹 연계 문서발송 요청
|
||||
* -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
|
||||
* </pre>
|
||||
* @param reqDTO KkopayDocBulkDTO.BulkSendRequests
|
||||
* @return ApiResponseDTO
|
||||
*/
|
||||
@Operation(summary = "대량 문서 발송 요청", description = "카카오페이 전자문서 서버로 대량 문서 발송 요청")
|
||||
@PostMapping(value = "/documents/bulk", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ApiResponseDTO<?> requestSendBulk(
|
||||
@RequestBody final KkopayDocBulkDTO.BulkSendRequests reqDTO
|
||||
) {
|
||||
return ApiResponseDTO.success(service.requestSendBulk(reqDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 모바일웹 연계 문서발송 요청
|
||||
* -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
|
||||
* </pre>
|
||||
* @param reqDTO KkopayDocBulkDTO.BulkStatusRequests
|
||||
* @return ApiResponseDTO
|
||||
*/
|
||||
@Operation(summary = "대량 문서 상태 조회 요청", description = "카카오페이 전자문서 서버로 대량 문서 상태 조회 요청")
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = {
|
||||
@Content(mediaType = "application/json", examples = {
|
||||
@ExampleObject(name = "Example"
|
||||
, summary = "부과정보 전송", description = "개별시스템 -> 세외수입 시스템 호출하여 응답 Response"
|
||||
, value = "{\"document_binder_uuids\":[\"BIN-ff806328863311ebb61432ac599d6151\",\"BIN-ff806328863311ebb61432ac599d6152\",\"BIN-ff806328863311ebb61432ac599d6153\"]}")
|
||||
})
|
||||
})
|
||||
@PostMapping(value = "/documents/bulk/status", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ApiResponseDTO<?> findBulkStatus(
|
||||
@RequestBody final KkopayDocBulkDTO.BulkStatusRequests reqDTO
|
||||
) {
|
||||
return ApiResponseDTO.success(service.findBulkStatus(reqDTO));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,335 @@
|
||||
package kr.xit.ens.kakao.talk.service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.*;
|
||||
|
||||
import javax.validation.*;
|
||||
|
||||
import org.apache.commons.lang3.*;
|
||||
import org.egovframe.rte.fdl.cmmn.*;
|
||||
import org.springframework.stereotype.*;
|
||||
|
||||
import kr.xit.biz.common.*;
|
||||
import kr.xit.biz.ens.model.kakao.pay.*;
|
||||
import kr.xit.biz.ens.model.kakao.talk.*;
|
||||
import kr.xit.core.exception.*;
|
||||
import kr.xit.core.model.*;
|
||||
import kr.xit.core.spring.annotation.*;
|
||||
import kr.xit.core.spring.util.*;
|
||||
import kr.xit.core.support.utils.*;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.*;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : 카카오톡 전자 문서 API dummy 테스트
|
||||
* packageName : kr.xit.ens.kakao.talk.service
|
||||
* fileName : KkotalkApiDummyTestService
|
||||
* author : julim
|
||||
* date : 2024-12-17
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2024-12-17 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class KkotalkApiDummyTestService extends EgovAbstractServiceImpl {
|
||||
|
||||
// FIXME:: API 호출 모듈 선택 - ApiWebClientUtil | ApiRestTemplateUtil | ApiHttpClientUtil
|
||||
private final ApiHttpClientUtil webClient;
|
||||
|
||||
private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||
private static final CharSequence ENVELOPE_ID = "{ENVELOPE_ID}";
|
||||
|
||||
|
||||
public KkotalkApiDTO.SendResponse requestSend(final KkotalkDTO.SendRequest reqDTO) {
|
||||
if(Checks.isEmpty(reqDTO.getProductCode())){
|
||||
throw BizRuntimeException.create("상품 코드는 필수 입니다.");
|
||||
}
|
||||
List<String> errors = new ArrayList<>();
|
||||
errors = validate(reqDTO.getEnvelope(), errors);
|
||||
|
||||
final KkotalkApiDTO.Envelope envelope = reqDTO.getEnvelope();
|
||||
if(envelope.getReviewExpiresAt() != null){
|
||||
if(envelope.getReviewExpiresAt().compareTo(envelope.getReadExpiresAt()) < 0){
|
||||
errors.add("reviewExpiresAt=재열람 만료일시를 최조 열람 만료일시 보다 큰 날짜로 입력해주세요.");
|
||||
}
|
||||
}
|
||||
|
||||
if(Checks.isEmpty(envelope.getCi())){
|
||||
if(Checks.isEmpty(envelope.getName())) Objects.requireNonNull(errors).add("name=받는이 이름은 필수입니다.");
|
||||
if(Checks.isEmpty(envelope.getPhoneNumber())) Objects.requireNonNull(errors).add("phoneNumber=받는이 전화번호는 필수입니다.");
|
||||
if(Checks.isEmpty(envelope.getBirthday())) Objects.requireNonNull(errors).add("birthday=받는이 생년월일은 필수입니다.");
|
||||
}
|
||||
if(!Objects.requireNonNull(errors).isEmpty()) throw BizRuntimeException.create(errors.toString());
|
||||
|
||||
|
||||
return KkotalkApiDTO.SendResponse
|
||||
.builder()
|
||||
.envelopeId("EVLP-01JAF27KSMDYSZ6JP1R66SV2ZC-06")
|
||||
.externalId("41220202410170021")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 토큰 유효성 검증(Redirect URL 접속 허용/불허)
|
||||
* </pre>
|
||||
* @param reqDTO KkoPayEltrDocDTO.RequestSendReq
|
||||
* @return KkotalkApiDTO.ValidTokenResponse
|
||||
*/
|
||||
public KkotalkApiDTO.ValidTokenResponse validToken(final KkotalkApiDTO.ValidTokenRequest reqDTO) {
|
||||
validate(reqDTO, null);
|
||||
|
||||
return KkotalkApiDTO.ValidTokenResponse
|
||||
.builder()
|
||||
.status(ApiConstants.KkotalkDocStatus.READ) //"USED"
|
||||
//.token_expires_at(1624344762L)
|
||||
.payload("payload 파라미터 입니다.")
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 모바일웹 연계 문서발송 요청
|
||||
* -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
|
||||
* </pre>
|
||||
* @param reqDTO KkotalkDTO.BulkSendRequest
|
||||
* @return KkotalkDTO.BulkSendResponse
|
||||
*/
|
||||
public KkotalkDTO.BulkSendResponse requestSendBulk(final KkotalkDTO.BulkSendRequest reqDTO) {
|
||||
if(Checks.isEmpty(reqDTO.getProductCode())){
|
||||
throw BizRuntimeException.create("상품 코드는 필수 입니다.");
|
||||
}
|
||||
|
||||
List<String> errors = new ArrayList<>();
|
||||
|
||||
List<KkotalkApiDTO.Envelope> envelopes = reqDTO.getEnvelopes();
|
||||
for(int idx = 0; idx < envelopes.size(); idx++) {
|
||||
final Set<ConstraintViolation<KkotalkApiDTO.Envelope>> list = validator.validate(envelopes.get(idx));
|
||||
if (!list.isEmpty()) {
|
||||
|
||||
int finalIdx = idx;
|
||||
errors.addAll(list.stream()
|
||||
.map(row -> String.format("%s[%d]=%s", row.getPropertyPath(), finalIdx +1, row.getMessageTemplate()))
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for(int idx = 0; idx < envelopes.size(); idx++) {
|
||||
final KkotalkApiDTO.Envelope envelope = envelopes.get(idx);
|
||||
|
||||
if(envelope.getReviewExpiresAt() != null){
|
||||
if(envelope.getReviewExpiresAt().compareTo(envelope.getReadExpiresAt()) < 0){
|
||||
errors.add("reviewExpiresAt=재열람 만료일시를 최조 열람 만료일시 보다 큰 날짜로 입력해주세요.");
|
||||
}
|
||||
}
|
||||
|
||||
if (Checks.isEmpty(envelope.getCi())) {
|
||||
if (Checks.isEmpty(envelope.getName())) errors.add(String.format("받는이 이름은 필수입니다(name[%d] 번째 오류)", idx+1));
|
||||
if (Checks.isEmpty(envelope.getPhoneNumber())) errors.add(String.format("받는이 전화번호는 필수입니다(phoneNumber[%d] 번째 오류)", idx+1));
|
||||
if (Checks.isEmpty(envelope.getBirthday())) errors.add(String.format("받는이 생년월일은 필수입니다(birthday[%d] 번째 오류)", idx+1));
|
||||
} else {
|
||||
final StringBuilder sb = new StringBuilder()
|
||||
.append(StringUtils.defaultString(envelope.getName(), StringUtils.EMPTY))
|
||||
.append(StringUtils.defaultString(envelope.getPhoneNumber(), StringUtils.EMPTY))
|
||||
.append(StringUtils.defaultString(envelope.getBirthday(), StringUtils.EMPTY));
|
||||
|
||||
if(Checks.isNotEmpty(sb.toString())){
|
||||
errors.add(String.format("CI가 지정 되었습니다(받는이 정보 불필요:[%d] 번째 오류) .", idx+1));
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!errors.isEmpty()){
|
||||
throw BizRuntimeException.create(errors.toString());
|
||||
}
|
||||
String param = "{\"envelopes\":" + JsonUtils.toJson(envelopes) + "}";
|
||||
|
||||
|
||||
List<KkotalkApiDTO.EnvelopeRes> resDTO = new ArrayList<>();
|
||||
resDTO.add(KkotalkApiDTO.EnvelopeRes.builder()
|
||||
.externalId(envelopes.get(0).getExternalId())
|
||||
.errorCode(ApiConstants.Error.NOT_FOUND.getCode())
|
||||
.errorMessage("요청 정보를 찾을 수 없습니다. documentBinder를 찾을수 없습니다.")
|
||||
.build()
|
||||
);
|
||||
|
||||
resDTO.add(KkotalkApiDTO.EnvelopeRes.builder()
|
||||
.externalId(envelopes.get(1).getExternalId())
|
||||
.envelopeId("EVLP-01JAF27KSMDYSZ6JP1R66SV2ZC-06")
|
||||
.edocGtid(null)
|
||||
.build()
|
||||
);
|
||||
|
||||
resDTO.add(KkotalkApiDTO.EnvelopeRes.builder()
|
||||
.externalId(envelopes.get(2).getExternalId())
|
||||
.envelopeId("EVLP-01J5YX9QNMS3Y8D3QZTB51NMMN-00")
|
||||
.edocGtid(null)
|
||||
.build()
|
||||
);
|
||||
|
||||
KkotalkDTO.BulkSendResponse res = KkotalkDTO.BulkSendResponse.builder()
|
||||
.envelopes(resDTO)
|
||||
.build();
|
||||
|
||||
KkopayDocBulkDTO.BulkSendResponses object = JsonUtils.toObject(JsonUtils.toJson(res),
|
||||
KkopayDocBulkDTO.BulkSendResponses.class);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
public ApiResponseDTO<?> requestSendBulkError(final KkotalkDTO.BulkSendRequest reqDTO) {
|
||||
if(Checks.isEmpty(reqDTO.getProductCode())){
|
||||
throw BizRuntimeException.create("상품 코드는 필수 입니다.");
|
||||
}
|
||||
|
||||
List<String> errors = new ArrayList<>();
|
||||
|
||||
List<KkotalkApiDTO.Envelope> envelopes = reqDTO.getEnvelopes();
|
||||
for(int idx = 0; idx < envelopes.size(); idx++) {
|
||||
final Set<ConstraintViolation<KkotalkApiDTO.Envelope>> list = validator.validate(envelopes.get(idx));
|
||||
if (!list.isEmpty()) {
|
||||
|
||||
int finalIdx = idx;
|
||||
errors.addAll(list.stream()
|
||||
.map(row -> String.format("%s[%d]=%s", row.getPropertyPath(), finalIdx +1, row.getMessageTemplate()))
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for(int idx = 0; idx < envelopes.size(); idx++) {
|
||||
final KkotalkApiDTO.Envelope envelope = envelopes.get(idx);
|
||||
|
||||
if(envelope.getReviewExpiresAt() != null){
|
||||
if(envelope.getReviewExpiresAt().compareTo(envelope.getReadExpiresAt()) < 0){
|
||||
errors.add("reviewExpiresAt=재열람 만료일시를 최조 열람 만료일시 보다 큰 날짜로 입력해주세요.");
|
||||
}
|
||||
}
|
||||
|
||||
if (Checks.isEmpty(envelope.getCi())) {
|
||||
if (Checks.isEmpty(envelope.getName())) errors.add(String.format("받는이 이름은 필수입니다(name[%d] 번째 오류)", idx+1));
|
||||
if (Checks.isEmpty(envelope.getPhoneNumber())) errors.add(String.format("받는이 전화번호는 필수입니다(phoneNumber[%d] 번째 오류)", idx+1));
|
||||
if (Checks.isEmpty(envelope.getBirthday())) errors.add(String.format("받는이 생년월일은 필수입니다(birthday[%d] 번째 오류)", idx+1));
|
||||
} else {
|
||||
final StringBuilder sb = new StringBuilder()
|
||||
.append(StringUtils.defaultString(envelope.getName(), StringUtils.EMPTY))
|
||||
.append(StringUtils.defaultString(envelope.getPhoneNumber(), StringUtils.EMPTY))
|
||||
.append(StringUtils.defaultString(envelope.getBirthday(), StringUtils.EMPTY));
|
||||
|
||||
if(Checks.isNotEmpty(sb.toString())){
|
||||
errors.add(String.format("CI가 지정 되었습니다(받는이 정보 불필요:[%d] 번째 오류) .", idx+1));
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!errors.isEmpty()){
|
||||
throw BizRuntimeException.create(errors.toString());
|
||||
}
|
||||
String param = "{\"envelopes\":" + JsonUtils.toJson(envelopes) + "}";
|
||||
|
||||
|
||||
String errorRtn = """
|
||||
{"timestamp":"2024-12-16T01:19:57.040+00:00","path":"/pxy/kkoNew/v1/bulk/envelopes/D10_2","status":500,"error":"Internal Server Error","requestId":"541faefe-45126"}
|
||||
""";
|
||||
|
||||
return ApiResponseDTO.success(errorRtn);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 대량(bulk) 문서 상태 조회 API
|
||||
* -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
|
||||
* : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
|
||||
* : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
|
||||
* -.doc_box_status 상태변경순서
|
||||
* : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
|
||||
* </pre>
|
||||
* @param reqDTO KkotalkDTO.BulkStatusRequest
|
||||
* @return KkotalkDTO.BulkStatusResponse
|
||||
*/
|
||||
public KkotalkDTO.BulkStatusResponse findBulkStatus(final KkotalkDTO.BulkStatusRequest reqDTO) {
|
||||
List<String> errors = new ArrayList<>();
|
||||
|
||||
List<String> envelopes = reqDTO.getEnvelopes();
|
||||
for(int idx = 0; idx < envelopes.size(); idx++) {
|
||||
final String binderUuid = envelopes.get(idx);
|
||||
if (Checks.isEmpty(binderUuid) || binderUuid.length() > 40) {
|
||||
errors.add(String.format("문서 식별 번호는 40자를 넘을 수 없습니다[%d번째]", idx+1));
|
||||
}
|
||||
}
|
||||
if(!errors.isEmpty()) {
|
||||
throw BizRuntimeException.create(errors.toString());
|
||||
}
|
||||
String param = "{\"envelopeIds\":" + JsonUtils.toJson(envelopes) + "}";
|
||||
|
||||
|
||||
List<KkotalkApiDTO.EnvelopeStatusResponse> resDTO = new ArrayList<>();
|
||||
|
||||
resDTO.add(KkotalkApiDTO.EnvelopeStatusResponse.builder()
|
||||
.envelopeId(reqDTO.getEnvelopes().get(0))
|
||||
.errorCode(ApiConstants.Error.NOT_FOUND.getCode())
|
||||
.errorMessage("요청 정보를 찾을 수 없습니다. documentBinder를 찾을수 없습니다.")
|
||||
.build()
|
||||
);
|
||||
|
||||
resDTO.add(KkotalkApiDTO.EnvelopeStatusResponse.builder()
|
||||
.envelopeId(reqDTO.getEnvelopes().get(1))
|
||||
.externalId("ddhhdhdhdhdhdhd")
|
||||
.status(ApiConstants.KkotalkDocStatus.RECEIVED)
|
||||
.sentAt("2024-08-16T15:24:39")
|
||||
.receivedAt("2024-08-16T15:24:39")
|
||||
.authenticatedAt("2024-08-30T11:14:33")
|
||||
.isNotificationUnavailable(false)
|
||||
.userNotifiedAt("2024-08-16T15:24:47")
|
||||
.receivedAt("2024-09-30T23:59:59")
|
||||
.build()
|
||||
);
|
||||
|
||||
resDTO.add(KkotalkApiDTO.EnvelopeStatusResponse.builder()
|
||||
.envelopeId(reqDTO.getEnvelopes().get(2))
|
||||
.externalId("ddhhdhdhdhdhdhd")
|
||||
.status(ApiConstants.KkotalkDocStatus.RECEIVED)
|
||||
.sentAt("2024-08-16T15:24:39")
|
||||
.receivedAt("2024-08-16T15:24:39")
|
||||
.authenticatedAt("2024-08-30T11:14:33")
|
||||
.isNotificationUnavailable(false)
|
||||
.userNotifiedAt("2024-08-16T15:24:47")
|
||||
.receivedAt("2024-09-30T23:59:59")
|
||||
.build()
|
||||
);
|
||||
|
||||
KkotalkDTO.BulkStatusResponse res = KkotalkDTO.BulkStatusResponse.builder()
|
||||
.envelopeStatus(resDTO)
|
||||
.build();
|
||||
|
||||
KkopayDocBulkDTO.BulkStatusResponses object = JsonUtils.toObject(JsonUtils.toJson(res),
|
||||
KkopayDocBulkDTO.BulkStatusResponses.class);
|
||||
return res;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
private static <T> List<String> validate(T t, List<String> errList) {
|
||||
final Set<ConstraintViolation<T>> list = validator.validate(t);
|
||||
|
||||
if(!list.isEmpty()) {
|
||||
final List<String> errors = list.stream()
|
||||
.map(row -> String.format("%s=%s", row.getPropertyPath(), row.getMessageTemplate()))
|
||||
.toList();
|
||||
|
||||
// 추가적인 유효성 검증이 필요 없는 경우
|
||||
if(errList == null){
|
||||
if(!errors.isEmpty()) throw BizRuntimeException.create(errors.toString());
|
||||
return null;
|
||||
}
|
||||
errList.addAll(errors);
|
||||
}
|
||||
return errList;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,133 @@
|
||||
package kr.xit.ens.kakao.talk.web;
|
||||
|
||||
import java.security.*;
|
||||
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import io.swagger.v3.oas.annotations.*;
|
||||
import io.swagger.v3.oas.annotations.media.*;
|
||||
import io.swagger.v3.oas.annotations.tags.*;
|
||||
import kr.xit.biz.ens.model.kakao.talk.*;
|
||||
import kr.xit.core.model.*;
|
||||
import kr.xit.ens.kakao.talk.service.*;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.*;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : 카카오톡 전자문서 API dummy 테스트 controller
|
||||
* packageName : kr.xit.ens.kakao.talk.controller
|
||||
* fileName : KkopayApiDummyTestController
|
||||
* author : julim
|
||||
* date : 2024-12-17
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2024-12-17 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Tag(name = "KkotalkApiDummyTestController", description = "카카오톡 전자문서 API dummy 테스트")
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping(value = "/api/kakao/talk/test")
|
||||
public class KkotalkApiDummyTestController {
|
||||
|
||||
private final KkotalkApiDummyTestService service;
|
||||
|
||||
@Operation(summary = "문서발송 요청", description = "카카오톡 전자문서 서버로 문서발송 처리를 요청")
|
||||
@PostMapping(value = "/documents", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ApiResponseDTO<?> requestSend(
|
||||
@RequestBody final KkotalkDTO.SendRequest reqDTO
|
||||
) {
|
||||
return ApiResponseDTO.success(service.requestSend(reqDTO));
|
||||
}
|
||||
|
||||
@Operation(summary = "토큰 유효성 검증", description = "Redirect URL 접속 허용/불허")
|
||||
@PostMapping(value = "/validToken", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ApiResponseDTO<?> validToken(
|
||||
@RequestBody final KkotalkApiDTO.ValidTokenRequest reqDTO
|
||||
) {
|
||||
return ApiResponseDTO.success(service.validToken(reqDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서 상태 조회 API
|
||||
* -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
|
||||
* : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
|
||||
* : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
|
||||
* -.doc_box_status 상태변경순서
|
||||
* : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
|
||||
* </pre>
|
||||
* @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid
|
||||
* @return ApiResponseDTO
|
||||
*/
|
||||
@Operation(summary = "문서 상태 조회", description = "문서 상태 조회")
|
||||
@PostMapping(value = "/findStatus", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ApiResponseDTO<?> findStatus(
|
||||
@RequestBody final KkotalkApiDTO.EnvelopeId reqDTO
|
||||
) {
|
||||
|
||||
SecureRandom random = new SecureRandom(); // Compliant for security-sensitive use cases
|
||||
int bound = 1000000000;
|
||||
|
||||
return ApiResponseDTO.success(KkotalkApiDTO.EnvelopeStatusResponse
|
||||
.builder()
|
||||
.payload(reqDTO.getEnvelopeId())
|
||||
//.doc_box_status(ApiConstants.KkopayDocStatus.SENT)
|
||||
//.doc_box_sent_at((long)(random.nextInt(bound)) + 1000000000)
|
||||
//.doc_box_received_at((long)(random.nextInt(bound)) + 1000000000)
|
||||
//.payload("payload 파라미터 입니다.")
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 모바일웹 연계 문서발송 요청
|
||||
* -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
|
||||
* </pre>
|
||||
* @param reqDTO KkotalkDTO.BulkSendRequest
|
||||
* @return ApiResponseDTO
|
||||
*/
|
||||
@Operation(summary = "대량 문서 발송 요청", description = "카카오톡 전자문서 서버로 대량 문서 발송 요청")
|
||||
@PostMapping(value = "/documents/bulk", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ApiResponseDTO<?> requestSendBulk(
|
||||
@RequestBody final KkotalkDTO.BulkSendRequest reqDTO
|
||||
) {
|
||||
return ApiResponseDTO.success(service.requestSendBulk(reqDTO));
|
||||
}
|
||||
|
||||
@Operation(summary = "대량 문서 발송 요청", description = "카카오톡 전자문서 서버로 대량 문서 발송 요청 에러")
|
||||
@PostMapping(value = "/documents/bulk/error", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ApiResponseDTO<?> requestSendBulkError(
|
||||
@RequestBody final KkotalkDTO.BulkSendRequest reqDTO
|
||||
) {
|
||||
return service.requestSendBulkError(reqDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 모바일웹 연계 문서발송 요청
|
||||
* -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
|
||||
* </pre>
|
||||
* @param reqDTO KkotalkDTO.BulkStatusRequest
|
||||
* @return ApiResponseDTO
|
||||
*/
|
||||
@Operation(summary = "대량 문서 상태 조회 요청", description = "카카오톡 전자문서 서버로 대량 문서 상태 조회 요청")
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = {
|
||||
@Content(mediaType = "application/json", examples = {
|
||||
@ExampleObject(name = "Example"
|
||||
, summary = "부과정보 전송", description = "개별시스템 -> 세외수입 시스템 호출하여 응답 Response"
|
||||
, value = "{\"envelopes\":[\"BIN-ff806328863311ebb61432ac599d6151\",\"BIN-ff806328863311ebb61432ac599d6152\",\"BIN-ff806328863311ebb61432ac599d6153\"]}")
|
||||
})
|
||||
})
|
||||
@PostMapping(value = "/documents/bulk/status", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ApiResponseDTO<?> findBulkStatus(
|
||||
@RequestBody final KkotalkDTO.BulkStatusRequest reqDTO
|
||||
) {
|
||||
return ApiResponseDTO.success(service.findBulkStatus(reqDTO));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,166 @@
|
||||
package kr.xit.biz.nice.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.*;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import kr.xit.biz.cmm.service.*;
|
||||
import kr.xit.biz.common.ApiConstants;
|
||||
import kr.xit.biz.ens.model.nice.*;
|
||||
import kr.xit.biz.ens.model.nice.NiceCiDTO.NiceCiInfo;
|
||||
import kr.xit.biz.ens.model.nice.NiceCiDTO.NiceCiRequest;
|
||||
import kr.xit.biz.ens.model.nice.NiceCiDTO.NiceTokenResponse;
|
||||
import kr.xit.biz.ens.model.nice.NiceCiDTO.ResponseDataHeader;
|
||||
import kr.xit.biz.ens.model.nice.NiceCiDTO.TokenRevokeResponse;
|
||||
import kr.xit.biz.ens.model.nice.NiceCiDTO.TokenResDataBody;
|
||||
import kr.xit.biz.nice.mapper.IBizNiceCiMapper;
|
||||
import kr.xit.core.exception.BizRuntimeException;
|
||||
import kr.xit.ens.nice.service.*;
|
||||
|
||||
import static kr.xit.core.support.utils.JsonUtils.toJson;
|
||||
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("local-ccn")
|
||||
class BizNiceCiServiceTest {
|
||||
|
||||
@Mock
|
||||
private INiceCiService niceCiService;
|
||||
|
||||
@Mock
|
||||
private CmmEnsCacheService cacheService;
|
||||
|
||||
@Mock
|
||||
private IBizNiceCiMapper niceCiMapper;
|
||||
|
||||
@InjectMocks
|
||||
private BizNiceCiService bizNiceCiService;
|
||||
|
||||
public BizNiceCiServiceTest() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
void testGenerateToken_whenTokenGeneratedSuccessfully_DoesNotRevokeTokens() {
|
||||
NiceCiRequest reqDTO = NiceCiRequest.builder()
|
||||
.signguCode("88328")
|
||||
.ffnlgCode("11")
|
||||
.build();
|
||||
|
||||
ResponseDataHeader responseHeader = new ResponseDataHeader();
|
||||
responseHeader.setGwRsltCd("1200");
|
||||
|
||||
TokenResDataBody responseBody = new TokenResDataBody();
|
||||
responseBody.setAccessToken("ThisIsToken");
|
||||
responseBody.setExpiresIn(3600);
|
||||
responseBody.setTokenType("Bearer");
|
||||
responseBody.setScope("read");
|
||||
|
||||
NiceTokenResponse initialResponse = new NiceTokenResponse();
|
||||
initialResponse.setDataHeader(responseHeader);
|
||||
initialResponse.setDataBody(responseBody);
|
||||
|
||||
when(niceCiService.generateToken(reqDTO)).thenReturn(initialResponse);
|
||||
|
||||
NiceTokenResponse result = bizNiceCiService.generateToken(reqDTO);
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("1200", result.getDataHeader().getGwRsltCd());
|
||||
verify(niceCiService, times(1)).generateToken(reqDTO);
|
||||
verify(niceCiMapper, times(1)).updateNiceCrtfToken(any(NiceCiInfo.class));
|
||||
verify(cacheService, times(1)).removeNiceCiInfoCache(reqDTO.getSignguCode(), reqDTO.getFfnlgCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
void testGenerateToken_whenPreviousTokenRevokedAndNewTokenGenerated() {
|
||||
NiceCiRequest reqDTO = NiceCiRequest.builder()
|
||||
.signguCode("88328")
|
||||
.ffnlgCode("11")
|
||||
.build();
|
||||
|
||||
ResponseDataHeader initialHeader = new ResponseDataHeader();
|
||||
initialHeader.setGwRsltCd("1800");
|
||||
|
||||
NiceTokenResponse initialResponse = new NiceTokenResponse();
|
||||
initialResponse.setDataHeader(initialHeader);
|
||||
|
||||
ResponseDataHeader revokeHeader = new ResponseDataHeader();
|
||||
revokeHeader.setGwRsltCd("1200");
|
||||
|
||||
TokenRevokeResponse revokedResponse = new TokenRevokeResponse();
|
||||
revokedResponse.setDataHeader(revokeHeader);
|
||||
revokedResponse.setDataBody(new NiceCiDTO.TokenRevokeResDataBody(true));
|
||||
|
||||
ResponseDataHeader finalHeader = new ResponseDataHeader();
|
||||
finalHeader.setGwRsltCd("1200");
|
||||
|
||||
TokenResDataBody finalBody = new TokenResDataBody();
|
||||
finalBody.setAccessToken("NewAccessToken");
|
||||
finalBody.setExpiresIn(7200);
|
||||
finalBody.setTokenType("Bearer");
|
||||
finalBody.setScope("write");
|
||||
|
||||
NiceTokenResponse finalResponse = new NiceTokenResponse();
|
||||
finalResponse.setDataHeader(finalHeader);
|
||||
finalResponse.setDataBody(finalBody);
|
||||
|
||||
when(niceCiService.generateToken(reqDTO))
|
||||
.thenReturn(initialResponse)
|
||||
.thenReturn(finalResponse);
|
||||
|
||||
when(niceCiService.revokeToken(reqDTO)).thenReturn(revokedResponse);
|
||||
|
||||
NiceTokenResponse result = bizNiceCiService.generateToken(reqDTO);
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("1200", result.getDataHeader().getGwRsltCd());
|
||||
assertEquals("NewAccessToken", result.getDataBody().getAccessToken());
|
||||
verify(niceCiService, times(2)).generateToken(reqDTO);
|
||||
verify(niceCiService, times(1)).revokeToken(reqDTO);
|
||||
verify(niceCiMapper, times(1)).updateNiceCrtfToken(any(NiceCiInfo.class));
|
||||
verify(cacheService, times(1)).removeNiceCiInfoCache(reqDTO.getSignguCode(), reqDTO.getFfnlgCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
void testGenerateToken_whenTokenRevocationFails_ThrowsException() {
|
||||
NiceCiRequest reqDTO = NiceCiRequest.builder()
|
||||
.signguCode("88328")
|
||||
.ffnlgCode("11")
|
||||
.build();
|
||||
|
||||
ResponseDataHeader initialHeader = new ResponseDataHeader();
|
||||
initialHeader.setGwRsltCd("1800");
|
||||
|
||||
NiceTokenResponse initialResponse = new NiceTokenResponse();
|
||||
initialResponse.setDataHeader(initialHeader);
|
||||
|
||||
ResponseDataHeader revokeHeader = new ResponseDataHeader();
|
||||
revokeHeader.setGwRsltCd("1200");
|
||||
|
||||
TokenRevokeResponse failedRevokeResponse = new TokenRevokeResponse();
|
||||
failedRevokeResponse.setDataHeader(revokeHeader);
|
||||
failedRevokeResponse.setDataBody(new NiceCiDTO.TokenRevokeResDataBody(false));
|
||||
|
||||
when(niceCiService.generateToken(reqDTO)).thenReturn(initialResponse);
|
||||
when(niceCiService.revokeToken(reqDTO)).thenReturn(failedRevokeResponse);
|
||||
|
||||
BizRuntimeException exception = assertThrows(BizRuntimeException.class, () -> {
|
||||
bizNiceCiService.generateToken(reqDTO);
|
||||
});
|
||||
|
||||
assertNotNull(exception);
|
||||
verify(niceCiService, times(1)).generateToken(reqDTO);
|
||||
verify(niceCiService, times(1)).revokeToken(reqDTO);
|
||||
verify(niceCiMapper, never()).updateNiceCrtfToken(any(NiceCiInfo.class));
|
||||
verify(cacheService, never()).removeNiceCiInfoCache(eq(reqDTO.getSignguCode()), eq(reqDTO.getFfnlgCode()));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,510 @@
|
||||
package kr.xit.biz.ens.web;
|
||||
|
||||
import static kr.xit.core.support.utils.JsonUtils.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
import org.apache.commons.collections4.*;
|
||||
import org.apache.commons.lang3.*;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import io.swagger.v3.oas.annotations.*;
|
||||
import io.swagger.v3.oas.annotations.media.*;
|
||||
import io.swagger.v3.oas.annotations.tags.*;
|
||||
import kr.xit.biz.ens.cmm.*;
|
||||
import kr.xit.biz.ens.model.cmm.*;
|
||||
import kr.xit.biz.ens.model.kakao.pay.*;
|
||||
import kr.xit.biz.ens.model.kakao.talk.*;
|
||||
import kr.xit.core.model.*;
|
||||
import kr.xit.core.spring.util.*;
|
||||
import kr.xit.core.support.utils.*;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.*;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description :
|
||||
*
|
||||
* packageName : kr.xit.biz.ens.web
|
||||
* fileName : KkotalkApiDummyTestController
|
||||
* author : limju
|
||||
* date : 2023-08-31
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-08-31 limju 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Tag(name = "KkotalkApiDummyTestController", description = "카카오톡 전자고지 API Dummy 테스트")
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping(value = "/batch/ens/test/kakao/talk")
|
||||
@Slf4j
|
||||
public class KkotalkApiDummyTestController {
|
||||
@Value("${app.contract.host}")
|
||||
private String apiHost;
|
||||
|
||||
private String requestSend = "/api/kakao/talk/test/documents";
|
||||
|
||||
private String apiBulkSend = "/api/kakao/talk/test/documents/bulk";
|
||||
private String apiBulkSendError = "/api/kakao/talk/test/documents/bulk/error";
|
||||
|
||||
private String apiBulkStatus = "/api/kakao/talk/test/documents/bulk/status";
|
||||
|
||||
private final ApiWebClientUtil apiWebClient;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 모바일웹 연계 문서발송 요청
|
||||
* -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
|
||||
* </pre>
|
||||
* @param reqDTO
|
||||
* @return ResponseEntity
|
||||
*/
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = {
|
||||
@Content(mediaType = "application/json", examples = {
|
||||
@ExampleObject(value = """
|
||||
{
|
||||
"productCode": "D10_2",
|
||||
"envelope": {
|
||||
"title": "문서 제목",
|
||||
"content": {
|
||||
"link": "http://ipAddress/api/kakaopay/v1/ott",
|
||||
"html": ""
|
||||
},
|
||||
"hash": "73deae8287321e7237ed080f515d064fffe9dec380c3d3cb57a9d436007ad9e2",
|
||||
"guide": "문서 정보 안내 화면에 노출할 문구",
|
||||
"payload": "payload 파라미터 입니다.",
|
||||
"readExpiresAt": "2023-12-31T10:00:00",
|
||||
"reviewExpiresAt": "2023-12-31T13:00:00",
|
||||
"useNonPersonalizedNotification": false,
|
||||
"ci": "",
|
||||
"phoneNumber": "01012345678",
|
||||
"name": "김페이",
|
||||
"birthday": "19801101",
|
||||
"externalId": ""
|
||||
},
|
||||
"signguCode": "51110",
|
||||
"ffnlgCode": "11",
|
||||
"profile": "local"
|
||||
}
|
||||
""")
|
||||
})
|
||||
})
|
||||
@Operation(summary = "문서발송 요청", description = "카카오페이 전자문서 서버로 문서발송 처리를 요청")
|
||||
@PostMapping(value = "/documents", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public IApiResponse requestSend(
|
||||
@RequestBody final KkopayDocDTO.SendRequest reqDTO
|
||||
) {
|
||||
StringBuilder url = new StringBuilder()
|
||||
.append(apiHost)
|
||||
.append(requestSend);
|
||||
|
||||
return apiWebClient.exchangeKkotalk(
|
||||
url.toString(),
|
||||
HttpMethod.POST,
|
||||
JsonUtils.toJson(reqDTO),
|
||||
ApiResponseDTO.class,
|
||||
getRlaybsnmDTO()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 모바일웹 연계 문서발송 요청
|
||||
* -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
|
||||
* </pre>
|
||||
* @param reqDTO KkopayDocBulkDTO.BulkSendRequests
|
||||
* @return ApiResponseDTO
|
||||
*/
|
||||
@Operation(summary = "대량 문서발송 요청", description = "카카오페이 전자문서 서버로 대량 문서발송 처리를 요청")
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = {
|
||||
@Content(mediaType = "application/json", examples = {
|
||||
@ExampleObject(value = """
|
||||
{
|
||||
"productCode": "D10_1",
|
||||
"envelopes": [
|
||||
{
|
||||
"title": "문서 제목",
|
||||
"content": {
|
||||
"link": "http://ipAddress/api/kakaopay/v1/ott",
|
||||
"html": ""
|
||||
},
|
||||
"hash": "1375b68517c3055a2d5b425d8c6d32f6dbc9c74fb7f5fc796eb16c11a7a183b7",
|
||||
"guide": "문서 정보 안내 화면에 노출할 문구",
|
||||
"payload": "payload 파라미터 입니다.",
|
||||
"readExpiresAt": "2023-12-31T10:00:00",
|
||||
"reviewExpiresAt": "2023-12-31T13:00:00",
|
||||
"useNonPersonalizedNotification": false,
|
||||
"ci": "",
|
||||
"phoneNumber": "01012345678",
|
||||
"name": "김페이",
|
||||
"birthday": "19801101",
|
||||
"externalId": "41220202410170021"
|
||||
},
|
||||
{
|
||||
"title": "문서 제목1",
|
||||
"content": {
|
||||
"link": "http://ipAddress/api/kakaopay/v1/ott",
|
||||
"html": ""
|
||||
},
|
||||
"hash": "ad23397eea6951e4a967e4561b9b0459d59239d5f1e92f3b3fe5a8a511e99e83",
|
||||
"guide": "문서 정보 안내 화면에 노출할 문구",
|
||||
"payload": "payload 파라미터 입니다1.",
|
||||
"readExpiresAt": "2023-12-31T10:00:00",
|
||||
"reviewExpiresAt": "2023-12-31T13:00:00",
|
||||
"useNonPersonalizedNotification": false,
|
||||
"ci": "",
|
||||
"phoneNumber": "01012345678",
|
||||
"name": "김페이1",
|
||||
"birthday": "19801101",
|
||||
"externalId": "4413311202408200033"
|
||||
},
|
||||
{
|
||||
"title": "문서 제목2",
|
||||
"content": {
|
||||
"link": "http://ipAddress/api/kakaopay/v1/ott",
|
||||
"html": ""
|
||||
},
|
||||
"hash": "73deae8287321e7237ed080f515d064fffe9dec380c3d3cb57a9d436007ad9e2",
|
||||
"guide": "문서 정보 안내 화면에 노출할 문구",
|
||||
"payload": "payload 파라미터 입니다2.",
|
||||
"readExpiresAt": "2023-12-31T10:00:00",
|
||||
"reviewExpiresAt": "2023-12-31T13:00:00",
|
||||
"useNonPersonalizedNotification": false,
|
||||
"ci": "",
|
||||
"phoneNumber": "01012345678",
|
||||
"name": "김페이2",
|
||||
"birthday": "19801101",
|
||||
"externalId": "41220202410170011"
|
||||
}
|
||||
],
|
||||
"signguCode": "51110",
|
||||
"ffnlgCode": "11",
|
||||
"profile": "local"
|
||||
}
|
||||
""")
|
||||
})
|
||||
})
|
||||
@PostMapping(value = "/documents/bulk", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public IApiResponse requestSendBulk(
|
||||
@RequestBody final KkotalkDTO.BulkSendRequest reqDTO
|
||||
) {
|
||||
final String url = apiHost + apiBulkSend;
|
||||
|
||||
return apiWebClient.exchangeKkotalk(
|
||||
url,
|
||||
HttpMethod.POST,
|
||||
JsonUtils.toJson(reqDTO),
|
||||
ApiResponseDTO.class,
|
||||
getRlaybsnmDTO()
|
||||
);
|
||||
}
|
||||
|
||||
@Operation(summary = "대량 문서발송 요청", description = "카카오페이 전자문서 서버로 대량 문서발송 처리를 요청")
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = {
|
||||
@Content(mediaType = "application/json", examples = {
|
||||
@ExampleObject(value = """
|
||||
{
|
||||
"productCode": "D10_1",
|
||||
"envelopes": [
|
||||
{
|
||||
"title": "문서 제목",
|
||||
"content": {
|
||||
"link": "http://ipAddress/api/kakaopay/v1/ott",
|
||||
"html": ""
|
||||
},
|
||||
"hash": "1375b68517c3055a2d5b425d8c6d32f6dbc9c74fb7f5fc796eb16c11a7a183b7",
|
||||
"guide": "문서 정보 안내 화면에 노출할 문구",
|
||||
"payload": "payload 파라미터 입니다.",
|
||||
"readExpiresAt": "2023-12-31T10:00:00",
|
||||
"reviewExpiresAt": "2023-12-31T13:00:00",
|
||||
"useNonPersonalizedNotification": false,
|
||||
"ci": "",
|
||||
"phoneNumber": "01012345678",
|
||||
"name": "김페이",
|
||||
"birthday": "19801101",
|
||||
"externalId": "41220202410170021"
|
||||
},
|
||||
{
|
||||
"title": "문서 제목1",
|
||||
"content": {
|
||||
"link": "http://ipAddress/api/kakaopay/v1/ott",
|
||||
"html": ""
|
||||
},
|
||||
"hash": "ad23397eea6951e4a967e4561b9b0459d59239d5f1e92f3b3fe5a8a511e99e83",
|
||||
"guide": "문서 정보 안내 화면에 노출할 문구",
|
||||
"payload": "payload 파라미터 입니다1.",
|
||||
"readExpiresAt": "2023-12-31T10:00:00",
|
||||
"reviewExpiresAt": "2023-12-31T13:00:00",
|
||||
"useNonPersonalizedNotification": false,
|
||||
"ci": "",
|
||||
"phoneNumber": "01012345678",
|
||||
"name": "김페이1",
|
||||
"birthday": "19801101",
|
||||
"externalId": "4413311202408200033"
|
||||
},
|
||||
{
|
||||
"title": "문서 제목2",
|
||||
"content": {
|
||||
"link": "http://ipAddress/api/kakaopay/v1/ott",
|
||||
"html": ""
|
||||
},
|
||||
"hash": "73deae8287321e7237ed080f515d064fffe9dec380c3d3cb57a9d436007ad9e2",
|
||||
"guide": "문서 정보 안내 화면에 노출할 문구",
|
||||
"payload": "payload 파라미터 입니다2.",
|
||||
"readExpiresAt": "2023-12-31T10:00:00",
|
||||
"reviewExpiresAt": "2023-12-31T13:00:00",
|
||||
"useNonPersonalizedNotification": false,
|
||||
"ci": "",
|
||||
"phoneNumber": "01012345678",
|
||||
"name": "김페이2",
|
||||
"birthday": "19801101",
|
||||
"externalId": "41220202410170011"
|
||||
}
|
||||
],
|
||||
"signguCode": "51110",
|
||||
"ffnlgCode": "11",
|
||||
"profile": "local"
|
||||
}
|
||||
""")
|
||||
})
|
||||
})
|
||||
@PostMapping(value = "/documents/bulk/error", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public IApiResponse requestSendBulkAndError(
|
||||
@RequestBody final KkotalkDTO.BulkSendRequest reqDTO
|
||||
) {
|
||||
final String url = apiHost + apiBulkSend;
|
||||
|
||||
final List<KkotalkDTO.BulkSendResponse> resList = new ArrayList<>();
|
||||
boolean isSuccess = false;
|
||||
String errMsg = null;
|
||||
final List<KkotalkApiDTO.Envelope> envelopes = reqDTO.getEnvelopes();
|
||||
|
||||
for(int idx = 0; idx<3; idx++) {
|
||||
ApiResponseDTO<?> response = apiWebClient.exchangeKkotalk(
|
||||
apiHost + (idx==0? apiBulkSendError : apiBulkSend),
|
||||
HttpMethod.POST,
|
||||
JsonUtils.toJson(reqDTO),
|
||||
ApiResponseDTO.class,
|
||||
getRlaybsnmDTO()
|
||||
);
|
||||
|
||||
if(response.getData() != null) {
|
||||
try {
|
||||
resList.add(toObjByObj(response.getData(), KkotalkDTO.BulkSendResponse.class));
|
||||
|
||||
// FIXME::카카오톡 API 수신 결과가 아닌 경우 - 이렇게 수신 되는 경우가 있는지 테스트 불가 하여 방어 코드 추가 함
|
||||
// {"timestamp":"2024-12-16T01:19:57.040+00:00","path":"/pxy/kkoNew/v1/bulk/envelopes/D10_2","status":500,"error":"Internal Server Error","requestId":"541faefe-45126"}
|
||||
if(ObjectUtils.isNotEmpty(resList)){
|
||||
KkotalkDTO.BulkSendResponse bulkSendResponse = resList.get(0);
|
||||
List<KkotalkApiDTO.EnvelopeRes> envelopeRes = bulkSendResponse.getEnvelopes();
|
||||
// FIXME::카카오톡 API 수신 결과가 아닌 경우 - 이렇게 수신 되는 경우가 있는지 테스트 불가 하여 방어 코드 추가 함
|
||||
if(ObjectUtils.isEmpty(envelopeRes) || ObjectUtils.isEmpty(envelopeRes.get(0).getExternalId())){
|
||||
resList.clear();
|
||||
setErrorSendDto(envelopes, resList, "Internal Server Error");
|
||||
}else{
|
||||
isSuccess = true;
|
||||
};
|
||||
}
|
||||
}catch (Exception e){
|
||||
log.error("API 호출 결과 비정상 데이타::apiResult.getData() - {}", response.getMessage());
|
||||
setErrorSendDto(envelopes, resList, "Internal Server Error");
|
||||
}
|
||||
//extractService.saveKkotalkSendResult(resList);
|
||||
resList.clear();
|
||||
continue;
|
||||
}else{
|
||||
log.error(">>>> API 호출 결과 에러[비정상 return - proxy 에서 error return]::ApiResponseDTO - {}", response);
|
||||
setErrorSendDto(envelopes, resList, "Internal Server Error");
|
||||
//extractService.saveKkotalkSendResult(resList);
|
||||
resList.clear();
|
||||
}
|
||||
errMsg = response.getMessage();
|
||||
}
|
||||
return ApiResponseDTO.success(isSuccess);
|
||||
}
|
||||
|
||||
@Operation(summary = "대량 문서발송 요청", description = "카카오페이 전자문서 서버로 대량 문서발송 처리를 요청")
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = {
|
||||
@Content(mediaType = "application/json", examples = {
|
||||
@ExampleObject(value = """
|
||||
{
|
||||
"productCode": "D10_1",
|
||||
"envelopes": [
|
||||
{
|
||||
"title": "문서 제목",
|
||||
"content": {
|
||||
"link": "http://ipAddress/api/kakaopay/v1/ott",
|
||||
"html": ""
|
||||
},
|
||||
"hash": "1375b68517c3055a2d5b425d8c6d32f6dbc9c74fb7f5fc796eb16c11a7a183b7",
|
||||
"guide": "문서 정보 안내 화면에 노출할 문구",
|
||||
"payload": "payload 파라미터 입니다.",
|
||||
"readExpiresAt": "2023-12-31T10:00:00",
|
||||
"reviewExpiresAt": "2023-12-31T13:00:00",
|
||||
"useNonPersonalizedNotification": false,
|
||||
"ci": "",
|
||||
"phoneNumber": "01012345678",
|
||||
"name": "김페이",
|
||||
"birthday": "19801101",
|
||||
"externalId": "41220202410170021"
|
||||
},
|
||||
{
|
||||
"title": "문서 제목1",
|
||||
"content": {
|
||||
"link": "http://ipAddress/api/kakaopay/v1/ott",
|
||||
"html": ""
|
||||
},
|
||||
"hash": "ad23397eea6951e4a967e4561b9b0459d59239d5f1e92f3b3fe5a8a511e99e83",
|
||||
"guide": "문서 정보 안내 화면에 노출할 문구",
|
||||
"payload": "payload 파라미터 입니다1.",
|
||||
"readExpiresAt": "2023-12-31T10:00:00",
|
||||
"reviewExpiresAt": "2023-12-31T13:00:00",
|
||||
"useNonPersonalizedNotification": false,
|
||||
"ci": "",
|
||||
"phoneNumber": "01012345678",
|
||||
"name": "김페이1",
|
||||
"birthday": "19801101",
|
||||
"externalId": "4413311202408200033"
|
||||
},
|
||||
{
|
||||
"title": "문서 제목2",
|
||||
"content": {
|
||||
"link": "http://ipAddress/api/kakaopay/v1/ott",
|
||||
"html": ""
|
||||
},
|
||||
"hash": "73deae8287321e7237ed080f515d064fffe9dec380c3d3cb57a9d436007ad9e2",
|
||||
"guide": "문서 정보 안내 화면에 노출할 문구",
|
||||
"payload": "payload 파라미터 입니다2.",
|
||||
"readExpiresAt": "2023-12-31T10:00:00",
|
||||
"reviewExpiresAt": "2023-12-31T13:00:00",
|
||||
"useNonPersonalizedNotification": false,
|
||||
"ci": "",
|
||||
"phoneNumber": "01012345678",
|
||||
"name": "김페이2",
|
||||
"birthday": "19801101",
|
||||
"externalId": "41220202410170011"
|
||||
}
|
||||
],
|
||||
"signguCode": "51110",
|
||||
"ffnlgCode": "11",
|
||||
"profile": "local"
|
||||
}
|
||||
""")
|
||||
})
|
||||
})
|
||||
@PostMapping(value = "/documents/bulk/error2", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public IApiResponse requestSendBulkAndError2(
|
||||
@RequestBody final KkotalkDTO.BulkSendRequest reqDTO
|
||||
) {
|
||||
|
||||
final List<KkotalkApiDTO.Envelope> envelopes = reqDTO.getEnvelopes();
|
||||
|
||||
|
||||
final List<List<KkotalkApiDTO.Envelope>> partitions = ListUtils.partition(envelopes, 1);
|
||||
Map<String, String> headerMap = CmmEnsBizUtils.getHeadeMap();
|
||||
|
||||
AtomicInteger idx = new AtomicInteger(-1);
|
||||
//noinspection rawtypes
|
||||
final List<ApiResponseDTO> apiResults = partitions.stream()
|
||||
.map(bulkSendList -> {
|
||||
idx.getAndIncrement();
|
||||
return apiWebClient.exchangeKkotalk(
|
||||
apiHost + (idx.get() ==0? apiBulkSendError : apiBulkSend),
|
||||
HttpMethod.POST,
|
||||
JsonUtils.toJson(reqDTO),
|
||||
ApiResponseDTO.class,
|
||||
getRlaybsnmDTO());
|
||||
}
|
||||
)
|
||||
.toList();
|
||||
|
||||
final List<KkotalkDTO.BulkSendResponse> resList = new ArrayList<>();
|
||||
boolean isSuccess = false;
|
||||
String errMsg = null;
|
||||
//noinspection rawtypes
|
||||
for(ApiResponseDTO apiResult : apiResults) {
|
||||
if(apiResult.getData() != null) {
|
||||
resList.add(toObjByObj(apiResult.getData(), KkotalkDTO.BulkSendResponse.class));
|
||||
// FIXME:: 카카오톡 bulk send별 결과 처리
|
||||
//extractService.saveKkotalkSendResult(resList);
|
||||
resList.clear();
|
||||
isSuccess = true;
|
||||
continue;
|
||||
}
|
||||
errMsg = apiResult.getMessage();
|
||||
}
|
||||
|
||||
|
||||
|
||||
return ApiResponseDTO.success(reqDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 모바일웹 연계 문서발송 요청
|
||||
* -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
|
||||
* </pre>
|
||||
* @param reqDTO KkopayDocBulkDTO.BulkStatusRequests
|
||||
* @return ApiResponseDTO<BulkStatusResponses.BulkStatusResponses>
|
||||
*/
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = {
|
||||
@Content(mediaType = "application/json", examples = {
|
||||
@ExampleObject(value = """
|
||||
{
|
||||
"document_binder_uuids": [
|
||||
"BIN-883246dbff7b11edb3bb7affed8a016d",
|
||||
"BIN-883246dbff7b11edb3bb7affed8a0161",
|
||||
"BIN-883246dbff7b11edb3bb7affed8a0162"
|
||||
]
|
||||
}
|
||||
""")
|
||||
})
|
||||
})
|
||||
@Operation(summary = "대량 문서 상태 조회 요청", description = "카카오페이 전자문서 서버로 대량 문서 상태 조회 요청")
|
||||
@PostMapping(value = "/documents/bulk/status", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public IApiResponse findBulkStatus(
|
||||
@RequestBody final KkopayDocBulkDTO.BulkStatusRequests reqDTO
|
||||
) {
|
||||
final String url = apiHost + apiBulkStatus;
|
||||
|
||||
return apiWebClient.exchangeKkotalk(
|
||||
url,
|
||||
HttpMethod.POST,
|
||||
JsonUtils.toJson(reqDTO),
|
||||
ApiResponseDTO.class,
|
||||
getRlaybsnmDTO()
|
||||
);
|
||||
}
|
||||
|
||||
private CmmEnsRlaybsnmDTO getRlaybsnmDTO(){
|
||||
return CmmEnsRlaybsnmDTO.builder()
|
||||
.kakaoClientId("")
|
||||
.kakaoContractUuid("")
|
||||
.build();
|
||||
}
|
||||
|
||||
private void setErrorSendDto(List<KkotalkApiDTO.Envelope> envelopes, List<KkotalkDTO.BulkSendResponse> resList, String errMsg){
|
||||
List<KkotalkApiDTO.EnvelopeRes> envelopeResList = new ArrayList<>();
|
||||
for(KkotalkApiDTO.Envelope envelope :envelopes){
|
||||
envelopeResList.add(
|
||||
KkotalkApiDTO.EnvelopeRes.builder()
|
||||
.externalId(envelope.getExternalId())
|
||||
.errorCode("E400_00")
|
||||
.errorMessage(errMsg)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
resList.add(
|
||||
KkotalkDTO.BulkSendResponse.builder()
|
||||
.envelopes(envelopeResList)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,143 @@
|
||||
package kr.xit.core.spring.config.support;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.http.client.config.*;
|
||||
import org.apache.http.config.*;
|
||||
import org.apache.http.impl.client.*;
|
||||
import org.apache.http.impl.conn.*;
|
||||
import org.springframework.beans.factory.annotation.*;
|
||||
import org.springframework.context.annotation.*;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.http.client.*;
|
||||
import org.springframework.web.client.*;
|
||||
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
|
||||
import kr.xit.core.model.*;
|
||||
import kr.xit.core.support.utils.*;
|
||||
import lombok.extern.slf4j.*;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class RestTemplateConfig {
|
||||
|
||||
@Value("${app.restTemplate.socketTimeout:5000}")
|
||||
private int socketTimeout;
|
||||
|
||||
@Value("${app.restTemplate.connectionTimeout:2000}")
|
||||
private int connectionTimeout;
|
||||
|
||||
@Value("${app.restTemplate.requestTimeout:2000}")
|
||||
private int requestTimeout;
|
||||
|
||||
@Value("${app.restTemplate.pool.max:10}")
|
||||
private int poolMax;
|
||||
|
||||
@Value("${app.restTemplate.pool.max-per-route:5}")
|
||||
private int maxPerRoute;
|
||||
|
||||
// FIXME:: RetryTemplate 미사용
|
||||
// @Value("${app.restTemplate.retry.delay:200}")
|
||||
// private int retryDelay;
|
||||
//
|
||||
// @Value("${app.restTemplate.retry.max-delay:5}")
|
||||
// private int retryMaxDelay;
|
||||
//
|
||||
// @Value("${app.restTemplate.retry.max-attempts:3}")
|
||||
// private int maxAttempts;
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
ConnectionConfig connectionConfig = ConnectionConfig.custom()
|
||||
.build();
|
||||
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setSocketTimeout(socketTimeout)
|
||||
.setConnectTimeout(connectionTimeout)
|
||||
.setConnectionRequestTimeout(requestTimeout)
|
||||
.build();
|
||||
|
||||
PoolingHttpClientConnectionManager connectionPool = new PoolingHttpClientConnectionManager();
|
||||
connectionPool.setMaxTotal(poolMax);
|
||||
connectionPool.setDefaultMaxPerRoute(maxPerRoute);
|
||||
connectionPool.setDefaultConnectionConfig(connectionConfig);
|
||||
|
||||
CloseableHttpClient httpClient = HttpClientBuilder.create()
|
||||
.setConnectionManager(connectionPool)
|
||||
.setDefaultRequestConfig(requestConfig)
|
||||
.evictExpiredConnections()
|
||||
.build();
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient));
|
||||
restTemplate.setErrorHandler(new CustomResponseErrorHandler());
|
||||
//restTemplate.getInterceptors().add(requestInterceptor);
|
||||
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
// FIXME:: RetryTemplate 미사용
|
||||
// @Bean
|
||||
// public ClientHttpRequestInterceptor requestInterceptor(final RetryTemplate retryTemplate) {
|
||||
// return (request, body, execution) ->
|
||||
// retryTemplate.execute(context -> execution.execute(request, body));
|
||||
// }
|
||||
|
||||
// @Bean
|
||||
// public RetryTemplate retryTemplate() {
|
||||
// BackOffPolicy backOffPolicy = BackOffPolicyBuilder.newBuilder()
|
||||
// .delay(retryDelay)
|
||||
// .maxDelay(retryMaxDelay)
|
||||
// .build();
|
||||
//
|
||||
// RetryTemplate retryTemplate = new RetryTemplate();
|
||||
// retryTemplate.setRetryPolicy(new SimpleRetryPolicy(maxAttempts));
|
||||
// retryTemplate.setBackOffPolicy(backOffPolicy);
|
||||
//
|
||||
// return retryTemplate;
|
||||
// }
|
||||
|
||||
class CustomResponseErrorHandler implements ResponseErrorHandler {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
public CustomResponseErrorHandler() {
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
|
||||
HttpStatus status = clientHttpResponse.getStatusCode();
|
||||
return status.is4xxClientError() || status.is5xxServerError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(ClientHttpResponse response) throws IOException {
|
||||
String responseAsXmlString = toString(response.getBody());
|
||||
String responseAsString = JsonUtils.jsonFromXml(responseAsXmlString, ApiResponseDTO.class);
|
||||
log.error("ResponseBody: {}", responseAsString);
|
||||
|
||||
throw new CustomException(responseAsString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
|
||||
String responseAsXmlString = toString(response.getBody());
|
||||
String responseAsString = JsonUtils.jsonFromXml(responseAsXmlString, ApiResponseDTO.class);
|
||||
log.error("URL: {}, HttpMethod: {}, ResponseBody: {}", url, method, responseAsString);
|
||||
|
||||
throw new CustomException(responseAsString);
|
||||
}
|
||||
|
||||
String toString(InputStream inputStream) {
|
||||
Scanner s = new Scanner(inputStream).useDelimiter("\\A");
|
||||
return s.hasNext() ? s.next() : "";
|
||||
}
|
||||
|
||||
static class CustomException extends IOException {
|
||||
public CustomException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,218 @@
|
||||
package kr.xit.core.spring.util;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.*;
|
||||
import java.nio.charset.*;
|
||||
import java.time.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.*;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.stereotype.*;
|
||||
|
||||
import com.fasterxml.jackson.core.type.*;
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
|
||||
import kr.xit.biz.ens.model.cmm.*;
|
||||
import kr.xit.core.consts.*;
|
||||
import kr.xit.core.exception.*;
|
||||
import kr.xit.core.spring.config.support.*;
|
||||
import kr.xit.core.support.utils.*;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.*;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : react Restfull Config
|
||||
* packageName : kr.xit.core.spring.support
|
||||
* fileName : ApiWebClientUtil
|
||||
* author : julim
|
||||
* date : 2023-09-06
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-09-06 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ApiHttpClientUtil {
|
||||
private static final String AUTH_TYPE_BEARER = "bearer";
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final HttpClient httpClient = HttpClient.newBuilder()
|
||||
.version(HttpClient.Version.HTTP_2)
|
||||
.followRedirects(HttpClient.Redirect.NORMAL)
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
|
||||
/**
|
||||
* kakao WebClient 호출 처리
|
||||
* 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
|
||||
* -> {@link WebClientConfig responseFilter} 에서 처리
|
||||
* @param url String
|
||||
* @param method HttpMethod
|
||||
* @param body Object
|
||||
* @param rtnClzz Class<T>
|
||||
* @param ensDTO CmmEnsRlaybsnmDTO
|
||||
* @return rtnClzz<T>
|
||||
*/
|
||||
public <T> T exchangeKkopay(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, final CmmEnsRlaybsnmDTO ensDTO) {
|
||||
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put(HttpHeaders.ACCEPT_CHARSET, StandardCharsets.UTF_8.name());
|
||||
map.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8");
|
||||
map.put(HttpHeaders.AUTHORIZATION,
|
||||
String.format("%s %s", Constants.JwtToken.GRANT_TYPE.getCode(), ensDTO.getKakaoAccessToken()));
|
||||
map.put(Constants.HeaderName.UUID.getCode(), ensDTO.getKakaoContractUuid());
|
||||
|
||||
return exchange(url, method, body, rtnClzz, map);
|
||||
}
|
||||
|
||||
public <T> T exchangeKkopayAsIsCan(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, Map<String, String> headerMap) {
|
||||
return exchange(url, method, body, rtnClzz, headerMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* kakaotalk WebClient 호출 처리
|
||||
* 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
|
||||
* -> {@link WebClientConfig responseFilter} 에서 처리
|
||||
* @param url String
|
||||
* @param method HttpMethod
|
||||
* @param body Object
|
||||
* @param rtnClzz Class<T>
|
||||
* @param ensDTO CmmEnsRlaybsnmDTO
|
||||
* @return rtnClzz<T>
|
||||
*/
|
||||
public <T> T exchangeKkotalk(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, final CmmEnsRlaybsnmDTO ensDTO) {
|
||||
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put(HttpHeaders.ACCEPT_CHARSET, StandardCharsets.UTF_8.name());
|
||||
map.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8");
|
||||
map.put(HttpHeaders.AUTHORIZATION, String.format("KakaoAK %s", ensDTO.getKakaoDealerRestApiKey()));
|
||||
map.put(Constants.JwtToken.PARTNER_REST_API_KEY.getCode(), String.format("KakaoAK %s", ensDTO.getKakaoPartnerRestApiKey()));
|
||||
map.put(Constants.JwtToken.SETTLE_ID.getCode(), ensDTO.getKakaoSettleId());
|
||||
|
||||
return exchange(url, method, body, rtnClzz, map);
|
||||
}
|
||||
|
||||
/**
|
||||
* KT-BC WebClient 호출 처리
|
||||
* 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
|
||||
* -> {@link WebClientConfig responseFilter} 에서 처리
|
||||
* @param url String
|
||||
* @param method HttpMethod
|
||||
* @param body Object
|
||||
* @param rtnClzz Class<T>
|
||||
* @param ensDTO CmmEnsRlaybsnmDTO
|
||||
* @return rtnClzz<T>
|
||||
*/
|
||||
public <T> T exchangeKt(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, final CmmEnsRlaybsnmDTO ensDTO) {
|
||||
final Map<String,String> headerMap = new HashMap<>();
|
||||
headerMap.put(HttpHeaders.AUTHORIZATION, String.format("%s %s", AUTH_TYPE_BEARER, ensDTO.getKtAccessToken()));
|
||||
headerMap.put("client-id", ensDTO.getKtClientId());
|
||||
headerMap.put("client-tp", ensDTO.getKtClientTp());
|
||||
|
||||
return exchange(url, method, body, rtnClzz, headerMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* KT-GIBIS WebClient 호출 처리
|
||||
* 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
|
||||
* -> {@link WebClientConfig responseFilter} 에서 처리
|
||||
* @param url String
|
||||
* @param method HttpMethod
|
||||
* @param body Object
|
||||
* @param rtnClzz Class<T>
|
||||
* @param ensDTO CmmEnsRlaybsnmDTO
|
||||
* @return rtnClzz<T>
|
||||
*/
|
||||
public <T> T exchangeKtGbs(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, final CmmEnsRlaybsnmDTO ensDTO) {
|
||||
final Map<String,String> headerMap = new HashMap<>();
|
||||
headerMap.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||
headerMap.put(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", ensDTO.getKtAccessToken()));
|
||||
|
||||
return exchange(url, method, body, rtnClzz, headerMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* WebClient 호출 처리
|
||||
* GET 요청시 url에 파라메터를 포함해야 함(?key=value&key2=value2)
|
||||
* 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
|
||||
* -> {@link WebClientConfig responseFilter} 에서 처리
|
||||
* @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) {
|
||||
|
||||
if(HttpMethod.POST.equals(method)) {
|
||||
final HttpRequest request = HttpRequest.newBuilder(URI.create(url))
|
||||
.POST(HttpRequest.BodyPublishers.ofString(ObjectUtils.isEmpty(body)? StringUtils.EMPTY: String.valueOf(body)))
|
||||
.headers(convertHeadersToVarArgs(headerMap))
|
||||
.build();
|
||||
|
||||
try {
|
||||
final HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
validateSuccess(response);
|
||||
return JsonUtils.toObject(response.body(), new TypeReference<T>() {});
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
throw BizRuntimeException.create(e);
|
||||
} catch (IOException e) {
|
||||
throw BizRuntimeException.create(e);
|
||||
}
|
||||
|
||||
}else{
|
||||
final HttpRequest request = HttpRequest.newBuilder(URI.create(url))
|
||||
.GET()
|
||||
.headers(convertHeadersToVarArgs(headerMap))
|
||||
.build();
|
||||
|
||||
try {
|
||||
final HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
validateSuccess(response);
|
||||
|
||||
return JsonUtils.toObject(response.body(), new TypeReference<T>() {});
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
throw BizRuntimeException.create(e);
|
||||
} catch (IOException e) {
|
||||
throw BizRuntimeException.create(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void validateSuccess(final HttpResponse<T> response) {
|
||||
final HttpStatus status = HttpStatus.resolve(response.statusCode());
|
||||
if (status == null || status.isError()) {
|
||||
//log.warn("URI: {}, STATUS: {}", response.uri(), response.statusCode());
|
||||
//throw BizRuntimeException.create("처리실패");
|
||||
}
|
||||
log.info("URI: {}, STATUS: {}, BODY : {}, ", response.uri(), response.statusCode(), response.body());
|
||||
}
|
||||
|
||||
private String[] convertHeadersToVarArgs(Map<String, String> headers) {
|
||||
if(ObjectUtils.isEmpty(headers)) return new String[]{};
|
||||
List<String> headerList = new ArrayList<>();
|
||||
for(Map.Entry<String, String> e : headers.entrySet()){
|
||||
headerList.add(e.getKey());
|
||||
headerList.add(e.getValue());
|
||||
}
|
||||
return headerList.toArray(new String[0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,213 @@
|
||||
package kr.xit.core.spring.util;
|
||||
|
||||
import java.net.*;
|
||||
import java.nio.charset.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.*;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.stereotype.*;
|
||||
import org.springframework.web.client.*;
|
||||
import org.springframework.web.util.*;
|
||||
|
||||
import kr.xit.biz.ens.model.cmm.*;
|
||||
import kr.xit.core.consts.*;
|
||||
import kr.xit.core.exception.*;
|
||||
import kr.xit.core.model.*;
|
||||
import kr.xit.core.spring.config.support.*;
|
||||
import kr.xit.core.support.utils.*;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.*;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : react Restfull Config
|
||||
* packageName : kr.xit.core.spring.support
|
||||
* fileName : ApiWebClientUtil
|
||||
* author : julim
|
||||
* date : 2023-09-06
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-09-06 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ApiRestTemplateUtil {
|
||||
private static final String AUTH_TYPE_BEARER = "bearer";
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
/**
|
||||
* kakao WebClient 호출 처리
|
||||
* 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
|
||||
* -> {@link WebClientConfig responseFilter} 에서 처리
|
||||
* @param url String
|
||||
* @param method HttpMethod
|
||||
* @param body Object
|
||||
* @param rtnClzz Class<T>
|
||||
* @param ensDTO CmmEnsRlaybsnmDTO
|
||||
* @return rtnClzz<T>
|
||||
*/
|
||||
public <T> T exchangeKkopay(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, final CmmEnsRlaybsnmDTO ensDTO) {
|
||||
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put(HttpHeaders.ACCEPT_CHARSET, StandardCharsets.UTF_8.name());
|
||||
map.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8");
|
||||
map.put(HttpHeaders.AUTHORIZATION,
|
||||
String.format("%s %s", Constants.JwtToken.GRANT_TYPE.getCode(), ensDTO.getKakaoAccessToken()));
|
||||
map.put(Constants.HeaderName.UUID.getCode(), ensDTO.getKakaoContractUuid());
|
||||
|
||||
return exchange(url, method, body, rtnClzz, map);
|
||||
}
|
||||
|
||||
public <T> T exchangeKkopayAsIsCan(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, Map<String, String> headerMap) {
|
||||
return exchange(url, method, body, rtnClzz, headerMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* kakaotalk WebClient 호출 처리
|
||||
* 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
|
||||
* -> {@link WebClientConfig responseFilter} 에서 처리
|
||||
* @param url String
|
||||
* @param method HttpMethod
|
||||
* @param body Object
|
||||
* @param rtnClzz Class<T>
|
||||
* @param ensDTO CmmEnsRlaybsnmDTO
|
||||
* @return rtnClzz<T>
|
||||
*/
|
||||
public <T> T exchangeKkotalk(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, final CmmEnsRlaybsnmDTO ensDTO) {
|
||||
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put(HttpHeaders.ACCEPT_CHARSET, StandardCharsets.UTF_8.name());
|
||||
map.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8");
|
||||
map.put(HttpHeaders.AUTHORIZATION, String.format("KakaoAK %s", ensDTO.getKakaoDealerRestApiKey()));
|
||||
map.put(Constants.JwtToken.PARTNER_REST_API_KEY.getCode(), String.format("KakaoAK %s", ensDTO.getKakaoPartnerRestApiKey()));
|
||||
map.put(Constants.JwtToken.SETTLE_ID.getCode(), ensDTO.getKakaoSettleId());
|
||||
|
||||
return exchange(url, method, body, rtnClzz, map);
|
||||
}
|
||||
|
||||
/**
|
||||
* KT-BC WebClient 호출 처리
|
||||
* 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
|
||||
* -> {@link WebClientConfig responseFilter} 에서 처리
|
||||
* @param url String
|
||||
* @param method HttpMethod
|
||||
* @param body Object
|
||||
* @param rtnClzz Class<T>
|
||||
* @param ensDTO CmmEnsRlaybsnmDTO
|
||||
* @return rtnClzz<T>
|
||||
*/
|
||||
public <T> T exchangeKt(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, final CmmEnsRlaybsnmDTO ensDTO) {
|
||||
final Map<String,String> headerMap = new HashMap<>();
|
||||
headerMap.put(HttpHeaders.AUTHORIZATION, String.format("%s %s", AUTH_TYPE_BEARER, ensDTO.getKtAccessToken()));
|
||||
headerMap.put("client-id", ensDTO.getKtClientId());
|
||||
headerMap.put("client-tp", ensDTO.getKtClientTp());
|
||||
|
||||
return exchange(url, method, body, rtnClzz, headerMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* KT-GIBIS WebClient 호출 처리
|
||||
* 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
|
||||
* -> {@link WebClientConfig responseFilter} 에서 처리
|
||||
* @param url String
|
||||
* @param method HttpMethod
|
||||
* @param body Object
|
||||
* @param rtnClzz Class<T>
|
||||
* @param ensDTO CmmEnsRlaybsnmDTO
|
||||
* @return rtnClzz<T>
|
||||
*/
|
||||
public <T> T exchangeKtGbs(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, final CmmEnsRlaybsnmDTO ensDTO) {
|
||||
final Map<String,String> headerMap = new HashMap<>();
|
||||
headerMap.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||
headerMap.put(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", ensDTO.getKtAccessToken()));
|
||||
|
||||
return exchange(url, method, body, rtnClzz, headerMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* WebClient 호출 처리
|
||||
* GET 요청시 url에 파라메터를 포함해야 함(?key=value&key2=value2)
|
||||
* 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
|
||||
* -> {@link WebClientConfig responseFilter} 에서 처리
|
||||
* @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) {
|
||||
|
||||
if(HttpMethod.POST.equals(method)) {
|
||||
|
||||
return restTemplate.exchange(
|
||||
url,
|
||||
method,
|
||||
new HttpEntity<>(ObjectUtils.isEmpty(body) ? null: body, getHeaders(headerMap)),
|
||||
rtnClzz
|
||||
).getBody();
|
||||
}else{
|
||||
return restTemplate.exchange(
|
||||
url,
|
||||
//createUrl(url),
|
||||
method,
|
||||
new HttpEntity<>(getHeaders(headerMap)),
|
||||
rtnClzz,
|
||||
body
|
||||
).getBody();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* WebClient form data 호출 처리
|
||||
* -> application/x-www-form-urlencoded 전송시
|
||||
* GET 요청시 url에 파라메터를 포함해야 함(?key=value&key2=value2)
|
||||
* 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
|
||||
* -> {@link WebClientConfig responseFilter} 에서 처리
|
||||
* @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 exchangeFormData(final String url, final HttpMethod method, final Object body, final Class<T> rtnClzz, final Map<String,String> headerMap) {
|
||||
|
||||
return exchange(url, method, JsonUtils.toMultiValue(body), rtnClzz, headerMap);
|
||||
}
|
||||
|
||||
public <T> ApiResponseDTO<T> sendError(final Throwable e) {
|
||||
|
||||
return ErrorParse.extractError(e.getCause());
|
||||
}
|
||||
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) {
|
||||
if(ObjectUtils.isEmpty(map)) return headers;
|
||||
for(Map.Entry<String, String> e : map.entrySet()){
|
||||
headers.add(e.getKey(), e.getValue());
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
private HttpHeaders getHeaders(final Map<String, String> map) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
if(ObjectUtils.isEmpty(map)) return headers;
|
||||
for(Map.Entry<String, String> e : map.entrySet()){
|
||||
headers.add(e.getKey(), e.getValue());
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,139 @@
|
||||
package kr.xit.core.support.utils;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.junit.jupiter.api.extension.*;
|
||||
import org.mockito.junit.jupiter.*;
|
||||
|
||||
import com.fasterxml.jackson.core.*;
|
||||
|
||||
import kr.xit.biz.common.*;
|
||||
import kr.xit.biz.ens.model.kakao.pay.*;
|
||||
import kr.xit.biz.ens.model.kakao.talk.*;
|
||||
import kr.xit.core.exception.*;
|
||||
import kr.xit.core.model.*;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.*;
|
||||
|
||||
@Slf4j
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class JsonUtilsTest {
|
||||
|
||||
/**
|
||||
* This class provides methods to test the functionality of JsonUtils.
|
||||
* The 'toObject' method converts a JSON string into an object of the specified class type.
|
||||
*/
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
static class TestObject {
|
||||
private String name;
|
||||
private int age;
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSendBulks() throws JsonProcessingException {
|
||||
String sendJson1 = """
|
||||
{"envelopes":[{"externalId":"41220110994794765","envelopeId":"EVLP-01JCQC971QBBQQ6N54TKSEP9WT-00"}]}
|
||||
""";
|
||||
String sendJson = "{\"envelopes\":[{\"externalId\":\"41220110994794765\",\"envelopeId\":\"EVLP-01JCQC971QBBQQ6N54TKSEP9WT-00\"}]}";
|
||||
String statusJson = """
|
||||
{"envelopeStatus":[{"envelopeId":"EVLP-01JCQC971QBBQQ6N54TKSEP9WT-00","externalId":"41220110994794765","status":"RECEIVED","sentAt":"2024-11-15T16:40:57","receivedAt":"2024-11-15T16:40:57","readExpiredAt":"2024-12-31T23:59:59","isNotificationUnavailable":false}]}
|
||||
""";
|
||||
ApiResponseDTO kkoStatusList = getKkoStatusList();
|
||||
List<ApiResponseDTO> apiResults = new ArrayList<>(Collections.singleton(kkoStatusList));
|
||||
|
||||
final List<KkopayDocBulkDTO.BulkStatusResponses> resList = new ArrayList<>();
|
||||
//noinspection rawtypes
|
||||
for(ApiResponseDTO apiResult : apiResults) {
|
||||
if(apiResult.getData() != null) {
|
||||
resList.add(toObjByObj(apiResult.getData(), KkopayDocBulkDTO.BulkStatusResponses.class));
|
||||
}
|
||||
}
|
||||
|
||||
toObject(sendJson, KkotalkDTO.BulkSendResponse.class);
|
||||
toObject(statusJson, KkotalkDTO.BulkStatusResponse.class);
|
||||
}
|
||||
|
||||
private static <T> T toObjByObj(Object obj, Class<T> clazz) {
|
||||
return JsonUtils.toObjByObj(obj, clazz);
|
||||
}
|
||||
|
||||
private static <T> T toObject(String obj, Class<T> clazz) {
|
||||
return JsonUtils.toObject(obj, clazz);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToObject_validJson() {
|
||||
String json = "{\"name\": \"John\", \"age\": 30}";
|
||||
TestObject expectedObject = new TestObject("John", 30);
|
||||
|
||||
TestObject result = JsonUtils.toObject(json, TestObject.class);
|
||||
|
||||
assertEquals(expectedObject, result, "Converting JSON to TestObject did not produce the expected object.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToObject_emptyString() {
|
||||
String json = "";
|
||||
|
||||
assertNull(JsonUtils.toObject(json, TestObject.class), "Empty string should result in a null object.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToObject_nullString() {
|
||||
assertNull(JsonUtils.toObject(null, TestObject.class), "Null string should result in a null object.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToObject_invalidJson() {
|
||||
String json = "{\"name\": \"John\", \"age\": \"thirty\"}";
|
||||
|
||||
assertThrows(BizRuntimeException.class, () -> JsonUtils.toObject(json, TestObject.class),
|
||||
"Invalid JSON should throw a BizRuntimeException.");
|
||||
}
|
||||
|
||||
private static ApiResponseDTO getKkoStatusList(){
|
||||
List<KkopayDocBulkDTO.BulkStatus> resDTO = new ArrayList<>();
|
||||
|
||||
resDTO.add(KkopayDocBulkDTO.BulkStatus.builder()
|
||||
.document_binder_uuid("dkdkdkkdkd")
|
||||
.error_code(ApiConstants.Error.NOT_FOUND.getCode())
|
||||
.error_message("요청 정보를 찾을 수 없습니다. documentBinder를 찾을수 없습니다.")
|
||||
.build()
|
||||
);
|
||||
|
||||
resDTO.add(KkopayDocBulkDTO.BulkStatus.builder()
|
||||
.document_binder_uuid("susuusus")
|
||||
.status_data(KkopayDocAttrDTO.DocStatus.builder()
|
||||
.doc_box_status(ApiConstants.KkopayDocStatus.RECEIVED)
|
||||
.doc_box_sent_at(1443456743L)
|
||||
.doc_box_received_at(1443456743L)
|
||||
.user_notified_at(1443456743L)
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
resDTO.add(KkopayDocBulkDTO.BulkStatus.builder()
|
||||
.document_binder_uuid("djdjfjjf")
|
||||
.status_data(KkopayDocAttrDTO.DocStatus.builder()
|
||||
.doc_box_status(ApiConstants.KkopayDocStatus.READ)
|
||||
.doc_box_sent_at(1443456743L)
|
||||
.doc_box_received_at(1443456743L)
|
||||
.doc_box_read_at(1443456743L)
|
||||
.authenticated_at(1443456743L)
|
||||
.token_used_at(1443456743L)
|
||||
.user_notified_at(1443456743L)
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
return ApiResponseDTO.success(KkopayDocBulkDTO.BulkStatusResponses.builder()
|
||||
.documents(resDTO)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue