package itn.let.kakao.kakaoComm; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.regex.Matcher; 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.MjonFTSendVO; import itn.com.cmm.MjonMsgSendVO; import itn.com.cmm.util.MsgSendUtils; import itn.com.cmm.util.StringUtil; import itn.let.kakao.kakaoComm.kakaoApi.KakaoApiJsonSave; import itn.let.kakao.kakaoComm.kakaoApi.KakaoApiTemplate; import itn.let.kakao.user.kakaoAt.service.impl.KakaoAlimTalkDAO; import itn.let.mail.service.StatusResponse; import itn.let.mjo.mjocommon.MjonCommon; import itn.let.mjo.msg.service.MjonMsgVO; import itn.let.mjo.msg.service.impl.MjonMsgDAO; import itn.let.mjo.msgdata.service.MjonMsgDataService; import itn.let.mjo.spammsg.web.ComGetSpamStringParser; import itn.let.module.base.PriceAndPoint; import itn.let.sym.site.service.JoinSettingVO; import itn.let.uss.umt.service.MberManageVO; import itn.let.uss.umt.service.UserManageVO; import lombok.extern.slf4j.Slf4j; @Slf4j @Component public class KakaoSendUtil { @Autowired KakaoApiJsonSave kakaoApiJsonSave; @Resource(name="kakaoAlimTalkDAO") private KakaoAlimTalkDAO kakaoAlimTalkDAO; @Resource(name = "MjonMsgDataService") private MjonMsgDataService mjonMsgDataService; @Resource(name = "mjonMsgDAO") private MjonMsgDAO mjonMsgDAO; @Autowired KakaoApiTemplate kakaoApiTemplate; @Autowired private PriceAndPoint priceAndPoint; @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"; // 장문 메세지 타입 public static final String LONG_MSG_TYPE = "MMS"; /** * @methodName : populateSendLists _advc * @author : 이호영 * @date : 2025. 3. 7. * @description : 기존 kakaoSendPrice 개선 * @return : KakaoVO * @param kakaoVO * @param statusResponse * @return * @throws Exception * */ public List populateSendLists(KakaoVO kakaoVO, boolean isNotified, StatusResponse statusResponse) throws Exception { //사용자 현재 보유 금액 불러오기(문자 발송 금액 차감 이전 금액) // String befCash = kakaoVO.getBefCash(); List kakaoSendAdvcListVO = new ArrayList<>(); Calendar calendar = setupBaseDate(kakaoVO, isNotified); KakaoReturnVO templateDetail = kakaoApiTemplate.selectKakaoApiTemplateDetail(kakaoVO); String templateContent = templateDetail.getTemplateContent(); // 알림톡 템플릿 kakaoVO.setTemplateContent(templateContent); String templateTitle = templateDetail.getTemplateTitle(); // log.info(" + templateDetail :: [{}]", templateDetail); // templateDetail.getButtonList().forEach(t->log.info(" + ButtonList :: [{}]", t.toString())); // API인지 확인하는 Boolean Boolean isApiData = "A".equals(kakaoVO.getSendKind()); 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()) || "IMAGE".equalsIgnoreCase(templateDetail.getTemplateEmphasizeType()); // 템플릿 강조 유형 이미지 유형을 알기 위해 추가 /** @jsonStr 반복유무 */ boolean needsJsonReplacement = hasTitleReplacement || hasButtonReplacement; String sharedJsonStr = null; String subMsgTxt = kakaoVO.getSubMsgTxt(); // 실패 대체 문자 // 시스템 기본 단가 정보 불러오기 JoinSettingVO sysJoinSetVO = mjonMsgDataService.selectJoinSettingInfo(); // 사용자 개인 단가 정보 불러오기 MberManageVO mberManageVO = mjonMsgDataService.selectMberManageInfo(kakaoVO.getUserId()); float shortPrice = getValidPrice(mberManageVO.getShortPrice(), sysJoinSetVO.getShortPrice()); float longPrice = getValidPrice(mberManageVO.getLongPrice(), sysJoinSetVO.getLongPrice()); float kakaoAtPrice = getValidPrice(mberManageVO.getKakaoAtPrice(), sysJoinSetVO.getKakaoAtPrice()); String shortPStr = Float.toString(shortPrice); String mmsPStr = Float.toString(longPrice); String kakaoAtPStr = Float.toString(kakaoAtPrice); /** @MSGID KEY값 */ List idList = mjonCommon.getNextCustomMsgCId(kakaoVO.getVarListMap().size()); // for (int i = 0; i < kakaoSendAdvcListVO.size(); i++) { // kakaoSendAdvcListVO.get(i).setMsgId(idList.get(i)); // kakaoSendAdvcListVO.get(i).setBizJsonName(idList.get(i)); // } String templateEmphasizeType = kakaoVO.getTemplateEmphasizeType(); // 분할 건수 카운터 int counter = 0; /** @Map에 총 갯수가 수신자 갯수와 동일함 */ List> varList = kakaoVO.getVarListMap(); for (int i = 0; i < varList.size(); i++) { // for(Map variables : kakaoVO.getVarListMap()) { // 치환 데이터 Map variables = varList.get(i); log.info(""); /** @공통 기본값 */ KakaoSendAdvcVO sendVO = createATSendVO(kakaoVO); // 공통 가격 설정 sendVO.setSmsPrice(shortPStr); sendVO.setMmsPrice(mmsPStr); sendVO.setKakaoAtPrice(kakaoAtPStr); String msgId = idList.get(i); sendVO.setMsgId(msgId); // step1 // Step 1-1: 값 치환 및 수신번호 셋팅 // Step 1-2: 수신자 정보 설정 (callToList는 항상 설정). if (variables.containsKey("callToList")) { sendVO.setCallTo(variables.get("callToList")); variables.remove("callToList"); // 사용 후 제거. } /** @Step1-3: 템플릿 치환데이터 설정 */ String templateContentTemp = templateContent; String templateTitleTemp = templateTitle; // api가 아니면 if(!isApiData) { if (hasContentReplacement) { templateContentTemp = mjonCommon.ATReplaceTemplateVariables(templateContent, variables); if(hasTitleReplacement) { templateTitleTemp = mjonCommon.ATReplaceTemplateVariables(templateTitle, variables); } } }else { templateContentTemp = variables.get("templateContent"); templateTitleTemp = variables.get("templateTitle"); } /** @버튼 치환 */ // 버튼 리스트가 있으면 치환 수행, 항상 sendVO에 설정 List buttonList = templateDetail.getButtonList(); if(hasButtonReplacement) { buttonList = replaceButtonLinks(buttonList, variables); } sendVO.setButtonList(buttonList); sendVO.setTemplateTitle(templateTitleTemp); sendVO.setTemplateContent(templateContentTemp); String subMsgTxtTemp = subMsgTxt; // Step 1-4: 실패 대체 문자 치환데이터 설정 if("Y".equals(kakaoVO.getSubMsgSendYn())) { // 대체문자가 있나? // api가 아니면 if(!isApiData) { if ("Y".equals(kakaoVO.getSubMsgTxtReplYn())) { // 치환데이터가 있나? subMsgTxtTemp = mjonCommon.ATReplaceTemplateVariables(subMsgTxt, variables); } }else { subMsgTxtTemp = variables.get("subMsgTxt"); } sendVO.setSubMsgTxt(subMsgTxtTemp);// 실패 } sendVO.setSubMsgSendYn(kakaoVO.getSubMsgSendYn()); if("Y".equals(kakaoVO.getSubMsgSendYn())) { int smsTxtByte = mjonCommon.getSmsTxtBytes(sendVO.getSubMsgTxt()); String sendType = getMsgType(smsTxtByte); sendVO.setSubMsgType(sendType); if ("INVALID".equals(sendType)) { // statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "전송 문자 길이를 초과하였습니다.");return kakaoSendAdvcListVO; StatusResponse.statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "전송 문자 길이를 초과하였습니다.", "STAT_1080");return kakaoSendAdvcListVO; } boolean isMms = "MMS".equals(sendType); sendVO.setEachPrice(isMms ? mmsPStr : shortPStr); } else { sendVO.setEachPrice(kakaoAtPStr); } // step4 // 예약 시간 설정 및 분할 데이터 설정 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(DATE_FORMATTER.format(calendar.getTime())); /** @step5 전송 메세지 설정 json파일 만들기*/ // 타이틀과 버튼이 있고 if(hasTitleOrButtons) { // 버튼과 타이틀에 치환데이터가 있으면 json String을 계속 생성 if(needsJsonReplacement) { sharedJsonStr = kakaoApiJsonSave.kakaoApiJsonSave_advc(sendVO, templateDetail); sendVO.setBizJsonName(msgId); sendVO.setJsonStr(sharedJsonStr); } else if (StringUtils.isEmpty(sharedJsonStr)) { // 치환 데이터가 없고 아직 생성되지 않았으면 한 번만 생성 sharedJsonStr = kakaoApiJsonSave.kakaoApiJsonSave_advc(sendVO, templateDetail); sendVO.setBizJsonName(idList.get(0)); sendVO.setJsonStr(sharedJsonStr); }else { sendVO.setBizJsonName(idList.get(0)); } } log.info(" sendVO :: [{}]", sendVO); kakaoSendAdvcListVO.add(sendVO); } return kakaoSendAdvcListVO; } /** * @methodName : populateSendListsFT * @author : 이호영 * @date : 2025. 4. 18. * @description : * @return : List * @param kakaoVO * @param isHolidayNotified * @param statusResponse * @return * @throws Exception * */ public List populateSendListsFT(KakaoVO kakaoVO , boolean isHolidayNotified , StatusResponse statusResponse , UserManageVO userManageVO , List resultSpamTxt ) throws Exception { //사용자 현재 보유 금액 불러오기(문자 발송 금액 차감 이전 금액) // String befCash = kakaoVO.getBefCash(); log.info("kakaoVO.ftToString() :: [{}]", kakaoVO.ftToString()); // 예약 시간 기본값 설정 Date now = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); String atSmishingYn = userManageVO.getAtSmishingYn(); String exceptSpamYn = userManageVO.getExceptSpamYn(); List kakaoSendAdvcListVO = new ArrayList<>(); Calendar calendar = setupBaseDateFT(kakaoVO); // 친구톡 내용 String templateContent = kakaoVO.getTemplateContent(); // 실패 대체 문자 String subMsgTxt = kakaoVO.getSubMsgTxt(); log.info(" + StringUtils.isNotEmpty(subMsgTxt) :: [{}]", StringUtils.isNotEmpty(subMsgTxt)); if(StringUtils.isNotEmpty(subMsgTxt)) { kakaoVO.setSubMsgSendYn("Y"); // 광고문자면 처리 - 광고 Y if ("Y".equals(kakaoVO.getAdFlag())) { subMsgTxt = "(광고)" + subMsgTxt + "\n" + "무료거부 0808800858"; } } // 사용자 개인 단가 정보 불러오기 MberManageVO mberManageVO = mjonMsgDataService.selectMberManageInfo(kakaoVO.getUserId()); // 시스템 기본 단가 정보 불러오기 JoinSettingVO sysJoinSetVO = mjonMsgDataService.selectJoinSettingInfo(); // 치환 문구가 있는지 확인 Boolean replaceYN = MsgSendUtils.getReplaceYN(templateContent); Boolean replaceSubYN = MsgSendUtils.getReplaceYN(subMsgTxt); /** @MSGID KEY값 */ List idList = mjonCommon.getNextCustomMsgCId(kakaoVO.getMjonFTSendVOList().size()); Map> placeholders = new HashMap<>(); placeholders.put("[*이름*]", MjonFTSendVO::getName); placeholders.put("[*1*]", MjonFTSendVO::getRep1); placeholders.put("[*2*]", MjonFTSendVO::getRep2); placeholders.put("[*3*]", MjonFTSendVO::getRep3); placeholders.put("[*4*]", MjonFTSendVO::getRep4); String imageType = kakaoVO.getImageType(); // 개인단가 Float kakaoMemberFtPrice = imageType == null ? mberManageVO.getKakaoFtPrice() : "I".equals(imageType) ? mberManageVO.getKakaoFtImgPrice() : "W".equals(imageType) ? mberManageVO.getKakaoFtWideImgPrice() : mberManageVO.getKakaoFtPrice(); // 시스템단가 Float kakaoSysJoinFtPrice = imageType == null ? sysJoinSetVO.getKakaoFtPrice() : "I".equals(imageType) ? sysJoinSetVO.getKakaoFtImgPrice() : "W".equals(imageType) ? sysJoinSetVO.getKakaoFtWideImgPrice() : sysJoinSetVO.getKakaoFtPrice(); Float kakaoFtPrice = getValidPrice(kakaoMemberFtPrice, kakaoSysJoinFtPrice); // 대체문자가 있을경우 사용 float shortPrice = getValidPrice(mberManageVO.getShortPrice(), sysJoinSetVO.getShortPrice()); float longPrice = getValidPrice(mberManageVO.getLongPrice(), sysJoinSetVO.getLongPrice()); float picturePrice = getValidPrice(mberManageVO.getPicturePrice(), sysJoinSetVO.getPicturePrice()); boolean hasPerformedSpamCheck = false; // 치환 문자가 없는 경우, 스팸 체크가 한 번만 수행되도록 제어 boolean hasPerformedSubSpamCheck = false; // 치환 문자가 없는 경우, 스팸 체크가 한 번만 수행되도록 제어 boolean hasPerformedMsgType = false; // 치환 문자가 없는 경우, 메세지 타입 체크 한번 boolean hasPerformedDelayYn = false; // 치환 문자가 없는 경우, String imgFilePath = ""; if(StringUtils.isNotEmpty(kakaoVO.getAtchFileId()) && ("I".equals(imageType) || "W".equals(imageType))) { imgFilePath = mjonMsgDAO.selectPhotoImgFileRealPath(kakaoVO.getAtchFileId()); } /** @jsonStr 필요유무 */ boolean hasButtons = CollectionUtils.isNotEmpty(kakaoVO.getButtonVOList()); String sharedJsonStr = null; // 치환데이터가 없는 경우 한 번만 계산하기 위한 캐시 변수 추가 Map sharedPricingResult = null; // 치환데이터가 없는 경우 for문 전에 한 번만 계산 if (!replaceSubYN && StringUtils.isNotEmpty(subMsgTxt)) { sharedPricingResult = calculateSubMsgPricing(subMsgTxt, imgFilePath, shortPrice, longPrice, picturePrice, kakaoFtPrice); // 사전계산에서 INVALID인 경우 즉시 리턴 String preSendType = (String) sharedPricingResult.get("sendType"); if ("INVALID".equals(preSendType)) { statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "전송 문자 길이를 초과하였습니다."); return kakaoSendAdvcListVO; } } List mjonFTSendVOList = kakaoVO.getMjonFTSendVOList(); for (int i = 0; i < mjonFTSendVOList.size(); i++) { MjonFTSendVO mjonFTSendVO = mjonFTSendVOList.get(i); KakaoSendAdvcVO sendVO = createFTSendVO(kakaoVO, calendar); // 공통 가격 설정 sendVO.setSmsPrice(Float.toString(shortPrice)); sendVO.setMmsPrice(Float.toString(longPrice)); sendVO.setPicturePrice(Float.toString(picturePrice)); sendVO.setCallTo(mjonFTSendVO.getPhone()); sendVO.setMsgId(idList.get(i)); // 친구톡 문자 String templateContentTemp = templateContent; // 치환 문자면 if(replaceYN) { // 각 치환 구문을 확인하고 치환할 값이 없으면 오류 반환 for (Map.Entry> entry : placeholders.entrySet()) { String placeholder = entry.getKey(); String value = entry.getValue().apply(mjonFTSendVO); if (templateContentTemp.contains(placeholder)) { if (StringUtils.isEmpty(value)) { statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "치환 문구중 " + placeholder + " 데이터가 없습니다."); return null; } templateContentTemp = templateContentTemp.replace(placeholder, value); } } } sendVO.setTemplateContent(templateContentTemp); // 실패 대체 문자 String subMsgTxtTemp = null; if(StringUtils.isNotEmpty(subMsgTxt)) { subMsgTxtTemp = subMsgTxt; if(replaceSubYN) { // 각 치환 구문을 확인하고 치환할 값이 없으면 오류 반환 for (Map.Entry> entry : placeholders.entrySet()) { String placeholder = entry.getKey(); String value = entry.getValue().apply(mjonFTSendVO); if (subMsgTxtTemp.contains(placeholder)) { if (StringUtils.isEmpty(value)) { statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "치환 문구중 " + placeholder + " 데이터가 없습니다."); return null; } subMsgTxtTemp = subMsgTxtTemp.replace(placeholder, value); } } } } sendVO.setSubMsgTxt(subMsgTxtTemp); //대체문자가 있으면 // Step 1-4: 실패 대체 문자 치환데이터 설정 if(StringUtils.isNotEmpty(subMsgTxtTemp)) { // 대체문자가 있나? // 최적화된 계산 로직 Map pricingResult; if (replaceSubYN) { // 치환데이터 있음 → 매번 새로 계산 pricingResult = calculateSubMsgPricing(subMsgTxtTemp, imgFilePath, shortPrice, longPrice, picturePrice, kakaoFtPrice); // INVALID 체크 String resultSendType = (String) pricingResult.get("sendType"); if ("INVALID".equals(resultSendType)) { statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "전송 문자 길이를 초과하였습니다."); return kakaoSendAdvcListVO; } } else { // 치환데이터 없음 → 미리 계산된 결과 재사용 pricingResult = sharedPricingResult; } // 결과 적용 applyPricingResult(sendVO, pricingResult); }else { // 대체문자가 없으면 카카오톡 단가 그대로 사용 sendVO.setEachPrice( Float.toString(kakaoFtPrice) ); } // 스팸 단어 체크 // exceptSpam는 사용자 스팸 단어 체크할건지에 대한 여부 N : 체크 if("N".equals(exceptSpamYn)) { // 친구톡 내용 if(replaceYN) { checkSpamAndSetStatus(kakaoVO , templateContentTemp , resultSpamTxt, isHolidayNotified); }else if(!hasPerformedSpamCheck) { checkSpamAndSetStatus(kakaoVO , templateContentTemp , resultSpamTxt, isHolidayNotified); hasPerformedSpamCheck = true; } // 대체문자 내용 if(StringUtils.isNotEmpty(subMsgTxtTemp)) { if(replaceSubYN) { checkSpamAndSetStatus(kakaoVO , subMsgTxtTemp , resultSpamTxt, isHolidayNotified); }else if(!hasPerformedSubSpamCheck) { checkSpamAndSetStatus(kakaoVO , subMsgTxtTemp , resultSpamTxt, isHolidayNotified); hasPerformedSubSpamCheck = true; } } } log.info(" kakaoVO.toString() :: [{}]",kakaoVO.ftToString()); /* @isHolidayNotified * - 관리자 알림 설정으로 인해 * - 해당 시간이면 지연 미처리 * @smishingYn * - 회원 별 '스미싱 온' 상태값 * - Y면 알림, 지연 처리해야 함 * */ if("Y".equalsIgnoreCase(atSmishingYn) && isHolidayNotified) { kakaoVO.setSpamStatus("Y"); kakaoVO.setSmishingYn("Y"); kakaoVO.setAtDelayYn("Y"); } // 지연 여부 처리 if (( "Y".equalsIgnoreCase(atSmishingYn) || "Y".equalsIgnoreCase(kakaoVO.getAtDelayYn())) && !hasPerformedDelayYn && isHolidayNotified) { calendar.add(Calendar.MINUTE, 30); // 모든 시간을 30분 뒤로 미룸 // TEST // calendar.add(Calendar.MINUTE, 5); // 모든 시간을 30분 뒤로 미룸 hasPerformedDelayYn = true; } sendVO.setReqDate(sdf.format(calendar.getTime())); // 분할된 시간 설정 또는 기본 예약 시간 사용 // 타이틀이나 버튼이 있고 if(hasButtons || StringUtils.isNotEmpty(kakaoVO.getTemplateImageUrl())) { // if (StringUtils.isEmpty(sharedJsonStr)) { // 치환 데이터가 없고 아직 생성되지 않았으면 한 번만 생성 sharedJsonStr = kakaoApiJsonSave.kakaoApiFTJsonSave_advc(kakaoVO); sendVO.setJsonStr(sharedJsonStr); } sendVO.setBizJsonName(idList.get(0)); } kakaoSendAdvcListVO.add(sendVO); log.info(" sendVO.toString() :: [{}]",sendVO.toString()); } return kakaoSendAdvcListVO; } private void checkSpamAndSetStatus(KakaoVO kakaoVO , String chkText , List resultSpamTxt, boolean isHolidayNotified) throws Exception { // TODO Auto-generated method stub kakaoVO.setSpamStatus("N"); kakaoVO.setAtDelayYn("N"); if(StringUtils.isNotEmpty(chkText)) { String resultParser = ComGetSpamStringParser.getSpamTextParse(chkText).trim(); int spmCnt = 0; String spmFilterTxt = ""; for (String spmTxt : resultSpamTxt) { String parserStr = ComGetSpamStringParser.getSpamTextParse(spmTxt).trim(); if (resultParser.contains(parserStr) || chkText.contains(parserStr)) { spmCnt++; spmFilterTxt += spmTxt + ","; } } if (spmCnt > 0) { // 스팸 문자가 포함된 경우 if (StringUtil.getWordRight(spmFilterTxt.trim(), 1).equals(",")) { // 처음부터 idx 만큼 잘라낸 나머지 글자 spmFilterTxt = StringUtil.getWordLeft(spmFilterTxt.trim(), 1); } /* @isHolidayNotified * - 관리자 알림 설정으로 인해 * - 해당 시간이면 지연 미처리 * */ kakaoVO.setSpamStatus("Y"); if(isHolidayNotified) { kakaoVO.setAtDelayYn("Y"); } } } } // TODO(human): 아래에 새로운 메소드 구현 /** * 대체문자 가격 계산 최적화 메소드 * @param subMsgTxt 대체문자 내용 * @param imgFilePath 이미지 파일 경로 * @param shortPrice 단문 가격 * @param longPrice 장문 가격 * @param picturePrice 사진 가격 * @param kakaoFtPrice 카카오 친구톡 가격 * @return 계산 결과 Map (sendType, chosenPrice, filePath 포함) * @throws UnsupportedEncodingException */ private Map calculateSubMsgPricing(String subMsgTxt, String imgFilePath, float shortPrice, float longPrice, float picturePrice, float kakaoFtPrice) throws UnsupportedEncodingException { Map result = new HashMap<>(); String sendType = "MMS"; if(StringUtils.isEmpty(imgFilePath)) { int smsTxtByte = mjonCommon.getSmsTxtBytes(subMsgTxt); sendType = getMsgType(smsTxtByte); } result.put("sendType", sendType); // INVALID인 경우 추가 처리 없이 반환 if ("INVALID".equals(sendType)) { return result; } float chosenPrice = 0f; if(StringUtils.isNotEmpty(imgFilePath)) { chosenPrice = Math.max(picturePrice, kakaoFtPrice); result.put("filePath", imgFilePath); } else if ("MMS".equals(sendType)) { chosenPrice = Math.max(longPrice, kakaoFtPrice); } else { chosenPrice = Math.max(shortPrice, kakaoFtPrice); } result.put("sendType", sendType); result.put("chosenPrice", Float.toString(chosenPrice)); return result; } /** * 가격 계산 결과를 sendVO에 적용하는 헬퍼 메소드 * @param sendVO 적용할 KakaoSendAdvcVO 객체 * @param pricingResult 가격 계산 결과 Map */ private void applyPricingResult(KakaoSendAdvcVO sendVO, Map pricingResult) { sendVO.setSubMsgType((String) pricingResult.get("sendType")); sendVO.setEachPrice((String) pricingResult.get("chosenPrice")); if (pricingResult.get("filePath") != null) { sendVO.setFilePath1((String) pricingResult.get("filePath")); sendVO.setFileCnt("1"); } } public static String getMsgTypeWithByteValidation(MjonFTSendVO sendVO, String p_smsTxt) throws UnsupportedEncodingException { // // 내문자저장함에 저장 후 문자를 발송하는 경우 문자 타입이 숫자가 아닌 문자로 넘어와서 변경 처리함 // if ("P".equals(msgType) || "L".equals(msgType)) { // msgType = "6"; // } else if ("S".equals(msgType)) { // msgType = "4"; // } int smsTxtByte = MjonCommon.getSmsTxtBytes(p_smsTxt); String msgType = SHORT_MSG_TYPE; // 1. 2000 Byte 초과는 에러 처리 if (smsTxtByte > 2000) { return "INVALID"; } // 2. 첨부파일 여부 확인 (첨부파일이 있으면 장문으로 설정) if (StringUtils.isNotEmpty(sendVO.getFilePath1())) { msgType = LONG_MSG_TYPE; } // 3. 문자 길이에 따라 메시지 타입 설정 (90 Byte 초과는 장문) else if (smsTxtByte > 90) { msgType = LONG_MSG_TYPE; } return msgType; } private Calendar setupBaseDateFT(KakaoVO kakaoVO) throws ParseException { // baseDate 추출 Date baseDate = resolveBaseDate(kakaoVO); // 시간 성정 Calendar calendar = Calendar.getInstance(); calendar.setTime(baseDate); // calendar에 baseDate 설정 return calendar; } private Calendar setupBaseDate(KakaoVO kakaoVO, boolean isHolidayNotified) throws ParseException { // baseDate 추출 Date baseDate = resolveBaseDate(kakaoVO); // 시간 성정 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; } public Date resolveBaseDate(KakaoVO kakaoVO) throws ParseException { Date now = new Date(); if (StringUtils.isEmpty(kakaoVO.getReqDate())) { kakaoVO.setReqDate(DATE_FORMATTER.format(now)); return now; } return DATE_FORMATTER.parse(kakaoVO.getReqDate()); } // 2. 친구톡 발송 제한 시간인지 확인 public boolean isRestrictedFriendTalkTime(Date baseDate) { Calendar cal = Calendar.getInstance(); cal.setTime(baseDate); int hour = cal.get(Calendar.HOUR_OF_DAY); int minute = cal.get(Calendar.MINUTE); // 20:50 이후 ~ 익일 08:00 이전은 제한 if ((hour == 20 && minute >= 50) || hour > 20 || hour < 8) { return true; } return false; } /** * @methodName : createSendVO * @author : 이호영 * @date : 2025. 3. 19. * @description : populateSendLists 반복에 필요한 공통생성 부분 * @return : KakaoSendAdvcVO * @param kakaoVO * @return * */ private KakaoSendAdvcVO createATSendVO(KakaoVO kakaoVO) { KakaoSendAdvcVO sendVO = new KakaoSendAdvcVO(); sendVO.setMsgType("8"); sendVO.setAgentCode("04"); sendVO.setSenderKey(kakaoVO.getSenderKey()); sendVO.setTemplateCode(kakaoVO.getTemplateCode()); sendVO.setUserId(kakaoVO.getUserId()); sendVO.setCallFrom(kakaoVO.getCallFrom()); return sendVO; } /** * @methodName : createFTSendVO * @author : 이호영 * @date : 2025. 4. 23. * @description : * @return : KakaoSendAdvcVO * @param kakaoVO * @return * */ private KakaoSendAdvcVO createFTSendVO(KakaoVO kakaoVO, Calendar calendar) { KakaoSendAdvcVO sendVO = new KakaoSendAdvcVO(); sendVO.setMsgType("9"); // 알림톡 8 친구톡 9 sendVO.setAgentCode("04"); // 발송시간 : 친구톡은 분할 발송이 없어 처음 vo 생성 시 입력 sendVO.setReqDate(DATE_FORMATTER.format(calendar.getTime())); sendVO.setSenderKey(kakaoVO.getSenderKey()); sendVO.setTemplateCode(kakaoVO.getTemplateCode()); sendVO.setUserId(kakaoVO.getUserId()); sendVO.setCallFrom(kakaoVO.getCallFrom()); sendVO.setSubMsgSendYn(kakaoVO.getSubMsgSendYn()); sendVO.setAdFlag(kakaoVO.getAdFlag()); return sendVO; } private List replaceButtonLinks(List buttonList, Map 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 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(); } public static Float getValidPrice(Float personalPrice, Float defaultPrice) { return (personalPrice != null && personalPrice > 0) ? personalPrice : defaultPrice; } /** * @methodName : getMsgType * @author : 이호영 * @date : 2025. 3. 12. * @description : 메세지 타입 구하기 * @return : String * @param smsTxtByte * @return * */ private String getMsgType(int smsTxtByte) { // TODO Auto-generated method stub String msgType = SHORT_MSG_TYPE; // 1. 2000 Byte 초과는 에러 처리 if (smsTxtByte > 2000) { return "INVALID"; } // 2. 문자 길이에 따라 메시지 타입 설정 (90 Byte 초과는 장문) if (smsTxtByte > 90) { msgType = LONG_MSG_TYPE; } return msgType; } /** * @Method Name : kakaoSendPrice * @작성일 : 2023. 2. 14. * @작성자 : WYH * @Method 설명 : 카카오 전송 가격 설정 */ public KakaoVO kakaoSendPrice(KakaoVO kakaoVO) throws Exception { System.out.println(" :: kakaoSendPrice :: "); //사용자 현재 보유 금액 불러오기(문자 발송 금액 차감 이전 금액) // String befCash = kakaoVO.getBefCash(); MjonMsgVO mjonMsgVO = new MjonMsgVO(); mjonMsgVO.setUserId(kakaoVO.getUserId()); String userMoney = mjonMsgDataService.selectBeforeCashData(mjonMsgVO); String userPoint = mjonMsgDataService.selectBeforePointData(mjonMsgVO); //1.시스템 기본 단가 정보 불러오기 JoinSettingVO sysJoinSetVO = mjonMsgDataService.selectJoinSettingInfo(); //2.사용자 개인 단가 정보 불러오기 MberManageVO mberManageVO = mjonMsgDataService.selectMberManageInfo(kakaoVO.getUserId()); Float kakaoAtPrice = mberManageVO.getKakaoAtPrice(); /** 대체문자 여부 체크(있으면 대체문자 가격으로 없으면 카카오톡 가격으로) */ //대체문자 발송 여부 확인 System.out.println(" :: kakaoVO.getSubMsgSendYn() :: "+ kakaoVO.getSubMsgSendYn()); if(kakaoVO.getSubMsgSendYn().equals("Y")) { String charset = "euc-kr"; //문자 바이트 계산에 필요한 캐릭터 셋 : 한글 2Byte로 계산 int callToCnt = kakaoVO.getCallToList().length; String sendType = ""; for(int count =0; count < callToCnt; count++) { String tempSubMagTxt = kakaoVO.getSubMsgTxt().replace("\r\n", "\n"); if(kakaoVO.getSubMsgTxtReplYn().equals("Y")) { tempSubMagTxt = kakaoSubMagTxtRepl(tempSubMagTxt, kakaoVO, count); } int bytes = tempSubMagTxt.getBytes(charset).length; if(bytes < 2000) { if(bytes > 90) { sendType = "MMS"; break; }else { sendType = "SMS"; } }else { kakaoVO.setResultCode("2000"); return kakaoVO; } } if(sendType.equals("MMS")) { //협의 단가가 없으면 시스템 단가로 지정 if(mberManageVO.getLongPrice() < 1) { kakaoAtPrice = sysJoinSetVO.getLongPrice(); kakaoVO.setSmsPrice(sysJoinSetVO.getShortPrice()); kakaoVO.setMmsPrice(sysJoinSetVO.getLongPrice()); kakaoVO.setKakaoAtPrice(sysJoinSetVO.getKakaoAtPrice()); }else { kakaoAtPrice = mberManageVO.getLongPrice(); kakaoVO.setSmsPrice(mberManageVO.getShortPrice()); kakaoVO.setMmsPrice(mberManageVO.getLongPrice()); if(mberManageVO.getKakaoAtPrice() < 1) { kakaoVO.setKakaoAtPrice(sysJoinSetVO.getKakaoAtPrice()); }else { kakaoVO.setKakaoAtPrice(mberManageVO.getKakaoAtPrice()); } } }else { //협의 단가가 없으면 시스템 단가로 지정 if(mberManageVO.getShortPrice() < 1) { kakaoAtPrice = sysJoinSetVO.getShortPrice(); kakaoVO.setSmsPrice(sysJoinSetVO.getShortPrice()); kakaoVO.setMmsPrice(sysJoinSetVO.getLongPrice()); kakaoVO.setKakaoAtPrice(sysJoinSetVO.getKakaoAtPrice()); }else { kakaoAtPrice = mberManageVO.getShortPrice(); kakaoVO.setSmsPrice(mberManageVO.getShortPrice()); kakaoVO.setMmsPrice(mberManageVO.getLongPrice()); if(mberManageVO.getKakaoAtPrice() < 1) { kakaoVO.setKakaoAtPrice(sysJoinSetVO.getKakaoAtPrice()); }else { kakaoVO.setKakaoAtPrice(mberManageVO.getKakaoAtPrice()); } } } }else { if(kakaoAtPrice < 1) { //협의 단가가 없으면 시스템 단가로 지정 kakaoAtPrice = sysJoinSetVO.getKakaoAtPrice(); kakaoVO.setSmsPrice(sysJoinSetVO.getShortPrice()); kakaoVO.setMmsPrice(sysJoinSetVO.getLongPrice()); kakaoVO.setKakaoAtPrice(sysJoinSetVO.getKakaoAtPrice()); }else { kakaoVO.setSmsPrice(mberManageVO.getShortPrice()); kakaoVO.setMmsPrice(mberManageVO.getLongPrice()); kakaoVO.setKakaoAtPrice(mberManageVO.getKakaoAtPrice()); } } /** 전송인원 확인*/ int totCallCnt = kakaoVO.getCallToList().length; Float kakaoTotPrice = totCallCnt * kakaoAtPrice; // 총결제 금액 = 총 전송수량 * 카카오 알림톡 단가 String totPrice = kakaoTotPrice.toString(); System.out.println("@@@@@@@ : "+kakaoTotPrice +" = "+totCallCnt+" * "+kakaoAtPrice); kakaoVO.setEachPrice(kakaoAtPrice.toString()); kakaoVO.setBefCash(userMoney); // 고객 충전금액 kakaoVO.setBefPoint(userPoint); // 고객 충전 포인트 kakaoVO.setTotPrice(totPrice); // 총 카카오 전송 금액 return kakaoVO; } /** * @Method Name : kakaoFTSendPrice * @작성일 : 2024. 1. 17. * @작성자 : WYH * @Method 설명 : 카카오 친구톡 전송 가격 설정 */ public KakaoVO kakaoFTSendPrice(KakaoVO kakaoVO) throws Exception { //사용자 현재 보유 금액 불러오기(문자 발송 금액 차감 이전 금액) String befCash = kakaoVO.getBefCash(); //VO에서 현재 보유금액이 없으면 디비에서 조회해서 불러옴 if("".equals(befCash) || befCash == null) { } MjonMsgVO mjonMsgVO = new MjonMsgVO(); mjonMsgVO.setUserId(kakaoVO.getUserId()); String userMoney = mjonMsgDataService.selectBeforeCashData(mjonMsgVO); String userPoint = mjonMsgDataService.selectBeforePointData(mjonMsgVO); //1.시스템 기본 단가 정보 불러오기 JoinSettingVO sysJoinSetVO = mjonMsgDataService.selectJoinSettingInfo(); //2.사용자 개인 단가 정보 불러오기 MberManageVO mberManageVO = mjonMsgDataService.selectMberManageInfo(kakaoVO.getUserId()); Float kakaoFtPrice = mberManageVO.getKakaoFtPrice(); /** 대체문자 여부 체크(있으면 대체문자 가격으로 없으면 카카오톡 가격으로) */ //대체문자 발송 여부 확인 if(kakaoVO.getSubMsgSendYn().equals("Y")) { String charset = "euc-kr"; //문자 바이트 계산에 필요한 캐릭터 셋 : 한글 2Byte로 계산 int callToCnt = kakaoVO.getCallToList().length; String sendType = ""; for(int count =0; count < callToCnt; count++) { String tempSubMagTxt = kakaoVO.getSubMsgTxt().replace("\r\n", "\n"); if(kakaoVO.getSubMsgTxtReplYn().equals("Y")) { tempSubMagTxt = kakaoFTSubMagTxtRepl(tempSubMagTxt, kakaoVO, count); } int bytes = tempSubMagTxt.getBytes(charset).length; if(bytes < 2000) { if(bytes > 90) { sendType = "MMS"; break; }else { sendType = "SMS"; } }else { kakaoVO.setResultCode("2000"); return kakaoVO; } } if(sendType.equals("MMS")) { //협의 단가가 없으면 시스템 단가로 지정 if(mberManageVO.getLongPrice() < 1) { kakaoFtPrice = sysJoinSetVO.getLongPrice(); kakaoVO.setSmsPrice(sysJoinSetVO.getShortPrice()); kakaoVO.setMmsPrice(sysJoinSetVO.getLongPrice()); kakaoVO.setKakaoFtPrice(sysJoinSetVO.getKakaoFtPrice()); }else { kakaoFtPrice = mberManageVO.getLongPrice(); kakaoVO.setSmsPrice(mberManageVO.getShortPrice()); kakaoVO.setMmsPrice(mberManageVO.getLongPrice()); if(mberManageVO.getKakaoFtPrice() < 1) { kakaoVO.setKakaoFtPrice(sysJoinSetVO.getKakaoFtPrice()); }else { kakaoVO.setKakaoFtPrice(mberManageVO.getKakaoFtPrice()); } } }else { //협의 단가가 없으면 시스템 단가로 지정 if(mberManageVO.getShortPrice() < 1) { kakaoFtPrice = sysJoinSetVO.getShortPrice(); kakaoVO.setSmsPrice(sysJoinSetVO.getShortPrice()); kakaoVO.setMmsPrice(sysJoinSetVO.getLongPrice()); kakaoVO.setKakaoFtPrice(sysJoinSetVO.getKakaoFtPrice()); }else { kakaoFtPrice = mberManageVO.getShortPrice(); kakaoVO.setSmsPrice(mberManageVO.getShortPrice()); kakaoVO.setMmsPrice(mberManageVO.getLongPrice()); if(mberManageVO.getKakaoFtPrice() < 1) { kakaoVO.setKakaoFtPrice(sysJoinSetVO.getKakaoFtPrice()); }else { kakaoVO.setKakaoFtPrice(mberManageVO.getKakaoFtPrice()); } } } }else { if(kakaoFtPrice < 1) { //협의 단가가 없으면 시스템 단가로 지정 kakaoFtPrice = sysJoinSetVO.getKakaoFtPrice(); kakaoVO.setSmsPrice(sysJoinSetVO.getShortPrice()); kakaoVO.setMmsPrice(sysJoinSetVO.getLongPrice()); kakaoVO.setKakaoFtPrice(sysJoinSetVO.getKakaoFtPrice()); }else { kakaoVO.setSmsPrice(mberManageVO.getShortPrice()); kakaoVO.setMmsPrice(mberManageVO.getLongPrice()); kakaoVO.setKakaoFtPrice(mberManageVO.getKakaoFtPrice()); } } /** 전송인원 확인*/ int totCallCnt = kakaoVO.getCallToList().length; Float kakaoTotPrice = totCallCnt * kakaoFtPrice; // 총결제 금액 = 총 전송수량 * 카카오 친구톡 단가 String totPrice = kakaoTotPrice.toString(); System.out.println("@@@@@@@ : "+kakaoTotPrice +" = "+totCallCnt+" * "+kakaoFtPrice); kakaoVO.setEachPrice(kakaoFtPrice.toString()); kakaoVO.setBefCash(userMoney); // 고객 충전금액 kakaoVO.setBefPoint(userPoint); // 고객 충전 포인트 kakaoVO.setTotPrice(totPrice); // 총 카카오 전송 금액 return kakaoVO; } /** * @methodName : selectSendPriceOfKakaoAtAndSmsAndMms * @author : 이호영 * @date : 2023.03.02 * @description : 알림톡 / sms / mms 가격 불러오기 * @param String userID * @return MberManageVO * @throws Exception */ public MberManageVO selectSendPriceOfKakaoAtAndSmsAndMms(String userId) throws Exception { //1.시스템 기본 단가 정보 불러오기 JoinSettingVO sysJoinSetVO = mjonMsgDataService.selectJoinSettingInfo(); //2.사용자 개인 단가 정보 불러오기 MberManageVO mberManageVO = mjonMsgDataService.selectMberManageInfo(userId); // kakao 단가 // 사용자 개인 단가가 없으면 시스템 단가로 if(mberManageVO.getKakaoAtPrice() == 0.0f) mberManageVO.setKakaoAtPrice(sysJoinSetVO.getKakaoAtPrice()); //카카오 친구톡 개인 단가가 없는 경우 시스템 단가로 if(mberManageVO.getKakaoFtPrice() == 0.0f) mberManageVO.setKakaoFtPrice(sysJoinSetVO.getKakaoFtPrice()); if(mberManageVO.getKakaoFtImgPrice() == 0.0f) mberManageVO.setKakaoFtImgPrice(sysJoinSetVO.getKakaoFtImgPrice()); if(mberManageVO.getKakaoFtWideImgPrice() == 0.0f) mberManageVO.setKakaoFtWideImgPrice(sysJoinSetVO.getKakaoFtWideImgPrice()); // SMS 인경우 // 사용자 개인 단가가 없으면 시스템 단가로 if(mberManageVO.getShortPrice() == 0.0f) mberManageVO.setShortPrice(sysJoinSetVO.getShortPrice()); // // MMS 인경우 // 사용자 개인 단가가 없으면 시스템 단가로 if(mberManageVO.getLongPrice() == 0.0f) mberManageVO.setLongPrice(sysJoinSetVO.getLongPrice()); if(mberManageVO.getPicturePrice() == 0.0f) mberManageVO.setPicturePrice(sysJoinSetVO.getPicturePrice()); return mberManageVO; } /** * @Method Name : kakaoSendMsg * @작성일 : 2023. 2. 14. * @작성자 : WYH * @Method 설명 : 카카오톡 전송 메세지 설정 */ public KakaoVO kakaoSendMsg(KakaoVO kakaoVO) throws Exception { List kakaoSendList = new ArrayList(); //전체 받는사람 수량만큼 반복 확인 int callToCnt = kakaoVO.getCallToList().length; try { for(int count =0; count < callToCnt; count++) { KakaoVO setSendMsgVO = new KakaoVO(); setSendMsgVO.setDestPhone(kakaoVO.getCallToList()[count]); // 수신 번호 // 카카오 전송내용 설정 // 변환문자 포함(Y), 미포함(N) if(kakaoVO.getTxtReplYn().equals("Y")) { String templateContent = kakaoSubMagTxtRepl(kakaoVO.getTemplateContent(), kakaoVO, count); setSendMsgVO.setTemplateContent(templateContent); if(kakaoVO.getTemplateEmphasizeType().equals("TEXT")) { String title = kakaoSubMagTxtRepl(kakaoVO.getTemplateTitle(), kakaoVO, count); String subTitle = kakaoVO.getTemplateSubtitle(); // title = title +"§§"+ subTitle; setSendMsgVO.setTemplateEmphasizeType(kakaoVO.getTemplateEmphasizeType()); setSendMsgVO.setTemplateTitle(title); } }else { if(kakaoVO.getTemplateEmphasizeType().equals("TEXT")) { String title = kakaoSubMagTxtRepl(kakaoVO.getTemplateTitle(), kakaoVO, count); String subTitle = kakaoVO.getTemplateSubtitle(); // title = title +"§§"+ subTitle; setSendMsgVO.setTemplateEmphasizeType(kakaoVO.getTemplateEmphasizeType()); setSendMsgVO.setTemplateTitle(title); } // 템플릿 내용 설정 setSendMsgVO.setTemplateContent(kakaoVO.getTemplateContent()); } //대체문자 포함(Y), 미포함(N) if(kakaoVO.getSubMsgSendYn().equals("Y")) { String charset = "euc-kr"; //문자 바이트 계산에 필요한 캐릭터 셋 : 한글 2Byte로 계산 String tempSubMagTxt = kakaoVO.getSubMsgTxt().replace("\r\n", "\n"); kakaoVO.setKakaoSubMagOrgnlTxt(tempSubMagTxt); if(kakaoVO.getSubMsgTxtReplYn().equals("Y")) { tempSubMagTxt = kakaoSubMagTxtRepl(tempSubMagTxt, kakaoVO, count); } System.out.println("@@ 대체문자내용 : " + tempSubMagTxt); setSendMsgVO.setSubMsgTxt(tempSubMagTxt); int FrBytes = tempSubMagTxt.getBytes(charset).length; System.out.println("@@ 대체문자길이 : " + FrBytes); //메세지 길이가 90Byte가 초과시 MMS if(FrBytes > 90) { setSendMsgVO.setSubMsgType("MMS"); }else {// 아니면 SMS setSendMsgVO.setSubMsgType("SMS"); } System.out.println("@@ 대체문자타입 : " + setSendMsgVO.getSubMsgType()); } if(kakaoVO.getBizJsonYn().equals("Y")) { kakaoVO.setDestPhone(kakaoVO.getCallToList()[count]); // 수신 번호 String[] varValInfo = null; if( kakaoVO.getVarValList().size() != 0) { varValInfo = kakaoVO.getVarValList().get(count); } String jsonFileName = kakaoApiJsonSave.kakaoApiJsonSave(kakaoVO, varValInfo); setSendMsgVO.setBizJsonName(jsonFileName); //json 파일명 } kakaoSendList.add(setSendMsgVO); } kakaoVO.setKakaoSendList(kakaoSendList); } catch (Exception e) { System.out.println(e.toString()); e.printStackTrace(); } return kakaoVO; } /** * @methodName : kakaoSendMsg_advc * @author : 이호영 * @date : 2025. 3. 13. * @description : kakaoSendMsg 개선 * @return : KakaoVO * @param kakaoVO * @return * @throws Exception * */ public KakaoVO kakaoSendMsg_advc(KakaoVO kakaoVO) throws Exception { List kakaoSendList = new ArrayList(); //전체 받는사람 수량만큼 반복 확인 int callToCnt = kakaoVO.getCallToList().length; try { for(int count =0; count < callToCnt; count++) { KakaoVO setSendMsgVO = new KakaoVO(); setSendMsgVO.setDestPhone(kakaoVO.getCallToList()[count]); // 수신 번호 // 카카오 전송내용 설정 // 변환문자 포함(Y), 미포함(N) if(kakaoVO.getTxtReplYn().equals("Y")) { String templateContent = kakaoSubMagTxtRepl(kakaoVO.getTemplateContent(), kakaoVO, count); setSendMsgVO.setTemplateContent(templateContent); if(kakaoVO.getTemplateEmphasizeType().equals("TEXT")) { String title = kakaoSubMagTxtRepl(kakaoVO.getTemplateTitle(), kakaoVO, count); String subTitle = kakaoVO.getTemplateSubtitle(); // title = title +"§§"+ subTitle; setSendMsgVO.setTemplateEmphasizeType(kakaoVO.getTemplateEmphasizeType()); setSendMsgVO.setTemplateTitle(title); } }else { if(kakaoVO.getTemplateEmphasizeType().equals("TEXT")) { String title = kakaoSubMagTxtRepl(kakaoVO.getTemplateTitle(), kakaoVO, count); String subTitle = kakaoVO.getTemplateSubtitle(); // title = title +"§§"+ subTitle; setSendMsgVO.setTemplateEmphasizeType(kakaoVO.getTemplateEmphasizeType()); setSendMsgVO.setTemplateTitle(title); } // 템플릿 내용 설정 setSendMsgVO.setTemplateContent(kakaoVO.getTemplateContent()); } //대체문자 포함(Y), 미포함(N) if(kakaoVO.getSubMsgSendYn().equals("Y")) { String charset = "euc-kr"; //문자 바이트 계산에 필요한 캐릭터 셋 : 한글 2Byte로 계산 String tempSubMagTxt = kakaoVO.getSubMsgTxt().replace("\r\n", "\n"); kakaoVO.setKakaoSubMagOrgnlTxt(tempSubMagTxt); if(kakaoVO.getSubMsgTxtReplYn().equals("Y")) { tempSubMagTxt = kakaoSubMagTxtRepl(tempSubMagTxt, kakaoVO, count); } System.out.println("@@ 대체문자내용 : " + tempSubMagTxt); setSendMsgVO.setSubMsgTxt(tempSubMagTxt); int FrBytes = tempSubMagTxt.getBytes(charset).length; System.out.println("@@ 대체문자길이 : " + FrBytes); //메세지 길이가 90Byte가 초과시 MMS if(FrBytes > 90) { setSendMsgVO.setSubMsgType("MMS"); }else {// 아니면 SMS setSendMsgVO.setSubMsgType("SMS"); } System.out.println("@@ 대체문자타입 : " + setSendMsgVO.getSubMsgType()); } if(kakaoVO.getBizJsonYn().equals("Y")) { kakaoVO.setDestPhone(kakaoVO.getCallToList()[count]); // 수신 번호 String[] varValInfo = null; if( kakaoVO.getVarValList().size() != 0) { varValInfo = kakaoVO.getVarValList().get(count); } String jsonFileName = kakaoApiJsonSave.kakaoApiJsonSave(kakaoVO, varValInfo); // String jsonFileName = kakaoApiJsonSave.kakaoApiJsonSave_advc(kakaoVO, varValInfo); // String jsonFileName = kakaoApiJsonSave.kakaoApiJsonSave(kakaoVO, kakaoVO.getVarValList().get(count)); setSendMsgVO.setBizJsonName(jsonFileName); //json 파일명 } kakaoSendList.add(setSendMsgVO); } kakaoVO.setKakaoSendList(kakaoSendList); } catch (Exception e) { System.out.println(e.toString()); e.printStackTrace(); } return kakaoVO; } /** * @Method Name : kakaoFTSendMsg * @작성일 : 2024. 1. 17. * @작성자 : 우영두 * @Method 설명 : 카카오 친톡 전송 메세지 설정 */ public KakaoVO kakaoFTSendMsg(KakaoVO kakaoVO) throws Exception { List kakaoSendList = new ArrayList(); //전체 받는사람 수량만큼 반복 확인 int callToCnt = kakaoVO.getCallToList().length; try { for(int count =0; count < callToCnt; count++) { KakaoVO setSendMsgVO = new KakaoVO(); setSendMsgVO.setDestPhone(kakaoVO.getCallToList()[count]); // 수신 번호 // 카카오 전송내용 설정 // 변환문자 포함(Y), 미포함(N) if(kakaoVO.getTxtReplYn().equals("Y")) { String templateContent = kakaoFTSubMagTxtRepl(kakaoVO.getTemplateContent(), kakaoVO, count); setSendMsgVO.setTemplateContent(templateContent); }else { // 템플릿 내용 설정 setSendMsgVO.setTemplateContent(kakaoVO.getTemplateContent()); } //대체문자 포함(Y), 미포함(N) if(kakaoVO.getSubMsgSendYn().equals("Y")) { String charset = "euc-kr"; //문자 바이트 계산에 필요한 캐릭터 셋 : 한글 2Byte로 계산 String tempSubMagTxt = kakaoVO.getSubMsgTxt().replace("\r\n", "\n"); kakaoVO.setKakaoSubMagOrgnlTxt(tempSubMagTxt); if(kakaoVO.getSubMsgTxtReplYn().equals("Y")) { tempSubMagTxt = kakaoFTSubMagTxtRepl(tempSubMagTxt, kakaoVO, count); } setSendMsgVO.setSubMsgTxt(tempSubMagTxt); int FrBytes = tempSubMagTxt.getBytes(charset).length; //메세지 길이가 90Byte가 초과시 MMS if(FrBytes > 90) { setSendMsgVO.setSubMsgType("MMS"); }else {// 아니면 SMS setSendMsgVO.setSubMsgType("SMS"); } } if(kakaoVO.getBizJsonYn().equals("Y")) { kakaoVO.setDestPhone(kakaoVO.getCallToList()[count]); // 수신 번호 String[] varValInfo = null; if( kakaoVO.getVarValList().size() != 0) { varValInfo = kakaoVO.getVarValList().get(count); } String jsonFileName = kakaoApiJsonSave.kakaoApiFTJsonSave(kakaoVO); // String jsonFileName = kakaoApiJsonSave.kakaoApiJsonSave(kakaoVO, kakaoVO.getVarValList().get(count)); setSendMsgVO.setBizJsonName(jsonFileName); //json 파일명 } kakaoSendList.add(setSendMsgVO); } kakaoVO.setKakaoSendList(kakaoSendList); } catch (Exception e) { System.out.println(e.toString()); e.printStackTrace(); } return kakaoVO; } public String kakaoSendMsgTest(KakaoVO kakaoVO) throws Exception { String templateContent = ""; try { templateContent = kakaoSubMagTxtRepl(kakaoVO.getTemplateContent(), kakaoVO, 0); } catch (Exception e) { System.out.println(e.toString()); e.printStackTrace(); } return templateContent; } public String kakaoSubMagTxtRepl(String tempSubMagTxt, KakaoVO kakaoVO, int count) { System.out.println("tempSubMagTxt : "+ tempSubMagTxt); // String tempSubMagTxt = kakaoVO.getSubMsgTxt().replace("\r\n", "\n"); // String tempSubMagTxt = msgTxt; //대체문자에 변환문자가 있는경우 String[] varNm = new String[kakaoVO.getVarNmList().size()]; int q=0; if(varNm.length != 0) { for(String temp : kakaoVO.getVarNmList()) { temp = temp.replaceAll("\\#\\{" , "§§"); temp = temp.replaceAll("\\}" , "§"); varNm[q] = temp; q++; } List varValList = kakaoVO.getVarValList(); // value 값 tempSubMagTxt = tempSubMagTxt.replaceAll(String.valueOf((char)13), ""); tempSubMagTxt = tempSubMagTxt.replaceAll("\\#\\{" , "§§"); tempSubMagTxt = tempSubMagTxt.replaceAll("\\}" , "§"); String[] array = varValList.get(count)[0].split("§"); for(int i=0; i < varNm.length; i++) { if (tempSubMagTxt.indexOf(varNm[i]) > -1) { if(array[i] != null) { System.out.println("as : "+varNm[i] +" : "+ StringUtil.getString(array[i]).replace('Ï', ',')); tempSubMagTxt = tempSubMagTxt.replaceAll(varNm[i] , StringUtil.getString(array[i]).replace('Ï', ',')); System.out.println(varNm[i] +" : "+ array[i].replace('Ï', ',')); }else { tempSubMagTxt = tempSubMagTxt.replaceAll(varNm[i] , ""); } } } } return tempSubMagTxt; } public String kakaoFTSubMagTxtRepl(String tempSubMagTxt, KakaoVO kakaoVO, int count) throws Exception{ List varValList = kakaoVO.getVarValList(); tempSubMagTxt = getKakaoFTCntReplace(varValList.get(count)[0], tempSubMagTxt); return tempSubMagTxt; } /* * 카카오 친구톡 치환 내용에 대한 변환 처리 * * * */ public String getKakaoFTCntReplace(String varValStr, String contents) throws Exception{ String[] array = varValStr.split("¶"); String tmpContents = contents; for(int j=0; j < array.length; j++) { String tmpVarVal = array[j].replaceAll("§", ","); if(tmpVarVal.length() > 0) { if(tmpContents.contains("#{이름}") && j == 0) { tmpContents = tmpContents.replaceAll("\\#\\{이름\\}", tmpVarVal); } //1번째에 핸드폰 번호가 포함 되어 있어서 건너뜀 if(tmpContents.contains("#{1}") && j == 2) { tmpContents = tmpContents.replaceAll("\\#\\{1\\}", tmpVarVal); } if(tmpContents.contains("#{2}") && j == 3) { tmpContents = tmpContents.replaceAll("\\#\\{2\\}", tmpVarVal); } if(tmpContents.contains("#{3}") && j == 4) { tmpContents = tmpContents.replaceAll("\\#\\{3\\}", tmpVarVal); } if(tmpContents.contains("#{4}") && j == 5) { tmpContents = tmpContents.replaceAll("\\#\\{4\\}", tmpVarVal); } } } return tmpContents; } /* * 카카오 친구톡 치환문자 내용 스팸 필터 * 치환변수 내용 및 템플릿 내용, 스팸필터 단어 리스트를 전달하면 스팸 문구 포함 문구 리턴 * 없으면 공백을 리턴 * * */ public String getKakaoFTCntRepToSpamFilter(List varValList, List resultSpamTxt, String contents) throws Exception { String spmFilterTxt = ""; for(int i=0; i < varValList.size(); i++) { String tmpContents = getKakaoFTCntReplace(varValList.get(i)[0], contents); //입력 문장에 대해서 우회 문장 또는 특수 기호 입력 제거 등 문장 재구성 처리, 한글 자모음 분리 및 재조함도 함께 처리함. String resultParser = ComGetSpamStringParser.getSpamTextParse(tmpContents).trim(); //List jasoList = HangulParser.disassemble(resultParser); //String assembleStr = HangulParser.assemble(jasoList); //데이터베이스에 등록된 스팸문구와 일치하는 단어/문구가 있는지 체크함. int spmCnt = 0; for(String spmTxt : resultSpamTxt) { String parserStr = ComGetSpamStringParser.getSpamTextParse(spmTxt).trim(); if(resultParser.contains(parserStr)) { //스팸 단어/문구가 있으면 콤마로 연결시킨 후 리턴해줌. spmFilterTxt += spmTxt + ","; spmCnt++; } } if(spmCnt > 0) {//스팸문자가 포함되어 있으면 문자열 끝 , 단어 삭제 처리 if (StringUtil.getWordRight(spmFilterTxt.trim(), 1).equals(",")) { // 처음부터 idx 만큼 잘라낸 나머지 글자 spmFilterTxt = StringUtil.getWordLeft(spmFilterTxt.trim(), 1); } System.out.println("++++++++++++++ spmFilterTxt ::: "+spmFilterTxt); return spmFilterTxt; } } return ""; } /* * 치환문자가 없는 내용에 대한 스팸필터링 처리 * * */ public String getKakaoFTCntToSpamFilter(List resultSpamTxt, String contents) throws Exception { String spmFilterTxt = ""; //for(int i=0; i < varValList.size(); i++) { //String[] array = varValList.get(i)[0].split("¶"); String tmpContents = contents; System.out.println(tmpContents); //입력 문장에 대해서 우회 문장 또는 특수 기호 입력 제거 등 문장 재구성 처리, 한글 자모음 분리 및 재조함도 함께 처리함. String resultParser = ComGetSpamStringParser.getSpamTextParse(tmpContents).trim(); //List jasoList = HangulParser.disassemble(resultParser); //String assembleStr = HangulParser.assemble(jasoList); System.out.println("++++++++++++++ spam resultParser ::: "+resultParser); //데이터베이스에 등록된 스팸문구와 일치하는 단어/문구가 있는지 체크함. int spmCnt = 0; for(String spmTxt : resultSpamTxt) { String parserStr = ComGetSpamStringParser.getSpamTextParse(spmTxt).trim(); if(resultParser.contains(parserStr)) { //스팸 단어/문구가 있으면 콤마로 연결시킨 후 리턴해줌. spmFilterTxt += spmTxt + ","; spmCnt++; } } if(spmCnt > 0) {//스팸문자가 포함되어 있으면 문자열 끝 , 단어 삭제 처리 if (StringUtil.getWordRight(spmFilterTxt.trim(), 1).equals(",")) { // 처음부터 idx 만큼 잘라낸 나머지 글자 spmFilterTxt = StringUtil.getWordLeft(spmFilterTxt.trim(), 1); } System.out.println("++++++++++++++ spmFilterTxt ::: "+spmFilterTxt); return spmFilterTxt; } //} return ""; } public static void statusResponseSet (StatusResponse statusResponse, HttpStatus httpStatus, String msg ) { statusResponse.setStatus(httpStatus); statusResponse.setMessage(msg); } // 보유 금액이 충분한지 확인하는 메서드 public boolean isCashSufficient(String userId, List kakaoSendAdvcListVO) throws Exception { String userMoney = priceAndPoint.getBefCash(userId); // 쉼표 제거 userMoney = userMoney.replace(",", ""); // 사용자 보유 금액 BigDecimal 변환 (HALF_EVEN 적용) BigDecimal befCash = new BigDecimal(userMoney).setScale(2, RoundingMode.HALF_EVEN); log.info(" + userMoney :: [{}]", userMoney); log.info(" + befCash :: [{}]", befCash); // 총 메시지 금액 계산 (HALF_EVEN 적용) BigDecimal totalEachPrice = kakaoSendAdvcListVO.stream() .map(msg -> new BigDecimal(String.valueOf(msg.getEachPrice()))) // 변환 오류 방지 .reduce(BigDecimal.ZERO, BigDecimal::add) .setScale(2, RoundingMode.HALF_EVEN); // 일관성 유지 log.info(" + totalEachPrice :: [{}]", totalEachPrice); // 비교 수행 return befCash.compareTo(totalEachPrice) >= 0; } /** * @methodName : insertKakaoAtDataJsonInfo_advc * @author : 이호영 * @date : 2025. 4. 24. * @description : INSERT INTO BIZ_ATTACHMENTS * @return : void * @param kakaoSendAdvcListVO * */ public void insertKakaoAtDataJsonInfo_advc(List kakaoSendAdvcListVO) { List jsonInfoData = new ArrayList<>(kakaoSendAdvcListVO); jsonInfoData.removeIf(t -> StringUtils.isBlank(t.getJsonStr())); log.info(" + jsonInfoData Insert :: [{}]", jsonInfoData.size()); if(jsonInfoData.size() > 0) { kakaoAlimTalkDAO.insertKakaoAtDataJsonInfo_advc(jsonInfoData); } } /** * @methodName : insertKakaoData_advc * @author : 이호영 * @date : 2025. 3. 20. * @description : 카카오 batch 발송 => mj_msg_data * @return : int * @param kakaoSendAdvcVOList * @param parentLoopCount * @param isJsonNotEmpty * @param isJsonNameAllSame * @return * */ public int insertKakaoData_advc(List kakaoSendAdvcVOList) { // 시작 시간 측정 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 batchExecutionTimes = new ArrayList<>(); // 첫 번째 배치에서만 삽입했는지 추적하는 플래그 for (int i = 0; i < totalSize; i += batchSize) { // Batch 시작 시간 측정 long batchStartTime = System.currentTimeMillis(); // Batch 리스트 생성 List batchList = kakaoSendAdvcVOList.subList(i, Math.min(i + batchSize, totalSize)); System.out.println("Batch 시작 인덱스: " + i); // mj_msg_data 테이블 insert int insertedCount = kakaoAlimTalkDAO.insertKakaoAtDataInfo_advc(batchList); /** @kakaoSendUtil.populateSendLists * 하단에서 * getJsonStr 데이터 처리 후 활용 * */ 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; } public void insertKakaoGroupDataTb_advc(int instCnt, KakaoVO kakaoVO, KakaoSendAdvcVO sendVO) throws Exception { // TODO Auto-generated method stub // log.info(" + insertKakaoGroupDataTb_advc kakaoVO :: \n[{}]", kakaoVO.toString());; // log.info(" + insertKakaoGroupDataTb_advc kakaoSendAdvcVOList :: \n[{}]", sendVO.toString()); sendVO.setTemplateContent(kakaoVO.getTemplateContent()); sendVO.setMsgGroupCnt(Integer.toString(instCnt)); sendVO.setReserveYn(kakaoVO.getReserveYn()); sendVO.setBefCash(priceAndPoint.getBefCash(sendVO.getUserId())); sendVO.setBefPoint(priceAndPoint.getBefPoint(sendVO.getUserId())); sendVO.setAdFlag(kakaoVO.getAdFlag()); Float eachPrice = Float.parseFloat(sendVO.getEachPrice()); Float totPrice = eachPrice * instCnt; sendVO.setTotPrice(String.format("%.1f", totPrice)); sendVO.setAtDelayYn(kakaoVO.getAtDelayYn()); sendVO.setBizKakaoResendOrgnlTxt(kakaoVO.getSubMsgTxt()); sendVO.setBizKakaoResendType(sendVO.getSubMsgType()); sendVO.setBizKakaoImageType(kakaoVO.getImageType()); kakaoAlimTalkDAO.insertKakaoGroupDataTb_advc(sendVO); } }