feat: 카카오톡 반영

main
Jonguk. Lim 2 months ago
parent ca60c92bf2
commit 38fa24fb04

@ -0,0 +1,75 @@
create table ENS_SND_DTL_KKO_TALK
(
SEND_DETAIL_ID NUMBER(19) not null
primary key,
TITLE VARCHAR2(40),
LINK VARCHAR2(500),
HASH VARCHAR2(99),
GUIDE VARCHAR2(2000),
PAYLOAD VARCHAR2(500),
READ_EXPIRES_AT VARCHAR2(20),
REVIEW_EXPIRES_AT VARCHAR2(20),
USE_NON_PERSONALIZED_NOTI VARCHAR2(10),
CI VARCHAR2(88),
PHONE_NUMBER VARCHAR2(30),
NAME VARCHAR2(50),
BIRTHDAY VARCHAR2(8),
EXTERNAL_ID VARCHAR2(40),
ENVELOPE_ID VARCHAR2(34),
ERROR_CODE VARCHAR2(40),
ERROR_MESSAGE CLOB,
LAST_UPDT_DT TIMESTAMP(6),
REGIST_DT TIMESTAMP(6),
BILL_UID VARCHAR2(45 char)
constraint FK_BILL_UID
references ENS_BILL (BILL_UID),
SEND_MAST_ID NUMBER(19)
constraint FK_SEND_MAST_ID
references ENS_SND_MAST(SEND_MAST_ID)
);
comment on table ENS_SND_DTL_KKO_TALK is '카카오톡 상세';
comment on column ENS_SND_DTL_KKO_TALK.SEND_DETAIL_ID is '발송 상세 ID';
comment on column ENS_SND_DTL_KKO_TALK.SEND_MAST_ID is '발송 마스터 ID';
comment on column ENS_SND_DTL_KKO_TALK.TITLE is '제목';
comment on column ENS_SND_DTL_KKO_TALK.LINK is '웹링크';
comment on column ENS_SND_DTL_KKO_TALK.HASH is '해시';
comment on column ENS_SND_DTL_KKO_TALK.GUIDE is '메시지';
comment on column ENS_SND_DTL_KKO_TALK.PAYLOAD is 'PAYLOAD';
comment on column ENS_SND_DTL_KKO_TALK.READ_EXPIRES_AT is '최초 열람 만료 일시';
comment on column ENS_SND_DTL_KKO_TALK.REVIEW_EXPIRES_AT is '재열람 만료 일시';
comment on column ENS_SND_DTL_KKO_TALK.USE_NON_PERSONALIZED_NOTI is '알림톡 개인정보 제거 여부';
comment on column ENS_SND_DTL_KKO_TALK.CI is 'CI';
comment on column ENS_SND_DTL_KKO_TALK.PHONE_NUMBER is '수신인 전화번호';
comment on column ENS_SND_DTL_KKO_TALK.NAME is '수신인 이름';
comment on column ENS_SND_DTL_KKO_TALK.BIRTHDAY is '수신인 생년월일';
comment on column ENS_SND_DTL_KKO_TALK.EXTERNAL_ID is '문서 매핑 식별 ID';
comment on column ENS_SND_DTL_KKO_TALK.ENVELOPE_ID is '문서 고유 ID';
comment on column ENS_SND_DTL_KKO_TALK.STATUS is '문서 상태';
comment on column ENS_SND_DTL_KKO_TALK.SENT_AT is '문서 송신 일시';
comment on column ENS_SND_DTL_KKO_TALK.RECEIVED_AT is '문서 수신 일시';
comment on column ENS_SND_DTL_KKO_TALK.READ_AT is '문서 열람 일시';
comment on column ENS_SND_DTL_KKO_TALK.AUTHENTICATED_AT is '문서 열람 인증 일시';
comment on column ENS_SND_DTL_KKO_TALK.OTT_VERIFIED_AT is '토큰 검증 일시';
comment on column ENS_SND_DTL_KKO_TALK.IS_NOTIFICATION_UNAVAILABLE is '알림톡 수신 가능 여부';
comment on column ENS_SND_DTL_KKO_TALK.USER_NOTIFIED_AT is '알림톡 수신 일시';
comment on column ENS_SND_DTL_KKO_TALK.DISTRIBUTION_RECEIVED_AT is '유통정보 수신 일시';
comment on column ENS_SND_DTL_KKO_TALK.ERROR_CODE is '에러 코드';
comment on column ENS_SND_DTL_KKO_TALK.ERROR_MESSAGE is '에러 메시지';
comment on column ENS_SND_DTL_KKO_TALK.REGIST_DT is '등록 일시';
comment on column ENS_SND_DTL_KKO_TALK.LAST_UPDT_DT is '수정 일시';
/* FIXME: 카카오톡에 맞게 변경 필요 */
create table ENS_TMPLT_MNG_KKO_TALK
(
CI_TRANS_USE_YN VARCHAR2(1 char) not null,
CS_NAME VARCHAR2(10 char) not null,
CS_NUMBER VARCHAR2(20 char) not null,
PRY_MESSAGE VARCHAR2(2000 char),
TMPLT_CS_INFO_USE_YN VARCHAR2(1 char) not null,
TMPLT_MSG_USE_YN VARCHAR2(1 char) not null,
ORG_CD VARCHAR2(255 char) not null,
TMPLT_CD VARCHAR2(30 char) not null,
primary key (ORG_CD, TMPLT_CD),
constraint FK_TMPLT_MNG_KKO_TALK
foreign key (ORG_CD, TMPLT_CD) references ENS_TMPLT_MNG
);

@ -1,8 +1,6 @@
package cokr.xit.ens.core.utils;
import lombok.extern.slf4j.Slf4j;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
@ -11,6 +9,10 @@ import java.util.Calendar;
import java.util.Date;
import java.util.Optional;
import org.apache.commons.lang.StringUtils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DateUtil {
@ -406,5 +408,14 @@ public class DateUtil {
return new SimpleDateFormat(pattern).format(date);
}
/**
* yyyy-MM-dd'T'HH:mm:ss String > fmt (null yyyy-MM-dd HH:mm:ss String )
* @param timeT
* @param fmt fromat
* @return
*/
public static String getTimeOfTimeT(String timeT, String fmt) {
return LocalDateTime.parse(timeT, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"))
.format(DateTimeFormatter.ofPattern(StringUtils.isEmpty(fmt)? "DEFAULT_YMD_DT_FMT": fmt));
}
}

@ -13,7 +13,9 @@ public enum PostSeCd implements CodeMapperType {
,kkoAlimtalk("카카오페이 알림톡")
,nvSigntalk("네이버 고지서(인증톡)")
,ktSigntalk("KT 인증톡")
,ktGibis("KT 인증톡(지비스)");
,ktGibis("KT 인증톡(지비스)")
,kkoTalk("카카오톡 인증톡")
;
@Getter

@ -1,16 +1,35 @@
package cokr.xit.ens.modules.common.ctgy.intgrnnoti.domain;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import cokr.xit.ens.modules.common.code.IntgrnDtlStatCd;
import cokr.xit.ens.modules.common.code.PostSeCd;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.Bill;
import cokr.xit.ens.modules.common.domain.BaseEntity;
import cokr.xit.ens.modules.common.domain.support.FieldError;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import javax.persistence.*;
@Entity
//@Data
@Getter
@ -31,27 +50,27 @@ public class IntgrnSendDetail extends BaseEntity {
@TableGenerator(table = "ens_seq_generator", name = "IntgrnSendDetail_Generator"
, pkColumnName = "seq_name", pkColumnValue = "IntgrnSendDetail_id"
, initialValue = 0, allocationSize = 50)
private Long intSendDetailId;
private Long intSendDetailId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "int_send_mast_id")
private IntgrnSendMast intgrnSendMast;
private IntgrnSendMast intgrnSendMast;
@Column(name = "linked_uuid", length = 40)
@Setter
private String linkedUuid;
@Column(name = "ci", nullable = true)
private String ci;
@Column(name = "jid", nullable = true, length = 24)
private String jid;
@Column(name = "mblphonNo", nullable = true)
private String mblphonNo;
@ -60,12 +79,12 @@ public class IntgrnSendDetail extends BaseEntity {
// @Lob
// private String jKkoBillLinkInfo;
@Column(name = "json_tmplt_msg_data", nullable = true)
@Lob
private String jTmpltMsgData;
@Column(name = "json_mbl_page_data", nullable = true)
@Lob
private String jMblPageData;
@ -74,15 +93,24 @@ public class IntgrnSendDetail extends BaseEntity {
@Column(name = "j_acpt_doc_kko_at", nullable = true)
@Lob
private String jAcptDocKkoAt;
@Column(name = "j_acpt_doc_kko_md", nullable = true)
@Lob
private String jAcptDocKkoMd;
// FIXME: kkotalk 추가
@Column(name = "j_acpt_doc_kko_talk", nullable = true)
@Lob
private String jAcptDocKkoTalk;
@Column(name = "j_acpt_doc_nv_st", nullable = true)
@Lob
private String jAcptDocNvSt;
@Column(name = "j_acpt_doc_kt_st", nullable = true)
@Lob
private String jAcptDocKtSt;
@Column(name = "j_acpt_doc_kt_gbs", nullable = true)
@Lob
private String jAcptDocKtGbs;
@ -91,61 +119,71 @@ public class IntgrnSendDetail extends BaseEntity {
@Column(name = "cur_post_se", nullable = true)
@Enumerated(EnumType.STRING)
private PostSeCd curPostSe;
@Setter
@Column(name = "send_detail_id_kko_at", nullable = true)
private Long sendDetailIdKkoAt;
@Setter
@Column(name = "send_detail_id_kko_md", nullable = true)
private Long sendDetailIdKkoMd;
// FIXME: kkotalk 추가
@Setter
@Column(name = "send_detail_id_kko_talk", nullable = true)
private Long sendDetailIdKkoTalk;
@Setter
@Column(name = "send_detail_id_nv_st", nullable = true)
private Long sendDetailIdNvSt;
@Setter
@Column(name = "send_detail_id_kt_st", nullable = true)
private Long sendDetailIdKtSt;
@Setter
@Column(name = "send_detail_id_kt_gbs", nullable = true)
private Long sendDetailIdKtGbs;
@Setter
@Enumerated(EnumType.STRING)
@Column(name = "cur_stat_cd", nullable = false)
private IntgrnDtlStatCd curStatCd;
@Column(name = "doc_sent_dt", nullable = true, length = 14)
@Setter
private String docSentDt;
@Column(name = "doc_received_dt", nullable = true, length = 14)
@Setter
private String docReceivedDt;
@Column(name = "doc_auth_frst_dt", nullable = true, length = 14)
@Setter
private String docAuthFrstDt;
@Column(name = "doc_token_vrfy_frst_dt", nullable = true, length = 14)
@Setter
private String docTokenVrfyFrstDt;
@Column(name = "doc_read_frst_dt", nullable = true, length = 14)
@Setter
private String docReadFrstDt;
@Column(name = "doc_user_notied_dt", nullable = true, length = 14)
@Setter
private String docUserNotiedDt;
@Column(name = "mk_bill_use_yn", nullable = true, length = 1)
private String mkBillUseYn;
// @Column(name = "mk_bill_uid", nullable = true, length = 45)
// private String mkBillUid;
@JoinColumn(name = "bill_uid", referencedColumnName = "bill_uid")
@OneToOne(fetch = FetchType.LAZY)
private Bill bill;

@ -1,14 +1,19 @@
package cokr.xit.ens.modules.common.ctgy.intgrnnoti.model.config;
import javax.validation.Valid;
import cokr.xit.ens.modules.kkoalimtalk.model.config.DocumentConfKkoAt;
import cokr.xit.ens.modules.kkomydoc.model.config.DocumentConfKkoMd;
import cokr.xit.ens.modules.kkotalk.model.KkotalkApiDTO;
import cokr.xit.ens.modules.ktsigntalk.direct.model.config.DocumentConfKtSt;
import cokr.xit.ens.modules.ktsigntalk.gibis.model.config.DocumentConfKtGbs;
import cokr.xit.ens.modules.nvsigntalk.model.config.DocumentConfNvSt;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.Valid;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Getter
@ToString
@ -26,6 +31,11 @@ public class AcptData {
@Valid
private DocumentConfKkoAt kko_at;
// FIXME: 카카오톡 접수요청 추가
@Schema(required = false, title = "카카오톡 접수요청", example = " ")
@Valid
private KkotalkApiDTO.Envelope kko_talk;
@Schema(required = false, title = "네이버 고지서(인증톡) 접수요청", example = " ")
@Valid
private DocumentConfNvSt nv_st;

@ -1,26 +1,28 @@
package cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support;
import java.util.List;
import cokr.xit.ens.modules.common.code.PostSeCd;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.domain.IntgrnSendDetail;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.domain.IntgrnSendMast;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.kkoalimtalk.AcceptDataByKkoAlimtalk;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.kkomydoc.AcceptDataByKkoMydoc;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.kkotalk.AcceptDataByKkoTalk;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.ktGibis.AcceptDataByKtGibis;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.ktSigntalk.AcceptDataByKtSigntalk;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.nvsigntalk.AcceptDataByNvSigntalk;
import lombok.RequiredArgsConstructor;
import java.util.List;
// TODO: Sender에서 call
@RequiredArgsConstructor
public class AcceptDataMakerFactory extends AcceptDataFactory<AcceptData> {
public class AcceptDataMakerFactory extends AcceptDataFactory<AcceptData<?>> {
private final IntgrnSendMast sendMast;
private final List<IntgrnSendDetail> sendDetails;
private final Boolean isReserveSend;
@Override
public AcceptData get(PostSeCd postSeCd) {
public AcceptData<?> get(PostSeCd postSeCd) {
switch (postSeCd) {
case kkoAlimtalk:
return new AcceptDataByKkoAlimtalk(sendMast, sendDetails, isReserveSend);
@ -32,6 +34,9 @@ public class AcceptDataMakerFactory extends AcceptDataFactory<AcceptData> {
return new AcceptDataByKtSigntalk(sendMast, sendDetails, isReserveSend);
case ktGibis:
return new AcceptDataByKtGibis(sendMast, sendDetails, isReserveSend);
// FIXME: kkotalk 추가
case kkoTalk:
return new AcceptDataByKkoTalk(sendMast, sendDetails, isReserveSend);
default:
throw new IllegalAccessError(postSeCd.getCode() + "에 대한 전자고지 접수 Class가 정의되지 않았습니다.");
}

@ -1,5 +1,24 @@
package cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import cokr.xit.ens.core.aop.EnsResponseVO;
import cokr.xit.ens.core.exception.EnsException;
import cokr.xit.ens.core.exception.code.EnsErrCd;
@ -26,21 +45,10 @@ import cokr.xit.ens.modules.common.ctgy.intgrnnoti.model.TmpltMngIntgrnDTO;
import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.OrgMng;
import cokr.xit.ens.modules.common.ctgy.sys.mng.service.OrgMngService;
import cokr.xit.ens.modules.common.ctgy.sys.mng.service.TmpltMngService;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
// TODO: Accept
@Slf4j
@Component
@RequiredArgsConstructor
@ -118,6 +126,28 @@ public class IntgrnNotiAcceptor implements EnsPhaseProcSupport<EnsResponseVO, In
result.add(String.format("마감일시보다 \"처리마감시간(상대시간)\"이 느립니다. [ document[%d].acpt_data.kko_md.read_expired_sec ]", i.get()));
}
}
// FIXME: kkotalk 추가
if (!CmmnUtil.isEmpty(document.getAcpt_data().getKko_talk())) {
if (!CmmnUtil.isEmpty(document.getAcpt_data().getKko_talk().getReadExpiresAt())) {
if(document.getAcpt_data().getKko_talk().getReviewExpiresAt().compareTo(document.getAcpt_data().getKko_talk().getReadExpiresAt()) < 0){
result.add(String.format("재열람 만료일시가 최초 열람 만료일시 보다 느립니다. [ document[%d].acpt_data.kko_talk.review_expires_at ]", i.get()));
}
}
if (!CmmnUtil.isEmpty(document.getAcpt_data().getKko_talk().getReadExpiresAt())) {
String expDate = DateUtil.getTimeOfTimeT(document.getAcpt_data().getKko_talk().getReadExpiresAt(), "yyyyMMddHHmmss");
int sec = DateUtil.secByFromBetweenTo(DateUtil.toLocalDateTime(expDate), DateUtil.toLocalDateTime(reqDTO.getClose_dt()));
if (sec < 0)
result.add(String.format("마감일시보다 \"최초열람마감시간\"이 느립니다. [ document[%d].acpt_data.kko_talk.read_expires_at ]", i.get()));
}
if (!CmmnUtil.isEmpty(document.getAcpt_data().getKko_talk().getReviewExpiresAt())) {
String expDate = DateUtil.getTimeOfTimeT(document.getAcpt_data().getKko_talk().getReviewExpiresAt(), "yyyyMMddHHmmss");
int sec = DateUtil.secByFromBetweenTo(DateUtil.toLocalDateTime(expDate), DateUtil.toLocalDateTime(reqDTO.getClose_dt()));
if (sec < 0)
result.add(String.format("마감일시보다 \"재열람마감시간\"이 느립니다. [ document[%d].acpt_data.kko_talk.review_expires_at ]", i.get()));
}
}
if (!CmmnUtil.isEmpty(document.getAcpt_data().getNv_st())) {
if (!CmmnUtil.isEmpty(document.getAcpt_data().getNv_st().getValid_duration_at())) {
String expDate = DateUtil.absTimeSecToDate(document.getAcpt_data().getNv_st().getValid_duration_at(), "yyyyMMddHHmmss");
@ -221,6 +251,8 @@ public class IntgrnNotiAcceptor implements EnsPhaseProcSupport<EnsResponseVO, In
.jMblPageData(CmmnUtil.isEmpty(document.getXit_property().getMbl_page_data()) ? null : gson.toJson(document.getXit_property().getMbl_page_data()))
.jAcptDocKkoAt(CmmnUtil.isEmpty(document.getAcpt_data().getKko_at()) ? null : gson.toJson(document.getAcpt_data().getKko_at()))
.jAcptDocKkoMd(CmmnUtil.isEmpty(document.getAcpt_data().getKko_md()) ? null : gson.toJson(document.getAcpt_data().getKko_md()))
// FIXME: kkotalk 추가
.jAcptDocKkoTalk(CmmnUtil.isEmpty(document.getAcpt_data().getKko_talk()) ? null : gson.toJson(document.getAcpt_data().getKko_talk()))
.jAcptDocNvSt(CmmnUtil.isEmpty(document.getAcpt_data().getNv_st()) ? null : gson.toJson(document.getAcpt_data().getNv_st()))
.jAcptDocKtSt(CmmnUtil.isEmpty(document.getAcpt_data().getKt_st()) ? null : gson.toJson(document.getAcpt_data().getKt_st()))
.jAcptDocKtGbs(CmmnUtil.isEmpty(document.getAcpt_data().getKt_gbs()) ? null : gson.toJson(document.getAcpt_data().getKt_gbs()))
@ -228,6 +260,8 @@ public class IntgrnNotiAcceptor implements EnsPhaseProcSupport<EnsResponseVO, In
.curStatCd(IntgrnDtlStatCd.REG)
.sendDetailIdKkoAt(null)
.sendDetailIdKkoMd(null)
// FIXME: kkotalk 추가
.sendDetailIdKkoTalk(null)
.sendDetailIdNvSt(null)
.sendDetailIdKtSt(null)
.sendDetailIdKtGbs(null)

@ -0,0 +1,120 @@
package cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.kkotalk;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import cokr.xit.ens.core.aop.EnsResponseVO;
import cokr.xit.ens.core.exception.code.EnsErrCd;
import cokr.xit.ens.core.monitor.slack.event.MonitorEvent;
import cokr.xit.ens.modules.common.code.PostSeCd;
import cokr.xit.ens.modules.common.code.StatCd;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.domain.IntgrnSendDetail;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.domain.repository.IntgrnSendDetailRepository;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.AcceptDataEventListener;
import cokr.xit.ens.modules.common.monitor.MessageByPhase;
import cokr.xit.ens.modules.kkomydoc.service.event.KkoMydocSendRealtimeEvent;
import cokr.xit.ens.modules.kkomydoc.service.event.KkoMydocSendReserveEvent;
import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO;
import cokr.xit.ens.modules.kkotalk.service.KkoTalkService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
// FIXME: 카카오톡 신규 추가
@Slf4j
@Component
@RequiredArgsConstructor
public class AcceptByKkoTalkEventListener implements AcceptDataEventListener<AcceptDataByKkoTalk, KkotalkDTO.SendDetailKkoTalkDTO> {
private final ApplicationEventPublisher applicationEventPublisher;
private final KkoTalkService kkoTalkService;
private final IntgrnSendDetailRepository intgrnSendDetailRepository;
@Override
public void accept(AcceptDataByKkoTalk ev) {
final PostSeCd POST_SE_CD = PostSeCd.kkoMydoc;
EnsResponseVO responseVO = null;
try {
responseVO = kkoTalkService.accept(ev.createReqDto());
Map<String, Object> resultInfo = (Map<String, Object>) responseVO.getResultInfo();
Long sendMastId = (Long) resultInfo.get("sendMastId");
List<KkotalkDTO.SendDetailKkoTalkDTO> sendDetails = (List<KkotalkDTO.SendDetailKkoTalkDTO>) resultInfo.get("sendDetails");
if (EnsErrCd.OK.equals(responseVO.getErrCode())) {
Map<String, Long> sendDetailIdMap = convertSendDetailIdMap(sendDetails);
this.acceptSuccess(ev.getIntgrnSendDetails(), POST_SE_CD, sendDetailIdMap);
intgrnSendDetailRepository.saveAll(ev.getIntgrnSendDetails());
} else {
this.acceptFail(ev.getIntgrnSendDetails(), POST_SE_CD, responseVO.getErrCode(), String.format("%s %s", responseVO.getErrMsg(), responseVO.getResultInfo().toString()));
intgrnSendDetailRepository.saveAll(ev.getIntgrnSendDetails());
return;
}
if (ev.IsReserveSend()) {
KkoMydocSendReserveEvent event = KkoMydocSendReserveEvent.builder()
.sendMastIds(Arrays.asList(sendMastId))
.build();
applicationEventPublisher.publishEvent(event);
} else {
KkoMydocSendRealtimeEvent event = KkoMydocSendRealtimeEvent.builder()
.sendMastIds(Arrays.asList(sendMastId))
// .callback(() -> this.fetch(sendMastId, ev.getSendDetails()))
.build();
applicationEventPublisher.publishEvent(event);
}
} catch (Exception e) {
this.acceptFail(ev.getIntgrnSendDetails(), POST_SE_CD, EnsErrCd.ACPT999, e.getMessage());
intgrnSendDetailRepository.saveAll(ev.getIntgrnSendDetails());
responseVO = EnsResponseVO.errBuilder().errCode(EnsErrCd.ERR999).errMsg(e.getMessage()).build();
} finally {
if (!EnsErrCd.OK.equals(responseVO.getErrCode())) {
String message = createFailMessage(ev.getIntgrnSendDetails().get(0).getIntgrnSendMast().getIntSendMastId(), POST_SE_CD, responseVO);
applicationEventPublisher.publishEvent(MonitorEvent.builder()
.message(MessageByPhase.builder()
.oClass(getClass().getSimpleName() + "." + new Throwable().getStackTrace()[0].getMethodName())
.postSeCd(PostSeCd.intgrnNoti)
.statCd(StatCd.sendfail)
.errCd(responseVO.getErrCode())
.message(message)
.build())
.build());
}
}
}
@Override
public Map<String, Long> convertSendDetailIdMap(List<KkotalkDTO.SendDetailKkoTalkDTO> sendDetails) {
Map<String, Long> sendDetailIdMap = sendDetails.stream()
.map(row -> {
Map<String, Object> m = new HashMap<>();
m.put("key", row.getExternalId());
m.put("value", row.getSendDetailId());
return m;
})
// .map(row -> {
// Map<String, Object> m = new HashMap<>();
// m.put("key", row.getPropExternalDocumentUuid());
// m.put("value", row.getSendDetailId());
// return m;
// })
.collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (Long) m.get("value"), (k1, k2) -> k1));
return sendDetailIdMap;
}
@Override
public void setSendDetailId(IntgrnSendDetail intgrnSendDetail, Long sendDetailId) {
intgrnSendDetail.setSendDetailIdKkoTalk(sendDetailId);
}
}

@ -0,0 +1,92 @@
package cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.kkotalk;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import cokr.xit.ens.core.model.EnsBillAcptReqDTO;
import cokr.xit.ens.core.utils.CmmnUtil;
import cokr.xit.ens.core.utils.crypto.AES256;
import cokr.xit.ens.core.utils.crypto.Crypto;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.domain.IntgrnSendDetail;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.domain.IntgrnSendMast;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.AcceptData;
import cokr.xit.ens.modules.kkomydoc.model.config.XitProperty;
import cokr.xit.ens.modules.kkotalk.model.KkotalkApiDTO;
import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO;
import cokr.xit.ens.modules.kkotalk.model.config.Document;
// FIXME: 카카오톡 신규 추가
public class AcceptDataByKkoTalk extends AcceptData<KkotalkDTO.KkoTalkAcceptReqDTO> {
private AES256 aes256 = new AES256(Crypto.AES256.getKey());
public AcceptDataByKkoTalk(IntgrnSendMast intgrnSendMast, List<IntgrnSendDetail> intgrnSendDetails, Boolean isReserveSend) {
super(intgrnSendMast, intgrnSendDetails, isReserveSend);
}
@Override
public KkotalkDTO.KkoTalkAcceptReqDTO createReqDto() {
return KkotalkDTO.KkoTalkAcceptReqDTO.builder()
.vender(intgrnSendMast.getVender().getCode())
.org_cd(intgrnSendMast.getOrgCd())
.tmplt_cd(intgrnSendMast.getTmpltCd())
.post_bundle_title(intgrnSendMast.getPostBundleTitle())
.send_dt(intgrnSendMast.getSendDt().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")))
.close_dt(intgrnSendMast.getCloseDt().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")))
.documents(intgrnSendDetails.stream().map(this::createDocument).collect(Collectors.toList()))
.build();
}
protected Document createDocument(IntgrnSendDetail intgrnSendDetail) {
KkotalkApiDTO.Envelope envelope = gson.fromJson(intgrnSendDetail.getJAcptDocKkoTalk(), KkotalkApiDTO.Envelope.class);
return Document.builder()
.title(envelope.getTitle())
.content(envelope.getContent())
.hash(envelope.getHash())
.guide(envelope.getGuide())
.payload(envelope.getPayload())
.readExpiresAt(envelope.getReadExpiresAt())
.reviewExpiresAt(envelope.getReviewExpiresAt())
.useNonPersonalizedNotification(envelope.getUseNonPersonalizedNotification())
.ci(envelope.getCi())
.phoneNumber(envelope.getPhoneNumber())
.name(envelope.getName())
.birthday(envelope.getBirthday())
.externalId(intgrnSendDetail.getLinkedUuid())
.xit_property(this.createXitProperty(intgrnSendDetail))
.build();
}
protected XitProperty createXitProperty(IntgrnSendDetail intgrnSendDetail) {
return XitProperty.builder()
.mbl_page_data(CmmnUtil.isEmpty(intgrnSendDetail.getJMblPageData()) ? null : gson.fromJson(intgrnSendDetail.getJMblPageData(), Map.class))
// .bill_link_info(this.createBillLinkInfo(intgrnSendDetail))
.bill_acpt_data(this.createBillAcptData(intgrnSendDetail))
// .jid(intgrnSendDetail.getJids())
.jid(aes256.decrypt(intgrnSendDetail.getJid()))
.tmplt_msg_data(this.createTmpltMsgData(intgrnSendDetail))
.build();
}
protected EnsBillAcptReqDTO createBillAcptData(IntgrnSendDetail intgrnSendDetail) {
if (CmmnUtil.isEmpty(intgrnSendDetail.getBill()))
return null;
return EnsBillAcptReqDTO.builder()
.billUid(intgrnSendDetail.getBill().getBillUid())
.billerUserKey(intgrnSendDetail.getBill().getBillerUserKey())
.billSe(intgrnSendDetail.getBill().getBillSeCd())
.build();
}
protected Map<String, String> createTmpltMsgData(IntgrnSendDetail intgrnSendDetail) {
if (CmmnUtil.isEmpty(intgrnSendDetail.getJTmpltMsgData()))
return null;
else
return gson.fromJson(intgrnSendDetail.getJTmpltMsgData(), Map.class);
}
}

@ -0,0 +1,18 @@
package cokr.xit.ens.modules.common.ctgy.sys.mng.mapper;
/**
* <pre>
* description :
* packageName : cokr.xit.ens.modules.common.ctgy.sys.mng.mapper
* fileName : IOrgMngMapper
* author : limju
* date : 2024 9 03
* ======================================================================
*
* ----------------------------------------------------------------------
* 2024 9 03 limju
*
* </pre>
*/
public interface IOrgMngMapper {
}

@ -1,5 +1,19 @@
package cokr.xit.ens.modules.common.ctgy.sys.mng.presentation;
import java.util.List;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import cokr.xit.ens.core.aop.EnsResponseVO;
import cokr.xit.ens.modules.common.ctgy.sys.mng.model.TmpltMngDTO;
import cokr.xit.ens.modules.common.ctgy.sys.mng.model.TmpltMngSearchDTO;
@ -13,13 +27,6 @@ import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@Tag(name = "TmpltMngController")
@Slf4j
@ -59,6 +66,7 @@ public class TmpltMngController {
}
// FIXME: 카카오톡 템플릿 신규 등록 추가 확인
@Operation(summary = "템플릿 신규 등록")
@Parameters({
@Parameter(in = ParameterIn.PATH, name = "dType"),
@ -85,7 +93,10 @@ public class TmpltMngController {
, value = "{\"orgCd\":\"EX_ORG001\",\"tmpltCd\":\"EX_TMPLT006\",\"title\":\"테스트 템플릿\",\"message\":\"해당 안내문은 다음과 같습니다.\",\"sndTelNo\":\"0212345678\",\"sendTel\":\"0256781234\",\"ciTransUseYn\":\"Y\",\"tmpltMsgUseYn\":\"Y\",\"tmpltCsInfoUseYn\":\"N\"}"),
@ExampleObject(name = "Example_07"
, summary = "KT 인증톡(ktgbs)"
, value = "{\"orgCd\":\"EX_ORG001\",\"tmpltCd\":\"EX_TMPLT007\",\"title\":\"테스트 템플릿\",\"message\":\"해당 안내문은 다음과 같습니다.\",\"msgCd\":\"MC001\",\"mtype\":\"mms\",\"optType\":1,\"sndTelNo\":\"044-211-3377\",\"sendTel\":\"044-211-3377\",\"ciTransUseYn\":\"Y\",\"tmpltMsgUseYn\":\"Y\",\"tmpltCsInfoUseYn\":\"Y\"}")
, value = "{\"orgCd\":\"EX_ORG001\",\"tmpltCd\":\"EX_TMPLT007\",\"title\":\"테스트 템플릿\",\"message\":\"해당 안내문은 다음과 같습니다.\",\"msgCd\":\"MC001\",\"mtype\":\"mms\",\"optType\":1,\"sndTelNo\":\"044-211-3377\",\"sendTel\":\"044-211-3377\",\"ciTransUseYn\":\"Y\",\"tmpltMsgUseYn\":\"Y\",\"tmpltCsInfoUseYn\":\"Y\"}"),
@ExampleObject(name = "Example_08"
, summary = "카카오톡 인증톡(kkotalk)"
, value = "{\"orgCd\":\"EX_ORG001\",\"tmpltCd\":\"EX_TMPLT008\",\"title\":\"테스트 템플릿\",\"message\":\"해당 안내문은 다음과 같습니다.\",\"csNumber\":\"02-123-1234\",\"csName\":\"고객센터\",\"ciTransUseYn\":\"Y\",\"tmpltMsgUseYn\":\"Y\",\"tmpltCsInfoUseYn\":\"N\"}")
})
})
@PostMapping(value = "/sys/mng/tmplt/{dType}", produces = MediaType.APPLICATION_JSON_VALUE)
@ -96,6 +107,7 @@ public class TmpltMngController {
}
// FIXME: 카카오톡 템플릿 변경 추가 확인
@Operation(summary = "템플릿 수정")
@Parameters({
@Parameter(in = ParameterIn.PATH, name = "dType"),
@ -122,7 +134,10 @@ public class TmpltMngController {
, value = "{\"orgCd\":\"EX_ORG001\",\"tmpltCd\":\"EX_TMPLT006\",\"title\":\"테스트 템플릿\",\"message\":\"해당 안내문은 다음과 같습니다.\",\"sndTelNo\":\"0212345678\",\"sendTel\":\"0256781234\",\"ciTransUseYn\":\"Y\",\"tmpltMsgUseYn\":\"Y\",\"tmpltCsInfoUseYn\":\"N\"}"),
@ExampleObject(name = "Example_07"
, summary = "KT 인증톡(ktgbs)"
, value = "{\"orgCd\":\"EX_ORG001\",\"tmpltCd\":\"EX_TMPLT007\",\"title\":\"테스트 템플릿\",\"message\":\"해당 안내문은 다음과 같습니다.\",\"msgCd\":\"MC001\",\"mtype\":\"mms\",\"optType\":1,\"sndTelNo\":\"044-211-3377\",\"sendTel\":\"044-211-3377\",\"ciTransUseYn\":\"Y\",\"tmpltMsgUseYn\":\"Y\",\"tmpltCsInfoUseYn\":\"Y\"}")
, value = "{\"orgCd\":\"EX_ORG001\",\"tmpltCd\":\"EX_TMPLT007\",\"title\":\"테스트 템플릿\",\"message\":\"해당 안내문은 다음과 같습니다.\",\"msgCd\":\"MC001\",\"mtype\":\"mms\",\"optType\":1,\"sndTelNo\":\"044-211-3377\",\"sendTel\":\"044-211-3377\",\"ciTransUseYn\":\"Y\",\"tmpltMsgUseYn\":\"Y\",\"tmpltCsInfoUseYn\":\"Y\"}"),
@ExampleObject(name = "Example_08"
, summary = "카카오톡 인증톡(kkotalk)"
, value = "{\"orgCd\":\"EX_ORG001\",\"tmpltCd\":\"EX_TMPLT008\",\"title\":\"테스트 템플릿\",\"message\":\"해당 안내문은 다음과 같습니다.\",\"csNumber\":\"02-123-1234\",\"csName\":\"고객센터\",\"ciTransUseYn\":\"Y\",\"tmpltMsgUseYn\":\"Y\",\"tmpltCsInfoUseYn\":\"N\"}")
})
})
@PutMapping(value = "/sys/mng/tmplt/{dType}", produces = MediaType.APPLICATION_JSON_VALUE)

@ -0,0 +1,154 @@
package cokr.xit.ens.modules.kkotalk.domain;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.Bill;
import cokr.xit.ens.modules.common.domain.BaseEntity;
import cokr.xit.ens.modules.common.domain.SendMast;
import cokr.xit.ens.modules.common.domain.support.FieldError;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
// FIXME: 카카오톡 신규 추가
@Entity
@Getter
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "ens_snd_dtl_kko_talk", schema = "", catalog = "", indexes = {
@Index(name = "idx_snd_dtl_kko_talk_uk_id", columnList = "envelope_id"),
@Index(name = "idx_snd_dtl_kko_talk_uk", columnList = "envelope_id, external_id"),
// @Index(name = "idx_kkomd_bill_uid", columnList = "mk_bill_uid"),
})
public class SendDetailKkoTalk extends BaseEntity {
@Id
// @GeneratedValue(strategy=GenerationType.AUTO)
@GeneratedValue(strategy = GenerationType.TABLE, generator = "SendDetailKkoTalk_Generator")
@TableGenerator(table = "ens_seq_generator", name = "SendDetailKkoTalk_Generator"
, pkColumnName = "seq_name", pkColumnValue = "SendDetailKkoTalk_id"
, initialValue = 0, allocationSize = 200)
private Long sendDetailId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "send_mast_id")
private SendMast sendMast;
@Column(name = "title", nullable = true, length = 40)
@Setter
private String title;
@Column(name = "link", nullable = true, length = 99)
@Setter
private String link;
@Column(name = "hash", nullable = true, length = 99)
@Setter
private String hash;
@Column(name = "read_expires_at", nullable = true)
private String readExpiresAt;
@Column(name = "review_expires_at", nullable = true)
private String reviewExpiresAt;
@Column(name = "guide", nullable = true)
private String guide;
/**
* payload
*/
@Column(name = "payload", nullable = true)
private String payload;
/**
* <pre>
*
* default: false
* </pre>
*/
@Column(name = "is_notification_unavailable", nullable = true)
private Boolean useNonPersonalizedNotification = false;
/**
* CI
*/
@Column(name = "ci", nullable = true)
private String ci;
/**
* <pre>
*
* ci
* </pre>
*/
@Column(name = "phone_number", nullable = true)
private String phoneNumber;
/**
* <pre>
*
* ci
* </pre>
*/
@Column(name = "name", nullable = true)
private String name;
/**
* <pre>
* (YYYY-MM-DD )
* ci
* </pre>
*/
@Column(name = "birthday", nullable = true)
private String birthday;
/**
* <pre>
* - 40
* </pre>
*/
@Column(name = "external_id", nullable = true)
private String externalId;
@Column(name = "envelope_id", nullable = true)
private String envelopeId;
@Embedded
@Setter
private FieldError error;
@Column(name = "mk_bill_use_yn", nullable = true, length = 1)
private String mkBillUseYn;
// @Column(name = "mk_bill_uid", nullable = true, length = 45)
// private String mkBillUid;
@JoinColumn(name = "bill_uid", referencedColumnName = "bill_uid")
@OneToOne(fetch = FetchType.LAZY)
private Bill bill;
@Column(name = "mk_jid", nullable = true, length = 24)
private String mkJid;
@Column(name = "mk_tmplt_msg_json_data", nullable = true, length = 4000)
private String mkTmpltMsgJsonData;
}

@ -0,0 +1,78 @@
package cokr.xit.ens.modules.kkotalk.domain;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.hibernate.proxy.HibernateProxy;
import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.TmpltMng;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
// FIXME: 카카오톡 신규 추가
@Entity
@Getter
@ToString
@SuperBuilder
@NoArgsConstructor
@Table(name = "ens_tmplt_mng_kko_talk", schema = "", catalog = "")
@DiscriminatorValue("kkotalk")
public class TmpltMngKkoTalk extends TmpltMng {
@Column(name = "pryMessage", nullable = true, length = 2000)
@Setter
private String pryMessage;
@Column(name = "cs_number", nullable = false, length = 20)
@Setter
private String csNumber;
@Column(name = "cs_name", nullable = false, length = 10)
@Setter
private String csName;
@Column(name = "ci_trans_use_yn", nullable = false, length = 1)
private String ciTransUseYn;
@Column(name = "tmplt_msg_use_yn", nullable = false, length = 1)
private String tmpltMsgUseYn;
@Column(name = "tmplt_cs_info_use_yn", nullable = false, length = 1)
private String tmpltCsInfoUseYn;
@Override
public final boolean equals(Object o) {
if (this == o)
return true;
if (o == null)
return false;
Class<?> oEffectiveClass = o instanceof HibernateProxy ?
((HibernateProxy)o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();
Class<?> thisEffectiveClass = this instanceof HibernateProxy ?
((HibernateProxy)this).getHibernateLazyInitializer().getPersistentClass() : this.getClass();
if (thisEffectiveClass != oEffectiveClass)
return false;
TmpltMngKkoTalk that = (TmpltMngKkoTalk)o;
return getOrgMng() != null && Objects.equals(getOrgMng(), that.getOrgMng());
}
@Override
public final int hashCode() {
return this instanceof HibernateProxy ?
((HibernateProxy)this).getHibernateLazyInitializer().getPersistentClass().hashCode() :
getClass().hashCode();
}
}

@ -0,0 +1,22 @@
// package cokr.xit.ens.modules.kkotalk.domain.repository;
//
// import java.util.List;
// import java.util.Optional;
//
// import org.springframework.data.jpa.repository.JpaRepository;
//
// import cokr.xit.ens.modules.common.domain.SendMast;
// import cokr.xit.ens.modules.kkotalk.domain.SendDetailKkoTalk;
//
// public interface SendDetailKkoTalkRepository
// extends JpaRepository<SendDetailKkoTalk, Long>, SendDetailKkoTalkRepositoryCustom {
//
// List<SendDetailKkoTalk> findAllBySendMast(SendMast sendMast);
//
// List<SendDetailKkoTalk> findAllBySendMastAndDocumentBinderUuidIsNotNull(SendMast sendMast);
//
// Optional<SendDetailKkoTalk> findByPropExternalDocumentUuidAndDocumentBinderUuid(String propExternalDocumentUuid, String documentBinderUuid);
//
// Optional<SendDetailKkoTalk> findByPropExternalDocumentUuid(String propExternalDocumentUuid);
//
// }

@ -0,0 +1,13 @@
// package cokr.xit.ens.modules.kkotalk.domain.repository;
//
// import java.util.List;
// import java.util.Optional;
//
// import cokr.xit.ens.modules.common.biztmplt.SendDetailRepositorySupport;
// import cokr.xit.ens.modules.kkotalk.domain.SendDetailKkoTalk;
//
// public interface SendDetailKkoTalkRepositoryCustom extends SendDetailRepositorySupport<SendDetailKkoTalk> {
//
// List<SendDetailKkoTalk> findAllFetchBySendMastId(Long sendMastId);
// Optional<SendDetailKkoTalk> findFetchByPropExternalDocumentUuidAndDocumentBinderUuid(String propExternalDocumentUuid, String documentBinderUuid);
// }

@ -0,0 +1,57 @@
// package cokr.xit.ens.modules.kkotalk.domain.repository;
//
// import static cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.QBill.*;
// import static cokr.xit.ens.modules.common.domain.QSendMast.*;
// import static cokr.xit.ens.modules.kkomydoc.domain.QSendDetailKkoMydoc.*;
//
// import java.util.List;
// import java.util.Optional;
//
// import com.querydsl.core.BooleanBuilder;
// import com.querydsl.jpa.impl.JPAQueryFactory;
//
// import cokr.xit.ens.core.utils.CmmnUtil;
// import cokr.xit.ens.modules.kkotalk.domain.SendDetailKkoTalk;
// import lombok.RequiredArgsConstructor;
//
// @RequiredArgsConstructor
// public class SendDetailKkoTalkRepositoryImpl implements SendDetailKkoTalkRepositoryCustom {
// private final JPAQueryFactory query;
//
// // @Override
// // public List<SendDetailKkoMydoc> findAllFetchBySendMastIdAndMkBillUseYAndUrlIsNull(Long sendMastId, boolean urlIsNull) {
// // return query.selectFrom(sendDetailKkoMydoc)
// // .innerJoin(sendDetailKkoMydoc.sendMast, sendMast)
// // .fetchJoin()
// // .leftJoin(sendDetailKkoMydoc.kkoBillMast, kkoBillMast)
// // .fetchJoin()
// // .where(sendDetailKkoMydoc.sendMast.sendMastId.eq(sendMastId)
// // .and(sendDetailKkoMydoc.mkBillUseYn.eq("Y"))
// // .and(urlIsNull ? sendDetailKkoMydoc.kkoBillMast.url.isNull() : sendDetailKkoMydoc.kkoBillMast.url.isNotNull())
// // )
// // .fetch();
// // }
//
// @Override
// public List<SendDetailKkoTalk> findAllFetchBySendMastId(Long sendMastId) {
// return query.selectFrom(sendDetailKkoTalk)
// .innerJoin(sendDetailKkoTalk.sendMast, sendMast).fetchJoin()
// // .leftJoin(sendDetailKkoMydoc.kkoBillMast, kkoBillMast)
// .leftJoin(sendDetailKkTalk.bill, bill).fetchJoin()
// .where(sendDetailKkoTalk.sendMast.sendMastId.eq(sendMastId))
// .fetch();
// }
//
// @Override
// public Optional<SendDetailKkoTalk> findFetchByPropExternalDocumentUuidAndDocumentBinderUuid(String propExternalDocumentUuid, String documentBinderUuid) {
// BooleanBuilder builder = new BooleanBuilder();
// builder.and(sendDetailKkoMydoc.documentBinderUuid.eq(documentBinderUuid));
// if (!CmmnUtil.isEmpty(propExternalDocumentUuid))
// builder.and(sendDetailKkoTalk.propExternalDocumentUuid.eq(propExternalDocumentUuid));
//
// return Optional.ofNullable(query.selectFrom(sendDetailKkoTalk)
// .innerJoin(sendDetailKkoTalk.sendMast, sendMast).fetchJoin()
// .where(builder)
// .fetchOne());
// }
// }

@ -0,0 +1,10 @@
package cokr.xit.ens.modules.kkotalk.domain.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import cokr.xit.ens.modules.kkotalk.domain.TmpltMngKkoTalk;
// FIXME: 카카오톡 신규 추가
public interface TmpltMngKkoTalkRepository extends JpaRepository<TmpltMngKkoTalk, Long>,
TmpltMngKkoTalkRepositoryCustom {
}

@ -0,0 +1,11 @@
package cokr.xit.ens.modules.kkotalk.domain.repository;
import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.repository.TmpltMngRepositorySupport;
import cokr.xit.ens.modules.common.ctgy.sys.mng.model.TmpltMngSearchDTO;
import cokr.xit.ens.modules.kkotalk.domain.TmpltMngKkoTalk;
import cokr.xit.ens.modules.kkotalk.model.TmpltMngKkoTalkDTO;
// FIXME: 카카오톡 신규 추가
public interface TmpltMngKkoTalkRepositoryCustom extends TmpltMngRepositorySupport<TmpltMngKkoTalk, TmpltMngSearchDTO, TmpltMngKkoTalkDTO> {
}

@ -0,0 +1,110 @@
package cokr.xit.ens.modules.kkotalk.domain.repository;
import static cokr.xit.ens.modules.common.ctgy.sys.mng.domain.QOrgMng.*;
import static cokr.xit.ens.modules.kkotalk.domain.QTmpltMngKkoTalk.*;
import java.util.List;
import java.util.Optional;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.QBean;
import com.querydsl.jpa.impl.JPAQueryFactory;
import cokr.xit.ens.core.utils.CmmnUtil;
import cokr.xit.ens.modules.common.ctgy.sys.mng.model.TmpltMngSearchDTO;
import cokr.xit.ens.modules.kkotalk.domain.TmpltMngKkoTalk;
import cokr.xit.ens.modules.kkotalk.model.TmpltMngKkoTalkDTO;
import lombok.RequiredArgsConstructor;
// FIXME: 카카오톡 신규 추가
@RequiredArgsConstructor
public class TmpltMngKkoTalkRepositoryImpl implements TmpltMngKkoTalkRepositoryCustom {
private final JPAQueryFactory query;
@Override
public Optional<TmpltMngKkoTalk> findFetchByOrgCdAndTmpltCd(String orgCd, String tmpltCd) {
return Optional.ofNullable(query.selectFrom(tmpltMngKkoTalk)
.where(tmpltMngKkoTalk.orgMng.orgCd.eq(orgCd)
.and(tmpltMngKkoTalk.tmpltCd.eq(tmpltCd)))
.fetchOne());
}
@Override
public List<TmpltMngKkoTalk> findAllFetchBySearchDTO(TmpltMngSearchDTO searchDTO) {
return query.selectFrom(tmpltMngKkoTalk)
.innerJoin(tmpltMngKkoTalk.orgMng, orgMng)
.fetchJoin()
.where(eqSearchDTO(searchDTO))
.fetch();
}
@Override
public Optional<TmpltMngKkoTalkDTO> findDtoByOrgCdAndTmpltCd(String orgCd, String tmpltCd) {
return Optional.ofNullable(query.select(mappedDto())
.from(tmpltMngKkoTalk)
.where(tmpltMngKkoTalk.orgMng.orgCd.eq(orgCd)
.and(tmpltMngKkoTalk.tmpltCd.eq(tmpltCd)))
.fetchOne());
}
@Override
public List<TmpltMngKkoTalkDTO> findAllDtoBySearchDTO(TmpltMngSearchDTO searchDTO) {
return query.select(mappedDto())
.from(tmpltMngKkoTalk)
.innerJoin(tmpltMngKkoTalk.orgMng, orgMng)
.where(eqSearchDTO(searchDTO))
.fetch();
}
private QBean<TmpltMngKkoTalkDTO> mappedDto() {
return Projections.fields(TmpltMngKkoTalkDTO.class
, tmpltMngKkoTalk.dtype
, tmpltMngKkoTalk.orgMng.orgCd
, tmpltMngKkoTalk.orgMng.orgNm
, tmpltMngKkoTalk.tmpltCd
, tmpltMngKkoTalk.title
, tmpltMngKkoTalk.message
, tmpltMngKkoTalk.useYn
, tmpltMngKkoTalk.csNumber
, tmpltMngKkoTalk.csName
, tmpltMngKkoTalk.ciTransUseYn
, tmpltMngKkoTalk.tmpltMsgUseYn
, tmpltMngKkoTalk.tmpltCsInfoUseYn
, tmpltMngKkoTalk.pryMessage
);
}
@Override
public Optional<TmpltMngKkoTalk> findFetchByOrgCdAndTmpltCdAndUseYn(String orgCd, String tmpltCd, String useYn) {
return Optional.ofNullable(
query.selectFrom(tmpltMngKkoTalk)
.innerJoin(tmpltMngKkoTalk.orgMng, orgMng)
.fetchJoin()
.where(
tmpltMngKkoTalk.orgMng.orgCd.eq(orgCd)
.and(tmpltMngKkoTalk.tmpltCd.eq(tmpltCd))
.and(tmpltMngKkoTalk.useYn.eq(useYn))
)
.fetchOne());
}
private BooleanBuilder eqSearchDTO(TmpltMngSearchDTO searchDTO) {
BooleanBuilder builder = new BooleanBuilder();
if (!CmmnUtil.isEmpty(searchDTO.getSchOrgCd()))
builder.and(tmpltMngKkoTalk.orgMng.orgCd.eq(searchDTO.getSchOrgCd()));
if (!CmmnUtil.isEmpty(searchDTO.getSchTmpltCd()))
builder.and(tmpltMngKkoTalk.tmpltCd.eq(searchDTO.getSchTmpltCd()));
if (!CmmnUtil.isEmpty(searchDTO.getSchTitle()))
builder.and(tmpltMngKkoTalk.title.like(searchDTO.getSchTitle() + "%"));
return builder;
}
}

@ -0,0 +1,34 @@
package cokr.xit.ens.modules.kkotalk.mapper;
import java.util.List;
import java.util.Optional;
import org.apache.ibatis.annotations.Mapper;
import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO;
/**
* <pre>
* description :
* packageName : cokr.xit.ens.modules.kkotalk.mapper
* fileName : IKkoTalkMapper
* author : limju
* date : 2024 9 03
* ======================================================================
*
* ----------------------------------------------------------------------
* 2024 9 03 limju
*
* </pre>
*/
// FIXME: 카카오톡 신규 추가
@Mapper
public interface IKkoTalkMapper {
void saveSndDtlKkoTalk(KkotalkDTO.SendDetailKkoTalkDTO sendDetailKkoTalk);
List<KkotalkDTO.SendDetailKkoTalkDTO> findAllBySendMastId(Long sendMastId);
List<KkotalkDTO.SendDetailKkoTalkDTO> findAllFetchBySendMastId(Long sendMastId);
Optional<KkotalkDTO.SendDetailKkoTalkDTO> findFetchByExternalIdAndEnvelopeId(String externalId, String envelopeId);
}

@ -1,10 +1,13 @@
package cokr.xit.ens.modules.kkotalk.model;
import java.time.LocalDateTime;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.Size;
import cokr.xit.ens.core.model.EnsAcceptReqDTO;
import cokr.xit.ens.modules.kkotalk.model.config.Document;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
@ -27,6 +30,7 @@ import lombok.experimental.SuperBuilder;
*
* </pre>
*/
// FIXME: 카카오톡 신규 추가
public class KkotalkDTO extends KkotalkApiDTO {
//------------------ envelop ----------------------------------------------------------------------
@ -108,4 +112,36 @@ public class KkotalkDTO extends KkotalkApiDTO {
}
//------------------ bulk ----------------------------------------------------------------------
@SuperBuilder
@Data
@Schema(name = "KkoTalkAcceptReqDTO")
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public static class KkoTalkAcceptReqDTO extends EnsAcceptReqDTO {
@Valid
private List<Document> documents;
}
@SuperBuilder
@Data
@Schema(name = "SendDetailKkoTalkDTO")
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public static class SendDetailKkoTalkDTO extends Envelope {
private String link;
private Long sendDetailId;
private Long sendMastId;
private String errorCode;
private String errorMessage;
//private FieldError error;
private String mkBillUseYn;
private String billUid;
private String mkJid;
private String mkTmpltMsgJsonData;
// Add created and updated timestamps from BaseEntity if needed
private LocalDateTime registAt;
private LocalDateTime lastUpdtDt;
}
}

@ -0,0 +1,53 @@
package cokr.xit.ens.modules.kkotalk.model;
import java.io.Serializable;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.Length;
import cokr.xit.ens.modules.common.ctgy.sys.mng.model.TmpltMngByTypeDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
// FIXME: 카카오톡 신규 추가
@EqualsAndHashCode(callSuper = true)
@Data
@SuperBuilder
@NoArgsConstructor
@Schema(name = "TmpltMngKkoTalkDTO")
public class TmpltMngKkoTalkDTO extends TmpltMngByTypeDTO implements Serializable {
@NotEmpty(message = "고객센터 전화번호는 필수 입력값 입니다.")
@Length(max = 20, message = "고객센터 전화번호의 최대 길이를 초과 했습니다.")
@Schema(required = true, title = "고객센터 전화번호", example = "02-123-4567")
private String csNumber;
@NotEmpty(message = "고객센터 명은 필수 입력값 입니다.")
@Length(max = 10, message = "고객센터 명의 최대 길이를 초과 했습니다.")
@Schema(required = true, title = "고객센터 명", example = "콜센터")
private String csName;
@NotEmpty(message = "CI변환사용여부는 필수 입력값 입니다.")
@Length(max = 1, message = "CI변환사용여부의 최대 길이를 초과 했습니다.")
@Pattern(regexp = "(Y|N)", message = "CI변환사용여부는 Y 또는 N 만 입력 할 수 있습니다.")
@Schema(required = true, title = "CI변환사용여부", example = "Y")
private String ciTransUseYn;
@NotEmpty(message = "템플릿 메시지 사용여부는 필수 입력값 입니다.")
@Length(max = 1, message = "템플릿 메시지 사용여부의 최대 길이를 초과 했습니다.")
@Pattern(regexp = "(Y|N)", message = "템플릿 메시지 사용여부는 Y 또는 N 만 입력 할 수 있습니다.")
@Schema(required = true, title = "템플릿 메시지 사용여부", example = "Y")
private String tmpltMsgUseYn;
@NotEmpty(message = "템플릿 CS 정보 사용여부는 필수 입력값 입니다.")
@Length(max = 1, message = "템플릿 CS 정보 사용여부의 최대 길이를 초과 했습니다.")
@Pattern(regexp = "(Y|N)", message = "템플릿 CS 정보 사용여부는 Y 또는 N 만 입력 할 수 있습니다.")
@Schema(required = true, title = "템플릿 CS 정보 사용여부", example = "Y")
private String tmpltCsInfoUseYn;
}

@ -0,0 +1,36 @@
package cokr.xit.ens.modules.kkotalk.model.config;
import javax.validation.Valid;
import javax.validation.constraints.Size;
import cokr.xit.ens.modules.kkomydoc.model.config.XitProperty;
import cokr.xit.ens.modules.kkotalk.model.KkotalkApiDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
// FIXME: 카카오톡 신규 추가
@Getter
@ToString
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "Document")
public class Document extends KkotalkApiDTO.Envelope {
/**
* <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";
@Valid
private XitProperty xit_property;
}

@ -0,0 +1,25 @@
package cokr.xit.ens.modules.kkotalk.model.struct;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import cokr.xit.ens.core.mapstruct.GenericMapper;
import cokr.xit.ens.core.mapstruct.StructMapperConfig;
import cokr.xit.ens.modules.kkotalk.domain.TmpltMngKkoTalk;
import cokr.xit.ens.modules.kkotalk.model.TmpltMngKkoTalkDTO;
// FIXME: 카카오톡 신규 추가
@Mapper(config = StructMapperConfig.class)
public interface TmpltMngKkoTalkMapper extends GenericMapper<TmpltMngKkoTalkDTO, TmpltMngKkoTalk> {
@Override
@Mapping(target = "orgMng.orgCd", source = "orgCd")
TmpltMngKkoTalk toEntity(TmpltMngKkoTalkDTO tmpltMngKkoTalkDTO);
@Override
@Mapping(target = "orgCd", expression = "java(tmpltMngKkoTalk.getOrgMng().getOrgCd())")
TmpltMngKkoTalkDTO toDto(TmpltMngKkoTalk tmpltMngKkoTalk);
}

@ -0,0 +1,497 @@
package cokr.xit.ens.modules.kkotalk.service;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import cokr.xit.ens.core.aop.EnsResponseVO;
import cokr.xit.ens.core.exception.EnsException;
import cokr.xit.ens.core.exception.code.EnsErrCd;
import cokr.xit.ens.core.utils.CmmnUtil;
import cokr.xit.ens.modules.common.code.PostSeCd;
import cokr.xit.ens.modules.common.code.StatCd;
import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.OrgMng;
import cokr.xit.ens.modules.common.ctgy.sys.mng.service.OrgMngService;
import cokr.xit.ens.modules.common.domain.SendMast;
import cokr.xit.ens.modules.common.domain.repository.SendMastRepository;
import cokr.xit.ens.modules.common.domain.support.FieldError;
import cokr.xit.ens.modules.kkomydoc.domain.SendDetailKkoMydocTokenHist;
import cokr.xit.ens.modules.kkomydoc.domain.repository.SendDetailKkoMydocTokenHistRepository;
import cokr.xit.ens.modules.kkotalk.model.KkotalkApiDTO;
import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO;
import cokr.xit.ens.modules.kkotalk.service.support.IKkoTalkApiService;
import cokr.xit.ens.modules.kkotalk.service.support.KkoTalkAcceptor;
import cokr.xit.ens.modules.kkotalk.service.support.KkoTalkMaker;
import cokr.xit.ens.modules.kkotalk.service.support.KkoTalkRsltFetcher;
import cokr.xit.ens.modules.kkotalk.service.support.KkoTalkRsltProvider;
import cokr.xit.ens.modules.kkotalk.service.support.KkoTalkSender;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
// FIXME: 카카오톡 신규 추가
@Slf4j
@Service
@RequiredArgsConstructor
public class KkoTalkService {
private final KkoTalkAcceptor kkoTalkAcceptor;
private final KkoTalkMaker kkoTalkMaker;
private final KkoTalkSender kkoTalkSender;
private final KkoTalkRsltFetcher kkoTalkRsltFetcher;
private final KkoTalkRsltProvider kkoTalkRsltProvider;
private final IKkoTalkApiService kkoTalkApi;
private final SendMastRepository sendMastRepository;
private final SendDetailKkoMydocTokenHistRepository sendDetailKkoMydocTokenHistRepository;
private final OrgMngService orgMngService;
/**
*
*
* @param reqDTO
* @return
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public EnsResponseVO<?> accept(KkotalkDTO.KkoTalkAcceptReqDTO reqDTO) {
EnsResponseVO responseVO = null;
try {
responseVO = kkoTalkAcceptor.statBegin(reqDTO);
if (!EnsErrCd.OK.equals(responseVO.getErrCode()))
return responseVO;
responseVO = kkoTalkAcceptor.execute(reqDTO);
} catch (EnsException e) {
responseVO = EnsResponseVO.errBuilder()
.errCode(e.getErrCd())
.errMsg(e.getMessage())
.build();
}
return responseVO;
}
/**
*
*
* @param sendMastId
* @return
*/
@Transactional(propagation = Propagation.SUPPORTS)
public EnsResponseVO<?> remake(Long sendMastId) {
try {
SendMast sendMast = sendMastRepository.findById(sendMastId)
.orElseThrow(() -> new EnsException(EnsErrCd.SEND404, "일치하는 자료가 없습니다."));
if (!PostSeCd.kkoMydoc.equals(sendMast.getPostSe()))
throw new EnsException(EnsErrCd.SEND404, String.format("%s 자료가 아닙니다.", PostSeCd.kkoMydoc.getCodeNm()));
if (!(StatCd.accept.equals(sendMast.getStatCd())
|| StatCd.makefail.equals(sendMast.getStatCd())))
throw new EnsException(EnsErrCd.SEND404, "접수/제작실패(accept/makefail) 단계가 아닙니다.");
} catch (EnsException e) {
return EnsResponseVO.errBuilder()
.errCode(e.getErrCd())
.errMsg(e.getMessage())
.build();
}
kkoTalkMaker.statReady(Collections.singletonList(sendMastId));
return this.make(sendMastId);
}
/**
* ()
*
* @return
*/
@Transactional(propagation = Propagation.SUPPORTS)
public EnsResponseVO<?> makeAll() {
List<Long> sendMastIds = sendMastRepository.findAllByPostSeAndStatCdIn(PostSeCd.kkoMydoc, Arrays.asList(StatCd.accept))
.stream()
.map(row -> row.getSendMastId())
.collect(Collectors.toList());
if (sendMastIds.isEmpty())
return EnsResponseVO.errBuilder()
.errCode(EnsErrCd.MAKE404)
.errMsg("\"접수(accept)\" 상태인 자료가 없습니다")
.build();
kkoTalkMaker.statReady(sendMastIds);
List<EnsResponseVO> resultInfo = sendMastIds.stream()
.map(sendMastId -> this.make(sendMastId))
.collect(Collectors.toList());
return EnsResponseVO.okBuilder().resultInfo(resultInfo).build();
}
private EnsResponseVO<?> make(Long sendMastId) {
EnsResponseVO responseVO = kkoTalkMaker.statBegin(sendMastId);
if (!EnsErrCd.OK.equals(responseVO.getErrCode()))
return responseVO;
Optional<SendMast> sendMast = sendMastRepository.findById(sendMastId);
try {
//
// responseVO = this.makeBillLink(sendMastId);
// if (!EnsErrCd.OK.equals(responseVO.getErrCode()))
// throw new EnsException(responseVO.getErrCode(), responseVO.getErrMsg());
responseVO = kkoTalkMaker.execute(sendMastId);
if (!EnsErrCd.OK.equals(responseVO.getErrCode()))
throw new EnsException(responseVO.getErrCode(), responseVO.getErrMsg());
} catch (EnsException e) {
return EnsResponseVO.errBuilder()
.errCode(e.getErrCd())
.errMsg(e.getMessage())
.build();
}
return responseVO;
}
/**
* ()
*
* @param sendMastId
* @return
*/
// @Transactional(propagation = Propagation.SUPPORTS)
// public EnsResponseVO makeBillLink(Long sendMastId) {
// EnsResponseVO responseVO = kkoMydocMakerOfBillLink.execute(sendMastId);
//
// return responseVO;
// }
/**
* ()
*
* @return
*/
@Transactional(propagation = Propagation.SUPPORTS)
public EnsResponseVO<?> sendBulk(Long sendMastId) {
try {
SendMast sendMast = sendMastRepository.findById(sendMastId)
.orElseThrow(() -> new EnsException(EnsErrCd.SEND404, "일치하는 자료가 없습니다."));
if (!PostSeCd.kkoMydoc.equals(sendMast.getPostSe()))
throw new EnsException(EnsErrCd.SEND404, String.format("%s 자료가 아닙니다.", PostSeCd.kkoMydoc.getCodeNm()));
if (!StatCd.makeok.equals(sendMast.getStatCd()))
throw new EnsException(EnsErrCd.SEND404, "제작성공(makeok) 단계가 아닙니다.");
} catch (EnsException e) {
return EnsResponseVO.errBuilder()
.errCode(e.getErrCd())
.errMsg(e.getMessage())
.build();
}
List<Long> sendMastIds = Arrays.asList(sendMastId);
EnsResponseVO responseVO = kkoTalkSender.statReady(sendMastIds);
if (!EnsErrCd.OK.equals(responseVO.getErrCode()))
return responseVO;
return this.send(sendMastIds);
}
/**
* () -
*
* @return
*/
@Transactional(propagation = Propagation.SUPPORTS)
public EnsResponseVO<?> sendBulkAll() {
List<Long> sendMastIds = sendMastRepository.findAllByPostSeAndStatCdInAndSendDtBefore(PostSeCd.kkoMydoc, Arrays.asList(StatCd.makeok), LocalDateTime.now())
.stream()
.map(row -> row.getSendMastId())
.collect(Collectors.toList());
if (sendMastIds.size() < 1)
return EnsResponseVO.errBuilder()
.errCode(EnsErrCd.SEND404)
.errMsg("발송시간이 경과한 자료 중 \"제작성공(makeok)\" 상태인 자료가 없습니다")
.build();
EnsResponseVO responseVO = kkoTalkSender.statReady(sendMastIds);
if (!EnsErrCd.OK.equals(responseVO.getErrCode()))
return responseVO;
return this.send(sendMastIds);
}
private EnsResponseVO<?> send(List<Long> sendMastIds) {
List<EnsResponseVO> resultInfo = sendMastIds.stream()
.map(sendMastId -> {
kkoTalkSender.statBegin(sendMastId);
return sendMastId;
})
.map(sendMastId -> kkoTalkSender.execute(sendMastId))
.collect(Collectors.toList());
return EnsResponseVO.okBuilder().resultInfo(resultInfo).build();
}
/**
* ()
*
* @return
*/
@Transactional(propagation = Propagation.SUPPORTS)
public EnsResponseVO statBulk(Long sendMastId) {
try {
SendMast sendMast = sendMastRepository.findById(sendMastId)
.orElseThrow(() -> new EnsException(EnsErrCd.RSLT404, "일치하는 자료가 없습니다."));
if (!PostSeCd.kkoMydoc.equals(sendMast.getPostSe()))
throw new EnsException(EnsErrCd.RSLT404, String.format("%s 자료가 아닙니다.", PostSeCd.kkoMydoc.getCodeNm()));
if (!Arrays.asList(StatCd.sendok, StatCd.sendcmplt, StatCd.open).stream()
.anyMatch(statCd -> statCd.equals(sendMast.getStatCd())))
throw new EnsException(EnsErrCd.RSLT404, "전송성공/전송완료/열람중(sendok/sendcmplt/open) 단계가 아닙니다.");
} catch (EnsException e) {
return EnsResponseVO.errBuilder()
.errCode(e.getErrCd())
.errMsg(e.getMessage())
.build();
}
List<EnsResponseVO> responseVOS = (List<EnsResponseVO>) this.stat(Arrays.asList(sendMastId)).getResultInfo();
return responseVOS.get(0);
}
/**
* () -
*
* @return
*/
@Transactional(propagation = Propagation.SUPPORTS)
public EnsResponseVO<?> statBulkAll() {
List<Long> sendMastIds = sendMastRepository.findAllByPostSeAndStatCdIn(PostSeCd.kkoMydoc, Arrays.asList(StatCd.sendok, StatCd.sendcmplt, StatCd.open))
.stream()
.map(row -> row.getSendMastId())
.collect(Collectors.toList());
if (sendMastIds.size() < 1)
return EnsResponseVO.errBuilder()
.errCode(EnsErrCd.RSLT404)
.errMsg("\"전송성공/전송완료/열람중(sendok/sendcmplt/open)\" 상태인 자료가 없습니다")
.build();
return this.stat(sendMastIds);
}
private EnsResponseVO<?> stat(List<Long> sendMastIds) {
List<EnsResponseVO> resultInfo = sendMastIds.stream()
.map(sendMastId -> kkoTalkRsltFetcher.execute(sendMastId))
.collect(Collectors.toList());
return EnsResponseVO.okBuilder().resultInfo(resultInfo).build();
}
/**
*
*
* @param externalDocumentUuid
* @param token
* @param externalDocumentUuid
* @return
*/
@Transactional
public EnsResponseVO<?> tokenVerify(String orgCd, String envelopId, String token, String externalDocumentUuid) {
EnsResponseVO responseVO = null;
try {
if (CmmnUtil.isEmpty(envelopId))
throw new EnsException(EnsErrCd.ERR401, "문서식별번호(은)는 필수조건 입니다.");
if (CmmnUtil.isEmpty(token))
throw new EnsException(EnsErrCd.ERR401, "토큰(은)는 필수조건 입니다.");
OrgMng orgMng = orgMngService.find(orgCd).getResultInfo();
KkotalkApiDTO.ValidTokenResponse resp = kkoTalkApi.validToken(
KkotalkApiDTO.ValidTokenRequest.builder()
.signguCode(orgMng.getOrgCd())
.ffnlgCode("11")
.envelopeId(envelopId)
.token(token)
.build()
);
if (!StringUtils.isEmpty(resp.getErrorCode())){
responseVO = EnsResponseVO.errRsltBuilder()
.errCode(EnsErrCd.ERR600)
.errMsg(String.format("%s %s", resp.getErrorCode(), resp.getErrorMessage()))
.resultInfo(resp)
.build();
}else {
responseVO = EnsResponseVO.okBuilder()
.resultInfo(resp)
.build();
}
// ResponseEntity<String> resp = kkoMydocApi.token(orgMng.getKkoMdAccessToken(), documentBinderUuid, token);
// if (resp.getStatusCode() != HttpStatus.OK)
// throw new EnsException(EnsErrCd.ERR620, String.format("토큰검증 실패. 실패사유: %s %s", resp.getStatusCode().toString(), resp.getBody()));
// Map<String, Object> mResponse = null;
// try {
// Gson gson = new GsonBuilder().disableHtmlEscaping().registerTypeAdapter(Map.class, new MapDeserailizer()).serializeNulls().create();
// mResponse = gson.fromJson(resp.getBody(), Map.class);
// } catch (Exception e) {
// throw new EnsException(EnsErrCd.ERR505, String.format("토큰검증 응답데이터 파싱 실패. %s", resp.getBody()));
// }
// String tokenUsedAt = (String) mResponse.get("token_status");
// if ("USED".equals(tokenUsedAt)) {
// responseVO = EnsResponseVO.okBuilder()
// .resultInfo(mResponse)
// .build();
// } else {
// String errorCode = (String) mResponse.get("error_code");
// String errorMessage = (String) mResponse.get("error_message");
// responseVO = EnsResponseVO.errRsltBuilder()
// .errCode(EnsErrCd.ERR600)
// .errMsg(String.format("%s %s", errorCode, errorMessage))
// .resultInfo(mResponse)
// .build();
// }
} catch (EnsException e) {
responseVO = EnsResponseVO.errBuilder()
.errCode(e.getErrCd())
.errMsg(e.getMessage())
.build();
} catch (Exception e) {
responseVO = EnsResponseVO.errBuilder()
.errCode(EnsErrCd.ERR999)
.errMsg(e.getMessage())
.build();
} finally {
if (EnsErrCd.OK.equals(responseVO.getErrCode())) {
Map<String, Object> resultInfo = (Map<String, Object>) responseVO.getResultInfo();
sendDetailKkoMydocTokenHistRepository.save(SendDetailKkoMydocTokenHist.builder()
.externalDocumentUuid(externalDocumentUuid)
.documentBinderUuid(envelopId)
.token(token)
.tokenStatus((String) resultInfo.get("token_status"))
.tokenUsedAt((Long) resultInfo.get("token_used_at"))
.docBoxSentAt((Long) resultInfo.get("doc_box_sent_at"))
.docBoxReceivedAt((Long) resultInfo.get("doc_box_received_at"))
.authenticatedAt((Long) resultInfo.get("authenticated_at"))
.userNotifiedAt((Long) resultInfo.get("user_notified_at"))
.payload((String) resultInfo.get("payload"))
.signedAt((String) resultInfo.get("signed_at"))
.build());
} else {
sendDetailKkoMydocTokenHistRepository.save(SendDetailKkoMydocTokenHist.builder()
.externalDocumentUuid(externalDocumentUuid)
.documentBinderUuid(envelopId)
.token(token)
.error(FieldError.initBuilder()
.errorCode(responseVO.getErrCode().getCode())
.errorMessage(responseVO.getErrMsg())
.build())
.build());
}
}
return responseVO;
}
/**
*
* -. "최초열람시간(doc_box_read_at)" .
*
* @param envelopeId
* @return
*/
@Transactional(propagation = Propagation.SUPPORTS)
public EnsResponseVO readCmplt(String orgCd, String envelopeId) {
EnsResponseVO responseVO = null;
try {
if (CmmnUtil.isEmpty(orgCd))
throw new EnsException(EnsErrCd.ERR401, "기관코드(은)는 필수조건 입니다.");
if (CmmnUtil.isEmpty(envelopeId))
throw new EnsException(EnsErrCd.ERR401, "문서식별번호(은)는 필수조건 입니다.");
OrgMng orgMng = orgMngService.find(orgCd).getResultInfo();
KkotalkApiDTO.KkotalkErrorDTO resp = kkoTalkApi.modifyStatus(
KkotalkDTO.EnvelopeId.builder()
.envelopeId(envelopeId)
.signguCode(orgMng.getOrgCd())
.ffnlgCode("11")
.build()
);
if (StringUtils.isEmpty(resp.getErrorCode())){
responseVO = EnsResponseVO.okBuilder().build();
}else{
responseVO = EnsResponseVO.errBuilder()
.errCode(EnsErrCd.ERR600)
.errMsg(String.format("%s %s", resp.getErrorCode(), resp.getErrorMessage()))
.build();
}
// ResponseEntity<String> resp = kkoTalkApi.readCompleted(orgMng.getKkoMdAccessToken(), documentBinderUuid);
//
//
// if (resp.getStatusCode().equals(HttpStatus.NO_CONTENT)) {
// responseVO = EnsResponseVO.okBuilder().build();
// } else {
// Map<String, Object> mResponse = null;
// try {
// Gson gson = new GsonBuilder().disableHtmlEscaping().create();
// mResponse = gson.fromJson(resp.getBody(), Map.class);
// } catch (Exception e) {
// throw new EnsException(EnsErrCd.ERR505, String.format("문서상태변경 응답데이터 파싱 실패. %s", resp.getBody()));
// }
// String errorCode = (String) mResponse.get("error_code");
// String errorMessage = (String) mResponse.get("error_message");
// responseVO = EnsResponseVO.errBuilder()
// .errCode(EnsErrCd.ERR600)
// .errMsg(String.format("%s %s", errorCode, errorMessage))
// .build();
// }
} catch (EnsException e) {
responseVO = EnsResponseVO.errBuilder()
.errCode(e.getErrCd())
.errMsg(e.getMessage())
.build();
} catch (Exception e) {
responseVO = EnsResponseVO.errBuilder()
.errCode(EnsErrCd.ERR999)
.errMsg(e.getMessage())
.build();
}
return responseVO;
}
/**
*
*
* @param sendMastId
* @return
*/
@Transactional(propagation = Propagation.SUPPORTS)
public EnsResponseVO sendResultProvide(Long sendMastId) {
return kkoTalkRsltProvider.execute(sendMastId);
}
}

@ -0,0 +1,103 @@
package cokr.xit.ens.modules.kkotalk.service.strategy;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import org.mapstruct.factory.Mappers;
import org.springframework.stereotype.Component;
import cokr.xit.ens.core.exception.EnsException;
import cokr.xit.ens.core.exception.code.EnsErrCd;
import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.OrgMng;
import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.repository.OrgMngRepository;
import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.repository.TmpltMngRepository;
import cokr.xit.ens.modules.common.ctgy.sys.mng.model.TmpltMngDTO;
import cokr.xit.ens.modules.common.ctgy.sys.mng.model.TmpltMngSearchDTO;
import cokr.xit.ens.modules.common.ctgy.sys.mng.service.strategy.TmpltMngStrategyTemplate;
import cokr.xit.ens.modules.kkotalk.domain.TmpltMngKkoTalk;
import cokr.xit.ens.modules.kkotalk.domain.repository.TmpltMngKkoTalkRepository;
import cokr.xit.ens.modules.kkotalk.model.TmpltMngKkoTalkDTO;
import cokr.xit.ens.modules.kkotalk.model.struct.TmpltMngKkoTalkMapper;
import lombok.RequiredArgsConstructor;
// FIXME: 카카오톡 신규 추가
@Component("tmpltMngStrategy_kkotalk")
@RequiredArgsConstructor
public class TmpltMngStrategyKkoTalk extends TmpltMngStrategyTemplate<Map<String, Object>, TmpltMngKkoTalkDTO> {
private final OrgMngRepository orgMngRepository;
private final TmpltMngRepository tmpltMngRepository;
private final TmpltMngKkoTalkRepository tmpltMngKkoTalkRepository;
private final TmpltMngKkoTalkMapper kkoTalkMapper = Mappers.getMapper(TmpltMngKkoTalkMapper.class);
// private final RedisTemplate redisTemplate;
@Override
public List<TmpltMngKkoTalkDTO> findAll(TmpltMngSearchDTO tmpltMngSearchDTO) {
return tmpltMngKkoTalkRepository.findAllDtoBySearchDTO(tmpltMngSearchDTO);
}
@Override
public Optional<TmpltMngKkoTalkDTO> find(String orgCd, String tmpltCd) {
return tmpltMngKkoTalkRepository.findDtoByOrgCdAndTmpltCd(orgCd, tmpltCd);
// String key = super.pushCache(redisTemplate, orgCd, tmpltCd, tmpltMngKkoMydocRepository);
// HashOperations<String, String, TmpltMngKkoMydocDTO> hashOperations = redisTemplate.opsForHash();
// return Optional.ofNullable(hashOperations.entries(KEY).get(key));
}
@Override
protected TmpltMngKkoTalkDTO validate(Map<String, Object> params) {
try {
TmpltMngKkoTalkDTO dto = mapper.convertValue(params, TmpltMngKkoTalkDTO.class);
final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<TmpltMngKkoTalkDTO>> list = validator.validate(dto);
if (!list.isEmpty()) {
throw new EnsException(EnsErrCd.ERR410, "유효하지 않은 요청 값 입니다.", list.stream()
.map(row -> String.format("%s [ %s ]", row.getMessageTemplate(), row.getPropertyPath()))
.collect(Collectors.toList())
);
}
return dto;
}catch (IllegalArgumentException e){
List<String> fields = Arrays.stream(TmpltMngDTO.class.getDeclaredFields()).map(row->row.getName()).collect(Collectors.toList());
fields.addAll(Arrays.stream(TmpltMngKkoTalkDTO.class.getDeclaredFields()).map(row->row.getName()).collect(Collectors.toList()));
throw new EnsException(EnsErrCd.ERR403, "유효하지 않은 파라미터 입니다. 사용가능한 파라미터는 resultInfo 를 참고하시기 바랍니다.", fields);
}
}
@Override
protected void addProc(TmpltMngKkoTalkDTO dto) {
OrgMng orgMng = orgMngRepository.findById(dto.getOrgCd()).orElseThrow(() -> new EnsException(EnsErrCd.ERR404, String.format("기관코드(%s)가 일치하는 자료가 없습니다.", dto.getOrgCd())));
tmpltMngRepository.findFetchByOrgCdAndTmpltCd(dto.getOrgCd(), dto.getTmpltCd())
.ifPresent(tmpltMng -> {
throw new EnsException(EnsErrCd.ERR540, String.format("이미 등록된 템플릿코드[기관코드 %s 템플릿코드 %s] 입니다.", tmpltMng.getOrgMng().getOrgCd(), tmpltMng.getTmpltCd()));
});
TmpltMngKkoTalk tmpltMng = kkoTalkMapper.toEntity(dto);
tmpltMng.setOrgMng(orgMng);
tmpltMng.setRegistId("ENS_SYS");
tmpltMngKkoTalkRepository.save(tmpltMng);
}
@Override
protected void modifyProc(TmpltMngKkoTalkDTO dto) {
TmpltMngKkoTalk tmpltMng = tmpltMngKkoTalkRepository.findFetchByOrgCdAndTmpltCd((dto).getOrgCd(), (dto).getTmpltCd())
.orElseThrow(() -> new EnsException(EnsErrCd.ERR404, String.format("기관코드(%s) 및 템플릿코드(%s)가 일치하는 자료가 없습니다.", dto.getOrgCd(), dto.getTmpltCd())));
if(!"Y".equals(tmpltMng.getUseYn()))
throw new EnsException(EnsErrCd.ERR402, "\"미사용\" 상태의 템플릿은 수정 할 수 없습니다.");
kkoTalkMapper.updateFromDto(dto, tmpltMng);
tmpltMng.setUpdId("ENS_SYS");
// super.deleteCache(redisTemplate, dto.getOrgCd(), dto.getTmpltCd());
}
}

@ -1,4 +1,4 @@
package cokr.xit.ens.modules.kkotalk.service;
package cokr.xit.ens.modules.kkotalk.service.support;
import cokr.xit.ens.modules.kkotalk.model.KkotalkApiDTO;
import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO;
@ -17,7 +17,7 @@ import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO;
*
* </pre>
*/
public interface IKkotalkEltrcDocService {
public interface IKkoTalkApiService {
/**
* <pre>
@ -48,7 +48,7 @@ public interface IKkotalkEltrcDocService {
* </pre>
* @param reqDTO KkotalkDTO.EnvelopeId
*/
void modifyStatus(final KkotalkDTO.EnvelopeId reqDTO);
KkotalkApiDTO.KkotalkErrorDTO modifyStatus(final KkotalkDTO.EnvelopeId reqDTO);
/**
@ -91,4 +91,3 @@ public interface IKkotalkEltrcDocService {
//KkotalkApiDTO.ValidTokenResponse findKkotalkReadyAndMblPage(KkotalkApiDTO.ValidTokenRequest reqDTO);
}

@ -0,0 +1,270 @@
package cokr.xit.ens.modules.kkotalk.service.support;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import cokr.xit.ens.core.aop.EnsResponseVO;
import cokr.xit.ens.core.exception.EnsException;
import cokr.xit.ens.core.exception.code.EnsErrCd;
import cokr.xit.ens.core.utils.CmmnUtil;
import cokr.xit.ens.core.utils.DateUtil;
import cokr.xit.ens.core.utils.IdGenerator;
import cokr.xit.ens.core.utils.MapDeserailizer;
import cokr.xit.ens.core.utils.crypto.AES256;
import cokr.xit.ens.core.utils.crypto.Crypto;
import cokr.xit.ens.modules.common.biztmplt.EnsPhaseProcSupport;
import cokr.xit.ens.modules.common.code.PostSeCd;
import cokr.xit.ens.modules.common.code.StatCd;
import cokr.xit.ens.modules.common.code.VenderCd;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.Bill;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.repository.BillRepository;
import cokr.xit.ens.modules.common.ctgy.mblpage.domain.SendDetailMblPage;
import cokr.xit.ens.modules.common.ctgy.mblpage.domain.repository.SendDetailMblPageRepository;
import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.OrgMng;
import cokr.xit.ens.modules.common.ctgy.sys.mng.service.OrgMngService;
import cokr.xit.ens.modules.common.ctgy.sys.mng.service.TmpltMngService;
import cokr.xit.ens.modules.common.domain.SendMast;
import cokr.xit.ens.modules.common.domain.repository.SendMastRepository;
import cokr.xit.ens.modules.kkomydoc.model.config.XitProperty;
import cokr.xit.ens.modules.kkotalk.mapper.IKkoTalkMapper;
import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
// FIXME: 카카오톡 신규 추가
@Slf4j
@Component
@RequiredArgsConstructor
public class KkoTalkAcceptor implements EnsPhaseProcSupport<EnsResponseVO<?>, KkotalkDTO.KkoTalkAcceptReqDTO> {
private final SendMastRepository sendMastRepository;
private final IKkoTalkMapper kkoTalkMapper;
private final SendDetailMblPageRepository sendDetailMblPageRepository;
private final BillRepository billRepository;
private final OrgMngService orgMngService;
private final TmpltMngService tmpltMngService;
private Gson gson = new GsonBuilder().registerTypeAdapter(Map.class, new MapDeserailizer()).disableHtmlEscaping().create();
@Override
public EnsResponseVO<?> statReady(List<KkotalkDTO.KkoTalkAcceptReqDTO> reqDTOs) {
log.info("no process");
return null;
}
@Override
public EnsResponseVO<?> statBegin(KkotalkDTO.KkoTalkAcceptReqDTO reqDTO) {
final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<KkotalkDTO.KkoTalkAcceptReqDTO>> list = validator.validate(reqDTO);
if (!list.isEmpty()) {
return EnsResponseVO.errRsltBuilder()
.errCode(EnsErrCd.ACPT402)
.errMsg("유효하지 않은 요청값 입니다.")
.resultInfo(list.stream()
.map(row -> String.format("%s [ %s ]", row.getMessageTemplate(), row.getPropertyPath()))
.collect(Collectors.toList()))
.build();
}
List<String> expiredDateValidateList = expiredDateVaildate(reqDTO);
if (!expiredDateValidateList.isEmpty()) {
return EnsResponseVO.errRsltBuilder()
.errCode(EnsErrCd.ACPT402)
.errMsg("유효하지 않은 유효기한 입니다. 자료의 유효기한은 마감일시 이내이어야 합니다.")
.resultInfo(expiredDateValidateList)
.build();
}
EnsResponseVO<OrgMng> orgMng = orgMngService.find(reqDTO.getOrg_cd());
if (!EnsErrCd.OK.equals(orgMng.getErrCode()))
return EnsResponseVO.errRsltBuilder()
.errCode(EnsErrCd.ACPT404)
.errMsg("일치하는 기관정보가 없습니다.")
.resultInfo(orgMng.getErrMsg())
.build();
EnsResponseVO<?> tmpltMng = tmpltMngService.find(reqDTO.getOrg_cd(), reqDTO.getTmplt_cd(), "kkotalk");
if (!EnsErrCd.OK.equals(tmpltMng.getErrCode()))
return EnsResponseVO.errRsltBuilder()
.errCode(EnsErrCd.ACPT404)
.errMsg("일치하는 템플릿정보가 없습니다.")
.resultInfo(tmpltMng.getErrMsg())
.build();
return EnsResponseVO.okBuilder().build();
}
private List<String> expiredDateVaildate(KkotalkDTO.KkoTalkAcceptReqDTO reqDTO) {
List<String> result = new ArrayList<>();
AtomicInteger i = new AtomicInteger();
reqDTO.getDocuments().forEach(document -> {
if (!CmmnUtil.isEmpty(document)) {
if (!CmmnUtil.isEmpty(document.getReadExpiresAt())) {
String expDate = DateUtil.getTimeOfTimeT(document.getReadExpiresAt(), "yyyyMMddHHmmss");
int sec = DateUtil.secByFromBetweenTo(DateUtil.toLocalDateTime(expDate), DateUtil.toLocalDateTime(reqDTO.getClose_dt()));
if (sec < 0)
result.add(String.format("마감일시보다 \"처리마감시간\"이 느립니다. [ document[%d].acpt_data.kko_talk.read_expires_at ]", i.get()));
}
if (!CmmnUtil.isEmpty(document.getReviewExpiresAt())) {
String expDate = DateUtil.getTimeOfTimeT(document.getReviewExpiresAt(), "yyyyMMddHHmmss");
int sec = DateUtil.secByFromBetweenTo(DateUtil.toLocalDateTime(expDate), DateUtil.toLocalDateTime(reqDTO.getClose_dt()));
if (sec < 0)
result.add(String.format("마감일시보다 \"처리마감시간\"이 느립니다. [ document[%d].acpt_data.kko_talk.read_expires_at ]", i.get()));
}
}
i.getAndIncrement();
});
return result;
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public EnsResponseVO<?> execute(KkotalkDTO.KkoTalkAcceptReqDTO reqDTO) throws EnsException {
AES256 aes256 = new AES256(Crypto.AES256.getKey());
EnsResponseVO<?> responseVO = null;
try {
SendMast sendMast = SendMast.builder()
.vender(VenderCd.valueOfEnum(reqDTO.getVender()))
.postSe(PostSeCd.kkoTalk)
.orgCd(reqDTO.getOrg_cd())
.tmpltCd(reqDTO.getTmplt_cd())
.postBundleTitle(reqDTO.getPost_bundle_title())
.statCd(StatCd.accept)
.sendDt(DateUtil.toLocalDateTime(reqDTO.getSend_dt()))
.sendCnt(reqDTO.getDocuments().size())
.closeDt(DateUtil.toLocalDateTime(reqDTO.getClose_dt()))
.build();
sendMastRepository.save(sendMast);
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
List<KkotalkDTO.SendDetailKkoTalkDTO> sendDetails = new ArrayList<>();
List<Map<String, Object>> sendDetailMblPages = new ArrayList<>();
List<Bill> bills = new ArrayList<>();
final String prefixBillUid = sendMast.getPostSe().getCode() + "-" + IdGenerator.getCurrentTimeSec();
AtomicInteger i = new AtomicInteger();
reqDTO.getDocuments().forEach(document -> {
i.getAndIncrement();
try {
Bill bill = null;
if (!CmmnUtil.isEmpty(document.getXit_property())
&& !CmmnUtil.isEmpty(document.getXit_property().getBill_acpt_data())) {
// if (document.getXit_property().getBill_acpt_data().getUse_bill_uid()) {
if (!CmmnUtil.isEmpty(document.getXit_property().getBill_acpt_data().getBillUid())) {
bill = billRepository.findByBillUid(document.getXit_property().getBill_acpt_data().getBillUid())
.orElseThrow(() -> new RuntimeException("등록된 청구서가 없습니다."));
} else {
bill = Bill.builder()
// .billId(document.getXit_property().getBill_acpt_data().getBill_id())
.billId(null)
.billUid(IdGenerator.getShortUUID(prefixBillUid))
.billerUserKey(document.getXit_property().getBill_acpt_data().getBillerUserKey())
.billSeCd(document.getXit_property().getBill_acpt_data().getBillSe())
.orgMng(OrgMng.builder().orgCd(reqDTO.getOrg_cd()).build())
// .docBillKko(CmmnUtil.isEmpty(document.getXit_property().getBill_acpt_data().getBill_kko()) ? null : gson.toJson(document.getXit_property().getBill_acpt_data().getBill_kko()))
// .docBillNv(CmmnUtil.isEmpty(document.getXit_property().getBill_acpt_data().getBill_nv()) ? null : gson.toJson(document.getXit_property().getBill_acpt_data().getBill_nv()))
.build();
}
bills.add(bill);
}
XitProperty xitProperty = Optional.ofNullable(document.getXit_property()).orElse(XitProperty.builder().build());
KkotalkDTO.SendDetailKkoTalkDTO sendDetail = KkotalkDTO.SendDetailKkoTalkDTO.builder()
.sendMastId(sendMast.getSendMastId())
.title(document.getTitle())
.link(document.getContent().getLink())
.hash(document.getHash())
.guide(document.getGuide())
.payload(document.getPayload())
.readExpiresAt(document.getReadExpiresAt())
.reviewExpiresAt(document.getReviewExpiresAt())
.useNonPersonalizedNotification(document.getUseNonPersonalizedNotification())
.ci(document.getCi())
.phoneNumber(document.getPhoneNumber())
.name(document.getName())
.birthday(document.getBirthday())
.externalId(document.getExternalId())
.mkBillUseYn(CmmnUtil.isEmpty(bill) ? "N" : "Y")
// .mkBillUid(bill == null ? null : bill.getBillUid())
.billUid(Objects.requireNonNull(bill).getBillUid())
// .mkJid(xitProperty.getJid())
.mkJid(aes256.encrypt(xitProperty.getJid()))
.mkTmpltMsgJsonData(CmmnUtil.isEmpty(xitProperty.getTmplt_msg_data()) ? null : gson.toJson(xitProperty.getTmplt_msg_data()))
.build();
sendDetails.add(sendDetail);
if (!(CmmnUtil.isEmpty(document.getXit_property()) || CmmnUtil.isEmpty(document.getXit_property().getMbl_page_data()))) {
Map<String, Object> mMblPage = new HashMap<>();
mMblPage.put("sendDetail", sendDetail);
mMblPage.put("details", gson.toJson(document.getXit_property().getMbl_page_data()));
sendDetailMblPages.add(mMblPage);
}
} catch (Exception e) {
throw new EnsException(EnsErrCd.ACPT500, String.format("%d번째 데이터 접수처리 중 오류 발생. 실패사유: %s", i.get(), e.getMessage()));
}
});
if (CmmnUtil.isEmpty(sendDetails))
throw new EnsException(EnsErrCd.ACPT410, "발송상세 자료가 없습니다.");
billRepository.saveAll(bills.stream()
.filter(row -> CmmnUtil.isEmpty(row.getBillId()))
.collect(Collectors.toList())
);
// kkotalk 발송상세 저장
sendDetails.forEach(kkoTalkMapper::saveSndDtlKkoTalk);
if (!sendDetailMblPages.isEmpty())
sendDetailMblPageRepository.saveAll(
sendDetailMblPages.stream()
.map(data -> {
KkotalkDTO.SendDetailKkoTalkDTO sendDetail = (KkotalkDTO.SendDetailKkoTalkDTO) data.get("sendDetail");
return SendDetailMblPage.builder()
.postSe(PostSeCd.kkoTalk.getCode())
.sendDetailId(sendDetail.getSendDetailId())
.details((String) data.get("details"))
.build();
})
.collect(Collectors.toList())
);
Map<String, Object> resultInfo = new HashMap<>();
resultInfo.put("sendMastId", sendMast.getSendMastId());
resultInfo.put("sendDetails", sendDetails);
responseVO = EnsResponseVO.okBuilder().resultInfo(resultInfo).build();
} catch (EnsException e) {
throw e;
} catch (Exception e) {
throw new EnsException(EnsErrCd.ACPT500, String.format("오류가 발생하여 접수에 실패 했습니다. 실패사유: %s", e.getMessage()));
}
return responseVO;
}
}

@ -1,4 +1,4 @@
package cokr.xit.ens.modules.kkotalk.service;
package cokr.xit.ens.modules.kkotalk.service.support;
import java.util.ArrayList;
import java.util.Collections;
@ -41,11 +41,12 @@ import lombok.extern.slf4j.Slf4j;
*
* </pre>
*/
// FIXME: 카카오톡 신규 추가
@Slf4j
@RequiredArgsConstructor
@Component
public class KkotalkEltrcDocService implements
IKkotalkEltrcDocService {
public class KkoTalkApiService implements
IKkoTalkApiService {
@Value("${contract.kakao.talk.host}")
private String HOST;
@ -140,12 +141,12 @@ public class KkotalkEltrcDocService implements
* @param reqDTO KkopayDocAttrDTO.EnvelopeId
*/
@Override
public void modifyStatus(final KkotalkDTO.EnvelopeId reqDTO){
public KkotalkApiDTO.KkotalkErrorDTO 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));
return webClient.exchangeKkotalk(url, HttpMethod.valueOf(API_MODIFY_STATUS[1]), null, KkotalkApiDTO.KkotalkErrorDTO.class, getRlaybsnmInfo(reqDTO));
}
/**

@ -0,0 +1,259 @@
package cokr.xit.ens.modules.kkotalk.service.support;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import cokr.xit.ens.core.aop.EnsResponseVO;
import cokr.xit.ens.core.exception.EnsException;
import cokr.xit.ens.core.exception.code.EnsErrCd;
import cokr.xit.ens.core.monitor.slack.event.MonitorEvent;
import cokr.xit.ens.core.utils.CmmnUtil;
import cokr.xit.ens.core.utils.IdGenerator;
import cokr.xit.ens.core.utils.MapDeserailizer;
import cokr.xit.ens.core.utils.crypto.AES256;
import cokr.xit.ens.core.utils.crypto.Crypto;
import cokr.xit.ens.core.utils.crypto.SHA256;
import cokr.xit.ens.modules.common.biztmplt.MakeProcTemplate;
import cokr.xit.ens.modules.common.code.PostSeCd;
import cokr.xit.ens.modules.common.code.StatCd;
import cokr.xit.ens.modules.common.ctgy.nicedici.service.NiceDiCiService;
import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.OrgMng;
import cokr.xit.ens.modules.common.ctgy.sys.mng.service.OrgMngService;
import cokr.xit.ens.modules.common.domain.SendMast;
import cokr.xit.ens.modules.common.domain.repository.SendMastRepository;
import cokr.xit.ens.modules.common.domain.support.FieldError;
import cokr.xit.ens.modules.common.event.SendMastStatUpdateEvent;
import cokr.xit.ens.modules.common.monitor.MessageByPhase;
import cokr.xit.ens.modules.kkotalk.mapper.IKkoTalkMapper;
import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO;
import cokr.xit.ens.modules.kkotalk.model.TmpltMngKkoTalkDTO;
import cokr.xit.ens.modules.kkotalk.service.strategy.TmpltMngStrategyKkoTalk;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
// FIXME: 카카오톡 신규 추가
@Slf4j
@Component
@RequiredArgsConstructor
public class KkoTalkMaker extends MakeProcTemplate {
private final ApplicationEventPublisher applicationEventPublisher;
private final SendMastRepository sendMastRepository;
//private final SendDetailKkoMydocRepository sendDetailKkoMydocRepository;
private final IKkoTalkMapper kkoTalkMapper;
private final OrgMngService orgMngService;
private final TmpltMngStrategyKkoTalk tmpltMngService;
private final NiceDiCiService niceDiCiService;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public EnsResponseVO<?> execute(Long sendMastId) {
AES256 aes256 = new AES256(Crypto.AES256.getKey());
SendMast sendMast = null;
List<KkotalkDTO.SendDetailKkoTalkDTO> sendDetails = null;
EnsResponseVO respVO = null;
try {
if (CmmnUtil.isEmpty(sendMastId))
throw new EnsException(EnsErrCd.MAKE410, "발송마스터ID(은)는 필수조건 입니다.");
sendMast = sendMastRepository.findById(sendMastId)
.orElseThrow(() -> new EnsException(EnsErrCd.MAKE404, String.format("일치하는 발송마스터 자료가 없습니다. [ sendMastId %s ]", sendMastId)));
// kkotalk 발송상세 조회
sendDetails = kkoTalkMapper.findAllBySendMastId(sendMastId);
SendMast finalSendMast = sendMast;
OrgMng orgMng = orgMngService.find(sendMast.getOrgCd()).getResultInfo();
// TmpltMngKkoMydoc tmpltMngDTO = tmpltMngRepository.findFetchByOrgCdAndTmpltCdAndUseYn(sendMast.getOrgCd(), sendMast.getTmpltCd(), "Y")
// .orElseThrow(() -> new EnsException(EnsErrCd.MAKE404, String.format("일치하는 템플릿 자료가 없거나 미사용 상태의 템플릿 입니다. [ sendMastId %s orgCd %s TmpltCd %s ]", sendMastId, finalSendMast.getOrgCd(), finalSendMast.getTmpltCd())));
TmpltMngKkoTalkDTO tmpltMngDTO = tmpltMngService.find(sendMast.getOrgCd(), sendMast.getTmpltCd())
.orElseThrow(() -> new EnsException(EnsErrCd.MAKE404, String.format("일치하는 템플릿 자료가 없습니다. [ sendMastId %s orgCd %s TmpltCd %s ]", sendMastId, finalSendMast.getOrgCd(), finalSendMast.getTmpltCd())));
if (!"Y".equals(tmpltMngDTO.getUseYn()))
throw new EnsException(EnsErrCd.MAKE521, String.format("미사용 상태의 템플릿 입니다. [ sendMastId %s orgCd %s TmpltCd %s ]", sendMastId, finalSendMast.getOrgCd(), finalSendMast.getTmpltCd()));
Map<String, String> mCi = new HashMap<>();
if ("Y".equals(tmpltMngDTO.getCiTransUseYn())) {
List<String> jids = sendDetails.stream()
// .filter(row -> "Y".equals(row.getMkCiTransUseYn()))
.filter(row -> !CmmnUtil.isEmpty(row.getMkJid()))
// .map(row -> row.getMkJid())
.map(row -> aes256.decrypt(row.getMkJid()))
.collect(Collectors.toList());
if (!CmmnUtil.isEmpty(jids)) {
val ciResponseVO = niceDiCiService.ci(orgMng.getNiceCdSiteCode(), orgMng.getNiceCdSitePw(), orgMng.getNiceCdClientId(), orgMng.getNiceCdClientSercet(), jids);
if (!EnsErrCd.OK.equals(ciResponseVO.getErrCode()))
throw new EnsException(EnsErrCd.MAKE513, String.format("%s %s", ciResponseVO.getErrCode(), ciResponseVO.getErrMsg()));
final List<EnsResponseVO> responseVOList = (List<EnsResponseVO>)ciResponseVO.getResultInfo();
mCi = responseVOList.stream()
.map(vo -> {
Map<String, String> resultInfo = (Map<String, String>) vo.getResultInfo();
Map<String, String> m = new HashMap<>();
m.put("key", resultInfo.get("jid"));
m.put("value", CmmnUtil.isEmpty(resultInfo.get("ci")) ? "" : resultInfo.get("ci"));
return m;
})
.collect(Collectors.toMap(m -> m.get("key"), m -> m.get("value"), (k1, k2) -> k1));
}
}
Map<String, String> finalMCi = mCi;
Optional<Integer> cntSuccess = sendDetails.stream()
.map(row -> {
try {
row.setTitle(Optional.ofNullable(tmpltMngDTO.getTitle())
.orElseThrow(() -> new EnsException(EnsErrCd.MAKE521, String.format("title 생성에 실패 했습니다. [ orgCd %s tmpltCd %s]", tmpltMngDTO.getOrgCd(), tmpltMngDTO.getTmpltCd()))));
if ("Y".equals(tmpltMngDTO.getTmpltCsInfoUseYn())) {
row.setPhoneNumber(Optional.ofNullable(tmpltMngDTO.getCsNumber())
.orElseThrow(() -> new EnsException(EnsErrCd.MAKE521,
String.format("phoneNumber 생성에 실패 했습니다. [ orgCd %s tmpltCd %s]",
tmpltMngDTO.getOrgCd(), tmpltMngDTO.getTmpltCd()))));
row.setName(Optional.ofNullable(tmpltMngDTO.getCsName())
.orElseThrow(() -> new EnsException(EnsErrCd.MAKE521,
String.format("name 생성에 실패 했습니다. [ orgCd %s tmpltCd %s]",
tmpltMngDTO.getOrgCd(), tmpltMngDTO.getTmpltCd()))));
}
//if ("Y".equals(tmpltMngDTO.getTmpltMsgUseYn())) {
// boolean usePryMsg = CmmnUtil.isEmpty(tmpltMngDTO.getPryMessage()) ? false : true;
// row.setPropMessage(Optional.ofNullable(this.msgTmplateToMessage(usePryMsg, tmpltMngDTO, row.getMkTmpltMsgJsonData()))
// .orElseThrow(() -> new EnsException(EnsErrCd.MAKE521, String.format("propMessage 생성에 실패 했습니다. [ orgCd %s tmpltCd %s tmpltMsgJsonData %s ]", tmpltMngDTO.getOrgCd(), tmpltMngDTO.getTmpltCd(), row.getMkTmpltMsgJsonData()))));
//}
if (CmmnUtil.isEmpty(row.getExternalId()))
row.setExternalId(Optional.ofNullable(IdGenerator.getUUID())
.orElseThrow(() -> new EnsException(EnsErrCd.MAKE521, String.format("propExternalDocumentUuid 생성에 실패 했습니다"))));
//if (CmmnUtil.isEmpty(row.getPropExternalDocumentUuid()))
// row.setPropExternalDocumentUuid(Optional.ofNullable(IdGenerator.getUUID())
// .orElseThrow(() -> new EnsException(EnsErrCd.MAKE521, String.format("propExternalDocumentUuid 생성에 실패 했습니다"))));
if (CmmnUtil.isEmpty(row.getHash()))
row.setHash(Optional.ofNullable(this.createHash(row))
.orElseThrow(() -> new EnsException(EnsErrCd.MAKE521, String.format("hash 생성에 실패 했습니다."))));
//if ("Y".equals(tmpltMngDTO.getCiTransUseYn()) && !CmmnUtil.isEmpty(row.getMkJid()))
// row.setRecvCi(Optional.ofNullable(finalMCi.get(aes256.decrypt(row.getMkJid()).replaceAll("[^0-9]", "")))
// .map(ci -> CmmnUtil.isEmpty(ci) ? null : ci)
// .orElseThrow(() -> new EnsException(EnsErrCd.MAKE610, String.format("recvCi 생성에 실패 했습니다. 주민번호에 대한 CI 값이 없습니다. [ jid %s ]", aes256.decrypt(row.getMkJid())))));
//row.setError(FieldError.initBuilder().build());
return 1;
} catch (EnsException e) {
row.setErrorCode(e.getErrCd().getCode());
row.setErrorMessage(e.getMessage());
//row.setError(FieldError.initBuilder()
// .errorCode(e.getErrCd().getCode())
// .errorMessage(e.getMessage())
// .build());
return 0;
}
})
.reduce(Integer::sum);
if (cntSuccess.get() > 0)
respVO = EnsResponseVO.okBuilder().resultInfo(String.format("총 %d건 중 %d건 제작 성공. [ sendMastId %s ]", sendDetails.size(), cntSuccess.get(), sendMastId)).build();
else
throw new EnsException(EnsErrCd.MAKE500, String.format("총 %d 건 제작 실패", sendDetails.size()));
} catch (EnsException e) {
respVO = EnsResponseVO.errBuilder().errCode(e.getErrCd()).errMsg(e.getMessage()).build();
} catch (Exception e) {
respVO = EnsResponseVO.errBuilder().errCode(EnsErrCd.MAKE500).errMsg(e.getMessage()).build();
} finally {
if (!CmmnUtil.isEmpty(sendMast))
if (EnsErrCd.OK.equals(respVO.getErrCode())) {
sendMast.setStatCd(StatCd.makeok);
sendMast.setError(FieldError.initBuilder().build());
} else {
sendMast.setStatCd(StatCd.makefail);
sendMast.setError(FieldError.initBuilder()
.errorCode(respVO.getErrCode().getCode())
.errorMessage(respVO.getErrMsg())
.build());
applicationEventPublisher.publishEvent(MonitorEvent.builder()
.message(MessageByPhase.builder()
.oClass(getClass().getSimpleName() + "." + new Throwable().getStackTrace()[0].getMethodName())
.postSeCd(PostSeCd.kkoMydoc)
.statCd(StatCd.makefail)
.errCd(respVO.getErrCode())
.message("-.SendMastId: " + sendMastId + "\n" + respVO.getErrMsg())
.build())
.build());
}
applicationEventPublisher.publishEvent(SendMastStatUpdateEvent.builder().sendMastId(sendMastId).build());
}
return respVO;
}
/**
* Hash .
*
* @param vo
* @return
*/
private String createHash(KkotalkDTO.SendDetailKkoTalkDTO vo) {
String hash = null;
try {
String text = vo.getExternalId()
+ vo.getLink();
// String text = new StringBuilder()
// .append(vo.getPropExternalDocumentUuid())
// .append(vo.getPropLink())
// .append(vo.getPropMessage())
// .toString();
hash = SHA256.encrypt(text);
} catch (Exception e) {
log.error("hash 값 생성 실패. [ sendDetailId {} ]", vo.getSendDetailId());
}
return hash;
}
/**
* 릿 .
*
* @param usePryMsg
* @param tmpltMngDTO
* @param tmplateMsgData
* @return
*/
private String msgTmplateToMessage(boolean usePryMsg, TmpltMngKkoTalkDTO tmpltMngDTO, String tmplateMsgData) {
String message = usePryMsg ? tmpltMngDTO.getPryMessage() : tmpltMngDTO.getMessage();
if (CmmnUtil.isEmpty(tmplateMsgData))
return message;
Gson gson = new GsonBuilder().registerTypeAdapter(Map.class, new MapDeserailizer()).serializeNulls().create();
try {
Map<String, Object> mMsgData = gson.fromJson(tmplateMsgData, Map.class);
for (String key : mMsgData.keySet()) {
message = message.replace(key, String.valueOf(mMsgData.get(key)));
}
} catch (Exception e) {
log.error("template Message 변환 실패. [ orgCd {} tmpltCd {} msgData {} ]", tmpltMngDTO.getOrgCd(), tmpltMngDTO.getTmpltCd(), tmplateMsgData, e);
return null;
}
return message;
}
}

@ -0,0 +1,380 @@
package cokr.xit.ens.modules.kkotalk.service.support;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import cokr.xit.ens.core.aop.EnsResponseVO;
import cokr.xit.ens.core.exception.EnsException;
import cokr.xit.ens.core.exception.code.EnsErrCd;
import cokr.xit.ens.core.utils.CmmnUtil;
import cokr.xit.ens.core.utils.DateUtil;
import cokr.xit.ens.core.utils.MapDeserailizer;
import cokr.xit.ens.modules.common.biztmplt.ResultProcTemplate;
import cokr.xit.ens.modules.common.code.StatCd;
import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.OrgMng;
import cokr.xit.ens.modules.common.ctgy.sys.mng.service.OrgMngService;
import cokr.xit.ens.modules.common.domain.SendMast;
import cokr.xit.ens.modules.common.domain.repository.SendMastRepository;
import cokr.xit.ens.modules.common.domain.support.FieldError;
import cokr.xit.ens.modules.common.event.SendMastStatUpdateEvent;
import cokr.xit.ens.modules.kkomydoc.code.KkoMydocStatusCd;
import cokr.xit.ens.modules.kkomydoc.domain.SendDetailKkoMydoc;
import cokr.xit.ens.modules.kkomydoc.domain.SendDetailKkoMydocStatHist;
import cokr.xit.ens.modules.kkomydoc.domain.repository.SendDetailKkoMydocRepository;
import cokr.xit.ens.modules.kkomydoc.domain.repository.SendDetailKkoMydocStatHistRepository;
import cokr.xit.ens.modules.kkomydoc.model.KkoMydocApiRespVO;
import cokr.xit.ens.modules.kkomydoc.service.support.KkoMydocApiSpec;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
// FIXME: 카카오톡 신규 추가
@Slf4j
@Component
@RequiredArgsConstructor
public class KkoTalkRsltFetcher extends ResultProcTemplate {
private final ApplicationEventPublisher applicationEventPublisher;
private final SendMastRepository sendMastRepository;
private final SendDetailKkoMydocRepository sendDetailKkoMydocRepository;
private final SendDetailKkoMydocStatHistRepository sendDetailKkoMydocStatHistRepository;
private final OrgMngService orgMngService;
private final KkoMydocApiSpec kkoMydocApi;
@Value("${contract.kakao.pay.mydoc.api.bulksend-batch-unit}")
private int SEND_BATCH_UNIT;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public EnsResponseVO execute(Long sendMastId) {
SendMast sendMast = null;
List<SendDetailKkoMydoc> sendDetails = null;
EnsResponseVO respVO = null;
Map<String, Long> resultInfo = new HashMap<>();
resultInfo.put("sendMastId", sendMastId);
try {
if (CmmnUtil.isEmpty(sendMastId))
throw new EnsException(EnsErrCd.RSLT410, "발송마스터ID(은)는 필수조건 입니다.");
sendMast = sendMastRepository.findById(sendMastId).orElseThrow(() -> new EnsException(EnsErrCd.RSLT404, String.format("일치하는 발송마스터 자료가 없습니다. [ sendMastId %s ]", sendMastId)));
sendDetails = sendDetailKkoMydocRepository.findAllBySendMastAndDocumentBinderUuidIsNotNull(sendMast);
OrgMng orgMng = orgMngService.find(sendMast.getOrgCd()).getResultInfo();
Lists.partition(sendDetails, SEND_BATCH_UNIT).stream()
.forEach(list -> {
String sendRespBody = null;
String jsonStr = null;
try {
EnsResponseVO message = this.makeMessage(list);
if (!(EnsErrCd.OK.equals(message.getErrCode()) || null == message.getErrCode()))
throw new EnsException(message.getErrCode(), message.getErrMsg());
jsonStr = String.valueOf(message.getResultInfo());
ResponseEntity<String> resp = kkoMydocApi.bulkStatus(orgMng.getKkoMdAccessToken(), orgMng.getKkoMdContractUuid(), jsonStr);
if (log.isDebugEnabled()) {
StringBuffer sb = new StringBuffer();
sb.append("\n==============================================================================")
.append("\n[ Kakao Mydoc - StatFetch ]")
.append("\n### Request Info...")
.append("\n" + jsonStr)
.append("\n### Response Info...")
.append("\n" + resp.getBody())
.append("\n==============================================================================");
log.debug(sb.toString());
}
sendRespBody = resp.getBody();
if (resp.getStatusCode() != HttpStatus.OK)
throw new EnsException(EnsErrCd.RSLT620, String.format("문서상태조회 중.. %s %s", resp.getStatusCode().toString(), resp.getBody()));
EnsResponseVO mResponse = this.respMsgToMap(resp.getBody());
if (!EnsErrCd.OK.equals(mResponse.getErrCode()))
throw new EnsException(mResponse.getErrCode(), mResponse.getErrMsg());
List<Map<String, Object>> documents = (List<Map<String, Object>>) ((Map<String, Object>) mResponse.getResultInfo()).get("documents");
Map<String, KkoMydocApiRespVO> mApiRespVOByDocumentBinderUuid = documents.stream()
.map(row -> this.toApiRespVOMap(row))
.collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (KkoMydocApiRespVO) m.get("value"), (k1, k2) -> k1));
list.stream().forEach(row -> this.modifyStatInfoByDocumentBinderUuid(row, mApiRespVOByDocumentBinderUuid));
} catch (EnsException e) {
list.stream()
.forEach(row -> row.setError(FieldError.initBuilder()
.errorCode(e.getErrCd().getCode())
.errorMessage(e.getMessage())
.build())
);
} catch (Exception e) {
list.stream()
.forEach(row -> row.setError(FieldError.initBuilder()
.errorCode(EnsErrCd.RSLT500.getCode())
.errorMessage(e.getMessage())
.build())
);
} finally {
if (!CmmnUtil.isEmpty(jsonStr))
sendDetailKkoMydocStatHistRepository.saveAll(this.toSendDetailStatHist(list, sendRespBody));
}
});
respVO = EnsResponseVO.okBuilder().resultInfo(resultInfo).build();
} catch (EnsException e) {
respVO = EnsResponseVO.errRsltBuilder().errCode(e.getErrCd()).errMsg(e.getMessage()).resultInfo(resultInfo).build();
} catch (Exception e) {
respVO = EnsResponseVO.errRsltBuilder().errCode(EnsErrCd.RSLT500).errMsg(e.getMessage()).resultInfo(resultInfo).build();
} finally {
if (!CmmnUtil.isEmpty(sendMast))
if (EnsErrCd.OK.equals(respVO.getErrCode())) {
sendMast.setError(FieldError.initBuilder().build());
Integer readCnt = sendDetails.stream()
.map(row -> KkoMydocStatusCd.READ.equals(row.getKkoDocStat()) ? 1 : 0)
.reduce(Integer::sum)
.orElseGet(() -> 0);
sendMast.setReadCnt(readCnt);
if (StatCd.sendok.equals(sendMast.getStatCd())) {
if (readCnt > 0)
sendMast.setStatCd(StatCd.open);
else
sendMast.setStatCd(StatCd.sendcmplt);
} else if (StatCd.sendcmplt.equals(sendMast.getStatCd())) {
if (readCnt > 0)
sendMast.setStatCd(StatCd.open);
}
} else {
sendMast.setError(FieldError.initBuilder()
.errorCode(respVO.getErrCode().getCode())
.errorMessage(respVO.getErrMsg())
.build());
}
applicationEventPublisher.publishEvent(SendMastStatUpdateEvent.builder().sendMastId(sendMastId).build());
}
return respVO;
}
/**
* API body Message .
*
* @param list
* @return
*/
private EnsResponseVO makeMessage(List<SendDetailKkoMydoc> list) {
String result = null;
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
EnsResponseVO resp = null;
try {
List<String> documentBinderUuids = list.stream()
.map(row -> row.getDocumentBinderUuid())
.collect(Collectors.toList());
Map<String, Object> mJson = new HashMap<>();
mJson.put("document_binder_uuids", documentBinderUuids);
result = gson.toJson(mJson);
resp = EnsResponseVO.okBuilder()
.resultInfo(result)
.build();
} catch (EnsException e) {
resp = EnsResponseVO.errBuilder()
.errCode(e.getErrCd())
.errMsg(e.getMessage())
.build();
} catch (Exception e) {
resp = EnsResponseVO.errBuilder()
.errCode(EnsErrCd.RSLT502)
.errMsg(String.format("요청 메시지 생성 중 오류 발생. %s", e.getMessage()))
.build();
}
return resp;
}
/**
* APIVO
*
* @param row
* @return
*/
private Map<String, Object> toApiRespVOMap(Map<String, Object> row) {
KkoMydocApiRespVO kkoMydocApiRespVO = KkoMydocApiRespVO.builder()
.error_code(row.containsKey("error_code") ? String.valueOf(row.get("error_code")) : null)
.error_message(row.containsKey("error_message") ? String.valueOf(row.get("error_message")) : null)
.data(row.containsKey("status_data") ? row.get("status_data") : new HashMap<>())
.build();
Map<String, Object> m = new HashMap<>();
m.put("key", row.get("document_binder_uuid"));
m.put("value", kkoMydocApiRespVO);
return m;
}
/**
* (update)
* -. / Error
*
* @param row
* @param mMydocApiRespVOByDocumentBinderUuid
*/
private void modifyStatInfoByDocumentBinderUuid(SendDetailKkoMydoc row, Map<String, KkoMydocApiRespVO> mMydocApiRespVOByDocumentBinderUuid) {
KkoMydocApiRespVO apiRespVO = mMydocApiRespVOByDocumentBinderUuid.get(row.getDocumentBinderUuid());
if (CmmnUtil.isEmpty(apiRespVO.getError_code())) {
Map<String, Object> statusData = (Map<String, Object>) apiRespVO.getData();
String docBoxStatus = statusData.containsKey("doc_box_status") ? (String) statusData.get("doc_box_status") : null;
Long docBoxSentAt = statusData.containsKey("doc_box_sent_at") ? (Long) statusData.get("doc_box_sent_at") : null;
Long docBoxReceivedAt = statusData.containsKey("doc_box_received_at") ? (Long) statusData.get("doc_box_received_at") : null;
Long authenticatedAt = statusData.containsKey("authenticated_at") ? (Long) statusData.get("authenticated_at") : null;
Long tokenUsedAt = statusData.containsKey("token_used_at") ? (Long) statusData.get("token_used_at") : null;
Long docBoxReadAt = statusData.containsKey("doc_box_read_at") ? (Long) statusData.get("doc_box_read_at") : null;
Long userNotifiedAt = statusData.containsKey("user_notified_at") ? (Long) statusData.get("user_notified_at") : null;
row.setKkoDocStat(CmmnUtil.isEmpty(docBoxStatus) ? row.getKkoDocStat() : KkoMydocStatusCd.valueOfEnum(docBoxStatus));
row.setKkoDocSentDt(CmmnUtil.isEmpty(row.getKkoDocSentDt()) ? DateUtil.absTimeSecToDate(docBoxSentAt, "yyyyMMddHHmmss") : row.getKkoDocSentDt());
row.setKkoDocReceivedDt(CmmnUtil.isEmpty(row.getKkoDocReceivedDt()) ? DateUtil.absTimeSecToDate(docBoxReceivedAt, "yyyyMMddHHmmss") : row.getKkoDocReceivedDt());
row.setKkoDocAuthFrstDt(CmmnUtil.isEmpty(row.getKkoDocAuthFrstDt()) ? DateUtil.absTimeSecToDate(authenticatedAt, "yyyyMMddHHmmss") : row.getKkoDocAuthFrstDt());
row.setKkoDocTokenVrfyFrstDt(CmmnUtil.isEmpty(row.getKkoDocTokenVrfyFrstDt()) ? DateUtil.absTimeSecToDate(tokenUsedAt, "yyyyMMddHHmmss") : row.getKkoDocTokenVrfyFrstDt());
row.setKkoDocReadFrstDt(CmmnUtil.isEmpty(row.getKkoDocReadFrstDt()) ? DateUtil.absTimeSecToDate(docBoxReadAt, "yyyyMMddHHmmss") : row.getKkoDocReadFrstDt());
row.setKkoDocUserNotiedDt(CmmnUtil.isEmpty(row.getKkoDocUserNotiedDt()) ? DateUtil.absTimeSecToDate(userNotifiedAt, "yyyyMMddHHmmss") : row.getKkoDocUserNotiedDt());
row.setError(FieldError.initBuilder().build());
} else {
if ("NOT_FOUND".equals(apiRespVO.getError_code()) && "documentBinder를 찾을 수 없습니다.".equals(apiRespVO.getError_message()))
row.setKkoDocStat(KkoMydocStatusCd.INTERNAL_SENT_ERR);
row.setError(FieldError.initBuilder()
.errorCode(EnsErrCd.RSLT630.getCode())
.errorMessage(String.format("%s %s", apiRespVO.getError_code(), apiRespVO.getError_message()))
.build());
}
}
/**
* Entity .
*
* @param list
* @param respMsg
* @return
*/
private List<SendDetailKkoMydocStatHist> toSendDetailStatHist(List<SendDetailKkoMydoc> list, String respMsg) {
// Map<String, KkoMydocApiRespVO> mMydocApiRespVOByExtDocUuid =
EnsResponseVO respVO = this.respMsgToMap(respMsg);
if (EnsErrCd.OK.equals(respVO.getErrCode())) {
List<Map<String, Object>> documents = (List<Map<String, Object>>) ((Map<String, Object>) respVO.getResultInfo()).get("documents");
Map<String, KkoMydocApiRespVO> mRespVOByExtDocUuid = documents.stream()
.map(row -> this.toApiRespVOMap(row))
.collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (KkoMydocApiRespVO) m.get("value"), (k1, k2) -> k1));
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
return list.stream()
.map(row -> {
KkoMydocApiRespVO apiRespVO = mRespVOByExtDocUuid.get(row.getDocumentBinderUuid());
Map<String, Object> statusData = (Map<String, Object>) apiRespVO.getData();
return SendDetailKkoMydocStatHist.builder()
.sendDetailId(row.getSendDetailId())
.documentBinderUuid(row.getDocumentBinderUuid())
.externalDocumentUuid(row.getPropExternalDocumentUuid())
// .respRawMsg(gson.toJson(statusData))
.docBoxStatus(statusData.containsKey("doc_box_status") ? (String) statusData.get("doc_box_status") : null)
.docBoxSentAt(statusData.containsKey("doc_box_sent_at") ? (Long) statusData.get("doc_box_sent_at") : null)
.docBoxReceivedAt(statusData.containsKey("doc_box_received_at") ? (Long) statusData.get("doc_box_received_at") : null)
.authenticatedAt(statusData.containsKey("authenticated_at") ? (Long) statusData.get("authenticated_at") : null)
.tokenUsedAt(statusData.containsKey("token_used_at") ? (Long) statusData.get("token_used_at") : null)
.docBoxReadAt(statusData.containsKey("doc_box_read_at") ? (Long) statusData.get("doc_box_read_at") : null)
.userNotifiedAt(statusData.containsKey("user_notified_at") ? (Long) statusData.get("user_notified_at") : null)
.docDistributionReceivedAt(statusData.containsKey("doc_distribution_received_at") ? (Long) statusData.get("doc_distribution_received_at") : null)
.payload(statusData.containsKey("payload") ? (String) statusData.get("payload") : null)
.error(FieldError.initBuilder()
.errorCode(apiRespVO.getError_code())
.errorMessage(apiRespVO.getError_message())
.build())
.build();
})
.collect(Collectors.toList());
} else {
return list.stream()
.map(row -> {
return SendDetailKkoMydocStatHist.builder()
.sendDetailId(row.getSendDetailId())
.documentBinderUuid(row.getDocumentBinderUuid())
.externalDocumentUuid(row.getPropExternalDocumentUuid())
.error(FieldError.initBuilder()
.errorCode(respVO.getErrCode().getCode())
.errorMessage(respVO.getErrMsg())
.build())
.build();
})
.collect(Collectors.toList());
}
}
private EnsResponseVO respMsgToMap(String respMsg) {
Gson gson = new GsonBuilder().registerTypeAdapter(Map.class, new MapDeserailizer()).serializeNulls().create();
EnsErrCd errCode = EnsErrCd.OK;
String errMsg = EnsErrCd.OK.getCodeNm();
Map<String, Object> mResp = null;
try {
mResp = gson.fromJson(respMsg, Map.class);
if (CmmnUtil.isEmpty(mResp.get("documents")))
throw new EnsException(EnsErrCd.RSLT620, String.format("문서상태조회API 오류. documents 키값이 없습니다. %s", respMsg));
} catch (EnsException e) {
errCode = e.getErrCd();
errMsg = e.getMessage();
} catch (Exception e) {
errCode = EnsErrCd.RSLT511;
errMsg = String.format("문서상태조회API 응답 데이터 JSON 객체 변환 실패. %s", respMsg);
}
return EnsErrCd.OK.equals(errCode) ?
EnsResponseVO.okBuilder().resultInfo(mResp).build()
: EnsResponseVO.errBuilder()
.errCode(errCode)
.errMsg(errMsg)
.build();
}
}

@ -0,0 +1,114 @@
package cokr.xit.ens.modules.kkotalk.service.support;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import cokr.xit.ens.core.aop.EnsResponseVO;
import cokr.xit.ens.core.exception.EnsException;
import cokr.xit.ens.core.exception.code.EnsErrCd;
import cokr.xit.ens.core.utils.CmmnUtil;
import cokr.xit.ens.modules.common.biztmplt.EnsPhaseProcSupport;
import cokr.xit.ens.modules.common.code.StatCd;
import cokr.xit.ens.modules.common.domain.SendMast;
import cokr.xit.ens.modules.common.domain.repository.SendMastRepository;
import cokr.xit.ens.modules.kkomydoc.domain.SendDetailKkoMydoc;
import cokr.xit.ens.modules.kkomydoc.domain.repository.SendDetailKkoMydocRepository;
import cokr.xit.ens.modules.kkomydoc.model.KkoMydocRsltRespDTO;
import cokr.xit.ens.modules.kkomydoc.model.config.KkoMydocStat;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
// FIXME: 카카오톡 신규 추가
@Slf4j
@Component
@RequiredArgsConstructor
public class KkoTalkRsltProvider implements EnsPhaseProcSupport<EnsResponseVO, Long> {
private final SendMastRepository sendMastRepository;
private final SendDetailKkoMydocRepository sendDetailKkoMydocRepository;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public EnsResponseVO statReady(List<Long> args) {
log.info("no process");
return null;
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public EnsResponseVO statBegin(Long arg) {
log.info("no process");
return null;
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public EnsResponseVO execute(Long sendMastId) {
try {
SendMast sendMast = sendMastRepository.findById(sendMastId)
.orElseThrow(() -> new EnsException(EnsErrCd.PRVD404, String.format("일치하는 발송마스터 자료가 없습니다. [ sendMastId %s ]", sendMastId)));
List<SendDetailKkoMydoc> sendDetailKkoMydocs = sendDetailKkoMydocRepository.findAllBySendMast(sendMast);
KkoMydocRsltRespDTO respDTO = KkoMydocRsltRespDTO.builder()
.sendMastId(sendMast.getSendMastId())
.statCd(sendMast.getStatCd())
.orgCd(sendMast.getOrgCd())
.tmpltCd(sendMast.getTmpltCd())
.postBundleTitle(sendMast.getPostBundleTitle())
.sendDt(sendMast.getSendDt().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")))
.closeDt(sendMast.getCloseDt().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")))
.closeAt(this.getCloseAt(sendMast))
.documents(sendDetailKkoMydocs.stream()
.map(row -> KkoMydocStat.builder()
.sendDetailId(row.getSendDetailId())
.externalDocumentUuid(row.getPropExternalDocumentUuid())
.documentBinderUuid(row.getDocumentBinderUuid())
.kkoDocStat(row.getKkoDocStat())
.kkoDocSentDt(row.getKkoDocSentDt())
.kkoDocReceivedDt(row.getKkoDocReceivedDt())
.kkoDocAuthFrstDt(row.getKkoDocAuthFrstDt())
.kkoDocTokenVrfyFrstDt(row.getKkoDocTokenVrfyFrstDt())
.kkoDocReadFrstDt(row.getKkoDocReadFrstDt())
.kkoDocUserNotiedDt(row.getKkoDocUserNotiedDt())
.errorCode(CmmnUtil.isEmpty(row.getError()) ? null : row.getError().getErrorCode())
.errorMessage(CmmnUtil.isEmpty(row.getError()) ? null : row.getError().getErrorMessage())
.build())
.collect(Collectors.toList()))
.build();
return EnsResponseVO.okBuilder().resultInfo(respDTO).build();
} catch (EnsException e) {
return EnsResponseVO.errRsltBuilder()
.errCode(e.getErrCd())
.errMsg(e.getMessage())
.resultInfo(KkoMydocRsltRespDTO.builder()
.sendMastId(sendMastId)
.build())
.build();
} catch (Exception e) {
return EnsResponseVO.errRsltBuilder()
.errCode(EnsErrCd.ERR999)
.errMsg(String.format("전송결과 응답 처리 실패. 실패사유: %s", e.getMessage()))
.resultInfo(KkoMydocRsltRespDTO.builder()
.sendMastId(sendMastId)
.build())
.build();
}
}
private boolean getCloseAt(SendMast sendMast) {
if (StatCd.close.equals(sendMast.getStatCd()))
return true;
else if (StatCd.sendfail.equals(sendMast.getStatCd()))
return true;
else
return false;
}
}

@ -0,0 +1,427 @@
package cokr.xit.ens.modules.kkotalk.service.support;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import cokr.xit.ens.core.aop.EnsResponseVO;
import cokr.xit.ens.core.exception.EnsException;
import cokr.xit.ens.core.exception.code.EnsErrCd;
import cokr.xit.ens.core.monitor.slack.event.MonitorEvent;
import cokr.xit.ens.core.utils.CmmnUtil;
import cokr.xit.ens.core.utils.MapDeserailizer;
import cokr.xit.ens.modules.common.biztmplt.SendProcTemplate;
import cokr.xit.ens.modules.common.code.PostSeCd;
import cokr.xit.ens.modules.common.code.StatCd;
import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.OrgMng;
import cokr.xit.ens.modules.common.ctgy.sys.mng.service.OrgMngService;
import cokr.xit.ens.modules.common.domain.SendMast;
import cokr.xit.ens.modules.common.domain.repository.SendMastRepository;
import cokr.xit.ens.modules.common.domain.support.FieldError;
import cokr.xit.ens.modules.common.event.SendMastStatUpdateEvent;
import cokr.xit.ens.modules.common.monitor.MessageByPhase;
import cokr.xit.ens.modules.kkomydoc.code.KkoMydocStatusCd;
import cokr.xit.ens.modules.kkomydoc.domain.SendDetailKkoMydoc;
import cokr.xit.ens.modules.kkomydoc.domain.SendDetailKkoMydocReqHist;
import cokr.xit.ens.modules.kkomydoc.domain.repository.SendDetailKkoMydocRepository;
import cokr.xit.ens.modules.kkomydoc.domain.repository.SendDetailKkoMydocReqHistRepository;
import cokr.xit.ens.modules.kkomydoc.model.KkoMydocApiRespVO;
import cokr.xit.ens.modules.kkomydoc.service.support.KkoMydocApiSpec;
import cokr.xit.ens.modules.kkotalk.mapper.IKkoTalkMapper;
import cokr.xit.ens.modules.kkotalk.model.KkotalkApiDTO;
import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
// FIXME: 카카오톡 신규 추가
@Slf4j
@Component
@RequiredArgsConstructor
public class KkoTalkSender extends SendProcTemplate {
private final ApplicationEventPublisher applicationEventPublisher;
private final SendMastRepository sendMastRepository;
private final SendDetailKkoMydocRepository sendDetailKkoMydocRepository;
private final IKkoTalkMapper talkMapper;
private final SendDetailKkoMydocReqHistRepository sendDetailKkoMydocReqHistRepository;
private final OrgMngService orgMngService;
private final KkoMydocApiSpec kkoMydocApi;
private final IKkoTalkApiService kkoTalkApi;
@Value("${contract.kakao.pay.mydoc.api.bulksend-batch-unit}")
private int SEND_BATCH_UNIT;
private Gson gson = new GsonBuilder().registerTypeAdapter(Map.class, new MapDeserailizer()).disableHtmlEscaping().create();
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public EnsResponseVO execute(Long sendMastId) {
SendMast sendMast = null;
List<KkotalkDTO.SendDetailKkoTalkDTO> sendDetails = null;
EnsResponseVO respVO = null;
try {
if (CmmnUtil.isEmpty(sendMastId))
throw new EnsException(EnsErrCd.SEND410, "발송마스터ID(은)는 필수조건 입니다.");
sendMast = sendMastRepository.findById(sendMastId).orElseThrow(() -> new EnsException(EnsErrCd.SEND404, String.format("일치하는 발송마스터 자료가 없습니다. [ sendMastId %s ]", sendMastId)));
sendDetails = talkMapper.findAllFetchBySendMastId(sendMast.getSendMastId());
//sendDetails = sendDetailKkoMydocRepository.findAllFetchBySendMastId(sendMast.getSendMastId());
OrgMng orgMng = orgMngService.find(sendMast.getOrgCd()).getResultInfo();
Lists.partition(sendDetails, SEND_BATCH_UNIT).stream()
.forEach(list -> {
String sendRespBody = null;
String jsonStr = null;
try {
List<KkotalkApiDTO.Envelope> envelopes = this.makeMessage(list);
KkotalkDTO.BulkSendResponse resp = kkoTalkApi.requestSendBulk(
KkotalkDTO.BulkSendRequest.builder()
.envelopes(envelopes)
.signguCode(orgMng.getOrgCd())
.ffnlgCode("11")
.build()
);
/*
EnsResponseVO message = this.makeMessage(list);
if (!(EnsErrCd.OK.equals(message.getErrCode()) || null == message.getErrCode()))
throw new EnsException(message.getErrCode(), message.getErrMsg());
jsonStr = String.valueOf(message.getResultInfo());
ResponseEntity<String> resp = kkoMydocApi.bulkSend(orgMng.getKkoMdAccessToken(), orgMng.getKkoMdContractUuid(), jsonStr);
if (log.isDebugEnabled()) {
StringBuffer sb = new StringBuffer();
sb.append("\n==============================================================================")
.append("\n[ Kakao Mydoc - Sending ]")
.append("\n### Request Info...")
.append("\n" + jsonStr)
.append("\n### Response Info...")
.append("\n" + resp.getBody())
.append("\n==============================================================================");
log.debug(sb.toString());
}
sendRespBody = resp.getBody();
if (resp.getStatusCode() != HttpStatus.OK)
throw new EnsException(EnsErrCd.SEND620, String.format("전송요청 중.. %s %s", resp.getStatusCode().toString(), resp.getBody()));
EnsResponseVO mResponse = this.respMsgToMap(resp.getBody());
if (!EnsErrCd.OK.equals(mResponse.getErrCode()))
throw new EnsException(mResponse.getErrCode(), mResponse.getErrMsg());
List<Map<String, Object>> documents = (List<Map<String, Object>>) ((Map<String, Object>) mResponse.getResultInfo()).get("documents");
Map<String, KkoMydocApiRespVO> mApiRespVOByExtDocUuid = documents.stream()
.map(row -> this.toApiRespVOMap(row))
.collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (KkoMydocApiRespVO) m.get("value"), (k1, k2) -> k1));
list.stream()
.forEach(row -> this.modifyDocSendRsltByExtDocUuid(row, mApiRespVOByExtDocUuid));
} catch (EnsException e) {
list.stream()
.forEach(row -> {
row.set(KkoMydocStatusCd.SENT_FAIL);
row.setError(FieldError.initBuilder()
.errorCode(e.getErrCd().getCode())
.errorMessage(e.getMessage())
.build());
});
} catch (Exception e) {
list.stream()
.forEach(row -> {
row.setKkoDocStat(KkoMydocStatusCd.SENT_FAIL);
row.setError(FieldError.initBuilder()
.errorCode(EnsErrCd.SEND500.getCode())
.errorMessage(e.getMessage())
.build());
});
*/
} finally {
// if (!CmmnUtil.isEmpty(jsonStr))
// sendDetailKkoMydocReqHistRepository.saveAll(this.toSendDetailReqHist(list, jsonStr, sendRespBody));
}
});
Optional<Integer> cntSuccess = sendDetails.stream()
.map(row -> CmmnUtil.isEmpty(row.getErrorCode()) ? 1 : 0)
.reduce(Integer::sum);
if (cntSuccess.get() > 0)
respVO = EnsResponseVO.okBuilder()
.resultInfo(sendDetails.stream()
// .map(row -> this.toResultInfo(row))
.collect(Collectors.toList()))
.build();
else
throw new EnsException(EnsErrCd.SEND500, String.format("전체건수 전송 실패(총 %d 건)", sendDetails.size()));
} catch (EnsException e) {
respVO = EnsResponseVO.errBuilder().errCode(e.getErrCd()).errMsg(e.getMessage()).build();
} catch (Exception e) {
respVO = EnsResponseVO.errBuilder().errCode(EnsErrCd.SEND500).errMsg(e.getMessage()).build();
} finally {
if (!CmmnUtil.isEmpty(sendMast)) {
if (EnsErrCd.OK.equals(respVO.getErrCode())) {
sendMast.setStatCd(StatCd.sendok);
sendMast.setError(FieldError.initBuilder().build());
} else {
sendMast.setStatCd(StatCd.sendfail);
sendMast.setError(FieldError.initBuilder()
.errorCode(respVO.getErrCode().getCode())
.errorMessage(respVO.getErrMsg())
.build());
applicationEventPublisher.publishEvent(MonitorEvent.builder()
.message(MessageByPhase.builder()
.oClass(getClass().getSimpleName() + "." + new Throwable().getStackTrace()[0].getMethodName())
.postSeCd(PostSeCd.kkoMydoc)
.statCd(StatCd.sendfail)
.errCd(respVO.getErrCode())
.message("-.SendMastId: " + sendMastId + "\n" + respVO.getErrMsg())
.build())
.build());
}
}
// if (respVO != null && EnsErrCd.OK.equals(respVO.getErrCode())) {
// applicationEventPublisher.publishEvent(KkoMydocSentEvent.builder().sendMastId(sendMastId).build());
// } else {
applicationEventPublisher.publishEvent(SendMastStatUpdateEvent.builder().sendMastId(sendMastId).build());
// }
}
return respVO;
}
/**
* API body Message .
*
* @param list
* @return
*/
private List<KkotalkApiDTO.Envelope> makeMessage(List<KkotalkDTO.SendDetailKkoTalkDTO> list) {
final List<KkotalkApiDTO.Envelope> bulkList = new ArrayList<>();
for (KkotalkDTO.SendDetailKkoTalkDTO sendTgtDTO : list) {
KkotalkApiDTO.Envelope bulkReqDTO = null;
if (StringUtils.isNotEmpty(sendTgtDTO.getCi())) {
bulkReqDTO = KkotalkApiDTO.Envelope.builder()
.ci(sendTgtDTO.getCi())
.build();
} else {
bulkReqDTO = KkotalkApiDTO.Envelope.builder()
.build();
}
final KkotalkApiDTO.Content content = KkotalkApiDTO.Content.builder()
.link(sendTgtDTO.getLink())
.build();
bulkReqDTO.setExternalId(sendTgtDTO.getSendDetailId().toString());
bulkReqDTO.setTitle(sendTgtDTO.getTitle());
bulkReqDTO.setPayload(sendTgtDTO.getPayload());
bulkReqDTO.setReadExpiresAt(sendTgtDTO.getReadExpiresAt());
bulkReqDTO.setReviewExpiresAt(sendTgtDTO.getReviewExpiresAt());
bulkReqDTO.setHash(sendTgtDTO.getHash());
bulkReqDTO.setGuide(sendTgtDTO.getGuide());
bulkReqDTO.setContent(content);
bulkList.add(bulkReqDTO);
}
return bulkList;
}
/**
* APIVO
*
* @param row
* @return
*/
private Map<String, Object> toApiRespVOMap(Map<String, Object> row) {
KkoMydocApiRespVO apiRespVO = KkoMydocApiRespVO.builder()
.error_code(row.containsKey("error_code") ? String.valueOf(row.get("error_code")) : null)
.error_message(row.containsKey("error_message") ? String.valueOf(row.get("error_message")) : null)
.data(row.containsKey("document_binder_uuid") ? String.valueOf(row.get("document_binder_uuid")) : null)
.build();
Map<String, Object> m = new HashMap<>();
m.put("key", row.get("external_document_uuid"));
m.put("value", apiRespVO);
return m;
}
/**
* (update)
* -. / (document_binder_uuid) Error
*
* @param row
* @param mApiRespVOByExtDocUuid
*/
private void modifyDocSendRsltByExtDocUuid(SendDetailKkoMydoc row, Map<String, KkoMydocApiRespVO> mApiRespVOByExtDocUuid) {
KkoMydocApiRespVO apiRespVO = mApiRespVOByExtDocUuid.get(row.getPropExternalDocumentUuid());
if (CmmnUtil.isEmpty(apiRespVO.getError_code())) {
row.setKkoDocStat(KkoMydocStatusCd.SENT);
row.setDocumentBinderUuid(String.valueOf(apiRespVO.getData()));
row.setError(FieldError.initBuilder().build());
} else {
row.setKkoDocStat(KkoMydocStatusCd.valueOfEnum(apiRespVO.getError_code()));
row.setError(FieldError.initBuilder()
.errorCode(EnsErrCd.SEND630.getCode())
.errorMessage(String.format("%s %s", apiRespVO.getError_code(), apiRespVO.getError_message()))
.build());
}
}
private Map<String, String> toResultInfo(SendDetailKkoMydoc row) {
Map<String, String> m = new HashMap<>();
// m.put("msgIdx", row.getMsgIdx());
m.put("documentBinderUuid", row.getDocumentBinderUuid());
m.put("externalDocumentUuid", row.getPropExternalDocumentUuid());
if (!CmmnUtil.isEmpty(row.getError())) {
m.put("errCd", row.getError().getErrorCode());
m.put("errMsg", row.getError().getErrorMessage());
}
return m;
}
/**
* Entity .
*
* @param sendMsg
* @param respMsg
* @return
*/
private List<SendDetailKkoMydocReqHist> toSendDetailReqHist(List<SendDetailKkoMydoc> list, String sendMsg, String respMsg) {
List<Map<String, Object>> sendMsgList = (List<Map<String, Object>>) gson.fromJson(sendMsg, Map.class).get("documents");
Map<String, Long> mSendDetailIds = list.stream()
.map(row -> {
Map<String, Object> m = new HashMap<>();
m.put("key", row.getPropExternalDocumentUuid());
m.put("value", row.getSendDetailId());
return m;
})
.collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (Long) m.get("value"), (k1, k2) -> k1));
EnsResponseVO respVO = this.respMsgToMap(respMsg);
if (EnsErrCd.OK.equals(respVO.getErrCode())) {
Map<String, Object> mRespBody = (Map<String, Object>) respVO.getResultInfo();
List<Map<String, String>> documents = (List<Map<String, String>>) mRespBody.get("documents");
Map<String, Map<String, String>> mRespMsg = documents.stream()
.map(row -> {
Map<String, Object> m = new HashMap<>();
m.put("key", String.valueOf(row.get("external_document_uuid")));
Map<String, String> mValue = new HashMap<>();
mValue.put("document_binder_uuid", row.get("document_binder_uuid"));
mValue.put("error_code", row.get("error_code"));
mValue.put("error_message", row.get("error_message"));
mValue.put("rawMsg", gson.toJson(row));
m.put("value", mValue);
return m;
})
.collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (Map<String, String>) m.get("value"), (k1, k2) -> k1));
return sendMsgList.stream()
.map(row -> {
Map<String, String> property = (Map<String, String>) row.get("property");
String externalDocumentUuid = property.get("external_document_uuid");
boolean isSuccess = CmmnUtil.isEmpty(mRespMsg.get(externalDocumentUuid).get("error_code"));
return SendDetailKkoMydocReqHist.builder()
.sendDetailId(mSendDetailIds.get(externalDocumentUuid))
.sendRawMsg(gson.toJson(row))
.externalDocumentUuid(externalDocumentUuid)
.documentBinderUuid(mRespMsg.get(externalDocumentUuid).get("document_binder_uuid"))
.respRawMsg(mRespMsg.get(externalDocumentUuid).get("rawMsg"))
.error(FieldError.initBuilder()
.errorCode(isSuccess ? null : EnsErrCd.SEND620.getCode())
.errorMessage(isSuccess ? null : String.format("%s %s"
, mRespMsg.get(externalDocumentUuid).get("error_code")
, mRespMsg.get(externalDocumentUuid).get("error_message")
))
.build())
.build();
})
.collect(Collectors.toList());
} else {
return sendMsgList.stream()
.map(row -> {
Map<String, String> property = (Map<String, String>) row.get("property");
String externalDocumentUuid = property.get("external_document_uuid");
return SendDetailKkoMydocReqHist.builder()
.sendDetailId(mSendDetailIds.get(externalDocumentUuid))
.sendRawMsg(gson.toJson(row))
.externalDocumentUuid(externalDocumentUuid)
.respRawMsg(respMsg)
.error(FieldError.initBuilder()
.errorCode(respVO.getErrCode().getCode())
.errorMessage(respVO.getErrMsg())
.build())
.build();
})
.collect(Collectors.toList());
}
}
private EnsResponseVO respMsgToMap(String respMsg) {
EnsErrCd errCode = EnsErrCd.OK;
String errMsg = EnsErrCd.OK.getCodeNm();
Map<String, Object> mResp = null;
try {
mResp = gson.fromJson(respMsg, Map.class);
if (CmmnUtil.isEmpty(mResp.get("documents")))
throw new EnsException(EnsErrCd.SEND620, String.format("전송요청API 오류. documents 키값이 없습니다. %s", respMsg));
} catch (EnsException e) {
errCode = e.getErrCd();
errMsg = e.getMessage();
} catch (Exception e) {
errCode = EnsErrCd.SEND511;
errMsg = String.format("전송요청API 응답데이터 JSON 객체 변환 실패. %s", respMsg);
}
return EnsErrCd.OK.equals(errCode) ?
EnsResponseVO.okBuilder().resultInfo(mResp).build()
: EnsResponseVO.errBuilder()
.errCode(errCode)
.errMsg(errMsg)
.build();
}
}

@ -10,7 +10,7 @@ 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 cokr.xit.ens.modules.kkotalk.service.support.IKkoTalkApiService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
@ -32,13 +32,13 @@ import lombok.extern.slf4j.Slf4j;
*
* </pre>
*/
@Tag(name = "KkotalkEltrcDocController", description = "카카오톡 전자문서 API")
@Tag(name = "KkotalkController", description = "카카오톡 전자문서 API")
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping(value = "/api/ens/kakao/v2")
public class KkotalkEltrcDocController {
private final IKkotalkEltrcDocService service;
public class KkotalkApiController {
private final IKkoTalkApiService service;
/**
* <pre>

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cokr.xit.ens.modules.kkotalk.mapper.IKkoTalkMapper">
<!-- // FIXME: 카카오톡 신규 추가 -->
<!-- =================================================================================== -->
<!-- ================================ accept =========================================== -->
<!-- =================================================================================== -->
<insert id="saveSndDtlKkoTalk" parameterType="cokr.xit.ens.modules.kkotalk.model.KkotalkDTO$SendDetailKkoTalkDTO">
/** iup-kkotalk-mapper|saveSndDtlKkoTalk-카카오톡발송상세생성|julim */
INSERT INTO ens_snd_dtl_kko_talk (
send_detail_id,
title,
link,
hash,
guide,
payload,
read_expires_at,
review_expires_at,
use_non_personalized_noti,
ci,
phone_number,
name,
birthday,
external_id,
error_code,
error_message,
bill_uid,
send_mast_id,
regist_dt
) VALUES (
#{sendDetailId},
#{title},
#{link},
#{hash},
#{guide},
#{payload},
#{readExpiresAt},
#{reviewExpiresAt},
#{useNonPersonalizedNoti},
#{ci},
#{phoneNumber},
#{name},
#{birthday},
#{externalId},
#{errorCode},
#{errorMessage},
#{billUid},
#{sendMastId},
sysdate
)
</insert>
<select id="findAllBySendMastId" parameterType="string" resultType="cokr.xit.ens.modules.kkotalk.model.KkotalkDTO$SendDetailKkoTalkDTO">
/** iup-kkotalk-mapper|findAllBySendMastId-카카오톡발송대상 조회|julim */
SELECT send_detail_id,
title,
link,
hash,
guide,
payload,
read_expires_at,
review_expires_at,
use_non_personalized_noti,
ci,
phone_number,
name,
birthday,
external_id,
error_code,
error_message,
bill_uid,
send_mast_id,
regist_dt
FROM ens_snd_dtl_kko_talk
WHERE send_mast_id = #{sendMastId}
</select>
</mapper>

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--
cacheEnabled 설정에서 각 mapper 에 설정된 캐시를 전역적으로 사용할지 말지에 대한 여부 true | false true
lazyLoadingEnabled 늦은 로딩을 사용할지에 대한 여부. 사용하지 않는다면 모두 즉시 로딩할 것이다. 이 값은 fetchType 속성을 사용해서 대체할 수 있다. true | false false
aggressiveLazyLoading 활성화 상태로 두게 되면 늦은(lazy) 로딩 프로퍼티를 가진 객체는 호출에 따라 로드될 것이다. 반면에 개별 프로퍼티는 요청할때 로드된다. true | false true
multipleResultSetsEnabled 한개의 구문에서 여러개의 ResultSet 을 허용할지의 여부(드라이버가 해당 기능을 지원해야 함) true | false true
useColumnLabel 칼럼명 대신에 칼럼라벨을 사용. 드라이버마다 조금 다르게 작동한다. 문서와 간단한 테스트를 통해 실제 기대하는 것처럼 작동하는지 확인해야 한다. true | false true
useGeneratedKeys 생성키에 대한 JDBC 지원을 허용. 지원하는 드라이버가 필요하다. true 로 설정하면 생성키를 강제로 생성한다. 일부 드라이버(예를들면, Derby)에서는 이 설정을 무시한다. true | false False
autoMappingBehavior MyBatis 가 칼럼을 필드/프로퍼티에 자동으로 매핑할지와 방법에 대해 명시. PARTIAL 은 간단한 자동매핑만 할뿐, 내포된 결과에 대해서는 처리하지 않는다. FULL 은 처리가능한 모든 자동매핑을 처리한다. NONE, PARTIAL, FULL PARTIAL
defaultExecutorType 디폴트 실행자(executor) 설정. SIMPLE 실행자는 특별히 하는 것이 없다. REUSE 실행자는 PreparedStatement 를 재사용한다. BATCH 실행자는 구문을 재사용하고 수정을 배치처리한다. SIMPLE REUSE BATCH SIMPLE
defaultStatementTimeout 데이터베이스로의 응답을 얼마나 오래 기다릴지를 판단하는 타임아웃을 셋팅 양수 셋팅되지 않음(null)
safeRowBoundsEnabled 중첩구문내 RowBound 사용을 허용 true | false False
mapUnderscoreToCamelCase 전통적인 데이터베이스 칼럼명 형태인 A_COLUMN을 CamelCase형태의 자바 프로퍼티명 형태인 aColumn으로 자동으로 매핑하도록 함 true | false False
localCacheScope 마이바티스는 순환참조를 막거나 반복된 쿼리의 속도를 높히기 위해 로컬캐시를 사용한다. 디폴트 설정인 SESSION을 사용해서 동일 세션의 모든 쿼리를 캐시한다. localCacheScope=STATEMENT 로 설정하면 로컬 세션은 구문 실행할때만 사용하고 같은 SqlSession에서 두개의 다른 호출사이에는 데이터를 공유하지 않는다. SESSION | STATEMENT SESSION
jdbcTypeForNull JDBC타입을 파라미터에 제공하지 않을때 null값을 처리한 JDBC타입을 명시한다. 일부 드라이버는 칼럼의 JDBC타입을 정의하도록 요구하지만 대부분은 NULL, VARCHAR 나 OTHER 처럼 일반적인 값을 사용해서 동작한다. JdbcType 이늄. 대부분은 NULL, VARCHAR 나 OTHER 를 공통적으로 사용한다. OTHER
lazyLoadTriggerMethods 늦은 로딩을 야기하는 객체의 메소드를 명시 메소드 이름을 나열하고 여러개일 경우 콤마(,) 로 구분 equals,clone,hashCode,toString
defaultScriptingLanguage 동적으로 SQL을 만들기 위해 기본적으로 사용하는 언어를 명시 타입별칭이나 패키지 경로를 포함한 클래스명 org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver
callSettersOnNulls 가져온 값이 null일때 setter나 맵의 put 메소드를 호출할지를 명시 Map.keySet() 이나 null값을 초기화할때 유용하다. int, boolean 등과 같은 원시타입은 null을 셋팅할 수 없다는 점은 알아두면 좋다. true | false false
logPrefix 마이바티스가 로거(logger) 이름에 추가할 접두사 문자열을 명시 문자열 셋팅하지 않음
logImpl 마이바티스가 사용할 로깅 구현체를 명시 이 설정을 사용하지 않으면 마이바티스가 사용할 로깅 구현체를 자동으로 찾는다. SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 셋팅하지 않음
proxyFactory 마이바티스가 늦은 로딩을 처리할 객체를 생성할 때 사용할 프록시 툴을 명시 CGLIB | JAVASSIST CGLIB
-->
<settings>
<!--
Settings 설정 옵션 사이트 참조
-.사이트주소: https://postitforhooney.tistory.com/entry/MyBatisSetting-Mybatis%EC%97%90%EC%84%9C-%ED%95%84%EC%9A%94%ED%95%9C-%EB%B6%80%EB%B6%84%EB%93%A4-setting%EC%9E%90%EB%A3%8C
-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="cacheEnabled" value="true" />
<setting name="jdbcTypeForNull" value="VARCHAR" /><!-- NULL / VARCHAR / OTHER-->
<setting name="callSettersOnNulls" value="true"/> <!-- resultType으로 Map Collection 지정 시 value가 null일 떄 컬럼 누락문제 해결을 위한 설정 -->
<setting name="lazyLoadingEnabled" value="false" />
<setting name="multipleResultSetsEnabled" value="true" />
<setting name="useColumnLabel" value="true" />
<setting name="useGeneratedKeys" value="false" />
<setting name="autoMappingBehavior" value="PARTIAL" /><!-- NONE / PARTIAL / FULL-->
<setting name="defaultExecutorType" value="SIMPLE" /><!-- SIMPLE / REUSE / BATCH-->
<setting name="defaultStatementTimeout" value="25" />
<setting name="safeRowBoundsEnabled" value="false" />
<setting name="localCacheScope" value="SESSION" /><!-- SESSION / STATEMENT-->
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
<setting name="aggressiveLazyLoading" value="true" />
</settings>
<!-- Type Aliases 설정-->
<!-- <typeAliases>-->
<!-- <typeAlias alias="egovMap" type="org.egovframe.rte.psl.dataaccess.util.EgovMap" />-->
<!-- <typeAlias alias="ComDefaultCodeVO" type="egovframework.com.cmm.model.ComDefaultCodeVO" />-->
<!-- <typeAlias alias="comDefaultVO" type="egovframework.com.cmm.model.ComDefaultVO" />-->
<!-- </typeAliases>-->
</configuration>
Loading…
Cancel
Save