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 @@ + + SELECT a.USER_MONEY AS mberMoney + FROM lettngnrlmber a + WHERE a.MBER_ID = #{mberId} + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/Send/SendMapper.xml b/src/main/resources/mapper/api/msg/send/SendMapper.xml similarity index 86% rename from src/main/resources/mapper/Send/SendMapper.xml rename to src/main/resources/mapper/api/msg/send/SendMapper.xml index 0250391..56a820c 100644 --- a/src/main/resources/mapper/Send/SendMapper.xml +++ b/src/main/resources/mapper/api/msg/send/SendMapper.xml @@ -3,7 +3,7 @@ - +