diff --git a/src/main/java/com/eactive/eai/custom/alarm/ums/IUmsGsonObject.java b/src/main/java/com/eactive/eai/custom/alarm/ums/IUmsGsonObject.java new file mode 100644 index 0000000..58037b1 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/alarm/ums/IUmsGsonObject.java @@ -0,0 +1,7 @@ +package com.eactive.eai.custom.alarm.ums; + +/** + * GSON Serialize 용 Object + */ +public interface IUmsGsonObject { +} diff --git a/src/main/java/com/eactive/eai/custom/alarm/ums/ObpMonitoringUmsTemplate.java b/src/main/java/com/eactive/eai/custom/alarm/ums/ObpMonitoringUmsTemplate.java new file mode 100644 index 0000000..1570885 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/alarm/ums/ObpMonitoringUmsTemplate.java @@ -0,0 +1,12 @@ +package com.eactive.eai.custom.alarm.ums; + +import com.google.gson.annotations.SerializedName; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ObpMonitoringUmsTemplate implements IUmsGsonObject { + @SerializedName("MOTR_EVAL_CNTN") + private String message; +} diff --git a/src/main/java/com/eactive/eai/custom/alarm/ums/UmsBizWorkCode.java b/src/main/java/com/eactive/eai/custom/alarm/ums/UmsBizWorkCode.java new file mode 100644 index 0000000..898f4b6 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/alarm/ums/UmsBizWorkCode.java @@ -0,0 +1,19 @@ +package com.eactive.eai.custom.alarm.ums; + +import lombok.Getter; + +public enum UmsBizWorkCode { + MONITORING("B_ELFN_09510", "OBP 모니터링"), + VERIFY_PHONE("S55_ICT_0006", "API Portal 인증번호"), + ; + + @Getter + private String code; + @Getter + private String displayName; + + UmsBizWorkCode(String code, String displayName) { + this.code = code; + this.displayName = displayName; + } +} diff --git a/src/main/java/com/eactive/eai/custom/alarm/ums/UmsCallResult.java b/src/main/java/com/eactive/eai/custom/alarm/ums/UmsCallResult.java new file mode 100644 index 0000000..f63b6d8 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/alarm/ums/UmsCallResult.java @@ -0,0 +1,18 @@ +package com.eactive.eai.custom.alarm.ums; + + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class UmsCallResult { + private String umsMessageId; + private boolean isSuccess; + + private String returnCode; + private String returnMessage; + private String returnPayload; + + private Throwable throwable; +} diff --git a/src/main/java/com/eactive/eai/custom/alarm/ums/UmsRequestPayload.java b/src/main/java/com/eactive/eai/custom/alarm/ums/UmsRequestPayload.java new file mode 100644 index 0000000..6e11bd1 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/alarm/ums/UmsRequestPayload.java @@ -0,0 +1,103 @@ +package com.eactive.eai.custom.alarm.ums; + +import com.google.gson.annotations.SerializedName; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.NotEmpty; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +@Data +@Builder +@Getter +public class UmsRequestPayload { + // MSG_IDNT_NO string 메시지식별번호 50 O "발송 구분을 위한 거래 번호 (GUID 권고) + //GUID로 입력이 어려울시 + //어플리케이션코드(3자리)+난수 로 유니크한 ID로 채번 권고" + @Length(min = 1, max = 50) + @NotEmpty + @SerializedName("MSG_IDNT_NO") + private String messageIdentityNo; + + // MSG_BZWK_CD string 메시지업무코드 30 O UMS에 등록된 메시지업무코드(AS-IS 템플릿ID) + @NotEmpty + @Length(min = 1, max = 30) + @SerializedName("MSG_BZWK_CD") + private String msgBizWorkCode; + + // 메시지업무파라미터내용 / 4000 / JSONObject + // MSG_BZWK_PARAM_CTNT object 메시지업무파라미터내용 4000 메시지업무코드의 템플릿에서 사용하는 변수와 변수값 + @NotNull + @SerializedName("MSG_BZWK_PARAM_CTNT") + private IUmsGsonObject content; + + // 발송예약일시 / 14 / 실 발송 일자(yyyyMMddHHmmss) + // SND_RESR_DTTM string 발송예약일시 14 20240418111500 O 실 발송 일자(현재시간) + @Pattern(regexp = "^[0-9]{14}$") + @NotEmpty + @SerializedName("SND_RESR_DTTM") + @Builder.Default + private String sendReservationDatetime = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now()); + + // 휴대전화번호 / 44 / 휴대폰번호(평문, 암호화 둘다가능) + // CPNO string 휴대전화번호 44 "암호화 또는 평문 (safedb not rrno) + //문자일 시 암호문 + //숫자일 시 전화번호 + //고객번호 또는 휴대전화번호 필수" + @NotEmpty + @Pattern(regexp = "^[0-9]{10,11}$") + @SerializedName("CPNO") + private String cellphone; + + // 발송부점코드 / 4 / + // SND_BRCD string 발송부점코드 4 O 0333 + @NotEmpty + @SerializedName("SND_BRCD") + @Builder.Default + private String sendBranchCode = "0990"; + + // 발송직원번호 / 7 / ECEB057(텔러번호?) + // SND_EMPNO string 발송직원번호 7 O ? 넣긴해야함 + @NotEmpty + @SerializedName("SND_EMPNO") + @Builder.Default + private String sendEmployeeNo = "ECEB057"; + + // SRVC_ID string 서비스ID 11 요청단 측 서비스ID + @NotEmpty + @SerializedName("SRVC_ID") + @Builder.Default + private String serviceId = "eAPIM"; + + // MSG_SND_CHNL_CD string 메시지발송채널코드 2 + @SerializedName("MSG_SND_CHNL_CD") + @NotNull + @Builder.Default + private ChannelCode msgSndChnlCd = ChannelCode.KM; + + // VALDTN_RSLT_CD string 검증결과코드 2 00으로 고정 + @SerializedName("VALDTN_RSLT_CD") + @NotEmpty + @Builder.Default + private String validationResultCode = "00"; + + public enum ChannelCode { + SM, // SMS + LM, // LMS + MM, // MMS + KM, // 카카오-알림톡 + FM, // 카카오-친구톡 + SR, // RCS-SMS + LR, // RCS-LMS + MR, // RCS-MMS + TR, // RCS-TEMPLATE + IR, // RCS-IMAGE Template + PM // Push + } +} + diff --git a/src/main/java/com/eactive/eai/custom/alarm/ums/UmsService.java b/src/main/java/com/eactive/eai/custom/alarm/ums/UmsService.java new file mode 100644 index 0000000..08b6135 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/alarm/ums/UmsService.java @@ -0,0 +1,133 @@ +package com.eactive.eai.custom.alarm.ums; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.EntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.ProtocolException; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.util.Timeout; +import org.springframework.stereotype.Component; +import org.springframework.util.StreamUtils; + +import com.eactive.eai.common.util.Logger; +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; + +@Component +public class UmsService { + + protected static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT); + + private final static String UMS_URL = "http://172.31.35.144:9021"; + private final static String API_KEY = "API_KEY"; + private final static String SEND_URL = "/ums/openapi/send"; + private final static String CONTENT_TYPE = "application/json; charset=utf-8"; + private final static String AUTHORIZATION_FMT = "Bearer %s"; + + Future send(String cpno, String message, UmsBizWorkCode code) throws Exception { + + if (message == null) { + throw new IllegalArgumentException("content is null"); + } + + String guid = String.format("APM-%s", + DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").format(LocalDateTime.now())); + + UmsRequestPayload payload = UmsRequestPayload.builder().messageIdentityNo(guid).msgBizWorkCode(code.getCode()) + .content(ObpMonitoringUmsTemplate.builder().message(message).build()).cellphone(cpno) + .msgSndChnlCd(UmsRequestPayload.ChannelCode.KM).build(); + + try { + ValidationUtil.validateOrThrow(payload); + } catch (Exception ex) { + logger.debug(String.format("UMS Request payload validation failed - %s", payload)); + throw ex; + } + + return call(SEND_URL, payload); + } + + private CloseableHttpClient createHttpClient() { + return HttpClients.createDefault(); + } + + private Future call(String url, UmsRequestPayload requestPayload) throws Exception { + + ExecutorService executor = Executors.newSingleThreadExecutor(); + Gson gson = new Gson(); + String json = gson.toJson(requestPayload); + + HttpPost httpPost = new HttpPost(UMS_URL + url); + httpPost.setHeader("Content-Type", CONTENT_TYPE); + httpPost.setHeader("Authorization", String.format(AUTHORIZATION_FMT, API_KEY)); + httpPost.setEntity(EntityBuilder.create().setText(json).setContentType(ContentType.APPLICATION_JSON).build()); + + if (logger.isDebugEnabled()) { + try { + logger.debug("Header - {}", httpPost.getHeader("Content-Type")); + logger.debug("Header - {}", httpPost.getHeader("Authorization")); + logger.debug("Request Body : {}", + StreamUtils.copyToString(httpPost.getEntity().getContent(), Charset.defaultCharset())); + } catch (ProtocolException | IOException e) { + throw new RuntimeException(e); + } + } + + httpPost.setConfig(RequestConfig.custom().setConnectTimeout(Timeout.ofSeconds(3)) + .setResponseTimeout(Timeout.ofSeconds(3)).build()); + Future future; + try (CloseableHttpClient httpClient = createHttpClient()) { + future = executor.submit(() -> { + return httpClient.execute(httpPost, res -> { + try { + UmsCallResult result; + String body = EntityUtils.toString(res.getEntity()); + if (logger.isDebugEnabled()) { + logger.debug("Response - {}", body); + } + result = gson.fromJson(body, UmsCallResult.class); + result.setUmsMessageId(requestPayload.getMessageIdentityNo()); + result.setSuccess(true); + return result; + } catch (JsonSyntaxException e) { + logger.error("UMS Call error(JSON Syntax)", e); + return UmsCallResult.builder().umsMessageId(requestPayload.getMessageIdentityNo()) + .isSuccess(false).throwable(e).build(); + } catch (Exception e) { + logger.error("UMS Call error", e); + return UmsCallResult.builder().umsMessageId(requestPayload.getMessageIdentityNo()) + .isSuccess(false).throwable(e).build(); + } finally { + logger.debug("Response status - {}", res.getCode()); + + if (res.getCode() != 200) { + Arrays.asList(res.getHeaders()).forEach(o -> { + logger.warn("[Header] {}: {}", o.getName(), o.getValue()); + }); + } + } + }); + }); + + return future; + } catch (Exception e) { + logger.error("UMS call exception", e); + throw new Exception("UMS call failed", e); + } finally { + executor.shutdown(); + } + + } +} diff --git a/src/main/java/com/eactive/eai/custom/alarm/ums/ValidationUtil.java b/src/main/java/com/eactive/eai/custom/alarm/ums/ValidationUtil.java new file mode 100644 index 0000000..7aacc4f --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/alarm/ums/ValidationUtil.java @@ -0,0 +1,53 @@ +package com.eactive.eai.custom.alarm.ums; + +import java.util.Set; +import java.util.stream.Collectors; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; + +import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator; + +public class ValidationUtil { + private static final ValidatorFactory factory; + private static final Validator validator; + + static { + factory = Validation.byDefaultProvider() + .configure() + .messageInterpolator(new ParameterMessageInterpolator()) + .buildValidatorFactory(); + + validator = factory.getValidator(); + } + + + private ValidationUtil() { + } + + + public static Set> validate(T target) { + return validator.validate(target); + } + + public static void validateOrThrow(T target) { + Set> violations = validate(target); + if (!violations.isEmpty()) { + String message = violations.stream() + .map(v -> v.getPropertyPath() + " : " + v.getMessage()) + .collect(Collectors.joining(", ")); + throw new ConstraintViolationException("Validation failed: " + message, violations); + } + } + + public static String getViolationMessage(T target) { + Set> violations = validate(target); + if (violations.isEmpty()) return null; + return violations.stream() + .map(v -> v.getPropertyPath() + " : " + v.getMessage()) + .collect(Collectors.joining(", ")); + } +} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/custom/alarm/ums/VerifyPhoneUmsTemplate.java b/src/main/java/com/eactive/eai/custom/alarm/ums/VerifyPhoneUmsTemplate.java new file mode 100644 index 0000000..7af2586 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/alarm/ums/VerifyPhoneUmsTemplate.java @@ -0,0 +1,12 @@ +package com.eactive.eai.custom.alarm.ums; + +import com.google.gson.annotations.SerializedName; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class VerifyPhoneUmsTemplate implements IUmsGsonObject { + @SerializedName("ATHN_NO") + private String authNumber; +}