package itn.com.cmm.util; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import itn.com.cmm.MjonMsgSendVO; import itn.let.mail.service.StatusResponse; import itn.let.mjo.msg.service.MjonMsgVO; import itn.let.mjo.spammsg.web.ComGetSpamStringParser; import itn.let.module.base.PriceAndPoint; import lombok.extern.slf4j.Slf4j; /** * * @author : 이호영 * @fileName : MsgSendUtils.java * @date : 2024.09.23 * @description : 메세지발송 데이터 다루는 유틸 * =========================================================== * DATE AUTHOR NOTE * ----------------------------------------------------------- * * 2024.09.23 이호영 최초 생성 * * * */ @Slf4j public final class MsgSendUtils { /** * @methodName : getSmsTxtBytes * @author : 이호영 * @date : 2024.09.23 * @description : sms 텍스트 바이트 계산 후 return; * @param smsTxt * @return * @throws UnsupportedEncodingException */ public static int getSmsTxtBytes(String smsTxt) throws UnsupportedEncodingException { //문자열 길이 체크 해주기 int smsBytes = 0; //문자 바이트 계산에 필요한 캐릭터 셋 : 한글 2Byte로 계산 String charset = "euc-kr"; if(StringUtils.isNotEmpty(smsTxt)) { String smsCont = smsTxt.replace("\r\n", "\n"); smsBytes = smsCont.getBytes(charset).length; } log.info(" + smsBytes :: [{}]", smsBytes); return smsBytes; } /** * @methodName : getMsgType * @author : 이호영 * @date : 2024.09.23 * @description : msgType 재정의 * @param mjonMsgVO * @param smsTxtByte * @return * @throws UnsupportedEncodingException */ public static String getMsgTypeWithByteValidation(MjonMsgSendVO sendVO, String p_smsTxt) throws UnsupportedEncodingException { int smsTxtByte = getSmsTxtBytes(p_smsTxt); String msgType = "4"; // // 내문자저장함에 저장 후 문자를 발송하는 경우 문자 타입이 숫자가 아닌 문자로 넘어와서 변경 처리함 // if ("P".equals(msgType) || "L".equals(msgType)) { // msgType = "6"; // } else if ("S".equals(msgType)) { // msgType = "4"; // } // 그림 이미지가 첨부된 경우 장문으로 설정 if (StringUtils.isNotEmpty(sendVO.getFilePath1())) { msgType = "6"; } else if (smsTxtByte > 2000) { // 2000 Byte를 초과할 경우 에러 처리 (이 부분은 호출부에서 검사하도록 유지할 수도 있음) return "INVALID"; // 이 값은 호출부에서 에러 처리를 하도록 활용할 수 있습니다. } else if (smsTxtByte > 90) { // 90Byte 초과 시 장문으로 설정 msgType = "6"; } return msgType; } public static float getValidPrice(Float personalPrice, Float defaultPrice) { return (personalPrice != null && personalPrice > 0) ? personalPrice : defaultPrice; } /** * @methodName : determinePriceByMsgType * @author : 이호영 * @date : 2024.09.24 * @description : 문자 메시지 타입에 따라 단가를 결정하는 메서드 * @param mjonMsgVO * @param shortPrice * @param longPrice * @param picturePrice * @param picture2Price * @param picture3Price * @return */ public static float determinePriceByMsgType(MjonMsgVO mjonMsgVO, float shortPrice, float longPrice, float picturePrice, float picture2Price, float picture3Price) { float price = 0; String msgType = mjonMsgVO.getMsgType(); if ("4".equals(msgType)) { price = shortPrice; } else if ("6".equals(msgType)) { // 파일 첨부 여부에 따라 장문 단가 설정 if (mjonMsgVO.getFileName3() != null) { price = picture3Price; } else if (mjonMsgVO.getFileName2() != null) { price = picture2Price; } else if (mjonMsgVO.getFileName1() != null) { price = picturePrice; } else { price = longPrice; } } return price; } /** * @methodName : isReplacementRequired * @author : 이호영 * @date : 2024.11.12 * @description : 치환데이터가 있는지 확인 * @param mjonMsgVO * @return */ public static boolean isRepleasYN(MjonMsgVO mjonMsgVO) { // 치환 구문 패턴 리스트 String[] placeholders = {"\\[\\*이름\\*\\]", "\\[\\*1\\*\\]", "\\[\\*2\\*\\]", "\\[\\*4\\*\\]", "\\[\\*3\\*\\]"}; for (String placeholder : placeholders) { Pattern pattern = Pattern.compile(placeholder); Matcher matcher = pattern.matcher(mjonMsgVO.getSmsTxt()); // 해당 패턴이 존재하면 true 반환 if (matcher.find()) { return true; } } return false; } /** * @methodName : populateReplacementLists * @author : 이호영 * @date : 2024.09.26 * @description : 배열에 데이터를 채우는 메서드 * @param mjonMsgVO * @param lists * @param statusResponse * @return * @throws Exception */ public static Boolean populateSendLists(MjonMsgVO mjonMsgVO, List mjonMsgSendListVO, StatusResponse statusResponse, List resultSpamTxt) throws Exception{ log.info(" :: populateSendLists :: "); int spamChkSize = getSpamChkSize(mjonMsgSendListVO.size()); int sampleCounter = 0; String smsTxtTemp = mjonMsgVO.getSmsTxt(); // 치환 구문과 필드 getter 매핑 Map> placeholders = new HashMap<>(); placeholders.put("[*이름*]", MjonMsgSendVO::getName); placeholders.put("[*1*]", MjonMsgSendVO::getRep1); placeholders.put("[*2*]", MjonMsgSendVO::getRep2); placeholders.put("[*3*]", MjonMsgSendVO::getRep3); placeholders.put("[*4*]", MjonMsgSendVO::getRep4); boolean hasPerformedSpamCheck = false; // 치환 문자가 없는 경우, 스팸 체크가 한 번만 수행되도록 제어 boolean hasPerformedMsgType = false; // 치환 문자가 없는 경우, 스팸 체크가 한 번만 수행되도록 제어 Boolean replaceYN = getReplaceYN(smsTxtTemp); String msgTypeResult = null; for (MjonMsgSendVO sendVO : mjonMsgSendListVO) { String smsTxt = smsTxtTemp; // 치환 문자면 if(replaceYN) { // 각 치환 구문을 확인하고 치환할 값이 없으면 오류 반환 for (Map.Entry> entry : placeholders.entrySet()) { String placeholder = entry.getKey(); String value = entry.getValue().apply(sendVO); if (smsTxtTemp.contains(placeholder)) { if (StringUtils.isEmpty(value)) { statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "치환 문구중 " + placeholder + " 데이터가 없습니다."); return false; } smsTxt = smsTxtTemp.replace(placeholder, value); } } } String smsSpamChkTxt = mjonMsgVO.getSmsTxt().replaceAll(String.valueOf((char) 13), ""); // 스팸문자 체크 // 치환문자가 아닐 경우 if (!replaceYN && !hasPerformedSpamCheck) { checkSpamAndSetStatus(mjonMsgVO, smsSpamChkTxt, resultSpamTxt); hasPerformedSpamCheck = true; // 스팸 체크가 한 번만 수행되도록 설정 // 치환 문자가 있는 경우에는 spamChkSize 카운트까지만 수행 } else if (replaceYN && sampleCounter < spamChkSize && !"Y".equals(mjonMsgVO.getSpamStatus())) { checkSpamAndSetStatus(mjonMsgVO, smsSpamChkTxt, resultSpamTxt); sampleCounter++; } log.info(" ++ smsTxt:: [{}]", smsTxt); sendVO.setSmsTxt(smsTxt); // 이미지 셋팅 setImagePathsForMsgSendVO(mjonMsgVO, sendVO); // msgType 셋팅 및 문자열 체크 log.info(" + smsTxt :: [{}]", smsTxt); if (!replaceYN && !hasPerformedMsgType) { log.info(" 치환 X "); // byte 체크와 msgType 구하기 msgTypeResult = getMsgTypeWithByteValidation(sendVO, smsTxt); if ("INVALID".equals(msgTypeResult)) { statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "문자 치환 후 전송 문자 길이를 초과하였습니다."); return false; } hasPerformedMsgType = true; // 스팸 체크가 한 번만 수행되도록 설정 }else if(replaceYN){ log.info(" 치환 O "); // byte 체크와 msgType 구하기 msgTypeResult = getMsgTypeWithByteValidation(sendVO, smsTxt); if ("INVALID".equals(msgTypeResult)) { statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "문자 치환 후 전송 문자 길이를 초과하였습니다."); return false; } } sendVO.setMsgType(msgTypeResult); } return true; } private static void setImagePathsForMsgSendVO(MjonMsgVO mjonMsgVO, MjonMsgSendVO sendVO) { int fileCount = Integer.parseInt(mjonMsgVO.getFileCnt()); switch (fileCount) { case 3: sendVO.setFilePath3(mjonMsgVO.getFileName3()); case 2: sendVO.setFilePath2(mjonMsgVO.getFileName2()); case 1: sendVO.setFilePath1(mjonMsgVO.getFileName1()); break; default: // fileCount가 0이거나 설정할 파일이 없는 경우 break; } sendVO.setFileCnt(mjonMsgVO.getFileCnt()); } private static void checkSpamAndSetStatus(MjonMsgVO mjonMsgVO, String personalizedSmsTxt, List resultSpamTxt) throws Exception { String resultParser = ComGetSpamStringParser.getSpamTextParse(personalizedSmsTxt).trim(); int spmCnt = 0; for (String spmTxt : resultSpamTxt) { String parserStr = ComGetSpamStringParser.getSpamTextParse(spmTxt).trim(); if (resultParser.contains(parserStr)) { spmCnt++; } } if (spmCnt > 0) { // 스팸 문자가 포함된 경우 System.out.println("++++++++++++++ spam smsTxt ::: " + resultParser); mjonMsgVO.setSpamStatus("Y"); }else {mjonMsgVO.setSpamStatus("N");} } private static Boolean getReplaceYN(String smsTxtTemplate) {// 여러 치환 구문이 포함된 정규식 패턴 Boolean replaceYN = false; Pattern pattern = Pattern.compile("\\[\\*이름\\*\\]|\\[\\*1\\*\\]|\\[\\*2\\*\\]|\\[\\*3\\*\\]|\\[\\*4\\*\\]"); Matcher matcher = pattern.matcher(smsTxtTemplate); // 정규식 패턴에 해당하는 치환 구문이 존재하는지 확인 if (matcher.find()) { replaceYN = true; } return replaceYN; } /** * @methodName : getSpamChkSize * @author : 이호영 * @date : 2024.11.13 * @description : 수신자 건수별로 스팸체크하는 갯수 설정 * @param size * @return */ private static int getSpamChkSize(int size) { int chkSize = 1; // 기본 샘플 크기 // 수신자 수에 따른 샘플 크기 결정 if (size > 100 && size <= 1000) { chkSize = 10; } else if (size > 1000) { chkSize = 50; } return chkSize; } /** * 특정 플레이스홀더를 리스트에서 가져온 값으로 치환합니다. * * @param smsTxt 문자 내용 * @param placeholder 치환할 플레이스홀더 (예: [*이름*]) * @param list 치환할 데이터 리스트 * @param index 현재 인덱스 * @return 치환된 문자 내용 */ private static String replacePlaceholder(String smsTxt, String placeholder, String[] list, int index) { if (smsTxt.contains(placeholder)) { if (list.length > index && StringUtil.isNotEmpty(list[index])) { smsTxt = smsTxt.replaceAll(placeholder, StringUtil.getString(list[index].replaceAll("§", ","))); } else { smsTxt = smsTxt.replaceAll(placeholder, ""); } } return smsTxt; } // 배열 인덱스를 안전하게 접근하는 메서드 private static String getSafeValue(String[] array, int index) { return (array != null && array.length > index) ? array[index] : " "; } // 치환 처리에 특수문자 제거 로직이 포함된 메서드 private static String getReplacementValue(String[] array, int index, String placeholder) { if (array != null && array.length > index && StringUtil.isNotEmpty(array[index])) { return StringUtil.getString(array[index].replaceAll("§", ",")); } else { return " "; } } private static void statusResponseSet (StatusResponse statusResponse, HttpStatus httpStatus, String msg ) { statusResponse.setStatus(httpStatus); statusResponse.setMessage(msg); } public static StatusResponse validateFilesForMessageSending(int fileCount, MjonMsgVO mjonMsgVO) { if (fileCount > 0) { boolean allFilesAreNull = Stream .of(mjonMsgVO.getFileName1(), mjonMsgVO.getFileName2(), mjonMsgVO.getFileName3()) .allMatch(Objects::isNull); if (allFilesAreNull) { return new StatusResponse(HttpStatus.NO_CONTENT, "문자 메세지 이미지 추가에 오류가 발생하여 문자 발송이 취소 되었습니다."); } } return null; // 유효성 검사를 통과한 경우 } /** * @methodName : validateReplacementData * @author : 이호영 * @date : 2024.09.25 * @description : 치환문자가 사용될 때 데이터가 올바른지 확인하는 메서드 * @param mjonMsgVO * @param list * @return boolean */ /* * public static boolean validateReplacementData(MjonMsgVO mjonMsgVO, * List list) { // 치환 구문과 필드 getter 매핑 Map> placeholders = new HashMap<>(); * placeholders.put("\\[\\*이름\\*\\]", MjonMsgSendVO::getName); * placeholders.put("\\[\\*1\\*\\]", MjonMsgSendVO::getRep1); * placeholders.put("\\[\\*2\\*\\]", MjonMsgSendVO::getRep2); * placeholders.put("\\[\\*3\\*\\]", MjonMsgSendVO::getRep3); * placeholders.put("\\[\\*4\\*\\]", MjonMsgSendVO::getRep4); * * // smsTxt 에서 필요한 치환 구문이 포함되어 있는지 확인 String smsTxt = mjonMsgVO.getSmsTxt(); * for (Map.Entry> entry : * placeholders.entrySet()) { if * (Pattern.compile(entry.getKey()).matcher(smsTxt).find()) { // 해당 치환 구문이 존재할 * 경우 모든 수신자에서 필드 값 확인 for (MjonMsgSendVO recipient : list) { if * (StringUtils.isEmpty(entry.getValue().apply(recipient))) { return false; // * 데이터가 없는 경우 } } } } // 모든 치환 구문이 유효한 데이터를 가지고 있으면 true 반환 return true; } */ // 배열이 비어있는지 확인하는 메서드 public static boolean isEmpty(String[] array) { return array == null || array.length == 0; } }