diff --git a/.gitignore b/.gitignore
index 89e9faf..95b6c65 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
HELP.md
target/
+log/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
@@ -198,3 +199,4 @@ fabric.properties
rebel.xml
/mvnw
/mvnw.cmd
+/log.config.path_IS_UNDEFINED/
diff --git a/pom.xml b/pom.xml
index 8a03c6b..76e588c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,6 +38,11 @@
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
org.modelmapper
diff --git a/src/main/java/com/itn/mjonApi/cmn/aop/LogAspect.java b/src/main/java/com/itn/mjonApi/cmn/aop/LogAspect.java
index fdf4ab7..36e5316 100644
--- a/src/main/java/com/itn/mjonApi/cmn/aop/LogAspect.java
+++ b/src/main/java/com/itn/mjonApi/cmn/aop/LogAspect.java
@@ -4,10 +4,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.itn.mjonApi.cmn.idgen.service.IdgenService;
import com.itn.mjonApi.cmn.msg.FailRestResponse;
import com.itn.mjonApi.cmn.msg.RestResponse;
-import com.itn.mjonApi.mjon.api.access.mapper.domain.AccessKeyVO;
-import com.itn.mjonApi.mjon.api.send.mapper.domain.MsgRequestVO;
-import com.itn.mjonApi.mjon.api.send.mapper.domain.MsgsRequestVO;
-import com.itn.mjonApi.mjon.api.send.mapper.domain.SendSucRestResponse;
+import com.itn.mjonApi.mjon.api.msg.send.mapper.domain.SendSucRestResponse;
import com.itn.mjonApi.mjon.log.service.mapper.LettnAccessLogMapper;
import com.itn.mjonApi.mjon.log.service.mapper.LettnApiSendMsgLogMapper;
import com.itn.mjonApi.mjon.log.service.mapper.MjMsgGroupDataMapper;
@@ -59,6 +56,7 @@ public class LogAspect {
@Autowired
MjMsgGroupDataMapper mjMsgGroupDataMapper;
+
@Resource(name = "apiAccessLog")
private IdgenService idgenApiAccessLogId;
@@ -72,16 +70,28 @@ public class LogAspect {
* - mberId
* - accessKey
*/
- @Before(value = "execution(* com.itn.mjonApi.mjon.api.*..*Impl.*(..))" )
+// @Before(value = "execution(* com.itn.mjonApi.mjon.api.*..*Impl.*(..))" )
+ @Before("execution(* com.itn.mjonApi.mjon.api..*Controller.*(..)) || execution(* com.itn.mjonApi.mjon.api..*Impl.*(..))")
public void before(JoinPoint joinPoint) throws IllegalAccessException, JsonProcessingException {
log.info(" :: AOP before :: ");
+//
+// for (Object arg : joinPoint.getArgs()) {
+// if (arg == null) continue;
+//
+// // JSON 직렬화해서 VO 전체 출력
+// log.info("VO 전체: {}", new ObjectMapper().writeValueAsString(arg));
+// log.info("VO 클래스명: {}", arg.getClass().getName());
+//
+//
+// }
+
HttpServletRequest request = this.getHttpServletRequest();
// VO 객체를 가져옴
Object objectVO = joinPoint.getArgs()[0];
- log.info("joinPoint.getArgs()[0] :: [{}]", joinPoint.getArgs()[0]);
+// log.info("joinPoint.getArgs()[0] :: [{}]", joinPoint.getArgs()[0]);
@@ -91,16 +101,27 @@ public class LogAspect {
String mberId = "";
String accessKey = "";
for(Field field : objectVO.getClass().getDeclaredFields()){;
- field.setAccessible(true);
-
- log.info("field.getName() :: [{}]", field.getName());
- log.info("field.get(objectVO) :: [{}]", field.get(objectVO));
+ try {
+ // JDK 내부 클래스 필드는 무시
+ if (field.getDeclaringClass().getName().startsWith("java.")) continue;
+ field.setAccessible(true);
+ if ("mberId".equals(field.getName())) {
+ mberId = String.valueOf(field.get(objectVO));
+ log.info("mberId :: [{}]", mberId);
+ }
+// log.info("field.get(objectVO) :: [{}]", field.get(objectVO));
- if("mberId".equals(field.getName())){ mberId=field.get(objectVO).toString(); }
- else if("accessKey".equals(field.getName())){ accessKey=field.get(objectVO).toString(); }
+ if("mberId".equals(field.getName())){ mberId=field.get(objectVO).toString(); }
+ else if("accessKey".equals(field.getName())){ accessKey=field.get(objectVO).toString(); }
- if(StringUtils.isNotEmpty(mberId) && StringUtils.isNotEmpty(accessKey)){ break; }
+ if(StringUtils.isNotEmpty(mberId) && StringUtils.isNotEmpty(accessKey)){ break; }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ log.warn("접근 불가 필드 스킵: {}", field.getName());
+ continue;
+ }
}
String nextStringId = idgenApiAccessLogId.getNextStringId();
@@ -162,7 +183,9 @@ public class LogAspect {
lettnApiSendMsgLogMapper.insert(apiSendMsgLogVO);
// 메세지 그룹 테이블에 발송 구분 업데이트
- this.updateMsgGroupTbSendKind(apiSendMsgLogVO);
+ // mjon_git에서 처리하는걸로 수정
+ // 20250729 이호영
+// this.updateMsgGroupTbSendKind(apiSendMsgLogVO);
}
@@ -172,18 +195,18 @@ public class LogAspect {
* @description 메세지 그룹 테이블에 발송 구분 업데이트
* @param apiSendMsgLogVO
*/
- private void updateMsgGroupTbSendKind(LettnApiSendMsgLogVO apiSendMsgLogVO) {
- if(StringUtils.isNotEmpty(apiSendMsgLogVO.getMsgGroupId()))
- {
- String[] msgGroupIds = null;
- if(apiSendMsgLogVO.getMsgGroupId().indexOf(",") > -1){
- msgGroupIds = apiSendMsgLogVO.getMsgGroupId().split(",");
- }else{
- msgGroupIds = new String[]{apiSendMsgLogVO.getMsgGroupId()};
- }
- mjMsgGroupDataMapper.update(msgGroupIds);
- }
- }
+// private void updateMsgGroupTbSendKind(LettnApiSendMsgLogVO apiSendMsgLogVO) {
+// if(StringUtils.isNotEmpty(apiSendMsgLogVO.getMsgGroupId()))
+// {
+// String[] msgGroupIds = null;
+// if(apiSendMsgLogVO.getMsgGroupId().indexOf(",") > -1){
+// msgGroupIds = apiSendMsgLogVO.getMsgGroupId().split(",");
+// }else{
+// msgGroupIds = new String[]{apiSendMsgLogVO.getMsgGroupId()};
+// }
+// mjMsgGroupDataMapper.update(msgGroupIds);
+// }
+// }
/**
* @description lettngnrlmber_api_send_msg_log 테이블에 저장할 데이터 만들기
@@ -252,6 +275,7 @@ public class LogAspect {
@NotNull
private static String getClassNmFromObject(Object returnValue) {
String classNmTemp = returnValue.getClass().getName();
+ log.info("getClassNmFromObject :: [{}]", classNmTemp);
String classNm = classNmTemp.substring(classNmTemp.lastIndexOf(".")+1, classNmTemp.length());
return classNm;
}
@@ -312,37 +336,29 @@ public class LogAspect {
* @return
*/
public String getJsonToString(Object returnValue) throws JsonProcessingException {
-
- if(ObjectUtils.isEmpty(returnValue)){
+ if (ObjectUtils.isEmpty(returnValue)) {
return null;
}
String classNm = this.getClassNmFromObject(returnValue);
- /**
- * @description : return Class가 추가되면 여기에 추가
- */
- if("AccessKeyVO".equals(classNm)) {
- AccessKeyVO accessKeyVO = (AccessKeyVO) returnValue;
- return ApiObjectUtil.getAccessKeyVOToJsonString(accessKeyVO);
+ // 처리 대상 클래스 목록 (선택적으로 필터링하고 싶다면 여기에 조건)
+ switch (classNm) {
+ case "AccessKeyVO":
+ case "RestResponse":
+ case "MsgsRequestVO":
+ case "MsgRequestVO":
+ case "MjKakaoProfileInfoVO":
+ case "BizTemplateRequest":
+ case "MsgAtRequestVO":
+ case "MsgFtRequestVO":
+ case "String":
+ case "ArrayList":
+ return ApiObjectUtil.toJson(returnValue);
+ default:
+ log.info("데이터를 추가해 주세요 [{}]", classNm);
+ return "데이터를 추가해 주세요";
}
- else if("RestResponse".equals(classNm)){
- RestResponse restResponse = (RestResponse) returnValue;
- return ApiObjectUtil.getRestResponseToJsonString(restResponse);
- }
- else if("MsgsRequestVO".equals(classNm)){
- MsgsRequestVO msgsRequestVO = (MsgsRequestVO) returnValue;
- return ApiObjectUtil.getMsgsRequestVOToJsonString(msgsRequestVO);
- }
- else if("MsgRequestVO".equals(classNm)){
- MsgRequestVO restResponse = (MsgRequestVO) returnValue;
- return ApiObjectUtil.getMsgRequestVOToJsonString(restResponse);
- }
- else{
- return "데이터를 추가해 주세요";
- }
-
-
}
}
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 eb709ae..bdbe643 100644
--- a/src/main/java/com/itn/mjonApi/cmn/apiServer/ApiService.java
+++ b/src/main/java/com/itn/mjonApi/cmn/apiServer/ApiService.java
@@ -1,10 +1,22 @@
package com.itn.mjonApi.cmn.apiServer;
import com.fasterxml.jackson.core.JsonProcessingException;
-import com.itn.mjonApi.mjon.api.send.mapper.domain.MjonResponseVO;
-import org.springframework.http.ResponseEntity;
+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;
+
/**
* packageName : com.itn.mjonApi.cmn.apiServer
@@ -18,6 +30,7 @@ import org.springframework.web.client.RestTemplate;
* -----------------------------------------------------------
* 2023-05-15 hylee 최초 생성
*/
+@Slf4j
@Service
public class ApiService {
@@ -37,13 +50,120 @@ public class ApiService {
* @throws JsonProcessingException
*/
public MjonResponseVO postForEntity(String url, Object request, Class responseType) throws JsonProcessingException {
- ResponseEntity spamChkEntity = (ResponseEntity) restTemplate.postForEntity(
+ ResponseEntity returnEntity = (ResponseEntity) restTemplate.postForEntity(
url
, request
, responseType
);
- MjonResponseVO spamResponse = MjonResponseVO.getMjonResponse(spamChkEntity);
- return spamResponse;
+ log.info("returnEntity :: [{}]", returnEntity.toString());
+ // ObjectMapper로 JSON 파싱
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode rootNode = mapper.readTree(returnEntity.getBody());
+
+// log.info("rootNode :: [{}]", rootNode.toString());
+
+
+ // "apiReturn" 필드만 추출
+ JsonNode apiReturnNode = rootNode.get("apiReturn");
+ if (apiReturnNode == null || apiReturnNode.isNull()) {
+ log.warn("apiReturn 필드가 응답에 존재하지 않습니다.");
+ return null;
+ }
+
+ return MjonResponseVO.getMjonResponse(apiReturnNode);
}
+
+ /**
+ * @param
+ * @return
+ * @throws Exception 처리 중 예외 발생 가능
+ * @date 2025-07-29
+ * @Discription 알림톡 템플릿 리스트 조회
+ * @author hylee
+ */
+ public TemplateListResponse postForBizTemplateListEntity(String url, BizTemplateRequest requestDto) throws JsonProcessingException {
+
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+
+ HttpEntity requestEntity = new HttpEntity<>(requestDto, headers);
+
+ TemplateListResponse returnEntity = restTemplate.exchange(
+ url,
+ HttpMethod.POST,
+ requestEntity,
+ TemplateListResponse.class
+ ).getBody();
+
+
+
+ return returnEntity;
+ }
+
+ /**
+ * @param
+ * @return
+ * @throws Exception 처리 중 예외 발생 가능
+ * @date 2025-07-29
+ * @Discription 알림톡 템플릿 상세 조회
+ * @author hylee
+ */
+ public TemplateDetailResponse postForBizTemplateDetailEntity(String url, BizTemplateRequest requestDto) throws JsonProcessingException {
+
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+
+ HttpEntity requestEntity = new HttpEntity<>(requestDto, headers);
+
+ TemplateDetailResponse returnEntity = restTemplate.exchange(
+ url,
+ HttpMethod.POST,
+ requestEntity,
+ TemplateDetailResponse.class
+ ).getBody();
+
+ log.info("returnEntity :: [{}]", returnEntity.toString());
+
+
+ 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.getTemplateMsgType() != null) {
+ body.add("imageType", msgFtRequestVO.getTemplateMsgType());
+ }
+ 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/CmnVO.java b/src/main/java/com/itn/mjonApi/cmn/domain/CmnVO.java
new file mode 100644
index 0000000..a2a9b13
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/cmn/domain/CmnVO.java
@@ -0,0 +1,19 @@
+package com.itn.mjonApi.cmn.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@SuperBuilder
+public class CmnVO {
+
+ private String mberId; // 사용자 ID
+
+ private String accessKey; // Api Key
+}
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/send/mapper/domain/MsgsRequestVO.java b/src/main/java/com/itn/mjonApi/cmn/domain/SendRequestCmnVO.java
similarity index 89%
rename from src/main/java/com/itn/mjonApi/mjon/api/send/mapper/domain/MsgsRequestVO.java
rename to src/main/java/com/itn/mjonApi/cmn/domain/SendRequestCmnVO.java
index d45d1f3..449448c 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/send/mapper/domain/MsgsRequestVO.java
+++ b/src/main/java/com/itn/mjonApi/cmn/domain/SendRequestCmnVO.java
@@ -1,104 +1,15 @@
-package com.itn.mjonApi.mjon.api.send.mapper.domain;
+package com.itn.mjonApi.cmn.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
-import java.io.Serializable;
-
-/**
- * packageName : com.itn.mjonApi.mjon.api.send.mapper.domain
- * fileName : MjonMsgVO
- * author : hylee
- * date : 2023-05-23
- * description : 문자 발송에 필요한 값들을 받는 vo
- * 1건~500건 대량 문자 개인별로 발송
- * ===========================================================
- * DATE AUTHOR NOTE
- * -----------------------------------------------------------
- * 2023-05-09 hylee 최초 생성
- */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
-public class MsgsRequestVO implements Serializable {
-
- /**
- * 값이 있는 경우
- * 문자온 프로젝트에서 처리해줌
- * null이면 에러
- */
- private static final long serialVersionUID = 1L;
-
- private String mberId; // 사용자 ID
-
- private String accessKey; // Api Key
-
- private String smsTxt; // SMS용 메시지본문
-
- private String[] callToList; // 수신번호리스트
-
- private String callFrom; // 발신번호 :: 정책이 필요함
-
- private String eachPrice = "0"; // 전송문자 개별가격
- private String sPrice = "0"; // 임시
-
- private String totPrice = "0"; // 전송문자 토탈가격
-
- private String fileCnt = "0"; // 첨부파일 갯수
-
- private String msgType = "4"; // 메시지의 (4: SMS 전송, 5: URL 전송, 6: MMS전송, 7: BARCODE전송, 8: 카카오 알림톡 전송)
-
- // ==== 단가 ====
- private float smsPrice = 0; // sms 단가 null 이면 에러
-
- private float mmsPrice = 0; // mms 단가 null 이면 에러
-// private float kakaoAtPrice; // 카카오 알림톡 단가
-// private float kakaoFtPrice; // 카카오 친구톡 단가
-// private float kakaoFtImgPrice;// 카카오 이미지 단가
-// private float kakaoFtWideImgPrice; // 카카오 와이드 이미지 단가
-
- private String[] imgFilePath = new String[0]; // 그림 이미지 경로
-
-
- private String spamStatus; // 스팸문자 유무 (Y/N) - 서비스단에서 처리 함
-
- private String txtReplYn = "N"; // 변환문자 유무 (Y/N) - 서비스단에서 처리 함
-
-// private String nameStr; // value = "치환 이름 리스트 |로 구분", example = "홍길동1|홍길동2|홍길동3"
-
-// private String rep1Str; // value = "치환 문자1 리스트 |로 구분", example = ""
-
-// private String rep2Str; // value = "치환 문자2 리스트 |로 구분", example = ""
-
-// private String rep3Str; // value = "치환 문자3 리스트 |로 구분", example = ""
-
-// private String rep4Str; // value = "치환 문자4 리스트 |로 구분", example = ""
-
-// private String[] nameList= new String[0]; // value = "nameStr 을 |로 split 후 담는 변수", example = ""
-
-// private String[] rep1List= new String[0]; // value = "rep1Str 을 |로 split 후 담는 변수", example = ""
-
-// private String[] rep2List= new String[0]; // value = "rep2Str 을 |로 split 후 담는 변수", example = ""
-
-// private String[] rep3List= new String[0]; // value = "rep3Str 을 |로 split 후 담는 변수", example = ""
-
-// private String[] rep4List= new String[0]; // value = "rep4Str 을 |로 split 후 담는 변수", example = ""
-
- private String reserveYn = "N"; // value = "예약 유무 (Y/N)", example = "N"
-
- // 치환 있을 경우 사용
-// private String shortMsgCnt; // value = "치환 후 단문 건수", example = ""
-
-// private String longMsgCnt; // value = "치환 후 장문 건수", example = ""
-
-
-// @ApiModelProperty(value = "문자 종류 일반:N, 광고:A, 선거:C", example = "N", hidden = true)
- private String msgKind = "N"; // '문자 종류 일반:N, 광고:A, 선거:C',
-
- private String test_yn; // 테스트 여부
+public class SendRequestCmnVO {
// 수신자 발송txt 각각 _1~_100
// 교차로 있어야 로직이 가능함
@@ -1105,6 +1016,4 @@ public class MsgsRequestVO implements Serializable {
// private String smsTxt_498;
// private String smsTxt_499;
// private String smsTxt_500;
-
-
}
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/domain/biz/template/BizTemplateRequest.java b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/BizTemplateRequest.java
new file mode 100644
index 0000000..b567f2e
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/BizTemplateRequest.java
@@ -0,0 +1,20 @@
+package com.itn.mjonApi.cmn.domain.biz.template;
+
+import com.itn.mjonApi.cmn.domain.CmnVO;
+import lombok.*;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+@SuperBuilder
+public class BizTemplateRequest extends CmnVO {
+ private String bizId;
+ private String apiKey;
+ private String senderKey;
+ private String templateCode;
+ private String test_yn;
+
+}
diff --git a/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/CommentsAttachment.java b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/CommentsAttachment.java
new file mode 100644
index 0000000..840c676
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/CommentsAttachment.java
@@ -0,0 +1,16 @@
+package com.itn.mjonApi.cmn.domain.biz.template.detail;
+
+import lombok.*;
+
+
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class CommentsAttachment {
+ private String originalFileName;
+ private String filePath;
+
+}
diff --git a/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateButton.java b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateButton.java
new file mode 100644
index 0000000..13abf53
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateButton.java
@@ -0,0 +1,23 @@
+package com.itn.mjonApi.cmn.domain.biz.template.detail;
+
+import lombok.*;
+
+
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TemplateButton {
+ private String bizFormId;
+ private String linkAnd;
+ private String linkIos;
+ private String linkMo;
+ private String linkPc;
+ private String linkType;
+ private String name;
+ private String ordering;
+ private String pluginId;
+
+}
diff --git a/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateComments.java b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateComments.java
new file mode 100644
index 0000000..30b5b76
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateComments.java
@@ -0,0 +1,20 @@
+package com.itn.mjonApi.cmn.domain.biz.template.detail;
+
+import lombok.*;
+
+import java.util.List;
+
+
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TemplateComments {
+ private String content;
+ private String createdAt;
+ private String status;
+ private List attachment;
+
+}
diff --git a/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateDetail.java b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateDetail.java
new file mode 100644
index 0000000..ffc7899
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateDetail.java
@@ -0,0 +1,41 @@
+package com.itn.mjonApi.cmn.domain.biz.template.detail;
+
+import lombok.*;
+
+import java.util.List;
+
+
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TemplateDetail {
+ private String block;
+ private String categoryCode;
+ private String createdAt;
+ private String dormant;
+ private String inspectionStatus;
+ private String modifiedAt;
+ private String securityFlag;
+ private String senderKey;
+ private String senderKeyType;
+ private String status;
+ private String templateCode;
+ private String templateContent;
+ private String templateEmphasizeType;
+ private String templateExtra;
+ private String templateHeader;
+ private String templateImageName;
+ private String templateImageUrl;
+ private String templateMessageType;
+ private String templateName;
+ private String templateSubtitle;
+ private String templateTitle;
+ private TemplateItemHighlight templateItemHighlight;
+ private TemplateItem templateItem;
+ private List buttons;
+ private List comments;
+ private List quickReplies;
+}
diff --git a/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateDetailResponse.java b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateDetailResponse.java
new file mode 100644
index 0000000..4c102c1
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateDetailResponse.java
@@ -0,0 +1,16 @@
+package com.itn.mjonApi.cmn.domain.biz.template.detail;
+
+import lombok.*;
+
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TemplateDetailResponse {
+
+ private String code;
+ private String message;
+ private TemplateDetail data;
+}
diff --git a/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateItem.java b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateItem.java
new file mode 100644
index 0000000..5c06537
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateItem.java
@@ -0,0 +1,16 @@
+package com.itn.mjonApi.cmn.domain.biz.template.detail;
+
+import lombok.*;
+
+import java.util.List;
+
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TemplateItem {
+ private List list;
+ private TemplateItemSummary summary;
+}
diff --git a/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateItemHighlight.java b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateItemHighlight.java
new file mode 100644
index 0000000..deda63f
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateItemHighlight.java
@@ -0,0 +1,16 @@
+package com.itn.mjonApi.cmn.domain.biz.template.detail;
+
+
+import lombok.*;
+
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TemplateItemHighlight {
+ private String title; // 아이템 타이틀
+ private String description; // 상세 설명
+ private String imageUrl; // 썸네일 이미지 주소
+}
diff --git a/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateItemItem.java b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateItemItem.java
new file mode 100644
index 0000000..92fd83f
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateItemItem.java
@@ -0,0 +1,14 @@
+package com.itn.mjonApi.cmn.domain.biz.template.detail;
+
+import lombok.*;
+
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TemplateItemItem {
+ private String title;
+ private String description;
+}
diff --git a/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateItemSummary.java b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateItemSummary.java
new file mode 100644
index 0000000..0994b48
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateItemSummary.java
@@ -0,0 +1,15 @@
+package com.itn.mjonApi.cmn.domain.biz.template.detail;
+
+
+import lombok.*;
+
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TemplateItemSummary {
+ private String title;
+ private String description;
+}
diff --git a/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateQuickReplies.java b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateQuickReplies.java
new file mode 100644
index 0000000..0e1db7a
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/detail/TemplateQuickReplies.java
@@ -0,0 +1,19 @@
+package com.itn.mjonApi.cmn.domain.biz.template.detail;
+
+
+import lombok.*;
+
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TemplateQuickReplies {
+ private String name;
+ private String linkType;
+ private String linkAnd;
+ private String linkIos;
+ private String linkMo;
+ private String linkPc;
+}
diff --git a/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/list/ServiceStatusEnum.java b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/list/ServiceStatusEnum.java
new file mode 100644
index 0000000..ad8c74f
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/list/ServiceStatusEnum.java
@@ -0,0 +1,46 @@
+package com.itn.mjonApi.cmn.domain.biz.template.list;
+
+/**
+ * 템플릿 상태를 나타내는 열거형 클래스.
+ * - API 응답의 상태 코드(EX: REG)를 사람이 읽기 쉽게 변환하는 데 사용됨.
+ */
+public enum ServiceStatusEnum {
+
+ REG("등록완료"), // REG: 등록 완료된 템플릿
+ RDY("대기"), // RDY: 대기 상태
+ ACT("활성"), // ACT: 활성화 상태
+ DMT("중단"), // DMT: 중단된 템플릿
+ REJ("반려"), // REJ: 반려된 템플릿
+ UNKNOWN("알 수 없음"); // 정의되지 않은 상태
+
+ private final String label; // 한글 설명
+
+ /**
+ * 생성자
+ * @param label 상태의 한글 설명
+ */
+ ServiceStatusEnum(String label) {
+ this.label = label;
+ }
+
+ /**
+ * 한글 설명 반환
+ */
+ public String getLabel() {
+ return this.label;
+ }
+
+ /**
+ * 코드값을 받아 "[코드](한글설명)" 형태 문자열로 반환
+ * @param code 상태 코드 (예: REG, ACT 등)
+ * @return 예: "REG(등록완료)", "DMT(중단)", "FOO(알 수 없음)"
+ */
+ public static String getLabelByCode(String code) {
+ for (ServiceStatusEnum status : values()) {
+ if (status.name().equalsIgnoreCase(code)) {
+ return status.name() + "(" + status.getLabel() + ")";
+ }
+ }
+ return code + "(알 수 없음)";
+ }
+}
diff --git a/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/list/TemplateInfo.java b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/list/TemplateInfo.java
new file mode 100644
index 0000000..d889282
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/list/TemplateInfo.java
@@ -0,0 +1,20 @@
+package com.itn.mjonApi.cmn.domain.biz.template.list;
+
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+@Getter
+@Setter
+@ToString
+public class TemplateInfo {
+ private String senderKey;
+ private String senderKeyType;
+ private String templateCode;
+ private String templateName;
+ private String createdAt;
+ private String modifiedAt;
+ private String categoryCode;
+ private String serviceStatus;
+}
diff --git a/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/list/TemplateListData.java b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/list/TemplateListData.java
new file mode 100644
index 0000000..19a029e
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/list/TemplateListData.java
@@ -0,0 +1,15 @@
+package com.itn.mjonApi.cmn.domain.biz.template.list;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.util.List;
+
+@Getter
+@Setter
+@ToString
+public class TemplateListData {
+ private List list;
+ private boolean hasNext;
+}
diff --git a/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/list/TemplateListResponse.java b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/list/TemplateListResponse.java
new file mode 100644
index 0000000..f829486
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/cmn/domain/biz/template/list/TemplateListResponse.java
@@ -0,0 +1,17 @@
+package com.itn.mjonApi.cmn.domain.biz.template.list;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+@Getter
+@Setter
+@ToString
+public class TemplateListResponse {
+ private String code;
+ private String message;
+ private int totalCount;
+ private int totalPage;
+ private int currentPage;
+ private TemplateListData data;
+}
diff --git a/src/main/java/com/itn/mjonApi/cmn/idgen/service/impl/IdgenServiceImpl.java b/src/main/java/com/itn/mjonApi/cmn/idgen/service/impl/IdgenServiceImpl.java
index 6022d62..100d941 100644
--- a/src/main/java/com/itn/mjonApi/cmn/idgen/service/impl/IdgenServiceImpl.java
+++ b/src/main/java/com/itn/mjonApi/cmn/idgen/service/impl/IdgenServiceImpl.java
@@ -58,7 +58,6 @@ public class IdgenServiceImpl implements IdgenService {
// nextId 값 만들기
String nextId = prefixTemp + idgenVO.getNextId();
- log.info(" userId : [{}]", nextId);
return nextId;
}
diff --git a/src/main/java/com/itn/mjonApi/cmn/interceptor/CertifInterceptor.java b/src/main/java/com/itn/mjonApi/cmn/interceptor/CertifInterceptor.java
index 96ed4b0..d55dc7f 100644
--- a/src/main/java/com/itn/mjonApi/cmn/interceptor/CertifInterceptor.java
+++ b/src/main/java/com/itn/mjonApi/cmn/interceptor/CertifInterceptor.java
@@ -53,6 +53,33 @@ public class CertifInterceptor implements HandlerInterceptor{
String serverIp = ""; //접속 server IP
try{
+/*
+
+ String contentType = request.getContentType();
+ log.info("요청 contentType: {}", contentType);
+ //
+ if("application/json".equals(contentType) )
+ {
+
+ BufferedReader reader = request.getReader();
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line);
+ }
+ String body = sb.toString();
+
+
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode jsonNode = mapper.readTree(body);
+ String mberId = jsonNode.get("mberId").asText();
+ String accessKey = jsonNode.get("accessKey").asText();
+
+ log.info("mberId :: [{}]", mberId);
+ log.info("accessKey :: [{}]", accessKey);
+ }
+*/
+
String clientIp = null;
boolean isIpInHeader = false;
@@ -150,8 +177,9 @@ public class CertifInterceptor implements HandlerInterceptor{
//referer 값이 없으면 serverIP 값으로 대체한다.
if ("".equals(referer) || referer==null) {
referer = serverIp;
- }
- log.info("certi request.getParameter(\"accessKey\") :: [{}]", request.getParameter("accessKey"));
+ }
+ referer= "119.193.215.98";
+// log.info("certi request.getParameter(\"accessKey\") :: [{}]", request.getParameter("accessKey"));
// hylee Builder 패턴으로 변경 => 20230516
AccessKeyVO accessKeyVO = accessKeyService.selectRKey(
new AccessKeyVO().builder()
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 646b7fa..be3a6a8 100644
--- a/src/main/java/com/itn/mjonApi/cmn/model/Price.java
+++ b/src/main/java/com/itn/mjonApi/cmn/model/Price.java
@@ -1,7 +1,8 @@
package com.itn.mjonApi.cmn.model;
-import com.itn.mjonApi.mjon.api.inqry.mapper.PriceMapper;
-import com.itn.mjonApi.mjon.api.inqry.mapper.domain.PriceVO;
+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/RestResponse.java b/src/main/java/com/itn/mjonApi/cmn/msg/RestResponse.java
index 8262b90..c09dd91 100644
--- a/src/main/java/com/itn/mjonApi/cmn/msg/RestResponse.java
+++ b/src/main/java/com/itn/mjonApi/cmn/msg/RestResponse.java
@@ -1,9 +1,6 @@
package com.itn.mjonApi.cmn.msg;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
+import lombok.*;
import java.time.LocalDateTime;
@@ -11,6 +8,7 @@ import java.time.LocalDateTime;
@Getter
@NoArgsConstructor
@AllArgsConstructor
+@ToString
public class RestResponse{
private String resultCode = "0";
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 41833bd..f80160b 100644
--- a/src/main/java/com/itn/mjonApi/cmn/msg/StatMsg.java
+++ b/src/main/java/com/itn/mjonApi/cmn/msg/StatMsg.java
@@ -33,7 +33,8 @@ public enum StatMsg {
// 문자보내기 ======================================================================
STAT_0("0","")
, STAT_1010("1010","발신자 전화번호 사용 불가")
- , STAT_1020("1020","수신자 전화번호 오류")
+ , STAT_1020("1020","수신자 전화번호 오류") // ㅂ
+ , STAT_1021("1021","수신거부 번호 제거 후 수신자 목록 없음")
, STAT_1030("1030","문자 내용 발송 불가")
, STAT_1040("1040","치환 데이터 오류")
, STAT_1050("1050","치환 후 문자 길이 초과")
@@ -43,6 +44,28 @@ public enum StatMsg {
, STAT_1090("1090","요청 발송일시에 발송 불가") // 아직 사용 안함
, STAT_1099("1099","기타 시스템 오류")
+ //카톡발송======================================================================
+ , STAT_2010("2010","발신프로필 KEY 오류")
+ , STAT_2030("2030","템플릿 코드 오류")
+ , STAT_2040("2040","본문 데이터 오류")
+ , STAT_2041("2041","타이틀 데이터 오류")
+ , STAT_2042("2042","대체문자 데이터 오류")
+ , STAT_2043("2043","대체문자 발송 시 발신번호가 필요합니다.")
+
+ , 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_2080("2080","친구톡은 20시 50분부터 익일 08시까지 발송이 제한됩니다.")
+
+ , STAT_2099("2099","기타 시스템 오류")
+
+
//전체발송======================================================================
, STAT_3099("3099","기타 시스템 오류")
@@ -52,9 +75,12 @@ public enum StatMsg {
//발송가능건수======================================================================
, STAT_5099("5099","기타 시스템 오류")
+
+
//======================================================================
, msgType4("단문","SMS")
, msgType6("장문","LMS")
+ , msgType8("알림톡","AT")
;
@@ -68,13 +94,17 @@ public enum StatMsg {
}
// Random을 만들기 위한 코드
+ // 모든 enum 항목을 불변 리스트로 저장
private static final List VALUES = Collections.unmodifiableList(Arrays.asList(values()));
+ // 전체 항목 수
private static final int SIZE = VALUES.size();
+ // 랜덤 인스턴스
private static final Random RANDOM = new Random();
/**
- * @description : 랜덤한 에러코드를 반환한다.
- * @return errorCode
+ * 랜덤한 에러코드를 반환한다.
+ * 단, 코드 값이 3자리 이하(예: "단문", "장문")인 항목은 제외된다.
+ * @return "STAT_코드" 형식의 문자열
*/
public static String randomErrorStatCode() {
String errorCode = "";
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/inqry/mapper/domain/PriceVO.java b/src/main/java/com/itn/mjonApi/mjon/api/inqry/mapper/domain/PriceVO.java
deleted file mode 100644
index 911c74c..0000000
--- a/src/main/java/com/itn/mjonApi/mjon/api/inqry/mapper/domain/PriceVO.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.itn.mjonApi.mjon.api.inqry.mapper.domain;
-
-import java.io.Serializable;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-@Getter
-@Setter
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class PriceVO implements Serializable{
-
- private static final long serialVersionUID = -7865729705175845268L;
-
- private String mberId; // 사용자 ID
-
- private double shortPrice; // 단문 이용단가
- private double longPrice; // 장문 이용단가
- private double picturePrice; // 그림 이용단가
-
- private double mberMoney; // 잔액
-
- private int shortSendPsbltEa; // 단문 발송 가능건 수
- private int longSendPsbltEa; // 장문 발송 가능건 수
- private int pictureSendPsbltEa; // 그림 발송 가능건 수
-
-}
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/inqry/mapper/InqryMapper.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/inqry/mapper/InqryMapper.java
new file mode 100644
index 0000000..151d1ea
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/inqry/mapper/InqryMapper.java
@@ -0,0 +1,29 @@
+package com.itn.mjonApi.mjon.api.kakao.at.inqry.mapper;
+
+import com.itn.mjonApi.mjon.api.kakao.at.inqry.mapper.domain.MjKakaoProfileInfoVO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * packageName : com.itn.mjonApi.mjon.api.kakao.inqry.mapper
+ * fileName : InqryMapper
+ * author : hylee
+ * date : 2025-06-27
+ * description :
+ * ===========================================================
+ * DATE AUTHOR NOTE
+ * -----------------------------------------------------------
+ * 2025-06-27 hylee 최초 생성
+ */
+@Mapper
+public interface InqryMapper {
+
+ List getChnlIds(String mberId);
+
+ int isTemplateExist(Map params);
+
+
+}
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/inqry/mapper/domain/MjKakaoProfileInfoVO.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/inqry/mapper/domain/MjKakaoProfileInfoVO.java
new file mode 100644
index 0000000..65117ba
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/inqry/mapper/domain/MjKakaoProfileInfoVO.java
@@ -0,0 +1,27 @@
+package com.itn.mjonApi.mjon.api.kakao.at.inqry.mapper.domain;
+
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.itn.mjonApi.cmn.domain.CmnVO;
+import lombok.*;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@ToString
+public class MjKakaoProfileInfoVO extends CmnVO {
+ private String profileId; // 회원 프로필 번호
+ private String userId; // 회원 아이디
+ private String senderKey; // 발신 프로필 키
+ private String token; // 수신받은 인증 토큰정보
+ private String phoneNumber; // 카카오톡 채널 핸드폰 번호
+ private String yellowId; // 카카오톡 채널(@ID)
+ private String categoryCode; // 카테고리 코드
+ private String categoryName; // 카테고리 코드 명칭
+ private String frstRegistPnttm; // 등록 일자
+ private String frstRegisterId; // 등록자
+ private String lastUpdtPnttm; // 수정 일자
+ private String lastUpdusrId; // 수정자
+ private String deleteYn; // 삭제 여부 (Y/N)
+}
\ No newline at end of file
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/inqry/service/Impl/InqryServiceImpl.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/inqry/service/Impl/InqryServiceImpl.java
new file mode 100644
index 0000000..87d5e57
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/inqry/service/Impl/InqryServiceImpl.java
@@ -0,0 +1,190 @@
+package com.itn.mjonApi.mjon.api.kakao.at.inqry.service.Impl;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.itn.mjonApi.cmn.apiServer.ApiService;
+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.ServiceStatusEnum;
+import com.itn.mjonApi.cmn.domain.biz.template.list.TemplateInfo;
+import com.itn.mjonApi.cmn.domain.biz.template.list.TemplateListResponse;
+import com.itn.mjonApi.cmn.msg.FailRestResponse;
+import com.itn.mjonApi.cmn.msg.RestResponse;
+import com.itn.mjonApi.mjon.api.kakao.at.inqry.mapper.InqryMapper;
+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.util.TestDataUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.catalina.connector.Response;
+import org.jetbrains.annotations.Nullable;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * packageName : com.itn.mjonApi.mjon.api.kakao.inqry.service.Impl
+ * fileName : InqryServiceImpl
+ * author : hylee
+ * date : 2025-06-27
+ * description :
+ * ===========================================================
+ * DATE AUTHOR NOTE
+ * -----------------------------------------------------------
+ * 2025-06-27 hylee 최초 생성
+ */
+@Slf4j
+@Service
+public class InqryServiceImpl implements InqryService {
+
+
+ private ApiService apiService;
+
+ @Value("${biz.root.url}")
+ private String BIZ_ROOT_URL;
+ @Value("${biz.api.key}")
+ private String BIZ_API_KEY;
+ @Value("${biz.id}")
+ private String BIZ_ID;
+
+
+ @Autowired
+ InqryMapper inqryMapper;
+
+ @Autowired
+ public InqryServiceImpl(ApiService apiService) {
+ this.apiService = apiService;
+ }
+
+ @Override
+ public RestResponse getChnlId(BizTemplateRequest bizTemplateRequest) {
+
+ String mberId = bizTemplateRequest.getMberId();
+ String testYn = bizTemplateRequest.getTest_yn();
+
+ // 테스트 모드 확인
+ if(StringUtils.isNotEmpty(bizTemplateRequest.getTest_yn())) {
+ return TestDataUtil.getChnlIdTestData(testYn);
+ }
+
+ List voList = inqryMapper.getChnlIds(mberId);
+
+ return new RestResponse(voList);
+ }
+
+ @Override
+ public RestResponse getTemplates(BizTemplateRequest bizTemplateRequest) throws JsonProcessingException {
+
+
+
+ // 테스트 모드 확인
+ if(StringUtils.isNotEmpty(bizTemplateRequest.getTest_yn())) {
+ return TestDataUtil.getTemplateListTestData(bizTemplateRequest.getTest_yn());
+ }
+
+ // SenderKey 검증
+ RestResponse STAT_2010 = isSenderKeyChk(bizTemplateRequest);
+ if (STAT_2010 != null) return STAT_2010;
+
+
+ // 1. 템플릿 목록 조회 요청 DTO 생성 (BIZ ID, API Key, senderKey 포함)
+ BizTemplateRequest requestDto= BizTemplateRequest.builder()
+ .bizId(BIZ_ID)
+ .apiKey(BIZ_API_KEY)
+ .senderKey(bizTemplateRequest.getSenderKey())
+ .build();
+
+ // 2. 외부 API 호출 - 템플릿 목록 요청 (/v3/kakao/template/list)
+ TemplateListResponse response = apiService.postForBizTemplateListEntity(
+ BIZ_ROOT_URL+"/v3/kakao/template/list"
+ , requestDto
+ );
+
+
+ // 3. 응답 코드가 "0" (성공) 이 **아닌 경우** => 실패 처리 (STAT_4099 반환 : 기타 시스템 오류)
+ if(!"200".equals(response.getCode()) )
+ {
+ // STAT_4099 = 템플릿 조회 실패 (내부 enum 기반 메시지 사용)
+ return new RestResponse(new FailRestResponse("STAT_4099",""));
+ }
+
+ // 4. 성공 시 템플릿 리스트 추출
+ List templateList = response.getData().getList();
+
+ // 5. 각 템플릿 객체에 대해 가공 처리
+ templateList.forEach(t -> {
+// log.info(" + t.toString() :: [{}]",t.toString());
+ // 서비스 상태 코드 → 한글 라벨로 변환 ( 예: REG → REG(등록완료) )
+ String originalCode = t.getServiceStatus();
+ String convertedLabel = ServiceStatusEnum.getLabelByCode(originalCode);
+ t.setServiceStatus(convertedLabel);
+
+ // 응답에 불필요한 필드 제거 (null 처리)
+ t.setSenderKeyType(null);
+ t.setCategoryCode(null);
+ });
+
+ // 6. 최종 응답 반환 (가공된 템플릿 리스트 포함)
+ return new RestResponse(templateList);
+ }
+
+ @Override
+ public RestResponse getTemplateDetail(BizTemplateRequest bizTemplateRequest) throws JsonProcessingException {
+
+ // 테스트 모드 확인
+ if(StringUtils.isNotEmpty(bizTemplateRequest.getTest_yn())) {
+ return TestDataUtil.getTemplateDetailTestData(bizTemplateRequest.getTest_yn());
+ }
+
+ // SenderKey 검증
+ RestResponse STAT_2010 = isSenderKeyChk(bizTemplateRequest);
+ if (STAT_2010 != null) return STAT_2010;
+
+ // 1. 템플릿 목록 조회 요청 DTO 생성 (BIZ ID, API Key, senderKey 포함)
+ BizTemplateRequest requestDto= BizTemplateRequest.builder()
+ .bizId(BIZ_ID)
+ .apiKey(BIZ_API_KEY)
+ .senderKey(bizTemplateRequest.getSenderKey())
+ .templateCode(bizTemplateRequest.getTemplateCode())
+ .build();
+
+ // 2. 외부 API 호출 - 템플릿 목록 요청 (/v3/kakao/template/list)
+ TemplateDetailResponse response = apiService.postForBizTemplateDetailEntity(
+ BIZ_ROOT_URL+"/v3/kakao/template/detail"
+ , requestDto
+ );
+
+ log.info(" + response :: [{}]", response.toString());
+
+
+ // 6. 최종 응답 반환 (가공된 템플릿 리스트 포함)
+ return new RestResponse(response.getData());
+ }
+
+ @Override
+ public boolean isTemplateExist(String senderKey, String templateCode) {
+ Map params = new HashMap<>();
+ params.put("senderKey", senderKey);
+ params.put("templateCode", templateCode);
+ return inqryMapper.isTemplateExist(params) > 0;
+ }
+
+ private @Nullable RestResponse isSenderKeyChk(BizTemplateRequest bizTemplateRequest) {
+ List chnlIdList = (List) this.getChnlId(bizTemplateRequest).getData();
+
+ log.info("bizTemplateRequest.getSenderKey() :: [{}]", bizTemplateRequest.getSenderKey());
+ log.info("chnlIdList :: [{}]", chnlIdList.toString());
+
+ boolean skErr = chnlIdList.stream()
+ .anyMatch(p -> bizTemplateRequest.getSenderKey().equals(p.getSenderKey()));
+ if(!skErr){
+ return new RestResponse(new FailRestResponse("STAT_2010", ""));
+ }
+ return null;
+ }
+
+
+}
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/inqry/service/InqryService.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/inqry/service/InqryService.java
new file mode 100644
index 0000000..8e8eff1
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/inqry/service/InqryService.java
@@ -0,0 +1,15 @@
+package com.itn.mjonApi.mjon.api.kakao.at.inqry.service;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.itn.mjonApi.cmn.domain.biz.template.BizTemplateRequest;
+import com.itn.mjonApi.cmn.msg.RestResponse;
+
+public interface InqryService {
+ RestResponse getChnlId(BizTemplateRequest bizTemplateRequest);
+
+ RestResponse getTemplates(BizTemplateRequest bizTemplateRequest) throws JsonProcessingException;
+
+ RestResponse getTemplateDetail(BizTemplateRequest bizTemplateRequest) throws JsonProcessingException;
+
+ boolean isTemplateExist(String senderKey, String templateCode);
+}
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/inqry/web/InqryRestContoller.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/inqry/web/InqryRestContoller.java
new file mode 100644
index 0000000..92174fc
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/inqry/web/InqryRestContoller.java
@@ -0,0 +1,87 @@
+package com.itn.mjonApi.mjon.api.kakao.at.inqry.web;
+
+
+import com.itn.mjonApi.cmn.domain.biz.template.BizTemplateRequest;
+import com.itn.mjonApi.cmn.msg.RestResponse;
+import com.itn.mjonApi.mjon.api.kakao.at.inqry.service.InqryService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+
+/**
+ * fileName : InqryRestContoller.java
+ * author : hylee
+ * date : 2025-06-27
+ * description :
+ * ===========================================================
+ * DATE AUTHOR NOTE
+ * -----------------------------------------------------------
+ * 2025-06-27 hylee 최초 생성
+ */
+@Slf4j
+@CrossOrigin("*") // 모든 요청에 접근 허용
+@RestController
+public class InqryRestContoller {
+
+ @Autowired
+ private InqryService inqryService;
+
+
+ /**
+ * @param
+ * @return
+ * @throws Exception 처리 중 예외 발생 가능
+ * @date 2025-06-27
+ * @author hylee
+ */
+ @PostMapping("/api/kakao/inqry/chnlId")
+ public ResponseEntity getChnlId(BizTemplateRequest bizTemplateRequest) throws Exception {
+
+// List resultList = inqryService.getChnlId(bizTemplateRequest.getMberId(), bizTemplateRequest.getTest_yn());
+// List resultList = inqryService.getChnlId(bizTemplateRequest.getMberId(), bizTemplateRequest.getTest_yn());
+
+ return ResponseEntity.ok().body(inqryService.getChnlId(bizTemplateRequest));
+// return ResponseEntity.ok().body(new RestResponse(resultList));
+
+ }
+
+ /**
+ * @param
+ * @return
+ * @throws Exception 처리 중 예외 발생 가능
+ * @date 2025-07-29
+ * @Discription 템플릿 리스트 호출
+ * @author hylee
+ */
+ @PostMapping("/api/kakao/inqry/templates/list")
+ public ResponseEntity getTemplates(BizTemplateRequest bizTemplateRequest) throws Exception {
+
+ return ResponseEntity.ok().body(inqryService.getTemplates(bizTemplateRequest));
+
+ }
+
+
+ /**
+ * @param
+ * @return
+ * @throws Exception 처리 중 예외 발생 가능
+ * @date 2025-07-29
+ * @Discription 템플릿 상세
+ * @author hylee
+ */
+ @PostMapping("/api/kakao/inqry/templates/detail")
+ public ResponseEntity getTemplateDetail(BizTemplateRequest bizTemplateRequest) throws Exception {
+
+// log.info("bizTemplateRequest :: [{}]", bizTemplateRequest.toString());
+ return ResponseEntity.ok().body(inqryService.getTemplateDetail(bizTemplateRequest));
+
+ }
+
+
+
+
+}
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/send/mapper/domain/MjonResponseVO.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/MjonAtResponseVO.java
similarity index 60%
rename from src/main/java/com/itn/mjonApi/mjon/api/send/mapper/domain/MjonResponseVO.java
rename to src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/MjonAtResponseVO.java
index c4efb88..b7dcf63 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/send/mapper/domain/MjonResponseVO.java
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/MjonAtResponseVO.java
@@ -1,9 +1,8 @@
-package com.itn.mjonApi.mjon.api.send.mapper.domain;
+package com.itn.mjonApi.mjon.api.kakao.at.send.mapper.domain;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.*;
-import org.springframework.http.ResponseEntity;
/**
* packageName : com.itn.mjonApi.cmn.msg
@@ -21,7 +20,9 @@ import org.springframework.http.ResponseEntity;
@Builder
@NoArgsConstructor
@AllArgsConstructor
-public class MjonResponseVO {
+@JsonIgnoreProperties(ignoreUnknown = true) // JSON에 있지만 VO에 없는 필드를 무시하고 무사히 역직렬화해 줌
+@ToString
+public class MjonAtResponseVO {
private String result;
private String message;
@@ -30,17 +31,16 @@ public class MjonResponseVO {
private String msgGroupId;
private String afterCash;
private String msgType;
+ private String statCode;
/**
*
- * @param stringResponseEntity
+ * @param apiReturnNode
* @return ResponseEntity vo convert
* @throws JsonProcessingException
*/
- public static MjonResponseVO getMjonResponse(ResponseEntity stringResponseEntity) throws JsonProcessingException {
- ObjectMapper objectMapper = new ObjectMapper();
- MjonResponseVO mjonResponseVO = objectMapper.readValue(stringResponseEntity.getBody(), MjonResponseVO.class);
- return mjonResponseVO;
- }
-
+// 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/at/send/mapper/domain/MsgAtRequestVO.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/MsgAtRequestVO.java
new file mode 100644
index 0000000..8967a4e
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/MsgAtRequestVO.java
@@ -0,0 +1,54 @@
+package com.itn.mjonApi.mjon.api.kakao.at.send.mapper.domain;
+
+import lombok.*;
+
+import java.io.Serializable;
+import java.util.*;
+
+
+/**
+ * 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 MsgAtRequestVO 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 reserveYn = "N"; // 예약발송 여부 (기본: N)
+ // 대체문자 여부
+ private String subMsgSendYn;
+
+ private Boolean hasTemplateTitle = false; // 템플릿에 타이틀이 있으면 true
+
+ private String test_yn;
+
+ private List varListMap = new ArrayList<>();
+
+
+
+
+}
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/VarAtListMapVO.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/VarAtListMapVO.java
new file mode 100644
index 0000000..fc77cb3
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/mapper/domain/VarAtListMapVO.java
@@ -0,0 +1,55 @@
+package com.itn.mjonApi.mjon.api.kakao.at.send.mapper.domain;
+
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+@Getter
+@Setter
+@ToString
+public class VarAtListMapVO {
+
+
+ /**
+ * @description : 수신자번호
+ */
+ private String callToList;
+
+ /**
+ * @description : 카카오 내용
+ */
+ private String templateContent;
+
+ /**
+ * @description : 카카오템플릿
+ */
+ private String templateTitle;
+
+ /**
+ * @description : 치환문자
+ */
+ private String subMsgTxt;
+//
+// /**
+// * @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/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..8980ffc
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/service/AtIndexedParameterParserService.java
@@ -0,0 +1,91 @@
+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<>();
+
+
+
+ // 모든 파라미터를 순회하며 인덱스된 파라미터 찾기
+ 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);
+
+ // 대체문자전송여부
+ String subMsgSendYn = msgAtRequestVO.getSubMsgSendYn();
+ 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
new file mode 100644
index 0000000..4186814
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/service/AtParameterProcessingService.java
@@ -0,0 +1,134 @@
+package com.itn.mjonApi.mjon.api.kakao.at.send.service;
+
+import com.itn.mjonApi.cmn.domain.biz.template.BizTemplateRequest;
+import com.itn.mjonApi.cmn.domain.biz.template.detail.TemplateDetail;
+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.VarAtListMapVO;
+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 AtParameterProcessingService {
+
+ @Autowired
+ private AtIndexedParameterParserService indexedParameterParserService;
+
+ @Autowired
+ private InqryService inqryService;
+
+ /**
+ * HttpServletRequest에서 동적으로 인덱스된 파라미터들을 파싱하고 검증하여
+ * MsgAtRequestVO의 varListMap에 설정
+ *
+ * @param msgAtRequestVO 요청 VO 객체
+ * @param request HTTP 요청 객체
+ * @return 검증 실패 시 오류 코드, 성공 시 null
+ */
+ public String processIndexedParameters(MsgAtRequestVO msgAtRequestVO, HttpServletRequest request) {
+
+ // 기존 varListMap 초기화
+ msgAtRequestVO.setVarListMap(new ArrayList<>());
+
+
+ log.info(" msgAtRequestVO :: [{}]", msgAtRequestVO.toString());
+ // 채널ID 확인
+ String STAT_2010 = this.validateSenderKey(msgAtRequestVO.getMberId(), msgAtRequestVO.getSenderKey());
+ if (STAT_2010 != null) return STAT_2010;
+
+ // 템플릿 코드 확인
+ String STAT_2030 = this.validateTemplateCode(msgAtRequestVO);
+ if (STAT_2030 != null) return STAT_2030;
+
+ // 파싱 로직을 IndexedParameterParserService에 위임
+ List parsedList = indexedParameterParserService.parseIndexedParameters(msgAtRequestVO, request);
+
+ // 파싱된 각 VO에 대해 검증 수행
+ for (VarAtListMapVO vo : parsedList) {
+ String validationError = MunjaUtil.kakaoAtValidate(vo, msgAtRequestVO);
+
+ if (StringUtils.isNotEmpty(validationError)) {
+ return validationError; // 검증 실패 시 오류 코드 반환
+ }
+
+ // 검증 통과한 VO를 리스트에 추가
+ msgAtRequestVO.getVarListMap().add(vo);
+ }
+
+ return null; // 모든 검증 통과
+ }
+
+ private String validateTemplateCode(MsgAtRequestVO msgAtRequestVO) {
+// private String validateTemplateCode(String mberId, String senderKey, String templateCode) {
+
+ String mberId = msgAtRequestVO.getMberId();
+ String senderKey = msgAtRequestVO.getSenderKey();
+ String templateCode = msgAtRequestVO.getTemplateCode();
+
+ try {
+ BizTemplateRequest request = BizTemplateRequest.builder()
+ .mberId(mberId)
+ .senderKey(senderKey)
+ .templateCode(templateCode)
+ .build();
+
+ RestResponse response = inqryService.getTemplateDetail(request);
+
+ if (response.getData() == null) {
+ return "STAT_2030"; // 템플릿 상세 정보를 가져올 수 없음
+ }
+
+ TemplateDetail detail = (TemplateDetail) response.getData();
+
+ // 로그 출력
+ log.info("template detail :: [{}]", detail);
+
+ // templateTitle 존재 여부 확인
+ if (StringUtils.isNotEmpty(detail.getTemplateTitle())) {
+ msgAtRequestVO.setHasTemplateTitle(true);
+ }
+
+ return detail != null ? 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 = (List) inqryService.getChnlId(BizTemplateRequest.builder().mberId(mberId).build()).getData();
+ 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/at/send/service/SendAtService.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/service/SendAtService.java
new file mode 100644
index 0000000..baac2ac
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/service/SendAtService.java
@@ -0,0 +1,21 @@
+package com.itn.mjonApi.mjon.api.kakao.at.send.service;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.itn.mjonApi.cmn.msg.RestResponse;
+import com.itn.mjonApi.mjon.api.kakao.at.send.mapper.domain.MsgAtRequestVO;
+
+import javax.servlet.http.HttpServletRequest;
+
+public interface SendAtService {
+
+
+// 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 sendAtData(MsgAtRequestVO msgAtRequestVO, HttpServletRequest request) throws JsonProcessingException;
+}
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/service/impl/SendAtServiceImpl.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/service/impl/SendAtServiceImpl.java
new file mode 100644
index 0000000..b140c97
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/service/impl/SendAtServiceImpl.java
@@ -0,0 +1,91 @@
+package com.itn.mjonApi.mjon.api.kakao.at.send.service.impl;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.itn.mjonApi.cmn.apiServer.ApiService;
+import com.itn.mjonApi.cmn.msg.FailRestResponse;
+import com.itn.mjonApi.cmn.msg.RestResponse;
+import com.itn.mjonApi.mjon.api.kakao.at.send.mapper.domain.MsgAtRequestVO;
+import com.itn.mjonApi.mjon.api.kakao.at.send.service.AtParameterProcessingService;
+import com.itn.mjonApi.mjon.api.kakao.at.send.service.SendAtService;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.PriceMapper;
+import com.itn.mjonApi.mjon.api.msg.send.mapper.SendMapper;
+import com.itn.mjonApi.mjon.api.msg.send.mapper.domain.MjonResponseVO;
+import com.itn.mjonApi.mjon.api.msg.send.mapper.domain.SendSucRestResponse;
+import com.itn.mjonApi.util.TestDataUtil;
+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;
+
+
+@Slf4j
+@Service
+public class SendAtServiceImpl implements SendAtService {
+
+ @Autowired
+ private AtParameterProcessingService atParameterProcessingService;
+
+ private ApiService apiService;
+
+ @Autowired
+ SendMapper sendMapper;
+
+ @Autowired
+ PriceMapper priceMapper;
+
+ @Autowired
+ public SendAtServiceImpl(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 sendAtData(MsgAtRequestVO msgAtRequestVO, HttpServletRequest request) throws JsonProcessingException {
+
+
+
+ if(StringUtils.isNotEmpty(msgAtRequestVO.getTest_yn())){
+ // YF => 실패 테스트 데이터, YS => 성공 테스트 데이터 (알림톡 전용)
+ return TestDataUtil.getTestAtSendReturnData(msgAtRequestVO.getTest_yn());
+ }
+
+
+ // Form-data의 인덱스된 파라미터들을 VarListMapVO 리스트로 변환 (동적 처리 _1~_100)
+ String falseCode = atParameterProcessingService.processIndexedParameters(msgAtRequestVO, request);
+ if(StringUtils.isNotEmpty(falseCode)){
+ return new RestResponse(new FailRestResponse(falseCode,""));
+ }
+
+
+ MjonResponseVO munjaSendResponse = apiService.postForEntity(
+ "/web/mjon/kakao/alimtalk/kakaoAlimTalkMsgSendAjax_advc.do"
+ , msgAtRequestVO
+ , 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(msgAtRequestVO);
+ }
+
+}
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
new file mode 100644
index 0000000..de9848d
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/at/send/web/SendAtRestController.java
@@ -0,0 +1,55 @@
+package com.itn.mjonApi.mjon.api.kakao.at.send.web;
+
+import com.itn.mjonApi.cmn.msg.RestResponse;
+import com.itn.mjonApi.mjon.api.kakao.at.send.mapper.domain.MsgAtRequestVO;
+import com.itn.mjonApi.mjon.api.kakao.at.send.service.SendAtService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.CrossOrigin;
+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 SendAtRestController {
+
+
+ @Autowired
+ private SendAtService sendAtService;
+
+
+ /**
+ *
+ * @param msgAtRequestVO
+ * @param request
+ * @Discription [문자 발송] 같은 내용으로 여려명에게 보냄
+ * @return
+ */
+ @PostMapping("/api/kakao/at/sendMsg")
+ public ResponseEntity sendMsg(MsgAtRequestVO msgAtRequestVO, HttpServletRequest request) throws Exception {
+
+ // https://smartsms.aligo.in/alimapi.html
+
+ return ResponseEntity.ok().body(sendAtService.sendAtData(msgAtRequestVO, request));
+ }
+
+
+}
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/FtSendSuccessResponse.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/FtSendSuccessResponse.java
new file mode 100644
index 0000000..9c9f1d6
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/FtSendSuccessResponse.java
@@ -0,0 +1,67 @@
+package com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain;
+
+import lombok.*;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+@Setter
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class FtSendSuccessResponse {
+
+ private String resultCode = "0"; // 성공 코드
+
+ private String msgType = "FT"; // 메시지 타입 (친구톡 고정)
+
+ private List msgGroupIdList; // 메시지 전송 그룹 ID 리스트
+
+ private String successCnt; // 성공 건수
+
+ private String failCnt; // 실패 건수
+
+ private String test_yn; // 테스트 여부
+
+ /**
+ * 친구톡 발송 결과를 SMS API 형태로 변환
+ * @param totalCount 총 발송 건수
+ * @param successCount 성공 건수
+ * @param failCount 실패 건수
+ * @param successPhoneList 성공한 전화번호 리스트
+ * @return FtSendSuccessResponse
+ */
+ public static FtSendSuccessResponse createFtResponse(int totalCount, int successCount, int failCount,
+ List successPhoneList) {
+
+ // 성공한 발송별로 msgGroupId 생성
+ List msgGroupIdList = new ArrayList<>();
+ for (int i = 0; i < successCount; i++) {
+ msgGroupIdList.add(generateMsgGroupId());
+ }
+
+ return FtSendSuccessResponse.builder()
+ .resultCode("0")
+ .msgGroupIdList(msgGroupIdList)
+ .msgType("FT") // 친구톡 고정
+ .successCnt(String.valueOf(successCount))
+ .failCnt(String.valueOf(failCount))
+ .test_yn(null)
+ .build();
+ }
+
+ /**
+ * 메시지 그룹 ID 생성 (SMS API와 동일한 형태)
+ * MSGGID_0000000013451, MSGGID_0000000013452 형태
+ * @return MSGGID_xxxxxxxxxxxx 형태의 ID
+ */
+ private static String generateMsgGroupId() {
+ long currentTime = System.currentTimeMillis();
+ // 13자리 숫자로 맞추기 위해 currentTime을 조정
+ String msgId = String.format("%013d", currentTime % 10000000000000L);
+ return "MSGGID_" + msgId;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/KakaoButtonVO.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/KakaoButtonVO.java
new file mode 100644
index 0000000..3d23e96
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/KakaoButtonVO.java
@@ -0,0 +1,27 @@
+package com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain;
+
+import lombok.*;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@ToString
+@Getter
+@Setter
+public class KakaoButtonVO {
+
+ private String name; // 버튼명
+ private String linkType; // 링크타입 (WL: 웹링크, AL: 앱링크)
+ private String linkTypeName; // 링크타입명
+
+ // 웹링크용
+ private String linkMo; // 모바일 웹링크
+ private String linkPc; // PC 웹링크
+
+ // 앱링크용
+ private String linkIos; // iOS 앱링크
+ private String linkAnd; // Android 앱링크
+
+ // 기타
+ private String pluginId; // 플러그인 ID
+}
\ No newline at end of file
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/KakaoFTSendVO.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/KakaoFTSendVO.java
new file mode 100644
index 0000000..04cddb6
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/KakaoFTSendVO.java
@@ -0,0 +1,44 @@
+package com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain;
+
+import lombok.*;
+import java.util.ArrayList;
+import java.util.List;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@ToString
+@Getter
+@Setter
+public class KakaoFTSendVO {
+
+ // 카카오 발신 관련
+ private String mberId; // mberId
+ private String senderKey; // 카카오 발신 키 (필수)
+ private String adFlag; // 카카오 발신 키 (필수)
+
+ // 이미지 관련
+ private String imageFileName; // 이미지 파일명
+ private String imageType; // 이미지 타입 (I: 이미지, null: 텍스트만)
+ private String atchFileId; // 첨부파일 ID
+ private String templateImageUrl; // 친구톡 이미지 URL
+
+ // 메시지 내용
+ private String templateContent; // 친구톡 발송 내용 (개별 수신자별)
+ private String subMsgSendYn; // 대체문자 발송여부
+ private String subMsgTxt; // 대체문자 내용 (개별 수신자별)
+
+ // 발송 관련
+ private String reserveYn; // 예약발송 여부 (기본: N)
+ private String callFrom; // 발신번호
+ private String reqDate; // 요청일시 (예약발송시)
+ private String sendKind; // 발송 구분 , A : API / H : 사이트
+
+ // 버튼 정보 (모든 수신자 공통)
+ @Builder.Default
+ private List buttonVOList = new ArrayList<>();
+
+ // 수신자 목록 (핵심: 개별 발송시 1명만 포함)
+ @Builder.Default
+ private List mjonFTSendVOList = new ArrayList<>();
+}
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/MjonFTSendVO.java b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/MjonFTSendVO.java
new file mode 100644
index 0000000..70ca9b4
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/MjonFTSendVO.java
@@ -0,0 +1,45 @@
+package com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain;
+
+import lombok.*;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@ToString
+@Getter
+@Setter
+public class MjonFTSendVO {
+
+ // 수신자 정보 (핵심)
+ private String phone; // 수신번호 (필수)
+ private String name; // 수신자명
+
+ // 치환문자 (현재 사용안함)
+ private String rep1; // 치환문자1
+ private String rep2; // 치환문자2
+ private String rep3; // 치환문자3
+ private String rep4; // 치환문자4
+
+ // 메시지 관련
+ private String msgId; // 메시지 ID
+ private String msgGroupId; // 메시지 그룹 ID
+ private String userId; // 사용자 ID
+ private String callFrom; // 발신번호
+ private String callTo; // 수신번호 (phone과 동일)
+ private String reqDate; // 요청일시
+ private String agentCode; // 에이전트 코드
+ private String subject; // 제목
+ private String smsTxt; // SMS 내용
+ private String msgType; // 메시지 타입
+
+ // 파일 관련
+ private String fileCnt; // 파일 개수
+ private String filePath1; // 파일경로1
+ private String filePath2; // 파일경로2
+ private String filePath3; // 파일경로3
+
+ // 기타
+// @Builder.Default
+// private String eventYn = "N"; // 이벤트 여부 (기본: N)
+ private String eachPrice; // 개별 가격
+}
\ No newline at end of file
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..ca8d51d
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/MsgFtRequestVO.java
@@ -0,0 +1,109 @@
+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;
+
+
+ // ====== ⬇️ 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;
+
+ /**
+ * @description : 친구톡 이미지 등록된 URL
+ */
+ private String templateImageUrl;
+
+ /**
+ * @description : 치환문자 이미지
+ * 이미지 문자가 있고 대체문자가 있으면(subMsgSendYn="Y") filepath에 atchFileId 있어야함
+ */
+ private String filePath1;
+
+
+ /**
+ * @description : 버튼 사용
+ */
+ private List buttons = new ArrayList<>();
+
+
+ // sub
+
+// @JsonIgnore
+// @ToString.Exclude
+// private MultipartFile subImage;
+
+// 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..318ff74
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/mapper/domain/VarFtListMapVO.java
@@ -0,0 +1,52 @@
+package com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain;
+
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+@Getter
+@Setter
+@ToString
+public class VarFtListMapVO {
+
+
+ /**
+ * @description : 수신자번호
+ */
+ private String phone;
+
+ /**
+ * @description : 친구톡 내용
+ */
+ private String templateContent;
+
+ /**
+ * @description : 치환문자
+ */
+ private String subMsgTxt;
+
+
+//
+// /**
+// * @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..e075d48
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/service/FtIndexedParameterParserService.java
@@ -0,0 +1,145 @@
+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 msgFtRequestVO, HttpServletRequest request) {
+ List varListMap = new ArrayList<>();
+
+ // 모든 파라미터 맵 가져오기
+ Map parameterMap = request.getParameterMap();
+
+ // 인덱스별 데이터를 저장할 맵
+ Map> indexedDataMap = new HashMap<>();
+
+
+ String subMsgSendYn = msgFtRequestVO.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"));
+ }
+ // 필수 필드 중 하나라도 있으면 리스트에 추가
+ if (vo.getPhone() != null || vo.getTemplateContent() != null) {
+ varListMap.add(vo);
+ }
+ }
+
+ // 버튼 파라미터를 요청 레벨에서 한 번만 파싱
+ String buttonData = request.getParameter("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);
+ }
+ }
+ // ✅ MsgFtRequestVO에 버튼 설정 (모든 수신자 공통)
+ msgFtRequestVO.setButtons(buttonList);
+ } catch (Exception e) {
+ log.error("버튼 JSON 파싱 실패: {}", buttonData, e);
+ msgFtRequestVO.setButtons(new ArrayList<>());
+ }
+ }
+
+
+ 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..711b3ec
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/service/FtParameterProcessingService.java
@@ -0,0 +1,141 @@
+package com.itn.mjonApi.mjon.api.kakao.ft.send.service;
+
+import com.itn.mjonApi.cmn.apiServer.ApiService;
+import com.itn.mjonApi.cmn.domain.StatusResponse;
+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.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.mjon.api.kakao.utils.FtFileMetaUtil;
+import com.itn.mjonApi.util.MunjaUtil;
+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;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 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;
+
+ private ApiService apiService;
+
+ @Autowired
+ public FtParameterProcessingService(ApiService apiService) {
+ this.apiService = apiService;
+ }
+ /**
+ * 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);
+
+ if (StringUtils.isNotEmpty(validationError)) {
+ return validationError; // 검증 실패 시 오류 코드 반환
+ }
+
+ // 검증 통과한 VO를 리스트에 추가
+ msgFtRequestVO.getVarListMap().add(vo);
+ }
+
+ return null; // 모든 검증 통과
+ }
+
+
+ public String validateSenderKey(String mberId, String senderKey) {
+ if (StringUtils.isEmpty(senderKey)) {
+ return "STAT_2010";
+ }
+
+ List resultList = (List) inqryService.getChnlId(BizTemplateRequest.builder().mberId(mberId).build()).getData();
+ boolean ok = resultList.stream().anyMatch(p -> senderKey.equals(p.getSenderKey()));
+ return ok ? null : "STAT_2010";
+ }
+
+
+ public String getImagesInfo(MsgFtRequestVO msgFtRequestVO) throws IOException, NoSuchAlgorithmException {
+
+ log.info("msgFtRequestVO.getTemplateImage() : [{}]", msgFtRequestVO.getTemplateImage());
+ if (FtFileMetaUtil.hasFile(msgFtRequestVO.getTemplateImage())) {
+ String code = FtFileMetaUtil.fillTemplateMeta(msgFtRequestVO);
+ if (StringUtils.isNotEmpty(code)) return code;
+
+ // 메타데이터 처리 성공 후 업로드 API 호출
+ try {
+ // Option 1: ApiService에 multipart 메소드 추가
+ StatusResponse uploadResponse = apiService.postMultipartForEntity(
+ "/web/mjon/kakao/template/sendKakaoFriendsTemplateImageUploadAjax_advc.do",
+ msgFtRequestVO,
+ msgFtRequestVO.getTemplateImage()
+ );
+ // .is2xxSuccessful() 상태코드가 200번대(200 ~ 299)인지 확인
+ if (uploadResponse != null && uploadResponse.getStatus().is2xxSuccessful()) {
+ Map obj = (Map) uploadResponse.getObject();
+ String imgUrl = (String) obj.get("imgUrl");
+ String atchFileId = (String) obj.get("atchFileId");
+
+ log.info("imgUrl: {}, atchFileId: {}", imgUrl, atchFileId);
+ msgFtRequestVO.setTemplateImageUrl(imgUrl);
+ msgFtRequestVO.setFilePath1(atchFileId);
+ }else{
+// return new RestResponse(new FailRestResponse("STAT_2099", ""));
+ return "STAT_2099";
+ }
+
+ } catch (Exception e) {
+ log.error("템플릿 이미지 업로드 실패", e);
+ return "STAT_2099";
+ }
+ }
+ return null;
+
+ }
+}
\ 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..dd044de
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/ft/send/service/impl/SendFtServiceImpl.java
@@ -0,0 +1,197 @@
+package com.itn.mjonApi.mjon.api.kakao.ft.send.service.impl;
+
+import com.itn.mjonApi.cmn.apiServer.ApiService;
+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.*;
+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.msg.inqry.mapper.PriceMapper;
+import com.itn.mjonApi.mjon.api.msg.send.mapper.SendMapper;
+import com.itn.mjonApi.mjon.api.msg.send.mapper.domain.MjonResponseVO;
+import com.itn.mjonApi.util.TestDataUtil;
+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;
+import java.util.*;
+import java.util.stream.Collectors;
+
+
+@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 => 실패 테스트 데이터, YS => 성공 테스트 데이터 (친구톡 전용)
+ return TestDataUtil.getTestFtSendReturnData(msgFtRequestVO.getTest_yn());
+ }
+
+ // 데이터
+ // 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,""));
+ }
+
+ // 이미지 정제
+ String falseImageCode = ftParameterProcessingService.getImagesInfo(msgFtRequestVO);
+ if(StringUtils.isNotEmpty(falseImageCode)) return new RestResponse(new FailRestResponse(falseImageCode,""));
+
+
+
+ // ========== 새로운 개별 발송 코드 ==========
+ List responses = new ArrayList<>();
+ List successList = new ArrayList<>();
+ List failList = new ArrayList<>();
+
+
+ log.info("msgFtRequestVO.toString() :: [{}]", msgFtRequestVO.toString());
+ // 각 수신자별로 개별 발송 처리
+// List individualRequestList = new ArrayList<>();
+ for (VarFtListMapVO recipient : msgFtRequestVO.getVarListMap()) {
+ try {
+ // 개별 수신자용 KakaoFTSendVO 생성
+ KakaoFTSendVO individualRequest = createKakaoFTSendVO(msgFtRequestVO, recipient);
+// individualRequestList.add(individualRequest);
+
+ log.info("individualRequest: {}", individualRequest);
+
+ // 개별 API 호출
+ MjonResponseVO response = apiService.postForEntity(
+ "/web/mjon/kakao/friendstalk/kakaoFriendsTalkMsgSendAjax_advc.do",
+ individualRequest,
+ String.class
+ );
+ log.info("response: {}", response);
+
+ responses.add(response);
+ successList.add(recipient.getPhone());
+
+ log.info("개별 발송 성공 - 수신번호: {}", recipient.getPhone());
+
+ } catch (Exception e) {
+ log.error("개별 발송 실패 - 수신번호: {}, 오류: {}", recipient.getPhone(), e.getMessage());
+ failList.add(recipient.getPhone());
+ }
+ }
+
+ // SMS API 형태의 응답 생성
+ FtSendSuccessResponse ftResponse = FtSendSuccessResponse.createFtResponse(
+ msgFtRequestVO.getVarListMap().size(),
+ successList.size(),
+ failList.size(),
+ successList
+ );
+
+ return new RestResponse(ftResponse);
+// return new RestResponse(individualRequestList);
+ }
+
+ /**
+ * MsgFtRequestVO + VarFtListMapVO를 KakaoFTSendVO로 변환
+ * (개별 수신자용 객체 생성)
+ */
+ private KakaoFTSendVO createKakaoFTSendVO(MsgFtRequestVO source, VarFtListMapVO recipient) {
+
+ // 버튼 변환 (FtButtonVO → KakaoButtonVO)
+ List kakaoButtons = source.getButtons().stream()
+ .map(this::convertToKakaoButton)
+ .collect(Collectors.toList());
+
+ // 개별 수신자 객체 생성
+ MjonFTSendVO mjonFTSend = MjonFTSendVO.builder()
+ .phone(recipient.getPhone()) // 핵심: 수신번호
+// .callTo(recipient.getPhone()) // 동일값
+// .eventYn("N") // 기본값
+ .build();
+
+ // KakaoFTSendVO 생성
+ return KakaoFTSendVO.builder()
+ // 공통 정보 (모든 요청에 동일)
+
+ .mberId(source.getMberId())
+ .sendKind(source.getSendKind())
+ .reserveYn("N")
+ .adFlag(source.getAdFlag())
+
+ .senderKey(source.getSenderKey())
+ .callFrom(source.getCallFrom())
+ .buttonVOList(kakaoButtons)
+
+ // 이미지 관련
+ .templateImageUrl(source.getTemplateImageUrl())
+ .atchFileId(source.getFilePath1())
+ .imageType(source.getTemplateImageUrl() != null ? source.getTemplateMsgType() : null)
+// .imageFileName(extractFileName(source.getTemplateImageUrl()))
+ .imageFileName(source.getTemplateImageName())
+
+ // 개별 수신자 정보
+ .templateContent(recipient.getTemplateContent()) // 개별 내용
+ .subMsgSendYn(source.getSubMsgSendYn()) // 개별 대체문자
+ .subMsgTxt(recipient.getSubMsgTxt()) // 개별 대체문자
+ .mjonFTSendVOList(Arrays.asList(mjonFTSend)) // 단일 수신자만
+
+ .build();
+ }
+
+ /**
+ * FtButtonVO를 KakaoButtonVO로 변환
+ */
+ private KakaoButtonVO convertToKakaoButton(FtButtonVO source) {
+ return KakaoButtonVO.builder()
+ .name(source.getName())
+ .linkType(source.getLinkType())
+ .linkTypeName(source.getLinkTypeName())
+ .linkMo(source.getLinkMo())
+ .linkPc(source.getLinkPc())
+ .linkIos(source.getLinkIos())
+ .linkAnd(source.getLinkAnd())
+ .build();
+ }
+
+ /**
+ * 이미지 URL에서 파일명 추출
+ */
+ private String extractFileName(String imageUrl) {
+ if (imageUrl == null) return null;
+ return imageUrl.substring(imageUrl.lastIndexOf('/') + 1);
+ }
+
+}
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..cc984fb
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/kakao/utils/FtFileMetaUtil.java
@@ -0,0 +1,279 @@
+package com.itn.mjonApi.mjon.api.kakao.utils;
+
+import com.itn.mjonApi.mjon.api.kakao.ft.send.mapper.domain.MsgFtRequestVO;
+import lombok.extern.slf4j.Slf4j;
+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)
+ */
+@Slf4j
+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();
+ log.info(" + ct :: [{}]", ct);
+
+ // MIME 타입이 application/octet-stream이면 파일명으로 추정
+ if ("application/octet-stream".equalsIgnoreCase(ct)) {
+ String filename = f.getOriginalFilename();
+ if (filename != null) {
+ String ext = filename.toLowerCase();
+ if (ext.endsWith(".jpg") || ext.endsWith(".jpeg")) {
+ ct = "image/jpeg";
+ } else if (ext.endsWith(".png")) {
+ ct = "image/png";
+ }
+ }
+ }
+
+ 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/inqry/mapper/HstryMapper.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/HstryMapper.java
similarity index 70%
rename from src/main/java/com/itn/mjonApi/mjon/api/inqry/mapper/HstryMapper.java
rename to src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/HstryMapper.java
index 5df979e..c0ca343 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/inqry/mapper/HstryMapper.java
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/HstryMapper.java
@@ -1,14 +1,14 @@
-package com.itn.mjonApi.mjon.api.inqry.mapper;
+package com.itn.mjonApi.mjon.api.msg.inqry.mapper;
-import com.itn.mjonApi.mjon.api.inqry.service.mapper.domain.HstryDetailVO;
-import com.itn.mjonApi.mjon.api.inqry.service.mapper.domain.HstryVO;
-import com.itn.mjonApi.mjon.api.inqry.service.mapper.domain.MjonResponseVO;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain.HstryDetailVO;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain.HstryVO;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain.MjonResponseVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
-* @packageName : com.itn.mjonApi.mjon.api.inqry.service.mapper
+* @packageName : com.itn.mjonApi.mjon.api.msg.inqry.service.mapper
* @fileName : PriceMapper.java
* @author : JunHo Lee
* @date : 2023.05.15
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/inqry/mapper/PriceMapper.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/PriceMapper.java
similarity index 85%
rename from src/main/java/com/itn/mjonApi/mjon/api/inqry/mapper/PriceMapper.java
rename to src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/PriceMapper.java
index 7ef7de0..278b65c 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/inqry/mapper/PriceMapper.java
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/PriceMapper.java
@@ -1,11 +1,11 @@
-package com.itn.mjonApi.mjon.api.inqry.mapper;
+package com.itn.mjonApi.mjon.api.msg.inqry.mapper;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
/**
-* @packageName : com.itn.mjonApi.mjon.api.inqry.service.mapper
+* @packageName : com.itn.mjonApi.mjon.api.msg.inqry.service.mapper
* @fileName : PriceMapper.java
* @author : JunHo Lee
* @date : 2023.05.15
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/inqry/service/mapper/domain/HstryDetailVO.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/HstryDetailVO.java
similarity index 93%
rename from src/main/java/com/itn/mjonApi/mjon/api/inqry/service/mapper/domain/HstryDetailVO.java
rename to src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/HstryDetailVO.java
index f632965..69ef54b 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/inqry/service/mapper/domain/HstryDetailVO.java
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/HstryDetailVO.java
@@ -1,4 +1,4 @@
-package com.itn.mjonApi.mjon.api.inqry.service.mapper.domain;
+package com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/inqry/service/mapper/domain/HstryResponse.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/HstryResponse.java
similarity index 94%
rename from src/main/java/com/itn/mjonApi/mjon/api/inqry/service/mapper/domain/HstryResponse.java
rename to src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/HstryResponse.java
index 744920c..fb71d0c 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/inqry/service/mapper/domain/HstryResponse.java
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/HstryResponse.java
@@ -1,4 +1,4 @@
-package com.itn.mjonApi.mjon.api.inqry.service.mapper.domain;
+package com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain;
import lombok.*;
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/inqry/service/mapper/domain/HstryVO.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/HstryVO.java
similarity index 94%
rename from src/main/java/com/itn/mjonApi/mjon/api/inqry/service/mapper/domain/HstryVO.java
rename to src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/HstryVO.java
index 1572c2c..e09e021 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/inqry/service/mapper/domain/HstryVO.java
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/HstryVO.java
@@ -1,4 +1,4 @@
-package com.itn.mjonApi.mjon.api.inqry.service.mapper.domain;
+package com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/inqry/service/mapper/domain/MjonResponseVO.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/MjonResponseVO.java
similarity index 96%
rename from src/main/java/com/itn/mjonApi/mjon/api/inqry/service/mapper/domain/MjonResponseVO.java
rename to src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/MjonResponseVO.java
index 49530f7..778b27d 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/inqry/service/mapper/domain/MjonResponseVO.java
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/MjonResponseVO.java
@@ -1,4 +1,4 @@
-package com.itn.mjonApi.mjon.api.inqry.service.mapper.domain;
+package com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain;
import lombok.*;
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/inqry/mapper/domain/PriceResponse.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/PriceResponse.java
similarity index 77%
rename from src/main/java/com/itn/mjonApi/mjon/api/inqry/mapper/domain/PriceResponse.java
rename to src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/PriceResponse.java
index d79da9a..4d9275d 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/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.inqry.mapper.domain;
-
-import java.time.LocalDateTime;
+package com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain;
+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
new file mode 100644
index 0000000..265b567
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/mapper/domain/PriceVO.java
@@ -0,0 +1,39 @@
+package com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain;
+
+import lombok.*;
+
+import java.io.Serializable;
+
+@Getter
+@Setter
+@Builder
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+public class PriceVO implements Serializable{
+
+ private static final long serialVersionUID = -7865729705175845268L;
+
+ private String mberId; // 사용자 ID
+
+ 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/inqry/service/HstryService.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/service/HstryService.java
similarity index 62%
rename from src/main/java/com/itn/mjonApi/mjon/api/inqry/service/HstryService.java
rename to src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/service/HstryService.java
index 2b3f33b..b6070d9 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/inqry/service/HstryService.java
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/service/HstryService.java
@@ -1,8 +1,8 @@
-package com.itn.mjonApi.mjon.api.inqry.service;
+package com.itn.mjonApi.mjon.api.msg.inqry.service;
import com.itn.mjonApi.cmn.msg.RestResponse;
-import com.itn.mjonApi.mjon.api.inqry.service.mapper.domain.HstryDetailVO;
-import com.itn.mjonApi.mjon.api.inqry.service.mapper.domain.HstryVO;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain.HstryDetailVO;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain.HstryVO;
public interface HstryService {
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/inqry/service/PriceService.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/service/PriceService.java
similarity index 81%
rename from src/main/java/com/itn/mjonApi/mjon/api/inqry/service/PriceService.java
rename to src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/service/PriceService.java
index 17a3d5a..02b896f 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/inqry/service/PriceService.java
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/service/PriceService.java
@@ -1,9 +1,9 @@
-package com.itn.mjonApi.mjon.api.inqry.service;
+package com.itn.mjonApi.mjon.api.msg.inqry.service;
import com.itn.mjonApi.cmn.msg.RestResponse;
/**
-* @packageName : com.itn.mjonApi.mjon.api.inqry.service
+* @packageName : com.itn.mjonApi.mjon.api.msg.inqry.service
* @fileName : PriceService.java
* @author : JunHo Lee
* @date : 2023.05.15
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/inqry/service/impl/HstryServiceImpl.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/service/impl/HstryServiceImpl.java
similarity index 93%
rename from src/main/java/com/itn/mjonApi/mjon/api/inqry/service/impl/HstryServiceImpl.java
rename to src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/service/impl/HstryServiceImpl.java
index 2164925..98f2ce8 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/inqry/service/impl/HstryServiceImpl.java
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/service/impl/HstryServiceImpl.java
@@ -1,15 +1,15 @@
-package com.itn.mjonApi.mjon.api.inqry.service.impl;
+package com.itn.mjonApi.mjon.api.msg.inqry.service.impl;
import com.itn.mjonApi.cmn.apiServer.ApiService;
import com.itn.mjonApi.cmn.msg.RestResponse;
import com.itn.mjonApi.cmn.msg.FailRestResponse;
-import com.itn.mjonApi.mjon.api.inqry.mapper.HstryMapper;
-import com.itn.mjonApi.mjon.api.inqry.service.HstryService;
-import com.itn.mjonApi.mjon.api.inqry.service.mapper.domain.HstryDetailVO;
-import com.itn.mjonApi.mjon.api.inqry.service.mapper.domain.HstryResponse;
-import com.itn.mjonApi.mjon.api.inqry.service.mapper.domain.HstryVO;
-import com.itn.mjonApi.mjon.api.inqry.service.mapper.domain.MjonResponseVO;
-import com.itn.mjonApi.mjon.api.send.mapper.SendMapper;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.HstryMapper;
+import com.itn.mjonApi.mjon.api.msg.inqry.service.HstryService;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain.HstryDetailVO;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain.HstryResponse;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain.HstryVO;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain.MjonResponseVO;
+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;
@@ -28,9 +28,7 @@ public class HstryServiceImpl implements HstryService {
@Autowired
HstryMapper hstryMapper;
-
- @Autowired
- SendMapper sendMapper;
+
@Autowired
public HstryServiceImpl(ApiService apiService) {
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/inqry/service/impl/PriceServiceImpl.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/service/impl/PriceServiceImpl.java
similarity index 71%
rename from src/main/java/com/itn/mjonApi/mjon/api/inqry/service/impl/PriceServiceImpl.java
rename to src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/service/impl/PriceServiceImpl.java
index a6cf459..a7915f7 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/inqry/service/impl/PriceServiceImpl.java
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/service/impl/PriceServiceImpl.java
@@ -1,13 +1,13 @@
-package com.itn.mjonApi.mjon.api.inqry.service.impl;
+package com.itn.mjonApi.mjon.api.msg.inqry.service.impl;
import com.itn.mjonApi.cmn.model.Price;
import com.itn.mjonApi.cmn.msg.FailRestResponse;
import com.itn.mjonApi.cmn.msg.RestResponse;
import com.itn.mjonApi.cmn.msg.StatMsg;
-import com.itn.mjonApi.mjon.api.inqry.mapper.PriceMapper;
-import com.itn.mjonApi.mjon.api.inqry.mapper.domain.PriceResponse;
-import com.itn.mjonApi.mjon.api.inqry.mapper.domain.PriceVO;
-import com.itn.mjonApi.mjon.api.inqry.service.PriceService;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.PriceMapper;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain.PriceResponse;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain.PriceVO;
+import com.itn.mjonApi.mjon.api.msg.inqry.service.PriceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -16,7 +16,7 @@ import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
/**
-* @packageName : com.itn.mjonApi.mjon.api.inqry.service.impl
+* @packageName : com.itn.mjonApi.mjon.api.msg.inqry.service.impl
* @fileName : PriceServiceImpl.java
* @author : JunHo Lee
* @date : 2023.05.15
@@ -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/mjon/api/inqry/web/HstryRestController.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/web/HstryRestController.java
similarity index 87%
rename from src/main/java/com/itn/mjonApi/mjon/api/inqry/web/HstryRestController.java
rename to src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/web/HstryRestController.java
index a888bdc..2433200 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/inqry/web/HstryRestController.java
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/web/HstryRestController.java
@@ -1,9 +1,9 @@
-package com.itn.mjonApi.mjon.api.inqry.web;
+package com.itn.mjonApi.mjon.api.msg.inqry.web;
import com.itn.mjonApi.cmn.msg.RestResponse;
-import com.itn.mjonApi.mjon.api.inqry.service.HstryService;
-import com.itn.mjonApi.mjon.api.inqry.service.mapper.domain.HstryDetailVO;
-import com.itn.mjonApi.mjon.api.inqry.service.mapper.domain.HstryVO;
+import com.itn.mjonApi.mjon.api.msg.inqry.service.HstryService;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain.HstryDetailVO;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain.HstryVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
@@ -24,7 +24,6 @@ import org.springframework.web.client.RestTemplate;
* 2023-02-15 hylee 최초 생성
*/
-// 치환문자가 있으면 , => §로 치환
@Slf4j
@RestController
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/inqry/web/PriceRestController.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/web/PriceRestController.java
similarity index 82%
rename from src/main/java/com/itn/mjonApi/mjon/api/inqry/web/PriceRestController.java
rename to src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/web/PriceRestController.java
index 9d31a3b..68b734b 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/inqry/web/PriceRestController.java
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/inqry/web/PriceRestController.java
@@ -1,4 +1,4 @@
-package com.itn.mjonApi.mjon.api.inqry.web;
+package com.itn.mjonApi.mjon.api.msg.inqry.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
@@ -6,11 +6,11 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import com.itn.mjonApi.cmn.msg.RestResponse;
-import com.itn.mjonApi.mjon.api.inqry.mapper.domain.PriceVO;
-import com.itn.mjonApi.mjon.api.inqry.service.PriceService;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.domain.PriceVO;
+import com.itn.mjonApi.mjon.api.msg.inqry.service.PriceService;
/**
-* @packageName : com.itn.mjonApi.mjon.api.inqry.web
+* @packageName : com.itn.mjonApi.mjon.api.msg.inqry.web
* @fileName : PriceRestController.java
* @author : JunHo Lee
* @date : 2023.05.15
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/send/mapper/SendMapper.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/SendMapper.java
similarity index 75%
rename from src/main/java/com/itn/mjonApi/mjon/api/send/mapper/SendMapper.java
rename to src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/SendMapper.java
index 910a5fe..3c284d7 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/send/mapper/SendMapper.java
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/SendMapper.java
@@ -1,10 +1,10 @@
-package com.itn.mjonApi.mjon.api.send.mapper;
+package com.itn.mjonApi.mjon.api.msg.send.mapper;
-import com.itn.mjonApi.mjon.api.send.mapper.domain.MsgRequestVO;
+import com.itn.mjonApi.mjon.api.msg.send.mapper.domain.MsgRequestVO;
import org.apache.ibatis.annotations.Mapper;
/**
- * packageName : com.itn.mjonApi.mjon.api.send.mapper.domain
+ * packageName : com.itn.mjonApi.mjon.api.msg.send.mapper.domain
* fileName : SendMapper
* author : hylee
* date : 2023-05-19
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/domain/MjonMsgSendVO.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/domain/MjonMsgSendVO.java
new file mode 100644
index 0000000..70051a5
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/domain/MjonMsgSendVO.java
@@ -0,0 +1,44 @@
+package com.itn.mjonApi.mjon.api.msg.send.mapper.domain;
+
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+@Getter
+@Setter
+@ToString
+public class MjonMsgSendVO{
+
+
+ /**
+ * @description : 수신자번호
+ */
+ private String phone;
+
+ /**
+ * @description : [*이름*] - 치환문자
+ */
+ private String name;
+
+ /**
+ * @description : [*1*] - 치환문자
+ */
+ private String rep1;
+
+ /**
+ * @description : [*2*] - 치환문자
+ */
+ private String rep2;
+
+ /**
+ * @description : [*3*] - 치환문자
+ */
+ private String rep3;
+
+ /**
+ * @description : [*4*] - 치환문자
+ */
+ private String rep4;
+
+}
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/domain/MjonResponseVO.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/domain/MjonResponseVO.java
new file mode 100644
index 0000000..33a1f66
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/domain/MjonResponseVO.java
@@ -0,0 +1,53 @@
+package com.itn.mjonApi.mjon.api.msg.send.mapper.domain;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+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 MjonResponseVO {
+
+ 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 MjonResponseVO getMjonResponse(JsonNode apiReturnNode) throws JsonProcessingException {
+ ObjectMapper objectMapper = new ObjectMapper();
+ return objectMapper.treeToValue(apiReturnNode, MjonResponseVO.class);
+ }
+// public static MjonResponseVO getMjonResponse(ResponseEntity stringResponseEntity) throws JsonProcessingException {
+// ObjectMapper objectMapper = new ObjectMapper();
+// return objectMapper.readValue(stringResponseEntity.getBody(), MjonResponseVO.class);
+// }
+
+}
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/send/mapper/domain/MsgRequestVO.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/domain/MsgRequestVO.java
similarity index 94%
rename from src/main/java/com/itn/mjonApi/mjon/api/send/mapper/domain/MsgRequestVO.java
rename to src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/domain/MsgRequestVO.java
index e305e6d..ff41949 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/send/mapper/domain/MsgRequestVO.java
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/domain/MsgRequestVO.java
@@ -1,11 +1,12 @@
-package com.itn.mjonApi.mjon.api.send.mapper.domain;
+package com.itn.mjonApi.mjon.api.msg.send.mapper.domain;
import lombok.*;
import java.io.Serializable;
+import java.util.List;
/**
- * packageName : com.itn.mjonApi.mjon.api.send.mapper.domain
+ * packageName : com.itn.mjonApi.mjon.api.msg.send.mapper.domain
* fileName : MjonMsgVO
* author : hylee
* date : 2023-05-09
@@ -18,6 +19,7 @@ import java.io.Serializable;
@NoArgsConstructor
@AllArgsConstructor
@Builder
+@ToString
public class MsgRequestVO implements Serializable {
/**
@@ -33,6 +35,8 @@ public class MsgRequestVO implements Serializable {
private String smsTxt; // value = "SMS용 메시지본문", example = "문자 메세지 본문"
+ private String smsTxtArea;//문자 작성 화면 본문 내용
+
private String[] callToList; // value = "수신번호리스트", dataType = "[Ljava.lang.String;", example = "01011112222,01022223333"
private String callFrom; // value = "발신번호 :: 정책이 필요함", example = "01011112222"
@@ -98,6 +102,10 @@ public class MsgRequestVO implements Serializable {
private String test_yn; // 테스트 여부
+ private String sendKind; // 문자종류
+
+
+ List mjonMsgSendVOList;
// private String msgId ;// '문자ID',
// private String userId ; // '문자온 일반회원ID',
@@ -113,7 +121,6 @@ public class MsgRequestVO implements Serializable {
// private String rsltCode2; // '결과처리 상세코드',
// private String rsltNet; // '결과처리 통신사',
// private String subject; // 'MMS용 메시지제목',
-// private String smsTxtArea;//문자 작성 화면 본문 내용
// private String msgPayCode; // '재전송 기능에 의한 최종전송콘텐트 종류 저장',
// private String contSeq; // COMMENT 'MMS의 콘텐츠 Key(MMS_CONTENTS_INFO의 CONT_SEQ)',
// private String msgTypeResend; // '재전송할 문자 타입. 값이 있으면 재전송. 없으면 단 건 전송',
@@ -228,6 +235,14 @@ public class MsgRequestVO implements Serializable {
this.smsTxt = smsTxt;
}
+ public String getSmsTxtArea() {
+ return smsTxtArea;
+ }
+
+ public void setSmsTxtArea(String smsTxtArea) {
+ this.smsTxtArea = smsTxtArea;
+ }
+
public String[] getCallToList() {
return callToList;
}
@@ -475,4 +490,20 @@ public class MsgRequestVO implements Serializable {
public void setTest_yn(String test_yn) {
this.test_yn = test_yn;
}
+
+ public String getSendKind() {
+ return sendKind;
+ }
+
+ public void setSendKind(String sendKind) {
+ this.sendKind = sendKind;
+ }
+
+ public List getMjonMsgSendVOList() {
+ return mjonMsgSendVOList;
+ }
+
+ public void setMjonMsgSendVOList(List mjonMsgSendVOList) {
+ this.mjonMsgSendVOList = mjonMsgSendVOList;
+ }
}
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/domain/MsgsRequestVO.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/domain/MsgsRequestVO.java
new file mode 100644
index 0000000..3818419
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/domain/MsgsRequestVO.java
@@ -0,0 +1,106 @@
+package com.itn.mjonApi.mjon.api.msg.send.mapper.domain;
+
+import com.itn.mjonApi.cmn.domain.SendRequestCmnVO;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+/**
+ * packageName : com.itn.mjonApi.mjon.api.msg.send.mapper.domain
+ * fileName : MjonMsgVO
+ * author : hylee
+ * date : 2023-05-23
+ * description : 문자 발송에 필요한 값들을 받는 vo
+ * 1건~500건 대량 문자 개인별로 발송
+ * ===========================================================
+ * DATE AUTHOR NOTE
+ * -----------------------------------------------------------
+ * 2023-05-09 hylee 최초 생성
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class MsgsRequestVO extends SendRequestCmnVO implements Serializable {
+
+ /**
+ * 값이 있는 경우
+ * 문자온 프로젝트에서 처리해줌
+ * null이면 에러
+ */
+ private static final long serialVersionUID = 1L;
+
+ private String mberId; // 사용자 ID
+
+ private String accessKey; // Api Key
+
+ private String smsTxt; // SMS용 메시지본문
+
+ private String[] callToList; // 수신번호리스트
+
+ private String callFrom; // 발신번호 :: 정책이 필요함
+
+ private String eachPrice = "0"; // 전송문자 개별가격
+ private String sPrice = "0"; // 임시
+
+ private String totPrice = "0"; // 전송문자 토탈가격
+
+ private String fileCnt = "0"; // 첨부파일 갯수
+
+ private String msgType = "4"; // 메시지의 (4: SMS 전송, 5: URL 전송, 6: MMS전송, 7: BARCODE전송, 8: 카카오 알림톡 전송)
+
+ // ==== 단가 ====
+ private float smsPrice = 0; // sms 단가 null 이면 에러
+
+ private float mmsPrice = 0; // mms 단가 null 이면 에러
+// private float kakaoAtPrice; // 카카오 알림톡 단가
+// private float kakaoFtPrice; // 카카오 친구톡 단가
+// private float kakaoFtImgPrice;// 카카오 이미지 단가
+// private float kakaoFtWideImgPrice; // 카카오 와이드 이미지 단가
+
+ private String[] imgFilePath = new String[0]; // 그림 이미지 경로
+
+
+ private String spamStatus; // 스팸문자 유무 (Y/N) - 서비스단에서 처리 함
+
+ private String txtReplYn = "N"; // 변환문자 유무 (Y/N) - 서비스단에서 처리 함
+
+// private String nameStr; // value = "치환 이름 리스트 |로 구분", example = "홍길동1|홍길동2|홍길동3"
+
+// private String rep1Str; // value = "치환 문자1 리스트 |로 구분", example = ""
+
+// private String rep2Str; // value = "치환 문자2 리스트 |로 구분", example = ""
+
+// private String rep3Str; // value = "치환 문자3 리스트 |로 구분", example = ""
+
+// private String rep4Str; // value = "치환 문자4 리스트 |로 구분", example = ""
+
+// private String[] nameList= new String[0]; // value = "nameStr 을 |로 split 후 담는 변수", example = ""
+
+// private String[] rep1List= new String[0]; // value = "rep1Str 을 |로 split 후 담는 변수", example = ""
+
+// private String[] rep2List= new String[0]; // value = "rep2Str 을 |로 split 후 담는 변수", example = ""
+
+// private String[] rep3List= new String[0]; // value = "rep3Str 을 |로 split 후 담는 변수", example = ""
+
+// private String[] rep4List= new String[0]; // value = "rep4Str 을 |로 split 후 담는 변수", example = ""
+
+ private String reserveYn = "N"; // value = "예약 유무 (Y/N)", example = "N"
+
+ // 치환 있을 경우 사용
+// private String shortMsgCnt; // value = "치환 후 단문 건수", example = ""
+
+// private String longMsgCnt; // value = "치환 후 장문 건수", example = ""
+
+
+// @ApiModelProperty(value = "문자 종류 일반:N, 광고:A, 선거:C", example = "N", hidden = true)
+ private String msgKind = "N"; // '문자 종류 일반:N, 광고:A, 선거:C',
+
+ private String test_yn; // 테스트 여부
+
+
+
+}
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/send/mapper/domain/SendSucRestResponse.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/domain/SendSucRestResponse.java
similarity index 55%
rename from src/main/java/com/itn/mjonApi/mjon/api/send/mapper/domain/SendSucRestResponse.java
rename to src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/domain/SendSucRestResponse.java
index 4b393c3..81b4281 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/send/mapper/domain/SendSucRestResponse.java
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/mapper/domain/SendSucRestResponse.java
@@ -1,4 +1,4 @@
-package com.itn.mjonApi.mjon.api.send.mapper.domain;
+package com.itn.mjonApi.mjon.api.msg.send.mapper.domain;
import com.itn.mjonApi.cmn.msg.StatMsg;
import lombok.*;
@@ -67,13 +67,31 @@ public class SendSucRestResponse {
private static List getMsgType(String msgType) {
List result = new ArrayList<>();
+ // msgType이 null이거나 빈 문자열인 경우 빈 리스트 반환
+ if (msgType == null || msgType.trim().isEmpty()) {
+ return result;
+ }
+
if(msgType.indexOf(",") > 0)
{
result = Arrays.stream(msgType.split(","))
- .map(s -> StatMsg.valueOf("msgType"+s).getMsg())
+ .filter(s -> s != null && !s.trim().isEmpty()) // 빈 문자열 필터링
+ .map(s -> {
+ try {
+ return StatMsg.valueOf("msgType" + s.trim()).getMsg();
+ } catch (IllegalArgumentException e) {
+ log.warn("Unknown msgType: {}, skipping", s.trim());
+ return null;
+ }
+ })
+ .filter(s -> s != null) // null 값 제거
.collect(Collectors.toList());
}else{
- result.add(StatMsg.valueOf("msgType"+ msgType).getMsg());
+ try {
+ result.add(StatMsg.valueOf("msgType" + msgType.trim()).getMsg());
+ } catch (IllegalArgumentException e) {
+ log.warn("Unknown msgType: {}, returning empty list", msgType.trim());
+ }
}
return result;
}
@@ -85,31 +103,41 @@ public class SendSucRestResponse {
*/
public static SendSucRestResponse SendSuccessMsgsRestResponse(List mjonResponseVOList) {
+ mjonResponseVOList.forEach(t->log.info(t.toString()));
+
// 실패 카운트
int failCnt = (int) mjonResponseVOList.stream()
- .filter(s->"fail".equals(s.getResult()))
+ .filter(s->!"OK".equals(s.getResult()))
.count();
// 성공 카운트
- int successCnt = mjonResponseVOList.parallelStream()
- .filter(s->!"fail".equals(s.getResult()))
- .mapToInt(s -> Integer.parseInt(s.getResultSts()))
- .sum();
+ int successCnt = (int) mjonResponseVOList.parallelStream()
+ .filter(s->"OK".equals(s.getResult()))
+ .count();
// 수신거부 카운트
- int blockCnt = mjonResponseVOList.parallelStream()
- .filter(s->!"fail".equals(s.getResult()))
- .mapToInt(s -> Integer.parseInt(s.getResultBlockSts()))
- .sum();
+// int blockCnt = mjonResponseVOList.parallelStream()
+// .filter(s->!"OK".equals(s.getResult()))
+// .mapToInt(s -> Integer.parseInt(s.getResultBlockSts()))
+// .sum();
// 성공한 메세지 그룹 아이디
List msgGroupIdList = mjonResponseVOList.stream()
- .filter(s->!"fail".equals(s.getResult()))
+ .filter(s->"OK".equals(s.getResult()))
.map(s -> s.getMsgGroupId())
.collect(Collectors.toList());
- // 성공한 메세지 그룹 아이디
+ // 메세지 타입
List msgTypeList = mjonResponseVOList.stream()
- .map(s -> StatMsg.valueOf("msgType"+s.getMsgType()).getMsg())
- .collect(Collectors.toList());
+ .filter(s -> s.getMsgType() != null && !s.getMsgType().trim().isEmpty())
+ .map(s -> {
+ try {
+ return StatMsg.valueOf("msgType" + s.getMsgType().trim()).getMsg();
+ } catch (IllegalArgumentException e) {
+ log.warn("Unknown msgType in list: {}, skipping", s.getMsgType().trim());
+ return null;
+ }
+ })
+ .filter(s -> s != null)
+ .collect(Collectors.toList());
@@ -118,8 +146,8 @@ public class SendSucRestResponse {
.resultCode(StatMsg.valueOf("STAT_0").getCode()) // 성공 코드 0 - StatMsg 참고
.msgGroupIdList(msgGroupIdList) // 전송 메세지 그룹 ID
.successCnt(Integer.toString(successCnt)) // 성공 건수
- .blockCnt(Integer.toString(blockCnt)) // 수신거부 건수
- .failCnt(Integer.toString(failCnt)) // 수신거부 건수
+// .blockCnt(Integer.toString(blockCnt)) // 수신거부 건수
+ .failCnt(Integer.toString(failCnt)) // 실패 건수
.msgTypeList(msgTypeList) // msgType List
.build();
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/msg/send/service/SendService.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/service/SendService.java
new file mode 100644
index 0000000..13d3323
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/service/SendService.java
@@ -0,0 +1,21 @@
+package com.itn.mjonApi.mjon.api.msg.send.service;
+
+import com.itn.mjonApi.cmn.msg.RestResponse;
+import com.itn.mjonApi.mjon.api.msg.send.mapper.domain.MsgRequestVO;
+import com.itn.mjonApi.mjon.api.msg.send.mapper.domain.MsgsRequestVO;
+
+import java.util.Map;
+
+public interface SendService {
+
+
+// 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 sendMsgsData_advc(MsgsRequestVO msgsRequestVO, Map allParams) throws Exception;
+}
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/msg/send/service/impl/SendServiceImpl.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/service/impl/SendServiceImpl.java
new file mode 100644
index 0000000..dfd1ca0
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/service/impl/SendServiceImpl.java
@@ -0,0 +1,367 @@
+package com.itn.mjonApi.mjon.api.msg.send.service.impl;
+
+import com.itn.mjonApi.cmn.apiServer.ApiService;
+import com.itn.mjonApi.cmn.msg.FailRestResponse;
+import com.itn.mjonApi.cmn.msg.RestResponse;
+import com.itn.mjonApi.mjon.api.msg.inqry.mapper.PriceMapper;
+import com.itn.mjonApi.mjon.api.msg.send.mapper.SendMapper;
+import com.itn.mjonApi.mjon.api.msg.send.mapper.domain.*;
+import com.itn.mjonApi.mjon.api.msg.send.service.SendService;
+import com.itn.mjonApi.util.MunjaUtil;
+import com.itn.mjonApi.util.TestDataUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.lang.reflect.Field;
+import java.util.*;
+
+
+@Slf4j
+@Service
+public class SendServiceImpl implements SendService {
+
+ private ApiService apiService;
+
+ @Autowired
+ SendMapper sendMapper;
+
+ @Autowired
+ PriceMapper priceMapper;
+
+ @Autowired
+ public SendServiceImpl(ApiService apiService) {
+ this.apiService = apiService;
+ }
+
+ private static final String replaseStrList = "[*이름*],[*1*],[*2*],[*3*],[*4*]";
+
+ @Override
+ public RestResponse sendMsgData_advc(MsgRequestVO msgRequestVO) throws Exception {
+ log.info(" :: sendMsgData_advc ::");
+
+ msgRequestVO.setSendKind("A");
+
+ if(StringUtils.isNotEmpty(msgRequestVO.getTest_yn())){
+ // YF => 실패 테스트 데이터
+ return TestDataUtil._getTestMsgReturnData(msgRequestVO.getTest_yn());
+ }
+
+ // step2.수신자 전화번호 정상 여부 체크(정상 번호에 대해서만 발송 가능)
+ // 1020
+ // 폰번호 확인 - 빈 값 -> 유효성 정규식
+ if(MunjaUtil.getCallToListChk(msgRequestVO.getCallToList())){
+ return new RestResponse(new FailRestResponse("STAT_1020",""));
+ }
+
+ // 치환 데이터 및 수신자 번호 VO 생성
+ msgRequestVO.setMjonMsgSendVOList(this.buildMsgSendVOList(msgRequestVO));
+
+ // sms 변수 변경
+ msgRequestVO.setSmsTxtArea(msgRequestVO.getSmsTxt());
+
+
+ MjonResponseVO munjaSendResponse = apiService.postForEntity(
+ "/web/mjon/msgdata/sendMsgDataAjax_advc.do"
+ , msgRequestVO
+ , 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(),""));
+ }
+
+
+
+ }
+
+ /**
+ * MsgRequestVO 내 nameStr, rep1Str~rep4Str, callToList 를 기반으로
+ * 각 항목을 MjonMsgSendVO 리스트로 매핑한다.
+ *
+ * @param msgRequestVO 메시지 요청 VO
+ * @return 수신자별 메시지 전송 VO 리스트
+ */
+ public List buildMsgSendVOList(MsgRequestVO msgRequestVO) {
+
+ // 수신자 이름 및 치환 문자들 파싱 (null-safe 처리)
+ String[] nameArr = Optional.ofNullable(msgRequestVO.getNameStr()).orElse("").split("\\|");
+ String[] rep1Arr = Optional.ofNullable(msgRequestVO.getRep1Str()).orElse("").split("\\|");
+ String[] rep2Arr = Optional.ofNullable(msgRequestVO.getRep2Str()).orElse("").split("\\|");
+ String[] rep3Arr = Optional.ofNullable(msgRequestVO.getRep3Str()).orElse("").split("\\|");
+ String[] rep4Arr = Optional.ofNullable(msgRequestVO.getRep4Str()).orElse("").split("\\|");
+
+ // 콤마(,) 기준으로 수신 번호 문자열을 파싱
+ String[] phoneArr = Optional.ofNullable(msgRequestVO.getCallToList())
+ .orElse(new String[0]);
+
+
+
+ List mjonMsgSendVOList = new ArrayList<>();
+
+ // 수신 번호 개수만큼 반복
+ for (int i = 0; i < phoneArr.length; i++) {
+ MjonMsgSendVO vo = new MjonMsgSendVO();
+
+ // 1. 수신 번호 세팅
+ vo.setPhone(phoneArr[i].trim());
+
+ // 2. 이름/치환문자 세팅
+ if (i < nameArr.length) vo.setName(nameArr[i].trim());
+ if (i < rep1Arr.length) vo.setRep1(rep1Arr[i].trim());
+ if (i < rep2Arr.length) vo.setRep2(rep2Arr[i].trim());
+ if (i < rep3Arr.length) vo.setRep3(rep3Arr[i].trim());
+ if (i < rep4Arr.length) vo.setRep4(rep4Arr[i].trim());
+
+ mjonMsgSendVOList.add(vo);
+ }
+
+ return mjonMsgSendVOList;
+ }
+
+
+
+
+
+ @Override
+ public RestResponse sendMsgsData_advc(MsgsRequestVO msgsRequestVO) throws Exception {
+
+
+ log.info(" :: sendMsgData_advc ::");
+
+
+ if(StringUtils.isNotEmpty(msgsRequestVO.getTest_yn())){
+ return TestDataUtil._getTestMsgsReturnData(msgsRequestVO.getTest_yn());
+ }
+
+ // msgsVO -> msgVO List로 변환
+ List msgRequestVOList = this.getDataCleaning_advc(msgsRequestVO);
+ msgRequestVOList.forEach(t->log.info(" + t.toString() :: [{}]", t.toString()));
+
+ log.info("msgRequestVOList :: [{}]", msgRequestVOList.size());
+
+
+ List mjonResponseVOList = new ArrayList();
+ for(MsgRequestVO aa : msgRequestVOList){
+
+ aa.setSmsTxtArea(aa.getSmsTxt());
+ aa.setSendKind("A");
+
+ MjonResponseVO munjaSendResponse = apiService.postForEntity(
+ "/web/mjon/msgdata/sendMsgDataAjax_advc.do"
+ , aa
+ , String.class
+ );
+
+ log.info("munjaSendResponse :: [{}]", munjaSendResponse.toString());
+ mjonResponseVOList.add(munjaSendResponse);
+ }
+
+
+
+
+ return new RestResponse(SendSucRestResponse.SendSuccessMsgsRestResponse(mjonResponseVOList));
+ }
+
+ @Override
+ public RestResponse sendMsgsData_advc(MsgsRequestVO msgsRequestVO, Map allParams) throws Exception {
+
+
+ log.info(" :: sendMsgData_advc with Map params ::");
+
+
+ if(StringUtils.isNotEmpty(msgsRequestVO.getTest_yn())){
+ return TestDataUtil._getTestMsgsReturnData(msgsRequestVO.getTest_yn());
+ }
+
+ // Map 기반 동적 파라미터 처리
+ List msgRequestVOList = this.getDataCleaning_advc_withMap(msgsRequestVO, allParams);
+
+ log.info("msgRequestVOList :: [{}]", msgRequestVOList.size());
+
+
+ List mjonResponseVOList = new ArrayList();
+ for(MsgRequestVO aa : msgRequestVOList){
+
+ aa.setSmsTxtArea(aa.getSmsTxt());
+ aa.setSendKind("A");
+
+ MjonResponseVO munjaSendResponse = apiService.postForEntity(
+ "/web/mjon/msgdata/sendMsgDataAjax_advc.do"
+ , aa
+ , String.class
+ );
+
+ log.info("munjaSendResponse :: [{}]", munjaSendResponse.toString());
+ mjonResponseVOList.add(munjaSendResponse);
+ }
+
+
+
+
+ return new RestResponse(SendSucRestResponse.SendSuccessMsgsRestResponse(mjonResponseVOList));
+ }
+
+ private static RestResponse callToErrorReturnData(MsgRequestVO msgRequestVO) {
+
+ FailRestResponse stat1020 = new FailRestResponse("STAT_1020","");
+ String errorMsg = stat1020.getMsg();
+ errorMsg.replace("수신자", "수신자(" + msgRequestVO.getCallToList()[0] + ")");
+ stat1020.setMsg(errorMsg);
+
+ return new RestResponse(stat1020);
+ }
+
+
+ private static List getDataCleaning_advc(MsgsRequestVO msgsRequestVO) {
+
+
+ List msgRequestVOList = new ArrayList<>();
+
+ // Reflection으로 Object Field에 접근한다.
+ Field[] declaredFields = msgsRequestVO.getClass().getDeclaredFields();
+
+ String mberId = msgsRequestVO.getMberId(); // 사용자 ID
+ String accessKey = msgsRequestVO.getAccessKey(); // accessKey
+ String callFrom = msgsRequestVO.getCallFrom(); // 발신자 번호
+ String callTo = null; // 수신자 번호
+
+ List mjonMsgSendVOList = new ArrayList<>();
+ for (Field field : declaredFields) {
+
+ Object value = null;
+ // private Field일 경우 접근을 허용한다.
+ field.setAccessible(true);
+
+ try {
+ // Field Value를 참조한다.
+ value = field.get(msgsRequestVO);
+ } catch (IllegalAccessException e) {
+ log.info("Reflection Error. {}", e);
+ }
+
+ // nullPointException 방지
+ if(value != null)
+ {
+ log.info("field.getName() : [{}]", field.getName());
+ log.info("value.toString() : [{}]", value.toString());
+ /**
+ * 필드 이름으로 분기하여
+ * 각각에 맞는 위치에 값을 넣어준다.
+ */
+ if(field.getName().startsWith("callTo")){ // 수신자 번호
+ callTo = value.toString();
+ }else if(field.getName().startsWith("smsTxt")){ // 문자 내용
+
+ // 값이 비여 있으면 다음 반복문으로 넘어간다.
+ if(StringUtils.isEmpty(value.toString())){
+ callTo = "";
+ continue;
+ }
+ MjonMsgSendVO vo = new MjonMsgSendVO();
+ vo.setPhone(callTo);
+ mjonMsgSendVOList.add(vo);
+
+ msgRequestVOList.add(
+ MsgRequestVO.builder()
+ .mberId(mberId)
+ .accessKey(accessKey)
+ .callFrom(callFrom)
+ .smsTxt(value.toString())
+ .mjonMsgSendVOList(mjonMsgSendVOList)
+ .build()
+ );
+ // 초기화
+ callTo = "";
+ }
+
+
+ }
+ }
+ return msgRequestVOList;
+ }
+
+ /**
+ * Map 기반 동적 파라미터 처리를 위한 새로운 메소드
+ * callTo_1, smsTxt_1, callTo_2, smsTxt_2 등의 동적 파라미터를 처리
+ */
+ private List getDataCleaning_advc_withMap(MsgsRequestVO msgsRequestVO, Map allParams) {
+
+ List msgRequestVOList = new ArrayList<>();
+
+ String mberId = msgsRequestVO.getMberId();
+ String accessKey = msgsRequestVO.getAccessKey();
+ String callFrom = msgsRequestVO.getCallFrom();
+
+ // 동적 파라미터를 순서대로 처리하기 위해 인덱스 기반으로 처리
+ Map callToMap = new HashMap<>();
+ Map smsTxtMap = new HashMap<>();
+
+ // 파라미터를 분석하여 인덱스별로 분류
+ for (Map.Entry entry : allParams.entrySet()) {
+ String paramName = entry.getKey();
+ String paramValue = entry.getValue();
+
+ log.info("Processing param: [{}] = [{}]", paramName, paramValue);
+
+ if (paramName.startsWith("callTo_")) {
+ try {
+ int index = Integer.parseInt(paramName.substring(7)); // "callTo_" 이후 숫자
+ callToMap.put(index, paramValue);
+ } catch (NumberFormatException e) {
+ log.warn("Invalid callTo parameter format: [{}]", paramName);
+ }
+ } else if (paramName.startsWith("smsTxt_")) {
+ try {
+ int index = Integer.parseInt(paramName.substring(7)); // "smsTxt_" 이후 숫자
+ if (StringUtils.isNotEmpty(paramValue)) {
+ smsTxtMap.put(index, paramValue);
+ }
+ } catch (NumberFormatException e) {
+ log.warn("Invalid smsTxt parameter format: [{}]", paramName);
+ }
+ }
+ }
+
+ // 인덱스별로 메시지 요청 VO 생성
+ for (Integer index : smsTxtMap.keySet()) {
+ String callTo = callToMap.get(index);
+ String smsTxt = smsTxtMap.get(index);
+
+ if (StringUtils.isNotEmpty(callTo) && StringUtils.isNotEmpty(smsTxt)) {
+ List mjonMsgSendVOList = new ArrayList<>();
+ MjonMsgSendVO vo = new MjonMsgSendVO();
+ vo.setPhone(callTo);
+ mjonMsgSendVOList.add(vo);
+
+ msgRequestVOList.add(
+ MsgRequestVO.builder()
+ .mberId(mberId)
+ .accessKey(accessKey)
+ .callFrom(callFrom)
+ .smsTxt(smsTxt)
+ .reserveYn("N")
+ .mjonMsgSendVOList(mjonMsgSendVOList)
+ .build()
+ );
+
+ log.info("Created MsgRequestVO for index [{}]: callTo=[{}], smsTxt=[{}]",
+ index, callTo, smsTxt);
+ }
+// else {
+// log.warn("Skipping index [{}]: callTo=[{}], smsTxt=[{}]", index, callTo, smsTxt);
+// }
+ }
+
+
+ msgRequestVOList.forEach(t->log.info("+ t.getReserveYn() :: [{}]", t.getReserveYn()));
+
+ return msgRequestVOList;
+ }
+
+}
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/send/web/SendRestController.java b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/web/SendRestController.java
similarity index 66%
rename from src/main/java/com/itn/mjonApi/mjon/api/send/web/SendRestController.java
rename to src/main/java/com/itn/mjonApi/mjon/api/msg/send/web/SendRestController.java
index ea1505d..c3cbf92 100644
--- a/src/main/java/com/itn/mjonApi/mjon/api/send/web/SendRestController.java
+++ b/src/main/java/com/itn/mjonApi/mjon/api/msg/send/web/SendRestController.java
@@ -1,16 +1,18 @@
-package com.itn.mjonApi.mjon.api.send.web;
+package com.itn.mjonApi.mjon.api.msg.send.web;
import com.itn.mjonApi.cmn.msg.RestResponse;
-import com.itn.mjonApi.mjon.api.send.mapper.domain.MsgRequestVO;
-import com.itn.mjonApi.mjon.api.send.mapper.domain.MsgsRequestVO;
-import com.itn.mjonApi.mjon.api.send.service.SendService;
+import com.itn.mjonApi.mjon.api.msg.send.mapper.domain.MsgRequestVO;
+import com.itn.mjonApi.mjon.api.msg.send.mapper.domain.MsgsRequestVO;
+import com.itn.mjonApi.mjon.api.msg.send.service.SendService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.client.RestTemplate;
+
+import java.util.Map;
/**
* packageName : com.itn.mjonApi.mjon.send.web
@@ -24,18 +26,11 @@ import org.springframework.web.client.RestTemplate;
* 2023-02-15 hylee 최초 생성
*/
-// 치환문자가 있으면 , => §로 치환
@Slf4j
@RestController
public class SendRestController {
- private final RestTemplate restTemplate;
-
- public SendRestController(RestTemplate restTemplate) {
- this.restTemplate = restTemplate;
- }
-
@Autowired
private SendService sendService;
@@ -49,20 +44,24 @@ public class SendRestController {
@CrossOrigin("*") // 모든 요청에 접근 허용
@PostMapping("/api/send/sendMsg")
public ResponseEntity sendMsg(MsgRequestVO msgRequestVO) throws Exception {
- return ResponseEntity.ok().body(sendService.sendMsgData(msgRequestVO));
+ return ResponseEntity.ok().body(sendService.sendMsgData_advc(msgRequestVO));
+// return ResponseEntity.ok().body(sendService.sendMsgData(msgRequestVO));
}
/**
*
* @param msgsRequestVO
- * @description [문자 발송] 다른 내용으로 여려명에게 보냄
+ * @param allParams
+ * @description [문자 발송] 다른 내용으로 여려명에게 보냄 - Map 기반 동적 파라미터 처리
* @return
* @throws Exception
*/
@CrossOrigin("*") // 모든 요청에 접근 허용
@PostMapping("/api/send/sendMsgs")
- public ResponseEntity sendMsgs(MsgsRequestVO msgsRequestVO) throws Exception {
- return ResponseEntity.ok().body(sendService.sendMsgsData(msgsRequestVO));
+ public ResponseEntity sendMsgs(MsgsRequestVO msgsRequestVO,
+ @RequestParam Map allParams) throws Exception {
+ return ResponseEntity.ok().body(sendService.sendMsgsData_advc(msgsRequestVO, allParams));
+// return ResponseEntity.ok().body(sendService.sendMsgsData(msgsRequestVO));
}
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/send/service/SendService.java b/src/main/java/com/itn/mjonApi/mjon/api/send/service/SendService.java
deleted file mode 100644
index 8362c06..0000000
--- a/src/main/java/com/itn/mjonApi/mjon/api/send/service/SendService.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.itn.mjonApi.mjon.api.send.service;
-
-import com.itn.mjonApi.cmn.msg.RestResponse;
-import com.itn.mjonApi.mjon.api.send.mapper.domain.MsgRequestVO;
-import com.itn.mjonApi.mjon.api.send.mapper.domain.MsgsRequestVO;
-
-public interface SendService {
-
-
- RestResponse sendMsgData(MsgRequestVO msgRequestVO) throws Exception;
-
- RestResponse sendMsgsData(MsgsRequestVO msgsRequestVO) throws Exception;
-}
diff --git a/src/main/java/com/itn/mjonApi/mjon/api/send/service/impl/SendServiceImpl.java b/src/main/java/com/itn/mjonApi/mjon/api/send/service/impl/SendServiceImpl.java
deleted file mode 100644
index 83fc8aa..0000000
--- a/src/main/java/com/itn/mjonApi/mjon/api/send/service/impl/SendServiceImpl.java
+++ /dev/null
@@ -1,625 +0,0 @@
-package com.itn.mjonApi.mjon.api.send.service.impl;
-
-import com.itn.mjonApi.cmn.apiServer.ApiService;
-import com.itn.mjonApi.cmn.model.Price;
-import com.itn.mjonApi.cmn.msg.FailRestResponse;
-import com.itn.mjonApi.cmn.msg.RestResponse;
-import com.itn.mjonApi.cmn.msg.StatMsg;
-import com.itn.mjonApi.mjon.api.inqry.mapper.PriceMapper;
-import com.itn.mjonApi.mjon.api.inqry.mapper.domain.PriceVO;
-import com.itn.mjonApi.mjon.api.send.mapper.SendMapper;
-import com.itn.mjonApi.mjon.api.send.mapper.domain.MjonResponseVO;
-import com.itn.mjonApi.mjon.api.send.mapper.domain.MsgRequestVO;
-import com.itn.mjonApi.mjon.api.send.mapper.domain.MsgsRequestVO;
-import com.itn.mjonApi.mjon.api.send.mapper.domain.SendSucRestResponse;
-import com.itn.mjonApi.mjon.api.send.service.SendService;
-import com.itn.mjonApi.util.MunjaUtil;
-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 java.io.UnsupportedEncodingException;
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-
-
-@Slf4j
-@Service
-public class SendServiceImpl implements SendService {
-
- private ApiService apiService;
-
- @Autowired
- SendMapper sendMapper;
-
- @Autowired
- PriceMapper priceMapper;
-
- @Autowired
- public SendServiceImpl(ApiService apiService) {
- this.apiService = apiService;
- }
-
- private static final String replaseStrList = "[*이름*],[*1*],[*2*],[*3*],[*4*]";
-
- @Override
- public RestResponse sendMsgData(MsgRequestVO msgRequestVO) throws Exception {
-
- if(StringUtils.isNotEmpty(msgRequestVO.getTest_yn())){
- // YF => 실패 테스트 데이터
- return this._getTestMsgReturnData(msgRequestVO.getTest_yn());
- }
- //sendMsg 문자 발송 전 체크 사항
-
- //step1.발신자 전화번호 사용 가능 여부 체크(해당 사용자의 기 등록된 번호만 발송 가능)
- // 1010
- msgRequestVO.setCallFrom(MunjaUtil.removeCharactersWithRegex(msgRequestVO.getCallFrom()));
- if(!sendMapper.findByCallFrom(msgRequestVO)){
- return new RestResponse(new FailRestResponse("STAT_1010",""));
- }
-
-
- // step2.수신자 전화번호 정상 여부 체크(정상 번호에 대해서만 발송 가능)
- // 1020
- // 폰번호 확인 - 빈 값 -> 유효성 정규식
- if(this.getCallToListChk(msgRequestVO)){
- return new RestResponse(new FailRestResponse("STAT_1020",""));
- }
-
- //step3.문자 내용 정상 여부 확인 - 스미싱 문구는 발송 30분 지연으로 처리됨
- // 1030 => 현재 사용안함
- // 스팸체크 하는 부분
- // apiService.postForEntity => restTemplate.postForEntity 호출 후 MjonResponseVO에 맞게 데이터 정제하는 메소드
- MjonResponseVO spamChkEntity = apiService.postForEntity(
- "/web/user/login/selectSpamTxtChkAjax.do"
- , msgRequestVO
- , String.class
- );
- // 스팸체크 결과값이 spams 이면 스팸문자로 처리
- if("spams".equals(spamChkEntity.getResult())){
- msgRequestVO.setSpamStatus("Y");
- };
-
- //step4.치환명 정상 여부 확인
- // 1040
- msgRequestVO.setTxtReplYn(this.getTxtReplYn(msgRequestVO));
- // 치환데이터가 있을 경우
- if("Y".equals(msgRequestVO.getTxtReplYn())){
- //일괄변환 문자에 콤마(,)가 들어가있으면 배열로 넘길때 문제가 발생하여 특수문자(§)로 치환하여 넘겨주도록 한다.
- msgRequestVO = this.getReplaceCommaToStrSymbol(msgRequestVO);
- // 치환 후 단문 장문 개수 구하기
- msgRequestVO = this.getLengthOfShortAndLongMsg(msgRequestVO);
- }
-
-
- PriceVO priceVO = Price.priceRefine(msgRequestVO.getMberId(), priceMapper);
-
-
- // +""; => Double to String 와 같은 기능
- msgRequestVO.setsPrice(priceVO.getShortPrice()+"");
- msgRequestVO.setmPrice(priceVO.getLongPrice()+"");
- msgRequestVO.setpPrice(priceVO.getPicturePrice()+"");
-
-
- //문자열 길이 체크 해주기
- // 문자 바이트 계산에 필요한 캐릭터 셋 : 한글 2Byte로 계산
- int FrBytes = getFrBytes(msgRequestVO);
- // 단문 장문 단가과 타입 지정
- if(FrBytes > 90){
- msgRequestVO.setEachPrice(msgRequestVO.getsPrice());
- msgRequestVO.setMsgType("6");
- }else {
- msgRequestVO.setEachPrice(msgRequestVO.getmPrice());
- msgRequestVO.setMsgType("4");
- }
-
-
-
-
- // 문자 전송하는 부분
- // apiService.postForEntity => restTemplate.postForEntity 호출 후 MjonResponseVO에 맞게 데이터 정제하는 메소드
- MjonResponseVO munjaSendResponse = apiService.postForEntity(
- "/web/user/login/sendMsgDataAjax.do"
- , msgRequestVO
- , String.class
- );
- log.info("munjaSendResponse : [{}]", munjaSendResponse.toString());
- log.info("munjaSendResponse : [{}]", munjaSendResponse.getResult());
-
- // convertMjonDataToApiResponse => MjonResponseVO 데이터를 ApiResponse 데이터로 변환하는 메소드
- if(!munjaSendResponse.getResult().equals("fail")){ // 성공
- return new RestResponse(SendSucRestResponse.convertMjonDataToApiResponse(munjaSendResponse));
- }else{ // 실패
- return new RestResponse(new FailRestResponse(StatMsg.randomErrorStatCode(),""));
- }
-
- //step5.발송일시 정상여부 확인
- // 1050
- //step6.문자 타입에 따른 비용 처리 가능 여부 확인
- // 1060
-
-
- }
-
- private static int getFrBytes(MsgRequestVO msgRequestVO) throws UnsupportedEncodingException {
- String smsCont = msgRequestVO.getSmsTxt().replace("\r\n", "\n");
- int FrBytes = smsCont.getBytes("euc-kr").length;
- return FrBytes;
- }
-
- private RestResponse _getTestMsgReturnData(String testYn)
- {
- // YF => 실패 테스트 데이터
- if("YF".equals(testYn))
- {
- // 실패 코드 중 랜덤으로 리턴
- return new RestResponse(new FailRestResponse(StatMsg.randomErrorStatCode(),"YF"));
-
-
-
- }else{
- return new RestResponse(
- SendSucRestResponse.builder()
- .resultCode("0")
- .msgGroupId("MSGGID_0000000000000") // 전송 메세지 그룹 ID
- .successCnt("5") // 성공 건수
- .blockCnt("2") // 수신거부 건수
- .msgType("LMS")
- .failCnt("0")
- .test_yn("YS")
- .build()
- );
- }
- }
-
- private RestResponse _getTestMsgsReturnData(String testYn)
- {
- // YF => 실패 테스트 데이터
- if("YF".equals(testYn))
- {
- return new RestResponse(new FailRestResponse(StatMsg.randomErrorStatCode(),"YF"));
- }else{ // YS => 성공 테스트 데이터
-
-
-
- List gIdList = new ArrayList<>();
- gIdList.add("MSGGID_0000000000000");
- gIdList.add("MSGGID_0000000000001");
- gIdList.add("MSGGID_0000000000002");
-
- List msgTypeList = new ArrayList<>();
- msgTypeList.add("SMS");
- msgTypeList.add("LMS");
- msgTypeList.add("LMS");
- return new RestResponse(
- SendSucRestResponse.builder()
- .resultCode("0")
- .msgGroupIdList(gIdList) // 전송 메세지 그룹 ID
- .successCnt("2") // 성공 건수
- .blockCnt("1") // 수신거부 건수
- .msgTypeList(msgTypeList)
- .failCnt("0")
- .test_yn("YS")
- .build()
- );
- }
- }
- @Override
- public RestResponse sendMsgsData(MsgsRequestVO msgsRequestVO) throws Exception {
-
- if(StringUtils.isNotEmpty(msgsRequestVO.getTest_yn())){
- return this._getTestMsgsReturnData(msgsRequestVO.getTest_yn());
- }
-
- // msgsVO -> msgVO List로 변환
- List msgRequestVOList = this.getDataCleaning(msgsRequestVO);
-
-
- //step1.발신자 전화번호 사용 가능 여부 체크(해당 사용자의 기 등록된 번호만 발송 가능)
- // 1010
- if(!sendMapper.findByCallFrom(msgRequestVOList.get(0))){
- return new RestResponse(new FailRestResponse("STAT_1010",""));
- }
-
-
- // step2.수신자 전화번호 정상 여부 체크(정상 번호에 대해서만 발송 가능)
- // 1020
- // 폰번호 확인 - 빈 값 -> 유효성 정규식
- for(MsgRequestVO msgRequestVO : msgRequestVOList){
- if(this.getCallToListChk(msgRequestVO)){
-// if(StringUtils.isNotEmpty(this.getCallToListChk(msgRequestVO))){
- return this.callToErrorReturnData(msgRequestVO);
- }
- }
-
- //사용자 잔액
-// double mberMoney = priceMapper.selectMberMoney(mberId);
- // 이용단가, 발송가능 건수
-// PriceVO priceVO = this.price_refine(mberId, mberMoney, priceMapper);
-
-
- PriceVO priceVO = Price.priceRefine(msgsRequestVO.getMberId(), priceMapper);
-
- // +""; => Double to String 와 같은 기능
- String sPrice = priceVO.getShortPrice()+"";
- String mPrice = priceVO.getLongPrice()+"";
- String pPrice = priceVO.getPicturePrice()+"";
- List mjonResponseVOList = new ArrayList();
- for(MsgRequestVO msgRequestVO : msgRequestVOList){
-
-
-
- //문자열 길이 체크 해주기
- // 문자 바이트 계산에 필요한 캐릭터 셋 : 한글 2Byte로 계산
- int FrBytes = getFrBytes(msgRequestVO);
- // 단문 장문 단가과 타입 지정
- if(FrBytes > 90){
- msgRequestVO.setEachPrice(sPrice);
- msgRequestVO.setMsgType("6");
- }else {
- msgRequestVO.setEachPrice(mPrice);
- msgRequestVO.setMsgType("4");
- }
-
- // 단가 셋팅
- msgRequestVO.setsPrice(sPrice); // 단문
- msgRequestVO.setmPrice(mPrice); // 장문
- msgRequestVO.setpPrice(pPrice); // 사진
-
-
- //step3.문자 내용 정상 여부 확인 - 스미싱 문구는 발송 30분 지연으로 처리됨
- // 1030 => 현재 사용안함
- // 스팸체크 하는 부분
- MjonResponseVO spamChkEntity = apiService.postForEntity(
- "/web/user/login/selectSpamTxtChkAjax.do"
- , msgRequestVO
- , String.class
- );
- // 스팸체크 결과값이 spams 이면 스팸문자로 처리
- if("spams".equals(spamChkEntity.getResult())){
- msgRequestVO.setSpamStatus("Y");
- };
-
- // 문자 전송하는 부분
- // apiService.postForEntity => restTemplate.postForEntity 호출 후 MjonResponseVO에 맞게 데이터 정제하는 메소드
- MjonResponseVO munjaSendResponse = apiService.postForEntity(
- "/web/user/login/sendMsgDataAjax.do"
- , msgRequestVO
- , String.class
- );
- //
- mjonResponseVOList.add(munjaSendResponse);
- }
-
-
- return new RestResponse(SendSucRestResponse.SendSuccessMsgsRestResponse(mjonResponseVOList));
- }
-
- private static RestResponse callToErrorReturnData(MsgRequestVO msgRequestVO) {
-
- FailRestResponse stat1020 = new FailRestResponse("STAT_1020","");
- String errorMsg = stat1020.getMsg();
- errorMsg.replace("수신자", "수신자(" + msgRequestVO.getCallToList()[0] + ")");
- stat1020.setMsg(errorMsg);
-
- return new RestResponse(stat1020);
- }
-
- /**
- * @description 최대 1~100개의 수신번호와 메세지를 MsgRequestVO로 정재하는 메소드.
- * @param msgsRequestVO
- * @return
- */
- private static List getDataCleaning(MsgsRequestVO msgsRequestVO) {
-
-
- List msgRequestVOList = new ArrayList<>();
-
- // Reflection으로 Object Field에 접근한다.
- Field[] declaredFields = msgsRequestVO.getClass().getDeclaredFields();
-
- String mberId = null; // 사용자 ID
- String accessKey = null; // accessKey
- String callFrom = null; // 발신자 번호
- String callTo = null; // 수신자 번호
-
- for (Field field : declaredFields) {
-
- Object value = null;
- // private Field일 경우 접근을 허용한다.
- field.setAccessible(true);
-
- try {
- // Field Value를 참조한다.
- value = field.get(msgsRequestVO);
- } catch (IllegalAccessException e) {
- log.info("Reflection Error. {}", e);
- }
-
- // nullPointException 방지
- if(value != null)
- {
- /**
- * 필드 이름으로 분기하여
- * 각각에 맞는 위치에 값을 넣어준다.
- */
- if("mberId".equals(field.getName())){ // 사용자 ID
- mberId = value.toString();
- }else if("accessKey".equals(field.getName())){ // accessKey
- accessKey = value.toString();
- }else if("callFrom".equals(field.getName())){ // 발신자 번호
- callFrom = value.toString();
- }else if(field.getName().startsWith("callTo")){ // 수신자 번호
- callTo = value.toString();
- }else if(field.getName().startsWith("smsTxt")){ // 문자 내용
-
- // 값이 비여 있으면 다음 반복문으로 넘어간다.
- if(StringUtils.isEmpty(value.toString())){
- callTo = "";
- continue;
- }
-
- msgRequestVOList.add(
- MsgRequestVO.builder()
- .mberId(mberId)
- .accessKey(accessKey)
- .callFrom(callFrom)
- .callToList(new String[]{callTo})
- .smsTxt(value.toString())
- .eachPrice("0") // 디폴트
- .sPrice("0") // 디폴트
- .totPrice("0") // 디폴트
- .fileCnt("0") // 디폴트
- .msgType("4") // 디폴트
- .smsPrice(0) // 디폴트
- .mmsPrice(0) // 디폴트
- .imgFilePath(new String[0]) // 디폴트
- .txtReplYn("N") // 디폴트
- .reserveYn("N") // 디폴트
- .msgKind("N") // 디폴트
- .build()
- );
- // 초기화
- callTo = "";
- }
-
-
- }
- }
- return msgRequestVOList;
- }
-
-
- /**
- * 치환 후 단문 장문 msg 개수 구하기
- * @param msgRequestVO
- * @return msgRequestVO
- * @throws UnsupportedEncodingException
- */
- private static MsgRequestVO getLengthOfShortAndLongMsg(MsgRequestVO msgRequestVO) throws UnsupportedEncodingException {
- String charset = "euc-kr";
- int totListCnt = msgRequestVO.getCallToList().length;
-
- int shortMsgCnt=0; // 치환 후 단문 개수
- int longMsgCnt=0; // 치환 후 장문 개수
- for(int i=0; i < totListCnt; i ++) {
- String smsTxt = msgRequestVO.getSmsTxt().replaceAll(String.valueOf((char)13), ""); //발송 문자 내용
-
- String[] nameList = msgRequestVO.getNameList(); //치환 이름 리스트
- String[] phone = msgRequestVO.getCallToList(); //수신자 휴대폰 번호
- String[] rep1 = msgRequestVO.getRep1List(); //치환 문자1 리스트
- String[] rep2 = msgRequestVO.getRep2List(); //치환 문자2 리스트
- String[] rep3 = msgRequestVO.getRep3List(); //치환 문자3 리스트
- String[] rep4 = msgRequestVO.getRep4List(); //치환 문자4 리스트
-
- if (smsTxt.indexOf("[*이름*]") > -1) {
- if(nameList.length > i && StringUtils.isNotEmpty(nameList[i])) {
- smsTxt = smsTxt.replaceAll("\\[\\*이름\\*\\]", MunjaUtil.getString(nameList[i].replaceAll("§", ",")));
- }else {
- smsTxt = smsTxt.replaceAll("\\[\\*이름\\*\\]", "");
- }
- }
- if (smsTxt.indexOf("[*1*]") > -1) {
- if(rep1.length > i && StringUtils.isNotEmpty(rep1[i])) {
- smsTxt = smsTxt.replaceAll("\\[\\*1\\*\\]", MunjaUtil.getString(rep1[i].replaceAll("§", ",")));
- }else {
- smsTxt = smsTxt.replaceAll("\\[\\*1\\*\\]", "");
- }
- }
- if (smsTxt.indexOf("[*2*]") > -1) {
- if(rep2.length > i && StringUtils.isNotEmpty(rep2[i])) {
- smsTxt = smsTxt.replaceAll("\\[\\*2\\*\\]", MunjaUtil.getString(rep2[i].replaceAll("§", ",")));
- }else {
- smsTxt = smsTxt.replaceAll("\\[\\*2\\*\\]", "");
- }
- }
- if (smsTxt.indexOf("[*3*]") > -1) {
- if(rep3.length > i && StringUtils.isNotEmpty(rep3[i])) {
- smsTxt = smsTxt.replaceAll("\\[\\*3\\*\\]", MunjaUtil.getString(rep3[i].replaceAll("§", ",")));
- }else {
- smsTxt = smsTxt.replaceAll("\\[\\*3\\*\\]", "");
- }
- }
- if (smsTxt.indexOf("[*4*]") > -1) {
- if(rep4.length > i && StringUtils.isNotEmpty(rep4[i])) {
- smsTxt = smsTxt.replaceAll("\\[\\*4\\*\\]", MunjaUtil.getString(rep4[i].replaceAll("§", ",")));
- }else {
- smsTxt = smsTxt.replaceAll("\\[\\*4\\*\\]", "");
- }
- }
-
-
- int bytes = smsTxt.getBytes(charset).length;
-
- if(bytes > 90) {//장문문자 리스트 만들기
- longMsgCnt++;
- }else {//단문문자 리스트 만들기
- shortMsgCnt++;
- }
- }
- msgRequestVO.setLongMsgCnt(Integer.toString(longMsgCnt));
- msgRequestVO.setShortMsgCnt(Integer.toString(shortMsgCnt));
-
- return msgRequestVO;
- }
-
- /**
- * 치환문자가 있으면 , => §로 치환
- * @param msgRequestVO
- * @return
- */
- private static MsgRequestVO getReplaceCommaToStrSymbol(MsgRequestVO msgRequestVO) {
-
- AtomicInteger index = new AtomicInteger();
-
- // 이름 배열
- if(StringUtils.isNotEmpty(msgRequestVO.getNameStr()))
- {
- msgRequestVO.setNameList(msgRequestVO.getNameStr().split("\\|"));
- String[] nameList = new String[msgRequestVO.getNameList().length];
- Arrays.stream(msgRequestVO.getNameList()).forEach(name -> {
- nameList[index.getAndIncrement()] = MunjaUtil.replaceCommaToStrSymbol(name);
- });
- msgRequestVO.setNameList(nameList);
- }
-
- // Rep1 배열
- if(StringUtils.isNotEmpty(msgRequestVO.getRep1Str()))
- {
- index.set(0);
- msgRequestVO.setRep1List(msgRequestVO.getRep1Str().split("\\|"));
- String[] rep1List = new String[msgRequestVO.getRep1List().length];
- Arrays.stream(msgRequestVO.getRep1List()).forEach(str -> {
- rep1List[index.getAndIncrement()] = MunjaUtil.replaceCommaToStrSymbol(str);
- });
- msgRequestVO.setRep1List(rep1List);
-
- }
-
- // Rep2 배열
- if(StringUtils.isNotEmpty(msgRequestVO.getRep2Str()))
- {
- index.set(0);
- msgRequestVO.setRep2List(msgRequestVO.getRep2Str().split("\\|"));
- String[] rep2List = new String[msgRequestVO.getRep2List().length];
- Arrays.stream(msgRequestVO.getRep2List()).forEach(str -> {
- rep2List[index.getAndIncrement()] = MunjaUtil.replaceCommaToStrSymbol(str);
- });
- msgRequestVO.setRep2List(rep2List);
- }
-
- // Rep3 배열
- if(StringUtils.isNotEmpty(msgRequestVO.getRep3Str()))
- {
- index.set(0);
- msgRequestVO.setRep3List(msgRequestVO.getRep3Str().split("\\|"));
- String[] rep3List = new String[msgRequestVO.getRep3List().length];
- Arrays.stream(msgRequestVO.getRep3List()).forEach(str -> {
- rep3List[index.getAndIncrement()] = MunjaUtil.replaceCommaToStrSymbol(str);
- });
- msgRequestVO.setRep3List(rep3List);
- }
-
- // Rep4 배열
- if(StringUtils.isNotEmpty(msgRequestVO.getRep4Str()))
- {
- index.set(0);
- msgRequestVO.setRep4List(msgRequestVO.getRep4Str().split("\\|"));
- String[] rep4List = new String[msgRequestVO.getRep4List().length];
- Arrays.stream(msgRequestVO.getRep4List()).forEach(str -> {
- rep4List[index.getAndIncrement()] = MunjaUtil.replaceCommaToStrSymbol(str);
- });
- msgRequestVO.setRep4List(rep4List);
- }
- return msgRequestVO;
- }
-
- /**
- * 치환 문자 여부 확인
- * @param msgRequestVO
- * @return
- */
- private static String getTxtReplYn(MsgRequestVO msgRequestVO) {
-
- int callLen = msgRequestVO.getCallToList().length;
- // 치환 데이터 확인
- Arrays.stream(replaseStrList.split(",")).forEach(
- str -> {
- if(msgRequestVO.getSmsTxt().indexOf(str) > -1){
- msgRequestVO.setTxtReplYn("Y");
- }
- }
- );
- return msgRequestVO.getTxtReplYn();
- }
-
- /**
- * 수신자 목록 번호 검증
- * message가 없으면 정상
- * @param msgRequestVO
- * @return String
- */
- private static Boolean getCallToListChk(MsgRequestVO msgRequestVO) {
- Boolean returnData = false;
- for(String callTo : msgRequestVO.getCallToList()){
- /*
- if(!MunjaUtil.checkPhoneNumberEmpty(callTo)){
- message = "수신 목록에 핸드폰 번호가 없는 항목이 있습니다."; break;};
- if(!MunjaUtil.validatePNumWithRegex(callTo)){
- message = "휴대폰 번호가 올바르지 않습니다. : " + callTo; break;};
- */
- if(!MunjaUtil.validatePNumWithRegex(callTo) // 비여있는지 체크
- || !MunjaUtil.validatePNumWithRegex(callTo) // 정규식으로 번호 체크
- )
- {
- returnData = true;
- break;
- };
-
- }
- return returnData;
- }
-
-
-
- /**
- * MjonResponseVO -> 변환 -> SendFailRestResponse
- * @param mjonResponseVO
- * @return
- */
- public static String convertMjonDataToApiResponse(MjonResponseVO mjonResponseVO) {
- String result = mjonResponseVO.getResult();
- log.info("convertMjonDataToApiResponse : [{}]", result);
-
- String message = mjonResponseVO.getMessage();
- log.info("convertMjonDataToApiResponse : [{}]", message);
-
- String statCode = "";
- switch (result) {
- case "statusFail" : statCode = "1070"; // 회원 정지
- break;
- case "smsLengFail" : statCode = "1080"; // 문자 길이 초과
- break;
- case "fail" : // 문자온 프로젝트에서 result가 fail로 다양한 에러가 리턴하여 분기처리함
- if(message.indexOf("문자 치환 후 전송 문자 길이를 초과하였습니다.")>-1)
- statCode = "1050"; // 치환 후 문자 길이 초과
- else if(message.indexOf("치환문자 데이터가 없습니다")>-1)
- statCode = "1040"; // 치환 데이터 오류
- else if(message.indexOf("치환 후 전송 문자 길이를 초과")>-1)
- statCode = "1050"; // 치환 데이터 오류
- else
- statCode = "1099"; // 기타 시스템 오류
- break;
- default: statCode = "1099"; // 기타 시스템 오류
- break;
- }
-
- return "STAT_"+statCode;
- }
-
-}
diff --git a/src/main/java/com/itn/mjonApi/util/ApiObjectUtil.java b/src/main/java/com/itn/mjonApi/util/ApiObjectUtil.java
index 8e6e40e..16fb55b 100644
--- a/src/main/java/com/itn/mjonApi/util/ApiObjectUtil.java
+++ b/src/main/java/com/itn/mjonApi/util/ApiObjectUtil.java
@@ -3,10 +3,6 @@ package com.itn.mjonApi.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
-import com.itn.mjonApi.cmn.msg.RestResponse;
-import com.itn.mjonApi.mjon.api.access.mapper.domain.AccessKeyVO;
-import com.itn.mjonApi.mjon.api.send.mapper.domain.MsgRequestVO;
-import com.itn.mjonApi.mjon.api.send.mapper.domain.MsgsRequestVO;
/**
* packageName : com.itn.mjonApi.util
@@ -21,45 +17,10 @@ import com.itn.mjonApi.mjon.api.send.mapper.domain.MsgsRequestVO;
*/
public class ApiObjectUtil {
+ private static final ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());
-
-
-
-
-
-
- /**
- * @description : VO를 json으로 변환
- * @param restResponse
- * @return String
- * @throws JsonProcessingException
- */
- public static String getRestResponseToJsonString(RestResponse restResponse) throws JsonProcessingException {
- ObjectMapper objectMapper = new ObjectMapper();
- // .registerModule(new JavaTimeModule()) : LocalDateTime을 json으로 변환하기 위함
- return objectMapper.registerModule(new JavaTimeModule()).writeValueAsString(restResponse);
+ public static String toJson(Object obj) throws JsonProcessingException {
+ if (obj == null) return null;
+ return objectMapper.writeValueAsString(obj);
}
- public static String getMsgsRequestVOToJsonString(MsgsRequestVO msgsRequestVO) throws JsonProcessingException {
- ObjectMapper objectMapper = new ObjectMapper();
- // .registerModule(new JavaTimeModule()) : LocalDateTime을 json으로 변환하기 위함
- return objectMapper.registerModule(new JavaTimeModule()).writeValueAsString(msgsRequestVO);
- }
- public static String getMsgRequestVOToJsonString(MsgRequestVO msgRequestVO) throws JsonProcessingException {
- ObjectMapper objectMapper = new ObjectMapper();
- // .registerModule(new JavaTimeModule()) : LocalDateTime을 json으로 변환하기 위함
- return objectMapper.registerModule(new JavaTimeModule()).writeValueAsString(msgRequestVO);
- }
-
- /**
- * @description : VO를 json으로 변환
- * @param accessKeyVO
- * @return String
- * @throws JsonProcessingException
- */
- public static String getAccessKeyVOToJsonString(AccessKeyVO accessKeyVO) throws JsonProcessingException {
- ObjectMapper objectMapper = new ObjectMapper();
- // .registerModule(new JavaTimeModule()) : LocalDateTime을 json으로 변환하기 위함
- return objectMapper.registerModule(new JavaTimeModule()).writeValueAsString(accessKeyVO);
- }
-
}
diff --git a/src/main/java/com/itn/mjonApi/util/MunjaUtil.java b/src/main/java/com/itn/mjonApi/util/MunjaUtil.java
index 7f02093..944fd48 100644
--- a/src/main/java/com/itn/mjonApi/util/MunjaUtil.java
+++ b/src/main/java/com/itn/mjonApi/util/MunjaUtil.java
@@ -1,5 +1,11 @@
package com.itn.mjonApi.util;
+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 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.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
/**
@@ -13,19 +19,10 @@ import org.apache.commons.lang3.StringUtils;
* -----------------------------------------------------------
* 2023-05-17 hylee 최초 생성
*/
+@Slf4j
public class MunjaUtil {
- /**
- * 폰번호 유효성 검사
- * @param callTo
- * @return
- */
- public static Boolean validatePNumWithRegex(String callTo){
- // 핸드폰 정규식
- String regex = "^01(?:0|1|[6-9])(?:\\d{3}|\\d{4})\\d{4}$";
- return callTo.matches(regex) ? true : false;
- }
/**
* 폰번호 빈값 검사
* @param str
@@ -49,7 +46,7 @@ public class MunjaUtil {
* 파라미터를 String 타입으로 가져옵니다.
* - null일 경우 빈 문자열을 가져옵니다.
* Get String(if object is null, return empty string).
- * @param Object
+ * @param obj
* @return String
*/
public static String getString(Object obj) {
@@ -59,5 +56,128 @@ public class MunjaUtil {
return String.valueOf(obj);
}
+ /**
+ * 수신자 목록 번호 검증
+ * message가 없으면 정상
+ * @param callToList
+ * @return String
+ */
+ public static Boolean getCallToListChk(String[] callToList) {
+ Boolean returnData = false;
+
+
+ if (ArrayUtils.isEmpty(callToList)) {
+ return true;
+ }
+
+ for(String callTo : callToList){
+ if(!MunjaUtil.checkPhoneNumberEmpty(callTo) // 비여있는지 체크
+ || !MunjaUtil.validatePNumWithRegex(callTo) // 정규식으로 번호 체크
+ )
+ {
+ return true;
+ };
+
+ }
+ return returnData;
+ }
+ public static Boolean getCallToChk(String callTo) {
+ Boolean returnData = false;
+
+ if(!MunjaUtil.checkPhoneNumberEmpty(callTo) // 비여있는지 체크
+ || !MunjaUtil.validatePNumWithRegex(callTo) // 정규식으로 번호 체크
+ )
+ {
+ return true;
+ };
+
+ return returnData;
+ }
+
+
+
+ // 기존 정규식 검사 메서드 (변경 없음)
+ public static boolean validatePNumWithRegex(String pNum) {
+ return pNum != null && pNum.matches("^\\d{10,11}$");
+ }
+
+
+
+ /**
+ * VarListMapVO의 필드들을 검증
+ *
+ * @param vo 검증할 VarListMapVO 객체
+ * @param msgAtRequestVO
+ * @return 검증 실패 시 오류 코드, 성공 시 null
+ */
+ public static String kakaoAtValidate(VarAtListMapVO vo, MsgAtRequestVO msgAtRequestVO) {
+
+
+ String subMsgSendYn = msgAtRequestVO.getSubMsgSendYn();
+ Boolean hasTemplateTitle = msgAtRequestVO.getHasTemplateTitle();
+ // 수신번호 검증
+ String callTo = vo.getCallToList();
+ if (MunjaUtil.getCallToChk(callTo)) {
+ return "STAT_1020"; // 수신자 전화번호 오류
+ }
+
+ // 본문 데이터 검증
+ String templateContent = vo.getTemplateContent();
+ if (StringUtils.isEmpty(templateContent)) {
+ return "STAT_2040"; // 본문 데이터 오류
+ }
+
+ // 템플릿에 타이틀이 있으면 값 확인
+ if (hasTemplateTitle && StringUtils.isEmpty(vo.getTemplateTitle())) {
+ return "STAT_2041"; // 타이틀 데이터 오류
+ }
+
+ // 대체문자 검증 (대체문자 발송이 활성화된 경우에만)
+ if ("Y".equals(subMsgSendYn)) {
+ String subMsgTxt = vo.getSubMsgTxt();
+ if (StringUtils.isEmpty(subMsgTxt)) {
+ return "STAT_2042"; // 대체문자 데이터 오류
+ }
+ }
+
+ // 모든 검증 통과
+ return null;
+ }
+
+
+ public static String kakaoFtValidate(VarFtListMapVO vo, MsgFtRequestVO msgFtRequestVO) {
+
+ log.info(" vo.toString() [{}]", vo.toString());
+ String ok = null;
+ // 수신번호 검증
+ 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(msgFtRequestVO.getSubMsgSendYn())) {
+ String subMsgTxt = vo.getSubMsgTxt();
+ if (StringUtils.isEmpty(subMsgTxt)) {
+ return "STAT_2042"; // 대체문자 데이터 오류
+ }
+
+ String callFrom = msgFtRequestVO.getCallFrom();
+ if (StringUtils.isEmpty(callFrom)) {
+ return "STAT_2043"; // 대체문자 발송 시 발신번호 필요
+ }
+
+ }
+
+ // 모든 검증 통과
+ return ok;
+ }
}
+
diff --git a/src/main/java/com/itn/mjonApi/util/TestDataUtil.java b/src/main/java/com/itn/mjonApi/util/TestDataUtil.java
new file mode 100644
index 0000000..4f41e44
--- /dev/null
+++ b/src/main/java/com/itn/mjonApi/util/TestDataUtil.java
@@ -0,0 +1,299 @@
+package com.itn.mjonApi.util;
+
+import com.itn.mjonApi.cmn.domain.biz.template.detail.TemplateComments;
+import com.itn.mjonApi.cmn.domain.biz.template.detail.TemplateDetail;
+import com.itn.mjonApi.cmn.domain.biz.template.list.TemplateInfo;
+import com.itn.mjonApi.cmn.msg.FailRestResponse;
+import com.itn.mjonApi.cmn.msg.RestResponse;
+import com.itn.mjonApi.cmn.msg.StatMsg;
+import com.itn.mjonApi.mjon.api.kakao.at.inqry.mapper.domain.MjKakaoProfileInfoVO;
+import com.itn.mjonApi.mjon.api.msg.send.mapper.domain.SendSucRestResponse;
+import org.apache.commons.lang3.time.DateFormatUtils;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 카카오 조회 API 테스트 데이터 유틸리티 클래스
+ * 기존 SMS/LMS 테스트 패턴을 재사용하여 일관성 유지
+ *
+ * @author system
+ * @date 2025-01-09
+ */
+public class TestDataUtil {
+
+
+ private static FailRestResponse randomFail(){
+
+ return new FailRestResponse(StatMsg.randomErrorStatCode(),"YF");
+ }
+
+ public static RestResponse _getTestMsgReturnData(String testYn)
+ {
+ // YF => 실패 테스트 데이터
+
+ if("YF".equals(testYn))
+ {
+ // 실패 코드 중 랜덤으로 리턴
+ return new RestResponse(randomFail());
+ }else{
+ return new RestResponse(
+ SendSucRestResponse.builder()
+ .resultCode("0")
+ .msgGroupId("MSGGID_0000000000000") // 전송 메세지 그룹 ID
+ .successCnt("5") // 성공 건수
+ .blockCnt("2") // 수신거부 건수
+ .msgType("LMS")
+ .failCnt("0")
+ .test_yn("YS")
+ .build()
+ );
+ }
+ }
+
+
+ /**
+ * 다중문자발송
+ *
+ * @param testYn 테스트 모드 (YS: 성공, YF: 실패)
+ * @return RestResponse 래핑된 테스트 데이터
+ */
+ public static RestResponse _getTestMsgsReturnData(String testYn)
+ {
+ // YF => 실패 테스트 데이터
+ if("YF".equals(testYn))
+ {
+ return new RestResponse(randomFail());
+ }else{ // YS => 성공 테스트 데이터
+
+
+
+ List gIdList = new ArrayList<>();
+ gIdList.add("MSGGID_0000000000000");
+ gIdList.add("MSGGID_0000000000001");
+ gIdList.add("MSGGID_0000000000002");
+
+ List msgTypeList = new ArrayList<>();
+ msgTypeList.add("SMS");
+ msgTypeList.add("LMS");
+ msgTypeList.add("LMS");
+ return new RestResponse(
+ SendSucRestResponse.builder()
+ .resultCode("0")
+ .msgGroupIdList(gIdList) // 전송 메세지 그룹 ID
+ .successCnt("2") // 성공 건수
+ .blockCnt("1") // 수신거부 건수
+ .msgTypeList(msgTypeList)
+ .failCnt("0")
+ .test_yn("YS")
+ .build()
+ );
+ }
+ }
+
+ /**
+ * 알림톡 발송 테스트 데이터 생성
+ * @param testYn 테스트 모드 (YS: 성공, YF: 실패)
+ * @return RestResponse 래핑된 테스트 데이터
+ */
+ public static RestResponse getTestAtSendReturnData(String testYn) {
+ if("YF".equals(testYn)) {
+ // 실패 테스트 데이터
+ return new RestResponse(randomFail());
+ } else {
+ // 성공 테스트: 알림톡 발송 성공 응답
+ return new RestResponse(
+ SendSucRestResponse.builder()
+ .resultCode("0")
+ .msgGroupId("MSGGID_AT_" + System.currentTimeMillis()) // 알림톡 전용 그룹 ID
+ .successCnt("2") // 성공 건수
+ .blockCnt("0") // 수신거부 건수
+ .msgType("AT") // 알림톡 타입
+ .failCnt("0")
+ .test_yn("YS")
+ .build()
+ );
+ }
+ }
+
+ /**
+ * 친구톡 발송 테스트 데이터 생성
+ * @param testYn 테스트 모드 (YS: 성공, YF: 실패)
+ * @return RestResponse 래핑된 테스트 데이터
+ */
+ public static RestResponse getTestFtSendReturnData(String testYn) {
+ if("YF".equals(testYn)) {
+ // 실패 테스트 데이터
+ return new RestResponse(randomFail());
+ } else {
+ // 친구톡용 msgGroupIdList 생성
+ List msgGroupIdList = new ArrayList<>();
+ long timestamp = System.currentTimeMillis();
+ msgGroupIdList.add("MSGGID_" + timestamp);
+ msgGroupIdList.add("MSGGID_" + timestamp);
+
+ // 성공 테스트: 친구톡 발송 성공 응답
+ return new RestResponse(
+ SendSucRestResponse.builder()
+ .resultCode("0")
+ .msgType("FT") // 친구톡 타입
+ .msgGroupIdList(msgGroupIdList) // 친구톡은 msgGroupIdList 사용
+ .successCnt("2") // 성공 건수
+ .failCnt("0")
+ .test_yn("YS")
+ .build()
+ );
+ }
+ }
+
+
+ /**
+ * 채널 ID 조회 테스트 데이터 생성
+ *
+ * @param testYn 테스트 모드 (YS: 성공, YF: 실패)
+ * @return RestResponse 래핑된 테스트 데이터
+ */
+ public static RestResponse getChnlIdTestData(String testYn) {
+ if ("YF".equals(testYn)) {
+ return new RestResponse(randomFail());
+ } else {
+
+
+ Date now = new Date();
+ // 원하는 포맷
+ String nowDate = DateFormatUtils.format(now, "yyyy-MM-dd HH:mm:ss");
+
+ // 성공 테스트: 모킹된 채널 정보 반환
+ List mockData = new ArrayList<>();
+
+ MjKakaoProfileInfoVO channel1 = new MjKakaoProfileInfoVO();
+ channel1.setSenderKey("test_sender_key_001");
+ channel1.setPhoneNumber("02-1234-0000");
+ channel1.setYellowId("@test_channel_001");
+ channel1.setFrstRegisterId("test_id_one");
+ channel1.setFrstRegistPnttm(nowDate);
+
+ MjKakaoProfileInfoVO channel2 = new MjKakaoProfileInfoVO();
+ channel2.setSenderKey("test_sender_key_002");
+ channel2.setPhoneNumber("02-1234-0001");
+ channel2.setYellowId("@test_channel_002");
+ channel2.setFrstRegisterId("test_id_tow");
+ channel2.setFrstRegistPnttm(nowDate);
+
+ mockData.add(channel1);
+ mockData.add(channel2);
+
+ return new RestResponse(mockData);
+ }
+ }
+
+ /**
+ * 템플릿 목록 조회 테스트 데이터 생성
+ *
+ * @param testYn 테스트 모드 (YS: 성공, YF: 실패)
+ * @return RestResponse 래핑된 테스트 데이터
+ */
+ public static RestResponse getTemplateListTestData(String testYn) {
+ if ("YF".equals(testYn)) {
+ return new RestResponse(randomFail());
+ } else {
+ // 성공 테스트: 모킹된 템플릿 목록 반환
+ List mockData = new ArrayList<>();
+
+ TemplateInfo template1 = new TemplateInfo();
+ template1.setSenderKey("test_sender_key_001");
+ template1.setTemplateCode("TEST_TEMPLATE_001");
+ template1.setTemplateName("테스트 템플릿 001");
+ template1.setCreatedAt("2025-01-01 10:00:00");
+ template1.setModifiedAt("2025-01-01 10:00:00");
+ template1.setServiceStatus("REG(등록완료)");
+
+ TemplateInfo template2 = new TemplateInfo();
+ template2.setSenderKey("test_sender_key_001");
+ template2.setTemplateCode("TEST_TEMPLATE_002");
+ template2.setTemplateName("테스트 템플릿 002");
+ template2.setCreatedAt("2025-01-01 11:00:00");
+ template2.setModifiedAt("2025-01-01 11:00:00");
+ template2.setServiceStatus("REG(등록완료)");
+
+ TemplateInfo template3 = new TemplateInfo();
+ template3.setSenderKey("test_sender_key_002");
+ template3.setTemplateCode("TEST_TEMPLATE_003");
+ template3.setTemplateName("테스트 템플릿 003");
+ template3.setCreatedAt("2025-01-01 12:00:00");
+ template3.setModifiedAt("2025-01-01 12:00:00");
+ template3.setServiceStatus("REJ(반려)");
+
+
+ mockData.add(template1);
+ mockData.add(template2);
+ mockData.add(template3);
+
+ return new RestResponse(mockData);
+ }
+ }
+
+ /**
+ * 템플릿 상세 조회 테스트 데이터 생성
+ *
+ * @param testYn 테스트 모드 (YS: 성공, YF: 실패)
+ * @return RestResponse 래핑된 테스트 데이터
+ */
+ public static RestResponse getTemplateDetailTestData(String testYn) {
+ if ("YF".equals(testYn)) {
+ return new RestResponse(randomFail());
+ } else {
+ // 성공 테스트: 실제 API 응답 구조를 반영한 템플릿 상세 반환
+ TemplateDetail templateDetail = TemplateDetail.builder()
+ .block("false")
+ .categoryCode("0000000")
+ .createdAt("2024-08-29 10:10:06")
+ .dormant("false")
+ .inspectionStatus("APR")
+ .modifiedAt("2025-03-27 15:41:57")
+ .securityFlag("false")
+ .senderKey("test_sender_key_1215123251234234234")
+ .senderKeyType("S")
+ .status("A")
+ .templateCode("bizp_test_template_0000000000000000")
+ .templateContent("[테스트]\\n안녕하세요 #{이름}님\\n테스트 물건1 #{물건1}\\n테스트 물건2 #{물건2}\\n테스트 물건3 #{물건3}\\n테스트 물건4 #{물건4}\\n테스트 물건5 #{물건5}\\n테스트 물건6 #{물건6}\\n입니다.")
+ .templateEmphasizeType("NONE")
+ .templateExtra("")
+ .templateHeader("")
+ .templateImageName("")
+ .templateImageUrl("")
+ .templateMessageType("BA")
+ .templateName("테스트 변수 템플릿")
+ .templateSubtitle("")
+ .templateTitle("")
+ .templateItemHighlight(null)
+ .templateItem(null)
+ .buttons(new ArrayList<>())
+ .comments(createMockComments())
+ .quickReplies(new ArrayList<>())
+ .build();
+
+ return new RestResponse(templateDetail);
+ }
+ }
+
+ /**
+ * 테스트용 Mock Comments 생성 헬퍼 메서드
+ * 실제 API 응답의 comments 구조를 반영
+ */
+ private static List createMockComments() {
+ List comments = new ArrayList<>();
+
+ // 실제 API 응답과 유사한 comment 구조
+ TemplateComments comment = new TemplateComments();
+ comment.setContent("안녕하세요. 카카오톡 알림톡 검수 담당자입니다.\\r\\n\\r\\n테스트 템플릿으로 확인되어 승인합니다.\\r\\n\\r\\n감사합니다.");
+ comment.setCreatedAt("2024-08-29 13:44:13");
+ comment.setStatus("APR");
+ comment.setAttachment(new ArrayList<>());
+
+ comments.add(comment);
+ return comments;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/itn/mjonApi/util/Email/EmailVO.java b/src/main/java/com/itn/mjonApi/util/email/EmailVO.java
similarity index 100%
rename from src/main/java/com/itn/mjonApi/util/Email/EmailVO.java
rename to src/main/java/com/itn/mjonApi/util/email/EmailVO.java
diff --git a/src/main/java/com/itn/mjonApi/util/Email/SMTPAuthenticator.java b/src/main/java/com/itn/mjonApi/util/email/SMTPAuthenticator.java
similarity index 100%
rename from src/main/java/com/itn/mjonApi/util/Email/SMTPAuthenticator.java
rename to src/main/java/com/itn/mjonApi/util/email/SMTPAuthenticator.java
diff --git a/src/main/java/com/itn/mjonApi/util/Email/SendMail.java b/src/main/java/com/itn/mjonApi/util/email/SendMail.java
similarity index 100%
rename from src/main/java/com/itn/mjonApi/util/Email/SendMail.java
rename to src/main/java/com/itn/mjonApi/util/email/SendMail.java
diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties
index 49778b3..96060b2 100644
--- a/src/main/resources/application-dev.properties
+++ b/src/main/resources/application-dev.properties
@@ -2,7 +2,7 @@
# DB INFO
spring.datasource.driverClassName=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
-spring.datasource.url=jdbc:log4jdbc:mysql://192.168.0.125:3306/mjon?serverTimezone=Asia/Seoul
+spring.datasource.url=jdbc:log4jdbc:mysql://192.168.0.60:3308/mjon_advc?serverTimezone=Asia/Seoul
#spring.datasource.url=jdbc:log4jdbc:mysql://139.150.72.157:3306/mjon?serverTimezone=Asia/Seoul
spring.datasource.username=mjonUr
@@ -10,11 +10,17 @@ spring.datasource.password=mjon!@#$
server.port=8088
-#logging.level.root=info
+# Logging Configuration
+logging.level.root=info
+logging.level.jdbc.sqltiming=info
+logging.level.jdbc.resultset=info
+logging.level.jdbc.resultsettable=info
+logging.level.jdbc.sqlonly=debug
+logging.level.jdbc.audit=warn
+logging.level.jdbc.connection=warn
#??? ?? ??
-api.root.url=http://192.168.0.60:8085/
-#api.root.url=https://www.munjaon.co.kr/
+api.root.url=http://192.168.0.176:9080/
Ganpandaup.receiver.email=hylee250@kakao.com
diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties
new file mode 100644
index 0000000..3ca6b5c
--- /dev/null
+++ b/src/main/resources/application-local.properties
@@ -0,0 +1,30 @@
+
+
+# DB INFO
+spring.datasource.driverClassName=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
+spring.datasource.url=jdbc:log4jdbc:mysql://192.168.0.60:3308/mjon_advc?serverTimezone=Asia/Seoul
+#spring.datasource.url=jdbc:log4jdbc:mysql://139.150.72.157:3306/mjon?serverTimezone=Asia/Seoul
+
+spring.datasource.username=mjonUr
+spring.datasource.password=mjon!@#$
+
+server.port=8088
+
+# Logging Configuration
+logging.level.root=info
+logging.level.jdbc.sqltiming=info
+logging.level.jdbc.resultset=info
+logging.level.jdbc.resultsettable=info
+logging.level.jdbc.sqlonly=debug
+logging.level.jdbc.audit=warn
+logging.level.jdbc.connection=warn
+
+#??? ?? ??
+api.root.url=http://localhost:8080/
+#api.root.url=http://192.168.0.60:8085/
+#api.root.url=https://www.munjaon.co.kr/
+
+Ganpandaup.receiver.email=hylee250@kakao.com
+
+
+server.tomcat.ajp.port=8009
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 01379ad..00653e9 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,12 +1,14 @@
-spring.profiles.active=dev
+spring.profiles.active=local
# mybatis setting
+mybatis.config-location=classpath:mybatis-config.xml
mybatis.mapper-locations=classpath:mapper/**/*.xml
-# model camel case set
-mybatis.configuration.map-underscore-to-camel-case=true
+
+spring.jackson.default-property-inclusion=non_null
+
#sql \ucd9c\ub825 log \uc124\uc815
logging.level.jdbc.sqlonly=off
@@ -34,4 +36,21 @@ spring.servlet.multipart.max-request-size=20MB
#management.endpoints.web.exposure.include=*
#management.endpoint.health.show-details=always
#
-Ganpandaup.estimate.template.url=https://www.munjaon.co.kr/publish/email_form_ganpandaum_contact.html
\ No newline at end of file
+Ganpandaup.estimate.template.url=https://www.munjaon.co.kr/publish/email_form_ganpandaum_contact.html
+
+
+biz.root.url=https://kapi.ppurio.com
+biz.api.key=dheBWCONP6J5
+biz.id=itn0202
+
+
+# ?? ?? actuator ?? ???? ?? ?
+# management.server.port=8081
+
+# actuator base path ?? (???: /actuator)
+# management.endpoints.web.base-path=/manage
+
+# health ?? ?? ??
+management.endpoint.health.show-details=always
+# actuator endpoint ?? ??
+management.endpoints.web.exposure.include=health,info,metrics
diff --git a/src/main/resources/log4jdbc.log4j2.properties b/src/main/resources/log4jdbc.log4j2.properties
index a48b3e9..48a146b 100644
--- a/src/main/resources/log4jdbc.log4j2.properties
+++ b/src/main/resources/log4jdbc.log4j2.properties
@@ -1,2 +1,20 @@
+# log4jdbc 설정
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
-log4jdbc.dump.sql.maxlinelength=0
\ No newline at end of file
+
+# SQL 출력 설정
+log4jdbc.dump.sql.maxlinelength=0
+log4jdbc.trim.sql=true
+log4jdbc.trim.sql.extrablanklines=false
+
+# ResultSet 출력 활성화 (가장 중요한 설정)
+log4jdbc.dump.sql.select=true
+log4jdbc.dump.sql.insert=true
+log4jdbc.dump.sql.update=true
+log4jdbc.dump.sql.delete=true
+
+# ResultSet 테이블 형태 출력을 위한 추가 설정
+log4jdbc.dump.fulldebugstacktrace=false
+log4jdbc.suppress.generated.keys.exception=false
+
+# 에러 억제 설정
+log4jdbc.auto.load.popular.drivers=false
\ No newline at end of file
diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml
index d2871ac..820b844 100644
--- a/src/main/resources/logback-spring.xml
+++ b/src/main/resources/logback-spring.xml
@@ -81,6 +81,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AND b.regdate DATE_ADD(CURDATE(), INTERVAL 1 DAY)
- AND STR_TO_DATE(CONCAT(#{endDate}, '235959'), '%Y%m%d%H%i%s') >= b.regdate
+ AND (
+ (b.regdate STR_TO_DATE(CONCAT(#{endDate}, '235959'), '%Y%m%d%H%i%s'))
+ OR
+ (b.req_date STR_TO_DATE(CONCAT(#{endDate}, '235959'), '%Y%m%d%H%i%s'))
+ )
AND STR_TO_DATE(date_format(DATE_ADD(NOW(), INTERVAL 1 day),'%Y%m%d000000'), '%Y%m%d%H%i%s') >= b.regdate
@@ -214,7 +221,10 @@
'2',
'3')
AND MSG_TYPE IN ('4',
- '6')
+ '6',
+ '8',
+ '9'
+ )
GROUP BY MSG_GROUP_ID
/*
ORDER BY 1=1,
@@ -301,7 +311,10 @@
WHERE 1 =1
AND MD.DEL_FLAG = 'N'
AND MD.MSG_TYPE IN ('4',
- '6')
+ '6',
+ '8',
+ '9'
+ )
)
A
GROUP BY A.MSG_GROUP_ID,
@@ -336,7 +349,7 @@