알림톡 발송개선 진행중

This commit is contained in:
hehihoho3@gmail.com 2025-03-21 09:58:55 +09:00
parent 3afef8e37c
commit 11e32cd532
12 changed files with 588 additions and 225 deletions

View File

@ -1,5 +1,9 @@
package itn.let.kakao.kakaoComm;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* @FileName : KakaoButtonVO.java
* @Project : mjon
@ -8,6 +12,9 @@ package itn.let.kakao.kakaoComm;
* @프로그램 설명 : button, quickReplies 변수
*/
@ToString
@Getter
@Setter
public class KakaoButtonVO {
private String name = ""; // 버튼명 - linkType AC 선택 버튼명은 채널추가 고정
@ -18,48 +25,4 @@ public class KakaoButtonVO {
private String linkPc = ""; // PC 링크 주소 (WL 사용시 선택)
private String pluginId = ""; // 플러그인 ID (P1, P2, P3 사용시 필수)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLinkType() {
return linkType;
}
public void setLinkType(String linkType) {
this.linkType = linkType;
}
public String getLinkAnd() {
return linkAnd;
}
public void setLinkAnd(String linkAnd) {
this.linkAnd = linkAnd;
}
public String getLinkIos() {
return linkIos;
}
public void setLinkIos(String linkIos) {
this.linkIos = linkIos;
}
public String getLinkMo() {
return linkMo;
}
public void setLinkMo(String linkMo) {
this.linkMo = linkMo;
}
public String getLinkPc() {
return linkPc;
}
public void setLinkPc(String linkPc) {
this.linkPc = linkPc;
}
public String getPluginId() {
return pluginId;
}
public void setPluginId(String pluginId) {
this.pluginId = pluginId;
}
}

View File

@ -3,6 +3,10 @@ package itn.let.kakao.kakaoComm;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* @FileName : KakaoCommentVO.java
* @Project : mjon
@ -11,6 +15,9 @@ import java.util.List;
* @프로그램 설명 : comment 변수
*/
@ToString
@Getter
@Setter
public class KakaoCommentVO {
private String content = ""; // 댓글 본분
@ -26,60 +33,4 @@ public class KakaoCommentVO {
private String originalFileName = "";
private String filePath = "";
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getCreatedAt() {
return createdAt;
}
public void setCreatedAt(String createdAt) {
this.createdAt = createdAt;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getOriginalFileName() {
return originalFileName;
}
public void setOriginalFileName(String originalFileName) {
this.originalFileName = originalFileName;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public List<KakaoCommentVO> getAttachFileList() {
return attachFileList;
}
public void setAttachFileList(List<KakaoCommentVO> attachFileList) {
this.attachFileList = attachFileList;
}
}

View File

@ -38,6 +38,7 @@ public class KakaoSendAdvcVO implements Serializable {
private String msgType; // 메시지 타입
private String templateContent; // 템플릿 내용
private String templateTitle; // 템플릿 제목
List<KakaoButtonVO> buttonList; // 템플릿 버튼 리스트
private String subMsgSendYn; // 대체문자 전송 여부
private String subMsgTxt; // 대체문자 내용
private String subMsgType; // 대체문자 타입
@ -48,9 +49,11 @@ public class KakaoSendAdvcVO implements Serializable {
// =====
// =====
private float smsPrice; // sms 단가
private float mmsPrice; // mms 단가
private float kakaoAtPrice; // 카카오 알림톡 단가
private String eachPrice; // sms 단가
private String smsPrice; // sms 단가
private String mmsPrice; // mms 단가
private String kakaoAtPrice; // 카카오 알림톡 단가
private String bizJsonName; // 카카오 알림톡 단가
@ -69,6 +72,7 @@ public class KakaoSendAdvcVO implements Serializable {
"\n , msgType=[" + msgType + "]" +
"\n , templateContent=[" + templateContent + "]" +
"\n , templateTitle=[" + templateTitle + "]" +
"\n , buttonList=[" + buttonList.toString() + "]" +
"\n , subMsgSendYn=[" + subMsgSendYn + "]" +
"\n , subMsgTxt=[" + subMsgTxt + "]" +
"\n , subMsgType=[" + subMsgType + "]" +
@ -76,9 +80,11 @@ public class KakaoSendAdvcVO implements Serializable {
"\n , jsonStr=[" + jsonStr + "]" +
"\n , ==== MJ_MSG_DATA INSERT DATA END =======" +
"\n " +
"\n , eachPrice=[" + eachPrice + "]" +
"\n , smsPrice=[" + smsPrice + "]" +
"\n , mmsPrice=[" + mmsPrice + "]" +
"\n , kakaoAtPrice=[" + kakaoAtPrice + "]" +
"\n , bizJsonName=[" + bizJsonName + "]" +
"\n ]";
}

View File

@ -1,5 +1,6 @@
package itn.let.kakao.kakaoComm;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
@ -11,11 +12,13 @@ import java.util.regex.Pattern;
import javax.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import egovframework.rte.fdl.idgnr.EgovIdGnrService;
import itn.com.cmm.util.StringUtil;
import itn.let.kakao.kakaoComm.kakaoApi.KakaoApiJsonSave;
import itn.let.kakao.kakaoComm.kakaoApi.KakaoApiTemplate;
@ -38,7 +41,7 @@ public class KakaoSendUtil {
@Resource(name = "MjonMsgDataService")
private MjonMsgDataService mjonMsgDataService;
@Autowired
KakaoApiTemplate kakaoApiTemplate;
@ -48,7 +51,10 @@ public class KakaoSendUtil {
@Autowired
private MjonCommon mjonCommon;
// 클래스 수준에서 정적 Pattern 정의 (성능 최적화)
private static final Pattern REPLACEMENT_PATTERN = Pattern.compile("#\\{[^}]+\\}");
private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
// 단문 메세지 타입
public static final String SHORT_MSG_TYPE = "SMS";
@ -67,43 +73,41 @@ public class KakaoSendUtil {
* @throws Exception
*
*/
public List<KakaoSendAdvcVO> populateSendLists(KakaoVO kakaoVO, StatusResponse statusResponse) throws Exception {
public List<KakaoSendAdvcVO> populateSendLists(KakaoVO kakaoVO, boolean isHolidayNotified, StatusResponse statusResponse) throws Exception {
//사용자 현재 보유 금액 불러오기(문자 발송 금액 차감 이전 금액)
// String befCash = kakaoVO.getBefCash();
log.info(" +kakaoVO.getVarListMap().size() :: [{}]", kakaoVO.getVarListMap().size());
List<KakaoSendAdvcVO> kakaoSendAdvcListVO = new ArrayList<>();
Calendar calendar = setupBaseDate(kakaoVO, isHolidayNotified);
// 예약 시간 기본값 설정
Date now = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
// ReqDate가 비어 있으면 현재 시간으로 설정, 그렇지 않으면 ReqDate로 설정
// 화면에서 예약문자면 예약시간을 regDate로 설정한다.
Date baseDate;
if (StringUtils.isEmpty(kakaoVO.getReqDate())) {
kakaoVO.setReqDate(sdf.format(now)); // ReqDate에 현재 시간 설정
baseDate = now;
} else {
baseDate = sdf.parse(kakaoVO.getReqDate()); // ReqDate를 baseDate로 설정
}
Calendar calendar = Calendar.getInstance();
calendar.setTime(baseDate); // calendar에 baseDate 설정
KakaoReturnVO templateDetail = kakaoApiTemplate.selectKakaoApiTemplateDetail(kakaoVO);
log.info(" + templateDetail :: [{}]", templateDetail.toString());
String templateContent = templateDetail.getTemplateContent(); // 알림톡 템플릿
kakaoVO.setTemplateContent(templateContent);
String templateTitle = templateDetail.getTemplateTitle();
log.info(" + templateTitle :: [{}]",templateTitle);
kakaoVO.setTxtReplYn(replYnChecker(templateContent+" "+templateTitle));
// log.info(" + templateDetail :: [{}]", templateDetail);
// templateDetail.getButtonList().forEach(t->log.info(" + ButtonList :: [{}]", t.toString()));
Boolean hasContentReplacement = this.replBooleanStrChecker(templateContent);
Boolean hasTitleReplacement = this.replBooleanStrChecker(templateTitle);
Boolean hasButtonReplacement = this.needsButtonReplacement(templateDetail.getButtonList());
/** @jsonStr 필요유무 */
boolean hasTitleOrButtons = StringUtils.isNotEmpty(templateTitle)
|| CollectionUtils.isNotEmpty(templateDetail.getButtonList());
/** @jsonStr 반복유무 */
boolean needsJsonReplacement = hasTitleReplacement || hasButtonReplacement;
String sharedJsonStr = null;
String subMsgTxt = kakaoVO.getSubMsgTxt(); // 실패 대체 문자
@ -113,19 +117,31 @@ public class KakaoSendUtil {
MberManageVO mberManageVO = mjonMsgDataService.selectMberManageInfo(kakaoVO.getUserId());
int counter = 0; // 분할 건수 카운터
// Map에 갯수가 수신자 갯수와 동일함
for(Map<String, String> variables : kakaoVO.getVarListMap()) {
/** @MSGID KEY값 */
log.info("idList ===================================================== ");
List<String> idList = mjonCommon.getNextCustomMsgCId(kakaoVO.getVarListMap().size());
log.info("+ idList:: [{}]", idList.toArray().toString());
// for (int i = 0; i < kakaoSendAdvcListVO.size(); i++) {
// kakaoSendAdvcListVO.get(i).setMsgId(idList.get(i));
// kakaoSendAdvcListVO.get(i).setBizJsonName(idList.get(i));
// }
// 분할 건수 카운터
int counter = 0;
/** @Map에 총 갯수가 수신자 갯수와 동일함 */
List<Map<String, String>> varList = kakaoVO.getVarListMap();
for (int i = 0; i < varList.size(); i++) {
// for(Map<String, String> variables : kakaoVO.getVarListMap()) {
// 치환 데이터
Map<String, String> variables = varList.get(i);
KakaoSendAdvcVO sendVO = new KakaoSendAdvcVO();
// 공통 기본값
sendVO.setMsgType("8");
sendVO.setSenderKey(kakaoVO.getSenderKey());
sendVO.setTemplateCode(kakaoVO.getTemplateCode());
sendVO.setUserId(kakaoVO.getUserId()); // 수신자
sendVO.setCallFrom(kakaoVO.getCallFrom()); // 발신자
sendVO.setAgentCode("04"); // 발신자
/** @공통 기본값 */
KakaoSendAdvcVO sendVO = createSendVO(kakaoVO);
// step1
// Step 1-1: 치환 수신번호 셋팅
@ -135,14 +151,20 @@ public class KakaoSendUtil {
variables.remove("callToList"); // 사용 제거.
}
// Step 1-3: 템플릿 치환데이터 설정
// TxtReplYn이 "Y" 때만 치환 수행
if ("Y".equals(kakaoVO.getTxtReplYn())) {
/** @Step1-3: 템플릿 치환데이터 설정 */
if (hasContentReplacement) {
templateContent = mjonCommon.ATReplaceTemplateVariables(templateContent, variables);
if("TEXT".equals(templateDetail.getTemplateEmphasizeType())) {
if(hasTitleReplacement) {
templateTitle = mjonCommon.ATReplaceTemplateVariables(templateTitle, variables);
}
}
/** @버튼 치환 */ // 버튼 리스트가 있으면 치환 수행, 항상 sendVO에 설정
List<KakaoButtonVO> buttonList = templateDetail.getButtonList();
if(hasButtonReplacement) {
buttonList = replaceButtonLinks(buttonList, variables);
}
sendVO.setButtonList(buttonList);
sendVO.setTemplateTitle(templateTitle);
sendVO.setTemplateContent(templateContent);
@ -154,6 +176,8 @@ public class KakaoSendUtil {
}
sendVO.setSubMsgTxt(subMsgTxt);// 실패
}
/*
log.info("kakaoSendAdvcVO Details: [callTo={}\n, templateContent=\n{}\n, subMsgTxt=\n{}]\n\n\n\n",
kakaoSendAdvcVO.getCallTo(),
@ -174,9 +198,13 @@ public class KakaoSendUtil {
float shortPrice = getValidPrice(mberManageVO.getShortPrice(), sysJoinSetVO.getShortPrice());
float longPrice = getValidPrice(mberManageVO.getLongPrice(), sysJoinSetVO.getLongPrice());
String shortPStr = Float.toString(shortPrice);
String mmsPStr = Float.toString(longPrice);
// 공통 가격 설정
sendVO.setSmsPrice(shortPrice);
sendVO.setMmsPrice(longPrice);
sendVO.setSmsPrice(shortPStr);
sendVO.setMmsPrice(mmsPStr);
if("Y".equals(kakaoVO.getSubMsgSendYn())) {
@ -189,44 +217,48 @@ public class KakaoSendUtil {
}
boolean isMms = "MMS".equals(sendType);
// kakaoAtPrice = isMms ? longPrice : shortPrice;
// sendVO.setKakaoAtPrice(getValidPrice(mberManageVO.getKakaoAtPrice(), sysJoinSetVO.getKakaoAtPrice()));
sendVO.setKakaoAtPrice(isMms ? longPrice : shortPrice);
sendVO.setEachPrice(isMms ? mmsPStr : shortPStr);
} else {
kakaoAtPrice = getValidPrice(mberManageVO.getKakaoAtPrice(), sysJoinSetVO.getKakaoAtPrice());
sendVO.setKakaoAtPrice(kakaoAtPrice);
sendVO.setEachPrice( Float.toString(kakaoAtPrice) );
}
// step4
// 예약 시간 설정 분할 데이터 설정
if ("Y".equalsIgnoreCase(kakaoVO.getReserveYn())) {
// 분할 발송일 경우
if ("Y".equalsIgnoreCase(kakaoVO.getDivideChk())) {
if (counter == Integer.parseInt(kakaoVO.getDivideCnt())) { // 지정된 건수마다 간격 추가
counter = 0;
calendar.add(Calendar.MINUTE, Integer.parseInt(kakaoVO.getDivideTime()));
}
counter++;
}
// 예약 시간 설정
if ("Y".equalsIgnoreCase(kakaoVO.getReserveYn())
&& "Y".equalsIgnoreCase(kakaoVO.getDivideChk())
&& counter == Integer.parseInt(kakaoVO.getDivideCnt()))
{
counter = 0;
calendar.add(Calendar.MINUTE, Integer.parseInt(kakaoVO.getDivideTime()));
}
counter++;
// 즉시 발송인경우 현재 시간
// 예약인 경우 위에 설정한 시간 입력
sendVO.setReqDate(sdf.format(calendar.getTime()));
sendVO.setReqDate(DATE_FORMATTER.format(calendar.getTime()));
// step5
// 전송 메세지 설정 json파일 만들기
if("Y".equals(kakaoVO.getBizJsonYn())) {
String jsonStr = kakaoApiJsonSave.kakaoApiJsonSave_advc(sendVO, templateDetail);
sendVO.setJsonStr(jsonStr);
/** @step5 전송 메세지 설정 json파일 만들기*/
// 타이틀과 버튼이 있고
String msgId = idList.get(i);
sendVO.setMsgId(msgId);
if(hasTitleOrButtons) {
// 버튼과 타이틀에 치환데이터가 있으면 json String을 계속 생성
if(needsJsonReplacement) {
sharedJsonStr = kakaoApiJsonSave.kakaoApiJsonSave_advc(sendVO, templateDetail);
sendVO.setBizJsonName(msgId);
} else if (StringUtils.isEmpty(sharedJsonStr)) {
// 치환 데이터가 없고 아직 생성되지 않았으면 번만 생성
sharedJsonStr = kakaoApiJsonSave.kakaoApiJsonSave_advc(sendVO, templateDetail);
sendVO.setBizJsonName(idList.get(0));
}
sendVO.setJsonStr(sharedJsonStr);
}
// log.info(" + sendVO :: [{}]", sendVO.toString());
kakaoSendAdvcListVO.add(sendVO);
}
@ -234,15 +266,108 @@ public class KakaoSendUtil {
return kakaoSendAdvcListVO;
}
private String replYnChecker(String input) {
// #{...} 패턴을 확인하는 정규 표현식
String regex = "#\\{[^}]+\\}";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);
private Calendar setupBaseDate(KakaoVO kakaoVO, boolean isHolidayNotified) throws ParseException {
// 예약 시간 기본값 설정
Date now = new Date();
// ReqDate가 비어 있으면 현재 시간으로 설정, 그렇지 않으면 ReqDate로 설정
// 화면에서 예약문자면 예약시간을 regDate로 설정한다.
Date baseDate;
if (StringUtils.isEmpty(kakaoVO.getReqDate())) {
kakaoVO.setReqDate(DATE_FORMATTER.format(now)); // ReqDate에 현재 시간 설정
baseDate = now;
} else {
baseDate = DATE_FORMATTER.parse(kakaoVO.getReqDate()); // ReqDate를 baseDate로 설정
}
// 패턴이 존재하면 "Y", 없으면 "N" 반환
return matcher.find() ? "Y" : "N";
// 시간 성정
Calendar calendar = Calendar.getInstance();
calendar.setTime(baseDate); // calendar에 baseDate 설정
// 지연 여부 처리
// 알림톡 스미싱의심 + 공휴일알림 조건이 맞으면 30분 delay
if ( "Y".equalsIgnoreCase(kakaoVO.getAtSmishingYn())
&& isHolidayNotified) {
calendar.add(Calendar.MINUTE, 30); // 모든 시간을 30분 뒤로 미룸
}
return calendar;
}
/**
* @methodName : createSendVO
* @author : 이호영
* @date : 2025. 3. 19.
* @description : populateSendLists 반복에 필요한 공통생성 부분
* @return : KakaoSendAdvcVO
* @param kakaoVO
* @return
*
*/
private KakaoSendAdvcVO createSendVO(KakaoVO kakaoVO) {
KakaoSendAdvcVO sendVO = new KakaoSendAdvcVO();
sendVO.setMsgType("8");
sendVO.setSenderKey(kakaoVO.getSenderKey());
sendVO.setTemplateCode(kakaoVO.getTemplateCode());
sendVO.setUserId(kakaoVO.getUserId());
sendVO.setCallFrom(kakaoVO.getCallFrom());
sendVO.setAgentCode("04");
return sendVO;
}
private List<KakaoButtonVO> replaceButtonLinks(List<KakaoButtonVO> buttonList,
Map<String, String> variables) {
if (buttonList != null) {
for (KakaoButtonVO button : buttonList) {
// 링크 필드에 대해 치환 수행
if (button.getLinkAnd() != null) {
button.setLinkAnd(mjonCommon.ATReplaceTemplateVariables(button.getLinkAnd(), variables));
}
if (button.getLinkIos() != null) {
button.setLinkIos(mjonCommon.ATReplaceTemplateVariables(button.getLinkIos(), variables));
}
if (button.getLinkMo() != null) {
button.setLinkMo(mjonCommon.ATReplaceTemplateVariables(button.getLinkMo(), variables));
}
if (button.getLinkPc() != null) {
button.setLinkPc(mjonCommon.ATReplaceTemplateVariables(button.getLinkPc(), variables));
}
}
// 치환된 버튼 리스트를 sendVO에 반영
// sendVO.setButtonList(buttonList); // KakaoSendAdvcVO에 setButtonList가 있다고 가정
}
return buttonList;
}
/**
* 버튼 리스트에 치환 패턴(#{...}) 있는지 확인합니다.
* @param buttonList 버튼 리스트 (null 가능)
* @return 치환 패턴이 있으면 true, 없으면 false
*/
private boolean needsButtonReplacement(List<KakaoButtonVO> buttonList) {
if (buttonList == null) {
return false;
}
return buttonList.stream().anyMatch(button ->
replBooleanStrChecker(button.getLinkAnd()) ||
replBooleanStrChecker(button.getLinkIos()) ||
replBooleanStrChecker(button.getLinkMo()) ||
replBooleanStrChecker(button.getLinkPc())
);
}
/**
* 입력 문자열에 치환 패턴(#{...}) 있는지 확인합니다.
* @param input 확인할 문자열 (null 가능)
* @return 치환 패턴이 있으면 true, 없으면 false
*/
private boolean replBooleanStrChecker(String input) {
// #{...} 패턴을 확인하는 정규 표현식
if (input == null) {
return false;
}
Matcher matcher = REPLACEMENT_PATTERN.matcher(input);
return matcher.find();
}

View File

@ -38,7 +38,7 @@ public class KakaoApiJsonSave {
// 버튼리스트 JSON 생성
JSONArray buttonList = new JSONArray();
for(KakaoButtonVO buttonInfoVO : templateDetail.getButtonList()) {
for(KakaoButtonVO buttonInfoVO : sendVO.getButtonList()) {
JSONObject buttonInfo = new JSONObject();
buttonInfo.put("name", buttonInfoVO.getName());

View File

@ -6,6 +6,7 @@ import java.util.List;
import org.springframework.stereotype.Repository;
import egovframework.rte.psl.dataaccess.EgovAbstractDAO;
import itn.let.kakao.kakaoComm.KakaoSendAdvcVO;
import itn.let.kakao.kakaoComm.KakaoVO;
@Repository("kakaoAlimTalkDAO")
@ -43,15 +44,6 @@ public class KakaoAlimTalkDAO extends EgovAbstractDAO {
return result;
}
public int insertKakaoAtDataInfo(List<KakaoVO> kakaoAtSandList) throws Exception{
int result = 0;
result = update("kakaoAlimTalkDAO.insertKakaoAtDataInfo", kakaoAtSandList);
return result;
}
public void insertKakaoSendPrice(KakaoVO kakaoVO) throws Exception{
insert("kakaoAlimTalkDAO.insertKakaoSendPrice",kakaoVO);
}
@ -94,4 +86,22 @@ public class KakaoAlimTalkDAO extends EgovAbstractDAO {
public void updateKakaoFtNotSend(KakaoVO kakaoVO) {
select("kakaoAlimTalkDAO.updateKakaoFtNotSend", kakaoVO);
}
public int insertKakaoAtDataInfo(List<KakaoVO> kakaoAtSandList) throws Exception{
int result = 0;
result = update("kakaoAlimTalkDAO.insertKakaoAtDataInfo", kakaoAtSandList);
return result;
}
public int insertKakaoAtDataInfo_advc(List<KakaoSendAdvcVO> kakaoSendAdvcVOList) {
return update("kakaoAlimTalkDAO.insertKakaoAtDataInfo_advc", kakaoSendAdvcVOList);
}
public void insertKakaoAtDataJsonInfo_advc(List<KakaoSendAdvcVO> kakaoSendAdvcVOList) {
insert("kakaoAlimTalkDAO.insertKakaoAtDataJsonInfo_advc", kakaoSendAdvcVOList);
}
}

View File

@ -11,10 +11,14 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
@ -23,6 +27,7 @@ import egovframework.rte.fdl.cmmn.EgovAbstractServiceImpl;
import egovframework.rte.fdl.idgnr.EgovIdGnrService;
import egovframework.rte.fdl.security.userdetails.util.EgovUserDetailsHelper;
import itn.com.cmm.LoginVO;
import itn.com.cmm.MjonMsgSendVO;
import itn.com.utl.fcc.service.EgovStringUtil;
import itn.let.kakao.kakaoComm.KakaoSendAdvcVO;
import itn.let.kakao.kakaoComm.KakaoSendUtil;
@ -44,6 +49,7 @@ import itn.let.module.base.PriceAndPoint;
import itn.let.sym.site.service.JoinSettingVO;
import itn.let.sym.site.service.impl.SiteManagerDAO;
import itn.let.uss.umt.service.EgovUserManageService;
import itn.let.uss.umt.service.UserManageVO;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ -67,6 +73,9 @@ public class KakaoAlimTalkServiceImpl extends EgovAbstractServiceImpl implements
@Resource(name = "egovMjonMsgIdGnrService")
private EgovIdGnrService idgenMsgId;
@Resource(name = "egovMjonMsgGroupIdGnrService")
private EgovIdGnrService idgenMjonMsgGroupId;
@Resource(name = "mjonPayService")
private MjonPayService mjonPayService;
@ -77,7 +86,7 @@ public class KakaoAlimTalkServiceImpl extends EgovAbstractServiceImpl implements
/** userManageService */
@Resource(name = "userManageService")
private EgovUserManageService userManageService;
@Autowired
KakaoSendUtil kakaoSendUtil;
@ -855,6 +864,9 @@ public class KakaoAlimTalkServiceImpl extends EgovAbstractServiceImpl implements
log.info(" :: [{}]", kakaoVO.toString());
// 측정할 메소드 호출 시간 기록
Instant start = Instant.now();
// KakaoSendAdvcVO
Map<String, Object> returnMap = new HashMap<>();
@ -884,59 +896,85 @@ public class KakaoAlimTalkServiceImpl extends EgovAbstractServiceImpl implements
StatusResponse statusResponse = new StatusResponse();
/** @isHolidayNotified
* @false : 알림 X
* @true : 알림 O */
boolean isHolidayNotified = mjonCommon.handleSmishingAlert();
/** @LETTNGNRLMBER 사용자 정보 -> 스미싱의심 여부 */
UserManageVO userManageVO = mjonCommon.getUserManageInfo(userId);
kakaoVO.setAtSmishingYn(userManageVO.getAtSmishingYn());
/** 카카오톡 전송 설정 -------------------------------------------*/
List<KakaoSendAdvcVO> kakaoSendAdvcListVO = kakaoSendUtil.populateSendLists(kakaoVO, statusResponse);
/** @카카오톡 전송 list 셋팅 -------------------------------------------*/
List<KakaoSendAdvcVO> kakaoSendAdvcListVO = kakaoSendUtil.populateSendLists(kakaoVO, isHolidayNotified, statusResponse);
if (statusResponse.getStatus() != null && !statusResponse.getStatus().equals(HttpStatus.OK)) {
log.error(" + populateSendLists 처리 중 오류 발생: {}", statusResponse.getMessage());
return statusResponse;
}
// 측정할 메소드 호출 시간 기록
Instant start = Instant.now();
/** 전송금액 확인 --------------------------------------------------*/
/** @전송금액 확인 --------------------------------------------------*/
if (!isCashSufficient(userId, kakaoSendAdvcListVO)) {
log.error("Insufficient balance for message sending.");
return new StatusResponse(HttpStatus.BAD_REQUEST, "문자 발송에 필요한 보유 잔액이 부족 합니다.");
}
// 측정할 메소드 호출 시간 기록
Instant end = Instant.now();
log.info(" + start :: [{}]", start);
// 실행 시간 계산 (나노초, 밀리초, )
System.out.println("메소드 실행 시간 (초): " + Duration.between(start, end).getSeconds() + " s");
System.out.println("메소드 실행 시간 (Nano): " + Duration.between(start, end).getNano() + " Nano");
start = Instant.now();
List<String> idList = mjonCommon.getNextCustomMsgCId(kakaoSendAdvcListVO.size());
for (int i = 0; i < kakaoSendAdvcListVO.size(); i++) {
kakaoSendAdvcListVO.get(i).setMsgId(idList.get(i));
Map<String, List<KakaoSendAdvcVO>> priceGroupedMessages = kakaoSendAdvcListVO.stream()
.collect(Collectors.groupingBy(KakaoSendAdvcVO::getEachPrice));
// json Str이 있는지 확인
Boolean isJsonNotEmpty = StringUtils.isNotEmpty(kakaoSendAdvcListVO.get(0).getJsonStr());
// json Str 값이 list에서 다른지 확인
Boolean isJsonNameAllSame = false;
if(isJsonNotEmpty) {
String firstBizJsonName = kakaoSendAdvcListVO.get(0).getBizJsonName();
isJsonNameAllSame = kakaoSendAdvcListVO.stream()
.allMatch(t -> Objects.equals(t.getBizJsonName(), firstBizJsonName));
}
// instTotalCnt : 화면에서 보여줄 발송건수
int instTotalCnt = 0;
List<Map.Entry<String, List<KakaoSendAdvcVO>>> entryList = new ArrayList<>(priceGroupedMessages.entrySet());
// 기본 for문으로 반복 카운트를 사용하기 위해 foreach -> for 수정
for (int i = 0; i < entryList.size(); i++) {
Map.Entry<String, List<KakaoSendAdvcVO>> entry = entryList.get(i);
List<KakaoSendAdvcVO> groupedMsgList = entry.getValue(); // 해당 가격의 메시지 리스트
String nextMsgGroupId = idgenMjonMsgGroupId.getNextStringId();
groupedMsgList.forEach(t -> t.setMsgGroupId(nextMsgGroupId));
// 발송 데이터 삽입
// int instCnt = this.insertKakaoData_advc(groupedMsgList, i, isJsonNotEmpty, isJsonNameAllSame);
int instCnt = 6;
if(instCnt > 0) {
instTotalCnt += instCnt;
// this.insertMsgGroupDataTb_advc(instCnt, mjonMsgVO, groupedMsgList);
this.insertKakaoGroupDataTb_advc(instCnt, kakaoVO, groupedMsgList);
}
}
// 측정할 메소드 호출 시간 기록
end = Instant.now();
Instant end = Instant.now();
log.info(" + start :: [{}]", start);
// 실행 시간 계산 (나노초, 밀리초, )
System.out.println("메소드 실행 시간 (초): " + Duration.between(start, end).getSeconds() + " s");
System.out.println("메소드 실행 시간 (Nano): " + Duration.between(start, end).getNano() + " Nano");
kakaoSendAdvcListVO.forEach(t-> log.info(" + t.toString() :: [{}]", t.toString()) );
kakaoSendAdvcListVO.forEach(t-> log.info(" + t.toString() :: [{}]", t.toString()) );
// idgenMjonMsgGroupId.getNextStringId();
@ -956,6 +994,133 @@ public class KakaoAlimTalkServiceImpl extends EgovAbstractServiceImpl implements
private void insertKakaoGroupDataTb_advc(int instCnt, KakaoVO kakaoVO, List<KakaoSendAdvcVO> kakaoSendAdvcVOList) {
// TODO Auto-generated method stub
log.info(" + insertKakaoGroupDataTb_advc kakaoVO :: \n[{}]", kakaoVO.toString());;
log.info(" + insertKakaoGroupDataTb_advc kakaoSendAdvcVOList :: \n[{}]", kakaoSendAdvcVOList.get(0).toString());
/*
#msgGroupId#,
#userId#,
#callFrom#,
#smsTxt#, mjonMsgVO.setSmsTxt(kakaoVO.getTemplateContent());
// #mmsSubject#,
#reqDate#,
#msgGroupCnt#,
// #conectMthd#,
#msgType#,
// #msgKind#,
#agentCode#,
#eachPrice#,
#reserveYn#,
#befCash#,
#befPoint#,
// #recommId#,
// #fileCnt#,
#totPrice#,
// #eventYn#,
// #delayYn#,
#atDelayYn#,
#kakaoSubMagOrgnlTxt#
*/
}
/**
* @methodName : insertKakaoData_advc
* @author : 이호영
* @date : 2025. 3. 20.
* @description : 카카오 batch 발송 => mj_msg_data
* @return : int
* @param kakaoSendAdvcVOList
* @param parentLoopCount
* @param isJsonNotEmpty
* @param isJsonNameAllSame
* @return
*
*/
private int insertKakaoData_advc(List<KakaoSendAdvcVO> kakaoSendAdvcVOList, int parentLoopCount, Boolean isJsonNotEmpty, Boolean isJsonNameAllSame) {
// 시작 시간 측정
long totalStartTime = System.currentTimeMillis();
int totalSize = kakaoSendAdvcVOList.size(); // 데이터 개수
// Batch 크기 설정 (고정값)
// int batchSize = 10000; 465
int batchSize = 50000; // 9분 18초
log.info("총 데이터 개수 :: [{}] ", totalSize);
log.info("설정된 Batch 크기 :: [{}] ", batchSize);
// insert 카운트
int instCnt = 0;
int batchCount = 0;
// 배치별 실행 시간 기록
List<Double> batchExecutionTimes = new ArrayList<>();
// 번째 배치에서만 삽입했는지 추적하는 플래그
boolean isJsonInserted = false;
for (int i = 0; i < totalSize; i += batchSize) {
// Batch 시작 시간 측정
long batchStartTime = System.currentTimeMillis();
// Batch 리스트 생성
List<KakaoSendAdvcVO> batchList = kakaoSendAdvcVOList.subList(i, Math.min(i + batchSize, totalSize));
System.out.println("Batch 시작 인덱스: " + i);
// mj_msg_data 테이블 insert
int insertedCount = kakaoAlimTalkDAO.insertKakaoAtDataInfo_advc(batchList);
if (isJsonNotEmpty) {
if (!isJsonNameAllSame) {
// BizJsonName이 동일하지 않으면 매번 삽입
kakaoAlimTalkDAO.insertKakaoAtDataJsonInfo_advc(batchList);
} else if (parentLoopCount==0) {
// BizJsonName이 동일하면 번째 배치에서만 삽입
kakaoAlimTalkDAO.insertKakaoAtDataJsonInfo_advc(batchList);
isJsonInserted = true; // 삽입 완료 플래그 설정
}
}
instCnt += insertedCount;
// Batch 종료 시간 측정 실행 시간 계산
long batchEndTime = System.currentTimeMillis();
double batchExecutionTimeInSeconds = (batchEndTime - batchStartTime) / 1000.0;
// 실행 시간 기록
batchExecutionTimes.add(batchExecutionTimeInSeconds);
batchCount++;
}
// 종료 시간 측정
long totalEndTime = System.currentTimeMillis();
// 실행 시간 계산 (밀리초 -> 초로 변환)
double totalExecutionTimeInSeconds = (totalEndTime - totalStartTime) / 1000.0;
// 실행 시간 출력
log.info("총 배치 실행 횟수 :: [{}] ", batchCount);
log.info("batchSize :: [{}] ", batchSize);
log.info("총 실행 시간 :: [{}] ", totalExecutionTimeInSeconds + "");
log.info("총 삽입 건수 :: [{}] ", instCnt);
// 배치별 실행 시간 출력
for (int k = 0; k < batchExecutionTimes.size(); k++) {
System.out.println("배치 " + (k + 1) + " 실행 시간 :: " + batchExecutionTimes.get(k) + "");
}
return instCnt;
}
// 보유 금액이 충분한지 확인하는 메서드
private boolean isCashSufficient(String userId, List<KakaoSendAdvcVO> kakaoSendAdvcListVO) throws Exception {
@ -969,7 +1134,7 @@ public class KakaoAlimTalkServiceImpl extends EgovAbstractServiceImpl implements
// 메시지 금액 계산 (HALF_EVEN 적용)
BigDecimal totalEachPrice = kakaoSendAdvcListVO.stream()
.map(msg -> new BigDecimal(String.valueOf(msg.getKakaoAtPrice()))) // 변환 오류 방지
.map(msg -> new BigDecimal(String.valueOf(msg.getEachPrice()))) // 변환 오류 방지
.reduce(BigDecimal.ZERO, BigDecimal::add)
.setScale(2, RoundingMode.HALF_EVEN); // 일관성 유지

View File

@ -563,7 +563,7 @@ private int parseIntOrDefault(String value, int defaultValue) {
mjonMsgVO.setSmishingYn(smishingYn); // MjonMsgVO에 스미싱 정보 설정
// 스미싱 알림 처리
return handleSmishingAlert(mjonMsgVO); // 알림 처리 결과 반환
return handleSmishingAlert(); // 알림 처리 결과 반환
}
return false; // 알림 처리되지 않음
@ -579,18 +579,28 @@ private int parseIntOrDefault(String value, int defaultValue) {
}
// 스미싱 알림 처리
public boolean handleSmishingAlert(MjonMsgVO mjonMsgVO) throws Exception {
public boolean handleSmishingAlert() throws Exception {
/**
* MJ_MBER_SETTING => 기본 시스템 알림 여부
* 슬랙 Y
* 야간스미싱알림 Y
* 등등
*
*/
JoinSettingVO joinSettingVO = egovSiteManagerService.selectAdminNotiDetail();
/** @시스템 설정에 야간스미싱 알림 || 슬랙알림이 N이면 false*/
if (joinSettingVO == null || !"Y".equals(joinSettingVO.getHoliSmishingNoti()) ||
!"Y".equals(joinSettingVO.getSlackNoti())) {
return false; // 알림 조건 미충족
}
// 알림 조건 충족 추가 작업
/** @MJ_SPAMPASS_ALARM : 현재 활성화된 알림 SELECT */
List<MsgAlarmSetVO> alarmList = getAlarmSettings();
/** @MJ_HOLIDAY 시스템에 등록된 공휴일 설정 */
List<MsgHolidayVO> holidayList = getHolidayList();
boolean isNotificationAllowed = new MjonHolidayApi().getHolidaySmishingPassStatus(alarmList, holidayList);
/** @MJ_HOLIDAY 시스템에 등록된 공휴일 설정 */
boolean isNotificationAllowed = new MjonHolidayApi().getHolidaySmishingPassStatus_advc(alarmList, holidayList);
return !isNotificationAllowed; // 알림 발송 조건 미충족
}

View File

@ -231,4 +231,71 @@ public class MjonHolidayApi {
return smishingAlarmPassSts;
}
/**
* @methodName : getHolidaySmishingPassStatus_advc
* @author : 이호영
* @date : 2025. 3. 19.
* @description : getHolidaySmishingPassStatus 개선 버전
* @return : boolean
* @param resultAlarmList
* @param resultHolidayList
* @return
* @throws Exception
*
*/
public boolean getHolidaySmishingPassStatus_advc(List<MsgAlarmSetVO> alarmList, List<MsgHolidayVO> holidayList) throws Exception{
Date now = new Date(); // 현재 시스템 시간
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); // 날짜-시간 포맷 (: 2025-03-18 14:30)
// 현재 날짜와 요일 계산
Calendar cal = Calendar.getInstance();
cal.setTime(now);
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); // 1(일요일) ~ 7(토요일)
// 오늘 날짜를 "yyyy-MM-dd" 형식으로 포맷팅 (mj_holiday.HOLIDAY_DATE와 비교용)
String today = String.format("%d-%02d-%02d",
cal.get(Calendar.YEAR),
cal.get(Calendar.MONTH) + 1, // Calendar.MONTH는 0부터 시작하므로 +1
cal.get(Calendar.DATE));
// 공휴일 여부 확인
// mj_holiday 테이블의 HOLIDAY_DATE와 오늘 날짜가 일치하는지 체크
// HOLIDAY_DATE는 'yyyy-MM-dd' 형식으로 저장됨 (: '2025-01-01')
boolean isHoliday = holidayList.stream()
.anyMatch(holiday -> today.equals(holiday.getHolidayDate()));
// 알람 설정 순회
// alarmList는 MsgAlarmSetVO 객체의 리스트로, 알람 타입과 시작/종료 시간을 포함
for (MsgAlarmSetVO alarm : alarmList) {
String alarmType = alarm.getAlarmType(); // 알람 유형: 'W'(평일), 'E'(주말), 'H'(공휴일)
// 오늘 날짜에 알람 시작/종료 시간을 붙여 Date 객체로 변환
Date start = sdf.parse(today + " " + alarm.getAlarmStart()); // : "2025-03-18 09:00"
Date end = sdf.parse(today + " " + alarm.getAlarmEnd()); // : "2025-03-18 18:00"
// 현재 시간이 알람 시작~종료 시간 범위 내에 있는지 확인
boolean isWithinTime = now.after(start) && now.before(end);
if (!isWithinTime) continue; // 시간 범위 밖이면 다음 알람으로
// 평일 체크 (~: dayOfWeek 2~6)
// alarmType 'W' 평일에만 적용
if (dayOfWeek > 1 && dayOfWeek < 7 && alarmType.equals("W")) {
return true; // 평일이고, 시간이 맞고, 타입이 'W' 스미싱 알람 통과
}
// 주말 체크 (:1, :7)
// alarmType 'E' 주말에만 적용
else if ((dayOfWeek == 1 || dayOfWeek == 7) && alarmType.equals("E")) {
return true; // 주말이고, 시간이 맞고, 타입이 'E' 스미싱 알람 통과
}
// 공휴일 체크
// alarmType 'H' mj_holiday에 등록된 공휴일에 적용
// HOLIDAY_TYPE(1:법정, 2:임시, 3:기타) 관계없이 날짜만 확인
else if (isHoliday && alarmType.equals("H")) {
return true; // 공휴일이고, 시간이 맞고, 타입이 'H' 스미싱 알람 통과
}
}
// 모든 조건에 부합하지 않으면 false 반환 (스미싱 알람 비활성화)
return false;
}
}

View File

@ -4061,7 +4061,7 @@ public class MjonMsgDataServiceImpl extends EgovAbstractServiceImpl implements M
System.out.println("================================");
// 스팸 스미싱 의심이면 slack 알림
boolean isHolidayNotified = mjonCommon.handleSmishingAlert(mjonMsgVO);
boolean isHolidayNotified = mjonCommon.handleSmishingAlert();
// 스팸관련 키워드 select
@ -4138,7 +4138,6 @@ public class MjonMsgDataServiceImpl extends EgovAbstractServiceImpl implements M
int instTotalCnt = 0;
// Step 2: 그룹화 데이터를 그룹별로 insert 처리
for (Map.Entry<String, List<MjonMsgSendVO>> entry : priceGroupedMessages.entrySet()) {
String price = entry.getKey(); // 가격 (String)
List<MjonMsgSendVO> groupedMsgList = entry.getValue(); // 해당 가격의 메시지 리스트
// msgGroupId 생성
@ -4173,7 +4172,7 @@ public class MjonMsgDataServiceImpl extends EgovAbstractServiceImpl implements M
// 수신거부 목록 업데이트
// returnMap.put("resultSts", instCnt);
log.debug("가격 [{}]의 총 갯수: [{}]", price, groupedMsgList.size());
log.debug("가격 [{}]의 총 갯수: [{}]", entry.getKey(), groupedMsgList.size());
}

View File

@ -4,9 +4,10 @@
========= ======= =================================================
2023.02.02 우영두
-->
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="kakaoAlimTalk">
<typeAlias alias="kakaoVO" type="itn.let.kakao.kakaoComm.KakaoVO"/>
<typeAlias alias="kakaoSendAdvcVO" type="itn.let.kakao.kakaoComm.KakaoSendAdvcVO"/>
<insert id="kakaoAlimTalkDAO.insertKakaoAtDataInfo" parameterClass="java.util.List">
INSERT INTO MJ_MSG_DATA
@ -54,6 +55,71 @@
</iterate>
</insert>
<insert id="kakaoAlimTalkDAO.insertKakaoAtDataInfo_advc" parameterClass="java.util.List">
INSERT INTO MJ_MSG_DATA
(
MSG_ID
, MSG_GROUP_ID
, USER_ID
, AGENT_CODE
, CUR_STATE
, MSG_NOTICETALK_SENDER_KEY
, MSG_NOTICETALK_TMP_KEY
, CALL_TO
, CALL_FROM
, MSG_TYPE
, SMS_TXT
, BIZ_KAKAO_TITLE
, BIZ_KAKAO_RESEND_YN
, BIZ_KAKAO_RESEND_DATA
, BIZ_KAKAO_RESEND_TYPE
, BIZ_KAKAO_JSON_FILE
, REQ_DATE
)VALUES
<iterate conjunction=",">
(
#[].msgId#
, #[].msgGroupId#
, #[].userId#
, #[].agentCode#
, 0
, #[].senderKey#
, #[].templateCode#
, #[].callTo#
, #[].callFrom#
, #[].msgType#
, #[].templateContent#
, #[].templateTitle#
, #[].subMsgSendYn#
, #[].subMsgTxt#
, #[].subMsgType#
, #[].bizJsonName#
, #[].reqDate#
)
</iterate>
</insert>
<insert id="kakaoAlimTalkDAO.insertKakaoAtDataJsonInfo_advc" parameterClass="java.util.List">
INSERT INTO BIZ_ATTACHMENTS
(
MSG_KEY
, TYPE
, CONTENTS
)VALUES
<iterate conjunction=",">
(
#[].msgId#
, 'JSON'
, #[].jsonStr#
)
</iterate>
</insert>
<insert id="kakaoAlimTalkDAO.insertKakaoSendPrice" parameterClass="kakaoVO">
INSERT INTO BIZ_KAKAO_PRICE
(

View File

@ -1315,6 +1315,7 @@
ADMIN_SMS_NOTICE_YN AS adminSmsNoticeYn
,PRE_PAYMENT_YN AS prePaymentYn
,SMISHING_YN AS smishingYn
,AT_SMISHING_YN AS atSmishingYn
,AUTO_CASH AS autoCash
,IFNULL(BLINE_CODE, 'N') AS blineCode
,IFNULL(RECOMMEND_ID, '') AS recommendId