diff --git a/src/main/java/com/itn/mjonApi/cmn/apiServer/ApiService.java b/src/main/java/com/itn/mjonApi/cmn/apiServer/ApiService.java index 7ac751c..04d512f 100644 --- a/src/main/java/com/itn/mjonApi/cmn/apiServer/ApiService.java +++ b/src/main/java/com/itn/mjonApi/cmn/apiServer/ApiService.java @@ -3,14 +3,19 @@ package com.itn.mjonApi.cmn.apiServer; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.itn.mjonApi.cmn.domain.StatusResponse; import com.itn.mjonApi.cmn.domain.biz.template.BizTemplateRequest; import com.itn.mjonApi.cmn.domain.biz.template.detail.TemplateDetailResponse; import com.itn.mjonApi.cmn.domain.biz.template.list.TemplateListResponse; +import com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain.MsgFtRequestVO; import com.itn.mjonApi.mjon.api.msg.send.mapper.domain.MjonResponseVO; import lombok.extern.slf4j.Slf4j; import org.springframework.http.*; import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; +import org.springframework.web.multipart.MultipartFile; /** @@ -126,4 +131,36 @@ public class ApiService { return returnEntity; } + public StatusResponse postMultipartForEntity(String url, MsgFtRequestVO msgFtRequestVO, MultipartFile templateImage) { + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + + MultiValueMap body = new LinkedMultiValueMap<>(); + + // 파일 추가 + body.add("templateImage", templateImage.getResource()); + + // MsgFtRequestVO 필드들을 kakaoVO에 맞게 매핑 + if (msgFtRequestVO.getSendKind() != null) { + body.add("sendKind", msgFtRequestVO.getSendKind()); + } + if (msgFtRequestVO.getTemplateCode() != null) { + body.add("templateCode", msgFtRequestVO.getTemplateCode()); + } + + + HttpEntity> requestEntity = new HttpEntity<>(body, headers); + + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.POST, + requestEntity, + StatusResponse.class + ); + + log.info("Upload response :: [{}]", response.getBody()); + return response.getBody(); + + } } diff --git a/src/main/java/com/itn/mjonApi/cmn/domain/StatusResponse.java b/src/main/java/com/itn/mjonApi/cmn/domain/StatusResponse.java new file mode 100644 index 0000000..0808675 --- /dev/null +++ b/src/main/java/com/itn/mjonApi/cmn/domain/StatusResponse.java @@ -0,0 +1,41 @@ +package com.itn.mjonApi.cmn.domain; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.springframework.http.HttpStatus; + +/* + * • 1XX : 조건부 응답 + * • 2XX : 성공 + * • 3XX : 리다이렉션 완료 + * • 4XX : 요청 오류 + * • 500 : 서버 오류 + * + * 참고 : https://km0830.tistory.com/33 + * + * ====== 자주 사용하는 코드 ===== + * 200 : Ok : 서버가 클라이언트의 요청을 성공적으로 처리, 웹 페이지에서는 페이지 요청이 정상적으로 완료 (Ok) + * 400 : Bad Request : 잘못 요청 (Bad Request) + * 401 : Unauthorized : 권한 없음, 예를 들면, 로그인 페이지가 필요한 페이지를 로그인 없이 접속하려는 경우 반환되는 코드 (인증 실패) (Unauthorized) + * + * */ +@Getter +@Setter +@NoArgsConstructor +@ToString +public class StatusResponse { + + private HttpStatus status; + + private String message; + + private Object object; + + private Object apiReturn; + + private String messageTemp; + +// private String timestamp; +} diff --git a/src/main/java/com/itn/mjonApi/cmn/model/Price.java b/src/main/java/com/itn/mjonApi/cmn/model/Price.java index ffc91a9..be3a6a8 100644 --- a/src/main/java/com/itn/mjonApi/cmn/model/Price.java +++ b/src/main/java/com/itn/mjonApi/cmn/model/Price.java @@ -2,6 +2,7 @@ package com.itn.mjonApi.cmn.model; import com.itn.mjonApi.mjon.api.msg.inqry.mapper.PriceMapper; import com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain.PriceVO; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.math.BigDecimal; @@ -19,6 +20,7 @@ import java.util.Map; * 2023-07-03 hylee 최초 생성 */ +@Slf4j @Component public class Price { @@ -29,48 +31,88 @@ public class Price { double mberMoney = priceMapper.selectMberMoney(mberId); //시스템 단가 변수 - double sys_shortPrice = 0.0f; - double sys_longPrice = 0.0f; - double sys_picturePrice = 0.0f; + double sys_shortPrice = 0.0f; + double sys_longPrice = 0.0f; + double sys_picturePrice = 0.0f; + double sys_kakaoAtPrice = 0.0f; + double sys_kakaoFtPrice = 0.0f; + double sys_kakaoFtImgPrice = 0.0f; + double sys_kakaoFtWideImgPrice = 0.0f; //최종 단가 변수 - double shortPrice = 0.0f; - double longPrice = 0.0f; - double picturePrice = 0.0f; + double shortPrice = 0.0f; + double longPrice = 0.0f; + double picturePrice = 0.0f; + double kakaoAtPrice = 0.0f; + double kakaoFtPrice = 0.0f; + double kakaoFtImgPrice = 0.0f; + double kakaoFtWideImgPrice = 0.0f; //1.시스템 기본 단가, 사용자 개인단가 정보 불러오기 Map priceMap = priceMapper.selectMberPriceInfo(mberId); //1-2.단가 계산을 위한 set - sys_shortPrice = Double.parseDouble(String.valueOf(priceMap.get("sysShortPrice"))); - sys_longPrice = Double.parseDouble(String.valueOf(priceMap.get("sysLongPrice"))); - sys_picturePrice = Double.parseDouble(String.valueOf(priceMap.get("sysPicturePrice"))); + sys_shortPrice = Double.parseDouble(String.valueOf(priceMap.get("sysShortPrice"))); + sys_longPrice = Double.parseDouble(String.valueOf(priceMap.get("sysLongPrice"))); + sys_picturePrice = Double.parseDouble(String.valueOf(priceMap.get("sysPicturePrice"))); + + sys_kakaoAtPrice = Double.parseDouble(String.valueOf(priceMap.get("sysKakaoAtPrice"))); + sys_kakaoFtPrice = Double.parseDouble(String.valueOf(priceMap.get("sysKakaoFtPrice"))); + sys_kakaoFtImgPrice = Double.parseDouble(String.valueOf(priceMap.get("sysKakaoFtImgPrice"))); + sys_kakaoFtWideImgPrice = Double.parseDouble(String.valueOf(priceMap.get("sysKakaoFtWideImgPrice"))); + + shortPrice = Double.parseDouble(String.valueOf(priceMap.get("shortPrice"))); longPrice = Double.parseDouble(String.valueOf(priceMap.get("longPrice"))); picturePrice = Double.parseDouble(String.valueOf(priceMap.get("picturePrice"))); + kakaoAtPrice = Double.parseDouble(String.valueOf(priceMap.get("kakaoAtPrice"))); + kakaoFtPrice = Double.parseDouble(String.valueOf(priceMap.get("kakaoFtPrice"))); + kakaoFtImgPrice = Double.parseDouble(String.valueOf(priceMap.get("kakaoFtImgPrice"))); + kakaoFtWideImgPrice = Double.parseDouble(String.valueOf(priceMap.get("kakaoFtWideImgPrice"))); + //1-3. 최종 단가 계산 shortPrice = shortPrice == 0.0f ? sys_shortPrice : shortPrice; longPrice = longPrice == 0.0f ? sys_longPrice : longPrice; picturePrice = picturePrice == 0.0f ? sys_picturePrice : picturePrice; + kakaoAtPrice = kakaoAtPrice == 0.0f ? sys_kakaoAtPrice : kakaoAtPrice; + kakaoFtPrice = kakaoFtPrice == 0.0f ? sys_kakaoFtPrice : kakaoFtPrice; + kakaoFtImgPrice = kakaoFtImgPrice == 0.0f ? sys_kakaoFtImgPrice : kakaoFtImgPrice; + kakaoFtWideImgPrice = kakaoFtWideImgPrice == 0.0f ? sys_kakaoFtWideImgPrice : kakaoFtWideImgPrice; + //2. 단가별 발송 가능건수 계산을위한 변수 set int shortSendPsbltEa = 0; int longSendPsbltEa = 0; int pictureSendPsbltEa = 0; + int kakaoAtSendPsbltEa = 0; + int kakaoFtSendPsbltEa = 0; + int kakaoFtImgSendPsbltEa = 0; + int kakaoFtWideImgSendPsbltEa = 0; + //2-1. 소수점 연산을 위한 BigDecimal Casting BigDecimal mberMoney_big = new BigDecimal(String.valueOf(mberMoney)); - BigDecimal shortPrice_big = new BigDecimal(String.valueOf(priceMap.get("sysShortPrice"))); - BigDecimal longPrice_big = new BigDecimal(String.valueOf(priceMap.get("sysLongPrice"))); - BigDecimal picturePrice_big = new BigDecimal(String.valueOf(priceMap.get("sysPicturePrice"))); + BigDecimal shortPrice_big = new BigDecimal(String.valueOf(shortPrice)); + BigDecimal longPrice_big = new BigDecimal(String.valueOf(longPrice)); + BigDecimal picturePrice_big = new BigDecimal(String.valueOf(picturePrice)); + + BigDecimal kakaoAtPrice_big = new BigDecimal(String.valueOf(kakaoAtPrice)); + BigDecimal kakaoFtPrice_big = new BigDecimal(String.valueOf(kakaoFtPrice)); + BigDecimal kakaoFtImgPrice_big = new BigDecimal(String.valueOf(kakaoFtImgPrice)); + BigDecimal kakaoFtWideImgPrice_big = new BigDecimal(String.valueOf(kakaoFtWideImgPrice)); //2-2. mberMoney가 0일경우 제외 if(mberMoney_big.compareTo(BigDecimal.ZERO) != 0) { - shortSendPsbltEa = mberMoney_big.divide(shortPrice_big, BigDecimal.ROUND_DOWN).intValue(); - longSendPsbltEa = mberMoney_big.divide(longPrice_big, BigDecimal.ROUND_DOWN).intValue(); - pictureSendPsbltEa = mberMoney_big.divide(picturePrice_big, BigDecimal.ROUND_DOWN).intValue(); + shortSendPsbltEa = mberMoney_big.divide(shortPrice_big, BigDecimal.ROUND_DOWN).intValue(); + longSendPsbltEa = mberMoney_big.divide(longPrice_big, BigDecimal.ROUND_DOWN).intValue(); + pictureSendPsbltEa = mberMoney_big.divide(picturePrice_big, BigDecimal.ROUND_DOWN).intValue(); + + kakaoAtSendPsbltEa = mberMoney_big.divide(kakaoAtPrice_big, BigDecimal.ROUND_DOWN).intValue(); + kakaoFtSendPsbltEa = mberMoney_big.divide(kakaoFtPrice_big, BigDecimal.ROUND_DOWN).intValue(); + kakaoFtImgSendPsbltEa = mberMoney_big.divide(kakaoFtImgPrice_big, BigDecimal.ROUND_DOWN).intValue(); + kakaoFtWideImgSendPsbltEa = mberMoney_big.divide(kakaoFtWideImgPrice_big, BigDecimal.ROUND_DOWN).intValue(); } //result set @@ -78,11 +120,28 @@ public class Price { .shortPrice(shortPrice) .longPrice(longPrice) .picturePrice(picturePrice) + + .kakaoAtPrice(kakaoAtPrice) + .kakaoFtPrice(kakaoFtPrice) + .kakaoFtImgPrice(kakaoFtImgPrice) + .kakaoFtWideImgPrice(kakaoFtWideImgPrice) + + .shortSendPsbltEa(shortSendPsbltEa) .longSendPsbltEa(longSendPsbltEa) .pictureSendPsbltEa(pictureSendPsbltEa) + + .kakaoAtSendPsbltEa(kakaoAtSendPsbltEa) + .kakaoFtSendPsbltEa(kakaoFtSendPsbltEa) + .kakaoFtImgSendPsbltEa(kakaoFtImgSendPsbltEa) + .kakaoFtWideImgSendPsbltEa(kakaoFtWideImgSendPsbltEa) + .mberMoney(mberMoney) .build(); + + log.info(" + priceVO :: [{}]", priceVO); + + return priceVO; } diff --git a/src/main/java/com/itn/mjonApi/cmn/msg/StatMsg.java b/src/main/java/com/itn/mjonApi/cmn/msg/StatMsg.java index d2ed520..def54b9 100644 --- a/src/main/java/com/itn/mjonApi/cmn/msg/StatMsg.java +++ b/src/main/java/com/itn/mjonApi/cmn/msg/StatMsg.java @@ -51,6 +51,15 @@ public enum StatMsg { , STAT_2041("2041","타이틀 데이터 오류") , STAT_2042("2042","대체문자 데이터 오류") + , STAT_2050("2050","지원하지 않는 이미지 형식입니다.") + , STAT_2051("2051","이미지 용량은 5MB 이내여야 합니다.") + , STAT_2052("2052","이미지를 읽을 수 없습니다.") + , STAT_2053("2053","지원하지 않는 이미지 형식입니다.") + , STAT_2054("2054","이미지 가로폭인 500px 미만입니다.") + , STAT_2055("2055","유효한 이미지 파일이 없습니다.") + , STAT_2056("2056","비율 2:1 이상 또는 3:4 이하만 허용됩니다.") + , STAT_2057("2057","대체문자(MMS) 이미지는 10MB를 초과할 수 없습니다.") + , STAT_2099("2099","기타 시스템 오류") diff --git a/src/main/java/com/itn/mjonApi/etc/ganpandaum/service/impl/GdServiceImpl.java b/src/main/java/com/itn/mjonApi/etc/ganpandaum/service/impl/GdServiceImpl.java index 8efc7b2..145dcc3 100644 --- a/src/main/java/com/itn/mjonApi/etc/ganpandaum/service/impl/GdServiceImpl.java +++ b/src/main/java/com/itn/mjonApi/etc/ganpandaum/service/impl/GdServiceImpl.java @@ -7,7 +7,6 @@ import com.itn.mjonApi.util.email.EmailVO; import com.itn.mjonApi.util.email.SendMail; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; -import org.jsoup.Jsoup; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @@ -16,7 +15,6 @@ import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.time.LocalDate; /** * packageName : com.itn.mjonApi.etc.ganpandaum.service.impl @@ -70,33 +68,16 @@ public class GdServiceImpl implements GdService { SendMail sMail = new SendMail(); try { - emailContent = Jsoup.connect(GANPANDAUP_ESTIMATE_TEMPLATE_URL) - .data("query", "Java") - .userAgent("Mozilla") - .cookie("auth", "token") - .timeout(3000) - .post() - .toString(); - // ./src/main/resources/templates/estimate.html - emailContent = emailContent - .replace("[[_Company_]]", gdVO.getGdCompany()) - .replace("[[_Name_]]", gdVO.getGdName()) - .replace("[[_Phone_]]", gdVO.getGdPhone()) - .replace("[[_Email_]]", gdVO.getGdEmail()) - .replace("[[_Addr_]]", gdVO.getGdAddr()) - .replace("[[_Content_]]", gdVO.getGdContent()) - ; // 메일 첨부파일을 위한 절대경로 // 메일 제목 - String mailTitle = "[간판다움 견적의뢰] "+gdVO.getGdName()+"["+gdVO.getGdCompany()+"]님의 견적 의뢰입니다._"+LocalDate.now(); sMail.itnSendMail( EmailVO.builder() - .title(mailTitle) - .contents(emailContent) + .title("현재달 월급명세서 전달드립니다.") + .contents("안녕하세요 귀하에 노고에 감사드립니다. 비밀번호는 생년워일입니다.") .fileInfo(p_file) .atch_file_name(fileNm) .send_to(GANPANDAUP_RECEIVER_EMAIL) diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/MsgAtRequestVO.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/MsgAtRequestVO.java index e5a7d24..22db93b 100644 --- a/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/MsgAtRequestVO.java +++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/MsgAtRequestVO.java @@ -1,6 +1,5 @@ package com.itn.mjonApi.mjon.api.kakao.at.send.mapper.domain; -import com.itn.mjonApi.cmn.domain.SendRequestCmnVO; import lombok.*; import java.io.Serializable; @@ -44,7 +43,7 @@ public class MsgAtRequestVO implements Serializable { private String test_yn; - private List varListMap = new ArrayList<>(); + private List varListMap = new ArrayList<>(); diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/VarListMapVO.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/VarAtListMapVO.java similarity index 96% rename from src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/VarListMapVO.java rename to src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/VarAtListMapVO.java index 76a4e96..fc77cb3 100644 --- a/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/VarListMapVO.java +++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/VarAtListMapVO.java @@ -8,7 +8,7 @@ import lombok.ToString; @Getter @Setter @ToString -public class VarListMapVO { +public class VarAtListMapVO { /** diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/service/AtIndexedParameterParserService.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/service/AtIndexedParameterParserService.java new file mode 100644 index 0000000..d952d7f --- /dev/null +++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/service/AtIndexedParameterParserService.java @@ -0,0 +1,90 @@ +package com.itn.mjonApi.mjon.api.kakao.at.send.service; + +import com.itn.mjonApi.mjon.api.kakao.at.send.mapper.domain.MsgAtRequestVO; +import com.itn.mjonApi.mjon.api.kakao.at.send.mapper.domain.VarAtListMapVO; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * fileName : IndexedParameterParserService.java + * author : hylee + * date : 2025-08-18 + * description : 알림톡 인덱스된 파라미터 파싱 서비스 + * parseIndexedParametersFromRequest 메서드의 아키텍처 분리를 위해 생성 + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2025-08-18 hylee 최초 생성 + */ +@Service +public class AtIndexedParameterParserService { + + // 정규식 패턴을 static final로 캐싱하여 성능 최적화 + private static final Pattern INDEX_PATTERN = + Pattern.compile("^(callTo|templateContent|templateTitle|subMsgTxt)_(\\d+)$"); + + /** + * HttpServletRequest에서 동적으로 인덱스된 파라미터들을 파싱하여 VarListMapVO 리스트로 변환 + * callTo_1~100, templateContent_1~100, templateTitle_1~100, subMsgTxt_1~100 등을 동적 처리 + * + * @param request HTTP 요청 객체 + * @return 파싱된 VarListMapVO 리스트 + */ + public List parseIndexedParameters(MsgAtRequestVO msgAtRequestVO, HttpServletRequest request) { + List varListMap = new ArrayList<>(); + + // 모든 파라미터 맵 가져오기 + Map parameterMap = request.getParameterMap(); + + // 인덱스별 데이터를 저장할 맵 + Map> indexedDataMap = new HashMap<>(); + + + String subMsgSendYn = msgAtRequestVO.getSubMsgSendYn(); + + // 모든 파라미터를 순회하며 인덱스된 파라미터 찾기 + for (Map.Entry entry : parameterMap.entrySet()) { + String paramName = entry.getKey(); + String[] paramValues = entry.getValue(); + + Matcher matcher = INDEX_PATTERN.matcher(paramName); + if (matcher.matches() && paramValues.length > 0) { + String fieldName = matcher.group(1); // callTo, templateContent 등 + int index = Integer.parseInt(matcher.group(2)); // 1, 2, 3 등 + String value = paramValues[0]; // 파라미터 값 + + // 인덱스별 데이터 맵에 저장 + indexedDataMap.computeIfAbsent(index, k -> new HashMap<>()).put(fieldName, value); + } + } + + // 인덱스 순서대로 VarListMapVO 생성 + List sortedIndexes = new ArrayList<>(indexedDataMap.keySet()); + Collections.sort(sortedIndexes); + + for (Integer index : sortedIndexes) { + Map dataMap = indexedDataMap.get(index); + + VarAtListMapVO vo = new VarAtListMapVO(); + vo.setCallToList(dataMap.get("callTo")); + vo.setTemplateContent(dataMap.get("templateContent")); + vo.setTemplateTitle(dataMap.get("templateTitle")); + + // 치환 데이터는 subMsgSendYn 이 Y일때 + if("Y".equals(subMsgSendYn)){ + vo.setSubMsgTxt(dataMap.get("subMsgTxt")); + } + + // 필수 필드 중 하나라도 있으면 리스트에 추가 + if (vo.getCallToList() != null || vo.getTemplateContent() != null || vo.getTemplateTitle() != null) { + varListMap.add(vo); + } + } + + return varListMap; + } +} \ No newline at end of file diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/service/AtParameterProcessingService.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/service/AtParameterProcessingService.java index 69e5650..09df0a9 100644 --- a/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/service/AtParameterProcessingService.java +++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/service/AtParameterProcessingService.java @@ -8,7 +8,7 @@ import com.itn.mjonApi.cmn.msg.RestResponse; import com.itn.mjonApi.mjon.api.kakao.at.inqry.mapper.domain.MjKakaoProfileInfoVO; import com.itn.mjonApi.mjon.api.kakao.at.inqry.service.InqryService; import com.itn.mjonApi.mjon.api.kakao.at.send.mapper.domain.MsgAtRequestVO; -import com.itn.mjonApi.mjon.api.kakao.at.send.mapper.domain.VarListMapVO; +import com.itn.mjonApi.mjon.api.kakao.at.send.mapper.domain.VarAtListMapVO; import com.itn.mjonApi.util.MunjaUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -35,7 +35,7 @@ import java.util.List; public class AtParameterProcessingService { @Autowired - private IndexedParameterParserService indexedParameterParserService; + private AtIndexedParameterParserService indexedParameterParserService; @Autowired private InqryService inqryService; @@ -63,11 +63,11 @@ public class AtParameterProcessingService { if (STAT_2030 != null) return STAT_2030; // 파싱 로직을 IndexedParameterParserService에 위임 - List parsedList = indexedParameterParserService.parseIndexedParameters(msgAtRequestVO, request); + List parsedList = indexedParameterParserService.parseIndexedParameters(msgAtRequestVO, request); // 파싱된 각 VO에 대해 검증 수행 - for (VarListMapVO vo : parsedList) { - String validationError = MunjaUtil.kakaoCmnValidate(vo, msgAtRequestVO.getSubMsgSendYn()); + for (VarAtListMapVO vo : parsedList) { + String validationError = MunjaUtil.kakaoAtValidate(vo, msgAtRequestVO.getSubMsgSendYn()); if (StringUtils.isNotEmpty(validationError)) { return validationError; // 검증 실패 시 오류 코드 반환 diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/web/SendAtRestController.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/web/SendAtRestController.java index d195bca..de9848d 100644 --- a/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/web/SendAtRestController.java +++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/web/SendAtRestController.java @@ -48,25 +48,8 @@ public class SendAtRestController { // https://smartsms.aligo.in/alimapi.html - return ResponseEntity.ok().body(sendAtService.sendAtData(msgAtRequestVO, request)); -// return ResponseEntity.ok().body(new RestResponse(msgAtRequestVO)); } - /** - * - * @param msgsRequestVO - * @description [문자 발송] 다른 내용으로 여려명에게 보냄 - * @return - * @throws Exception - */ -/* @PostMapping("/api/send/sendMsgs") - public ResponseEntity sendMsgs(MsgsRequestVO msgsRequestVO) throws Exception { - return ResponseEntity.ok().body(sendAtService.sendMsgsData_advc(msgsRequestVO)); - }*/ - - - - } diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/FtButtonVO.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/FtButtonVO.java new file mode 100644 index 0000000..5625dd6 --- /dev/null +++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/FtButtonVO.java @@ -0,0 +1,19 @@ +package com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain; + +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@ToString +public class FtButtonVO { + private String name; + private String linkType; // AC, DS, WL, AL, BK, MD + private String linkTypeName; // 채널 추가, 배송조회, 웹링크, 앱링크, 봇키워드, 메시지전달 + private String linkMo; // 모바일 링크 + private String linkPc; // PC 링크 + private String linkIos; // iOS Scheme + private String linkAnd; // Android Scheme +} diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/MjonFtResponseVO.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/MjonFtResponseVO.java new file mode 100644 index 0000000..3539c6a --- /dev/null +++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/MjonFtResponseVO.java @@ -0,0 +1,46 @@ +package com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.*; + +/** + * packageName : com.itn.mjonApi.cmn.msg + * fileName : mjonResponse + * author : hylee + * date : 2023-05-12 + * description : 문자온 프로젝트에서 받은 리턴값 + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-05-12 hylee 최초 생성 + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) // JSON에 있지만 VO에 없는 필드를 무시하고 무사히 역직렬화해 줌 +@ToString +public class MjonFtResponseVO { + + private String result; + private String message; + private String resultSts; // 전송결과 갯수 + private String resultBlockSts; // 수신거부 갯수 + private String msgGroupId; + private String afterCash; + private String msgType; + private String statCode; + + /** + * + * @param apiReturnNode + * @return ResponseEntity vo convert + * @throws JsonProcessingException + */ +// public static MjonAtResponseVO getMjonResponse(JsonNode apiReturnNode) throws JsonProcessingException { +// ObjectMapper objectMapper = new ObjectMapper(); +// return objectMapper.treeToValue(apiReturnNode, MjonAtResponseVO.class); +// } +} diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/MsgFtRequestVO.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/MsgFtRequestVO.java new file mode 100644 index 0000000..b93b919 --- /dev/null +++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/MsgFtRequestVO.java @@ -0,0 +1,90 @@ +package com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + + +/** + * fileName : MsgAtRequestVO.java + * author : hylee + * date : 2025-07-29 + * description : 알림톡 + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2025-07-29 hylee 최초 생성 + */ +@NoArgsConstructor +@AllArgsConstructor +@Builder +@ToString +@Getter +@Setter +public class MsgFtRequestVO implements Serializable { + + private static final long serialVersionUID = 1L; + + private String sendKind = "A"; + + private String mberId; // value = "사용자 ID", example = "goodgkdus" + + private String accessKey; // value = "Api Key", example = "0367a25ec370d1141898a0b9767103" + + private String senderKey; // 카카오 알림톡 채널ID + + private String templateCode; // 카카오 알림톡 템플릿 코드 + + private String callFrom; // value = "발신번호 :: 정책이 필요함", example = "01011112222" + + // 대체문자 여부 + private String subMsgSendYn; + + // 광고 여부 + private String adFlag; + + private String test_yn; + + + // 실제 업로드 파일(로그/직렬화 제외) + @JsonIgnore + @ToString.Exclude + private MultipartFile templateImage; + + @JsonIgnore + @ToString.Exclude + private MultipartFile subImage; + + // ====== ⬇️ AOP/DB 저장용(문자열/숫자만) 메타데이터 필드들 ====== + // template + private String templateImageName; + private String templateImageContentType; + private Long templateImageSize; // bytes + private Integer templateImageWidth; // px + private Integer templateImageHeight; // px + private String templateImageSha256; // hexdigest + private String templateImageSavedPath; // 서버 저장 경로(선택) + private String templateAtchFileId; // 내부 첨부ID(선택) + private String templateMsgType; + private String templateImageUrl; + + // sub +// private String subImageName; +// private String subImageContentType; +// private Long subImageSize; +// private Integer subImageWidth; +// private Integer subImageHeight; +// private String subImageSha256; +// private String subImageSavedPath; +// private String subAtchFileId; +// private String subMsgType; + + private List varListMap = new ArrayList<>(); + + + +} diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/VarFtListMapVO.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/VarFtListMapVO.java new file mode 100644 index 0000000..f9682fe --- /dev/null +++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/VarFtListMapVO.java @@ -0,0 +1,64 @@ +package com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain; + + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +@ToString +public class VarFtListMapVO { + + + /** + * @description : 수신자번호 + */ + private String phone; + + /** + * @description : 친구톡 내용 + */ + private String templateContent; + + /** + * @description : 치환문자 + */ + private String subMsgTxt; + + /** + * @description : 치환문자 이미지 + */ + private String filePath1; + + /** + * @description : 버튼 사용 + */ + private List buttons = new ArrayList<>(); + +// +// /** +// * @description : [*3*] - 치환문자 +// */ +// private String rep3; +// +// /** +// * @description : [*4*] - 치환문자 +// */ +// private String rep4; +// +// /** +// * @description : 제목 +// */ +// private String subject; +// +// /** +// * @description : 내용 +// */ +// private String message; + + +} diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/service/FtIndexedParameterParserService.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/service/FtIndexedParameterParserService.java new file mode 100644 index 0000000..95d751b --- /dev/null +++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/service/FtIndexedParameterParserService.java @@ -0,0 +1,146 @@ +package com.itn.mjonApi.mjon.api.kakao.ft.send.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain.FtButtonVO; +import com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain.MsgFtRequestVO; +import com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain.VarFtListMapVO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * fileName : IndexedParameterParserService.java + * author : hylee + * date : 2025-08-18 + * description : 알림톡 인덱스된 파라미터 파싱 서비스 + * parseIndexedParametersFromRequest 메서드의 아키텍처 분리를 위해 생성 + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2025-08-18 hylee 최초 생성 + */ +@Service +@Slf4j +public class FtIndexedParameterParserService { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + // 정규식 패턴을 static final로 캐싱하여 성능 최적화 + private static final Pattern INDEX_PATTERN = + Pattern.compile("^(callTo|templateContent|subMsgTxt|button)_(\\d+)$"); + + /** + * HttpServletRequest에서 동적으로 인덱스된 파라미터들을 파싱하여 VarListMapVO 리스트로 변환 + * callTo_1~100, templateContent_1~100, templateTitle_1~100, subMsgTxt_1~100 등을 동적 처리 + * + * @param request HTTP 요청 객체 + * @return 파싱된 VarListMapVO 리스트 + */ + public List parseIndexedParameters(MsgFtRequestVO msgAtRequestVO, HttpServletRequest request) { + List varListMap = new ArrayList<>(); + + // 모든 파라미터 맵 가져오기 + Map parameterMap = request.getParameterMap(); + + // 인덱스별 데이터를 저장할 맵 + Map> indexedDataMap = new HashMap<>(); + + + String subMsgSendYn = msgAtRequestVO.getSubMsgSendYn(); + + // 모든 파라미터를 순회하며 인덱스된 파라미터 찾기 + for (Map.Entry entry : parameterMap.entrySet()) { + String paramName = entry.getKey(); + String[] paramValues = entry.getValue(); + + Matcher matcher = INDEX_PATTERN.matcher(paramName); + if (matcher.matches() && paramValues.length > 0) { + String fieldName = matcher.group(1); // callTo, templateContent 등 + int index = Integer.parseInt(matcher.group(2)); // 1, 2, 3 등 + String value = paramValues[0]; // 파라미터 값 + + // 인덱스별 데이터 맵에 저장 + indexedDataMap.computeIfAbsent(index, k -> new HashMap<>()).put(fieldName, value); + } + } + + // 인덱스 순서대로 VarListMapVO 생성 + List sortedIndexes = new ArrayList<>(indexedDataMap.keySet()); + Collections.sort(sortedIndexes); + + for (Integer index : sortedIndexes) { + Map dataMap = indexedDataMap.get(index); + + VarFtListMapVO vo = new VarFtListMapVO(); + vo.setPhone(dataMap.get("callTo")); + vo.setTemplateContent(dataMap.get("templateContent")); + + // 치환 데이터는 subMsgSendYn 이 Y일때 + if("Y".equals(subMsgSendYn)){ + vo.setSubMsgTxt(dataMap.get("subMsgTxt")); + } + + // 버튼 데이터 처리 + String buttonData = dataMap.get("button"); + if (buttonData != null && !buttonData.trim().isEmpty()) { + try { + JsonNode buttonJson = objectMapper.readTree(buttonData); + JsonNode buttonArray = buttonJson.get("button"); + + List buttonList = new ArrayList<>(); + if (buttonArray != null && buttonArray.isArray()) { + for (JsonNode button : buttonArray) { + FtButtonVO ftButton = FtButtonVO.builder() + .name(getJsonText(button, "name")) + .linkType(getJsonText(button, "linkType")) + .linkTypeName(getJsonText(button, "linkTypeName")) + .linkMo(getJsonText(button, "linkMo")) + .linkPc(getJsonText(button, "linkPc")) + .linkIos(getJsonText(button, "linkIos")) + .linkAnd(getJsonText(button, "linkAnd")) + .build(); + buttonList.add(ftButton); + } + } + vo.setButtons(buttonList); + } catch (Exception e) { + log.error("버튼 JSON 파싱 실패 - index: {}, data: {}", index, buttonData, e); + vo.setButtons(new ArrayList<>()); + } + } + + // 필수 필드 중 하나라도 있으면 리스트에 추가 + if (vo.getPhone() != null || vo.getTemplateContent() != null) { + varListMap.add(vo); + } + + + + } + + return varListMap; + } + + /** + * JsonNode에서 안전하게 텍스트 값을 추출하는 헬퍼 메서드 + * + * @param node JSON 노드 + * @param fieldName 필드명 + * @return 텍스트 값 (null 또는 빈 값일 경우 null 반환) + */ + private String getJsonText(JsonNode node, String fieldName) { + if (node != null && node.has(fieldName)) { + JsonNode fieldNode = node.get(fieldName); + if (!fieldNode.isNull()) { + String text = fieldNode.asText(); + return text.isEmpty() ? null : text; + } + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/service/FtParameterProcessingService.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/service/FtParameterProcessingService.java new file mode 100644 index 0000000..5bd9384 --- /dev/null +++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/service/FtParameterProcessingService.java @@ -0,0 +1,125 @@ +package com.itn.mjonApi.mjon.api.kakao.ft.send.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.itn.mjonApi.cmn.domain.biz.template.BizTemplateRequest; +import com.itn.mjonApi.cmn.domain.biz.template.detail.TemplateDetailResponse; +import com.itn.mjonApi.cmn.msg.RestResponse; +import com.itn.mjonApi.mjon.api.kakao.at.inqry.mapper.domain.MjKakaoProfileInfoVO; +import com.itn.mjonApi.mjon.api.kakao.at.inqry.service.InqryService; +import com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain.MsgFtRequestVO; +import com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain.VarFtListMapVO; +import com.itn.mjonApi.util.MunjaUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; + +/** + * fileName : AtParameterProcessingService.java + * author : hylee + * date : 2025-08-18 + * description : 알림톡 파라미터 처리 서비스 + * MsgAtRequestVO의 비즈니스 로직을 담당 + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2025-08-18 hylee 최초 생성 + */ +@Slf4j +@Service +public class FtParameterProcessingService { + + @Autowired + private FtIndexedParameterParserService indexedParameterParserService; + + @Autowired + private InqryService inqryService; + + /** + * HttpServletRequest에서 동적으로 인덱스된 파라미터들을 파싱하고 검증하여 + * MsgAtRequestVO의 varListMap에 설정 + * + * @param msgFtRequestVO 요청 VO 객체 + * @param request HTTP 요청 객체 + * @return 검증 실패 시 오류 코드, 성공 시 null + */ + public String processIndexedParameters(MsgFtRequestVO msgFtRequestVO, HttpServletRequest request) { + + // 기존 varListMap 초기화 + msgFtRequestVO.setVarListMap(new ArrayList<>()); + + + // 채널ID 확인 + String STAT_2010 = this.validateSenderKey(msgFtRequestVO.getMberId(), msgFtRequestVO.getSenderKey()); + if (STAT_2010 != null) return STAT_2010; + + // 템플릿 코드 확인 +// String STAT_2030 = this.validateTemplateCode(msgFtRequestVO.getMberId(), msgFtRequestVO.getSenderKey(), msgFtRequestVO.getTemplateCode()); +// if (STAT_2030 != null) return STAT_2030; + + // 파싱 로직을 IndexedParameterParserService에 위임 + List parsedList = indexedParameterParserService.parseIndexedParameters(msgFtRequestVO, request); + + // 파싱된 각 VO에 대해 검증 수행 + for (VarFtListMapVO vo : parsedList) { + String validationError = MunjaUtil.kakaoFtValidate(vo, msgFtRequestVO.getSubMsgSendYn()); + + if (StringUtils.isNotEmpty(validationError)) { + return validationError; // 검증 실패 시 오류 코드 반환 + } + + // 검증 통과한 VO를 리스트에 추가 + msgFtRequestVO.getVarListMap().add(vo); + } + + return null; // 모든 검증 통과 + } + + private String validateTemplateCode(String mberId, String senderKey, String templateCode) { + try { + BizTemplateRequest request = BizTemplateRequest.builder() + .mberId(mberId) + .senderKey(senderKey) + .templateCode(templateCode) + .build(); + + RestResponse response = inqryService.getTemplateDetail(request); + JsonNode node = new ObjectMapper().valueToTree(response.getData()); + // 전체 출력 +// log.info("data 전체 :: {}", node.toPrettyString()); + // resultCode가 있으면 채널ID 오류 + inqryService.getTemplateDetail 참조 + if (node.has("resultCode")) { + return "STAT_"+node.get("resultCode").asText(); + } + + TemplateDetailResponse detail = (TemplateDetailResponse) + response.getData(); + + // 템플릿 상세 정보 활용 + log.info("template detail :: [{}]", detail); + return "200".equals(detail.getCode()) ? null : "STAT_2030"; + + } catch (Exception e) { + e.printStackTrace(); + return "STAT_2099"; + } + } + + public String validateSenderKey(String mberId, String senderKey) { + if (StringUtils.isEmpty(senderKey)) { + return "STAT_2010"; + } + List resultList = inqryService.getChnlId(mberId); + boolean ok = resultList.stream().anyMatch(p -> senderKey.equals(p.getSenderKey())); + return ok ? null : "STAT_2010"; + } + + + + +} \ No newline at end of file diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/service/SendFtService.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/service/SendFtService.java new file mode 100644 index 0000000..f401c2b --- /dev/null +++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/service/SendFtService.java @@ -0,0 +1,23 @@ +package com.itn.mjonApi.mjon.api.kakao.ft.send.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.itn.mjonApi.cmn.msg.RestResponse; +import com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain.MsgFtRequestVO; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; + +public interface SendFtService { + + +// RestResponse sendMsgData(MsgRequestVO msgRequestVO) throws Exception; +// +// RestResponse sendMsgData_advc(MsgRequestVO msgRequestVO) throws Exception; +// +// RestResponse sendMsgsData(MsgsRequestVO msgsRequestVO) throws Exception; +// +// RestResponse sendMsgsData_advc(MsgsRequestVO msgsRequestVO) throws Exception; + + RestResponse sendFtData(MsgFtRequestVO msgFtRequestVO, HttpServletRequest request) throws IOException, NoSuchAlgorithmException; +} diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/service/impl/SendFtServiceImpl.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/service/impl/SendFtServiceImpl.java new file mode 100644 index 0000000..5ef64e8 --- /dev/null +++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/service/impl/SendFtServiceImpl.java @@ -0,0 +1,137 @@ +package com.itn.mjonApi.mjon.api.kakao.ft.send.service.impl; + +import com.itn.mjonApi.cmn.apiServer.ApiService; +import com.itn.mjonApi.cmn.domain.StatusResponse; +import com.itn.mjonApi.cmn.msg.FailRestResponse; +import com.itn.mjonApi.cmn.msg.RestResponse; +import com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain.MsgFtRequestVO; +import com.itn.mjonApi.mjon.api.kakao.ft.send.service.FtParameterProcessingService; +import com.itn.mjonApi.mjon.api.kakao.ft.send.service.SendFtService; +import com.itn.mjonApi.mjon.api.kakao.utils.FtFileMetaUtil; +import com.itn.mjonApi.mjon.api.msg.inqry.mapper.PriceMapper; +import com.itn.mjonApi.mjon.api.msg.send.mapper.SendMapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.catalina.connector.Response; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; + + +@Slf4j +@Service +public class SendFtServiceImpl implements SendFtService { + + @Autowired + private FtParameterProcessingService ftParameterProcessingService; + + private ApiService apiService; + + @Autowired + SendMapper sendMapper; + + @Autowired + PriceMapper priceMapper; + + @Autowired + public SendFtServiceImpl(ApiService apiService) { + this.apiService = apiService; + } + + private static final String replaseStrList = "[*이름*],[*1*],[*2*],[*3*],[*4*]"; + + + /** + * @param + * @return + * @throws Exception 처리 중 예외 발생 가능 + * @date 2025-07-29 + * @Discription 치환없는 알림톡 데이터 + * @author hylee + */ + @Override + public RestResponse sendFtData(MsgFtRequestVO msgFtRequestVO, HttpServletRequest request) throws IOException, NoSuchAlgorithmException { + + if(StringUtils.isNotEmpty(msgFtRequestVO.getTest_yn())){ + // YF => 실패 테스트 데이터 +// return this._getTestMsgReturnData(msgRequestVO.getTest_yn()); + } + + + + if (FtFileMetaUtil.hasFile(msgFtRequestVO.getTemplateImage())) { + String code = FtFileMetaUtil.fillTemplateMeta(msgFtRequestVO); + if (StringUtils.isNotEmpty(code)) return new RestResponse(new FailRestResponse(code, "")); + } +// if (FtFileMetaUtil.hasFile(msgFtRequestVO.getSubImage())) { +// code = FtFileMetaUtil.fillSubMeta(msgFtRequestVO); +// if (StringUtils.isNotEmpty(code)) return new RestResponse(new FailRestResponse(code, "")); +// } + if (FtFileMetaUtil.hasFile(msgFtRequestVO.getTemplateImage())) { + String code = FtFileMetaUtil.fillTemplateMeta(msgFtRequestVO); + if (StringUtils.isNotEmpty(code)) return new RestResponse(new FailRestResponse(code, "")); + + // 메타데이터 처리 성공 후 업로드 API 호출 + try { + // Option 1: ApiService에 multipart 메소드 추가 + StatusResponse uploadResponse = apiService.postMultipartForEntity( + "/web/mjon/kakao/template/sendKakaoFriendsTemplateImageUploadAjax_advc.do", + msgFtRequestVO, + msgFtRequestVO.getTemplateImage() + ); + + log.info("전체 StatusResponse :: [{}]", uploadResponse); + log.info("object 필드만 :: [{}]", uploadResponse.getObject()); + + // 업로드 결과 처리 +// if ("OK".equals(uploadResponse.getResult())) { +// // 성공 처리 +// log.info("템플릿 이미지 업로드 성공"); +// } else { +// // 실패 처리 +// return new RestResponse(new FailRestResponse(uploadResponse.getStatCode(), "")); +// } + + } catch (Exception e) { + log.error("템플릿 이미지 업로드 실패", e); + return new RestResponse(new FailRestResponse("UPLOAD_ERROR", "")); + } + } + + + // Form-data의 인덱스된 파라미터들을 VarListMapVO 리스트로 변환 (동적 처리 _1~_100) + String falseCode = ftParameterProcessingService.processIndexedParameters(msgFtRequestVO, request); + if(StringUtils.isNotEmpty(falseCode)){ + log.info("falseCode :: [{}]", falseCode); + return new RestResponse(new FailRestResponse(falseCode,"")); + } + + + + +// +// +// +// +// MjonResponseVO munjaSendResponse = apiService.postForEntity( +// "/web/mjon/kakao/friendstalk/kakaoFriendsTalkMsgSendAjax_advc.do" +// , msgFtRequestVO +// , String.class +// ); + + + // convertMjonDataToApiResponse => MjonResponseVO 데이터를 ApiResponse 데이터로 변환하는 메소드 +// log.info(" + munjaSendResponse :: [{}]", munjaSendResponse.toString()); +// if("OK".equals(munjaSendResponse.getResult())){ // 성공 +// return new RestResponse(SendSucRestResponse.convertMjonDataToApiResponse(munjaSendResponse)); +// }else{ // 실패 +// return new RestResponse(new FailRestResponse(munjaSendResponse.getStatCode(),"")); +// } + + return new RestResponse(msgFtRequestVO); + } + +} diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/web/SendFtRestController.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/web/SendFtRestController.java new file mode 100644 index 0000000..95f496f --- /dev/null +++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/web/SendFtRestController.java @@ -0,0 +1,60 @@ +package com.itn.mjonApi.mjon.api.kakao.ft.send.web; + +import com.itn.mjonApi.cmn.msg.RestResponse; +import com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain.MsgFtRequestVO; +import com.itn.mjonApi.mjon.api.kakao.ft.send.service.SendFtService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; + +/** + * packageName : com.itn.mjonApi.mjon.send.web + * fileName : SendRestController + * author : hylee + * date : 2023-02-15 + * description : + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-02-15 hylee 최초 생성 + */ + +// 치환문자가 있으면 , => §로 치환 + +@CrossOrigin("*") // 모든 요청에 접근 허용 +@Slf4j +@RestController +public class SendFtRestController { + + + @Autowired + private SendFtService sendFtService; + + + /** + * + * @param msgFtRequestVO + * @param request + * @Discription [문자 발송] 같은 내용으로 여려명에게 보냄 + * @return + */ + @PostMapping(value = "/api/kakao/ft/sendMsg", + consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity sendMsg( + @ModelAttribute MsgFtRequestVO msgFtRequestVO // 폼 텍스트들 바인딩 + , HttpServletRequest request) throws Exception { + + // https://smartsms.aligo.in/friendapi.html + + return ResponseEntity.ok().body(sendFtService.sendFtData(msgFtRequestVO, request)); + } + + +} diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/utils/FtFileMetaUtil.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/utils/FtFileMetaUtil.java new file mode 100644 index 0000000..c21f23d --- /dev/null +++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/utils/FtFileMetaUtil.java @@ -0,0 +1,260 @@ +package com.itn.mjonApi.mjon.api.kakao.utils; + +import com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain.MsgFtRequestVO; +import org.springframework.web.multipart.MultipartFile; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.function.Consumer; + +/** + * Kakao FT 이미지 메타데이터 유틸리티. + * - 템플릿/서브 이미지의 파일명, 용량, 폭/높이, SHA-256 해시 세팅 + * - 템플릿: I/W 타입 판정(2:1, 4:3, 가로≥500) 및 검증 + * - 서브(MMS): jpg/jpeg/png/gif, ≤10MB만 검증(권장 640×960은 안내 수준) + * - 실패 시 코드 문자열 반환(성공 시 null) + */ +public class FtFileMetaUtil { + + // ===== 실패코드 상수 정의 ===== + private static final String OK = null; // 성공 시 null 반환 + private static final String FT_E_EMPTY = "STAT_2055"; // 파일이 비어있음 + private static final String FT_E_IMG_READ_FAIL = "STAT_2052"; // 이미지 읽기 실패 + private static final String FT_E_WIDTH_LT_500 = "STAT_2054"; // 가로폭 500px 미만 + private static final String FT_E_RATIO_OUT_OF_RANGE = "STAT_2056"; // 비율이 2:1~4:3 범위 밖 + private static final String FT_E_SIZE_GT_5MB = "STAT_2051"; // 파일 크기 초과(5MB 이상) + private static final String FT_E_CONTENT_TYPE = "STAT_2050"; // 허용되지 않는 콘텐츠 타입// 추가 + private static final String FT_E_SIZE_GT_10MB = "STAT_2057"; // 대체문자(MMS) 이미지는 10MB를 초과할 수 없습니다. + + + + + /** + * 템플릿 이미지의 메타데이터(파일명, 용량, 폭/높이, 해시, 타입)를 MsgFtRequestVO에 채움 + * @param vo 메타데이터를 세팅할 VO + * @throws IOException + * @throws NoSuchAlgorithmException + */ + public static String fillTemplateMeta(MsgFtRequestVO vo) throws IOException, NoSuchAlgorithmException { + return fillMeta(vo.getTemplateImage(), + (name) -> vo.setTemplateImageName(name), + (ct) -> vo.setTemplateImageContentType(ct), + (size) -> vo.setTemplateImageSize(size), + (w) -> vo.setTemplateImageWidth(w), + (h) -> vo.setTemplateImageHeight(h), + (hash) -> vo.setTemplateImageSha256(hash), + (type) -> vo.setTemplateMsgType(type) // I/W 세팅 + ); + } + + /** + * 서브(와이드) 이미지의 메타데이터를 MsgFtRequestVO에 채움 + * - MMS 규격 검증(타입/10MB)만 수행, I/W 판정 없음 + * @param vo 메타데이터를 세팅할 VO + * @throws IOException + * @throws NoSuchAlgorithmException + */ +// public static String fillSubMeta(MsgFtRequestVO vo) throws IOException, NoSuchAlgorithmException { +// return fillMetaForSubImage(vo.getSubImage(), +// (name) -> vo.setSubImageName(name), +// (ct) -> vo.setSubImageContentType(ct), +// (size) -> vo.setSubImageSize(size), +// (w) -> vo.setSubImageWidth(w), +// (h) -> vo.setSubImageHeight(h), +// (hash) -> vo.setSubImageSha256(hash) +// ); +// } + + /** + * subImage(MMS) 전용 메타데이터 수집/검증 메서드 + * - 검증: 파일 존재 여부, 용량(≤10MB), Content-Type(jpg/jpeg/png/gif) + * - 세팅: 파일명, Content-Type, 파일 크기, width/height, SHA-256 + * - 비율/가로 500px 검증은 하지 않음 (권장 640×960은 안내 수준) + * + * @param f 업로드된 서브 이미지 파일 + * @param nameC 파일명 setter + * @param ctC Content-Type setter + * @param sizeC 파일 크기 setter + * @param wC width setter + * @param hC height setter + * @param hashC SHA-256 setter + * @return 실패코드(String), 성공 시 null(OK) + * @throws IOException + * @throws NoSuchAlgorithmException + */ + private static String fillMetaForSubImage( + MultipartFile f, + Consumer nameC, + Consumer ctC, + Consumer sizeC, + Consumer wC, + Consumer hC, + Consumer hashC + ) throws IOException, NoSuchAlgorithmException { + // 1) 파일 존재/빈 파일 체크 + if (f == null || f.isEmpty()) return FT_E_EMPTY; + + // 2) 용량(10MB) 제한 + if (f.getSize() > 10L * 1024 * 1024) return FT_E_SIZE_GT_10MB; + + // 3) 허용 Content-Type 확인 (jpg/jpeg/png/gif) + String ct = f.getContentType(); + if (!isAllowedSubCt(ct)) return FT_E_CONTENT_TYPE; + + // 4) 기본 메타 세팅 + nameC.accept(f.getOriginalFilename()); + ctC.accept(ct); + sizeC.accept(f.getSize()); + + // 5) 이미지 읽기 → width/height 세팅 (비율 제한 없음) + int w = 0, h = 0; + try (InputStream is = f.getInputStream()) { + BufferedImage img = ImageIO.read(is); + if (img == null) return FT_E_IMG_READ_FAIL; + w = img.getWidth(); + h = img.getHeight(); + wC.accept(w); + hC.accept(h); + } catch (Exception e) { + return FT_E_IMG_READ_FAIL; + } + + // sha-256 + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(f.getBytes()); + hashC.accept(bytesToHex(md.digest())); + + // 권장 사이즈(640×960)는 안내사항이므로 실패코드 미반환 + return OK; + } + + /** + * 공통 메타데이터 채우기 유틸 + * - 파일명, Content-Type, 파일 크기 + * - 이미지 폭/높이 + * - 파일 SHA-256 해시 + * - 가로/세로 비율 기반의 메시지 타입(I/W) 판정 + * + * @param f MultipartFile (이미지 파일) + * @param nameC 파일명 setter + * @param ctC ContentType setter + * @param sizeC 파일 크기 setter + * @param wC width setter + * @param hC height setter + * @param hashC SHA-256 setter + * @param typeC 이미지 타입(I/W) setter + * @return 실패코드(String), 성공 시 null 반환 + */ + private static String fillMeta(MultipartFile f, + Consumer nameC, + Consumer ctC, + Consumer sizeC, + Consumer wC, + Consumer hC, + Consumer hashC, + Consumer typeC + ) throws IOException, NoSuchAlgorithmException { + if (f == null || f.isEmpty()) return FT_E_EMPTY; + + // 용량/콘텐츠타입 1차 검증 + if (f.getSize() > 5L * 1024 * 1024) return FT_E_SIZE_GT_5MB; + String ct = f.getContentType(); + if (ct == null || !(ct.equalsIgnoreCase("image/jpeg") || ct.equalsIgnoreCase("image/png"))) { + return FT_E_CONTENT_TYPE; + } + + nameC.accept(f.getOriginalFilename()); + ctC.accept(ct); + sizeC.accept(f.getSize()); + + int w = 0, h = 0; + try (InputStream is = f.getInputStream()) { + BufferedImage img = ImageIO.read(is); + if (img == null) return FT_E_IMG_READ_FAIL; + w = img.getWidth(); + h = img.getHeight(); + wC.accept(w); + hC.accept(h); + } catch (Exception e) { + return FT_E_IMG_READ_FAIL; + } + + // sha-256 + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(f.getBytes()); + hashC.accept(bytesToHex(md.digest())); + + // 규칙 검증 + 타입(I/W) 판정 + String typeOrCode = calcFtMsgTypeOrCode(w, h); + if (typeOrCode.length() == 1) { // "I" or "W" + typeC.accept(typeOrCode); + return OK; + } + return typeOrCode; // 실패코드 리턴 + } + + /** + * byte[] → 16진수 문자열 변환 + * @param bytes SHA-256 결과 바이트 배열 + * @return Hex 문자열 + */ + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(bytes.length * 2); + for (byte b : bytes) sb.append(String.format("%02x", b)); + return sb.toString(); + } + + + + /** + * 가로/세로 크기를 기반으로 이미지 타입 판정 + * - 폭 < 500px → 오류코드 반환 + * - 비율이 2:1(약 2.0) ±10% → "I" + * - 비율이 4:3(약 1.333) ±10% → "W" + * - 그 외 → 오류코드 반환 + * + * @param width 이미지 가로 폭 + * @param height 이미지 세로 높이 + * @return "I" 또는 "W" / 실패코드 + */ + public static String calcFtMsgTypeOrCode(int width, int height) { + if (width < 500 || height <= 0) return FT_E_WIDTH_LT_500; + + double r = (double) width / (double) height; + // 2:1 ~= 2.0, 4:3 ~= 1.333... (±10%) + boolean isI = (r >= 1.8 && r <= 2.2); + boolean isW = (r >= 1.28 && r <= 1.36); + + if (isI) return "I"; + if (isW) return "W"; + return FT_E_RATIO_OUT_OF_RANGE; + } + + /** + * MultipartFile 존재 여부 헬퍼 + * @param f 업로드 파일 + * @return 파일이 null이 아니고 비어있지 않으면 true + */ + public static boolean hasFile(MultipartFile f){ return f != null && !f.isEmpty(); } + + + /** + * 서브(MMS) 허용 Content-Type 검사 + * 허용: image/jpeg, image/jpg, image/png, image/gif + * @param ct 요청의 Content-Type + * @return 허용 타입이면 true + */ + private static boolean isAllowedSubCt(String ct) { + return ct != null && ( + ct.equalsIgnoreCase("image/jpeg") || + ct.equalsIgnoreCase("image/jpg") || // 일부 클라이언트 대비 + ct.equalsIgnoreCase("image/png") || + ct.equalsIgnoreCase("image/gif") + ); + } + +} \ No newline at end of file diff --git a/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/PriceResponse.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/PriceResponse.java index 618e9f1..4d9275d 100644 --- a/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/PriceResponse.java +++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/PriceResponse.java @@ -1,14 +1,9 @@ package com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain; -import java.time.LocalDateTime; - +import lombok.*; import org.springframework.http.HttpStatus; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import java.time.LocalDateTime; @Setter @Getter @@ -27,13 +22,23 @@ public class PriceResponse { private double shortPrice; // 단문 이용단가 private double longPrice; // 장문 이용단가 private double picturePrice; // 그림 이용단가 - + + private double kakaoAtPrice; // 그림 이용단가 + private double kakaoFtPrice; // 그림 이용단가 + private double kakaoFtImgPrice; // 그림 이용단가 + private double kakaoFtWideImgPrice; // 그림 이용단가 + private double mberMoney; // 잔액 private int shortSendPsbltEa; // 단문 발송 가능건 수 private int longSendPsbltEa; // 장문 발송 가능건 수 private int pictureSendPsbltEa; // 그림 발송 가능건 수 + private int kakaoAtSendPsbltEa; // 그림 발송 가능건 수 + private int kakaoFtSendPsbltEa; // 그림 발송 가능건 수 + private int kakaoFtImgSendPsbltEa; // 그림 발송 가능건 수 + private int kakaoFtWideImgSendPsbltEa; // 그림 발송 가능건 수 + /* * 200-OK : 정상접속 * 401-Unauthorized : 인증실패 diff --git a/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/PriceVO.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/PriceVO.java index 2bc32b8..265b567 100644 --- a/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/PriceVO.java +++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/PriceVO.java @@ -1,16 +1,13 @@ package com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain; -import java.io.Serializable; +import lombok.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import java.io.Serializable; @Getter @Setter @Builder +@ToString @NoArgsConstructor @AllArgsConstructor public class PriceVO implements Serializable{ @@ -22,11 +19,21 @@ public class PriceVO implements Serializable{ private double shortPrice; // 단문 이용단가 private double longPrice; // 장문 이용단가 private double picturePrice; // 그림 이용단가 - + + private double kakaoAtPrice; // 알림톡 이용단가 + private double kakaoFtPrice; // 친구톡 이용단가 + private double kakaoFtImgPrice; // 친구톡 그림 이용단가 + private double kakaoFtWideImgPrice; // 친구톡 와이드 그림 이용단가 + private double mberMoney; // 잔액 private int shortSendPsbltEa; // 단문 발송 가능건 수 private int longSendPsbltEa; // 장문 발송 가능건 수 private int pictureSendPsbltEa; // 그림 발송 가능건 수 - + + private int kakaoAtSendPsbltEa; // 알림톡 발송 가능건 수 + private int kakaoFtSendPsbltEa; // 친구톡 발송 가능건 수 + private int kakaoFtImgSendPsbltEa; // 친구톡 그림 발송 가능건 수 + private int kakaoFtWideImgSendPsbltEa; // 친구톡 와이드 그림 발송 가능건 수 + } diff --git a/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/service/impl/PriceServiceImpl.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/service/impl/PriceServiceImpl.java index 23b22c6..a7915f7 100644 --- a/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/service/impl/PriceServiceImpl.java +++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/service/impl/PriceServiceImpl.java @@ -59,10 +59,20 @@ public class PriceServiceImpl implements PriceService { .shortPrice(priceVO.getShortPrice()) .longPrice(priceVO.getLongPrice()) .picturePrice(priceVO.getPicturePrice()) + + .kakaoAtPrice(priceVO.getKakaoAtPrice()) + .kakaoFtPrice(priceVO.getKakaoFtPrice()) + .kakaoFtImgPrice(priceVO.getKakaoFtImgPrice()) + .kakaoFtWideImgPrice(priceVO.getKakaoFtWideImgPrice()) //3. 발송가능건수 .shortSendPsbltEa(priceVO.getShortSendPsbltEa()) .longSendPsbltEa(priceVO.getLongSendPsbltEa()) .pictureSendPsbltEa(priceVO.getPictureSendPsbltEa()) + + .kakaoAtSendPsbltEa(priceVO.getKakaoAtSendPsbltEa()) + .kakaoFtSendPsbltEa(priceVO.getKakaoFtSendPsbltEa()) + .kakaoFtImgSendPsbltEa(priceVO.getKakaoFtImgSendPsbltEa()) + .kakaoFtWideImgSendPsbltEa(priceVO.getKakaoFtWideImgSendPsbltEa()) .build(); } catch (Exception e) { diff --git a/src/main/java/com/itn/mjonApi/util/MunjaUtil.java b/src/main/java/com/itn/mjonApi/util/MunjaUtil.java index ec5cb5b..80a66fa 100644 --- a/src/main/java/com/itn/mjonApi/util/MunjaUtil.java +++ b/src/main/java/com/itn/mjonApi/util/MunjaUtil.java @@ -1,6 +1,8 @@ package com.itn.mjonApi.util; -import com.itn.mjonApi.mjon.api.kakao.at.send.mapper.domain.VarListMapVO; +import com.itn.mjonApi.mjon.api.kakao.at.send.mapper.domain.VarAtListMapVO; +import com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain.VarFtListMapVO; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; @@ -15,6 +17,7 @@ import org.apache.commons.lang3.StringUtils; * ----------------------------------------------------------- * 2023-05-17 hylee 최초 생성 */ +@Slf4j public class MunjaUtil { @@ -105,7 +108,7 @@ public class MunjaUtil { * @param subMsgSendYn 대체문자 발송 여부 * @return 검증 실패 시 오류 코드, 성공 시 null */ - public static String kakaoCmnValidate(VarListMapVO vo, String subMsgSendYn) { + public static String kakaoAtValidate(VarAtListMapVO vo, String subMsgSendYn) { // 수신번호 검증 String callTo = vo.getCallToList(); @@ -138,5 +141,33 @@ public class MunjaUtil { } + public static String kakaoFtValidate(VarFtListMapVO vo, String subMsgSendYn) { + + log.info(" vo.toString() [{}]", vo.toString()); + + // 수신번호 검증 + String callTo = vo.getPhone(); + if (MunjaUtil.getCallToChk(callTo)) { + return "STAT_1020"; // 수신자 전화번호 오류 + } + + // 본문 데이터 검증 + String smsTxt = vo.getTemplateContent(); + if (StringUtils.isEmpty(smsTxt)) { + return "STAT_2040"; // 본문 데이터 오류 + } + + // 대체문자 검증 (대체문자 발송이 활성화된 경우에만) + if ("Y".equals(subMsgSendYn)) { + String subMsgTxt = vo.getSubMsgTxt(); + if (StringUtils.isEmpty(subMsgTxt)) { + return "STAT_2042"; // 대체문자 데이터 오류 + } + } + + // 모든 검증 통과 + return null; + } + } diff --git a/src/main/resources/mapper/api/msg/inqry/PriceMapper.xml b/src/main/resources/mapper/api/msg/inqry/PriceMapper.xml index a24259f..c73b886 100644 --- a/src/main/resources/mapper/api/msg/inqry/PriceMapper.xml +++ b/src/main/resources/mapper/api/msg/inqry/PriceMapper.xml @@ -19,16 +19,24 @@ resultType="hashmap" > - SELECT a.SHORT_PRICE AS sysShortPrice, - a.LONG_PRICE AS sysLongPrice, - a.PICTURE_PRICE AS sysPicturePrice, - a.PICTURE2_PRICE AS sysPicturePrice2, - a.PICTURE3_PRICE AS sysPicturePrice3, - b.SHORT_PRICE AS shortPrice, - b.LONG_PRICE AS longPrice, - b.PICTURE_PRICE AS picturePrice, - b.PICTURE2_PRICE AS picturePrice2, - b.PICTURE3_PRICE AS picturePrice3 + SELECT a.SHORT_PRICE AS sysShortPrice + , a.LONG_PRICE AS sysLongPrice + , a.PICTURE_PRICE AS sysPicturePrice + , a.PICTURE2_PRICE AS sysPicturePrice2 + , a.PICTURE3_PRICE AS sysPicturePrice3 + , a.KAKAO_AT_PRICE AS sysKakaoAtPrice + , a.KAKAO_FT_PRICE AS sysKakaoFtPrice + , a.KAKAO_FT_IMG_PRICE AS sysKakaoFtImgPrice + , a.KAKAO_FT_WIDE_IMG_PRICE AS sysKakaoFtWideImgPrice + , b.SHORT_PRICE AS shortPrice + , b.LONG_PRICE AS longPrice + , b.PICTURE_PRICE AS picturePrice + , b.PICTURE2_PRICE AS picturePrice2 + , b.PICTURE3_PRICE AS picturePrice3 + , b.KAKAO_AT_PRICE AS kakaoAtPrice + , b.KAKAO_FT_PRICE AS kakaoFtPrice + , b.KAKAO_FT_IMG_PRICE AS kakaoFtImgPrice + , b.KAKAO_FT_WIDE_IMG_PRICE AS kakaoFtWideImgPrice FROM mj_mber_setting a , lettngnrlmber b WHERE b.mber_id = #{mberId} diff --git a/src/test/java/com/itn/mjonApi/common/TestUtils.java b/src/test/java/com/itn/mjonApi/common/TestUtils.java index e5a2e54..a73232b 100644 --- a/src/test/java/com/itn/mjonApi/common/TestUtils.java +++ b/src/test/java/com/itn/mjonApi/common/TestUtils.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.itn.mjonApi.cmn.domain.biz.template.BizTemplateRequest; import com.itn.mjonApi.mjon.api.kakao.at.inqry.mapper.domain.MjKakaoProfileInfoVO; import com.itn.mjonApi.mjon.api.kakao.at.send.mapper.domain.MsgAtRequestVO; -import com.itn.mjonApi.mjon.api.kakao.at.send.mapper.domain.VarListMapVO; +import com.itn.mjonApi.mjon.api.kakao.at.send.mapper.domain.VarAtListMapVO; import java.util.ArrayList; import java.util.List; @@ -38,7 +38,7 @@ public class TestUtils { vo.setTest_yn("Y"); // 테스트 모드 // VarListMap 생성 (동적 파라미터) - List varListMap = new ArrayList<>(); + List varListMap = new ArrayList<>(); varListMap.add(createVarListMapVO("01012345678", "테스트사용자1", "테스트사용자1")); varListMap.add(createVarListMapVO("01087654321", "테스트사용자2", "테스트사용자2")); vo.setVarListMap(varListMap); @@ -54,8 +54,8 @@ public class TestUtils { * @param replaceValue 치환값 * @return 테스트용 VarListMapVO 객체 */ - public static VarListMapVO createVarListMapVO(String phone, String name, String replaceValue) { - VarListMapVO vo = new VarListMapVO(); + public static VarAtListMapVO createVarListMapVO(String phone, String name, String replaceValue) { + VarAtListMapVO vo = new VarAtListMapVO(); vo.setCallToList(phone); vo.setTemplateTitle(name); vo.setTemplateContent("테스트 메시지 " + name + "님 안녕하세요"); @@ -210,8 +210,8 @@ public class TestUtils { * @param count 생성할 개수 * @return VarListMapVO 목록 */ - public static List createVarListMap(int count) { - List varListMap = new ArrayList<>(); + public static List createVarListMap(int count) { + List varListMap = new ArrayList<>(); for (int i = 1; i <= count; i++) { varListMap.add(createVarListMapVO(