feat: 카카오톡 API 반영
parent
1f01212ba5
commit
b5d538baa9
@ -0,0 +1,274 @@
|
||||
package cokr.xit.ens.core.aop;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import cokr.xit.ens.core.exception.BizRuntimeException;
|
||||
import cokr.xit.ens.core.exception.code.ErrorCode;
|
||||
import cokr.xit.ens.core.utils.Checks;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : Api 응답
|
||||
* TODO :: 프로젝트별 json 결과에서 제외하려면 @JsonIgnore 사용
|
||||
* packageName : kr.xit.core.model
|
||||
* fileName : ApiResponseDTO
|
||||
* author : julim
|
||||
* date : 2023-04-28
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-04-28 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Schema(name = "ApiResponseDTO", description = "Restful API 결과")
|
||||
@Data
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@JsonRootName("result")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class ApiResponseDTO<T> implements IApiResponse {
|
||||
private static final String FAIL_STATUS = "fail";
|
||||
private static final String ERROR_STATUS = "error";
|
||||
|
||||
@Schema(example = "true", description = "에러인 경우 false", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private boolean success;
|
||||
|
||||
@Schema(example = " ", description = "HttpStatus.OK", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String code;
|
||||
|
||||
@Schema(description = "결과 데이타, 오류시 null", example = " ")
|
||||
private T data;
|
||||
|
||||
@Schema(description = "오류 발생시 오류 메세지", example = " ", requiredMode = Schema.RequiredMode.AUTO)
|
||||
@Setter
|
||||
private String message;
|
||||
|
||||
@JsonIgnore
|
||||
@Schema(example = " ", description = "HttpStatus.OK", requiredMode = Schema.RequiredMode.AUTO)
|
||||
private HttpStatus httpStatus;
|
||||
|
||||
@Schema(description = "API 실행 결과 데이타 수")
|
||||
private int count;
|
||||
|
||||
/**
|
||||
* 비동기 정상 데이타 ApiResponseDTO<T> return
|
||||
*
|
||||
* @param future CompletableFuture<T>
|
||||
* @return ApiResponseDTO<T>
|
||||
*/
|
||||
//@SuppressWarnings("unchecked")
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static <T> ApiResponseDTO of(final CompletableFuture<T> future) {
|
||||
try {
|
||||
if(future.get() instanceof ApiResponseDTO<?>) return (ApiResponseDTO)future.get();
|
||||
return new ApiResponseDTO<>(true, future.get(), String.valueOf(HttpStatus.OK.value()),
|
||||
HttpStatus.OK.name(), HttpStatus.OK);
|
||||
|
||||
} catch (InterruptedException ie){
|
||||
// thread pool에 에러 상태 전송
|
||||
Thread.currentThread().interrupt();
|
||||
throw BizRuntimeException.create(ie);
|
||||
|
||||
} catch (ExecutionException ee) {
|
||||
throw BizRuntimeException.create(ee);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 정상 데이타 ApiResponseDTO<T> return
|
||||
* @return ApiResponseDTO<T>
|
||||
*/
|
||||
public static <T> ApiResponseDTO<T> success() {
|
||||
return new ApiResponseDTO<>(true, null, String.valueOf(HttpStatus.OK.value()), HttpStatus.OK.name(), HttpStatus.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* 정상 데이타 ApiResponseDTO<T> return
|
||||
* @param data T
|
||||
* @return ApiResponseDTO<T>
|
||||
*/
|
||||
public static <T> ApiResponseDTO<T> success(T data) {
|
||||
return new ApiResponseDTO<>(true, data, String.valueOf(HttpStatus.OK.value()), HttpStatus.OK.name(), HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 정상 데이타 ApiResponseDTO<T> return
|
||||
* @param httpStatus HttpStatus
|
||||
* @return ApiResponseDTO<T>
|
||||
*/
|
||||
public static <T> ApiResponseDTO<T> success(final HttpStatus httpStatus) {
|
||||
return new ApiResponseDTO<>(true, null, String.valueOf(httpStatus.value()), httpStatus.name(), httpStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* 정상 데이타 ApiResponseDTO<T> return
|
||||
* @param data T 데이타
|
||||
* @param httpStatus HttpStatus
|
||||
* @return ApiResponseDTO<T>
|
||||
*/
|
||||
public static <T> ApiResponseDTO<T> success(final T data, final HttpStatus httpStatus) {
|
||||
return new ApiResponseDTO<>(true, data, String.valueOf(httpStatus.value()), httpStatus.name(), httpStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* 정상 데이타 ApiResponseDTO<T> return
|
||||
* @param data T 데이타
|
||||
* @param message String
|
||||
* @return ApiResponseDTO<T>
|
||||
*/
|
||||
public static <T> ApiResponseDTO<T> success(final T data, final String message) {
|
||||
return new ApiResponseDTO<>(true, data, String.valueOf(HttpStatus.OK.value()), message, HttpStatus.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* 정상 return - body empty
|
||||
* @return ApiResponseDTO
|
||||
*/
|
||||
public static <T> ApiResponseDTO<T> empty() {
|
||||
return new ApiResponseDTO<>(true, null, String.valueOf(HttpStatus.NO_CONTENT.value()), HttpStatus.NO_CONTENT.name(), HttpStatus.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error ApiResponseDTO return
|
||||
* Hibernate Validator에 의해 유효하지 않은 데이터로 인해 API 호출이 거부될때 반환
|
||||
* @param bindingResult BindingResult
|
||||
* @return ApiResponseDTO
|
||||
*/
|
||||
public static <T> ApiResponseDTO<T> error(final BindingResult bindingResult) {
|
||||
Map<String, String> errors = new HashMap<>();
|
||||
|
||||
List<ObjectError> allErrors = bindingResult.getAllErrors();
|
||||
for (ObjectError error : allErrors) {
|
||||
if (error instanceof FieldError) {
|
||||
errors.put(((FieldError) error).getField(), error.getDefaultMessage());
|
||||
} else {
|
||||
errors.put( error.getObjectName(), error.getDefaultMessage());
|
||||
}
|
||||
}
|
||||
return new ApiResponseDTO<>(false, null, FAIL_STATUS, errors.toString(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error ApiResponseDTO return
|
||||
* @param message 에러 메세지
|
||||
* @return ApiResponseDTO
|
||||
*/
|
||||
public static <T> ApiResponseDTO<T> error(final String message) {
|
||||
return new ApiResponseDTO<>(false, null, ERROR_STATUS, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error ApiResponseDTO return
|
||||
* @param errorCode ErrorCode
|
||||
* @return ApiResponseDTO
|
||||
*/
|
||||
public static <T> ApiResponseDTO<T> error(final ErrorCode errorCode) {
|
||||
return new ApiResponseDTO<>(false, null, errorCode.name(), errorCode.getMessage(), errorCode.getHttpStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Error ApiResponseDTO return
|
||||
* @param e BizRuntimeException
|
||||
* @return ApiResponseDTO
|
||||
*/
|
||||
public static <T> ApiResponseDTO<T> error(final BizRuntimeException e) {
|
||||
|
||||
if (Checks.isNotEmpty(e.getErrorCode())) {
|
||||
return ApiResponseDTO.error(e.getErrorCode());
|
||||
}
|
||||
return new ApiResponseDTO<>(
|
||||
false,
|
||||
null,
|
||||
org.apache.commons.lang3.StringUtils.defaultString(e.getCode(), org.apache.commons.lang3.StringUtils.EMPTY),
|
||||
org.apache.commons.lang3.StringUtils.defaultString(e.getMessage(), org.apache.commons.lang3.StringUtils.EMPTY),
|
||||
e.getHttpStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Error ApiResponseDTO return
|
||||
* @param code 에러코드
|
||||
* @param message 에러메세지
|
||||
* @return ApiResponseDTO
|
||||
*/
|
||||
public static <T> ApiResponseDTO<T> error(final String code, final String message) {
|
||||
return new ApiResponseDTO<>(false, null, code, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error ApiResponseDTO return
|
||||
* @param code 에러코드
|
||||
* @param message 에러메세지
|
||||
* @param httpStatus HttpStatus
|
||||
* @return ApiResponseDTO
|
||||
*/
|
||||
public static <T> ApiResponseDTO<T> error(final String code, final String message, HttpStatus httpStatus) {
|
||||
return new ApiResponseDTO<>(false, null, code, message, httpStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param success true|false
|
||||
* @param data data
|
||||
* @param code 에러코드
|
||||
* @param message 메세지(에러 발생시 필수)
|
||||
* @param httpStatus HttpStatus
|
||||
*/
|
||||
private ApiResponseDTO(final boolean success, final T data, final String code, final String message, final HttpStatus httpStatus) {
|
||||
this.success = success;
|
||||
this.data = data;
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.httpStatus = httpStatus;
|
||||
|
||||
if(data == null){
|
||||
this.count = 0;
|
||||
|
||||
}else {
|
||||
|
||||
if (Collection.class.isAssignableFrom(data.getClass())) {
|
||||
this.count = (((Collection<?>) data).size());
|
||||
|
||||
} else {
|
||||
this.count = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(httpStatus == null){
|
||||
if(!success) this.httpStatus = HttpStatus.BAD_REQUEST;
|
||||
else this.httpStatus = HttpStatus.OK;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// value가 null값인 경우도 생성
|
||||
GsonBuilder builder = new GsonBuilder().serializeNulls();
|
||||
builder.disableHtmlEscaping();
|
||||
return builder.setPrettyPrinting().create().toJson(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package cokr.xit.ens.core.aop;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description :
|
||||
*
|
||||
* packageName : kr.xit.core.model
|
||||
* fileName : IApiResponse
|
||||
* author : limju
|
||||
* date : 2023-05-31
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-05-31 limju 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public interface IApiResponse {
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package cokr.xit.ens.core.config.support;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.cfg.CoercionAction;
|
||||
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
|
||||
import cokr.xit.ens.core.utils.CoreSpringUtils;
|
||||
import cokr.xit.ens.core.utils.JsonUtils;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : Jackson(ObjectMapper) 설정 재정의
|
||||
* 모든 ObjectMapper 사용시 이 설정이 적용되도록 해야 한다.
|
||||
* - Cannot coerce empty String("") to ...
|
||||
* value(but could if coercion was enabled using `CoercionConfig`) 에러 방어
|
||||
* packageName : kr.xit.core.spring.config.support
|
||||
* fileName : CustomJacksonConfig
|
||||
* author : julim
|
||||
* date : 2023-09-13
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-09-13 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
* @see CoreSpringUtils#getObjectMapper()
|
||||
* @see JsonUtils
|
||||
* @see WebClientConfig
|
||||
*/
|
||||
@Configuration
|
||||
public class CustomJacksonConfig {
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 기본 설정에 override
|
||||
* Cannot coerce empty String("") to ...
|
||||
* value(but could if coercion was enabled using `CoercionConfig`) 에러 방어
|
||||
* </pre>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
public ObjectMapper objectMapper() {
|
||||
ObjectMapper om = new ObjectMapper()
|
||||
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
|
||||
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||
//.setSerializationInclusion(Include.NON_NULL)
|
||||
//.setSerializationInclusion(Include.NON_EMPTY)
|
||||
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
|
||||
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, false)
|
||||
.registerModule(new JavaTimeModule());
|
||||
|
||||
om.coercionConfigDefaults()
|
||||
.setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsNull);
|
||||
// om.coercionConfigFor(LogicalType.Enum)
|
||||
// .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsNull);
|
||||
// om.coercionConfigFor(LogicalType.POJO)
|
||||
// .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsNull);
|
||||
|
||||
return om;
|
||||
}
|
||||
}
|
@ -0,0 +1,245 @@
|
||||
package cokr.xit.ens.core.config.support;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
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 com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import cokr.xit.ens.core.exception.BizRuntimeException;
|
||||
import cokr.xit.ens.core.exception.ClientErrorException;
|
||||
import cokr.xit.ens.core.exception.ServerErrorException;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import io.netty.handler.timeout.WriteTimeoutHandler;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.netty.resources.ConnectionProvider;
|
||||
import reactor.netty.transport.logging.AdvancedByteBufFormat;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : WebClient configuration
|
||||
* Spring WebFlux 기반 HTTP Client Config
|
||||
* logging : - ExchangeFilterFunction 구현 처리 가능(requestFilter, responseFilter)
|
||||
* - logging.level.reactor.netty.http.client: DEBUG|ERROR
|
||||
*
|
||||
* packageName : kr.xit.core.spring.config.support
|
||||
* fileName : WebClientConfig
|
||||
* author : julim
|
||||
* date : 2023-09-06
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-09-06 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Configuration
|
||||
public class WebClientConfig {
|
||||
|
||||
@Value("${app.contract.connection.timeout:5000}")
|
||||
private int connectTimeout;
|
||||
@Value("${app.contract.connection.readTimeout:5000}")
|
||||
private int readTimeout;
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
|
||||
|
||||
/**
|
||||
* setEncodingMode : GET 요청의 파라미터 셋팅을 하기 위한 URI 템플릿의 인코딩을 위한 설정
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public WebClient webClient() {
|
||||
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
|
||||
|
||||
return WebClient.builder()
|
||||
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.uriBuilderFactory(factory)
|
||||
.clientConnector(new ReactorClientHttpConnector(defaultHttpClient()))
|
||||
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
|
||||
.exchangeStrategies(defaultExchangeStrategies())
|
||||
.filters(exchangeFilterFunctions -> {
|
||||
//exchangeFilterFunctions.add(requestFilter());
|
||||
exchangeFilterFunctions.add(responseFilter());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* Http client 생성
|
||||
* - 요청 / 응답 debugging을 위해 wiretap 설정 - AdvancedByteBufFormat.TEXTUAL
|
||||
* @return HttpClient
|
||||
* </pre>
|
||||
*/
|
||||
@Bean
|
||||
public HttpClient defaultHttpClient() {
|
||||
try {
|
||||
// SSL check bypass
|
||||
SslContext sslContext = SslContextBuilder
|
||||
.forClient()
|
||||
.trustManager(InsecureTrustManagerFactory.INSTANCE)
|
||||
.build();
|
||||
|
||||
return HttpClient.create(connectionProvider())
|
||||
.secure(t -> t.sslContext(sslContext))
|
||||
.wiretap(this.getClass().getCanonicalName(), LogLevel.DEBUG,
|
||||
AdvancedByteBufFormat.TEXTUAL)
|
||||
.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)));
|
||||
}catch(SSLException se){
|
||||
throw BizRuntimeException.create(se.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* maxConnections : connection pool의 갯수
|
||||
* pendingAcquireTimeout : 커넥션 풀에서 커넥션을 얻기 위해 기다리는 최대 시간
|
||||
* pendingAcquireMaxCount : 커넥션 풀에서 커넥션을 가져오는 시도 횟수 (-1: no limit)
|
||||
* maxIdleTime : 커넥션 풀에서 idle 상태의 커넥션을 유지하는 시간
|
||||
* @return ConnectionProvider
|
||||
*/
|
||||
@Bean
|
||||
public ConnectionProvider connectionProvider() {
|
||||
return ConnectionProvider.builder("http-pool")
|
||||
.maxConnections(100)
|
||||
.pendingAcquireTimeout(Duration.ofMillis(0))
|
||||
.pendingAcquireMaxCount(-1)
|
||||
.maxIdleTime(Duration.ofMillis(2000L))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 1. 256KB 보다 큰 HTTP 메시지를 처리 시도 → DataBufferLimitException 에러 발생 방어
|
||||
* 2. messageWriters를 통한 logging(setEnableLoggingRequestDetails(true)
|
||||
* -> org.springframework.web.reactive.function.client.ExchangeFunctions: DEBUG 하여 활성
|
||||
* -> defaultHttpClient()의 wiretap 사용으로 비활성
|
||||
* </pre>
|
||||
* @return ExchangeStrategies
|
||||
*/
|
||||
@Bean
|
||||
public ExchangeStrategies defaultExchangeStrategies() {
|
||||
// 256KB 보다 큰 HTTP 메시지를 처리 시도 → DataBufferLimitException 에러 발생 방어
|
||||
return ExchangeStrategies.builder()
|
||||
.codecs(config -> {
|
||||
config.defaultCodecs().maxInMemorySize(2 * 1024 * 1024);
|
||||
config.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper, MediaType.APPLICATION_JSON));
|
||||
config.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper, MediaType.APPLICATION_JSON));
|
||||
})
|
||||
.build();
|
||||
|
||||
// es.messageWriters()
|
||||
// .stream()
|
||||
// .filter(LoggingCodecSupport.class::isInstance)
|
||||
// .forEach(writer -> ((LoggingCodecSupport)writer).setEnableLoggingRequestDetails(true));
|
||||
}
|
||||
|
||||
private ExchangeFilterFunction requestFilter() {
|
||||
return ExchangeFilterFunction.ofRequestProcessor(cr -> {
|
||||
if (log.isDebugEnabled()) {
|
||||
StringBuilder sb = new StringBuilder("\n>>>>>>>>>> WebClient Http Request <<<<<<<<<<<<<\n");
|
||||
sb.append(logMethodAndUrl(cr));
|
||||
sb.append(logHeaders(cr));
|
||||
sb.append("-------------------------------------------------------");
|
||||
log.debug(sb.toString());
|
||||
}
|
||||
return Mono.just(cr);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* reponse logging && error Handling
|
||||
* @return ExchangeFilterFunction
|
||||
*/
|
||||
private ExchangeFilterFunction responseFilter() {
|
||||
return ExchangeFilterFunction.ofResponseProcessor(cr -> {
|
||||
|
||||
HttpStatus status = cr.statusCode();
|
||||
|
||||
if(cr.statusCode().is4xxClientError()) {
|
||||
return cr.bodyToMono(String.class)
|
||||
.flatMap(errorBody -> Mono.error(new ClientErrorException(status, errorBody)));
|
||||
|
||||
} else if(cr.statusCode().is5xxServerError()) {
|
||||
return cr.bodyToMono(String.class)
|
||||
.flatMap(errorBody -> Mono.error(new ServerErrorException(status, errorBody)));
|
||||
}
|
||||
|
||||
// if(log.isDebugEnabled()) {
|
||||
// StringBuilder sb = new StringBuilder(
|
||||
// "\n>>>>>>>>>> WebClient Http Response <<<<<<<<<<<<<\n");
|
||||
// sb.append(logStatus(cr));
|
||||
// sb.append(logHeaders(cr));
|
||||
// sb.append("-------------------------------------------------------");
|
||||
// log.debug(sb.toString());
|
||||
// }
|
||||
return Mono.just(cr);
|
||||
});
|
||||
}
|
||||
|
||||
private static String logStatus(ClientResponse response) {
|
||||
HttpStatus status = response.statusCode();
|
||||
return String.format("Returned staus code %s (%s)", status.value(), status.getReasonPhrase());
|
||||
}
|
||||
|
||||
private static String logHeaders(ClientRequest request) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
request.headers()
|
||||
.forEach((name, values) ->
|
||||
values.forEach(value -> sb.append(name).append(": ").append(value).append("\n"))
|
||||
);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String logHeaders(ClientResponse response) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
response.headers()
|
||||
.asHttpHeaders()
|
||||
.forEach((name, values) ->
|
||||
values.forEach(value -> sb.append(name).append(": ").append(value).append("\n"))
|
||||
);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String logMethodAndUrl(ClientRequest request) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(request.method().name());
|
||||
sb.append(" to ");
|
||||
sb.append(request.url());
|
||||
|
||||
return sb.append("\n").toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,224 @@
|
||||
package cokr.xit.ens.core.exception;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import cokr.xit.ens.core.exception.code.ErrorCode;
|
||||
import cokr.xit.ens.core.utils.CoreSpringUtils;
|
||||
import cokr.xit.ens.core.utils.MessageUtil;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : egov BaseRuntimeException 상속
|
||||
* packageName : kr.xit.core.exception
|
||||
* fileName : BizRuntimeException
|
||||
* author : julim
|
||||
* date : 2023-04-28
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-04-28 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Slf4j
|
||||
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "Invalid parameter")
|
||||
public class BizRuntimeException extends RuntimeException{
|
||||
private String code;
|
||||
private String message;
|
||||
private ErrorCode errorCode;
|
||||
|
||||
private static final MessageUtil messageUtil = CoreSpringUtils.getMessageUtil();
|
||||
|
||||
/**
|
||||
* BizRuntimeException 생성자.
|
||||
* @param errorCode ErrorCode 지정
|
||||
*/
|
||||
public static BizRuntimeException create(ErrorCode errorCode) {
|
||||
return new BizRuntimeException(errorCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* BizRuntimeException 생성자.
|
||||
* @param errorCode ErrorCode 지정
|
||||
*/
|
||||
private BizRuntimeException(ErrorCode errorCode) {
|
||||
super(errorCode.getMessage());
|
||||
this.code = String.valueOf(errorCode.getHttpStatus().value());
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* BizRuntimeException 생성자.
|
||||
* @param errorCode 에러코드
|
||||
*/
|
||||
public static BizRuntimeException of(String errorCode) {
|
||||
BizRuntimeException e = new BizRuntimeException();
|
||||
e.setCode(errorCode);
|
||||
e.setMessage(messageUtil.getMessage(errorCode));
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
* BizRuntimeException 생성자.
|
||||
* @param defaultMessage 메세지 지정
|
||||
*/
|
||||
public static BizRuntimeException create(String defaultMessage) {
|
||||
return new BizRuntimeException(defaultMessage, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* BizRuntimeException 생성자.
|
||||
* @param code
|
||||
* @param defaultMessage 메세지 지정
|
||||
*/
|
||||
public static BizRuntimeException create(String code, String defaultMessage) {
|
||||
BizRuntimeException e = new BizRuntimeException();
|
||||
e.setCode(code);
|
||||
e.setMessage(defaultMessage);
|
||||
return e;
|
||||
}
|
||||
|
||||
public static BizRuntimeException create(String code, Object[] messageParameters) {
|
||||
BizRuntimeException e = new BizRuntimeException();
|
||||
e.setCode(code);
|
||||
e.setMessage(messageUtil.getMessage(code, messageParameters));
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
* BizRuntimeException 생성자.
|
||||
* @param wrappedException 원인 Exception
|
||||
*/
|
||||
public static BizRuntimeException create(Throwable wrappedException) {
|
||||
return new BizRuntimeException("BizRuntimeException without message", null, wrappedException);
|
||||
}
|
||||
|
||||
/**
|
||||
* BizRuntimeException 생성자.
|
||||
* @param defaultMessage 메세지 지정
|
||||
* @param wrappedException 원인 Exception
|
||||
*/
|
||||
public static BizRuntimeException create(String defaultMessage, Throwable wrappedException) {
|
||||
return new BizRuntimeException(defaultMessage, null, wrappedException);
|
||||
}
|
||||
|
||||
/**
|
||||
* BizRuntimeException 생성자.
|
||||
* @param defaultMessage 메세지 지정(변수지정)
|
||||
* @param messageParameters 치환될 메세지 리스트
|
||||
* @param wrappedException 원인 Exception
|
||||
*/
|
||||
private static BizRuntimeException create(String defaultMessage, Object[] messageParameters, Throwable wrappedException) {
|
||||
return new BizRuntimeException(defaultMessage, messageParameters, wrappedException);
|
||||
}
|
||||
|
||||
/**
|
||||
* BizRuntimeException 기본 생성자.
|
||||
*/
|
||||
private BizRuntimeException() {
|
||||
this("BizRuntimeException without message", null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* BizRuntimeException 생성자.
|
||||
* @param defaultMessage 메세지 지정(변수지정)
|
||||
* @param messageParameters 치환될 메세지 리스트
|
||||
* @param wrappedException 원인 Exception
|
||||
*/
|
||||
private BizRuntimeException(String defaultMessage, Object[] messageParameters, Throwable wrappedException) {
|
||||
super(wrappedException);
|
||||
if(messageParameters != null) {
|
||||
message = MessageFormat.format(defaultMessage, messageParameters);
|
||||
}else{
|
||||
message = defaultMessage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BizRuntimeException 생성자.
|
||||
* @param messageSource 메세지 리소스
|
||||
* @param messageKey 메세지키값
|
||||
*/
|
||||
public static BizRuntimeException create(MessageSource messageSource, String messageKey) {
|
||||
return new BizRuntimeException(messageSource, messageKey, null, null, Locale.getDefault(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* BizRuntimeException 생성자.
|
||||
* @param messageSource 메세지 리소스
|
||||
* @param messageKey 메세지키값
|
||||
*/
|
||||
public static BizRuntimeException create(MessageSource messageSource, String messageKey, Throwable wrappedException) {
|
||||
return new BizRuntimeException(messageSource, messageKey, null, null, Locale.getDefault(), wrappedException);
|
||||
}
|
||||
|
||||
/**
|
||||
* BizRuntimeException 생성자.
|
||||
* @param messageSource 메세지 리소스
|
||||
* @param messageKey 메세지키값
|
||||
* @param locale 국가/언어지정
|
||||
* @param wrappedException 원인 Exception
|
||||
*/
|
||||
public static BizRuntimeException create(MessageSource messageSource, String messageKey, Locale locale, Throwable wrappedException) {
|
||||
return new BizRuntimeException(messageSource, messageKey, null, null, locale, wrappedException);
|
||||
}
|
||||
|
||||
/**
|
||||
* BizRuntimeException 생성자.
|
||||
* @param messageSource 메세지 리소스
|
||||
* @param messageKey 메세지키값
|
||||
* @param messageParameters 치환될 메세지 리스트
|
||||
* @param locale 국가/언어지정
|
||||
* @param wrappedException 원인 Exception
|
||||
*/
|
||||
public static BizRuntimeException create(MessageSource messageSource, String messageKey, Object[] messageParameters, Locale locale, Throwable wrappedException) {
|
||||
return new BizRuntimeException(messageSource, messageKey, messageParameters, null, locale, wrappedException);
|
||||
}
|
||||
|
||||
/**
|
||||
* BizRuntimeException 생성자.
|
||||
* @param messageSource 메세지 리소스
|
||||
* @param messageKey 메세지키값
|
||||
* @param messageParameters 치환될 메세지 리스트
|
||||
* @param wrappedException 원인 Exception
|
||||
*/
|
||||
public static BizRuntimeException create(MessageSource messageSource, String messageKey, Object[] messageParameters, Throwable wrappedException) {
|
||||
return new BizRuntimeException(messageSource, messageKey, messageParameters, null, Locale.getDefault(), wrappedException);
|
||||
}
|
||||
|
||||
/**
|
||||
* BizRuntimeException 생성자.
|
||||
* @param messageSource 메세지 리소스
|
||||
* @param messageKey 메세지키값
|
||||
* @param messageParameters 치환될 메세지 리스트
|
||||
* @param defaultMessage 메세지 지정(변수지정)
|
||||
* @param wrappedException 원인 Exception
|
||||
*/
|
||||
public static BizRuntimeException create(MessageSource messageSource, String messageKey, Object[] messageParameters, String defaultMessage, Throwable wrappedException) {
|
||||
return new BizRuntimeException(messageSource, messageKey, messageParameters, defaultMessage, Locale.getDefault(), wrappedException);
|
||||
}
|
||||
|
||||
public BizRuntimeException(MessageSource messageSource, String messageKey, Object[] messageParameters, String defaultMessage, Locale locale, Throwable wrappedException) {
|
||||
super(wrappedException);
|
||||
code = messageKey;
|
||||
message = messageSource.getMessage(messageKey, messageParameters, defaultMessage, locale);
|
||||
}
|
||||
|
||||
public HttpStatus getHttpStatus(){
|
||||
return HttpStatus.BAD_REQUEST;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package cokr.xit.ens.core.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import cokr.xit.ens.core.utils.ApiWebClientUtil;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : WebClient 4xx 에러 : 에러 핸들러에서 사용
|
||||
*
|
||||
* packageName : kr.xit.core.exception
|
||||
* fileName : ClientErrorException
|
||||
* author : limju
|
||||
* date : 2023-05-25
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-05-25 limju 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
* @see ApiWebClientUtil
|
||||
*/
|
||||
@Getter
|
||||
public class ClientErrorException extends RuntimeException {
|
||||
private final HttpStatus status;
|
||||
private final String body;
|
||||
|
||||
public ClientErrorException(HttpStatus status, String body) {
|
||||
this.status = status;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
package cokr.xit.ens.core.exception;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.reactive.function.client.WebClientRequestException;
|
||||
|
||||
import cokr.xit.ens.core.aop.ApiResponseDTO;
|
||||
import cokr.xit.ens.core.utils.Checks;
|
||||
import io.netty.channel.ConnectTimeoutException;
|
||||
import io.netty.handler.timeout.ReadTimeoutException;
|
||||
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description :
|
||||
*
|
||||
* packageName : kr.xit.core.exception
|
||||
* fileName : ErrorParse
|
||||
* author : limju
|
||||
* date : 2023-06-01
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-06-01 limju 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public class ErrorParse {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static ApiResponseDTO extractError(final Throwable e){
|
||||
String errCode = String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
String message = Checks.isNotNull(e) ? e.getLocalizedMessage() : StringUtils.EMPTY;
|
||||
HttpStatus httpStatus = null;
|
||||
|
||||
if(e instanceof BizRuntimeException) {
|
||||
BizRuntimeException be = (BizRuntimeException) e;
|
||||
return ApiResponseDTO.error(be.getCode(), be.getMessage(), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
if(e instanceof ClientErrorException) {
|
||||
ClientErrorException ce = (ClientErrorException) e;
|
||||
return ApiResponseDTO.error(String.valueOf(ce.getStatus()), ce.getBody(), ce.getStatus());
|
||||
}
|
||||
|
||||
if(e instanceof ServerErrorException) {
|
||||
ServerErrorException ce = (ServerErrorException) e;
|
||||
return ApiResponseDTO.error(String.valueOf(ce.getStatus()), ce.getBody(), ce.getStatus());
|
||||
}
|
||||
|
||||
// Async(React) Exception 처리
|
||||
if(e instanceof WebClientRequestException) {
|
||||
if (e.getCause() instanceof ConnectTimeoutException || e.getCause() instanceof ReadTimeoutException) {
|
||||
return getTimeoutException();
|
||||
}
|
||||
}
|
||||
|
||||
if(e instanceof ExecutionException) {
|
||||
if (e.getCause() instanceof WebClientRequestException) {
|
||||
message = e.getCause().getMessage();
|
||||
|
||||
// Timeout 에러
|
||||
if (e.getCause().getCause() instanceof ReadTimeoutException || e.getCause().getCause() instanceof ConnectTimeoutException) {
|
||||
return getTimeoutException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(e instanceof ReadTimeoutException){
|
||||
return getTimeoutException();
|
||||
}
|
||||
|
||||
// Async(React) Exception 처리
|
||||
if(e.getCause() instanceof WebClientRequestException){
|
||||
|
||||
// Timeout 에러
|
||||
if(e.getCause().getCause() instanceof ReadTimeoutException){
|
||||
return getTimeoutException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(Checks.isNotEmpty(e.getCause())) {
|
||||
message = e.getCause().getMessage();
|
||||
}
|
||||
|
||||
return ApiResponseDTO.error(errCode, message, httpStatus);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static ApiResponseDTO getTimeoutException(){
|
||||
return ApiResponseDTO.error(
|
||||
String.valueOf(HttpStatus.REQUEST_TIMEOUT.value()),
|
||||
HttpStatus.REQUEST_TIMEOUT.getReasonPhrase(),
|
||||
HttpStatus.REQUEST_TIMEOUT);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package cokr.xit.ens.core.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import cokr.xit.ens.core.utils.ApiWebClientUtil;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : WebClient 5xx 에러 : 에러 핸들러에서 사용
|
||||
*
|
||||
* packageName : kr.xit.core.exception
|
||||
* fileName : ServerErrorException
|
||||
* author : limju
|
||||
* date : 2023-05-25
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-05-25 limju 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
* @see ApiWebClientUtil
|
||||
*/
|
||||
@Getter
|
||||
public class ServerErrorException extends RuntimeException {
|
||||
private final HttpStatus status;
|
||||
private final String body;
|
||||
|
||||
public ServerErrorException(HttpStatus status, String body) {
|
||||
this.status = status;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package cokr.xit.ens.core.exception.code;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : HttpStatus 기준 에러 코드 정의
|
||||
* packageName : kr.xit.core.const
|
||||
* fileName : ErrorCode
|
||||
* author : julim
|
||||
* date : 2023-04-28
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-04-28 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
* @see HttpStatus
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
|
||||
public enum
|
||||
ErrorCode {
|
||||
|
||||
/*
|
||||
200 : OK, 요청 정상 처리
|
||||
201 : Created, 생성 요청 성공
|
||||
202 : Accepted, 비동기 요청 성공
|
||||
204 : No Content, 요청 정상 처리, 응답 데이터 없음.
|
||||
|
||||
실패
|
||||
400 : Bad Request, 요청이 부적절 할 때, 유효성 검증 실패, 필수 값 누락 등.
|
||||
401 : Unauthorized, 인증 실패, 로그인하지 않은 사용자 또는 권한 없는 사용자 처리
|
||||
402 : Payment Required
|
||||
403 : Forbidden, 인증 성공 그러나 자원에 대한 권한 없음. 삭제, 수정시 권한 없음.
|
||||
404 : Not Found, 요청한 URI에 대한 리소스 없을 때 사용.
|
||||
405 : Method Not Allowed, 사용 불가능한 Method를 이용한 경우.
|
||||
406 : Not Acceptable, 요청된 리소스의 미디어 타입을 제공하지 못할 때 사용.
|
||||
408 : Request Timeout
|
||||
409 : Conflict, 리소스 상태에 위반되는 행위 시 사용.
|
||||
413 : Payload Too Large
|
||||
423 : Locked
|
||||
428 : Precondition Required
|
||||
429 : Too Many Requests
|
||||
|
||||
500 : 서버 에러
|
||||
|
||||
*/
|
||||
|
||||
|
||||
BAD_REQUEST(HttpStatus.BAD_REQUEST, "요청 매개변수 오류 입니다."),
|
||||
NOT_FOUND(HttpStatus.NOT_FOUND, "잘못된 요청 입니다"),
|
||||
FILE_NOT_FOUND(HttpStatus.NOT_FOUND, "파일이 존재하지 않습니다"),
|
||||
DATA_NOT_FOUND(HttpStatus.NOT_FOUND, "처리 데이타 오류(처리 요청 데이타 미존재)"),
|
||||
|
||||
/* 400 BAD_REQUEST : 잘못된 요청 */
|
||||
CANNOT_FOLLOW_MYSELF(HttpStatus.BAD_REQUEST, "자기 자신은 팔로우 할 수 없습니다"),
|
||||
|
||||
/* 401 UNAUTHORIZED : 인증되지 않은 사용자 */
|
||||
INVALID_AUTH_TOKEN(HttpStatus.FORBIDDEN, "인가된 사용자가 아닙니다"),
|
||||
UN_AUTHORIZED_USER(HttpStatus.UNAUTHORIZED, "계정 정보가 존재하지 않습니다"),
|
||||
AUTH_HEADER_NOT_EXISTS(HttpStatus.UNAUTHORIZED, "헤더에 인증 정보를 찾을 수 없습니다"),
|
||||
LOGOUT_USER(HttpStatus.UNAUTHORIZED, "로그아웃된 사용자 입니다"),
|
||||
NOT_EXISTS_SECURITY_AUTH(HttpStatus.UNAUTHORIZED, "Security Context 에 인증 정보가 없습니다"),
|
||||
|
||||
NOT_EXISTS_TOKEN(HttpStatus.UNAUTHORIZED, "인증된 토큰이 없습니다"),
|
||||
NOT_EXISTS_SAVED_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "저장된 인증 토큰이 없습니다"),
|
||||
INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "유효한 토큰이 아닙니다"),
|
||||
INVALID_ROLE_TOKEN(HttpStatus.UNAUTHORIZED, "사용 권한이 없는 토큰 입니다"),
|
||||
EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "유효기간이 경과된 토큰 입니다"),
|
||||
INVALID_SIGN_TOKEN(HttpStatus.UNAUTHORIZED, "잘못된 서명의 토큰 입니다"),
|
||||
INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "리프레시 토큰이 유효하지 않습니다"),
|
||||
MISMATCH_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "리프레시 토큰의 유저 정보가 일치하지 않습니다"),
|
||||
MISMATCH_REFRESH_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "발급된 refresh token의 정보가 일치하지 않습니다"),
|
||||
NOT_EXPIRED_TOKEN_YET(HttpStatus.UNAUTHORIZED, "토큰 유효기간이 경과되지 않았습니다"),
|
||||
|
||||
/* 404 NOT_FOUND : Resource 를 찾을 수 없음 */
|
||||
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자 정보를 찾을 수 없습니다"),
|
||||
REFRESH_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "로그아웃 된 사용자입니다"),
|
||||
|
||||
/* 409 CONFLICT : Resource 의 현재 상태와 충돌. 보통 중복된 데이터 존재 */
|
||||
DUPLICATE_RESOURCE(HttpStatus.CONFLICT, "데이터가 이미 존재합니다"),
|
||||
MEMBER_EXISTS(HttpStatus.CONFLICT, "가입되어 있는 회원 입니다"),
|
||||
|
||||
// JPA query error
|
||||
SQL_DATA_RESOURCE_INVALID(HttpStatus.CONFLICT, "SQL 오류(데이터가 이미 존재합니다)"),
|
||||
|
||||
MPOWER_CONNECT_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "MPower DB 접속 에러 입니다"),
|
||||
MPOWER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "MPower DB 에러 입니다"),
|
||||
|
||||
FORBIDDEN(HttpStatus.FORBIDDEN, "FORBIDDEN"),
|
||||
INVALID_CODE(HttpStatus.BAD_REQUEST, "유효하지 않은 HttpStatus 상태 코드 입니다"),
|
||||
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "INTERNAL_SERVER_ERROR"),
|
||||
|
||||
MISMATCH_PASSWORD(HttpStatus.BAD_REQUEST, "비밀 번호가 일치하지 않습니다."),
|
||||
|
||||
CONNECT_TIMEOUT(HttpStatus.REQUEST_TIMEOUT, "서버 접속 에러입니다(Connect timeout)")
|
||||
;
|
||||
|
||||
|
||||
private HttpStatus httpStatus;
|
||||
private String message;
|
||||
|
||||
ErrorCode(HttpStatus httpStatus, String message){
|
||||
this.httpStatus = httpStatus;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
@ -0,0 +1,253 @@
|
||||
package cokr.xit.ens.core.utils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.MultipartBodyBuilder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import cokr.xit.ens.core.aop.ApiResponseDTO;
|
||||
import cokr.xit.ens.core.config.support.WebClientConfig;
|
||||
import cokr.xit.ens.core.exception.ClientErrorException;
|
||||
import cokr.xit.ens.core.exception.ErrorParse;
|
||||
import cokr.xit.ens.modules.kkotalk.model.CmmEnsRlaybsnmDTO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : react Restfull Util
|
||||
* error(.onStatus)는 ExchangeFilterFunction {@link WebClientConfig responseFilter} 에서 처리
|
||||
* packageName : kr.xit.core.spring.util
|
||||
* fileName : ApiWebClientUtil
|
||||
* author : julim
|
||||
* date : 2023-09-06
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-09-06 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
* @see WebClientConfig
|
||||
* @see kr.xit.core.spring.config.AsyncExecutorConfig
|
||||
* @see ClientErrorException
|
||||
* @see ServerErrorException
|
||||
* @see ErrorParse
|
||||
* @see kr.xit.core.spring.config.support.CustomJacksonConfig
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ApiWebClientUtil {
|
||||
private static final String AUTH_TYPE_BEARER = "bearer";
|
||||
private final WebClientConfig webClientConfig;
|
||||
|
||||
/**
|
||||
* WebClient GET 호출 처리
|
||||
* 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
|
||||
* -> {@link WebClientConfig responseFilter} 에서 처리
|
||||
* @param url String
|
||||
* @param responseDtoClass Class<T>
|
||||
* @param headerMap Map<String, String>
|
||||
* @return responseDtoClass
|
||||
*/
|
||||
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()
|
||||
.bodyToMono(responseDtoClass)
|
||||
.block();
|
||||
}
|
||||
|
||||
/**
|
||||
* WebClient POST 호출 처리
|
||||
* 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
|
||||
* -> {@link WebClientConfig responseFilter} 에서 처리
|
||||
* @param url String
|
||||
* @param requestDto V
|
||||
* @param responseDtoClass Class<T>
|
||||
* @param headerMap Map<String, String>
|
||||
* @return responseDtoClass
|
||||
*/
|
||||
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 != null ? requestDto : "")
|
||||
.retrieve()
|
||||
.bodyToMono(responseDtoClass)
|
||||
.block();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||
map.put(HttpHeaders.AUTHORIZATION, String.format("KakaoAK %s", ensDTO.getKakaoDealerRestApiKey()));
|
||||
map.put("Target-Authorization", String.format("KakaoAK %s", ensDTO.getKakaoPartnerRestApiKey()));
|
||||
map.put("settle-Id", 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) {
|
||||
|
||||
return webClientConfig.webClient()
|
||||
.method(method)
|
||||
.uri(url)
|
||||
.headers(httpHeaders -> getHeaders(httpHeaders, headerMap))
|
||||
.bodyValue(body != null ? body : "")
|
||||
.exchangeToMono(res -> res.bodyToMono(rtnClzz))
|
||||
.block();
|
||||
}
|
||||
|
||||
/**
|
||||
* <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 webClientConfig.webClient()
|
||||
.method(method)
|
||||
.uri(url)
|
||||
.headers(httpHeaders -> getHeaders(httpHeaders, headerMap))
|
||||
.body(ObjectUtils.isNotEmpty(body)? BodyInserters.fromFormData(JsonUtils.toMultiValue(body)): BodyInserters.empty())
|
||||
.exchangeToMono(res -> res.bodyToMono(rtnClzz))
|
||||
.block();
|
||||
}
|
||||
|
||||
/**
|
||||
* webclient file data 호출 처리
|
||||
* -> multipart/form-data 전송시
|
||||
* url에 파라메터를 포함해야 함(?key=value&key2=value2)
|
||||
* 에러(.onStatus status.is4xxClientError() || status.is5xxServerError())
|
||||
* -> WebClientConfig.responseFilter() 에서 처리
|
||||
* @param url
|
||||
* @param method
|
||||
* @param files
|
||||
* @param rtnClzz
|
||||
* @return rtnClzz
|
||||
*/
|
||||
public <T> T exchangeFileData(final String url, final HttpMethod method, final List<MultipartFile> files, final String pFileName, final Class<T> rtnClzz) {
|
||||
MultipartBodyBuilder builder = new MultipartBodyBuilder();
|
||||
for(MultipartFile mf : files) {
|
||||
//builder.part(mf.getOriginalFilename().split("\\.")[0], mf.getResource());
|
||||
builder.part(pFileName, mf.getResource());
|
||||
}
|
||||
|
||||
return webClientConfig.webClient()
|
||||
.method(method)
|
||||
.uri(url)
|
||||
.contentType(MediaType.MULTIPART_FORM_DATA)
|
||||
.body(BodyInserters.fromMultipartData(builder.build()))
|
||||
.exchangeToMono(res -> res.bodyToMono(rtnClzz))
|
||||
.block();
|
||||
// .blockOptional()
|
||||
// .orElse("");
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package cokr.xit.ens.core.utils;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : Get Bean Object
|
||||
* Filter / Interceptor 등에서 Bean 사용시 필요
|
||||
* (Bean으로 등록되는 클래스 내에서만 @Autowired / @Resource 등이 동작)
|
||||
* packageName : kr.xit.core.spring.util
|
||||
* fileName : CoreSpringUtils
|
||||
* author : julim
|
||||
* date : 2023-04-28
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-04-28 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
* @see ApplicationContextProvider
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class CoreSpringUtils {
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
return ApplicationContextProvider.getApplicationContext();
|
||||
}
|
||||
|
||||
public static boolean containsBean(String beanName) {
|
||||
return getApplicationContext().containsBean(beanName);
|
||||
}
|
||||
|
||||
public static Object getBean(String beanName) {
|
||||
return getApplicationContext().getBean(beanName);
|
||||
}
|
||||
|
||||
public static Object getBean(Class<?> clazz) {
|
||||
return getApplicationContext().getBean(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return MessageSourceAccessor
|
||||
*/
|
||||
public static MessageUtil getMessageUtil(){
|
||||
return (MessageUtil)getBean(MessageUtil.class);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return MessageSourceAccessor
|
||||
*/
|
||||
public static MessageSource getMessageSource(){
|
||||
return (MessageSource)getBean(MessageSource.class);
|
||||
}
|
||||
|
||||
public static Environment getEnvironment(){
|
||||
return (Environment)getBean(Environment.class);
|
||||
}
|
||||
|
||||
public static ObjectMapper getObjectMapper(){
|
||||
return (ObjectMapper)getBean(ObjectMapper.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,339 @@
|
||||
package cokr.xit.ens.core.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import cokr.xit.ens.core.config.support.CustomJacksonConfig;
|
||||
import cokr.xit.ens.core.exception.BizRuntimeException;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : JSON Utility
|
||||
*
|
||||
* packageName : kr.xit.core.support.utils
|
||||
* fileName : JsonUtils
|
||||
* author : limju
|
||||
* date : 2023-09-04
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-09-04 limju 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
* @see CustomJacksonConfig
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class JsonUtils {
|
||||
|
||||
private static final ObjectMapper OM = CoreSpringUtils.getObjectMapper();
|
||||
|
||||
/**
|
||||
* json string 인지 판별
|
||||
* @param str String
|
||||
* @return boolean
|
||||
*/
|
||||
public static boolean isJson(final String str) {
|
||||
try {
|
||||
OM.readTree(str);
|
||||
return true;
|
||||
} catch (JsonProcessingException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Object -> json string
|
||||
* @return String
|
||||
* @param obj Object
|
||||
*/
|
||||
public static String toJson(final Object obj) {
|
||||
try {
|
||||
return ObjectUtils.isNotEmpty(obj)? OM.writeValueAsString(obj) : null;
|
||||
} catch (JsonProcessingException e) {
|
||||
throw BizRuntimeException.create(e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Json string -> class로 변환
|
||||
* @return T
|
||||
* @param str String
|
||||
* @param cls Class
|
||||
*/
|
||||
public static <T> T toObject(final String str, final Class<T> cls) {
|
||||
try {
|
||||
return ObjectUtils.isNotEmpty(str)? OM.readValue(str, cls) : null;
|
||||
} catch (JsonProcessingException e) {
|
||||
throw BizRuntimeException.create(e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Object -> class로 변환
|
||||
* @param obj Object
|
||||
* @param cls Class
|
||||
* @return T
|
||||
*/
|
||||
public static <T> T toObjByObj(final Object obj, final Class<T> cls) {
|
||||
try {
|
||||
return ObjectUtils.isNotEmpty(obj)? OM.convertValue(obj, cls) : null;
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw BizRuntimeException.create(e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* xml String -> cls
|
||||
* @param xml String
|
||||
* @param cls Class
|
||||
* @return cls Class
|
||||
*/
|
||||
public static <T> T toObjByXml(String xml, final Class<T> cls){
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder builder = null;
|
||||
try {
|
||||
builder = factory.newDocumentBuilder();
|
||||
Document document = builder.parse(new InputSource(new StringReader(xml)));
|
||||
return toObject(OM.writeValueAsString(document), cls);
|
||||
} catch (ParserConfigurationException | SAXException | IOException e) {
|
||||
throw BizRuntimeException.create(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Json string -> class list로 변환
|
||||
* @return T
|
||||
* @param str
|
||||
* @param cls
|
||||
* @throws IOException
|
||||
*/
|
||||
public static <T> List<T> toObjectList(final String str, final Class<T> cls) {
|
||||
if(ObjectUtils.isNotEmpty(str)){
|
||||
try {
|
||||
return OM.readValue(str, OM.getTypeFactory().constructCollectionType(List.class, cls));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw BizRuntimeException.create(e.getLocalizedMessage());
|
||||
}
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 문자열 -> Map 구조체로 변환
|
||||
* @param str String str
|
||||
* @return Map
|
||||
*/
|
||||
public static Map<String, Object> toMap(final String str) {
|
||||
try {
|
||||
return ObjectUtils.isNotEmpty(str)? OM.readValue(str, new TypeReference<Map<String, Object>>(){}) : null;
|
||||
} catch (JsonProcessingException e) {
|
||||
throw BizRuntimeException.create(e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Object
|
||||
* -> MultiValueMap<String, String> return
|
||||
* @param obj Object
|
||||
* @return MultiValueMap<String, String>
|
||||
*/
|
||||
public static MultiValueMap<String, String> toMultiValue(final Object obj){
|
||||
if(ObjectUtils.isEmpty(obj)) return null;
|
||||
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
|
||||
JSONObject jsonObj = toObjByObj(obj, JSONObject.class);
|
||||
for (Object key : jsonObj.keySet()) {
|
||||
formData.add((String) key, (String) jsonObj.get(key));
|
||||
}
|
||||
return formData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Object의 key와 value를 추출
|
||||
* -> key배열, value배열의 JSONObject return
|
||||
* @param obj key, value를 추출할 Object
|
||||
* @param keyName key배열의 JSON key name
|
||||
* @param valueName value배열의 JSON key name
|
||||
* @return JSONObject key배열, value배열의 JSONObject
|
||||
*/
|
||||
public static JSONObject extractObjKeyValue(final Object obj, final String keyName, final String valueName){
|
||||
return extractJsonKeyValue(toObjByObj(obj, JSONObject.class), keyName, valueName);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSONObject의 key와 value를 추출
|
||||
* -> key배열, value배열의 JSONObject return
|
||||
* @param json
|
||||
* @param keyName key배열의 JSON key name
|
||||
* @param valueName value배열의 JSON key name
|
||||
* @return JSONObject key배열, value배열의 JSONObject
|
||||
*/
|
||||
public static JSONObject extractJsonKeyValue(final JSONObject json, final String keyName, final String valueName){
|
||||
final JSONObject rtnJson = new JSONObject();
|
||||
final JSONArray keys = new JSONArray();
|
||||
final JSONArray values = new JSONArray();
|
||||
for (Object key : json.keySet()) {
|
||||
Object value = json.get(key);
|
||||
|
||||
if (value instanceof JSONObject) {
|
||||
JSONObject jo = (JSONObject) value;
|
||||
extractJsonKeyValue(jo, keyName, valueName);
|
||||
} else if (value instanceof JSONArray) {
|
||||
JSONArray ja = (JSONArray) value;
|
||||
ja.forEach(obj -> extractJsonKeyValue((JSONObject)obj, keyName, valueName));
|
||||
} else{
|
||||
//System.out.println(key + ", " + value);
|
||||
keys.add(String.valueOf(key));
|
||||
values.add(value);
|
||||
}
|
||||
}
|
||||
rtnJson.put(keyName, keys);
|
||||
rtnJson.put(valueName, values);
|
||||
return rtnJson;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSONArray의 key와 value를 추출
|
||||
* -> key배열, value배열의 JSONObject return
|
||||
* @param jsons JSONArray
|
||||
* @param keyName key배열의 JSON key name
|
||||
* @param valueName value배열의 JSON key name
|
||||
* @return JSONObject key배열, value배열의 JSONObject
|
||||
*/
|
||||
public static JSONObject extractJsonArrayKeyValue(final net.sf.json.JSONArray jsons, final String keyName, final String valueName){
|
||||
final JSONObject rtnJson = new JSONObject();
|
||||
final JSONArray keys = new JSONArray();
|
||||
final JSONArray values = new JSONArray();
|
||||
for(int i = 0; i<jsons.size(); i++){
|
||||
JSONObject json = extract(jsons.getJSONObject(i), keyName, valueName);
|
||||
if(i == 0) rtnJson.put(keyName, json.get(keyName));
|
||||
values.add(json.get(valueName));
|
||||
}
|
||||
rtnJson.put(valueName, values);
|
||||
return rtnJson;
|
||||
}
|
||||
|
||||
private static JSONObject extract(net.sf.json.JSONObject json, String keyName, String valueName) {
|
||||
final JSONObject rtnJson = new JSONObject();
|
||||
final JSONArray keys = new JSONArray();
|
||||
final JSONArray values = new JSONArray();
|
||||
for (Object key : json.keySet()) {
|
||||
Object value = json.get(key);
|
||||
|
||||
if (value instanceof JSONObject) {
|
||||
JSONObject jo = (JSONObject) value;
|
||||
extractJsonKeyValue(jo, keyName, valueName);
|
||||
} else if (value instanceof JSONArray) {
|
||||
JSONArray ja = (JSONArray) value;
|
||||
ja.forEach(obj -> extractJsonKeyValue((JSONObject)obj, keyName, valueName));
|
||||
} else {
|
||||
//System.out.println(key + ", " + value);
|
||||
keys.add(String.valueOf(key));
|
||||
values.add(value);
|
||||
}
|
||||
}
|
||||
rtnJson.put(keyName, keys);
|
||||
rtnJson.put(valueName, values);
|
||||
return rtnJson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Json 데이터 보기 좋게 변환.
|
||||
* @param obj Object json
|
||||
* @return String
|
||||
*/
|
||||
public static String jsonEnterConvert(final Object obj) {
|
||||
|
||||
try {
|
||||
return jsonEnterConvert((JsonUtils.toJson(obj)));
|
||||
} catch(Exception e) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Json 데이터 보기 좋게 변환.
|
||||
* @param json String json
|
||||
* @return String
|
||||
*/
|
||||
private static String jsonEnterConvert(String json) {
|
||||
|
||||
if( json == null || json.length() < 2 )
|
||||
return json;
|
||||
|
||||
final int len = json.length();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
char c;
|
||||
String tab = "";
|
||||
boolean beginEnd = true;
|
||||
for( int i=0 ; i<len ; i++ ) {
|
||||
c = json.charAt(i);
|
||||
switch( c ) {
|
||||
case '{': case '[':{
|
||||
sb.append( c );
|
||||
if( beginEnd ){
|
||||
tab += "\t";
|
||||
sb.append("\n");
|
||||
sb.append( tab );
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '}': case ']':{
|
||||
if( beginEnd ){
|
||||
tab = tab.substring(0, tab.length()-1);
|
||||
sb.append("\n");
|
||||
sb.append( tab );
|
||||
}
|
||||
sb.append( c );
|
||||
break;
|
||||
}
|
||||
case '"':{
|
||||
if( json.charAt(i-1)!='\\' )
|
||||
beginEnd = ! beginEnd;
|
||||
sb.append( c );
|
||||
break;
|
||||
}
|
||||
case ',':{
|
||||
sb.append( c );
|
||||
if( beginEnd ){
|
||||
sb.append("\n");
|
||||
sb.append( tab );
|
||||
}
|
||||
break;
|
||||
}
|
||||
default :{
|
||||
sb.append( c );
|
||||
}
|
||||
} // switch end
|
||||
|
||||
}
|
||||
if( sb.length() > 0 )
|
||||
sb.insert(0, '\n');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,64 @@
|
||||
package cokr.xit.ens.core.utils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : Spring MessageSource를 통한 메세지 read
|
||||
*
|
||||
* packageName : kr.xit.core.spring.util
|
||||
* fileName : MessageUtil
|
||||
* author : julim
|
||||
* date : 2023-04-28
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-04-28 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Component
|
||||
public class MessageUtil {
|
||||
|
||||
@Resource(name = "messageSource")
|
||||
private MessageSource messageSource;
|
||||
|
||||
/**
|
||||
* messageSource 에 코드값을 넘겨 메시지를 찾아 리턴한다.
|
||||
*
|
||||
* @param code
|
||||
* @return
|
||||
*/
|
||||
public String getMessage(String code) {
|
||||
return this.getMessage(code, new Object[]{});
|
||||
}
|
||||
|
||||
/**
|
||||
* messageSource 에 코드값과 인자를 넘겨 메시지를 찾아 리턴한다.
|
||||
*
|
||||
* @param code
|
||||
* @param args
|
||||
* @return
|
||||
*/
|
||||
public String getMessage(String code, Object[] args) {
|
||||
return this.getMessage(code, args, LocaleContextHolder.getLocale());
|
||||
}
|
||||
|
||||
/**
|
||||
* messageSource 에 코드값, 인자, 지역정보를 넘겨 메시지를 찾아 리턴한다.
|
||||
*
|
||||
* @param code
|
||||
* @param args
|
||||
* @param locale
|
||||
* @return
|
||||
*/
|
||||
public String getMessage(String code, Object[] args, Locale locale) {
|
||||
return messageSource.getMessage(code, args, locale);
|
||||
}
|
||||
}
|
@ -0,0 +1,393 @@
|
||||
package cokr.xit.ens.modules.kkotalk;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import cokr.xit.ens.core.exception.BizRuntimeException;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description :
|
||||
*
|
||||
* packageName : kr.xit.ens.support.common
|
||||
* fileName : KakaoConstants
|
||||
* author : limju
|
||||
* date : 2023-05-04
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-05-04 limju 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public class ApiConstants {
|
||||
|
||||
/**
|
||||
* profile
|
||||
*/
|
||||
public static final String PROFILE = System.getProperty("spring.profiles.active");
|
||||
/**
|
||||
* profile : local 여부
|
||||
*/
|
||||
public static final boolean IS_PROFILE_LOCAL = PROFILE.matches("local-.*");
|
||||
|
||||
/**
|
||||
* profile : 춘천 시스템 여부
|
||||
*/
|
||||
public static final boolean IS_CCN = PROFILE.matches(".*-ccn");
|
||||
public static final String FFNLN_CODE = "11";
|
||||
|
||||
/**
|
||||
* 구분자 없는 date-time 포맷
|
||||
*/
|
||||
public static final String FMT_DT_EMPTY_DLT = "yyyyMMddHHmmss";
|
||||
|
||||
/**
|
||||
* date-time 표준 포맷
|
||||
*/
|
||||
public static final String FMT_DT_STD = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서 조회 버튼의 명칭을 구분하기 위한 값
|
||||
* code(실제 파라미터로 보내는 값) : button_name(내문서함 내부에 표기되는 버튼 명칭)- name(내문서함 내부에서 구분하기 위한 명칭)
|
||||
* Default : 문서확인 - Default
|
||||
* BILL : 문서확인 - 고지서
|
||||
* BILL_PAY : 문서확인후 납부 - 납부가 포함된 고지서
|
||||
* NOTICE : 문서확인 - 안내문
|
||||
* CONTRACT : 문서확인 - 계약서
|
||||
* REPORT : 문서확인 - 리포트
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
public enum Categories {
|
||||
DEFAULT("Default")
|
||||
, BILL("BILL")
|
||||
, BILL_PAY("BILL_PAY")
|
||||
, NOTICE("NOTICE")
|
||||
, CONTRACT("CONTRACT")
|
||||
, REPORT("REPORT")
|
||||
;
|
||||
|
||||
private final String code;
|
||||
|
||||
Categories(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* INVALID_VALUE : http status code : 400
|
||||
* 파라미터가 형식에 맞지않음 혹은 필수파라미터 누락
|
||||
* {"error_code": "INVALID_VALUE", "error_message": "유효하지 않은 값입니다."
|
||||
* UNIDENTIFIED_USER : http status code : 400
|
||||
* 받는이의 정보로 발송대상을 특정 할 수 없을때
|
||||
* {"error_code": "INVALID_VALUE", "error_message": "유효하지 않은 값입니다."
|
||||
* UNAUTHORIZED : http status code : 401
|
||||
* access token이 유효하지 않거나 잘못된 경우
|
||||
* {"error_code": "UNAUTHORIZED","error_message": "접근 권한이 없습니다."
|
||||
* FORBIDDEN : http status code : 403
|
||||
* 문서의 대상자가 내문서함에서 수신거부를 한 경우
|
||||
* {"error_code": "FORBIDDEN","error_message": "허용되지 않는 요청입니다. 수신거부된 사용자 입니다."}
|
||||
* NOT_FOUND : http status code : 404
|
||||
* "Contract-Uuid" or "document_binder_uuid"가 유효하지 않거나 잘못된 경우
|
||||
* {"error_code": "NOT_FOUND","error_message": "요청 정보를 찾을 수 없습니다."
|
||||
* INTERNAL_ERROR : http status code : 500
|
||||
* 카카오페이 서버에러
|
||||
* {"error_code": "INTERNAL_SERVER_ERROR","error_message": "서버 에러입니다. 다시 시도해 주세요."}
|
||||
* </pre>
|
||||
* 카카오페이 전자문서 발송 요청 에러 코드
|
||||
*/
|
||||
@Getter
|
||||
public enum Error {
|
||||
INVALID_VALUE("INVALID_VALUE")
|
||||
, UNIDENTIFIED_USER("UNIDENTIFIED_USER")
|
||||
, UNAUTHORIZED("UNAUTHORIZED")
|
||||
, FORBIDDEN("FORBIDDEN")
|
||||
, NOT_FOUND("NOT_FOUND")
|
||||
, INTERNAL_ERROR("INTERNAL_ERROR")
|
||||
;
|
||||
|
||||
private final String code;
|
||||
|
||||
Error(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 카카오페이 문서 상태
|
||||
* SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
public enum KkopayDocStatus {
|
||||
SENT("SENT")
|
||||
, RECEIVED("RECEIVED")
|
||||
, READ("READ")
|
||||
, EXPIRED("EXPIRED")
|
||||
;
|
||||
|
||||
private final String code;
|
||||
|
||||
KkopayDocStatus(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 카카오톡 문서 상태
|
||||
* RECEIVE(수신, 미열람) > READ(열람)/EXPIRED(최초열람만료일시 또는 재열람 만료일시 초과)
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
public enum KkotalkDocStatus {
|
||||
RECEIVED("RECEIVE")
|
||||
, READ("READ")
|
||||
, EXPIRED("EXPIRED")
|
||||
;
|
||||
|
||||
private final String code;
|
||||
|
||||
KkotalkDocStatus(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 발송처리상태 : ENS003
|
||||
*/
|
||||
@Getter
|
||||
public enum SndngProcessStatus {
|
||||
ACCEPT("accept"),
|
||||
ACCEPT_OK("accept-ok"),
|
||||
ACCEPT_FAIL("accept-fail"),
|
||||
MAKE_OK("make-ok"),
|
||||
MAKE_FAIL1("make-fail1"),
|
||||
MAKE_FAIL2("make-fail2"),
|
||||
MAKE_FAIL3("make-fail3"),
|
||||
SENDING1("sending1"),
|
||||
SENDING2("sending2"),
|
||||
SEND_OK("send-ok"),
|
||||
SEND_FAIL1("send-fail1"),
|
||||
SEND_FAIL2("send-fail2"),
|
||||
SEND_FAIL3("send-fail3"),
|
||||
CLOSE("close")
|
||||
|
||||
;
|
||||
|
||||
private final String code;
|
||||
|
||||
SndngProcessStatus(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 발송구분코드
|
||||
*/
|
||||
@Getter
|
||||
public enum SndngSeCode {
|
||||
KAKAO("KKO-MY-DOC", "카카오"),
|
||||
KAKAO_NEW("KKO-NEW", "카카오NEW"),
|
||||
KT_BC("KT-BC", "공공알림문자"),
|
||||
KT_GIBIS("KT-GIBIS", "GIBIS알림문자"),
|
||||
;
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
SndngSeCode(final String code, final String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public static SndngSeCode getSndngSeCode(final String code){
|
||||
return Arrays.stream(SndngSeCode.values())
|
||||
.filter(ssc -> ssc.getCode().equals(code))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> BizRuntimeException.create(String.format("미정의된 문서 중계자가[%s]", code)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SignguCode
|
||||
*/
|
||||
@Getter
|
||||
public enum SignguCode {
|
||||
/**
|
||||
* 춘천
|
||||
*/
|
||||
CHUNCHEON("51110"),
|
||||
/**
|
||||
* 천안동남구청
|
||||
*/
|
||||
CHEONAN_ES("44131"),
|
||||
/**
|
||||
* 천안서북
|
||||
*/
|
||||
CHEONAN_WN("44133"),
|
||||
;
|
||||
|
||||
private final String code;
|
||||
|
||||
SignguCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum NiceCiWrkDiv {
|
||||
TOKEN,
|
||||
PUBLIC_KEY,
|
||||
SYM_KEY,
|
||||
CI
|
||||
}
|
||||
|
||||
public enum KtServiceCode {
|
||||
SISUL,
|
||||
CHUMO;
|
||||
|
||||
public static KtServiceCode compare(final String en){
|
||||
return valueOf(en);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 춘천의 발송처리상태 : ENS003 매핑
|
||||
* 01: 요청, 11: 접수완료, 19: 접수실패
|
||||
* 22: 제작완료, 29: 제작실패, 08: 발송완료, 09: 마감, 99: 발송실패
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
public enum MappingCcnSndngProcessStatus {
|
||||
ACCEPT("accept", "01"),
|
||||
ACCEPT_OK("accept-ok", "11"),
|
||||
ACCEPT_FAIL("accept-fail", "19"),
|
||||
MAKE_OK("make-ok", "22"),
|
||||
MAKE_FAIL1("make-fail1", "29"),
|
||||
MAKE_FAIL2("make-fail2", "29"),
|
||||
MAKE_FAIL3("make-fail3", "29"),
|
||||
SENDING1("sending1", "08"),
|
||||
SENDING2("sending2", "08"),
|
||||
SEND_OK("send-ok", "08"),
|
||||
SEND_FAIL1("send-fail1", "99"),
|
||||
SEND_FAIL2("send-fail2", "99"),
|
||||
SEND_FAIL3("send-fail3", "99"),
|
||||
CLOSE("close", "09")
|
||||
|
||||
;
|
||||
|
||||
private final String ensCode;
|
||||
private final String trfCode;
|
||||
|
||||
MappingCcnSndngProcessStatus(String ensCode, String trfCode) {
|
||||
this.ensCode = ensCode;
|
||||
this.trfCode = trfCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* traffic 상태코드를 ens 상태코드로 매핑
|
||||
* @param trfCode
|
||||
* @return traffic 상태코드에 매핑된 ens 상태코드
|
||||
* </pre>
|
||||
*/
|
||||
public static String fromTraffic(final String trfCode){
|
||||
return Arrays.stream(MappingCcnSndngProcessStatus.values())
|
||||
.filter(en -> en.getTrfCode().equals(trfCode))
|
||||
.findFirst()
|
||||
.map(MappingCcnSndngProcessStatus::getEnsCode)
|
||||
.orElseThrow(() -> BizRuntimeException.create(String.format("미정의된 상태코드가[%s]", trfCode)));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* ens 상태코드를 traffic 상태코드로 매핑
|
||||
* @param ensCode
|
||||
* @return ens 상태코드에 매핑된 traffic 상태코드
|
||||
* </pre>
|
||||
*/
|
||||
public static String fromEns(final String ensCode){
|
||||
return Arrays.stream(MappingCcnSndngProcessStatus.values())
|
||||
.filter(en -> en.getEnsCode().equals(ensCode))
|
||||
.findFirst()
|
||||
.map(MappingCcnSndngProcessStatus::getTrfCode)
|
||||
.orElseThrow(() -> BizRuntimeException.create(String.format("미정의된 상태코드가[%s]", ensCode)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 천안의 발송처리상태 : ENS003 매핑
|
||||
* 01: 요청, 11: 접수완료, 19: 접수실패
|
||||
* 22: 제작완료, 29: 제작실패, 08: 발송완료, 09: 마감, 99: 발송실패
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
public enum MappingCanSndngProcessStatus {
|
||||
ACCEPT("accept", "accept"),
|
||||
ACCEPT_OK("accept-ok", "acptok"),
|
||||
ACCEPT_FAIL("accept-fail", "acptfail"),
|
||||
MAKE_OK("make-ok", "ensmake"),
|
||||
MAKE_FAIL1("make-fail1", "makefail"),
|
||||
MAKE_FAIL2("make-fail2", "makefail"),
|
||||
MAKE_FAIL3("make-fail3", "makefail"),
|
||||
SENDING1("sending1", "ensok"),
|
||||
SENDING2("sending2", "ensok"),
|
||||
SEND_OK("send-ok", "ensopen"),
|
||||
SEND_FAIL1("send-fail1", "ensfail"),
|
||||
SEND_FAIL2("send-fail2", "ensfail"),
|
||||
SEND_FAIL3("send-fail3", "ensfail"),
|
||||
CLOSE("close", "ensclose")
|
||||
|
||||
;
|
||||
|
||||
private final String ensCode;
|
||||
private final String trfCode;
|
||||
|
||||
MappingCanSndngProcessStatus(String ensCode, String trfCode) {
|
||||
this.ensCode = ensCode;
|
||||
this.trfCode = trfCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* traffic 상태코드를 ens 상태코드로 매핑
|
||||
* @param trfCode
|
||||
* @return traffic 상태코드에 매핑된 ens 상태코드
|
||||
* </pre>
|
||||
*/
|
||||
public static String fromTraffic(final String trfCode){
|
||||
return Arrays.stream(MappingCanSndngProcessStatus.values())
|
||||
.filter(en -> en.getTrfCode().equals(trfCode))
|
||||
.findFirst()
|
||||
.map(MappingCanSndngProcessStatus::getEnsCode)
|
||||
.orElseThrow(() -> BizRuntimeException.create(String.format("미정의된 상태코드가[%s]", trfCode)));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* ens 상태코드를 traffic 상태코드로 매핑
|
||||
* @param ensCode
|
||||
* @return ens 상태코드에 매핑된 traffic 상태코드
|
||||
* </pre>
|
||||
*/
|
||||
public static String fromEns(final String ensCode){
|
||||
return Arrays.stream(MappingCanSndngProcessStatus.values())
|
||||
.filter(en -> en.getEnsCode().equals(ensCode))
|
||||
.findFirst()
|
||||
.map(MappingCanSndngProcessStatus::getTrfCode)
|
||||
.orElseThrow(() -> BizRuntimeException.create(String.format("미정의된 상태코드가[%s]", ensCode)));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,285 @@
|
||||
// package cokr.xit.ens.modules.kkotalk;
|
||||
//
|
||||
// import java.nio.charset.StandardCharsets;
|
||||
// import java.security.InvalidAlgorithmParameterException;
|
||||
// import java.security.InvalidKeyException;
|
||||
// import java.security.KeyFactory;
|
||||
// import java.security.MessageDigest;
|
||||
// import java.security.NoSuchAlgorithmException;
|
||||
// import java.security.spec.InvalidKeySpecException;
|
||||
// import java.security.spec.X509EncodedKeySpec;
|
||||
// import java.util.ArrayList;
|
||||
// import java.util.Base64;
|
||||
// import java.util.List;
|
||||
// import java.util.Locale;
|
||||
// import java.util.Random;
|
||||
// import java.util.Set;
|
||||
// import java.util.UUID;
|
||||
//
|
||||
// import javax.crypto.BadPaddingException;
|
||||
// import javax.crypto.Cipher;
|
||||
// import javax.crypto.IllegalBlockSizeException;
|
||||
// import javax.crypto.Mac;
|
||||
// import javax.crypto.NoSuchPaddingException;
|
||||
// import javax.crypto.SecretKey;
|
||||
// import javax.crypto.spec.IvParameterSpec;
|
||||
// import javax.crypto.spec.SecretKeySpec;
|
||||
// import javax.validation.ConstraintViolation;
|
||||
// import javax.validation.Validation;
|
||||
// import javax.validation.Validator;
|
||||
//
|
||||
// import org.apache.commons.lang3.ObjectUtils;
|
||||
// import org.apache.commons.lang3.StringUtils;
|
||||
// import org.springframework.util.Base64Utils;
|
||||
//
|
||||
// import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ErrorMsg;
|
||||
//
|
||||
// import cokr.xit.ens.core.exception.BizRuntimeException;
|
||||
// import cokr.xit.ens.core.utils.CoreSpringUtils;
|
||||
// import cokr.xit.ens.core.utils.MessageUtil;
|
||||
// import cokr.xit.ens.modules.kkotalk.model.CmmEnsRequestDTO;
|
||||
// import cokr.xit.ens.modules.kkotalk.model.CmmEnsRlaybsnmDTO;
|
||||
// import lombok.AccessLevel;
|
||||
// import lombok.NoArgsConstructor;
|
||||
//
|
||||
// /**
|
||||
// * <pre>
|
||||
// * description : ENS 공통 method
|
||||
// *
|
||||
// * packageName : kr.xit.ens.cmm
|
||||
// * fileName : CmmNiceCiUtils
|
||||
// * author : limju
|
||||
// * date : 2023-09-19
|
||||
// * ======================================================================
|
||||
// * 변경일 변경자 변경 내용
|
||||
// * ----------------------------------------------------------------------
|
||||
// * 2023-09-19 limju 최초 생성
|
||||
// *
|
||||
// * </pre>
|
||||
// */
|
||||
//
|
||||
// @NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
// public class CmmEnsUtils {
|
||||
// private static final MessageUtil messageUtil = CoreSpringUtils.getMessageUtil();
|
||||
// private static final ICmmEnsCacheService cacheService = ApiSpringUtils.getCmmEnsCacheService();
|
||||
// private static final IBizKtBcService bizKtService = ApiSpringUtils.getBizKtMmsService();
|
||||
//
|
||||
// /**
|
||||
// * 문서 중개자 인증 정보 조회
|
||||
// * @param signguCode string
|
||||
// * @param ffnlgCode String
|
||||
// * @param seCode SndngSeCode 문서중개자 구분 코드
|
||||
// * @return CmmEnsRlaybsnmDTO 문서중개자 정보
|
||||
// */
|
||||
// public static CmmEnsRlaybsnmDTO getRlaybsnmInfo(final String signguCode, final String ffnlgCode, final
|
||||
// SndngSeCode seCode) {
|
||||
// CmmEnsRequestDTO ensDTO = CmmEnsRequestDTO.builder()
|
||||
// .signguCode(signguCode)
|
||||
// .ffnlgCode(ffnlgCode)
|
||||
// .profile(ApiConstants.IS_PROFILE_LOCAL? "local" : "prod")
|
||||
// .build();
|
||||
//
|
||||
// final CmmEnsRlaybsnmDTO dto = cacheService.getRlaybsnmInfoCache(ensDTO);
|
||||
// if(ObjectUtils.isEmpty(dto)) throw BizRuntimeException.create(messageUtil.getMessage("fail.api.rlaybsnm.info"));
|
||||
//
|
||||
// // KT인 경우 토큰유효기간 check
|
||||
// if(SndngSeCode.KT_BC.equals(seCode)){
|
||||
//
|
||||
// if(StringUtils.isNotEmpty(dto.getKtTokenExpiresIn())
|
||||
// && DateUtils.getTodayAndNowTime(ApiConstants.FMT_DT_STD).compareTo(dto.getKtTokenExpiresIn()) < 0
|
||||
// && ObjectUtils.isNotEmpty(dto.getKtAccessToken())
|
||||
// ) return dto;
|
||||
//
|
||||
// // 유효기간이 경과된 경우 재발급
|
||||
// bizKtService.requestToken(
|
||||
// KtMnsRequest.builder()
|
||||
// .signguCode(signguCode)
|
||||
// .ffnlgCode(ffnlgCode)
|
||||
// .profile(ApiConstants.IS_PROFILE_LOCAL? "local" : "prod")
|
||||
// .build()
|
||||
// );
|
||||
// return cacheService.getRlaybsnmInfoCache(ensDTO);
|
||||
// }
|
||||
// return dto;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * <pre>
|
||||
// * parameter validation check
|
||||
// * invalid parameter message -> String으로 BizRuntimeException throw
|
||||
// * @param t T
|
||||
// * </pre>
|
||||
// */
|
||||
// public static <T> void validate(T t) {
|
||||
// Locale.setDefault(Locale.KOREA);
|
||||
// final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||
// final Set<ConstraintViolation<T>> list = validator.validate(t);
|
||||
//
|
||||
// if (!list.isEmpty()) {
|
||||
// throw BizRuntimeException.create(
|
||||
// list.stream()
|
||||
// .map(row -> String.format("%s=%s", row.getPropertyPath(), row.getMessageTemplate()))
|
||||
// //.map(row -> String.format("%s=%s", row.getPropertyPath(), row.get()) ? row.getMessage(): row.getMessageTemplate()))
|
||||
// .toList().toString());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * parameter validation check
|
||||
// * @param t T
|
||||
// * @return List<ErrorMsg>
|
||||
// */
|
||||
// public static <T> List<ErrorMsg> getValidateErrors(T t) {
|
||||
// Locale.setDefault(Locale.KOREA);
|
||||
// final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||
// final Set<ConstraintViolation<T>> list = validator.validate(t);
|
||||
//
|
||||
// if (!list.isEmpty()) {
|
||||
// return list.stream()
|
||||
// .map(row -> new ErrorMsg(String.format("%s=%s", row.getPropertyPath(), row.getMessageTemplate())))
|
||||
// .toList();
|
||||
// }
|
||||
// return new ArrayList<>();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * length 길이의 UUID String return -> '-' remove
|
||||
// * @param length
|
||||
// * @return
|
||||
// */
|
||||
// public static String generateLengthUuid(int length) {
|
||||
// final String allChars = UUID.randomUUID().toString().replace("-", "");
|
||||
// final Random random = new Random();
|
||||
// final char[] otp = new char[length];
|
||||
// for (int i = 0; i < length; i++) {
|
||||
// otp[i] = allChars.charAt(random.nextInt(allChars.length()));
|
||||
// }
|
||||
// return String.valueOf(otp);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 공개키로 암호화를 수행
|
||||
// *
|
||||
// * @param publicKeyString String
|
||||
// * @param symkeyRegInfo String
|
||||
// * @return String
|
||||
// */
|
||||
// public static String encSymkeyRegInfo(String publicKeyString, String symkeyRegInfo) {
|
||||
// try {
|
||||
// KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
// byte[] cipherEnc = Base64.getDecoder().decode(publicKeyString);
|
||||
// X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(cipherEnc);
|
||||
// java.security.PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
|
||||
//
|
||||
// Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
||||
// cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
// byte[] bytePlain = cipher.doFinal(symkeyRegInfo.getBytes());
|
||||
//
|
||||
// return Base64Utils.encodeToString(bytePlain);
|
||||
// } catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException |
|
||||
// IllegalBlockSizeException | BadPaddingException | InvalidKeyException e){
|
||||
// throw BizRuntimeException.create(e.getMessage());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * sha256 암호화
|
||||
// *
|
||||
// * @param text String
|
||||
// * @return String
|
||||
// */
|
||||
// public static String hexSha256(final String text) {
|
||||
// final StringBuilder sbuf = new StringBuilder();
|
||||
//
|
||||
// try {
|
||||
// final MessageDigest mDigest = MessageDigest.getInstance("SHA-256");
|
||||
// mDigest.update(text.getBytes());
|
||||
//
|
||||
// final byte[] msgStr = mDigest.digest();
|
||||
//
|
||||
// for(final byte tmpStrByte : msgStr) {
|
||||
// final String tmpEncTxt = Integer.toString((tmpStrByte & 0xff) + 0x100, 16)
|
||||
// .substring(1);
|
||||
//
|
||||
// sbuf.append(tmpEncTxt);
|
||||
// }
|
||||
// } catch (NoSuchAlgorithmException nae){
|
||||
// throw BizRuntimeException.create(nae.getMessage());
|
||||
// }
|
||||
// return sbuf.toString();
|
||||
// }
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * <pre>
|
||||
// * AES 암호화 -> Base64 encoding return
|
||||
// * -> Nice ci 데이타 암호화
|
||||
// *
|
||||
// * @param key String
|
||||
// * @param iv String
|
||||
// * @param planText String
|
||||
// * @return String Base64 encoding data
|
||||
// * </pre>
|
||||
// */
|
||||
// public static String encodeAesData(final String key, final String iv, final String planText) {
|
||||
// final SecretKey secureKey = new SecretKeySpec(key.getBytes(), "AES");
|
||||
// try {
|
||||
// final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
// cipher.init(Cipher.ENCRYPT_MODE, secureKey, new IvParameterSpec(iv.getBytes()));
|
||||
//
|
||||
// final byte[] encData = cipher.doFinal(planText.trim().getBytes(StandardCharsets.UTF_8));
|
||||
// return Base64.getEncoder().encodeToString(encData);
|
||||
//
|
||||
// }catch (NoSuchPaddingException | NoSuchAlgorithmException |
|
||||
// InvalidAlgorithmParameterException | InvalidKeyException |
|
||||
// IllegalBlockSizeException | BadPaddingException e){
|
||||
// throw BizRuntimeException.create(e.getMessage());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * <pre>
|
||||
// * Hmac 무결성체크값(integrity_value) 생성
|
||||
// * @param hmacKey String
|
||||
// * @param message String
|
||||
// * @return String
|
||||
// * </pre>
|
||||
// */
|
||||
// public static String encodeHmacSha256(final String hmacKey, final String message) {
|
||||
// try {
|
||||
// final Mac mac = Mac.getInstance("HmacSHA256");
|
||||
// final SecretKeySpec sks = new SecretKeySpec(hmacKey.getBytes(), "HmacSHA256");
|
||||
// mac.init(sks);
|
||||
// final byte[] hmac256 = mac.doFinal(message.getBytes());
|
||||
// return Base64.getEncoder().encodeToString(hmac256);
|
||||
//
|
||||
// }catch (NoSuchAlgorithmException|InvalidKeyException e){
|
||||
// throw BizRuntimeException.create(e.getMessage());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * AES 복호화
|
||||
// * @param encData String
|
||||
// * @param key String
|
||||
// * @param iv String
|
||||
// * @return String
|
||||
// */
|
||||
// public static String decodeAesData(String encData, String key, String iv) {
|
||||
//
|
||||
// final byte[] respDataEnc = Base64.getDecoder().decode(encData.getBytes());
|
||||
// final SecretKey secureKey = new SecretKeySpec(key.getBytes(), "AES");
|
||||
//
|
||||
// try {
|
||||
// final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
// cipher.init(Cipher.DECRYPT_MODE, secureKey, new IvParameterSpec(iv.getBytes()));
|
||||
// final byte[] decrypted = cipher.doFinal(respDataEnc);
|
||||
// return new String(decrypted);
|
||||
//
|
||||
// }catch (NoSuchPaddingException | NoSuchAlgorithmException |
|
||||
// InvalidAlgorithmParameterException | InvalidKeyException |
|
||||
// IllegalBlockSizeException | BadPaddingException e){
|
||||
// throw BizRuntimeException.create(e.getMessage());
|
||||
// }
|
||||
// }
|
||||
// }
|
@ -0,0 +1,67 @@
|
||||
package cokr.xit.ens.modules.kkotalk.model;
|
||||
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema.RequiredMode;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : 전자고지 문서중계자 Request 공통 DTO
|
||||
*
|
||||
* packageName : kr.xit.biz.ens.model.kt
|
||||
* fileName : CmmEnsRequestDTO
|
||||
* author : limju
|
||||
* date : 2023-09-22
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-09-22 limju 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public class CmmEnsRequestDTO {
|
||||
|
||||
/**
|
||||
* 시군구 코드
|
||||
*/
|
||||
@Schema(requiredMode = RequiredMode.REQUIRED, title = "시군구코드", example = "51110")
|
||||
@Size(min = 1, max = 10, message = "시군구 코드는 필수 입니다")
|
||||
@JsonProperty("signguCode")
|
||||
private String signguCode;
|
||||
|
||||
/**
|
||||
* 과태료 코드
|
||||
*/
|
||||
@Schema(requiredMode = RequiredMode.REQUIRED, title = "과태료코드", example = "11")
|
||||
@Size(min = 1, max = 2, message = "과태료 코드는 필수 입니다")
|
||||
@JsonProperty("ffnlgCode")
|
||||
private String ffnlgCode = "11";
|
||||
|
||||
/**
|
||||
* active profile
|
||||
*/
|
||||
@Schema(requiredMode = RequiredMode.AUTO, title = "profile", example = "local")
|
||||
@JsonProperty("profile")
|
||||
private String profile;
|
||||
|
||||
/**
|
||||
* 1차 발송
|
||||
*/
|
||||
@Schema(hidden = true, requiredMode = RequiredMode.AUTO, title = "1차 발송", example = "KKO-MY-DOC")
|
||||
@JsonProperty("try1")
|
||||
private String try1;
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
package cokr.xit.ens.modules.kkotalk.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : 전자고지 문서중계자 정보 DTO
|
||||
*
|
||||
* packageName : kr.xit.biz.ens.model.kt
|
||||
* fileName : KtMmsDTO
|
||||
* author : limju
|
||||
* date : 2023-09-22
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-09-22 limju 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class CmmEnsRlaybsnmDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 시군구 코드
|
||||
*/
|
||||
private String signguCode;
|
||||
/**
|
||||
* 과태료 코드
|
||||
*/
|
||||
private String ffnlgCode;
|
||||
/**
|
||||
* profile
|
||||
*/
|
||||
private String profile;
|
||||
/**
|
||||
* 시군구 명
|
||||
*/
|
||||
private String signguNm;
|
||||
/**
|
||||
* 과태료 명
|
||||
*/
|
||||
private String ffnlgNm;
|
||||
/**
|
||||
* KAKAO CLIENT ID
|
||||
*/
|
||||
private String kakaoClientId;
|
||||
/**
|
||||
* KAKAO 상품 코드
|
||||
*/
|
||||
private String kakaoProductCd;
|
||||
/**
|
||||
* KAKAO ACCESS TOKEN
|
||||
*/
|
||||
private String kakaoAccessToken;
|
||||
/**
|
||||
* KAKAO CONTRACT UUID
|
||||
*/
|
||||
private String kakaoContractUuid;
|
||||
/**
|
||||
* KAKAO_NEW PARTNER KEY
|
||||
*/
|
||||
private String kakaoPartnerRestApiKey;
|
||||
/**
|
||||
* KAKAO_NEW DEALER KEY
|
||||
*/
|
||||
private String kakaoDealerRestApiKey;
|
||||
/**
|
||||
* KAKAO_NEW SETTLE ID
|
||||
*/
|
||||
private String kakaoSettleId;
|
||||
/**
|
||||
* KT client id
|
||||
*/
|
||||
private String ktClientId;
|
||||
/**
|
||||
* KT client tp
|
||||
*/
|
||||
private String ktClientTp;
|
||||
/**
|
||||
* KT Scope
|
||||
*/
|
||||
private String ktScope;
|
||||
/**
|
||||
* KT Service code
|
||||
*/
|
||||
private String ktServiceCode;
|
||||
/**
|
||||
* KT service client ID
|
||||
*/
|
||||
private String ktSvcClientId;
|
||||
/**
|
||||
* KT service client secret
|
||||
*/
|
||||
private String ktSvcClientSecret;
|
||||
/**
|
||||
* KT service cerf key
|
||||
*/
|
||||
private String ktSvcCerfKey;
|
||||
/**
|
||||
* KT_ACCESS_TOKEN
|
||||
*/
|
||||
private String ktAccessToken;
|
||||
|
||||
/**
|
||||
* KT 토큰 유효 기간
|
||||
*/
|
||||
private String ktTokenExpiresIn;
|
||||
/**
|
||||
* KT 토큰 식별자
|
||||
*/
|
||||
private String ktTokenJti;
|
||||
/**
|
||||
* postplus apiKey
|
||||
*/
|
||||
private String pplusApiKey;
|
||||
|
||||
/**
|
||||
* EPost service key
|
||||
*/
|
||||
private String epostServiceKey;
|
||||
|
||||
/**
|
||||
* 발송인 명
|
||||
*/
|
||||
private String senderNm;
|
||||
/**
|
||||
* 발송인 우편번호
|
||||
*/
|
||||
private String senderZipNo;
|
||||
/**
|
||||
* 발송인 주소
|
||||
*/
|
||||
private String senderAddr;
|
||||
/**
|
||||
* 발송인 상세 주소
|
||||
*/
|
||||
private String senderDetailAddr;
|
||||
|
||||
/**
|
||||
* 등록 일시
|
||||
*/
|
||||
@JsonDeserialize(using = LocalDateDeserializer.class)
|
||||
@JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss")
|
||||
private LocalDateTime registDt;
|
||||
/**
|
||||
* 등록자
|
||||
*/
|
||||
private String register;
|
||||
/**
|
||||
* 수정 일시
|
||||
*/
|
||||
@JsonDeserialize(using = LocalDateDeserializer.class)
|
||||
@JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss")
|
||||
private LocalDateTime updtDt;
|
||||
/**
|
||||
* 수정자
|
||||
*/
|
||||
private String updusr;
|
||||
}
|
@ -0,0 +1,416 @@
|
||||
package cokr.xit.ens.modules.kkotalk.model;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
import cokr.xit.ens.core.aop.IApiResponse;
|
||||
import cokr.xit.ens.modules.kkotalk.ApiConstants;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : 카카오톡 전자문서 DTO
|
||||
* API 호출시 속성값이 없는 경우 속성을 json에 포함시키지 않는다.
|
||||
* => @JsonInclude(JsonInclude.Include.NON_EMPTY) 설정
|
||||
* packageName : kr.xit.biz.ens.model.kakao.talk
|
||||
* fileName : KkotalkApiDTO
|
||||
* author : limju
|
||||
* date : 2024-08-12
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2024-08-12 limju 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public class KkotalkApiDTO {
|
||||
|
||||
//------------------- Envelope ------------------------------------------------------------------------------------------------
|
||||
@Schema(name = "Envelope", description = "문서발송(단건) 요청 파라메터 DTO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public static class Envelope {
|
||||
/**
|
||||
* 발송할 문서의 제목 : 필수 - max 40자
|
||||
*/
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "발송할 문서의 제목", example = "문서 제목")
|
||||
@Size(max = 40, message = "발송할 문서의 제목은 필수 입니다(max:40)")
|
||||
private String title;
|
||||
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@Valid
|
||||
private Content content;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서 원문(열람정보)에 대한 hash 값
|
||||
* D10_2 상품 사용시 필수
|
||||
* </pre>
|
||||
*/
|
||||
@Schema(title = "문서 원문(열람정보)에 대한 hash 값", example = "6EFE827AC88914DE471C621AE")
|
||||
@Pattern(regexp = "^$|^[a-fA-F0-9]{44}$|^[a-fA-F0-9]{64}$", message = "문서 해시값은 44자 또는 64자의 16진수여야 합니다")
|
||||
private String hash;
|
||||
|
||||
/**
|
||||
* 문서 정보 안내 화면에 노출할 문구(최대: 500자) - 필수
|
||||
*/
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "문서 정보 안내 화면에 노출할 문구(최대: 500자)", example = "문서 정보 안내 화면에 노출할 문구")
|
||||
@Size(min=1, max = 500, message = "문서 정보 안내 화면에 노출할 문구(max=500)")
|
||||
private String guide;
|
||||
|
||||
/**
|
||||
* payload
|
||||
*/
|
||||
@Schema(title = "payload", example = "payload 파라미터 입니다.")
|
||||
@Size(max = 200, message = "payload(max=200)")
|
||||
private String payload;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 최초 열람 만료 일시 지정(최대: 요청 일시로부터 6개월 이내) - 필수
|
||||
* yyyy-MM-dd'T'HH:mm:ss 형식 > DateUtils.getTimeTOfNow()
|
||||
* </pre>
|
||||
*/
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "최초열람마감시간(yyyy-MM-dd'T'HH:mm:ss)", example = "2023-12-31T10:00:00")
|
||||
@Size(min = 19, max = 19, message = "최초열람마감시간(yyyy-MM-dd'T'HH:mm:ss)")
|
||||
private String readExpiresAt;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 재열람 만료 일시 지정
|
||||
* 최소: readExpiredAt 이후의 시각
|
||||
* 최대값: 9999-12-31'T'23:59:59, null 값이면 무제한
|
||||
* 비고: DX0 상품인 경우 최대값은 요청일로부터 6개월 이내
|
||||
* </pre>
|
||||
*/
|
||||
@Schema(title = "재열람 만료 일시(yyyy-MM-dd'T'HH:mm:ss)", example = "2023-12-31T13:00:00")
|
||||
@Size(max = 19, message = "재열람 만료 일시(yyyy-MM-dd'T'HH:mm:ss)")
|
||||
private String reviewExpiresAt;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 알림톡 내용에 개인정보 제거 여부
|
||||
* default: false
|
||||
* </pre>
|
||||
*/
|
||||
@Schema(title = "알림톡 내용에 개인정보 제거 여부", example = " ", defaultValue = "false", allowableValues = {"true", "false"})
|
||||
@Builder.Default
|
||||
private Boolean useNonPersonalizedNotification = false;
|
||||
|
||||
/**
|
||||
* 수신자 CI
|
||||
*/
|
||||
@Schema(title = "받는이 CI", example = " ")
|
||||
@Size(max=88, message = "수신자 CI(max=88)")
|
||||
private String ci;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 수신자 전화번호
|
||||
* ci 미전송시 필수
|
||||
* </pre>
|
||||
*/
|
||||
@Schema(title = "수신자 전화번호", example = "01012345678")
|
||||
@Pattern(regexp = "^$|^\\d{11}$", message = "수신자 전화번호(max=11)")
|
||||
private String phoneNumber;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 수신자 이름
|
||||
* ci 미전송시 필수
|
||||
* </pre>
|
||||
*/
|
||||
@Schema(title = "수신자 이름", example = "김페이")
|
||||
@Size(max = 26, message = "수신자 이름(max=26)")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 수신자 생년월일 (YYYY-MM-DD 형식)
|
||||
* ci 미전송시 필수
|
||||
* </pre>
|
||||
*/
|
||||
@Schema(requiredMode = Schema.RequiredMode.AUTO, title = "수신자 생년월일 (YYYYMMDD 형식)", example = "19801101")
|
||||
@Pattern(regexp = "^$|^(19\\d{2}|20\\d{2})(0[1-9]|1[0-2])(0[1-9]|[1-2]\\d|3[0-1])$", message = "수신자 생년월일(YYYYMMDD)")
|
||||
private String birthday;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서매핑용 식별자 - 최대 40자
|
||||
* </pre>
|
||||
*/
|
||||
@Schema(requiredMode = Schema.RequiredMode.AUTO, title = "문서매핑용 식별자", example = " ")
|
||||
@Size(max=40, message = "문서매핑용 식별자(max=40)")
|
||||
private String externalId;
|
||||
}
|
||||
|
||||
@Schema(name = "Content", description = "문서 원문 웹링크 또는 HTML")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public static class Content {
|
||||
/**
|
||||
* <pre>
|
||||
* 본인인증 후 사용자에게 보여줄 웹페이지 주소
|
||||
* D10 상품 사용시 필수
|
||||
* 1000자 이하의 URL 형식
|
||||
* </pre>
|
||||
*/
|
||||
@Schema(title = "본인인증 후 사용자에게 보여줄 웹페이지 주소", example = "http://ipAddress/api/kakaopay/v1/ott")
|
||||
@Size(max = 1000, message = "본인인증후 사용자에게 보여줄 페이지 주소는 필수입니다(max=1000)")
|
||||
private String link;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* HTML 전문
|
||||
* D11 상품 사용시 필수(최대 64KB)
|
||||
*/
|
||||
@Schema(title = "HTML 전문", example = " ")
|
||||
@Size(max = 65536, message = "HTML 전문(max=64KB)")
|
||||
private String html;
|
||||
}
|
||||
|
||||
//------------------- ValidToken ------------------------------------------------------------------------------------------------
|
||||
@Schema(name = "ValidTokenRequest DTO", description = "카카오톡 전자문서 토큰 유효성 검증 파라메터 DTO")
|
||||
@Data
|
||||
@SuperBuilder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public static class ValidTokenRequest extends CmmEnsRequestDTO {
|
||||
/**
|
||||
* 문서 고유 ID, 34자로 고정
|
||||
*/
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "문서 고유 ID, 34자로 고정", example = " ")
|
||||
@Size(min = 34, max = 34, message = "문서 고유 ID는 필수입니다(34자)")
|
||||
private String envelopeId;
|
||||
|
||||
/**
|
||||
* 카카오톡 전자문서 서버에서 생성한 일회용 토큰 : 필수
|
||||
*/
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "카카오톡 전자문서 서버에서 생성한 일회용 토큰", example = "CON-cc375944ae3d11ecb91e42193199ee3c")
|
||||
@NotEmpty(message = "카카오톡 전자문서 서버 토큰은 필수입니다")
|
||||
private String token;
|
||||
}
|
||||
|
||||
@Schema(name = "ValidTokenResponse DTO", description = "카카오톡 토큰 검증 response DTO")
|
||||
@Data
|
||||
@SuperBuilder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public static class ValidTokenResponse extends KkotalkErrorDTO implements IApiResponse {
|
||||
/**
|
||||
* 문서 고유 ID, 34자로 고정
|
||||
*/
|
||||
private String envelopeId;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서매핑용 식별자 - 최대 40자
|
||||
* </pre>
|
||||
*/
|
||||
private String externalId;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 진행상태 : 토큰검증시 필수
|
||||
* 수신,미열람|열람|최초열람만료일시 또는 재열람 만료일시 초과
|
||||
* RECEIVE|READ|EXPIRED
|
||||
* </pre>
|
||||
* @see ApiConstants.KkotalkDocStatus
|
||||
*/
|
||||
private ApiConstants.KkotalkDocStatus status;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서 송신 일시 - 토큰검증시 필수
|
||||
* yyyy-MM-dd'T'HH:mm:ss 형식 > DateUtils.getTimeTOfNow()
|
||||
* </
|
||||
*/
|
||||
private String sentAt;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서 수신 일시 - 토큰검증시 필수
|
||||
* yyyy-MM-dd'T'HH:mm:ss 형식 > DateUtils.getTimeTOfNow()
|
||||
* </pre>
|
||||
*/
|
||||
private String receivedAt;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서 열람 일시
|
||||
* yyyy-MM-dd'T'HH:mm:ss 형식 > DateUtils.getTimeTOfNow()
|
||||
* </pre>
|
||||
*/
|
||||
private String readAt;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서 열람 인증 일시
|
||||
* yyyy-MM-dd'T'HH:mm:ss 형식 > DateUtils.getTimeTOfNow()
|
||||
* </pre>
|
||||
*/
|
||||
private String authenticatedAt;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 토큰 검증 일시
|
||||
* yyyy-MM-dd'T'HH:mm:ss 형식 > DateUtils.getTimeTOfNow()
|
||||
* </pre>
|
||||
*/
|
||||
private String ottVerifiedAt;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 사용자의 알림톡 수신 가능 여부
|
||||
* </pre>
|
||||
*/
|
||||
private Boolean isNotificationUnavailable;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 사용자의 알림톡 수신 일시
|
||||
* yyyy-MM-dd'T'HH:mm:ss 형식 > DateUtils.getTimeTOfNow()
|
||||
* </pre>
|
||||
*/
|
||||
private String userNotifiedAt;
|
||||
|
||||
/**
|
||||
* 문서 발송하기 API 요청 시 전달한 페이로드
|
||||
*/
|
||||
private String payload;
|
||||
}
|
||||
//------------------ ValidToken ----------------------------------------------------------------------
|
||||
|
||||
//------------------ DocStatus ----------------------------------------------------------------------
|
||||
@Schema(name = "EnvelopeStatusResponse DTO", description = "카카오톡 상태조회 response DTO")
|
||||
@Data
|
||||
@SuperBuilder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public static class EnvelopeStatusResponse extends ValidTokenResponse implements IApiResponse {
|
||||
/** /**
|
||||
* <pre>
|
||||
* 문서 열람 만료 일시
|
||||
* yyyy-MM-dd'T'HH:mm:ss 형식 > DateUtils.getTimeTOfNow()
|
||||
* </pre>
|
||||
*/
|
||||
private String readExpiredAt;
|
||||
|
||||
/** /**
|
||||
* <pre>
|
||||
* 유통정보의 수신 시각
|
||||
* 공인전자주소 활성화와 문서 수신이 모두 완료된 시각
|
||||
* yyyy-MM-dd'T'HH:mm:ss 형식 > DateUtils.getTimeTOfNow()
|
||||
* </pre>
|
||||
*/
|
||||
private String distributionReceivedAt;
|
||||
}
|
||||
//------------------ DocStatus ----------------------------------------------------------------------
|
||||
|
||||
|
||||
//------------------ SendResponse ----------------------------------------------------------------------
|
||||
@Schema(name = "EnvelopeRes DTO", description = "문서발송 응답 DTO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
//@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public static class EnvelopeRes extends KkotalkErrorDTO{
|
||||
/**
|
||||
* 문서 고유 ID, 34자로 고정
|
||||
*/
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "문서 고유 ID, 34자로 고정", example = " ")
|
||||
@Size(min = 34, max = 34, message = "문서 고유 ID는 필수입니다(34자)")
|
||||
private String envelopeId;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서매핑용 식별자 - 최대 40자
|
||||
* </pre>
|
||||
*/
|
||||
private String externalId;
|
||||
}
|
||||
|
||||
@Schema(name = "KkotalkErrorResponse DTO", description = "카카오톡 에러 응답 DTO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
public static class KkotalkErrorDTO {
|
||||
/**
|
||||
* 에러코드
|
||||
*/
|
||||
private String errorCode;
|
||||
|
||||
/**
|
||||
* 에러 메세지
|
||||
*/
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 트래킹(Tracking) ID
|
||||
*/
|
||||
private String edocGtid;
|
||||
}
|
||||
|
||||
|
||||
@Schema(name = "SendResponse DTO", description = "문서발송 응답 DTO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public static class SendResponse extends KkotalkErrorDTO {
|
||||
/**
|
||||
* 문서 고유 ID, 34자로 고정 - 필수
|
||||
*/
|
||||
private String envelopeId;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서매핑용 식별자 - 최대 40자
|
||||
* </pre>
|
||||
*/
|
||||
private String externalId;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------
|
||||
|
||||
@Schema(name = "EnvelopeId DTO", description = "EnvelopeId DTO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public static class EnvelopeId extends CmmEnsRequestDTO{
|
||||
/**
|
||||
* 문서 고유 ID, 34자로 고정
|
||||
*/
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "문서 고유 ID, 34자로 고정", example = " ")
|
||||
@Size(min = 34, max = 34, message = "문서 고유 ID는 필수입니다(34자)")
|
||||
private String envelopeId;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
package cokr.xit.ens.modules.kkotalk.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : 카카오톡 전자문서 요청 파라메터 및 응답 DTO
|
||||
*
|
||||
* packageName : kr.xit.ens.model.kakao.talk
|
||||
* fileName : KkotalkDTO
|
||||
* author : limju
|
||||
* date : 2024-08-12
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2024-08-12 limju 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public class KkotalkDTO extends KkotalkApiDTO {
|
||||
|
||||
//------------------ envelop ----------------------------------------------------------------------
|
||||
@Schema(name = "SendRequest DTO", description = "문서발송 request DTO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public static class SendRequest extends CmmEnsRequestDTO {
|
||||
/**
|
||||
* <pre>
|
||||
* 상품 코드 - 필수
|
||||
* D10_1|D10_2|D11_1|D11_2
|
||||
* </pre>
|
||||
*/
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "상품코드", example = "D10_2", allowableValues = {"D10_1","D10_2","D11_1","D11_2"})
|
||||
@Size(min = 3, max = 5, message = "상품 코드는 필수 입니다(\"D10_1\",\"D10_2\",\"D11_1\",\"D11_2\"")
|
||||
private String productCode = "D10_2";
|
||||
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@Valid
|
||||
private Envelope envelope;
|
||||
}
|
||||
//------------------ envelop ----------------------------------------------------------------------
|
||||
|
||||
//------------------ bulk ----------------------------------------------------------------------
|
||||
@Schema(name = "BulkSendRequest DTO", description = "문서발송[bulk] request DTO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public static class BulkSendRequest extends CmmEnsRequestDTO {
|
||||
/**
|
||||
* <pre>
|
||||
* 상품 코드 - 필수
|
||||
* D10_1|D10_2|D11_1|D11_2
|
||||
* </pre>
|
||||
*/
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "상품코드", example = "D10_1", allowableValues = {"D10_1","D10_2","D11_1","D11_2"})
|
||||
@Size(min = 3, max = 5, message = "상품 코드는 필수 입니다(\"D10_1\",\"D10_2\",\"D11_1\",\"D11_2\"")
|
||||
private String productCode = "D10_2";
|
||||
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@Valid
|
||||
private List<Envelope> envelopes;
|
||||
}
|
||||
|
||||
@Schema(name = "BulkSendResponse DTO", description = "문서발송(bulk) response DTO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public static class BulkSendResponse {
|
||||
private List<EnvelopeRes> envelopes;
|
||||
}
|
||||
|
||||
@Schema(name = "BulkStatusRequest DTO", description = "문서상태조회[bulk] request DTO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public static class BulkStatusRequest extends CmmEnsRequestDTO {
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@Valid
|
||||
private List<String> envelopes;
|
||||
}
|
||||
|
||||
@Schema(name = "BulkStatusResponse DTO", description = "문서상태조회(bulk) response DTO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@SuperBuilder
|
||||
public static class BulkStatusResponse {
|
||||
private List<KkotalkApiDTO.EnvelopeStatusResponse> envelopeStatus;
|
||||
}
|
||||
//------------------ bulk ----------------------------------------------------------------------
|
||||
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package cokr.xit.ens.modules.kkotalk.service;
|
||||
|
||||
import cokr.xit.ens.modules.kkotalk.model.KkotalkApiDTO;
|
||||
import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : 카카오 페이 전자 문서 발송 요청 인터 페이스
|
||||
* packageName : kr.xit.ens.kakao.talk.service
|
||||
* fileName : IKkopayEltrcDocService
|
||||
* author : julim
|
||||
* date : 2023-04-28
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-04-28 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public interface IKkotalkEltrcDocService {
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 모바일웹 연계 문서발송 요청
|
||||
* -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
|
||||
* </pre>
|
||||
* @param reqDTO KkotalkApiDTO.SendRequest
|
||||
* @return KkotalkApiDTO.SendResponse
|
||||
*/
|
||||
KkotalkDTO.SendResponse requestSend(final KkotalkDTO.SendRequest reqDTO);
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 토큰 유효성 검증(Redirect URL 접속 허용/불허)
|
||||
* </pre>
|
||||
* @param reqDTO KkotalkApiDTO.ValidTokenRequest
|
||||
* @return KkotalkApiDTO.ValidTokenResponse>
|
||||
*/
|
||||
KkotalkApiDTO.ValidTokenResponse validToken(final KkotalkApiDTO.ValidTokenRequest reqDTO);
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서 열람 처리 API
|
||||
* -.문서에 대해서 열람 상태로 변경. 사용자가 문서열람 시(OTT 검증 완료 후 페이지 로딩 완료 시점) 반드시 문서 열람 상태 변경 API를 호출해야 함.
|
||||
* -.미 호출 시 아래와 같은 문제 발생
|
||||
* 1)유통증명시스템을 사용하는 경우 해당 API를 호출한 시점으로 열람정보가 등록되어 미 호출 시 열람정보가 등록 되지 않음.
|
||||
* 2)문서상태조회 API(/v1/envelopes/${ENVELOPE_ID}/read) 호출 시 read_at최초 열람시간) 데이터가 내려가지 않음.
|
||||
* </pre>
|
||||
* @param reqDTO KkotalkDTO.EnvelopeId
|
||||
*/
|
||||
void modifyStatus(final KkotalkDTO.EnvelopeId reqDTO);
|
||||
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서 상태 조회 API
|
||||
* -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
|
||||
* : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
|
||||
* : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
|
||||
* -.doc_box_status 상태변경순서
|
||||
* : RECEIVE(수신, 미처리) > READ(열람)/EXPIRED
|
||||
* </pre>
|
||||
* @param reqDTO KkotalkDTO.EnvelopeId
|
||||
* @return KkotalkApiDTO.EnvelopeStatusResponse
|
||||
*/
|
||||
KkotalkApiDTO.EnvelopeStatusResponse findStatus(final KkotalkApiDTO.EnvelopeId reqDTO);
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 대량(bulk) 문서발송 요청
|
||||
* -.이용기관 서버에서 카카오페이 내문서함 서버로 대량(bulk) 문서발송 처리를 요청합니다.
|
||||
* </pre>
|
||||
* @param reqDTO KkopayDocBulkDTO.BulkSendRequests
|
||||
* @return KkopayDocBulkDTO.BulkSendResponses
|
||||
*/
|
||||
KkotalkDTO.BulkSendResponse requestSendBulk(final KkotalkDTO.BulkSendRequest reqDTO);
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 대량(bulk) 문서 상태 조회 API
|
||||
* -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
|
||||
* : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
|
||||
* : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
|
||||
* : RECEIVED(수신,미수신) > READ(열람)/EXPIRED
|
||||
* </pre>
|
||||
* @param reqDTO KkotalkDTO.BulkStatusRequest
|
||||
* @return KkotalkDTO.BulkStatusResponse
|
||||
*/
|
||||
KkotalkDTO.BulkStatusResponse findBulkStatus(final KkotalkDTO.BulkStatusRequest reqDTO);
|
||||
|
||||
|
||||
//KkotalkApiDTO.ValidTokenResponse findKkotalkReadyAndMblPage(KkotalkApiDTO.ValidTokenRequest reqDTO);
|
||||
}
|
||||
|
@ -0,0 +1,317 @@
|
||||
package cokr.xit.ens.modules.kkotalk.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import cokr.xit.ens.core.exception.BizRuntimeException;
|
||||
import cokr.xit.ens.core.utils.ApiWebClientUtil;
|
||||
import cokr.xit.ens.core.utils.Checks;
|
||||
import cokr.xit.ens.core.utils.JsonUtils;
|
||||
import cokr.xit.ens.modules.kkotalk.model.CmmEnsRequestDTO;
|
||||
import cokr.xit.ens.modules.kkotalk.model.CmmEnsRlaybsnmDTO;
|
||||
import cokr.xit.ens.modules.kkotalk.model.KkotalkApiDTO;
|
||||
import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : 카카오 페이 전자 문서 발송 요청 서비스
|
||||
* packageName : kr.xit.ens.kakao.talk.service
|
||||
* fileName : KkopayEltrcDocService
|
||||
* author : julim
|
||||
* date : 2023-04-28
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-04-28 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class KkotalkEltrcDocService implements
|
||||
IKkotalkEltrcDocService {
|
||||
|
||||
@Value("${contract.kakao.talk.host}")
|
||||
private String HOST;
|
||||
|
||||
@Value("#{'${contract.kakao.talk.send}'.split(';')}")
|
||||
private String[] API_SEND;
|
||||
|
||||
@Value("#{'${contract.kakao.talk.bulksend}'.split(';')}")
|
||||
private String[] API_BULKSEND;
|
||||
|
||||
@Value("#{'${contract.kakao.talk.validToken}'.split(';')}")
|
||||
private String[] API_VALID_TOKEN;
|
||||
|
||||
@Value("#{'${contract.kakao.talk.modifyStatus}'.split(';')}")
|
||||
private String[] API_MODIFY_STATUS;
|
||||
|
||||
|
||||
@Value("#{'${contract.kakao.talk.bulkstatus}'.split(';')}")
|
||||
private String[] API_BULKSTATUS;
|
||||
|
||||
private final ApiWebClientUtil webClient;
|
||||
private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||
private static final CharSequence ENVELOPE_ID = "{ENVELOPE_ID}";
|
||||
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 모바일웹 연계 문서발송 요청 : POST
|
||||
* -.이용기관 서버에서 전자문서 서버로 문서발송 처리 요청
|
||||
* </pre>
|
||||
* @param reqDTO KkoPayEltrDocDTO.RequestSendReq
|
||||
* @return ApiResponseDTO<KkopayDocDTO.SendResponse>
|
||||
*/
|
||||
@Override
|
||||
public KkotalkDTO.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 webClient.exchangeKkotalk(
|
||||
HOST + API_SEND[0].replace("{PRODUCT_CODE}", reqDTO.getProductCode()),
|
||||
HttpMethod.valueOf(API_SEND[1]),
|
||||
JsonUtils.toJson(envelope),
|
||||
KkotalkDTO.SendResponse.class,
|
||||
getRlaybsnmInfo(reqDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 토큰 유효성 검증(Redirect URL 접속 허용/불허) : GET
|
||||
* </pre>
|
||||
* @param reqDTO KkopayDocDTO.ValidTokenRequest
|
||||
* @return ApiResponseDTO<KkopayDocDTO.ValidTokenResponse>
|
||||
*/
|
||||
@Override
|
||||
public KkotalkApiDTO.ValidTokenResponse validToken(final KkotalkApiDTO.ValidTokenRequest reqDTO) {
|
||||
validate(reqDTO, null);
|
||||
|
||||
return webClient.exchangeKkotalk(
|
||||
HOST
|
||||
+ API_VALID_TOKEN[0].replace(ENVELOPE_ID, reqDTO.getEnvelopeId())
|
||||
.replace("{TOKEN}", reqDTO.getToken()),
|
||||
HttpMethod.valueOf(API_VALID_TOKEN[1]),
|
||||
null,
|
||||
KkotalkApiDTO.ValidTokenResponse.class,
|
||||
getRlaybsnmInfo(reqDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서 열람 처리 API : POST
|
||||
* -.문서에 대해서 열람 상태로 변경. 사용자가 문서열람 시(OTT 검증 완료 후 페이지 로딩 완료 시점) 반드시 문서 열람 상태 변경 API를 호출해야 함.
|
||||
* -.미 호출 시 아래와 같은 문제 발생
|
||||
* 1)유통증명시스템을 사용하는 경우 해당 API를 호출한 시점으로 열람정보가 등록되어 미 호출 시 열람정보가 등록 되지 않음.
|
||||
* 2)문서상태조회 API(/v1/envelopes/${ENVELOPE_ID}/read) 호출 시 read_at최초 열람시간) 데이터가 내려가지 않음.
|
||||
* </pre>
|
||||
* @param reqDTO KkopayDocAttrDTO.EnvelopeId
|
||||
*/
|
||||
@Override
|
||||
public void modifyStatus(final KkotalkDTO.EnvelopeId reqDTO){
|
||||
validate(reqDTO.getEnvelopeId(), null);
|
||||
|
||||
final String url = HOST + API_MODIFY_STATUS[0].replace(ENVELOPE_ID, reqDTO.getEnvelopeId());
|
||||
|
||||
webClient.exchangeKkotalk(url, HttpMethod.valueOf(API_MODIFY_STATUS[1]), null, Void.class, getRlaybsnmInfo(reqDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서 상태 조회 API : GET
|
||||
* -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
|
||||
* : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
|
||||
* : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
|
||||
* : RECEIVE(수신,미수신) > READ(열람)/EXPIRED
|
||||
* </pre>
|
||||
* @param reqDTO KkotalkDTO.EnvelopeId
|
||||
* @return KkotalkApiDTO.EnvelopeStatusResponse
|
||||
*/
|
||||
@Override
|
||||
public KkotalkApiDTO.EnvelopeStatusResponse findStatus(final KkotalkApiDTO.EnvelopeId reqDTO){
|
||||
validate(reqDTO, null);
|
||||
|
||||
String param = "{\"envelopeIds\":" + JsonUtils.toJson(Collections.singletonList(reqDTO.getEnvelopeId())) + "}";
|
||||
KkotalkDTO.BulkStatusResponse res = webClient.exchangeKkotalk(
|
||||
HOST + API_BULKSTATUS[0],
|
||||
HttpMethod.valueOf(API_BULKSTATUS[1]),
|
||||
param,
|
||||
KkotalkDTO.BulkStatusResponse.class,
|
||||
getRlaybsnmInfo(reqDTO));
|
||||
return res.getEnvelopeStatus().get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 모바일웹 연계 문서발송 요청 : POST
|
||||
* -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
|
||||
* </pre>
|
||||
* @param reqDTO KkotalkDTO.BulkSendRequest
|
||||
* @return KkotalkDTO.BulkSendResponse
|
||||
*/
|
||||
@Override
|
||||
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()))
|
||||
.collect(Collectors.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) + "}";
|
||||
return webClient.exchangeKkotalk(
|
||||
HOST + API_BULKSEND[0].replace("{PRODUCT_CODE}", reqDTO.getProductCode()),
|
||||
HttpMethod.valueOf(API_BULKSEND[1]),
|
||||
param,
|
||||
KkotalkDTO.BulkSendResponse.class,
|
||||
getRlaybsnmInfo(reqDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 대량(bulk) 문서 상태 조회 API : POST
|
||||
* -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
|
||||
* : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
|
||||
* : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
|
||||
* : RECEIVE(수신,미수신) > READ(열람)/EXPIRED
|
||||
* </pre>
|
||||
* @param reqDTO KkotalkDTO.BulkStatusRequest
|
||||
* @return KkotalkDTO.BulkStatusResponse
|
||||
*/
|
||||
@Override
|
||||
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) + "}";
|
||||
return webClient.exchangeKkotalk(
|
||||
HOST + API_BULKSTATUS[0],
|
||||
HttpMethod.valueOf(API_BULKSTATUS[1]),
|
||||
param,
|
||||
KkotalkDTO.BulkStatusResponse.class,
|
||||
getRlaybsnmInfo(reqDTO));
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public KkotalkApiDTO.ValidTokenResponse findKkotalkReadyAndMblPage(final KkotalkApiDTO.ValidTokenRequest reqDTO) {
|
||||
// final String url = HOST + API_VALID_TOKEN[0].replace(ENVELOPE_ID, reqDTO.getEnvelopeId())
|
||||
// .replace("{TOKEN}", reqDTO.getToken());
|
||||
//
|
||||
// // 유효성 검증
|
||||
// final KkotalkApiDTO.ValidTokenResponse validTokenRes = webClient.exchangeKkotalk(url, HttpMethod.valueOf(API_VALID_TOKEN[1]), null,
|
||||
// KkotalkApiDTO.ValidTokenResponse.class, getRlaybsnmInfo(reqDTO));
|
||||
//
|
||||
// // 문서상태 변경
|
||||
// final String url2 = HOST + API_MODIFY_STATUS[0].replace(ENVELOPE_ID, reqDTO.getEnvelopeId());
|
||||
//
|
||||
// // 정상 : HttpStatus.NO_CONTENT(204) return
|
||||
// // error : body에 error_code, error_message return
|
||||
// final KkotalkApiDTO.KkotalkErrorDTO errorDTO = webClient.exchangeKkotalk(url2, HttpMethod.valueOf(API_MODIFY_STATUS[1]), null, KkotalkApiDTO.KkotalkErrorDTO.class, getRlaybsnmInfo(reqDTO));
|
||||
// if(errorDTO != null){
|
||||
// return ApiResponseDTO.error(errorDTO.getErrorCode(), errorDTO.getErrorMessage());
|
||||
// }
|
||||
// return ApiResponseDTO.success();
|
||||
// }
|
||||
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
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()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 추가적인 유효성 검증이 필요 없는 경우
|
||||
if(errList == null){
|
||||
if(!errors.isEmpty()) throw BizRuntimeException.create(errors.toString());
|
||||
return null;
|
||||
}
|
||||
errList.addAll(errors);
|
||||
}
|
||||
return errList;
|
||||
}
|
||||
|
||||
private CmmEnsRlaybsnmDTO getRlaybsnmInfo(final CmmEnsRequestDTO request){
|
||||
return null;//CmmEnsUtils.getRlaybsnmInfo(request.getSignguCode(), request.getFfnlgCode(), SndngSeCode.KAKAO);
|
||||
}
|
||||
}
|
@ -0,0 +1,256 @@
|
||||
package cokr.xit.ens.modules.kkotalk.web;
|
||||
|
||||
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 cokr.xit.ens.core.aop.ApiResponseDTO;
|
||||
import cokr.xit.ens.core.aop.IApiResponse;
|
||||
import cokr.xit.ens.modules.kkotalk.model.KkotalkApiDTO;
|
||||
import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO;
|
||||
import cokr.xit.ens.modules.kkotalk.service.IKkotalkEltrcDocService;
|
||||
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 lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : 카카오톡 전자 문서 발송 controller
|
||||
* packageName : kr.xit.ens.kakao.talk.web
|
||||
* fileName : KkotalkEltrcDocController
|
||||
* author : julim
|
||||
* date : 2024-08-12
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2024-08-12 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Tag(name = "KkotalkEltrcDocController", description = "카카오톡 전자문서 API")
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping(value = "/api/ens/kakao/v2")
|
||||
public class KkotalkEltrcDocController {
|
||||
private final IKkotalkEltrcDocService service;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 모바일웹 연계 문서발송 요청
|
||||
* -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
|
||||
* </pre>
|
||||
* @param reqDTO KkopayDocDTO.SendRequest
|
||||
* @return ApiResponseDTO<KkopayDocDTO.SendResponse>
|
||||
*/
|
||||
@Operation(summary = "문서발송 요청", description = "카카오톡 전자문서 서버로 문서발송 처리를 요청")
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = {
|
||||
@Content(mediaType = "application/json", examples = {
|
||||
@ExampleObject(
|
||||
name = "D10",
|
||||
value = "{\n" +
|
||||
" \"productCode\": \"D10_1\",\n" +
|
||||
" \"envelope\": {\n" +
|
||||
" \"title\": \"전자문서\",\n" +
|
||||
" \"content\": {\n" +
|
||||
" \"link\": \"https://nps.or.kr\"\n" +
|
||||
" },\n" +
|
||||
" \"guide\": \"국민연금 공단에서 보내는 문서입니다.\",\n" +
|
||||
" \"payload\": \"이용기관 페이로드\",\n" +
|
||||
" \"readExpiresAt\": \"2023-12-31T10:00:00\",\n" +
|
||||
" \"reviewExpiresAt\": \"2023-12-31T13:00:00\",\n" +
|
||||
" \"useNonPersonalizedNotification\": true,\n" +
|
||||
" \"phoneNumber\": \"01099999999\",\n" +
|
||||
" \"name\": \"홍길동\",\n" +
|
||||
" \"birthday\": \"20000303\",\n" +
|
||||
" \"externalId\": \"external_id1\"\n" +
|
||||
" },\n" +
|
||||
" \"signguCode\": \"51110\",\n" +
|
||||
" \"ffnlgCode\": \"11\"\n" +
|
||||
"}"
|
||||
),
|
||||
@ExampleObject(
|
||||
name = "D11",
|
||||
value = "{\n" +
|
||||
" \"productCode\": \"D11_1\",\n" +
|
||||
" \"envelope\": {\n" +
|
||||
" \"title\": \"전자문서\",\n" +
|
||||
" \"content\": {\n" +
|
||||
" \"html\": \"<!DOCTYPEhtml><html><body><h1>MyFirstHeading</h1><p>Myfirstparagraph.</p></body></html>\"\n" +
|
||||
" },\n" +
|
||||
" \"guide\": \"국민연금 공단에서 보내는 문서입니다.\",\n" +
|
||||
" \"readExpiresAt\": \"2023-12-31T10:00:00\",\n" +
|
||||
" \"reviewExpiresAt\": \"2023-12-31T13:00:00\",\n" +
|
||||
" \"ci\": \"${CI}\"\n" +
|
||||
" },\n" +
|
||||
" \"signguCode\": \"51110\",\n" +
|
||||
" \"ffnlgCode\": \"11\"\n" +
|
||||
"}"
|
||||
)
|
||||
})
|
||||
}) @PostMapping(value = "/envelopes", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public IApiResponse requestSend(
|
||||
@RequestBody final KkotalkDTO.SendRequest reqDTO
|
||||
) {
|
||||
return ApiResponseDTO.success(service.requestSend(reqDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 토큰 유효성 검증(Redirect URL 접속 허용/불허)
|
||||
* </pre>
|
||||
* @param reqDTO KkopayDocDTO.ValidTokenRequest
|
||||
* @return ApiResponseDTO<KkopayDocDTO.ValidTokenResponse>
|
||||
*/
|
||||
@Operation(summary = "토큰 유효성 검증", description = "Redirect URL 접속 허용/불허")
|
||||
@PostMapping(value = "/validToken", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public IApiResponse validToken(
|
||||
@RequestBody final KkotalkDTO.ValidTokenRequest reqDTO
|
||||
) {
|
||||
return ApiResponseDTO.success(service.validToken(reqDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서 열람처리 API
|
||||
* -.문서에 대해서 열람 상태로 변경. 사용자가 문서열람 시(OTT 검증 완료 후 페이지 로딩 완료 시점) 반드시 문서 열람 상태 변경 API를 호출해야 함.
|
||||
* -.미 호출 시 아래와 같은 문제 발생
|
||||
* 1)유통증명시스템을 사용하는 경우 해당 API를 호출한 시점으로 열람정보가 등록되어 미 호출 시 열람정보가 등록 되지 않음.
|
||||
* 2)문서상태조회 API(/v1/envelopes/${ENVELOPE_ID}/read) 호출 시 read_at최초 열람시간) 데이터가 내려가지 않음.
|
||||
* </pre>
|
||||
* @param reqDTO KkotalkApiDTO.EnvelopeStatusResponse
|
||||
* @return ApiResponseDTO<Void>
|
||||
*/
|
||||
@Operation(summary = "문서열람처리(문서 상태 변경)", description = "문서열람처리(문서 상태 변경)")
|
||||
@PostMapping(value = "/modifyStatus", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public IApiResponse modifyStatus(
|
||||
@RequestBody final KkotalkApiDTO.EnvelopeId reqDTO
|
||||
) {
|
||||
service.modifyStatus(reqDTO);
|
||||
return ApiResponseDTO.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 문서 상태 조회 API
|
||||
* -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
|
||||
* : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
|
||||
* : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
|
||||
* -.doc_box_status 상태변경순서
|
||||
* : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
|
||||
* </pre>
|
||||
* @param reqDTO KkotalkDTO.EnvelopeId
|
||||
* @return ApiResponseDTO<KkotalkApiDTO.EnvelopeStatusResponse>
|
||||
*/
|
||||
@Operation(summary = "문서 상태 조회", description = "문서 상태 조회")
|
||||
@PostMapping(value = "/findStatus", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public IApiResponse findStatus(
|
||||
@RequestBody final KkotalkApiDTO.EnvelopeId reqDTO
|
||||
) {
|
||||
return ApiResponseDTO.success(service.findStatus(reqDTO));
|
||||
}
|
||||
|
||||
@Operation(summary = "대량 문서발송 요청 -> batch sendBulks 에서 호출", description = "카카오페이 전자문서 서버로 대량 문서발송 처리를 요청 -> batch sendBulks 에서 호출")
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = {
|
||||
@Content(mediaType = "application/json", examples = {
|
||||
@ExampleObject(
|
||||
name = "D10",
|
||||
value = "{\n" +
|
||||
" \"productCode\": \"D10_1\",\n" +
|
||||
" \"signguCode\": \"51110\",\n" +
|
||||
" \"ffnlgCode\": \"11\",\n" +
|
||||
" \"envelopes\": [\n" +
|
||||
" {\n" +
|
||||
" \"title\": \"전자문서\",\n" +
|
||||
" \"content\": {\n" +
|
||||
" \"html\": \"<!DOCTYPEhtml><html><body><h1>MyFirstHeading</h1><p>Myfirstparagraph.</p></body></html>\"\n" +
|
||||
" },\n" +
|
||||
" \"guide\": \"국민연금 공단에서 보내는 문서입니다.\",\n" +
|
||||
" \"readExpiresAt\": \"2024-12-31T10:00:00\",\n" +
|
||||
" \"reviewExpiresAt\": \"2025-03-31T13:00:00\",\n" +
|
||||
" \"phoneNumber\": \"01099999999\",\n" +
|
||||
" \"name\": \"홍길동\",\n" +
|
||||
" \"birthday\": \"20000303\",\n" +
|
||||
" \"externalId\": \"external_id1\"\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"title\": \"전자문서\",\n" +
|
||||
" \"content\": {\n" +
|
||||
" \"html\": \"<!DOCTYPEhtml><html><body><h1>MyFirstHeading</h1><p>Myfirstparagraph.</p></body></html>\"\n" +
|
||||
" },\n" +
|
||||
" \"guide\": \"국민연금 공단에서 보내는 문서입니다.\",\n" +
|
||||
" \"readExpiresAt\": \"2024-12-31T10:00:00\",\n" +
|
||||
" \"reviewExpiresAt\": \"2025-03-31T13:00:00\",\n" +
|
||||
" \"hash\": \"b0c34fdc5e2ecb0335919fdad3b2ada28fa3ab90ec16e9055c3e9e05c431c6e8\",\n" +
|
||||
" \"ci\": \"vMtqVxJX56lBgbf9heK3QTc+jVndTfK77i/UJKAzPmBG4n9CazCdd/8YytlFZnN4qofIqgxHpSoiG0yYzgEpJg==\",\n" +
|
||||
" \"externalId\": \"external_id2\"\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
"}"
|
||||
),
|
||||
@ExampleObject(
|
||||
name = "D11",
|
||||
value = "{\n" +
|
||||
" \"productCode\": \"D11_1\",\n" +
|
||||
" \"signguCode\": \"51110\",\n" +
|
||||
" \"ffnlgCode\": \"11\",\n" +
|
||||
" \"envelopes\": [\n" +
|
||||
" {\n" +
|
||||
" \"title\": \"전자문서\",\n" +
|
||||
" \"content\": {\n" +
|
||||
" \"link\": \"https://nps.or.kr\"\n" +
|
||||
" },\n" +
|
||||
" \"guide\": \"국민연금 공단에서 보내는 문서입니다.\",\n" +
|
||||
" \"payload\": \"이용기관 페이로드\",\n" +
|
||||
" \"readExpiresAt\": \"2024-12-31T10:00:00\",\n" +
|
||||
" \"reviewExpiresAt\": \"2025-03-31T13:00:00\",\n" +
|
||||
" \"phoneNumber\": \"01099999999\",\n" +
|
||||
" \"name\": \"홍길동\",\n" +
|
||||
" \"birthday\": \"20000303\",\n" +
|
||||
" \"externalId\": \"external_id1\"\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"title\": \"전자문서\",\n" +
|
||||
" \"content\": {\n" +
|
||||
" \"link\": \"https://nps.or.kr\"\n" +
|
||||
" },\n" +
|
||||
" \"guide\": \"국민연금 공단에서 보내는 문서입니다.\",\n" +
|
||||
" \"payload\": \"이용기관 페이로드\",\n" +
|
||||
" \"readExpiresAt\": \"2024-12-31T10:00:00\",\n" +
|
||||
" \"reviewExpiresAt\": \"2025-03-31T13:00:00\",\n" +
|
||||
" \"ci\": \"${CI}\",\n" +
|
||||
" \"externalId\": \"external_id2\"\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
"}"
|
||||
)
|
||||
})
|
||||
})
|
||||
@PostMapping(value = "/envelopes/bulk", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public IApiResponse requestSendBulk(
|
||||
@RequestBody final KkotalkDTO.BulkSendRequest reqDTO
|
||||
) {
|
||||
return ApiResponseDTO.success(service.requestSendBulk(reqDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 모바일웹 연계 문서발송 요청
|
||||
* -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
|
||||
* </pre>
|
||||
* @param reqDTO KkotalkApiDTO.BulkStatusRequest
|
||||
* @return KkotalkApiDTO.BulkStatusResponse
|
||||
*/
|
||||
@Operation(summary = "대량 문서 상태 조회 요청 -> batch statusBulks 에서 호출", description = "카카오페이 전자문서 서버로 대량 문서 상태 조회 요청 -> batch statusBulks 에서 호출")
|
||||
@PostMapping(value = "/envelopes/bulk/status", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public IApiResponse findBulkStatus(
|
||||
@RequestBody final KkotalkDTO.BulkStatusRequest reqDTO
|
||||
) {
|
||||
return ApiResponseDTO.success(service.findBulkStatus(reqDTO));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue