commit 5bb9354b43bd155a571ed638c55ef0be5a5bf0af Author: limju Date: Wed Aug 30 18:02:49 2023 +0900 feat: init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c72de14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +/.idea/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..cb28b0e Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..3c6fda8 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.2/apache-maven-3.9.2-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/mens-api/.gitignore b/mens-api/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/mens-api/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/mens-api/README.md b/mens-api/README.md new file mode 100644 index 0000000..9e42de9 --- /dev/null +++ b/mens-api/README.md @@ -0,0 +1,171 @@ + +### API 가이드 +[카카오페이 문서발송 단건](./document/카카오페이내문서함_1.문서발송(단건).pdf) +[카카오페이 문서발송 대량](./document/카카오페이내문서함_1.문서발송(대량).pdf) +[카카오페이 문서발송 네트워크가이드](./document/카카오페이내문서함_1.네트워크가이드.pdf) + +### swagger +[API URL](http://localhost:8081/swagger-ui.html) +[Front URL](http://localhost:8080/swagger-ui.html) +[Front test page](http://localhost:8080/api/kakaopay/test) + +### API 결과 수신 +* 정상 수신 +```java +public class ApiResponseDTO implements Serializable { + private static final String FAIL_STATUS = "fail"; + private static final String ERROR_STATUS = "error"; + + @Schema(example = "true", description = "에러인 경우 false", requiredMode = Schema.RequiredMode.REQUIRED) + private boolean success; + + @Schema(example = " ", description = "HttpStatus.OK", requiredMode = Schema.RequiredMode.REQUIRED) + private String code; + + @Schema(description = "결과 데이타, 오류시 null", example = " ") + private T data; + + @Schema(description = "오류 발생시 오류 메세지", example = " ", requiredMode = Schema.RequiredMode.AUTO) + @Setter + private String message; + + @Schema(example = " ", description = "HttpStatus.OK", requiredMode = Schema.RequiredMode.AUTO) + private HttpStatus httpStatus; + + @Schema(description = "API 실행 결과 데이타 수") + private int count; +} +``` +* 정상 수신 +```json +{ + "success": true, + "code": "200", + "httpStatus": "OK", + "message": "성공했습니다.", + "data": { + "token_status": "USED", + "token_expires_at": 1624344762, + "token_used_at": 0, + "doc_box_sent_at": 0, + "doc_box_received_at": 0, + "authenticated_at": 0, + "user_notified_at": 0, + "payload": "payload 파라미터 입니다.", + "signed_at": 0 + }, + "count": 1, + "paginationInfo": null +} +``` +* 에러 수신 +```json +{ + "success": false, + "code": "error", + "data": null, + "message": "로그인 정보가 올바르지 않습니다.", + "httpStatus": "BAD_REQUEST", + "count": 0, + "paginationInfo": null +} +``` +* API 호출 결과가 서버등(네트웍장애)의 장애인 경우를 제외 하고 + 예외로 return 되는 경우는 없다(발생시 공통팀에 반드시 알려 줄 것) +```js + $.ajax({ + url: url, + type: method, + contentType: "application/json; charset=utf-8", + dataType: "json", + data: JSON.stringify(data), + beforeSend: (xhr) => { + //xhr.setRequestHeader(header, token); + $("#loading").show(); + + }, + success: function (res, textStatus) { + console.log( JSON.stringify(res)); + if(res.success){ + //정상 응답 + $("#resData").text(res.data) + }else{ + //에러 응답 + $("#errData").text(JSON.stringify(res)); + } + }, + error : function(data) { + // 여기로 오는 경우 공통팀에 알려 주세요 + alert("점검필요-error로 return", data.responseText); + }, + complete: () => { + $("#loading").hide(); + } + }); +``` +### API(Restful call) validation +* Controller 단에서 @Validated 사용으로 처리 가능 +* But, 이경우 API 로그를 남기기 위해 Service 단에서 체크 하도록 컨트롤러 단에서는 유효성 체크 skip + +### spring validation +```text +@Valid는 Java, @Validated는 Spring에서 지원하는 어노테이션 +@Validated는 @Valid의 기능을 포함하고, 유효성을 검토할 그룹을 지정할 수 있는 기능이 추가됨 +``` + +```java +@Null // null만 혀용 +@NotNull // null을 허용하지 않습니다. "", " "는 허용 +@NotEmpty // null, ""을 허용하지 않습니다. " "는 허용 +@NotBlank // null, "", " " 모두 허용하지 않습니다. + +@Email // 이메일 형식을 검사합니다. 다만 ""의 경우를 통과 시킵니다 +@Pattern(regexp = ) // 정규식을 검사할 때 사용됩니다. +@Size(min=, max=) // 길이를 제한할 때 사용됩니다. + +@Max(value = ) // value 이하의 값을 받을 때 사용됩니다. +@Min(value = ) // value 이상의 값을 받을 때 사용됩니다. + +@Positive // 값을 양수로 제한합니다. +@PositiveOrZero // 값을 양수와 0만 가능하도록 제한합니다. + +@Negative // 값을 음수로 제한합니다. +@NegativeOrZero // 값을 음수와 0만 가능하도록 제한합니다. + +@Future // 현재보다 미래 +@Past // 현재보다 과거 + +@AssertFalse // false 여부, null은 체크하지 않습니다. +@AssertTrue // true 여부, null은 체크하지 않습니다. +``` +### intellij devtools 활성 +```text +1. IntelliJ - Preferencs… +2. 컴파일러 - build project automatically(프로젝트 자동 빌드) 체크 +3. Advanced Settings > Compiler + Allow auto-make to start even if developed application is current running + (개발된 애플리케이션이 현재 실행 중인 경우에도 auto-make가 시작되도록 허용) 체크 +# 1 ~ 3항 까지 설정후 에도 안되는 경우만 4번 설정 +4. 서버설정 : Edit Configurations... + Modfy Options > On Update Action > Update Resources + +``` +### ens-api 배포 및 run : profile에 따라 local|dev|prod +```shell +# jdk : azul-17.0.1 +# 프로젝트 root 폴더로 이동 : ens-parent +# 패키지 생성 : local|dev|prod +$ mvnw clean package -P local + +# 실행 : 프로젝트폴더/ens-parent/ens-api/target에 생성된 jar파일 실행 +$ c:\tools\java\azul-17.0.1\java -jar -Dspring.profiles.active=local .\ens-api.jar + +# mvn 명령어 설명 +# -pl [모듈명] : 모듈명의 프로젝트만 빌드 +# -am : 의존성 있는 프로젝트 함께 빌드 - C가 A를 디펜던시로 가지고 있으며 C를 빌드하면 A -> C 순으로 빌드 +$ mvnw clean package -pl ens-api -am -P local +# -amd : 의존성 있는 타 프로젝트 빌드 - C가 A를 디펜던시로 가지고 있는 경우 A를 빌드 하면 A -> C 순으로 빌드 +$ mvnw clean package -pl egov-core -amd -P local +``` +### 스프링 배치 DB schema +[mysql DDL 스크립트](./document/batch-schema-mysql.sql) diff --git a/mens-api/document/batch-schema-mysql.sql b/mens-api/document/batch-schema-mysql.sql new file mode 100644 index 0000000..1a5d87b --- /dev/null +++ b/mens-api/document/batch-schema-mysql.sql @@ -0,0 +1,101 @@ +-- Autogenerated: do not edit this file + +CREATE TABLE BATCH_JOB_INSTANCE ( + JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY , + VERSION BIGINT , + JOB_NAME VARCHAR(100) NOT NULL, + JOB_KEY VARCHAR(32) NOT NULL, + constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) +) ENGINE=InnoDB; + +CREATE TABLE BATCH_JOB_EXECUTION ( + JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , + VERSION BIGINT , + JOB_INSTANCE_ID BIGINT NOT NULL, + CREATE_TIME DATETIME(6) NOT NULL, + START_TIME DATETIME(6) DEFAULT NULL , + END_TIME DATETIME(6) DEFAULT NULL , + STATUS VARCHAR(10) , + EXIT_CODE VARCHAR(2500) , + EXIT_MESSAGE VARCHAR(2500) , + LAST_UPDATED DATETIME(6), + JOB_CONFIGURATION_LOCATION VARCHAR(2500) NULL, + constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) + references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) +) ENGINE=InnoDB; + +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL , + TYPE_CD VARCHAR(6) NOT NULL , + KEY_NAME VARCHAR(100) NOT NULL , + STRING_VAL VARCHAR(250) , + DATE_VAL DATETIME(6) DEFAULT NULL , + LONG_VAL BIGINT , + DOUBLE_VAL DOUBLE PRECISION , + IDENTIFYING CHAR(1) NOT NULL , + constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) + references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) +) ENGINE=InnoDB; + +CREATE TABLE BATCH_STEP_EXECUTION ( + STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , + VERSION BIGINT NOT NULL, + STEP_NAME VARCHAR(100) NOT NULL, + JOB_EXECUTION_ID BIGINT NOT NULL, + START_TIME DATETIME(6) NOT NULL , + END_TIME DATETIME(6) DEFAULT NULL , + STATUS VARCHAR(10) , + COMMIT_COUNT BIGINT , + READ_COUNT BIGINT , + FILTER_COUNT BIGINT , + WRITE_COUNT BIGINT , + READ_SKIP_COUNT BIGINT , + WRITE_SKIP_COUNT BIGINT , + PROCESS_SKIP_COUNT BIGINT , + ROLLBACK_COUNT BIGINT , + EXIT_CODE VARCHAR(2500) , + EXIT_MESSAGE VARCHAR(2500) , + LAST_UPDATED DATETIME(6), + constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID) + references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) +) ENGINE=InnoDB; + +CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( + STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, + SHORT_CONTEXT VARCHAR(2500) NOT NULL, + SERIALIZED_CONTEXT TEXT , + constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID) + references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) +) ENGINE=InnoDB; + +CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( + JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, + SHORT_CONTEXT VARCHAR(2500) NOT NULL, + SERIALIZED_CONTEXT TEXT , + constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID) + references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) +) ENGINE=InnoDB; + +CREATE TABLE BATCH_STEP_EXECUTION_SEQ ( + ID BIGINT NOT NULL, + UNIQUE_KEY CHAR(1) NOT NULL, + constraint UNIQUE_KEY_UN unique (UNIQUE_KEY) +) ENGINE=InnoDB; + +INSERT INTO BATCH_STEP_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_STEP_EXECUTION_SEQ); + +CREATE TABLE BATCH_JOB_EXECUTION_SEQ ( + ID BIGINT NOT NULL, + UNIQUE_KEY CHAR(1) NOT NULL, + constraint UNIQUE_KEY_UN unique (UNIQUE_KEY) +) ENGINE=InnoDB; + +INSERT INTO BATCH_JOB_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_EXECUTION_SEQ); + +CREATE TABLE BATCH_JOB_SEQ ( + ID BIGINT NOT NULL, + UNIQUE_KEY CHAR(1) NOT NULL, + constraint UNIQUE_KEY_UN unique (UNIQUE_KEY) +) ENGINE=InnoDB; + +INSERT INTO BATCH_JOB_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_SEQ); diff --git a/mens-api/document/카카오페이내문서함_1.네트워크가이드.pdf b/mens-api/document/카카오페이내문서함_1.네트워크가이드.pdf new file mode 100644 index 0000000..5813709 Binary files /dev/null and b/mens-api/document/카카오페이내문서함_1.네트워크가이드.pdf differ diff --git a/mens-api/document/카카오페이내문서함_1.문서발송(단건).pdf b/mens-api/document/카카오페이내문서함_1.문서발송(단건).pdf new file mode 100644 index 0000000..638b3ee Binary files /dev/null and b/mens-api/document/카카오페이내문서함_1.문서발송(단건).pdf differ diff --git a/mens-api/document/카카오페이내문서함_1.문서발송(대량).pdf b/mens-api/document/카카오페이내문서함_1.문서발송(대량).pdf new file mode 100644 index 0000000..100d720 Binary files /dev/null and b/mens-api/document/카카오페이내문서함_1.문서발송(대량).pdf differ diff --git a/mens-api/lib/ojdbc6.jar b/mens-api/lib/ojdbc6.jar new file mode 100644 index 0000000..b663cd2 Binary files /dev/null and b/mens-api/lib/ojdbc6.jar differ diff --git a/mens-api/pom.xml b/mens-api/pom.xml new file mode 100644 index 0000000..08c3b1b --- /dev/null +++ b/mens-api/pom.xml @@ -0,0 +1,163 @@ + + + 4.0.0 + + kr.xit + mens-parent + 1.0.0 + + + mens-api + 1.0.0 + jar + mens-api + Mobile Electronic Notice System API + + + + kr.xit + mens-core + ${project.version} + + + + org.springframework.boot + spring-boot-starter-validation + + + org.apache.tomcat.embed + tomcat-embed-core + + + + + org.springframework.boot + spring-boot-starter-batch + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.batch + spring-batch-test + test + + + io.projectreactor + reactor-test + test + + + org.springframework.retry + spring-retry + 1.2.5.RELEASE + + + + org.projectlombok + lombok + ${lombok.version} + true + + + + com.oracle + ojdbc6 + 11.2.0.3 + system + ${basedir}/lib/ojdbc6.jar + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-jta-atomikos + + + + + + + install + ${basedir}/target + ${project.name} + + + src/main/resources + + * + static/**/* + egovframework/**/* + config/application.yml + config/application-app.yml + config/application-ens.yml + config/application-jpa.yml + config/application-${env}.yml + + true + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.3.0 + + + jar-with-dependencies + + + + + false + + + + package + + single + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + kr.xit.EnsApiApplication + + true + + + + + repackage + + + + + + + org.apache.maven.plugins + maven-source-plugin + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + diff --git a/mens-api/src/main/java/kr/xit/EnsApiApplication.java b/mens-api/src/main/java/kr/xit/EnsApiApplication.java new file mode 100644 index 0000000..0def45b --- /dev/null +++ b/mens-api/src/main/java/kr/xit/EnsApiApplication.java @@ -0,0 +1,89 @@ +package kr.xit; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import org.springframework.boot.Banner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.ApplicationPidFileWriter; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.boot.web.servlet.ServletComponentScan; +import org.springframework.context.annotation.ComponentScan; + +import kr.xit.core.spring.config.support.CustomBeanNameGenerator; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : ens API application main
+ *               ServletComponentScan
+ *               - 서블릿컴포넌트(필터, 서블릿, 리스너)를 스캔해서 빈으로 등록
+ *               - WebFilter, WebServlet, WebListener annotaion sacan
+ *               - SpringBoot의 내장톰캣을 사용하는 경우에만 동작
+ *               ConfigurationPropertiesScan
+ *               - ConfigurationProperties annotaion class scan 등록
+ *               - EnableConfigurationProperties 대체
+ * packageName : kr.xit
+ * fileName    : EnsApiApplication
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ */ +@Slf4j +@SpringBootApplication +@ConfigurationPropertiesScan(basePackages = {"egovframework", "kr.xit"}) +@ServletComponentScan +@ComponentScan( + nameGenerator = CustomBeanNameGenerator.class, + basePackages = {"egovframework", "kr.xit"} +) +public class EnsApiApplication { + static final List basePackages = new ArrayList<>( + Arrays.asList("egovframework", "kr.xit") + ); + + public static void main(String[] args) { + final String line = "===================================================================="; + log.info(line); + log.info("==== EnsApiApplication start :: active profiles - {} ====", System.getProperty("spring.profiles.active")); + if(Objects.isNull(System.getProperty("spring.profiles.active"))) { + + log.error(">>>>>>>>>>>>>> Undefined start VM option <<<<<<<<<<<<<<"); + log.error(">>>>>>>>>>>>>> -Dspring.profiles.active=local|dev|prd <<<<<<<<<<<<<<"); + log.error("============== EnsApiApplication start fail ==============="); + log.error(line); + System.exit(-1); + } + log.info(line); + + // beanName Generator 등록 : API v1, v2 등으로 분류하는 경우 + // Bean 이름 식별시 풀패키지 명으로 식별 하도록 함 + CustomBeanNameGenerator beanNameGenerator = new CustomBeanNameGenerator(); + beanNameGenerator.addBasePackages(basePackages); + + SpringApplicationBuilder applicationBuilder = new SpringApplicationBuilder(EnsApiApplication.class); + applicationBuilder.beanNameGenerator(beanNameGenerator); + + SpringApplication application = applicationBuilder.build(); + application.setBannerMode(Banner.Mode.OFF); + application.setLogStartupInfo(false); + + //TODO : 이벤트 실행 시점이 Application context 실행 이전인 경우 리스너 추가 + //PID(Process ID 작성) + application.addListeners(new ApplicationPidFileWriter()) ; + application.run(args); + + log.info("========================================================================================="); + log.info("========== EnsApiApplication load Complete :: active profiles - {} ==========", System.getProperty("spring.profiles.active")); + log.info("========================================================================================="); + } +} diff --git a/mens-api/src/main/java/kr/xit/biz/ens/mapper/IEnsCctvFileMapper.java b/mens-api/src/main/java/kr/xit/biz/ens/mapper/IEnsCctvFileMapper.java new file mode 100644 index 0000000..3680964 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/ens/mapper/IEnsCctvFileMapper.java @@ -0,0 +1,32 @@ +package kr.xit.biz.ens.mapper; + +import kr.xit.biz.ens.model.EnsDTO; +import org.egovframe.rte.psl.dataaccess.mapper.Mapper; + +import java.util.List; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.ens.mapper
+ * fileName    : IEnsCctvFileMapper
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +@Mapper +public interface IEnsCctvFileMapper { + int selectLicense(String jobSeCode); + int insertNtcnCntcData(EnsDTO.NtcnCntcData dto); + + List selectEnsNtncCntcSndngs(); + int insertCntcSndngMst(EnsDTO.EnsNtncCntcSndngTgts dto); + int insertCntcSndngDtl(EnsDTO.EnsNtncCntcSndngTgts dto); + int updateEnsNtcnCntcData(EnsDTO.EnsNtncCntcSndngTgts dto); +} diff --git a/mens-api/src/main/java/kr/xit/biz/ens/mapper/ISendMessageLinkMapper.java b/mens-api/src/main/java/kr/xit/biz/ens/mapper/ISendMessageLinkMapper.java new file mode 100644 index 0000000..f900d76 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/ens/mapper/ISendMessageLinkMapper.java @@ -0,0 +1,254 @@ +package kr.xit.biz.ens.mapper; + +import java.util.List; +import java.util.Optional; + +import org.egovframe.rte.psl.dataaccess.mapper.Mapper; + +import kr.xit.biz.ens.model.CntcDTO; +import kr.xit.biz.ens.model.EnsDTO; +import kr.xit.ens.support.kakao.model.KkopayDocAttrDTO; +import kr.xit.ens.support.kakao.model.KkopayDocBulkDTO; +import kr.xit.ens.support.kakao.model.KkopayDocDTO; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.ens.mapper
+ * fileName    : ISendMessageLinkMapper
+ * author      : limju
+ * date        : 2023-06-05
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-05    limju       최초 생성
+ *
+ * 
+ */ +@Mapper +public interface ISendMessageLinkMapper { + + //---------------------------------------------------------------------- + // accept + //---------------------------------------------------------------------- + /** + * 접수대상 조회 + * @param t + * @return List + */ + List selectAcceptTgts(final T t); + /** + * 접수대상 조회 + * @param t + * @return List + */ + List selectAcceptVali(final T t); + + /** + * 통합발송마스터 생성 + * @param t status + * @return int + */ + int insertUnitySndngMst(final T t); + + /** + * 통합발송상세 생성 + * @param t status + * @return int + */ + int insertUnitySndngDtls(final T t); + + /** + * 연계발송마스터 상태 변경 + * @param t status, newStatus + * @return int + */ + int updateProcessSttusCntcSndngMst(final T t); + //---------------------------------------------------------------------- + // accept + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // make + //---------------------------------------------------------------------- + /** + * 생성 대상 조회 + * @param t + * @return List + */ + List selectMakeTgts(final T t); + + /** + * 2차 발송 대상 건수 조회 + * @param t + * @return int + */ + int selectSendOkTgts(final T t); + + /** + * 발송마스터 생성 + * @param t status + * @return int + */ + int insertSndngMst(final T t); + + /** + * 카카오페이 내문서함 생성 + * @param t + * @return int + */ + int insertKakaoMyDocs(final T t); + + /** + * 모바일페이지 관리 생성 + * @param t + * @return int + */ + int insertMobilePageManage(final T t); + + /** + * 문자발송 데이터 생성 + * @param t + * @return int + */ + int insertSmsSndng(final T t); + + /** + * 우편발송 데이터 생성 + * @param t + * @return int + */ + int insertPostSndng(final T t); + + /** + * 통합발송마스터 상태 변경 + * @param t status, newStatus + * @return int + */ + int updateProcessSttusUnitySndngMst(final T t); + //---------------------------------------------------------------------- + // make + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // close + //---------------------------------------------------------------------- + List selectCloseTgts(final String sndngPprocessSttus); + //---------------------------------------------------------------------- + // close + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // send + //---------------------------------------------------------------------- + /** + * 통합발송 대상 조회 + * @param t + * @return List + */ + List selectSendBulkTgts(final T t); + + /** + * 카카오페이 문서요청 대상 목록 조회 + * @param t status + * @return List + */ + List selectKakaoSendTgts(final T t); + + /** + * E-GREEN 우편발송 대상 목록 조회 + * @param t status + * @return List + */ + List selectPostTgts(final T t); + + /** + * SMS 발송 대상 목록 조회 + * @param t status + * @return List + */ + List selectSmsSendTgts(final T t); + + + /** + * 발송상태 조회 : 발송후 발송 연계 마스터의 발송상태 변경값 조회 + * @param t + * @return EnsDTO.SndngMssageParam + */ + Optional selectSndProcessStatus(final T t); + /** + * 카카오페이 문서요청 결과 반영 + * @param t 문서ID, 에러코드, 에러메세지, 외부문서ID + * @return int + */ + int updateKakaoSendBulksResult(final T t); + + + /** + * 발송마스터 상태 변경 + * @param t status, newStatus + * @return int + */ + int updateProcessSttusSndngMst(final T t); + //---------------------------------------------------------------------- + // send + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // status + //---------------------------------------------------------------------- + + /** + * 발송마스터 상태 다건 변경 + * @param t status, newStatus + * @return int + */ + int updateProcessSttusBulkSndngMst(final T t); + //---------------------------------------------------------------------- + // send + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // status + //---------------------------------------------------------------------- + + /** + * 카카오 문서 상태 조회 대상 목록 조회 + * @param sndngPprocessSttus + * @return List + */ + List selectKakaoStatusTgts(final String sndngPprocessSttus); + + /** + * 카카오 문서 상태 조회 결과 반영 + * @param dto EnsDTO.KakaoMyDoc + * @return int + */ + //int updateKakaoStatusInfo(final EnsDTO.KakaoMyDoc dto); + int updateKakaoStatusInfo(final KkopayDocBulkDTO.BulkStatus dto); + //---------------------------------------------------------------------- + // status + //---------------------------------------------------------------------- + + + + //---------------------------------------------------------------------- + // result + //---------------------------------------------------------------------- + /** + *
+     * 연계발송결과 반영
+     * @param dto 발송구분코드, 발송결과 상태, 송신(요청)/수신(조회)/최초열람 일시, 에러내용
+     * @return int
+     * 
+ */ + int insertCntcSndngResult(final CntcDTO.SndngResult dto); + int updateCntcSndngResult(final CntcDTO.SndngResult dto); + //---------------------------------------------------------------------- + // result + //---------------------------------------------------------------------- + + Optional selectTmplat(final String tmplatId); + EnsDTO.MobilePageManage selectMobilePage(final KkopayDocDTO.OneTimeToken dto); +} diff --git a/mens-api/src/main/java/kr/xit/biz/ens/model/CntcDTO.java b/mens-api/src/main/java/kr/xit/biz/ens/model/CntcDTO.java new file mode 100644 index 0000000..878cd8c --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/ens/model/CntcDTO.java @@ -0,0 +1,197 @@ +package kr.xit.biz.ens.model; + +import java.io.Serializable; + +import kr.xit.core.biz.model.AuditFields; +import kr.xit.ens.support.kakao.model.KkopayDocBulkDTO; +import lombok.*; +import lombok.experimental.SuperBuilder; + +/** + *
+ * description : tb_cntc_ Entity DTO
+ *
+ * packageName : kr.xit.biz.ens.model
+ * fileName    : CntcDTO
+ * author      : limju
+ * date        : 2023-06-05
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-05    limju       최초 생성
+ *
+ * 
+ */ +public class CntcDTO { + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = false) + public static class SndngMst extends AuditFields implements Serializable { + /** + * 통합 발송 마스터 id + */ + private String unitySndngMastrId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 템플릿 ID + */ + private String tmplatId; + /** + * 발송 유형 코드 + */ + private String sndngTyCode; + /** + * 발송 건수 + */ + private int sndngCo; + /** + * 발송 처리 상태 + */ + private String sndngProcessSttus; + /** + * 발송 일시 + */ + private String sndngDt; + /** + * 마감 일시 + */ + private String closDt; + /** + * 에러 코드 + */ + private String errorCode; + /** + * 에러 메시지 + */ + private String errorMssage; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = false) + public static class SndngDtl extends AuditFields implements Serializable { + + /** + * 통합 발송 상세 id + */ + private String unitySndngDetailId; + /** + * 통합 발송 마스터 id + */ + private String unitySndngMastrId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 메인 코드 + */ + private String mainCode; + /** + * 주민등록번호 + */ + private String ihidnum; + /** + * 핸드폰 번호 + */ + private String moblphonNo; + /** + * 성명 + */ + private String nm; + /** + * 주소 + */ + private String adres; + /** + * 상세 주소 + */ + private String detailAdres; + /** + * 우편번호 + */ + private String zip; + /** + * 템플릿 메시지 데이터 + */ + private String tmpltMsgData; + /** + * 모바일 페이지 내용 + */ + private String mobilePageCn; + /** + * 이용 기관 식별 ID + */ + private String useInsttIdntfcId; + /** + * 외부 문서 식별 번호 + */ + private String externalDocumentUuid; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = false) + public static class SndngResult extends AuditFields implements Serializable { + + /** + * 통합 발송 상세 id + */ + private String unitySndngDetailId; + /** + * 발송 구분 코드 + */ + private String sndngSeCode; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 발송 결과 상태 + */ + private String sndngResultSttus; + /** + * 요청 일시 + */ + private String requstDt; + /** + * 조회 일시 + */ + private String inqireDt; + /** + * 열람 일시 + */ + private String readngDt; + /** + * 오류 내용 + */ + private String errorCn; + + /** + * documentBinderUuid + */ + private String documentBinderUuid; + } +} diff --git a/mens-api/src/main/java/kr/xit/biz/ens/model/EnsDTO.java b/mens-api/src/main/java/kr/xit/biz/ens/model/EnsDTO.java new file mode 100644 index 0000000..68ea5d3 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/ens/model/EnsDTO.java @@ -0,0 +1,971 @@ +package kr.xit.biz.ens.model; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; + +import io.swagger.v3.oas.annotations.media.Schema; +import kr.xit.core.biz.model.AuditFields; +import kr.xit.ens.support.common.ApiConstants; +import kr.xit.ens.support.kakao.model.KkopayDocBulkDTO; +import lombok.*; +import lombok.experimental.SuperBuilder; + +import javax.validation.Valid; +import javax.validation.constraints.Digits; +import javax.validation.constraints.Size; + +/** + *
+ * description : tb_ens_ Entity DTO
+ *
+ * packageName : kr.xit.biz.ens.model
+ * fileName    : EnsDTO
+ * author      : limju
+ * date        : 2023-06-05
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-05    limju       최초 생성
+ *
+ * 
+ */ +public class EnsDTO { + + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class SndngMssageParam implements Serializable { + + /** + * 통합 발송 마스터 id + */ + private String unitySndngMastrId; + /** + * 통합 발송 마스터 id + */ + private String unitySndngDetailId; + /** + * 발송 마스터 id + */ + private String sndngMastrId; + + /** + * 발송상세ID + */ + private String sndngDetailId; + + /** + * 템플릿ID + */ + private String tmplatId; + + /** + * 발송 건수 + */ + private int sndngCo; + /** + * 발송 처리 상태 + */ + private String sndngProcessSttus; + + /** + * 발송 처리 상태 + */ + private String newSndngProcessSttus; + + private String try1; + private String try2; + private String try3; + private int tryCnt; + private int trySeq; + private String sndngSeCode; + private String sndngDt; + private String sndngDt2; + private String sndngDt3; + private String try2Minute; + private String try3Minute; + private String errorMssage; + private String errorCode; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = true) + public static class SendKakaoTgt extends EnsDTO.KakaoMyDoc implements Serializable { + + /** + * 발송 마스터 id + */ + private String sndngMastrId; + + /** + * 통합 발송 마스터 id + */ + private String unitySndngMastrId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 템플릿 ID + */ + private String tmplatId; + /** + * 발송 유형 코드 + */ + private String sndngTyCode; + /** + * 발송 건수 + */ + private int sndngCo; + /** + * 발송 처리 상태 + */ + //private String sndngProcessSttus; + /** + * 마감 일시 + */ + private long closDt; + } + + + @Data + @NoArgsConstructor + @AllArgsConstructor + @ToString + @SuperBuilder + public static class UnitySndngMst implements Serializable { + + /** + * 통합 발송 마스터 id + */ + private String unitySndngMastrId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 템플릿 ID + */ + private String tmplatId; + /** + * 발송 유형 코드 + */ + private String sndngTyCode; + /** + * 발송 건수 + */ + private int sndngCo; + /** + * 발송 처리 상태 + */ + private String sndngProcessSttus; + /** + * 발송 일시 + */ + private String sndngDt1; + /** + * 발송 일시 2 + */ + private String sndngDt2; + /** + * 발송 일시 3 + */ + private String sndngDt3; + /** + * try1 + */ + private String try1; + /** + * try2 + */ + private String try2; + /** + * try3 + */ + private String try3; + /** + * try_cnt + */ + private int tryCnt; + /** + * 마감 일시 + */ + private String closDt; + /** + * 에러 코드 + */ + private String errorCode; + /** + * 에러 메시지 + */ + private String errorMssage; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = false) + public static class UnitySndngDtl extends AuditFields implements Serializable { + + /** + * 통합 발송 상세 id + */ + private String unitySndngDetailId; + /** + * 통합 발송 마스터 id + */ + private String unitySndngMastrId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 메인 코드 + */ + private String mainCode; + /** + * 차량번호 + */ + private String vhcleNo; + /** + * 주민등록번호 + */ + private String ihidnum; + /** + * 핸드폰 번호 + */ + private String moblphonNo; + /** + * 성명 + */ + private String nm; + /** + * 주소 + */ + private String adres; + /** + * 상세 주소 + */ + private String detailAdres; + /** + * 우편번호 + */ + private String zip; + /** + * 템플릿 메시지 데이터 + */ + private String tmpltMsgData; + /** + * 모바일 페이지 내용 + */ + private String mobilePageCn; + /** + * 이용 기관 식별 ID + */ + private String useInsttIdntfcId; + /** + * 외부 문서 식별 번호 + */ + private String externalDocumentUuid; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = false) + public static class TmplatManage extends AuditFields implements Serializable { + + /** + * 템플릿 ID + */ + private String tmplatId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 발송 유형 코드 + */ + private String sndngTyCode; + /** + * 템플릿 명 + */ + private String tmplatNm; + /** + * 템플릿 제목 + */ + private String tmplatSj; + /** + * 템플릿 내용 + */ + private String tmplatCn; + /** + * 고객 센터 전화 번호 + */ + private String cstmrCnterTlphonNo; + /** + * REDIRECT URL + */ + private String redirectUrl; + /** + * try1 + */ + private String try1; + /** + * try2 + */ + private String try2; + /** + * try3 + */ + private String try3; + /** + * try2_minute + */ + private int try2Minute; + /** + * try3_minute + */ + private int try3Minute; + /** + * 사용 여부 + */ + private String useAt; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = false) + public static class KakaoMyDoc extends AuditFields implements Serializable { + + /** + * 발송 상세 id + */ + private String sndngDetailId; + /** + * 통합 발송 상세 id + */ + private String unitySndngDetailId; + /** + * 발송 마스터 id + */ + private String sndngMastrId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 제목 + */ + private String title; + /** + * 해쉬 + */ + private String hash; + /** + * 메타 정보 + */ + private String commonCategories; + /** + * 처리 마감 시간 + */ + private double readExpiredAt; + /** + * 받는 이 ci + */ + private String recvCi; + /** + * 받는 이 전화번호 + */ + private String recvPhoneNumber; + /** + * 받는 이 성명 + */ + private String recvName; + /** + * 받는 이 생년월일 + */ + private String recvBirthday; + /** + * 성명 검증 옵션 + */ + private String recvIsRequiredVerifyName; + /** + * 모바일 페이지 URL + */ + private String propLink; + /** + * payload + */ + private String propPayload; + /** + * 메시지 + */ + private String propMessage; + /** + * 고객센터 전화번호 + */ + private String propCsNumber; + /** + * 고객센터 명 + */ + private String propCsName; + /** + * 외부 문서 식별 번호 + */ + private String externalDocumentUuid; + /** + * 내부 문서 식별 번호 + */ + private String documentBinderUuid; + /** + * 에러 코드 + */ + private String errorCode; + /** + * 에러 메시지 + */ + private String errorMessage; + /** + * 진행 상태 + */ + private String docBoxStatus; + /** + * 송신 시간 + */ + private String docBoxSentAt; + /** + * 수신 시간 + */ + private String docBoxReceivedAt; + /** + * 최초 열람 인증 시간 + */ + private String authenticatedAt; + /** + * 최초 OTT 검증 시간 + */ + private String tokenUsedAt; + /** + * 최초 열람 시간 + */ + private String docBoxReadAt; + /** + * 알림톡 수신 시간 + */ + private String userNotifiedAt; + + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = false) + public static class PostSndng extends AuditFields implements Serializable { + + /** + * 통합 발송 마스터 id + */ + private String unitySndngMastrId; + /** + * 발송 상세 id + */ + private String sndngDetailId; + /** + * 통합 발송 상세 id + */ + private String unitySndngDetailId; + /** + * 발송 마스터 id + */ + private String sndngMastrId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 서비스 코드 + */ + private String serviceCd; + /** + * 연계 식별 키 + */ + private String conKey; + /** + * 발송인 명 + */ + private String senderNm; + /** + * 발송인 우편번호 + */ + private String senderZipNo; + /** + * 발송인 주소 + */ + private String senderAddr; + /** + * 발송인 상세 주소 + */ + private String senderDetailAddr; + /** + * 수취인 일련 번호 + */ + private String receiverSendNo; + /** + * 수취인 명 + */ + private String receiverNm; + /** + * 수취인 우편번호 + */ + private String receiverZipNo; + /** + * 수취인 주소 + */ + private String receiverAddr; + /** + * 수취인 상세 주소 + */ + private String receiverDetailAddr; + /** + * 가변 1 + */ + private String sschnge1; + /** + * 가변 2 + */ + private String sschnge2; + /** + * 가변 3 + */ + private String sschnge3; + /** + * 발송 처리 상태 + */ + private String sndngProcessSttus; + } + + + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class MobilePageManage implements Serializable { + + /** + * 발송 상세 id + */ + private String sndngDetailId; + /** + * 발송 구분 코드 + */ + private String sndngSeCode; + /** + * 모바일 페이지 내용 + */ + private String mobilePageCn; + /** + * 등록 일시 + */ + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss") + private LocalDateTime registDt; + /** + * 등록자 + */ + private String register; + } + + @Schema(name = "sndngVali", description = "Ac 요청 결과 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class sndngVali { + /* mastr */ + /** + * 시군구 코드 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "시군구 코드 (max:5)", example = " ") + @Size(max = 5) + private String signguCode; + /** + * 과태료 코드 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "과태료 코드 (max:2)", example = " ") + @Size(max = 2) + private String ffnlgCode; + /** + * 템플릿 ID + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "템플릿 ID (max:5)", example = " ") + @Size(max = 5) + private String tmplatId; + /** + * 발송 유형 코드 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "발송 유형 코드 (max:3)", example = " ") + @Size(max = 3) + private String sndngTyCode; + /** + * 발송 건수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "발송 건수 (max:1)", example = " ") + @Digits(integer = 1, fraction = 0, message = "발송 건수는 필수입니다(max:1)") + private int sndngCo; + /** + * 발송 일시 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "발송 일시 (max:14)", example = " ") + @Size(max = 14) + private String sndngDt; + /** + * 마감 일시 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "마감 일시 (max:14)", example = " ") + @Size(max = 14) + private String closDt; + + /* detail */ + /** + * 메인 코드 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "메인 코드 (max:20)", example = " ") + @Size(max = 20) + private String mainCode; + /** + * 차량 번호 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "차량 번호 (max:30)", example = " ") + @Size(max = 30) + private String vhcleNo; + /** + * 성명 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "성명 (max:200)", example = " ") + @Size(max = 200) + private String nm; + /** + * 시군구 코드(detail) + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "시군구 코드 detail (max:5)", example = " ") + @Size(max = 5) + private String signguCodeDe; + /** + * 과태료 코드(detail) + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "과태료 코드 detail (max:2)", example = " ") + @Size(max = 2) + private String ffnlgCodeDe; + /* other */ + /** + * 주민등록번호 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "주민등록번호 (max:100)", example = " ") + @Size(max = 100) + private String ihidnum; + /** + * 핸드폰 번호 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "핸드폰 번호 (max:20)", example = " ") + @Size(max = 20) + private String moblphonNo; + /** + * 모바일 페이지 내용 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "모바일 페이지 내용", example = " ") + private String mobilePageCn; + /** + * 주소 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "주소 (max:300)", example = " ") + @Size(max = 300) + private String adres; + /** + * 상세 주소 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "상세 주소 (max:300)", example = " ") + @Size(max = 300) + private String detailAdres; + /** + * 우편번호 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "우편번호 (max:5)", example = " ") + @Size(max = 5) + private String zip; + /** + * try1 + */ + private String try1; + /** + * try2 + */ + private String try2; + /** + * try3 + */ + private String try3; + private int trySeq; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class SmsSndng implements Serializable { + + /** + * 발송 상세 id + */ + private String sndngDetailId; + /** + * 통합 발송 상세 id + */ + private String unitySndngDetailId; + /** + * 발송 마스터 id + */ + private String sndngMastrId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 차량 번호 + */ + private String vhcleNo; + /** + * 문자 발송 일시 + */ + private String smsSndngDt; + /** + * 문자 전송 형태 + */ + private String smsTrnsmisStle; + /** + * 문자 송신 전화 번호 + */ + private String smsTrnsmitTlphonNo; + /** + * 문자 수신 전화 번호 + */ + private String smsRecptnTlphonNo; + /** + * 문자 메시지 + */ + private String smsMssage; + /** + * 문자 발송 상태 + */ + private String smsSndngSttus; + /** + * 문자 발송 처리 상태 + */ + private String smsSndngProcessSttus; + /** + * 등록 일시 + */ + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss") + private LocalDateTime registDt; + /** + * 등록자 + */ + private String register; + + private String unitySndngMastrId; + private String sndngProcessSttus; + + } + + + + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class NtcnCntcData implements Serializable { + /** + * 전자고지 연계 data id + */ + private String ensCntcDataId; + /** + * 단속 일시 + */ + private String regltDt; + /** + * 차량 번호 + */ + private String vhcleNo; + /** + * 현장 명 + */ + private String sptNm; + /** + * 현장 별 코드 + */ + private String sptAcctoCode; + /** + * 파일 명 + */ + private String fileNm; + /** + * 처리 여부 + */ + private String processAt; + /** + * 대상 여부 + */ + private String trgetAt; + /** + * 통합 발송 상세 id + */ + private String unitySndngDetailId; + + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss") + private LocalDateTime registDt; + /** + * 등록자 + */ + private String register; + + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class EnsNtncCntcSndngTgts implements Serializable { + /** + * 전자고지 연계 data id + */ + private String ensCntcDataId; + /** + * 차량 번호 + */ + private String vhcleNo; + /** + * 처리 여부 + */ + private String processAt; + /** + * 대상여부 + */ + private String trgetAt; + /** + * 핸드폰 번호 + */ + private String moblphonNo; + /** + * 이름 + */ + private String nm; + /** + * 생년월일 + */ + private String brthdy; + /** + * 우편번호 + */ + private String zip; + /** + * 주소 + */ + private String adres; + /** + * 상세 주소 + */ + private String detailAdres; + /** + * 단속 일시 + */ + private String regltDt; + /** + * 현장 명 + */ + private String sptNm; + /** + * 마감일시 + */ + private String closDt; + /*모바일 페이지 내용*/ + private String mobilePageCn; + /*삭제여부*/ + private String deleteAt; + /** + * 발송건수 + */ + private int sndngCo; + /** + * 발송유형코드 + */ + @Builder.Default + private String sndngTyCode = "ENS"; + /** + * FIXME :: 알림톡 추가시 변경 필요 + * 템플릿 ID + */ + private String tmplatId = "JU102"; + /** + * 사전알림 데이타 생성 대상 여부 + */ + private String tgtYn; + + /** + * 통합발송 마스터ID + */ + private String unitySndngMastrId; + /** + * 통합발송 상세ID + */ + private String unitySndngDetailId; + /** + * 발송처리상태 + */ + @Builder.Default + private String sndngProcessSttus = ApiConstants.SndngProcessStatus.ACCEPT.getCode(); + } +} diff --git a/mens-api/src/main/java/kr/xit/biz/ens/package-info.java b/mens-api/src/main/java/kr/xit/biz/ens/package-info.java new file mode 100644 index 0000000..1659f66 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/ens/package-info.java @@ -0,0 +1,11 @@ +/** + * ENS business packages + *

+ * 전자고지 : ens + * sms + *

+ * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.biz.ens; diff --git a/mens-api/src/main/java/kr/xit/biz/ens/service/EnsCctvFileService.java b/mens-api/src/main/java/kr/xit/biz/ens/service/EnsCctvFileService.java new file mode 100644 index 0000000..5585e98 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/ens/service/EnsCctvFileService.java @@ -0,0 +1,311 @@ +package kr.xit.biz.ens.service; + +import kr.xit.biz.ens.mapper.IEnsCctvFileMapper; +import kr.xit.biz.ens.model.EnsDTO; +import kr.xit.core.support.utils.Checks; +import kr.xit.core.support.utils.DateUtils; +import kr.xit.core.support.utils.SFTPUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static egovframework.com.cmm.util.EgovDateUtil.formatDate; +import static egovframework.com.cmm.util.EgovDateUtil.formatTime; + +/** + *
+ * description : 전자 고지 CCTV 파일 서비스
+ *
+ * packageName : kr.xit.biz.ens.service
+ * fileName    : EnsCctvFileService
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Service +public class EnsCctvFileService extends EgovAbstractServiceImpl implements IEnsCctvFileService { + + @Value("${app.ssh.host}") private String host; + @Value("${app.ssh.port}") private int port; + @Value("${app.ssh.id}") private String id; + @Value("${app.ssh.passwd}") private String passwd; + + @Value("${app.ssh.sg.ens-path}") private String ensPath; + @Value("${app.ssh.sg.rcv}") private String rcvPath; + @Value("${app.ssh.sg.backup}") private String backupPath; + @Value("${app.ssh.sg.err}") private String errPath; + + private final IEnsCctvFileMapper mapper; + + /** + * 전자 고지 cctv 단속 자료 생성(서광) + */ + @Override + @Transactional + public void createCctvFileOfSg() { + + // 전자 고지 라이선스 유효성 체크 + int licenseCnt = mapper.selectLicense("ENS"); + if(licenseCnt == 0) return; + + SFTPUtils sftp = null; + + try { + // SFTP connect + sftp = new SFTPUtils(); + sftp.init(host, port, id, passwd, StringUtils.EMPTY); + + // 서광 CCTV 전자 고지 대상 조회 + final String srcPath = ensPath + rcvPath; + ArrayList fileNameList = sftp.findFileNameList(srcPath); + //if(fileNameList.size() == 0) throw BizRuntimeException.create("사전고지[서광] 처리 대상이 없습니다"); + + // 에러 파일 목록 + ArrayList errFileList = new ArrayList<>(); + // 성공 파일 목록 + ArrayList rtnFileList = new ArrayList<>(); + for (String fn : fileNameList) { + String[] arrFi = fn.split("_"); + + // 파일 이름 정보 오류시 + if (arrFi.length != 4 || arrFi[0].length() != 14) { + errFileList.add(fn); + continue; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // FIXME :: GS 임시 소스 + // 파일 다운로드 + final String downLoadPath = "D:/ImageData/ENS" + "/" + DateUtils.getToday(StringUtils.EMPTY); + + File Folder = new File(downLoadPath); + if (!Folder.exists()) Folder.mkdir(); //폴더 생성합니다. + sftp.fileDownload(srcPath + "/" + fn, downLoadPath); + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // FIXME : 중복데이타(단속시간 + 차량번호)인 경우 데이타 skip 하도록 해야 할 듯 + // 전자 고지 연계 데이타 생성 + mapper.insertNtcnCntcData( + EnsDTO.NtcnCntcData.builder() + .regltDt(arrFi[0]) + .vhcleNo(arrFi[1]) + .sptNm(arrFi[2]) + .sptAcctoCode(arrFi[3].split("\\.")[0]) + .fileNm(fn) + .build() + ); + rtnFileList.add(fn); + + // FIXME :: sftp rm 기능 추후 제거 + // 파일 삭제 + sftp.rm(fn); + } + + // FIXME :: sftp move 기능 추후 사용 + // 처리한 데이타 backup + //fileBackup(sftp, srcPath, errFileList, rtnFileList); + + } finally { + if(sftp != null) sftp.disconnect(); + } + } + + /** + * 전자 고지 accept 연계발송 데이타 생성 + * 1. 전자 고지 연계 발송 데이타 생성 대상 조회 + * 2. 전자 고지 연계 발송 마스터 생성 + * 3. 전자 고지 연계 발송 상세 생성 + * 4. 전자 고지 연계 결과 반영 + */ + @Override + @Transactional + public void acceptEnsNtnccntcSndng() { + List tgtList = mapper.selectEnsNtncCntcSndngs(); + + if(tgtList.size() == 0) return; + + // search 전자 고지 연계발송 데이타 생성 대상 + Optional first = tgtList.stream() + .filter(dto -> "Y".equals(dto.getTgtYn())) + .findFirst(); + + // 전자 고지 연계발송 master 생성 + final String unitySndngMastrId; + final String closDt; + if(first.isPresent()){ + EnsDTO.EnsNtncCntcSndngTgts tgtDTO = first.get(); + mapper.insertCntcSndngMst(tgtDTO); + unitySndngMastrId = tgtDTO.getUnitySndngMastrId(); + closDt = tgtDTO.getClosDt(); + MDC.put("unitySndngMastrId", unitySndngMastrId); + } else { + unitySndngMastrId = null; + closDt = null; + } + + // 전자 고지 연계발송 상세 생성 및 결과 처리(전자 고지 연계 데이타 반영) + // 전자 고지 대상 -> 전자 고지 연계 발송 상세 생성 + // 전자 고지 연계 데이타 -> 연계 결과 반영 + // : 대상 : 연계대상-Y, 진행상태-Y, 통합발송상세ID + // 미대상 : 연계대상-N, 진행상태-Y, 통합발송상세ID = null + tgtList.forEach(dto -> { + dto.setProcessAt("Y"); + if("Y".equals(dto.getTgtYn())){ + dto.setUnitySndngMastrId(unitySndngMastrId); + dto.setClosDt(closDt); + dto.setMobilePageCn(jsonCn(dto)); + mapper.insertCntcSndngDtl(dto); + } + mapper.updateEnsNtcnCntcData(dto); + }); + } + + private void fileBackup(SFTPUtils sftp, String srcPath, ArrayList errFileList, ArrayList rtnFileList) { + final String errorPath = ensPath + errPath; + String destPath = ensPath + backupPath; + destPath = destPath + "/" + DateUtils.getToday(StringUtils.EMPTY); + + log.info("src path::[{}]", srcPath); + log.info("tgt path::[{}]", destPath); + + // file backup + for (String fn : rtnFileList) { + log.info("fileName::[{}]", fn); + sftp.mv(srcPath + "/" + fn, destPath + "/" + fn); + } + + // error file backup + for (String fn : errFileList) { + sftp.mv(srcPath + "/" + fn, errorPath + "/" + fn); + } + } + + private String jsonCn(EnsDTO.EnsNtncCntcSndngTgts dto){ + String jsonCn = "{" + + "\"details\": [" + + "{" + + "\"title\": \"주정차 위반 과태료 통지서\"," + + "\"item_type\": \"SUBJECT_TEXT\"," + + "\"elements\": [" + + "\"\"" + + "]" + + "}," + + "{" + + "\"title\": \"위반내역\"," + + "\"item_type\": \"KEY_VALUE\"," + + "\"properties\": {" + + "}," + + "\"elements\": [" + + "{" + + "\"key\": \"차량번호\"," + + "\"value\": \""+ Checks.checkVal(dto.getVhcleNo(),"") +"\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"성명\"," + + "\"value\": \""+ Checks.checkVal(dto.getNm(),"") +"\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"위반일시\"," + + "\"value\": \""+ Checks.checkVal(formatDate(dto.getRegltDt().substring(0,8), "-") + " " + formatTime(dto.getRegltDt().substring(8,14), ":"),"") +"\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"위반내용\"," + + "\"value\": \"주정차금지 구역\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"위반장소\"," + + "\"value\": \""+ Checks.checkVal(dto.getSptNm(),"") +"\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"납부금액\"," + + "\"value\": \"32,000원\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"단속구분\"," + + "\"value\": \"CCTV\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"자진납부 기한\"," + + "\"value\": \""+ Checks.checkVal(formatDate(dto.getClosDt().substring(0,8), "-"),"") +"\\\\n위 납부 기한이 경과 시에는 과태료 감경 혜택을 받으실 수 없습니다.\"," + + "\"level\": 1" + + "}" + + "]" + + "}," + + "{" + + "\"title\": \"과태료 부과 및 감경\"," + + "\"item_type\": \"TABLE\"," + + "\"elements\": {" + + "\"head\": [" + + "\"구분\"," + + "\"승용 감경금액\\\\n(부과금액)\"," + + "\"승합 감경금액\\\\n(부과금액)\"" + + "]," + + "\"rows\": [" + + "[" + + "\"주정차금지구역\"," + + "\"32,000원\\\\n(40,000원)\"," + + "\"40,000원\\\\n(50,000원)\"" + + "]," + + "[" + + "\"같은 장소 2시간 초과\"," + + "\"40,000원\\\\n(50,000원)\"," + + "\"48,000원\\\\n(60,000원)\"" + + "]" + + "]" + + "}" + + "}," + + "{" + + "\"title\": \"질서위반행위 규제법 시행령 규정 감경 대상\"," + + "\"item_type\": \"KEY_VALUE\"," + + "\"elements\": [" + + "{" + + "\"key\": \"감경대상자\"," + + "\"value\": \"○ 국민기초생활 수급자\\\\n○ 한부모가족 보호대상자\\\\n○ 장애의 정도가 심한 장애인\\\\n○ 국가유공자(상이등급 3급 이상)\\\\n○ 미성년자\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"시행일\"," + + "\"value\": \"2010년 1월 16일 부터\\\\n○ 적용 : 시행일 이후 단속된 차량\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"감경율(%)\"," + + "\"value\": \"과태료 부과금액의 50%\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"비고\"," + + "\"value\": \"※ 자진 납부 시 추가감경 가능\"," + + "\"level\": 1" + + "}" + + "]" + + "}" + + "]" + +"}"; + return jsonCn; + } +} diff --git a/mens-api/src/main/java/kr/xit/biz/ens/service/IEnsCctvFileService.java b/mens-api/src/main/java/kr/xit/biz/ens/service/IEnsCctvFileService.java new file mode 100644 index 0000000..f9367eb --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/ens/service/IEnsCctvFileService.java @@ -0,0 +1,28 @@ +package kr.xit.biz.ens.service; + +/** + *
+ * description : 전자 고지 파일 서비스
+ *
+ * packageName : kr.xit.biz.ens.service
+ * fileName    : IEnsCctvFileService
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +public interface IEnsCctvFileService { + /** + * 전자 고지 cctv 단속 자료 생성(서광) + */ + void createCctvFileOfSg(); + + /** + * 전자 고지 accept 연계발송 데이타 생성 + */ + void acceptEnsNtnccntcSndng(); +} diff --git a/mens-api/src/main/java/kr/xit/biz/ens/service/ISendMessageLinkService.java b/mens-api/src/main/java/kr/xit/biz/ens/service/ISendMessageLinkService.java new file mode 100644 index 0000000..1d644fe --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/ens/service/ISendMessageLinkService.java @@ -0,0 +1,41 @@ +package kr.xit.biz.ens.service; + +import java.util.List; + +import kr.xit.biz.ens.model.EnsDTO; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.ens.support.kakao.model.KkopayDocDTO; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.ens.service
+ * fileName    : ISendMessageLinkService
+ * author      : limju
+ * date        : 2023-06-05
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-05    limju       최초 생성
+ *
+ * 
+ */ +public interface ISendMessageLinkService { + + void accept(final String sndngProcessSttus); + void make(final String sndngProcessSttus); + void close(final String sndngProcessSttus); + + void sendBulks(final String sndngProcessSttus); + + void findKkoMyDocStatusBulks(final String sndngProcessSttus); + + List findKakaoSendTargets(final String sndngProcessSttus); + + + + ApiResponseDTO findKkoMyDocReadyAndMblPage(KkopayDocDTO.OneTimeToken reqDTO); + + void updateErrorLog(EnsDTO.SndngMssageParam dto); +} diff --git a/mens-api/src/main/java/kr/xit/biz/ens/service/SendMessageLinkService.java b/mens-api/src/main/java/kr/xit/biz/ens/service/SendMessageLinkService.java new file mode 100644 index 0000000..c0134f5 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/ens/service/SendMessageLinkService.java @@ -0,0 +1,882 @@ +package kr.xit.biz.ens.service; + +import java.io.*; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import egovframework.com.cmm.util.EgovDateUtil; +import egovframework.com.cmm.util.EgovStringUtil; +import kr.xit.biz.sms.service.ISmsMessageService; +import kr.xit.core.consts.ErrorCode; +import kr.xit.core.support.utils.DateUtils; +import kr.xit.ens.support.kakao.model.KkopayDocDTO; +import kr.xit.ens.support.kakao.service.IKkopayEltrcDocService; +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.lang3.StringUtils; +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.egovframe.rte.fdl.cryptography.EgovPasswordEncoder; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import kr.xit.biz.ens.mapper.ISendMessageLinkMapper; +import kr.xit.biz.ens.model.CntcDTO; +import kr.xit.biz.ens.model.EnsDTO; +import kr.xit.core.exception.BizRuntimeException; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.core.support.utils.Checks; +import kr.xit.ens.support.common.ApiConstants; +import kr.xit.ens.support.kakao.model.KkopayDocAttrDTO; +import kr.xit.ens.support.kakao.model.KkopayDocBulkDTO; +import kr.xit.ens.support.kakao.service.IAsyncKkopayEltrcDocService; +import lombok.RequiredArgsConstructor; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; + +/** + *
+ * description : 전자고지 통합발송 연계 서비스(배치)
+ *
+ * packageName : kr.xit.biz.ens.service
+ * fileName    : SendMessageLinkService
+ * author      : limju
+ * date        : 2023-06-05
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-05    limju       최초 생성
+ *
+ * 
+ */ +@RequiredArgsConstructor +@Service +public class SendMessageLinkService extends EgovAbstractServiceImpl implements ISendMessageLinkService { + private final ISendMessageLinkMapper mapper; + private final EgovPasswordEncoder encryptor; + private final IAsyncKkopayEltrcDocService asyncService; + private final IKkopayEltrcDocService service; + private final ISmsMessageService smsService; + + private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + + @Value("${contract.provider.kakao.isAsync:false}") + private boolean isAsync; + + @Value("${contract.provider.kakao.bulk-max-cnt}") + private int bulkMaxCnt; + + @Value("${file.cmm.upload.root}") + private String fileRoot; + + @Value("${file.cmm.upload.post}") + private String filePost; + + private static final String KKO_MY_DOC = "KKO-MY-DOC"; + private static final String E_GREEN = "E-GREEN"; + private static final String SMS = "SMS"; + private static final String SNDNG_PROCESS_STTUS = "sndngProcessSttus"; + private static final String UNITY_SNDNG_MST_ID = "unitySndngMastrId"; + private static final String YMDHMS = "yyyyMMddHHmmss"; + + //----------------------------------------------------------------------------------------------------------------- + // accept + //----------------------------------------------------------------------------------------------------------------- + /** + * + * @param sndngProcessSttus + */ + @Override + @Transactional + public void accept(final String sndngProcessSttus) { + final List list = mapper.selectAcceptTgts(sndngProcessSttus); + String sndngDt = null; + + for(EnsDTO.SndngMssageParam dto : list) { + final List mstIdList = new ArrayList<>(); + String mstId = ""; + if(!Objects.equals(mstId, dto.getUnitySndngMastrId())){ + mstId = dto.getUnitySndngMastrId(); + mstIdList.add(mstId); + MDC.put(UNITY_SNDNG_MST_ID, mstId); + } + + // validation check + final List valiList = mapper.selectAcceptVali(dto); + try { + validatedAccept(valiList); + }catch (Exception e){ + updateAcceptCntcSndngMstFailStatus(mstIdList, "accept 생성 요청 실패(파라메터 오류) : " + EgovStringUtil.cutString(e.getMessage(), 300)); + continue; + } + + // 초기 1회 셋팅 (발송 일시2, 발송 일시3 : 템플릿 관리 try2_minute, try3_minute 값에 따라 처리) + if(sndngDt == null) { + sndngDt = StringUtils.defaultString(dto.getSndngDt()); + if(dto.getTry2() != null && !"".equals(sndngDt)) dto.setSndngDt2(EgovDateUtil.addMinute(sndngDt,Integer.parseInt(dto.getTry2Minute()))); + if(dto.getTry3() != null && !"".equals(sndngDt)) dto.setSndngDt3(EgovDateUtil.addMinute(sndngDt,Integer.parseInt(dto.getTry3Minute()))); + } + + dto.setSndngProcessSttus(sndngProcessSttus); + dto.setNewSndngProcessSttus(ApiConstants.SndngProcessStatus.ACCETP_OK.getCode()); + if(mapper.insertUnitySndngMst(dto) != 1) throw BizRuntimeException.create("접수 데이타 생성(마스터) 실패"); + int insCnt = mapper.insertUnitySndngDtls(dto); + if(insCnt != dto.getSndngCo()) throw BizRuntimeException.create(String.format("접수 상세 데이타 생성 실패-발송건수[%d]와 생성건수[%d] 불일치", dto.getSndngCo(), insCnt)); + if(mapper.updateProcessSttusCntcSndngMst(dto) != 1) throw BizRuntimeException.create("접수 데이타 생성(통합발송마스터 상태변경) 실패"); + } + } + + /** + * accept 파라메터 유효성 체크 + * @param valiList List + */ + private void validatedAccept(List valiList){ + List errors = new ArrayList<>(); + int idx = 0; + + List> arrList = ListUtils.partition(valiList, bulkMaxCnt); + for(List list: arrList) { + for (EnsDTO.sndngVali dto : list) { + Set> errList = validator.validate(dto); + + if (list.size() > 0) { + int finalIdx = idx; + errors.addAll(errList.stream() + .map(row -> String.format("%s[%d]=%s", row.getPropertyPath(), finalIdx + 1, row.getMessageTemplate())) + .collect(Collectors.toList()) + ); + } + + // 발송일시보다 마감일시가 빠르면 안되고, 마감일시가 현재 시간보다 빠르면 오류 + if(!Checks.isEmpty(dto.getSndngDt()) && !Checks.isEmpty(dto.getClosDt())){ + if(!DateUtils.isBeforeLocalDateTime(dto.getSndngDt(),dto.getClosDt(),YMDHMS)) + errors.add(String.format("발송일시보다 마감일시가 빠를 수 없습니다(dto.getSndngDt[%d] 번째 오류)", idx+1)); + if(!DateUtils.isBeforeLocalDateTime(DateUtils.getTodayAndNowTime(YMDHMS),dto.getClosDt(),YMDHMS)) + errors.add(String.format("현재 시간보다 마감일시가 빠를 수 없습니다(dto.getSndngDt[%d] 번째 오류)", idx+1)); + } + + String[] tryVal = {Checks.checkVal(dto.getTry1(),""), Checks.checkVal(dto.getTry2(),""), Checks.checkVal(dto.getTry3(),"")}; + switch (tryVal[dto.getTrySeq()-1]){ + case KKO_MY_DOC: + if(Checks.isEmpty(dto.getIhidnum())) errors.add(String.format("주민등록번호는 필수입니다(dto.getIhidnum[%d] 번째 오류)", idx+1)); + if(Checks.isEmpty(dto.getMoblphonNo())) errors.add(String.format("핸드폰 번호는 필수입니다(dto.getMoblphonNo[%d] 번째 오류)", idx+1)); + if(Checks.isEmpty(dto.getIhidnum())) errors.add(String.format("받는이 이름은 필수입니다(dto.getIhidnum[%d] 번째 오류)", idx+1)); + break; + + case E_GREEN: + if(Checks.isEmpty(dto.getAdres())) errors.add(String.format("주소는 필수입니다(dto.getAdres[%d] 번째 오류)", idx+1)); + if(Checks.isEmpty(dto.getDetailAdres())) errors.add(String.format("상세 주소는 필수입니다(dto.getDetailAdres[%d] 번째 오류)", idx+1)); + if(Checks.isEmpty(dto.getZip())) errors.add(String.format("우편번호는 필수입니다(dto.getZip[%d] 번째 오류)", idx+1)); + break; + + case SMS: + if(Checks.isEmpty(dto.getMoblphonNo())) errors.add(String.format("핸드폰 번호는 필수입니다(dto.getMoblphonNo[%d] 번째 오류)", idx+1)); + break; + + default: + break; + } + idx++; + } + } + + if(errors.size() > 0) { + throw BizRuntimeException.create(errors.toString()); + } + } + + /** + * accpet 파라메처 체크오류시 마스터 상태 변경 - 실패 + * @param mstIdList + * @param stsErrMsg + */ + private void updateAcceptCntcSndngMstFailStatus(final List mstIdList, final String stsErrMsg) { + + for(String id : mstIdList) { + EnsDTO.SndngMssageParam paramDTO = EnsDTO.SndngMssageParam.builder() + .unitySndngMastrId(id) + .newSndngProcessSttus("accept-fail") + .errorMssage(stsErrMsg) + .build(); + + if(mapper.updateProcessSttusCntcSndngMst(paramDTO) != 1) throw BizRuntimeException.create("[accept]연계 발송 마스터 상태변경 실패"); + } + } + //----------------------------------------------------------------------------------------------------------------- + // accept + //----------------------------------------------------------------------------------------------------------------- + + //----------------------------------------------------------------------------------------------------------------- + // make + //----------------------------------------------------------------------------------------------------------------- + /** + * tryCnt 값에 따라 분기 - 1:카카오 + * @param sndngProcessSttus + */ + @Override + @Transactional + public void make(final String sndngProcessSttus) { + final String sndngProcessSttus2 = ApiConstants.SndngProcessStatus.SENDING1.getCode(); + final String sndngProcessSttus3 = ApiConstants.SndngProcessStatus.SENDING2.getCode(); + final List list = mapper.selectMakeTgts(sndngProcessSttus); + final List list2 = mapper.selectMakeTgts(sndngProcessSttus2); + + for(EnsDTO.SndngMssageParam dto : list) { + MDC.put(UNITY_SNDNG_MST_ID, dto.getUnitySndngMastrId()); + MDC.put(SNDNG_PROCESS_STTUS, "make-fail1"); + + dto.setSndngProcessSttus(sndngProcessSttus); + dto.setNewSndngProcessSttus(ApiConstants.SndngProcessStatus.MAKE_OK.getCode()); + //FIXME: try1,try2,tr3이 있는 경우 try1 이전에 try2,3이 실행될 수 있는 경우??? + dto.setTrySeq(1); + dto.setSndngSeCode(dto.getTry1()); + + makeMstStatusUpdate(dto); + } + + for(EnsDTO.SndngMssageParam dto : list2) { + MDC.put(UNITY_SNDNG_MST_ID, dto.getUnitySndngMastrId()); + MDC.put(SNDNG_PROCESS_STTUS, "make-fail2"); + + //try2 발송 대상이 있는지 확인 + final int sendCnt = mapper.selectSendOkTgts(dto); + + //없으면 연계 발송 마스터, 통합 발송 마스터 send-ok + if(sendCnt == 0) { + dto.setNewSndngProcessSttus(ApiConstants.SndngProcessStatus.SEND_OK.getCode()); + + if(mapper.updateProcessSttusCntcSndngMst(dto) != 1) throw BizRuntimeException.create("[make]연계 발송 마스터 상태변경 실패"); + if(mapper.updateProcessSttusUnitySndngMst(dto) != 1) throw BizRuntimeException.create("[make]통합 발송 마스터 상태변경 실패"); + } else { + //있으면 make + dto.setSndngProcessSttus(sndngProcessSttus2); + dto.setNewSndngProcessSttus(ApiConstants.SndngProcessStatus.MAKE_OK.getCode()); + dto.setTrySeq(2); + dto.setSndngCo(sendCnt); + dto.setSndngSeCode(dto.getTry2()); + + makeMstStatusUpdate(dto); + } + } + + //TODO:: GS 인증 시험에서 카카오, E-GREEN만 있어서 3단계는 필요 시 구현 + } + + /** + * make 데이타 생성 + * 1. master + * 2. detail + * 2. master 상태 변경 + * @param dto + */ + private void makeMstStatusUpdate(EnsDTO.SndngMssageParam dto) { + // 1. master + if(mapper.insertSndngMst(dto) != 1) throw BizRuntimeException.create("[make]데이타 생성(마스터) 실패"); + + // 2. detail + int insCnt = insertEnsDetailByTry(dto); + if(insCnt != dto.getSndngCo()) throw BizRuntimeException.create(String.format("[make]상세 데이타 생성 실패-발송건수[%d]와 생성건수[%d] 불일치", dto.getSndngCo(), insCnt)); + + // 3. status + if(mapper.updateProcessSttusCntcSndngMst(dto) != 1) throw BizRuntimeException.create("[make]데이타 생성(발송마스터 상태변경) 실패"); + if(mapper.updateProcessSttusUnitySndngMst(dto) != 1) throw BizRuntimeException.create("[make]데이타 생성(통합발송마스터 상태변경) 실패"); + } + + /** + * make 데이타 생성 - 2. detail + * @param dto + * @return + */ + private int insertEnsDetailByTry(final EnsDTO.SndngMssageParam dto){ + String[] tryVal = {Checks.checkVal(dto.getTry1(),""), Checks.checkVal(dto.getTry2(),""), Checks.checkVal(dto.getTry3(),"")}; + switch (tryVal[dto.getTrySeq()-1]){ + case KKO_MY_DOC: + return mapper.insertKakaoMyDocs(dto); + + case E_GREEN: + return mapper.insertPostSndng(dto); + + case SMS: + return mapper.insertSmsSndng(dto); + + default: + return 1; + } + } + //----------------------------------------------------------------------------------------------------------------- + // make + //----------------------------------------------------------------------------------------------------------------- + + //----------------------------------------------------------------------------------------------------------------- + // send + //----------------------------------------------------------------------------------------------------------------- + /** + * 전자고지(문서) send - 업무에 따라 카카오 | E-GREEN | SMS 발송 + * 1. 발송/통합발송 마스터에서 대상 조회 + * 2. send(발송) + * @param sndngProcessSttus String + */ + @Override + @Transactional + public void sendBulks(final String sndngProcessSttus) { + + // 발송/통합발송 마스터에서 대상 조회 + final List tgtList = mapper.selectSendBulkTgts(sndngProcessSttus); + for(EnsDTO.SndngMssageParam tgtDTO : tgtList){ + MDC.put(UNITY_SNDNG_MST_ID, tgtDTO.getUnitySndngMastrId()); + MDC.put("sndngMastrId", tgtDTO.getSndngMastrId()); + MDC.put(SNDNG_PROCESS_STTUS, "send-fail" + tgtDTO.getTrySeq()); + + String[] tryVal = {Checks.checkVal(tgtDTO.getTry1(),""), Checks.checkVal(tgtDTO.getTry2(),""), Checks.checkVal(tgtDTO.getTry3(),"")}; + + // 마스터 상태 변경값을 파라메터에서 받은 상태값으로 set + tgtDTO.setNewSndngProcessSttus(sndngProcessSttus); + + // 업무 문서 구분에 따른 분기 + switch (tryVal[tgtDTO.getTrySeq() -1]){ + // 카카오 + case KKO_MY_DOC: + sendBulkKakaoMyDocs(tgtDTO); + break; + + // E-GREEN + case E_GREEN: + sendEgreen(tgtDTO); + break; + + // SMS + case SMS: + sendSms(tgtDTO); + break; + + default: + break; + } + } + } + + /** + * 카카오페이 전자문서 발송요청 처리 + * @param tgtDTO EnsDTO.SndngMssageParam + */ + @Transactional + public void sendBulkKakaoMyDocs(final EnsDTO.SndngMssageParam tgtDTO) { + final List list = mapper.selectKakaoSendTgts(tgtDTO); + final List bulkList = new ArrayList<>(); + final List mstIdList = new ArrayList<>(); + + setKkoMyDocSendBulks(list, bulkList, mstIdList); + + // validation check + try { + validatedKkoMyDocSendBulks(bulkList); + }catch (Exception e){ + updateKkoMyDocSndngMstFailStatus(mstIdList, "[send]카카오 문서 발송(bulks)요청 실패(파라메터 오류)"); + throw e; + } + + List> apiResults; + List> partitions = ListUtils.partition(bulkList, bulkMaxCnt); + + if(isAsync) { + // 카카오페이 전자문서발송 벌크 최대수 단위로 분할하여 실행 + List>> apiAsyncResults = partitions.stream() + .map(bulkSendList -> + asyncService.requestSendBulk( + KkopayDocBulkDTO.BulkSendRequests.builder() + .documents(bulkSendList) + .build()) + ) + .collect(Collectors.toList()); + + // GET API 호출 결과 + apiResults = CompletableFuture.allOf(apiAsyncResults.toArray(new CompletableFuture[0])) + .thenApply(Void -> apiAsyncResults.stream() + .map(CompletableFuture::join) + .collect(Collectors.toList())) + .join(); + + }else{ + apiResults = partitions.stream() + .map(bulkSendList -> + service.requestSendBulk( + KkopayDocBulkDTO.BulkSendRequests.builder() + .documents(bulkSendList) + .build()) + ) + .collect(Collectors.toList()); + } + // GET 결과 목록 + List resList = apiResults.stream() + .map(ApiResponseDTO::getData) + .collect(Collectors.toList()); + + // 카카오 문서 요청 결과 반영 + saveKkoMyDocResult(mstIdList, tgtDTO.getUnitySndngMastrId(), resList); + } + + /** + * 카카오문서 요청 결과 반영 + * @param mstIdList List 발송마스터 ID 목록 + * @param resList List 카카오내문서함 발송요청 결과 목록 + * @param unitySndMstId String 통합발송 마스터 ID + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void saveKkoMyDocResult(final List mstIdList, String unitySndMstId, final List resList) { + // 결과 반영 + resList.forEach(o -> + o.getDocuments().forEach( + t -> { + // 카카오페이 문서요청 결과 반영 + mapper.updateKakaoSendBulksResult(t); + // 모바일 페이지 컨텐트 생성 + if(Checks.isNotEmpty(t.getDocument_binder_uuid())){ + //tgtDTO.setUnitySndngDetailId(t.getExternal_document_uuid()); + mapper.insertMobilePageManage(t.getExternal_document_uuid()); + } + // 연계발송결과 생성 + insertKkoMyDocCntcSndngResult(ApiConstants.SndngSeCode.KAKAO_MY_DOC.getCode(), t.getExternal_document_uuid(), t.getError_message()); + }) + ); + + // 마스터 상태 변경 + updateKkoMyDocSendSndngMstStatus(mstIdList, unitySndMstId, "카카오 문서 발송요청 실패(발송마스터 데이타 오류)"); + } + + /** + * GET 카카오 문서발송요청 파라메터 목록 + * @param list List 문서발송요청 대상 목록 + * @param bulkList List 문서발송요청 파라메터 목록 + * @param mstIdList List 문서발송요청 대상 마스터ID 목록 + */ + private static void setKkoMyDocSendBulks(List list, List bulkList, List mstIdList) { + String mstId = null; + + for(EnsDTO.SendKakaoTgt sendTgtDTO : list){ + if(!Objects.equals(mstId, sendTgtDTO.getSndngMastrId())){ + mstId = sendTgtDTO.getSndngMastrId(); + mstIdList.add(mstId); + } + KkopayDocAttrDTO.Receiver receiver = KkopayDocAttrDTO.Receiver.builder() + .phone_number(sendTgtDTO.getRecvPhoneNumber()) + .name(sendTgtDTO.getRecvName()) + .birthday(sendTgtDTO.getRecvBirthday()) + .is_required_verify_name(false) + .build(); + + KkopayDocBulkDTO.PropertyBulk property = KkopayDocBulkDTO.PropertyBulk.builder() + .link(sendTgtDTO.getPropLink()) + .payload(sendTgtDTO.getPropPayload()) + .message(sendTgtDTO.getPropMessage()) + .cs_name(sendTgtDTO.getPropCsName()) + .cs_number(sendTgtDTO.getPropCsNumber()) + .external_document_uuid(sendTgtDTO.getUnitySndngDetailId()) + .build(); + + KkopayDocBulkDTO.BulkSendReq bulkReqDTO = KkopayDocBulkDTO.BulkSendReq.builder() + .title(sendTgtDTO.getTitle()) + .common_categories(Collections.singletonList(ApiConstants.Categories.NOTICE)) + .read_expired_at(sendTgtDTO.getClosDt()) + // + .hash(sendTgtDTO.getHash()) + //.hash(encryptor.encryptPassword(sendTgtDTO.getUnitySndngDetailId())) + .receiver(receiver) + .property(property) + .build(); + + bulkList.add(bulkReqDTO); + } + } + + /** + * 카카오문서 발송요청 파라메터 유효성 체크 + * @param bulkList List 카카오내문서함 발송요청 파라메터 목록 + */ + private void validatedKkoMyDocSendBulks(List bulkList){ + List errors = new ArrayList<>(); + int idx = 0; + + List> arrList = ListUtils.partition(bulkList, bulkMaxCnt); + for(List list: arrList) { + for(KkopayDocBulkDTO.BulkSendReq dto: list) { + Set> errList = validator.validate(dto); + + if (list.size() > 0) { + + int finalIdx = idx; + errors.addAll(errList.stream() + .map(row -> String.format("%s[%d]=%s", row.getPropertyPath(), finalIdx + 1, row.getMessageTemplate())) + .collect(Collectors.toList()) + ); + } + + + if(dto.getRead_expired_at() != null && dto.getRead_expired_sec() != null){ + errors.add("처리마감시간(절대시간 또는 상대시간)은 하나만 지정해야 합니다."); + } + if(dto.getRead_expired_at() == null && dto.getRead_expired_sec() == null){ + errors.add("처리마감시간(절대시간 또는 상대시간)을 지정해야 합니다."); + } + + KkopayDocAttrDTO.Receiver receiver = dto.getReceiver(); + if (Checks.isEmpty(receiver.getCi())) { + if (Checks.isEmpty(receiver.getName())) errors.add(String.format("받는이 이름은 필수입니다(receiver.name[%d] 번째 오류)", idx+1)); + if (Checks.isEmpty(receiver.getPhone_number())) errors.add(String.format("받는이 전화번호는 필수입니다(receiver.phone_number[%d] 번째 오류)", idx+1)); + if (Checks.isEmpty(receiver.getBirthday())) errors.add(String.format("받는이 생년월일은 필수입니다(receiver.birthday[%d] 번째 오류)", idx+1)); + } else { + StringBuilder sb = new StringBuilder() + .append(StringUtils.defaultString(receiver.getName(), StringUtils.EMPTY)) + .append(StringUtils.defaultString(receiver.getPhone_number(), StringUtils.EMPTY)) + .append(StringUtils.defaultString(receiver.getBirthday(), StringUtils.EMPTY)); + + if(Checks.isNotEmpty(sb.toString())){ + errors.add(String.format("CI가 지정 되었습니다(받는이 정보 불필요:[%d] 번째 오류) .", idx+1)); + } + } + idx++; + } + } + + if(errors.size() > 0){ + throw BizRuntimeException.create(errors.toString()); + } + } + + /** + * E-GREEN 우편 발송요청 파일 처리 + * @param tgtDTO EnsDTO.SndngMssageParam + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void sendEgreen(final EnsDTO.SndngMssageParam tgtDTO) { + final List list = mapper.selectPostTgts(tgtDTO); + final String filePath = fileRoot+filePost; + final String fileName = list.get(0).getConKey()+".txt"; + + if(!egreenFileWrite(filePath, fileName, list)) { + throw BizRuntimeException.create("우편 파일 생성 실패"); + } + + EnsDTO.SndngMssageParam paramDTO = EnsDTO.SndngMssageParam.builder() + .unitySndngMastrId(list.get(0).getUnitySndngMastrId()) + .sndngMastrId(list.get(0).getSndngMastrId()) + .newSndngProcessSttus(list.get(0).getSndngProcessSttus()) + .build(); + updateStepSendMststatus(paramDTO, "E-GREEN"); + } + + /** + * E-GREEN 발송 처리 - 파일 생성 + * @param filePath String + * @param fileName String + * @param list List + * @return boolean + */ + private boolean egreenFileWrite (final String filePath, final String fileName, final List list){ + try { + File folder = new File(filePath); + if(!folder.exists()) //noinspection ResultOfMethodCallIgnored + folder.mkdirs(); + + File file = new File(filePath+fileName); + if(!file.exists()) { + if(!file.createNewFile()){ + return false; + }; + } + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file, true))) { + + for (EnsDTO.PostSndng postSndng : list) { + writer.write(StringUtils.defaultString(postSndng.getConKey()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getSenderNm()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getSenderZipNo()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getSenderAddr()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getSenderDetailAddr()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getReceiverSendNo()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getReceiverNm()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getReceiverZipNo()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getReceiverAddr()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getReceiverDetailAddr()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getSschnge1()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getSschnge2()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getSschnge3())); + writer.newLine(); + } + writer.flush(); + } + }catch (IOException ie){ + return false; + } + return true; + } + + /** + * SMS 전송 처리 + * - xit SMS : 별도 서비스 call + * @param tgtDTO EnsDTO.SndngMssageParam + * @see kr.xit.biz.sms.service.SmsMessageService#sendSmsList(List) + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void sendSms(final EnsDTO.SndngMssageParam tgtDTO) { + final List list = mapper.selectSmsSendTgts(tgtDTO); + + // Orcale DB - 서비스 분리 + smsService.sendSmsList(list); + + EnsDTO.SndngMssageParam paramDTO = EnsDTO.SndngMssageParam.builder() + .unitySndngMastrId(list.get(0).getUnitySndngMastrId()) + .sndngMastrId(list.get(0).getSndngMastrId()) + .newSndngProcessSttus(list.get(0).getSndngProcessSttus()) + .build(); + updateStepSendMststatus(paramDTO, "SMS"); + } + + /** + * 문서 전송후 마스터 상태 변경 + * @param paramDTO EnsDTO.SndngMssageParam + * @param workCfg String + */ + @Transactional + public void updateStepSendMststatus(final EnsDTO.SndngMssageParam paramDTO, final String workCfg) { + if(mapper.updateProcessSttusCntcSndngMst(paramDTO) != 1) throw BizRuntimeException.create(String.format("[send-%s]연계 발송 마스터 상태변경 실패", workCfg)); + if(mapper.updateProcessSttusUnitySndngMst(paramDTO) != 1) throw BizRuntimeException.create(String.format("[send-%s]통합 발송 마스터 상태변경 실패", workCfg)); + if(mapper.updateProcessSttusSndngMst(paramDTO) != 1) throw BizRuntimeException.create(String.format("[send-%s]발송 마스터 상태변경 실패", workCfg)); + } + + /** + * 카카오 내문서함 문서발송요청 연계결과(tb_cntc_sndng_result) 생성 + * @param sndngSeCode 발송구분코드 - KKO-MY-DOC|E-GREEN|SMS + * @param unitySndngDetailId 통합 발송 상세 ID + * @param errMessage 에러메세지(API호출 결과 에러 메세지) + */ + @Transactional + public void insertKkoMyDocCntcSndngResult(final String sndngSeCode, final String unitySndngDetailId, final String errMessage) { + String rsltSts = StringUtils.defaultString(errMessage, ApiConstants.DocBoxStatus.SENT.getCode()); + mapper.insertCntcSndngResult(CntcDTO.SndngResult.builder() + .unitySndngDetailId(unitySndngDetailId) + .sndngSeCode(sndngSeCode) + .sndngResultSttus(rsltSts.equals(ApiConstants.DocBoxStatus.SENT.getCode())? "SENT" : "FAIL") + .errorCn(errMessage) + .build()); + } + + /** + * 카카오 내문서함 발송요청후 연계발송마스터 / 통합발송마스터 / 발송마스터 상태 변경 + * send-ok|sending1|sending2 + * @param mstIdList 발송마스터 ID 목록 + * @param unitySndMstId String 통합발송 마스터 ID + * @param stsErrMsg + */ + @Transactional + public void updateKkoMyDocSendSndngMstStatus(final List mstIdList, final String unitySndMstId, final String stsErrMsg) { + + for(String id : mstIdList) { + EnsDTO.SndngMssageParam dto = mapper.selectSndProcessStatus(id).orElseThrow(() -> BizRuntimeException.create(stsErrMsg)); + EnsDTO.SndngMssageParam paramDTO = EnsDTO.SndngMssageParam.builder() + .sndngMastrId(id) + .unitySndngMastrId(unitySndMstId) + .newSndngProcessSttus(dto.getNewSndngProcessSttus()) + .build(); + + if(mapper.updateProcessSttusCntcSndngMst(paramDTO) != 1) throw BizRuntimeException.create("[send-카카오]연계 발송 마스터 상태변경 실패"); + if(mapper.updateProcessSttusUnitySndngMst(paramDTO) != 1) throw BizRuntimeException.create("[send-카카오]통합 발송 마스터 상태변경 실패"); + if(mapper.updateProcessSttusSndngMst(paramDTO) != 1) throw BizRuntimeException.create("[send-카카오]발송 마스터 상태변경 실패"); + } + } + + /** + * 카카오 문서 발송요청 파라메터 체크 오류 반영 - 발송 관련 마스터 테이블 + * @param mstIdList List + * @param stsErrMsg String + */ + @Transactional + public void updateKkoMyDocSndngMstFailStatus(final List mstIdList, final String stsErrMsg) { + + for(String id : mstIdList) { + EnsDTO.SndngMssageParam dto = mapper.selectSndProcessStatus(id).orElseThrow(() -> BizRuntimeException.create(stsErrMsg)); + + String newSndngProcessSttus; + if(dto.getTrySeq() == 2) newSndngProcessSttus = "send-fail2"; + else if(dto.getTrySeq() == 3) newSndngProcessSttus = "send-fail3"; + else newSndngProcessSttus = "send-fail1"; + + EnsDTO.SndngMssageParam paramDTO = EnsDTO.SndngMssageParam.builder() + .unitySndngMastrId(dto.getUnitySndngMastrId()) + .sndngMastrId(dto.getSndngMastrId()) + .newSndngProcessSttus(newSndngProcessSttus) + .build(); + + if(mapper.updateProcessSttusCntcSndngMst(paramDTO) != 1) throw BizRuntimeException.create("[send-카카오]연계 발송 마스터 상태변경 실패"); + if(mapper.updateProcessSttusUnitySndngMst(paramDTO) != 1) throw BizRuntimeException.create("[send-카카오]통합 발송 마스터 상태변경 실패"); + if(mapper.updateProcessSttusSndngMst(paramDTO) != 1) throw BizRuntimeException.create("[send-카카오]발송 마스터 상태변경 실패"); + } + } + //----------------------------------------------------------------------------------------------------------------- + // send + //----------------------------------------------------------------------------------------------------------------- + + + //----------------------------------------------------------------------------------------------------------------- + // status + //----------------------------------------------------------------------------------------------------------------- + /** + * 카카오 내문서함 상태 조회(bulks) + * @param sndngProcessSttus + * @return + */ + @Override + @Transactional + public void findKkoMyDocStatusBulks(final String sndngProcessSttus) { + final List docsBinderUuids = mapper.selectKakaoStatusTgts(sndngProcessSttus); + + List> apiResults; + List> partitions = ListUtils.partition(docsBinderUuids, bulkMaxCnt); + + if(isAsync) { + List>> apiAsyncResults = partitions.stream() + .map(uuids -> + asyncService.findBulkStatus( + KkopayDocBulkDTO.BulkStatusRequests.builder() + .document_binder_uuids(uuids) + .build()) + ) + .collect(Collectors.toList()); + + apiResults = CompletableFuture.allOf(apiAsyncResults.toArray(new CompletableFuture[0])) + .thenApply(Void -> + apiAsyncResults.stream() + .map(CompletableFuture::join) + .collect(Collectors.toList())) + .join(); + }else{ + apiResults = partitions.stream() + .map(uuids -> + service.findBulkStatus( + KkopayDocBulkDTO.BulkStatusRequests.builder() + .document_binder_uuids(uuids) + .build()) + ).collect(Collectors.toList()); + } + + List resList = apiResults.stream() + .map(ApiResponseDTO::getData) + .collect(Collectors.toList()); + + // 결과 반영 + resList.forEach(o -> + o.getDocuments().forEach(t -> { + mapper.updateKakaoStatusInfo(t); + mapper.updateCntcSndngResult(CntcDTO.SndngResult.builder() + .documentBinderUuid(t.getDocument_binder_uuid()) + .sndngResultSttus(StringUtils.defaultString(String.valueOf(t.getStatus_data().getDoc_box_status()), "FAIL")) + .requstDt(Checks.isEmpty(t.getStatus_data().getDoc_box_sent_at())? null: t.getStatus_data().getDoc_box_sent_at().toString()) + .inqireDt(Checks.isEmpty(t.getStatus_data().getDoc_box_received_at())? null: t.getStatus_data().getDoc_box_received_at().toString()) + .readngDt(Checks.isEmpty(t.getStatus_data().getDoc_box_read_at())? null: t.getStatus_data().getDoc_box_read_at().toString()) + .errorCn(t.getError_message()) + .build()); + }) + ); + } + //----------------------------------------------------------------------------------------------------------------- + // status + //----------------------------------------------------------------------------------------------------------------- + + + //----------------------------------------------------------------------------------------------------------------- + // close + //----------------------------------------------------------------------------------------------------------------- + /** + * send-ok 값에 따라 close + * @param sndngProcessSttus + */ + @Override + @Transactional + public void close(final String sndngProcessSttus) { + final List list = mapper.selectCloseTgts(sndngProcessSttus); + + // 연계발송 마스터(tb_cntc_sndng_mastr) 상태 변경 : send-ok -> close + for(String id : list) { + EnsDTO.SndngMssageParam paramDTO = EnsDTO.SndngMssageParam.builder() + .unitySndngMastrId(id) + .sndngProcessSttus(sndngProcessSttus) + .newSndngProcessSttus(ApiConstants.SndngProcessStatus.CLOSE.getCode()) + .build(); + + if(mapper.updateProcessSttusCntcSndngMst(paramDTO) != 1) throw BizRuntimeException.create("[close]연계 발송 마스터 상태변경 실패"); + if(mapper.updateProcessSttusUnitySndngMst(paramDTO) != 1) throw BizRuntimeException.create("[close]통합 발송 마스터 상태변경 실패"); + // 2차 이상 발송 시 발송 마스터가 1개 이상 존재 + if(mapper.updateProcessSttusBulkSndngMst(paramDTO) < 1) throw BizRuntimeException.create("[close]발송 마스터 상태변경 실패"); + } + } + //----------------------------------------------------------------------------------------------------------------- + // close + //----------------------------------------------------------------------------------------------------------------- + + /** + *
+     * 카카오 내문서함 모바일 페이지 컨탠트 요청
+     * - 대상 : 문서발송요청(bulks-배치처리)을 통해 document_binder_uuid를 발급 받은 데이타
+     * - 모바일의 redirect url을 통해 들어온 요청 처리
+     *   -> 서버에서 해당 문서에 매핑한 모바일 페이지 내용을 앱에 전달
+     *   -> 카카오페이 > 내문서함 > 문서 클릭시 실행
+     *
+     * 처리 내용
+     * 1. 토큰유효성 검증(redirect url 접속 허용/불허
+     * 2. 문서상태 변경
+     *
+     * @param reqDTO KkopayDocDTO.OneTimeToken
+     * 
+ */ + @Override + public ApiResponseDTO findKkoMyDocReadyAndMblPage(KkopayDocDTO.OneTimeToken reqDTO) { + + if (Checks.isEmpty(reqDTO.getToken()) || Checks.isEmpty(reqDTO.getDocument_binder_uuid()) || Checks.isEmpty(reqDTO.getExternal_document_uuid())) + throw BizRuntimeException.create(String.valueOf(ErrorCode.BAD_REQUEST.getHttpStatus().value()), "정상적인 요청이 아닙니다. 재인증 후 시도하시기 바랍니다."); + // document_binder_uuid와 external_document_uuid로 데이타 검증 + EnsDTO.MobilePageManage mobilePageManage = mapper.selectMobilePage(reqDTO); + if(mobilePageManage == null) throw BizRuntimeException.create("데이타 오류[내문서함 문서가 없습니다]"); + + ApiResponseDTO res = service.findMyDocReadyAndMblPage(reqDTO); + if(!res.isSuccess()){ + return res; + } + return ApiResponseDTO.success(mobilePageManage.getMobilePageCn()); + } + + /** + * 에러 발생시 마스터 테이블 처리 + * @param dto EnsDTO.SndngMssageParam + * @see kr.xit.ens.support.batch.task.cmm.TaskCmmUtils#taskEnsMessageLinkServiceUpdateErrorLog(String, String, String) + */ + public void updateErrorLog(EnsDTO.SndngMssageParam dto) { + if ("SndngAcceptJob".equals(dto.getErrorCode())) { + mapper.updateProcessSttusCntcSndngMst(dto); + } else if ("SndngMakeJob".equals(dto.getErrorCode())){ + mapper.updateProcessSttusCntcSndngMst(dto); + mapper.updateProcessSttusUnitySndngMst(dto); + } else if ("SndngSendBulksJob".equals(dto.getErrorCode())) { + mapper.updateProcessSttusCntcSndngMst(dto); + mapper.updateProcessSttusUnitySndngMst(dto); + mapper.updateProcessSttusSndngMst(dto); + } + } + + + /** + * 카카오 내문서함 발송요청 대상 조회(bulks) + * - + * @param sndngProcessSttus + * @return + * + */ + @Override + public List findKakaoSendTargets(final String sndngProcessSttus) { + return mapper.selectKakaoSendTgts(sndngProcessSttus); + } + + +} diff --git a/mens-api/src/main/java/kr/xit/biz/ens/web/EnsCctvFileController.java b/mens-api/src/main/java/kr/xit/biz/ens/web/EnsCctvFileController.java new file mode 100644 index 0000000..685bee4 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/ens/web/EnsCctvFileController.java @@ -0,0 +1,49 @@ +package kr.xit.biz.ens.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.xit.biz.ens.service.IEnsCctvFileService; +import kr.xit.biz.pni.service.IPniCctvFileService; +import kr.xit.core.model.ApiResponseDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.pni.web
+ * fileName    : PniCctvFileController
+ * author      : limju
+ * date        : 2023-07-07
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-07    limju       최초 생성
+ *
+ * 
+ */ +@Tag(name = " EnsCctvFileController", description = "전자고지 CCTV 파일 연계 서비스") +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/ens/v1") +public class EnsCctvFileController { + private final IEnsCctvFileService service; + + @Operation(summary = "전자고지 파일 연계 데이타 생성(서광)", description = "전자고지 파일 연계 데이타 생성(서광)") + @PostMapping(value = "/createCctvFileOfSg", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO createCctvFileOfSg() { + service.createCctvFileOfSg(); + return ApiResponseDTO.success(); + } + + @Operation(summary = "전자고지 연계 데이타 생성(accept)", description = "전자고지 연계 데이타 생성(accept)") + @PostMapping(value = "/acceptEnsNtnccntcSndng", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO acceptEnsNtnccntcSndng() { + service.acceptEnsNtnccntcSndng(); + return ApiResponseDTO.success(); + } +} diff --git a/mens-api/src/main/java/kr/xit/biz/ens/web/KkoMyDocController.java b/mens-api/src/main/java/kr/xit/biz/ens/web/KkoMyDocController.java new file mode 100644 index 0000000..0c80519 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/ens/web/KkoMyDocController.java @@ -0,0 +1,49 @@ +package kr.xit.biz.ens.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.xit.biz.ens.service.ISendMessageLinkService; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.ens.support.kakao.model.KkopayDocDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.ens.web
+ * fileName    : KkoMyDocController
+ * author      : limju
+ * date        : 2023-06-05
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-05    limju       최초 생성
+ *
+ * 
+ */ +@Tag(name = "KkoMyDocController", description = "전자고지 통합발송(카카오페이전자문서) 연계 서비스") +@RequiredArgsConstructor +@RestController +@RequestMapping(value = "/api/ens/v1") +public class KkoMyDocController { + private final ISendMessageLinkService service; + + + @Operation(summary = "모바일 데이타 요청(모바일 페이지에서 호출)", description = "모바일 데이타 요청(모바일 페이지에서 호출)") + @PostMapping(value = "/mblData", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO findKkoMyDocReadyAndMblData( + @RequestBody final KkopayDocDTO.OneTimeToken reqDTO + ) { + return service.findKkoMyDocReadyAndMblPage(reqDTO); + } +} diff --git a/mens-api/src/main/java/kr/xit/biz/ens/web/SendMessageLinkController.java b/mens-api/src/main/java/kr/xit/biz/ens/web/SendMessageLinkController.java new file mode 100644 index 0000000..9163151 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/ens/web/SendMessageLinkController.java @@ -0,0 +1,113 @@ +package kr.xit.biz.ens.web; + +import java.util.Map; + +import kr.xit.ens.support.kakao.model.KkopayDocDTO; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.xit.biz.ens.service.ISendMessageLinkService; +import kr.xit.core.model.ApiResponseDTO; +import lombok.RequiredArgsConstructor; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.ens.web
+ * fileName    : SendMessageLinkController
+ * author      : limju
+ * date        : 2023-06-05
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-05    limju       최초 생성
+ *
+ * 
+ */ +@Tag(name = "SendMessageLinkController", description = "전자고지 통합발송 연계 서비스(배치) 테스트") +@RequiredArgsConstructor +@RestController +@RequestMapping(value = "/api/ens/v1") +public class SendMessageLinkController { + private static final String SNDNG_PROCESS_STTUS = "sndngProcessSttus"; + + private final ISendMessageLinkService service; + + @Operation(summary = "통합문서 접수", description = "통합문서 접수") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"accept\"}") + }) + }) + @PostMapping(value = "/accept", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO accept(@RequestBody final Map map) { + service.accept(map.get(SNDNG_PROCESS_STTUS)); + return ApiResponseDTO.success(); + } + + @Operation(summary = "통합문서 발송 데이타 생성", description = "통합문서 발송데이타 생성") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"accept-ok\"}") + }) + }) + @PostMapping(value = "/make", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO make(@RequestBody final Map map){ + service.make(map.get(SNDNG_PROCESS_STTUS)); + return ApiResponseDTO.success(); + } + + @Operation(summary = "통합문서 발송 데이타 종료", description = "통합문서 발송 데이타 종료") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"send-ok\"}") + }) + }) + @PostMapping(value = "/close", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO close(@RequestBody final Map map){ + service.close(map.get(SNDNG_PROCESS_STTUS)); + return ApiResponseDTO.success(); + } + + @Operation(summary = "통합문서(bulk) 발송 요청", description = "통합문서 발송 요청") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"make-ok\"}") + }) + }) + @PostMapping(value = "/sendBulks", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO sendBulks(@RequestBody final Map map){ + service.sendBulks(map.get(SNDNG_PROCESS_STTUS)); + return ApiResponseDTO.success(); + } + + @Operation(summary = "통합문서(bulk) 상태 조회", description = "통합문서 상태 조회") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"send-ok\"}") + }) + }) + @PostMapping(value = "/findStatusBulks", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO findStatusBulks(@RequestBody final Map map){ + service.findKkoMyDocStatusBulks(map.get(SNDNG_PROCESS_STTUS)); + return ApiResponseDTO.success(); + } + + + @Operation(summary = "통합 문서발송 목록 조회", description = "통합 문서발송 목록 조회") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"make-ok\"}") + }) + }) + @PostMapping(value = "/findKakaoSendTargets", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO findkakaoSendTargets(@RequestBody final Map map){ + return ApiResponseDTO.success(service.findKakaoSendTargets(map.get(SNDNG_PROCESS_STTUS))); + } +} diff --git a/mens-api/src/main/java/kr/xit/biz/package-info.java b/mens-api/src/main/java/kr/xit/biz/package-info.java new file mode 100644 index 0000000..74eaffc --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/package-info.java @@ -0,0 +1,12 @@ +/** + * ENS business packages + *

+ * 전자고지 : ens + * 사전알림 : pni + * sms + *

+ * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.biz; diff --git a/mens-api/src/main/java/kr/xit/biz/pni/mapper/IPniCctvFileMapper.java b/mens-api/src/main/java/kr/xit/biz/pni/mapper/IPniCctvFileMapper.java new file mode 100644 index 0000000..b53d7a4 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/pni/mapper/IPniCctvFileMapper.java @@ -0,0 +1,32 @@ +package kr.xit.biz.pni.mapper; + +import kr.xit.biz.pni.model.PniDTO; +import org.egovframe.rte.psl.dataaccess.mapper.Mapper; + +import java.util.List; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.pni.mapper
+ * fileName    : IPniCctvFileMapper
+ * author      : limju
+ * date        : 2023-07-07
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-07    limju       최초 생성
+ *
+ * 
+ */ +@Mapper +public interface IPniCctvFileMapper { + int selectLicense(String jobSeCode); + int insertNtcnCntcData(PniDTO.NtcnCntcData dto); + + List selectPniNtncCntcSndngs(); + int insertCntcSndngMst(PniDTO.PniNtncCntcSndngTgts dto); + int insertCntcSndngDtl(PniDTO.PniNtncCntcSndngTgts dto); + int updatePniNtcnCntcData(PniDTO.PniNtncCntcSndngTgts dto); +} diff --git a/mens-api/src/main/java/kr/xit/biz/pni/model/PniDTO.java b/mens-api/src/main/java/kr/xit/biz/pni/model/PniDTO.java new file mode 100644 index 0000000..5d08126 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/pni/model/PniDTO.java @@ -0,0 +1,164 @@ +package kr.xit.biz.pni.model; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import kr.xit.ens.support.common.ApiConstants; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + *
+ * description : tb_pni_ Entity DTO
+ *
+ * packageName : kr.xit.biz.pni.model
+ * fileName    : PniDTO
+ * author      : limju
+ * date        : 2023-07-07
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-07    limju       최초 생성
+ *
+ * 
+ */ +public class PniDTO { + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class NtcnCntcData implements Serializable { + /** + * 사전 알림 연계 data id + */ + private String ntcnCntcDataId; + /** + * 단속 일시 + */ + private String regltDt; + /** + * 차량 번호 + */ + private String vhcleNo; + /** + * 현장 명 + */ + private String sptNm; + /** + * 현장 별 코드 + */ + private String sptAcctoCode; + /** + * 파일 명 + */ + private String fileNm; + /** + * 처리 여부 + */ + private String processAt; + /** + * 대상 여부 + */ + private String trgetAt; + /** + * 통합 발송 상세 id + */ + private String unitySndngDetailId; + + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss") + private LocalDateTime registDt; + /** + * 등록자 + */ + private String register; + + } + + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class PniNtncCntcSndngTgts implements Serializable { + /** + * 사전 알림 연계 data id + */ + private String ntcnCntcDataId; + /** + * 차량 번호 + */ + private String vhcleNo; + /** + * 처리 여부 + */ + private String processAt; + /** + * 대상여부 + */ + private String trgetAt; + /** + * 사전알림대상ID + */ + private String ntcnTrgetId; + /** + * 핸드폰 번호 + */private String moblphonNo; + + /** + * 이름 + */ + private String nm; + /** + * 생년월일 + */ + private String brthdy; + /*삭제여부*/ + private String deleteAt; + /** + * 발송건수 + */ + private int sndngCo; + /** + * 발송유형코드 + */ + @Builder.Default + private String sndngTyCode = "PNI"; + /** + * 템플릿 ID + */ + private String tmplatId; + /** + * 사전알림 데이타 생성 대상 여부 + */ + private String tgtYn; + + /** + * 통합발송 마스터ID + */ + private String unitySndngMastrId; + /** + * 통합발송 상세ID + */ + private String unitySndngDetailId; + /** + * 발송처리상태 + */ + @Builder.Default + private String sndngProcessSttus = ApiConstants.SndngProcessStatus.ACCEPT.getCode(); + /** + * 템플릿메세지 데이타 + */ + private String tmpltMsgData; + /** + * 모바일 페이지 내용 + */ + private String mobilePageCn; + } +} diff --git a/mens-api/src/main/java/kr/xit/biz/pni/service/IPniCctvFileService.java b/mens-api/src/main/java/kr/xit/biz/pni/service/IPniCctvFileService.java new file mode 100644 index 0000000..5ad24ed --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/pni/service/IPniCctvFileService.java @@ -0,0 +1,28 @@ +package kr.xit.biz.pni.service; + +/** + *
+ * description : 사전알림 파일 서비스
+ *
+ * packageName : kr.xit.biz.pni.service
+ * fileName    : IPniCctvFileService
+ * author      : limju
+ * date        : 2023-07-07
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-07    limju       최초 생성
+ *
+ * 
+ */ +public interface IPniCctvFileService { + /** + * 사전알림 cctv 단속 자료 생성(서광) + */ + void createCctvFileOfSg(); + + /** + * 사전알림 accept 연계발송 데이타 생성 + */ + void acceptPniNtnccntcSndng(); +} diff --git a/mens-api/src/main/java/kr/xit/biz/pni/service/PniCctvFileService.java b/mens-api/src/main/java/kr/xit/biz/pni/service/PniCctvFileService.java new file mode 100644 index 0000000..a7676e6 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/pni/service/PniCctvFileService.java @@ -0,0 +1,239 @@ +package kr.xit.biz.pni.service; + +import com.jcraft.jsch.SftpATTRS; +import com.jcraft.jsch.SftpException; +import kr.xit.biz.pni.mapper.IPniCctvFileMapper; +import kr.xit.biz.pni.model.PniDTO; +import kr.xit.core.exception.BizRuntimeException; +import kr.xit.core.support.utils.DateUtils; +import kr.xit.core.support.utils.SFTPUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + *
+ * description : 사전알림 CCTV 파일 서비스
+ *
+ * packageName : kr.xit.biz.pni.service
+ * fileName    : PniCctvFileService
+ * author      : limju
+ * date        : 2023-07-07
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-07    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Service +public class PniCctvFileService extends EgovAbstractServiceImpl implements IPniCctvFileService { + + @Value("${app.ssh.host}") private String host; + @Value("${app.ssh.port}") private int port; + @Value("${app.ssh.id}") private String id; + @Value("${app.ssh.passwd}") private String passwd; + + @Value("${app.ssh.sg.root-path}") private String rootPath; + @Value("${app.ssh.sg.rcv}") private String rcvPath; + @Value("${app.ssh.sg.backup}") private String backupPath; + @Value("${app.ssh.sg.err}") private String errPath; + + private final IPniCctvFileMapper mapper; + + /** + * 사전알림 cctv 단속 자료 생성(서광) + */ + @Override + @Transactional + public void createCctvFileOfSg() { + + // 사전알림 라이선스 유효성 체크 + int licenseCnt = mapper.selectLicense("PNI"); + if(licenseCnt == 0) return; + + SFTPUtils sftp = null; + + try { + // SFTP connect + sftp = new SFTPUtils(); + sftp.init(host, port, id, passwd, StringUtils.EMPTY); + + // 서광 CCTV 사전알림 대상 조회 + final String srcPath = rootPath + rcvPath; + ArrayList fileNameList = sftp.findFileNameList(srcPath); + //if(fileNameList.size() == 0) throw BizRuntimeException.create("사전고지[서광] 처리 대상이 없습니다"); + + // 에러 파일 목록 + ArrayList errFileList = new ArrayList<>(); + // 성공 파일 목록 + ArrayList rtnFileList = new ArrayList<>(); + for (String fn : fileNameList) { + String[] arrFi = fn.split("_"); + + // 파일 이름 정보 오류시 + if (arrFi.length != 4 || arrFi[0].length() != 14) { + errFileList.add(fn); + continue; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // FIXME :: GS 임시 소스 + // 파일 다운로드 + final String downLoadPath = "D:/ImageData/PNI" + "/" + DateUtils.getToday(StringUtils.EMPTY); + + File Folder = new File(downLoadPath); + if (!Folder.exists()) Folder.mkdir(); //폴더 생성합니다. + sftp.fileDownload(srcPath + "/" + fn, downLoadPath); + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // FIXME : 중복데이타(단속시간 + 차량번호)인 경우 데이타 skip 하도록 해야 할 듯 + // 사전알림 연계 데이타 생성 + mapper.insertNtcnCntcData( + PniDTO.NtcnCntcData.builder() + .regltDt(arrFi[0]) + .vhcleNo(arrFi[1]) + .sptNm(arrFi[2]) + .sptAcctoCode(arrFi[3].split("\\.")[0]) + .fileNm(fn) + .build() + ); + rtnFileList.add(fn); + + // FIXME :: sftp rm 기능 추후 제거 + // 파일 삭제 + sftp.rm(fn); + } + + // FIXME :: sftp move 기능 추후 사용 + // 처리한 데이타 backup + //fileBackup(sftp, srcPath, errFileList, rtnFileList); + + } finally { + if(sftp != null) sftp.disconnect(); + } + } + + /** + * 사전알림 accept 연계발송 데이타 생성 + * 1. 사전알림 연계 발송 데이타 생성 대상 조회 + * 2. 사전알림 연계 발송 마스터 생성 + * 3. 사전알림 연계 발송 상세 생성 + * 4. 사전알림 연계 결과 반영 + */ + @Override + @Transactional + public void acceptPniNtnccntcSndng() { + List tgtList = mapper.selectPniNtncCntcSndngs(); + + if(tgtList.size() == 0) return; + + // search 사전알림 연계발송 SMS 데이타 생성 대상 + Optional sms = tgtList.stream() + .filter(dto -> "JU201".equals(dto.getTmplatId())) + .filter(dto -> "Y".equals(dto.getTgtYn())) + .findFirst(); + + // search 사전알림 연계발송 카카오 알림톡 데이타 생성 대상 + Optional kkoMyDoc = tgtList.stream() + .filter(dto -> "JU202".equals(dto.getTmplatId())) + .filter(dto -> "Y".equals(dto.getTgtYn())) + .findFirst(); + + // 사전알림 연계발송 master 생성 + String smsUnitySndngMastrId; + String kkoUnitySndngMastrId; + String unitySndngMastrId = null; + + // sms 대상 있는지 확인해서 있으면 연계발송 master 생성 + if(sms.isPresent()){ + PniDTO.PniNtncCntcSndngTgts tgtDTO = sms.get(); + mapper.insertCntcSndngMst(tgtDTO); + smsUnitySndngMastrId = tgtDTO.getUnitySndngMastrId(); + MDC.put("unitySndngMastrId", smsUnitySndngMastrId); + } else { + smsUnitySndngMastrId = null; + } + + // 카카오 알림톡 대상 있는지 확인해서 있으면 연계발송 master 생성 + if(kkoMyDoc.isPresent()){ + PniDTO.PniNtncCntcSndngTgts tgtDTO = kkoMyDoc.get(); + mapper.insertCntcSndngMst(tgtDTO); + kkoUnitySndngMastrId = tgtDTO.getUnitySndngMastrId(); + MDC.put("unitySndngMastrId", kkoUnitySndngMastrId); + } else { + kkoUnitySndngMastrId = null; + } + + // 사전알림 연계발송 상세 생성 및 결과 처리(사전알림 연계 데이타 반영) + // 사전알림 대상 -> 사전알림 sms / 카카오 알림톡 연계 발송 상세 생성 + // 사전알림 연계 데이타 -> 연계 결과 반영 + // : 대상 : 연계대상-Y, 진행상태-Y, 통합발송상세ID + // 미대상 : 연계대상-N, 진행상태-Y, 통합발송상세ID = null + tgtList.forEach(dto -> { + dto.setProcessAt("Y"); + if("Y".equals(dto.getTgtYn())){ + // sms / 카카오 알림톡 템플릿메시지 data 생성 + StringBuffer strTmpltMsgData = new StringBuffer(); + strTmpltMsgData.append("[주정차 단속 사전 알림]"); + strTmpltMsgData.append ( "\n" ); + strTmpltMsgData.append("CCTV 주정차 단속 지역입니다."); + strTmpltMsgData.append ( "\n" ); + strTmpltMsgData.append(dto.getVhcleNo() + " 차량은 즉시 이동바랍니다."); + dto.setTmpltMsgData(strTmpltMsgData.toString()); + + // FIXME :: GS 인증 시험 알림톡이 안되어서 인증톡으로 대체 알림톡 운영 시 필요 없음 + // 카카오 알림톡 모바일 페이지 문구 + StringBuffer strmobilePageCn = new StringBuffer(); + strmobilePageCn.append("{"); + strmobilePageCn.append("\"details\" : ["); + strmobilePageCn.append( "{"); + strmobilePageCn.append( "\"title\" : \"주정차 단속 사전 알림\","); + strmobilePageCn.append ( "\"item_type\" : \"PRE_TEXT\","); + strmobilePageCn.append ( "\"elements\" : \"CCTV 주정차 단속 지역입니다.\\n"+ dto.getVhcleNo() + " 차량은 즉시 이동바랍니다.\\n- 서광시스템\""); + strmobilePageCn.append( "}"); + strmobilePageCn.append( "]"); + strmobilePageCn.append("}"); + dto.setMobilePageCn(strmobilePageCn.toString()); + + if("JU201".equals(dto.getTmplatId())) dto.setUnitySndngMastrId(smsUnitySndngMastrId); + else if("JU202".equals(dto.getTmplatId()))dto.setUnitySndngMastrId(kkoUnitySndngMastrId); + mapper.insertCntcSndngDtl(dto); + } + mapper.updatePniNtcnCntcData(dto); + }); + + } + + private void fileBackup(SFTPUtils sftp, String srcPath, ArrayList errFileList, ArrayList rtnFileList) { + final String errorPath = rootPath + errPath; + String destPath = rootPath + backupPath; + destPath = destPath + "/" + DateUtils.getToday(StringUtils.EMPTY); + + log.info("src path::[{}]", srcPath); + log.info("tgt path::[{}]", destPath); + + // file backup + for (String fn : rtnFileList) { + log.info("fileName::[{}]", fn); + sftp.mv(srcPath + "/" + fn, destPath + "/" + fn); + } + + // error file backup + for (String fn : errFileList) { + sftp.mv(srcPath + "/" + fn, errorPath + "/" + fn); + } + } +} diff --git a/mens-api/src/main/java/kr/xit/biz/pni/web/PniCctvFileController.java b/mens-api/src/main/java/kr/xit/biz/pni/web/PniCctvFileController.java new file mode 100644 index 0000000..809cb80 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/pni/web/PniCctvFileController.java @@ -0,0 +1,46 @@ +package kr.xit.biz.pni.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.xit.biz.pni.service.IPniCctvFileService; +import kr.xit.core.model.ApiResponseDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.pni.web
+ * fileName    : PniCctvFileController
+ * author      : limju
+ * date        : 2023-07-07
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-07    limju       최초 생성
+ *
+ * 
+ */ +@Tag(name = "PniCctvFileController", description = "사전고지 CCTV 파일 연계 서비스") +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/pni/v1") +public class PniCctvFileController { + private final IPniCctvFileService service; + + @Operation(summary = "사전고지 파일 연계 데이타 생성(서광)", description = "사전고지 파일 연계 데이타 생성(서광)") + @PostMapping(value = "/cctvFileOfSg", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO cctvFileOfSg() { + service.createCctvFileOfSg(); + return ApiResponseDTO.success(); + } + + @Operation(summary = "사전고지 연계 데이타 생성(accept)", description = "사전고지 연계 데이타 생성(accept)") + @PostMapping(value = "/acceptPniNtnccntcSndng", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO acceptPniNtnccntcSndng() { + service.acceptPniNtnccntcSndng(); + return ApiResponseDTO.success(); + } +} diff --git a/mens-api/src/main/java/kr/xit/biz/sms/mapper/ISmsMessageMapper.java b/mens-api/src/main/java/kr/xit/biz/sms/mapper/ISmsMessageMapper.java new file mode 100644 index 0000000..265e7f5 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/sms/mapper/ISmsMessageMapper.java @@ -0,0 +1,29 @@ +package kr.xit.biz.sms.mapper; + +import org.egovframe.rte.psl.dataaccess.mapper.Mapper; + +/** + *
+ * description : SMS 발송(oracle DB)
+ *
+ * packageName : kr.xit.biz.sms.mapper
+ * fileName    : ISmsMessageMapper
+ * author      : limju
+ * date        : 2023-06-26
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-26    limju       최초 생성
+ *
+ * 
+ */ +@Mapper +public interface ISmsMessageMapper { + + /** + * sms 발송 데이타 생성 + * @param t status + * @return int + */ + int insertScTran(final T t); +} diff --git a/mens-api/src/main/java/kr/xit/biz/sms/service/ISmsMessageService.java b/mens-api/src/main/java/kr/xit/biz/sms/service/ISmsMessageService.java new file mode 100644 index 0000000..0505bb4 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/sms/service/ISmsMessageService.java @@ -0,0 +1,24 @@ +package kr.xit.biz.sms.service; + +import kr.xit.biz.ens.model.EnsDTO; + +import java.util.List; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.sms.service
+ * fileName    : ISmsMessageService
+ * author      : limju
+ * date        : 2023-06-26
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-26    limju       최초 생성
+ *
+ * 
+ */ +public interface ISmsMessageService { + void sendSmsList(List list); +} diff --git a/mens-api/src/main/java/kr/xit/biz/sms/service/SmsMessageService.java b/mens-api/src/main/java/kr/xit/biz/sms/service/SmsMessageService.java new file mode 100644 index 0000000..b61c5bc --- /dev/null +++ b/mens-api/src/main/java/kr/xit/biz/sms/service/SmsMessageService.java @@ -0,0 +1,42 @@ +package kr.xit.biz.sms.service; + +import kr.xit.biz.sms.mapper.ISmsMessageMapper; +import lombok.RequiredArgsConstructor; +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + *
+ * description : xit SMS 발송 서비스 - Oracle 11
+ *
+ * packageName : kr.xit.biz.sms.service
+ * fileName    : SmsMessageService
+ * author      : limju
+ * date        : 2023-06-26
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-26    limju       최초 생성
+ *
+ * 
+ */ +@RequiredArgsConstructor +@Service +public class SmsMessageService extends EgovAbstractServiceImpl implements ISmsMessageService { + + private final ISmsMessageMapper mapper; + + /** + * SMS 발송 - xit SMS 발송 테이블 데이타 생성 + * @param list List SMS 발송 목록 + */ + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void sendSmsList(List list) { + for(T t : list) mapper.insertScTran(t); + } +} diff --git a/mens-api/src/main/java/kr/xit/core/aop/TraceLoggerAspect.java b/mens-api/src/main/java/kr/xit/core/aop/TraceLoggerAspect.java new file mode 100644 index 0000000..5809b25 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/aop/TraceLoggerAspect.java @@ -0,0 +1,260 @@ +package kr.xit.core.aop; + +import java.util.Arrays; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import kr.xit.core.exception.BizRuntimeException; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.core.biz.model.LoggingDTO; +import kr.xit.core.biz.service.ILoggingService; +import kr.xit.core.spring.util.error.ErrorParse; +import kr.xit.core.support.slack.SlackWebhookPush; +import kr.xit.core.support.utils.Checks; +import kr.xit.core.support.utils.JsonUtils; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : logging trace aspect
+ *               공통 core 모듈의 LoggerAspect 상속 -> traceLogging / traceLoggingError / traceLoggingResult 구현
+ *
+ *               Logging trace 구현시
+ *               - MDC(Mapped Diagnostic Context : logback, log4j에 포함) 사용 로깅
+ *               - ThreadLocal 사용
+ *               - nginx : proxy_set_header X-RequestID $request_id;
+ *               - logback log pattern : [traceId=%X{request_id}]
+ *
+ *               app.slack-webhook.enabled: true인 경우 slack push
+ *               Slack webhook : SlackWebhookPush
+ *
+ * packageName : kr.xit.core.aop
+ * fileName    : TraceLoggerAspect
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ * 2023-06-12    julim       배치처리시 RequestContextHolder.HttpServletRequest 객체 미사용에 따른 처리 추가
+ * 
+ * @see kr.xit.core.support.slack.SlackWebhookPush#sendSlackAlertLog(String, String, String) + */ + +@Slf4j +@Aspect +@Component +public class TraceLoggerAspect { + + @Value("${app.slack-webhook.enabled:false}") + private boolean isSlackEnabled; + + @Value("#{'${app.mdc.log.trace.uris}'.split(',')}") + private String[] uris; + + private final ILoggingService loggingService; + private final SlackWebhookPush slackWebhookPush; + private static final String MESSAGE = "message"; + private static final String CODE = "code"; + private static final String REQUEST_TRACE_ID = "request_trace_id"; + + public TraceLoggerAspect(@Lazy ILoggingService loggingService, SlackWebhookPush slackWebhookPush) { + this.loggingService = loggingService; + this.slackWebhookPush = slackWebhookPush; + } + + @Pointcut("execution(public * egovframework..*.*(..)) || execution(public * kr.xit..*.*(..))") + public void errorPointCut() { + } + + @Around(value = "@annotation(kr.xit.core.spring.annotation.TraceLogging)") + public Object serviceTraceLogging(final ProceedingJoinPoint pjp) throws Throwable { + ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = attributes != null? attributes.getRequest(): null; + + traceLogging(JsonUtils.toJson(pjp.getArgs()), request); + Object result = pjp.proceed(); + + if(result instanceof CompletableFuture){ + CompletableFuture future = (CompletableFuture)result; + + while(true) { + if (future.isDone()) break; + } + traceLoggingResult(future.get()); + }else{ + traceLoggingResult(result); + } + return result; + } + + + //@AfterThrowing(value = "@annotation(kr.xit.core.spring.annotation.TraceLogging)", throwing = "error") + @AfterThrowing(value = "errorPointCut()", throwing="error") + public void afterThrowingProceed(final JoinPoint jp, final Throwable error) { + traceLoggingError(jp, error); + } + + + /** + * 배치 실행시 여기에서 set한 MDC 값이 batch 모듈에서 reading이 불가하여, 배치 tasklet에서 set한 trace_id로 set + * @param params + * @param request + */ + protected void traceLogging(final String params, final HttpServletRequest request) { + if(request != null) { + String uri = request.getRequestURI().toString(); + if(Arrays.asList(uris).stream().anyMatch(regx -> uri.matches(regx))) return; + + MDC.put(REQUEST_TRACE_ID, + StringUtils.defaultString(MDC.get("request_trace_batch_id"), UUID.randomUUID().toString().replaceAll("/-/g", ""))); + MDC.put("method", request.getMethod()); + MDC.put("uri", uri); + MDC.put("ip", request.getRemoteAddr()); + MDC.put("sessionId", request.getSession().getId()); + + // batch로 실행되는 경우 task에서 설정 : uri / method + }else{ + // request_id는 중복 처리 되면 않되므로 여기에서 생성 + MDC.put(REQUEST_TRACE_ID, StringUtils.defaultString(MDC.get("request_trace_batch_id"), UUID.randomUUID().toString().replaceAll("-", ""))); + } +log.info("@@@@@@@@@@@@@@@@@로깅 start : [\n{}\n]",MDC.getCopyOfContextMap()); + MDC.put("systemId", "ENS"); + MDC.put("reqSystemId", "KAKAO"); + MDC.put("param", params); + MDC.put("accessToken", ""); + + LoggingDTO loggingDTO = LoggingDTO.builder() + .requestId(MDC.getCopyOfContextMap().get(REQUEST_TRACE_ID)) + .systemId("ENS") + .reqSystemId("KAKAO") + .method(MDC.getCopyOfContextMap().get("method")) + .uri(MDC.getCopyOfContextMap().get("uri")) + .param(params) + .ip(MDC.getCopyOfContextMap().get("ip")) + .accessToken("") + .sessionId(MDC.getCopyOfContextMap().get("sessionId")) + .build(); + loggingService.saveLogging(loggingDTO); + + } + + protected void traceLoggingResult(final Object result) { + String success = "true"; + //FIXME: slack webhook log push + if(result instanceof ApiResponseDTO){ + ApiResponseDTO apiResponseDTO = (ApiResponseDTO)result; + if(!apiResponseDTO.isSuccess()){ + success = "false"; + + if(isSlackEnabled) { + + if(RequestContextHolder.getRequestAttributes() != null) { + slackWebhookPush.sendSlackAlertLog( + String.format("[%s]%s", MDC.get(REQUEST_TRACE_ID), apiResponseDTO.getMessage()), + ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest()); + }else{ + slackWebhookPush.sendSlackAlertLog( + String.format("[%s]%s", MDC.get(REQUEST_TRACE_ID), apiResponseDTO.getMessage()), + MDC.get("uri"), + "batch call"); + } + } + }else{ + if(apiResponseDTO.getData() != null){ + log.info("~~~~"); + } + } + } + + LoggingDTO reqDTO = LoggingDTO + .builder() + .requestId(MDC.get(REQUEST_TRACE_ID)) + .success(success) + .response(getResult(result)) + .message(HttpStatus.OK.name()) + .build(); + //} + loggingService.modifyLogging(reqDTO); + //loggingService.saveLogging(reqDTO); + + log.info("@@@@@@@@@@@@@@로깅 end[\n{}\n]", MDC.getCopyOfContextMap()); + + //if(RequestContextHolder.getRequestAttributes() != null) + MDC.clear(); + //} + } + + private String getResult(final Object o){ + if(o instanceof Future) { + try { + Future future = (Future)o; + return JsonUtils.toJson(future.get()); + } catch (InterruptedException ie){ + // thread pool에 에러 상태 전송 + Thread.currentThread().interrupt(); + throw BizRuntimeException.create(ie); + + } catch (ExecutionException ee) { + throw BizRuntimeException.create(ee); + } + }else{ + return JsonUtils.toJson(o); + } + } + + protected void traceLoggingError(final JoinPoint jp, final Throwable e) { + log.info("~~~~~~~~~~~~~~~~~~~~~~~>>>>{}", MDC.get(REQUEST_TRACE_ID)); + if(Checks.isEmpty(MDC.get(REQUEST_TRACE_ID))) return; + + Map map = ErrorParse.extractError(e); + + loggingService.modifyLogging( + LoggingDTO + .builder() + .requestId(MDC.get(REQUEST_TRACE_ID)) + .success("false") + .response(JsonUtils.toJson(ApiResponseDTO.error(String.valueOf(map.get(CODE)), String.valueOf(map.get(MESSAGE)), (HttpStatus)map.get("httpStatus")))) + .message(String.valueOf(map.get(MESSAGE))) + .build()); + + //FIXME :: slack webhook log push + if(isSlackEnabled){ + if(RequestContextHolder.getRequestAttributes() != null) { + slackWebhookPush.sendSlackAlertLog( + String.format("[%s]%s(%s)", MDC.get(REQUEST_TRACE_ID), String.valueOf(map.get(CODE)), + String.valueOf(map.get(MESSAGE))), + ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest()); + }else{ + slackWebhookPush.sendSlackAlertLog( + String.format("[%s]%s(%s)", MDC.get(REQUEST_TRACE_ID), String.valueOf(map.get(CODE)), + String.valueOf(map.get(MESSAGE))), + MDC.get("uri"), + "batch call"); + } + } + log.info("@@@@@@@@@@@@ 로깅 end[\n{}\n]", MDC.getCopyOfContextMap()); + //if(RequestContextHolder.getRequestAttributes() != null) + MDC.clear(); + } +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/batch/CustomRunIdIncrementer.java b/mens-api/src/main/java/kr/xit/core/biz/batch/CustomRunIdIncrementer.java new file mode 100644 index 0000000..5934565 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/batch/CustomRunIdIncrementer.java @@ -0,0 +1,38 @@ +package kr.xit.core.biz.batch; + +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.launch.support.RunIdIncrementer; + +/** + *
+ * description :  Spring Boot 2.0.x에서 발생하는 버그
+ *                => 배치 Job이 실패할 경우 이후에 파라미터를 변경해도 계속 실패한 파라미터가 사용
+ *                Spring Boot 2.1.0 이상부터는
+ *                => 이전의 Job이 실행시 사용한 파라미터 중 하나가 다음 실행시 누락되면 누락된 파라미터를 재사용
+ *                   --> 넘어온 파라미터와 run.id만 사용
+ *                  .incrementer(new RunIdIncrementer()) -> .incrementer(new UniqueRunIdIncrementer())
+ *
+ * packageName : kr.xit.core.biz.batch
+ * fileName    : CustomRunIdIncrementer
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ */ +public class CustomRunIdIncrementer extends RunIdIncrementer { + private static final String RUN_ID = "run.id"; + + @Override + public JobParameters getNext(JobParameters parameters) { + JobParameters params = (parameters == null) ? new JobParameters() : parameters; + return new JobParametersBuilder() + .addLong(RUN_ID, params.getLong(RUN_ID, 0L) + 1) + .toJobParameters(); + } +} + diff --git a/mens-api/src/main/java/kr/xit/core/biz/batch/ListReader.java b/mens-api/src/main/java/kr/xit/core/biz/batch/ListReader.java new file mode 100644 index 0000000..4eb7492 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/batch/ListReader.java @@ -0,0 +1,37 @@ +package kr.xit.core.biz.batch; + +import java.util.List; + +/** + *
+ * description : ItemReaderAdapter 를 사용한 List 데이타의 read() 구현
+ *               -> ItemReader 의 read
+ *               ItemReaderAdapter의 targetObject 지정시 사용
+ *               -> ItemReaderAdapter.setTargetObject(Object o)
+ *                  : new ListReader<>(대상목록-서비스 조회결과)
+ *                  : new ListReader<>(loggingService.findLogging(null))
+ * packageName : kr.xit.core.biz.batch
+ * fileName    : ListReader
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ */ +public class ListReader { + private List list; + + public ListReader(List list){ + this.list = list; + } + + public T read() { + if(list.isEmpty()){ + return null; + } + return list.remove(0); + } + } diff --git a/mens-api/src/main/java/kr/xit/core/biz/batch/listener/CustomChunkListener.java b/mens-api/src/main/java/kr/xit/core/biz/batch/listener/CustomChunkListener.java new file mode 100644 index 0000000..cfa4e91 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/batch/listener/CustomChunkListener.java @@ -0,0 +1,48 @@ +package kr.xit.core.biz.batch.listener; + +import org.springframework.batch.core.ChunkListener; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description :  Chunk 실행시 로그 출력
+ *
+ * packageName : kr.xit.core.biz.batch.listener
+ * fileName    : CustomChunkListener
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@Component +public class CustomChunkListener { + + @Bean + public ChunkListener chunkListener() { + return new ChunkListener() { + @Override + public void beforeChunk(ChunkContext context) { + log.info("↓ Chunk start"); + } + + @Override + public void afterChunk(ChunkContext context) { + log.info("↑ Chunk end"); + } + + @Override + public void afterChunkError(ChunkContext context) { + // Do nothing + } + }; + } +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/batch/listener/CustomItemListner.java b/mens-api/src/main/java/kr/xit/core/biz/batch/listener/CustomItemListner.java new file mode 100644 index 0000000..785edf1 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/batch/listener/CustomItemListner.java @@ -0,0 +1,29 @@ +package kr.xit.core.biz.batch.listener; + +import java.util.List; + +import org.springframework.batch.core.listener.ItemListenerSupport; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class CustomItemListner extends ItemListenerSupport { + + public void onReadError(Exception ex) { + log.error("onReadError", ex); + } + + @Override + public void onProcessError(Object item, Exception e) { + log.error("onProcessError", e); + super.onProcessError(item, e); + } + + @Override + public void onWriteError(Exception ex, List item) { + log.error("onWriteError", ex); + } + + + +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/batch/listener/CustomJobListener.java b/mens-api/src/main/java/kr/xit/core/biz/batch/listener/CustomJobListener.java new file mode 100644 index 0000000..5af0a8f --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/batch/listener/CustomJobListener.java @@ -0,0 +1,41 @@ +package kr.xit.core.biz.batch.listener; + +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.listener.JobExecutionListenerSupport; + +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description :  Job 실행시 로그 출력
+ *
+ * packageName : kr.xit.core.biz.batch.listener
+ * fileName    : CustomJobListener
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +public class CustomJobListener extends JobExecutionListenerSupport { + + @Override + public void beforeJob(JobExecution jobExecution) { + log.info("\n"); + log.info("===================================================="); + log.info("========== ↓ [{}] Job start ===========", jobExecution.getJobInstance().getJobName()); + log.info("===================================================="); + } + + @Override + public void afterJob(JobExecution jobExecution) { + log.info("==============================================================="); + log.info("========== ↑ [{}] Job end :: {} ==========", jobExecution.getJobInstance().getJobName(), jobExecution.getStatus()); + log.info("==============================================================="); + log.info("\n"); + } +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/batch/listener/CustomStepListener.java b/mens-api/src/main/java/kr/xit/core/biz/batch/listener/CustomStepListener.java new file mode 100644 index 0000000..3606a4a --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/batch/listener/CustomStepListener.java @@ -0,0 +1,40 @@ +package kr.xit.core.biz.batch.listener; + +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.listener.StepExecutionListenerSupport; + +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description :  step 실행시 로그 출력
+ *
+ * packageName : kr.xit.core.biz.batch.listener
+ * fileName    : CustomStepListener
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +public class CustomStepListener extends StepExecutionListenerSupport { + + @Override + public void beforeStep(StepExecution stepExecution) { + log.info("##### ↓ [{}] Step start", stepExecution.getStepName()); + //log.info("##### [{}-{}] Step is start", stepExecution.getJobExecution().getJobInstance().getJobName(), stepExecution.getStepName()); + } + + @Override + public ExitStatus afterStep(StepExecution stepExecution) { + log.info("##### ↑ [{}] Step end :: {}", stepExecution.getStepName(), stepExecution.getStatus()); + //log.info("##### [{}-{}] Step is completed {}", stepExecution.getJobExecution().getJobInstance().getJobName(), stepExecution.getStepName(), stepExecution.getStatus()); + return stepExecution.getExitStatus(); + } +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/batch/listener/NoWorkFoundStepListener.java b/mens-api/src/main/java/kr/xit/core/biz/batch/listener/NoWorkFoundStepListener.java new file mode 100644 index 0000000..952dd4a --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/batch/listener/NoWorkFoundStepListener.java @@ -0,0 +1,16 @@ +package kr.xit.core.biz.batch.listener; + +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListenerSupport; + +public class NoWorkFoundStepListener extends StepExecutionListenerSupport { + + public ExitStatus afterStep(StepExecution stepExecution) { + if (stepExecution.getReadCount() == 0) { + return ExitStatus.FAILED; + } + return null; + } + +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/batch/mapper/IBatchCmmMapper.java b/mens-api/src/main/java/kr/xit/core/biz/batch/mapper/IBatchCmmMapper.java new file mode 100644 index 0000000..092d1b3 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/batch/mapper/IBatchCmmMapper.java @@ -0,0 +1,32 @@ +package kr.xit.core.biz.batch.mapper; + +import java.util.Optional; + +import org.egovframe.rte.psl.dataaccess.mapper.Mapper; + +import kr.xit.core.biz.batch.model.BatchCmmDTO; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.core.biz.batch.mapper
+ * fileName    : IBatchCmmMapper
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ */ +@Mapper +public interface IBatchCmmMapper { + Optional selectBatchLockByInstanceId(final String instanceId); + int insertBatchLock(final BatchCmmDTO dto); + int updateBatchLock(final BatchCmmDTO dto); + + int insertBatchLog(final BatchCmmDTO dto); + int updateBatchLog(final BatchCmmDTO dto); +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/batch/model/BatchCmmDTO.java b/mens-api/src/main/java/kr/xit/core/biz/batch/model/BatchCmmDTO.java new file mode 100644 index 0000000..5c3087d --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/batch/model/BatchCmmDTO.java @@ -0,0 +1,68 @@ +package kr.xit.core.biz.batch.model; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.core.biz.batch.model
+ * fileName    : BatchCmmDTO
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ * 2023-06-15    limju       배치로그 추가
+ *
+ * 
+ */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class BatchCmmDTO { + /** + * 배치 로그 ID + */ + private String batchLogId; + /** + * 배치 인스턴스 ID + */ + private String instanceId; + /** + * 배치 API trace ID + */ + private String traceId; + /** + * 실행 결과 + */ + private String result; + /** + * 에러메세지 + */ + private String message; + /** + * 배치 실행중 여부 + */ + private String useYn; + + //@Convert(converter = Jsr310.LocalDateTimeConverter.class) + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss.SSS") + private LocalDateTime registDt; + + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss.SSS") + private LocalDateTime updateDt; +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/batch/service/BatchCmmService.java b/mens-api/src/main/java/kr/xit/core/biz/batch/service/BatchCmmService.java new file mode 100644 index 0000000..9ebefd9 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/batch/service/BatchCmmService.java @@ -0,0 +1,65 @@ +package kr.xit.core.biz.batch.service; + +import java.util.Optional; + +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import kr.xit.core.biz.batch.mapper.IBatchCmmMapper; +import kr.xit.core.biz.batch.model.BatchCmmDTO; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.core.biz.service
+ * fileName    : BatchLockService
+ * author      : limju
+ * date        : 2023-05-18
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-18    limju       최초 생성
+ *
+ * 
+ */ +@Service +public class BatchCmmService extends EgovAbstractServiceImpl implements IBatchCmmService { + private final IBatchCmmMapper mapper; + + public BatchCmmService(IBatchCmmMapper mapper) { + this.mapper = mapper; + } + + @Override + @Transactional(readOnly = true) + public Optional findById(final String instanceId) { + return mapper.selectBatchLockByInstanceId(instanceId); + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void addBatchLock(final BatchCmmDTO dto) { + mapper.insertBatchLock(dto); + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void modifyBatchLock(final BatchCmmDTO dto) { + mapper.updateBatchLock(dto); + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void addBatchLog(final BatchCmmDTO dto) { + mapper.insertBatchLog(dto); + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void modifyBatchLog(final BatchCmmDTO dto) { + mapper.updateBatchLog(dto); + } +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/batch/service/IBatchCmmService.java b/mens-api/src/main/java/kr/xit/core/biz/batch/service/IBatchCmmService.java new file mode 100644 index 0000000..dbae56d --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/batch/service/IBatchCmmService.java @@ -0,0 +1,29 @@ +package kr.xit.core.biz.batch.service; + +import java.util.Optional; + +import kr.xit.core.biz.batch.model.BatchCmmDTO; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.core.biz.service
+ * fileName    : IBatchCmmService
+ * author      : limju
+ * date        : 2023-05-18
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-18    limju       최초 생성
+ *
+ * 
+ */ +public interface IBatchCmmService { + Optional findById(final String instanceId); + void addBatchLock(final BatchCmmDTO dto); + void modifyBatchLock(final BatchCmmDTO dto); + + void addBatchLog(final BatchCmmDTO dto); + void modifyBatchLog(final BatchCmmDTO dto); +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/batch/task/BatchEndTasklet.java b/mens-api/src/main/java/kr/xit/core/biz/batch/task/BatchEndTasklet.java new file mode 100644 index 0000000..5641426 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/batch/task/BatchEndTasklet.java @@ -0,0 +1,57 @@ +package kr.xit.core.biz.batch.task; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.MDC; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +import kr.xit.core.biz.batch.model.BatchCmmDTO; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : 배치 종료시 lock table update
+ *               종료하는 배치 인스턴스의 사용여부(use_yn)를 미사용(N)으로 변경
+ *
+ * packageName : kr.xit.core.biz.batch.task
+ * fileName    : BatchEndTasklet
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ * @see kr.xit.core.aop.TraceLoggerAspect + */ +@Slf4j +public class BatchEndTasklet implements Tasklet { + private final IBatchCmmService batchCmmService; + + public BatchEndTasklet(IBatchCmmService batchCmmService) { + this.batchCmmService = batchCmmService; + } + + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + String jobName = contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(); + contribution.setExitStatus(ExitStatus.COMPLETED); + BatchCmmDTO dto = BatchCmmDTO + .builder() + .instanceId(jobName) + .result(contribution.getExitStatus().getExitCode()) + .useYn("N") + .batchLogId(MDC.get("batch_log_id")) + .traceId(StringUtils.defaultString(MDC.get("request_trace_id"), MDC.get("request_trace_batch_id"))) + .build(); +log.info("@@@@@@@@@@@@@{}", MDC.getCopyOfContextMap()); + batchCmmService.modifyBatchLock(dto); + batchCmmService.modifyBatchLog(dto); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/batch/task/BatchFailEndTasklet.java b/mens-api/src/main/java/kr/xit/core/biz/batch/task/BatchFailEndTasklet.java new file mode 100644 index 0000000..bc04cde --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/batch/task/BatchFailEndTasklet.java @@ -0,0 +1,59 @@ +package kr.xit.core.biz.batch.task; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.MDC; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +import kr.xit.core.biz.batch.model.BatchCmmDTO; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : 배치 종료시 lock table update
+ *               종료하는 배치 인스턴스의 사용여부(use_yn)를 미사용(N)으로 변경
+ *
+ * packageName : kr.xit.core.biz.batch.task
+ * fileName    : BatchEndTasklet
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ * @see kr.xit.core.aop.TraceLoggerAspect + */ +@Slf4j +public class BatchFailEndTasklet implements Tasklet { + private final IBatchCmmService batchCmmService; + + public BatchFailEndTasklet(IBatchCmmService batchCmmService) { + this.batchCmmService = batchCmmService; + } + + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + String jobName = contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(); + contribution.setExitStatus(ExitStatus.FAILED); + BatchCmmDTO dto = BatchCmmDTO + .builder() + .instanceId(jobName) + .result(contribution.getExitStatus().getExitCode()) + .useYn("N") + .batchLogId(MDC.get("batch_log_id")) + .traceId(StringUtils.defaultString(MDC.get("request_trace_id"), MDC.get("request_trace_batch_id"))) + .build(); + log.info("@@@@@@@@@@@@@{}", MDC.getCopyOfContextMap()); + + batchCmmService.modifyBatchLock(dto); + batchCmmService.modifyBatchLog(dto); + + return RepeatStatus.FINISHED; + } +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/batch/task/BatchStartTasklet.java b/mens-api/src/main/java/kr/xit/core/biz/batch/task/BatchStartTasklet.java new file mode 100644 index 0000000..b2890ed --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/batch/task/BatchStartTasklet.java @@ -0,0 +1,75 @@ +package kr.xit.core.biz.batch.task; + +import org.slf4j.MDC; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +import kr.xit.core.biz.batch.model.BatchCmmDTO; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.support.utils.Checks; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : 배치 시작시 lock table insert or update
+ *               시작하는 배치 인스턴스의 사용여부(use_yn)를 사용(Y)으로 변경(생성)
+ *               -> 사용여부가 사용(Y) 이면 실행중 이므로 배치 종료
+ *
+ * packageName : kr.xit.core.biz.batch.task
+ * fileName    : BatchStartTasklet
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ * 2023-08-21    limju       lock이 걸린 경우 release 하도록 변경 : 중복실행 되지 않도록 배치 시간 보장 전제
+ * 
+ * @see kr.xit.core.aop.TraceLoggerAspect + */ +@Slf4j +public class BatchStartTasklet implements Tasklet { + private final IBatchCmmService batchCmmService; + + public BatchStartTasklet(IBatchCmmService batchCmmService) { + this.batchCmmService = batchCmmService; + } + + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + String jobName = contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(); + BatchCmmDTO dto = batchCmmService.findById(jobName) + .orElseGet(()-> BatchCmmDTO + .builder() + .build()); + + if(Checks.isEmpty(dto.getInstanceId())){ + dto.setInstanceId(jobName); + dto.setResult(null); + dto.setUseYn("Y"); + batchCmmService.addBatchLock(dto); + batchCmmService.addBatchLog(dto); + MDC.put("batch_log_id", dto.getBatchLogId()); + }else{ + if("Y".equals(dto.getUseYn())){ + log.error("======= [{}] 배치 실행(사용) 중으로 종료 =======", jobName); + contribution.setExitStatus(ExitStatus.FAILED); + dto.setResult("배치 실행(사용) 중으로 종료"); + // lock이 걸린 경우 reset : 2023-08-21 + dto.setUseYn("N"); + batchCmmService.modifyBatchLock(dto); + batchCmmService.addBatchLog(dto); + }else{ + dto.setUseYn("Y"); + dto.setResult(null); + batchCmmService.modifyBatchLock(dto); + batchCmmService.addBatchLog(dto); + MDC.put("batch_log_id", dto.getBatchLogId()); + } + } + return RepeatStatus.FINISHED; + } +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/mapper/IAuthApiMapper.java b/mens-api/src/main/java/kr/xit/core/biz/mapper/IAuthApiMapper.java new file mode 100644 index 0000000..125e016 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/mapper/IAuthApiMapper.java @@ -0,0 +1,28 @@ +package kr.xit.core.biz.mapper; + +import org.egovframe.rte.psl.dataaccess.mapper.Mapper; + +import egovframework.com.cmm.LoginVO; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.core.biz.mapper
+ * fileName    : IAuthApiMapper
+ * author      : limju
+ * date        : 2023-05-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-11    limju       최초 생성
+ *
+ * 
+ */ +@Mapper +public interface IAuthApiMapper { + LoginVO actionLogin(LoginVO vo); + // LoginVO searchId(LoginVO vo); + // LoginVO searchPassword(LoginVO vo); + // void updatePassword(LoginVO vo); +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/mapper/ILoggingMapper.java b/mens-api/src/main/java/kr/xit/core/biz/mapper/ILoggingMapper.java new file mode 100644 index 0000000..a496161 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/mapper/ILoggingMapper.java @@ -0,0 +1,32 @@ +package kr.xit.core.biz.mapper; + +import java.util.List; + +import org.egovframe.rte.psl.dataaccess.mapper.Mapper; + +import kr.xit.core.biz.model.LoggingDTO; + +/** + *
+ * description : API db logging
+ *
+ * packageName : kr.xit.core.biz.mapper
+ * fileName    : ILoggingMapper
+ * author      : limju
+ * date        : 2023-05-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-11    limju       최초 생성
+ *
+ * 
+ */ +@Mapper +public interface ILoggingMapper { + + List selectLogging(final LoggingDTO reqDTO); + LoggingDTO selectLogging(); + + void saveLogging(final LoggingDTO reqDTO); + void updateLogging(final LoggingDTO reqDTO); +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/model/AuditFields.java b/mens-api/src/main/java/kr/xit/core/biz/model/AuditFields.java new file mode 100644 index 0000000..fb4af58 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/model/AuditFields.java @@ -0,0 +1,57 @@ +package kr.xit.core.biz.model; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +/** + *
+ * description : Audit column (등록 및 변경 정보 저장 필드)
+ *
+ * packageName : kr.xit.core.biz.model
+ * fileName    : AuditFields
+ * author      : limju
+ * date        : 2023-06-05
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-05    limju       최초 생성
+ *
+ * 
+ */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@SuperBuilder +public class AuditFields { + /** + * 등록 일시 + */ + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss") + private LocalDateTime registDt; + /** + * 등록자 + */ + private String register; + /** + * 수정 일시 + */ + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss") + private LocalDateTime updtDt; + /** + * 수정자 + */ + private String updusr; +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/model/LoggingDTO.java b/mens-api/src/main/java/kr/xit/core/biz/model/LoggingDTO.java new file mode 100644 index 0000000..9d7fba5 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/model/LoggingDTO.java @@ -0,0 +1,103 @@ +package kr.xit.core.biz.model; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.core.biz.model
+ * fileName    : LoggingDTO
+ * author      : limju
+ * date        : 2023-05-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-11    limju       최초 생성
+ *
+ * 
+ */ +@Schema(name = "LoggingDTO", description = "API 로깅 DTO") +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@ToString +public class LoggingDTO { + /** + * 요청 ID + */ + private String requestId; + /** + * 시스템 ID(ENS|FIMS등) + */ + private String systemId; + /** + * 요청시스템 ID(KAKAO|KT등) + */ + private String reqSystemId; + /** + * 메소드(GET|PUT|POST|DELETE) + */ + private String method; + /** + * 호출 URI + */ + private String uri; + /** + * 성공/실패(true|false) + */ + private String success; + /** + * 파라메터 + */ + private String param; + /** + * 호출 결과 + */ + private String response; + /** + * 메세지(에러메세지) + */ + private String message; + /** + * IP + */ + private String ip; + /** + * 토큰 + */ + private String accessToken; + /** + * 세션ID + */ + private String sessionId; + /** + * 변경일시(now(3)-밀리세컨드까지) + */ + //private java.sql.Timestamp updtDt; + /** + * 변경자 + */ + private String updtId; + /** + * 생성일시(now(3)-밀리세컨드까지) + */ + //private java.sql.Timestamp registDt; + /** + * 생성자 + */ + //@Convert(converter = Jsr310.LocalDateTimeConverter.class) + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss.SSS") + private String registId; +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/service/AuthApiService.java b/mens-api/src/main/java/kr/xit/core/biz/service/AuthApiService.java new file mode 100644 index 0000000..5e076ef --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/service/AuthApiService.java @@ -0,0 +1,42 @@ +package kr.xit.core.biz.service; + +import javax.annotation.Resource; + +import egovframework.com.cmm.util.EgovFileScrty; +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.springframework.stereotype.Service; + +import egovframework.com.cmm.LoginVO; +import kr.xit.core.biz.mapper.IAuthApiMapper; + +@Service +public class AuthApiService extends EgovAbstractServiceImpl implements IAuthApiService { + + @Resource + private IAuthApiMapper mapper; + + /** + * 일반 로그인 처리 + * @param vo LoginVO + * @return LoginVO + */ + @Override + public LoginVO actionLogin(LoginVO vo) { + + // 1. 입력한 비밀번호를 암호화한다. + String enpassword = EgovFileScrty.encryptPassword(vo.getPassword(), vo.getId()); + vo.setPassword(enpassword); + + // 2. 아이디와 암호화된 비밀번호가 DB와 일치하는지 확인한다. + LoginVO loginVO = mapper.actionLogin(vo); //loginDAO.actionLogin(vo); + + // 3. 결과를 리턴한다. + if (loginVO != null && !loginVO.getId().equals("") && !loginVO.getPassword().equals("")) { + return loginVO; + } else { + loginVO = new LoginVO(); + } + + return loginVO; + } +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/service/IAuthApiService.java b/mens-api/src/main/java/kr/xit/core/biz/service/IAuthApiService.java new file mode 100644 index 0000000..b0b6eef --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/service/IAuthApiService.java @@ -0,0 +1,32 @@ +package kr.xit.core.biz.service; + +import egovframework.com.cmm.LoginVO; + +/** + * 일반 로그인을 처리하는 비즈니스 구현 클래스 + * @author 공통서비스 개발팀 박지욱 + * @since 2009.03.06 + * @version 1.0 + * @see + * + *
+ * << 개정이력(Modification Information) >>
+ *
+ *   수정일      수정자          수정내용
+ *  -------    --------    ---------------------------
+ *  2009.03.06  박지욱          최초 생성
+ *  2011.08.31  JJY            경량환경 템플릿 커스터마이징버전 생성
+ *
+ *  
+ */ +public interface IAuthApiService { + + /** + * 일반 로그인 처리 + * + * @param vo LoginVO + * @return LoginVO + */ + LoginVO actionLogin(LoginVO vo); + +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/service/ILoggingService.java b/mens-api/src/main/java/kr/xit/core/biz/service/ILoggingService.java new file mode 100644 index 0000000..008f05d --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/service/ILoggingService.java @@ -0,0 +1,27 @@ +package kr.xit.core.biz.service; + +import java.util.List; + +import kr.xit.core.biz.model.LoggingDTO; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.core.biz.service
+ * fileName    : ILoggingService
+ * author      : limju
+ * date        : 2023-05-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-11    limju       최초 생성
+ *
+ * 
+ */ +public interface ILoggingService { + List findLogging(LoggingDTO reqDTO); + LoggingDTO findLogging(); + void saveLogging(LoggingDTO reqDTO); + void modifyLogging(LoggingDTO reqDTO); +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/service/LoggingService.java b/mens-api/src/main/java/kr/xit/core/biz/service/LoggingService.java new file mode 100644 index 0000000..09599d0 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/service/LoggingService.java @@ -0,0 +1,60 @@ +package kr.xit.core.biz.service; + +import java.util.List; + +import javax.transaction.Transactional; + +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.springframework.stereotype.Service; + +import kr.xit.core.biz.mapper.ILoggingMapper; +import kr.xit.core.biz.model.LoggingDTO; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.core.biz.service
+ * fileName    : LoggingServiceImpl
+ * author      : limju
+ * date        : 2023-05-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-11    limju       최초 생성
+ *
+ * 
+ */ + +@Slf4j +@Service +public class LoggingService extends EgovAbstractServiceImpl implements ILoggingService{ + private final ILoggingMapper mapper; + + public LoggingService(ILoggingMapper mapper) { + this.mapper = mapper; + } + + @Transactional + @Override + public void saveLogging(final LoggingDTO reqDTO){ + mapper.saveLogging(reqDTO); + } + + @Transactional + @Override + public void modifyLogging(final LoggingDTO reqDTO){ + mapper.updateLogging(reqDTO); + } + + @Override + public List findLogging(final LoggingDTO reqDTO) { + return mapper.selectLogging(reqDTO); + } + + @Override + public LoggingDTO findLogging() { + return mapper.selectLogging(); + } +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/web/AuthApiController.java b/mens-api/src/main/java/kr/xit/core/biz/web/AuthApiController.java new file mode 100644 index 0000000..41647f8 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/web/AuthApiController.java @@ -0,0 +1,184 @@ +package kr.xit.core.biz.web; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import egovframework.com.cmm.EgovMessageSource; +import egovframework.com.cmm.LoginVO; +import egovframework.com.cmm.jwt.config.EgovJwtTokenUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.core.biz.service.IAuthApiService; +import kr.xit.core.consts.Constants; +import lombok.RequiredArgsConstructor; + +/** + *
+ * description : 인증 로그인 처리
+ *
+ * packageName : kr.xit.biz.auth
+ * fileName    : AuthApiController
+ * author      : limju
+ * date        : 2023-04-26
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-26    limju       최초 생성
+ *
+ * 
+ * @see + */ +//FIXME::세션에서 인증 관리 할지 여부 결정 필요 +@Tag(name = "AuthApiController", description = "인증 관리") +@RequiredArgsConstructor +@RestController +@RequestMapping(value = "/api/core/auth") +public class AuthApiController { + @Value("${app.token.saveType:header}") + private String authSaveType; + + /** EgovLoginService */ + + private final IAuthApiService loginService; + + /** EgovMessageSource */ + + private final EgovMessageSource egovMessageSource; + private final EgovJwtTokenUtil egovJwtTokenUtil; + + /** + * 일반 로그인을 처리한다 + * @param loginVO 아이디, 비밀번호가 담긴 LoginVO + * @param request 세션처리를 위한 HttpServletRequest + * @return 로그인결과(세션정보) + */ + @Operation(summary = "로그인" , description = "로그인") + @io.swagger.v3.oas.annotations.parameters.RequestBody( + required = true, + content = { + @Content( + mediaType = "application/json", + examples = { + @ExampleObject( + value = "{\"id\":\"admin\",\"password\":\"1\",\"userSe\":\"USR\"}") + } + ) + } + ) + @PostMapping(value = "/login", consumes = {MediaType.APPLICATION_JSON_VALUE , MediaType.TEXT_HTML_VALUE}) + public ApiResponseDTO login(@RequestBody final LoginVO loginVO, HttpServletRequest request) { + // 1. 일반 로그인 처리 + LoginVO loginResultVO = loginService.actionLogin(loginVO); + + if (loginResultVO != null && loginResultVO.getId() != null && !loginResultVO.getId().equals("")) { + request.getSession().setAttribute(Constants.AuthSaveSession.LOGIN_VO.getCode(), loginResultVO); + return ApiResponseDTO.success(loginResultVO); + } + return ApiResponseDTO.success(egovMessageSource.getMessage("fail.common.login")); + } + + @Operation(summary = "로그인(JWT)" , description = "로그인(JWT)") + @io.swagger.v3.oas.annotations.parameters.RequestBody( + required = true, + content = { + @Content( + mediaType = "application/json", + examples = { + @ExampleObject( + name = "admin", + description = "admin", + value = "{\"id\":\"admin\",\"password\":\"1\",\"userSe\":\"USR\"}"), + @ExampleObject( + name = "admin1", + description = "admin1", + value = "{\"id\":\"admin1\",\"password\":\"1\",\"userSe\":\"USR\"}"), + @ExampleObject( + name = "admin2", + description = "admin2", + value = "{\"id\":\"admin2\",\"password\":\"1\",\"userSe\":\"USR\"}") + } + ) + } + ) + @PostMapping(value = "/loginJwt") + public ApiResponseDTO loginJWT(@RequestBody final LoginVO loginVO, HttpServletRequest request) { + HashMap resultMap = new HashMap(); + + // 1. 일반 로그인 처리 + LoginVO loginResultVO = loginService.actionLogin(loginVO); + + if (loginResultVO != null && loginResultVO.getId() != null && !loginResultVO.getId().equals("")) { + + Map claimsMap = new HashMap<>(); + // claimsMap.put("dkkdk", "kdkkdkdkd"); + String jwtToken = egovJwtTokenUtil.generateToken(loginVO, claimsMap); + // String jwtToken = egovJwtTokenUtil.generateToken(loginVO.getId()); + + String username = egovJwtTokenUtil.getUsernameFromToken(jwtToken); + + + // System.out.println("Dec jwtToken username = "+username); + + //서버사이드 권한 체크 통과를 위해 삽입 + //EgovUserDetailsHelper.isAuthenticated() 가 그 역할 수행. DB에 정보가 없으면 403을 돌려 줌. 로그인으로 튕기는 건 프론트 쪽에서 처리 + request.getSession().setAttribute(Constants.AuthSaveSession.LOGIN_VO.getCode(), loginResultVO); + + + + + // UsernamePasswordAuthenticationToken authenticationToken = jwtTokenProvider.toAuthentication(loginVO.getId(), loginVO.getPassword()); + // Authentication authentication = authenticationManager.authenticate(authenticationToken); + // + // + // // Authentication 저장 + // if(Objects.equals(authSaveType, Constants.AuthSaveType.SECURITY.getCode())){ + // // TODO :: SessionCreationPolicy.STATELESS 인 경우 사용 불가 + // SecurityContextHolder.getContext().setAuthentication(authentication); + // + // }else if(Objects.equals(authSaveType, Constants.AuthSaveType.SESSION.getCode())){ + // session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext()); + // } + + //Map infoMap = new HashMap<>(); + //infoMap.put(Constants.JwtToken.TOKEN_USER_ID.getCode(), loginVO.getId()); + //infoMap.put(Constants.JwtToken.TOKEN_USER_MAIL.getCode(), loginVO.getEmail()); + + //String jwtToken = jwtTokenProvider.generateJwtAccessToken(authentication, infoMap); + //String jwtToken = jwtTokenProvider.generateJwtAccessToken(loginVO.getId(), "ROLE_USER"); + + resultMap.put("resultVO", loginResultVO); + resultMap.put("token", jwtToken); + return ApiResponseDTO.success(resultMap); + + } + return ApiResponseDTO.error(egovMessageSource.getMessage("fail.common.login") ); + } + + /** + * 로그아웃한다. + * @return resultVO + * @exception Exception + */ + @Operation(summary = "logout" , description = "로그아웃") + @GetMapping(value = "/logout") + public ApiResponseDTO actionLogoutJSON(HttpServletRequest request) { + + RequestContextHolder.currentRequestAttributes().removeAttribute(Constants.AuthSaveSession.LOGIN_VO.getCode(), RequestAttributes.SCOPE_SESSION); + return ApiResponseDTO.empty(); + } +} diff --git a/mens-api/src/main/java/kr/xit/core/biz/web/LoggingController.java b/mens-api/src/main/java/kr/xit/core/biz/web/LoggingController.java new file mode 100644 index 0000000..e11de9d --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/biz/web/LoggingController.java @@ -0,0 +1,60 @@ +package kr.xit.core.biz.web; + +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.core.biz.model.LoggingDTO; +import kr.xit.core.biz.service.ILoggingService; +import lombok.RequiredArgsConstructor; + +/** + *
+ * description : API Logging cotroller
+ *
+ * packageName : kr.xit.core.biz.web
+ * fileName    : LoggingController
+ * author      : limju
+ * date        : 2023-05-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-11    limju       최초 생성
+ *
+ * 
+ */ +@Tag(name = "LoggingController", description = "API logging 관리") +@RequiredArgsConstructor +@RestController +@RequestMapping(value = "/api/core/log") +public class LoggingController { + + private final ILoggingService loggingService; + + @Operation(summary = "로깅 정보 저장" , description = "로깅 정보 저장") + @io.swagger.v3.oas.annotations.parameters.RequestBody( + required = true, + content = { + @Content( + mediaType = "application/json", + examples = { + @ExampleObject( + value = "{\"requestId\":\"7f1f5978fc1941c4a6d37351a6d692e6\"}") + } + ) + } + ) + @PostMapping(value = "/save", consumes = {MediaType.APPLICATION_JSON_VALUE , MediaType.TEXT_HTML_VALUE}) + public ApiResponseDTO saveLogging(@RequestBody final LoggingDTO reqDTO){ + loggingService.saveLogging(reqDTO); + return ApiResponseDTO.empty(); + } +} diff --git a/mens-api/src/main/java/kr/xit/core/spring/config/CustomBatchConfigurer.java b/mens-api/src/main/java/kr/xit/core/spring/config/CustomBatchConfigurer.java new file mode 100644 index 0000000..073c867 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/spring/config/CustomBatchConfigurer.java @@ -0,0 +1,55 @@ +package kr.xit.core.spring.config; + +import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + *
+ * description : Spring batch Meta table 비활성 : 비 권고
+ *               @EnableBatchProcessing -> BatchConfigurationSelector import
+ *               BatchConfigurationSelector -> SimpleBatchConfiguration 반환
+ *               SimpleBatchConfiguration -> BatchConfigurer 타입의 빈을 조회하여 사용
+ *               - jobRepository, jobLauncher, transactionManager, jobRegistry, jobExplorer 인스턴스 생성
+ *               - @Bean 어노테이션에 의해 빈으로 등록
+ *               - 생성되는 인스턴스는 DefaultBatchConfigurer에 정의
+ *               메타테이블 비활성을 위해
+ *               - DefaultBatchConfigurer 상속
+ *               - EnableBatchProcessing 지정
+ *               - setDatasource 메소드 override
+ *
+ * fileName    : CustomBatchConfigurer
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ */ +@Configuration +@EnableBatchProcessing +@EnableScheduling +public class CustomBatchConfigurer extends DefaultBatchConfigurer { + +/* + @Override + public void setDataSource(DataSource dataSource) { + // Do nothing + } +*/ + /** + * FIXME::JPA 사용시 추가 + * @param emf + * @return + */ + // @Bean + //@Primary // SimpleBatchConfiguration 에서 생성된 빈 대체 + // public PlatformTransactionManager jpaTransactionManager(EntityManagerFactory emf) { + // return new JpaTransactionManager(emf); + // } + + +} diff --git a/mens-api/src/main/java/kr/xit/core/spring/config/SpringDocsApiConfig.java b/mens-api/src/main/java/kr/xit/core/spring/config/SpringDocsApiConfig.java new file mode 100644 index 0000000..a6b6573 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/spring/config/SpringDocsApiConfig.java @@ -0,0 +1,90 @@ +package kr.xit.core.spring.config; + +import org.springdoc.core.GroupedOpenApi; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *
+ * description : Springdoc(swagger) 설정
+ *               설정내용이 상이한 경우 동일한 파일로 재정의 하거나 상속받아 사용
+ * packageName : kr.xit.core.spring.config
+ * fileName    : SpringDocsConfig
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ */ +@ConditionalOnProperty(value = "springdoc.swagger-ui.enabled", havingValue = "true", matchIfMissing = false) +@Configuration +public class SpringDocsApiConfig { + @Bean + public GroupedOpenApi authentification() { + return GroupedOpenApi.builder() + .group("1. Core API") + .pathsToMatch( + "/api/core/**" + ) + .build(); + } + + @Bean + public GroupedOpenApi kakaopayEltrcDoc() { + return GroupedOpenApi.builder() + .group("2. 카카오페이 MyDoc API") + .pathsToMatch( + "/api/kakaopay/v1/**", + "/api/kakaopay/v2/**" + ) + .build(); + } + + @Bean + public GroupedOpenApi kakaopayEltrcDocBatch() { + return GroupedOpenApi.builder() + .group("3. 전자고지 통합발송 연계 Batch WEB") + .pathsToMatch( + "/api/batch/v1/**" + ) + .build(); + } + + @Bean + public GroupedOpenApi bizDoc() { + return GroupedOpenApi.builder() + .group("4. 전자고지 통합발송 연계 API") + .pathsToMatch( + "/api/ens/v1/**" + ) + .build(); + } + + @Bean + public GroupedOpenApi pniDoc() { + return GroupedOpenApi.builder() + .group("6. 사전고지 API") + .pathsToMatch( + "/api/pni/v1/**" + ) + .build(); + } + + + @Bean + public GroupedOpenApi kakaopayDummyTest() { + return GroupedOpenApi.builder() + .group("9. 카카오페이 MyDoc dummy 테스트") + .pathsToMatch( + "/api/kakaopay/test/**", + "/api/sample/**" + ) + .build(); + } + + +} diff --git a/mens-api/src/main/java/kr/xit/core/spring/config/WebMvcConfig.java b/mens-api/src/main/java/kr/xit/core/spring/config/WebMvcConfig.java new file mode 100644 index 0000000..e0d13be --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/spring/config/WebMvcConfig.java @@ -0,0 +1,216 @@ +package kr.xit.core.spring.config; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.web.filter.CommonsRequestLoggingFilter; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import kr.xit.core.consts.Constants; +import kr.xit.core.spring.config.auth.AuthentificationInterceptor; +import kr.xit.core.spring.config.properties.CorsProperties; +import kr.xit.core.spring.filter.LoggingFilter; +import kr.xit.core.spring.filter.ReadableRequestWrapperFilter; +import kr.xit.core.spring.filter.SimpleCORSFilter; +import kr.xit.core.spring.resolver.CustomArgumentResolver; +import kr.xit.core.spring.resolver.PageableArgumentResolver; +import lombok.RequiredArgsConstructor; + +/** + *
+ * description : Spring MVC 설정
+ *               - filter, interceptor
+ *               - AuthentificationInterceptor : 인증처리
+ *               - CommonsRequestLoggingFilter : request logging
+ *               - ReadableRequestWrapperFilter : post logging 처리를 위한 필터
+ *               - SimpleCORSFilter : cors
+ * packageName : kr.xit.core.spring.config
+ * fileName    : WebMvcConfig
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ * @see AuthentificationInterceptor + * @see CommonsRequestLoggingFilter + * @see ReadableRequestWrapperFilter + * @see SimpleCORSFilter + * @see LoggingFilter + */ +@RequiredArgsConstructor +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + /** + * logging exclude path + */ + @Value("${app.param.log.exclude-patterns}") + private List EXCLUDE_URL_REGEXS; + + private final CorsProperties corsProperties; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new AuthentificationInterceptor()) + .addPathPatterns("/**/*") + .excludePathPatterns( + "/api/core/*" + // "/api/v1/kakaopay/*" + ); + } + + // ------------------------------------------------------------- + // RequestMappingHandlerMapping 설정 View Controller 추가 + // ------------------------------------------------------------- + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/cmmn/validator.do") + .setViewName("cmmn/validator"); + registry.addViewController("/").setViewName("forward:/index.html"); + } + + //TODO :: ArgumentResolver add + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(new CustomArgumentResolver()); + resolvers.add(new PageableArgumentResolver()); + //WebMvcConfigurer.super.addArgumentResolvers(resolvers); + } + + /** + * CommonsRequestLoggingFiler 등록 + * app.param.log.enabled: true시 로그 출력 + * @return + */ + @ConditionalOnProperty(value = "app.param.log.enabled", havingValue = "true", matchIfMissing = false) + @Bean + public FilterRegistrationBean requestLoggingFilter() { + CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter(){ + @Override + protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + String path = request.getServletPath(); + return EXCLUDE_URL_REGEXS.stream().anyMatch(regex -> path.matches(regex)); + } + }; + loggingFilter.setIncludeClientInfo(true); + loggingFilter.setIncludeHeaders(false); + loggingFilter.setBeforeMessagePrefix("\n//========================== Request(Before) ================================\n"); + loggingFilter.setBeforeMessageSuffix("\n//==========================================================================="); + + loggingFilter.setIncludeQueryString(true); + loggingFilter.setIncludePayload(true); + loggingFilter.setMaxPayloadLength(1024* 1024); + loggingFilter.setAfterMessagePrefix("\n//=========================== Request(After) ================================\n"); + loggingFilter.setAfterMessageSuffix("\n//==========================================================================="); + + FilterRegistrationBean bean = new FilterRegistrationBean<>(loggingFilter); + bean.setOrder(Ordered.HIGHEST_PRECEDENCE); + bean.addUrlPatterns(Constants.API_URL_PATTERNS); + return bean; + } + + /** + * exclude pattern 지정 + * @return FilterRegistrationBean + */ + @ConditionalOnProperty(value = "app.param.log.custom.enabled", havingValue = "true", matchIfMissing = false) + @Bean + public FilterRegistrationBean loggingFilter() { + Map initMap = new HashMap<>(); + initMap.put("excludedUrls", StringUtils.join(EXCLUDE_URL_REGEXS,",")); + + FilterRegistrationBean frb = new FilterRegistrationBean<>(new LoggingFilter()); + frb.setOrder(1); + frb.addUrlPatterns(Constants.API_URL_PATTERNS); + frb.setInitParameters(initMap); + frb.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR, DispatcherType.ASYNC); + + return frb; + } + + /** + * Post 요청시 request(stream) logging 처리를 위한 필터 + * @return + */ + @ConditionalOnProperty(value = "app.param.log.custom.enabled", havingValue = "true", matchIfMissing = false) + @Bean + public FilterRegistrationBean readableRequestWrapperFilter() { + ReadableRequestWrapperFilter readableRequestWrapperFilter = new ReadableRequestWrapperFilter(); + + FilterRegistrationBean bean = new FilterRegistrationBean(readableRequestWrapperFilter); + bean.setOrder(Ordered.HIGHEST_PRECEDENCE); + bean.addUrlPatterns(Constants.API_URL_PATTERNS); + return bean; + } + + /** + * CORS Filter 등록 + * @return + */ + @Bean + public FilterRegistrationBean simpleCorsFilter() { + + SimpleCORSFilter corsFilter = new SimpleCORSFilter(); + FilterRegistrationBean bean = new FilterRegistrationBean(corsFilter); + bean.setOrder(Ordered.LOWEST_PRECEDENCE); + bean.addUrlPatterns(Constants.API_URL_PATTERNS); + return bean; + + // CorsConfiguration config = new CorsConfiguration(); + // config.setAllowedOrigins(Collections.singletonList(corsProperties.getAllowedOrigins())); + // config.setAllowedMethods(Collections.singletonList(corsProperties.getAllowedMethods())); + // config.setAllowedHeaders(Collections.singletonList(corsProperties.getAllowedHeaders())); + // config.setAllowCredentials(corsProperties.getAllowCredentials()); + // config.setMaxAge(corsProperties.getMaxAge()); + // config.setExposedHeaders(Collections.singletonList(corsProperties.getExposeHeader())); + // + // UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + // source.registerCorsConfiguration("/**", config); + // + // FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); + // bean.setOrder(Ordered.HIGHEST_PRECEDENCE); + // return bean; + + } + + + // @Override + // public void addCorsMappings(CorsRegistry registry) { + // //WebMvcConfigurer.super.addCorsMappings(registry); + // registry.addMapping("/**") + // .allowedOrigins(String.valueOf(Collections.singletonList(corsProperties.getAllowedOrigins()))) + // .allowedMethods(String.valueOf(Collections.singletonList(corsProperties.getAllowedMethods()))); + // } + + +// /** +// * HandlerExceptionResolver 를 상속받은 resolver 등록 +// * @param resolvers the list of configured resolvers to extend +// */ +// @Override +// public void extendHandlerExceptionResolvers(List resolvers) { +// HandlerExceptionResolver exceptionHandlerExceptionResolver = resolvers.stream().filter(x -> x instanceof ExceptionHandlerExceptionResolver).findAny().get(); +// int index = resolvers.indexOf(exceptionHandlerExceptionResolver); +// resolvers.add(index, new CustomRuntimeResolver()); +// WebMvcConfigurer.super.extendHandlerExceptionResolvers(resolvers); +// } + +} diff --git a/mens-api/src/main/java/kr/xit/core/spring/config/support/DatasourceConfig.java b/mens-api/src/main/java/kr/xit/core/spring/config/support/DatasourceConfig.java new file mode 100644 index 0000000..290867b --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/spring/config/support/DatasourceConfig.java @@ -0,0 +1,73 @@ +package kr.xit.core.spring.config.support; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import kr.xit.core.consts.Constants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy; + +import javax.sql.DataSource; + +//FIXME :: 재설정이 필요한 경우 해당 프로젝트에 동일한 파일로 재정의 하여 사용 +/** + *
+ * description : Datasource 설정 - FIXME:: spring.datasource 설정이 있는 경우만 loading
+ *               - 조건 : spring.datasource
+ *               실제 필요한 경우만 커넥션을 점유하도록
+ *               LazyConnectionDataSourceProxy 사용
+ *               일반 Datasource 사용시
+ *               - Spring은 트랜잭션에 진입시 데이타 소스의 커넥션을 get
+ *               - ehcache, hibernate 영속성 컨택슽트 1차캐시 등에도 커넥션을 get
+ *               - multi-datasource 에서 트랜잭션 진입 이후 datasource 분기 불가
+ * packageName : kr.xit.core.spring.config.support
+ * fileName    : DatasourceConfig
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ * @see PrimaryMybatisConfig + */ +//@ConditionalOnProperty(value = "spring", havingValue = "datasource", matchIfMissing = false) +@Slf4j +@Configuration +public class DatasourceConfig { + @Bean(name = "primaryHikariConfig") + @Primary + @ConfigurationProperties(prefix = "spring.datasource.hikari.maria") + public HikariConfig primaryHikariConfig() { + // HikariConfig hikariConfig = new HikariConfig("spring.datasource.hikari"); + // hikariConfig.setAutoCommit(false); + // return hikariConfig; + return new HikariConfig(); + } + + @Bean(Constants.PRIMARY_DATA_SOURCE) + @Primary + public DataSource primaryDataSource() throws Exception{ + //return new LazyConnectionDataSourceProxy(new HikariDataSource(primaryHikariConfig())); + return new HikariDataSource(primaryHikariConfig()); + } + + @Bean(name = "secondaryHikariConfig") + @ConfigurationProperties(prefix = "spring.datasource.hikari.oracle") + public HikariConfig secondaryHikariConfig() { + // HikariConfig hikariConfig = new HikariConfig("spring.datasource.hikari"); + // hikariConfig.setAutoCommit(false); + // return hikariConfig; + return new HikariConfig(); + } + + @Bean(name = Constants.SECONDARY_DATA_SOURCE) + public DataSource secondaryDataSource() throws Exception{ + //return new LazyConnectionDataSourceProxy(new HikariDataSource(secondaryHikariConfig())); + return new HikariDataSource(secondaryHikariConfig()); + } +} diff --git a/mens-api/src/main/java/kr/xit/core/spring/config/support/PrimaryMybatisConfig.java b/mens-api/src/main/java/kr/xit/core/spring/config/support/PrimaryMybatisConfig.java new file mode 100644 index 0000000..f735cb8 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/spring/config/support/PrimaryMybatisConfig.java @@ -0,0 +1,76 @@ +package kr.xit.core.spring.config.support; + +import kr.xit.core.consts.Constants; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.SqlSessionTemplate; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.*; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.support.lob.DefaultLobHandler; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; + +//FIXME :: 재설정이 필요한 경우 해당프로젝트에 동일한 파일로 재정의 하여 사용 +/** + *
+ * description : Mybatis 설정 - FIXME:: @DependsOn(value = {"dataSource"}) loading
+ *               - 조건 : @DependsOn(value = {"dataSource"})
+ * packageName : kr.xit.core.spring.config.support
+ * fileName    : MybatisConfig
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ * @see DatasourceConfig + */ +@Slf4j +@Configuration +@MapperScan( + basePackages = { + "kr.xit.core.biz.mapper", + "kr.xit.core.biz.batch.mapper", + "kr.xit.biz.ens.mapper", + "kr.xit.biz.pni.mapper", + "kr.xit.biz.sample.mapper", + }, + sqlSessionFactoryRef = Constants.PRIMARY_SQL_SESSION +) +public class PrimaryMybatisConfig { + + static final String MYBATIS_CONFIG_FILE = "classpath:/egovframework/mapper/mapper-config.xml"; + + @ConditionalOnMissingBean + @Bean + @Lazy + public DefaultLobHandler lobHandler() { + return new DefaultLobHandler(); + } + + @Primary + @Bean(name = Constants.PRIMARY_SQL_SESSION) + public SqlSessionFactory primarySqlSession(@Qualifier(Constants.PRIMARY_DATA_SOURCE)DataSource dataSource) throws Exception { + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); + sessionFactory.setDataSource(dataSource); + sessionFactory.setConfigLocation(resolver.getResource(MYBATIS_CONFIG_FILE)); + sessionFactory.setMapperLocations(resolver.getResources("classpath:/egovframework/mapper/**/*-mysql-mapper.xml")); + return sessionFactory.getObject(); + } + + @Primary + @Bean(name = "primarySqlSessionTemplate") + public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier(Constants.PRIMARY_SQL_SESSION) SqlSessionFactory sqlSessionFactory) { + return new SqlSessionTemplate(sqlSessionFactory); + } +} diff --git a/mens-api/src/main/java/kr/xit/core/spring/config/support/RestResponseErrorHandler.java b/mens-api/src/main/java/kr/xit/core/spring/config/support/RestResponseErrorHandler.java new file mode 100644 index 0000000..4f339a6 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/spring/config/support/RestResponseErrorHandler.java @@ -0,0 +1,53 @@ +package kr.xit.core.spring.config.support; + +import kr.xit.core.exception.BizRuntimeException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.web.client.DefaultResponseErrorHandler; + +import java.io.IOException; + +/** + *
+ * description : RestTemplate Error Handler
+ * packageName : kr.xit.core.spring.config.support
+ * fileName    : RestResponseErrorHandler
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ * @see kr.xit.core.spring.config.RestTemplateConfig#restTemplate + */ +@Slf4j +public class RestResponseErrorHandler extends DefaultResponseErrorHandler { + + @Override + public void handleError(ClientHttpResponse response) throws IOException { + String seriesName = "UNKNOWN_ERROR"; + + if (response.getStatusCode() == HttpStatus.PRECONDITION_FAILED) { + seriesName = String.valueOf(HttpStatus.PRECONDITION_FAILED.value()); + + }else if (response.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR) { + // handle SERVER_ERROR + seriesName = HttpStatus.Series.SERVER_ERROR.name(); + + } else if (response.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR) { + // handle CLIENT_ERROR + seriesName = HttpStatus.Series.CLIENT_ERROR.name(); + if (response.getStatusCode() == HttpStatus.NOT_FOUND) { + seriesName = String.valueOf(HttpStatus.NOT_FOUND.value()); + } + } + log.error("========================== RestResponse Error =============================="); + log.error("SERIES NAME : {}", seriesName); + log.error("STATUS CODE : {}", response.getStatusCode()); + log.error("============================================================================="); + //throw BizRuntimeException.create(String.valueOf(response.getStatusCode().value()), null); + } +} diff --git a/mens-api/src/main/java/kr/xit/core/spring/config/support/SecondaryMybatisConfig.java b/mens-api/src/main/java/kr/xit/core/spring/config/support/SecondaryMybatisConfig.java new file mode 100644 index 0000000..808e8f0 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/spring/config/support/SecondaryMybatisConfig.java @@ -0,0 +1,66 @@ +package kr.xit.core.spring.config.support; + +import kr.xit.core.consts.Constants; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.SqlSessionTemplate; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Primary; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.support.lob.DefaultLobHandler; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; + +//FIXME :: 재설정이 필요한 경우 해당프로젝트에 동일한 파일로 재정의 하여 사용 + +/** + *
+ * description : Mybatis 설정 - FIXME:: @DependsOn(value = {"dataSource"}) loading
+ *               - 조건 : @DependsOn(value = {"dataSource"})
+ * packageName : kr.xit.core.spring.config.support
+ * fileName    : MybatisConfig
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ * @see DatasourceConfig + */ +@Slf4j +@Configuration +@MapperScan( + basePackages = {"kr.xit.biz.sms.mapper"}, + sqlSessionFactoryRef = Constants.SECONDARY_SQL_SESSION +) +public class SecondaryMybatisConfig { + + static final String MYBATIS_CONFIG_FILE = "classpath:/egovframework/mapper/mapper-config.xml"; + + + @Bean(name = Constants.SECONDARY_SQL_SESSION) + public SqlSessionFactory secondarySqlSession(@Qualifier(Constants.SECONDARY_DATA_SOURCE) DataSource dataSource) throws Exception { + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); + sessionFactory.setDataSource(dataSource); + sessionFactory.setConfigLocation(resolver.getResource(MYBATIS_CONFIG_FILE)); + sessionFactory.setMapperLocations(resolver.getResources("classpath:/egovframework/mapper/**/*-oracle-mapper.xml")); + return sessionFactory.getObject(); + } + + @Bean(name = "secondarySqlSessionTemplate") + public SqlSessionTemplate secondarySqlSessionTemplate(@Qualifier(Constants.SECONDARY_SQL_SESSION) SqlSessionFactory sqlSessionFactory) { + return new SqlSessionTemplate(sqlSessionFactory); + } +} diff --git a/mens-api/src/main/java/kr/xit/core/spring/config/support/TransactionConfig.java b/mens-api/src/main/java/kr/xit/core/spring/config/support/TransactionConfig.java new file mode 100644 index 0000000..9ab3c26 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/spring/config/support/TransactionConfig.java @@ -0,0 +1,64 @@ +package kr.xit.core.spring.config.support; + +import kr.xit.core.consts.Constants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.transaction.ChainedTransactionManager; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; + +//FIXME :: 재설정이 필요한 경우 해당프로젝트에 동일한 파일로 재정의 하여 사용 + +/** + *
+ * description : Mybatis 설정 - FIXME:: @DependsOn(value = {"dataSource"}) loading
+ *               - 조건 : @DependsOn(value = {"dataSource"})
+ * packageName : kr.xit.core.spring.config.support
+ * fileName    : MybatisConfig
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ * @see DatasourceConfig + */ +@Slf4j +@Configuration +@EnableTransactionManagement +public class TransactionConfig { + //////////////////////////////////////////////////////////////////////////////////////////// + // ChainedTransactionManager : trsnsaction binding + /////////////////////////////////////////////////////////////////////////////////////////// + /** + * mariaDB & oracleDB Transaction binding + * @param mariaDS Maria DataSource + * @param oracleDS Oracle DataSource + * @return PlatformTransactionManager + * @throws Exception Exception + */ + @Primary + @Bean + public PlatformTransactionManager transactionManager(@Qualifier(Constants.PRIMARY_DATA_SOURCE)DataSource mariaDS, + @Qualifier(Constants.SECONDARY_DATA_SOURCE)DataSource oracleDS) { + DataSourceTransactionManager mariaTm = new DataSourceTransactionManager(mariaDS); + mariaTm.setGlobalRollbackOnParticipationFailure(false); + mariaTm.setNestedTransactionAllowed(true); + + DataSourceTransactionManager oracleTm = new DataSourceTransactionManager(oracleDS); + oracleTm.setGlobalRollbackOnParticipationFailure(false); + oracleTm.setNestedTransactionAllowed(true); + + // creates chained transaction manager + return new ChainedTransactionManager(mariaTm, oracleTm); + } + ///////////////////////////////////////////////////////////////////////////////////// +} diff --git a/mens-api/src/main/java/kr/xit/core/spring/util/ApiSpringUtils.java b/mens-api/src/main/java/kr/xit/core/spring/util/ApiSpringUtils.java new file mode 100644 index 0000000..81618b8 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/core/spring/util/ApiSpringUtils.java @@ -0,0 +1,66 @@ +package kr.xit.core.spring.util; + +import kr.xit.biz.ens.service.ISendMessageLinkService; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.core.env.Environment; + +import egovframework.com.cmm.jwt.config.EgovJwtTokenUtil; +import egovframework.com.cmm.jwt.config.JwtVerification; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.service.ILoggingService; +import kr.xit.core.spring.config.properties.CorsProperties; +import kr.xit.core.spring.config.support.ApplicationContextProvider; +import kr.xit.core.support.slack.SlackWebhookPush; + +/** + *
+ * description : Get Bean Object
+ *               Filter / Interceptor 등에서 Bean 사용시 필요
+ *               (Bean으로 등록되는 클래스 내에서만 @Autowired / @Resource 등이 동작)
+ * packageName : kr.xit.core.spring.util
+ * fileName    : SpringUtils
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ * @see ApplicationContextProvider + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ApiSpringUtils { + + public static ApplicationContext getApplicationContext() { + return ApplicationContextProvider.getApplicationContext(); + } + + public static boolean containsBean(String beanName) { + return getApplicationContext().containsBean(beanName); + } + + public static Object getBean(String beanName) { + return getApplicationContext().getBean(beanName); + } + + public static Object getBean(Class clazz) { + return getApplicationContext().getBean(clazz); + } + + public static IBatchCmmService getBatchCmmService(){ + return (IBatchCmmService)getBean(IBatchCmmService.class); + } + + public static ISendMessageLinkService getSendMessageLinkService(){ + return (ISendMessageLinkService)getBean(ISendMessageLinkService.class); + } + + public static SlackWebhookPush getSlackWebhookPush(){ + return (SlackWebhookPush)getBean(SlackWebhookPush.class); + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/job/EnsCctvAcceptJobConfg.java b/mens-api/src/main/java/kr/xit/ens/support/batch/job/EnsCctvAcceptJobConfg.java new file mode 100644 index 0000000..8587e3a --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/job/EnsCctvAcceptJobConfg.java @@ -0,0 +1,113 @@ +package kr.xit.ens.support.batch.job; + +import kr.xit.biz.ens.service.IEnsCctvFileService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.listener.NoWorkFoundStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import kr.xit.ens.support.batch.task.EnsCctvAcceptTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *
+ * description : 전자 고지 CCTV accept
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : EnsCctvAcceptJobConfg
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class EnsCctvAcceptJobConfg { + private static final String JOB_NAME = "EnsCctvAcceptJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final IEnsCctvFileService service; + + @Bean(name = JOB_NAME) + public Job ensCctvAcceptJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + .listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + //.from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new EnsCctvAcceptTasklet(service)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/job/EnsCctvFileJobConfg.java b/mens-api/src/main/java/kr/xit/ens/support/batch/job/EnsCctvFileJobConfg.java new file mode 100644 index 0000000..eb0e0f3 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/job/EnsCctvFileJobConfg.java @@ -0,0 +1,113 @@ +package kr.xit.ens.support.batch.job; + +import kr.xit.biz.ens.service.IEnsCctvFileService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.listener.NoWorkFoundStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import kr.xit.ens.support.batch.task.EnsCctvFileTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *
+ * description : 전자 고지 CCTV 파일 처리(서광)
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : EnsCctvFileJobConfg
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class EnsCctvFileJobConfg { + private static final String JOB_NAME = "EnsCctvFileJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final IEnsCctvFileService service; + + @Bean(name = JOB_NAME) + public Job ensCctvFileJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + .listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + //.from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new EnsCctvFileTasklet(service)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/job/PniCctvAcceptJobConfg.java b/mens-api/src/main/java/kr/xit/ens/support/batch/job/PniCctvAcceptJobConfg.java new file mode 100644 index 0000000..b02fdf6 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/job/PniCctvAcceptJobConfg.java @@ -0,0 +1,114 @@ +package kr.xit.ens.support.batch.job; + +import kr.xit.biz.pni.service.IPniCctvFileService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.listener.NoWorkFoundStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import kr.xit.ens.support.batch.task.PniCctvAcceptTasklet; +import kr.xit.ens.support.batch.task.PniCctvFileTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *
+ * description : 사전고지 CCTV accept
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : PniCctvAcceptJobConfg
+ * author      : limju
+ * date        : 2023-07-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-11    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class PniCctvAcceptJobConfg { + private static final String JOB_NAME = "PniCctvAcceptJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final IPniCctvFileService service; + + @Bean(name = JOB_NAME) + public Job pniCctvAcceptJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + .listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + //.from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new PniCctvAcceptTasklet(service)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/job/PniCctvFileJobConfg.java b/mens-api/src/main/java/kr/xit/ens/support/batch/job/PniCctvFileJobConfg.java new file mode 100644 index 0000000..4193ea4 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/job/PniCctvFileJobConfg.java @@ -0,0 +1,113 @@ +package kr.xit.ens.support.batch.job; + +import kr.xit.biz.pni.service.IPniCctvFileService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.listener.NoWorkFoundStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import kr.xit.ens.support.batch.task.PniCctvFileTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *
+ * description : 사전고지 CCTV 파일 처리(서광)
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : PniCctvFileJobConfg
+ * author      : limju
+ * date        : 2023-07-10
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-10    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class PniCctvFileJobConfg { + private static final String JOB_NAME = "PniCctvFileJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final IPniCctvFileService service; + + @Bean(name = JOB_NAME) + public Job pniCctvFileJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + .listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + //.from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new PniCctvFileTasklet(service)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/job/SndngAcceptJobConfig.java b/mens-api/src/main/java/kr/xit/ens/support/batch/job/SndngAcceptJobConfig.java new file mode 100644 index 0000000..acc4749 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/job/SndngAcceptJobConfig.java @@ -0,0 +1,113 @@ +package kr.xit.ens.support.batch.job; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import kr.xit.biz.ens.service.ISendMessageLinkService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import kr.xit.ens.support.batch.task.SndngAcceptTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : 문자 발송 시스템 연계 - 접수
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : SndngAcceptJobConfig
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class SndngAcceptJobConfig { + private static final String JOB_NAME = "SndngAcceptJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final ISendMessageLinkService linkService; + + @Bean(name = JOB_NAME) + public Job sndngAcceptJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + //.listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + //.from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new SndngAcceptTasklet(linkService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/job/SndngCloseJobConfig.java b/mens-api/src/main/java/kr/xit/ens/support/batch/job/SndngCloseJobConfig.java new file mode 100644 index 0000000..26f4ca9 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/job/SndngCloseJobConfig.java @@ -0,0 +1,114 @@ +package kr.xit.ens.support.batch.job; + +import kr.xit.biz.ens.service.ISendMessageLinkService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.listener.NoWorkFoundStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import kr.xit.ens.support.batch.task.SndngCloseTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : SndngCloseJobConfig
+ * author      : seojh
+ * date        : 2023-06-14
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-14    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class SndngCloseJobConfig { + private static final String JOB_NAME = "SndngCloseJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final ISendMessageLinkService linkService; + + @Bean(name = JOB_NAME) + public Job sndngCloseJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + .listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + //.from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new SndngCloseTasklet(linkService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/job/SndngMakeJobConfig.java b/mens-api/src/main/java/kr/xit/ens/support/batch/job/SndngMakeJobConfig.java new file mode 100644 index 0000000..ce6ea33 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/job/SndngMakeJobConfig.java @@ -0,0 +1,114 @@ +package kr.xit.ens.support.batch.job; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import kr.xit.biz.ens.service.ISendMessageLinkService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.listener.NoWorkFoundStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import kr.xit.ens.support.batch.task.SndngMakeTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : 문자 발송 시스템 연계 - 전송 대상 생성
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : SndngMakeJobConfig
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class SndngMakeJobConfig { + private static final String JOB_NAME = "SndngMakeJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final ISendMessageLinkService linkService; + + @Bean(name = JOB_NAME) + public Job sndngMakeJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + .listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + .from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new SndngMakeTasklet(linkService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/job/SndngSnedBulksJobConfig.java b/mens-api/src/main/java/kr/xit/ens/support/batch/job/SndngSnedBulksJobConfig.java new file mode 100644 index 0000000..82cb9ea --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/job/SndngSnedBulksJobConfig.java @@ -0,0 +1,114 @@ +package kr.xit.ens.support.batch.job; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import kr.xit.biz.ens.service.ISendMessageLinkService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.listener.NoWorkFoundStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import kr.xit.ens.support.batch.task.SndngSendBulksTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : 문자 발송 시스템 연계 - 전송
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : SndngSnedBulksJobConfig
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class SndngSnedBulksJobConfig { + private static final String JOB_NAME = "SndngSendBulksJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final ISendMessageLinkService linkService; + + @Bean(name = JOB_NAME) + public Job sndngSendBulksJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + .listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + //.from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new SndngSendBulksTasklet(linkService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/job/SndngStatusBulksJobConfig.java b/mens-api/src/main/java/kr/xit/ens/support/batch/job/SndngStatusBulksJobConfig.java new file mode 100644 index 0000000..7dbff1c --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/job/SndngStatusBulksJobConfig.java @@ -0,0 +1,114 @@ +package kr.xit.ens.support.batch.job; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import kr.xit.biz.ens.service.ISendMessageLinkService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.listener.NoWorkFoundStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import kr.xit.ens.support.batch.task.SndngStatusBulksTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : 문자 발송 시스템 연계 - 상태조회
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : SndngStatusBulksJobConfig
+ * author      : limju
+ * date        : 2023-06-13
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-13    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class SndngStatusBulksJobConfig { + private static final String JOB_NAME = "SndngStatusBulksJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final ISendMessageLinkService linkService; + + @Bean(name = JOB_NAME) + public Job sndngStatusBulksJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + .listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + .from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new SndngStatusBulksTasklet(linkService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/rowmapper/LoggingRowMapper.java b/mens-api/src/main/java/kr/xit/ens/support/batch/rowmapper/LoggingRowMapper.java new file mode 100644 index 0000000..dc65f9a --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/rowmapper/LoggingRowMapper.java @@ -0,0 +1,33 @@ +package kr.xit.ens.support.batch.rowmapper; + + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.springframework.jdbc.core.RowMapper; + +import kr.xit.core.biz.model.LoggingDTO; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.batch.rowmapper
+ * fileName    : LoggingRowMapper
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ */ +public class LoggingRowMapper implements RowMapper { + @Override + public LoggingDTO mapRow(ResultSet rs, int rowNum) throws SQLException { + LoggingDTO dto = new LoggingDTO(); + dto.setRequestId(rs.getString("requestId")); + return dto; + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/EnsCctvAcceptJobScheduler.java b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/EnsCctvAcceptJobScheduler.java new file mode 100644 index 0000000..905585f --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/EnsCctvAcceptJobScheduler.java @@ -0,0 +1,67 @@ +package kr.xit.ens.support.batch.scheduler; + +import kr.xit.ens.support.batch.job.EnsCctvAcceptJobConfg; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + *
+ * description : 전자 고지 CCTV accept
+ *
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : EnsCctvAcceptJobScheduler
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class EnsCctvAcceptJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final EnsCctvAcceptJobConfg jobConfiguration; + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //FIXME : 전자 고지 CCTV 파일 처리(서광) 실행 간격 정의 + private final static int RUN_EXEC_MIS = 10000; + + //@Scheduled(initialDelay = RUN_EXEC_MIS, fixedDelay = RUN_EXEC_MIS) + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.ensCctvAcceptJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/EnsCctvFileJobScheduler.java b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/EnsCctvFileJobScheduler.java new file mode 100644 index 0000000..a110a03 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/EnsCctvFileJobScheduler.java @@ -0,0 +1,67 @@ +package kr.xit.ens.support.batch.scheduler; + +import kr.xit.ens.support.batch.job.EnsCctvFileJobConfg; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + *
+ * description : 전자 고지 CCTV 파일 처리(서광)
+ *
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : EnsCctvFileJobScheduler
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class EnsCctvFileJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final EnsCctvFileJobConfg jobConfiguration; + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //FIXME : 전자 고지 CCTV 파일 처리(서광) 실행 간격 정의 + private final static int RUN_EXEC_MIS = 10000; + + //@Scheduled(initialDelay = RUN_EXEC_MIS, fixedDelay = RUN_EXEC_MIS) + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.ensCctvFileJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/PniCctvAcceptJobScheduler.java b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/PniCctvAcceptJobScheduler.java new file mode 100644 index 0000000..9a8c54a --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/PniCctvAcceptJobScheduler.java @@ -0,0 +1,68 @@ +package kr.xit.ens.support.batch.scheduler; + +import kr.xit.ens.support.batch.job.PniCctvAcceptJobConfg; +import kr.xit.ens.support.batch.job.PniCctvFileJobConfg; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + *
+ * description : 사전고지 CCTV accept
+ *
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : PniCctvAcceptJobScheduler
+ * author      : limju
+ * date        : 2023-07-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-11    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class PniCctvAcceptJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final PniCctvAcceptJobConfg jobConfiguration; + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //FIXME : 사전고지 CCTV 파일 처리(서광) 실행 간격 정의 + private final static int RUN_EXEC_MIS = 10000; + + //@Scheduled(initialDelay = RUN_EXEC_MIS, fixedDelay = RUN_EXEC_MIS) + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.pniCctvAcceptJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/PniCctvFileJobScheduler.java b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/PniCctvFileJobScheduler.java new file mode 100644 index 0000000..cb60da7 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/PniCctvFileJobScheduler.java @@ -0,0 +1,67 @@ +package kr.xit.ens.support.batch.scheduler; + +import kr.xit.ens.support.batch.job.PniCctvFileJobConfg; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + *
+ * description : 사전고지 CCTV 파일 처리(서광)
+ *
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : PniCctvFileJobScheduler
+ * author      : limju
+ * date        : 2023-07-10
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-10    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class PniCctvFileJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final PniCctvFileJobConfg jobConfiguration; + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //FIXME : 사전고지 CCTV 파일 처리(서광) 실행 간격 정의 + private final static int RUN_EXEC_MIS = 10000; + + //@Scheduled(initialDelay = RUN_EXEC_MIS, fixedDelay = RUN_EXEC_MIS) + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.pniCctvFileJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/SndngAcceptJobScheduler.java b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/SndngAcceptJobScheduler.java new file mode 100644 index 0000000..5027386 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/SndngAcceptJobScheduler.java @@ -0,0 +1,71 @@ +package kr.xit.ens.support.batch.scheduler; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import kr.xit.ens.support.batch.job.SndngAcceptJobConfig; +import kr.xit.ens.support.common.ApiConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : 연계 발송 - 접수 데이타 생성
+ *               대상 : 연계발송마스터(tb_cntc_sndng_mastr) : sndng_process_sttus = 'accept'
+ *                     연계발송상세(tb_cntc_sndng_detail)
+ *               ->  통합발송 마스터(tb_ens_unity_sndng_mastr)
+ *                   통합발송상세(tb_ens_unity_sndng_detail)
+ *                : 연계발송마스터 and tb_ens_unity_sndng_mastr - sndng_process_sttus = 'accept-ok'
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : SndngAcceptJobScheduler
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class SndngAcceptJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final SndngAcceptJobConfig jobConfiguration; + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //@Scheduled(cron = "${app.batch.cron.ens.accept}") + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + confMap.put("sndngProcessSttus", new JobParameter(ApiConstants.SndngProcessStatus.ACCEPT.getCode())); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.sndngAcceptJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/SndngCloseJobScheduler.java b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/SndngCloseJobScheduler.java new file mode 100644 index 0000000..914898b --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/SndngCloseJobScheduler.java @@ -0,0 +1,70 @@ +package kr.xit.ens.support.batch.scheduler; + +import kr.xit.ens.support.batch.job.SndngAcceptJobConfig; +import kr.xit.ens.support.batch.job.SndngCloseJobConfig; +import kr.xit.ens.support.common.ApiConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + *
+ * description :상태 close - send-ok
+ *              대상 : 연계 발송 마스터(tb_cntc_sndng_mastr)
+ *                ->  연계 발송 마스터(tb_cntc_sndng_mastr)
+ *                    통합 발송 마스터(tb_ens_unity_sndng_mastr)
+ *                    발송 마스터(tb_ens_sndng_mastr)
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : SndngCloseJobScheduler
+ * author      : seojh
+ * date        : 2023-06-14
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-14    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class SndngCloseJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final SndngCloseJobConfig jobConfiguration; + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //@Scheduled(cron = "${app.batch.cron.ens.close}") + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + confMap.put("sndngProcessSttus", new JobParameter(ApiConstants.SndngProcessStatus.SEND_OK.getCode())); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.sndngCloseJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/SndngMakeJobScheduler.java b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/SndngMakeJobScheduler.java new file mode 100644 index 0000000..5a75798 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/SndngMakeJobScheduler.java @@ -0,0 +1,70 @@ +package kr.xit.ens.support.batch.scheduler; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import kr.xit.ens.support.batch.job.SndngMakeJobConfig; +import kr.xit.ens.support.common.ApiConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : 연계 발송 - 전송 대상 데이타 생성
+ *               대상 : 통합발송 마스터(tb_ens_unity_sndng_mastr)
+ *  *                  통합발송상세(tb_ens_unity_sndng_detail)
+ *               ->  발송 마스터(tb_ens_sndng_mastr)
+ *                   발송상세(tb_ens_kakao_my_doc)
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : SndngMakeJobScheduler
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class SndngMakeJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final SndngMakeJobConfig jobConfiguration; + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //@Scheduled(cron = "${app.batch.cron.ens.make}") + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + confMap.put("sndngProcessSttus", new JobParameter(ApiConstants.SndngProcessStatus.ACCETP_OK.getCode())); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.sndngMakeJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/SndngSendBulksJobScheduler.java b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/SndngSendBulksJobScheduler.java new file mode 100644 index 0000000..8585ed3 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/SndngSendBulksJobScheduler.java @@ -0,0 +1,69 @@ +package kr.xit.ens.support.batch.scheduler; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import kr.xit.ens.support.batch.job.SndngSnedBulksJobConfig; +import kr.xit.ens.support.common.ApiConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : 연계 발송 - 전송
+ *               대상 : 발송 마스터(tb_ens_sndng_mastr)
+ *                     발송상세(tb_ens_kakao_my_doc)
+ *                     연계발송결과(tb_cntc_sndng_result)
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : SndngSendBulksJobScheduler
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class SndngSendBulksJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final SndngSnedBulksJobConfig jobConfiguration; + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //@Scheduled(cron = "${app.batch.cron.ens.send}") + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + confMap.put("sndngProcessSttus", new JobParameter(ApiConstants.SndngProcessStatus.MAKE_OK.getCode())); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.sndngSendBulksJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/SndngStatusBulksJobScheduler.java b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/SndngStatusBulksJobScheduler.java new file mode 100644 index 0000000..cd7c58b --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/scheduler/SndngStatusBulksJobScheduler.java @@ -0,0 +1,70 @@ +package kr.xit.ens.support.batch.scheduler; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import kr.xit.ens.support.batch.job.SndngSnedBulksJobConfig; +import kr.xit.ens.support.batch.job.SndngStatusBulksJobConfig; +import kr.xit.ens.support.common.ApiConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : 연계 발송 - 상태조회
+ *               대상 : 연계발송결과(tb_cntc_sndng_result)
+ *                     발송상세(tb_ens_kakao_my_doc)
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : SndngStatusBulksJobScheduler
+ * author      : limju
+ * date        : 2023-06-13
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-13    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class SndngStatusBulksJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final SndngStatusBulksJobConfig jobConfiguration; + + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //@Scheduled(cron = "${app.batch.cron.ens.kko-status}") + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + confMap.put("sndngProcessSttus", new JobParameter(ApiConstants.SndngProcessStatus.SEND_OK.getCode())); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.sndngStatusBulksJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/task/EnsCctvAcceptTasklet.java b/mens-api/src/main/java/kr/xit/ens/support/batch/task/EnsCctvAcceptTasklet.java new file mode 100644 index 0000000..ba54370 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/task/EnsCctvAcceptTasklet.java @@ -0,0 +1,61 @@ +package kr.xit.ens.support.batch.task; + +import kr.xit.biz.ens.service.IEnsCctvFileService; +import kr.xit.ens.support.batch.task.cmm.TaskCmmUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +/** + *
+ * description : 전자 고지 CCTV accept
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : EnsCctvAcceptTasklet
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +public class EnsCctvAcceptTasklet implements Tasklet { + private final IEnsCctvFileService service; + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + service.acceptEnsNtnccntcSndng(); + }catch(Exception e){ + log.error("EnsCctvAcceptTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + TaskCmmUtils.taskEnsMessageLinkServiceUpdateErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/task/EnsCctvFileTasklet.java b/mens-api/src/main/java/kr/xit/ens/support/batch/task/EnsCctvFileTasklet.java new file mode 100644 index 0000000..e12090d --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/task/EnsCctvFileTasklet.java @@ -0,0 +1,55 @@ +package kr.xit.ens.support.batch.task; + +import kr.xit.biz.ens.service.IEnsCctvFileService; +import kr.xit.ens.support.batch.task.cmm.TaskCmmUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +/** + *
+ * description : 전자 고지 CCTV 파일 처리(서광)
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : EnsCctvFileTasklet
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +public class EnsCctvFileTasklet implements Tasklet { + private final IEnsCctvFileService service; + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + service.createCctvFileOfSg(); + }catch(Exception e){ + log.error("EnsCctvFileTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/task/PniCctvAcceptTasklet.java b/mens-api/src/main/java/kr/xit/ens/support/batch/task/PniCctvAcceptTasklet.java new file mode 100644 index 0000000..a48dfb6 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/task/PniCctvAcceptTasklet.java @@ -0,0 +1,61 @@ +package kr.xit.ens.support.batch.task; + +import kr.xit.biz.pni.service.IPniCctvFileService; +import kr.xit.ens.support.batch.task.cmm.TaskCmmUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +/** + *
+ * description : 사전고지 CCTV accept
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : PniCctvAcceptTasklet
+ * author      : limju
+ * date        : 2023-07-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-11    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +public class PniCctvAcceptTasklet implements Tasklet { + private final IPniCctvFileService service; + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + service.acceptPniNtnccntcSndng(); + }catch(Exception e){ + log.error("PniCctvAcceptTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + TaskCmmUtils.taskEnsMessageLinkServiceUpdateErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/task/PniCctvFileTasklet.java b/mens-api/src/main/java/kr/xit/ens/support/batch/task/PniCctvFileTasklet.java new file mode 100644 index 0000000..b852300 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/task/PniCctvFileTasklet.java @@ -0,0 +1,55 @@ +package kr.xit.ens.support.batch.task; + +import kr.xit.biz.pni.service.IPniCctvFileService; +import kr.xit.ens.support.batch.task.cmm.TaskCmmUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +/** + *
+ * description : 사전고지 CCTV 파일 처리(서광)
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : PniCctvFileTasklet
+ * author      : limju
+ * date        : 2023-07-10
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-10    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +public class PniCctvFileTasklet implements Tasklet { + private final IPniCctvFileService service; + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + service.createCctvFileOfSg(); + }catch(Exception e){ + log.error("PniCctvFileTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/task/SndngAcceptTasklet.java b/mens-api/src/main/java/kr/xit/ens/support/batch/task/SndngAcceptTasklet.java new file mode 100644 index 0000000..9050a04 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/task/SndngAcceptTasklet.java @@ -0,0 +1,62 @@ +package kr.xit.ens.support.batch.task; + +import kr.xit.ens.support.batch.task.cmm.TaskCmmUtils; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +import kr.xit.biz.ens.service.ISendMessageLinkService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : SndngAcceptTasklet
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +public class SndngAcceptTasklet implements Tasklet { + private final ISendMessageLinkService service; + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + String sndngProcessSttus = contribution.getStepExecution().getJobParameters().getString("sndngProcessSttus"); + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + service.accept(sndngProcessSttus); + }catch(Exception e){ + log.error("SndngAcceptTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + TaskCmmUtils.taskEnsMessageLinkServiceUpdateErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/task/SndngCloseTasklet.java b/mens-api/src/main/java/kr/xit/ens/support/batch/task/SndngCloseTasklet.java new file mode 100644 index 0000000..bd83cc4 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/task/SndngCloseTasklet.java @@ -0,0 +1,59 @@ +package kr.xit.ens.support.batch.task; + +import kr.xit.biz.ens.service.ISendMessageLinkService; +import kr.xit.ens.support.batch.task.cmm.TaskCmmUtils; +import lombok.extern.slf4j.Slf4j; + +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : SndngCloseTasklet
+ * author      : seojh
+ * date        : 2023-06-14
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-14    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +public class SndngCloseTasklet implements Tasklet { + private final ISendMessageLinkService service; + + public SndngCloseTasklet(ISendMessageLinkService service) { + this.service = service; + } + + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + String sndngProcessSttus = contribution.getStepExecution().getJobParameters().getString("sndngProcessSttus"); + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + service.close(sndngProcessSttus); + }catch(Exception e){ + log.error("SndngCloseTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/task/SndngMakeTasklet.java b/mens-api/src/main/java/kr/xit/ens/support/batch/task/SndngMakeTasklet.java new file mode 100644 index 0000000..ae05ebf --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/task/SndngMakeTasklet.java @@ -0,0 +1,62 @@ +package kr.xit.ens.support.batch.task; + +import kr.xit.ens.support.batch.task.cmm.TaskCmmUtils; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +import kr.xit.biz.ens.service.ISendMessageLinkService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : SndngMakeTasklet
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +public class SndngMakeTasklet implements Tasklet { + private final ISendMessageLinkService service; + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + String sndngProcessSttus = contribution.getStepExecution().getJobParameters().getString("sndngProcessSttus"); + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + service.make(sndngProcessSttus); + }catch(Exception e){ + log.error("SndngMakeTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + TaskCmmUtils.taskEnsMessageLinkServiceUpdateErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/task/SndngSendBulksTasklet.java b/mens-api/src/main/java/kr/xit/ens/support/batch/task/SndngSendBulksTasklet.java new file mode 100644 index 0000000..720766e --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/task/SndngSendBulksTasklet.java @@ -0,0 +1,69 @@ +package kr.xit.ens.support.batch.task; + +import java.util.UUID; + +import kr.xit.ens.support.batch.task.cmm.TaskCmmUtils; +import org.slf4j.MDC; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +import kr.xit.biz.ens.service.ISendMessageLinkService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : SndngSendBulksTasklet
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +public class SndngSendBulksTasklet implements Tasklet { + private final ISendMessageLinkService service; + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + String sndngProcessSttus = contribution.getStepExecution().getJobParameters().getString("sndngProcessSttus"); + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + MDC.put("request_trace_batch_id", UUID.randomUUID().toString().replace("-", "")); + MDC.put("uri", "/v1/documents/bulk"); + MDC.put("method", "POST"); + + service.sendBulks(sndngProcessSttus); + }catch(Exception e){ + log.error("SndngSendBulksTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + TaskCmmUtils.taskEnsMessageLinkServiceUpdateErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/task/SndngStatusBulksTasklet.java b/mens-api/src/main/java/kr/xit/ens/support/batch/task/SndngStatusBulksTasklet.java new file mode 100644 index 0000000..870bf3b --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/task/SndngStatusBulksTasklet.java @@ -0,0 +1,63 @@ +package kr.xit.ens.support.batch.task; + +import kr.xit.ens.support.batch.task.cmm.TaskCmmUtils; +import org.slf4j.MDC; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +import kr.xit.biz.ens.service.ISendMessageLinkService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.UUID; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : SndngStatusBulksTasklet
+ * author      : limju
+ * date        : 2023-06-13
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-13    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +public class SndngStatusBulksTasklet implements Tasklet { + private final ISendMessageLinkService service; + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + final String sndngProcessSttus = contribution.getStepExecution().getJobParameters().getString("sndngProcessSttus"); + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + MDC.put("request_trace_batch_id", UUID.randomUUID().toString().replace("-", "")); + MDC.put("uri", "/v1/documents/bulk/status"); + MDC.put("method", "POST"); + + service.findKkoMyDocStatusBulks(sndngProcessSttus); + }catch(Exception e){ + log.error("SndngStatusBulksTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/task/cmm/TaskCmmUtils.java b/mens-api/src/main/java/kr/xit/ens/support/batch/task/cmm/TaskCmmUtils.java new file mode 100644 index 0000000..006dbb8 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/task/cmm/TaskCmmUtils.java @@ -0,0 +1,83 @@ +package kr.xit.ens.support.batch.task.cmm; + +import kr.xit.biz.ens.model.EnsDTO; +import kr.xit.core.biz.batch.model.BatchCmmDTO; +import kr.xit.core.spring.util.ApiSpringUtils; +import kr.xit.ens.support.common.ApiConstants; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.slf4j.MDC; + +import static egovframework.com.cmm.util.EgovStringUtil.cutString; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.batch.task.cmm
+ * fileName    : TaskCmmUtils
+ * author      : limju
+ * date        : 2023-06-19
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-19    limju       최초 생성
+ *
+ * 
+ */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TaskCmmUtils { + public static void taskBatchErrorLog(final String instanceId, final String errorMsg, final String isSlackEnabled){ + ApiSpringUtils.getBatchCmmService().modifyBatchLog( + BatchCmmDTO + .builder() + .batchLogId(MDC.get("batch_log_id")) + .instanceId(instanceId) + .traceId(MDC.get("request_trace_id")) + .message(errorMsg) + .build() + ); + if(Boolean.parseBoolean(isSlackEnabled)) { + ApiSpringUtils.getSlackWebhookPush().sendSlackAlertLog( + errorMsg, + MDC.get("uri"), + "batch call"); + } + } + + /** + * 배치 실행시 발생한 에러 처리 + *

업무단에서 발생한 에러 처리를 위한 메소드 + * @param instanceId String 배치인스턴스 + * @param errorMsg String 에러 메세지 + * @param isSlackEnabled boolean 슬랙 push 여부 + */ + public static void taskEnsMessageLinkServiceUpdateErrorLog(final String instanceId, final String errorMsg, final String isSlackEnabled){ + final String UNITY_SNDNG_MASTR_ID = "unitySndngMastrId"; + + String unitySndngMastrId = null; + String sndngProcessSttus = null; + String sndngMastrId = null; + + if("SndngAcceptJob".equals(instanceId) || "PniCctvAcceptJob".equals(instanceId)){ + unitySndngMastrId = MDC.get(UNITY_SNDNG_MASTR_ID); + sndngProcessSttus = ApiConstants.SndngProcessStatus.ACCETP_FAIL.getCode(); + } else if ("SndngMakeJob".equals(instanceId)) { + unitySndngMastrId = MDC.get(UNITY_SNDNG_MASTR_ID); + sndngProcessSttus = MDC.get("sndngProcessSttus"); + } else if ("SndngSendBulksJob".equals(instanceId)) { + unitySndngMastrId = MDC.get(UNITY_SNDNG_MASTR_ID); + sndngMastrId = MDC.get("sndngMastrId"); + sndngProcessSttus = MDC.get("sndngProcessSttus"); + } + ApiSpringUtils.getSendMessageLinkService().updateErrorLog( + EnsDTO.SndngMssageParam.builder() + .unitySndngMastrId(unitySndngMastrId) + .sndngMastrId(sndngMastrId) + .newSndngProcessSttus(sndngProcessSttus) + .errorCode(instanceId) + .errorMssage(cutString(errorMsg, 300)) + .build() + ); + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/batch/web/KkopayEltrcDocBatchController.java b/mens-api/src/main/java/kr/xit/ens/support/batch/web/KkopayEltrcDocBatchController.java new file mode 100644 index 0000000..1938e0e --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/batch/web/KkopayEltrcDocBatchController.java @@ -0,0 +1,224 @@ +package kr.xit.ens.support.batch.web; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import kr.xit.core.support.utils.Checks; +import kr.xit.ens.support.batch.job.*; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.ens.support.batch.scheduler.SndngSendBulksJobScheduler; +import kr.xit.ens.support.common.ApiConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *

+ * description : 카카오페이 전자문서 발송요청 배치 컨트롤러
+ *
+ * packageName : kr.xit.ens.support.batch.web
+ * fileName    : KkopayEltrcDocBatchController
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ */ +@Tag(name = "KkopayEltrcDocBatchController", description = "전자고지 통합발송 연계 배치 WEB") +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping(value = "/api/batch/v1") +public class KkopayEltrcDocBatchController { + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final SndngAcceptJobConfig acceptJobConfig; + private final SndngMakeJobConfig makeJobConfig; + private final SndngSnedBulksJobConfig sendBulksJobConfig; + private final SndngStatusBulksJobConfig statusJobConfig; + private final SndngCloseJobConfig closeJobConfig; + private final PniCctvFileJobConfg pniCctvFileJobConfg; + private final PniCctvAcceptJobConfg pniCctvAcceptJobConfg; + + @Operation(summary = "accept", description = "accept") + @GetMapping(value = "/accept", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO accept() { + try { + JobExecution jobExecution = jobLauncher.run(acceptJobConfig.sndngAcceptJob(), getJobParameters(ApiConstants.SndngProcessStatus.ACCEPT.getCode())); + + while(jobExecution.isRunning()){ + log.info("..."); + } + printLog(jobExecution); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } + return ApiResponseDTO.success(HttpStatus.OK); + } + + @Operation(summary = "make", description = "make") + @GetMapping(value = "/make", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO make() { + + try { + JobExecution jobExecution = jobLauncher.run(makeJobConfig.sndngMakeJob(), getJobParameters(ApiConstants.SndngProcessStatus.ACCETP_OK.getCode())); + + while(jobExecution.isRunning()){ + log.info("..."); + } + printLog(jobExecution); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } + return ApiResponseDTO.success(HttpStatus.OK); + } + + @Operation(summary = "close", description = "close") + @GetMapping(value = "/close", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO close() { + + try { + JobExecution jobExecution = jobLauncher.run(closeJobConfig.sndngCloseJob(), getJobParameters(ApiConstants.SndngProcessStatus.SEND_OK.getCode())); + + while(jobExecution.isRunning()){ + log.info("..."); + } + printLog(jobExecution); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } + return ApiResponseDTO.success(HttpStatus.OK); + } + + @Operation(summary = "sendBulks", description = "sendBulks") + @GetMapping(value = "/sendBulks", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO sendBulks() { + + try { + JobExecution jobExecution = jobLauncher.run(sendBulksJobConfig.sndngSendBulksJob(), getJobParameters(ApiConstants.SndngProcessStatus.MAKE_OK.getCode())); + + while(jobExecution.isRunning()){ + log.info("..."); + } + printLog(jobExecution); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } + return ApiResponseDTO.success(HttpStatus.OK); + } + + @Operation(summary = "statusBulks", description = "statusBulks") + @GetMapping(value = "/statusBulks", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO statusBulks() { + + try { + JobExecution jobExecution = jobLauncher.run(statusJobConfig.sndngStatusBulksJob(), getJobParameters(ApiConstants.SndngProcessStatus.SEND_OK.getCode())); + + while(jobExecution.isRunning()){ + log.info("..."); + } + printLog(jobExecution); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } + return ApiResponseDTO.success(HttpStatus.OK); + } + + @Operation(summary = "pniCctvFileService", description = "CCTV 사전알림(서광)") + @GetMapping(value = "/pniCctvFileService", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO pniCctvFileService() { + + try { + JobExecution jobExecution = jobLauncher.run(pniCctvFileJobConfg.pniCctvFileJob(), getJobParameters(null)); + + while(jobExecution.isRunning()){ + log.info("..."); + } + printLog(jobExecution); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } + return ApiResponseDTO.success(HttpStatus.OK); + } + + @Operation(summary = "pniCctvAccept", description = "CCTV 단속 사전알림 accept") + @GetMapping(value = "/pniCctvAccept", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO pniCctvAccept() { + + try { + JobExecution jobExecution = jobLauncher.run(pniCctvAcceptJobConfg.pniCctvAcceptJob(), getJobParameters(null)); + + while(jobExecution.isRunning()){ + log.info("..."); + } + printLog(jobExecution); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } + return ApiResponseDTO.success(HttpStatus.OK); + } + + private JobParameters getJobParameters(final String processStatus){ + Map confMap = new HashMap<>(); + + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + if(Checks.isNotEmpty(processStatus)) confMap.put("sndngProcessSttus", new JobParameter(processStatus)); + return new JobParameters(confMap); + } + + private static void printLog(JobExecution jobExecution) { + log.info("Job Execution: " + jobExecution.getStatus()); + log.info("Job getJobConfigurationName: " + jobExecution.getJobConfigurationName()); + log.info("Job getJobId: " + jobExecution.getJobId()); + log.info("Job getExitStatus: " + jobExecution.getExitStatus()); + log.info("Job getJobInstance: " + jobExecution.getJobInstance()); + log.info("Job getStepExecutions: " + jobExecution.getStepExecutions()); + log.info("Job getLastUpdated: " + jobExecution.getLastUpdated()); + log.info("Job getFailureExceptions: " + jobExecution.getFailureExceptions()); + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/common/ApiConstants.java b/mens-api/src/main/java/kr/xit/ens/support/common/ApiConstants.java new file mode 100644 index 0000000..54a7c6a --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/common/ApiConstants.java @@ -0,0 +1,168 @@ +package kr.xit.ens.support.common; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.common
+ * fileName    : KakaoConstants
+ * author      : xitdev
+ * date        : 2023-05-04
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-04    xitdev       최초 생성
+ *
+ * 
+ */ +public class ApiConstants { + + /** + *
+     * 문서 조회 버튼의 명칭을 구분하기 위한 값
+     * code(실제 파라미터로 보내는 값) : button_name(내문서함 내부에 표기되는 버튼 명칭)- name(내문서함 내부에서 구분하기 위한 명칭)
+     * Default : 문서확인 - Default
+     * BILL : 문서확인 - 고지서
+     * BILL_PAY : 문서확인후 납부 - 납부가 포함된 고지서
+     * NOTICE : 문서확인 - 안내문
+     * CONTRACT : 문서확인 - 계약서
+     * REPORT : 문서확인 - 리포트
+     * 
+ */ + public enum Categories { + DEFAULT("Default") + , BILL("BILL") + , BILL_PAY("BILL_PAY") + , NOTICE("NOTICE") + , CONTRACT("CONTRACT") + , REPORT("REPORT") + ; + + private final String code; + + Categories(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + } + + /** + *
+     * INVALID_VALUE : http status code : 400
+     *                 파라미터가 형식에 맞지않음 혹은 필수파라미터 누락
+     *                 {"error_code": "INVALID_VALUE", "error_message": "유효하지 않은 값입니다."
+     * UNIDENTIFIED_USER : http status code : 400
+     *                     받는이의 정보로 발송대상을 특정 할 수 없을때
+     *                     {"error_code": "INVALID_VALUE", "error_message": "유효하지 않은 값입니다."
+     * UNAUTHORIZED : http status code : 401
+     *                access token이 유효하지 않거나 잘못된 경우
+     *                {"error_code": "UNAUTHORIZED","error_message": "접근 권한이 없습니다."
+     * FORBIDDEN : http status code : 403
+     *             문서의 대상자가 내문서함에서 수신거부를 한 경우
+     *             {"error_code": "FORBIDDEN","error_message": "허용되지 않는 요청입니다. 수신거부된 사용자 입니다."}
+     * NOT_FOUND : http status code : 404
+     *             "Contract-Uuid" or "document_binder_uuid"가 유효하지 않거나 잘못된 경우
+     *             {"error_code": "NOT_FOUND","error_message": "요청 정보를 찾을 수 없습니다."
+     * INTERNAL_ERROR : http status code : 500
+     *                  카카오페이 서버에러
+     *                  {"error_code": "INTERNAL_SERVER_ERROR","error_message": "서버 에러입니다. 다시 시도해 주세요."}
+     *  
+ * 카카오페이 전자문서 발송 요청 에러 코드 + */ + public enum Error { + INVALID_VALUE("INVALID_VALUE") + , UNIDENTIFIED_USER("UNIDENTIFIED_USER") + , UNAUTHORIZED("UNAUTHORIZED") + , FORBIDDEN("FORBIDDEN") + , NOT_FOUND("NOT_FOUND") + , INTERNAL_ERROR("INTERNAL_ERROR") + ; + + private final String code; + + Error(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + } + + /** + * 카카오페이 문서 상태 + * SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료) + */ + public enum DocBoxStatus { + SENT("SENT") + , RECEIVED("RECEIVED") + , READ("READ") + , EXPIRED("EXPIRED") + ; + + private final String code; + + DocBoxStatus(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + } + + /** + * 발송처리상태 : ENS003 + */ + public enum SndngProcessStatus { + ACCEPT("accept"), + ACCETP_OK("accept-ok"), + ACCETP_FAIL("accept-fail"), + MAKE_OK("make-ok"), + MAKE_FAIL1("make-fail1"), + MAKE_FAIL2("make-fail2"), + MAKE_FAIL3("make-fail3"), + SENDING1("sending1"), + SENDING2("sending2"), + SEND_OK("send-ok"), + SEND_FAIL1("send-fail1"), + SEND_FAIL2("send-fail2"), + SEND_FAIL3("send-fail3"), + CLOSE("close") + + ; + + private final String code; + + SndngProcessStatus(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + } + + /** + * 발송구분코드 + */ + public enum SndngSeCode { + SMS("SMS"), + KAKAO_MY_DOC("KKO-MY-DOC"), + E_GREEN("E-GREEN") + ; + + private final String code; + + SndngSeCode(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/common/code/KkoReponseCode.java b/mens-api/src/main/java/kr/xit/ens/support/common/code/KkoReponseCode.java new file mode 100644 index 0000000..7d918b5 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/common/code/KkoReponseCode.java @@ -0,0 +1,56 @@ +package kr.xit.ens.support.common.code; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +/** + *
+ * description :
+ * packageName : kr.xit.ens.support.common.code
+ * fileName    : KkoReponseCode
+ * author      : minuk
+ * date        : 2023/05/07
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023/05/07   minuk       최초 생성
+ *
+ * 
+ */ +//TODO:: KakaoConstants 로 통합 +public class KkoReponseCode { + + public enum ErrorCode { + +// INVALID_VALUE("INVALID_VALUE", "INVALID_VALUE", "유효하지 않은 값입니다."), +// UNIDENTIFIED_USER("UNIDENTIFIED_USER", "INVALID_VALUE", "유효하지 않은 값입니다."), +// UNAUTHORIZED("UNAUTHORIZED", "UNAUTHORIZED", "접근 권한이 없습니다."), +// FORBIDDEN("FORBIDDEN", "FORBIDDEN", "허용되지 않는 요청입니다. 수신거부된 사용자 입니다."), +// NOT_FOUND("NOT_FOUND", "NOT_FOUND", "요청 정보를 찾을 수 없습니다."), +// INTERNAL_ERROR("INTERNAL_ERROR", "INTERNAL_SERVER_ERROR", "서버 에러입니다. 다시 시도해 주세요."), +// ; + + INVALID_VALUE(400, "INVALID_VALUE", "유효하지 않은 값입니다."), + UNIDENTIFIED_USER(400, "INVALID_VALUE", "유효하지 않은 값입니다."), + UNAUTHORIZED(401, "UNAUTHORIZED", "접근 권한이 없습니다."), + FORBIDDEN(403, "FORBIDDEN", "허용되지 않는 요청입니다. 수신거부된 사용자 입니다."), + NOT_FOUND(404, "NOT_FOUND", "요청 정보를 찾을 수 없습니다."), + INTERNAL_ERROR(500, "INTERNAL_SERVER_ERROR", "서버 에러입니다. 다시 시도해 주세요."), + ; + + @Getter + private int errorCode; + + @Getter + private String errorString; + + @Getter + private String message; + + ErrorCode(int errorCode, String errorString, String message){ + this.errorCode = errorCode; + this.errorString = errorString; + this.message = message; + } + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/kakao/model/KkopayDocAttrDTO.java b/mens-api/src/main/java/kr/xit/ens/support/kakao/model/KkopayDocAttrDTO.java new file mode 100644 index 0000000..92d573d --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/kakao/model/KkopayDocAttrDTO.java @@ -0,0 +1,273 @@ +package kr.xit.ens.support.kakao.model; + +import java.util.List; + +import javax.validation.Valid; +import javax.validation.constraints.Digits; +import javax.validation.constraints.Size; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import io.swagger.v3.oas.annotations.media.Schema; +import kr.xit.core.model.IApiResponse; +import kr.xit.ens.support.common.ApiConstants; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + *
+ * description : 카카오페이 전자문서 DTO
+ *
+ * packageName : kr.xit.ens.support.kakaopay.model
+ * fileName    : KkopayDocDTO
+ * author      : xitdev
+ * date        : 2023-05-03
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-03    xitdev       최초 생성
+ *
+ * 
+ */ +public class KkopayDocAttrDTO { + + //------------------- requestSend ------------------------------------------------------------------------------------------------ + @Schema(name = "Send", description = "RequestSend(문서발송 요청 파라메터)의 요청 파라메터 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Send implements IApiResponse { + /** + * 발송할 문서의 제목 : 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "발송할 문서의 제목", example = "문서 제목") + @Size(min = 1, max = 40, message = "문서제목은 필수입니다(max:40)") + private String title; + + /** + * 처리마감시간(절대시간) - 카카오톡 메시지를 수신한 사용자가 전자문서를 열람을 할 수 있는 시간 + * read_expired_sec 미전송시 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "처리마감시간(절대시간)", example = "1617202800") + @Digits(integer = 10, fraction = 0, message = "처리마감시간(절대시간:max=10자리)") + private Long read_expired_at; + + /** + * 처리마감시간(상대시간) - 권장값: 30일 (2592000 sec) + * read_expired_at 미전송시 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "처리마감시간(상대시간)", example = " ") + @Digits(integer = 8, fraction = 0, message = "처리마감시간(절대시간:max=8자리)") + private Integer read_expired_sec; + + /** + * 문서 원문(열람정보)에 대한 hash 값 - 공인전자문서 유통정보 등록 시 필수 + */ + @Schema(title = "문서 원문(열람정보)에 대한 hash 값", example = "6EFE827AC88914DE471C621AE") + @Size(max = 99, message = "문서 원문(열람정보)에 대한 hash 값(max=99)") + private String hash; + + /** + * 문서의 메타정보 - 향후 문서 검색을 위해서 활용될 메타 정보(현재 미 제공) + */ + @Schema(title = "문서의 메타정보(현재 미 제공)", example = "[\"NOTICE\"]") + private List common_categories; + + /** + * 받는이에 대한 정보 - 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private Receiver receiver; + + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private Property property; + } + + @Schema(name = "Receiver", description = "RequestSend(문서발송 요청 파라메터)의 receiver(받는이)에 대한 정보 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Receiver { + /** + * 받는이 CI + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "받는이 CI", example = " ") + @Size(max = 88, message = "받는이 CI(max=88)") + private String ci; + + /** + * 받는이 전화번호 + * ci 미전송시 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "받는이 전화번호", example = "01012345678") + @Size(max = 11, message = "받는이 전화번호(max=11)") + private String phone_number; + + /** + * 받는 이 이름 + * ci 미전송시 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "받는이 이름", example = "김페이") + @Size(max = 20, message = "받는이 이름(max=20)") + private String name; + + /** + * 받는 이 생년월일 (YYYYMMDD 형식) + * ci 미전송시 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "받는이 생년월일 (YYYYMMDD 형식)", example = "19801101") + //@Pattern(regexp = "^(19[0-9][0-9]|20\\d{2})(0[0-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])$") + @Size(max = 8, message = "받는이 생년월일(YYYYMMDD:max=8)") + private String birthday; + + /** + * 성명 검증 옵션 + * CI 전송 시 생략 가능 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "성명 검증 옵션", example = "false") + private Boolean is_required_verify_name; + } + + @Schema(name = "Property", description = "RequestSend(문서발송 요청 파라메터)의 property(문서속성)에 대한 정보 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Property { + /** + * 본인인증 후 사용자에게 보여줄 웹페이지 주소 : 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "본인인증 후 사용자에게 보여줄 웹페이지 주소", example = "http://ipAddress/api/kakaopay/v1/ott") + //@NotBlank + @Size(min = 10, max = 100, message = "본인인증후 사용자에게 보여줄 페이지 주소는 필수입니다(max=100)") + private String link; + + /** + * 고객센터 전화번호 : 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "고객센터 전화번호", example = "02-123-4567") + @Size(min = 10, max = 20, message = "고객센터 전화번호는 필수입니다(max=20)") + private String cs_number; + + /** + * 고객센터 전화번호 : 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "고객센터 명", example = "콜센터") + //@NotBlank + @Size(min = 1, max = 10, message = "고객센터명은 필수입니다(max=10)") + private String cs_name; + + /** + * 이용기관에서 해당 값을 다시 받고자 할 내용의 값 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "이용기관에서 해당 값을 다시 받고자 할 내용의 값", example = "payload 파라미터 입니다.") + @Size(max = 200, message = "이용기관에서 해당 값을 다시 받고자 할 내용의 값(max=200)") + private String payload; + + /** + * 메세지 - 사용자에게 전송하는 문서에 대한 설명 + * 노출위치 : 문서수신 메시지(알림톡)가 도착했음을 알리는 카카오톡 메시지 내부 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "사용자에게 전송하는 문서에 대한 설명", example = "해당 안내문은 다음과 같습니다.") + @Size(max = 500, message = "메세지(max=500)") + private String message; + } + //------------------------------------------------------------------------------------------------------------------- + + + @Schema(name = "DocumentBinderUuid DTO", description = "카카오페이 문서식별번호") + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class DocumentBinderUuid implements IApiResponse { + /** + * 카카오페이 문서식별번호(max:40) - 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "카카오페이 문서식별번호(max:40)", example = "BIN-ff806328863311ebb61432ac599d6150") + @Size(min = 1, max = 40, message = "카카오페이 문서식별번호는 필수입니다(max:40)") + private String document_binder_uuid; + } + + @Schema(name = "DocStatus DTO", description = "카카오페이 전자문서 상태 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class DocStatus implements IApiResponse { + /** + *
+         * 진행상태(max:20) : 필수
+         * 송신|수신|열람|만료
+         * SENT|RECEIVED|READ|EXPIRED
+         * SENT - 문서발송요청 성공(실 사용자에게 문서가 도달되지 않은 상태, 알림톡은 수신했으나 페이회원이 아니어서 실 문서 발송이 않됨)
+         * RECEIVED - 사용자에 실문서 도달 완료
+         * READ - OTT 검증 완료후 문서 상태 변경 API 호출에 성공한 상태
+         * EXPIRED - 미열람 문서에 대한 열람 만료 시간이 지난 상태
+         * 
+ * @see ApiConstants.DocBoxStatus + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, maxLength = 20, title = "진행상태(max:20)", example = " ") + @Size(min = 1, max = 20, message = "진행상태는 필수입니다(max:20)") + private ApiConstants.DocBoxStatus doc_box_status; + + /** + * 송신 시간(long max:10) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "송신 시간(max:10)", example = " ") + @Digits(integer = 10, fraction = 0, message = "송신시간(max:10)") + private Long doc_box_sent_at; + + /** + * 수신 시간(long max:10) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "수신 시간(max:10)", example = " ") + @Digits(integer = 10, fraction = 0, message = "수신시간(max:10)") + private Long doc_box_received_at; + + /** + * 열람인증성공 최초 시간(long max:10) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "열람인증성공 최초 시간(max:10)", example = " ") + @Digits(integer = 10, fraction = 0, message = "열람인증성공 최초시간(max:10)") + private Long authenticated_at; + + /** + * OTT 검증 성공 최초 시간(long max:10) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "OTT 검증 성공 최초 시간(max:10)", example = " ") + @Digits(integer = 10, fraction = 0, message = "OTT 검증 성공 최초 시간(max:10)") + private Long token_used_at; + + /** + * 최초 열람 시간(long max:10) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "최초 열람 시간(max:10)", example = " ") + @Digits(integer = 10, fraction = 0, message = "최초 열람 시간(max:10)") + private Long doc_box_read_at; + + /** + * 송신 시간(long max:10) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "알림톡 수신 시간(max:10)", example = " ") + @Digits(integer = 10, fraction = 0, message = "알림톡 수신 시간(max:10)") + private Long user_notified_at; + + /** + * 이용기관 생성 payload(max:200) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "이용기관 생성 payload(max:200)", example = " ") + @Size(max = 200, message = "이용기관 생성 payload(max:200)") + private String payload; + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/kakao/model/KkopayDocBulkDTO.java b/mens-api/src/main/java/kr/xit/ens/support/kakao/model/KkopayDocBulkDTO.java new file mode 100644 index 0000000..ac21acc --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/kakao/model/KkopayDocBulkDTO.java @@ -0,0 +1,187 @@ +package kr.xit.ens.support.kakao.model; + +import java.util.List; + +import javax.validation.Valid; +import javax.validation.constraints.Size; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import io.swagger.v3.oas.annotations.media.Schema; +import kr.xit.ens.support.common.ApiConstants; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.kakao.model
+ * fileName    : KkopayEltrDocBulkDTO
+ * author      : limju
+ * date        : 2023-05-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-12    limju       최초 생성
+ *
+ * 
+ */ +public class KkopayDocBulkDTO extends KkopayDocAttrDTO { + + //------------ BulkSendRequests ----------------------------------------------------------------------------- + @Schema(name = "BulkSendRequests DTO", description = "문서발송(bulk) 요청 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class BulkSendRequests { + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private List documents; + } + + @Schema(name = "BulkSendReq", description = "문서발송(bulk) 요청 파라메터 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = true) + public static class BulkSendReq extends Send { + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private PropertyBulk property; + } + + @Schema(name = "PropertyBulk", description = "SendRequestBulk(문서발송 요청 파라메터)의 property(문서속성)에 대한 정보 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class PropertyBulk extends Property { + /** + * 문서 아이디(외부)(max=40) - 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "외부 문서 아이디(max=40)", example = "A000001") + @Size(min = 1, max = 40, message = "문서 아이디(외부)는 필수 입니다(max=40)") + private String external_document_uuid; + } + //---------------------------------------------------------------------------------- + + //------------------- BulkSendResponses --------------------------------------------------------------- + @Schema(name = "BulkSendResponses DTO", description = "문서발송(bulk) 응답 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class BulkSendResponses { + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private List documents; + } + + @Schema(name = "BulkSendRes DTO", description = "문서발송(bulk) 요청 결과 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class BulkSendRes { + /** + * 문서 아이디(외부)(max=40) - 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "외부 문서 아이디(max=40)", example = " ") + @Size(min = 1, max = 40) + private String external_document_uuid; + + /** + * 카카오페이 문서식별 번호(max:40) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "카카오페이 문서식별 번호(max:40)", example = " ") + @Size(max = 40) + private String document_binder_uuid; + + /** + * 에러 코드(max:40) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 코드(max:40)", example = " ") + @Size(max = 40) + private String error_code; + + /** + * 에러 메세지(max:500) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 메세지(max:500)", example = " ") + @Size(max = 500) + private String error_message; + } + //--------------------------------------------------------------------------------------- + + + //----------------- BulkStatusRequests ---------------------------------------------------------------------- + @Schema(name = "BulkStatusRequests DTO", description = "문서상태(bulk) 조회 요청 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class BulkStatusRequests { + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private List document_binder_uuids; + } + //-------------------------------------------------------------------------------------- + + //-------------- BulkStatusResponses ------------------------------------------------------------------------ + @Schema(name = "BulkStatusResponses DTO", description = "문서상태(bulk) 조회 응답 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class BulkStatusResponses { + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private List documents; + } + + @Schema(name = "BulkStatus DTO", description = "BulkStatusReponse()의 문서상태(bulk)에 대한 정보 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class BulkStatus { + /** + * 카카오페이 문서식별 번호(max:40) - 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "카카오페이 문서식별번호(max:40)", example = " ") + @Size(min = 1, max = 40, message = "카카오페이 문서식별번호는 필수입니다(max:40)") + private String document_binder_uuid; + + /** + * 에러 코드(max:40) + * @see ApiConstants.Error + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 코드(max:40)", example = " ") + @Size(max = 40, message = "에러 코드(max:40)") + private String error_code; + + /** + * 에러 메세지(max:500) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 메세지(max:500)", example = " ") + @Size(max = 500, message = "에러 메세지(max:500)") + private String error_message; + + /** + * 문서 상태 - 성공시 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO) + @Valid + private DocStatus status_data; + } + //----------------------------------------------------------------------------------------------- +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/kakao/model/KkopayDocDTO.java b/mens-api/src/main/java/kr/xit/ens/support/kakao/model/KkopayDocDTO.java new file mode 100644 index 0000000..c7f3e51 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/kakao/model/KkopayDocDTO.java @@ -0,0 +1,214 @@ +package kr.xit.ens.support.kakao.model; + +import javax.validation.Valid; +import javax.validation.constraints.Digits; +import javax.validation.constraints.Size; + +import io.swagger.v3.oas.annotations.media.Schema; +import kr.xit.core.model.IApiResponse; +import lombok.*; +import lombok.experimental.SuperBuilder; + +import org.hibernate.validator.constraints.Length; + +import com.fasterxml.jackson.annotation.JsonInclude; + +/** + *
+ * description : 카카오페이 전자문서 요청 DTO
+ *
+ * packageName : kr.xit.ens.support.kakaopay.model
+ * fileName    : KkopayEltrDocDTO
+ * author      : xitdev
+ * date        : 2023-05-03
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-03    xitdev       최초 생성
+ *
+ * 
+ */ +public class KkopayDocDTO extends KkopayDocAttrDTO { + + //------------------ SendRequest ---------------------------------------------------------------------- + @Schema(name = "SendRequest DTO", description = "문서발송 요청 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class SendRequest { + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private RequestSend document; + } + + @Schema(name = "RequestSend", description = "문서발송 요청 파라메터 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = true) + public static class RequestSend extends KkopayDocAttrDTO.Send { + /** + * 문서 속성 정보 - 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private Property property; + } + //------------------------------------------------------------------------------------- + //------------------ SendResponse ---------------------------------------------------------------------- + @Schema(name = "SendResponse DTO", description = "문서발송 응답 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class SendResponse { + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "카카오페이 문서식별번호(max:40)", example = " ") + @Size(min = 1, max = 40) + private String document_binder_uuid; + + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 코드(max:40)", example = " ") + @Size(max = 40, message = "에러 코드(max:40)") + private String error_code; + + /** + * 에러 메세지(max:500) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 메세지(max:500)", example = " ") + @Size(max = 500, message = "에러 메세지(max:500)") + private String error_message; + } + //------------------------------------------------------------------------------------- + + //------------------- ValidTokenRequest ------------------------------------------------------------------ + @Schema(name = "ValidTokenRequest DTO", description = "카카오페이 전자문서 토큰 유효성 검증 파라메터 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class ValidTokenRequest extends DocumentBinderUuid{ + /** + * 카카오페이 전자문서 서버에서 생성한 토큰(max:50) : 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "카카오페이 전자문서 서버로 부터 받은 토큰(max:50)", example = "CON-cc375944ae3d11ecb91e42193199ee3c") + //@Size(min = 1, max = 50, message = "카카오페이 전자문서 서버 토큰은 필수입니다(max:50)") + @Length(min = 1, max = 50, message = "카카오페이 전자문서 서버 토큰은 필수입니다(max:50)") + private String token; + } + //----------------------------------------------------------------------------------------- + + //----------------- ValidTokenResponse ----------------------------------------------------------------------- + @Schema(name = "ValidTokenResponse DTO", description = "카카오페이 전자문서 토큰 유효성 검증 결과 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class ValidTokenResponse implements IApiResponse { + /** + * 토큰상태값(성공시 USED) : 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "토큰상태값(성공시 USED)", example = " ") + @Size(min = 1, max = 10, message = "토큰상태값은 필수입니다(성공:USED)") + private String token_status; + + /** + * 토큰 만료일시(max:20) : 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "토큰 만료일시(max:20)", example = " ") + @Digits(integer = 20, fraction = 0, message = "토큰 만료일시는 필수입니다(max:20)") + private Long token_expires_at; + + /** + * 토큰 사용일시(long max:20) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "토큰 사용일시(max:20)", example = " ") + @Digits(integer = 20, fraction = 0, message = "토큰 사용일시는 필수입니다(max:20)") + private Long token_used_at; + + /** + * 송신시간(long max:20) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "송신시간(max:20)", example = " ") + @Digits(integer = 20, fraction = 0, message = "송신시간(max:20)") + private Long doc_box_sent_at; + + /** + * 수신시간(long max:20) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "수신시간(max:20)", example = " ") + @Digits(integer = 20, fraction = 0, message = "수신시간(max:20)") + private Long doc_box_received_at; + + /** + * 열람인증을 성공한 최초 시간(long max:20) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "열람인증을 성공한 최초 시간(max:20)", example = " ") + @Digits(integer = 20, fraction = 0, message = "열람인증 최초 시간(max:20)") + private Long authenticated_at; + + /** + * 토큰 사용일시(long max:20) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "알림톡 수신 시간(max:20)", example = " ") + @Digits(integer = 20, fraction = 0, message = "알림톡 수신 시간(max:20)") + private Long user_notified_at; + + /** + * 이용기관 생성 payload(max:200) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "이용기관 생성 payload(max:200)", example = " ") + @Size(max = 200, message = "이용기관 생성 payload(max:200)") + private String payload; + + /** + * 토큰 서명일시(long max:20) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "토큰 서명일시(max:20)", example = " ") + @Digits(integer = 20, fraction = 0, message = "토큰 서명일시(max:20)") + private Long signed_at; + + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 코드(max:40)", example = " ") + @Size(max = 40, message = "에러 코드(max:40)") + private String error_code; + + /** + * 에러 메세지(max:500) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 메세지(max:500)", example = " ") + @Size(max = 500, message = "에러 메세지(max:500)") + private String error_message; + } + //----------------------------------------------------------------------------------------------------- + + + //----------------------- OneTimeToken ----------------------------------------------------------------- + @Schema(name = "OneTimeToken DTO", description = "카카오페이 OTT 요청 파라메터 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + @ToString + @JsonInclude(JsonInclude.Include.NON_NULL) + @EqualsAndHashCode(callSuper = true) + public static class OneTimeToken extends ValidTokenRequest { + /** + * 문서 식별 번호(외부 - max:40) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "외부 문서 식별 번호(max:40)", example = " ") + @Length(max = 40, message = "외부 문서 식별 번호(max:40)") + private String external_document_uuid; + } + //----------------------------------------------------------------------------------------------- + + //----------------- DocStatusResponse ------------------------------------------------------------------------------ + @Schema(name = "DocStatusResponse DTO", description = "카카오페이 전자문서 상태 응답 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class DocStatusResponse extends DocStatus { + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/kakao/service/AsyncKkopayEltrcDocService.java b/mens-api/src/main/java/kr/xit/ens/support/kakao/service/AsyncKkopayEltrcDocService.java new file mode 100644 index 0000000..119523d --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/kakao/service/AsyncKkopayEltrcDocService.java @@ -0,0 +1,278 @@ +package kr.xit.ens.support.kakao.service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; + +import kr.xit.ens.support.kakao.model.KkopayDocAttrDTO; +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpMethod; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import kr.xit.core.exception.BizRuntimeException; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.core.spring.annotation.TraceLogging; +import kr.xit.core.spring.util.ApiWebClient; +import kr.xit.core.support.utils.Checks; +import kr.xit.core.support.utils.JsonUtils; +import kr.xit.ens.support.kakao.model.KkopayDocBulkDTO; +import kr.xit.ens.support.kakao.model.KkopayDocDTO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : 카카오 페이 전자 문서 발송 요청 서비스
+ * packageName : kr.xit.ens.support.kakao.service
+ * fileName    : KkopayEltrcDocService
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ */ +@SuppressWarnings("unchecked") +@Slf4j +@RequiredArgsConstructor +@Component +public class AsyncKkopayEltrcDocService extends EgovAbstractServiceImpl implements IAsyncKkopayEltrcDocService { + + @Value("${contract.provider.kakao.host}") + private String HOST; + @Value("#{'${contract.provider.kakao.api.send}'.split(';')}") + private String[] API_SEND; + @Value("#{'${contract.provider.kakao.api.validToken}'.split(';')}") + private String[] API_VALID_TOKEN; + @Value("#{'${contract.provider.kakao.api.modifyStatus}'.split(';')}") + private String[] API_MODIFY_STATUS; + @Value("#{'${contract.provider.kakao.api.findStatus}'.split(';')}") + private String[] API_STATUS; + @Value("#{'${contract.provider.kakao.api.bulksend}'.split(';')}") + private String[] API_BULKSEND; + @Value("#{'${contract.provider.kakao.api.bulkstatus}'.split(';')}") + private String[] API_BULKSTATUS; + + private final ApiWebClient webClient; + private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + private static final CharSequence DOCUMENT_BINDER_UUID = "{document_binder_uuid}"; + + /** + *
+     * 모바일웹 연계 문서발송 요청 : POST
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리 요청
+     * 
+ * @param reqDTO KkopayDocDTO.SendRequest + * @return CompletableFuture> + */ + @Override + @TraceLogging + @Async("asyncExecutor") + public CompletableFuture> requestSend(final KkopayDocDTO.SendRequest reqDTO) { + List errors = new ArrayList<>(); + errors = validate(reqDTO.getDocument(), errors); + + KkopayDocDTO.RequestSend reqSendDTO = reqDTO.getDocument(); + if(reqSendDTO.getRead_expired_at() != null && reqSendDTO.getRead_expired_sec() != null){ + Objects.requireNonNull(errors).add("처리마감시간(절대시간 또는 상대시간)은 하나만 지정해야 합니다."); + } + + if(reqSendDTO.getRead_expired_at() == null && reqSendDTO.getRead_expired_sec() == null){ + Objects.requireNonNull(errors).add("처리마감시간(절대시간 또는 상대시간)을 지정해야 합니다."); + } + + KkopayDocAttrDTO.Receiver receiver = reqSendDTO.getReceiver(); + if(Checks.isEmpty(receiver.getCi())){ + if(Checks.isEmpty(receiver.getName())) Objects.requireNonNull(errors).add("receiver.name=받는이 이름은 필수입니다."); + if(Checks.isEmpty(receiver.getPhone_number())) Objects.requireNonNull(errors).add("receiver.phone_number=받는이 전화번호는 필수입니다."); + if(Checks.isEmpty(receiver.getBirthday())) Objects.requireNonNull(errors).add("receiver.birthday=받는이 생년월일은 필수입니다."); + } + if(Objects.requireNonNull(errors).size() > 0) throw BizRuntimeException.create(errors.toString()); + + return CompletableFuture.supplyAsync(() -> + webClient.exchange(HOST + API_SEND[0], HttpMethod.valueOf(API_SEND[1]), JsonUtils.toJson(reqDTO), KkopayDocDTO.SendResponse.class)) + .handle((r, e) -> { + if(e != null){ + return webClient.sendError(e); + } + return ApiResponseDTO.success(r); + }); + } + + /** + *
+     * 토큰 유효성 검증(Redirect URL  접속 허용/불허) : GET
+     * 
+ * @param reqDTO KkopayDocDTO.ValidTokenRequest + * @return CompletableFuture> + */ + @Override + @TraceLogging + @Async("asyncExecutor") + public CompletableFuture> validToken(final KkopayDocDTO.ValidTokenRequest reqDTO) { + validate(reqDTO, null); + + String url = HOST + + API_VALID_TOKEN[0].replace(DOCUMENT_BINDER_UUID, reqDTO.getDocument_binder_uuid()) + .replace("{tokens}", reqDTO.getToken()); + + return CompletableFuture.supplyAsync(() -> + webClient.exchange(url, HttpMethod.valueOf(API_VALID_TOKEN[1]), null, KkopayDocDTO.ValidTokenResponse.class)) + .handle((r, e) -> { + if(e != null){ + return webClient.sendError(e); + } + return ApiResponseDTO.success(r); + }); + } + + /** + *
+     * 문서 상태 변경 API : POST
+     * -.문서에 대해서 열람 상태로 변경. 사용자가 문서열람 시(OTT 검증 완료 후 페이지 로딩 완료 시점) 반드시 문서 열람 상태 변경 API를 호출해야 함.
+     * -.미 호출 시 아래와 같은 문제 발생
+     * 1)유통증명시스템을 사용하는 경우 해당 API를 호출한 시점으로 열람정보가 등록되어 미 호출 시 열람정보가 등록 되지 않음.
+     * 2)문서상태조회 API(/v1/documents/{document_binder_uuid}/status) 호출 시 read_at최초 열람시간) 데이터가 내려가지 않음.
+     * 
+ * @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid + * @return CompletableFuture> + */ + @Override + @TraceLogging + @Async("asyncExecutor") + public CompletableFuture> modifyStatus(final KkopayDocAttrDTO.DocumentBinderUuid reqDTO){ + validate(reqDTO, null); + + String body = "{\"document\": {\"is_detail_read\": true} }"; + String url = HOST + API_MODIFY_STATUS[0].replace(DOCUMENT_BINDER_UUID, reqDTO.getDocument_binder_uuid()); + + return CompletableFuture.supplyAsync(() -> + webClient.exchange(url, HttpMethod.valueOf(API_MODIFY_STATUS[1]), body, Void.class)) + .handle((r, e) -> { + if(e != null){ + return webClient.sendError(e); + } + return ApiResponseDTO.success(r); + }); + } + + /** + *
+     * 문서 상태 조회 API : GET
+     * -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
+     * : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
+     * : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
+     * -.doc_box_status 상태변경순서
+     * : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
+     * 
+ * @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid + * @return CompletableFuture> + */ + @Override + @TraceLogging + @Async("asyncExecutor") + public CompletableFuture> findStatus(final KkopayDocAttrDTO.DocumentBinderUuid reqDTO){ + validate(reqDTO, null); + + String url = HOST + API_STATUS[0].replace(DOCUMENT_BINDER_UUID, reqDTO.getDocument_binder_uuid()); + return CompletableFuture.supplyAsync(() -> + webClient.exchange(url, HttpMethod.valueOf(API_STATUS[1]), null, KkopayDocDTO.DocStatusResponse.class)) + .handle((r, e) -> { + if(e != null){ + return webClient.sendError(e); + } + return ApiResponseDTO.success(r); + }); + } + + /** + *
+     * 모바일웹 연계 문서발송 요청 : POST
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO KkopayDocBulkDTO.BulkSendRequests + * @return CompletableFuture> + */ + @Override + @TraceLogging + @Async("asyncExecutor") + public CompletableFuture> requestSendBulk(final KkopayDocBulkDTO.BulkSendRequests reqDTO) { + return CompletableFuture.supplyAsync(() -> + webClient.exchange(HOST + API_BULKSEND[0], HttpMethod.valueOf(API_BULKSEND[1]), JsonUtils.toJson(reqDTO), KkopayDocBulkDTO.BulkSendResponses.class)) + .handle((r, e) -> { + if(e != null){ + return webClient.sendError(e); + } + return ApiResponseDTO.success(r); + }); + } + + /** + *
+     * 대량(bulk) 문서 상태 조회 API : POST
+     * -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
+     * : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
+     * : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
+     * -.doc_box_status 상태변경순서
+     * : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
+     * 
+ * @param reqDTO KkopayDocBulkDTO.BulkStatusRequests + * @return CompletableFuture> + */ + @Override + @TraceLogging + @Async("asyncExecutor") + public CompletableFuture> findBulkStatus(final KkopayDocBulkDTO.BulkStatusRequests reqDTO) { + List errors = new ArrayList<>(); + + List dtos = reqDTO.getDocument_binder_uuids(); + for(int idx = 0; idx < dtos.size(); idx++) { + String binderUuid = dtos.get(idx); + if (Checks.isEmpty(binderUuid) || binderUuid.length() > 40) { + errors.add(String.format("문서 식별 번호는 40자를 넘을 수 없습니다[%d번째]", idx+1)); + } + } + if(errors.size() > 0) { + throw BizRuntimeException.create(errors.toString()); + } + + return CompletableFuture.supplyAsync(() -> + webClient.exchange(HOST + API_BULKSTATUS[0], HttpMethod.valueOf(API_BULKSTATUS[1]), JsonUtils.toJson(reqDTO), KkopayDocBulkDTO.BulkStatusResponses.class)) + .handle((r, e) -> { + if(e != null){ + return webClient.sendError(e); + } + return ApiResponseDTO.success(r); + }); + } + + //------------------------------------------------------------------------------------------------------------------- + private static List validate(T t, List errList) { + Set> list = validator.validate(t); + + if (list.size() > 0) { + List errors = list.stream() + .map(row -> String.format("%s=%s", row.getPropertyPath(), row.getMessageTemplate())) + .collect(Collectors.toList()); + + // 추가적인 유효성 검증이 필요 없는 경우 + if(errList == null){ + if(errors.size() > 0) throw BizRuntimeException.create(errors.toString()); + return null; + } + errList.addAll(errors); + } + return errList; + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/kakao/service/IAsyncKkopayEltrcDocService.java b/mens-api/src/main/java/kr/xit/ens/support/kakao/service/IAsyncKkopayEltrcDocService.java new file mode 100644 index 0000000..f899b5c --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/kakao/service/IAsyncKkopayEltrcDocService.java @@ -0,0 +1,98 @@ +package kr.xit.ens.support.kakao.service; + +import java.util.concurrent.CompletableFuture; + +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.ens.support.kakao.model.KkopayDocAttrDTO; +import kr.xit.ens.support.kakao.model.KkopayDocBulkDTO; +import kr.xit.ens.support.kakao.model.KkopayDocDTO; + +/** + *
+ * description : 카카오 페이 전자 문서 발송 요청 인터 페이스
+ * packageName : kr.xit.ens.support.kakao.service
+ * fileName    : IKkopayEltrcDocService
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ */ +public interface IAsyncKkopayEltrcDocService { + + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO KkopayDocDTO.SendRequest + * @return CompletableFuture> + */ + CompletableFuture> requestSend(final KkopayDocDTO.SendRequest reqDTO); + + + /** + *
+     * 토큰 유효성 검증(Redirect URL  접속 허용/불허)
+     * 
+ * @param reqDTO KkopayDocDTO.ValidTokenRequest + * @return CompletableFuture> + */ + CompletableFuture> validToken(final KkopayDocDTO.ValidTokenRequest reqDTO); + + /** + *
+     * 문서 상태 변경 API
+     * -.문서에 대해서 열람 상태로 변경. 사용자가 문서열람 시(OTT 검증 완료 후 페이지 로딩 완료 시점) 반드시 문서 열람 상태 변경 API를 호출해야 함.
+     * -.미 호출 시 아래와 같은 문제 발생
+     * 1)유통증명시스템을 사용하는 경우 해당 API를 호출한 시점으로 열람정보가 등록되어 미 호출 시 열람정보가 등록 되지 않음.
+     * 2)문서상태조회 API(/v1/documents/{document_binder_uuid}/status) 호출 시 read_at최초 열람시간) 데이터가 내려가지 않음.
+     * 
+ * @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid + * @return CompletableFuture> + */ + CompletableFuture> modifyStatus(final KkopayDocAttrDTO.DocumentBinderUuid reqDTO); + + + /** + *
+     * 문서 상태 조회 API
+     * -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
+     * : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
+     * : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
+     * -.doc_box_status 상태변경순서
+     * : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
+     * 
+ * @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid + * @return CompletableFuture> + */ + CompletableFuture> findStatus(final KkopayDocAttrDTO.DocumentBinderUuid reqDTO); + + /** + *
+     * 대량(bulk) 문서발송 요청
+     * -.이용기관 서버에서 카카오페이 내문서함 서버로 대량(bulk) 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO KkopayDocBulkDTO.BulkSendRequests + * @return CompletableFuture> + */ + CompletableFuture> requestSendBulk(final KkopayDocBulkDTO.BulkSendRequests reqDTO); + + /** + *
+     * 대량(bulk) 문서 상태 조회 API
+     * -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
+     * : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
+     * : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
+     * -.doc_box_status 상태변경순서
+     * : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
+     * 
+ * @param reqDTO KkopayDocBulkDTO.BulkStatusRequests + * @return CompletableFuture> + */ + CompletableFuture> findBulkStatus(final KkopayDocBulkDTO.BulkStatusRequests reqDTO); +} + diff --git a/mens-api/src/main/java/kr/xit/ens/support/kakao/service/IKkopayEltrcDocService.java b/mens-api/src/main/java/kr/xit/ens/support/kakao/service/IKkopayEltrcDocService.java new file mode 100644 index 0000000..6436906 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/kakao/service/IKkopayEltrcDocService.java @@ -0,0 +1,98 @@ +package kr.xit.ens.support.kakao.service; + +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.ens.support.kakao.model.KkopayDocAttrDTO; +import kr.xit.ens.support.kakao.model.KkopayDocBulkDTO; +import kr.xit.ens.support.kakao.model.KkopayDocDTO; + +/** + *
+ * description : 카카오 페이 전자 문서 발송 요청 인터 페이스
+ * packageName : kr.xit.ens.support.kakao.service
+ * fileName    : IKkopayEltrcDocService
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ */ +public interface IKkopayEltrcDocService { + + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO KkopayDocDTO.SendRequest + * @return ApiResponseDTO + */ + ApiResponseDTO requestSend(final KkopayDocDTO.SendRequest reqDTO); + + /** + *
+     * 토큰 유효성 검증(Redirect URL  접속 허용/불허)
+     * 
+ * @param reqDTO KkopayDocDTO.ValidTokenRequest + * @return ApiResponseDTO + */ + ApiResponseDTO validToken(final KkopayDocDTO.ValidTokenRequest reqDTO); + + /** + *
+     * 문서 상태 변경 API
+     * -.문서에 대해서 열람 상태로 변경. 사용자가 문서열람 시(OTT 검증 완료 후 페이지 로딩 완료 시점) 반드시 문서 열람 상태 변경 API를 호출해야 함.
+     * -.미 호출 시 아래와 같은 문제 발생
+     * 1)유통증명시스템을 사용하는 경우 해당 API를 호출한 시점으로 열람정보가 등록되어 미 호출 시 열람정보가 등록 되지 않음.
+     * 2)문서상태조회 API(/v1/documents/{document_binder_uuid}/status) 호출 시 read_at최초 열람시간) 데이터가 내려가지 않음.
+     * 
+ * @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid + * @return ApiResponseDTO + */ + ApiResponseDTO modifyStatus(final KkopayDocAttrDTO.DocumentBinderUuid reqDTO); + + + /** + *
+     * 문서 상태 조회 API
+     * -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
+     * : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
+     * : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
+     * -.doc_box_status 상태변경순서
+     * : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
+     * 
+ * @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid + * @return ApiResponseDTO + */ + ApiResponseDTO findStatus(final KkopayDocAttrDTO.DocumentBinderUuid reqDTO); + + /** + *
+     * 대량(bulk) 문서발송 요청
+     * -.이용기관 서버에서 카카오페이 내문서함 서버로 대량(bulk) 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO KkopayDocBulkDTO.BulkSendRequests + * @return ApiResponseDTO + */ + ApiResponseDTO requestSendBulk(final KkopayDocBulkDTO.BulkSendRequests reqDTO); + + /** + *
+     * 대량(bulk) 문서 상태 조회 API
+     * -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
+     * : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
+     * : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
+     * -.doc_box_status 상태변경순서
+     * : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
+     * 
+ * @param reqDTO KkopayDocBulkDTO.BulkStatusRequests + * @return ApiResponseDTO + */ + ApiResponseDTO findBulkStatus(final KkopayDocBulkDTO.BulkStatusRequests reqDTO); + + + ApiResponseDTO findMyDocReadyAndMblPage(KkopayDocDTO.OneTimeToken reqDTO); +} + diff --git a/mens-api/src/main/java/kr/xit/ens/support/kakao/service/IKkopayEltrcDocTestService.java b/mens-api/src/main/java/kr/xit/ens/support/kakao/service/IKkopayEltrcDocTestService.java new file mode 100644 index 0000000..16f6ad3 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/kakao/service/IKkopayEltrcDocTestService.java @@ -0,0 +1,68 @@ +package kr.xit.ens.support.kakao.service; + +import kr.xit.ens.support.kakao.model.KkopayDocAttrDTO; +import kr.xit.ens.support.kakao.model.KkopayDocBulkDTO; +import kr.xit.ens.support.kakao.model.KkopayDocDTO; + +/** + *
+ * description : 카카오 페이 전자 문서 발송 요청 테스트 인터 페이스
+ * packageName : kr.xit.ens.support.kakao.service
+ * fileName    : IKkopayEltrcDocTestService
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ */ +public interface IKkopayEltrcDocTestService { + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO + * @return ResponseEntity + */ + KkopayDocAttrDTO.DocumentBinderUuid requestSend(final KkopayDocDTO.SendRequest reqDTO); + + + /** + *
+     * 토큰 유효성 검증(Redirect URL  접속 허용/불허)
+     * 
+ * @param reqDTO KkoPayEltrDocDTO.RequestSendReq + * @return ResponseEntity + */ + KkopayDocDTO.ValidTokenResponse validToken(final KkopayDocDTO.ValidTokenRequest reqDTO); + + /** + *
+     * 대량(bulk) 문서발송 요청
+     * -.이용기관 서버에서 카카오페이 내문서함 서버로 대량(bulk) 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO + * @return KkopayDocBulkDTO.SendBulkResponse + */ + KkopayDocBulkDTO.BulkSendResponses requestSendBulk(final KkopayDocBulkDTO.BulkSendRequests reqDTO); + + /** + *
+     * 대량(bulk) 문서 상태 조회 API
+     * -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
+     * : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
+     * : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
+     * -.doc_box_status 상태변경순서
+     * : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
+     * 
+ * @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid + * @return KkopayDocBulkDTO.BulkStatusResponse + */ + KkopayDocBulkDTO.BulkStatusResponses findBulkStatus(final KkopayDocBulkDTO.BulkStatusRequests reqDTO); + + KkopayDocDTO.DocStatusResponse callApi(KkopayDocAttrDTO.DocumentBinderUuid reqDTO); +} + diff --git a/mens-api/src/main/java/kr/xit/ens/support/kakao/service/KkopayEltrcDocService.java b/mens-api/src/main/java/kr/xit/ens/support/kakao/service/KkopayEltrcDocService.java new file mode 100644 index 0000000..7fc3c1f --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/kakao/service/KkopayEltrcDocService.java @@ -0,0 +1,303 @@ +package kr.xit.ens.support.kakao.service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import kr.xit.core.exception.BizRuntimeException; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.core.model.ErrorDTO; +import kr.xit.core.spring.annotation.TraceLogging; +import kr.xit.core.spring.util.ApiWebClient; +import kr.xit.core.support.utils.Checks; +import kr.xit.core.support.utils.JsonUtils; +import kr.xit.ens.support.kakao.model.KkopayDocAttrDTO; +import kr.xit.ens.support.kakao.model.KkopayDocBulkDTO; +import kr.xit.ens.support.kakao.model.KkopayDocDTO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import org.apache.commons.lang3.StringUtils; +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Component; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; + +/** + *
+ * description : 카카오 페이 전자 문서 발송 요청 서비스
+ * packageName : kr.xit.ens.support.kakao.service
+ * fileName    : KkopayEltrcDocService
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class KkopayEltrcDocService extends EgovAbstractServiceImpl implements IKkopayEltrcDocService { + + @Value("${contract.provider.kakao.host}") + private String HOST; + @Value("#{'${contract.provider.kakao.api.send}'.split(';')}") + private String[] API_SEND; + @Value("#{'${contract.provider.kakao.api.validToken}'.split(';')}") + private String[] API_VALID_TOKEN; + @Value("#{'${contract.provider.kakao.api.modifyStatus}'.split(';')}") + private String[] API_MODIFY_STATUS; + @Value("#{'${contract.provider.kakao.api.findStatus}'.split(';')}") + private String[] API_STATUS; + @Value("#{'${contract.provider.kakao.api.bulksend}'.split(';')}") + private String[] API_BULKSEND; + @Value("#{'${contract.provider.kakao.api.bulkstatus}'.split(';')}") + private String[] API_BULKSTATUS; + + private final ApiWebClient webClient; + private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + private static final CharSequence DOCUMENT_BINDER_UUID = "{document_binder_uuid}"; + + + /** + *
+     * 모바일웹 연계 문서발송 요청 : POST
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리 요청
+     * 
+ * @param reqDTO KkoPayEltrDocDTO.RequestSendReq + * @return ApiResponseDTO + */ + @Override + @TraceLogging + public ApiResponseDTO requestSend(final KkopayDocDTO.SendRequest reqDTO) { + List errors = new ArrayList<>(); + errors = validate(reqDTO.getDocument(), errors); + + KkopayDocDTO.RequestSend reqSendDTO = reqDTO.getDocument(); + if(reqSendDTO.getRead_expired_at() != null && reqSendDTO.getRead_expired_sec() != null){ + Objects.requireNonNull(errors).add("처리마감시간(절대시간 또는 상대시간)은 하나만 지정해야 합니다."); + } + if(reqSendDTO.getRead_expired_at() == null && reqSendDTO.getRead_expired_sec() == null){ + Objects.requireNonNull(errors).add("처리마감시간(절대시간 또는 상대시간)을 지정해야 합니다."); + } + + KkopayDocAttrDTO.Receiver receiver = reqSendDTO.getReceiver(); + if(Checks.isEmpty(receiver.getCi())){ + if(Checks.isEmpty(receiver.getName())) Objects.requireNonNull(errors).add("receiver.name=받는이 이름은 필수입니다."); + if(Checks.isEmpty(receiver.getPhone_number())) Objects.requireNonNull(errors).add("receiver.phone_number=받는이 전화번호는 필수입니다."); + if(Checks.isEmpty(receiver.getBirthday())) Objects.requireNonNull(errors).add("receiver.birthday=받는이 생년월일은 필수입니다."); + } + if(Objects.requireNonNull(errors).size() > 0) throw BizRuntimeException.create(errors.toString()); + return ApiResponseDTO.success(webClient.exchange(HOST + API_SEND[0], HttpMethod.valueOf(API_SEND[1]), JsonUtils.toJson(reqDTO), KkopayDocDTO.SendResponse.class)); + } + + /** + *
+     * 토큰 유효성 검증(Redirect URL  접속 허용/불허) : GET
+     * 
+ * @param reqDTO KkopayDocDTO.ValidTokenRequest + * @return ApiResponseDTO + */ + @Override + @TraceLogging + public ApiResponseDTO validToken(final KkopayDocDTO.ValidTokenRequest reqDTO) { + validate(reqDTO, null); + + String url = HOST + + API_VALID_TOKEN[0].replace(DOCUMENT_BINDER_UUID, reqDTO.getDocument_binder_uuid()) + .replace("{tokens}", reqDTO.getToken()); + return ApiResponseDTO.success(webClient.exchange(url, HttpMethod.valueOf(API_VALID_TOKEN[1]), null, KkopayDocDTO.ValidTokenResponse.class)); + } + + /** + *
+     * 문서 상태 변경 API : POST
+     * -.문서에 대해서 열람 상태로 변경. 사용자가 문서열람 시(OTT 검증 완료 후 페이지 로딩 완료 시점) 반드시 문서 열람 상태 변경 API를 호출해야 함.
+     * -.미 호출 시 아래와 같은 문제 발생
+     * 1)유통증명시스템을 사용하는 경우 해당 API를 호출한 시점으로 열람정보가 등록되어 미 호출 시 열람정보가 등록 되지 않음.
+     * 2)문서상태조회 API(/v1/documents/{document_binder_uuid}/status) 호출 시 read_at최초 열람시간) 데이터가 내려가지 않음.
+     * 
+ * @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid + * @return ApiResponseDTO + */ + @Override + @TraceLogging + public ApiResponseDTO modifyStatus(final KkopayDocAttrDTO.DocumentBinderUuid reqDTO){ + validate(reqDTO, null); + + String body = "{\"document\": {\"is_detail_read\": true} }"; + String url = HOST + API_MODIFY_STATUS[0].replace(DOCUMENT_BINDER_UUID, reqDTO.getDocument_binder_uuid()); + + return ApiResponseDTO.success(webClient.exchange(url, HttpMethod.valueOf(API_MODIFY_STATUS[1]), body, Void.class)); + } + + /** + *
+     * 문서 상태 조회 API : GET
+     * -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
+     * : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
+     * : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
+     * -.doc_box_status 상태변경순서
+     * : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
+     * 
+ * @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid + * @return ApiResponseDTO + */ + @Override + @TraceLogging + public ApiResponseDTO findStatus(final KkopayDocAttrDTO.DocumentBinderUuid reqDTO){ + validate(reqDTO, null); + + String url = HOST + API_STATUS[0].replace(DOCUMENT_BINDER_UUID, reqDTO.getDocument_binder_uuid()); + return ApiResponseDTO.success(webClient.exchange(url, HttpMethod.valueOf(API_STATUS[1]), null, KkopayDocDTO.DocStatusResponse.class)); + } + + /** + *
+     * 모바일웹 연계 문서발송 요청 : POST
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO KkopayDocBulkDTO.BulkSendRequests + * @return ApiResponseDTO + */ + @Override + @TraceLogging + public ApiResponseDTO requestSendBulk(final KkopayDocBulkDTO.BulkSendRequests reqDTO) { + List errors = new ArrayList<>(); + + //TODO :: Collection validation + List dtos = reqDTO.getDocuments(); + for(int idx = 0; idx < dtos.size(); idx++) { + Set> list = validator.validate(dtos.get(idx)); + if (list.size() > 0) { + + int finalIdx = idx; + errors.addAll(list.stream() + .map(row -> String.format("%s[%d]=%s", row.getPropertyPath(), finalIdx +1, row.getMessageTemplate())) + .collect(Collectors.toList()) + ); + } + } + + for(int idx = 0; idx < dtos.size(); idx++) { + if(dtos.get(idx).getRead_expired_at() != null && dtos.get(idx).getRead_expired_sec() != null){ + errors.add("처리마감시간(절대시간 또는 상대시간)은 하나만 지정해야 합니다."); + } + if(dtos.get(idx).getRead_expired_at() == null && dtos.get(idx).getRead_expired_sec() == null){ + errors.add("처리마감시간(절대시간 또는 상대시간)을 지정해야 합니다."); + } + + KkopayDocAttrDTO.Receiver receiver = dtos.get(idx).getReceiver(); + if (Checks.isEmpty(receiver.getCi())) { + if (Checks.isEmpty(receiver.getName())) errors.add(String.format("받는이 이름은 필수입니다(receiver.name[%d] 번째 오류)", idx+1)); + if (Checks.isEmpty(receiver.getPhone_number())) errors.add(String.format("받는이 전화번호는 필수입니다(receiver.phone_number[%d] 번째 오류)", idx+1)); + if (Checks.isEmpty(receiver.getBirthday())) errors.add(String.format("받는이 생년월일은 필수입니다(receiver.birthday[%d] 번째 오류)", idx+1)); + } else { + StringBuilder sb = new StringBuilder() + .append(StringUtils.defaultString(receiver.getName(), StringUtils.EMPTY)) + .append(StringUtils.defaultString(receiver.getPhone_number(), StringUtils.EMPTY)) + .append(StringUtils.defaultString(receiver.getBirthday(), StringUtils.EMPTY)); + + if(Checks.isNotEmpty(sb.toString())){ + errors.add(String.format("CI가 지정 되었습니다(받는이 정보 불필요:[%d] 번째 오류) .", idx+1)); + } + } + } + if(errors.size() > 0){ + throw BizRuntimeException.create(errors.toString()); + } + + return ApiResponseDTO.success(webClient.exchange(HOST + API_BULKSEND[0], HttpMethod.valueOf(API_BULKSEND[1]), JsonUtils.toJson(reqDTO), KkopayDocBulkDTO.BulkSendResponses.class)); + } + + /** + *
+     * 대량(bulk) 문서 상태 조회 API : POST
+     * -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
+     * : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
+     * : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
+     * -.doc_box_status 상태변경순서
+     * : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
+     * 
+ * @param reqDTO KkopayDocBulkDTO.BulkStatusRequests + * @return ApiResponseDTO + */ + @Override + @TraceLogging + public ApiResponseDTO findBulkStatus(final KkopayDocBulkDTO.BulkStatusRequests reqDTO) { + List errors = new ArrayList<>(); + + List dtos = reqDTO.getDocument_binder_uuids(); + for(int idx = 0; idx < dtos.size(); idx++) { + String binderUuid = dtos.get(idx); + if (Checks.isEmpty(binderUuid) || binderUuid.length() > 40) { + errors.add(String.format("문서 식별 번호는 40자를 넘을 수 없습니다[%d번째]", idx+1)); + } + } + if(errors.size() > 0) { + throw BizRuntimeException.create(errors.toString()); + } + return ApiResponseDTO.success(webClient.exchange(HOST + API_BULKSTATUS[0], HttpMethod.valueOf(API_BULKSTATUS[1]), JsonUtils.toJson(reqDTO), KkopayDocBulkDTO.BulkStatusResponses.class)); + } + + + + + + @Override + public ApiResponseDTO findMyDocReadyAndMblPage(KkopayDocDTO.OneTimeToken reqDTO) { + + String url = HOST + API_VALID_TOKEN[0].replace(DOCUMENT_BINDER_UUID, reqDTO.getDocument_binder_uuid()) + .replace("{tokens}", reqDTO.getToken()); + + // 유효성 검증 + KkopayDocDTO.ValidTokenResponse validTokenRes = webClient.exchange(url, HttpMethod.valueOf(API_VALID_TOKEN[1]), null, + KkopayDocDTO.ValidTokenResponse.class); + + if(!"USED".equals(validTokenRes.getToken_status())){ + return ApiResponseDTO.error(validTokenRes.getError_code(), validTokenRes.getError_message()); + } + + // 문서상태 변경 + final String body = "{\"document\": {\"is_detail_read\": true} }"; + String url2 = HOST + API_MODIFY_STATUS[0].replace(DOCUMENT_BINDER_UUID, reqDTO.getDocument_binder_uuid()); + + // 정상 : HttpStatus.NO_CONTENT(204) return + // error : body에 error_code, error_message return + ErrorDTO errorDTO = webClient.exchange(url2, HttpMethod.valueOf(API_MODIFY_STATUS[1]), body, ErrorDTO.class); + if(errorDTO != null){ + return ApiResponseDTO.error(errorDTO.getErrorCode(), errorDTO.getErrorMessage()); + } + + + return ApiResponseDTO.success(); + } + + //------------------------------------------------------------------------------------------------------------------- + private static List validate(T t, List errList) { + Set> list = validator.validate(t); + + if (list.size() > 0) { + List errors = list.stream() + .map(row -> String.format("%s=%s", row.getPropertyPath(), row.getMessageTemplate())) + .collect(Collectors.toList()); + + // 추가적인 유효성 검증이 필요 없는 경우 + if(errList == null){ + if(errors.size() > 0) throw BizRuntimeException.create(errors.toString()); + return null; + } + errList.addAll(errors); + } + return errList; + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/kakao/service/KkopayEltrcDocTestService.java b/mens-api/src/main/java/kr/xit/ens/support/kakao/service/KkopayEltrcDocTestService.java new file mode 100644 index 0000000..f213256 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/kakao/service/KkopayEltrcDocTestService.java @@ -0,0 +1,288 @@ +package kr.xit.ens.support.kakao.service; + +import kr.xit.core.exception.BizRuntimeException; +import kr.xit.core.spring.annotation.TraceLogging; +import kr.xit.core.spring.util.ApiWebClient; +import kr.xit.core.support.utils.Checks; +import kr.xit.core.support.utils.JsonUtils; +import kr.xit.ens.support.common.ApiConstants; +import kr.xit.ens.support.kakao.model.KkopayDocAttrDTO; +import kr.xit.ens.support.kakao.model.KkopayDocBulkDTO; +import kr.xit.ens.support.kakao.model.KkopayDocDTO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import org.apache.commons.lang3.StringUtils; +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; + +import java.util.*; +import java.util.stream.Collectors; + +/** + *
+ * description : 카카오 페이 전자 문서 발송 요청 서비스 테스트
+ * packageName : kr.xit.ens.support.kakao.service
+ * fileName    : KkopayEltrcDocTestServiceImpl
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class KkopayEltrcDocTestService extends EgovAbstractServiceImpl implements IKkopayEltrcDocTestService { + + @Value("${contract.provider.kakao.host}") + private String HOST; + @Value("#{'${contract.provider.kakao.api.bulksend}'.split(';')}") + private String[] API_BULKSEND; + + private final ApiWebClient webClient; + + + + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO + * @return ResponseEntity + */ + @Override + @TraceLogging + public KkopayDocAttrDTO.DocumentBinderUuid requestSend(final KkopayDocDTO.SendRequest reqDTO) { + List errors = new ArrayList<>(); + + final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + // 실제 데이타만 검증 + Set> list = validator.validate(reqDTO.getDocument()); + if (list.size() > 0) { + + errors = list.stream() + .map(row -> String.format("%s=%s", row.getPropertyPath(), row.getMessageTemplate())) + .collect(Collectors.toList()); + } + + KkopayDocDTO.RequestSend reqSendDTO = reqDTO.getDocument(); + if(reqSendDTO.getRead_expired_at() != null && reqSendDTO.getRead_expired_sec() != null){ + errors.add("처리마감시간(절대시간 또는 상대시간)은 하나만 지정해야 합니다."); + } + if(reqSendDTO.getRead_expired_at() == null && reqSendDTO.getRead_expired_sec() == null){ + errors.add("처리마감시간(절대시간 또는 상대시간)을 지정해야 합니다."); + } + + KkopayDocAttrDTO.Receiver receiver = reqSendDTO.getReceiver(); + if(Checks.isEmpty(receiver.getCi())){ + if(Checks.isEmpty(receiver.getName())) errors.add("받는이 이름은 필수입니다."); + if(Checks.isEmpty(receiver.getPhone_number())) errors.add("받는이 전화번호는 필수입니다."); + if(Checks.isEmpty(receiver.getBirthday())) errors.add("받는이 생년월일은 필수입니다."); + } + if(errors.size() > 0){ + throw BizRuntimeException.create(errors.toString()); + } + + return KkopayDocAttrDTO.DocumentBinderUuid + .builder() + .document_binder_uuid("BIN-eue7e73uw737377eeeee") + .build(); + } + + /** + *
+     * 토큰 유효성 검증(Redirect URL  접속 허용/불허)
+     * 
+ * @param reqDTO KkoPayEltrDocDTO.RequestSendReq + * @return ResponseEntity + */ + @Override + @TraceLogging + public KkopayDocDTO.ValidTokenResponse validToken(final KkopayDocDTO.ValidTokenRequest reqDTO) { + final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + // 실제 데이타만 검증 + Set> list = validator.validate(reqDTO); + if (list.size() > 0) { + + List errors = list.stream() + .map(row -> String.format("%s=%s", row.getPropertyPath(), row.getMessageTemplate())) + .collect(Collectors.toList()); + + throw BizRuntimeException.create(errors.toString()); + } + + return KkopayDocDTO.ValidTokenResponse + .builder() + .token_status("USED") + .token_expires_at(1624344762L) + .payload("payload 파라미터 입니다.") + .build(); + } + + + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO KkoPayEltrDocDTO.RequestSendReq + * @return + */ + @Override + @TraceLogging + public KkopayDocBulkDTO.BulkSendResponses requestSendBulk(final KkopayDocBulkDTO.BulkSendRequests reqDTO) { + List errors = new ArrayList<>(); + final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + + //TODO :: Collection validation + List dtos = reqDTO.getDocuments(); + for(int idx = 0; idx < dtos.size(); idx++) { + Set> list = validator.validate(dtos.get(idx)); + if (list.size() > 0) { + + int finalIdx = idx; + errors.addAll(list.stream() + .map(row -> String.format("%s[%d]=%s", row.getPropertyPath(), finalIdx +1, row.getMessageTemplate())) + .collect(Collectors.toList()) + ); + } + } + + for(int idx = 0; idx < dtos.size(); idx++) { + + if(dtos.get(idx).getRead_expired_at() != null && dtos.get(idx).getRead_expired_sec() != null){ + errors.add("처리마감시간(절대시간 또는 상대시간)은 하나만 지정해야 합니다."); + } + if(dtos.get(idx).getRead_expired_at() == null && dtos.get(idx).getRead_expired_sec() == null){ + errors.add("처리마감시간(절대시간 또는 상대시간)을 지정해야 합니다."); + } + + KkopayDocAttrDTO.Receiver receiver = dtos.get(idx).getReceiver(); + if (Checks.isEmpty(receiver.getCi())) { + if (Checks.isEmpty(receiver.getName())) errors.add(String.format("받는이 이름은 필수입니다([%d] 번째 오류)", idx+1)); + if (Checks.isEmpty(receiver.getPhone_number())) errors.add(String.format("받는이 전화번호는 필수입니다([%d] 번째 오류)", idx+1)); + if (Checks.isEmpty(receiver.getBirthday())) errors.add(String.format("받는이 생년월일은 필수입니다([%d] 번째 오류)", idx+1)); + } else { + StringBuilder sb = new StringBuilder() + .append(StringUtils.defaultString(receiver.getName(), StringUtils.EMPTY)) + .append(StringUtils.defaultString(receiver.getPhone_number(), StringUtils.EMPTY)) + .append(StringUtils.defaultString(receiver.getBirthday(), StringUtils.EMPTY)); + + if(Checks.isNotEmpty(sb.toString())){ + errors.add(String.format("CI가 지정 되었습니다(받는이 정보 불필요:[%d] 번째 오류) .", idx+1)); + } + } + } + if(errors.size() > 0){ + throw BizRuntimeException.create(errors.toString()); + } + + List resDTO = new ArrayList<>(); + resDTO.add(KkopayDocBulkDTO.BulkSendRes.builder() + .external_document_uuid(reqDTO.getDocuments().get(0).getProperty().getExternal_document_uuid()) + .document_binder_uuid("BIN-127de7hdcyeuudfeuuewe8e8e") + .build() + ); + resDTO.add(KkopayDocBulkDTO.BulkSendRes.builder() + .external_document_uuid(reqDTO.getDocuments().get(0).getProperty().getExternal_document_uuid()) + .error_code(ApiConstants.Error.NOT_FOUND.getCode()) + .error_message("요청 정보를 찾을 수 없습니다. documentBinder를 찾을수 없습니다.") + .build() + ); + + return KkopayDocBulkDTO.BulkSendResponses.builder() + .documents(resDTO) + .build(); + } + + /** + *
+     * 대량(bulk) 문서 상태 조회 API
+     * -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
+     * : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
+     * : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
+     * -.doc_box_status 상태변경순서
+     * : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
+     * 
+ * @param reqDTO + * @return KkopayDocBulkDTO.BulkStatusResponse + */ + @Override + @TraceLogging + public KkopayDocBulkDTO.BulkStatusResponses findBulkStatus(final KkopayDocBulkDTO.BulkStatusRequests reqDTO) { + List errors = new ArrayList<>(); + + List dtos = reqDTO.getDocument_binder_uuids(); + for(int idx = 0; idx < dtos.size(); idx++) { + String binderUuid = dtos.get(idx); + if (Checks.isEmpty(binderUuid) || binderUuid.length() > 40) { + errors.add(String.format("문서 식별 번호는 40자를 넘을 수 없습니다[%d번째]", idx+1)); + } + } + if(errors.size() > 0) { + throw BizRuntimeException.create(errors.toString()); + } + + List resDTO = new ArrayList<>(); + + resDTO.add(KkopayDocBulkDTO.BulkStatus.builder() + .document_binder_uuid(reqDTO.getDocument_binder_uuids().get(0)) + .error_code(ApiConstants.Error.NOT_FOUND.getCode()) + .error_message("요청 정보를 찾을 수 없습니다. documentBinder를 찾을수 없습니다.") + .build() + ); + + resDTO.add(KkopayDocBulkDTO.BulkStatus.builder() + .document_binder_uuid(reqDTO.getDocument_binder_uuids().get(1)) + .status_data(KkopayDocAttrDTO.DocStatus.builder() + .doc_box_status(ApiConstants.DocBoxStatus.RECEIVED) + .doc_box_sent_at(1443456743L) + .doc_box_received_at(1443456743L) + .user_notified_at(1443456743L) + .build()) + .build() + ); + + resDTO.add(KkopayDocBulkDTO.BulkStatus.builder() + .document_binder_uuid(reqDTO.getDocument_binder_uuids().get(2)) + .status_data(KkopayDocAttrDTO.DocStatus.builder() + .doc_box_status(ApiConstants.DocBoxStatus.READ) + .doc_box_sent_at(1443456743L) + .doc_box_received_at(1443456743L) + .doc_box_read_at(1443456743L) + .authenticated_at(1443456743L) + .token_used_at(1443456743L) + .user_notified_at(1443456743L) + .build()) + .build() + ); + + return KkopayDocBulkDTO.BulkStatusResponses.builder() + .documents(resDTO) + .build(); + } + + + + @Override + @TraceLogging + public KkopayDocDTO.DocStatusResponse callApi(final KkopayDocAttrDTO.DocumentBinderUuid reqDTO) { + StringBuilder url = new StringBuilder() + .append("http://localhost:8081") + .append("/api/kakaopay/test/findStatus"); + + return webClient.exchange(url.toString(), HttpMethod.POST, JsonUtils.toJson(reqDTO), KkopayDocDTO.DocStatusResponse.class); + } +} + diff --git a/mens-api/src/main/java/kr/xit/ens/support/kakao/web/AsyncKkopayEltrcDocController.java b/mens-api/src/main/java/kr/xit/ens/support/kakao/web/AsyncKkopayEltrcDocController.java new file mode 100644 index 0000000..ce3934b --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/kakao/web/AsyncKkopayEltrcDocController.java @@ -0,0 +1,203 @@ +package kr.xit.ens.support.kakao.web; + +import kr.xit.ens.support.kakao.model.KkopayDocAttrDTO; +import kr.xit.ens.support.kakao.model.KkopayDocBulkDTO; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.ens.support.kakao.model.KkopayDocDTO; +import kr.xit.ens.support.kakao.service.IAsyncKkopayEltrcDocService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : 카카오 페이 전자 문서 발송 요청 controller
+ * packageName : kr.xit.ens.support.kakao.controller
+ * fileName    : KkopayEltrcDocController
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ */ +@Tag(name = "AsyncKkopayEltrcDocController", description = "카카오페이 MyDoc API(비동기)") +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping(value = "/api/kakaopay/v2") +public class AsyncKkopayEltrcDocController { + + private final IAsyncKkopayEltrcDocService service; + + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO KkopayDocDTO.SendRequest + * @return ApiResponseDTO + */ + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"document\": {\n" + + " \"title\": \"문서 제목\",\n" + + " \"read_expired_sec\": 3600,\n" + + " \"hash\": \"6EFE827AC88914DE471C621AE\",\n" + + " \"common_categories\": [\n" + + " \"NOTICE\"\n" + + " ],\n" + + " \"receiver\": {\n" + + " \"phone_number\": \"01093414345\",\n" + + " \"name\": \"김지호\",\n" + + " \"birthday\": \"19831218\",\n" + + " \"is_required_verify_name\": false\n" + + " },\n" + + " \"property\": {\n" + + " \"link\": \"http://ip:8081/api/kakaopay/v1/ott\",\n" + + " \"cs_number\": \"02-123-4567\",\n" + + " \"cs_name\": \"콜센터\",\n" + + " \"payload\": \"payload 파라미터 입니다.\",\n" + + " \"message\": \"해당 안내문은 다음과 같습니다.\"\n" + + " }\n" + + " }}") + }) + }) + @Operation(summary = "문서발송 요청", description = "카카오페이 전자문서 서버로 문서발송 처리를 요청") + @PostMapping(value = "/documents", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO requestSend( + @RequestBody final KkopayDocDTO.SendRequest reqDTO + ) { + return ApiResponseDTO.of(service.requestSend(reqDTO)); + } + + + /** + *
+     * 토큰 유효성 검증(Redirect URL  접속 허용/불허)
+     * 
+ * @param reqDTO KkopayDocDTO.ValidTokenRequest + * @return ApiResponseDTO + */ + @Operation(summary = "토큰 유효성 검증", description = "Redirect URL 접속 허용/불허") + @PostMapping(value = "/validToken", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO validToken( + @RequestBody final KkopayDocDTO.ValidTokenRequest reqDTO + ) { + return ApiResponseDTO.of(service.validToken(reqDTO)); + } + + /** + *
+     * 문서 상태 변경 API
+     * -.문서에 대해서 열람 상태로 변경. 사용자가 문서열람 시(OTT 검증 완료 후 페이지 로딩 완료 시점) 반드시 문서 열람 상태 변경 API를 호출해야 함.
+     * -.미 호출 시 아래와 같은 문제 발생
+     * 1)유통증명시스템을 사용하는 경우 해당 API를 호출한 시점으로 열람정보가 등록되어 미 호출 시 열람정보가 등록 되지 않음.
+     * 2)문서상태조회 API(/v1/documents/{document_binder_uuid}/status) 호출 시 read_at최초 열람시간) 데이터가 내려가지 않음.
+     * 
+ * @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid + * @return ApiResponseDTO + */ + @Operation(summary = "문서 상태 변경", description = "문서 상태 변경") + @PostMapping(value = "/modifyStatus", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO modifyStatus( + @RequestBody final KkopayDocAttrDTO.DocumentBinderUuid reqDTO + ) { + return ApiResponseDTO.of(service.modifyStatus(reqDTO)); + } + + /** + *
+     * 문서 상태 조회 API
+     * -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
+     * : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
+     * : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
+     * -.doc_box_status 상태변경순서
+     * : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
+     * 
+ * @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid + * @return ApiResponseDTO + */ + @Operation(summary = "문서 상태 조회", description = "문서 상태 조회") + @PostMapping(value = "/findStatus", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO findStatus( + @RequestBody final KkopayDocAttrDTO.DocumentBinderUuid reqDTO + ) { + return ApiResponseDTO.of(service.findStatus(reqDTO)); + } + + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO KkopayDocBulkDTO.BulkSendRequests + * @return ApiResponseDTO + */ + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"documents\": [\n" + + " {\n" + + " \"title\": \"문서 제목\",\n" + + " \"read_expired_sec\": 3600,\n" + + " \"hash\": \"6EFE827AC88914DE471C621AE\",\n" + + " \"common_categories\": [\n" + + " \"NOTICE\"\n" + + " ],\n" + + " \"receiver\": {\n" + + " \"phone_number\": \"01093414345\",\n" + + " \"name\": \"김지호\",\n" + + " \"birthday\": \"19831218\",\n" + + " \"is_required_verify_name\": false\n" + + " },\n" + + " \"property\": {\n" + + " \"link\": \"http://ip:8081/api/kakaopay/v1/ott\",\n" + + " \"cs_number\": \"02-123-4567\",\n" + + " \"cs_name\": \"콜센터\",\n" + + " \"payload\": \"payload 파라미터 입니다.\",\n" + + " \"message\": \"해당 안내문은 다음과 같습니다.\",\n" + + " \"external_document_uuid\": \"A000001\"\n" + + " }\n" + + " }\n" + + " ]}") + }) + }) + @Operation(summary = "대량 문서발송 요청", description = "카카오페이 전자문서 서버로 대량 문서발송 처리를 요청") + @PostMapping(value = "/documents/bulk", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO requestSendBulk( + @RequestBody final KkopayDocBulkDTO.BulkSendRequests reqDTO + ) { + return ApiResponseDTO.of(service.requestSendBulk(reqDTO)); + } + + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO KkopayDocBulkDTO.BulkStatusRequests + * @return ApiResponseDTO + */ + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"document_binder_uuids\": [\n" + + " \"BIN-883246dbff7b11edb3bb7affed8a016d\"\n" + + " ]}") + }) + }) + @Operation(summary = "대량 문서 상태 조회 요청", description = "카카오페이 전자문서 서버로 대량 문서 상태 조회 요청") + @PostMapping(value = "/documents/bulk/status", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO findBulkStatus( + @RequestBody final KkopayDocBulkDTO.BulkStatusRequests reqDTO + ) { + return ApiResponseDTO.of(service.findBulkStatus(reqDTO)); + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/kakao/web/KkopayBatchController.java b/mens-api/src/main/java/kr/xit/ens/support/kakao/web/KkopayBatchController.java new file mode 100644 index 0000000..28d2298 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/kakao/web/KkopayBatchController.java @@ -0,0 +1,172 @@ +package kr.xit.ens.support.kakao.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.ens.support.kakao.model.KkopayDocAttrDTO; +import kr.xit.ens.support.kakao.model.KkopayDocBulkDTO; +import kr.xit.ens.support.kakao.model.KkopayDocDTO; +import kr.xit.ens.support.kakao.service.IKkopayEltrcDocService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *
+ * description : 카카오 페이 전자 문서 발송 요청 controller
+ * packageName : kr.xit.ens.support.kakao.controller
+ * fileName    : KkopayBatchController
+ * author      : julim
+ * date        : 2023-08-30
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ */ +@Tag(name = "KkopayEltrcDocController", description = "카카오페이 MyDoc API") +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping(value = "/api/kakaopay/batch/v1") +public class KkopayBatchController { + @Value("${contract.provider.kakao.token}") + private String accessToken; + + private final IKkopayEltrcDocService service; + + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO + * @return ResponseEntity + */ + @Operation(summary = "문서발송 요청", description = "카카오페이 전자문서 서버로 문서발송 처리를 요청") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"document\": {\n" + + " \"title\": \"문서 제목\",\n" + + " \"read_expired_sec\": 3600,\n" + + " \"hash\": \"6EFE827AC88914DE471C621AE\",\n" + + " \"common_categories\": [\n" + + " \"NOTICE\"\n" + + " ],\n" + + " \"receiver\": {\n" + + " \"phone_number\": \"01093414345\",\n" + + " \"name\": \"김지호\",\n" + + " \"birthday\": \"19831218\",\n" + + " \"is_required_verify_name\": false\n" + + " },\n" + + " \"property\": {\n" + + " \"link\": \"http://ip:8081/api/kakaopay/v1/ott\",\n" + + " \"cs_number\": \"02-123-4567\",\n" + + " \"cs_name\": \"콜센터\",\n" + + " \"payload\": \"payload 파라미터 입니다.\",\n" + + " \"message\": \"해당 안내문은 다음과 같습니다.\"\n" + + " }\n" + + " }}") + }) + }) + @PostMapping(value = "/documents", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO requestSend( + @RequestBody final KkopayDocDTO.SendRequest reqDTO + ) { + return service.requestSend(reqDTO); + } + + /** + *
+     * 토큰 유효성 검증(Redirect URL  접속 허용/불허)
+     * 
+ * @param reqDTO KkopayDocDTO.ValidTokenRequest + * @return ApiResponseDTO + */ + @Operation(summary = "토큰 유효성 검증", description = "Redirect URL 접속 허용/불허") + @PostMapping(value = "/validToken", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO validToken( + @RequestBody final KkopayDocDTO.ValidTokenRequest reqDTO + ) { + return service.validToken(reqDTO); + } + + /** + *
+     * 문서 상태 변경 API
+     * -.문서에 대해서 열람 상태로 변경. 사용자가 문서열람 시(OTT 검증 완료 후 페이지 로딩 완료 시점) 반드시 문서 열람 상태 변경 API를 호출해야 함.
+     * -.미 호출 시 아래와 같은 문제 발생
+     * 1)유통증명시스템을 사용하는 경우 해당 API를 호출한 시점으로 열람정보가 등록되어 미 호출 시 열람정보가 등록 되지 않음.
+     * 2)문서상태조회 API(/v1/documents/{document_binder_uuid}/status) 호출 시 read_at최초 열람시간) 데이터가 내려가지 않음.
+     * 
+ * @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid + * @return ApiResponseDTO + */ + @Operation(summary = "문서 상태 변경", description = "문서 상태 변경") + @PostMapping(value = "/modifyStatus", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO modifyStatus( + @RequestBody final KkopayDocAttrDTO.DocumentBinderUuid reqDTO + ) { + return service.modifyStatus(reqDTO); + } + + /** + *
+     * 문서 상태 조회 API
+     * -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
+     * : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
+     * : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
+     * -.doc_box_status 상태변경순서
+     * : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
+     * 
+ * @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid + * @return ApiResponseDTO + */ + @Operation(summary = "문서 상태 조회", description = "문서 상태 조회") + @PostMapping(value = "/findStatus", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO findStatus( + @RequestBody final KkopayDocAttrDTO.DocumentBinderUuid reqDTO + ) { + return service.findStatus(reqDTO); + } + + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO KkopayDocBulkDTO.BulkSendRequests + * @return ApiResponseDTO + */ + @Operation(summary = "대량 문서발송 요청", description = "카카오페이 전자문서 서버로 대량 문서발송 처리를 요청") + @PostMapping(value = "/documents/bulk", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO requestSendBulk( + @RequestBody final KkopayDocBulkDTO.BulkSendRequests reqDTO + ) { + return service.requestSendBulk(reqDTO); + } + + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO KkopayDocBulkDTO.BulkStatusRequests + * @return ApiResponseDTO + */ + @Operation(summary = "대량 문서 상태 조회 요청", description = "카카오페이 전자문서 서버로 대량 문서 상태 조회 요청") + @PostMapping(value = "/documents/bulk/status", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO findBulkStatus( + @RequestBody final KkopayDocBulkDTO.BulkStatusRequests reqDTO + ) { + return service.findBulkStatus(reqDTO); + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/kakao/web/KkopayDummyTestController.java b/mens-api/src/main/java/kr/xit/ens/support/kakao/web/KkopayDummyTestController.java new file mode 100644 index 0000000..dc1c203 --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/kakao/web/KkopayDummyTestController.java @@ -0,0 +1,174 @@ +package kr.xit.ens.support.kakao.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.core.exception.BizRuntimeException; +import kr.xit.core.support.utils.Checks; +import kr.xit.ens.support.common.ApiConstants; +import kr.xit.ens.support.kakao.model.KkopayDocAttrDTO; +import kr.xit.ens.support.kakao.model.KkopayDocBulkDTO; +import kr.xit.ens.support.kakao.model.KkopayDocDTO; +import kr.xit.ens.support.kakao.service.IKkopayEltrcDocTestService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.security.SecureRandom; + +/** + *
+ * description : 카카오 페이 전자 문서 발송 요청 테스트 controller
+ * packageName : kr.xit.ens.support.kakao.controller
+ * fileName    : KkopayEltrcDocTestController
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ */ +@Tag(name = "KkopayDummyTestController", description = "카카오페이 MyDoc dummy 테스트") +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping(value = "/api/kakaopay/test") +public class KkopayDummyTestController { + + private final IKkopayEltrcDocTestService service; + + @Operation(summary = "문서발송 요청", description = "카카오페이 전자문서 서버로 문서발송 처리를 요청") + @PostMapping(value = "/documents", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO requestSend( + @RequestBody final KkopayDocDTO.SendRequest reqDTO + ) { + KkopayDocAttrDTO.Receiver receiver = reqDTO.getDocument().getReceiver(); + if(Checks.isEmpty(receiver.getCi())){ + if(Checks.isEmpty(receiver.getName())) throw BizRuntimeException.create("받는이 이름은 필수입니다."); + if(Checks.isEmpty(receiver.getPhone_number())) throw BizRuntimeException.create("받는이 전화번호는 필수입니다."); + if(Checks.isEmpty(receiver.getBirthday())) throw BizRuntimeException.create("받는이 생년월일은 필수입니다."); + } + return ApiResponseDTO.success(service.requestSend(reqDTO)); + } + + @Operation(summary = "토큰 유효성 검증", description = "Redirect URL 접속 허용/불허") + @PostMapping(value = "/validToken", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO validToken( + @RequestBody final KkopayDocDTO.ValidTokenRequest reqDTO + ) { + return ApiResponseDTO.success(service.validToken(reqDTO)); + } + + /** + *
+     * 문서 상태 변경 API
+     * -.문서에 대해서 열람 상태로 변경. 사용자가 문서열람 시(OTT 검증 완료 후 페이지 로딩 완료 시점) 반드시 문서 열람 상태 변경 API를 호출해야 함.
+     * -.미 호출 시 아래와 같은 문제 발생
+     * 1)유통증명시스템을 사용하는 경우 해당 API를 호출한 시점으로 열람정보가 등록되어 미 호출 시 열람정보가 등록 되지 않음.
+     * 2)문서상태조회 API(/v1/documents/{document_binder_uuid}/status) 호출 시 read_at최초 열람시간) 데이터가 내려가지 않음.
+     * 
+ * @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid + * @return ApiResponseDTO + */ + @Operation(summary = "문서 상태 변경", description = "문서 상태 변경") + @PostMapping(value = "/modifyStatus", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO modifyStatus( + @RequestBody final KkopayDocAttrDTO.DocumentBinderUuid reqDTO + ) { + return ApiResponseDTO.empty(); + } + + /** + *
+     * 문서 상태 조회 API
+     * -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
+     * : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
+     * : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
+     * -.doc_box_status 상태변경순서
+     * : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
+     * 
+ * @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid + * @return ApiResponseDTO + */ + @Operation(summary = "문서 상태 조회", description = "문서 상태 조회") + @PostMapping(value = "/findStatus", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO findStatus( + @RequestBody final KkopayDocAttrDTO.DocumentBinderUuid reqDTO + ) { + + SecureRandom random = new SecureRandom(); // Compliant for security-sensitive use cases + int bound = 1000000000; + + return ApiResponseDTO.success(KkopayDocDTO.DocStatusResponse + .builder() + .payload(reqDTO.getDocument_binder_uuid()) + .doc_box_status(ApiConstants.DocBoxStatus.SENT) + .doc_box_sent_at((long)(random.nextInt(bound)) + 1000000000) + .doc_box_received_at((long)(random.nextInt(bound)) + 1000000000) + //.payload("payload 파라미터 입니다.") + .build()); + } + + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO KkopayDocBulkDTO.BulkSendRequests + * @return ApiResponseDTO + */ + @Operation(summary = "대량 문서 발송 요청", description = "카카오페이 전자문서 서버로 대량 문서 발송 요청") + @PostMapping(value = "/documents/bulk", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO requestSendBulk( + @RequestBody final KkopayDocBulkDTO.BulkSendRequests reqDTO + ) { + return ApiResponseDTO.success(service.requestSendBulk(reqDTO)); + } + + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO KkopayDocBulkDTO.BulkStatusRequests + * @return ApiResponseDTO + */ + @Operation(summary = "대량 문서 상태 조회 요청", description = "카카오페이 전자문서 서버로 대량 문서 상태 조회 요청") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(name = "Example" + , summary = "부과정보 전송", description = "개별시스템 -> 세외수입 시스템 호출하여 응답 Response" + , value = "{\"document_binder_uuids\":[\"BIN-ff806328863311ebb61432ac599d6151\",\"BIN-ff806328863311ebb61432ac599d6152\",\"BIN-ff806328863311ebb61432ac599d6153\"]}") + }) + }) + @PostMapping(value = "/documents/bulk/status", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO findBulkStatus( + @RequestBody final KkopayDocBulkDTO.BulkStatusRequests reqDTO + ) { + return ApiResponseDTO.success(service.findBulkStatus(reqDTO)); + } + + + + + + + + + + + @Operation(summary = "Restful API 호출 테스트", description = "Restful API 호출 테스트") + @PostMapping(value = "/callApi", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO callApi(@RequestBody final KkopayDocAttrDTO.DocumentBinderUuid reqDTO) { + return ApiResponseDTO.success(service.callApi(reqDTO)); + } +} diff --git a/mens-api/src/main/java/kr/xit/ens/support/kakao/web/KkopayEltrcDocController.java b/mens-api/src/main/java/kr/xit/ens/support/kakao/web/KkopayEltrcDocController.java new file mode 100644 index 0000000..e09842e --- /dev/null +++ b/mens-api/src/main/java/kr/xit/ens/support/kakao/web/KkopayEltrcDocController.java @@ -0,0 +1,171 @@ +package kr.xit.ens.support.kakao.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.ens.support.kakao.model.KkopayDocAttrDTO; +import kr.xit.ens.support.kakao.model.KkopayDocBulkDTO; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; + +import kr.xit.ens.support.kakao.model.KkopayDocDTO; +import kr.xit.ens.support.kakao.service.IKkopayEltrcDocService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : 카카오 페이 전자 문서 발송 요청 controller
+ * packageName : kr.xit.ens.support.kakao.controller
+ * fileName    : KkopayEltrcDocController
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ */ +@Tag(name = "KkopayEltrcDocController", description = "카카오페이 MyDoc API") +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping(value = "/api/kakaopay/v1") +public class KkopayEltrcDocController { + @Value("${contract.provider.kakao.token}") + private String accessToken; + + private final IKkopayEltrcDocService service; + + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO + * @return ResponseEntity + */ + @Operation(summary = "문서발송 요청", description = "카카오페이 전자문서 서버로 문서발송 처리를 요청") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"document\": {\n" + + " \"title\": \"문서 제목\",\n" + + " \"read_expired_sec\": 3600,\n" + + " \"hash\": \"6EFE827AC88914DE471C621AE\",\n" + + " \"common_categories\": [\n" + + " \"NOTICE\"\n" + + " ],\n" + + " \"receiver\": {\n" + + " \"phone_number\": \"01093414345\",\n" + + " \"name\": \"김지호\",\n" + + " \"birthday\": \"19831218\",\n" + + " \"is_required_verify_name\": false\n" + + " },\n" + + " \"property\": {\n" + + " \"link\": \"http://ip:8081/api/kakaopay/v1/ott\",\n" + + " \"cs_number\": \"02-123-4567\",\n" + + " \"cs_name\": \"콜센터\",\n" + + " \"payload\": \"payload 파라미터 입니다.\",\n" + + " \"message\": \"해당 안내문은 다음과 같습니다.\"\n" + + " }\n" + + " }}") + }) + }) + @PostMapping(value = "/documents", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO requestSend( + @RequestBody final KkopayDocDTO.SendRequest reqDTO + ) { + return service.requestSend(reqDTO); + } + + /** + *
+     * 토큰 유효성 검증(Redirect URL  접속 허용/불허)
+     * 
+ * @param reqDTO KkopayDocDTO.ValidTokenRequest + * @return ApiResponseDTO + */ + @Operation(summary = "토큰 유효성 검증", description = "Redirect URL 접속 허용/불허") + @PostMapping(value = "/validToken", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO validToken( + @RequestBody final KkopayDocDTO.ValidTokenRequest reqDTO + ) { + return service.validToken(reqDTO); + } + + /** + *
+     * 문서 상태 변경 API
+     * -.문서에 대해서 열람 상태로 변경. 사용자가 문서열람 시(OTT 검증 완료 후 페이지 로딩 완료 시점) 반드시 문서 열람 상태 변경 API를 호출해야 함.
+     * -.미 호출 시 아래와 같은 문제 발생
+     * 1)유통증명시스템을 사용하는 경우 해당 API를 호출한 시점으로 열람정보가 등록되어 미 호출 시 열람정보가 등록 되지 않음.
+     * 2)문서상태조회 API(/v1/documents/{document_binder_uuid}/status) 호출 시 read_at최초 열람시간) 데이터가 내려가지 않음.
+     * 
+ * @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid + * @return ApiResponseDTO + */ + @Operation(summary = "문서 상태 변경", description = "문서 상태 변경") + @PostMapping(value = "/modifyStatus", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO modifyStatus( + @RequestBody final KkopayDocAttrDTO.DocumentBinderUuid reqDTO + ) { + return service.modifyStatus(reqDTO); + } + + /** + *
+     * 문서 상태 조회 API
+     * -.이용기관 서버에서 카카오페이 전자문서 서버로 문서 상태에 대한 조회를 요청 합니다.
+     * : 발송된 문서의 진행상태를 알고 싶은 경우, flow와 상관없이 요청 가능
+     * : polling 방식으로 호출할 경우, 호출 간격은 5초를 권장.
+     * -.doc_box_status 상태변경순서
+     * : SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료)
+     * 
+ * @param reqDTO KkopayDocAttrDTO.DocumentBinderUuid + * @return ApiResponseDTO + */ + @Operation(summary = "문서 상태 조회", description = "문서 상태 조회") + @PostMapping(value = "/findStatus", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO findStatus( + @RequestBody final KkopayDocAttrDTO.DocumentBinderUuid reqDTO + ) { + return service.findStatus(reqDTO); + } + + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO KkopayDocBulkDTO.BulkSendRequests + * @return ApiResponseDTO + */ + @Operation(summary = "대량 문서발송 요청", description = "카카오페이 전자문서 서버로 대량 문서발송 처리를 요청") + @PostMapping(value = "/documents/bulk", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO requestSendBulk( + @RequestBody final KkopayDocBulkDTO.BulkSendRequests reqDTO + ) { + return service.requestSendBulk(reqDTO); + } + + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO KkopayDocBulkDTO.BulkStatusRequests + * @return ApiResponseDTO + */ + @Operation(summary = "대량 문서 상태 조회 요청", description = "카카오페이 전자문서 서버로 대량 문서 상태 조회 요청") + @PostMapping(value = "/documents/bulk/status", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO findBulkStatus( + @RequestBody final KkopayDocBulkDTO.BulkStatusRequests reqDTO + ) { + return service.findBulkStatus(reqDTO); + } +} diff --git a/mens-api/src/main/java/kr/xit/package-info.java b/mens-api/src/main/java/kr/xit/package-info.java new file mode 100644 index 0000000..05e490c --- /dev/null +++ b/mens-api/src/main/java/kr/xit/package-info.java @@ -0,0 +1,12 @@ +/** + * ENS API and batch + *

+ * kakaopay 전자문서 요청 + * E-GREEN + * SMS + *

+ * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit; diff --git a/mens-api/src/main/resources/config/application-app.yml b/mens-api/src/main/resources/config/application-app.yml new file mode 100644 index 0000000..53c3db6 --- /dev/null +++ b/mens-api/src/main/resources/config/application-app.yml @@ -0,0 +1,58 @@ +#----------------------------------------------------------------------- +# application 설정 +#----------------------------------------------------------------------- + +app: + # request log + param: + log: + # exclude pattern : CommonsRequestLoggingFilter && LoggingFilter 적용 + exclude-patterns: '/swagger-ui/(.*), /api-docs/(.*)' + # CommonsRequestLoggingFilter 사용 parameter 로그 출력 + enabled: true + # parameter custom 로그 출력(LoggerAspect) + custom.enabled: false + # MDC logging trace 활성 + mdc: + log: + trace: + enabled: false + uris: '/api/kakao/(.*), /api/v1/ens/sendBulks(.*)' + + # slack + slack-webhook: + enabled: false + url: https://hooks.slack.com/services/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 + + ssh: + host: 192.168.200.52 + port: 22 + id: administrator + passwd: 1q2w3e4r5t! + sg: + root-path: /SMSImage + ens-path: /ENSData + rcv: / + backup: /backup + err: /err + + batch: + chunkSize: 2 + cron: + ens: + accept: '0/10 * 8-20 * * *' + make: '0/10 * 8-20 * * *' + send: '0/10 * 8-20 * * *' + close: '0 0/10 7 * * *' + kko-status: '0 0/5 * * * *' + pni: + accept: '0 0/1 23 * * *' +# 사용자 ID 정보를 어디에 저장할 것인지 설정 +# JWT 토큰을 사용하고 SessionCreationPolicy.STATELESS인 경우는 SecurityContext 사용불가 +# --> session에 저장하는 방식은 가능 +# security | session | header +# jwt secret key 설정 +#jwt.secret: 8sknjlO3NPTBqo319DHLNqsQAfRJEdKsETOds +# 토큰 재발급시 토큰(refresh) 전달 방식 : COOKIE | HEADER | DTO +#jwt.refresh.save.type: COOKIE +#jwt.secret: c3ByaW5nLWJvb3Qtc2VjdXJpdHktand0LWlucGl4LWNvcmUtamF2YWZyYW1ld29yay1qYXZhLXRva2VuLWtleQ diff --git a/mens-api/src/main/resources/config/application-dev.yml b/mens-api/src/main/resources/config/application-dev.yml new file mode 100644 index 0000000..0ca7bae --- /dev/null +++ b/mens-api/src/main/resources/config/application-dev.yml @@ -0,0 +1,66 @@ +#----------------------------------------------------------------------- +# local 설정 +#----------------------------------------------------------------------- +spring: + + devtools: + livereload: + enabled: true + +springdoc: + api-docs: + enabled: true + swagger-ui: + enabled: true + +logging: + level: + root: debug + org.apache.http: warn + reactor.netty: warn + io.netty: warn + org.springframework.web.reactive.function.client.ExchangeFunctions: debug + org.springframework: warn + file: + # 로그파일 위치 + path: ${app.data.root.path}/ens/logs + name: ${app.name} + +# Spring Security cors 설정 :: CorsConfiguration 설정 값 +cors: + allowed-origins: http://211.119.124.9:8080 + +# ================================================================================================================== +# SQL logging lib setting +# ================================================================================================================== +decorator: + datasource: + p6spy: + enable-logging: true + +app: + license: + path: ${app.data.root.path}/ens/.pem/ + key: 'mxLAM1fAEDPWkFz8' + data1: 'WEcpGVH9g01hS8L0ke6sL9g8Hmsy4yngfEwH5L/ax62IzmdjIU6mE6GoGnC3iuRN1ENCYlVz2b8BYtAKDltMYZWx+qBjY+KscgfFoMDLIVHMGAUSDF+PyE6KV2DsKoAhc2ZdNnqbFJDgDP/qyFSRjwD1dvzeXTuCcz4mnQxOovXqiMkXwnBKhUBzq9dFeLlsk1e/YoR9SZTrpt0x0T8IeyzqI568FFqNAznmbJ+1kpi9yRaJ5c8MXXEIrfPJVONrLfeVezg1Z9jDj56zPL6gqrKhKtHJwkxpESx+5ik8O01GDcmytsLAeoIz0BiZVLJXUc/T6mZBVP6FfZnDC0dWfA==' + data2: 'YBF68uN720dTzblufB/AkZTxZnDIt6G29UkjIkJL9TDH2UkTB9FPNgtk4TqSTCFr1C4SsSU9Z/XQ957+c6K1JZe20JzDlhRWr6Zty0lCORI6erdpRq0zUsKANeJoB2hgqgeLcDxuOUN2S19MacD2VA4H0+7zKATiT8P7OEWXrAU9M2YmbEMMxHQsc6SZIa3EoGrJdNZrgeVXxL2+aJN28gmMm3CgoxLEEtuSZXK3LEZLlLEZvZtDxuxqx7Os4CtNwvHsDyFcIot6Ghbr+G3EsT+jghvvei0Cg4Qgm4n11yj0hsWR8CwmV7FTTqo03s0zBDRhYhgML81KkE/AtZSVvA==' + data3: 'IWPfGH1nDoOsevS4eyw4fItEqNymamycYa+rN+3v/iQxwy5mohdYNgm/+HcOGua2FJ6VxgGC/9i5b0jKB5L2p8bSBrEQBmluh+DsEMzeUxP8mfD2hTIUfeA9osmXW0al6BfcoJKZyiUGzxoSDY1MMdbAB9FQSBWY9yniGBaEXt3CgJmloXsVoRRolalmO6oqRJq4t1kV0fRijGGMQ5/0nu0Z5fuhCukGmEAsIW0abRXARDzoXYfAZKbKW0L70X8htpFAkk9t2IukeZemHGBBinDBWsh0ZlysHJfcXlFnFh83hXrsYcpUAn34PSd3TfLPeymdoduTF8wX7sOVMuPUVg==' + + # swagger url + url: 'http://211.119.124.9:${server.port}${server.servlet.context-path:}/' + data: + root: + path: /data + param: + log: + # CommonsRequestLoggingFilter 사용 parameter 로그 출력 + enabled: true + # parameter custom 로그 출력(LoggerAspect) + custom.enabled: true + # MDC logging trace 활성 + mdc.log.trace.enabled: true + + # slack + slack-webhook: + enabled: false + url: https://hooks.slack.com/services/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 diff --git a/mens-api/src/main/resources/config/application-ens.yml b/mens-api/src/main/resources/config/application-ens.yml new file mode 100644 index 0000000..62103ca --- /dev/null +++ b/mens-api/src/main/resources/config/application-ens.yml @@ -0,0 +1,28 @@ +#----------------------------------------------------------------------- +# application 설정 +#----------------------------------------------------------------------- +contract: + provider: + # milisecond + connection: + timeout: 30000 + readTimeout: 30000 + thread: + # 동시 실행 스레드 개수 + corePoolSize: 5 + # 스레드 풀에서 사용할 수 있는 최대 개수 + maxPoolSize: 10 + kakao: + isAsync: false + bulk-max-cnt: 10 + host: https://docs-gw.kakaopay.com + #host: https://dummy.restapiexample.com + token: dd394da7f66211eb9cbe46e139ceffc2 + uuid: CON-41ef0535f67211ebbdedd2e6ed332381 + api: + send: /v1/documents;POST + validToken: /v1/{document_binder_uuid}/tokens/{tokens};GET + modifyStatus: /v1/documents/{document_binder_uuid};POST + findStatus: /v1/documents/{document_binder_uuid}/status;GET + bulksend: /v1/documents/bulk;POST + bulkstatus: /v1/documents/bulk/status;POST diff --git a/mens-api/src/main/resources/config/application-jpa.yml b/mens-api/src/main/resources/config/application-jpa.yml new file mode 100644 index 0000000..be0217e --- /dev/null +++ b/mens-api/src/main/resources/config/application-jpa.yml @@ -0,0 +1,52 @@ +#----------------------------------------------------------------------- +# JPA 설정 +#----------------------------------------------------------------------- + +app: + # jpa 활성 여부 + jpa: + enabled: false + +spring: + jpa: + database-platform: org.hibernate.dialect.MySQL5InnoDBDialect + # 템플릿 view 화면의 렌더링이 끝날 때 까지 Lazy fetch 가 가능하도록 해주는 속성 + open-in-view: false + generate-ddl: false + show-sql: true + properties: + order_inserts: true + order_updates: true + default_batch_fetch_size: ${chunkSize:100} + current_session_context_class: org.springframework.orm.hibernate5.SpringSessionContext + implicit_naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + physical_naming_strategy: kr.xit.core.spring.config.support.LowercaseSnakePhysicalNamingStrategy + hibernate: + hbm2ddl: + auto: none + import_files_sql_extractor: org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor + format_sql: true + use_sql_comments: false + jdbc: + batch_size: 20 + lob: + # postgres 사용시 createLob() 미구현 경고 삭제 + non_contextual_creation: true + # Jdbc 환경구성을 하는 과정에서 Default Metadata를 사용할 지 여부 + temp: + use_jdbc_metadata_defaults: false + +# ================================================================================================================== +# JPA logging lib setting +# ================================================================================================================== +decorator: + datasource: + p6spy: + # profiles 별로 정의 + #enable-logging: true + multiline: true + logging: slf4j + custom-appender-class: com.p6spy.engine.spy.appender.Slf4JLogger + log-format: com.xit.core.config.support.P6spySqlFormatConfiguration + tracing: + include-parameter-values: true diff --git a/mens-api/src/main/resources/config/application-local.yml b/mens-api/src/main/resources/config/application-local.yml new file mode 100644 index 0000000..86b432e --- /dev/null +++ b/mens-api/src/main/resources/config/application-local.yml @@ -0,0 +1,100 @@ +#----------------------------------------------------------------------- +# local 설정 +#----------------------------------------------------------------------- +spring: + datasource: + # =============== p6spy =============================== + #driver-class-name: org.mariadb.jdbc.Driver + #url: jdbc:mariadb://211.119.124.9:4407/ens?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true&autoReconnect=true + # root / xit1807 + hikari: + # 9 server + maria: + driver-class-name: org.mariadb.jdbc.Driver + jdbc-url: jdbc:mariadb://211.119.124.9:4407/mens?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true&autoReconnect=true + username: root + password: xit1807 + read-only: false + + # 115 server + # jdbc:oracle:thin:@211.119.124.115:1521:XITSMS + # xit_sms_lg / xit_sms_lg + oracle: + driver-class-name: oracle.jdbc.OracleDriver + jdbc-url: jdbc:oracle:thin:@211.119.124.115:1521:XITSMS + username: xit_sms_lg + password: xit_sms_lg + read-only: false + + devtools: + livereload: + enabled: true + +springdoc: + api-docs: + enabled: true + swagger-ui: + enabled: true + +logging: + level: + root: debug + org.apache.http: warn + reactor.netty: warn + io.netty: warn + org.springframework.web.reactive.function.client.ExchangeFunctions: debug + org.springframework: warn + file: + # 로그파일 위치 + path: ${app.data.root.path}/ens/logs + name: ${app.name} + +# Spring Security cors 설정 :: CorsConfiguration 설정 값 +cors: + allowed-origins: http://localhost:8080 + +# ================================================================================================================== +# SQL logging lib setting +# ================================================================================================================== +decorator: + datasource: + p6spy: + enable-logging: true + +app: + license: + path: ${app.data.root.path}/ens/.pem/ + key: 'mxLAM1fAEDPWkFz8' + data1: 'jz5LT6TlZtewv1GRVN3cI6CgoPgS89Sfh7qSKkCVjjMPOyBKkT386tlnMnjXluTSr8OIvI1pHd96fHxRZHNUBuLeQOkUeWuzkfxlP8C7nDyIrG36T2aontjroAxoNk3oYdIYRVWNs1Iqw39v9xF8NYFFLGqtfv/wnCGxlwTsDwCf9bjAtyd9cTiS27dVrIbrAVKnchgxIF/DUQQc901l4DZ5gsT6aLx6TmzhvAewPK1HfiG9WrMWxpw0TMxt++0Vedh+oZEs48ACMpuHFFh406LynyxxE7boRVbKmh7Tn87gKa6+zzdzIN1kS8sk58Ms1HPvBfvwwnD8qiJItXO6DQ==' + data2: 'Te3bfmvdiMZdpfRwC3OO9UjwbNkvbf16kqEqn9VqwbVztizA9rvMFshlI0vuqai9Hml31IsNINKg+OYkhmkH6ic1I10r6MNIVl3WL5YxfeK7YBmjvNuGZtKwchlWzhMODsgNAq0aIQVi4kLk5filDaZESY10xlNdbf9c/SKGfJeLZxY7DCchkAgj/ZnmZNqOE2kDAoC+O1ksDNTS0+cr+WsKsoFON0EpNI5B2ElBtnT1LmQQ3R+FNCtp7YJaRZA3RPsata05kKH7sL1J0M6A6HIVxisOU3bjH0hB+60BHJfdlEiXo6RJvsPyXotwe8MYVrHJbIQgsepxSDpMFZe8HA==' + data3: 't9qYJTU49dbaeezJkzpM2uY3iLIcy/V/VnyVsWIcd0f4QMLJ3cmLZ0QcMyKoR7CL2CuHMnPJz8j7KFOTQRPpeN/Dl4bCpOu+BM3foYpn4wb5HcLdHJxp5CuFmhTfqRGuUxurv6jcqkwmRzPW35UjQLjeKSdv6m+2b84PN4sZSNeMjQDH0QC85yKphHKV8m6bzqUbHLiZwDXndgpq2/YGKdWjPinlH7PZ+L2xfrfhdWXoY9QrHYVOSPogd81EizzyQseif8GAkeUG1OKAOomhyEuTOxtdGbUew59YuuBlUpORgj/Koclyd2shHyne9CJdqnQqAA2mh61V1ZzBUvSIWQ==' + # swagger url + url: 'http://localhost:${server.port}${server.servlet.context-path:}/' + data: + root: + path: D:/data + param: + log: + # CommonsRequestLoggingFilter 사용 parameter 로그 출력 + enabled: true + # parameter custom 로그 출력(LoggerAspect) + custom.enabled: true + # MDC logging trace 활성 + mdc.log.trace.enabled: true + + # slack + slack-webhook: + enabled: false + url: https://hooks.slack.com/services/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 + + ssh: + host: 211.119.124.9 + port: 22 + id: xituser + passwd: xituser!@ + sg: + root-path: /data/ens/sg-pni-cctv + ens-path: /data/ens/sg-ens-cctv + rcv: /rcv + backup: /backup + err: /err diff --git a/mens-api/src/main/resources/config/application-prod.yml b/mens-api/src/main/resources/config/application-prod.yml new file mode 100644 index 0000000..5b3cd98 --- /dev/null +++ b/mens-api/src/main/resources/config/application-prod.yml @@ -0,0 +1,92 @@ +#----------------------------------------------------------------------- +# local 설정 +#----------------------------------------------------------------------- +spring: + datasource: + # ================ log4jdbc =========================== + #driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy + # mysql에서 batchUpdate 사용하기 위해 rewriteBatchedStatements 필요 + #url: jdbc:log4jdbc:mariadb://211.119.124.117:53306/ens?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true + # ===================================================== + # =============== p6spy =============================== + #driver-class-name: org.mariadb.jdbc.Driver + #url: jdbc:mariadb://211.119.124.9:4407/ens?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true&autoReconnect=true + + # r2dbc 설정 : 추후 진행 여부 결정 + #driver-class-name: org.mariadb.jdbc.Driver + #url: r2dbc:pool:mariadb://211.119.124.9:4407/ens?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true + # ===================================================== + #username: root + #password: xit1807 + hikari: + # 9 server + maria: + driver-class-name: org.mariadb.jdbc.Driver + # jdbc:mariadb://127.0.0.1:4407/ens?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true&autoReconnect=true + jdbc-url: ENC(QrsIhXG/ZgD5a1SYaf+1CaZYkLxuMP4s7//+NXV1SlLHKXRFm7FFA8iP10Dlak3VOFjFSaWEkchSWcNQ4+4m94fSWW7uy+/4nW3jBq4aHuvVVCqy2rfgInqYbYMlno91vUmWkMbKuX8T5nF7UltaAuQOXH/ImCT8whPxAE+VOM1onyuIr0fbVg==) + username: ENC(L8Xp+ZuLxlXahupxYDWzgA==) + password: ENC(eDCxblnWkDHhJEEXMdkzww==) + read-only: false + + # 115 server + oracle: + driver-class-name: oracle.jdbc.OracleDriver + jdbc-url: ENC(zWLLN8fkbayeHrlyXlG8sHIA9J1OdCZcDy3Snj/5hdDr9GfBIkMsboW3Z9KTRrCV7b7LU5+FIFY=) + username: ENC(wiNWcgb1ADyNwabf+S6KwDX/ou2aV6Z8) + password: ENC(5ejKeOJ/t7YEn/ikHKmNHdejhHHWOBuC) + read-only: false + + devtools: + livereload: + enabled: true + +springdoc: + api-docs: + enabled: true + swagger-ui: + enabled: true + +logging: + level: + root: debug + org.apache.http: warn + reactor.netty: warn + io.netty: warn + org.springframework.web.reactive.function.client.ExchangeFunctions: debug + org.springframework: warn + file: + # 로그파일 위치 + path: ${app.data.root.path}/ens/logs + name: ${app.name} + +# Spring Security cors 설정 :: CorsConfiguration 설정 값 +cors: + allowed-origins: http://localhost:8080 + +# ================================================================================================================== +# SQL logging lib setting +# ================================================================================================================== +decorator: + datasource: + p6spy: + enable-logging: true + +app: + # swagger url + url: 'http://localhost:${server.port}${server.servlet.context-path:}/' + data: + root: + path: d:/data + param: + log: + # CommonsRequestLoggingFilter 사용 parameter 로그 출력 + enabled: true + # parameter custom 로그 출력(LoggerAspect) + custom.enabled: true + # MDC logging trace 활성 + mdc.log.trace.enabled: true + + # slack + slack-webhook: + enabled: false + url: https://hooks.slack.com/services/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 diff --git a/mens-api/src/main/resources/config/application.yml b/mens-api/src/main/resources/config/application.yml new file mode 100644 index 0000000..b1273ac --- /dev/null +++ b/mens-api/src/main/resources/config/application.yml @@ -0,0 +1,97 @@ +#----------------------------------------------------------------------- +# +# egovframework 설정 +# api 공통 설정 +#----------------------------------------------------------------------- +Globals: + # DB서버 타입(hsql,mysql,oracle,altibase,tibero) - datasource 및 sqlMap 파일 지정에 사용됨 + DbType: mysql + + # MainPage Setting + MainPage: /cmm/main/mainPage.do + +#server.servlet.context-path=/sht_boot_web +server: + port: 8081 + error: + whitelabel: + enabled: false +app: + name: mens-api + # springdoc url 정보 + desc: 모바일 전자고지 Rest API + data: + root: + path: /data + +spring: + main: + # 순환참조 에러 무시 + allow-circular-references: true + pid: + file: ${app.data.root.path}/${app.name}.pid + profiles: + active: '@springProfilesActive@' + + # core의 application-common.yml과 application-auth.yml include + include: + - common + - auth + - app + - ens + + batch: + jdbc: + initialize-schema: NEVER #NEVER|ALWAYS + # JPA does not support custom isolation levels, so locks may not be taken when launching Jobs. + # To silence this warning, set 'spring.batch.jdbc.isolation-level-for-create' to 'default'. + isolation-level-for-create: default + job: + enabled: false + + #----------------------------------------------------------------- + # xit framework 설정 + #----------------------------------------------------------------- + datasource: + #type: com.zaxxer.hikari.HikariDataSource + hikari: + maria: + pool-name: xit-maria-pool + auto-commit: false + # 인프라의 적용된 connection time limit보다 작아야함 + max-lifetime: 1800000 + maximum-pool-size: 15 + minimum-idle: 5 + #transaction-isolation: TRANSACTION_READ_UNCOMMITTED + data-source-properties: + rewriteBatchedStatements: true + + oracle: + pool-name: xit-oracle-pool + auto-commit: false + # 인프라의 적용된 connection time limit보다 작아야함 + max-lifetime: 1800000 + maximum-pool-size: 15 + minimum-idle: 5 +# transaction-isolation: TRANSACTION_READ_UNCOMMITTED + data-source-properties: + rewriteBatchedStatements: true + + # @JsonInclude(JsonInclude.Include.NON_NULL) 설정 + jackson: + default-property-inclusion=non_null: non_null + + + + +logging: + level: + root: error + +file: + cmm: + upload: + root: ${app.data.root.path} + # root: /data + # E-GREEN 우편 발송 + post: /post/rcv/ diff --git a/mens-api/src/main/resources/config/google_checks.xml b/mens-api/src/main/resources/config/google_checks.xml new file mode 100644 index 0000000..1ca2848 --- /dev/null +++ b/mens-api/src/main/resources/config/google_checks.xml @@ -0,0 +1,382 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mens-api/src/main/resources/egovframework/mapper/biz/ens-cctv-mysql-mapper.xml b/mens-api/src/main/resources/egovframework/mapper/biz/ens-cctv-mysql-mapper.xml new file mode 100644 index 0000000..e21880c --- /dev/null +++ b/mens-api/src/main/resources/egovframework/mapper/biz/ens-cctv-mysql-mapper.xml @@ -0,0 +1,152 @@ + + + + + + + + /** ens-mysql-mapper|insertNtcnCntcData-전자 고지연계데이타 생성|seojh */ + + SELECT concat(date_format(now(), '%Y%m%d'), LPAD(NEXTVAL(tb_ens_cntc_data_seq), 12, '0')) from dual + + INSERT + INTO tb_ens_cntc_data ( + ens_cntc_data_id, /* 전자 고지연계데이타 ID*/ + reglt_dt, /* 단속일시 */ + vhcle_no, /* 차량번호 */ + spt_nm, /* 현장명 */ + spt_accto_code, /* 현장별코드 */ + file_nm, /* 파일명 */ + regist_dt, + register + ) VALUES ( + #{ensCntcDataId}, + #{regltDt}, + #{vhcleNo}, + #{sptNm}, + #{sptAcctoCode}, + #{fileNm}, + now(), + 'batch' + ) + + + + + + /** ens-mysql-mapper|insertCntcSndngMst-연계발송마스터 생성|seojh */ + + SELECT concat('E', signgu_code, ffnlg_code, RIGHT(date_format(now(), '%Y'),2), LPAD(NEXTVAL(tb_cntc_sndng_mastr_seq), 7, '0')) AS unitySndngMastrId + , date_format(date_add(now(), interval + 20 day), '%Y%m%d%H%i%S') AS closDt + FROM tb_ens_tmplat_manage + WHERE sndng_ty_code = #{sndngTyCode} + AND tmplat_id = #{tmplatId} + + INSERT + INTO tb_cntc_sndng_mastr ( + unity_sndng_mastr_id, /* 통합발송마스터 id */ + signgu_code, /* 시군구코드 */ + ffnlg_code, /* 과태료코드 */ + tmplat_id, /* 템플릿ID */ + sndng_ty_code, /* 발송유형 코드 */ + sndng_co, /* 발송 건수 */ + sndng_process_sttus, /* 발송처리 상태 */ + sndng_dt, /* 발송일시 */ + clos_dt, /* 마감일시 */ + regist_dt, + register + ) + SELECT #{unitySndngMastrId} + , tetm.signgu_code + , tetm.ffnlg_code + , tetm.tmplat_id + , tetm.sndng_ty_code + , #{sndngCo} + , #{sndngProcessSttus} + , date_format(now(), '%Y%m%d%H%i%S') + , #{closDt} + , now() + , 'batch' + FROM tb_ens_tmplat_manage tetm + WHERE sndng_ty_code = #{sndngTyCode} + AND tmplat_id = #{tmplatId} + + + + /** ens-mysql-mapper|insertCntcSndngDtl-연계발송상세 생성|seojh */ + + SELECT concat('E', signgu_code, ffnlg_code, RIGHT(date_format(now(), '%Y'),2), LPAD(NEXTVAL(tb_cntc_sndng_detail_seq), 7, '0')) + FROM tb_ens_tmplat_manage + WHERE sndng_ty_code = #{sndngTyCode} + AND tmplat_id = #{tmplatId} + + INSERT + INTO tb_cntc_sndng_detail ( + unity_sndng_detail_id, /* 통합발송 상세ID */ + unity_sndng_mastr_id, /* 통합발송 마스터ID */ + signgu_code, /* 시군구 코드 */ + ffnlg_code, /* 과태료 코드 */ + vhcle_no, /* 차량번호 */ + ihidnum, /* 주민번호 - 현재는 전화번호 기반 생년월일 */ + moblphon_no, /* 핸드폰 번호 */ + nm, /* 이름 */ + adres, /* 주소 */ + detail_adres, /* 상세 주소 */ + zip, /* 우편번호 */ + mobile_page_cn, /* 모바일 페이지 내용 */ + regist_dt, + register + ) + SELECT #{unitySndngDetailId} + , #{unitySndngMastrId} + , tetm.signgu_code + , tetm.ffnlg_code + , #{vhcleNo} + , #{brthdy} + , #{moblphonNo} + , #{nm} + , #{adres} + , #{detailAdres} + , #{zip} + , #{mobilePageCn} + , now() + , 'batch' + FROM tb_ens_tmplat_manage tetm + WHERE sndng_ty_code = #{sndngTyCode} + AND tmplat_id = #{tmplatId} + + + + /** ens-mysql-mapper|updateEnsNtcnCntcData-전자 고지 연계데이타 연계결과 반영|seojh */ + UPDATE tb_ens_cntc_data + SET process_at = #{processAt} + , unity_sndng_detail_id = #{unitySndngDetailId} + , updt_dt = now() + , updusr = 'batch' + WHERE ens_cntc_data_id = #{ensCntcDataId} + + diff --git a/mens-api/src/main/resources/egovframework/mapper/biz/ens-mysql-mapper.xml b/mens-api/src/main/resources/egovframework/mapper/biz/ens-mysql-mapper.xml new file mode 100644 index 0000000..49ad4bb --- /dev/null +++ b/mens-api/src/main/resources/egovframework/mapper/biz/ens-mysql-mapper.xml @@ -0,0 +1,748 @@ + + + + + + + + + + + /** ens-mysql-mapper|insertUnitySndngMst-통합발송마스터 생성|julim */ + INSERT + INTO tb_ens_unity_sndng_mastr ( + unity_sndng_mastr_id, /* 통합발송마스터 ID */ + signgu_code, /* 시군구 코드 */ + ffnlg_code, /* 과태료 코드 */ + tmplat_id, /* 템플릿 Id */ + sndng_ty_code, /* 발송유형 코드 */ + sndng_co, /* 발송건수 */ + sndng_process_sttus, /* 발송처리 상태 */ + sndng_dt_1, /* 발송 일시 */ + sndng_dt_2, /* 발송 일시2 */ + sndng_dt_3, /* 발송 일시3 */ + try1, + try2, + try3, + try_cnt, + clos_dt, /* 마감일시 */ + regist_dt, + register + ) + SELECT tcsm.unity_sndng_mastr_id /* 통합발송마스터 ID */ + , tcsm.signgu_code /* 시군구 코드 */ + , tcsm.ffnlg_code /* 과태료 코드 */ + , tcsm.tmplat_id /* 템플릿 Id */ + , tetm.sndng_ty_code /* 발송유형 코드 */ + , tcsm.sndng_co /* 발송건수 */ + , 'accept-ok' /* 발송처리 상태 */ + , tcsm.sndng_dt + , #{sndngDt2} + , #{sndngDt3} + , tetm.try1 + , tetm.try2 + , tetm.try3 + , CASE WHEN IFNULL(tetm.try3, '') != '' + THEN 3 + ELSE IF(IFNULL(tetm.try2, '') != '', 2, 1) + END /* try3 값이 있으면 3, try2 값이 있으면 2, try1 */ + , clos_dt /* 마감일시 */ + , now() + , 'batch' + FROM tb_cntc_sndng_mastr tcsm + JOIN tb_ens_tmplat_manage tetm + ON tcsm.tmplat_id = tetm.tmplat_id + WHERE tcsm.unity_sndng_mastr_id = #{unitySndngMastrId} + AND tcsm.sndng_process_sttus = #{sndngProcessSttus} + + + + /** ens-mysql-mapper|insertUnitySndngDtls-통합발송상세 생성|julim */ + INSERT + INTO tb_ens_unity_sndng_detail ( + unity_sndng_detail_id, /* 통합발송상세 ID*/ + unity_sndng_mastr_id, /* 통합발송마스터 ID */ + signgu_code, /* 시군구 코드 */ + ffnlg_code, /* 과태료 코드 */ + main_code, + vhcle_no, /* 차량번호 */ + ihidnum, /* 주민번호 */ + moblphon_no, /* 전화번호 */ + nm, /* 이름 */ + adres, /* 주소 */ + detail_adres, /* 상세주소 */ + zip, /* 우편번호 */ + tmplt_msg_data, /* 템플릿메세지 데이타 */ + mobile_page_cn, /* 모바일페이지내용 */ + use_instt_idntfc_id, /* 이용기관식별Id*/ + regist_dt, + register + ) + SELECT tcsd.unity_sndng_detail_id /* 통합발송상세 ID*/ + , tcsd.unity_sndng_mastr_id /* 통합발송마스터 ID */ + , tcsd.signgu_code /* 시군구 코드 */ + , tcsd.ffnlg_code /* 과태료 코드 */ + , tcsd.main_code + , tcsd.vhcle_no /* 차량번호 */ + , tcsd.ihidnum /* 주민번호 */ + , tcsd.moblphon_no /* 전화번호 */ + , tcsd.nm /* 이름 */ + , tcsd.adres /* 주소 */ + , tcsd.detail_adres /* 상세주소 */ + , tcsd.zip /* 우편번호 */ + , tcsd.tmplt_msg_data /* 템플릿메세지 데이타 */ + , tcsd.mobile_page_cn /* 모바일페이지내용 */ + , tcsd.use_instt_idntfc_id /* 이용기관식별Id*/ + , now() + , 'batch' + FROM tb_cntc_sndng_mastr tcsm + JOIN tb_cntc_sndng_detail tcsd + ON tcsm.unity_sndng_mastr_id = tcsd.unity_sndng_mastr_id + WHERE tcsm.unity_sndng_mastr_id = #{unitySndngMastrId} + AND tcsm.sndng_process_sttus = #{sndngProcessSttus} + + + + /** ens-mysql-mapper|updateProcessSttusCntcSndngMst-연계발송마스터 상태 변경|julim */ + UPDATE tb_cntc_sndng_mastr + SET sndng_process_sttus = #{newSndngProcessSttus} + , error_code = #{errorCode} + , error_mssage = #{errorMssage} + , updt_dt = now() + , updusr = 'batch' + WHERE unity_sndng_mastr_id = #{unitySndngMastrId} + + + + + + + + + + + + + + /** ens-mysql-mapper|insertSndngMst-발송마스터 생성|julim */ + + SELECT concat(date_format(now(), '%Y%m%d'), LPAD(NEXTVAL(tb_ens_sndng_mastr_seq), 12, '0')) from dual + + INSERT + INTO tb_ens_sndng_mastr ( + sndng_mastr_id, /* 발송마스터 ID*/ + unity_sndng_mastr_id, /* 통합발송마스터 ID */ + signgu_code, /* 시군구 코드 */ + ffnlg_code, /* 과태료 코드 */ + tmplat_id, /* 템플릿 Id */ + sndng_ty_code, /* 발송유형 코드 */ + sndng_se_code, /* 발송구분 코드 */ + sndng_co, /* 발송건수 */ + try_seq, + try_cnt, + sndng_process_sttus, /* 발송처리 상태 */ + sndng_dt, /* 발송일시 */ + clos_dt, /* 마감일시 */ + regist_dt, + register + ) + SELECT #{sndngMastrId} + , teusm.unity_sndng_mastr_id /* 통합발송마스터 ID */ + , teusm.signgu_code /* 시군구 코드 */ + , teusm.ffnlg_code /* 과태료 코드 */ + , teusm.tmplat_id /* 템플릿 Id */ + , teusm.sndng_ty_code /* 발송유형 코드 */ + , #{sndngSeCode} /* 발송구분 코드 */ + , #{sndngCo} /* 발송건수 */ + , #{trySeq} + , teusm.try_cnt + , 'make-ok' /* 발송처리 상태 */ + , CASE WHEN #{trySeq}=3 THEN teusm.sndng_dt_3 + WHEN #{trySeq}=2 THEN teusm.sndng_dt_2 + ELSE teusm.sndng_dt_1 + END /* 1차, 2차, 3차에 따른 발송일시 */ + , teusm.clos_dt /* 마감일시 */ + , now() + , 'batch' + FROM tb_ens_unity_sndng_mastr teusm + WHERE teusm.unity_sndng_mastr_id = #{unitySndngMastrId} + AND teusm.sndng_process_sttus = #{sndngProcessSttus} + + + +/** ens-mysql-mapper|insertKakaoMyDocs-카카오내문서함 생성|julim */ +INSERT +INTO tb_ens_kakao_my_doc ( + sndng_detail_id, /* 발송상세ID */ + unity_sndng_detail_id, /* 통합발송상세ID */ + sndng_mastr_id, /* 발송마스터ID */ + signgu_code, /* 시군구코드 */ + ffnlg_code, /* 과태료코드 */ + title, /* 제목 */ + hash, + common_categories, + read_expired_at, /* 처리마감시간 */ + -- recv_ci, /* 받는이 CI */ + recv_phone_number, /* 받는이 전화번호 */ + recv_name, /* 받는이 이름 */ + recv_birthday, /* 받는이 생년월일 */ + recv_is_required_verify_name, /* 성명검증옵션 */ + prop_link, /* 모바일페이지 URL */ + prop_payload, + prop_message, + prop_cs_number, /* 고객센터 전화번호 */ + prop_cs_name, /* 고객센터 명 */ + -- external_document_uuid, /* 외부문서 식별번호 */ + regist_dt, + register +) +SELECT LPAD(NEXTVAL(sndng_detail_id_seq), 20, '0') + , teusd.unity_sndng_detail_id + , #{sndngMastrId} + , teusm.signgu_code + , teusm.ffnlg_code + , tetm.tmplat_nm + , SHA2(teusd.unity_sndng_detail_id, 256) + , '[NOTICE]' + , unix_timestamp(teusm.clos_dt) + -- , NULL + , teusd.moblphon_no + , teusd.nm + , teusd.ihidnum + , 'false' + , tetm.redirect_url + , tetm.tmplat_sj + , CASE WHEN teusm.sndng_ty_code='PNI' THEN teusd.tmplt_msg_data + ELSE tetm.tmplat_cn END + , tetm.cstmr_cnter_tlphon_no + , '콜센터' + -- , null + , now() + , 'batch' +FROM tb_ens_unity_sndng_mastr teusm + LEFT JOIN tb_ens_tmplat_manage tetm + ON teusm.tmplat_id = tetm.tmplat_id + LEFT JOIN tb_ens_unity_sndng_detail teusd + ON teusm.unity_sndng_mastr_id = teusd.unity_sndng_mastr_id + WHERE teusm.unity_sndng_mastr_id = #{unitySndngMastrId} + AND teusm.sndng_process_sttus = #{sndngProcessSttus} + + + + /** ens-mysql-mapper|insertSmsSndng-문자발송 데이터 생성|julim */ + INSERT + INTO tb_ens_sms_sndng ( + sndng_detail_id, /* 발송 상세 id */ + unity_sndng_detail_id, /* 통합 발송 상세 id */ + sndng_mastr_id, /* 발송 마스터 id */ + signgu_code, /* 시군구 코드 */ + ffnlg_code, /* 과태료 코드 */ + vhcle_no, /* 차량 번호 */ + sms_sndng_dt, /* 문자 발송 일시 */ + sms_trnsmis_stle, /* 문자 전송 형태 */ + sms_trnsmit_tlphon_no, /* 문자 송신 전화 번호 */ + sms_recptn_tlphon_no, /* 문자 수신 전화 번호 */ + sms_mssage, /* 문자 메시지 */ + sms_sndng_sttus, /* 문자 발송 상태 */ + sms_sndng_process_sttus, /* 문자 발송 처리 상태 */ + regist_dt, + register + ) + SELECT LPAD(NEXTVAL(sndng_detail_id_seq), 20, '0') + , teusd.unity_sndng_detail_id + , #{sndngMastrId} + , teusm.signgu_code + , teusm.ffnlg_code + , teusd.vhcle_no + , date_format(now(), '%Y%m%d%H%i%S') + , '0' + , REPLACE(tetm.cstmr_cnter_tlphon_no, '-', '') + , teusd.moblphon_no + , teusd.tmplt_msg_data + , '2' + , '06' + , now() + , 'batch' + FROM tb_ens_unity_sndng_mastr teusm + LEFT JOIN tb_ens_tmplat_manage tetm + ON teusm.tmplat_id = tetm.tmplat_id + LEFT JOIN tb_ens_unity_sndng_detail teusd + ON teusm.unity_sndng_mastr_id = teusd.unity_sndng_mastr_id + WHERE teusm.unity_sndng_mastr_id = #{unitySndngMastrId} + AND teusm.sndng_process_sttus = #{sndngProcessSttus} + + + + INSERT + INTO tb_ens_post_sndng ( + sndng_detail_id, /* 발송 상세 id */ + unity_sndng_detail_id, /* 통합 발송 상세 id */ + sndng_mastr_id, /* 발송 마스터 id */ + signgu_code, /* 시군구 코드 */ + ffnlg_code, /* 과태료 코드 */ + service_cd, /* 서비스 코드 */ + con_key, /* 연계 식별 키 */ + sender_nm, /* 발송인 명 */ + sender_zip_no, /* 발송인 우편번호 */ + sender_addr, /* 발송인 주소 */ + sender_detail_addr, /* 발송인 상세 주소 */ + receiver_send_no, /* 수취인 일련 번호 */ + receiver_nm, /* 수취인 명 */ + receiver_zip_no, /* 수취인 우편번호 */ + receiver_addr, /* 수취인 주소 */ + receiver_detail_addr, /* 수취인 상세 주소 */ + sschnge_1, /* 가변 1 */ + sschnge_2, /* 가변 2 */ + sschnge_3, /* 가변 3 */ + regist_dt, + register + ) + SELECT LPAD(NEXTVAL(sndng_detail_id_seq), 20, '0') + , teusd.unity_sndng_detail_id + , #{sndngMastrId} + , teusm.signgu_code + , teusm.ffnlg_code + , 'PST' + , (SELECT CONCAT('0009900112Z1',date_format(now(), '%Y%m%d%H%i%S')) from dual) + , term.sender_nm + , term.sender_zip_no + , term.sender_addr + , term.sender_detail_addr + , row_number() over(order by teusd.unity_sndng_detail_id) + , teusd.nm + , teusd.zip + , teusd.adres + , teusd.detail_adres + , CONCAT(teusd.vhcle_no, '|', tecd.spt_nm) + , CONCAT(tecd.reglt_dt, '|', date_format(date_add(now(), interval + 20 day), '%Y%m%d')) + , '32000' + , now() + , 'batch' + FROM tb_ens_unity_sndng_mastr teusm + LEFT JOIN tb_ens_tmplat_manage tetm + ON teusm.tmplat_id = tetm.tmplat_id + LEFT JOIN tb_ens_unity_sndng_detail teusd + ON teusm.unity_sndng_mastr_id = teusd.unity_sndng_mastr_id + LEFT JOIN tb_ens_rlaybsnm_manage term + ON teusm.signgu_code = term.signgu_code AND teusm.ffnlg_code = term.ffnlg_code + LEFT JOIN tb_ens_cntc_data tecd + ON teusd.unity_sndng_detail_id=tecd.unity_sndng_detail_id + + INNER JOIN tb_ens_kakao_my_doc tekmd + ON teusd.unity_sndng_detail_id = tekmd.unity_sndng_detail_id + AND (NOT (tekmd.doc_box_status = 'READ' AND tekmd.error_code IS NULL) OR (tekmd.doc_box_status IS NULL and tekmd.error_code IS NULL)) + + WHERE teusm.unity_sndng_mastr_id = #{unitySndngMastrId} + AND teusm.sndng_process_sttus = #{sndngProcessSttus} + + + + /** ens-mysql-mapper|updateProcessSttusUntySndngMst-통합발송마스터 상태 변경|julim */ + UPDATE tb_ens_unity_sndng_mastr + SET sndng_process_sttus = #{newSndngProcessSttus} + , error_code = #{errorCode} + , error_mssage = #{errorMssage} + , updt_dt = now() + , updusr = 'batch' + WHERE unity_sndng_mastr_id = #{unitySndngMastrId} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /** ens-mysql-mapper|updateKakaoSendBulksResult-카카오페이 문서요청 결과 반영|julim */ + UPDATE tb_ens_kakao_my_doc + SET external_document_uuid = #{external_document_uuid} + , document_binder_uuid = #{document_binder_uuid} + , error_code = #{error_code} + , error_message = #{error_message} + , updt_dt = now() + , updusr = 'batch' + WHERE unity_sndng_detail_id = #{external_document_uuid} + + + + /** ens-mysql-mapper|insertMobilePageManage-모바일페이지관리 데이타 생성|julim */ + INSERT + INTO tb_ens_mobile_page_manage ( + sndng_detail_id, /* 발송상세 ID*/ + sndng_se_code, /* 발송 구분 코드 */ + mobile_page_cn, /* 모바일 페이지 내용 */ + regist_dt, + register + ) + SELECT tekmd.sndng_detail_id + , 'KKO-MY-DOC' + , teusd.mobile_page_cn + , date_format(now(), '%Y%m%d%H%i%s') + , 'batch' + FROM tb_ens_kakao_my_doc tekmd + JOIN tb_ens_unity_sndng_detail teusd + ON tekmd.unity_sndng_detail_id = teusd.unity_sndng_detail_id + WHERE teusd.unity_sndng_detail_id = #{unitySndngDetailId} + + + + /** ens-mysql-mapper|updateProcessSttusSndngMst-발송마스터 상태 변경|julim */ + UPDATE tb_ens_sndng_mastr + SET sndng_process_sttus = #{newSndngProcessSttus} + , error_code = #{errorCode} + , error_mssage = #{errorMssage} + , updt_dt = now() + , updusr = 'batch' + WHERE sndng_mastr_id = #{sndngMastrId} + + + + /** ens-mysql-mapper|updateProcessSttusBulkSndngMst-발송마스터 상태 다건 변경|julim */ + UPDATE tb_ens_sndng_mastr + SET sndng_process_sttus = #{newSndngProcessSttus} + , updt_dt = now() + , updusr = 'batch' + WHERE unity_sndng_mastr_id = #{unitySndngMastrId} + + + + + + + + + + + + /** ens-mysql-mapper|updateKakaoStatusInfo-카카오 문서 상태 조회 결과 반영|julim */ + UPDATE tb_ens_kakao_my_doc + SET doc_box_status = #{status_data.doc_box_status} /* 진행상태 */ + , doc_box_sent_at = #{status_data.doc_box_sent_at} /* 송신시간 */ + , doc_box_received_at = #{status_data.doc_box_received_at} /* 수신시간 */ + , doc_box_read_at = #{status_data.doc_box_read_at} /* 최초열람시간 */ + , authenticated_at = #{status_data.authenticated_at} /* 최초열람인증시간 */ + , token_used_at = #{status_data.token_used_at} /* 최초OTT 검증시간 */ + , user_notified_at = #{status_data.user_notified_at} /* 알림톡 수신시간 */ + , updt_dt = now() + , updusr = 'batch' + WHERE unity_sndng_detail_id = (SELECT tekmd.unity_sndng_detail_id + FROM tb_ens_kakao_my_doc tekmd + WHERE tekmd.document_binder_uuid = #{document_binder_uuid}) + + + + + + + + + + + /** ens-mysql-mapper|insertCntcSndngResult-연계발송결과반영|julim */ + INSERT + INTO tb_cntc_sndng_result ( + unity_sndng_detail_id, /* 통합 발송 상세 ID*/ + sndng_se_code, /* 발송구분코드 */ + signgu_code, /* 시군구코드 */ + ffnlg_code, /* 과태료코드 */ + sndng_result_sttus, + requst_dt, /* 송신(요청) 일시 */ + inqire_dt, /* 수신(조회) 일시 */ + readng_dt, /* 최초열람일시 */ + error_cn, /* 에러내용 */ + regist_dt, + register + ) SELECT unity_sndng_detail_id /* 통합 발송 상세 ID*/ + , #{sndngSeCode} /* 발송구분코드 - KAKAO-MY-DOC */ + , signgu_code /* 시군구코드 */ + , ffnlg_code /* 과태료코드 */ + , #{sndngResultSttus} + , from_unixtime(#{requstDt}, '%Y%m%d%H%i%s') /* 송신(요청) 일시 */ + , from_unixtime(#{inqireDt}, '%Y%m%d%H%i%s') /* 수신(조회) 일시 */ + , from_unixtime(#{readngDt}, '%Y%m%d%H%i%s') /* 최초열람일시 */ + , #{errorCn} /* 에러내용 */ + , now() + , 'batch' + FROM tb_ens_unity_sndng_detail + WHERE unity_sndng_detail_id = #{unitySndngDetailId} + + + + /** ens-mysql-mapper|updateCntcSndngResult-연계발송결과반영|julim */ + UPDATE tb_cntc_sndng_result + SET sndng_result_sttus = #{sndngResultSttus} + , requst_dt = from_unixtime(#{requstDt}, '%Y%m%d%H%i%s') /* 송신(요청) 일시 */ + , inqire_dt = from_unixtime(#{inqireDt}, '%Y%m%d%H%i%s') /* 수신(조회) 일시 */ + , readng_dt = from_unixtime(#{readngDt}, '%Y%m%d%H%i%s') /* 최초열람일시 */ + , error_cn = #{errorCn} + , updt_dt = now() + , updusr = 'batch' + WHERE unity_sndng_detail_id = (SELECT tekmd.unity_sndng_detail_id + FROM tb_ens_kakao_my_doc tekmd + WHERE tekmd.document_binder_uuid = #{documentBinderUuid}) + + + + + + + + + + + + + + + diff --git a/mens-api/src/main/resources/egovframework/mapper/biz/ens-oracle-mapper.xml b/mens-api/src/main/resources/egovframework/mapper/biz/ens-oracle-mapper.xml new file mode 100644 index 0000000..b3fc167 --- /dev/null +++ b/mens-api/src/main/resources/egovframework/mapper/biz/ens-oracle-mapper.xml @@ -0,0 +1,36 @@ + + + + + + + + + /** ens-oracle-mapper|insertScTran-sms 발송 데이타 생성|julim */ + INSERT + INTO sc_tran ( + tr_num, + tr_senddate, + tr_sendstat, + tr_msgtype, + tr_phone, + tr_callback, + tr_msg, + tr_etc1 + ) VALUES ( + sc_tran_seq.nextval, + sysdate, + '0', /* 문자 발송 상태 */ + '0', /* 문자 전송 형태 */ + #{smsRecptnTlphonNo}, /* 문자 수신 전화 번호 */ + #{smsTrnsmitTlphonNo}, /* 문자 송신 전화 번호 */ + #{smsMssage}, /* 문자 메세지 */ + #{sndngDetailId} /* 발송 상세 ID */ + ) + + + + + + diff --git a/mens-api/src/main/resources/egovframework/mapper/biz/pni-mysql-mapper.xml b/mens-api/src/main/resources/egovframework/mapper/biz/pni-mysql-mapper.xml new file mode 100644 index 0000000..2ef7375 --- /dev/null +++ b/mens-api/src/main/resources/egovframework/mapper/biz/pni-mysql-mapper.xml @@ -0,0 +1,151 @@ + + + + + + + + /** pni-mysql-mapper|insertNtcnCntcData-사전알림연계데이타 생성|julim */ + + SELECT concat(date_format(now(), '%Y%m%d'), LPAD(NEXTVAL(tb_pni_ntcn_cntc_data_seq), 12, '0')) from dual + + INSERT + INTO tb_pni_ntcn_cntc_data ( + ntcn_cntc_data_id, /* 사전알림연계데이타 ID*/ + reglt_dt, /* 단속일시 */ + vhcle_no, /* 차량번호 */ + spt_nm, /* 현장명 */ + spt_accto_code, /* 현장별코드 */ + file_nm, /* 파일명 */ + regist_dt, + register + ) VALUES ( + #{ntcnCntcDataId}, + #{regltDt}, + #{vhcleNo}, + #{sptNm}, + #{sptAcctoCode}, + #{fileNm}, + now(), + 'batch' + ) + + + + + + /** pni-mysql-mapper|insertCntcSndngMst-연계발송마스터 생성|julim */ + + SELECT concat('P', signgu_code, ffnlg_code, RIGHT(date_format(now(), '%Y'),2), LPAD(NEXTVAL(tb_cntc_sndng_mastr_seq), 7, '0')) + FROM tb_ens_tmplat_manage + WHERE sndng_ty_code = #{sndngTyCode} + AND tmplat_id = #{tmplatId} + + INSERT + INTO tb_cntc_sndng_mastr ( + unity_sndng_mastr_id, /* 통합발송마스터 id */ + signgu_code, /* 시군구코드 */ + ffnlg_code, /* 과태료코드 */ + tmplat_id, /* 템플릿ID */ + sndng_ty_code, /* 발송유형 코드 */ + sndng_co, /* 발송 건수 */ + sndng_process_sttus, /* 발송처리 상태 */ + sndng_dt, /* 발송일시 */ + clos_dt, /* 마감일시 */ + regist_dt, + register + ) + SELECT #{unitySndngMastrId} + , tetm.signgu_code + , tetm.ffnlg_code + , tetm.tmplat_id + , tetm.sndng_ty_code + , #{sndngCo} + , #{sndngProcessSttus} + , date_format(now(), '%Y%m%d%H%i%S') + , date_format(date_add(now(), interval +1 day), '%Y%m%d%H%i%S') + , now() + , 'batch' + FROM tb_ens_tmplat_manage tetm + WHERE sndng_ty_code = #{sndngTyCode} + AND tmplat_id = #{tmplatId} + + + + /** pni-mysql-mapper|insertCntcSndngDtl-연계발송상세 생성|julim */ + + SELECT concat('P', signgu_code, ffnlg_code, RIGHT(date_format(now(), '%Y'),2), LPAD(NEXTVAL(tb_cntc_sndng_detail_seq), 7, '0')) + FROM tb_ens_tmplat_manage + WHERE sndng_ty_code = #{sndngTyCode} + AND tmplat_id = #{tmplatId} + + INSERT + INTO tb_cntc_sndng_detail ( + unity_sndng_detail_id, /* 통합발송 상세ID */ + unity_sndng_mastr_id, /* 통합발송 마스터ID */ + signgu_code, /* 시군구 코드 */ + ffnlg_code, /* 과태료 코드 */ + ihidnum, /* 주민번호 */ + vhcle_no, /* 차량번호 */ + moblphon_no, /* 핸드폰 번호 */ + nm, /* 이름 */ + tmplt_msg_data, /* 템플릿메시지 데이타 */ + mobile_page_cn, /* 모바일 페이지 내용 */ + regist_dt, + register + ) + SELECT #{unitySndngDetailId} + , #{unitySndngMastrId} + , tetm.signgu_code + , tetm.ffnlg_code + , #{brthdy} + , #{vhcleNo} + , #{moblphonNo} + , #{nm} + , #{tmpltMsgData} + , #{mobilePageCn} + , now() + , 'batch' + FROM tb_ens_tmplat_manage tetm + WHERE sndng_ty_code = #{sndngTyCode} + AND tmplat_id = #{tmplatId} + + + + /** pni-mysql-mapper|updatePniNtcnCntcData-사전알림 연계데이타 연계결과 반영|julim */ + UPDATE tb_pni_ntcn_cntc_data + SET process_at = #{processAt} + , trget_at = #{tgtYn} + , unity_sndng_detail_id = #{unitySndngDetailId} + , updt_dt = now() + , updusr = 'batch' + WHERE ntcn_cntc_data_id = #{ntcnCntcDataId} + + diff --git a/mens-api/src/main/resources/egovframework/mapper/core/auth-mysql-mapper.xml b/mens-api/src/main/resources/egovframework/mapper/core/auth-mysql-mapper.xml new file mode 100644 index 0000000..87d4f38 --- /dev/null +++ b/mens-api/src/main/resources/egovframework/mapper/core/auth-mysql-mapper.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/mens-api/src/main/resources/egovframework/mapper/core/cmm-batch-mysql-mapper.xml b/mens-api/src/main/resources/egovframework/mapper/core/cmm-batch-mysql-mapper.xml new file mode 100644 index 0000000..79474cf --- /dev/null +++ b/mens-api/src/main/resources/egovframework/mapper/core/cmm-batch-mysql-mapper.xml @@ -0,0 +1,68 @@ + + + + + + + + /** cmm-batch-mapper|insertBatchLock-배치락 데이타 생성|julim */ + INSERT INTO tb_cmm_batch_lock ( + instance_id, + regist_dt, + use_yn + ) VALUES ( + #{instanceId}, + NOW(3), + #{useYn} + ) + + + + /** cmm-batch-mapper|updateBatchLock-배치 실행 상태 및 결과 반영|julim */ + UPDATE tb_cmm_batch_lock + SET result = #{result} + , use_yn = #{useYn} + , updt_dt = NOW(3) + WHERE instance_id = #{instanceId} + + + + /** cmm-batch-mapper|insertBatchLog-배치 로그 데이타 생성|julim */ + + SELECT LPAD(NEXTVAL(tb_cmm_batch_log_seq), 20, '0') + + INSERT INTO tb_cmm_batch_log ( + batch_log_id, + instance_id, + trace_id, + result, + message, + regist_dt + ) VALUES ( + #{batchLogId}, + #{instanceId}, + #{traceId}, + #{result}, + #{message}, + NOW(3) + ) + + + + /** cmm-batch-mapper|updateBatchLog-배치 결과 반영|julim */ + UPDATE tb_cmm_batch_log + SET trace_id = IFNULL(#{traceId}, trace_id) + , result = IFNULL(#{result}, result) + , message = IFNULL(SUBSTRING(#{message}, 1, 100), message) + , updt_dt = NOW(3) + WHERE batch_log_id = #{batchLogId} + + diff --git a/mens-api/src/main/resources/egovframework/mapper/core/logging-mysql-mapper.xml b/mens-api/src/main/resources/egovframework/mapper/core/logging-mysql-mapper.xml new file mode 100644 index 0000000..ac4db08 --- /dev/null +++ b/mens-api/src/main/resources/egovframework/mapper/core/logging-mysql-mapper.xml @@ -0,0 +1,80 @@ + + + + + + + /** logging-mysql-mapper|saveLogging|julim */ + INSERT INTO tb_cmm_api_log ( + request_id + , system_id + , req_system_id + , method + , uri + , param + , ip + , access_token + , session_id + , success + , response + , message + , regist_dt + , regist_id + ) VALUES ( + #{requestId} + , #{systemId} + , #{reqSystemId} + , #{method} + , #{uri} + , #{param} + , #{ip} + , #{accessToken} + , #{sessionId} + , #{success} + , #{response} + , #{message} + , now(3) + , #{registId} + ) + + + + /** logging-mysql-mapper|updateLogging|julim */ + UPDATE tb_cmm_api_log + SET success = #{success} + , response = #{response} + , message = #{message} + , updt_dt = now(3) + , updt_id = #{updtId} + WHERE request_id = #{requestId} + + + + diff --git a/mens-api/src/main/resources/egovframework/mapper/mapper-config.xml b/mens-api/src/main/resources/egovframework/mapper/mapper-config.xml new file mode 100644 index 0000000..54a6663 --- /dev/null +++ b/mens-api/src/main/resources/egovframework/mapper/mapper-config.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mens-api/src/main/resources/egovframework/message/com/message-common.properties b/mens-api/src/main/resources/egovframework/message/com/message-common.properties new file mode 100644 index 0000000..4cdeb51 --- /dev/null +++ b/mens-api/src/main/resources/egovframework/message/com/message-common.properties @@ -0,0 +1,294 @@ +fail.common.msg=\uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! +fail.common.sql=sql \uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! error code: {0}, error msg: {1} +info.nodata.msg=\ud574\ub2f9 \ub370\uc774\ud130\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. + +#UI Common resource# +button.search=\uac80\uc0c9 +button.use=\uc0ac\uc6a9 +button.notUsed=\uc0ac\uc6a9\uc911\uc9c0 +button.inquire=\uc870\ud68c +button.update=\uc218\uc815 +button.create=\ub4f1\ub85d +button.delete=\uc0ad\uc81c +button.deleteDatabase=\uc644\uc804\uc0ad\uc81c +button.close=\ub2eb\uae30 +button.save=\uc800\uc7a5 +button.list=\ubaa9\ub85d +button.reset=\ucde8\uc18c +button.passwordUpdate=\uc554\ud638\ubcc0\uacbd +button.subscribe=\uac00\uc785\uc2e0\uccad +button.realname=\uc2e4\uba85\ud655\uc778 +button.moveToGpin=GPIN\uc2e4\uba85\ud655\uc778\uc73c\ub85c \uc774\ub3d9 +button.moveToIhidnum=\uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638 \uc2e4\uba85\ud655\uc778\uc73c\ub85c \uc774\ub3d9 +button.agree=\ub3d9\uc758 +button.disagree=\ube44\ub3d9\uc758 +button.possible = \uac00\ub2a5 +button.impossible = \ubd88\uac00\ub2a5 +button.qnaregist=Q&A\ub4f1\ub85d +button.cnsltregist=\uc0c1\ub2f4\ub4f1\ub85d +button.preview=\ubbf8\ub9ac\ubcf4\uae30 +button.next=\ub2e4\uc74c +button.add=\ubc14\ub85c\ucd94\uac00 +button.confirm=\ud655\uc778 + + +#UI Common Message# +common.save.msg=\uc800\uc7a5\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.regist.msg=\ub4f1\ub85d\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.delete.msg=\uc0ad\uc81c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.update.msg=\uc218\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.nodata.msg=\uc790\ub8cc\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\ub978 \uac80\uc0c9\uc870\uac74\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694 +common.required.msg=(\uc740)\ub294 \ud544\uc218\uc785\ub825\ud56d\ubaa9\uc785\ub2c8\ub2e4. +common.acknowledgement.msg=\uc2b9\uc778\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.acknowledgementcancel.msg=\uc2b9\uc778\ucde8\uc18c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? + +success.common.select=\uc815\uc0c1\uc801\uc73c\ub85c \uc870\ud68c\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.insert=\uc815\uc0c1\uc801\uc73c\ub85c \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.update=\uc815\uc0c1\uc801\uc73c\ub85c \uc218\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.delete=\uc815\uc0c1\uc801\uc73c\ub85c \uc0ad\uc81c\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +success.request.msg = \uc694\uccad\ucc98\ub9ac\uac00 \uc131\uacf5\uc801\uc73c\ub85c \uc218\ud589\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +common.imposbl.fileupload = \ub354 \uc774\uc0c1 \ud30c\uc77c\uc744 \ucca8\ubd80\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +common.isConfmDe.msg=\uc2b9\uc778\uc77c\uc790\ub97c \ud655\uc778 \ubc14\ub78d\ub2c8\ub2e4. + + +fail.common.insert = \uc0dd\uc131\uc774 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.update = \uc218\uc815\uc774 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.delete = \uc0ad\uc81c\uac00 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.delete.upperMenuExist = \ucc38\uc870\ub418\ub294 \uba54\ub274\uac00 \uc788\uc5b4 \uc0ad\uc81c\uac00 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.select = \uc870\ud68c\uc5d0 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +common.isExist.msg = \uc774\ubbf8 \uc874\uc7ac\ud558\uac70\ub098 \uacfc\uac70\uc5d0 \ub4f1\ub85d\uc774 \ub418\uc5c8\ub358 \uc0c1\ud0dc\uc785\ub2c8\ub2e4. +fail.common.login = \ub85c\uadf8\uc778 \uc815\ubcf4\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.common.idsearch = \uc544\uc774\ub514\ub97c \ucc3e\uc744\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +fail.common.pwsearch = \ube44\ubc00\ubc88\ud638\ub97c \ucc3e\uc744\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +fail.request.msg = \uc694\uccad\ucc98\ub9ac\ub97c \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. + +#UI User Message# +fail.user.passwordUpdate1=\ud604\uc7ac \ube44\ubc00\ubc88\ud638\uac00 \ub9de\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.user.passwordUpdate2=\ube44\ubc00\ubc88\ud638\uc640 \ube44\ubc00\ubc88\ud638 \ud655\uc778\uc774 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +info.user.rlnmCnfirm=\uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2e4\uba85\ud655\uc778\uc744 \ud558\uc2ed\uc2dc\uc624. +success.user.rlnmCnfirm=\ud589\uc815\uc548\uc804\ubd80\uc758 \uc8fc\ubbfc\ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud569\ub2c8\ub2e4. +fail.user.rlnmCnfirm=\ud589\uc815\uc548\uc804\ubd80\uc758 \uc8fc\ubbfc\ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.user.connectFail=\uc2dc\uc2a4\ud15c \uc7a5\uc560\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.(\uc778\uc99d\uc11c\ubc84 \uc5f0\uacb0 \uc2e4\ud328) +info.user.rlnmPinCnfirm=\uacf5\uacf5 \uc544\uc774\ud540 \uc544\uc774\ub514\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2e4\uba85\ud655\uc778\uc744 \ud558\uc2ed\uc2dc\uc624. +success.user.rlnmPinCnfirm=\uacf5\uacf5\uc544\uc774\ud540\uc758 \ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud569\ub2c8\ub2e4. +fail.user.rlnmPinCnfirm=\uacf5\uacf5\uc544\uc774\ud540\uc758 \ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + + +#UI Cop Message# +cop.extrlUser = \uc678\ubd80\uc0ac\uc6a9\uc790 +cop.intrlUser = \ub0b4\ubd80\uc0ac\uc6a9\uc790 +cop.private = \ube44\uacf5\uac1c +cop.public = \uacf5\uac1c + +cop.adbkNm = \uc8fc\uc18c\ub85d\uba85 +cop.othbcScope = \uacf5\uac1c\ubc94\uc704 +cop.company = \ud68c\uc0ac +cop.part = \ubd80\uc11c +cop.man = \uac1c\uc778 +cop.adbkUser = \uad6c\uc131\uc6d0 +cop.bbsNm = \uac8c\uc2dc\ud310\uba85 +cop.bbsIntrcn = \uac8c\uc2dc\ud310\uc18c\uac1c +cop.bbsTyCode = \uac8c\uc2dc\ud310 \uc720\ud615 +cop.bbsAttrbCode = \uac8c\uc2dc\ud310 \uc18d\uc131 +cop.replyPosblAt = \ub2f5\uc7a5\uac00\ub2a5\uc5ec\ubd80 +cop.fileAtchPosblAt = \ud30c\uc77c\ucca8\ubd80\uac00\ub2a5\uc5ec\ubd80 +cop.posblAtchFileNumber = \ucca8\ubd80\uac00\ub2a5\ud30c\uc77c \uc22b\uc790 +cop.tmplatId = \ud15c\ud50c\ub9bf \uc815\ubcf4 +cop.guestList.subject = \ubc29\uba85\ub85d \uac8c\uc2dc\uae00\uc785\ub2c8\ub2e4. +cop.nttSj = \uc81c\ubaa9 +cop.nttCn = \uae00\ub0b4\uc6a9 +cop.ntceBgnde = \uac8c\uc2dc\uc2dc\uc791\uc77c +cop.ntceEndde = \uac8c\uc2dc\uc885\ub8cc\uc77c +cop.ntcrNm = \uc791\uc131\uc790 +cop.password = \ud328\uc2a4\uc6cc\ub4dc +cop.atchFile = \ud30c\uc77c\ucca8\ubd80 +cop.guestList = \ubc29\uba85\ub85d +cop.guestListCn = \ubc29\uba85\ub85d \ub0b4\uc6a9 +cop.noticeTerm = \uac8c\uc2dc\uae30\uac04 +cop.atchFileList = \ucca8\ubd80\ud30c\uc77c\ubaa9\ub85d +cop.cmmntyNm = \ucee4\ubba4\ub2c8\ud2f0\uba85 +cop.cmmntyIntrcn = \ucee4\ubba4\ub2c8\ud2f0 \uc18c\uac1c +cop.cmmntyMngr = \ucee4\ubba4\ub2c8\ud2f0 \uad00\ub9ac\uc790 +cop.clbOprtr = \ub3d9\ud638\ud68c \uc6b4\uc601\uc790 +cop.clbIntrcn = \ub3d9\ud638\ud68c \uc18c\uac1c +cop.clbNm = \ub3d9\ud638\ud68c \uba85 +cop.tmplatNm = \ud15c\ud50c\ub9bf\uba85 +cop.tmplatSeCode = \ud15c\ud50c\ub9bf \uad6c\ubd84 +cop.tmplatCours = \ud15c\ud50c\ub9bf\uacbd\ub85c +cop.useAt = \uc0ac\uc6a9\uc5ec\ubd80 +cop.ncrdNm = \uc774\ub984 +cop.cmpnyNm = \ud68c\uc0ac\uba85 +cop.deptNm = \ubd80\uc11c\uba85 +cop.ofcpsNm = \uc9c1\uc704 +cop.clsfNm = \uc9c1\uae09 +cop.emailAdres = \uc774\uba54\uc77c\uc8fc\uc18c +cop.telNo = \uc804\ud654\ubc88\ud638 +cop.mbtlNum = \ud734\ub300\ud3f0\ubc88\ud638 +cop.adres = \uc8fc\uc18c +cop.extrlUserAt = \uc678\ubd80\uc0ac\uc6a9\uc790\uc5ec\ubd80 +cop.publicAt = \uacf5\uac1c\uc5ec\ubd80 +cop.remark = \ube44\uace0 +cop.trgetNm = \ucee4\ubba4\ub2c8\ud2f0/\ub3d9\ud638\ud68c \uc815\ubcf4 +cop.preview = \ubbf8\ub9ac\ubcf4\uae30 + +cop.withdraw.msg=\ud0c8\ud1f4\ucc98\ub9ac \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.reregist.msg=\uc7ac\uac00\uc785 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.registmanager.msg=\uc6b4\uc601\uc9c4\uc73c\ub85c \ub4f1\ub85d\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.use.msg=\uc0ac\uc6a9 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.unuse.msg=\uc0ac\uc6a9\uc911\uc9c0 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.delete.confirm.msg=\uc0ac\uc6a9\uc911\uc9c0\ub97c \uc120\ud0dd\ud558\uc2e4 \uacbd\uc6b0 \ub2e4\uc2dc \uc0ac\uc6a9\uc73c\ub85c \ubcc0\uacbd\uc774 \ubd88\uac00\ub2a5\ud569\ub2c8\ub2e4. +cop.ing.msg=\uc2b9\uc778\uc694\uccad \uc911\uc785\ub2c8\ub2e4. +cop.request.msg=\uac00\uc785\uc2e0\uccad\uc774 \uc815\uc0c1\uc801\uc73c\ub85c \uc694\uccad\ub418\uc5c8\uc2b5\ub2c8\ub2e4 +cop.password.msg=\ud328\uc2a4\uc6cc\ub4dc\ub97c \uc785\ub825\ud574 \uc8fc\uc2ed\uc2dc\uc624. +cop.password.not.same.msg=\ud328\uc2a4\uc6cc\ub4dc\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + +cop.comment.wrterNm = \uc791\uc131\uc790 +cop.comment.commentCn = \ub0b4\uc6a9 +cop.comment.commentPassword = \ud328\uc2a4\uc6cc\ub4dc + +cop.satisfaction.wrterNm = \uc791\uc131\uc790 +cop.satisfaction.stsfdgCn = \ub0b4\uc6a9 +cop.satisfaction.stsfdg = \ub9cc\uc871\ub3c4 +cop.satisfaction.stsfdgPassword = \ud328\uc2a4\uc6cc\ub4dc + +cop.scrap.scrapNm = \uc2a4\ud06c\ub7a9\uba85 + +#UI USS Message# +uss.ion.noi.ntfcSj=\uc81c\ubaa9 +uss.ion.noi.ntfcCn=\ub0b4\uc6a9 +uss.ion.noi.ntfcDate=\uc54c\ub9bc\uc77c\uc790 +uss.ion.noi.ntfcTime=\uc54c\ub9bc\uc2dc\uac04 +uss.ion.noi.ntfcHH=\uc54c\ub9bc\uc2dc\uac04 +uss.ion.noi.ntfcMM=\uc54c\ub9bc\ubd84 +uss.ion.noi.bhNtfcIntrvl=\uc0ac\uc804\uc54c\ub9bc\uac04\uaca9 +uss.ion.noi.bhNtfcIntrvl.msg=\uc0ac\uc804\uc54c\ub9bc\uac04\uaca9 \uc9c0\uc815\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. +uss.ion.noi.alertNtfcTime=\uc54c\ub9bc\uc77c\uc790 \ubc0f \uc2dc\uac04\uc774 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + +#UI COP Message# +cop.sms.trnsmitTelno=\ubc1c\uc2e0\uc804\ud654\ubc88\ud638 +cop.sms.trnsmitCn=\uc804\uc1a1\ub0b4\uc6a9 +cop.sms.recptnTelno=\uc218\uc2e0\uc804\ud654\ubc88\ud638 +cop.sms.send=\uc804\uc1a1 +cop.sms.addRecptn=\ucd94\uac00 +cop.sms.recptnTelno.msg=\uc218\uc2e0\uc804\ud654\ubc88\ud638 \uc9c0\uc815\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. + +#UI sym.log Message# +sym.log.histSeCode = \uc774\ub825\uad6c\ubd84 +sym.log.sysNm = \uc2dc\uc2a4\ud15c\uba85 +sym.log.histCn = \uc774\ub825\ub0b4\uc6a9 +sym.log.atchFile = \ucca8\ubd80\ud30c\uc77c +sym.log.atchFileList = \ucca8\ubd80\ud30c\uc77c\ubaa9\ub85d +sym.ems.receiver = \ubc1b\ub294\uc0ac\ub78c +sym.ems.title = \uc81c\ubaa9 +sym.ems.content = \ubc1c\uc2e0\ub0b4\uc6a9 + +#Vlidator Errors# +errors.prefix=
+errors.suffix=

+ +errors.required={0}\uc740(\ub294) \ud544\uc218 \uc785\ub825\uac12\uc785\ub2c8\ub2e4. +errors.minlength={0}\uc740(\ub294) {1}\uc790 \uc774\uc0c1 \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.maxlength={0}\uc740(\ub294) {1}\uc790 \uc774\uc0c1 \uc785\ub825\ud560\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +errors.invalid={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uac12\uc785\ub2c8\ub2e4. +errors.minInteger={0}\uc740(\ub294) \uc720\ud6a8\ud55c \uac12\uc774 \uc544\ub2d9\ub2c8\ub2e4. 1 \uc774\uc0c1\uc758 \uac12\uc744 \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.byte={0}\uc740(\ub294) byte\ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.short={0}\uc740(\ub294) short\ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.integer={0}\uc740(\ub294) \uc815\uc218 \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.long={0}\uc740(\ub294) long \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.float={0}\uc740(\ub294) \uc2e4\uc218 \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.double={0}\uc740(\ub294) double \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. + +errors.date={0}\uc740(\ub294) \ub0a0\uc9dc \uc720\ud615\uc774 \uc544\ub2d9\ub2c8\ub2e4. +errors.range={0}\uc740(\ub294) {1}\uacfc {2} \uc0ac\uc774\uc758 \uac12\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.creditcard={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc2e0\uc6a9\uce74\ub4dc \ubc88\ud638\uc785\ub2c8\ub2e4. +errors.email={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc774\uba54\uc77c \uc8fc\uc18c\uc785\ub2c8\ub2e4. + +errors.ihidnum=\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638\uc785\ub2c8\ub2e4. +errors.korean={0}\uc740(\ub294) \ud55c\uae00\uc744 \uc785\ub825\ud558\uc154\uc57c \ud569\ub2c8\ub2e4. +errors.ip=\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 IP\uc8fc\uc18c\uc785\ub2c8\ub2e4. + +errors.password1={0}\uc740(\ub294) 8~20\uc790 \ub0b4\uc5d0\uc11c \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.password2={0}\uc740(\ub294) \ud55c\uae00,\ud2b9\uc218\ubb38\uc790,\ub744\uc5b4\uc4f0\uae30\ub294 \ud5c8\uc6a9\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +errors.password3={0}\uc740(\ub294) \uc21c\ucc28\uc801\uc778 \uc22b\uc790\ub97c 4\uac1c\uc774\uc0c1 \uc5f0\uc18d\ud574\uc11c \uc0ac\uc6a9\ud560\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +errors.password4={0}\uc740(\ub294) \ubc18\ubcf5\ub418\ub294 \ubb38\uc790\ub098 \uc22b\uc790\ub97c 4\uac1c\uc774\uc0c1 \uc5f0\uc18d\ud574\uc11c \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. + +errors.notKorean={0}\uc740(\ub294) \ud55c\uae00\uc744 \uc0ac\uc6a9\ud558\uc2e4\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +error.security.runtime.error = error +#Vlidator Errors- wordDicaryVO# +wordDicaryVO.wordNm=\uc6a9\uc5b4\uba85 +wordDicaryVO.engNm=\uc601\ubb38\uba85 +wordDicaryVO.wordDc=\uc6a9\uc5b4\uc124\uba85 +wordDicaryVO.synonm=\ub3d9\uc758\uc5b4 + +#Vlidator Errors- cnsltManageVO# +cnsltManageVO.cnsltSj=\uc0c1\ub2f4\uc81c\ubaa9 +cnsltManageVO.cnsltCn=\uc0c1\ub2f4\ub0b4\uc6a9 +cnsltManageVO.writngPassword=\uc791\uc131\ube44\ubc00\ubc88\ud638 +cnsltManageVO.areaNo=\uc9c0\uc5ed\ubc88\ud638 +cnsltManageVO.middleTelno=\uc911\uac04\uc804\ud654\ubc88\ud638 +cnsltManageVO.endTelno=\ub05d\uc804\ud654\ubc88\ud638 +cnsltManageVO.wrterNm=\uc791\uc131\uc790\uba85 +cnsltManageVO.managtCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- siteManageVO# +siteManageVO.siteNm=\uc0ac\uc774\ud2b8\uba85 +siteManageVO.siteUrl=\uc0ac\uc774\ud2b8 URL +siteManageVO.siteDc=\uc0ac\uc774\ud2b8\uc124\uba85 +siteManageVO.siteThemaClCode=\uc0ac\uc774\ud2b8\uc8fc\uc81c\ubd84\ub958 +siteManageVO.actvtyAt=\ud65c\uc131\uc5ec\ubd80 +siteManageVO.useAt=\uc0ac\uc6a9\uc5ec\ubd80 + +#Vlidator Errors- recomendSiteManageVO# +recomendSiteManageVO.recomendSiteNm=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uba85 +recomendSiteManageVO.recomendSiteUrl=\ucd94\ucc9c\uc0ac\uc774\ud2b8 URL +recomendSiteManageVO.recomendSiteDc=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uc124\uba85 +recomendSiteManageVO.recomendResnCn=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uc2b9\uc778\uc0ac\uc720 +recomendSiteManageVO.confmDe=\uc2b9\uc778\uc77c\uc790 + +#Vlidator Errors- hpcmManageVO# +hpcmManageVO.hpcmSeCode=\ub3c4\uc6c0\ub9d0\uad6c\ubd84 +hpcmManageVO.hpcmDf=\ub3c4\uc6c0\ub9d0\uc815\uc758 +hpcmManageVO.hpcmDc=\ub3c4\uc6c0\ub9d0\uc124\uba85 + +#Vlidator Errors- newsManageVO# +newsManageVO.newsSj=\ub274\uc2a4\uc81c\ubaa9 +newsManageVO.newsCn=\ub274\uc2a4\ub0b4\uc6a9 +newsManageVO.ntceDe=\uac8c\uc2dc\uc77c\uc790 + +#Vlidator Errors- faqManageVO# +faqManageVO.qestnSj=\uc9c8\ubb38\uc81c\ubaa9 +faqManageVO.qestnCn=\uc9c8\ubb38\ub0b4\uc6a9 +faqManageVO.answerCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- stplatManageVO# +stplatManageVO.useStplatNm=\uc774\uc6a9\uc57d\uad00\uba85 +stplatManageVO.useStplatCn=\uc774\uc6a9\uc57d\uad00\ub0b4\uc6a9 +stplatManageVO.infoProvdAgreCn=\uc815\ubcf4\uc81c\uacf5\ub3d9\uc758\ub0b4\uc6a9 + +#Vlidator Errors- cpyrhtPrtcPolicyVO# +cpyrhtPrtcPolicyVO.cpyrhtPrtcPolicyCn=\uc800\uc791\uad8c\ubcf4\ud638\uc815\ucc45\ub0b4\uc6a9 + +#Vlidator Errors- qnaManageVO# +qnaManageVO.qestnSj=\uc9c8\ubb38\uc81c\ubaa9 +qnaManageVO.qestnCn=\uc9c8\ubb38\ub0b4\uc6a9 +qnaManageVO.writngPassword=\uc791\uc131\ube44\ubc00\ubc88\ud638 +qnaManageVO.areaNo=\uc9c0\uc5ed\ubc88\ud638 +qnaManageVO.middleTelno=\uc911\uac04\uc804\ud654\ubc88\ud638 +qnaManageVO.endTelno=\ub05d\uc804\ud654\ubc88\ud638 +qnaManageVO.wrterNm=\uc791\uc131\uc790\uba85 +qnaManageVO.answerCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- ReprtStatsVO# +sts.reprtId = \ubcf4\uace0\uc11cID +sts.title = \ubcf4\uace0\uc11c\uba85 +sts.category = \ubcf4\uace0\uc11c\uc720\ud615 +sts.status = \uc9c4\ud589\uc0c1\ud0dc +sts.regDate = \ub4f1\ub85d\uc77c\uc2dc + +#Rest day messages# +sym.cal.restDay = \ud734\uc77c\uc77c\uc790 +sym.cal.restName = \ud734\uc77c\uba85 +sym.cal.restDetail = \ud734\uc77c\uc124\uba85 +sym.cal.restCategory = \ud734\uc77c\uad6c\ubd84 \ No newline at end of file diff --git a/mens-api/src/main/resources/egovframework/message/com/message-common_en.properties b/mens-api/src/main/resources/egovframework/message/com/message-common_en.properties new file mode 100644 index 0000000..1d5b3f8 --- /dev/null +++ b/mens-api/src/main/resources/egovframework/message/com/message-common_en.properties @@ -0,0 +1,196 @@ +fail.common.msg=error ocurred! +fail.common.sql=sql error ocurred! error code: {0}, error msg: {1} +info.nodata.msg=no data found. + +#UI Common resource# +button.search=Search +button.use=use +button.notUsed=Not used +button.inquire=inquire +button.update=update +button.create=create +button.delete=delete +button.close=close +button.save=save +button.list=list +button.reset=reset +button.passwordUpdate=password update +button.subscribe=subscribe +button.realname=realname confirm +button.moveToGpin=move to gpin confirm +button.moveToIhidnum=move to ihidnum confirm +button.agree=agree +button.disagree=disagree +button.possible = possible +button.impossible = impossible + +#UI Common Message# +common.save.msg=confirm save? +common.regist.msg=confirm regist? +common.delete.msg=confirm delete? +common.update.msg=confirm update? +common.nodata.msg=There is no data. please choose another seach keyword +common.required.msg=is required field +common.acknowledgement.msg=confirm acknowledgement? +common.acknowledgementcancel.msg=confirm acknowledgement cancel? + +success.request.msg=you're request successfully done + +success.common.select=successfully selected +success.common.insert=successfully inserted +success.common.update=successfully updated +success.common.delete=successfully deleted + +common.imposbl.fileupload = cannot upload files + +fail.common.insert = fail to insert. +fail.common.update = fail to update +fail.common.delete = fail to delete +fail.common.delete.upperMenuExist = fail to delete[upperMenuId foreign key error] +fail.common.select = fail to select +common.isExist.msg = already exist +fail.common.login = login information is not correct +fail.common.idsearch = can not find id +fail.common.pwsearch = can not find password + + +#UI User Message# +fail.user.passwordUpdate1=current password is not correct +fail.user.passwordUpdate2=password confirm is not correct +info.user.rlnmCnfirm=realname confirm ready +success.user.rlnmCnfirm=it is realname +fail.user.rlnmCnfirm=it is not realname +fail.user.connectFail=connection fail + +#UI Cop Message# +cop.extrlUser = External User +cop.intrlUser = Internal User +cop.private = private +cop.public = public + +cop.bbsNm = BBS Name +cop.bbsIntrcn = BBS Introduction +cop.bbsTyCode = BBS Type +cop.bbsAttrbCode = BBS Attribute +cop.replyPosblAt = Reply Possible Alternative +cop.fileAtchPosblAt = File Attach Possible Alternative +cop.posblAtchFileNumber = Possible Attach File Number +cop.tmplatId = Template Information +cop.guestList.subject = This article registered by Guest List +cop.nttSj = Notice Subject +cop.nttCn = Notice Contents +cop.ntceBgnde = Notice Start Date +cop.ntceEndde = Notice End Date +cop.ntcrNm = Noticer Name +cop.password = PassWord +cop.atchFile = Attach Files +cop.guestList = Guest List +cop.guestListCn = Guest List Contents +cop.noticeTerm = Notice term +cop.atchFileList = Attached File List +cop.cmmntyNm = Community Name +cop.cmmntyIntrcn = Community Introduction +cop.cmmntyMngr = Community Manager +cop.clbOprtr = Club Operator +cop.clbIntrcn = Club Introduction +cop.clbNm = Club Name +cop.tmplatNm = Template Name +cop.tmplatSeCode = Template Se Code +cop.tmplatCours = Template Cours +cop.useAt = Use Alternative +cop.ncrdNm = NameCard user name +cop.cmpnyNm = Company name +cop.deptNm = Department name +cop.ofcpsNm = OFCPS name +cop.clsfNm = Class Name +cop.emailAdres = E-mail +cop.telNo = Tel No. +cop.mbtlNum = Mobile +cop.adres = Address +cop.extrlUserAt = External User alternative +cop.publicAt = Public open alternative +cop.remark = Remark +cop.trgetNm = Company/Club Information +cop.preview = preview + +cop.withdraw.msg=confirm withdrawal memebership? +cop.reregist.msg=confirm re-registration? +cop.registmanager.msg=confirm registration of manager? +cop.use.msg=confirm use? +cop.unuse.msg=confirm stop using? +cop.delete.confirm.msg=If you choose to disable the re-use change is impossible. +cop.ing.msg=Approval is being requested. +cop.request.msg=Signup is normally requested. +cop.password.msg=Please enter your password. +cop.password.not.same.msg=Password do not match. + +cop.comment.wrterNm = Writer Name +cop.comment.commentCn = Comment +cop.comment.commentPassword = Password + +cop.satisfaction.wrterNm = Writer Name +cop.satisfaction.stsfdgCn = Satisfaction +cop.satisfaction.stsfdg = Satisfaction Degree +cop.satisfaction.stsfdgPassword = Password + +cop.scrap.scrapNm = Scrap Name + +#UI USS Message# +uss.ion.noi.ntfcSj=Subject +uss.ion.noi.ntfcCn=Contents +uss.ion.noi.ntfcDate=Notification Date +uss.ion.noi.ntfcTime=Notification Time +uss.ion.noi.ntfcHH=Notification Hour +uss.ion.noi.ntfcMM=Notification Minute +uss.ion.noi.bhNtfcIntrvl=Beforehand Interval +uss.ion.noi.bhNtfcIntrvl.msg=Beforehand Interval is required. +uss.ion.noi.alertNtfcTime=Date and time of notification is not valid. + +#UI COP Message# +cop.sms.trnsmitTelno=Sender +cop.sms.trnsmitCn=Contents +cop.sms.recptnTelno=Receiver(s) +cop.sms.send=Send +cop.sms.addRecptn=Add +cop.sms.recptnTelno.msg=The phone number of receiver is required. + +#UI sym.log Message# +sym.log.histSeCode = History Code +sym.log.sysNm = System Name +sym.log.histCn = History Contents +sym.log.atchFile = Attached File +sym.log.atchFileList = Attached File List +sym.ems.receiver = Receiver +sym.ems.title = Title +sym.ems.content = Content + +#Vlidator Errors# +errors.required={0} is required. +errors.minlength={0} can not be less than {1} characters. +errors.maxlength={0} can not be greater than {1} characters. +errors.invalid={0} is invalid. + +errors.byte={0} must be a byte. +errors.short={0} must be a short. +errors.integer={0} must be an integer. +errors.long={0} must be a long. +errors.float={0} must be a float. +errors.double={0} must be a double. + +errors.date={0} is not a date. +errors.range={0} is not in the range {1} through {2}. +errors.creditcard={0} is an invalid credit card number. +errors.email={0} is an invalid e-mail address. + +#Vlidator Errors- ReprtStatsVO# +sts.reprtId = Report ID +sts.title = Report Title +sts.category = Report Category +sts.status = Report Status +sts.regDate = Registration Date + +#Rest day messages# +sym.cal.restDay = Holiday Date +sym.cal.restName = Holiday Name +sym.cal.restDetail = Holiday Detail +sym.cal.restCategory = Holiday Category \ No newline at end of file diff --git a/mens-api/src/main/resources/egovframework/message/com/message-common_ko.properties b/mens-api/src/main/resources/egovframework/message/com/message-common_ko.properties new file mode 100644 index 0000000..4cdeb51 --- /dev/null +++ b/mens-api/src/main/resources/egovframework/message/com/message-common_ko.properties @@ -0,0 +1,294 @@ +fail.common.msg=\uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! +fail.common.sql=sql \uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! error code: {0}, error msg: {1} +info.nodata.msg=\ud574\ub2f9 \ub370\uc774\ud130\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. + +#UI Common resource# +button.search=\uac80\uc0c9 +button.use=\uc0ac\uc6a9 +button.notUsed=\uc0ac\uc6a9\uc911\uc9c0 +button.inquire=\uc870\ud68c +button.update=\uc218\uc815 +button.create=\ub4f1\ub85d +button.delete=\uc0ad\uc81c +button.deleteDatabase=\uc644\uc804\uc0ad\uc81c +button.close=\ub2eb\uae30 +button.save=\uc800\uc7a5 +button.list=\ubaa9\ub85d +button.reset=\ucde8\uc18c +button.passwordUpdate=\uc554\ud638\ubcc0\uacbd +button.subscribe=\uac00\uc785\uc2e0\uccad +button.realname=\uc2e4\uba85\ud655\uc778 +button.moveToGpin=GPIN\uc2e4\uba85\ud655\uc778\uc73c\ub85c \uc774\ub3d9 +button.moveToIhidnum=\uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638 \uc2e4\uba85\ud655\uc778\uc73c\ub85c \uc774\ub3d9 +button.agree=\ub3d9\uc758 +button.disagree=\ube44\ub3d9\uc758 +button.possible = \uac00\ub2a5 +button.impossible = \ubd88\uac00\ub2a5 +button.qnaregist=Q&A\ub4f1\ub85d +button.cnsltregist=\uc0c1\ub2f4\ub4f1\ub85d +button.preview=\ubbf8\ub9ac\ubcf4\uae30 +button.next=\ub2e4\uc74c +button.add=\ubc14\ub85c\ucd94\uac00 +button.confirm=\ud655\uc778 + + +#UI Common Message# +common.save.msg=\uc800\uc7a5\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.regist.msg=\ub4f1\ub85d\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.delete.msg=\uc0ad\uc81c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.update.msg=\uc218\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.nodata.msg=\uc790\ub8cc\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\ub978 \uac80\uc0c9\uc870\uac74\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694 +common.required.msg=(\uc740)\ub294 \ud544\uc218\uc785\ub825\ud56d\ubaa9\uc785\ub2c8\ub2e4. +common.acknowledgement.msg=\uc2b9\uc778\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.acknowledgementcancel.msg=\uc2b9\uc778\ucde8\uc18c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? + +success.common.select=\uc815\uc0c1\uc801\uc73c\ub85c \uc870\ud68c\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.insert=\uc815\uc0c1\uc801\uc73c\ub85c \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.update=\uc815\uc0c1\uc801\uc73c\ub85c \uc218\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.delete=\uc815\uc0c1\uc801\uc73c\ub85c \uc0ad\uc81c\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +success.request.msg = \uc694\uccad\ucc98\ub9ac\uac00 \uc131\uacf5\uc801\uc73c\ub85c \uc218\ud589\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +common.imposbl.fileupload = \ub354 \uc774\uc0c1 \ud30c\uc77c\uc744 \ucca8\ubd80\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +common.isConfmDe.msg=\uc2b9\uc778\uc77c\uc790\ub97c \ud655\uc778 \ubc14\ub78d\ub2c8\ub2e4. + + +fail.common.insert = \uc0dd\uc131\uc774 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.update = \uc218\uc815\uc774 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.delete = \uc0ad\uc81c\uac00 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.delete.upperMenuExist = \ucc38\uc870\ub418\ub294 \uba54\ub274\uac00 \uc788\uc5b4 \uc0ad\uc81c\uac00 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.select = \uc870\ud68c\uc5d0 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +common.isExist.msg = \uc774\ubbf8 \uc874\uc7ac\ud558\uac70\ub098 \uacfc\uac70\uc5d0 \ub4f1\ub85d\uc774 \ub418\uc5c8\ub358 \uc0c1\ud0dc\uc785\ub2c8\ub2e4. +fail.common.login = \ub85c\uadf8\uc778 \uc815\ubcf4\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.common.idsearch = \uc544\uc774\ub514\ub97c \ucc3e\uc744\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +fail.common.pwsearch = \ube44\ubc00\ubc88\ud638\ub97c \ucc3e\uc744\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +fail.request.msg = \uc694\uccad\ucc98\ub9ac\ub97c \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. + +#UI User Message# +fail.user.passwordUpdate1=\ud604\uc7ac \ube44\ubc00\ubc88\ud638\uac00 \ub9de\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.user.passwordUpdate2=\ube44\ubc00\ubc88\ud638\uc640 \ube44\ubc00\ubc88\ud638 \ud655\uc778\uc774 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +info.user.rlnmCnfirm=\uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2e4\uba85\ud655\uc778\uc744 \ud558\uc2ed\uc2dc\uc624. +success.user.rlnmCnfirm=\ud589\uc815\uc548\uc804\ubd80\uc758 \uc8fc\ubbfc\ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud569\ub2c8\ub2e4. +fail.user.rlnmCnfirm=\ud589\uc815\uc548\uc804\ubd80\uc758 \uc8fc\ubbfc\ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.user.connectFail=\uc2dc\uc2a4\ud15c \uc7a5\uc560\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.(\uc778\uc99d\uc11c\ubc84 \uc5f0\uacb0 \uc2e4\ud328) +info.user.rlnmPinCnfirm=\uacf5\uacf5 \uc544\uc774\ud540 \uc544\uc774\ub514\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2e4\uba85\ud655\uc778\uc744 \ud558\uc2ed\uc2dc\uc624. +success.user.rlnmPinCnfirm=\uacf5\uacf5\uc544\uc774\ud540\uc758 \ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud569\ub2c8\ub2e4. +fail.user.rlnmPinCnfirm=\uacf5\uacf5\uc544\uc774\ud540\uc758 \ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + + +#UI Cop Message# +cop.extrlUser = \uc678\ubd80\uc0ac\uc6a9\uc790 +cop.intrlUser = \ub0b4\ubd80\uc0ac\uc6a9\uc790 +cop.private = \ube44\uacf5\uac1c +cop.public = \uacf5\uac1c + +cop.adbkNm = \uc8fc\uc18c\ub85d\uba85 +cop.othbcScope = \uacf5\uac1c\ubc94\uc704 +cop.company = \ud68c\uc0ac +cop.part = \ubd80\uc11c +cop.man = \uac1c\uc778 +cop.adbkUser = \uad6c\uc131\uc6d0 +cop.bbsNm = \uac8c\uc2dc\ud310\uba85 +cop.bbsIntrcn = \uac8c\uc2dc\ud310\uc18c\uac1c +cop.bbsTyCode = \uac8c\uc2dc\ud310 \uc720\ud615 +cop.bbsAttrbCode = \uac8c\uc2dc\ud310 \uc18d\uc131 +cop.replyPosblAt = \ub2f5\uc7a5\uac00\ub2a5\uc5ec\ubd80 +cop.fileAtchPosblAt = \ud30c\uc77c\ucca8\ubd80\uac00\ub2a5\uc5ec\ubd80 +cop.posblAtchFileNumber = \ucca8\ubd80\uac00\ub2a5\ud30c\uc77c \uc22b\uc790 +cop.tmplatId = \ud15c\ud50c\ub9bf \uc815\ubcf4 +cop.guestList.subject = \ubc29\uba85\ub85d \uac8c\uc2dc\uae00\uc785\ub2c8\ub2e4. +cop.nttSj = \uc81c\ubaa9 +cop.nttCn = \uae00\ub0b4\uc6a9 +cop.ntceBgnde = \uac8c\uc2dc\uc2dc\uc791\uc77c +cop.ntceEndde = \uac8c\uc2dc\uc885\ub8cc\uc77c +cop.ntcrNm = \uc791\uc131\uc790 +cop.password = \ud328\uc2a4\uc6cc\ub4dc +cop.atchFile = \ud30c\uc77c\ucca8\ubd80 +cop.guestList = \ubc29\uba85\ub85d +cop.guestListCn = \ubc29\uba85\ub85d \ub0b4\uc6a9 +cop.noticeTerm = \uac8c\uc2dc\uae30\uac04 +cop.atchFileList = \ucca8\ubd80\ud30c\uc77c\ubaa9\ub85d +cop.cmmntyNm = \ucee4\ubba4\ub2c8\ud2f0\uba85 +cop.cmmntyIntrcn = \ucee4\ubba4\ub2c8\ud2f0 \uc18c\uac1c +cop.cmmntyMngr = \ucee4\ubba4\ub2c8\ud2f0 \uad00\ub9ac\uc790 +cop.clbOprtr = \ub3d9\ud638\ud68c \uc6b4\uc601\uc790 +cop.clbIntrcn = \ub3d9\ud638\ud68c \uc18c\uac1c +cop.clbNm = \ub3d9\ud638\ud68c \uba85 +cop.tmplatNm = \ud15c\ud50c\ub9bf\uba85 +cop.tmplatSeCode = \ud15c\ud50c\ub9bf \uad6c\ubd84 +cop.tmplatCours = \ud15c\ud50c\ub9bf\uacbd\ub85c +cop.useAt = \uc0ac\uc6a9\uc5ec\ubd80 +cop.ncrdNm = \uc774\ub984 +cop.cmpnyNm = \ud68c\uc0ac\uba85 +cop.deptNm = \ubd80\uc11c\uba85 +cop.ofcpsNm = \uc9c1\uc704 +cop.clsfNm = \uc9c1\uae09 +cop.emailAdres = \uc774\uba54\uc77c\uc8fc\uc18c +cop.telNo = \uc804\ud654\ubc88\ud638 +cop.mbtlNum = \ud734\ub300\ud3f0\ubc88\ud638 +cop.adres = \uc8fc\uc18c +cop.extrlUserAt = \uc678\ubd80\uc0ac\uc6a9\uc790\uc5ec\ubd80 +cop.publicAt = \uacf5\uac1c\uc5ec\ubd80 +cop.remark = \ube44\uace0 +cop.trgetNm = \ucee4\ubba4\ub2c8\ud2f0/\ub3d9\ud638\ud68c \uc815\ubcf4 +cop.preview = \ubbf8\ub9ac\ubcf4\uae30 + +cop.withdraw.msg=\ud0c8\ud1f4\ucc98\ub9ac \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.reregist.msg=\uc7ac\uac00\uc785 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.registmanager.msg=\uc6b4\uc601\uc9c4\uc73c\ub85c \ub4f1\ub85d\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.use.msg=\uc0ac\uc6a9 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.unuse.msg=\uc0ac\uc6a9\uc911\uc9c0 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.delete.confirm.msg=\uc0ac\uc6a9\uc911\uc9c0\ub97c \uc120\ud0dd\ud558\uc2e4 \uacbd\uc6b0 \ub2e4\uc2dc \uc0ac\uc6a9\uc73c\ub85c \ubcc0\uacbd\uc774 \ubd88\uac00\ub2a5\ud569\ub2c8\ub2e4. +cop.ing.msg=\uc2b9\uc778\uc694\uccad \uc911\uc785\ub2c8\ub2e4. +cop.request.msg=\uac00\uc785\uc2e0\uccad\uc774 \uc815\uc0c1\uc801\uc73c\ub85c \uc694\uccad\ub418\uc5c8\uc2b5\ub2c8\ub2e4 +cop.password.msg=\ud328\uc2a4\uc6cc\ub4dc\ub97c \uc785\ub825\ud574 \uc8fc\uc2ed\uc2dc\uc624. +cop.password.not.same.msg=\ud328\uc2a4\uc6cc\ub4dc\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + +cop.comment.wrterNm = \uc791\uc131\uc790 +cop.comment.commentCn = \ub0b4\uc6a9 +cop.comment.commentPassword = \ud328\uc2a4\uc6cc\ub4dc + +cop.satisfaction.wrterNm = \uc791\uc131\uc790 +cop.satisfaction.stsfdgCn = \ub0b4\uc6a9 +cop.satisfaction.stsfdg = \ub9cc\uc871\ub3c4 +cop.satisfaction.stsfdgPassword = \ud328\uc2a4\uc6cc\ub4dc + +cop.scrap.scrapNm = \uc2a4\ud06c\ub7a9\uba85 + +#UI USS Message# +uss.ion.noi.ntfcSj=\uc81c\ubaa9 +uss.ion.noi.ntfcCn=\ub0b4\uc6a9 +uss.ion.noi.ntfcDate=\uc54c\ub9bc\uc77c\uc790 +uss.ion.noi.ntfcTime=\uc54c\ub9bc\uc2dc\uac04 +uss.ion.noi.ntfcHH=\uc54c\ub9bc\uc2dc\uac04 +uss.ion.noi.ntfcMM=\uc54c\ub9bc\ubd84 +uss.ion.noi.bhNtfcIntrvl=\uc0ac\uc804\uc54c\ub9bc\uac04\uaca9 +uss.ion.noi.bhNtfcIntrvl.msg=\uc0ac\uc804\uc54c\ub9bc\uac04\uaca9 \uc9c0\uc815\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. +uss.ion.noi.alertNtfcTime=\uc54c\ub9bc\uc77c\uc790 \ubc0f \uc2dc\uac04\uc774 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + +#UI COP Message# +cop.sms.trnsmitTelno=\ubc1c\uc2e0\uc804\ud654\ubc88\ud638 +cop.sms.trnsmitCn=\uc804\uc1a1\ub0b4\uc6a9 +cop.sms.recptnTelno=\uc218\uc2e0\uc804\ud654\ubc88\ud638 +cop.sms.send=\uc804\uc1a1 +cop.sms.addRecptn=\ucd94\uac00 +cop.sms.recptnTelno.msg=\uc218\uc2e0\uc804\ud654\ubc88\ud638 \uc9c0\uc815\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. + +#UI sym.log Message# +sym.log.histSeCode = \uc774\ub825\uad6c\ubd84 +sym.log.sysNm = \uc2dc\uc2a4\ud15c\uba85 +sym.log.histCn = \uc774\ub825\ub0b4\uc6a9 +sym.log.atchFile = \ucca8\ubd80\ud30c\uc77c +sym.log.atchFileList = \ucca8\ubd80\ud30c\uc77c\ubaa9\ub85d +sym.ems.receiver = \ubc1b\ub294\uc0ac\ub78c +sym.ems.title = \uc81c\ubaa9 +sym.ems.content = \ubc1c\uc2e0\ub0b4\uc6a9 + +#Vlidator Errors# +errors.prefix=
+errors.suffix=

+ +errors.required={0}\uc740(\ub294) \ud544\uc218 \uc785\ub825\uac12\uc785\ub2c8\ub2e4. +errors.minlength={0}\uc740(\ub294) {1}\uc790 \uc774\uc0c1 \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.maxlength={0}\uc740(\ub294) {1}\uc790 \uc774\uc0c1 \uc785\ub825\ud560\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +errors.invalid={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uac12\uc785\ub2c8\ub2e4. +errors.minInteger={0}\uc740(\ub294) \uc720\ud6a8\ud55c \uac12\uc774 \uc544\ub2d9\ub2c8\ub2e4. 1 \uc774\uc0c1\uc758 \uac12\uc744 \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.byte={0}\uc740(\ub294) byte\ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.short={0}\uc740(\ub294) short\ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.integer={0}\uc740(\ub294) \uc815\uc218 \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.long={0}\uc740(\ub294) long \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.float={0}\uc740(\ub294) \uc2e4\uc218 \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.double={0}\uc740(\ub294) double \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. + +errors.date={0}\uc740(\ub294) \ub0a0\uc9dc \uc720\ud615\uc774 \uc544\ub2d9\ub2c8\ub2e4. +errors.range={0}\uc740(\ub294) {1}\uacfc {2} \uc0ac\uc774\uc758 \uac12\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.creditcard={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc2e0\uc6a9\uce74\ub4dc \ubc88\ud638\uc785\ub2c8\ub2e4. +errors.email={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc774\uba54\uc77c \uc8fc\uc18c\uc785\ub2c8\ub2e4. + +errors.ihidnum=\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638\uc785\ub2c8\ub2e4. +errors.korean={0}\uc740(\ub294) \ud55c\uae00\uc744 \uc785\ub825\ud558\uc154\uc57c \ud569\ub2c8\ub2e4. +errors.ip=\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 IP\uc8fc\uc18c\uc785\ub2c8\ub2e4. + +errors.password1={0}\uc740(\ub294) 8~20\uc790 \ub0b4\uc5d0\uc11c \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.password2={0}\uc740(\ub294) \ud55c\uae00,\ud2b9\uc218\ubb38\uc790,\ub744\uc5b4\uc4f0\uae30\ub294 \ud5c8\uc6a9\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +errors.password3={0}\uc740(\ub294) \uc21c\ucc28\uc801\uc778 \uc22b\uc790\ub97c 4\uac1c\uc774\uc0c1 \uc5f0\uc18d\ud574\uc11c \uc0ac\uc6a9\ud560\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +errors.password4={0}\uc740(\ub294) \ubc18\ubcf5\ub418\ub294 \ubb38\uc790\ub098 \uc22b\uc790\ub97c 4\uac1c\uc774\uc0c1 \uc5f0\uc18d\ud574\uc11c \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. + +errors.notKorean={0}\uc740(\ub294) \ud55c\uae00\uc744 \uc0ac\uc6a9\ud558\uc2e4\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +error.security.runtime.error = error +#Vlidator Errors- wordDicaryVO# +wordDicaryVO.wordNm=\uc6a9\uc5b4\uba85 +wordDicaryVO.engNm=\uc601\ubb38\uba85 +wordDicaryVO.wordDc=\uc6a9\uc5b4\uc124\uba85 +wordDicaryVO.synonm=\ub3d9\uc758\uc5b4 + +#Vlidator Errors- cnsltManageVO# +cnsltManageVO.cnsltSj=\uc0c1\ub2f4\uc81c\ubaa9 +cnsltManageVO.cnsltCn=\uc0c1\ub2f4\ub0b4\uc6a9 +cnsltManageVO.writngPassword=\uc791\uc131\ube44\ubc00\ubc88\ud638 +cnsltManageVO.areaNo=\uc9c0\uc5ed\ubc88\ud638 +cnsltManageVO.middleTelno=\uc911\uac04\uc804\ud654\ubc88\ud638 +cnsltManageVO.endTelno=\ub05d\uc804\ud654\ubc88\ud638 +cnsltManageVO.wrterNm=\uc791\uc131\uc790\uba85 +cnsltManageVO.managtCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- siteManageVO# +siteManageVO.siteNm=\uc0ac\uc774\ud2b8\uba85 +siteManageVO.siteUrl=\uc0ac\uc774\ud2b8 URL +siteManageVO.siteDc=\uc0ac\uc774\ud2b8\uc124\uba85 +siteManageVO.siteThemaClCode=\uc0ac\uc774\ud2b8\uc8fc\uc81c\ubd84\ub958 +siteManageVO.actvtyAt=\ud65c\uc131\uc5ec\ubd80 +siteManageVO.useAt=\uc0ac\uc6a9\uc5ec\ubd80 + +#Vlidator Errors- recomendSiteManageVO# +recomendSiteManageVO.recomendSiteNm=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uba85 +recomendSiteManageVO.recomendSiteUrl=\ucd94\ucc9c\uc0ac\uc774\ud2b8 URL +recomendSiteManageVO.recomendSiteDc=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uc124\uba85 +recomendSiteManageVO.recomendResnCn=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uc2b9\uc778\uc0ac\uc720 +recomendSiteManageVO.confmDe=\uc2b9\uc778\uc77c\uc790 + +#Vlidator Errors- hpcmManageVO# +hpcmManageVO.hpcmSeCode=\ub3c4\uc6c0\ub9d0\uad6c\ubd84 +hpcmManageVO.hpcmDf=\ub3c4\uc6c0\ub9d0\uc815\uc758 +hpcmManageVO.hpcmDc=\ub3c4\uc6c0\ub9d0\uc124\uba85 + +#Vlidator Errors- newsManageVO# +newsManageVO.newsSj=\ub274\uc2a4\uc81c\ubaa9 +newsManageVO.newsCn=\ub274\uc2a4\ub0b4\uc6a9 +newsManageVO.ntceDe=\uac8c\uc2dc\uc77c\uc790 + +#Vlidator Errors- faqManageVO# +faqManageVO.qestnSj=\uc9c8\ubb38\uc81c\ubaa9 +faqManageVO.qestnCn=\uc9c8\ubb38\ub0b4\uc6a9 +faqManageVO.answerCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- stplatManageVO# +stplatManageVO.useStplatNm=\uc774\uc6a9\uc57d\uad00\uba85 +stplatManageVO.useStplatCn=\uc774\uc6a9\uc57d\uad00\ub0b4\uc6a9 +stplatManageVO.infoProvdAgreCn=\uc815\ubcf4\uc81c\uacf5\ub3d9\uc758\ub0b4\uc6a9 + +#Vlidator Errors- cpyrhtPrtcPolicyVO# +cpyrhtPrtcPolicyVO.cpyrhtPrtcPolicyCn=\uc800\uc791\uad8c\ubcf4\ud638\uc815\ucc45\ub0b4\uc6a9 + +#Vlidator Errors- qnaManageVO# +qnaManageVO.qestnSj=\uc9c8\ubb38\uc81c\ubaa9 +qnaManageVO.qestnCn=\uc9c8\ubb38\ub0b4\uc6a9 +qnaManageVO.writngPassword=\uc791\uc131\ube44\ubc00\ubc88\ud638 +qnaManageVO.areaNo=\uc9c0\uc5ed\ubc88\ud638 +qnaManageVO.middleTelno=\uc911\uac04\uc804\ud654\ubc88\ud638 +qnaManageVO.endTelno=\ub05d\uc804\ud654\ubc88\ud638 +qnaManageVO.wrterNm=\uc791\uc131\uc790\uba85 +qnaManageVO.answerCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- ReprtStatsVO# +sts.reprtId = \ubcf4\uace0\uc11cID +sts.title = \ubcf4\uace0\uc11c\uba85 +sts.category = \ubcf4\uace0\uc11c\uc720\ud615 +sts.status = \uc9c4\ud589\uc0c1\ud0dc +sts.regDate = \ub4f1\ub85d\uc77c\uc2dc + +#Rest day messages# +sym.cal.restDay = \ud734\uc77c\uc77c\uc790 +sym.cal.restName = \ud734\uc77c\uba85 +sym.cal.restDetail = \ud734\uc77c\uc124\uba85 +sym.cal.restCategory = \ud734\uc77c\uad6c\ubd84 \ No newline at end of file diff --git a/mens-api/src/main/resources/logback-spring.xml b/mens-api/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..1a30325 --- /dev/null +++ b/mens-api/src/main/resources/logback-spring.xml @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + ${CONSOLE_LOG_CHARSET} + + + + + + + + + + + ${LOG_PATH}/${LOG_FILE}.log + + DEBUG + ACCEPT + + ACCEPT + + + + + + ${FILE_LOG_PATTERN} + ${FILE_LOG_CHARSET} + + + + ${LOG_PATH}/backup/${LOG_FILE}_%d{yyyy-MM-dd}.%i.log.gz + + 30MB + + 50 + + + + + + + + + + 2048 + 20 + + 6000 + + + true + + ${isIncludeCallerData} + + + DEBUG + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mens-api/src/main/resources/static/error.html b/mens-api/src/main/resources/static/error.html new file mode 100644 index 0000000..a49f913 --- /dev/null +++ b/mens-api/src/main/resources/static/error.html @@ -0,0 +1,10 @@ + + + + + Title + + +

Error

+ + diff --git a/mens-api/src/main/resources/static/index.html b/mens-api/src/main/resources/static/index.html new file mode 100644 index 0000000..f6e2484 --- /dev/null +++ b/mens-api/src/main/resources/static/index.html @@ -0,0 +1,16 @@ + + + + + 전자고지 API Framework + + +

전자고지 API Framework

+

+ API Document +

+

+ 자세한 사항은 README.md 참고 +

+ + diff --git a/mens-api/src/test/java/ApiWebClientTest.java b/mens-api/src/test/java/ApiWebClientTest.java new file mode 100644 index 0000000..5589e01 --- /dev/null +++ b/mens-api/src/test/java/ApiWebClientTest.java @@ -0,0 +1,31 @@ +import static org.assertj.core.api.Assertions.*; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.*; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpMethod; +import org.springframework.test.web.reactive.server.WebTestClient; + + +@AutoConfigureWebClient +@SpringBootTest(webEnvironment = RANDOM_PORT) +public class ApiWebClientTest { + + @Autowired + WebTestClient webTestClient; + + @Test + public void helloWebClient() { + webTestClient.method(HttpMethod.GET) + .uri("/hello") + .exchange() + .expectStatus().isOk() // 응답 코드 기대값 + .expectBody(String.class) // 응답 body 클래스 타입 기대값 + .value(response -> { // 응답 바디 response + System.out.println("response = " + response); + assertThat(response).isEqualToIgnoringCase("Hello World"); + }); + } +} diff --git a/mens-batch/.gitignore b/mens-batch/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/mens-batch/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/mens-batch/README.md b/mens-batch/README.md new file mode 100644 index 0000000..9e42de9 --- /dev/null +++ b/mens-batch/README.md @@ -0,0 +1,171 @@ + +### API 가이드 +[카카오페이 문서발송 단건](./document/카카오페이내문서함_1.문서발송(단건).pdf) +[카카오페이 문서발송 대량](./document/카카오페이내문서함_1.문서발송(대량).pdf) +[카카오페이 문서발송 네트워크가이드](./document/카카오페이내문서함_1.네트워크가이드.pdf) + +### swagger +[API URL](http://localhost:8081/swagger-ui.html) +[Front URL](http://localhost:8080/swagger-ui.html) +[Front test page](http://localhost:8080/api/kakaopay/test) + +### API 결과 수신 +* 정상 수신 +```java +public class ApiResponseDTO implements Serializable { + private static final String FAIL_STATUS = "fail"; + private static final String ERROR_STATUS = "error"; + + @Schema(example = "true", description = "에러인 경우 false", requiredMode = Schema.RequiredMode.REQUIRED) + private boolean success; + + @Schema(example = " ", description = "HttpStatus.OK", requiredMode = Schema.RequiredMode.REQUIRED) + private String code; + + @Schema(description = "결과 데이타, 오류시 null", example = " ") + private T data; + + @Schema(description = "오류 발생시 오류 메세지", example = " ", requiredMode = Schema.RequiredMode.AUTO) + @Setter + private String message; + + @Schema(example = " ", description = "HttpStatus.OK", requiredMode = Schema.RequiredMode.AUTO) + private HttpStatus httpStatus; + + @Schema(description = "API 실행 결과 데이타 수") + private int count; +} +``` +* 정상 수신 +```json +{ + "success": true, + "code": "200", + "httpStatus": "OK", + "message": "성공했습니다.", + "data": { + "token_status": "USED", + "token_expires_at": 1624344762, + "token_used_at": 0, + "doc_box_sent_at": 0, + "doc_box_received_at": 0, + "authenticated_at": 0, + "user_notified_at": 0, + "payload": "payload 파라미터 입니다.", + "signed_at": 0 + }, + "count": 1, + "paginationInfo": null +} +``` +* 에러 수신 +```json +{ + "success": false, + "code": "error", + "data": null, + "message": "로그인 정보가 올바르지 않습니다.", + "httpStatus": "BAD_REQUEST", + "count": 0, + "paginationInfo": null +} +``` +* API 호출 결과가 서버등(네트웍장애)의 장애인 경우를 제외 하고 + 예외로 return 되는 경우는 없다(발생시 공통팀에 반드시 알려 줄 것) +```js + $.ajax({ + url: url, + type: method, + contentType: "application/json; charset=utf-8", + dataType: "json", + data: JSON.stringify(data), + beforeSend: (xhr) => { + //xhr.setRequestHeader(header, token); + $("#loading").show(); + + }, + success: function (res, textStatus) { + console.log( JSON.stringify(res)); + if(res.success){ + //정상 응답 + $("#resData").text(res.data) + }else{ + //에러 응답 + $("#errData").text(JSON.stringify(res)); + } + }, + error : function(data) { + // 여기로 오는 경우 공통팀에 알려 주세요 + alert("점검필요-error로 return", data.responseText); + }, + complete: () => { + $("#loading").hide(); + } + }); +``` +### API(Restful call) validation +* Controller 단에서 @Validated 사용으로 처리 가능 +* But, 이경우 API 로그를 남기기 위해 Service 단에서 체크 하도록 컨트롤러 단에서는 유효성 체크 skip + +### spring validation +```text +@Valid는 Java, @Validated는 Spring에서 지원하는 어노테이션 +@Validated는 @Valid의 기능을 포함하고, 유효성을 검토할 그룹을 지정할 수 있는 기능이 추가됨 +``` + +```java +@Null // null만 혀용 +@NotNull // null을 허용하지 않습니다. "", " "는 허용 +@NotEmpty // null, ""을 허용하지 않습니다. " "는 허용 +@NotBlank // null, "", " " 모두 허용하지 않습니다. + +@Email // 이메일 형식을 검사합니다. 다만 ""의 경우를 통과 시킵니다 +@Pattern(regexp = ) // 정규식을 검사할 때 사용됩니다. +@Size(min=, max=) // 길이를 제한할 때 사용됩니다. + +@Max(value = ) // value 이하의 값을 받을 때 사용됩니다. +@Min(value = ) // value 이상의 값을 받을 때 사용됩니다. + +@Positive // 값을 양수로 제한합니다. +@PositiveOrZero // 값을 양수와 0만 가능하도록 제한합니다. + +@Negative // 값을 음수로 제한합니다. +@NegativeOrZero // 값을 음수와 0만 가능하도록 제한합니다. + +@Future // 현재보다 미래 +@Past // 현재보다 과거 + +@AssertFalse // false 여부, null은 체크하지 않습니다. +@AssertTrue // true 여부, null은 체크하지 않습니다. +``` +### intellij devtools 활성 +```text +1. IntelliJ - Preferencs… +2. 컴파일러 - build project automatically(프로젝트 자동 빌드) 체크 +3. Advanced Settings > Compiler + Allow auto-make to start even if developed application is current running + (개발된 애플리케이션이 현재 실행 중인 경우에도 auto-make가 시작되도록 허용) 체크 +# 1 ~ 3항 까지 설정후 에도 안되는 경우만 4번 설정 +4. 서버설정 : Edit Configurations... + Modfy Options > On Update Action > Update Resources + +``` +### ens-api 배포 및 run : profile에 따라 local|dev|prod +```shell +# jdk : azul-17.0.1 +# 프로젝트 root 폴더로 이동 : ens-parent +# 패키지 생성 : local|dev|prod +$ mvnw clean package -P local + +# 실행 : 프로젝트폴더/ens-parent/ens-api/target에 생성된 jar파일 실행 +$ c:\tools\java\azul-17.0.1\java -jar -Dspring.profiles.active=local .\ens-api.jar + +# mvn 명령어 설명 +# -pl [모듈명] : 모듈명의 프로젝트만 빌드 +# -am : 의존성 있는 프로젝트 함께 빌드 - C가 A를 디펜던시로 가지고 있으며 C를 빌드하면 A -> C 순으로 빌드 +$ mvnw clean package -pl ens-api -am -P local +# -amd : 의존성 있는 타 프로젝트 빌드 - C가 A를 디펜던시로 가지고 있는 경우 A를 빌드 하면 A -> C 순으로 빌드 +$ mvnw clean package -pl egov-core -amd -P local +``` +### 스프링 배치 DB schema +[mysql DDL 스크립트](./document/batch-schema-mysql.sql) diff --git a/mens-batch/document/batch-schema-mysql.sql b/mens-batch/document/batch-schema-mysql.sql new file mode 100644 index 0000000..1a5d87b --- /dev/null +++ b/mens-batch/document/batch-schema-mysql.sql @@ -0,0 +1,101 @@ +-- Autogenerated: do not edit this file + +CREATE TABLE BATCH_JOB_INSTANCE ( + JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY , + VERSION BIGINT , + JOB_NAME VARCHAR(100) NOT NULL, + JOB_KEY VARCHAR(32) NOT NULL, + constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) +) ENGINE=InnoDB; + +CREATE TABLE BATCH_JOB_EXECUTION ( + JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , + VERSION BIGINT , + JOB_INSTANCE_ID BIGINT NOT NULL, + CREATE_TIME DATETIME(6) NOT NULL, + START_TIME DATETIME(6) DEFAULT NULL , + END_TIME DATETIME(6) DEFAULT NULL , + STATUS VARCHAR(10) , + EXIT_CODE VARCHAR(2500) , + EXIT_MESSAGE VARCHAR(2500) , + LAST_UPDATED DATETIME(6), + JOB_CONFIGURATION_LOCATION VARCHAR(2500) NULL, + constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) + references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) +) ENGINE=InnoDB; + +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL , + TYPE_CD VARCHAR(6) NOT NULL , + KEY_NAME VARCHAR(100) NOT NULL , + STRING_VAL VARCHAR(250) , + DATE_VAL DATETIME(6) DEFAULT NULL , + LONG_VAL BIGINT , + DOUBLE_VAL DOUBLE PRECISION , + IDENTIFYING CHAR(1) NOT NULL , + constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) + references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) +) ENGINE=InnoDB; + +CREATE TABLE BATCH_STEP_EXECUTION ( + STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , + VERSION BIGINT NOT NULL, + STEP_NAME VARCHAR(100) NOT NULL, + JOB_EXECUTION_ID BIGINT NOT NULL, + START_TIME DATETIME(6) NOT NULL , + END_TIME DATETIME(6) DEFAULT NULL , + STATUS VARCHAR(10) , + COMMIT_COUNT BIGINT , + READ_COUNT BIGINT , + FILTER_COUNT BIGINT , + WRITE_COUNT BIGINT , + READ_SKIP_COUNT BIGINT , + WRITE_SKIP_COUNT BIGINT , + PROCESS_SKIP_COUNT BIGINT , + ROLLBACK_COUNT BIGINT , + EXIT_CODE VARCHAR(2500) , + EXIT_MESSAGE VARCHAR(2500) , + LAST_UPDATED DATETIME(6), + constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID) + references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) +) ENGINE=InnoDB; + +CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( + STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, + SHORT_CONTEXT VARCHAR(2500) NOT NULL, + SERIALIZED_CONTEXT TEXT , + constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID) + references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) +) ENGINE=InnoDB; + +CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( + JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, + SHORT_CONTEXT VARCHAR(2500) NOT NULL, + SERIALIZED_CONTEXT TEXT , + constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID) + references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) +) ENGINE=InnoDB; + +CREATE TABLE BATCH_STEP_EXECUTION_SEQ ( + ID BIGINT NOT NULL, + UNIQUE_KEY CHAR(1) NOT NULL, + constraint UNIQUE_KEY_UN unique (UNIQUE_KEY) +) ENGINE=InnoDB; + +INSERT INTO BATCH_STEP_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_STEP_EXECUTION_SEQ); + +CREATE TABLE BATCH_JOB_EXECUTION_SEQ ( + ID BIGINT NOT NULL, + UNIQUE_KEY CHAR(1) NOT NULL, + constraint UNIQUE_KEY_UN unique (UNIQUE_KEY) +) ENGINE=InnoDB; + +INSERT INTO BATCH_JOB_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_EXECUTION_SEQ); + +CREATE TABLE BATCH_JOB_SEQ ( + ID BIGINT NOT NULL, + UNIQUE_KEY CHAR(1) NOT NULL, + constraint UNIQUE_KEY_UN unique (UNIQUE_KEY) +) ENGINE=InnoDB; + +INSERT INTO BATCH_JOB_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_SEQ); diff --git a/mens-batch/document/카카오페이내문서함_1.네트워크가이드.pdf b/mens-batch/document/카카오페이내문서함_1.네트워크가이드.pdf new file mode 100644 index 0000000..5813709 Binary files /dev/null and b/mens-batch/document/카카오페이내문서함_1.네트워크가이드.pdf differ diff --git a/mens-batch/document/카카오페이내문서함_1.문서발송(단건).pdf b/mens-batch/document/카카오페이내문서함_1.문서발송(단건).pdf new file mode 100644 index 0000000..638b3ee Binary files /dev/null and b/mens-batch/document/카카오페이내문서함_1.문서발송(단건).pdf differ diff --git a/mens-batch/document/카카오페이내문서함_1.문서발송(대량).pdf b/mens-batch/document/카카오페이내문서함_1.문서발송(대량).pdf new file mode 100644 index 0000000..100d720 Binary files /dev/null and b/mens-batch/document/카카오페이내문서함_1.문서발송(대량).pdf differ diff --git a/mens-batch/lib/ojdbc6.jar b/mens-batch/lib/ojdbc6.jar new file mode 100644 index 0000000..b663cd2 Binary files /dev/null and b/mens-batch/lib/ojdbc6.jar differ diff --git a/mens-batch/pom.xml b/mens-batch/pom.xml new file mode 100644 index 0000000..a041145 --- /dev/null +++ b/mens-batch/pom.xml @@ -0,0 +1,163 @@ + + + 4.0.0 + + kr.xit + mens-parent + 1.0.0 + + + mens-batch + 1.0.0 + jar + mens-batch + Mobile Electronic Notice System batch + + + + kr.xit + mens-core + ${project.version} + + + + org.springframework.boot + spring-boot-starter-validation + + + org.apache.tomcat.embed + tomcat-embed-core + + + + + org.springframework.boot + spring-boot-starter-batch + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.batch + spring-batch-test + test + + + io.projectreactor + reactor-test + test + + + org.springframework.retry + spring-retry + 1.2.5.RELEASE + + + + org.projectlombok + lombok + ${lombok.version} + true + + + + com.oracle + ojdbc6 + 11.2.0.3 + system + ${basedir}/lib/ojdbc6.jar + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-jta-atomikos + + + + + + + install + ${basedir}/target + ${project.name} + + + src/main/resources + + * + static/**/* + egovframework/**/* + config/application.yml + config/application-app.yml + config/application-ens.yml + config/application-jpa.yml + config/application-${env}.yml + + true + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.3.0 + + + jar-with-dependencies + + + + + false + + + + package + + single + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + kr.xit.EnsBatchApplication + + true + + + + + repackage + + + + + + + org.apache.maven.plugins + maven-source-plugin + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + diff --git a/mens-batch/src/main/java/kr/xit/EnsBatchApplication.java b/mens-batch/src/main/java/kr/xit/EnsBatchApplication.java new file mode 100644 index 0000000..1a8dd4d --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/EnsBatchApplication.java @@ -0,0 +1,89 @@ +package kr.xit; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import org.springframework.boot.Banner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.ApplicationPidFileWriter; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.boot.web.servlet.ServletComponentScan; +import org.springframework.context.annotation.ComponentScan; + +import kr.xit.core.spring.config.support.CustomBeanNameGenerator; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : ens API application main
+ *               ServletComponentScan
+ *               - 서블릿컴포넌트(필터, 서블릿, 리스너)를 스캔해서 빈으로 등록
+ *               - WebFilter, WebServlet, WebListener annotaion sacan
+ *               - SpringBoot의 내장톰캣을 사용하는 경우에만 동작
+ *               ConfigurationPropertiesScan
+ *               - ConfigurationProperties annotaion class scan 등록
+ *               - EnableConfigurationProperties 대체
+ * packageName : kr.xit
+ * fileName    : EnsBatchApplication
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ */ +@Slf4j +@SpringBootApplication +@ConfigurationPropertiesScan(basePackages = {"egovframework", "kr.xit"}) +@ServletComponentScan +@ComponentScan( + nameGenerator = CustomBeanNameGenerator.class, + basePackages = {"egovframework", "kr.xit"} +) +public class EnsBatchApplication { + static final List basePackages = new ArrayList<>( + Arrays.asList("egovframework", "kr.xit") + ); + + public static void main(String[] args) { + final String line = "===================================================================="; + log.info(line); + log.info("==== EnsBatchApplication start :: active profiles - {} ====", System.getProperty("spring.profiles.active")); + if(Objects.isNull(System.getProperty("spring.profiles.active"))) { + + log.error(">>>>>>>>>>>>>> Undefined start VM option <<<<<<<<<<<<<<"); + log.error(">>>>>>>>>>>>>> -Dspring.profiles.active=local|dev|prd <<<<<<<<<<<<<<"); + log.error("============== EnsBatchApplication start fail ==============="); + log.error(line); + System.exit(-1); + } + log.info(line); + + // beanName Generator 등록 : API v1, v2 등으로 분류하는 경우 + // Bean 이름 식별시 풀패키지 명으로 식별 하도록 함 + CustomBeanNameGenerator beanNameGenerator = new CustomBeanNameGenerator(); + beanNameGenerator.addBasePackages(basePackages); + + SpringApplicationBuilder applicationBuilder = new SpringApplicationBuilder(EnsBatchApplication.class); + applicationBuilder.beanNameGenerator(beanNameGenerator); + + SpringApplication application = applicationBuilder.build(); + application.setBannerMode(Banner.Mode.OFF); + application.setLogStartupInfo(false); + + //TODO : 이벤트 실행 시점이 Application context 실행 이전인 경우 리스너 추가 + //PID(Process ID 작성) + application.addListeners(new ApplicationPidFileWriter()) ; + application.run(args); + + log.info("========================================================================================="); + log.info("========== EnsBatchApplication load Complete :: active profiles - {} ==========", System.getProperty("spring.profiles.active")); + log.info("========================================================================================="); + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/job/EnsCctvAcceptJobConfg.java b/mens-batch/src/main/java/kr/xit/batch/ens/job/EnsCctvAcceptJobConfg.java new file mode 100644 index 0000000..004e5c0 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/job/EnsCctvAcceptJobConfg.java @@ -0,0 +1,113 @@ +package kr.xit.batch.ens.job; + +import kr.xit.batch.ens.task.EnsCctvAcceptTasklet; +import kr.xit.biz.ens.service.IEnsCctvFileService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.listener.NoWorkFoundStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *
+ * description : 전자 고지 CCTV accept
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : EnsCctvAcceptJobConfg
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class EnsCctvAcceptJobConfg { + private static final String JOB_NAME = "EnsCctvAcceptJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final IEnsCctvFileService service; + + @Bean(name = JOB_NAME) + public Job ensCctvAcceptJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + .listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + //.from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new EnsCctvAcceptTasklet(service)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/job/EnsCctvFileJobConfg.java b/mens-batch/src/main/java/kr/xit/batch/ens/job/EnsCctvFileJobConfg.java new file mode 100644 index 0000000..eb5e43b --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/job/EnsCctvFileJobConfg.java @@ -0,0 +1,113 @@ +package kr.xit.batch.ens.job; + +import kr.xit.batch.ens.task.EnsCctvFileTasklet; +import kr.xit.biz.ens.service.IEnsCctvFileService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.listener.NoWorkFoundStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *
+ * description : 전자 고지 CCTV 파일 처리(서광)
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : EnsCctvFileJobConfg
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class EnsCctvFileJobConfg { + private static final String JOB_NAME = "EnsCctvFileJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final IEnsCctvFileService service; + + @Bean(name = JOB_NAME) + public Job ensCctvFileJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + .listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + //.from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new EnsCctvFileTasklet(service)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/job/PniCctvAcceptJobConfg.java b/mens-batch/src/main/java/kr/xit/batch/ens/job/PniCctvAcceptJobConfg.java new file mode 100644 index 0000000..7170610 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/job/PniCctvAcceptJobConfg.java @@ -0,0 +1,113 @@ +package kr.xit.batch.ens.job; + +import kr.xit.batch.ens.task.PniCctvAcceptTasklet; +import kr.xit.biz.pni.service.IPniCctvFileService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.listener.NoWorkFoundStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *
+ * description : 사전고지 CCTV accept
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : PniCctvAcceptJobConfg
+ * author      : limju
+ * date        : 2023-07-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-11    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class PniCctvAcceptJobConfg { + private static final String JOB_NAME = "PniCctvAcceptJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final IPniCctvFileService service; + + @Bean(name = JOB_NAME) + public Job pniCctvAcceptJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + .listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + //.from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new PniCctvAcceptTasklet(service)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/job/PniCctvFileJobConfg.java b/mens-batch/src/main/java/kr/xit/batch/ens/job/PniCctvFileJobConfg.java new file mode 100644 index 0000000..db8e53e --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/job/PniCctvFileJobConfg.java @@ -0,0 +1,113 @@ +package kr.xit.batch.ens.job; + +import kr.xit.batch.ens.task.PniCctvFileTasklet; +import kr.xit.biz.pni.service.IPniCctvFileService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.listener.NoWorkFoundStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *
+ * description : 사전고지 CCTV 파일 처리(서광)
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : PniCctvFileJobConfg
+ * author      : limju
+ * date        : 2023-07-10
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-10    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class PniCctvFileJobConfg { + private static final String JOB_NAME = "PniCctvFileJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final IPniCctvFileService service; + + @Bean(name = JOB_NAME) + public Job pniCctvFileJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + .listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + //.from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new PniCctvFileTasklet(service)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/job/SndngAcceptJobConfig.java b/mens-batch/src/main/java/kr/xit/batch/ens/job/SndngAcceptJobConfig.java new file mode 100644 index 0000000..28e9eb6 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/job/SndngAcceptJobConfig.java @@ -0,0 +1,112 @@ +package kr.xit.batch.ens.job; + +import kr.xit.batch.ens.task.SndngAcceptTasklet; +import kr.xit.biz.ens.service.ISendMessageLinkService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *
+ * description : 문자 발송 시스템 연계 - 접수
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : SndngAcceptJobConfig
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class SndngAcceptJobConfig { + private static final String JOB_NAME = "SndngAcceptJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final ISendMessageLinkService linkService; + + @Bean(name = JOB_NAME) + public Job sndngAcceptJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + //.listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + //.from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new SndngAcceptTasklet(linkService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/job/SndngCloseJobConfig.java b/mens-batch/src/main/java/kr/xit/batch/ens/job/SndngCloseJobConfig.java new file mode 100644 index 0000000..7991a41 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/job/SndngCloseJobConfig.java @@ -0,0 +1,113 @@ +package kr.xit.batch.ens.job; + +import kr.xit.batch.ens.task.SndngCloseTasklet; +import kr.xit.biz.ens.service.ISendMessageLinkService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.listener.NoWorkFoundStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : SndngCloseJobConfig
+ * author      : seojh
+ * date        : 2023-06-14
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-14    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class SndngCloseJobConfig { + private static final String JOB_NAME = "SndngCloseJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final ISendMessageLinkService linkService; + + @Bean(name = JOB_NAME) + public Job sndngCloseJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + .listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + //.from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new SndngCloseTasklet(linkService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/job/SndngMakeJobConfig.java b/mens-batch/src/main/java/kr/xit/batch/ens/job/SndngMakeJobConfig.java new file mode 100644 index 0000000..c2014a2 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/job/SndngMakeJobConfig.java @@ -0,0 +1,113 @@ +package kr.xit.batch.ens.job; + +import kr.xit.batch.ens.task.SndngMakeTasklet; +import kr.xit.biz.ens.service.ISendMessageLinkService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.listener.NoWorkFoundStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *
+ * description : 문자 발송 시스템 연계 - 전송 대상 생성
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : SndngMakeJobConfig
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class SndngMakeJobConfig { + private static final String JOB_NAME = "SndngMakeJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final ISendMessageLinkService linkService; + + @Bean(name = JOB_NAME) + public Job sndngMakeJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + .listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + .from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new SndngMakeTasklet(linkService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/job/SndngSnedBulksJobConfig.java b/mens-batch/src/main/java/kr/xit/batch/ens/job/SndngSnedBulksJobConfig.java new file mode 100644 index 0000000..e84ee48 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/job/SndngSnedBulksJobConfig.java @@ -0,0 +1,113 @@ +package kr.xit.batch.ens.job; + +import kr.xit.batch.ens.task.SndngSendBulksTasklet; +import kr.xit.biz.ens.service.ISendMessageLinkService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.listener.NoWorkFoundStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *
+ * description : 문자 발송 시스템 연계 - 전송
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : SndngSnedBulksJobConfig
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class SndngSnedBulksJobConfig { + private static final String JOB_NAME = "SndngSendBulksJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final ISendMessageLinkService linkService; + + @Bean(name = JOB_NAME) + public Job sndngSendBulksJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + .listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + //.from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new SndngSendBulksTasklet(linkService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/job/SndngStatusBulksJobConfig.java b/mens-batch/src/main/java/kr/xit/batch/ens/job/SndngStatusBulksJobConfig.java new file mode 100644 index 0000000..9a09155 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/job/SndngStatusBulksJobConfig.java @@ -0,0 +1,113 @@ +package kr.xit.batch.ens.job; + +import kr.xit.batch.ens.task.SndngStatusBulksTasklet; +import kr.xit.biz.ens.service.ISendMessageLinkService; +import kr.xit.core.biz.batch.CustomRunIdIncrementer; +import kr.xit.core.biz.batch.listener.CustomJobListener; +import kr.xit.core.biz.batch.listener.CustomStepListener; +import kr.xit.core.biz.batch.listener.NoWorkFoundStepListener; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.batch.task.BatchEndTasklet; +import kr.xit.core.biz.batch.task.BatchFailEndTasklet; +import kr.xit.core.biz.batch.task.BatchStartTasklet; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *
+ * description : 문자 발송 시스템 연계 - 상태조회
+ *
+ * packageName : kr.xit.ens.support.batch.job
+ * fileName    : SndngStatusBulksJobConfig
+ * author      : limju
+ * date        : 2023-06-13
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-13    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Configuration +public class SndngStatusBulksJobConfig { + private static final String JOB_NAME = "SndngStatusBulksJob"; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobBuilderFactory jobBuilderFactory; + private final StepBuilderFactory stepBuilderFactory; + + private final IBatchCmmService lockService; + private final ISendMessageLinkService linkService; + + @Bean(name = JOB_NAME) + public Job sndngStatusBulksJob() { + return jobBuilderFactory.get(JOB_NAME) + .incrementer(new CustomRunIdIncrementer()) + .listener(new CustomJobListener()) + .listener(new CustomStepListener()) + .listener(new NoWorkFoundStepListener()) + + // JOB 시작 + // start() 결과가 FAILED 인경우 종료 + .start(start()) + .on("FAILED") + .end() + + // start()의 결과로 부터 FAILED를 제외한 모든 경우 to() 실행 + .from(start()) + .on("*") + .to(execution()) + .on("FAILED") + .to(fail()) + + .from(execution()) + // to() 실행 결과와 상관 없이 end() 실행 + .on("*") + .to(end()) + + // JOB 종료 + .end() + + .build(); + } + + @Bean(name = JOB_NAME + "_step") + public Step execution() { + return stepBuilderFactory.get(JOB_NAME + "_step") + .tasklet(new SndngStatusBulksTasklet(linkService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_start_step") + protected Step start() { + return stepBuilderFactory.get("Job_Locking") + .tasklet(new BatchStartTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_end_step") + protected Step end() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchEndTasklet(lockService)) + .build(); + } + + @JobScope + @Bean(name = JOB_NAME + "_fail_step") + protected Step fail() { + return stepBuilderFactory.get("Lock_Release") + .tasklet(new BatchFailEndTasklet(lockService)) + .build(); + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/rowmapper/LoggingRowMapper.java b/mens-batch/src/main/java/kr/xit/batch/ens/rowmapper/LoggingRowMapper.java new file mode 100644 index 0000000..9cf1578 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/rowmapper/LoggingRowMapper.java @@ -0,0 +1,31 @@ +package kr.xit.batch.ens.rowmapper; + + +import java.sql.ResultSet; +import java.sql.SQLException; +import kr.xit.core.biz.model.LoggingDTO; +import org.springframework.jdbc.core.RowMapper; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.batch.rowmapper
+ * fileName    : LoggingRowMapper
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ */ +public class LoggingRowMapper implements RowMapper { + @Override + public LoggingDTO mapRow(ResultSet rs, int rowNum) throws SQLException { + LoggingDTO dto = new LoggingDTO(); + dto.setRequestId(rs.getString("requestId")); + return dto; + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/EnsCctvAcceptJobScheduler.java b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/EnsCctvAcceptJobScheduler.java new file mode 100644 index 0000000..4445bae --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/EnsCctvAcceptJobScheduler.java @@ -0,0 +1,66 @@ +package kr.xit.batch.ens.scheduler; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import kr.xit.batch.ens.job.EnsCctvAcceptJobConfg; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + *
+ * description : 전자 고지 CCTV accept
+ *
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : EnsCctvAcceptJobScheduler
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class EnsCctvAcceptJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final EnsCctvAcceptJobConfg jobConfiguration; + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //FIXME : 전자 고지 CCTV 파일 처리(서광) 실행 간격 정의 + private final static int RUN_EXEC_MIS = 10000; + + //@Scheduled(initialDelay = RUN_EXEC_MIS, fixedDelay = RUN_EXEC_MIS) + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.ensCctvAcceptJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/EnsCctvFileJobScheduler.java b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/EnsCctvFileJobScheduler.java new file mode 100644 index 0000000..f869b53 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/EnsCctvFileJobScheduler.java @@ -0,0 +1,66 @@ +package kr.xit.batch.ens.scheduler; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import kr.xit.batch.ens.job.EnsCctvFileJobConfg; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + *
+ * description : 전자 고지 CCTV 파일 처리(서광)
+ *
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : EnsCctvFileJobScheduler
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class EnsCctvFileJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final EnsCctvFileJobConfg jobConfiguration; + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //FIXME : 전자 고지 CCTV 파일 처리(서광) 실행 간격 정의 + private final static int RUN_EXEC_MIS = 10000; + + //@Scheduled(initialDelay = RUN_EXEC_MIS, fixedDelay = RUN_EXEC_MIS) + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.ensCctvFileJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/PniCctvAcceptJobScheduler.java b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/PniCctvAcceptJobScheduler.java new file mode 100644 index 0000000..c7a46fb --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/PniCctvAcceptJobScheduler.java @@ -0,0 +1,66 @@ +package kr.xit.batch.ens.scheduler; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import kr.xit.batch.ens.job.PniCctvAcceptJobConfg; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + *
+ * description : 사전고지 CCTV accept
+ *
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : PniCctvAcceptJobScheduler
+ * author      : limju
+ * date        : 2023-07-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-11    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class PniCctvAcceptJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final PniCctvAcceptJobConfg jobConfiguration; + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //FIXME : 사전고지 CCTV 파일 처리(서광) 실행 간격 정의 + private final static int RUN_EXEC_MIS = 10000; + + //@Scheduled(initialDelay = RUN_EXEC_MIS, fixedDelay = RUN_EXEC_MIS) + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.pniCctvAcceptJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/PniCctvFileJobScheduler.java b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/PniCctvFileJobScheduler.java new file mode 100644 index 0000000..dfabdb3 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/PniCctvFileJobScheduler.java @@ -0,0 +1,66 @@ +package kr.xit.batch.ens.scheduler; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import kr.xit.batch.ens.job.PniCctvFileJobConfg; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + *
+ * description : 사전고지 CCTV 파일 처리(서광)
+ *
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : PniCctvFileJobScheduler
+ * author      : limju
+ * date        : 2023-07-10
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-10    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class PniCctvFileJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final PniCctvFileJobConfg jobConfiguration; + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //FIXME : 사전고지 CCTV 파일 처리(서광) 실행 간격 정의 + private final static int RUN_EXEC_MIS = 10000; + + //@Scheduled(initialDelay = RUN_EXEC_MIS, fixedDelay = RUN_EXEC_MIS) + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.pniCctvFileJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/SndngAcceptJobScheduler.java b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/SndngAcceptJobScheduler.java new file mode 100644 index 0000000..e8ef88e --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/SndngAcceptJobScheduler.java @@ -0,0 +1,69 @@ +package kr.xit.batch.ens.scheduler; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import kr.xit.batch.ens.job.SndngAcceptJobConfig; +import kr.xit.biz.common.ApiConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + *
+ * description : 연계 발송 - 접수 데이타 생성
+ *               대상 : 연계발송마스터(tb_cntc_sndng_mastr) : sndng_process_sttus = 'accept'
+ *                     연계발송상세(tb_cntc_sndng_detail)
+ *               ->  통합발송 마스터(tb_ens_unity_sndng_mastr)
+ *                   통합발송상세(tb_ens_unity_sndng_detail)
+ *                : 연계발송마스터 and tb_ens_unity_sndng_mastr - sndng_process_sttus = 'accept-ok'
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : SndngAcceptJobScheduler
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class SndngAcceptJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final SndngAcceptJobConfig jobConfiguration; + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //@Scheduled(cron = "${app.batch.cron.ens.accept}") + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + confMap.put("sndngProcessSttus", new JobParameter(ApiConstants.SndngProcessStatus.ACCEPT.getCode())); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.sndngAcceptJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/SndngCloseJobScheduler.java b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/SndngCloseJobScheduler.java new file mode 100644 index 0000000..e060420 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/SndngCloseJobScheduler.java @@ -0,0 +1,68 @@ +package kr.xit.batch.ens.scheduler; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import kr.xit.batch.ens.job.SndngCloseJobConfig; +import kr.xit.biz.common.ApiConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + *
+ * description :상태 close - send-ok
+ *              대상 : 연계 발송 마스터(tb_cntc_sndng_mastr)
+ *                ->  연계 발송 마스터(tb_cntc_sndng_mastr)
+ *                    통합 발송 마스터(tb_ens_unity_sndng_mastr)
+ *                    발송 마스터(tb_ens_sndng_mastr)
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : SndngCloseJobScheduler
+ * author      : seojh
+ * date        : 2023-06-14
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-14    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class SndngCloseJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final SndngCloseJobConfig jobConfiguration; + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //@Scheduled(cron = "${app.batch.cron.ens.close}") + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + confMap.put("sndngProcessSttus", new JobParameter(ApiConstants.SndngProcessStatus.SEND_OK.getCode())); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.sndngCloseJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/SndngMakeJobScheduler.java b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/SndngMakeJobScheduler.java new file mode 100644 index 0000000..c0e06b2 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/SndngMakeJobScheduler.java @@ -0,0 +1,68 @@ +package kr.xit.batch.ens.scheduler; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import kr.xit.batch.ens.job.SndngMakeJobConfig; +import kr.xit.biz.common.ApiConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + *
+ * description : 연계 발송 - 전송 대상 데이타 생성
+ *               대상 : 통합발송 마스터(tb_ens_unity_sndng_mastr)
+ *  *                  통합발송상세(tb_ens_unity_sndng_detail)
+ *               ->  발송 마스터(tb_ens_sndng_mastr)
+ *                   발송상세(tb_ens_kakao_my_doc)
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : SndngMakeJobScheduler
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class SndngMakeJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final SndngMakeJobConfig jobConfiguration; + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //@Scheduled(cron = "${app.batch.cron.ens.make}") + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + confMap.put("sndngProcessSttus", new JobParameter(ApiConstants.SndngProcessStatus.ACCETP_OK.getCode())); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.sndngMakeJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/SndngSendBulksJobScheduler.java b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/SndngSendBulksJobScheduler.java new file mode 100644 index 0000000..9ba94e8 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/SndngSendBulksJobScheduler.java @@ -0,0 +1,67 @@ +package kr.xit.batch.ens.scheduler; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import kr.xit.batch.ens.job.SndngSnedBulksJobConfig; +import kr.xit.biz.common.ApiConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + *
+ * description : 연계 발송 - 전송
+ *               대상 : 발송 마스터(tb_ens_sndng_mastr)
+ *                     발송상세(tb_ens_kakao_my_doc)
+ *                     연계발송결과(tb_cntc_sndng_result)
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : SndngSendBulksJobScheduler
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class SndngSendBulksJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final SndngSnedBulksJobConfig jobConfiguration; + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //@Scheduled(cron = "${app.batch.cron.ens.send}") + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + confMap.put("sndngProcessSttus", new JobParameter(ApiConstants.SndngProcessStatus.MAKE_OK.getCode())); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.sndngSendBulksJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/SndngStatusBulksJobScheduler.java b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/SndngStatusBulksJobScheduler.java new file mode 100644 index 0000000..effa8bd --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/scheduler/SndngStatusBulksJobScheduler.java @@ -0,0 +1,67 @@ +package kr.xit.batch.ens.scheduler; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import kr.xit.batch.ens.job.SndngStatusBulksJobConfig; +import kr.xit.biz.common.ApiConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + *
+ * description : 연계 발송 - 상태조회
+ *               대상 : 연계발송결과(tb_cntc_sndng_result)
+ *                     발송상세(tb_ens_kakao_my_doc)
+ * packageName : kr.xit.ens.support.batch.scheduler
+ * fileName    : SndngStatusBulksJobScheduler
+ * author      : limju
+ * date        : 2023-06-13
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-13    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Component +public class SndngStatusBulksJobScheduler { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final SndngStatusBulksJobConfig jobConfiguration; + + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + //@Scheduled(cron = "${app.batch.cron.ens.kko-status}") + public void runJob() { + + Map confMap = new HashMap<>(); + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + confMap.put("sndngProcessSttus", new JobParameter(ApiConstants.SndngProcessStatus.SEND_OK.getCode())); + JobParameters jobParameters = new JobParameters(confMap); + + try { + jobLauncher.run(jobConfiguration.sndngStatusBulksJob(), jobParameters); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/task/EnsCctvAcceptTasklet.java b/mens-batch/src/main/java/kr/xit/batch/ens/task/EnsCctvAcceptTasklet.java new file mode 100644 index 0000000..49cf8d8 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/task/EnsCctvAcceptTasklet.java @@ -0,0 +1,61 @@ +package kr.xit.batch.ens.task; + +import kr.xit.batch.ens.task.cmm.TaskCmmUtils; +import kr.xit.biz.ens.service.IEnsCctvFileService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +/** + *
+ * description : 전자 고지 CCTV accept
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : EnsCctvAcceptTasklet
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +public class EnsCctvAcceptTasklet implements Tasklet { + private final IEnsCctvFileService service; + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + service.acceptEnsNtnccntcSndng(); + }catch(Exception e){ + log.error("EnsCctvAcceptTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + TaskCmmUtils.taskEnsMessageLinkServiceUpdateErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/task/EnsCctvFileTasklet.java b/mens-batch/src/main/java/kr/xit/batch/ens/task/EnsCctvFileTasklet.java new file mode 100644 index 0000000..ed69781 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/task/EnsCctvFileTasklet.java @@ -0,0 +1,55 @@ +package kr.xit.batch.ens.task; + +import kr.xit.batch.ens.task.cmm.TaskCmmUtils; +import kr.xit.biz.ens.service.IEnsCctvFileService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +/** + *
+ * description : 전자 고지 CCTV 파일 처리(서광)
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : EnsCctvFileTasklet
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +public class EnsCctvFileTasklet implements Tasklet { + private final IEnsCctvFileService service; + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + service.createCctvFileOfSg(); + }catch(Exception e){ + log.error("EnsCctvFileTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/task/PniCctvAcceptTasklet.java b/mens-batch/src/main/java/kr/xit/batch/ens/task/PniCctvAcceptTasklet.java new file mode 100644 index 0000000..28021ba --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/task/PniCctvAcceptTasklet.java @@ -0,0 +1,61 @@ +package kr.xit.batch.ens.task; + +import kr.xit.batch.ens.task.cmm.TaskCmmUtils; +import kr.xit.biz.pni.service.IPniCctvFileService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +/** + *
+ * description : 사전고지 CCTV accept
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : PniCctvAcceptTasklet
+ * author      : limju
+ * date        : 2023-07-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-11    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +public class PniCctvAcceptTasklet implements Tasklet { + private final IPniCctvFileService service; + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + service.acceptPniNtnccntcSndng(); + }catch(Exception e){ + log.error("PniCctvAcceptTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + TaskCmmUtils.taskEnsMessageLinkServiceUpdateErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/task/PniCctvFileTasklet.java b/mens-batch/src/main/java/kr/xit/batch/ens/task/PniCctvFileTasklet.java new file mode 100644 index 0000000..1f2028d --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/task/PniCctvFileTasklet.java @@ -0,0 +1,55 @@ +package kr.xit.batch.ens.task; + +import kr.xit.batch.ens.task.cmm.TaskCmmUtils; +import kr.xit.biz.pni.service.IPniCctvFileService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +/** + *
+ * description : 사전고지 CCTV 파일 처리(서광)
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : PniCctvFileTasklet
+ * author      : limju
+ * date        : 2023-07-10
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-10    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +public class PniCctvFileTasklet implements Tasklet { + private final IPniCctvFileService service; + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + service.createCctvFileOfSg(); + }catch(Exception e){ + log.error("PniCctvFileTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/task/SndngAcceptTasklet.java b/mens-batch/src/main/java/kr/xit/batch/ens/task/SndngAcceptTasklet.java new file mode 100644 index 0000000..0f7faf4 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/task/SndngAcceptTasklet.java @@ -0,0 +1,61 @@ +package kr.xit.batch.ens.task; + +import kr.xit.batch.ens.task.cmm.TaskCmmUtils; +import kr.xit.biz.ens.service.ISendMessageLinkService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : SndngAcceptTasklet
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +public class SndngAcceptTasklet implements Tasklet { + private final ISendMessageLinkService service; + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + String sndngProcessSttus = contribution.getStepExecution().getJobParameters().getString("sndngProcessSttus"); + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + service.accept(sndngProcessSttus); + }catch(Exception e){ + log.error("SndngAcceptTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + TaskCmmUtils.taskEnsMessageLinkServiceUpdateErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/task/SndngCloseTasklet.java b/mens-batch/src/main/java/kr/xit/batch/ens/task/SndngCloseTasklet.java new file mode 100644 index 0000000..843358b --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/task/SndngCloseTasklet.java @@ -0,0 +1,58 @@ +package kr.xit.batch.ens.task; + +import kr.xit.batch.ens.task.cmm.TaskCmmUtils; +import kr.xit.biz.ens.service.ISendMessageLinkService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : SndngCloseTasklet
+ * author      : seojh
+ * date        : 2023-06-14
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-14    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +public class SndngCloseTasklet implements Tasklet { + private final ISendMessageLinkService service; + + public SndngCloseTasklet(ISendMessageLinkService service) { + this.service = service; + } + + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + String sndngProcessSttus = contribution.getStepExecution().getJobParameters().getString("sndngProcessSttus"); + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + service.close(sndngProcessSttus); + }catch(Exception e){ + log.error("SndngCloseTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/task/SndngMakeTasklet.java b/mens-batch/src/main/java/kr/xit/batch/ens/task/SndngMakeTasklet.java new file mode 100644 index 0000000..a27021c --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/task/SndngMakeTasklet.java @@ -0,0 +1,61 @@ +package kr.xit.batch.ens.task; + +import kr.xit.batch.ens.task.cmm.TaskCmmUtils; +import kr.xit.biz.ens.service.ISendMessageLinkService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : SndngMakeTasklet
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +public class SndngMakeTasklet implements Tasklet { + private final ISendMessageLinkService service; + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + String sndngProcessSttus = contribution.getStepExecution().getJobParameters().getString("sndngProcessSttus"); + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + service.make(sndngProcessSttus); + }catch(Exception e){ + log.error("SndngMakeTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + TaskCmmUtils.taskEnsMessageLinkServiceUpdateErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/task/SndngSendBulksTasklet.java b/mens-batch/src/main/java/kr/xit/batch/ens/task/SndngSendBulksTasklet.java new file mode 100644 index 0000000..17c2a87 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/task/SndngSendBulksTasklet.java @@ -0,0 +1,67 @@ +package kr.xit.batch.ens.task; + +import java.util.UUID; +import kr.xit.batch.ens.task.cmm.TaskCmmUtils; +import kr.xit.biz.ens.service.ISendMessageLinkService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : SndngSendBulksTasklet
+ * author      : limju
+ * date        : 2023-06-12
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-12    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +public class SndngSendBulksTasklet implements Tasklet { + private final ISendMessageLinkService service; + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + String sndngProcessSttus = contribution.getStepExecution().getJobParameters().getString("sndngProcessSttus"); + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + MDC.put("request_trace_batch_id", UUID.randomUUID().toString().replace("-", "")); + MDC.put("uri", "/v1/documents/bulk"); + MDC.put("method", "POST"); + + service.sendBulks(sndngProcessSttus); + }catch(Exception e){ + log.error("SndngSendBulksTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + + TaskCmmUtils.taskEnsMessageLinkServiceUpdateErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/task/SndngStatusBulksTasklet.java b/mens-batch/src/main/java/kr/xit/batch/ens/task/SndngStatusBulksTasklet.java new file mode 100644 index 0000000..b01423d --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/task/SndngStatusBulksTasklet.java @@ -0,0 +1,61 @@ +package kr.xit.batch.ens.task; + +import java.util.UUID; +import kr.xit.batch.ens.task.cmm.TaskCmmUtils; +import kr.xit.biz.ens.service.ISendMessageLinkService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.batch.task
+ * fileName    : SndngStatusBulksTasklet
+ * author      : limju
+ * date        : 2023-06-13
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-13    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +public class SndngStatusBulksTasklet implements Tasklet { + private final ISendMessageLinkService service; + @Override + @JobScope + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + final String sndngProcessSttus = contribution.getStepExecution().getJobParameters().getString("sndngProcessSttus"); + final String isSlackEnabled = contribution.getStepExecution().getJobParameters().getString("isSlackEnabled"); + + try { + MDC.put("request_trace_batch_id", UUID.randomUUID().toString().replace("-", "")); + MDC.put("uri", "/v1/documents/bulk/status"); + MDC.put("method", "POST"); + + service.findKkoMyDocStatusBulks(sndngProcessSttus); + }catch(Exception e){ + log.error("SndngStatusBulksTasklet error :: {}", e.getMessage()); + + TaskCmmUtils.taskBatchErrorLog( + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + e.getMessage(), + isSlackEnabled + ); + contribution.setExitStatus(ExitStatus.FAILED); + return RepeatStatus.FINISHED; + } + contribution.setExitStatus(ExitStatus.COMPLETED); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/task/cmm/TaskCmmUtils.java b/mens-batch/src/main/java/kr/xit/batch/ens/task/cmm/TaskCmmUtils.java new file mode 100644 index 0000000..036287a --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/task/cmm/TaskCmmUtils.java @@ -0,0 +1,83 @@ +package kr.xit.batch.ens.task.cmm; + +import static egovframework.com.cmm.util.EgovStringUtil.cutString; + +import kr.xit.biz.common.ApiConstants; +import kr.xit.biz.ens.model.EnsDTO; +import kr.xit.core.biz.batch.model.BatchCmmDTO; +import kr.xit.core.spring.util.ApiSpringUtils; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.slf4j.MDC; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.ens.support.batch.task.cmm
+ * fileName    : TaskCmmUtils
+ * author      : limju
+ * date        : 2023-06-19
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-19    limju       최초 생성
+ *
+ * 
+ */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TaskCmmUtils { + public static void taskBatchErrorLog(final String instanceId, final String errorMsg, final String isSlackEnabled){ + ApiSpringUtils.getBatchCmmService().modifyBatchLog( + BatchCmmDTO + .builder() + .batchLogId(MDC.get("batch_log_id")) + .instanceId(instanceId) + .traceId(MDC.get("request_trace_id")) + .message(errorMsg) + .build() + ); + if(Boolean.parseBoolean(isSlackEnabled)) { + ApiSpringUtils.getSlackWebhookPush().sendSlackAlertLog( + errorMsg, + MDC.get("uri"), + "batch call"); + } + } + + /** + * 배치 실행시 발생한 에러 처리 + *

업무단에서 발생한 에러 처리를 위한 메소드 + * @param instanceId String 배치인스턴스 + * @param errorMsg String 에러 메세지 + * @param isSlackEnabled boolean 슬랙 push 여부 + */ + public static void taskEnsMessageLinkServiceUpdateErrorLog(final String instanceId, final String errorMsg, final String isSlackEnabled){ + final String UNITY_SNDNG_MASTR_ID = "unitySndngMastrId"; + + String unitySndngMastrId = null; + String sndngProcessSttus = null; + String sndngMastrId = null; + + if("SndngAcceptJob".equals(instanceId) || "PniCctvAcceptJob".equals(instanceId)){ + unitySndngMastrId = MDC.get(UNITY_SNDNG_MASTR_ID); + sndngProcessSttus = ApiConstants.SndngProcessStatus.ACCETP_FAIL.getCode(); + } else if ("SndngMakeJob".equals(instanceId)) { + unitySndngMastrId = MDC.get(UNITY_SNDNG_MASTR_ID); + sndngProcessSttus = MDC.get("sndngProcessSttus"); + } else if ("SndngSendBulksJob".equals(instanceId)) { + unitySndngMastrId = MDC.get(UNITY_SNDNG_MASTR_ID); + sndngMastrId = MDC.get("sndngMastrId"); + sndngProcessSttus = MDC.get("sndngProcessSttus"); + } + ApiSpringUtils.getSendMessageLinkService().updateErrorLog( + EnsDTO.SndngMssageParam.builder() + .unitySndngMastrId(unitySndngMastrId) + .sndngMastrId(sndngMastrId) + .newSndngProcessSttus(sndngProcessSttus) + .errorCode(instanceId) + .errorMssage(cutString(errorMsg, 300)) + .build() + ); + } +} diff --git a/mens-batch/src/main/java/kr/xit/batch/ens/web/KkopayEltrcDocBatchController.java b/mens-batch/src/main/java/kr/xit/batch/ens/web/KkopayEltrcDocBatchController.java new file mode 100644 index 0000000..a4d4478 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/batch/ens/web/KkopayEltrcDocBatchController.java @@ -0,0 +1,227 @@ +package kr.xit.batch.ens.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import kr.xit.batch.ens.job.PniCctvAcceptJobConfg; +import kr.xit.batch.ens.job.PniCctvFileJobConfg; +import kr.xit.batch.ens.job.SndngAcceptJobConfig; +import kr.xit.batch.ens.job.SndngCloseJobConfig; +import kr.xit.batch.ens.job.SndngMakeJobConfig; +import kr.xit.batch.ens.job.SndngSnedBulksJobConfig; +import kr.xit.batch.ens.job.SndngStatusBulksJobConfig; +import kr.xit.biz.common.ApiConstants; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.core.support.utils.Checks; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * description : 카카오페이 전자문서 발송요청 배치 컨트롤러
+ *
+ * packageName : kr.xit.ens.support.batch.web
+ * fileName    : KkopayEltrcDocBatchController
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ */ +@Tag(name = "KkopayEltrcDocBatchController", description = "전자고지 통합발송 연계 배치 WEB") +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping(value = "/batch/v1") +public class KkopayEltrcDocBatchController { + @Value("${app.slack-webhook.enabled:false}") + private String isSlackEnabled; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + private final JobLauncher jobLauncher; + private final SndngAcceptJobConfig acceptJobConfig; + private final SndngMakeJobConfig makeJobConfig; + private final SndngSnedBulksJobConfig sendBulksJobConfig; + private final SndngStatusBulksJobConfig statusJobConfig; + private final SndngCloseJobConfig closeJobConfig; + private final PniCctvFileJobConfg pniCctvFileJobConfg; + private final PniCctvAcceptJobConfg pniCctvAcceptJobConfg; + + @Operation(summary = "accept", description = "accept") + @GetMapping(value = "/accept", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO accept() { + try { + JobExecution jobExecution = jobLauncher.run(acceptJobConfig.sndngAcceptJob(), getJobParameters( + ApiConstants.SndngProcessStatus.ACCEPT.getCode())); + + while(jobExecution.isRunning()){ + log.info("..."); + } + printLog(jobExecution); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } + return ApiResponseDTO.success(HttpStatus.OK); + } + + @Operation(summary = "make", description = "make") + @GetMapping(value = "/make", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO make() { + + try { + JobExecution jobExecution = jobLauncher.run(makeJobConfig.sndngMakeJob(), getJobParameters(ApiConstants.SndngProcessStatus.ACCETP_OK.getCode())); + + while(jobExecution.isRunning()){ + log.info("..."); + } + printLog(jobExecution); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } + return ApiResponseDTO.success(HttpStatus.OK); + } + + @Operation(summary = "close", description = "close") + @GetMapping(value = "/close", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO close() { + + try { + JobExecution jobExecution = jobLauncher.run(closeJobConfig.sndngCloseJob(), getJobParameters(ApiConstants.SndngProcessStatus.SEND_OK.getCode())); + + while(jobExecution.isRunning()){ + log.info("..."); + } + printLog(jobExecution); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } + return ApiResponseDTO.success(HttpStatus.OK); + } + + @Operation(summary = "sendBulks", description = "sendBulks") + @GetMapping(value = "/sendBulks", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO sendBulks() { + + try { + JobExecution jobExecution = jobLauncher.run(sendBulksJobConfig.sndngSendBulksJob(), getJobParameters(ApiConstants.SndngProcessStatus.MAKE_OK.getCode())); + + while(jobExecution.isRunning()){ + log.info("..."); + } + printLog(jobExecution); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } + return ApiResponseDTO.success(HttpStatus.OK); + } + + @Operation(summary = "statusBulks", description = "statusBulks") + @GetMapping(value = "/statusBulks", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO statusBulks() { + + try { + JobExecution jobExecution = jobLauncher.run(statusJobConfig.sndngStatusBulksJob(), getJobParameters(ApiConstants.SndngProcessStatus.SEND_OK.getCode())); + + while(jobExecution.isRunning()){ + log.info("..."); + } + printLog(jobExecution); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } + return ApiResponseDTO.success(HttpStatus.OK); + } + + @Operation(summary = "pniCctvFileService", description = "CCTV 사전알림(서광)") + @GetMapping(value = "/pniCctvFileService", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO pniCctvFileService() { + + try { + JobExecution jobExecution = jobLauncher.run(pniCctvFileJobConfg.pniCctvFileJob(), getJobParameters(null)); + + while(jobExecution.isRunning()){ + log.info("..."); + } + printLog(jobExecution); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } + return ApiResponseDTO.success(HttpStatus.OK); + } + + @Operation(summary = "pniCctvAccept", description = "CCTV 단속 사전알림 accept") + @GetMapping(value = "/pniCctvAccept", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO pniCctvAccept() { + + try { + JobExecution jobExecution = jobLauncher.run(pniCctvAcceptJobConfg.pniCctvAcceptJob(), getJobParameters(null)); + + while(jobExecution.isRunning()){ + log.info("..."); + } + printLog(jobExecution); + + } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException + | JobParametersInvalidException | org.springframework.batch.core.repository.JobRestartException e) { + + log.error(e.getMessage()); + } + return ApiResponseDTO.success(HttpStatus.OK); + } + + private JobParameters getJobParameters(final String processStatus){ + Map confMap = new HashMap<>(); + + confMap.put("startDate", new JobParameter(new Date())); + confMap.put("isSlackEnabled", new JobParameter(isSlackEnabled)); + if(Checks.isNotEmpty(processStatus)) confMap.put("sndngProcessSttus", new JobParameter(processStatus)); + return new JobParameters(confMap); + } + + private static void printLog(JobExecution jobExecution) { + log.info("Job Execution: " + jobExecution.getStatus()); + log.info("Job getJobConfigurationName: " + jobExecution.getJobConfigurationName()); + log.info("Job getJobId: " + jobExecution.getJobId()); + log.info("Job getExitStatus: " + jobExecution.getExitStatus()); + log.info("Job getJobInstance: " + jobExecution.getJobInstance()); + log.info("Job getStepExecutions: " + jobExecution.getStepExecutions()); + log.info("Job getLastUpdated: " + jobExecution.getLastUpdated()); + log.info("Job getFailureExceptions: " + jobExecution.getFailureExceptions()); + } +} diff --git a/mens-batch/src/main/java/kr/xit/biz/ens/mapper/IEnsCctvFileMapper.java b/mens-batch/src/main/java/kr/xit/biz/ens/mapper/IEnsCctvFileMapper.java new file mode 100644 index 0000000..3680964 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/ens/mapper/IEnsCctvFileMapper.java @@ -0,0 +1,32 @@ +package kr.xit.biz.ens.mapper; + +import kr.xit.biz.ens.model.EnsDTO; +import org.egovframe.rte.psl.dataaccess.mapper.Mapper; + +import java.util.List; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.ens.mapper
+ * fileName    : IEnsCctvFileMapper
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +@Mapper +public interface IEnsCctvFileMapper { + int selectLicense(String jobSeCode); + int insertNtcnCntcData(EnsDTO.NtcnCntcData dto); + + List selectEnsNtncCntcSndngs(); + int insertCntcSndngMst(EnsDTO.EnsNtncCntcSndngTgts dto); + int insertCntcSndngDtl(EnsDTO.EnsNtncCntcSndngTgts dto); + int updateEnsNtcnCntcData(EnsDTO.EnsNtncCntcSndngTgts dto); +} diff --git a/mens-batch/src/main/java/kr/xit/biz/ens/mapper/ISendMessageLinkMapper.java b/mens-batch/src/main/java/kr/xit/biz/ens/mapper/ISendMessageLinkMapper.java new file mode 100644 index 0000000..e934cd0 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/ens/mapper/ISendMessageLinkMapper.java @@ -0,0 +1,254 @@ +package kr.xit.biz.ens.mapper; + +import java.util.List; +import java.util.Optional; + +import org.egovframe.rte.psl.dataaccess.mapper.Mapper; + +import kr.xit.biz.ens.model.CntcDTO; +import kr.xit.biz.ens.model.EnsDTO; +import kr.xit.biz.ens.model.kakao.KkopayDocAttrDTO; +import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO; +import kr.xit.biz.ens.model.kakao.KkopayDocDTO; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.ens.mapper
+ * fileName    : ISendMessageLinkMapper
+ * author      : limju
+ * date        : 2023-06-05
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-05    limju       최초 생성
+ *
+ * 
+ */ +@Mapper +public interface ISendMessageLinkMapper { + + //---------------------------------------------------------------------- + // accept + //---------------------------------------------------------------------- + /** + * 접수대상 조회 + * @param t + * @return List + */ + List selectAcceptTgts(final T t); + /** + * 접수대상 조회 + * @param t + * @return List + */ + List selectAcceptVali(final T t); + + /** + * 통합발송마스터 생성 + * @param t status + * @return int + */ + int insertUnitySndngMst(final T t); + + /** + * 통합발송상세 생성 + * @param t status + * @return int + */ + int insertUnitySndngDtls(final T t); + + /** + * 연계발송마스터 상태 변경 + * @param t status, newStatus + * @return int + */ + int updateProcessSttusCntcSndngMst(final T t); + //---------------------------------------------------------------------- + // accept + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // make + //---------------------------------------------------------------------- + /** + * 생성 대상 조회 + * @param t + * @return List + */ + List selectMakeTgts(final T t); + + /** + * 2차 발송 대상 건수 조회 + * @param t + * @return int + */ + int selectSendOkTgts(final T t); + + /** + * 발송마스터 생성 + * @param t status + * @return int + */ + int insertSndngMst(final T t); + + /** + * 카카오페이 내문서함 생성 + * @param t + * @return int + */ + int insertKakaoMyDocs(final T t); + + /** + * 모바일페이지 관리 생성 + * @param t + * @return int + */ + int insertMobilePageManage(final T t); + + /** + * 문자발송 데이터 생성 + * @param t + * @return int + */ + int insertSmsSndng(final T t); + + /** + * 우편발송 데이터 생성 + * @param t + * @return int + */ + int insertPostSndng(final T t); + + /** + * 통합발송마스터 상태 변경 + * @param t status, newStatus + * @return int + */ + int updateProcessSttusUnitySndngMst(final T t); + //---------------------------------------------------------------------- + // make + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // close + //---------------------------------------------------------------------- + List selectCloseTgts(final String sndngPprocessSttus); + //---------------------------------------------------------------------- + // close + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // send + //---------------------------------------------------------------------- + /** + * 통합발송 대상 조회 + * @param t + * @return List + */ + List selectSendBulkTgts(final T t); + + /** + * 카카오페이 문서요청 대상 목록 조회 + * @param t status + * @return List + */ + List selectKakaoSendTgts(final T t); + + /** + * E-GREEN 우편발송 대상 목록 조회 + * @param t status + * @return List + */ + List selectPostTgts(final T t); + + /** + * SMS 발송 대상 목록 조회 + * @param t status + * @return List + */ + List selectSmsSendTgts(final T t); + + + /** + * 발송상태 조회 : 발송후 발송 연계 마스터의 발송상태 변경값 조회 + * @param t + * @return EnsDTO.SndngMssageParam + */ + Optional selectSndProcessStatus(final T t); + /** + * 카카오페이 문서요청 결과 반영 + * @param t 문서ID, 에러코드, 에러메세지, 외부문서ID + * @return int + */ + int updateKakaoSendBulksResult(final T t); + + + /** + * 발송마스터 상태 변경 + * @param t status, newStatus + * @return int + */ + int updateProcessSttusSndngMst(final T t); + //---------------------------------------------------------------------- + // send + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // status + //---------------------------------------------------------------------- + + /** + * 발송마스터 상태 다건 변경 + * @param t status, newStatus + * @return int + */ + int updateProcessSttusBulkSndngMst(final T t); + //---------------------------------------------------------------------- + // send + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // status + //---------------------------------------------------------------------- + + /** + * 카카오 문서 상태 조회 대상 목록 조회 + * @param sndngPprocessSttus + * @return List + */ + List selectKakaoStatusTgts(final String sndngPprocessSttus); + + /** + * 카카오 문서 상태 조회 결과 반영 + * @param dto EnsDTO.KakaoMyDoc + * @return int + */ + //int updateKakaoStatusInfo(final EnsDTO.KakaoMyDoc dto); + int updateKakaoStatusInfo(final KkopayDocBulkDTO.BulkStatus dto); + //---------------------------------------------------------------------- + // status + //---------------------------------------------------------------------- + + + + //---------------------------------------------------------------------- + // result + //---------------------------------------------------------------------- + /** + *
+     * 연계발송결과 반영
+     * @param dto 발송구분코드, 발송결과 상태, 송신(요청)/수신(조회)/최초열람 일시, 에러내용
+     * @return int
+     * 
+ */ + int insertCntcSndngResult(final CntcDTO.SndngResult dto); + int updateCntcSndngResult(final CntcDTO.SndngResult dto); + //---------------------------------------------------------------------- + // result + //---------------------------------------------------------------------- + + Optional selectTmplat(final String tmplatId); + EnsDTO.MobilePageManage selectMobilePage(final KkopayDocDTO.OneTimeToken dto); +} diff --git a/mens-batch/src/main/java/kr/xit/biz/ens/model/CntcDTO.java b/mens-batch/src/main/java/kr/xit/biz/ens/model/CntcDTO.java new file mode 100644 index 0000000..b7907c6 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/ens/model/CntcDTO.java @@ -0,0 +1,197 @@ +package kr.xit.biz.ens.model; + +import java.io.Serializable; + +import kr.xit.core.biz.model.AuditFields; +import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO; +import lombok.*; +import lombok.experimental.SuperBuilder; + +/** + *
+ * description : tb_cntc_ Entity DTO
+ *
+ * packageName : kr.xit.biz.ens.model
+ * fileName    : CntcDTO
+ * author      : limju
+ * date        : 2023-06-05
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-05    limju       최초 생성
+ *
+ * 
+ */ +public class CntcDTO { + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = false) + public static class SndngMst extends AuditFields implements Serializable { + /** + * 통합 발송 마스터 id + */ + private String unitySndngMastrId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 템플릿 ID + */ + private String tmplatId; + /** + * 발송 유형 코드 + */ + private String sndngTyCode; + /** + * 발송 건수 + */ + private int sndngCo; + /** + * 발송 처리 상태 + */ + private String sndngProcessSttus; + /** + * 발송 일시 + */ + private String sndngDt; + /** + * 마감 일시 + */ + private String closDt; + /** + * 에러 코드 + */ + private String errorCode; + /** + * 에러 메시지 + */ + private String errorMssage; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = false) + public static class SndngDtl extends AuditFields implements Serializable { + + /** + * 통합 발송 상세 id + */ + private String unitySndngDetailId; + /** + * 통합 발송 마스터 id + */ + private String unitySndngMastrId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 메인 코드 + */ + private String mainCode; + /** + * 주민등록번호 + */ + private String ihidnum; + /** + * 핸드폰 번호 + */ + private String moblphonNo; + /** + * 성명 + */ + private String nm; + /** + * 주소 + */ + private String adres; + /** + * 상세 주소 + */ + private String detailAdres; + /** + * 우편번호 + */ + private String zip; + /** + * 템플릿 메시지 데이터 + */ + private String tmpltMsgData; + /** + * 모바일 페이지 내용 + */ + private String mobilePageCn; + /** + * 이용 기관 식별 ID + */ + private String useInsttIdntfcId; + /** + * 외부 문서 식별 번호 + */ + private String externalDocumentUuid; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = false) + public static class SndngResult extends AuditFields implements Serializable { + + /** + * 통합 발송 상세 id + */ + private String unitySndngDetailId; + /** + * 발송 구분 코드 + */ + private String sndngSeCode; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 발송 결과 상태 + */ + private String sndngResultSttus; + /** + * 요청 일시 + */ + private String requstDt; + /** + * 조회 일시 + */ + private String inqireDt; + /** + * 열람 일시 + */ + private String readngDt; + /** + * 오류 내용 + */ + private String errorCn; + + /** + * documentBinderUuid + */ + private String documentBinderUuid; + } +} diff --git a/mens-batch/src/main/java/kr/xit/biz/ens/model/EnsDTO.java b/mens-batch/src/main/java/kr/xit/biz/ens/model/EnsDTO.java new file mode 100644 index 0000000..8d753f4 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/ens/model/EnsDTO.java @@ -0,0 +1,969 @@ +package kr.xit.biz.ens.model; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; + +import io.swagger.v3.oas.annotations.media.Schema; +import kr.xit.biz.common.ApiConstants; +import kr.xit.core.biz.model.AuditFields; +import lombok.*; +import lombok.experimental.SuperBuilder; + +import javax.validation.constraints.Digits; +import javax.validation.constraints.Size; + +/** + *
+ * description : tb_ens_ Entity DTO
+ *
+ * packageName : kr.xit.biz.ens.model
+ * fileName    : EnsDTO
+ * author      : limju
+ * date        : 2023-06-05
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-05    limju       최초 생성
+ *
+ * 
+ */ +public class EnsDTO { + + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class SndngMssageParam implements Serializable { + + /** + * 통합 발송 마스터 id + */ + private String unitySndngMastrId; + /** + * 통합 발송 마스터 id + */ + private String unitySndngDetailId; + /** + * 발송 마스터 id + */ + private String sndngMastrId; + + /** + * 발송상세ID + */ + private String sndngDetailId; + + /** + * 템플릿ID + */ + private String tmplatId; + + /** + * 발송 건수 + */ + private int sndngCo; + /** + * 발송 처리 상태 + */ + private String sndngProcessSttus; + + /** + * 발송 처리 상태 + */ + private String newSndngProcessSttus; + + private String try1; + private String try2; + private String try3; + private int tryCnt; + private int trySeq; + private String sndngSeCode; + private String sndngDt; + private String sndngDt2; + private String sndngDt3; + private String try2Minute; + private String try3Minute; + private String errorMssage; + private String errorCode; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = true) + public static class SendKakaoTgt extends EnsDTO.KakaoMyDoc implements Serializable { + + /** + * 발송 마스터 id + */ + private String sndngMastrId; + + /** + * 통합 발송 마스터 id + */ + private String unitySndngMastrId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 템플릿 ID + */ + private String tmplatId; + /** + * 발송 유형 코드 + */ + private String sndngTyCode; + /** + * 발송 건수 + */ + private int sndngCo; + /** + * 발송 처리 상태 + */ + //private String sndngProcessSttus; + /** + * 마감 일시 + */ + private long closDt; + } + + + @Data + @NoArgsConstructor + @AllArgsConstructor + @ToString + @SuperBuilder + public static class UnitySndngMst implements Serializable { + + /** + * 통합 발송 마스터 id + */ + private String unitySndngMastrId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 템플릿 ID + */ + private String tmplatId; + /** + * 발송 유형 코드 + */ + private String sndngTyCode; + /** + * 발송 건수 + */ + private int sndngCo; + /** + * 발송 처리 상태 + */ + private String sndngProcessSttus; + /** + * 발송 일시 + */ + private String sndngDt1; + /** + * 발송 일시 2 + */ + private String sndngDt2; + /** + * 발송 일시 3 + */ + private String sndngDt3; + /** + * try1 + */ + private String try1; + /** + * try2 + */ + private String try2; + /** + * try3 + */ + private String try3; + /** + * try_cnt + */ + private int tryCnt; + /** + * 마감 일시 + */ + private String closDt; + /** + * 에러 코드 + */ + private String errorCode; + /** + * 에러 메시지 + */ + private String errorMssage; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = false) + public static class UnitySndngDtl extends AuditFields implements Serializable { + + /** + * 통합 발송 상세 id + */ + private String unitySndngDetailId; + /** + * 통합 발송 마스터 id + */ + private String unitySndngMastrId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 메인 코드 + */ + private String mainCode; + /** + * 차량번호 + */ + private String vhcleNo; + /** + * 주민등록번호 + */ + private String ihidnum; + /** + * 핸드폰 번호 + */ + private String moblphonNo; + /** + * 성명 + */ + private String nm; + /** + * 주소 + */ + private String adres; + /** + * 상세 주소 + */ + private String detailAdres; + /** + * 우편번호 + */ + private String zip; + /** + * 템플릿 메시지 데이터 + */ + private String tmpltMsgData; + /** + * 모바일 페이지 내용 + */ + private String mobilePageCn; + /** + * 이용 기관 식별 ID + */ + private String useInsttIdntfcId; + /** + * 외부 문서 식별 번호 + */ + private String externalDocumentUuid; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = false) + public static class TmplatManage extends AuditFields implements Serializable { + + /** + * 템플릿 ID + */ + private String tmplatId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 발송 유형 코드 + */ + private String sndngTyCode; + /** + * 템플릿 명 + */ + private String tmplatNm; + /** + * 템플릿 제목 + */ + private String tmplatSj; + /** + * 템플릿 내용 + */ + private String tmplatCn; + /** + * 고객 센터 전화 번호 + */ + private String cstmrCnterTlphonNo; + /** + * REDIRECT URL + */ + private String redirectUrl; + /** + * try1 + */ + private String try1; + /** + * try2 + */ + private String try2; + /** + * try3 + */ + private String try3; + /** + * try2_minute + */ + private int try2Minute; + /** + * try3_minute + */ + private int try3Minute; + /** + * 사용 여부 + */ + private String useAt; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = false) + public static class KakaoMyDoc extends AuditFields implements Serializable { + + /** + * 발송 상세 id + */ + private String sndngDetailId; + /** + * 통합 발송 상세 id + */ + private String unitySndngDetailId; + /** + * 발송 마스터 id + */ + private String sndngMastrId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 제목 + */ + private String title; + /** + * 해쉬 + */ + private String hash; + /** + * 메타 정보 + */ + private String commonCategories; + /** + * 처리 마감 시간 + */ + private double readExpiredAt; + /** + * 받는 이 ci + */ + private String recvCi; + /** + * 받는 이 전화번호 + */ + private String recvPhoneNumber; + /** + * 받는 이 성명 + */ + private String recvName; + /** + * 받는 이 생년월일 + */ + private String recvBirthday; + /** + * 성명 검증 옵션 + */ + private String recvIsRequiredVerifyName; + /** + * 모바일 페이지 URL + */ + private String propLink; + /** + * payload + */ + private String propPayload; + /** + * 메시지 + */ + private String propMessage; + /** + * 고객센터 전화번호 + */ + private String propCsNumber; + /** + * 고객센터 명 + */ + private String propCsName; + /** + * 외부 문서 식별 번호 + */ + private String externalDocumentUuid; + /** + * 내부 문서 식별 번호 + */ + private String documentBinderUuid; + /** + * 에러 코드 + */ + private String errorCode; + /** + * 에러 메시지 + */ + private String errorMessage; + /** + * 진행 상태 + */ + private String docBoxStatus; + /** + * 송신 시간 + */ + private String docBoxSentAt; + /** + * 수신 시간 + */ + private String docBoxReceivedAt; + /** + * 최초 열람 인증 시간 + */ + private String authenticatedAt; + /** + * 최초 OTT 검증 시간 + */ + private String tokenUsedAt; + /** + * 최초 열람 시간 + */ + private String docBoxReadAt; + /** + * 알림톡 수신 시간 + */ + private String userNotifiedAt; + + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = false) + public static class PostSndng extends AuditFields implements Serializable { + + /** + * 통합 발송 마스터 id + */ + private String unitySndngMastrId; + /** + * 발송 상세 id + */ + private String sndngDetailId; + /** + * 통합 발송 상세 id + */ + private String unitySndngDetailId; + /** + * 발송 마스터 id + */ + private String sndngMastrId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 서비스 코드 + */ + private String serviceCd; + /** + * 연계 식별 키 + */ + private String conKey; + /** + * 발송인 명 + */ + private String senderNm; + /** + * 발송인 우편번호 + */ + private String senderZipNo; + /** + * 발송인 주소 + */ + private String senderAddr; + /** + * 발송인 상세 주소 + */ + private String senderDetailAddr; + /** + * 수취인 일련 번호 + */ + private String receiverSendNo; + /** + * 수취인 명 + */ + private String receiverNm; + /** + * 수취인 우편번호 + */ + private String receiverZipNo; + /** + * 수취인 주소 + */ + private String receiverAddr; + /** + * 수취인 상세 주소 + */ + private String receiverDetailAddr; + /** + * 가변 1 + */ + private String sschnge1; + /** + * 가변 2 + */ + private String sschnge2; + /** + * 가변 3 + */ + private String sschnge3; + /** + * 발송 처리 상태 + */ + private String sndngProcessSttus; + } + + + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class MobilePageManage implements Serializable { + + /** + * 발송 상세 id + */ + private String sndngDetailId; + /** + * 발송 구분 코드 + */ + private String sndngSeCode; + /** + * 모바일 페이지 내용 + */ + private String mobilePageCn; + /** + * 등록 일시 + */ + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss") + private LocalDateTime registDt; + /** + * 등록자 + */ + private String register; + } + + @Schema(name = "sndngVali", description = "Ac 요청 결과 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class sndngVali { + /* mastr */ + /** + * 시군구 코드 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "시군구 코드 (max:5)", example = " ") + @Size(max = 5) + private String signguCode; + /** + * 과태료 코드 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "과태료 코드 (max:2)", example = " ") + @Size(max = 2) + private String ffnlgCode; + /** + * 템플릿 ID + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "템플릿 ID (max:5)", example = " ") + @Size(max = 5) + private String tmplatId; + /** + * 발송 유형 코드 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "발송 유형 코드 (max:3)", example = " ") + @Size(max = 3) + private String sndngTyCode; + /** + * 발송 건수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "발송 건수 (max:1)", example = " ") + @Digits(integer = 1, fraction = 0, message = "발송 건수는 필수입니다(max:1)") + private int sndngCo; + /** + * 발송 일시 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "발송 일시 (max:14)", example = " ") + @Size(max = 14) + private String sndngDt; + /** + * 마감 일시 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "마감 일시 (max:14)", example = " ") + @Size(max = 14) + private String closDt; + + /* detail */ + /** + * 메인 코드 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "메인 코드 (max:20)", example = " ") + @Size(max = 20) + private String mainCode; + /** + * 차량 번호 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "차량 번호 (max:30)", example = " ") + @Size(max = 30) + private String vhcleNo; + /** + * 성명 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "성명 (max:200)", example = " ") + @Size(max = 200) + private String nm; + /** + * 시군구 코드(detail) + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "시군구 코드 detail (max:5)", example = " ") + @Size(max = 5) + private String signguCodeDe; + /** + * 과태료 코드(detail) + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "과태료 코드 detail (max:2)", example = " ") + @Size(max = 2) + private String ffnlgCodeDe; + /* other */ + /** + * 주민등록번호 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "주민등록번호 (max:100)", example = " ") + @Size(max = 100) + private String ihidnum; + /** + * 핸드폰 번호 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "핸드폰 번호 (max:20)", example = " ") + @Size(max = 20) + private String moblphonNo; + /** + * 모바일 페이지 내용 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "모바일 페이지 내용", example = " ") + private String mobilePageCn; + /** + * 주소 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "주소 (max:300)", example = " ") + @Size(max = 300) + private String adres; + /** + * 상세 주소 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "상세 주소 (max:300)", example = " ") + @Size(max = 300) + private String detailAdres; + /** + * 우편번호 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "우편번호 (max:5)", example = " ") + @Size(max = 5) + private String zip; + /** + * try1 + */ + private String try1; + /** + * try2 + */ + private String try2; + /** + * try3 + */ + private String try3; + private int trySeq; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class SmsSndng implements Serializable { + + /** + * 발송 상세 id + */ + private String sndngDetailId; + /** + * 통합 발송 상세 id + */ + private String unitySndngDetailId; + /** + * 발송 마스터 id + */ + private String sndngMastrId; + /** + * 시군구 코드 + */ + private String signguCode; + /** + * 과태료 코드 + */ + private String ffnlgCode; + /** + * 차량 번호 + */ + private String vhcleNo; + /** + * 문자 발송 일시 + */ + private String smsSndngDt; + /** + * 문자 전송 형태 + */ + private String smsTrnsmisStle; + /** + * 문자 송신 전화 번호 + */ + private String smsTrnsmitTlphonNo; + /** + * 문자 수신 전화 번호 + */ + private String smsRecptnTlphonNo; + /** + * 문자 메시지 + */ + private String smsMssage; + /** + * 문자 발송 상태 + */ + private String smsSndngSttus; + /** + * 문자 발송 처리 상태 + */ + private String smsSndngProcessSttus; + /** + * 등록 일시 + */ + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss") + private LocalDateTime registDt; + /** + * 등록자 + */ + private String register; + + private String unitySndngMastrId; + private String sndngProcessSttus; + + } + + + + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class NtcnCntcData implements Serializable { + /** + * 전자고지 연계 data id + */ + private String ensCntcDataId; + /** + * 단속 일시 + */ + private String regltDt; + /** + * 차량 번호 + */ + private String vhcleNo; + /** + * 현장 명 + */ + private String sptNm; + /** + * 현장 별 코드 + */ + private String sptAcctoCode; + /** + * 파일 명 + */ + private String fileNm; + /** + * 처리 여부 + */ + private String processAt; + /** + * 대상 여부 + */ + private String trgetAt; + /** + * 통합 발송 상세 id + */ + private String unitySndngDetailId; + + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss") + private LocalDateTime registDt; + /** + * 등록자 + */ + private String register; + + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class EnsNtncCntcSndngTgts implements Serializable { + /** + * 전자고지 연계 data id + */ + private String ensCntcDataId; + /** + * 차량 번호 + */ + private String vhcleNo; + /** + * 처리 여부 + */ + private String processAt; + /** + * 대상여부 + */ + private String trgetAt; + /** + * 핸드폰 번호 + */ + private String moblphonNo; + /** + * 이름 + */ + private String nm; + /** + * 생년월일 + */ + private String brthdy; + /** + * 우편번호 + */ + private String zip; + /** + * 주소 + */ + private String adres; + /** + * 상세 주소 + */ + private String detailAdres; + /** + * 단속 일시 + */ + private String regltDt; + /** + * 현장 명 + */ + private String sptNm; + /** + * 마감일시 + */ + private String closDt; + /*모바일 페이지 내용*/ + private String mobilePageCn; + /*삭제여부*/ + private String deleteAt; + /** + * 발송건수 + */ + private int sndngCo; + /** + * 발송유형코드 + */ + @Builder.Default + private String sndngTyCode = "ENS"; + /** + * FIXME :: 알림톡 추가시 변경 필요 + * 템플릿 ID + */ + private String tmplatId = "JU102"; + /** + * 사전알림 데이타 생성 대상 여부 + */ + private String tgtYn; + + /** + * 통합발송 마스터ID + */ + private String unitySndngMastrId; + /** + * 통합발송 상세ID + */ + private String unitySndngDetailId; + /** + * 발송처리상태 + */ + @Builder.Default + private String sndngProcessSttus = ApiConstants.SndngProcessStatus.ACCEPT.getCode(); + } +} diff --git a/mens-batch/src/main/java/kr/xit/biz/ens/package-info.java b/mens-batch/src/main/java/kr/xit/biz/ens/package-info.java new file mode 100644 index 0000000..1659f66 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/ens/package-info.java @@ -0,0 +1,11 @@ +/** + * ENS business packages + *

+ * 전자고지 : ens + * sms + *

+ * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.biz.ens; diff --git a/mens-batch/src/main/java/kr/xit/biz/ens/service/EnsCctvFileService.java b/mens-batch/src/main/java/kr/xit/biz/ens/service/EnsCctvFileService.java new file mode 100644 index 0000000..5585e98 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/ens/service/EnsCctvFileService.java @@ -0,0 +1,311 @@ +package kr.xit.biz.ens.service; + +import kr.xit.biz.ens.mapper.IEnsCctvFileMapper; +import kr.xit.biz.ens.model.EnsDTO; +import kr.xit.core.support.utils.Checks; +import kr.xit.core.support.utils.DateUtils; +import kr.xit.core.support.utils.SFTPUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static egovframework.com.cmm.util.EgovDateUtil.formatDate; +import static egovframework.com.cmm.util.EgovDateUtil.formatTime; + +/** + *
+ * description : 전자 고지 CCTV 파일 서비스
+ *
+ * packageName : kr.xit.biz.ens.service
+ * fileName    : EnsCctvFileService
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Service +public class EnsCctvFileService extends EgovAbstractServiceImpl implements IEnsCctvFileService { + + @Value("${app.ssh.host}") private String host; + @Value("${app.ssh.port}") private int port; + @Value("${app.ssh.id}") private String id; + @Value("${app.ssh.passwd}") private String passwd; + + @Value("${app.ssh.sg.ens-path}") private String ensPath; + @Value("${app.ssh.sg.rcv}") private String rcvPath; + @Value("${app.ssh.sg.backup}") private String backupPath; + @Value("${app.ssh.sg.err}") private String errPath; + + private final IEnsCctvFileMapper mapper; + + /** + * 전자 고지 cctv 단속 자료 생성(서광) + */ + @Override + @Transactional + public void createCctvFileOfSg() { + + // 전자 고지 라이선스 유효성 체크 + int licenseCnt = mapper.selectLicense("ENS"); + if(licenseCnt == 0) return; + + SFTPUtils sftp = null; + + try { + // SFTP connect + sftp = new SFTPUtils(); + sftp.init(host, port, id, passwd, StringUtils.EMPTY); + + // 서광 CCTV 전자 고지 대상 조회 + final String srcPath = ensPath + rcvPath; + ArrayList fileNameList = sftp.findFileNameList(srcPath); + //if(fileNameList.size() == 0) throw BizRuntimeException.create("사전고지[서광] 처리 대상이 없습니다"); + + // 에러 파일 목록 + ArrayList errFileList = new ArrayList<>(); + // 성공 파일 목록 + ArrayList rtnFileList = new ArrayList<>(); + for (String fn : fileNameList) { + String[] arrFi = fn.split("_"); + + // 파일 이름 정보 오류시 + if (arrFi.length != 4 || arrFi[0].length() != 14) { + errFileList.add(fn); + continue; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // FIXME :: GS 임시 소스 + // 파일 다운로드 + final String downLoadPath = "D:/ImageData/ENS" + "/" + DateUtils.getToday(StringUtils.EMPTY); + + File Folder = new File(downLoadPath); + if (!Folder.exists()) Folder.mkdir(); //폴더 생성합니다. + sftp.fileDownload(srcPath + "/" + fn, downLoadPath); + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // FIXME : 중복데이타(단속시간 + 차량번호)인 경우 데이타 skip 하도록 해야 할 듯 + // 전자 고지 연계 데이타 생성 + mapper.insertNtcnCntcData( + EnsDTO.NtcnCntcData.builder() + .regltDt(arrFi[0]) + .vhcleNo(arrFi[1]) + .sptNm(arrFi[2]) + .sptAcctoCode(arrFi[3].split("\\.")[0]) + .fileNm(fn) + .build() + ); + rtnFileList.add(fn); + + // FIXME :: sftp rm 기능 추후 제거 + // 파일 삭제 + sftp.rm(fn); + } + + // FIXME :: sftp move 기능 추후 사용 + // 처리한 데이타 backup + //fileBackup(sftp, srcPath, errFileList, rtnFileList); + + } finally { + if(sftp != null) sftp.disconnect(); + } + } + + /** + * 전자 고지 accept 연계발송 데이타 생성 + * 1. 전자 고지 연계 발송 데이타 생성 대상 조회 + * 2. 전자 고지 연계 발송 마스터 생성 + * 3. 전자 고지 연계 발송 상세 생성 + * 4. 전자 고지 연계 결과 반영 + */ + @Override + @Transactional + public void acceptEnsNtnccntcSndng() { + List tgtList = mapper.selectEnsNtncCntcSndngs(); + + if(tgtList.size() == 0) return; + + // search 전자 고지 연계발송 데이타 생성 대상 + Optional first = tgtList.stream() + .filter(dto -> "Y".equals(dto.getTgtYn())) + .findFirst(); + + // 전자 고지 연계발송 master 생성 + final String unitySndngMastrId; + final String closDt; + if(first.isPresent()){ + EnsDTO.EnsNtncCntcSndngTgts tgtDTO = first.get(); + mapper.insertCntcSndngMst(tgtDTO); + unitySndngMastrId = tgtDTO.getUnitySndngMastrId(); + closDt = tgtDTO.getClosDt(); + MDC.put("unitySndngMastrId", unitySndngMastrId); + } else { + unitySndngMastrId = null; + closDt = null; + } + + // 전자 고지 연계발송 상세 생성 및 결과 처리(전자 고지 연계 데이타 반영) + // 전자 고지 대상 -> 전자 고지 연계 발송 상세 생성 + // 전자 고지 연계 데이타 -> 연계 결과 반영 + // : 대상 : 연계대상-Y, 진행상태-Y, 통합발송상세ID + // 미대상 : 연계대상-N, 진행상태-Y, 통합발송상세ID = null + tgtList.forEach(dto -> { + dto.setProcessAt("Y"); + if("Y".equals(dto.getTgtYn())){ + dto.setUnitySndngMastrId(unitySndngMastrId); + dto.setClosDt(closDt); + dto.setMobilePageCn(jsonCn(dto)); + mapper.insertCntcSndngDtl(dto); + } + mapper.updateEnsNtcnCntcData(dto); + }); + } + + private void fileBackup(SFTPUtils sftp, String srcPath, ArrayList errFileList, ArrayList rtnFileList) { + final String errorPath = ensPath + errPath; + String destPath = ensPath + backupPath; + destPath = destPath + "/" + DateUtils.getToday(StringUtils.EMPTY); + + log.info("src path::[{}]", srcPath); + log.info("tgt path::[{}]", destPath); + + // file backup + for (String fn : rtnFileList) { + log.info("fileName::[{}]", fn); + sftp.mv(srcPath + "/" + fn, destPath + "/" + fn); + } + + // error file backup + for (String fn : errFileList) { + sftp.mv(srcPath + "/" + fn, errorPath + "/" + fn); + } + } + + private String jsonCn(EnsDTO.EnsNtncCntcSndngTgts dto){ + String jsonCn = "{" + + "\"details\": [" + + "{" + + "\"title\": \"주정차 위반 과태료 통지서\"," + + "\"item_type\": \"SUBJECT_TEXT\"," + + "\"elements\": [" + + "\"\"" + + "]" + + "}," + + "{" + + "\"title\": \"위반내역\"," + + "\"item_type\": \"KEY_VALUE\"," + + "\"properties\": {" + + "}," + + "\"elements\": [" + + "{" + + "\"key\": \"차량번호\"," + + "\"value\": \""+ Checks.checkVal(dto.getVhcleNo(),"") +"\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"성명\"," + + "\"value\": \""+ Checks.checkVal(dto.getNm(),"") +"\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"위반일시\"," + + "\"value\": \""+ Checks.checkVal(formatDate(dto.getRegltDt().substring(0,8), "-") + " " + formatTime(dto.getRegltDt().substring(8,14), ":"),"") +"\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"위반내용\"," + + "\"value\": \"주정차금지 구역\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"위반장소\"," + + "\"value\": \""+ Checks.checkVal(dto.getSptNm(),"") +"\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"납부금액\"," + + "\"value\": \"32,000원\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"단속구분\"," + + "\"value\": \"CCTV\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"자진납부 기한\"," + + "\"value\": \""+ Checks.checkVal(formatDate(dto.getClosDt().substring(0,8), "-"),"") +"\\\\n위 납부 기한이 경과 시에는 과태료 감경 혜택을 받으실 수 없습니다.\"," + + "\"level\": 1" + + "}" + + "]" + + "}," + + "{" + + "\"title\": \"과태료 부과 및 감경\"," + + "\"item_type\": \"TABLE\"," + + "\"elements\": {" + + "\"head\": [" + + "\"구분\"," + + "\"승용 감경금액\\\\n(부과금액)\"," + + "\"승합 감경금액\\\\n(부과금액)\"" + + "]," + + "\"rows\": [" + + "[" + + "\"주정차금지구역\"," + + "\"32,000원\\\\n(40,000원)\"," + + "\"40,000원\\\\n(50,000원)\"" + + "]," + + "[" + + "\"같은 장소 2시간 초과\"," + + "\"40,000원\\\\n(50,000원)\"," + + "\"48,000원\\\\n(60,000원)\"" + + "]" + + "]" + + "}" + + "}," + + "{" + + "\"title\": \"질서위반행위 규제법 시행령 규정 감경 대상\"," + + "\"item_type\": \"KEY_VALUE\"," + + "\"elements\": [" + + "{" + + "\"key\": \"감경대상자\"," + + "\"value\": \"○ 국민기초생활 수급자\\\\n○ 한부모가족 보호대상자\\\\n○ 장애의 정도가 심한 장애인\\\\n○ 국가유공자(상이등급 3급 이상)\\\\n○ 미성년자\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"시행일\"," + + "\"value\": \"2010년 1월 16일 부터\\\\n○ 적용 : 시행일 이후 단속된 차량\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"감경율(%)\"," + + "\"value\": \"과태료 부과금액의 50%\"," + + "\"level\": 1" + + "}," + + "{" + + "\"key\": \"비고\"," + + "\"value\": \"※ 자진 납부 시 추가감경 가능\"," + + "\"level\": 1" + + "}" + + "]" + + "}" + + "]" + +"}"; + return jsonCn; + } +} diff --git a/mens-batch/src/main/java/kr/xit/biz/ens/service/IEnsCctvFileService.java b/mens-batch/src/main/java/kr/xit/biz/ens/service/IEnsCctvFileService.java new file mode 100644 index 0000000..f9367eb --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/ens/service/IEnsCctvFileService.java @@ -0,0 +1,28 @@ +package kr.xit.biz.ens.service; + +/** + *
+ * description : 전자 고지 파일 서비스
+ *
+ * packageName : kr.xit.biz.ens.service
+ * fileName    : IEnsCctvFileService
+ * author      : seojh
+ * date        : 2023-07-17
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-17    seojh       최초 생성
+ *
+ * 
+ */ +public interface IEnsCctvFileService { + /** + * 전자 고지 cctv 단속 자료 생성(서광) + */ + void createCctvFileOfSg(); + + /** + * 전자 고지 accept 연계발송 데이타 생성 + */ + void acceptEnsNtnccntcSndng(); +} diff --git a/mens-batch/src/main/java/kr/xit/biz/ens/service/ISendMessageLinkService.java b/mens-batch/src/main/java/kr/xit/biz/ens/service/ISendMessageLinkService.java new file mode 100644 index 0000000..0017166 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/ens/service/ISendMessageLinkService.java @@ -0,0 +1,37 @@ +package kr.xit.biz.ens.service; + +import java.util.List; + +import kr.xit.biz.ens.model.EnsDTO; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.biz.ens.model.kakao.KkopayDocDTO; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.ens.service
+ * fileName    : ISendMessageLinkService
+ * author      : limju
+ * date        : 2023-06-05
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-05    limju       최초 생성
+ *
+ * 
+ */ +public interface ISendMessageLinkService { + + void accept(final String sndngProcessSttus); + void make(final String sndngProcessSttus); + void close(final String sndngProcessSttus); + + void sendBulks(final String sndngProcessSttus); + + void findKkoMyDocStatusBulks(final String sndngProcessSttus); + + List findKakaoSendTargets(final String sndngProcessSttus); + + void updateErrorLog(EnsDTO.SndngMssageParam dto); +} diff --git a/mens-batch/src/main/java/kr/xit/biz/ens/service/SendMessageLinkService.java b/mens-batch/src/main/java/kr/xit/biz/ens/service/SendMessageLinkService.java new file mode 100644 index 0000000..30ae609 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/ens/service/SendMessageLinkService.java @@ -0,0 +1,890 @@ +package kr.xit.biz.ens.service; + +import java.io.*; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import egovframework.com.cmm.util.EgovDateUtil; +import egovframework.com.cmm.util.EgovStringUtil; +import kr.xit.biz.common.ApiConstants; +import kr.xit.biz.ens.model.EnsDTO.SendKakaoTgt; +import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.BulkSendReq; +import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.BulkSendRequests; +import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.BulkSendResponses; +import kr.xit.biz.ens.model.kakao.KkopayDocDTO.DocStatusResponse; +import kr.xit.biz.sms.service.ISmsMessageService; +import kr.xit.core.consts.ErrorCode; +import kr.xit.core.spring.util.ApiWebClient; +import kr.xit.core.support.utils.DateUtils; +import kr.xit.biz.ens.model.kakao.KkopayDocDTO; +import kr.xit.core.support.utils.JsonUtils; +//import kr.xit.ens.support.kakao.service.IKkopayEltrcDocService; +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.lang3.StringUtils; +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.egovframe.rte.fdl.cryptography.EgovPasswordEncoder; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import kr.xit.biz.ens.mapper.ISendMessageLinkMapper; +import kr.xit.biz.ens.model.CntcDTO; +import kr.xit.biz.ens.model.EnsDTO; +import kr.xit.core.exception.BizRuntimeException; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.core.support.utils.Checks; +import kr.xit.biz.ens.model.kakao.KkopayDocAttrDTO; +import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO; +import lombok.RequiredArgsConstructor; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; +import org.springframework.web.reactive.function.client.WebClient; + +/** + *
+ * description : 전자고지 통합발송 연계 서비스(배치)
+ *
+ * packageName : kr.xit.biz.ens.service
+ * fileName    : SendMessageLinkService
+ * author      : limju
+ * date        : 2023-06-05
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-05    limju       최초 생성
+ *
+ * 
+ */ +@RequiredArgsConstructor +@Service +public class SendMessageLinkService extends EgovAbstractServiceImpl implements ISendMessageLinkService { + private final ApiWebClient apiWebClient; + private final ISendMessageLinkMapper mapper; + private final EgovPasswordEncoder encryptor; + //private final IAsyncKkopayEltrcDocService asyncService; + //private final IKkopayEltrcDocService service; + private final ISmsMessageService smsService; + + private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + + @Value("${contract.provider.kakao.isAsync:false}") + private boolean isAsync; + + @Value("${contract.provider.kakao.bulk-max-cnt}") + private int bulkMaxCnt; + + @Value("${file.cmm.upload.root}") + private String fileRoot; + + @Value("${file.cmm.upload.post}") + private String filePost; + + private static final String KKO_MY_DOC = "KKO-MY-DOC"; + private static final String E_GREEN = "E-GREEN"; + private static final String SMS = "SMS"; + private static final String SNDNG_PROCESS_STTUS = "sndngProcessSttus"; + private static final String UNITY_SNDNG_MST_ID = "unitySndngMastrId"; + private static final String YMDHMS = "yyyyMMddHHmmss"; + + //----------------------------------------------------------------------------------------------------------------- + // accept + //----------------------------------------------------------------------------------------------------------------- + /** + * + * @param sndngProcessSttus + */ + @Override + @Transactional + public void accept(final String sndngProcessSttus) { + final List list = mapper.selectAcceptTgts(sndngProcessSttus); + String sndngDt = null; + + for(EnsDTO.SndngMssageParam dto : list) { + final List mstIdList = new ArrayList<>(); + String mstId = ""; + if(!Objects.equals(mstId, dto.getUnitySndngMastrId())){ + mstId = dto.getUnitySndngMastrId(); + mstIdList.add(mstId); + MDC.put(UNITY_SNDNG_MST_ID, mstId); + } + + // validation check + final List valiList = mapper.selectAcceptVali(dto); + try { + validatedAccept(valiList); + }catch (Exception e){ + updateAcceptCntcSndngMstFailStatus(mstIdList, "accept 생성 요청 실패(파라메터 오류) : " + EgovStringUtil.cutString(e.getMessage(), 300)); + continue; + } + + // 초기 1회 셋팅 (발송 일시2, 발송 일시3 : 템플릿 관리 try2_minute, try3_minute 값에 따라 처리) + if(sndngDt == null) { + sndngDt = StringUtils.defaultString(dto.getSndngDt()); + if(dto.getTry2() != null && !"".equals(sndngDt)) dto.setSndngDt2(EgovDateUtil.addMinute(sndngDt,Integer.parseInt(dto.getTry2Minute()))); + if(dto.getTry3() != null && !"".equals(sndngDt)) dto.setSndngDt3(EgovDateUtil.addMinute(sndngDt,Integer.parseInt(dto.getTry3Minute()))); + } + + dto.setSndngProcessSttus(sndngProcessSttus); + dto.setNewSndngProcessSttus(ApiConstants.SndngProcessStatus.ACCETP_OK.getCode()); + if(mapper.insertUnitySndngMst(dto) != 1) throw BizRuntimeException.create("접수 데이타 생성(마스터) 실패"); + int insCnt = mapper.insertUnitySndngDtls(dto); + if(insCnt != dto.getSndngCo()) throw BizRuntimeException.create(String.format("접수 상세 데이타 생성 실패-발송건수[%d]와 생성건수[%d] 불일치", dto.getSndngCo(), insCnt)); + if(mapper.updateProcessSttusCntcSndngMst(dto) != 1) throw BizRuntimeException.create("접수 데이타 생성(통합발송마스터 상태변경) 실패"); + } + } + + /** + * accept 파라메터 유효성 체크 + * @param valiList List + */ + private void validatedAccept(List valiList){ + List errors = new ArrayList<>(); + int idx = 0; + + List> arrList = ListUtils.partition(valiList, bulkMaxCnt); + for(List list: arrList) { + for (EnsDTO.sndngVali dto : list) { + Set> errList = validator.validate(dto); + + if (list.size() > 0) { + int finalIdx = idx; + errors.addAll(errList.stream() + .map(row -> String.format("%s[%d]=%s", row.getPropertyPath(), finalIdx + 1, row.getMessageTemplate())) + .collect(Collectors.toList()) + ); + } + + // 발송일시보다 마감일시가 빠르면 안되고, 마감일시가 현재 시간보다 빠르면 오류 + if(!Checks.isEmpty(dto.getSndngDt()) && !Checks.isEmpty(dto.getClosDt())){ + if(!DateUtils.isBeforeLocalDateTime(dto.getSndngDt(),dto.getClosDt(),YMDHMS)) + errors.add(String.format("발송일시보다 마감일시가 빠를 수 없습니다(dto.getSndngDt[%d] 번째 오류)", idx+1)); + if(!DateUtils.isBeforeLocalDateTime(DateUtils.getTodayAndNowTime(YMDHMS),dto.getClosDt(),YMDHMS)) + errors.add(String.format("현재 시간보다 마감일시가 빠를 수 없습니다(dto.getSndngDt[%d] 번째 오류)", idx+1)); + } + + String[] tryVal = {Checks.checkVal(dto.getTry1(),""), Checks.checkVal(dto.getTry2(),""), Checks.checkVal(dto.getTry3(),"")}; + switch (tryVal[dto.getTrySeq()-1]){ + case KKO_MY_DOC: + if(Checks.isEmpty(dto.getIhidnum())) errors.add(String.format("주민등록번호는 필수입니다(dto.getIhidnum[%d] 번째 오류)", idx+1)); + if(Checks.isEmpty(dto.getMoblphonNo())) errors.add(String.format("핸드폰 번호는 필수입니다(dto.getMoblphonNo[%d] 번째 오류)", idx+1)); + if(Checks.isEmpty(dto.getIhidnum())) errors.add(String.format("받는이 이름은 필수입니다(dto.getIhidnum[%d] 번째 오류)", idx+1)); + break; + + case E_GREEN: + if(Checks.isEmpty(dto.getAdres())) errors.add(String.format("주소는 필수입니다(dto.getAdres[%d] 번째 오류)", idx+1)); + if(Checks.isEmpty(dto.getDetailAdres())) errors.add(String.format("상세 주소는 필수입니다(dto.getDetailAdres[%d] 번째 오류)", idx+1)); + if(Checks.isEmpty(dto.getZip())) errors.add(String.format("우편번호는 필수입니다(dto.getZip[%d] 번째 오류)", idx+1)); + break; + + case SMS: + if(Checks.isEmpty(dto.getMoblphonNo())) errors.add(String.format("핸드폰 번호는 필수입니다(dto.getMoblphonNo[%d] 번째 오류)", idx+1)); + break; + + default: + break; + } + idx++; + } + } + + if(errors.size() > 0) { + throw BizRuntimeException.create(errors.toString()); + } + } + + /** + * accpet 파라메처 체크오류시 마스터 상태 변경 - 실패 + * @param mstIdList + * @param stsErrMsg + */ + private void updateAcceptCntcSndngMstFailStatus(final List mstIdList, final String stsErrMsg) { + + for(String id : mstIdList) { + EnsDTO.SndngMssageParam paramDTO = EnsDTO.SndngMssageParam.builder() + .unitySndngMastrId(id) + .newSndngProcessSttus("accept-fail") + .errorMssage(stsErrMsg) + .build(); + + if(mapper.updateProcessSttusCntcSndngMst(paramDTO) != 1) throw BizRuntimeException.create("[accept]연계 발송 마스터 상태변경 실패"); + } + } + //----------------------------------------------------------------------------------------------------------------- + // accept + //----------------------------------------------------------------------------------------------------------------- + + //----------------------------------------------------------------------------------------------------------------- + // make + //----------------------------------------------------------------------------------------------------------------- + /** + * tryCnt 값에 따라 분기 - 1:카카오 + * @param sndngProcessSttus + */ + @Override + @Transactional + public void make(final String sndngProcessSttus) { + final String sndngProcessSttus2 = ApiConstants.SndngProcessStatus.SENDING1.getCode(); + final String sndngProcessSttus3 = ApiConstants.SndngProcessStatus.SENDING2.getCode(); + final List list = mapper.selectMakeTgts(sndngProcessSttus); + final List list2 = mapper.selectMakeTgts(sndngProcessSttus2); + + for(EnsDTO.SndngMssageParam dto : list) { + MDC.put(UNITY_SNDNG_MST_ID, dto.getUnitySndngMastrId()); + MDC.put(SNDNG_PROCESS_STTUS, "make-fail1"); + + dto.setSndngProcessSttus(sndngProcessSttus); + dto.setNewSndngProcessSttus(ApiConstants.SndngProcessStatus.MAKE_OK.getCode()); + //FIXME: try1,try2,tr3이 있는 경우 try1 이전에 try2,3이 실행될 수 있는 경우??? + dto.setTrySeq(1); + dto.setSndngSeCode(dto.getTry1()); + + makeMstStatusUpdate(dto); + } + + for(EnsDTO.SndngMssageParam dto : list2) { + MDC.put(UNITY_SNDNG_MST_ID, dto.getUnitySndngMastrId()); + MDC.put(SNDNG_PROCESS_STTUS, "make-fail2"); + + //try2 발송 대상이 있는지 확인 + final int sendCnt = mapper.selectSendOkTgts(dto); + + //없으면 연계 발송 마스터, 통합 발송 마스터 send-ok + if(sendCnt == 0) { + dto.setNewSndngProcessSttus(ApiConstants.SndngProcessStatus.SEND_OK.getCode()); + + if(mapper.updateProcessSttusCntcSndngMst(dto) != 1) throw BizRuntimeException.create("[make]연계 발송 마스터 상태변경 실패"); + if(mapper.updateProcessSttusUnitySndngMst(dto) != 1) throw BizRuntimeException.create("[make]통합 발송 마스터 상태변경 실패"); + } else { + //있으면 make + dto.setSndngProcessSttus(sndngProcessSttus2); + dto.setNewSndngProcessSttus(ApiConstants.SndngProcessStatus.MAKE_OK.getCode()); + dto.setTrySeq(2); + dto.setSndngCo(sendCnt); + dto.setSndngSeCode(dto.getTry2()); + + makeMstStatusUpdate(dto); + } + } + + //TODO:: GS 인증 시험에서 카카오, E-GREEN만 있어서 3단계는 필요 시 구현 + } + + /** + * make 데이타 생성 + * 1. master + * 2. detail + * 2. master 상태 변경 + * @param dto + */ + private void makeMstStatusUpdate(EnsDTO.SndngMssageParam dto) { + // 1. master + if(mapper.insertSndngMst(dto) != 1) throw BizRuntimeException.create("[make]데이타 생성(마스터) 실패"); + + // 2. detail + int insCnt = insertEnsDetailByTry(dto); + if(insCnt != dto.getSndngCo()) throw BizRuntimeException.create(String.format("[make]상세 데이타 생성 실패-발송건수[%d]와 생성건수[%d] 불일치", dto.getSndngCo(), insCnt)); + + // 3. status + if(mapper.updateProcessSttusCntcSndngMst(dto) != 1) throw BizRuntimeException.create("[make]데이타 생성(발송마스터 상태변경) 실패"); + if(mapper.updateProcessSttusUnitySndngMst(dto) != 1) throw BizRuntimeException.create("[make]데이타 생성(통합발송마스터 상태변경) 실패"); + } + + /** + * make 데이타 생성 - 2. detail + * @param dto + * @return + */ + private int insertEnsDetailByTry(final EnsDTO.SndngMssageParam dto){ + String[] tryVal = {Checks.checkVal(dto.getTry1(),""), Checks.checkVal(dto.getTry2(),""), Checks.checkVal(dto.getTry3(),"")}; + switch (tryVal[dto.getTrySeq()-1]){ + case KKO_MY_DOC: + return mapper.insertKakaoMyDocs(dto); + + case E_GREEN: + return mapper.insertPostSndng(dto); + + case SMS: + return mapper.insertSmsSndng(dto); + + default: + return 1; + } + } + //----------------------------------------------------------------------------------------------------------------- + // make + //----------------------------------------------------------------------------------------------------------------- + + //----------------------------------------------------------------------------------------------------------------- + // send + //----------------------------------------------------------------------------------------------------------------- + /** + * 전자고지(문서) send - 업무에 따라 카카오 | E-GREEN | SMS 발송 + * 1. 발송/통합발송 마스터에서 대상 조회 + * 2. send(발송) + * @param sndngProcessSttus String + */ + @Override + @Transactional + public void sendBulks(final String sndngProcessSttus) { + + // 발송/통합발송 마스터에서 대상 조회 + final List tgtList = mapper.selectSendBulkTgts(sndngProcessSttus); + for(EnsDTO.SndngMssageParam tgtDTO : tgtList){ + MDC.put(UNITY_SNDNG_MST_ID, tgtDTO.getUnitySndngMastrId()); + MDC.put("sndngMastrId", tgtDTO.getSndngMastrId()); + MDC.put(SNDNG_PROCESS_STTUS, "send-fail" + tgtDTO.getTrySeq()); + + String[] tryVal = {Checks.checkVal(tgtDTO.getTry1(),""), Checks.checkVal(tgtDTO.getTry2(),""), Checks.checkVal(tgtDTO.getTry3(),"")}; + + // 마스터 상태 변경값을 파라메터에서 받은 상태값으로 set + tgtDTO.setNewSndngProcessSttus(sndngProcessSttus); + + // 업무 문서 구분에 따른 분기 + switch (tryVal[tgtDTO.getTrySeq() -1]){ + // 카카오 + case KKO_MY_DOC: + sendBulkKakaoMyDocs(tgtDTO); + break; + + // E-GREEN + case E_GREEN: + sendEgreen(tgtDTO); + break; + + // SMS + case SMS: + sendSms(tgtDTO); + break; + + default: + break; + } + } + } + + /** + * 카카오페이 전자문서 발송요청 처리 + * @param tgtDTO EnsDTO.SndngMssageParam + */ + @Transactional + public void sendBulkKakaoMyDocs(final EnsDTO.SndngMssageParam tgtDTO) { + final List list = mapper.selectKakaoSendTgts(tgtDTO); + final List bulkList = new ArrayList<>(); + final List mstIdList = new ArrayList<>(); + + setKkoMyDocSendBulks(list, bulkList, mstIdList); + + // validation check + try { + validatedKkoMyDocSendBulks(bulkList); + }catch (Exception e){ + updateKkoMyDocSndngMstFailStatus(mstIdList, "[send]카카오 문서 발송(bulks)요청 실패(파라메터 오류)"); + throw e; + } + + List> apiResults = null; + List> partitions = ListUtils.partition(bulkList, bulkMaxCnt); + + if(isAsync) { + // 카카오페이 전자문서발송 벌크 최대수 단위로 분할하여 실행 + /* + List>> apiAsyncResults = partitions.stream() + .map(bulkSendList -> + asyncService.requestSendBulk( + KkopayDocBulkDTO.BulkSendRequests.builder() + .documents(bulkSendList) + .build()) + ) + .collect(Collectors.toList()); + + // GET API 호출 결과 + apiResults = CompletableFuture.allOf(apiAsyncResults.toArray(new CompletableFuture[0])) + .thenApply(Void -> apiAsyncResults.stream() + .map(CompletableFuture::join) + .collect(Collectors.toList())) + .join(); +*/ + }else{ + StringBuilder url = new StringBuilder() + .append("http://localhost:8081") + .append("/api/kakaopay/batch/v1/documents/bulk"); + + //return ApiResponseDTO.success(webClient.exchange(HOST + API_BULKSEND[0], HttpMethod.valueOf(API_BULKSEND[1]), JsonUtils.toJson(reqDTO), KkopayDocBulkDTO.BulkSendResponses.class)); + + List> result = new ArrayList<>(); + for (List bulkSendList : partitions) { + ApiResponseDTO bulkSendResponsesApiResponseDTO = + ApiResponseDTO.success(apiWebClient.exchange(url.toString(), + HttpMethod.POST, + JsonUtils.toJson(BulkSendRequests.builder() + .documents(bulkSendList) + .build()), + BulkSendResponses.class)); + result.add(bulkSendResponsesApiResponseDTO); + } + apiResults = result; + + /* + apiResults = partitions.stream() + .map(bulkSendList -> + service.requestSendBulk( + KkopayDocBulkDTO.BulkSendRequests.builder() + .documents(bulkSendList) + .build()) + ) + .collect(Collectors.toList()); + */ + + } + // GET 결과 목록 + List resList = apiResults.stream() + .map(ApiResponseDTO::getData) + .collect(Collectors.toList()); + + // 카카오 문서 요청 결과 반영 + saveKkoMyDocResult(mstIdList, tgtDTO.getUnitySndngMastrId(), resList); + } + + + + + + /** + * 카카오문서 요청 결과 반영 + * @param mstIdList List 발송마스터 ID 목록 + * @param resList List 카카오내문서함 발송요청 결과 목록 + * @param unitySndMstId String 통합발송 마스터 ID + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void saveKkoMyDocResult(final List mstIdList, String unitySndMstId, final List resList) { + // 결과 반영 + resList.forEach(o -> + o.getDocuments().forEach( + t -> { + // 카카오페이 문서요청 결과 반영 + mapper.updateKakaoSendBulksResult(t); + // 모바일 페이지 컨텐트 생성 + if(Checks.isNotEmpty(t.getDocument_binder_uuid())){ + //tgtDTO.setUnitySndngDetailId(t.getExternal_document_uuid()); + mapper.insertMobilePageManage(t.getExternal_document_uuid()); + } + // 연계발송결과 생성 + insertKkoMyDocCntcSndngResult(ApiConstants.SndngSeCode.KAKAO_MY_DOC.getCode(), t.getExternal_document_uuid(), t.getError_message()); + }) + ); + + // 마스터 상태 변경 + updateKkoMyDocSendSndngMstStatus(mstIdList, unitySndMstId, "카카오 문서 발송요청 실패(발송마스터 데이타 오류)"); + } + + /** + * GET 카카오 문서발송요청 파라메터 목록 + * @param list List 문서발송요청 대상 목록 + * @param bulkList List 문서발송요청 파라메터 목록 + * @param mstIdList List 문서발송요청 대상 마스터ID 목록 + */ + private static void setKkoMyDocSendBulks(List list, List bulkList, List mstIdList) { + String mstId = null; + + for(EnsDTO.SendKakaoTgt sendTgtDTO : list){ + if(!Objects.equals(mstId, sendTgtDTO.getSndngMastrId())){ + mstId = sendTgtDTO.getSndngMastrId(); + mstIdList.add(mstId); + } + KkopayDocAttrDTO.Receiver receiver = KkopayDocAttrDTO.Receiver.builder() + .phone_number(sendTgtDTO.getRecvPhoneNumber()) + .name(sendTgtDTO.getRecvName()) + .birthday(sendTgtDTO.getRecvBirthday()) + .is_required_verify_name(false) + .build(); + + KkopayDocBulkDTO.PropertyBulk property = KkopayDocBulkDTO.PropertyBulk.builder() + .link(sendTgtDTO.getPropLink()) + .payload(sendTgtDTO.getPropPayload()) + .message(sendTgtDTO.getPropMessage()) + .cs_name(sendTgtDTO.getPropCsName()) + .cs_number(sendTgtDTO.getPropCsNumber()) + .external_document_uuid(sendTgtDTO.getUnitySndngDetailId()) + .build(); + + KkopayDocBulkDTO.BulkSendReq bulkReqDTO = KkopayDocBulkDTO.BulkSendReq.builder() + .title(sendTgtDTO.getTitle()) + .common_categories(Collections.singletonList(ApiConstants.Categories.NOTICE)) + .read_expired_at(sendTgtDTO.getClosDt()) + // + .hash(sendTgtDTO.getHash()) + //.hash(encryptor.encryptPassword(sendTgtDTO.getUnitySndngDetailId())) + .receiver(receiver) + .property(property) + .build(); + + bulkList.add(bulkReqDTO); + } + } + + /** + * 카카오문서 발송요청 파라메터 유효성 체크 + * @param bulkList List 카카오내문서함 발송요청 파라메터 목록 + */ + private void validatedKkoMyDocSendBulks(List bulkList){ + List errors = new ArrayList<>(); + int idx = 0; + + List> arrList = ListUtils.partition(bulkList, bulkMaxCnt); + for(List list: arrList) { + for(KkopayDocBulkDTO.BulkSendReq dto: list) { + Set> errList = validator.validate(dto); + + if (list.size() > 0) { + + int finalIdx = idx; + errors.addAll(errList.stream() + .map(row -> String.format("%s[%d]=%s", row.getPropertyPath(), finalIdx + 1, row.getMessageTemplate())) + .collect(Collectors.toList()) + ); + } + + + if(dto.getRead_expired_at() != null && dto.getRead_expired_sec() != null){ + errors.add("처리마감시간(절대시간 또는 상대시간)은 하나만 지정해야 합니다."); + } + if(dto.getRead_expired_at() == null && dto.getRead_expired_sec() == null){ + errors.add("처리마감시간(절대시간 또는 상대시간)을 지정해야 합니다."); + } + + KkopayDocAttrDTO.Receiver receiver = dto.getReceiver(); + if (Checks.isEmpty(receiver.getCi())) { + if (Checks.isEmpty(receiver.getName())) errors.add(String.format("받는이 이름은 필수입니다(receiver.name[%d] 번째 오류)", idx+1)); + if (Checks.isEmpty(receiver.getPhone_number())) errors.add(String.format("받는이 전화번호는 필수입니다(receiver.phone_number[%d] 번째 오류)", idx+1)); + if (Checks.isEmpty(receiver.getBirthday())) errors.add(String.format("받는이 생년월일은 필수입니다(receiver.birthday[%d] 번째 오류)", idx+1)); + } else { + StringBuilder sb = new StringBuilder() + .append(StringUtils.defaultString(receiver.getName(), StringUtils.EMPTY)) + .append(StringUtils.defaultString(receiver.getPhone_number(), StringUtils.EMPTY)) + .append(StringUtils.defaultString(receiver.getBirthday(), StringUtils.EMPTY)); + + if(Checks.isNotEmpty(sb.toString())){ + errors.add(String.format("CI가 지정 되었습니다(받는이 정보 불필요:[%d] 번째 오류) .", idx+1)); + } + } + idx++; + } + } + + if(errors.size() > 0){ + throw BizRuntimeException.create(errors.toString()); + } + } + + /** + * E-GREEN 우편 발송요청 파일 처리 + * @param tgtDTO EnsDTO.SndngMssageParam + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void sendEgreen(final EnsDTO.SndngMssageParam tgtDTO) { + final List list = mapper.selectPostTgts(tgtDTO); + final String filePath = fileRoot+filePost; + final String fileName = list.get(0).getConKey()+".txt"; + + if(!egreenFileWrite(filePath, fileName, list)) { + throw BizRuntimeException.create("우편 파일 생성 실패"); + } + + EnsDTO.SndngMssageParam paramDTO = EnsDTO.SndngMssageParam.builder() + .unitySndngMastrId(list.get(0).getUnitySndngMastrId()) + .sndngMastrId(list.get(0).getSndngMastrId()) + .newSndngProcessSttus(list.get(0).getSndngProcessSttus()) + .build(); + updateStepSendMststatus(paramDTO, "E-GREEN"); + } + + /** + * E-GREEN 발송 처리 - 파일 생성 + * @param filePath String + * @param fileName String + * @param list List + * @return boolean + */ + private boolean egreenFileWrite (final String filePath, final String fileName, final List list){ + try { + File folder = new File(filePath); + if(!folder.exists()) //noinspection ResultOfMethodCallIgnored + folder.mkdirs(); + + File file = new File(filePath+fileName); + if(!file.exists()) { + if(!file.createNewFile()){ + return false; + }; + } + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file, true))) { + + for (EnsDTO.PostSndng postSndng : list) { + writer.write(StringUtils.defaultString(postSndng.getConKey()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getSenderNm()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getSenderZipNo()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getSenderAddr()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getSenderDetailAddr()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getReceiverSendNo()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getReceiverNm()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getReceiverZipNo()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getReceiverAddr()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getReceiverDetailAddr()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getSschnge1()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getSschnge2()) + "|"); + writer.write(StringUtils.defaultString(postSndng.getSschnge3())); + writer.newLine(); + } + writer.flush(); + } + }catch (IOException ie){ + return false; + } + return true; + } + + /** + * SMS 전송 처리 + * - xit SMS : 별도 서비스 call + * @param tgtDTO EnsDTO.SndngMssageParam + * @see kr.xit.biz.sms.service.SmsMessageService#sendSmsList(List) + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void sendSms(final EnsDTO.SndngMssageParam tgtDTO) { + final List list = mapper.selectSmsSendTgts(tgtDTO); + + // Orcale DB - 서비스 분리 + smsService.sendSmsList(list); + + EnsDTO.SndngMssageParam paramDTO = EnsDTO.SndngMssageParam.builder() + .unitySndngMastrId(list.get(0).getUnitySndngMastrId()) + .sndngMastrId(list.get(0).getSndngMastrId()) + .newSndngProcessSttus(list.get(0).getSndngProcessSttus()) + .build(); + updateStepSendMststatus(paramDTO, "SMS"); + } + + /** + * 문서 전송후 마스터 상태 변경 + * @param paramDTO EnsDTO.SndngMssageParam + * @param workCfg String + */ + @Transactional + public void updateStepSendMststatus(final EnsDTO.SndngMssageParam paramDTO, final String workCfg) { + if(mapper.updateProcessSttusCntcSndngMst(paramDTO) != 1) throw BizRuntimeException.create(String.format("[send-%s]연계 발송 마스터 상태변경 실패", workCfg)); + if(mapper.updateProcessSttusUnitySndngMst(paramDTO) != 1) throw BizRuntimeException.create(String.format("[send-%s]통합 발송 마스터 상태변경 실패", workCfg)); + if(mapper.updateProcessSttusSndngMst(paramDTO) != 1) throw BizRuntimeException.create(String.format("[send-%s]발송 마스터 상태변경 실패", workCfg)); + } + + /** + * 카카오 내문서함 문서발송요청 연계결과(tb_cntc_sndng_result) 생성 + * @param sndngSeCode 발송구분코드 - KKO-MY-DOC|E-GREEN|SMS + * @param unitySndngDetailId 통합 발송 상세 ID + * @param errMessage 에러메세지(API호출 결과 에러 메세지) + */ + @Transactional + public void insertKkoMyDocCntcSndngResult(final String sndngSeCode, final String unitySndngDetailId, final String errMessage) { + String rsltSts = StringUtils.defaultString(errMessage, ApiConstants.DocBoxStatus.SENT.getCode()); + mapper.insertCntcSndngResult(CntcDTO.SndngResult.builder() + .unitySndngDetailId(unitySndngDetailId) + .sndngSeCode(sndngSeCode) + .sndngResultSttus(rsltSts.equals(ApiConstants.DocBoxStatus.SENT.getCode())? "SENT" : "FAIL") + .errorCn(errMessage) + .build()); + } + + /** + * 카카오 내문서함 발송요청후 연계발송마스터 / 통합발송마스터 / 발송마스터 상태 변경 + * send-ok|sending1|sending2 + * @param mstIdList 발송마스터 ID 목록 + * @param unitySndMstId String 통합발송 마스터 ID + * @param stsErrMsg + */ + @Transactional + public void updateKkoMyDocSendSndngMstStatus(final List mstIdList, final String unitySndMstId, final String stsErrMsg) { + + for(String id : mstIdList) { + EnsDTO.SndngMssageParam dto = mapper.selectSndProcessStatus(id).orElseThrow(() -> BizRuntimeException.create(stsErrMsg)); + EnsDTO.SndngMssageParam paramDTO = EnsDTO.SndngMssageParam.builder() + .sndngMastrId(id) + .unitySndngMastrId(unitySndMstId) + .newSndngProcessSttus(dto.getNewSndngProcessSttus()) + .build(); + + if(mapper.updateProcessSttusCntcSndngMst(paramDTO) != 1) throw BizRuntimeException.create("[send-카카오]연계 발송 마스터 상태변경 실패"); + if(mapper.updateProcessSttusUnitySndngMst(paramDTO) != 1) throw BizRuntimeException.create("[send-카카오]통합 발송 마스터 상태변경 실패"); + if(mapper.updateProcessSttusSndngMst(paramDTO) != 1) throw BizRuntimeException.create("[send-카카오]발송 마스터 상태변경 실패"); + } + } + + /** + * 카카오 문서 발송요청 파라메터 체크 오류 반영 - 발송 관련 마스터 테이블 + * @param mstIdList List + * @param stsErrMsg String + */ + @Transactional + public void updateKkoMyDocSndngMstFailStatus(final List mstIdList, final String stsErrMsg) { + + for(String id : mstIdList) { + EnsDTO.SndngMssageParam dto = mapper.selectSndProcessStatus(id).orElseThrow(() -> BizRuntimeException.create(stsErrMsg)); + + String newSndngProcessSttus; + if(dto.getTrySeq() == 2) newSndngProcessSttus = "send-fail2"; + else if(dto.getTrySeq() == 3) newSndngProcessSttus = "send-fail3"; + else newSndngProcessSttus = "send-fail1"; + + EnsDTO.SndngMssageParam paramDTO = EnsDTO.SndngMssageParam.builder() + .unitySndngMastrId(dto.getUnitySndngMastrId()) + .sndngMastrId(dto.getSndngMastrId()) + .newSndngProcessSttus(newSndngProcessSttus) + .build(); + + if(mapper.updateProcessSttusCntcSndngMst(paramDTO) != 1) throw BizRuntimeException.create("[send-카카오]연계 발송 마스터 상태변경 실패"); + if(mapper.updateProcessSttusUnitySndngMst(paramDTO) != 1) throw BizRuntimeException.create("[send-카카오]통합 발송 마스터 상태변경 실패"); + if(mapper.updateProcessSttusSndngMst(paramDTO) != 1) throw BizRuntimeException.create("[send-카카오]발송 마스터 상태변경 실패"); + } + } + //----------------------------------------------------------------------------------------------------------------- + // send + //----------------------------------------------------------------------------------------------------------------- + + + //----------------------------------------------------------------------------------------------------------------- + // status + //----------------------------------------------------------------------------------------------------------------- + /** + * 카카오 내문서함 상태 조회(bulks) + * @param sndngProcessSttus + * @return + */ + @Override + @Transactional + public void findKkoMyDocStatusBulks(final String sndngProcessSttus) { +/* + final List docsBinderUuids = mapper.selectKakaoStatusTgts(sndngProcessSttus); + + List> apiResults; + List> partitions = ListUtils.partition(docsBinderUuids, bulkMaxCnt); + + if(isAsync) { + List>> apiAsyncResults = partitions.stream() + .map(uuids -> + asyncService.findBulkStatus( + KkopayDocBulkDTO.BulkStatusRequests.builder() + .document_binder_uuids(uuids) + .build()) + ) + .collect(Collectors.toList()); + + apiResults = CompletableFuture.allOf(apiAsyncResults.toArray(new CompletableFuture[0])) + .thenApply(Void -> + apiAsyncResults.stream() + .map(CompletableFuture::join) + .collect(Collectors.toList())) + .join(); + }else{ + apiResults = partitions.stream() + .map(uuids -> + service.findBulkStatus( + KkopayDocBulkDTO.BulkStatusRequests.builder() + .document_binder_uuids(uuids) + .build()) + ).collect(Collectors.toList()); + } + + List resList = apiResults.stream() + .map(ApiResponseDTO::getData) + .collect(Collectors.toList()); + + // 결과 반영 + resList.forEach(o -> + o.getDocuments().forEach(t -> { + mapper.updateKakaoStatusInfo(t); + mapper.updateCntcSndngResult(CntcDTO.SndngResult.builder() + .documentBinderUuid(t.getDocument_binder_uuid()) + .sndngResultSttus(StringUtils.defaultString(String.valueOf(t.getStatus_data().getDoc_box_status()), "FAIL")) + .requstDt(Checks.isEmpty(t.getStatus_data().getDoc_box_sent_at())? null: t.getStatus_data().getDoc_box_sent_at().toString()) + .inqireDt(Checks.isEmpty(t.getStatus_data().getDoc_box_received_at())? null: t.getStatus_data().getDoc_box_received_at().toString()) + .readngDt(Checks.isEmpty(t.getStatus_data().getDoc_box_read_at())? null: t.getStatus_data().getDoc_box_read_at().toString()) + .errorCn(t.getError_message()) + .build()); + }) + ); +*/ + + } + //----------------------------------------------------------------------------------------------------------------- + // status + //----------------------------------------------------------------------------------------------------------------- + + + //----------------------------------------------------------------------------------------------------------------- + // close + //----------------------------------------------------------------------------------------------------------------- + /** + * send-ok 값에 따라 close + * @param sndngProcessSttus + */ + @Override + @Transactional + public void close(final String sndngProcessSttus) { + final List list = mapper.selectCloseTgts(sndngProcessSttus); + + // 연계발송 마스터(tb_cntc_sndng_mastr) 상태 변경 : send-ok -> close + for(String id : list) { + EnsDTO.SndngMssageParam paramDTO = EnsDTO.SndngMssageParam.builder() + .unitySndngMastrId(id) + .sndngProcessSttus(sndngProcessSttus) + .newSndngProcessSttus(ApiConstants.SndngProcessStatus.CLOSE.getCode()) + .build(); + + if(mapper.updateProcessSttusCntcSndngMst(paramDTO) != 1) throw BizRuntimeException.create("[close]연계 발송 마스터 상태변경 실패"); + if(mapper.updateProcessSttusUnitySndngMst(paramDTO) != 1) throw BizRuntimeException.create("[close]통합 발송 마스터 상태변경 실패"); + // 2차 이상 발송 시 발송 마스터가 1개 이상 존재 + if(mapper.updateProcessSttusBulkSndngMst(paramDTO) < 1) throw BizRuntimeException.create("[close]발송 마스터 상태변경 실패"); + } + } + //----------------------------------------------------------------------------------------------------------------- + // close + //----------------------------------------------------------------------------------------------------------------- + + /** + * 에러 발생시 마스터 테이블 처리 + * @param dto EnsDTO.SndngMssageParam + * @see kr.xit.ens.support.batch.task.cmm.TaskCmmUtils#taskEnsMessageLinkServiceUpdateErrorLog(String, String, String) + */ + public void updateErrorLog(EnsDTO.SndngMssageParam dto) { + if ("SndngAcceptJob".equals(dto.getErrorCode())) { + mapper.updateProcessSttusCntcSndngMst(dto); + } else if ("SndngMakeJob".equals(dto.getErrorCode())){ + mapper.updateProcessSttusCntcSndngMst(dto); + mapper.updateProcessSttusUnitySndngMst(dto); + } else if ("SndngSendBulksJob".equals(dto.getErrorCode())) { + mapper.updateProcessSttusCntcSndngMst(dto); + mapper.updateProcessSttusUnitySndngMst(dto); + mapper.updateProcessSttusSndngMst(dto); + } + } + + + /** + * 카카오 내문서함 발송요청 대상 조회(bulks) + * - + * @param sndngProcessSttus + * @return + * + */ + @Override + public List findKakaoSendTargets(final String sndngProcessSttus) { + return mapper.selectKakaoSendTgts(sndngProcessSttus); + } + + +} diff --git a/mens-batch/src/main/java/kr/xit/biz/ens/web/ApiCallTestController.java b/mens-batch/src/main/java/kr/xit/biz/ens/web/ApiCallTestController.java new file mode 100644 index 0000000..e9be764 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/ens/web/ApiCallTestController.java @@ -0,0 +1,246 @@ +package kr.xit.biz.ens.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO; +import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.BulkSendReq; +import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.BulkSendRequests; +import kr.xit.biz.ens.model.kakao.KkopayDocBulkDTO.BulkSendResponses; +import kr.xit.biz.ens.model.kakao.KkopayDocDTO; +import kr.xit.biz.ens.model.kakao.KkopayDocDTO.SendResponse; +import kr.xit.biz.ens.service.ISendMessageLinkService; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.core.spring.util.ApiWebClient; +import kr.xit.core.support.utils.JsonUtils; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.ens.web
+ * fileName    : SendMessageLinkController
+ * author      : limju
+ * date        : 2023-06-05
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-05    limju       최초 생성
+ *
+ * 
+ */ +@Tag(name = "ApiCallTestController", description = "전자고지 통합발송 연계 서비스(배치) 테스트") +@RequiredArgsConstructor +@RestController +@RequestMapping(value = "/batch/ens/test/v1") +public class ApiCallTestController { + private final ApiWebClient apiWebClient; + private static final String SNDNG_PROCESS_STTUS = "sndngProcessSttus"; + + private final ISendMessageLinkService service; + + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO + * @return ResponseEntity + */ + @Operation(summary = "문서발송 요청", description = "카카오페이 전자문서 서버로 문서발송 처리를 요청") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"document\": {\n" + + " \"title\": \"문서 제목\",\n" + + " \"read_expired_sec\": 3600,\n" + + " \"hash\": \"6EFE827AC88914DE471C621AE\",\n" + + " \"common_categories\": [\n" + + " \"NOTICE\"\n" + + " ],\n" + + " \"receiver\": {\n" + + " \"phone_number\": \"01093414345\",\n" + + " \"name\": \"김지호\",\n" + + " \"birthday\": \"19831218\",\n" + + " \"is_required_verify_name\": false\n" + + " },\n" + + " \"property\": {\n" + + " \"link\": \"http://ip:8081/api/kakaopay/v1/ott\",\n" + + " \"cs_number\": \"02-123-4567\",\n" + + " \"cs_name\": \"콜센터\",\n" + + " \"payload\": \"payload 파라미터 입니다.\",\n" + + " \"message\": \"해당 안내문은 다음과 같습니다.\"\n" + + " }\n" + + " }}") + }) + }) + @PostMapping(value = "/documents", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO requestSend( + @RequestBody final KkopayDocDTO.SendRequest reqDTO + ) { + //return service.requestSend(reqDTO); + + StringBuilder url = new StringBuilder() + .append("http://localhost:8081") + .append("/api/kakaopay/v1/documents"); + + return + ApiResponseDTO.success(apiWebClient.exchange(url.toString(), + HttpMethod.POST, + JsonUtils.toJson(reqDTO), + SendResponse.class)); + } + + + /** + *
+     * 모바일웹 연계 문서발송 요청
+     * -.이용기관 서버에서 전자문서 서버로 문서발송 처리를 요청합니다.
+     * 
+ * @param reqDTO KkopayDocBulkDTO.BulkSendRequests + * @return ApiResponseDTO + */ + @Operation(summary = "대량 문서발송 요청", description = "카카오페이 전자문서 서버로 대량 문서발송 처리를 요청") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\n" + + " \"documents\": [\n" + + " {\n" + + " \"title\": \"문서 제목\",\n" + + " \"read_expired_sec\": 3600,\n" + + " \"hash\": \"6EFE827AC88914DE471C621AE\",\n" + + " \"common_categories\": [\n" + + " \"NOTICE\"\n" + + " ],\n" + + " \"receiver\": {\n" + + " \"phone_number\": \"01093414345\",\n" + + " \"name\": \"김지호\",\n" + + " \"birthday\": \"19831218\",\n" + + " \"is_required_verify_name\": false\n" + + " },\n" + + " \"property\": {\n" + + " \"link\": \"http://ip:8081/api/kakaopay/v1/ott\",\n" + + " \"cs_number\": \"02-123-4567\",\n" + + " \"cs_name\": \"콜센터\",\n" + + " \"payload\": \"payload 파라미터 입니다.\",\n" + + " \"message\": \"해당 안내문은 다음과 같습니다.\",\n" + + " \"external_document_uuid\": \"A000001\"\n" + + " }\n" + + " }\n" + + " ]\n" + + "}") + }) + }) + @PostMapping(value = "/documents/bulk", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO requestSendBulk( + @RequestBody final KkopayDocBulkDTO.BulkSendRequests reqDTO + ) { + //return service.requestSendBulk(reqDTO); + StringBuilder url = new StringBuilder() + .append("http://localhost:8081") + .append("/api/kakaopay/v1/documents/bulk"); + + + ApiResponseDTO responseDTO = apiWebClient.exchange(url.toString(), + HttpMethod.POST, + JsonUtils.toJson(reqDTO), + BulkSendResponses.class); + + return + ApiResponseDTO.success(apiWebClient.exchange(url.toString(), + HttpMethod.POST, + JsonUtils.toJson(reqDTO), + BulkSendResponses.class)); + } + + + + + + + + + + + @Operation(summary = "통합문서 접수", description = "통합문서 접수") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"accept\"}") + }) + }) + @PostMapping(value = "/accept", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO accept(@RequestBody final Map map) { + service.accept(map.get(SNDNG_PROCESS_STTUS)); + return ApiResponseDTO.success(); + } + + @Operation(summary = "통합문서 발송 데이타 생성", description = "통합문서 발송데이타 생성") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"accept-ok\"}") + }) + }) + @PostMapping(value = "/make", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO make(@RequestBody final Map map){ + service.make(map.get(SNDNG_PROCESS_STTUS)); + return ApiResponseDTO.success(); + } + + @Operation(summary = "통합문서 발송 데이타 종료", description = "통합문서 발송 데이타 종료") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"send-ok\"}") + }) + }) + @PostMapping(value = "/close", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO close(@RequestBody final Map map){ + service.close(map.get(SNDNG_PROCESS_STTUS)); + return ApiResponseDTO.success(); + } + + @Operation(summary = "통합문서(bulk) 발송 요청", description = "통합문서 발송 요청") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"make-ok\"}") + }) + }) + @PostMapping(value = "/sendBulks", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO sendBulks(@RequestBody final Map map){ + service.sendBulks(map.get(SNDNG_PROCESS_STTUS)); + return ApiResponseDTO.success(); + } + + @Operation(summary = "통합문서(bulk) 상태 조회", description = "통합문서 상태 조회") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"send-ok\"}") + }) + }) + @PostMapping(value = "/findStatusBulks", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO findStatusBulks(@RequestBody final Map map){ + service.findKkoMyDocStatusBulks(map.get(SNDNG_PROCESS_STTUS)); + return ApiResponseDTO.success(); + } + + + @Operation(summary = "통합 문서발송 목록 조회", description = "통합 문서발송 목록 조회") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"make-ok\"}") + }) + }) + @PostMapping(value = "/findKakaoSendTargets", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO findkakaoSendTargets(@RequestBody final Map map){ + return ApiResponseDTO.success(service.findKakaoSendTargets(map.get(SNDNG_PROCESS_STTUS))); + } +} diff --git a/mens-batch/src/main/java/kr/xit/biz/ens/web/EnsCctvFileController.java b/mens-batch/src/main/java/kr/xit/biz/ens/web/EnsCctvFileController.java new file mode 100644 index 0000000..0953942 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/ens/web/EnsCctvFileController.java @@ -0,0 +1,49 @@ +package kr.xit.biz.ens.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.xit.biz.ens.service.IEnsCctvFileService; +import kr.xit.biz.pni.service.IPniCctvFileService; +import kr.xit.core.model.ApiResponseDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.pni.web
+ * fileName    : PniCctvFileController
+ * author      : limju
+ * date        : 2023-07-07
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-07    limju       최초 생성
+ *
+ * 
+ */ +@Tag(name = " EnsCctvFileController", description = "전자고지 CCTV 파일 연계 서비스") +@RequiredArgsConstructor +@RestController +@RequestMapping("/batch/ens/v1") +public class EnsCctvFileController { + private final IEnsCctvFileService service; + + @Operation(summary = "전자고지 파일 연계 데이타 생성(서광)", description = "전자고지 파일 연계 데이타 생성(서광)") + @PostMapping(value = "/createCctvFileOfSg", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO createCctvFileOfSg() { + service.createCctvFileOfSg(); + return ApiResponseDTO.success(); + } + + @Operation(summary = "전자고지 연계 데이타 생성(accept)", description = "전자고지 연계 데이타 생성(accept)") + @PostMapping(value = "/acceptEnsNtnccntcSndng", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO acceptEnsNtnccntcSndng() { + service.acceptEnsNtnccntcSndng(); + return ApiResponseDTO.success(); + } +} diff --git a/mens-batch/src/main/java/kr/xit/biz/ens/web/SendMessageLinkController.java b/mens-batch/src/main/java/kr/xit/biz/ens/web/SendMessageLinkController.java new file mode 100644 index 0000000..bba7d51 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/ens/web/SendMessageLinkController.java @@ -0,0 +1,113 @@ +package kr.xit.biz.ens.web; + +import java.util.Map; + +import kr.xit.biz.ens.model.kakao.KkopayDocDTO; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.xit.biz.ens.service.ISendMessageLinkService; +import kr.xit.core.model.ApiResponseDTO; +import lombok.RequiredArgsConstructor; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.ens.web
+ * fileName    : SendMessageLinkController
+ * author      : limju
+ * date        : 2023-06-05
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-05    limju       최초 생성
+ *
+ * 
+ */ +@Tag(name = "SendMessageLinkController", description = "전자고지 통합발송 연계 서비스(배치) 테스트") +@RequiredArgsConstructor +@RestController +@RequestMapping(value = "/batch/ens/v1") +public class SendMessageLinkController { + private static final String SNDNG_PROCESS_STTUS = "sndngProcessSttus"; + + private final ISendMessageLinkService service; + + @Operation(summary = "통합문서 접수", description = "통합문서 접수") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"accept\"}") + }) + }) + @PostMapping(value = "/accept", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO accept(@RequestBody final Map map) { + service.accept(map.get(SNDNG_PROCESS_STTUS)); + return ApiResponseDTO.success(); + } + + @Operation(summary = "통합문서 발송 데이타 생성", description = "통합문서 발송데이타 생성") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"accept-ok\"}") + }) + }) + @PostMapping(value = "/make", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO make(@RequestBody final Map map){ + service.make(map.get(SNDNG_PROCESS_STTUS)); + return ApiResponseDTO.success(); + } + + @Operation(summary = "통합문서 발송 데이타 종료", description = "통합문서 발송 데이타 종료") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"send-ok\"}") + }) + }) + @PostMapping(value = "/close", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO close(@RequestBody final Map map){ + service.close(map.get(SNDNG_PROCESS_STTUS)); + return ApiResponseDTO.success(); + } + + @Operation(summary = "통합문서(bulk) 발송 요청", description = "통합문서 발송 요청") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"make-ok\"}") + }) + }) + @PostMapping(value = "/sendBulks", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO sendBulks(@RequestBody final Map map){ + service.sendBulks(map.get(SNDNG_PROCESS_STTUS)); + return ApiResponseDTO.success(); + } + + @Operation(summary = "통합문서(bulk) 상태 조회", description = "통합문서 상태 조회") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"send-ok\"}") + }) + }) + @PostMapping(value = "/findStatusBulks", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO findStatusBulks(@RequestBody final Map map){ + service.findKkoMyDocStatusBulks(map.get(SNDNG_PROCESS_STTUS)); + return ApiResponseDTO.success(); + } + + + @Operation(summary = "통합 문서발송 목록 조회", description = "통합 문서발송 목록 조회") + @io.swagger.v3.oas.annotations.parameters.RequestBody(required = true, content = { + @Content(mediaType = "application/json", examples = { + @ExampleObject(value = "{\"sndngProcessSttus\":\"make-ok\"}") + }) + }) + @PostMapping(value = "/findKakaoSendTargets", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO findkakaoSendTargets(@RequestBody final Map map){ + return ApiResponseDTO.success(service.findKakaoSendTargets(map.get(SNDNG_PROCESS_STTUS))); + } +} diff --git a/mens-batch/src/main/java/kr/xit/biz/package-info.java b/mens-batch/src/main/java/kr/xit/biz/package-info.java new file mode 100644 index 0000000..74eaffc --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/package-info.java @@ -0,0 +1,12 @@ +/** + * ENS business packages + *

+ * 전자고지 : ens + * 사전알림 : pni + * sms + *

+ * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.biz; diff --git a/mens-batch/src/main/java/kr/xit/biz/pni/mapper/IPniCctvFileMapper.java b/mens-batch/src/main/java/kr/xit/biz/pni/mapper/IPniCctvFileMapper.java new file mode 100644 index 0000000..b53d7a4 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/pni/mapper/IPniCctvFileMapper.java @@ -0,0 +1,32 @@ +package kr.xit.biz.pni.mapper; + +import kr.xit.biz.pni.model.PniDTO; +import org.egovframe.rte.psl.dataaccess.mapper.Mapper; + +import java.util.List; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.pni.mapper
+ * fileName    : IPniCctvFileMapper
+ * author      : limju
+ * date        : 2023-07-07
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-07    limju       최초 생성
+ *
+ * 
+ */ +@Mapper +public interface IPniCctvFileMapper { + int selectLicense(String jobSeCode); + int insertNtcnCntcData(PniDTO.NtcnCntcData dto); + + List selectPniNtncCntcSndngs(); + int insertCntcSndngMst(PniDTO.PniNtncCntcSndngTgts dto); + int insertCntcSndngDtl(PniDTO.PniNtncCntcSndngTgts dto); + int updatePniNtcnCntcData(PniDTO.PniNtncCntcSndngTgts dto); +} diff --git a/mens-batch/src/main/java/kr/xit/biz/pni/model/PniDTO.java b/mens-batch/src/main/java/kr/xit/biz/pni/model/PniDTO.java new file mode 100644 index 0000000..ac168a1 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/pni/model/PniDTO.java @@ -0,0 +1,164 @@ +package kr.xit.biz.pni.model; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import kr.xit.biz.common.ApiConstants; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + *
+ * description : tb_pni_ Entity DTO
+ *
+ * packageName : kr.xit.biz.pni.model
+ * fileName    : PniDTO
+ * author      : limju
+ * date        : 2023-07-07
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-07    limju       최초 생성
+ *
+ * 
+ */ +public class PniDTO { + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class NtcnCntcData implements Serializable { + /** + * 사전 알림 연계 data id + */ + private String ntcnCntcDataId; + /** + * 단속 일시 + */ + private String regltDt; + /** + * 차량 번호 + */ + private String vhcleNo; + /** + * 현장 명 + */ + private String sptNm; + /** + * 현장 별 코드 + */ + private String sptAcctoCode; + /** + * 파일 명 + */ + private String fileNm; + /** + * 처리 여부 + */ + private String processAt; + /** + * 대상 여부 + */ + private String trgetAt; + /** + * 통합 발송 상세 id + */ + private String unitySndngDetailId; + + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss") + private LocalDateTime registDt; + /** + * 등록자 + */ + private String register; + + } + + + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class PniNtncCntcSndngTgts implements Serializable { + /** + * 사전 알림 연계 data id + */ + private String ntcnCntcDataId; + /** + * 차량 번호 + */ + private String vhcleNo; + /** + * 처리 여부 + */ + private String processAt; + /** + * 대상여부 + */ + private String trgetAt; + /** + * 사전알림대상ID + */ + private String ntcnTrgetId; + /** + * 핸드폰 번호 + */private String moblphonNo; + + /** + * 이름 + */ + private String nm; + /** + * 생년월일 + */ + private String brthdy; + /*삭제여부*/ + private String deleteAt; + /** + * 발송건수 + */ + private int sndngCo; + /** + * 발송유형코드 + */ + @Builder.Default + private String sndngTyCode = "PNI"; + /** + * 템플릿 ID + */ + private String tmplatId; + /** + * 사전알림 데이타 생성 대상 여부 + */ + private String tgtYn; + + /** + * 통합발송 마스터ID + */ + private String unitySndngMastrId; + /** + * 통합발송 상세ID + */ + private String unitySndngDetailId; + /** + * 발송처리상태 + */ + @Builder.Default + private String sndngProcessSttus = ApiConstants.SndngProcessStatus.ACCEPT.getCode(); + /** + * 템플릿메세지 데이타 + */ + private String tmpltMsgData; + /** + * 모바일 페이지 내용 + */ + private String mobilePageCn; + } +} diff --git a/mens-batch/src/main/java/kr/xit/biz/pni/service/IPniCctvFileService.java b/mens-batch/src/main/java/kr/xit/biz/pni/service/IPniCctvFileService.java new file mode 100644 index 0000000..5ad24ed --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/pni/service/IPniCctvFileService.java @@ -0,0 +1,28 @@ +package kr.xit.biz.pni.service; + +/** + *
+ * description : 사전알림 파일 서비스
+ *
+ * packageName : kr.xit.biz.pni.service
+ * fileName    : IPniCctvFileService
+ * author      : limju
+ * date        : 2023-07-07
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-07    limju       최초 생성
+ *
+ * 
+ */ +public interface IPniCctvFileService { + /** + * 사전알림 cctv 단속 자료 생성(서광) + */ + void createCctvFileOfSg(); + + /** + * 사전알림 accept 연계발송 데이타 생성 + */ + void acceptPniNtnccntcSndng(); +} diff --git a/mens-batch/src/main/java/kr/xit/biz/pni/service/PniCctvFileService.java b/mens-batch/src/main/java/kr/xit/biz/pni/service/PniCctvFileService.java new file mode 100644 index 0000000..a7676e6 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/pni/service/PniCctvFileService.java @@ -0,0 +1,239 @@ +package kr.xit.biz.pni.service; + +import com.jcraft.jsch.SftpATTRS; +import com.jcraft.jsch.SftpException; +import kr.xit.biz.pni.mapper.IPniCctvFileMapper; +import kr.xit.biz.pni.model.PniDTO; +import kr.xit.core.exception.BizRuntimeException; +import kr.xit.core.support.utils.DateUtils; +import kr.xit.core.support.utils.SFTPUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + *
+ * description : 사전알림 CCTV 파일 서비스
+ *
+ * packageName : kr.xit.biz.pni.service
+ * fileName    : PniCctvFileService
+ * author      : limju
+ * date        : 2023-07-07
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-07    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@RequiredArgsConstructor +@Service +public class PniCctvFileService extends EgovAbstractServiceImpl implements IPniCctvFileService { + + @Value("${app.ssh.host}") private String host; + @Value("${app.ssh.port}") private int port; + @Value("${app.ssh.id}") private String id; + @Value("${app.ssh.passwd}") private String passwd; + + @Value("${app.ssh.sg.root-path}") private String rootPath; + @Value("${app.ssh.sg.rcv}") private String rcvPath; + @Value("${app.ssh.sg.backup}") private String backupPath; + @Value("${app.ssh.sg.err}") private String errPath; + + private final IPniCctvFileMapper mapper; + + /** + * 사전알림 cctv 단속 자료 생성(서광) + */ + @Override + @Transactional + public void createCctvFileOfSg() { + + // 사전알림 라이선스 유효성 체크 + int licenseCnt = mapper.selectLicense("PNI"); + if(licenseCnt == 0) return; + + SFTPUtils sftp = null; + + try { + // SFTP connect + sftp = new SFTPUtils(); + sftp.init(host, port, id, passwd, StringUtils.EMPTY); + + // 서광 CCTV 사전알림 대상 조회 + final String srcPath = rootPath + rcvPath; + ArrayList fileNameList = sftp.findFileNameList(srcPath); + //if(fileNameList.size() == 0) throw BizRuntimeException.create("사전고지[서광] 처리 대상이 없습니다"); + + // 에러 파일 목록 + ArrayList errFileList = new ArrayList<>(); + // 성공 파일 목록 + ArrayList rtnFileList = new ArrayList<>(); + for (String fn : fileNameList) { + String[] arrFi = fn.split("_"); + + // 파일 이름 정보 오류시 + if (arrFi.length != 4 || arrFi[0].length() != 14) { + errFileList.add(fn); + continue; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // FIXME :: GS 임시 소스 + // 파일 다운로드 + final String downLoadPath = "D:/ImageData/PNI" + "/" + DateUtils.getToday(StringUtils.EMPTY); + + File Folder = new File(downLoadPath); + if (!Folder.exists()) Folder.mkdir(); //폴더 생성합니다. + sftp.fileDownload(srcPath + "/" + fn, downLoadPath); + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // FIXME : 중복데이타(단속시간 + 차량번호)인 경우 데이타 skip 하도록 해야 할 듯 + // 사전알림 연계 데이타 생성 + mapper.insertNtcnCntcData( + PniDTO.NtcnCntcData.builder() + .regltDt(arrFi[0]) + .vhcleNo(arrFi[1]) + .sptNm(arrFi[2]) + .sptAcctoCode(arrFi[3].split("\\.")[0]) + .fileNm(fn) + .build() + ); + rtnFileList.add(fn); + + // FIXME :: sftp rm 기능 추후 제거 + // 파일 삭제 + sftp.rm(fn); + } + + // FIXME :: sftp move 기능 추후 사용 + // 처리한 데이타 backup + //fileBackup(sftp, srcPath, errFileList, rtnFileList); + + } finally { + if(sftp != null) sftp.disconnect(); + } + } + + /** + * 사전알림 accept 연계발송 데이타 생성 + * 1. 사전알림 연계 발송 데이타 생성 대상 조회 + * 2. 사전알림 연계 발송 마스터 생성 + * 3. 사전알림 연계 발송 상세 생성 + * 4. 사전알림 연계 결과 반영 + */ + @Override + @Transactional + public void acceptPniNtnccntcSndng() { + List tgtList = mapper.selectPniNtncCntcSndngs(); + + if(tgtList.size() == 0) return; + + // search 사전알림 연계발송 SMS 데이타 생성 대상 + Optional sms = tgtList.stream() + .filter(dto -> "JU201".equals(dto.getTmplatId())) + .filter(dto -> "Y".equals(dto.getTgtYn())) + .findFirst(); + + // search 사전알림 연계발송 카카오 알림톡 데이타 생성 대상 + Optional kkoMyDoc = tgtList.stream() + .filter(dto -> "JU202".equals(dto.getTmplatId())) + .filter(dto -> "Y".equals(dto.getTgtYn())) + .findFirst(); + + // 사전알림 연계발송 master 생성 + String smsUnitySndngMastrId; + String kkoUnitySndngMastrId; + String unitySndngMastrId = null; + + // sms 대상 있는지 확인해서 있으면 연계발송 master 생성 + if(sms.isPresent()){ + PniDTO.PniNtncCntcSndngTgts tgtDTO = sms.get(); + mapper.insertCntcSndngMst(tgtDTO); + smsUnitySndngMastrId = tgtDTO.getUnitySndngMastrId(); + MDC.put("unitySndngMastrId", smsUnitySndngMastrId); + } else { + smsUnitySndngMastrId = null; + } + + // 카카오 알림톡 대상 있는지 확인해서 있으면 연계발송 master 생성 + if(kkoMyDoc.isPresent()){ + PniDTO.PniNtncCntcSndngTgts tgtDTO = kkoMyDoc.get(); + mapper.insertCntcSndngMst(tgtDTO); + kkoUnitySndngMastrId = tgtDTO.getUnitySndngMastrId(); + MDC.put("unitySndngMastrId", kkoUnitySndngMastrId); + } else { + kkoUnitySndngMastrId = null; + } + + // 사전알림 연계발송 상세 생성 및 결과 처리(사전알림 연계 데이타 반영) + // 사전알림 대상 -> 사전알림 sms / 카카오 알림톡 연계 발송 상세 생성 + // 사전알림 연계 데이타 -> 연계 결과 반영 + // : 대상 : 연계대상-Y, 진행상태-Y, 통합발송상세ID + // 미대상 : 연계대상-N, 진행상태-Y, 통합발송상세ID = null + tgtList.forEach(dto -> { + dto.setProcessAt("Y"); + if("Y".equals(dto.getTgtYn())){ + // sms / 카카오 알림톡 템플릿메시지 data 생성 + StringBuffer strTmpltMsgData = new StringBuffer(); + strTmpltMsgData.append("[주정차 단속 사전 알림]"); + strTmpltMsgData.append ( "\n" ); + strTmpltMsgData.append("CCTV 주정차 단속 지역입니다."); + strTmpltMsgData.append ( "\n" ); + strTmpltMsgData.append(dto.getVhcleNo() + " 차량은 즉시 이동바랍니다."); + dto.setTmpltMsgData(strTmpltMsgData.toString()); + + // FIXME :: GS 인증 시험 알림톡이 안되어서 인증톡으로 대체 알림톡 운영 시 필요 없음 + // 카카오 알림톡 모바일 페이지 문구 + StringBuffer strmobilePageCn = new StringBuffer(); + strmobilePageCn.append("{"); + strmobilePageCn.append("\"details\" : ["); + strmobilePageCn.append( "{"); + strmobilePageCn.append( "\"title\" : \"주정차 단속 사전 알림\","); + strmobilePageCn.append ( "\"item_type\" : \"PRE_TEXT\","); + strmobilePageCn.append ( "\"elements\" : \"CCTV 주정차 단속 지역입니다.\\n"+ dto.getVhcleNo() + " 차량은 즉시 이동바랍니다.\\n- 서광시스템\""); + strmobilePageCn.append( "}"); + strmobilePageCn.append( "]"); + strmobilePageCn.append("}"); + dto.setMobilePageCn(strmobilePageCn.toString()); + + if("JU201".equals(dto.getTmplatId())) dto.setUnitySndngMastrId(smsUnitySndngMastrId); + else if("JU202".equals(dto.getTmplatId()))dto.setUnitySndngMastrId(kkoUnitySndngMastrId); + mapper.insertCntcSndngDtl(dto); + } + mapper.updatePniNtcnCntcData(dto); + }); + + } + + private void fileBackup(SFTPUtils sftp, String srcPath, ArrayList errFileList, ArrayList rtnFileList) { + final String errorPath = rootPath + errPath; + String destPath = rootPath + backupPath; + destPath = destPath + "/" + DateUtils.getToday(StringUtils.EMPTY); + + log.info("src path::[{}]", srcPath); + log.info("tgt path::[{}]", destPath); + + // file backup + for (String fn : rtnFileList) { + log.info("fileName::[{}]", fn); + sftp.mv(srcPath + "/" + fn, destPath + "/" + fn); + } + + // error file backup + for (String fn : errFileList) { + sftp.mv(srcPath + "/" + fn, errorPath + "/" + fn); + } + } +} diff --git a/mens-batch/src/main/java/kr/xit/biz/pni/web/PniCctvFileController.java b/mens-batch/src/main/java/kr/xit/biz/pni/web/PniCctvFileController.java new file mode 100644 index 0000000..0ca780c --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/pni/web/PniCctvFileController.java @@ -0,0 +1,46 @@ +package kr.xit.biz.pni.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.xit.biz.pni.service.IPniCctvFileService; +import kr.xit.core.model.ApiResponseDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.pni.web
+ * fileName    : PniCctvFileController
+ * author      : limju
+ * date        : 2023-07-07
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-07-07    limju       최초 생성
+ *
+ * 
+ */ +@Tag(name = "PniCctvFileController", description = "사전고지 CCTV 파일 연계 서비스") +@RequiredArgsConstructor +@RestController +@RequestMapping("/batch/pni/v1") +public class PniCctvFileController { + private final IPniCctvFileService service; + + @Operation(summary = "사전고지 파일 연계 데이타 생성(서광)", description = "사전고지 파일 연계 데이타 생성(서광)") + @PostMapping(value = "/cctvFileOfSg", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO cctvFileOfSg() { + service.createCctvFileOfSg(); + return ApiResponseDTO.success(); + } + + @Operation(summary = "사전고지 연계 데이타 생성(accept)", description = "사전고지 연계 데이타 생성(accept)") + @PostMapping(value = "/acceptPniNtnccntcSndng", produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponseDTO acceptPniNtnccntcSndng() { + service.acceptPniNtnccntcSndng(); + return ApiResponseDTO.success(); + } +} diff --git a/mens-batch/src/main/java/kr/xit/biz/sms/mapper/ISmsMessageMapper.java b/mens-batch/src/main/java/kr/xit/biz/sms/mapper/ISmsMessageMapper.java new file mode 100644 index 0000000..265e7f5 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/sms/mapper/ISmsMessageMapper.java @@ -0,0 +1,29 @@ +package kr.xit.biz.sms.mapper; + +import org.egovframe.rte.psl.dataaccess.mapper.Mapper; + +/** + *
+ * description : SMS 발송(oracle DB)
+ *
+ * packageName : kr.xit.biz.sms.mapper
+ * fileName    : ISmsMessageMapper
+ * author      : limju
+ * date        : 2023-06-26
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-26    limju       최초 생성
+ *
+ * 
+ */ +@Mapper +public interface ISmsMessageMapper { + + /** + * sms 발송 데이타 생성 + * @param t status + * @return int + */ + int insertScTran(final T t); +} diff --git a/mens-batch/src/main/java/kr/xit/biz/sms/service/ISmsMessageService.java b/mens-batch/src/main/java/kr/xit/biz/sms/service/ISmsMessageService.java new file mode 100644 index 0000000..0505bb4 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/sms/service/ISmsMessageService.java @@ -0,0 +1,24 @@ +package kr.xit.biz.sms.service; + +import kr.xit.biz.ens.model.EnsDTO; + +import java.util.List; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.biz.sms.service
+ * fileName    : ISmsMessageService
+ * author      : limju
+ * date        : 2023-06-26
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-26    limju       최초 생성
+ *
+ * 
+ */ +public interface ISmsMessageService { + void sendSmsList(List list); +} diff --git a/mens-batch/src/main/java/kr/xit/biz/sms/service/SmsMessageService.java b/mens-batch/src/main/java/kr/xit/biz/sms/service/SmsMessageService.java new file mode 100644 index 0000000..b61c5bc --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/biz/sms/service/SmsMessageService.java @@ -0,0 +1,42 @@ +package kr.xit.biz.sms.service; + +import kr.xit.biz.sms.mapper.ISmsMessageMapper; +import lombok.RequiredArgsConstructor; +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + *
+ * description : xit SMS 발송 서비스 - Oracle 11
+ *
+ * packageName : kr.xit.biz.sms.service
+ * fileName    : SmsMessageService
+ * author      : limju
+ * date        : 2023-06-26
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-26    limju       최초 생성
+ *
+ * 
+ */ +@RequiredArgsConstructor +@Service +public class SmsMessageService extends EgovAbstractServiceImpl implements ISmsMessageService { + + private final ISmsMessageMapper mapper; + + /** + * SMS 발송 - xit SMS 발송 테이블 데이타 생성 + * @param list List SMS 발송 목록 + */ + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void sendSmsList(List list) { + for(T t : list) mapper.insertScTran(t); + } +} diff --git a/mens-batch/src/main/java/kr/xit/core/aop/TraceLoggerAspect.java b/mens-batch/src/main/java/kr/xit/core/aop/TraceLoggerAspect.java new file mode 100644 index 0000000..5809b25 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/aop/TraceLoggerAspect.java @@ -0,0 +1,260 @@ +package kr.xit.core.aop; + +import java.util.Arrays; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import kr.xit.core.exception.BizRuntimeException; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.core.biz.model.LoggingDTO; +import kr.xit.core.biz.service.ILoggingService; +import kr.xit.core.spring.util.error.ErrorParse; +import kr.xit.core.support.slack.SlackWebhookPush; +import kr.xit.core.support.utils.Checks; +import kr.xit.core.support.utils.JsonUtils; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : logging trace aspect
+ *               공통 core 모듈의 LoggerAspect 상속 -> traceLogging / traceLoggingError / traceLoggingResult 구현
+ *
+ *               Logging trace 구현시
+ *               - MDC(Mapped Diagnostic Context : logback, log4j에 포함) 사용 로깅
+ *               - ThreadLocal 사용
+ *               - nginx : proxy_set_header X-RequestID $request_id;
+ *               - logback log pattern : [traceId=%X{request_id}]
+ *
+ *               app.slack-webhook.enabled: true인 경우 slack push
+ *               Slack webhook : SlackWebhookPush
+ *
+ * packageName : kr.xit.core.aop
+ * fileName    : TraceLoggerAspect
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ * 2023-06-12    julim       배치처리시 RequestContextHolder.HttpServletRequest 객체 미사용에 따른 처리 추가
+ * 
+ * @see kr.xit.core.support.slack.SlackWebhookPush#sendSlackAlertLog(String, String, String) + */ + +@Slf4j +@Aspect +@Component +public class TraceLoggerAspect { + + @Value("${app.slack-webhook.enabled:false}") + private boolean isSlackEnabled; + + @Value("#{'${app.mdc.log.trace.uris}'.split(',')}") + private String[] uris; + + private final ILoggingService loggingService; + private final SlackWebhookPush slackWebhookPush; + private static final String MESSAGE = "message"; + private static final String CODE = "code"; + private static final String REQUEST_TRACE_ID = "request_trace_id"; + + public TraceLoggerAspect(@Lazy ILoggingService loggingService, SlackWebhookPush slackWebhookPush) { + this.loggingService = loggingService; + this.slackWebhookPush = slackWebhookPush; + } + + @Pointcut("execution(public * egovframework..*.*(..)) || execution(public * kr.xit..*.*(..))") + public void errorPointCut() { + } + + @Around(value = "@annotation(kr.xit.core.spring.annotation.TraceLogging)") + public Object serviceTraceLogging(final ProceedingJoinPoint pjp) throws Throwable { + ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = attributes != null? attributes.getRequest(): null; + + traceLogging(JsonUtils.toJson(pjp.getArgs()), request); + Object result = pjp.proceed(); + + if(result instanceof CompletableFuture){ + CompletableFuture future = (CompletableFuture)result; + + while(true) { + if (future.isDone()) break; + } + traceLoggingResult(future.get()); + }else{ + traceLoggingResult(result); + } + return result; + } + + + //@AfterThrowing(value = "@annotation(kr.xit.core.spring.annotation.TraceLogging)", throwing = "error") + @AfterThrowing(value = "errorPointCut()", throwing="error") + public void afterThrowingProceed(final JoinPoint jp, final Throwable error) { + traceLoggingError(jp, error); + } + + + /** + * 배치 실행시 여기에서 set한 MDC 값이 batch 모듈에서 reading이 불가하여, 배치 tasklet에서 set한 trace_id로 set + * @param params + * @param request + */ + protected void traceLogging(final String params, final HttpServletRequest request) { + if(request != null) { + String uri = request.getRequestURI().toString(); + if(Arrays.asList(uris).stream().anyMatch(regx -> uri.matches(regx))) return; + + MDC.put(REQUEST_TRACE_ID, + StringUtils.defaultString(MDC.get("request_trace_batch_id"), UUID.randomUUID().toString().replaceAll("/-/g", ""))); + MDC.put("method", request.getMethod()); + MDC.put("uri", uri); + MDC.put("ip", request.getRemoteAddr()); + MDC.put("sessionId", request.getSession().getId()); + + // batch로 실행되는 경우 task에서 설정 : uri / method + }else{ + // request_id는 중복 처리 되면 않되므로 여기에서 생성 + MDC.put(REQUEST_TRACE_ID, StringUtils.defaultString(MDC.get("request_trace_batch_id"), UUID.randomUUID().toString().replaceAll("-", ""))); + } +log.info("@@@@@@@@@@@@@@@@@로깅 start : [\n{}\n]",MDC.getCopyOfContextMap()); + MDC.put("systemId", "ENS"); + MDC.put("reqSystemId", "KAKAO"); + MDC.put("param", params); + MDC.put("accessToken", ""); + + LoggingDTO loggingDTO = LoggingDTO.builder() + .requestId(MDC.getCopyOfContextMap().get(REQUEST_TRACE_ID)) + .systemId("ENS") + .reqSystemId("KAKAO") + .method(MDC.getCopyOfContextMap().get("method")) + .uri(MDC.getCopyOfContextMap().get("uri")) + .param(params) + .ip(MDC.getCopyOfContextMap().get("ip")) + .accessToken("") + .sessionId(MDC.getCopyOfContextMap().get("sessionId")) + .build(); + loggingService.saveLogging(loggingDTO); + + } + + protected void traceLoggingResult(final Object result) { + String success = "true"; + //FIXME: slack webhook log push + if(result instanceof ApiResponseDTO){ + ApiResponseDTO apiResponseDTO = (ApiResponseDTO)result; + if(!apiResponseDTO.isSuccess()){ + success = "false"; + + if(isSlackEnabled) { + + if(RequestContextHolder.getRequestAttributes() != null) { + slackWebhookPush.sendSlackAlertLog( + String.format("[%s]%s", MDC.get(REQUEST_TRACE_ID), apiResponseDTO.getMessage()), + ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest()); + }else{ + slackWebhookPush.sendSlackAlertLog( + String.format("[%s]%s", MDC.get(REQUEST_TRACE_ID), apiResponseDTO.getMessage()), + MDC.get("uri"), + "batch call"); + } + } + }else{ + if(apiResponseDTO.getData() != null){ + log.info("~~~~"); + } + } + } + + LoggingDTO reqDTO = LoggingDTO + .builder() + .requestId(MDC.get(REQUEST_TRACE_ID)) + .success(success) + .response(getResult(result)) + .message(HttpStatus.OK.name()) + .build(); + //} + loggingService.modifyLogging(reqDTO); + //loggingService.saveLogging(reqDTO); + + log.info("@@@@@@@@@@@@@@로깅 end[\n{}\n]", MDC.getCopyOfContextMap()); + + //if(RequestContextHolder.getRequestAttributes() != null) + MDC.clear(); + //} + } + + private String getResult(final Object o){ + if(o instanceof Future) { + try { + Future future = (Future)o; + return JsonUtils.toJson(future.get()); + } catch (InterruptedException ie){ + // thread pool에 에러 상태 전송 + Thread.currentThread().interrupt(); + throw BizRuntimeException.create(ie); + + } catch (ExecutionException ee) { + throw BizRuntimeException.create(ee); + } + }else{ + return JsonUtils.toJson(o); + } + } + + protected void traceLoggingError(final JoinPoint jp, final Throwable e) { + log.info("~~~~~~~~~~~~~~~~~~~~~~~>>>>{}", MDC.get(REQUEST_TRACE_ID)); + if(Checks.isEmpty(MDC.get(REQUEST_TRACE_ID))) return; + + Map map = ErrorParse.extractError(e); + + loggingService.modifyLogging( + LoggingDTO + .builder() + .requestId(MDC.get(REQUEST_TRACE_ID)) + .success("false") + .response(JsonUtils.toJson(ApiResponseDTO.error(String.valueOf(map.get(CODE)), String.valueOf(map.get(MESSAGE)), (HttpStatus)map.get("httpStatus")))) + .message(String.valueOf(map.get(MESSAGE))) + .build()); + + //FIXME :: slack webhook log push + if(isSlackEnabled){ + if(RequestContextHolder.getRequestAttributes() != null) { + slackWebhookPush.sendSlackAlertLog( + String.format("[%s]%s(%s)", MDC.get(REQUEST_TRACE_ID), String.valueOf(map.get(CODE)), + String.valueOf(map.get(MESSAGE))), + ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest()); + }else{ + slackWebhookPush.sendSlackAlertLog( + String.format("[%s]%s(%s)", MDC.get(REQUEST_TRACE_ID), String.valueOf(map.get(CODE)), + String.valueOf(map.get(MESSAGE))), + MDC.get("uri"), + "batch call"); + } + } + log.info("@@@@@@@@@@@@ 로깅 end[\n{}\n]", MDC.getCopyOfContextMap()); + //if(RequestContextHolder.getRequestAttributes() != null) + MDC.clear(); + } +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/batch/CustomRunIdIncrementer.java b/mens-batch/src/main/java/kr/xit/core/biz/batch/CustomRunIdIncrementer.java new file mode 100644 index 0000000..5934565 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/batch/CustomRunIdIncrementer.java @@ -0,0 +1,38 @@ +package kr.xit.core.biz.batch; + +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.launch.support.RunIdIncrementer; + +/** + *
+ * description :  Spring Boot 2.0.x에서 발생하는 버그
+ *                => 배치 Job이 실패할 경우 이후에 파라미터를 변경해도 계속 실패한 파라미터가 사용
+ *                Spring Boot 2.1.0 이상부터는
+ *                => 이전의 Job이 실행시 사용한 파라미터 중 하나가 다음 실행시 누락되면 누락된 파라미터를 재사용
+ *                   --> 넘어온 파라미터와 run.id만 사용
+ *                  .incrementer(new RunIdIncrementer()) -> .incrementer(new UniqueRunIdIncrementer())
+ *
+ * packageName : kr.xit.core.biz.batch
+ * fileName    : CustomRunIdIncrementer
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ */ +public class CustomRunIdIncrementer extends RunIdIncrementer { + private static final String RUN_ID = "run.id"; + + @Override + public JobParameters getNext(JobParameters parameters) { + JobParameters params = (parameters == null) ? new JobParameters() : parameters; + return new JobParametersBuilder() + .addLong(RUN_ID, params.getLong(RUN_ID, 0L) + 1) + .toJobParameters(); + } +} + diff --git a/mens-batch/src/main/java/kr/xit/core/biz/batch/ListReader.java b/mens-batch/src/main/java/kr/xit/core/biz/batch/ListReader.java new file mode 100644 index 0000000..4eb7492 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/batch/ListReader.java @@ -0,0 +1,37 @@ +package kr.xit.core.biz.batch; + +import java.util.List; + +/** + *
+ * description : ItemReaderAdapter 를 사용한 List 데이타의 read() 구현
+ *               -> ItemReader 의 read
+ *               ItemReaderAdapter의 targetObject 지정시 사용
+ *               -> ItemReaderAdapter.setTargetObject(Object o)
+ *                  : new ListReader<>(대상목록-서비스 조회결과)
+ *                  : new ListReader<>(loggingService.findLogging(null))
+ * packageName : kr.xit.core.biz.batch
+ * fileName    : ListReader
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ */ +public class ListReader { + private List list; + + public ListReader(List list){ + this.list = list; + } + + public T read() { + if(list.isEmpty()){ + return null; + } + return list.remove(0); + } + } diff --git a/mens-batch/src/main/java/kr/xit/core/biz/batch/listener/CustomChunkListener.java b/mens-batch/src/main/java/kr/xit/core/biz/batch/listener/CustomChunkListener.java new file mode 100644 index 0000000..cfa4e91 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/batch/listener/CustomChunkListener.java @@ -0,0 +1,48 @@ +package kr.xit.core.biz.batch.listener; + +import org.springframework.batch.core.ChunkListener; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description :  Chunk 실행시 로그 출력
+ *
+ * packageName : kr.xit.core.biz.batch.listener
+ * fileName    : CustomChunkListener
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +@Component +public class CustomChunkListener { + + @Bean + public ChunkListener chunkListener() { + return new ChunkListener() { + @Override + public void beforeChunk(ChunkContext context) { + log.info("↓ Chunk start"); + } + + @Override + public void afterChunk(ChunkContext context) { + log.info("↑ Chunk end"); + } + + @Override + public void afterChunkError(ChunkContext context) { + // Do nothing + } + }; + } +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/batch/listener/CustomItemListner.java b/mens-batch/src/main/java/kr/xit/core/biz/batch/listener/CustomItemListner.java new file mode 100644 index 0000000..785edf1 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/batch/listener/CustomItemListner.java @@ -0,0 +1,29 @@ +package kr.xit.core.biz.batch.listener; + +import java.util.List; + +import org.springframework.batch.core.listener.ItemListenerSupport; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class CustomItemListner extends ItemListenerSupport { + + public void onReadError(Exception ex) { + log.error("onReadError", ex); + } + + @Override + public void onProcessError(Object item, Exception e) { + log.error("onProcessError", e); + super.onProcessError(item, e); + } + + @Override + public void onWriteError(Exception ex, List item) { + log.error("onWriteError", ex); + } + + + +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/batch/listener/CustomJobListener.java b/mens-batch/src/main/java/kr/xit/core/biz/batch/listener/CustomJobListener.java new file mode 100644 index 0000000..5af0a8f --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/batch/listener/CustomJobListener.java @@ -0,0 +1,41 @@ +package kr.xit.core.biz.batch.listener; + +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.listener.JobExecutionListenerSupport; + +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description :  Job 실행시 로그 출력
+ *
+ * packageName : kr.xit.core.biz.batch.listener
+ * fileName    : CustomJobListener
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +public class CustomJobListener extends JobExecutionListenerSupport { + + @Override + public void beforeJob(JobExecution jobExecution) { + log.info("\n"); + log.info("===================================================="); + log.info("========== ↓ [{}] Job start ===========", jobExecution.getJobInstance().getJobName()); + log.info("===================================================="); + } + + @Override + public void afterJob(JobExecution jobExecution) { + log.info("==============================================================="); + log.info("========== ↑ [{}] Job end :: {} ==========", jobExecution.getJobInstance().getJobName(), jobExecution.getStatus()); + log.info("==============================================================="); + log.info("\n"); + } +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/batch/listener/CustomStepListener.java b/mens-batch/src/main/java/kr/xit/core/biz/batch/listener/CustomStepListener.java new file mode 100644 index 0000000..3606a4a --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/batch/listener/CustomStepListener.java @@ -0,0 +1,40 @@ +package kr.xit.core.biz.batch.listener; + +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.listener.StepExecutionListenerSupport; + +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description :  step 실행시 로그 출력
+ *
+ * packageName : kr.xit.core.biz.batch.listener
+ * fileName    : CustomStepListener
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ */ +@Slf4j +public class CustomStepListener extends StepExecutionListenerSupport { + + @Override + public void beforeStep(StepExecution stepExecution) { + log.info("##### ↓ [{}] Step start", stepExecution.getStepName()); + //log.info("##### [{}-{}] Step is start", stepExecution.getJobExecution().getJobInstance().getJobName(), stepExecution.getStepName()); + } + + @Override + public ExitStatus afterStep(StepExecution stepExecution) { + log.info("##### ↑ [{}] Step end :: {}", stepExecution.getStepName(), stepExecution.getStatus()); + //log.info("##### [{}-{}] Step is completed {}", stepExecution.getJobExecution().getJobInstance().getJobName(), stepExecution.getStepName(), stepExecution.getStatus()); + return stepExecution.getExitStatus(); + } +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/batch/listener/NoWorkFoundStepListener.java b/mens-batch/src/main/java/kr/xit/core/biz/batch/listener/NoWorkFoundStepListener.java new file mode 100644 index 0000000..952dd4a --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/batch/listener/NoWorkFoundStepListener.java @@ -0,0 +1,16 @@ +package kr.xit.core.biz.batch.listener; + +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListenerSupport; + +public class NoWorkFoundStepListener extends StepExecutionListenerSupport { + + public ExitStatus afterStep(StepExecution stepExecution) { + if (stepExecution.getReadCount() == 0) { + return ExitStatus.FAILED; + } + return null; + } + +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/batch/mapper/IBatchCmmMapper.java b/mens-batch/src/main/java/kr/xit/core/biz/batch/mapper/IBatchCmmMapper.java new file mode 100644 index 0000000..092d1b3 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/batch/mapper/IBatchCmmMapper.java @@ -0,0 +1,32 @@ +package kr.xit.core.biz.batch.mapper; + +import java.util.Optional; + +import org.egovframe.rte.psl.dataaccess.mapper.Mapper; + +import kr.xit.core.biz.batch.model.BatchCmmDTO; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.core.biz.batch.mapper
+ * fileName    : IBatchCmmMapper
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ */ +@Mapper +public interface IBatchCmmMapper { + Optional selectBatchLockByInstanceId(final String instanceId); + int insertBatchLock(final BatchCmmDTO dto); + int updateBatchLock(final BatchCmmDTO dto); + + int insertBatchLog(final BatchCmmDTO dto); + int updateBatchLog(final BatchCmmDTO dto); +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/batch/model/BatchCmmDTO.java b/mens-batch/src/main/java/kr/xit/core/biz/batch/model/BatchCmmDTO.java new file mode 100644 index 0000000..5c3087d --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/batch/model/BatchCmmDTO.java @@ -0,0 +1,68 @@ +package kr.xit.core.biz.batch.model; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.core.biz.batch.model
+ * fileName    : BatchCmmDTO
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ * 2023-06-15    limju       배치로그 추가
+ *
+ * 
+ */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class BatchCmmDTO { + /** + * 배치 로그 ID + */ + private String batchLogId; + /** + * 배치 인스턴스 ID + */ + private String instanceId; + /** + * 배치 API trace ID + */ + private String traceId; + /** + * 실행 결과 + */ + private String result; + /** + * 에러메세지 + */ + private String message; + /** + * 배치 실행중 여부 + */ + private String useYn; + + //@Convert(converter = Jsr310.LocalDateTimeConverter.class) + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss.SSS") + private LocalDateTime registDt; + + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss.SSS") + private LocalDateTime updateDt; +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/batch/service/BatchCmmService.java b/mens-batch/src/main/java/kr/xit/core/biz/batch/service/BatchCmmService.java new file mode 100644 index 0000000..9ebefd9 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/batch/service/BatchCmmService.java @@ -0,0 +1,65 @@ +package kr.xit.core.biz.batch.service; + +import java.util.Optional; + +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import kr.xit.core.biz.batch.mapper.IBatchCmmMapper; +import kr.xit.core.biz.batch.model.BatchCmmDTO; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.core.biz.service
+ * fileName    : BatchLockService
+ * author      : limju
+ * date        : 2023-05-18
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-18    limju       최초 생성
+ *
+ * 
+ */ +@Service +public class BatchCmmService extends EgovAbstractServiceImpl implements IBatchCmmService { + private final IBatchCmmMapper mapper; + + public BatchCmmService(IBatchCmmMapper mapper) { + this.mapper = mapper; + } + + @Override + @Transactional(readOnly = true) + public Optional findById(final String instanceId) { + return mapper.selectBatchLockByInstanceId(instanceId); + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void addBatchLock(final BatchCmmDTO dto) { + mapper.insertBatchLock(dto); + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void modifyBatchLock(final BatchCmmDTO dto) { + mapper.updateBatchLock(dto); + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void addBatchLog(final BatchCmmDTO dto) { + mapper.insertBatchLog(dto); + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void modifyBatchLog(final BatchCmmDTO dto) { + mapper.updateBatchLog(dto); + } +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/batch/service/IBatchCmmService.java b/mens-batch/src/main/java/kr/xit/core/biz/batch/service/IBatchCmmService.java new file mode 100644 index 0000000..dbae56d --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/batch/service/IBatchCmmService.java @@ -0,0 +1,29 @@ +package kr.xit.core.biz.batch.service; + +import java.util.Optional; + +import kr.xit.core.biz.batch.model.BatchCmmDTO; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.core.biz.service
+ * fileName    : IBatchCmmService
+ * author      : limju
+ * date        : 2023-05-18
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-18    limju       최초 생성
+ *
+ * 
+ */ +public interface IBatchCmmService { + Optional findById(final String instanceId); + void addBatchLock(final BatchCmmDTO dto); + void modifyBatchLock(final BatchCmmDTO dto); + + void addBatchLog(final BatchCmmDTO dto); + void modifyBatchLog(final BatchCmmDTO dto); +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/batch/task/BatchEndTasklet.java b/mens-batch/src/main/java/kr/xit/core/biz/batch/task/BatchEndTasklet.java new file mode 100644 index 0000000..5641426 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/batch/task/BatchEndTasklet.java @@ -0,0 +1,57 @@ +package kr.xit.core.biz.batch.task; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.MDC; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +import kr.xit.core.biz.batch.model.BatchCmmDTO; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : 배치 종료시 lock table update
+ *               종료하는 배치 인스턴스의 사용여부(use_yn)를 미사용(N)으로 변경
+ *
+ * packageName : kr.xit.core.biz.batch.task
+ * fileName    : BatchEndTasklet
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ * @see kr.xit.core.aop.TraceLoggerAspect + */ +@Slf4j +public class BatchEndTasklet implements Tasklet { + private final IBatchCmmService batchCmmService; + + public BatchEndTasklet(IBatchCmmService batchCmmService) { + this.batchCmmService = batchCmmService; + } + + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + String jobName = contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(); + contribution.setExitStatus(ExitStatus.COMPLETED); + BatchCmmDTO dto = BatchCmmDTO + .builder() + .instanceId(jobName) + .result(contribution.getExitStatus().getExitCode()) + .useYn("N") + .batchLogId(MDC.get("batch_log_id")) + .traceId(StringUtils.defaultString(MDC.get("request_trace_id"), MDC.get("request_trace_batch_id"))) + .build(); +log.info("@@@@@@@@@@@@@{}", MDC.getCopyOfContextMap()); + batchCmmService.modifyBatchLock(dto); + batchCmmService.modifyBatchLog(dto); + return RepeatStatus.FINISHED; + } +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/batch/task/BatchFailEndTasklet.java b/mens-batch/src/main/java/kr/xit/core/biz/batch/task/BatchFailEndTasklet.java new file mode 100644 index 0000000..bc04cde --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/batch/task/BatchFailEndTasklet.java @@ -0,0 +1,59 @@ +package kr.xit.core.biz.batch.task; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.MDC; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +import kr.xit.core.biz.batch.model.BatchCmmDTO; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : 배치 종료시 lock table update
+ *               종료하는 배치 인스턴스의 사용여부(use_yn)를 미사용(N)으로 변경
+ *
+ * packageName : kr.xit.core.biz.batch.task
+ * fileName    : BatchEndTasklet
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ *
+ * 
+ * @see kr.xit.core.aop.TraceLoggerAspect + */ +@Slf4j +public class BatchFailEndTasklet implements Tasklet { + private final IBatchCmmService batchCmmService; + + public BatchFailEndTasklet(IBatchCmmService batchCmmService) { + this.batchCmmService = batchCmmService; + } + + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + String jobName = contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(); + contribution.setExitStatus(ExitStatus.FAILED); + BatchCmmDTO dto = BatchCmmDTO + .builder() + .instanceId(jobName) + .result(contribution.getExitStatus().getExitCode()) + .useYn("N") + .batchLogId(MDC.get("batch_log_id")) + .traceId(StringUtils.defaultString(MDC.get("request_trace_id"), MDC.get("request_trace_batch_id"))) + .build(); + log.info("@@@@@@@@@@@@@{}", MDC.getCopyOfContextMap()); + + batchCmmService.modifyBatchLock(dto); + batchCmmService.modifyBatchLog(dto); + + return RepeatStatus.FINISHED; + } +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/batch/task/BatchStartTasklet.java b/mens-batch/src/main/java/kr/xit/core/biz/batch/task/BatchStartTasklet.java new file mode 100644 index 0000000..b2890ed --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/batch/task/BatchStartTasklet.java @@ -0,0 +1,75 @@ +package kr.xit.core.biz.batch.task; + +import org.slf4j.MDC; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +import kr.xit.core.biz.batch.model.BatchCmmDTO; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.support.utils.Checks; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description : 배치 시작시 lock table insert or update
+ *               시작하는 배치 인스턴스의 사용여부(use_yn)를 사용(Y)으로 변경(생성)
+ *               -> 사용여부가 사용(Y) 이면 실행중 이므로 배치 종료
+ *
+ * packageName : kr.xit.core.biz.batch.task
+ * fileName    : BatchStartTasklet
+ * author      : limju
+ * date        : 2023-05-16
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-16    limju       최초 생성
+ * 2023-08-21    limju       lock이 걸린 경우 release 하도록 변경 : 중복실행 되지 않도록 배치 시간 보장 전제
+ * 
+ * @see kr.xit.core.aop.TraceLoggerAspect + */ +@Slf4j +public class BatchStartTasklet implements Tasklet { + private final IBatchCmmService batchCmmService; + + public BatchStartTasklet(IBatchCmmService batchCmmService) { + this.batchCmmService = batchCmmService; + } + + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + String jobName = contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(); + BatchCmmDTO dto = batchCmmService.findById(jobName) + .orElseGet(()-> BatchCmmDTO + .builder() + .build()); + + if(Checks.isEmpty(dto.getInstanceId())){ + dto.setInstanceId(jobName); + dto.setResult(null); + dto.setUseYn("Y"); + batchCmmService.addBatchLock(dto); + batchCmmService.addBatchLog(dto); + MDC.put("batch_log_id", dto.getBatchLogId()); + }else{ + if("Y".equals(dto.getUseYn())){ + log.error("======= [{}] 배치 실행(사용) 중으로 종료 =======", jobName); + contribution.setExitStatus(ExitStatus.FAILED); + dto.setResult("배치 실행(사용) 중으로 종료"); + // lock이 걸린 경우 reset : 2023-08-21 + dto.setUseYn("N"); + batchCmmService.modifyBatchLock(dto); + batchCmmService.addBatchLog(dto); + }else{ + dto.setUseYn("Y"); + dto.setResult(null); + batchCmmService.modifyBatchLock(dto); + batchCmmService.addBatchLog(dto); + MDC.put("batch_log_id", dto.getBatchLogId()); + } + } + return RepeatStatus.FINISHED; + } +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/mapper/IAuthApiMapper.java b/mens-batch/src/main/java/kr/xit/core/biz/mapper/IAuthApiMapper.java new file mode 100644 index 0000000..125e016 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/mapper/IAuthApiMapper.java @@ -0,0 +1,28 @@ +package kr.xit.core.biz.mapper; + +import org.egovframe.rte.psl.dataaccess.mapper.Mapper; + +import egovframework.com.cmm.LoginVO; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.core.biz.mapper
+ * fileName    : IAuthApiMapper
+ * author      : limju
+ * date        : 2023-05-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-11    limju       최초 생성
+ *
+ * 
+ */ +@Mapper +public interface IAuthApiMapper { + LoginVO actionLogin(LoginVO vo); + // LoginVO searchId(LoginVO vo); + // LoginVO searchPassword(LoginVO vo); + // void updatePassword(LoginVO vo); +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/mapper/ILoggingMapper.java b/mens-batch/src/main/java/kr/xit/core/biz/mapper/ILoggingMapper.java new file mode 100644 index 0000000..a496161 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/mapper/ILoggingMapper.java @@ -0,0 +1,32 @@ +package kr.xit.core.biz.mapper; + +import java.util.List; + +import org.egovframe.rte.psl.dataaccess.mapper.Mapper; + +import kr.xit.core.biz.model.LoggingDTO; + +/** + *
+ * description : API db logging
+ *
+ * packageName : kr.xit.core.biz.mapper
+ * fileName    : ILoggingMapper
+ * author      : limju
+ * date        : 2023-05-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-11    limju       최초 생성
+ *
+ * 
+ */ +@Mapper +public interface ILoggingMapper { + + List selectLogging(final LoggingDTO reqDTO); + LoggingDTO selectLogging(); + + void saveLogging(final LoggingDTO reqDTO); + void updateLogging(final LoggingDTO reqDTO); +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/model/AuditFields.java b/mens-batch/src/main/java/kr/xit/core/biz/model/AuditFields.java new file mode 100644 index 0000000..fb4af58 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/model/AuditFields.java @@ -0,0 +1,57 @@ +package kr.xit.core.biz.model; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +/** + *
+ * description : Audit column (등록 및 변경 정보 저장 필드)
+ *
+ * packageName : kr.xit.core.biz.model
+ * fileName    : AuditFields
+ * author      : limju
+ * date        : 2023-06-05
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-06-05    limju       최초 생성
+ *
+ * 
+ */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@SuperBuilder +public class AuditFields { + /** + * 등록 일시 + */ + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss") + private LocalDateTime registDt; + /** + * 등록자 + */ + private String register; + /** + * 수정 일시 + */ + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss") + private LocalDateTime updtDt; + /** + * 수정자 + */ + private String updusr; +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/model/LoggingDTO.java b/mens-batch/src/main/java/kr/xit/core/biz/model/LoggingDTO.java new file mode 100644 index 0000000..9d7fba5 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/model/LoggingDTO.java @@ -0,0 +1,103 @@ +package kr.xit.core.biz.model; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.core.biz.model
+ * fileName    : LoggingDTO
+ * author      : limju
+ * date        : 2023-05-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-11    limju       최초 생성
+ *
+ * 
+ */ +@Schema(name = "LoggingDTO", description = "API 로깅 DTO") +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@ToString +public class LoggingDTO { + /** + * 요청 ID + */ + private String requestId; + /** + * 시스템 ID(ENS|FIMS등) + */ + private String systemId; + /** + * 요청시스템 ID(KAKAO|KT등) + */ + private String reqSystemId; + /** + * 메소드(GET|PUT|POST|DELETE) + */ + private String method; + /** + * 호출 URI + */ + private String uri; + /** + * 성공/실패(true|false) + */ + private String success; + /** + * 파라메터 + */ + private String param; + /** + * 호출 결과 + */ + private String response; + /** + * 메세지(에러메세지) + */ + private String message; + /** + * IP + */ + private String ip; + /** + * 토큰 + */ + private String accessToken; + /** + * 세션ID + */ + private String sessionId; + /** + * 변경일시(now(3)-밀리세컨드까지) + */ + //private java.sql.Timestamp updtDt; + /** + * 변경자 + */ + private String updtId; + /** + * 생성일시(now(3)-밀리세컨드까지) + */ + //private java.sql.Timestamp registDt; + /** + * 생성자 + */ + //@Convert(converter = Jsr310.LocalDateTimeConverter.class) + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss.SSS") + private String registId; +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/service/AuthApiService.java b/mens-batch/src/main/java/kr/xit/core/biz/service/AuthApiService.java new file mode 100644 index 0000000..5e076ef --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/service/AuthApiService.java @@ -0,0 +1,42 @@ +package kr.xit.core.biz.service; + +import javax.annotation.Resource; + +import egovframework.com.cmm.util.EgovFileScrty; +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.springframework.stereotype.Service; + +import egovframework.com.cmm.LoginVO; +import kr.xit.core.biz.mapper.IAuthApiMapper; + +@Service +public class AuthApiService extends EgovAbstractServiceImpl implements IAuthApiService { + + @Resource + private IAuthApiMapper mapper; + + /** + * 일반 로그인 처리 + * @param vo LoginVO + * @return LoginVO + */ + @Override + public LoginVO actionLogin(LoginVO vo) { + + // 1. 입력한 비밀번호를 암호화한다. + String enpassword = EgovFileScrty.encryptPassword(vo.getPassword(), vo.getId()); + vo.setPassword(enpassword); + + // 2. 아이디와 암호화된 비밀번호가 DB와 일치하는지 확인한다. + LoginVO loginVO = mapper.actionLogin(vo); //loginDAO.actionLogin(vo); + + // 3. 결과를 리턴한다. + if (loginVO != null && !loginVO.getId().equals("") && !loginVO.getPassword().equals("")) { + return loginVO; + } else { + loginVO = new LoginVO(); + } + + return loginVO; + } +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/service/IAuthApiService.java b/mens-batch/src/main/java/kr/xit/core/biz/service/IAuthApiService.java new file mode 100644 index 0000000..b0b6eef --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/service/IAuthApiService.java @@ -0,0 +1,32 @@ +package kr.xit.core.biz.service; + +import egovframework.com.cmm.LoginVO; + +/** + * 일반 로그인을 처리하는 비즈니스 구현 클래스 + * @author 공통서비스 개발팀 박지욱 + * @since 2009.03.06 + * @version 1.0 + * @see + * + *
+ * << 개정이력(Modification Information) >>
+ *
+ *   수정일      수정자          수정내용
+ *  -------    --------    ---------------------------
+ *  2009.03.06  박지욱          최초 생성
+ *  2011.08.31  JJY            경량환경 템플릿 커스터마이징버전 생성
+ *
+ *  
+ */ +public interface IAuthApiService { + + /** + * 일반 로그인 처리 + * + * @param vo LoginVO + * @return LoginVO + */ + LoginVO actionLogin(LoginVO vo); + +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/service/ILoggingService.java b/mens-batch/src/main/java/kr/xit/core/biz/service/ILoggingService.java new file mode 100644 index 0000000..008f05d --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/service/ILoggingService.java @@ -0,0 +1,27 @@ +package kr.xit.core.biz.service; + +import java.util.List; + +import kr.xit.core.biz.model.LoggingDTO; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.core.biz.service
+ * fileName    : ILoggingService
+ * author      : limju
+ * date        : 2023-05-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-11    limju       최초 생성
+ *
+ * 
+ */ +public interface ILoggingService { + List findLogging(LoggingDTO reqDTO); + LoggingDTO findLogging(); + void saveLogging(LoggingDTO reqDTO); + void modifyLogging(LoggingDTO reqDTO); +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/service/LoggingService.java b/mens-batch/src/main/java/kr/xit/core/biz/service/LoggingService.java new file mode 100644 index 0000000..09599d0 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/service/LoggingService.java @@ -0,0 +1,60 @@ +package kr.xit.core.biz.service; + +import java.util.List; + +import javax.transaction.Transactional; + +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; +import org.springframework.stereotype.Service; + +import kr.xit.core.biz.mapper.ILoggingMapper; +import kr.xit.core.biz.model.LoggingDTO; +import lombok.extern.slf4j.Slf4j; + +/** + *
+ * description :
+ *
+ * packageName : kr.xit.core.biz.service
+ * fileName    : LoggingServiceImpl
+ * author      : limju
+ * date        : 2023-05-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-11    limju       최초 생성
+ *
+ * 
+ */ + +@Slf4j +@Service +public class LoggingService extends EgovAbstractServiceImpl implements ILoggingService{ + private final ILoggingMapper mapper; + + public LoggingService(ILoggingMapper mapper) { + this.mapper = mapper; + } + + @Transactional + @Override + public void saveLogging(final LoggingDTO reqDTO){ + mapper.saveLogging(reqDTO); + } + + @Transactional + @Override + public void modifyLogging(final LoggingDTO reqDTO){ + mapper.updateLogging(reqDTO); + } + + @Override + public List findLogging(final LoggingDTO reqDTO) { + return mapper.selectLogging(reqDTO); + } + + @Override + public LoggingDTO findLogging() { + return mapper.selectLogging(); + } +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/web/AuthApiController.java b/mens-batch/src/main/java/kr/xit/core/biz/web/AuthApiController.java new file mode 100644 index 0000000..a6bc1cf --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/web/AuthApiController.java @@ -0,0 +1,184 @@ +package kr.xit.core.biz.web; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import egovframework.com.cmm.EgovMessageSource; +import egovframework.com.cmm.LoginVO; +import egovframework.com.cmm.jwt.config.EgovJwtTokenUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.core.biz.service.IAuthApiService; +import kr.xit.core.consts.Constants; +import lombok.RequiredArgsConstructor; + +/** + *
+ * description : 인증 로그인 처리
+ *
+ * packageName : kr.xit.biz.auth
+ * fileName    : AuthApiController
+ * author      : limju
+ * date        : 2023-04-26
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-26    limju       최초 생성
+ *
+ * 
+ * @see + */ +//FIXME::세션에서 인증 관리 할지 여부 결정 필요 +@Tag(name = "AuthApiController", description = "인증 관리") +@RequiredArgsConstructor +@RestController +@RequestMapping(value = "/batch/core/auth") +public class AuthApiController { + @Value("${app.token.saveType:header}") + private String authSaveType; + + /** EgovLoginService */ + + private final IAuthApiService loginService; + + /** EgovMessageSource */ + + private final EgovMessageSource egovMessageSource; + private final EgovJwtTokenUtil egovJwtTokenUtil; + + /** + * 일반 로그인을 처리한다 + * @param loginVO 아이디, 비밀번호가 담긴 LoginVO + * @param request 세션처리를 위한 HttpServletRequest + * @return 로그인결과(세션정보) + */ + @Operation(summary = "로그인" , description = "로그인") + @io.swagger.v3.oas.annotations.parameters.RequestBody( + required = true, + content = { + @Content( + mediaType = "application/json", + examples = { + @ExampleObject( + value = "{\"id\":\"admin\",\"password\":\"1\",\"userSe\":\"USR\"}") + } + ) + } + ) + @PostMapping(value = "/login", consumes = {MediaType.APPLICATION_JSON_VALUE , MediaType.TEXT_HTML_VALUE}) + public ApiResponseDTO login(@RequestBody final LoginVO loginVO, HttpServletRequest request) { + // 1. 일반 로그인 처리 + LoginVO loginResultVO = loginService.actionLogin(loginVO); + + if (loginResultVO != null && loginResultVO.getId() != null && !loginResultVO.getId().equals("")) { + request.getSession().setAttribute(Constants.AuthSaveSession.LOGIN_VO.getCode(), loginResultVO); + return ApiResponseDTO.success(loginResultVO); + } + return ApiResponseDTO.success(egovMessageSource.getMessage("fail.common.login")); + } + + @Operation(summary = "로그인(JWT)" , description = "로그인(JWT)") + @io.swagger.v3.oas.annotations.parameters.RequestBody( + required = true, + content = { + @Content( + mediaType = "application/json", + examples = { + @ExampleObject( + name = "admin", + description = "admin", + value = "{\"id\":\"admin\",\"password\":\"1\",\"userSe\":\"USR\"}"), + @ExampleObject( + name = "admin1", + description = "admin1", + value = "{\"id\":\"admin1\",\"password\":\"1\",\"userSe\":\"USR\"}"), + @ExampleObject( + name = "admin2", + description = "admin2", + value = "{\"id\":\"admin2\",\"password\":\"1\",\"userSe\":\"USR\"}") + } + ) + } + ) + @PostMapping(value = "/loginJwt") + public ApiResponseDTO loginJWT(@RequestBody final LoginVO loginVO, HttpServletRequest request) { + HashMap resultMap = new HashMap(); + + // 1. 일반 로그인 처리 + LoginVO loginResultVO = loginService.actionLogin(loginVO); + + if (loginResultVO != null && loginResultVO.getId() != null && !loginResultVO.getId().equals("")) { + + Map claimsMap = new HashMap<>(); + // claimsMap.put("dkkdk", "kdkkdkdkd"); + String jwtToken = egovJwtTokenUtil.generateToken(loginVO, claimsMap); + // String jwtToken = egovJwtTokenUtil.generateToken(loginVO.getId()); + + String username = egovJwtTokenUtil.getUsernameFromToken(jwtToken); + + + // System.out.println("Dec jwtToken username = "+username); + + //서버사이드 권한 체크 통과를 위해 삽입 + //EgovUserDetailsHelper.isAuthenticated() 가 그 역할 수행. DB에 정보가 없으면 403을 돌려 줌. 로그인으로 튕기는 건 프론트 쪽에서 처리 + request.getSession().setAttribute(Constants.AuthSaveSession.LOGIN_VO.getCode(), loginResultVO); + + + + + // UsernamePasswordAuthenticationToken authenticationToken = jwtTokenProvider.toAuthentication(loginVO.getId(), loginVO.getPassword()); + // Authentication authentication = authenticationManager.authenticate(authenticationToken); + // + // + // // Authentication 저장 + // if(Objects.equals(authSaveType, Constants.AuthSaveType.SECURITY.getCode())){ + // // TODO :: SessionCreationPolicy.STATELESS 인 경우 사용 불가 + // SecurityContextHolder.getContext().setAuthentication(authentication); + // + // }else if(Objects.equals(authSaveType, Constants.AuthSaveType.SESSION.getCode())){ + // session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext()); + // } + + //Map infoMap = new HashMap<>(); + //infoMap.put(Constants.JwtToken.TOKEN_USER_ID.getCode(), loginVO.getId()); + //infoMap.put(Constants.JwtToken.TOKEN_USER_MAIL.getCode(), loginVO.getEmail()); + + //String jwtToken = jwtTokenProvider.generateJwtAccessToken(authentication, infoMap); + //String jwtToken = jwtTokenProvider.generateJwtAccessToken(loginVO.getId(), "ROLE_USER"); + + resultMap.put("resultVO", loginResultVO); + resultMap.put("token", jwtToken); + return ApiResponseDTO.success(resultMap); + + } + return ApiResponseDTO.error(egovMessageSource.getMessage("fail.common.login") ); + } + + /** + * 로그아웃한다. + * @return resultVO + * @exception Exception + */ + @Operation(summary = "logout" , description = "로그아웃") + @GetMapping(value = "/logout") + public ApiResponseDTO actionLogoutJSON(HttpServletRequest request) { + + RequestContextHolder.currentRequestAttributes().removeAttribute(Constants.AuthSaveSession.LOGIN_VO.getCode(), RequestAttributes.SCOPE_SESSION); + return ApiResponseDTO.empty(); + } +} diff --git a/mens-batch/src/main/java/kr/xit/core/biz/web/LoggingController.java b/mens-batch/src/main/java/kr/xit/core/biz/web/LoggingController.java new file mode 100644 index 0000000..7f404a3 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/biz/web/LoggingController.java @@ -0,0 +1,60 @@ +package kr.xit.core.biz.web; + +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.core.biz.model.LoggingDTO; +import kr.xit.core.biz.service.ILoggingService; +import lombok.RequiredArgsConstructor; + +/** + *
+ * description : API Logging cotroller
+ *
+ * packageName : kr.xit.core.biz.web
+ * fileName    : LoggingController
+ * author      : limju
+ * date        : 2023-05-11
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-05-11    limju       최초 생성
+ *
+ * 
+ */ +@Tag(name = "LoggingController", description = "API logging 관리") +@RequiredArgsConstructor +@RestController +@RequestMapping(value = "/batch/core/log") +public class LoggingController { + + private final ILoggingService loggingService; + + @Operation(summary = "로깅 정보 저장" , description = "로깅 정보 저장") + @io.swagger.v3.oas.annotations.parameters.RequestBody( + required = true, + content = { + @Content( + mediaType = "application/json", + examples = { + @ExampleObject( + value = "{\"requestId\":\"7f1f5978fc1941c4a6d37351a6d692e6\"}") + } + ) + } + ) + @PostMapping(value = "/save", consumes = {MediaType.APPLICATION_JSON_VALUE , MediaType.TEXT_HTML_VALUE}) + public ApiResponseDTO saveLogging(@RequestBody final LoggingDTO reqDTO){ + loggingService.saveLogging(reqDTO); + return ApiResponseDTO.empty(); + } +} diff --git a/mens-batch/src/main/java/kr/xit/core/spring/config/CustomBatchConfigurer.java b/mens-batch/src/main/java/kr/xit/core/spring/config/CustomBatchConfigurer.java new file mode 100644 index 0000000..073c867 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/spring/config/CustomBatchConfigurer.java @@ -0,0 +1,55 @@ +package kr.xit.core.spring.config; + +import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + *
+ * description : Spring batch Meta table 비활성 : 비 권고
+ *               @EnableBatchProcessing -> BatchConfigurationSelector import
+ *               BatchConfigurationSelector -> SimpleBatchConfiguration 반환
+ *               SimpleBatchConfiguration -> BatchConfigurer 타입의 빈을 조회하여 사용
+ *               - jobRepository, jobLauncher, transactionManager, jobRegistry, jobExplorer 인스턴스 생성
+ *               - @Bean 어노테이션에 의해 빈으로 등록
+ *               - 생성되는 인스턴스는 DefaultBatchConfigurer에 정의
+ *               메타테이블 비활성을 위해
+ *               - DefaultBatchConfigurer 상속
+ *               - EnableBatchProcessing 지정
+ *               - setDatasource 메소드 override
+ *
+ * fileName    : CustomBatchConfigurer
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ */ +@Configuration +@EnableBatchProcessing +@EnableScheduling +public class CustomBatchConfigurer extends DefaultBatchConfigurer { + +/* + @Override + public void setDataSource(DataSource dataSource) { + // Do nothing + } +*/ + /** + * FIXME::JPA 사용시 추가 + * @param emf + * @return + */ + // @Bean + //@Primary // SimpleBatchConfiguration 에서 생성된 빈 대체 + // public PlatformTransactionManager jpaTransactionManager(EntityManagerFactory emf) { + // return new JpaTransactionManager(emf); + // } + + +} diff --git a/mens-batch/src/main/java/kr/xit/core/spring/config/SpringDocsApiConfig.java b/mens-batch/src/main/java/kr/xit/core/spring/config/SpringDocsApiConfig.java new file mode 100644 index 0000000..93ff0f0 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/spring/config/SpringDocsApiConfig.java @@ -0,0 +1,66 @@ +package kr.xit.core.spring.config; + +import org.springdoc.core.GroupedOpenApi; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *
+ * description : Springdoc(swagger) 설정
+ *               설정내용이 상이한 경우 동일한 파일로 재정의 하거나 상속받아 사용
+ * packageName : kr.xit.core.spring.config
+ * fileName    : SpringDocsConfig
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ */ +@ConditionalOnProperty(value = "springdoc.swagger-ui.enabled", havingValue = "true", matchIfMissing = false) +@Configuration +public class SpringDocsApiConfig { + @Bean + public GroupedOpenApi authentification() { + return GroupedOpenApi.builder() + .group("1. Core API") + .pathsToMatch( + "/batch/core/**" + ) + .build(); + } + + @Bean + public GroupedOpenApi kakaopayEltrcDocBatch() { + return GroupedOpenApi.builder() + .group("2. 전자고지 통합발송 연계 Batch WEB") + .pathsToMatch( + "/batch/v1/**" + ) + .build(); + } + + @Bean + public GroupedOpenApi bizDoc() { + return GroupedOpenApi.builder() + .group("3. 전자고지 통합발송 연계 API") + .pathsToMatch( + "/batch/ens/**" + ) + .build(); + } + + @Bean + public GroupedOpenApi pniDoc() { + return GroupedOpenApi.builder() + .group("6. 사전고지 API") + .pathsToMatch( + "/batch/pni/v1/**" + ) + .build(); + } + +} diff --git a/mens-batch/src/main/java/kr/xit/core/spring/config/WebMvcConfig.java b/mens-batch/src/main/java/kr/xit/core/spring/config/WebMvcConfig.java new file mode 100644 index 0000000..e0d13be --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/spring/config/WebMvcConfig.java @@ -0,0 +1,216 @@ +package kr.xit.core.spring.config; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.web.filter.CommonsRequestLoggingFilter; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import kr.xit.core.consts.Constants; +import kr.xit.core.spring.config.auth.AuthentificationInterceptor; +import kr.xit.core.spring.config.properties.CorsProperties; +import kr.xit.core.spring.filter.LoggingFilter; +import kr.xit.core.spring.filter.ReadableRequestWrapperFilter; +import kr.xit.core.spring.filter.SimpleCORSFilter; +import kr.xit.core.spring.resolver.CustomArgumentResolver; +import kr.xit.core.spring.resolver.PageableArgumentResolver; +import lombok.RequiredArgsConstructor; + +/** + *
+ * description : Spring MVC 설정
+ *               - filter, interceptor
+ *               - AuthentificationInterceptor : 인증처리
+ *               - CommonsRequestLoggingFilter : request logging
+ *               - ReadableRequestWrapperFilter : post logging 처리를 위한 필터
+ *               - SimpleCORSFilter : cors
+ * packageName : kr.xit.core.spring.config
+ * fileName    : WebMvcConfig
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ * @see AuthentificationInterceptor + * @see CommonsRequestLoggingFilter + * @see ReadableRequestWrapperFilter + * @see SimpleCORSFilter + * @see LoggingFilter + */ +@RequiredArgsConstructor +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + /** + * logging exclude path + */ + @Value("${app.param.log.exclude-patterns}") + private List EXCLUDE_URL_REGEXS; + + private final CorsProperties corsProperties; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new AuthentificationInterceptor()) + .addPathPatterns("/**/*") + .excludePathPatterns( + "/api/core/*" + // "/api/v1/kakaopay/*" + ); + } + + // ------------------------------------------------------------- + // RequestMappingHandlerMapping 설정 View Controller 추가 + // ------------------------------------------------------------- + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/cmmn/validator.do") + .setViewName("cmmn/validator"); + registry.addViewController("/").setViewName("forward:/index.html"); + } + + //TODO :: ArgumentResolver add + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(new CustomArgumentResolver()); + resolvers.add(new PageableArgumentResolver()); + //WebMvcConfigurer.super.addArgumentResolvers(resolvers); + } + + /** + * CommonsRequestLoggingFiler 등록 + * app.param.log.enabled: true시 로그 출력 + * @return + */ + @ConditionalOnProperty(value = "app.param.log.enabled", havingValue = "true", matchIfMissing = false) + @Bean + public FilterRegistrationBean requestLoggingFilter() { + CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter(){ + @Override + protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + String path = request.getServletPath(); + return EXCLUDE_URL_REGEXS.stream().anyMatch(regex -> path.matches(regex)); + } + }; + loggingFilter.setIncludeClientInfo(true); + loggingFilter.setIncludeHeaders(false); + loggingFilter.setBeforeMessagePrefix("\n//========================== Request(Before) ================================\n"); + loggingFilter.setBeforeMessageSuffix("\n//==========================================================================="); + + loggingFilter.setIncludeQueryString(true); + loggingFilter.setIncludePayload(true); + loggingFilter.setMaxPayloadLength(1024* 1024); + loggingFilter.setAfterMessagePrefix("\n//=========================== Request(After) ================================\n"); + loggingFilter.setAfterMessageSuffix("\n//==========================================================================="); + + FilterRegistrationBean bean = new FilterRegistrationBean<>(loggingFilter); + bean.setOrder(Ordered.HIGHEST_PRECEDENCE); + bean.addUrlPatterns(Constants.API_URL_PATTERNS); + return bean; + } + + /** + * exclude pattern 지정 + * @return FilterRegistrationBean + */ + @ConditionalOnProperty(value = "app.param.log.custom.enabled", havingValue = "true", matchIfMissing = false) + @Bean + public FilterRegistrationBean loggingFilter() { + Map initMap = new HashMap<>(); + initMap.put("excludedUrls", StringUtils.join(EXCLUDE_URL_REGEXS,",")); + + FilterRegistrationBean frb = new FilterRegistrationBean<>(new LoggingFilter()); + frb.setOrder(1); + frb.addUrlPatterns(Constants.API_URL_PATTERNS); + frb.setInitParameters(initMap); + frb.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR, DispatcherType.ASYNC); + + return frb; + } + + /** + * Post 요청시 request(stream) logging 처리를 위한 필터 + * @return + */ + @ConditionalOnProperty(value = "app.param.log.custom.enabled", havingValue = "true", matchIfMissing = false) + @Bean + public FilterRegistrationBean readableRequestWrapperFilter() { + ReadableRequestWrapperFilter readableRequestWrapperFilter = new ReadableRequestWrapperFilter(); + + FilterRegistrationBean bean = new FilterRegistrationBean(readableRequestWrapperFilter); + bean.setOrder(Ordered.HIGHEST_PRECEDENCE); + bean.addUrlPatterns(Constants.API_URL_PATTERNS); + return bean; + } + + /** + * CORS Filter 등록 + * @return + */ + @Bean + public FilterRegistrationBean simpleCorsFilter() { + + SimpleCORSFilter corsFilter = new SimpleCORSFilter(); + FilterRegistrationBean bean = new FilterRegistrationBean(corsFilter); + bean.setOrder(Ordered.LOWEST_PRECEDENCE); + bean.addUrlPatterns(Constants.API_URL_PATTERNS); + return bean; + + // CorsConfiguration config = new CorsConfiguration(); + // config.setAllowedOrigins(Collections.singletonList(corsProperties.getAllowedOrigins())); + // config.setAllowedMethods(Collections.singletonList(corsProperties.getAllowedMethods())); + // config.setAllowedHeaders(Collections.singletonList(corsProperties.getAllowedHeaders())); + // config.setAllowCredentials(corsProperties.getAllowCredentials()); + // config.setMaxAge(corsProperties.getMaxAge()); + // config.setExposedHeaders(Collections.singletonList(corsProperties.getExposeHeader())); + // + // UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + // source.registerCorsConfiguration("/**", config); + // + // FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); + // bean.setOrder(Ordered.HIGHEST_PRECEDENCE); + // return bean; + + } + + + // @Override + // public void addCorsMappings(CorsRegistry registry) { + // //WebMvcConfigurer.super.addCorsMappings(registry); + // registry.addMapping("/**") + // .allowedOrigins(String.valueOf(Collections.singletonList(corsProperties.getAllowedOrigins()))) + // .allowedMethods(String.valueOf(Collections.singletonList(corsProperties.getAllowedMethods()))); + // } + + +// /** +// * HandlerExceptionResolver 를 상속받은 resolver 등록 +// * @param resolvers the list of configured resolvers to extend +// */ +// @Override +// public void extendHandlerExceptionResolvers(List resolvers) { +// HandlerExceptionResolver exceptionHandlerExceptionResolver = resolvers.stream().filter(x -> x instanceof ExceptionHandlerExceptionResolver).findAny().get(); +// int index = resolvers.indexOf(exceptionHandlerExceptionResolver); +// resolvers.add(index, new CustomRuntimeResolver()); +// WebMvcConfigurer.super.extendHandlerExceptionResolvers(resolvers); +// } + +} diff --git a/mens-batch/src/main/java/kr/xit/core/spring/config/support/DatasourceConfig.java b/mens-batch/src/main/java/kr/xit/core/spring/config/support/DatasourceConfig.java new file mode 100644 index 0000000..290867b --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/spring/config/support/DatasourceConfig.java @@ -0,0 +1,73 @@ +package kr.xit.core.spring.config.support; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import kr.xit.core.consts.Constants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy; + +import javax.sql.DataSource; + +//FIXME :: 재설정이 필요한 경우 해당 프로젝트에 동일한 파일로 재정의 하여 사용 +/** + *
+ * description : Datasource 설정 - FIXME:: spring.datasource 설정이 있는 경우만 loading
+ *               - 조건 : spring.datasource
+ *               실제 필요한 경우만 커넥션을 점유하도록
+ *               LazyConnectionDataSourceProxy 사용
+ *               일반 Datasource 사용시
+ *               - Spring은 트랜잭션에 진입시 데이타 소스의 커넥션을 get
+ *               - ehcache, hibernate 영속성 컨택슽트 1차캐시 등에도 커넥션을 get
+ *               - multi-datasource 에서 트랜잭션 진입 이후 datasource 분기 불가
+ * packageName : kr.xit.core.spring.config.support
+ * fileName    : DatasourceConfig
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ * @see PrimaryMybatisConfig + */ +//@ConditionalOnProperty(value = "spring", havingValue = "datasource", matchIfMissing = false) +@Slf4j +@Configuration +public class DatasourceConfig { + @Bean(name = "primaryHikariConfig") + @Primary + @ConfigurationProperties(prefix = "spring.datasource.hikari.maria") + public HikariConfig primaryHikariConfig() { + // HikariConfig hikariConfig = new HikariConfig("spring.datasource.hikari"); + // hikariConfig.setAutoCommit(false); + // return hikariConfig; + return new HikariConfig(); + } + + @Bean(Constants.PRIMARY_DATA_SOURCE) + @Primary + public DataSource primaryDataSource() throws Exception{ + //return new LazyConnectionDataSourceProxy(new HikariDataSource(primaryHikariConfig())); + return new HikariDataSource(primaryHikariConfig()); + } + + @Bean(name = "secondaryHikariConfig") + @ConfigurationProperties(prefix = "spring.datasource.hikari.oracle") + public HikariConfig secondaryHikariConfig() { + // HikariConfig hikariConfig = new HikariConfig("spring.datasource.hikari"); + // hikariConfig.setAutoCommit(false); + // return hikariConfig; + return new HikariConfig(); + } + + @Bean(name = Constants.SECONDARY_DATA_SOURCE) + public DataSource secondaryDataSource() throws Exception{ + //return new LazyConnectionDataSourceProxy(new HikariDataSource(secondaryHikariConfig())); + return new HikariDataSource(secondaryHikariConfig()); + } +} diff --git a/mens-batch/src/main/java/kr/xit/core/spring/config/support/PrimaryMybatisConfig.java b/mens-batch/src/main/java/kr/xit/core/spring/config/support/PrimaryMybatisConfig.java new file mode 100644 index 0000000..f735cb8 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/spring/config/support/PrimaryMybatisConfig.java @@ -0,0 +1,76 @@ +package kr.xit.core.spring.config.support; + +import kr.xit.core.consts.Constants; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.SqlSessionTemplate; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.*; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.support.lob.DefaultLobHandler; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; + +//FIXME :: 재설정이 필요한 경우 해당프로젝트에 동일한 파일로 재정의 하여 사용 +/** + *
+ * description : Mybatis 설정 - FIXME:: @DependsOn(value = {"dataSource"}) loading
+ *               - 조건 : @DependsOn(value = {"dataSource"})
+ * packageName : kr.xit.core.spring.config.support
+ * fileName    : MybatisConfig
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ * @see DatasourceConfig + */ +@Slf4j +@Configuration +@MapperScan( + basePackages = { + "kr.xit.core.biz.mapper", + "kr.xit.core.biz.batch.mapper", + "kr.xit.biz.ens.mapper", + "kr.xit.biz.pni.mapper", + "kr.xit.biz.sample.mapper", + }, + sqlSessionFactoryRef = Constants.PRIMARY_SQL_SESSION +) +public class PrimaryMybatisConfig { + + static final String MYBATIS_CONFIG_FILE = "classpath:/egovframework/mapper/mapper-config.xml"; + + @ConditionalOnMissingBean + @Bean + @Lazy + public DefaultLobHandler lobHandler() { + return new DefaultLobHandler(); + } + + @Primary + @Bean(name = Constants.PRIMARY_SQL_SESSION) + public SqlSessionFactory primarySqlSession(@Qualifier(Constants.PRIMARY_DATA_SOURCE)DataSource dataSource) throws Exception { + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); + sessionFactory.setDataSource(dataSource); + sessionFactory.setConfigLocation(resolver.getResource(MYBATIS_CONFIG_FILE)); + sessionFactory.setMapperLocations(resolver.getResources("classpath:/egovframework/mapper/**/*-mysql-mapper.xml")); + return sessionFactory.getObject(); + } + + @Primary + @Bean(name = "primarySqlSessionTemplate") + public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier(Constants.PRIMARY_SQL_SESSION) SqlSessionFactory sqlSessionFactory) { + return new SqlSessionTemplate(sqlSessionFactory); + } +} diff --git a/mens-batch/src/main/java/kr/xit/core/spring/config/support/RestResponseErrorHandler.java b/mens-batch/src/main/java/kr/xit/core/spring/config/support/RestResponseErrorHandler.java new file mode 100644 index 0000000..4f339a6 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/spring/config/support/RestResponseErrorHandler.java @@ -0,0 +1,53 @@ +package kr.xit.core.spring.config.support; + +import kr.xit.core.exception.BizRuntimeException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.web.client.DefaultResponseErrorHandler; + +import java.io.IOException; + +/** + *
+ * description : RestTemplate Error Handler
+ * packageName : kr.xit.core.spring.config.support
+ * fileName    : RestResponseErrorHandler
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ * @see kr.xit.core.spring.config.RestTemplateConfig#restTemplate + */ +@Slf4j +public class RestResponseErrorHandler extends DefaultResponseErrorHandler { + + @Override + public void handleError(ClientHttpResponse response) throws IOException { + String seriesName = "UNKNOWN_ERROR"; + + if (response.getStatusCode() == HttpStatus.PRECONDITION_FAILED) { + seriesName = String.valueOf(HttpStatus.PRECONDITION_FAILED.value()); + + }else if (response.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR) { + // handle SERVER_ERROR + seriesName = HttpStatus.Series.SERVER_ERROR.name(); + + } else if (response.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR) { + // handle CLIENT_ERROR + seriesName = HttpStatus.Series.CLIENT_ERROR.name(); + if (response.getStatusCode() == HttpStatus.NOT_FOUND) { + seriesName = String.valueOf(HttpStatus.NOT_FOUND.value()); + } + } + log.error("========================== RestResponse Error =============================="); + log.error("SERIES NAME : {}", seriesName); + log.error("STATUS CODE : {}", response.getStatusCode()); + log.error("============================================================================="); + //throw BizRuntimeException.create(String.valueOf(response.getStatusCode().value()), null); + } +} diff --git a/mens-batch/src/main/java/kr/xit/core/spring/config/support/SecondaryMybatisConfig.java b/mens-batch/src/main/java/kr/xit/core/spring/config/support/SecondaryMybatisConfig.java new file mode 100644 index 0000000..808e8f0 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/spring/config/support/SecondaryMybatisConfig.java @@ -0,0 +1,66 @@ +package kr.xit.core.spring.config.support; + +import kr.xit.core.consts.Constants; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.SqlSessionTemplate; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Primary; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.support.lob.DefaultLobHandler; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; + +//FIXME :: 재설정이 필요한 경우 해당프로젝트에 동일한 파일로 재정의 하여 사용 + +/** + *
+ * description : Mybatis 설정 - FIXME:: @DependsOn(value = {"dataSource"}) loading
+ *               - 조건 : @DependsOn(value = {"dataSource"})
+ * packageName : kr.xit.core.spring.config.support
+ * fileName    : MybatisConfig
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ * @see DatasourceConfig + */ +@Slf4j +@Configuration +@MapperScan( + basePackages = {"kr.xit.biz.sms.mapper"}, + sqlSessionFactoryRef = Constants.SECONDARY_SQL_SESSION +) +public class SecondaryMybatisConfig { + + static final String MYBATIS_CONFIG_FILE = "classpath:/egovframework/mapper/mapper-config.xml"; + + + @Bean(name = Constants.SECONDARY_SQL_SESSION) + public SqlSessionFactory secondarySqlSession(@Qualifier(Constants.SECONDARY_DATA_SOURCE) DataSource dataSource) throws Exception { + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); + sessionFactory.setDataSource(dataSource); + sessionFactory.setConfigLocation(resolver.getResource(MYBATIS_CONFIG_FILE)); + sessionFactory.setMapperLocations(resolver.getResources("classpath:/egovframework/mapper/**/*-oracle-mapper.xml")); + return sessionFactory.getObject(); + } + + @Bean(name = "secondarySqlSessionTemplate") + public SqlSessionTemplate secondarySqlSessionTemplate(@Qualifier(Constants.SECONDARY_SQL_SESSION) SqlSessionFactory sqlSessionFactory) { + return new SqlSessionTemplate(sqlSessionFactory); + } +} diff --git a/mens-batch/src/main/java/kr/xit/core/spring/config/support/TransactionConfig.java b/mens-batch/src/main/java/kr/xit/core/spring/config/support/TransactionConfig.java new file mode 100644 index 0000000..9ab3c26 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/spring/config/support/TransactionConfig.java @@ -0,0 +1,64 @@ +package kr.xit.core.spring.config.support; + +import kr.xit.core.consts.Constants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.transaction.ChainedTransactionManager; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; + +//FIXME :: 재설정이 필요한 경우 해당프로젝트에 동일한 파일로 재정의 하여 사용 + +/** + *
+ * description : Mybatis 설정 - FIXME:: @DependsOn(value = {"dataSource"}) loading
+ *               - 조건 : @DependsOn(value = {"dataSource"})
+ * packageName : kr.xit.core.spring.config.support
+ * fileName    : MybatisConfig
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ * @see DatasourceConfig + */ +@Slf4j +@Configuration +@EnableTransactionManagement +public class TransactionConfig { + //////////////////////////////////////////////////////////////////////////////////////////// + // ChainedTransactionManager : trsnsaction binding + /////////////////////////////////////////////////////////////////////////////////////////// + /** + * mariaDB & oracleDB Transaction binding + * @param mariaDS Maria DataSource + * @param oracleDS Oracle DataSource + * @return PlatformTransactionManager + * @throws Exception Exception + */ + @Primary + @Bean + public PlatformTransactionManager transactionManager(@Qualifier(Constants.PRIMARY_DATA_SOURCE)DataSource mariaDS, + @Qualifier(Constants.SECONDARY_DATA_SOURCE)DataSource oracleDS) { + DataSourceTransactionManager mariaTm = new DataSourceTransactionManager(mariaDS); + mariaTm.setGlobalRollbackOnParticipationFailure(false); + mariaTm.setNestedTransactionAllowed(true); + + DataSourceTransactionManager oracleTm = new DataSourceTransactionManager(oracleDS); + oracleTm.setGlobalRollbackOnParticipationFailure(false); + oracleTm.setNestedTransactionAllowed(true); + + // creates chained transaction manager + return new ChainedTransactionManager(mariaTm, oracleTm); + } + ///////////////////////////////////////////////////////////////////////////////////// +} diff --git a/mens-batch/src/main/java/kr/xit/core/spring/util/ApiSpringUtils.java b/mens-batch/src/main/java/kr/xit/core/spring/util/ApiSpringUtils.java new file mode 100644 index 0000000..81618b8 --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/core/spring/util/ApiSpringUtils.java @@ -0,0 +1,66 @@ +package kr.xit.core.spring.util; + +import kr.xit.biz.ens.service.ISendMessageLinkService; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.core.env.Environment; + +import egovframework.com.cmm.jwt.config.EgovJwtTokenUtil; +import egovframework.com.cmm.jwt.config.JwtVerification; +import kr.xit.core.biz.batch.service.IBatchCmmService; +import kr.xit.core.biz.service.ILoggingService; +import kr.xit.core.spring.config.properties.CorsProperties; +import kr.xit.core.spring.config.support.ApplicationContextProvider; +import kr.xit.core.support.slack.SlackWebhookPush; + +/** + *
+ * description : Get Bean Object
+ *               Filter / Interceptor 등에서 Bean 사용시 필요
+ *               (Bean으로 등록되는 클래스 내에서만 @Autowired / @Resource 등이 동작)
+ * packageName : kr.xit.core.spring.util
+ * fileName    : SpringUtils
+ * author      : julim
+ * date        : 2023-04-28
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2023-04-28    julim       최초 생성
+ *
+ * 
+ * @see ApplicationContextProvider + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ApiSpringUtils { + + public static ApplicationContext getApplicationContext() { + return ApplicationContextProvider.getApplicationContext(); + } + + public static boolean containsBean(String beanName) { + return getApplicationContext().containsBean(beanName); + } + + public static Object getBean(String beanName) { + return getApplicationContext().getBean(beanName); + } + + public static Object getBean(Class clazz) { + return getApplicationContext().getBean(clazz); + } + + public static IBatchCmmService getBatchCmmService(){ + return (IBatchCmmService)getBean(IBatchCmmService.class); + } + + public static ISendMessageLinkService getSendMessageLinkService(){ + return (ISendMessageLinkService)getBean(ISendMessageLinkService.class); + } + + public static SlackWebhookPush getSlackWebhookPush(){ + return (SlackWebhookPush)getBean(SlackWebhookPush.class); + } +} diff --git a/mens-batch/src/main/java/kr/xit/package-info.java b/mens-batch/src/main/java/kr/xit/package-info.java new file mode 100644 index 0000000..05e490c --- /dev/null +++ b/mens-batch/src/main/java/kr/xit/package-info.java @@ -0,0 +1,12 @@ +/** + * ENS API and batch + *

+ * kakaopay 전자문서 요청 + * E-GREEN + * SMS + *

+ * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit; diff --git a/mens-batch/src/main/resources/config/application-app.yml b/mens-batch/src/main/resources/config/application-app.yml new file mode 100644 index 0000000..53c3db6 --- /dev/null +++ b/mens-batch/src/main/resources/config/application-app.yml @@ -0,0 +1,58 @@ +#----------------------------------------------------------------------- +# application 설정 +#----------------------------------------------------------------------- + +app: + # request log + param: + log: + # exclude pattern : CommonsRequestLoggingFilter && LoggingFilter 적용 + exclude-patterns: '/swagger-ui/(.*), /api-docs/(.*)' + # CommonsRequestLoggingFilter 사용 parameter 로그 출력 + enabled: true + # parameter custom 로그 출력(LoggerAspect) + custom.enabled: false + # MDC logging trace 활성 + mdc: + log: + trace: + enabled: false + uris: '/api/kakao/(.*), /api/v1/ens/sendBulks(.*)' + + # slack + slack-webhook: + enabled: false + url: https://hooks.slack.com/services/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 + + ssh: + host: 192.168.200.52 + port: 22 + id: administrator + passwd: 1q2w3e4r5t! + sg: + root-path: /SMSImage + ens-path: /ENSData + rcv: / + backup: /backup + err: /err + + batch: + chunkSize: 2 + cron: + ens: + accept: '0/10 * 8-20 * * *' + make: '0/10 * 8-20 * * *' + send: '0/10 * 8-20 * * *' + close: '0 0/10 7 * * *' + kko-status: '0 0/5 * * * *' + pni: + accept: '0 0/1 23 * * *' +# 사용자 ID 정보를 어디에 저장할 것인지 설정 +# JWT 토큰을 사용하고 SessionCreationPolicy.STATELESS인 경우는 SecurityContext 사용불가 +# --> session에 저장하는 방식은 가능 +# security | session | header +# jwt secret key 설정 +#jwt.secret: 8sknjlO3NPTBqo319DHLNqsQAfRJEdKsETOds +# 토큰 재발급시 토큰(refresh) 전달 방식 : COOKIE | HEADER | DTO +#jwt.refresh.save.type: COOKIE +#jwt.secret: c3ByaW5nLWJvb3Qtc2VjdXJpdHktand0LWlucGl4LWNvcmUtamF2YWZyYW1ld29yay1qYXZhLXRva2VuLWtleQ diff --git a/mens-batch/src/main/resources/config/application-dev.yml b/mens-batch/src/main/resources/config/application-dev.yml new file mode 100644 index 0000000..0ca7bae --- /dev/null +++ b/mens-batch/src/main/resources/config/application-dev.yml @@ -0,0 +1,66 @@ +#----------------------------------------------------------------------- +# local 설정 +#----------------------------------------------------------------------- +spring: + + devtools: + livereload: + enabled: true + +springdoc: + api-docs: + enabled: true + swagger-ui: + enabled: true + +logging: + level: + root: debug + org.apache.http: warn + reactor.netty: warn + io.netty: warn + org.springframework.web.reactive.function.client.ExchangeFunctions: debug + org.springframework: warn + file: + # 로그파일 위치 + path: ${app.data.root.path}/ens/logs + name: ${app.name} + +# Spring Security cors 설정 :: CorsConfiguration 설정 값 +cors: + allowed-origins: http://211.119.124.9:8080 + +# ================================================================================================================== +# SQL logging lib setting +# ================================================================================================================== +decorator: + datasource: + p6spy: + enable-logging: true + +app: + license: + path: ${app.data.root.path}/ens/.pem/ + key: 'mxLAM1fAEDPWkFz8' + data1: 'WEcpGVH9g01hS8L0ke6sL9g8Hmsy4yngfEwH5L/ax62IzmdjIU6mE6GoGnC3iuRN1ENCYlVz2b8BYtAKDltMYZWx+qBjY+KscgfFoMDLIVHMGAUSDF+PyE6KV2DsKoAhc2ZdNnqbFJDgDP/qyFSRjwD1dvzeXTuCcz4mnQxOovXqiMkXwnBKhUBzq9dFeLlsk1e/YoR9SZTrpt0x0T8IeyzqI568FFqNAznmbJ+1kpi9yRaJ5c8MXXEIrfPJVONrLfeVezg1Z9jDj56zPL6gqrKhKtHJwkxpESx+5ik8O01GDcmytsLAeoIz0BiZVLJXUc/T6mZBVP6FfZnDC0dWfA==' + data2: 'YBF68uN720dTzblufB/AkZTxZnDIt6G29UkjIkJL9TDH2UkTB9FPNgtk4TqSTCFr1C4SsSU9Z/XQ957+c6K1JZe20JzDlhRWr6Zty0lCORI6erdpRq0zUsKANeJoB2hgqgeLcDxuOUN2S19MacD2VA4H0+7zKATiT8P7OEWXrAU9M2YmbEMMxHQsc6SZIa3EoGrJdNZrgeVXxL2+aJN28gmMm3CgoxLEEtuSZXK3LEZLlLEZvZtDxuxqx7Os4CtNwvHsDyFcIot6Ghbr+G3EsT+jghvvei0Cg4Qgm4n11yj0hsWR8CwmV7FTTqo03s0zBDRhYhgML81KkE/AtZSVvA==' + data3: 'IWPfGH1nDoOsevS4eyw4fItEqNymamycYa+rN+3v/iQxwy5mohdYNgm/+HcOGua2FJ6VxgGC/9i5b0jKB5L2p8bSBrEQBmluh+DsEMzeUxP8mfD2hTIUfeA9osmXW0al6BfcoJKZyiUGzxoSDY1MMdbAB9FQSBWY9yniGBaEXt3CgJmloXsVoRRolalmO6oqRJq4t1kV0fRijGGMQ5/0nu0Z5fuhCukGmEAsIW0abRXARDzoXYfAZKbKW0L70X8htpFAkk9t2IukeZemHGBBinDBWsh0ZlysHJfcXlFnFh83hXrsYcpUAn34PSd3TfLPeymdoduTF8wX7sOVMuPUVg==' + + # swagger url + url: 'http://211.119.124.9:${server.port}${server.servlet.context-path:}/' + data: + root: + path: /data + param: + log: + # CommonsRequestLoggingFilter 사용 parameter 로그 출력 + enabled: true + # parameter custom 로그 출력(LoggerAspect) + custom.enabled: true + # MDC logging trace 활성 + mdc.log.trace.enabled: true + + # slack + slack-webhook: + enabled: false + url: https://hooks.slack.com/services/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 diff --git a/mens-batch/src/main/resources/config/application-ens.yml b/mens-batch/src/main/resources/config/application-ens.yml new file mode 100644 index 0000000..62103ca --- /dev/null +++ b/mens-batch/src/main/resources/config/application-ens.yml @@ -0,0 +1,28 @@ +#----------------------------------------------------------------------- +# application 설정 +#----------------------------------------------------------------------- +contract: + provider: + # milisecond + connection: + timeout: 30000 + readTimeout: 30000 + thread: + # 동시 실행 스레드 개수 + corePoolSize: 5 + # 스레드 풀에서 사용할 수 있는 최대 개수 + maxPoolSize: 10 + kakao: + isAsync: false + bulk-max-cnt: 10 + host: https://docs-gw.kakaopay.com + #host: https://dummy.restapiexample.com + token: dd394da7f66211eb9cbe46e139ceffc2 + uuid: CON-41ef0535f67211ebbdedd2e6ed332381 + api: + send: /v1/documents;POST + validToken: /v1/{document_binder_uuid}/tokens/{tokens};GET + modifyStatus: /v1/documents/{document_binder_uuid};POST + findStatus: /v1/documents/{document_binder_uuid}/status;GET + bulksend: /v1/documents/bulk;POST + bulkstatus: /v1/documents/bulk/status;POST diff --git a/mens-batch/src/main/resources/config/application-jpa.yml b/mens-batch/src/main/resources/config/application-jpa.yml new file mode 100644 index 0000000..be0217e --- /dev/null +++ b/mens-batch/src/main/resources/config/application-jpa.yml @@ -0,0 +1,52 @@ +#----------------------------------------------------------------------- +# JPA 설정 +#----------------------------------------------------------------------- + +app: + # jpa 활성 여부 + jpa: + enabled: false + +spring: + jpa: + database-platform: org.hibernate.dialect.MySQL5InnoDBDialect + # 템플릿 view 화면의 렌더링이 끝날 때 까지 Lazy fetch 가 가능하도록 해주는 속성 + open-in-view: false + generate-ddl: false + show-sql: true + properties: + order_inserts: true + order_updates: true + default_batch_fetch_size: ${chunkSize:100} + current_session_context_class: org.springframework.orm.hibernate5.SpringSessionContext + implicit_naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + physical_naming_strategy: kr.xit.core.spring.config.support.LowercaseSnakePhysicalNamingStrategy + hibernate: + hbm2ddl: + auto: none + import_files_sql_extractor: org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor + format_sql: true + use_sql_comments: false + jdbc: + batch_size: 20 + lob: + # postgres 사용시 createLob() 미구현 경고 삭제 + non_contextual_creation: true + # Jdbc 환경구성을 하는 과정에서 Default Metadata를 사용할 지 여부 + temp: + use_jdbc_metadata_defaults: false + +# ================================================================================================================== +# JPA logging lib setting +# ================================================================================================================== +decorator: + datasource: + p6spy: + # profiles 별로 정의 + #enable-logging: true + multiline: true + logging: slf4j + custom-appender-class: com.p6spy.engine.spy.appender.Slf4JLogger + log-format: com.xit.core.config.support.P6spySqlFormatConfiguration + tracing: + include-parameter-values: true diff --git a/mens-batch/src/main/resources/config/application-local.yml b/mens-batch/src/main/resources/config/application-local.yml new file mode 100644 index 0000000..86b432e --- /dev/null +++ b/mens-batch/src/main/resources/config/application-local.yml @@ -0,0 +1,100 @@ +#----------------------------------------------------------------------- +# local 설정 +#----------------------------------------------------------------------- +spring: + datasource: + # =============== p6spy =============================== + #driver-class-name: org.mariadb.jdbc.Driver + #url: jdbc:mariadb://211.119.124.9:4407/ens?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true&autoReconnect=true + # root / xit1807 + hikari: + # 9 server + maria: + driver-class-name: org.mariadb.jdbc.Driver + jdbc-url: jdbc:mariadb://211.119.124.9:4407/mens?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true&autoReconnect=true + username: root + password: xit1807 + read-only: false + + # 115 server + # jdbc:oracle:thin:@211.119.124.115:1521:XITSMS + # xit_sms_lg / xit_sms_lg + oracle: + driver-class-name: oracle.jdbc.OracleDriver + jdbc-url: jdbc:oracle:thin:@211.119.124.115:1521:XITSMS + username: xit_sms_lg + password: xit_sms_lg + read-only: false + + devtools: + livereload: + enabled: true + +springdoc: + api-docs: + enabled: true + swagger-ui: + enabled: true + +logging: + level: + root: debug + org.apache.http: warn + reactor.netty: warn + io.netty: warn + org.springframework.web.reactive.function.client.ExchangeFunctions: debug + org.springframework: warn + file: + # 로그파일 위치 + path: ${app.data.root.path}/ens/logs + name: ${app.name} + +# Spring Security cors 설정 :: CorsConfiguration 설정 값 +cors: + allowed-origins: http://localhost:8080 + +# ================================================================================================================== +# SQL logging lib setting +# ================================================================================================================== +decorator: + datasource: + p6spy: + enable-logging: true + +app: + license: + path: ${app.data.root.path}/ens/.pem/ + key: 'mxLAM1fAEDPWkFz8' + data1: 'jz5LT6TlZtewv1GRVN3cI6CgoPgS89Sfh7qSKkCVjjMPOyBKkT386tlnMnjXluTSr8OIvI1pHd96fHxRZHNUBuLeQOkUeWuzkfxlP8C7nDyIrG36T2aontjroAxoNk3oYdIYRVWNs1Iqw39v9xF8NYFFLGqtfv/wnCGxlwTsDwCf9bjAtyd9cTiS27dVrIbrAVKnchgxIF/DUQQc901l4DZ5gsT6aLx6TmzhvAewPK1HfiG9WrMWxpw0TMxt++0Vedh+oZEs48ACMpuHFFh406LynyxxE7boRVbKmh7Tn87gKa6+zzdzIN1kS8sk58Ms1HPvBfvwwnD8qiJItXO6DQ==' + data2: 'Te3bfmvdiMZdpfRwC3OO9UjwbNkvbf16kqEqn9VqwbVztizA9rvMFshlI0vuqai9Hml31IsNINKg+OYkhmkH6ic1I10r6MNIVl3WL5YxfeK7YBmjvNuGZtKwchlWzhMODsgNAq0aIQVi4kLk5filDaZESY10xlNdbf9c/SKGfJeLZxY7DCchkAgj/ZnmZNqOE2kDAoC+O1ksDNTS0+cr+WsKsoFON0EpNI5B2ElBtnT1LmQQ3R+FNCtp7YJaRZA3RPsata05kKH7sL1J0M6A6HIVxisOU3bjH0hB+60BHJfdlEiXo6RJvsPyXotwe8MYVrHJbIQgsepxSDpMFZe8HA==' + data3: 't9qYJTU49dbaeezJkzpM2uY3iLIcy/V/VnyVsWIcd0f4QMLJ3cmLZ0QcMyKoR7CL2CuHMnPJz8j7KFOTQRPpeN/Dl4bCpOu+BM3foYpn4wb5HcLdHJxp5CuFmhTfqRGuUxurv6jcqkwmRzPW35UjQLjeKSdv6m+2b84PN4sZSNeMjQDH0QC85yKphHKV8m6bzqUbHLiZwDXndgpq2/YGKdWjPinlH7PZ+L2xfrfhdWXoY9QrHYVOSPogd81EizzyQseif8GAkeUG1OKAOomhyEuTOxtdGbUew59YuuBlUpORgj/Koclyd2shHyne9CJdqnQqAA2mh61V1ZzBUvSIWQ==' + # swagger url + url: 'http://localhost:${server.port}${server.servlet.context-path:}/' + data: + root: + path: D:/data + param: + log: + # CommonsRequestLoggingFilter 사용 parameter 로그 출력 + enabled: true + # parameter custom 로그 출력(LoggerAspect) + custom.enabled: true + # MDC logging trace 활성 + mdc.log.trace.enabled: true + + # slack + slack-webhook: + enabled: false + url: https://hooks.slack.com/services/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 + + ssh: + host: 211.119.124.9 + port: 22 + id: xituser + passwd: xituser!@ + sg: + root-path: /data/ens/sg-pni-cctv + ens-path: /data/ens/sg-ens-cctv + rcv: /rcv + backup: /backup + err: /err diff --git a/mens-batch/src/main/resources/config/application-prod.yml b/mens-batch/src/main/resources/config/application-prod.yml new file mode 100644 index 0000000..5b3cd98 --- /dev/null +++ b/mens-batch/src/main/resources/config/application-prod.yml @@ -0,0 +1,92 @@ +#----------------------------------------------------------------------- +# local 설정 +#----------------------------------------------------------------------- +spring: + datasource: + # ================ log4jdbc =========================== + #driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy + # mysql에서 batchUpdate 사용하기 위해 rewriteBatchedStatements 필요 + #url: jdbc:log4jdbc:mariadb://211.119.124.117:53306/ens?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true + # ===================================================== + # =============== p6spy =============================== + #driver-class-name: org.mariadb.jdbc.Driver + #url: jdbc:mariadb://211.119.124.9:4407/ens?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true&autoReconnect=true + + # r2dbc 설정 : 추후 진행 여부 결정 + #driver-class-name: org.mariadb.jdbc.Driver + #url: r2dbc:pool:mariadb://211.119.124.9:4407/ens?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true + # ===================================================== + #username: root + #password: xit1807 + hikari: + # 9 server + maria: + driver-class-name: org.mariadb.jdbc.Driver + # jdbc:mariadb://127.0.0.1:4407/ens?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true&autoReconnect=true + jdbc-url: ENC(QrsIhXG/ZgD5a1SYaf+1CaZYkLxuMP4s7//+NXV1SlLHKXRFm7FFA8iP10Dlak3VOFjFSaWEkchSWcNQ4+4m94fSWW7uy+/4nW3jBq4aHuvVVCqy2rfgInqYbYMlno91vUmWkMbKuX8T5nF7UltaAuQOXH/ImCT8whPxAE+VOM1onyuIr0fbVg==) + username: ENC(L8Xp+ZuLxlXahupxYDWzgA==) + password: ENC(eDCxblnWkDHhJEEXMdkzww==) + read-only: false + + # 115 server + oracle: + driver-class-name: oracle.jdbc.OracleDriver + jdbc-url: ENC(zWLLN8fkbayeHrlyXlG8sHIA9J1OdCZcDy3Snj/5hdDr9GfBIkMsboW3Z9KTRrCV7b7LU5+FIFY=) + username: ENC(wiNWcgb1ADyNwabf+S6KwDX/ou2aV6Z8) + password: ENC(5ejKeOJ/t7YEn/ikHKmNHdejhHHWOBuC) + read-only: false + + devtools: + livereload: + enabled: true + +springdoc: + api-docs: + enabled: true + swagger-ui: + enabled: true + +logging: + level: + root: debug + org.apache.http: warn + reactor.netty: warn + io.netty: warn + org.springframework.web.reactive.function.client.ExchangeFunctions: debug + org.springframework: warn + file: + # 로그파일 위치 + path: ${app.data.root.path}/ens/logs + name: ${app.name} + +# Spring Security cors 설정 :: CorsConfiguration 설정 값 +cors: + allowed-origins: http://localhost:8080 + +# ================================================================================================================== +# SQL logging lib setting +# ================================================================================================================== +decorator: + datasource: + p6spy: + enable-logging: true + +app: + # swagger url + url: 'http://localhost:${server.port}${server.servlet.context-path:}/' + data: + root: + path: d:/data + param: + log: + # CommonsRequestLoggingFilter 사용 parameter 로그 출력 + enabled: true + # parameter custom 로그 출력(LoggerAspect) + custom.enabled: true + # MDC logging trace 활성 + mdc.log.trace.enabled: true + + # slack + slack-webhook: + enabled: false + url: https://hooks.slack.com/services/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 diff --git a/mens-batch/src/main/resources/config/application.yml b/mens-batch/src/main/resources/config/application.yml new file mode 100644 index 0000000..3ce79b8 --- /dev/null +++ b/mens-batch/src/main/resources/config/application.yml @@ -0,0 +1,97 @@ +#----------------------------------------------------------------------- +# +# egovframework 설정 +# api 공통 설정 +#----------------------------------------------------------------------- +Globals: + # DB서버 타입(hsql,mysql,oracle,altibase,tibero) - datasource 및 sqlMap 파일 지정에 사용됨 + DbType: mysql + + # MainPage Setting + MainPage: /cmm/main/mainPage.do + +#server.servlet.context-path=/sht_boot_web +server: + port: 8082 + error: + whitelabel: + enabled: false +app: + name: mens-batch + # springdoc url 정보 + desc: 모바일 전자고지 batch + data: + root: + path: /data + +spring: + main: + # 순환참조 에러 무시 + allow-circular-references: true + pid: + file: ${app.data.root.path}/${app.name}.pid + profiles: + active: '@springProfilesActive@' + + # core의 application-common.yml과 application-auth.yml include + include: + - common + - auth + - app + - ens + + batch: + jdbc: + initialize-schema: NEVER #NEVER|ALWAYS + # JPA does not support custom isolation levels, so locks may not be taken when launching Jobs. + # To silence this warning, set 'spring.batch.jdbc.isolation-level-for-create' to 'default'. + isolation-level-for-create: default + job: + enabled: false + + #----------------------------------------------------------------- + # xit framework 설정 + #----------------------------------------------------------------- + datasource: + #type: com.zaxxer.hikari.HikariDataSource + hikari: + maria: + pool-name: xit-maria-pool + auto-commit: false + # 인프라의 적용된 connection time limit보다 작아야함 + max-lifetime: 1800000 + maximum-pool-size: 10 + minimum-idle: 5 + #transaction-isolation: TRANSACTION_READ_UNCOMMITTED + data-source-properties: + rewriteBatchedStatements: true + + oracle: + pool-name: xit-oracle-pool + auto-commit: false + # 인프라의 적용된 connection time limit보다 작아야함 + max-lifetime: 1800000 + maximum-pool-size: 10 + minimum-idle: 5 +# transaction-isolation: TRANSACTION_READ_UNCOMMITTED + data-source-properties: + rewriteBatchedStatements: true + + # @JsonInclude(JsonInclude.Include.NON_NULL) 설정 + jackson: + default-property-inclusion=non_null: non_null + + + + +logging: + level: + root: error + +file: + cmm: + upload: + root: ${app.data.root.path} + # root: /data + # E-GREEN 우편 발송 + post: /post/rcv/ diff --git a/mens-batch/src/main/resources/config/google_checks.xml b/mens-batch/src/main/resources/config/google_checks.xml new file mode 100644 index 0000000..1ca2848 --- /dev/null +++ b/mens-batch/src/main/resources/config/google_checks.xml @@ -0,0 +1,382 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mens-batch/src/main/resources/egovframework/mapper/biz/ens-cctv-mysql-mapper.xml b/mens-batch/src/main/resources/egovframework/mapper/biz/ens-cctv-mysql-mapper.xml new file mode 100644 index 0000000..e21880c --- /dev/null +++ b/mens-batch/src/main/resources/egovframework/mapper/biz/ens-cctv-mysql-mapper.xml @@ -0,0 +1,152 @@ + + + + + + + + /** ens-mysql-mapper|insertNtcnCntcData-전자 고지연계데이타 생성|seojh */ + + SELECT concat(date_format(now(), '%Y%m%d'), LPAD(NEXTVAL(tb_ens_cntc_data_seq), 12, '0')) from dual + + INSERT + INTO tb_ens_cntc_data ( + ens_cntc_data_id, /* 전자 고지연계데이타 ID*/ + reglt_dt, /* 단속일시 */ + vhcle_no, /* 차량번호 */ + spt_nm, /* 현장명 */ + spt_accto_code, /* 현장별코드 */ + file_nm, /* 파일명 */ + regist_dt, + register + ) VALUES ( + #{ensCntcDataId}, + #{regltDt}, + #{vhcleNo}, + #{sptNm}, + #{sptAcctoCode}, + #{fileNm}, + now(), + 'batch' + ) + + + + + + /** ens-mysql-mapper|insertCntcSndngMst-연계발송마스터 생성|seojh */ + + SELECT concat('E', signgu_code, ffnlg_code, RIGHT(date_format(now(), '%Y'),2), LPAD(NEXTVAL(tb_cntc_sndng_mastr_seq), 7, '0')) AS unitySndngMastrId + , date_format(date_add(now(), interval + 20 day), '%Y%m%d%H%i%S') AS closDt + FROM tb_ens_tmplat_manage + WHERE sndng_ty_code = #{sndngTyCode} + AND tmplat_id = #{tmplatId} + + INSERT + INTO tb_cntc_sndng_mastr ( + unity_sndng_mastr_id, /* 통합발송마스터 id */ + signgu_code, /* 시군구코드 */ + ffnlg_code, /* 과태료코드 */ + tmplat_id, /* 템플릿ID */ + sndng_ty_code, /* 발송유형 코드 */ + sndng_co, /* 발송 건수 */ + sndng_process_sttus, /* 발송처리 상태 */ + sndng_dt, /* 발송일시 */ + clos_dt, /* 마감일시 */ + regist_dt, + register + ) + SELECT #{unitySndngMastrId} + , tetm.signgu_code + , tetm.ffnlg_code + , tetm.tmplat_id + , tetm.sndng_ty_code + , #{sndngCo} + , #{sndngProcessSttus} + , date_format(now(), '%Y%m%d%H%i%S') + , #{closDt} + , now() + , 'batch' + FROM tb_ens_tmplat_manage tetm + WHERE sndng_ty_code = #{sndngTyCode} + AND tmplat_id = #{tmplatId} + + + + /** ens-mysql-mapper|insertCntcSndngDtl-연계발송상세 생성|seojh */ + + SELECT concat('E', signgu_code, ffnlg_code, RIGHT(date_format(now(), '%Y'),2), LPAD(NEXTVAL(tb_cntc_sndng_detail_seq), 7, '0')) + FROM tb_ens_tmplat_manage + WHERE sndng_ty_code = #{sndngTyCode} + AND tmplat_id = #{tmplatId} + + INSERT + INTO tb_cntc_sndng_detail ( + unity_sndng_detail_id, /* 통합발송 상세ID */ + unity_sndng_mastr_id, /* 통합발송 마스터ID */ + signgu_code, /* 시군구 코드 */ + ffnlg_code, /* 과태료 코드 */ + vhcle_no, /* 차량번호 */ + ihidnum, /* 주민번호 - 현재는 전화번호 기반 생년월일 */ + moblphon_no, /* 핸드폰 번호 */ + nm, /* 이름 */ + adres, /* 주소 */ + detail_adres, /* 상세 주소 */ + zip, /* 우편번호 */ + mobile_page_cn, /* 모바일 페이지 내용 */ + regist_dt, + register + ) + SELECT #{unitySndngDetailId} + , #{unitySndngMastrId} + , tetm.signgu_code + , tetm.ffnlg_code + , #{vhcleNo} + , #{brthdy} + , #{moblphonNo} + , #{nm} + , #{adres} + , #{detailAdres} + , #{zip} + , #{mobilePageCn} + , now() + , 'batch' + FROM tb_ens_tmplat_manage tetm + WHERE sndng_ty_code = #{sndngTyCode} + AND tmplat_id = #{tmplatId} + + + + /** ens-mysql-mapper|updateEnsNtcnCntcData-전자 고지 연계데이타 연계결과 반영|seojh */ + UPDATE tb_ens_cntc_data + SET process_at = #{processAt} + , unity_sndng_detail_id = #{unitySndngDetailId} + , updt_dt = now() + , updusr = 'batch' + WHERE ens_cntc_data_id = #{ensCntcDataId} + + diff --git a/mens-batch/src/main/resources/egovframework/mapper/biz/ens-mysql-mapper.xml b/mens-batch/src/main/resources/egovframework/mapper/biz/ens-mysql-mapper.xml new file mode 100644 index 0000000..49ad4bb --- /dev/null +++ b/mens-batch/src/main/resources/egovframework/mapper/biz/ens-mysql-mapper.xml @@ -0,0 +1,748 @@ + + + + + + + + + + + /** ens-mysql-mapper|insertUnitySndngMst-통합발송마스터 생성|julim */ + INSERT + INTO tb_ens_unity_sndng_mastr ( + unity_sndng_mastr_id, /* 통합발송마스터 ID */ + signgu_code, /* 시군구 코드 */ + ffnlg_code, /* 과태료 코드 */ + tmplat_id, /* 템플릿 Id */ + sndng_ty_code, /* 발송유형 코드 */ + sndng_co, /* 발송건수 */ + sndng_process_sttus, /* 발송처리 상태 */ + sndng_dt_1, /* 발송 일시 */ + sndng_dt_2, /* 발송 일시2 */ + sndng_dt_3, /* 발송 일시3 */ + try1, + try2, + try3, + try_cnt, + clos_dt, /* 마감일시 */ + regist_dt, + register + ) + SELECT tcsm.unity_sndng_mastr_id /* 통합발송마스터 ID */ + , tcsm.signgu_code /* 시군구 코드 */ + , tcsm.ffnlg_code /* 과태료 코드 */ + , tcsm.tmplat_id /* 템플릿 Id */ + , tetm.sndng_ty_code /* 발송유형 코드 */ + , tcsm.sndng_co /* 발송건수 */ + , 'accept-ok' /* 발송처리 상태 */ + , tcsm.sndng_dt + , #{sndngDt2} + , #{sndngDt3} + , tetm.try1 + , tetm.try2 + , tetm.try3 + , CASE WHEN IFNULL(tetm.try3, '') != '' + THEN 3 + ELSE IF(IFNULL(tetm.try2, '') != '', 2, 1) + END /* try3 값이 있으면 3, try2 값이 있으면 2, try1 */ + , clos_dt /* 마감일시 */ + , now() + , 'batch' + FROM tb_cntc_sndng_mastr tcsm + JOIN tb_ens_tmplat_manage tetm + ON tcsm.tmplat_id = tetm.tmplat_id + WHERE tcsm.unity_sndng_mastr_id = #{unitySndngMastrId} + AND tcsm.sndng_process_sttus = #{sndngProcessSttus} + + + + /** ens-mysql-mapper|insertUnitySndngDtls-통합발송상세 생성|julim */ + INSERT + INTO tb_ens_unity_sndng_detail ( + unity_sndng_detail_id, /* 통합발송상세 ID*/ + unity_sndng_mastr_id, /* 통합발송마스터 ID */ + signgu_code, /* 시군구 코드 */ + ffnlg_code, /* 과태료 코드 */ + main_code, + vhcle_no, /* 차량번호 */ + ihidnum, /* 주민번호 */ + moblphon_no, /* 전화번호 */ + nm, /* 이름 */ + adres, /* 주소 */ + detail_adres, /* 상세주소 */ + zip, /* 우편번호 */ + tmplt_msg_data, /* 템플릿메세지 데이타 */ + mobile_page_cn, /* 모바일페이지내용 */ + use_instt_idntfc_id, /* 이용기관식별Id*/ + regist_dt, + register + ) + SELECT tcsd.unity_sndng_detail_id /* 통합발송상세 ID*/ + , tcsd.unity_sndng_mastr_id /* 통합발송마스터 ID */ + , tcsd.signgu_code /* 시군구 코드 */ + , tcsd.ffnlg_code /* 과태료 코드 */ + , tcsd.main_code + , tcsd.vhcle_no /* 차량번호 */ + , tcsd.ihidnum /* 주민번호 */ + , tcsd.moblphon_no /* 전화번호 */ + , tcsd.nm /* 이름 */ + , tcsd.adres /* 주소 */ + , tcsd.detail_adres /* 상세주소 */ + , tcsd.zip /* 우편번호 */ + , tcsd.tmplt_msg_data /* 템플릿메세지 데이타 */ + , tcsd.mobile_page_cn /* 모바일페이지내용 */ + , tcsd.use_instt_idntfc_id /* 이용기관식별Id*/ + , now() + , 'batch' + FROM tb_cntc_sndng_mastr tcsm + JOIN tb_cntc_sndng_detail tcsd + ON tcsm.unity_sndng_mastr_id = tcsd.unity_sndng_mastr_id + WHERE tcsm.unity_sndng_mastr_id = #{unitySndngMastrId} + AND tcsm.sndng_process_sttus = #{sndngProcessSttus} + + + + /** ens-mysql-mapper|updateProcessSttusCntcSndngMst-연계발송마스터 상태 변경|julim */ + UPDATE tb_cntc_sndng_mastr + SET sndng_process_sttus = #{newSndngProcessSttus} + , error_code = #{errorCode} + , error_mssage = #{errorMssage} + , updt_dt = now() + , updusr = 'batch' + WHERE unity_sndng_mastr_id = #{unitySndngMastrId} + + + + + + + + + + + + + + /** ens-mysql-mapper|insertSndngMst-발송마스터 생성|julim */ + + SELECT concat(date_format(now(), '%Y%m%d'), LPAD(NEXTVAL(tb_ens_sndng_mastr_seq), 12, '0')) from dual + + INSERT + INTO tb_ens_sndng_mastr ( + sndng_mastr_id, /* 발송마스터 ID*/ + unity_sndng_mastr_id, /* 통합발송마스터 ID */ + signgu_code, /* 시군구 코드 */ + ffnlg_code, /* 과태료 코드 */ + tmplat_id, /* 템플릿 Id */ + sndng_ty_code, /* 발송유형 코드 */ + sndng_se_code, /* 발송구분 코드 */ + sndng_co, /* 발송건수 */ + try_seq, + try_cnt, + sndng_process_sttus, /* 발송처리 상태 */ + sndng_dt, /* 발송일시 */ + clos_dt, /* 마감일시 */ + regist_dt, + register + ) + SELECT #{sndngMastrId} + , teusm.unity_sndng_mastr_id /* 통합발송마스터 ID */ + , teusm.signgu_code /* 시군구 코드 */ + , teusm.ffnlg_code /* 과태료 코드 */ + , teusm.tmplat_id /* 템플릿 Id */ + , teusm.sndng_ty_code /* 발송유형 코드 */ + , #{sndngSeCode} /* 발송구분 코드 */ + , #{sndngCo} /* 발송건수 */ + , #{trySeq} + , teusm.try_cnt + , 'make-ok' /* 발송처리 상태 */ + , CASE WHEN #{trySeq}=3 THEN teusm.sndng_dt_3 + WHEN #{trySeq}=2 THEN teusm.sndng_dt_2 + ELSE teusm.sndng_dt_1 + END /* 1차, 2차, 3차에 따른 발송일시 */ + , teusm.clos_dt /* 마감일시 */ + , now() + , 'batch' + FROM tb_ens_unity_sndng_mastr teusm + WHERE teusm.unity_sndng_mastr_id = #{unitySndngMastrId} + AND teusm.sndng_process_sttus = #{sndngProcessSttus} + + + +/** ens-mysql-mapper|insertKakaoMyDocs-카카오내문서함 생성|julim */ +INSERT +INTO tb_ens_kakao_my_doc ( + sndng_detail_id, /* 발송상세ID */ + unity_sndng_detail_id, /* 통합발송상세ID */ + sndng_mastr_id, /* 발송마스터ID */ + signgu_code, /* 시군구코드 */ + ffnlg_code, /* 과태료코드 */ + title, /* 제목 */ + hash, + common_categories, + read_expired_at, /* 처리마감시간 */ + -- recv_ci, /* 받는이 CI */ + recv_phone_number, /* 받는이 전화번호 */ + recv_name, /* 받는이 이름 */ + recv_birthday, /* 받는이 생년월일 */ + recv_is_required_verify_name, /* 성명검증옵션 */ + prop_link, /* 모바일페이지 URL */ + prop_payload, + prop_message, + prop_cs_number, /* 고객센터 전화번호 */ + prop_cs_name, /* 고객센터 명 */ + -- external_document_uuid, /* 외부문서 식별번호 */ + regist_dt, + register +) +SELECT LPAD(NEXTVAL(sndng_detail_id_seq), 20, '0') + , teusd.unity_sndng_detail_id + , #{sndngMastrId} + , teusm.signgu_code + , teusm.ffnlg_code + , tetm.tmplat_nm + , SHA2(teusd.unity_sndng_detail_id, 256) + , '[NOTICE]' + , unix_timestamp(teusm.clos_dt) + -- , NULL + , teusd.moblphon_no + , teusd.nm + , teusd.ihidnum + , 'false' + , tetm.redirect_url + , tetm.tmplat_sj + , CASE WHEN teusm.sndng_ty_code='PNI' THEN teusd.tmplt_msg_data + ELSE tetm.tmplat_cn END + , tetm.cstmr_cnter_tlphon_no + , '콜센터' + -- , null + , now() + , 'batch' +FROM tb_ens_unity_sndng_mastr teusm + LEFT JOIN tb_ens_tmplat_manage tetm + ON teusm.tmplat_id = tetm.tmplat_id + LEFT JOIN tb_ens_unity_sndng_detail teusd + ON teusm.unity_sndng_mastr_id = teusd.unity_sndng_mastr_id + WHERE teusm.unity_sndng_mastr_id = #{unitySndngMastrId} + AND teusm.sndng_process_sttus = #{sndngProcessSttus} + + + + /** ens-mysql-mapper|insertSmsSndng-문자발송 데이터 생성|julim */ + INSERT + INTO tb_ens_sms_sndng ( + sndng_detail_id, /* 발송 상세 id */ + unity_sndng_detail_id, /* 통합 발송 상세 id */ + sndng_mastr_id, /* 발송 마스터 id */ + signgu_code, /* 시군구 코드 */ + ffnlg_code, /* 과태료 코드 */ + vhcle_no, /* 차량 번호 */ + sms_sndng_dt, /* 문자 발송 일시 */ + sms_trnsmis_stle, /* 문자 전송 형태 */ + sms_trnsmit_tlphon_no, /* 문자 송신 전화 번호 */ + sms_recptn_tlphon_no, /* 문자 수신 전화 번호 */ + sms_mssage, /* 문자 메시지 */ + sms_sndng_sttus, /* 문자 발송 상태 */ + sms_sndng_process_sttus, /* 문자 발송 처리 상태 */ + regist_dt, + register + ) + SELECT LPAD(NEXTVAL(sndng_detail_id_seq), 20, '0') + , teusd.unity_sndng_detail_id + , #{sndngMastrId} + , teusm.signgu_code + , teusm.ffnlg_code + , teusd.vhcle_no + , date_format(now(), '%Y%m%d%H%i%S') + , '0' + , REPLACE(tetm.cstmr_cnter_tlphon_no, '-', '') + , teusd.moblphon_no + , teusd.tmplt_msg_data + , '2' + , '06' + , now() + , 'batch' + FROM tb_ens_unity_sndng_mastr teusm + LEFT JOIN tb_ens_tmplat_manage tetm + ON teusm.tmplat_id = tetm.tmplat_id + LEFT JOIN tb_ens_unity_sndng_detail teusd + ON teusm.unity_sndng_mastr_id = teusd.unity_sndng_mastr_id + WHERE teusm.unity_sndng_mastr_id = #{unitySndngMastrId} + AND teusm.sndng_process_sttus = #{sndngProcessSttus} + + + + INSERT + INTO tb_ens_post_sndng ( + sndng_detail_id, /* 발송 상세 id */ + unity_sndng_detail_id, /* 통합 발송 상세 id */ + sndng_mastr_id, /* 발송 마스터 id */ + signgu_code, /* 시군구 코드 */ + ffnlg_code, /* 과태료 코드 */ + service_cd, /* 서비스 코드 */ + con_key, /* 연계 식별 키 */ + sender_nm, /* 발송인 명 */ + sender_zip_no, /* 발송인 우편번호 */ + sender_addr, /* 발송인 주소 */ + sender_detail_addr, /* 발송인 상세 주소 */ + receiver_send_no, /* 수취인 일련 번호 */ + receiver_nm, /* 수취인 명 */ + receiver_zip_no, /* 수취인 우편번호 */ + receiver_addr, /* 수취인 주소 */ + receiver_detail_addr, /* 수취인 상세 주소 */ + sschnge_1, /* 가변 1 */ + sschnge_2, /* 가변 2 */ + sschnge_3, /* 가변 3 */ + regist_dt, + register + ) + SELECT LPAD(NEXTVAL(sndng_detail_id_seq), 20, '0') + , teusd.unity_sndng_detail_id + , #{sndngMastrId} + , teusm.signgu_code + , teusm.ffnlg_code + , 'PST' + , (SELECT CONCAT('0009900112Z1',date_format(now(), '%Y%m%d%H%i%S')) from dual) + , term.sender_nm + , term.sender_zip_no + , term.sender_addr + , term.sender_detail_addr + , row_number() over(order by teusd.unity_sndng_detail_id) + , teusd.nm + , teusd.zip + , teusd.adres + , teusd.detail_adres + , CONCAT(teusd.vhcle_no, '|', tecd.spt_nm) + , CONCAT(tecd.reglt_dt, '|', date_format(date_add(now(), interval + 20 day), '%Y%m%d')) + , '32000' + , now() + , 'batch' + FROM tb_ens_unity_sndng_mastr teusm + LEFT JOIN tb_ens_tmplat_manage tetm + ON teusm.tmplat_id = tetm.tmplat_id + LEFT JOIN tb_ens_unity_sndng_detail teusd + ON teusm.unity_sndng_mastr_id = teusd.unity_sndng_mastr_id + LEFT JOIN tb_ens_rlaybsnm_manage term + ON teusm.signgu_code = term.signgu_code AND teusm.ffnlg_code = term.ffnlg_code + LEFT JOIN tb_ens_cntc_data tecd + ON teusd.unity_sndng_detail_id=tecd.unity_sndng_detail_id + + INNER JOIN tb_ens_kakao_my_doc tekmd + ON teusd.unity_sndng_detail_id = tekmd.unity_sndng_detail_id + AND (NOT (tekmd.doc_box_status = 'READ' AND tekmd.error_code IS NULL) OR (tekmd.doc_box_status IS NULL and tekmd.error_code IS NULL)) + + WHERE teusm.unity_sndng_mastr_id = #{unitySndngMastrId} + AND teusm.sndng_process_sttus = #{sndngProcessSttus} + + + + /** ens-mysql-mapper|updateProcessSttusUntySndngMst-통합발송마스터 상태 변경|julim */ + UPDATE tb_ens_unity_sndng_mastr + SET sndng_process_sttus = #{newSndngProcessSttus} + , error_code = #{errorCode} + , error_mssage = #{errorMssage} + , updt_dt = now() + , updusr = 'batch' + WHERE unity_sndng_mastr_id = #{unitySndngMastrId} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /** ens-mysql-mapper|updateKakaoSendBulksResult-카카오페이 문서요청 결과 반영|julim */ + UPDATE tb_ens_kakao_my_doc + SET external_document_uuid = #{external_document_uuid} + , document_binder_uuid = #{document_binder_uuid} + , error_code = #{error_code} + , error_message = #{error_message} + , updt_dt = now() + , updusr = 'batch' + WHERE unity_sndng_detail_id = #{external_document_uuid} + + + + /** ens-mysql-mapper|insertMobilePageManage-모바일페이지관리 데이타 생성|julim */ + INSERT + INTO tb_ens_mobile_page_manage ( + sndng_detail_id, /* 발송상세 ID*/ + sndng_se_code, /* 발송 구분 코드 */ + mobile_page_cn, /* 모바일 페이지 내용 */ + regist_dt, + register + ) + SELECT tekmd.sndng_detail_id + , 'KKO-MY-DOC' + , teusd.mobile_page_cn + , date_format(now(), '%Y%m%d%H%i%s') + , 'batch' + FROM tb_ens_kakao_my_doc tekmd + JOIN tb_ens_unity_sndng_detail teusd + ON tekmd.unity_sndng_detail_id = teusd.unity_sndng_detail_id + WHERE teusd.unity_sndng_detail_id = #{unitySndngDetailId} + + + + /** ens-mysql-mapper|updateProcessSttusSndngMst-발송마스터 상태 변경|julim */ + UPDATE tb_ens_sndng_mastr + SET sndng_process_sttus = #{newSndngProcessSttus} + , error_code = #{errorCode} + , error_mssage = #{errorMssage} + , updt_dt = now() + , updusr = 'batch' + WHERE sndng_mastr_id = #{sndngMastrId} + + + + /** ens-mysql-mapper|updateProcessSttusBulkSndngMst-발송마스터 상태 다건 변경|julim */ + UPDATE tb_ens_sndng_mastr + SET sndng_process_sttus = #{newSndngProcessSttus} + , updt_dt = now() + , updusr = 'batch' + WHERE unity_sndng_mastr_id = #{unitySndngMastrId} + + + + + + + + + + + + /** ens-mysql-mapper|updateKakaoStatusInfo-카카오 문서 상태 조회 결과 반영|julim */ + UPDATE tb_ens_kakao_my_doc + SET doc_box_status = #{status_data.doc_box_status} /* 진행상태 */ + , doc_box_sent_at = #{status_data.doc_box_sent_at} /* 송신시간 */ + , doc_box_received_at = #{status_data.doc_box_received_at} /* 수신시간 */ + , doc_box_read_at = #{status_data.doc_box_read_at} /* 최초열람시간 */ + , authenticated_at = #{status_data.authenticated_at} /* 최초열람인증시간 */ + , token_used_at = #{status_data.token_used_at} /* 최초OTT 검증시간 */ + , user_notified_at = #{status_data.user_notified_at} /* 알림톡 수신시간 */ + , updt_dt = now() + , updusr = 'batch' + WHERE unity_sndng_detail_id = (SELECT tekmd.unity_sndng_detail_id + FROM tb_ens_kakao_my_doc tekmd + WHERE tekmd.document_binder_uuid = #{document_binder_uuid}) + + + + + + + + + + + /** ens-mysql-mapper|insertCntcSndngResult-연계발송결과반영|julim */ + INSERT + INTO tb_cntc_sndng_result ( + unity_sndng_detail_id, /* 통합 발송 상세 ID*/ + sndng_se_code, /* 발송구분코드 */ + signgu_code, /* 시군구코드 */ + ffnlg_code, /* 과태료코드 */ + sndng_result_sttus, + requst_dt, /* 송신(요청) 일시 */ + inqire_dt, /* 수신(조회) 일시 */ + readng_dt, /* 최초열람일시 */ + error_cn, /* 에러내용 */ + regist_dt, + register + ) SELECT unity_sndng_detail_id /* 통합 발송 상세 ID*/ + , #{sndngSeCode} /* 발송구분코드 - KAKAO-MY-DOC */ + , signgu_code /* 시군구코드 */ + , ffnlg_code /* 과태료코드 */ + , #{sndngResultSttus} + , from_unixtime(#{requstDt}, '%Y%m%d%H%i%s') /* 송신(요청) 일시 */ + , from_unixtime(#{inqireDt}, '%Y%m%d%H%i%s') /* 수신(조회) 일시 */ + , from_unixtime(#{readngDt}, '%Y%m%d%H%i%s') /* 최초열람일시 */ + , #{errorCn} /* 에러내용 */ + , now() + , 'batch' + FROM tb_ens_unity_sndng_detail + WHERE unity_sndng_detail_id = #{unitySndngDetailId} + + + + /** ens-mysql-mapper|updateCntcSndngResult-연계발송결과반영|julim */ + UPDATE tb_cntc_sndng_result + SET sndng_result_sttus = #{sndngResultSttus} + , requst_dt = from_unixtime(#{requstDt}, '%Y%m%d%H%i%s') /* 송신(요청) 일시 */ + , inqire_dt = from_unixtime(#{inqireDt}, '%Y%m%d%H%i%s') /* 수신(조회) 일시 */ + , readng_dt = from_unixtime(#{readngDt}, '%Y%m%d%H%i%s') /* 최초열람일시 */ + , error_cn = #{errorCn} + , updt_dt = now() + , updusr = 'batch' + WHERE unity_sndng_detail_id = (SELECT tekmd.unity_sndng_detail_id + FROM tb_ens_kakao_my_doc tekmd + WHERE tekmd.document_binder_uuid = #{documentBinderUuid}) + + + + + + + + + + + + + + + diff --git a/mens-batch/src/main/resources/egovframework/mapper/biz/ens-oracle-mapper.xml b/mens-batch/src/main/resources/egovframework/mapper/biz/ens-oracle-mapper.xml new file mode 100644 index 0000000..b3fc167 --- /dev/null +++ b/mens-batch/src/main/resources/egovframework/mapper/biz/ens-oracle-mapper.xml @@ -0,0 +1,36 @@ + + + + + + + + + /** ens-oracle-mapper|insertScTran-sms 발송 데이타 생성|julim */ + INSERT + INTO sc_tran ( + tr_num, + tr_senddate, + tr_sendstat, + tr_msgtype, + tr_phone, + tr_callback, + tr_msg, + tr_etc1 + ) VALUES ( + sc_tran_seq.nextval, + sysdate, + '0', /* 문자 발송 상태 */ + '0', /* 문자 전송 형태 */ + #{smsRecptnTlphonNo}, /* 문자 수신 전화 번호 */ + #{smsTrnsmitTlphonNo}, /* 문자 송신 전화 번호 */ + #{smsMssage}, /* 문자 메세지 */ + #{sndngDetailId} /* 발송 상세 ID */ + ) + + + + + + diff --git a/mens-batch/src/main/resources/egovframework/mapper/biz/pni-mysql-mapper.xml b/mens-batch/src/main/resources/egovframework/mapper/biz/pni-mysql-mapper.xml new file mode 100644 index 0000000..2ef7375 --- /dev/null +++ b/mens-batch/src/main/resources/egovframework/mapper/biz/pni-mysql-mapper.xml @@ -0,0 +1,151 @@ + + + + + + + + /** pni-mysql-mapper|insertNtcnCntcData-사전알림연계데이타 생성|julim */ + + SELECT concat(date_format(now(), '%Y%m%d'), LPAD(NEXTVAL(tb_pni_ntcn_cntc_data_seq), 12, '0')) from dual + + INSERT + INTO tb_pni_ntcn_cntc_data ( + ntcn_cntc_data_id, /* 사전알림연계데이타 ID*/ + reglt_dt, /* 단속일시 */ + vhcle_no, /* 차량번호 */ + spt_nm, /* 현장명 */ + spt_accto_code, /* 현장별코드 */ + file_nm, /* 파일명 */ + regist_dt, + register + ) VALUES ( + #{ntcnCntcDataId}, + #{regltDt}, + #{vhcleNo}, + #{sptNm}, + #{sptAcctoCode}, + #{fileNm}, + now(), + 'batch' + ) + + + + + + /** pni-mysql-mapper|insertCntcSndngMst-연계발송마스터 생성|julim */ + + SELECT concat('P', signgu_code, ffnlg_code, RIGHT(date_format(now(), '%Y'),2), LPAD(NEXTVAL(tb_cntc_sndng_mastr_seq), 7, '0')) + FROM tb_ens_tmplat_manage + WHERE sndng_ty_code = #{sndngTyCode} + AND tmplat_id = #{tmplatId} + + INSERT + INTO tb_cntc_sndng_mastr ( + unity_sndng_mastr_id, /* 통합발송마스터 id */ + signgu_code, /* 시군구코드 */ + ffnlg_code, /* 과태료코드 */ + tmplat_id, /* 템플릿ID */ + sndng_ty_code, /* 발송유형 코드 */ + sndng_co, /* 발송 건수 */ + sndng_process_sttus, /* 발송처리 상태 */ + sndng_dt, /* 발송일시 */ + clos_dt, /* 마감일시 */ + regist_dt, + register + ) + SELECT #{unitySndngMastrId} + , tetm.signgu_code + , tetm.ffnlg_code + , tetm.tmplat_id + , tetm.sndng_ty_code + , #{sndngCo} + , #{sndngProcessSttus} + , date_format(now(), '%Y%m%d%H%i%S') + , date_format(date_add(now(), interval +1 day), '%Y%m%d%H%i%S') + , now() + , 'batch' + FROM tb_ens_tmplat_manage tetm + WHERE sndng_ty_code = #{sndngTyCode} + AND tmplat_id = #{tmplatId} + + + + /** pni-mysql-mapper|insertCntcSndngDtl-연계발송상세 생성|julim */ + + SELECT concat('P', signgu_code, ffnlg_code, RIGHT(date_format(now(), '%Y'),2), LPAD(NEXTVAL(tb_cntc_sndng_detail_seq), 7, '0')) + FROM tb_ens_tmplat_manage + WHERE sndng_ty_code = #{sndngTyCode} + AND tmplat_id = #{tmplatId} + + INSERT + INTO tb_cntc_sndng_detail ( + unity_sndng_detail_id, /* 통합발송 상세ID */ + unity_sndng_mastr_id, /* 통합발송 마스터ID */ + signgu_code, /* 시군구 코드 */ + ffnlg_code, /* 과태료 코드 */ + ihidnum, /* 주민번호 */ + vhcle_no, /* 차량번호 */ + moblphon_no, /* 핸드폰 번호 */ + nm, /* 이름 */ + tmplt_msg_data, /* 템플릿메시지 데이타 */ + mobile_page_cn, /* 모바일 페이지 내용 */ + regist_dt, + register + ) + SELECT #{unitySndngDetailId} + , #{unitySndngMastrId} + , tetm.signgu_code + , tetm.ffnlg_code + , #{brthdy} + , #{vhcleNo} + , #{moblphonNo} + , #{nm} + , #{tmpltMsgData} + , #{mobilePageCn} + , now() + , 'batch' + FROM tb_ens_tmplat_manage tetm + WHERE sndng_ty_code = #{sndngTyCode} + AND tmplat_id = #{tmplatId} + + + + /** pni-mysql-mapper|updatePniNtcnCntcData-사전알림 연계데이타 연계결과 반영|julim */ + UPDATE tb_pni_ntcn_cntc_data + SET process_at = #{processAt} + , trget_at = #{tgtYn} + , unity_sndng_detail_id = #{unitySndngDetailId} + , updt_dt = now() + , updusr = 'batch' + WHERE ntcn_cntc_data_id = #{ntcnCntcDataId} + + diff --git a/mens-batch/src/main/resources/egovframework/mapper/core/auth-mysql-mapper.xml b/mens-batch/src/main/resources/egovframework/mapper/core/auth-mysql-mapper.xml new file mode 100644 index 0000000..87d4f38 --- /dev/null +++ b/mens-batch/src/main/resources/egovframework/mapper/core/auth-mysql-mapper.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/mens-batch/src/main/resources/egovframework/mapper/core/cmm-batch-mysql-mapper.xml b/mens-batch/src/main/resources/egovframework/mapper/core/cmm-batch-mysql-mapper.xml new file mode 100644 index 0000000..79474cf --- /dev/null +++ b/mens-batch/src/main/resources/egovframework/mapper/core/cmm-batch-mysql-mapper.xml @@ -0,0 +1,68 @@ + + + + + + + + /** cmm-batch-mapper|insertBatchLock-배치락 데이타 생성|julim */ + INSERT INTO tb_cmm_batch_lock ( + instance_id, + regist_dt, + use_yn + ) VALUES ( + #{instanceId}, + NOW(3), + #{useYn} + ) + + + + /** cmm-batch-mapper|updateBatchLock-배치 실행 상태 및 결과 반영|julim */ + UPDATE tb_cmm_batch_lock + SET result = #{result} + , use_yn = #{useYn} + , updt_dt = NOW(3) + WHERE instance_id = #{instanceId} + + + + /** cmm-batch-mapper|insertBatchLog-배치 로그 데이타 생성|julim */ + + SELECT LPAD(NEXTVAL(tb_cmm_batch_log_seq), 20, '0') + + INSERT INTO tb_cmm_batch_log ( + batch_log_id, + instance_id, + trace_id, + result, + message, + regist_dt + ) VALUES ( + #{batchLogId}, + #{instanceId}, + #{traceId}, + #{result}, + #{message}, + NOW(3) + ) + + + + /** cmm-batch-mapper|updateBatchLog-배치 결과 반영|julim */ + UPDATE tb_cmm_batch_log + SET trace_id = IFNULL(#{traceId}, trace_id) + , result = IFNULL(#{result}, result) + , message = IFNULL(SUBSTRING(#{message}, 1, 100), message) + , updt_dt = NOW(3) + WHERE batch_log_id = #{batchLogId} + + diff --git a/mens-batch/src/main/resources/egovframework/mapper/core/logging-mysql-mapper.xml b/mens-batch/src/main/resources/egovframework/mapper/core/logging-mysql-mapper.xml new file mode 100644 index 0000000..ac4db08 --- /dev/null +++ b/mens-batch/src/main/resources/egovframework/mapper/core/logging-mysql-mapper.xml @@ -0,0 +1,80 @@ + + + + + + + /** logging-mysql-mapper|saveLogging|julim */ + INSERT INTO tb_cmm_api_log ( + request_id + , system_id + , req_system_id + , method + , uri + , param + , ip + , access_token + , session_id + , success + , response + , message + , regist_dt + , regist_id + ) VALUES ( + #{requestId} + , #{systemId} + , #{reqSystemId} + , #{method} + , #{uri} + , #{param} + , #{ip} + , #{accessToken} + , #{sessionId} + , #{success} + , #{response} + , #{message} + , now(3) + , #{registId} + ) + + + + /** logging-mysql-mapper|updateLogging|julim */ + UPDATE tb_cmm_api_log + SET success = #{success} + , response = #{response} + , message = #{message} + , updt_dt = now(3) + , updt_id = #{updtId} + WHERE request_id = #{requestId} + + + + diff --git a/mens-batch/src/main/resources/egovframework/mapper/mapper-config.xml b/mens-batch/src/main/resources/egovframework/mapper/mapper-config.xml new file mode 100644 index 0000000..54a6663 --- /dev/null +++ b/mens-batch/src/main/resources/egovframework/mapper/mapper-config.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mens-batch/src/main/resources/egovframework/message/com/message-common.properties b/mens-batch/src/main/resources/egovframework/message/com/message-common.properties new file mode 100644 index 0000000..4cdeb51 --- /dev/null +++ b/mens-batch/src/main/resources/egovframework/message/com/message-common.properties @@ -0,0 +1,294 @@ +fail.common.msg=\uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! +fail.common.sql=sql \uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! error code: {0}, error msg: {1} +info.nodata.msg=\ud574\ub2f9 \ub370\uc774\ud130\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. + +#UI Common resource# +button.search=\uac80\uc0c9 +button.use=\uc0ac\uc6a9 +button.notUsed=\uc0ac\uc6a9\uc911\uc9c0 +button.inquire=\uc870\ud68c +button.update=\uc218\uc815 +button.create=\ub4f1\ub85d +button.delete=\uc0ad\uc81c +button.deleteDatabase=\uc644\uc804\uc0ad\uc81c +button.close=\ub2eb\uae30 +button.save=\uc800\uc7a5 +button.list=\ubaa9\ub85d +button.reset=\ucde8\uc18c +button.passwordUpdate=\uc554\ud638\ubcc0\uacbd +button.subscribe=\uac00\uc785\uc2e0\uccad +button.realname=\uc2e4\uba85\ud655\uc778 +button.moveToGpin=GPIN\uc2e4\uba85\ud655\uc778\uc73c\ub85c \uc774\ub3d9 +button.moveToIhidnum=\uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638 \uc2e4\uba85\ud655\uc778\uc73c\ub85c \uc774\ub3d9 +button.agree=\ub3d9\uc758 +button.disagree=\ube44\ub3d9\uc758 +button.possible = \uac00\ub2a5 +button.impossible = \ubd88\uac00\ub2a5 +button.qnaregist=Q&A\ub4f1\ub85d +button.cnsltregist=\uc0c1\ub2f4\ub4f1\ub85d +button.preview=\ubbf8\ub9ac\ubcf4\uae30 +button.next=\ub2e4\uc74c +button.add=\ubc14\ub85c\ucd94\uac00 +button.confirm=\ud655\uc778 + + +#UI Common Message# +common.save.msg=\uc800\uc7a5\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.regist.msg=\ub4f1\ub85d\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.delete.msg=\uc0ad\uc81c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.update.msg=\uc218\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.nodata.msg=\uc790\ub8cc\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\ub978 \uac80\uc0c9\uc870\uac74\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694 +common.required.msg=(\uc740)\ub294 \ud544\uc218\uc785\ub825\ud56d\ubaa9\uc785\ub2c8\ub2e4. +common.acknowledgement.msg=\uc2b9\uc778\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.acknowledgementcancel.msg=\uc2b9\uc778\ucde8\uc18c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? + +success.common.select=\uc815\uc0c1\uc801\uc73c\ub85c \uc870\ud68c\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.insert=\uc815\uc0c1\uc801\uc73c\ub85c \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.update=\uc815\uc0c1\uc801\uc73c\ub85c \uc218\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.delete=\uc815\uc0c1\uc801\uc73c\ub85c \uc0ad\uc81c\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +success.request.msg = \uc694\uccad\ucc98\ub9ac\uac00 \uc131\uacf5\uc801\uc73c\ub85c \uc218\ud589\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +common.imposbl.fileupload = \ub354 \uc774\uc0c1 \ud30c\uc77c\uc744 \ucca8\ubd80\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +common.isConfmDe.msg=\uc2b9\uc778\uc77c\uc790\ub97c \ud655\uc778 \ubc14\ub78d\ub2c8\ub2e4. + + +fail.common.insert = \uc0dd\uc131\uc774 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.update = \uc218\uc815\uc774 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.delete = \uc0ad\uc81c\uac00 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.delete.upperMenuExist = \ucc38\uc870\ub418\ub294 \uba54\ub274\uac00 \uc788\uc5b4 \uc0ad\uc81c\uac00 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.select = \uc870\ud68c\uc5d0 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +common.isExist.msg = \uc774\ubbf8 \uc874\uc7ac\ud558\uac70\ub098 \uacfc\uac70\uc5d0 \ub4f1\ub85d\uc774 \ub418\uc5c8\ub358 \uc0c1\ud0dc\uc785\ub2c8\ub2e4. +fail.common.login = \ub85c\uadf8\uc778 \uc815\ubcf4\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.common.idsearch = \uc544\uc774\ub514\ub97c \ucc3e\uc744\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +fail.common.pwsearch = \ube44\ubc00\ubc88\ud638\ub97c \ucc3e\uc744\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +fail.request.msg = \uc694\uccad\ucc98\ub9ac\ub97c \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. + +#UI User Message# +fail.user.passwordUpdate1=\ud604\uc7ac \ube44\ubc00\ubc88\ud638\uac00 \ub9de\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.user.passwordUpdate2=\ube44\ubc00\ubc88\ud638\uc640 \ube44\ubc00\ubc88\ud638 \ud655\uc778\uc774 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +info.user.rlnmCnfirm=\uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2e4\uba85\ud655\uc778\uc744 \ud558\uc2ed\uc2dc\uc624. +success.user.rlnmCnfirm=\ud589\uc815\uc548\uc804\ubd80\uc758 \uc8fc\ubbfc\ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud569\ub2c8\ub2e4. +fail.user.rlnmCnfirm=\ud589\uc815\uc548\uc804\ubd80\uc758 \uc8fc\ubbfc\ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.user.connectFail=\uc2dc\uc2a4\ud15c \uc7a5\uc560\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.(\uc778\uc99d\uc11c\ubc84 \uc5f0\uacb0 \uc2e4\ud328) +info.user.rlnmPinCnfirm=\uacf5\uacf5 \uc544\uc774\ud540 \uc544\uc774\ub514\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2e4\uba85\ud655\uc778\uc744 \ud558\uc2ed\uc2dc\uc624. +success.user.rlnmPinCnfirm=\uacf5\uacf5\uc544\uc774\ud540\uc758 \ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud569\ub2c8\ub2e4. +fail.user.rlnmPinCnfirm=\uacf5\uacf5\uc544\uc774\ud540\uc758 \ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + + +#UI Cop Message# +cop.extrlUser = \uc678\ubd80\uc0ac\uc6a9\uc790 +cop.intrlUser = \ub0b4\ubd80\uc0ac\uc6a9\uc790 +cop.private = \ube44\uacf5\uac1c +cop.public = \uacf5\uac1c + +cop.adbkNm = \uc8fc\uc18c\ub85d\uba85 +cop.othbcScope = \uacf5\uac1c\ubc94\uc704 +cop.company = \ud68c\uc0ac +cop.part = \ubd80\uc11c +cop.man = \uac1c\uc778 +cop.adbkUser = \uad6c\uc131\uc6d0 +cop.bbsNm = \uac8c\uc2dc\ud310\uba85 +cop.bbsIntrcn = \uac8c\uc2dc\ud310\uc18c\uac1c +cop.bbsTyCode = \uac8c\uc2dc\ud310 \uc720\ud615 +cop.bbsAttrbCode = \uac8c\uc2dc\ud310 \uc18d\uc131 +cop.replyPosblAt = \ub2f5\uc7a5\uac00\ub2a5\uc5ec\ubd80 +cop.fileAtchPosblAt = \ud30c\uc77c\ucca8\ubd80\uac00\ub2a5\uc5ec\ubd80 +cop.posblAtchFileNumber = \ucca8\ubd80\uac00\ub2a5\ud30c\uc77c \uc22b\uc790 +cop.tmplatId = \ud15c\ud50c\ub9bf \uc815\ubcf4 +cop.guestList.subject = \ubc29\uba85\ub85d \uac8c\uc2dc\uae00\uc785\ub2c8\ub2e4. +cop.nttSj = \uc81c\ubaa9 +cop.nttCn = \uae00\ub0b4\uc6a9 +cop.ntceBgnde = \uac8c\uc2dc\uc2dc\uc791\uc77c +cop.ntceEndde = \uac8c\uc2dc\uc885\ub8cc\uc77c +cop.ntcrNm = \uc791\uc131\uc790 +cop.password = \ud328\uc2a4\uc6cc\ub4dc +cop.atchFile = \ud30c\uc77c\ucca8\ubd80 +cop.guestList = \ubc29\uba85\ub85d +cop.guestListCn = \ubc29\uba85\ub85d \ub0b4\uc6a9 +cop.noticeTerm = \uac8c\uc2dc\uae30\uac04 +cop.atchFileList = \ucca8\ubd80\ud30c\uc77c\ubaa9\ub85d +cop.cmmntyNm = \ucee4\ubba4\ub2c8\ud2f0\uba85 +cop.cmmntyIntrcn = \ucee4\ubba4\ub2c8\ud2f0 \uc18c\uac1c +cop.cmmntyMngr = \ucee4\ubba4\ub2c8\ud2f0 \uad00\ub9ac\uc790 +cop.clbOprtr = \ub3d9\ud638\ud68c \uc6b4\uc601\uc790 +cop.clbIntrcn = \ub3d9\ud638\ud68c \uc18c\uac1c +cop.clbNm = \ub3d9\ud638\ud68c \uba85 +cop.tmplatNm = \ud15c\ud50c\ub9bf\uba85 +cop.tmplatSeCode = \ud15c\ud50c\ub9bf \uad6c\ubd84 +cop.tmplatCours = \ud15c\ud50c\ub9bf\uacbd\ub85c +cop.useAt = \uc0ac\uc6a9\uc5ec\ubd80 +cop.ncrdNm = \uc774\ub984 +cop.cmpnyNm = \ud68c\uc0ac\uba85 +cop.deptNm = \ubd80\uc11c\uba85 +cop.ofcpsNm = \uc9c1\uc704 +cop.clsfNm = \uc9c1\uae09 +cop.emailAdres = \uc774\uba54\uc77c\uc8fc\uc18c +cop.telNo = \uc804\ud654\ubc88\ud638 +cop.mbtlNum = \ud734\ub300\ud3f0\ubc88\ud638 +cop.adres = \uc8fc\uc18c +cop.extrlUserAt = \uc678\ubd80\uc0ac\uc6a9\uc790\uc5ec\ubd80 +cop.publicAt = \uacf5\uac1c\uc5ec\ubd80 +cop.remark = \ube44\uace0 +cop.trgetNm = \ucee4\ubba4\ub2c8\ud2f0/\ub3d9\ud638\ud68c \uc815\ubcf4 +cop.preview = \ubbf8\ub9ac\ubcf4\uae30 + +cop.withdraw.msg=\ud0c8\ud1f4\ucc98\ub9ac \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.reregist.msg=\uc7ac\uac00\uc785 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.registmanager.msg=\uc6b4\uc601\uc9c4\uc73c\ub85c \ub4f1\ub85d\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.use.msg=\uc0ac\uc6a9 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.unuse.msg=\uc0ac\uc6a9\uc911\uc9c0 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.delete.confirm.msg=\uc0ac\uc6a9\uc911\uc9c0\ub97c \uc120\ud0dd\ud558\uc2e4 \uacbd\uc6b0 \ub2e4\uc2dc \uc0ac\uc6a9\uc73c\ub85c \ubcc0\uacbd\uc774 \ubd88\uac00\ub2a5\ud569\ub2c8\ub2e4. +cop.ing.msg=\uc2b9\uc778\uc694\uccad \uc911\uc785\ub2c8\ub2e4. +cop.request.msg=\uac00\uc785\uc2e0\uccad\uc774 \uc815\uc0c1\uc801\uc73c\ub85c \uc694\uccad\ub418\uc5c8\uc2b5\ub2c8\ub2e4 +cop.password.msg=\ud328\uc2a4\uc6cc\ub4dc\ub97c \uc785\ub825\ud574 \uc8fc\uc2ed\uc2dc\uc624. +cop.password.not.same.msg=\ud328\uc2a4\uc6cc\ub4dc\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + +cop.comment.wrterNm = \uc791\uc131\uc790 +cop.comment.commentCn = \ub0b4\uc6a9 +cop.comment.commentPassword = \ud328\uc2a4\uc6cc\ub4dc + +cop.satisfaction.wrterNm = \uc791\uc131\uc790 +cop.satisfaction.stsfdgCn = \ub0b4\uc6a9 +cop.satisfaction.stsfdg = \ub9cc\uc871\ub3c4 +cop.satisfaction.stsfdgPassword = \ud328\uc2a4\uc6cc\ub4dc + +cop.scrap.scrapNm = \uc2a4\ud06c\ub7a9\uba85 + +#UI USS Message# +uss.ion.noi.ntfcSj=\uc81c\ubaa9 +uss.ion.noi.ntfcCn=\ub0b4\uc6a9 +uss.ion.noi.ntfcDate=\uc54c\ub9bc\uc77c\uc790 +uss.ion.noi.ntfcTime=\uc54c\ub9bc\uc2dc\uac04 +uss.ion.noi.ntfcHH=\uc54c\ub9bc\uc2dc\uac04 +uss.ion.noi.ntfcMM=\uc54c\ub9bc\ubd84 +uss.ion.noi.bhNtfcIntrvl=\uc0ac\uc804\uc54c\ub9bc\uac04\uaca9 +uss.ion.noi.bhNtfcIntrvl.msg=\uc0ac\uc804\uc54c\ub9bc\uac04\uaca9 \uc9c0\uc815\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. +uss.ion.noi.alertNtfcTime=\uc54c\ub9bc\uc77c\uc790 \ubc0f \uc2dc\uac04\uc774 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + +#UI COP Message# +cop.sms.trnsmitTelno=\ubc1c\uc2e0\uc804\ud654\ubc88\ud638 +cop.sms.trnsmitCn=\uc804\uc1a1\ub0b4\uc6a9 +cop.sms.recptnTelno=\uc218\uc2e0\uc804\ud654\ubc88\ud638 +cop.sms.send=\uc804\uc1a1 +cop.sms.addRecptn=\ucd94\uac00 +cop.sms.recptnTelno.msg=\uc218\uc2e0\uc804\ud654\ubc88\ud638 \uc9c0\uc815\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. + +#UI sym.log Message# +sym.log.histSeCode = \uc774\ub825\uad6c\ubd84 +sym.log.sysNm = \uc2dc\uc2a4\ud15c\uba85 +sym.log.histCn = \uc774\ub825\ub0b4\uc6a9 +sym.log.atchFile = \ucca8\ubd80\ud30c\uc77c +sym.log.atchFileList = \ucca8\ubd80\ud30c\uc77c\ubaa9\ub85d +sym.ems.receiver = \ubc1b\ub294\uc0ac\ub78c +sym.ems.title = \uc81c\ubaa9 +sym.ems.content = \ubc1c\uc2e0\ub0b4\uc6a9 + +#Vlidator Errors# +errors.prefix=
+errors.suffix=

+ +errors.required={0}\uc740(\ub294) \ud544\uc218 \uc785\ub825\uac12\uc785\ub2c8\ub2e4. +errors.minlength={0}\uc740(\ub294) {1}\uc790 \uc774\uc0c1 \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.maxlength={0}\uc740(\ub294) {1}\uc790 \uc774\uc0c1 \uc785\ub825\ud560\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +errors.invalid={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uac12\uc785\ub2c8\ub2e4. +errors.minInteger={0}\uc740(\ub294) \uc720\ud6a8\ud55c \uac12\uc774 \uc544\ub2d9\ub2c8\ub2e4. 1 \uc774\uc0c1\uc758 \uac12\uc744 \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.byte={0}\uc740(\ub294) byte\ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.short={0}\uc740(\ub294) short\ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.integer={0}\uc740(\ub294) \uc815\uc218 \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.long={0}\uc740(\ub294) long \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.float={0}\uc740(\ub294) \uc2e4\uc218 \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.double={0}\uc740(\ub294) double \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. + +errors.date={0}\uc740(\ub294) \ub0a0\uc9dc \uc720\ud615\uc774 \uc544\ub2d9\ub2c8\ub2e4. +errors.range={0}\uc740(\ub294) {1}\uacfc {2} \uc0ac\uc774\uc758 \uac12\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.creditcard={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc2e0\uc6a9\uce74\ub4dc \ubc88\ud638\uc785\ub2c8\ub2e4. +errors.email={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc774\uba54\uc77c \uc8fc\uc18c\uc785\ub2c8\ub2e4. + +errors.ihidnum=\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638\uc785\ub2c8\ub2e4. +errors.korean={0}\uc740(\ub294) \ud55c\uae00\uc744 \uc785\ub825\ud558\uc154\uc57c \ud569\ub2c8\ub2e4. +errors.ip=\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 IP\uc8fc\uc18c\uc785\ub2c8\ub2e4. + +errors.password1={0}\uc740(\ub294) 8~20\uc790 \ub0b4\uc5d0\uc11c \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.password2={0}\uc740(\ub294) \ud55c\uae00,\ud2b9\uc218\ubb38\uc790,\ub744\uc5b4\uc4f0\uae30\ub294 \ud5c8\uc6a9\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +errors.password3={0}\uc740(\ub294) \uc21c\ucc28\uc801\uc778 \uc22b\uc790\ub97c 4\uac1c\uc774\uc0c1 \uc5f0\uc18d\ud574\uc11c \uc0ac\uc6a9\ud560\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +errors.password4={0}\uc740(\ub294) \ubc18\ubcf5\ub418\ub294 \ubb38\uc790\ub098 \uc22b\uc790\ub97c 4\uac1c\uc774\uc0c1 \uc5f0\uc18d\ud574\uc11c \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. + +errors.notKorean={0}\uc740(\ub294) \ud55c\uae00\uc744 \uc0ac\uc6a9\ud558\uc2e4\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +error.security.runtime.error = error +#Vlidator Errors- wordDicaryVO# +wordDicaryVO.wordNm=\uc6a9\uc5b4\uba85 +wordDicaryVO.engNm=\uc601\ubb38\uba85 +wordDicaryVO.wordDc=\uc6a9\uc5b4\uc124\uba85 +wordDicaryVO.synonm=\ub3d9\uc758\uc5b4 + +#Vlidator Errors- cnsltManageVO# +cnsltManageVO.cnsltSj=\uc0c1\ub2f4\uc81c\ubaa9 +cnsltManageVO.cnsltCn=\uc0c1\ub2f4\ub0b4\uc6a9 +cnsltManageVO.writngPassword=\uc791\uc131\ube44\ubc00\ubc88\ud638 +cnsltManageVO.areaNo=\uc9c0\uc5ed\ubc88\ud638 +cnsltManageVO.middleTelno=\uc911\uac04\uc804\ud654\ubc88\ud638 +cnsltManageVO.endTelno=\ub05d\uc804\ud654\ubc88\ud638 +cnsltManageVO.wrterNm=\uc791\uc131\uc790\uba85 +cnsltManageVO.managtCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- siteManageVO# +siteManageVO.siteNm=\uc0ac\uc774\ud2b8\uba85 +siteManageVO.siteUrl=\uc0ac\uc774\ud2b8 URL +siteManageVO.siteDc=\uc0ac\uc774\ud2b8\uc124\uba85 +siteManageVO.siteThemaClCode=\uc0ac\uc774\ud2b8\uc8fc\uc81c\ubd84\ub958 +siteManageVO.actvtyAt=\ud65c\uc131\uc5ec\ubd80 +siteManageVO.useAt=\uc0ac\uc6a9\uc5ec\ubd80 + +#Vlidator Errors- recomendSiteManageVO# +recomendSiteManageVO.recomendSiteNm=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uba85 +recomendSiteManageVO.recomendSiteUrl=\ucd94\ucc9c\uc0ac\uc774\ud2b8 URL +recomendSiteManageVO.recomendSiteDc=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uc124\uba85 +recomendSiteManageVO.recomendResnCn=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uc2b9\uc778\uc0ac\uc720 +recomendSiteManageVO.confmDe=\uc2b9\uc778\uc77c\uc790 + +#Vlidator Errors- hpcmManageVO# +hpcmManageVO.hpcmSeCode=\ub3c4\uc6c0\ub9d0\uad6c\ubd84 +hpcmManageVO.hpcmDf=\ub3c4\uc6c0\ub9d0\uc815\uc758 +hpcmManageVO.hpcmDc=\ub3c4\uc6c0\ub9d0\uc124\uba85 + +#Vlidator Errors- newsManageVO# +newsManageVO.newsSj=\ub274\uc2a4\uc81c\ubaa9 +newsManageVO.newsCn=\ub274\uc2a4\ub0b4\uc6a9 +newsManageVO.ntceDe=\uac8c\uc2dc\uc77c\uc790 + +#Vlidator Errors- faqManageVO# +faqManageVO.qestnSj=\uc9c8\ubb38\uc81c\ubaa9 +faqManageVO.qestnCn=\uc9c8\ubb38\ub0b4\uc6a9 +faqManageVO.answerCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- stplatManageVO# +stplatManageVO.useStplatNm=\uc774\uc6a9\uc57d\uad00\uba85 +stplatManageVO.useStplatCn=\uc774\uc6a9\uc57d\uad00\ub0b4\uc6a9 +stplatManageVO.infoProvdAgreCn=\uc815\ubcf4\uc81c\uacf5\ub3d9\uc758\ub0b4\uc6a9 + +#Vlidator Errors- cpyrhtPrtcPolicyVO# +cpyrhtPrtcPolicyVO.cpyrhtPrtcPolicyCn=\uc800\uc791\uad8c\ubcf4\ud638\uc815\ucc45\ub0b4\uc6a9 + +#Vlidator Errors- qnaManageVO# +qnaManageVO.qestnSj=\uc9c8\ubb38\uc81c\ubaa9 +qnaManageVO.qestnCn=\uc9c8\ubb38\ub0b4\uc6a9 +qnaManageVO.writngPassword=\uc791\uc131\ube44\ubc00\ubc88\ud638 +qnaManageVO.areaNo=\uc9c0\uc5ed\ubc88\ud638 +qnaManageVO.middleTelno=\uc911\uac04\uc804\ud654\ubc88\ud638 +qnaManageVO.endTelno=\ub05d\uc804\ud654\ubc88\ud638 +qnaManageVO.wrterNm=\uc791\uc131\uc790\uba85 +qnaManageVO.answerCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- ReprtStatsVO# +sts.reprtId = \ubcf4\uace0\uc11cID +sts.title = \ubcf4\uace0\uc11c\uba85 +sts.category = \ubcf4\uace0\uc11c\uc720\ud615 +sts.status = \uc9c4\ud589\uc0c1\ud0dc +sts.regDate = \ub4f1\ub85d\uc77c\uc2dc + +#Rest day messages# +sym.cal.restDay = \ud734\uc77c\uc77c\uc790 +sym.cal.restName = \ud734\uc77c\uba85 +sym.cal.restDetail = \ud734\uc77c\uc124\uba85 +sym.cal.restCategory = \ud734\uc77c\uad6c\ubd84 \ No newline at end of file diff --git a/mens-batch/src/main/resources/egovframework/message/com/message-common_en.properties b/mens-batch/src/main/resources/egovframework/message/com/message-common_en.properties new file mode 100644 index 0000000..1d5b3f8 --- /dev/null +++ b/mens-batch/src/main/resources/egovframework/message/com/message-common_en.properties @@ -0,0 +1,196 @@ +fail.common.msg=error ocurred! +fail.common.sql=sql error ocurred! error code: {0}, error msg: {1} +info.nodata.msg=no data found. + +#UI Common resource# +button.search=Search +button.use=use +button.notUsed=Not used +button.inquire=inquire +button.update=update +button.create=create +button.delete=delete +button.close=close +button.save=save +button.list=list +button.reset=reset +button.passwordUpdate=password update +button.subscribe=subscribe +button.realname=realname confirm +button.moveToGpin=move to gpin confirm +button.moveToIhidnum=move to ihidnum confirm +button.agree=agree +button.disagree=disagree +button.possible = possible +button.impossible = impossible + +#UI Common Message# +common.save.msg=confirm save? +common.regist.msg=confirm regist? +common.delete.msg=confirm delete? +common.update.msg=confirm update? +common.nodata.msg=There is no data. please choose another seach keyword +common.required.msg=is required field +common.acknowledgement.msg=confirm acknowledgement? +common.acknowledgementcancel.msg=confirm acknowledgement cancel? + +success.request.msg=you're request successfully done + +success.common.select=successfully selected +success.common.insert=successfully inserted +success.common.update=successfully updated +success.common.delete=successfully deleted + +common.imposbl.fileupload = cannot upload files + +fail.common.insert = fail to insert. +fail.common.update = fail to update +fail.common.delete = fail to delete +fail.common.delete.upperMenuExist = fail to delete[upperMenuId foreign key error] +fail.common.select = fail to select +common.isExist.msg = already exist +fail.common.login = login information is not correct +fail.common.idsearch = can not find id +fail.common.pwsearch = can not find password + + +#UI User Message# +fail.user.passwordUpdate1=current password is not correct +fail.user.passwordUpdate2=password confirm is not correct +info.user.rlnmCnfirm=realname confirm ready +success.user.rlnmCnfirm=it is realname +fail.user.rlnmCnfirm=it is not realname +fail.user.connectFail=connection fail + +#UI Cop Message# +cop.extrlUser = External User +cop.intrlUser = Internal User +cop.private = private +cop.public = public + +cop.bbsNm = BBS Name +cop.bbsIntrcn = BBS Introduction +cop.bbsTyCode = BBS Type +cop.bbsAttrbCode = BBS Attribute +cop.replyPosblAt = Reply Possible Alternative +cop.fileAtchPosblAt = File Attach Possible Alternative +cop.posblAtchFileNumber = Possible Attach File Number +cop.tmplatId = Template Information +cop.guestList.subject = This article registered by Guest List +cop.nttSj = Notice Subject +cop.nttCn = Notice Contents +cop.ntceBgnde = Notice Start Date +cop.ntceEndde = Notice End Date +cop.ntcrNm = Noticer Name +cop.password = PassWord +cop.atchFile = Attach Files +cop.guestList = Guest List +cop.guestListCn = Guest List Contents +cop.noticeTerm = Notice term +cop.atchFileList = Attached File List +cop.cmmntyNm = Community Name +cop.cmmntyIntrcn = Community Introduction +cop.cmmntyMngr = Community Manager +cop.clbOprtr = Club Operator +cop.clbIntrcn = Club Introduction +cop.clbNm = Club Name +cop.tmplatNm = Template Name +cop.tmplatSeCode = Template Se Code +cop.tmplatCours = Template Cours +cop.useAt = Use Alternative +cop.ncrdNm = NameCard user name +cop.cmpnyNm = Company name +cop.deptNm = Department name +cop.ofcpsNm = OFCPS name +cop.clsfNm = Class Name +cop.emailAdres = E-mail +cop.telNo = Tel No. +cop.mbtlNum = Mobile +cop.adres = Address +cop.extrlUserAt = External User alternative +cop.publicAt = Public open alternative +cop.remark = Remark +cop.trgetNm = Company/Club Information +cop.preview = preview + +cop.withdraw.msg=confirm withdrawal memebership? +cop.reregist.msg=confirm re-registration? +cop.registmanager.msg=confirm registration of manager? +cop.use.msg=confirm use? +cop.unuse.msg=confirm stop using? +cop.delete.confirm.msg=If you choose to disable the re-use change is impossible. +cop.ing.msg=Approval is being requested. +cop.request.msg=Signup is normally requested. +cop.password.msg=Please enter your password. +cop.password.not.same.msg=Password do not match. + +cop.comment.wrterNm = Writer Name +cop.comment.commentCn = Comment +cop.comment.commentPassword = Password + +cop.satisfaction.wrterNm = Writer Name +cop.satisfaction.stsfdgCn = Satisfaction +cop.satisfaction.stsfdg = Satisfaction Degree +cop.satisfaction.stsfdgPassword = Password + +cop.scrap.scrapNm = Scrap Name + +#UI USS Message# +uss.ion.noi.ntfcSj=Subject +uss.ion.noi.ntfcCn=Contents +uss.ion.noi.ntfcDate=Notification Date +uss.ion.noi.ntfcTime=Notification Time +uss.ion.noi.ntfcHH=Notification Hour +uss.ion.noi.ntfcMM=Notification Minute +uss.ion.noi.bhNtfcIntrvl=Beforehand Interval +uss.ion.noi.bhNtfcIntrvl.msg=Beforehand Interval is required. +uss.ion.noi.alertNtfcTime=Date and time of notification is not valid. + +#UI COP Message# +cop.sms.trnsmitTelno=Sender +cop.sms.trnsmitCn=Contents +cop.sms.recptnTelno=Receiver(s) +cop.sms.send=Send +cop.sms.addRecptn=Add +cop.sms.recptnTelno.msg=The phone number of receiver is required. + +#UI sym.log Message# +sym.log.histSeCode = History Code +sym.log.sysNm = System Name +sym.log.histCn = History Contents +sym.log.atchFile = Attached File +sym.log.atchFileList = Attached File List +sym.ems.receiver = Receiver +sym.ems.title = Title +sym.ems.content = Content + +#Vlidator Errors# +errors.required={0} is required. +errors.minlength={0} can not be less than {1} characters. +errors.maxlength={0} can not be greater than {1} characters. +errors.invalid={0} is invalid. + +errors.byte={0} must be a byte. +errors.short={0} must be a short. +errors.integer={0} must be an integer. +errors.long={0} must be a long. +errors.float={0} must be a float. +errors.double={0} must be a double. + +errors.date={0} is not a date. +errors.range={0} is not in the range {1} through {2}. +errors.creditcard={0} is an invalid credit card number. +errors.email={0} is an invalid e-mail address. + +#Vlidator Errors- ReprtStatsVO# +sts.reprtId = Report ID +sts.title = Report Title +sts.category = Report Category +sts.status = Report Status +sts.regDate = Registration Date + +#Rest day messages# +sym.cal.restDay = Holiday Date +sym.cal.restName = Holiday Name +sym.cal.restDetail = Holiday Detail +sym.cal.restCategory = Holiday Category \ No newline at end of file diff --git a/mens-batch/src/main/resources/egovframework/message/com/message-common_ko.properties b/mens-batch/src/main/resources/egovframework/message/com/message-common_ko.properties new file mode 100644 index 0000000..4cdeb51 --- /dev/null +++ b/mens-batch/src/main/resources/egovframework/message/com/message-common_ko.properties @@ -0,0 +1,294 @@ +fail.common.msg=\uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! +fail.common.sql=sql \uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! error code: {0}, error msg: {1} +info.nodata.msg=\ud574\ub2f9 \ub370\uc774\ud130\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. + +#UI Common resource# +button.search=\uac80\uc0c9 +button.use=\uc0ac\uc6a9 +button.notUsed=\uc0ac\uc6a9\uc911\uc9c0 +button.inquire=\uc870\ud68c +button.update=\uc218\uc815 +button.create=\ub4f1\ub85d +button.delete=\uc0ad\uc81c +button.deleteDatabase=\uc644\uc804\uc0ad\uc81c +button.close=\ub2eb\uae30 +button.save=\uc800\uc7a5 +button.list=\ubaa9\ub85d +button.reset=\ucde8\uc18c +button.passwordUpdate=\uc554\ud638\ubcc0\uacbd +button.subscribe=\uac00\uc785\uc2e0\uccad +button.realname=\uc2e4\uba85\ud655\uc778 +button.moveToGpin=GPIN\uc2e4\uba85\ud655\uc778\uc73c\ub85c \uc774\ub3d9 +button.moveToIhidnum=\uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638 \uc2e4\uba85\ud655\uc778\uc73c\ub85c \uc774\ub3d9 +button.agree=\ub3d9\uc758 +button.disagree=\ube44\ub3d9\uc758 +button.possible = \uac00\ub2a5 +button.impossible = \ubd88\uac00\ub2a5 +button.qnaregist=Q&A\ub4f1\ub85d +button.cnsltregist=\uc0c1\ub2f4\ub4f1\ub85d +button.preview=\ubbf8\ub9ac\ubcf4\uae30 +button.next=\ub2e4\uc74c +button.add=\ubc14\ub85c\ucd94\uac00 +button.confirm=\ud655\uc778 + + +#UI Common Message# +common.save.msg=\uc800\uc7a5\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.regist.msg=\ub4f1\ub85d\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.delete.msg=\uc0ad\uc81c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.update.msg=\uc218\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.nodata.msg=\uc790\ub8cc\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\ub978 \uac80\uc0c9\uc870\uac74\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694 +common.required.msg=(\uc740)\ub294 \ud544\uc218\uc785\ub825\ud56d\ubaa9\uc785\ub2c8\ub2e4. +common.acknowledgement.msg=\uc2b9\uc778\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.acknowledgementcancel.msg=\uc2b9\uc778\ucde8\uc18c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? + +success.common.select=\uc815\uc0c1\uc801\uc73c\ub85c \uc870\ud68c\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.insert=\uc815\uc0c1\uc801\uc73c\ub85c \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.update=\uc815\uc0c1\uc801\uc73c\ub85c \uc218\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.delete=\uc815\uc0c1\uc801\uc73c\ub85c \uc0ad\uc81c\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +success.request.msg = \uc694\uccad\ucc98\ub9ac\uac00 \uc131\uacf5\uc801\uc73c\ub85c \uc218\ud589\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +common.imposbl.fileupload = \ub354 \uc774\uc0c1 \ud30c\uc77c\uc744 \ucca8\ubd80\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +common.isConfmDe.msg=\uc2b9\uc778\uc77c\uc790\ub97c \ud655\uc778 \ubc14\ub78d\ub2c8\ub2e4. + + +fail.common.insert = \uc0dd\uc131\uc774 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.update = \uc218\uc815\uc774 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.delete = \uc0ad\uc81c\uac00 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.delete.upperMenuExist = \ucc38\uc870\ub418\ub294 \uba54\ub274\uac00 \uc788\uc5b4 \uc0ad\uc81c\uac00 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.select = \uc870\ud68c\uc5d0 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +common.isExist.msg = \uc774\ubbf8 \uc874\uc7ac\ud558\uac70\ub098 \uacfc\uac70\uc5d0 \ub4f1\ub85d\uc774 \ub418\uc5c8\ub358 \uc0c1\ud0dc\uc785\ub2c8\ub2e4. +fail.common.login = \ub85c\uadf8\uc778 \uc815\ubcf4\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.common.idsearch = \uc544\uc774\ub514\ub97c \ucc3e\uc744\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +fail.common.pwsearch = \ube44\ubc00\ubc88\ud638\ub97c \ucc3e\uc744\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +fail.request.msg = \uc694\uccad\ucc98\ub9ac\ub97c \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. + +#UI User Message# +fail.user.passwordUpdate1=\ud604\uc7ac \ube44\ubc00\ubc88\ud638\uac00 \ub9de\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.user.passwordUpdate2=\ube44\ubc00\ubc88\ud638\uc640 \ube44\ubc00\ubc88\ud638 \ud655\uc778\uc774 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +info.user.rlnmCnfirm=\uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2e4\uba85\ud655\uc778\uc744 \ud558\uc2ed\uc2dc\uc624. +success.user.rlnmCnfirm=\ud589\uc815\uc548\uc804\ubd80\uc758 \uc8fc\ubbfc\ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud569\ub2c8\ub2e4. +fail.user.rlnmCnfirm=\ud589\uc815\uc548\uc804\ubd80\uc758 \uc8fc\ubbfc\ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.user.connectFail=\uc2dc\uc2a4\ud15c \uc7a5\uc560\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.(\uc778\uc99d\uc11c\ubc84 \uc5f0\uacb0 \uc2e4\ud328) +info.user.rlnmPinCnfirm=\uacf5\uacf5 \uc544\uc774\ud540 \uc544\uc774\ub514\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2e4\uba85\ud655\uc778\uc744 \ud558\uc2ed\uc2dc\uc624. +success.user.rlnmPinCnfirm=\uacf5\uacf5\uc544\uc774\ud540\uc758 \ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud569\ub2c8\ub2e4. +fail.user.rlnmPinCnfirm=\uacf5\uacf5\uc544\uc774\ud540\uc758 \ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + + +#UI Cop Message# +cop.extrlUser = \uc678\ubd80\uc0ac\uc6a9\uc790 +cop.intrlUser = \ub0b4\ubd80\uc0ac\uc6a9\uc790 +cop.private = \ube44\uacf5\uac1c +cop.public = \uacf5\uac1c + +cop.adbkNm = \uc8fc\uc18c\ub85d\uba85 +cop.othbcScope = \uacf5\uac1c\ubc94\uc704 +cop.company = \ud68c\uc0ac +cop.part = \ubd80\uc11c +cop.man = \uac1c\uc778 +cop.adbkUser = \uad6c\uc131\uc6d0 +cop.bbsNm = \uac8c\uc2dc\ud310\uba85 +cop.bbsIntrcn = \uac8c\uc2dc\ud310\uc18c\uac1c +cop.bbsTyCode = \uac8c\uc2dc\ud310 \uc720\ud615 +cop.bbsAttrbCode = \uac8c\uc2dc\ud310 \uc18d\uc131 +cop.replyPosblAt = \ub2f5\uc7a5\uac00\ub2a5\uc5ec\ubd80 +cop.fileAtchPosblAt = \ud30c\uc77c\ucca8\ubd80\uac00\ub2a5\uc5ec\ubd80 +cop.posblAtchFileNumber = \ucca8\ubd80\uac00\ub2a5\ud30c\uc77c \uc22b\uc790 +cop.tmplatId = \ud15c\ud50c\ub9bf \uc815\ubcf4 +cop.guestList.subject = \ubc29\uba85\ub85d \uac8c\uc2dc\uae00\uc785\ub2c8\ub2e4. +cop.nttSj = \uc81c\ubaa9 +cop.nttCn = \uae00\ub0b4\uc6a9 +cop.ntceBgnde = \uac8c\uc2dc\uc2dc\uc791\uc77c +cop.ntceEndde = \uac8c\uc2dc\uc885\ub8cc\uc77c +cop.ntcrNm = \uc791\uc131\uc790 +cop.password = \ud328\uc2a4\uc6cc\ub4dc +cop.atchFile = \ud30c\uc77c\ucca8\ubd80 +cop.guestList = \ubc29\uba85\ub85d +cop.guestListCn = \ubc29\uba85\ub85d \ub0b4\uc6a9 +cop.noticeTerm = \uac8c\uc2dc\uae30\uac04 +cop.atchFileList = \ucca8\ubd80\ud30c\uc77c\ubaa9\ub85d +cop.cmmntyNm = \ucee4\ubba4\ub2c8\ud2f0\uba85 +cop.cmmntyIntrcn = \ucee4\ubba4\ub2c8\ud2f0 \uc18c\uac1c +cop.cmmntyMngr = \ucee4\ubba4\ub2c8\ud2f0 \uad00\ub9ac\uc790 +cop.clbOprtr = \ub3d9\ud638\ud68c \uc6b4\uc601\uc790 +cop.clbIntrcn = \ub3d9\ud638\ud68c \uc18c\uac1c +cop.clbNm = \ub3d9\ud638\ud68c \uba85 +cop.tmplatNm = \ud15c\ud50c\ub9bf\uba85 +cop.tmplatSeCode = \ud15c\ud50c\ub9bf \uad6c\ubd84 +cop.tmplatCours = \ud15c\ud50c\ub9bf\uacbd\ub85c +cop.useAt = \uc0ac\uc6a9\uc5ec\ubd80 +cop.ncrdNm = \uc774\ub984 +cop.cmpnyNm = \ud68c\uc0ac\uba85 +cop.deptNm = \ubd80\uc11c\uba85 +cop.ofcpsNm = \uc9c1\uc704 +cop.clsfNm = \uc9c1\uae09 +cop.emailAdres = \uc774\uba54\uc77c\uc8fc\uc18c +cop.telNo = \uc804\ud654\ubc88\ud638 +cop.mbtlNum = \ud734\ub300\ud3f0\ubc88\ud638 +cop.adres = \uc8fc\uc18c +cop.extrlUserAt = \uc678\ubd80\uc0ac\uc6a9\uc790\uc5ec\ubd80 +cop.publicAt = \uacf5\uac1c\uc5ec\ubd80 +cop.remark = \ube44\uace0 +cop.trgetNm = \ucee4\ubba4\ub2c8\ud2f0/\ub3d9\ud638\ud68c \uc815\ubcf4 +cop.preview = \ubbf8\ub9ac\ubcf4\uae30 + +cop.withdraw.msg=\ud0c8\ud1f4\ucc98\ub9ac \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.reregist.msg=\uc7ac\uac00\uc785 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.registmanager.msg=\uc6b4\uc601\uc9c4\uc73c\ub85c \ub4f1\ub85d\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.use.msg=\uc0ac\uc6a9 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.unuse.msg=\uc0ac\uc6a9\uc911\uc9c0 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.delete.confirm.msg=\uc0ac\uc6a9\uc911\uc9c0\ub97c \uc120\ud0dd\ud558\uc2e4 \uacbd\uc6b0 \ub2e4\uc2dc \uc0ac\uc6a9\uc73c\ub85c \ubcc0\uacbd\uc774 \ubd88\uac00\ub2a5\ud569\ub2c8\ub2e4. +cop.ing.msg=\uc2b9\uc778\uc694\uccad \uc911\uc785\ub2c8\ub2e4. +cop.request.msg=\uac00\uc785\uc2e0\uccad\uc774 \uc815\uc0c1\uc801\uc73c\ub85c \uc694\uccad\ub418\uc5c8\uc2b5\ub2c8\ub2e4 +cop.password.msg=\ud328\uc2a4\uc6cc\ub4dc\ub97c \uc785\ub825\ud574 \uc8fc\uc2ed\uc2dc\uc624. +cop.password.not.same.msg=\ud328\uc2a4\uc6cc\ub4dc\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + +cop.comment.wrterNm = \uc791\uc131\uc790 +cop.comment.commentCn = \ub0b4\uc6a9 +cop.comment.commentPassword = \ud328\uc2a4\uc6cc\ub4dc + +cop.satisfaction.wrterNm = \uc791\uc131\uc790 +cop.satisfaction.stsfdgCn = \ub0b4\uc6a9 +cop.satisfaction.stsfdg = \ub9cc\uc871\ub3c4 +cop.satisfaction.stsfdgPassword = \ud328\uc2a4\uc6cc\ub4dc + +cop.scrap.scrapNm = \uc2a4\ud06c\ub7a9\uba85 + +#UI USS Message# +uss.ion.noi.ntfcSj=\uc81c\ubaa9 +uss.ion.noi.ntfcCn=\ub0b4\uc6a9 +uss.ion.noi.ntfcDate=\uc54c\ub9bc\uc77c\uc790 +uss.ion.noi.ntfcTime=\uc54c\ub9bc\uc2dc\uac04 +uss.ion.noi.ntfcHH=\uc54c\ub9bc\uc2dc\uac04 +uss.ion.noi.ntfcMM=\uc54c\ub9bc\ubd84 +uss.ion.noi.bhNtfcIntrvl=\uc0ac\uc804\uc54c\ub9bc\uac04\uaca9 +uss.ion.noi.bhNtfcIntrvl.msg=\uc0ac\uc804\uc54c\ub9bc\uac04\uaca9 \uc9c0\uc815\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. +uss.ion.noi.alertNtfcTime=\uc54c\ub9bc\uc77c\uc790 \ubc0f \uc2dc\uac04\uc774 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + +#UI COP Message# +cop.sms.trnsmitTelno=\ubc1c\uc2e0\uc804\ud654\ubc88\ud638 +cop.sms.trnsmitCn=\uc804\uc1a1\ub0b4\uc6a9 +cop.sms.recptnTelno=\uc218\uc2e0\uc804\ud654\ubc88\ud638 +cop.sms.send=\uc804\uc1a1 +cop.sms.addRecptn=\ucd94\uac00 +cop.sms.recptnTelno.msg=\uc218\uc2e0\uc804\ud654\ubc88\ud638 \uc9c0\uc815\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. + +#UI sym.log Message# +sym.log.histSeCode = \uc774\ub825\uad6c\ubd84 +sym.log.sysNm = \uc2dc\uc2a4\ud15c\uba85 +sym.log.histCn = \uc774\ub825\ub0b4\uc6a9 +sym.log.atchFile = \ucca8\ubd80\ud30c\uc77c +sym.log.atchFileList = \ucca8\ubd80\ud30c\uc77c\ubaa9\ub85d +sym.ems.receiver = \ubc1b\ub294\uc0ac\ub78c +sym.ems.title = \uc81c\ubaa9 +sym.ems.content = \ubc1c\uc2e0\ub0b4\uc6a9 + +#Vlidator Errors# +errors.prefix=
+errors.suffix=

+ +errors.required={0}\uc740(\ub294) \ud544\uc218 \uc785\ub825\uac12\uc785\ub2c8\ub2e4. +errors.minlength={0}\uc740(\ub294) {1}\uc790 \uc774\uc0c1 \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.maxlength={0}\uc740(\ub294) {1}\uc790 \uc774\uc0c1 \uc785\ub825\ud560\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +errors.invalid={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uac12\uc785\ub2c8\ub2e4. +errors.minInteger={0}\uc740(\ub294) \uc720\ud6a8\ud55c \uac12\uc774 \uc544\ub2d9\ub2c8\ub2e4. 1 \uc774\uc0c1\uc758 \uac12\uc744 \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.byte={0}\uc740(\ub294) byte\ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.short={0}\uc740(\ub294) short\ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.integer={0}\uc740(\ub294) \uc815\uc218 \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.long={0}\uc740(\ub294) long \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.float={0}\uc740(\ub294) \uc2e4\uc218 \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.double={0}\uc740(\ub294) double \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. + +errors.date={0}\uc740(\ub294) \ub0a0\uc9dc \uc720\ud615\uc774 \uc544\ub2d9\ub2c8\ub2e4. +errors.range={0}\uc740(\ub294) {1}\uacfc {2} \uc0ac\uc774\uc758 \uac12\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.creditcard={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc2e0\uc6a9\uce74\ub4dc \ubc88\ud638\uc785\ub2c8\ub2e4. +errors.email={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc774\uba54\uc77c \uc8fc\uc18c\uc785\ub2c8\ub2e4. + +errors.ihidnum=\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638\uc785\ub2c8\ub2e4. +errors.korean={0}\uc740(\ub294) \ud55c\uae00\uc744 \uc785\ub825\ud558\uc154\uc57c \ud569\ub2c8\ub2e4. +errors.ip=\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 IP\uc8fc\uc18c\uc785\ub2c8\ub2e4. + +errors.password1={0}\uc740(\ub294) 8~20\uc790 \ub0b4\uc5d0\uc11c \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.password2={0}\uc740(\ub294) \ud55c\uae00,\ud2b9\uc218\ubb38\uc790,\ub744\uc5b4\uc4f0\uae30\ub294 \ud5c8\uc6a9\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +errors.password3={0}\uc740(\ub294) \uc21c\ucc28\uc801\uc778 \uc22b\uc790\ub97c 4\uac1c\uc774\uc0c1 \uc5f0\uc18d\ud574\uc11c \uc0ac\uc6a9\ud560\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +errors.password4={0}\uc740(\ub294) \ubc18\ubcf5\ub418\ub294 \ubb38\uc790\ub098 \uc22b\uc790\ub97c 4\uac1c\uc774\uc0c1 \uc5f0\uc18d\ud574\uc11c \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. + +errors.notKorean={0}\uc740(\ub294) \ud55c\uae00\uc744 \uc0ac\uc6a9\ud558\uc2e4\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +error.security.runtime.error = error +#Vlidator Errors- wordDicaryVO# +wordDicaryVO.wordNm=\uc6a9\uc5b4\uba85 +wordDicaryVO.engNm=\uc601\ubb38\uba85 +wordDicaryVO.wordDc=\uc6a9\uc5b4\uc124\uba85 +wordDicaryVO.synonm=\ub3d9\uc758\uc5b4 + +#Vlidator Errors- cnsltManageVO# +cnsltManageVO.cnsltSj=\uc0c1\ub2f4\uc81c\ubaa9 +cnsltManageVO.cnsltCn=\uc0c1\ub2f4\ub0b4\uc6a9 +cnsltManageVO.writngPassword=\uc791\uc131\ube44\ubc00\ubc88\ud638 +cnsltManageVO.areaNo=\uc9c0\uc5ed\ubc88\ud638 +cnsltManageVO.middleTelno=\uc911\uac04\uc804\ud654\ubc88\ud638 +cnsltManageVO.endTelno=\ub05d\uc804\ud654\ubc88\ud638 +cnsltManageVO.wrterNm=\uc791\uc131\uc790\uba85 +cnsltManageVO.managtCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- siteManageVO# +siteManageVO.siteNm=\uc0ac\uc774\ud2b8\uba85 +siteManageVO.siteUrl=\uc0ac\uc774\ud2b8 URL +siteManageVO.siteDc=\uc0ac\uc774\ud2b8\uc124\uba85 +siteManageVO.siteThemaClCode=\uc0ac\uc774\ud2b8\uc8fc\uc81c\ubd84\ub958 +siteManageVO.actvtyAt=\ud65c\uc131\uc5ec\ubd80 +siteManageVO.useAt=\uc0ac\uc6a9\uc5ec\ubd80 + +#Vlidator Errors- recomendSiteManageVO# +recomendSiteManageVO.recomendSiteNm=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uba85 +recomendSiteManageVO.recomendSiteUrl=\ucd94\ucc9c\uc0ac\uc774\ud2b8 URL +recomendSiteManageVO.recomendSiteDc=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uc124\uba85 +recomendSiteManageVO.recomendResnCn=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uc2b9\uc778\uc0ac\uc720 +recomendSiteManageVO.confmDe=\uc2b9\uc778\uc77c\uc790 + +#Vlidator Errors- hpcmManageVO# +hpcmManageVO.hpcmSeCode=\ub3c4\uc6c0\ub9d0\uad6c\ubd84 +hpcmManageVO.hpcmDf=\ub3c4\uc6c0\ub9d0\uc815\uc758 +hpcmManageVO.hpcmDc=\ub3c4\uc6c0\ub9d0\uc124\uba85 + +#Vlidator Errors- newsManageVO# +newsManageVO.newsSj=\ub274\uc2a4\uc81c\ubaa9 +newsManageVO.newsCn=\ub274\uc2a4\ub0b4\uc6a9 +newsManageVO.ntceDe=\uac8c\uc2dc\uc77c\uc790 + +#Vlidator Errors- faqManageVO# +faqManageVO.qestnSj=\uc9c8\ubb38\uc81c\ubaa9 +faqManageVO.qestnCn=\uc9c8\ubb38\ub0b4\uc6a9 +faqManageVO.answerCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- stplatManageVO# +stplatManageVO.useStplatNm=\uc774\uc6a9\uc57d\uad00\uba85 +stplatManageVO.useStplatCn=\uc774\uc6a9\uc57d\uad00\ub0b4\uc6a9 +stplatManageVO.infoProvdAgreCn=\uc815\ubcf4\uc81c\uacf5\ub3d9\uc758\ub0b4\uc6a9 + +#Vlidator Errors- cpyrhtPrtcPolicyVO# +cpyrhtPrtcPolicyVO.cpyrhtPrtcPolicyCn=\uc800\uc791\uad8c\ubcf4\ud638\uc815\ucc45\ub0b4\uc6a9 + +#Vlidator Errors- qnaManageVO# +qnaManageVO.qestnSj=\uc9c8\ubb38\uc81c\ubaa9 +qnaManageVO.qestnCn=\uc9c8\ubb38\ub0b4\uc6a9 +qnaManageVO.writngPassword=\uc791\uc131\ube44\ubc00\ubc88\ud638 +qnaManageVO.areaNo=\uc9c0\uc5ed\ubc88\ud638 +qnaManageVO.middleTelno=\uc911\uac04\uc804\ud654\ubc88\ud638 +qnaManageVO.endTelno=\ub05d\uc804\ud654\ubc88\ud638 +qnaManageVO.wrterNm=\uc791\uc131\uc790\uba85 +qnaManageVO.answerCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- ReprtStatsVO# +sts.reprtId = \ubcf4\uace0\uc11cID +sts.title = \ubcf4\uace0\uc11c\uba85 +sts.category = \ubcf4\uace0\uc11c\uc720\ud615 +sts.status = \uc9c4\ud589\uc0c1\ud0dc +sts.regDate = \ub4f1\ub85d\uc77c\uc2dc + +#Rest day messages# +sym.cal.restDay = \ud734\uc77c\uc77c\uc790 +sym.cal.restName = \ud734\uc77c\uba85 +sym.cal.restDetail = \ud734\uc77c\uc124\uba85 +sym.cal.restCategory = \ud734\uc77c\uad6c\ubd84 \ No newline at end of file diff --git a/mens-batch/src/main/resources/logback-spring.xml b/mens-batch/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..1a30325 --- /dev/null +++ b/mens-batch/src/main/resources/logback-spring.xml @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + ${CONSOLE_LOG_CHARSET} + + + + + + + + + + + ${LOG_PATH}/${LOG_FILE}.log + + DEBUG + ACCEPT + + ACCEPT + + + + + + ${FILE_LOG_PATTERN} + ${FILE_LOG_CHARSET} + + + + ${LOG_PATH}/backup/${LOG_FILE}_%d{yyyy-MM-dd}.%i.log.gz + + 30MB + + 50 + + + + + + + + + + 2048 + 20 + + 6000 + + + true + + ${isIncludeCallerData} + + + DEBUG + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mens-batch/src/main/resources/static/error.html b/mens-batch/src/main/resources/static/error.html new file mode 100644 index 0000000..a49f913 --- /dev/null +++ b/mens-batch/src/main/resources/static/error.html @@ -0,0 +1,10 @@ + + + + + Title + + +

Error

+ + diff --git a/mens-batch/src/main/resources/static/index.html b/mens-batch/src/main/resources/static/index.html new file mode 100644 index 0000000..f6e2484 --- /dev/null +++ b/mens-batch/src/main/resources/static/index.html @@ -0,0 +1,16 @@ + + + + + 전자고지 API Framework + + +

전자고지 API Framework

+

+ API Document +

+

+ 자세한 사항은 README.md 참고 +

+ + diff --git a/mens-batch/src/test/java/ApiWebClientTest.java b/mens-batch/src/test/java/ApiWebClientTest.java new file mode 100644 index 0000000..5589e01 --- /dev/null +++ b/mens-batch/src/test/java/ApiWebClientTest.java @@ -0,0 +1,31 @@ +import static org.assertj.core.api.Assertions.*; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.*; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpMethod; +import org.springframework.test.web.reactive.server.WebTestClient; + + +@AutoConfigureWebClient +@SpringBootTest(webEnvironment = RANDOM_PORT) +public class ApiWebClientTest { + + @Autowired + WebTestClient webTestClient; + + @Test + public void helloWebClient() { + webTestClient.method(HttpMethod.GET) + .uri("/hello") + .exchange() + .expectStatus().isOk() // 응답 코드 기대값 + .expectBody(String.class) // 응답 body 클래스 타입 기대값 + .value(response -> { // 응답 바디 response + System.out.println("response = " + response); + assertThat(response).isEqualToIgnoringCase("Hello World"); + }); + } +} diff --git a/mens-core/.gitignore b/mens-core/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/mens-core/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/mens-core/README.md b/mens-core/README.md new file mode 100644 index 0000000..4929efe --- /dev/null +++ b/mens-core/README.md @@ -0,0 +1,91 @@ + +## 전자정부 프레임워크 호환성 +[호환성 가이드 문서](./egov_compatibility_guide.md) + +### log 설정 : application-${spring.profiles.active}.yml + +```yml +# application-local.yml 파일 기준 +app: + # parameter 로그 출력 + param.log.enabled: true + # parameter custom 로그 출력(LoggerAspect) + param.log.custom.enabled: false + # MDC logging trace 활성 + mdc.log.trace.enabled: true + + # SQL 로그 : p6spy + sql.logging.enabled: true + + + +# 로그 파일 위치 +logging: + level: + root: debug + file: + path: D:/data/ens/logs +``` +### SQL 로그 : p6spy 또는 log4jdbc +```yml +# SQL 로그를 log4jdbc로 교체할 경우 +# pom.xml 변경 : 아래 comment 제거 + +# application-local.yml 파일 spring.datasouce의 driver 와 url 변경 + # ================ log4jdbc =========================== + #driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy + #url: jdbc:log4jdbc:mariadb://211.119.124.122:3306/xplatform?useUnicode=true&characterEncoding=utf-8 + # ===================================================== + # =============== p6spy =============================== + driver-class-name: org.mariadb.jdbc.Driver + url: jdbc:mariadb://211.119.124.122:3306/xplatform?useUnicode=true&characterEncoding=utf-8 + # ===================================================== + +# sql.logging.enabled: true -> false로 변경 : p6spy log disalbled +# /resources/logback-spring-log4jdbc.xml -> logback-spring.xml 으로 변경 +``` + +### JPA 활성 : application-jpa.yml +```yml +app: + # jpa 활성 여부 + jpa: + enabled: false -> true +``` + + +## 암호화(license) 적용 +### 모듈 import : xit-init.jar + -> 복호화 메소드만 제공 +```text +1. /resources/META-INF/spring.factories 파일 생성 + +# META-INF/spring.factories : xit-init +org.springframework.context.ApplicationContextInitializer=xit.core.init.custom.CustomContextInitalizer +``` +### 정보(license) 암호화 +```text +# 암호화 필요 정보 encoding : 전자서명 알고리즘- RSA, 블록암호화알고리즘 - SEEDEngine +kr.xit.core.spring.config.custom.bouncy.BouncyUtils 사용 +-> packaging시 제외되는 클래스로 로컬에서만 사용 +ex) app: + license: + path: ${app.data.root.path}/ens/.pem/ + key: 'mxLAM1fAEDPWkFz8' + data1: 'jz5LT6TlZtewv1GRVN3cI6CgoPgS89Sfh7qSKkCVjjMPOyBKkT386tlnMnjXluTSr8OIvI1pHd96fHxRZHNUBuLeQOkUeWuzkfxlP8C7nDyIrG36T2aontjroAxoNk3oYdIYRVWNs1Iqw39v9xF8NYFFLGqtfv/wnCGxlwTsDwCf9bjAtyd9cTiS27dVrIbrAVKnchgxIF/DUQQc901l4DZ5gsT6aLx6TmzhvAewPK1HfiG9WrMWxpw0TMxt++0Vedh+oZEs48ACMpuHFFh406LynyxxE7boRVbKmh7Tn87gKa6+zzdzIN1kS8sk58Ms1HPvBfvwwnD8qiJItXO6DQ==' + data2: 'Te3bfmvdiMZdpfRwC3OO9UjwbNkvbf16kqEqn9VqwbVztizA9rvMFshlI0vuqai9Hml31IsNINKg+OYkhmkH6ic1I10r6MNIVl3WL5YxfeK7YBmjvNuGZtKwchlWzhMODsgNAq0aIQVi4kLk5filDaZESY10xlNdbf9c/SKGfJeLZxY7DCchkAgj/ZnmZNqOE2kDAoC+O1ksDNTS0+cr+WsKsoFON0EpNI5B2ElBtnT1LmQQ3R+FNCtp7YJaRZA3RPsata05kKH7sL1J0M6A6HIVxisOU3bjH0hB+60BHJfdlEiXo6RJvsPyXotwe8MYVrHJbIQgsepxSDpMFZe8HA==' + data3: 't9qYJTU49dbaeezJkzpM2uY3iLIcy/V/VnyVsWIcd0f4QMLJ3cmLZ0QcMyKoR7CL2CuHMnPJz8j7KFOTQRPpeN/Dl4bCpOu+BM3foYpn4wb5HcLdHJxp5CuFmhTfqRGuUxurv6jcqkwmRzPW35UjQLjeKSdv6m+2b84PN4sZSNeMjQDH0QC85yKphHKV8m6bzqUbHLiZwDXndgpq2/YGKdWjPinlH7PZ+L2xfrfhdWXoY9QrHYVOSPogd81EizzyQseif8GAkeUG1OKAOomhyEuTOxtdGbUew59YuuBlUpORgj/Koclyd2shHyne9CJdqnQqAA2mh61V1ZzBUvSIWQ==' + + +# 암호화된 정보 등록 : ens-api 모듈에 적용 +/resources/config/application-${profiles}.yml +- app.license 속성 + +# 필요시 xit-init 모듈 AppInitHelper.init() method 재정의 +``` diff --git a/mens-core/document/egov_compatibility_guide.pdf b/mens-core/document/egov_compatibility_guide.pdf new file mode 100644 index 0000000..cfbc6c0 Binary files /dev/null and b/mens-core/document/egov_compatibility_guide.pdf differ diff --git a/mens-core/egov_compatibility_guide.md b/mens-core/egov_compatibility_guide.md new file mode 100644 index 0000000..f5177d2 --- /dev/null +++ b/mens-core/egov_compatibility_guide.md @@ -0,0 +1,63 @@ +# 전자정부 프레임워크 호환성 가이드 +[호환성가이드 문서](./document/egov_compatibility_guide.pdf) + +### Index +[1. 실행환경 변경 금지](#실행환경-변경-금지) +[2. 설정파일 위치 규칙](#설정파일-위치-규칙) +[3. 데이터 액세스 아키텍처 규칙](#데이터-액세스-아키텍처-규칙) +[4. MVC 아키텍처 규칙](#MVC-아키텍처-규칙) +[5. 서비스 아키텍처 규칙](#서비스-아키텍처-규칙) +[6. 실행환경 확장 규칙](#실행환경-확장-규칙) +[7. 표준프레임워크 활용 규칙](#표준프레임워크-활용-규칙) + +#### 실행확경 변경 금지 +```text +# 필수 dependency +egovframework.rte.ptl.mvc.jar +egovframework.rte.fdl.cmmn.jar +egovframework.rte.psl.dataaccess.jar +egovframework.rte.fdl.logging.jar + + +``` + +#### 설정파일 위치 규칙 +```text +# 설정파일은 프로젝트 루트에 위히할 수 없다 +# 설정파일은 특정 위치에 존재하여야 한다 +# 설정파일들은 공통적인 상위 디렉토리를 가져야 한다 +``` + +#### 데이터 액세스 아키텍처 규칙 +```text +# ibatis - EgovAbstractDAO 상속 +# mybatis - EgovAbstractMapper 상속 +# mybatis Mapper Interface 방식 사용시 + - MapperConfigurer 설정시 프레임워크에서 제공하는 MapperConfigurer와 @Mapper 사용 + - 프로젝트에 부적합한 경우 해당 클래스를 상속받아 구현 가능 +``` + +#### MVC 아키텍처 규칙 +```text +# Ibatis SqlMapClientDaoSupport, Mybatis SqlSessionDaoSupport 클래스 메소드 호출 불가 +``` + +#### 서비스 아키텍처 규칙 +```text +# EgovAbstractServiceImpl 상속 받아야 한다 +# 특정 인터페이스를 구현하여야 한다 +``` + +#### 실행환경 확장 규칙 +```text +# egovframework.rte 패키지에 속한 클래스를 상속받은 클래스는 모두 대상 +# egovframework.rte 패키지 내에 정의될 수 없다 +# Egov라는 이름으로 시작할 수 없다 +``` + +#### 표준프레임워크 활용 규칙 +```text +# 한 개 이상의 실행환경 라이브러리가 존재 +# 한 개 이상의 DAO 클래스 +# 한 개 이상의 Service 클래스 +``` diff --git a/mens-core/pom.xml b/mens-core/pom.xml new file mode 100644 index 0000000..c97e4d9 --- /dev/null +++ b/mens-core/pom.xml @@ -0,0 +1,418 @@ + + + 4.0.0 + + + kr.xit + mens-parent + 1.0.0 + + + mens-core + 1.0.0 + jar + mens-core + Mobile Electronic Notice System Core Framework + + + + + kr.xit + xit-init + ${project.version} + + + + + org.springframework.boot + spring-boot-properties-migrator + runtime + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-jdbc + + + com.github.ulisesbocchio + jasypt-spring-boot-starter + 3.0.3 + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + + + com.github.gavlyukovskiy + p6spy-spring-boot-starter + 1.8.1 + + + + org.hibernate + hibernate-core + 6.2.2.Final + + + + + + org.egovframe.rte + org.egovframe.rte.ptl.mvc + ${org.egovframe.rte.version} + + + org.egovframe.rte + org.egovframe.rte.psl.dataaccess + ${org.egovframe.rte.version} + + + org.apache.logging.log4j + log4j-slf4j-impl + + + + + + + + org.egovframe.rte + org.egovframe.rte.fdl.crypto + ${org.egovframe.rte.version} + + + + org.egovframe.rte + org.egovframe.rte.fdl.string + ${org.egovframe.rte.version} + + + + org.egovframe.rte + org.egovframe.rte.fdl.property + ${org.egovframe.rte.version} + + + + org.springdoc + springdoc-openapi-ui + 1.6.14 + + + io.swagger.core.v3 + swagger-annotations + 2.2.6 + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + + javax.servlet.jsp.jstl + jstl-api + 1.2 + + + + + org.apache.commons + commons-dbcp2 + + + + commons-codec + commons-codec + + + + org.hibernate + hibernate-validator + 5.2.4.Final + + + + + + taglibs + standard + 1.1.2 + + + + cglib + cglib + 3.3.0 + + + + org.antlr + antlr + 3.5 + + + + org.apache.commons + commons-compress + 1.21 + + + + org.hsqldb + hsqldb + 2.7.1 + + + + + org.mariadb.jdbc + mariadb-java-client + + + + + com.googlecode.log4jdbc + log4jdbc + 1.2 + + + slf4j-api + org.slf4j + + + + + + org.apache.logging.log4j + log4j-core + 2.17.2 + + + + + io.jsonwebtoken + jjwt-api + 0.11.2 + + + io.jsonwebtoken + jjwt-jackson + 0.11.2 + runtime + + + io.jsonwebtoken + jjwt-impl + 0.11.2 + runtime + + + + + commons-fileupload + commons-fileupload + 1.4 + + + + javax.servlet.jsp + javax.servlet.jsp-api + 2.3.3 + provided + + + + com.ibm.icu + icu4j + 71.1 + + + + + + + org.projectlombok + lombok + ${lombok.version} + true + + + + org.bgee.log4jdbc-log4j2 + log4jdbc-log4j2-jdbc4.1 + 1.16 + + + + + com.googlecode.json-simple + json-simple + 1.1.1 + + + com.google.code.gson + gson + 2.9.0 + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + + + com.fasterxml.jackson.datatype + jackson-datatype-joda + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + commons-configuration + commons-configuration + 1.10 + + + + org.apache.commons + commons-collections4 + 4.3 + + + + org.apache.commons + commons-text + 1.10.0 + + + + org.apache.httpcomponents + httpcore + 4.4.6 + + + + org.apache.httpcomponents + httpclient + 4.5.11 + + + + + com.slack.api + slack-api-client + 1.28.0 + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + com.jcraft + jsch + 0.1.55 + + + + org.bouncycastle + bcprov-jdk15on + 1.70 + + + org.bouncycastle + bcpkix-jdk15on + 1.70 + + + com.vaadin.external.google + android-json + 0.0.20131108.vaadin1 + compile + + + org.junit.jupiter + junit-jupiter-api + test + + + org.assertj + assertj-core + test + + + + + + install + ${basedir}/target + ${project.name} + + + org.apache.maven.plugins + maven-compiler-plugin + + + **/BouncyUtils.java + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + **/BouncyUtils.java + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + diff --git a/mens-core/src/main/java/egovframework/com/cmm/EgovComExcepHndlr.java b/mens-core/src/main/java/egovframework/com/cmm/EgovComExcepHndlr.java new file mode 100644 index 0000000..51571d3 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/EgovComExcepHndlr.java @@ -0,0 +1,34 @@ +package egovframework.com.cmm; + +import org.egovframe.rte.fdl.cmmn.exception.handler.ExceptionHandler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import lombok.extern.slf4j.Slf4j; + +/** + * @Class Name : EgovComExcepHndlr.java + * @Description : 공통서비스의 exception 처리 클래스 + * @Modification Information + * + * 수정일 수정자 수정내용 + * ------- ------- ------------------- + * 2009. 3. 13. 이삼섭 + * + * @author 공통 서비스 개발팀 이삼섭 + * @since 2009. 3. 13. + * @version + * @see + * + */ +@Slf4j +public class EgovComExcepHndlr implements ExceptionHandler { + + /** + * 발생된 Exception을 처리한다. + */ + public void occur(Exception e, String packageName) { + log.error("{} error: {}", packageName, e.getMessage()); + } +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/EgovComOthersExcepHndlr.java b/mens-core/src/main/java/egovframework/com/cmm/EgovComOthersExcepHndlr.java new file mode 100644 index 0000000..53cd71a --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/EgovComOthersExcepHndlr.java @@ -0,0 +1,16 @@ +package egovframework.com.cmm; + +import org.egovframe.rte.fdl.cmmn.exception.handler.ExceptionHandler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class EgovComOthersExcepHndlr implements ExceptionHandler { + + public void occur(Exception e, String packageName) { + log.error("{} error: {}", packageName, e.getMessage()); + } +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/EgovComTraceHandler.java b/mens-core/src/main/java/egovframework/com/cmm/EgovComTraceHandler.java new file mode 100644 index 0000000..bf590e4 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/EgovComTraceHandler.java @@ -0,0 +1,33 @@ +package egovframework.com.cmm; + +import org.egovframe.rte.fdl.cmmn.trace.handler.TraceHandler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @Class Name : EgovComTraceHandler.java + * @Description : 공통서비스의 trace 처리 클래스 + * @Modification Information + * + * 수정일 수정자 수정내용 + * ------- ------- ------------------- + * 2011. 09. 30. JJY + * + * @author JJY + * @since 2011. 9. 30. + * + */ +public class EgovComTraceHandler implements TraceHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(EgovComTraceHandler.class); + + /** + * 발생된 메시지를 출력한다. + */ + public void todo(Class clazz, String message) { + LOGGER.debug("[TRACE]CLASS::: {}", clazz.getName()); + LOGGER.debug("[TRACE]MESSAGE::: {}", message); + //이곳에서 후속처리로 필요한 액션을 취할 수 있다. + } +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/EgovComponentChecker.java b/mens-core/src/main/java/egovframework/com/cmm/EgovComponentChecker.java new file mode 100644 index 0000000..593b1ed --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/EgovComponentChecker.java @@ -0,0 +1,61 @@ +package egovframework.com.cmm; + +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Service; +import org.springframework.util.ObjectUtils; + + +/** + * EgovComUtil 클래스 + * + * @author 서준식 + * @since 2011.09.15 + * @version 1.0 + * @see + * + *
+ * << 개정이력(Modification Information) >>
+ *
+ *   수정일      수정자           수정내용
+ *  -------    -------------    ----------------------
+ *   2011.09.15  서준식        최초 생성
+ * 
+ */ + +@Service("egovUtil") +public class EgovComponentChecker extends EgovAbstractServiceImpl implements ApplicationContextAware{ + + + private static ApplicationContext context; + + @Override + @SuppressWarnings("static-access") + public void setApplicationContext(ApplicationContext context) + throws BeansException { + + this.context = context; + } + + /** + * Spring MVC에서 설정한 빈이 아닌 서비스 빈(컴포넌트)만을 검색할 수 있음 + * + */ + public static boolean hasComponent(String componentName){ + + try{ + Object component = context.getBean(componentName); + + // Fix: Null pointers should not be dereferenced 이슈 수정 + return !ObjectUtils.isEmpty(component); + + }catch(NoSuchBeanDefinitionException ex){// 해당 컴포넌트를 찾을 수없을 경우 false반환 + return false; + } + } + +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/EgovMessageSource.java b/mens-core/src/main/java/egovframework/com/cmm/EgovMessageSource.java new file mode 100644 index 0000000..63a1d5b --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/EgovMessageSource.java @@ -0,0 +1,55 @@ +package egovframework.com.cmm; + +import java.util.Locale; + +import org.springframework.context.MessageSource; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; + +/** + * 메시지 리소스 사용을 위한 MessageSource 인터페이스 및 ReloadableResourceBundleMessageSource 클래스의 구현체 + * @author 공통서비스 개발팀 이문준 + * @since 2009.06.01 + * @version 1.0 + * @see + * + *
+ * << 개정이력(Modification Information) >>
+ *   
+ *   수정일      수정자           수정내용
+ *  -------    --------    ---------------------------
+ *   2009.03.11  이문준          최초 생성
+ *
+ * 
+ */ + +public class EgovMessageSource extends ReloadableResourceBundleMessageSource implements MessageSource { + + private ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource; + + /** + * getReloadableResourceBundleMessageSource() + * @param reloadableResourceBundleMessageSource - resource MessageSource + * @return ReloadableResourceBundleMessageSource + */ + public void setReloadableResourceBundleMessageSource(ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource) { + this.reloadableResourceBundleMessageSource = reloadableResourceBundleMessageSource; + } + + /** + * getReloadableResourceBundleMessageSource() + * @return ReloadableResourceBundleMessageSource + */ + public ReloadableResourceBundleMessageSource getReloadableResourceBundleMessageSource() { + return reloadableResourceBundleMessageSource; + } + + /** + * 정의된 메세지 조회 + * @param code - 메세지 코드 + * @return String + */ + public String getMessage(String code) { + return getReloadableResourceBundleMessageSource().getMessage(code, null, Locale.getDefault()); + } + +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/EgovWebUtil.java b/mens-core/src/main/java/egovframework/com/cmm/EgovWebUtil.java new file mode 100644 index 0000000..2d95318 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/EgovWebUtil.java @@ -0,0 +1,46 @@ +package egovframework.com.cmm; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.commons.text.StringEscapeUtils; + +import java.util.regex.Pattern; + +/** + * 교차접속 스크립트 공격 취약성 방지(파라미터 문자열 교체) + * + *
+ * << 개정이력(Modification Information) >>
+ *
+ *   수정일              수정자           수정내용
+ *  -----------  --------  ---------------------------
+ *   2011.10.10  한성곤           최초 생성
+ *	 2017-02-07   이정은           시큐어코딩(ES) - 시큐어코딩 경로 조작 및 자원 삽입[CWE-22, CWE-23, CWE-95, CWE-99]
+ *   2018.08.17  신용호           filePathBlackList 수정
+ *   2018.10.10  신용호           . => \\.으로 수정
+ * 
+ */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class EgovWebUtil { + public static String clearXSSMinimum(String value) { + if (value == null || value.trim().equals("")) { + return ""; + } + return StringEscapeUtils.escapeHtml4(value); + } + + public static String filePathBlackList(String value) { + String returnValue = value; + if (returnValue == null || returnValue.trim().equals("")) { + return ""; + } + + returnValue = returnValue.replaceAll("/\\.\\./g", ""); + + return returnValue; + } + + public static String removeCRLF(String parameter) { + return parameter.replaceAll("(\r\n|\r|\n|\n\r)", ""); + } +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/ImagePaginationRenderer.java b/mens-core/src/main/java/egovframework/com/cmm/ImagePaginationRenderer.java new file mode 100644 index 0000000..58d9947 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/ImagePaginationRenderer.java @@ -0,0 +1,46 @@ +package egovframework.com.cmm; + +import lombok.NoArgsConstructor; +import org.egovframe.rte.ptl.mvc.tags.ui.pagination.AbstractPaginationRenderer; + +import javax.servlet.ServletContext; + +import org.springframework.web.context.ServletContextAware; +/** + * ImagePaginationRenderer.java 클래스 + * + * @author 서준식 + * @since 2011. 9. 16. + * @version 1.0 + * @see + * + *
+ * << 개정이력(Modification Information) >>
+ *   
+ *   수정일      수정자           수정내용
+ *  -------    -------------    ----------------------
+ *   2011. 9. 16.   서준식       이미지 경로에 ContextPath추가
+ * 
+ */ +@NoArgsConstructor +public class ImagePaginationRenderer extends AbstractPaginationRenderer implements ServletContextAware{ + + private ServletContext servletContext; + + public void initVariables(){ + firstPageLabel = "
  •  
  • \"처음\"
  • "; + previousPageLabel = "
  • \"이전\"
  • "; + currentPageLabel = "
  • {0}
  • "; + otherPageLabel = "
  • {2}
  • "; + nextPageLabel = "
  •  \"다음\"
  • "; + lastPageLabel = "
  • \"마지막\"
  • "; + } + + + + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + initVariables(); + } + +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/LoginVO.java b/mens-core/src/main/java/egovframework/com/cmm/LoginVO.java new file mode 100644 index 0000000..9c21226 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/LoginVO.java @@ -0,0 +1,245 @@ +package egovframework.com.cmm; + +import java.io.Serializable; + +/** + * @Class Name : LoginVO.java + * @Description : Login VO class + * @Modification Information + * @ + * @ 수정일 수정자 수정내용 + * @ ------- -------- --------------------------- + * @ 2009.03.03 박지욱 최초 생성 + * + * @author 공통서비스 개발팀 박지욱 + * @since 2009.03.03 + * @version 1.0 + * @see + * + */ +public class LoginVO implements Serializable{ + + /** 아이디 */ + private String id; + /** 이름 */ + private String name; + /** 주민등록번호 */ + private String ihidNum; + /** 이메일주소 */ + private String email; + /** 비밀번호 */ + private String password; + /** 비밀번호 힌트 */ + private String passwordHint; + /** 비밀번호 정답 */ + private String passwordCnsr; + /** 사용자구분 */ + private String userSe; + /** 조직(부서)ID */ + private String orgnztId; + /** 조직(부서)명 */ + private String orgnztNm; + /** 고유아이디 */ + private String uniqId; + /** 로그인 후 이동할 페이지 */ + private String url; + /** 사용자 IP정보 */ + private String ip; + /** GPKI인증 DN */ + private String dn; + /** + * id attribute 를 리턴한다. + * @return String + */ + public String getId() { + return id; + } + /** + * id attribute 값을 설정한다. + * @param id String + */ + public void setId(String id) { + this.id = id; + } + /** + * name attribute 를 리턴한다. + * @return String + */ + public String getName() { + return name; + } + /** + * name attribute 값을 설정한다. + * @param name String + */ + public void setName(String name) { + this.name = name; + } + /** + * ihidNum attribute 를 리턴한다. + * @return String + */ + public String getIhidNum() { + return ihidNum; + } + /** + * ihidNum attribute 값을 설정한다. + * @param ihidNum String + */ + public void setIhidNum(String ihidNum) { + this.ihidNum = ihidNum; + } + /** + * email attribute 를 리턴한다. + * @return String + */ + public String getEmail() { + return email; + } + /** + * email attribute 값을 설정한다. + * @param email String + */ + public void setEmail(String email) { + this.email = email; + } + /** + * password attribute 를 리턴한다. + * @return String + */ + public String getPassword() { + return password; + } + /** + * password attribute 값을 설정한다. + * @param password String + */ + public void setPassword(String password) { + this.password = password; + } + /** + * passwordHint attribute 를 리턴한다. + * @return String + */ + public String getPasswordHint() { + return passwordHint; + } + /** + * passwordHint attribute 값을 설정한다. + * @param passwordHint String + */ + public void setPasswordHint(String passwordHint) { + this.passwordHint = passwordHint; + } + /** + * passwordCnsr attribute 를 리턴한다. + * @return String + */ + public String getPasswordCnsr() { + return passwordCnsr; + } + /** + * passwordCnsr attribute 값을 설정한다. + * @param passwordCnsr String + */ + public void setPasswordCnsr(String passwordCnsr) { + this.passwordCnsr = passwordCnsr; + } + /** + * userSe attribute 를 리턴한다. + * @return String + */ + public String getUserSe() { + return userSe; + } + /** + * userSe attribute 값을 설정한다. + * @param userSe String + */ + public void setUserSe(String userSe) { + this.userSe = userSe; + } + /** + * orgnztId attribute 를 리턴한다. + * @return String + */ + public String getOrgnztId() { + return orgnztId; + } + /** + * orgnztId attribute 값을 설정한다. + * @param orgnztId String + */ + public void setOrgnztId(String orgnztId) { + this.orgnztId = orgnztId; + } + /** + * uniqId attribute 를 리턴한다. + * @return String + */ + public String getUniqId() { + return uniqId; + } + /** + * uniqId attribute 값을 설정한다. + * @param uniqId String + */ + public void setUniqId(String uniqId) { + this.uniqId = uniqId; + } + /** + * url attribute 를 리턴한다. + * @return String + */ + public String getUrl() { + return url; + } + /** + * url attribute 값을 설정한다. + * @param url String + */ + public void setUrl(String url) { + this.url = url; + } + /** + * ip attribute 를 리턴한다. + * @return String + */ + public String getIp() { + return ip; + } + /** + * ip attribute 값을 설정한다. + * @param ip String + */ + public void setIp(String ip) { + this.ip = ip; + } + /** + * dn attribute 를 리턴한다. + * @return String + */ + public String getDn() { + return dn; + } + /** + * dn attribute 값을 설정한다. + * @param dn String + */ + public void setDn(String dn) { + this.dn = dn; + } + /** + * @return the orgnztNm + */ + public String getOrgnztNm() { + return orgnztNm; + } + /** + * @param orgnztNm the orgnztNm to set + */ + public void setOrgnztNm(String orgnztNm) { + this.orgnztNm = orgnztNm; + } + +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/config/EgovConfigAppAspect.java b/mens-core/src/main/java/egovframework/com/cmm/config/EgovConfigAppAspect.java new file mode 100644 index 0000000..33ec4dd --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/config/EgovConfigAppAspect.java @@ -0,0 +1,92 @@ +package egovframework.com.cmm.config; + +import org.egovframe.rte.fdl.cmmn.aspect.ExceptionTransfer; +import org.egovframe.rte.fdl.cmmn.exception.handler.ExceptionHandler; +import org.egovframe.rte.fdl.cmmn.exception.manager.DefaultExceptionHandleManager; +import org.egovframe.rte.fdl.cmmn.exception.manager.ExceptionHandlerService; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.util.AntPathMatcher; + +import egovframework.com.cmm.EgovComExcepHndlr; +import egovframework.com.cmm.EgovComOthersExcepHndlr; +import egovframework.com.cmm.interceptor.AopExceptionTransfer; +import lombok.RequiredArgsConstructor; + +/** + * @ClassName : EgovConfigAppAspect.java + * @Description : Aspect 설정 + * + * @author : 윤주호 + * @since : 2021. 7. 20 + * @version : 1.0 + * + *
    + * << 개정이력(Modification Information) >>
    + *
    + *   수정일              수정자               수정내용
    + *  -------------  ------------   ---------------------
    + *   2021. 7. 20    윤주호               최초 생성
    + * 
    + * + */ +@RequiredArgsConstructor +@Configuration +@EnableAspectJAutoProxy +public class EgovConfigAppAspect { + + private final AntPathMatcher antPathMatcher; + + @Bean + public EgovComExcepHndlr egovHandler() { + return new EgovComExcepHndlr(); + } + + @Bean + public EgovComOthersExcepHndlr otherHandler() { + return new EgovComOthersExcepHndlr(); + } + + @Bean + public DefaultExceptionHandleManager defaultExceptionHandleManager(ExceptionHandler egovHandler) { + DefaultExceptionHandleManager defaultExceptionHandleManager = new DefaultExceptionHandleManager(); + defaultExceptionHandleManager.setReqExpMatcher(antPathMatcher); + defaultExceptionHandleManager.setPatterns(new String[] {"**.service.**Service.*","**service.impl.*"}); + defaultExceptionHandleManager.setHandlers(new ExceptionHandler[] {egovHandler}); + return defaultExceptionHandleManager; + } + + @Bean + public DefaultExceptionHandleManager otherExceptionHandleManager() { + DefaultExceptionHandleManager defaultExceptionHandleManager = new DefaultExceptionHandleManager(); + defaultExceptionHandleManager.setReqExpMatcher(antPathMatcher); + defaultExceptionHandleManager.setPatterns(new String[] {"**.service.**Service.*","**service.impl.*"}); + defaultExceptionHandleManager.setHandlers(new ExceptionHandler[] {otherHandler()}); + return defaultExceptionHandleManager; + } + + /** + * @return + * Exception 발생시 후처리를 위해 표준프레임워크 실행환경의 ExceptionTransfer를 활용하도록 설정 + */ + @Bean + public ExceptionTransfer exceptionTransfer( + @Qualifier("defaultExceptionHandleManager") DefaultExceptionHandleManager defaultExceptionHandleManager, + @Qualifier("otherExceptionHandleManager") DefaultExceptionHandleManager otherExceptionHandleManager) { + ExceptionTransfer exceptionTransfer = new ExceptionTransfer(); + exceptionTransfer.setExceptionHandlerService(new ExceptionHandlerService[] { + defaultExceptionHandleManager, otherExceptionHandleManager + }); + return exceptionTransfer; + } + + @Bean + public AopExceptionTransfer aopExceptionTransfer(ExceptionTransfer exceptionTransfer) { + AopExceptionTransfer aopExceptionTransfer = new AopExceptionTransfer(); + aopExceptionTransfer.setExceptionTransfer(exceptionTransfer); + return aopExceptionTransfer; + } + +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/config/EgovConfigAppCommon.java b/mens-core/src/main/java/egovframework/com/cmm/config/EgovConfigAppCommon.java new file mode 100644 index 0000000..1cdac7e --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/config/EgovConfigAppCommon.java @@ -0,0 +1,186 @@ +package egovframework.com.cmm.config; + +import egovframework.com.cmm.EgovComTraceHandler; +import egovframework.com.cmm.EgovMessageSource; +import egovframework.com.cmm.ImagePaginationRenderer; +//import egovframework.com.cmm.web.EgovMultipartResolver; +import java.util.HashMap; +import java.util.Map; +import org.egovframe.rte.fdl.cmmn.trace.LeaveaTrace; +import org.egovframe.rte.fdl.cmmn.trace.handler.TraceHandler; +import org.egovframe.rte.fdl.cmmn.trace.manager.DefaultTraceHandleManager; +import org.egovframe.rte.fdl.cmmn.trace.manager.TraceHandlerService; +import org.egovframe.rte.fdl.cryptography.EgovPasswordEncoder; +import org.egovframe.rte.fdl.cryptography.impl.EgovARIACryptoServiceImpl; +import org.egovframe.rte.ptl.mvc.tags.ui.pagination.DefaultPaginationManager; +import org.egovframe.rte.ptl.mvc.tags.ui.pagination.PaginationRenderer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; +import org.springframework.util.AntPathMatcher; + +/** + * @ClassName : EgovConfigAppCommon.java + * @Description : 공통 Bean 설정 + * + * @author : 윤주호 + * @since : 2021. 7. 20 + * @version : 1.0 + * + *
    + * << 개정이력(Modification Information) >>
    + *
    + *   수정일              수정자               수정내용
    + *  -------------  ------------   ---------------------
    + *   2021. 7. 20    윤주호               최초 생성
    + * 
    + * + */ +@Configuration +public class EgovConfigAppCommon { + + @Value("${app.encrypt.alg}") + private String encryptAlg; + @Value("${app.encrypt.passwd}") + private String encryptPasswd; + + /** + * @return AntPathMatcher 등록. Ant 경로 패턴 경로와 일치하는지 여부를 확인 + */ + @Bean + public AntPathMatcher antPathMatcher() { + return new AntPathMatcher(); + } + + /** + * @return [Resource 설정] 메세지 Properties 경로 설정 + */ + @Bean + public ReloadableResourceBundleMessageSource messageSource() { + ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource = new ReloadableResourceBundleMessageSource(); + + reloadableResourceBundleMessageSource.setBasenames( + "classpath*:/egovframework/message/com/**/*", + "classpath:/org/egovframe/rte/fdl/idgnr/messages/idgnr", + "classpath:/org/egovframe/rte/fdl/property/messages/properties", + "classpath:/egovframework/egovProps/globals"); + reloadableResourceBundleMessageSource.setCacheSeconds(60); + return reloadableResourceBundleMessageSource; + } + + /** + * @return [Resource 설정] 메세지 소스 등록 + */ + @Bean + public EgovMessageSource egovMessageSource() { + EgovMessageSource egovMessageSource = new EgovMessageSource(); + egovMessageSource.setReloadableResourceBundleMessageSource(messageSource()); + return egovMessageSource; + } + + /** + * @return [LeaveaTrace 설정] defaultTraceHandler 등록 + */ + @Bean + public EgovComTraceHandler defaultTraceHandler() { + return new EgovComTraceHandler(); + } + + /** + * @return [LeaveaTrace 설정] traceHandlerService 등록. TraceHandler 설정 + */ + @Bean + public DefaultTraceHandleManager traceHandlerService() { + DefaultTraceHandleManager defaultTraceHandleManager = new DefaultTraceHandleManager(); + defaultTraceHandleManager.setReqExpMatcher(antPathMatcher()); + defaultTraceHandleManager.setPatterns(new String[] {"*"}); + defaultTraceHandleManager.setHandlers(new TraceHandler[] {defaultTraceHandler()}); + return defaultTraceHandleManager; + } + + /** + * @return [LeaveaTrace 설정] LeaveaTrace 등록 + */ + @Bean + public LeaveaTrace leaveaTrace() { + LeaveaTrace leaveaTrace = new LeaveaTrace(); + leaveaTrace.setTraceHandlerServices(new TraceHandlerService[] {traceHandlerService()}); + return leaveaTrace; + } + + /** + * @return [ImagePaginationRenderer 설정] ImagePaginationRenderer 등록 + */ + @Bean + public ImagePaginationRenderer imageRenderer() { + return new ImagePaginationRenderer(); + } + + /** + * @return [ImagePaginationRenderer 설정] defaultPaginationManager 설정. + */ + @Bean + public DefaultPaginationManager paginationManager() { + DefaultPaginationManager defaultPaginationManager = new DefaultPaginationManager(); + + Map rendererType = new HashMap<>(); + rendererType.put("image", imageRenderer()); + defaultPaginationManager.setRendererType(rendererType); + + return defaultPaginationManager; + } + + /** + * @return [MultipartResolver 설정] CommonsMultipartResolver 등록 + */ +// @Bean +// public CommonsMultipartResolver springRegularCommonsMultipartResolver() { +// CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver(); +// commonsMultipartResolver.setMaxUploadSize(100000000); +// commonsMultipartResolver.setMaxInMemorySize(100000000); +// return commonsMultipartResolver; +// } +// +// /** +// * 확장자 제한 : globals.properties > Globals.fileUpload.Extensions로 설정 +// * @return [MultipartResolver 설정] EgovMultipartResolver 등록 +// */ +// @Bean +// public EgovMultipartResolver localMultiCommonsMultipartResolver() { +// EgovMultipartResolver egovMultipartResolver = new EgovMultipartResolver(); +// egovMultipartResolver.setMaxUploadSize(100000000); +// egovMultipartResolver.setMaxInMemorySize(100000000); +// return egovMultipartResolver; +// } +// +// @Bean +// public CommonsMultipartResolver multipartResolver() { +// return localMultiCommonsMultipartResolver(); +// } + + /** + * 암복호화 + * @return [EgovPasswordEncoder 설정] EgovPasswordEncoder 등록 + */ + @Bean + public EgovPasswordEncoder egovPasswordEncoder() { + EgovPasswordEncoder egovPasswordEncoder = new EgovPasswordEncoder(); + egovPasswordEncoder.setAlgorithm(encryptAlg); + egovPasswordEncoder.setHashedPassword(encryptPasswd); + return egovPasswordEncoder; + } + + /** + * 암복호화 + * @return [EgovARIACryptoServiceImpl 설정] EgovARIACryptoServiceImpl 등록 + */ + @Bean + public EgovARIACryptoServiceImpl egovARIACryptoService() { + EgovARIACryptoServiceImpl egovARIACryptoServiceImpl = new EgovARIACryptoServiceImpl(); + egovARIACryptoServiceImpl.setPasswordEncoder(egovPasswordEncoder()); + egovARIACryptoServiceImpl.setBlockSize(1024); + return egovARIACryptoServiceImpl; + } + +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/config/EgovConfigAppValidator.java b/mens-core/src/main/java/egovframework/com/cmm/config/EgovConfigAppValidator.java new file mode 100644 index 0000000..9d57470 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/config/EgovConfigAppValidator.java @@ -0,0 +1,89 @@ +package egovframework.com.cmm.config; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springmodules.validation.commons.DefaultBeanValidator; +import org.springmodules.validation.commons.DefaultValidatorFactory; + +/** + * @ClassName : EgovConfigAppValidator.java + * @Description : Validator 설정 + * + * @author : 윤주호 + * @since : 2021. 7. 20 + * @version : 1.0 + * + *
    + * << 개정이력(Modification Information) >>
    + *
    + *   수정일              수정자               수정내용
    + *  -------------  ------------   ---------------------
    + *   2021. 7. 20    윤주호               최초 생성
    + * 
    + * + */ +@Configuration +public class EgovConfigAppValidator { + private static final Logger LOGGER = LoggerFactory.getLogger(EgovConfigAppValidator.class); + + @Value("${Globals.validator.rule}") + private String validatorRuleFile; + @Value("${Globals.validator.file}") + private String validatorFilePath; + + + @Bean + public DefaultBeanValidator beanValidator() { + DefaultBeanValidator defaultBeanValidator = new DefaultBeanValidator(); + defaultBeanValidator.setValidatorFactory(validatorFactory()); + return defaultBeanValidator; + + } + + /** validation config location 설정 + * @return + */ + @Bean + public DefaultValidatorFactory validatorFactory() { + DefaultValidatorFactory defaultValidatorFactory = new DefaultValidatorFactory(); + + defaultValidatorFactory.setValidationConfigLocations(getValidationConfigLocations()); + + return defaultValidatorFactory; + } + + private Resource[] getValidationConfigLocations() { + + PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver(); + + List validationConfigLocations = new ArrayList<>(); + + Resource[] validationRulesConfigLocations = new Resource[] { + pathMatchingResourcePatternResolver + .getResource(validatorRuleFile) + }; + + Resource[] validationFormSetLocations = new Resource[] {}; + try { + validationFormSetLocations = pathMatchingResourcePatternResolver + .getResources(validatorFilePath); + } catch (IOException e) { + LOGGER.error(e.getMessage()); + } + + validationConfigLocations.addAll(Arrays.asList(validationRulesConfigLocations)); + validationConfigLocations.addAll(Arrays.asList(validationFormSetLocations)); + + return validationConfigLocations.toArray(new Resource[validationConfigLocations.size()]); + } +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/config/package-info.java b/mens-core/src/main/java/egovframework/com/cmm/config/package-info.java new file mode 100644 index 0000000..a6d50b6 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/config/package-info.java @@ -0,0 +1,10 @@ +/** + * egov framework config package classes + *

    + * + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package egovframework.com.cmm.config; diff --git a/mens-core/src/main/java/egovframework/com/cmm/interceptor/AopExceptionTransfer.java b/mens-core/src/main/java/egovframework/com/cmm/interceptor/AopExceptionTransfer.java new file mode 100644 index 0000000..c0c9ffe --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/interceptor/AopExceptionTransfer.java @@ -0,0 +1,25 @@ +package egovframework.com.cmm.interceptor; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; + +import org.egovframe.rte.fdl.cmmn.aspect.ExceptionTransfer; + +@Aspect +public class AopExceptionTransfer { + private ExceptionTransfer exceptionTransfer; + + public void setExceptionTransfer(ExceptionTransfer exceptionTransfer) { + this.exceptionTransfer = exceptionTransfer; + } + + @Pointcut("execution(* egovframework.let..impl.*Impl.*(..)) or execution(* egovframework.com..impl.*Impl.*(..))") + private void exceptionTransferService() {} + + @AfterThrowing(pointcut= "exceptionTransferService()", throwing="ex") + public void doAfterThrowingExceptionTransferService(JoinPoint thisJoinPoint, Exception ex) throws Exception{ + exceptionTransfer.transfer(thisJoinPoint, ex); + } +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/interceptor/package-info.java b/mens-core/src/main/java/egovframework/com/cmm/interceptor/package-info.java new file mode 100644 index 0000000..c7716dd --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/interceptor/package-info.java @@ -0,0 +1,10 @@ +/** + * egov framework interceptor package classes + *

    + * + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package egovframework.com.cmm.interceptor; diff --git a/mens-core/src/main/java/egovframework/com/cmm/jwt/config/EgovJwtTokenUtil.java b/mens-core/src/main/java/egovframework/com/cmm/jwt/config/EgovJwtTokenUtil.java new file mode 100644 index 0000000..f9ffc14 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/jwt/config/EgovJwtTokenUtil.java @@ -0,0 +1,131 @@ +package egovframework.com.cmm.jwt.config; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import kr.xit.core.spring.config.properties.JwtProperties; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import egovframework.com.cmm.LoginVO; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import kr.xit.core.consts.ErrorCode; +import kr.xit.core.exception.BizRuntimeException; + +//security 관련 제외한 jwt util 클래스 + +@Component +public class EgovJwtTokenUtil implements Serializable{ + + private Key key; + + private final transient JwtProperties jwtProp; + + public EgovJwtTokenUtil(@Value("${app.token.secretKey}")String secret, JwtProperties jwtProperties) { + this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + this.jwtProp = jwtProperties; + } + + //retrieve username from jwt token + public String getUsernameFromToken(String token) { + return getClaimFromToken(token, Claims::getSubject); + } + + //retrieve expiration date from jwt token + public Date getExpirationDateFromToken(String token) { + return getClaimFromToken(token, Claims::getExpiration); + } + + //generate token for user + public String generateToken(LoginVO loginVO) { + return doGenerateToken(new HashMap<>(), loginVO.getUserSe()+loginVO.getId()); + } + + /** + * + * @param loginVO LoginVO + * @param claims Map JWT Payload(Claims)에 해당 속성 추가 + * @return + */ + public String generateToken(LoginVO loginVO, Map claims) { + return doGenerateToken(claims, loginVO.getUserSe()+loginVO.getId()); + } + + /** + * 토큰 유효성 검증 + * + * @param token + * @param loginVO + * @return + */ + public Boolean validateToken(String token, LoginVO loginVO) { + final String username = getUsernameFromToken(token); + if(!username.equals(loginVO.getUserSe()+loginVO.getId())){ + throw BizRuntimeException.create(ErrorCode.INVALID_TOKEN); + } + return isTokenExpired(token); + } + + + //---------------------------------------------------------------------------------------- + + //while creating the token - + //1. Define claims of the token, like Issuer, Expiration, Subject, and the ID + //2. Sign the JWT using the HS512 algorithm and secret key. + //3. According to JWS Compact Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1) + // compaction of the JWT to a URL-safe string + private String doGenerateToken(final Map claimMap, final String subject) { + Instant now = new Date().toInstant(); + + // payload(claimMap)에 시스템 속성 추가 + Claims claims = Jwts.claims(claimMap) + .setIssuer(jwtProp.getIssuer()) + .setAudience(jwtProp.getAudience()) + .setSubject(subject) + .setIssuedAt(Date.from(now)) + .setExpiration(Date.from(now.plus(jwtProp.getTokenExpiry(), ChronoUnit.DAYS))); + + return Jwts.builder() + // 2. Signature + .signWith(key, SignatureAlgorithm.valueOf(jwtProp.getAlg())) // header "alg": "HS512" + .setHeaderParam("typ", jwtProp.getTyp()) + .setHeaderParam("alg", jwtProp.getAlg()) + + // 1. Payload claim + .setClaims(claims) + + // 3. JWT compact + .compact(); + } + + private T getClaimFromToken(String token, Function claimsResolver) { + final Claims claims = getAllClaimsFromToken(token); + return claimsResolver.apply(claims); + } + + //for retrieveing any information from token we will need the secret key + private Claims getAllClaimsFromToken(String token) { + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + } + + //check if the token has expired + private Boolean isTokenExpired(String token) { + final Date expiration = getExpirationDateFromToken(token); + if(expiration.before(new Date())) throw BizRuntimeException.create(ErrorCode.EXPIRED_TOKEN); + return true; + } +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/jwt/config/JwtVerification.java b/mens-core/src/main/java/egovframework/com/cmm/jwt/config/JwtVerification.java new file mode 100644 index 0000000..74b0dcb --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/jwt/config/JwtVerification.java @@ -0,0 +1,64 @@ +package egovframework.com.cmm.jwt.config; + +import javax.servlet.http.HttpServletRequest; + +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import egovframework.com.cmm.LoginVO; +import egovframework.com.cmm.util.EgovStringUtil; +import egovframework.com.cmm.util.EgovUserDetailsHelper; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.UnsupportedJwtException; +import kr.xit.core.consts.Constants; + +@RequiredArgsConstructor +@Component +public class JwtVerification { + + private final EgovJwtTokenUtil jwtTokenUtil; + + private final Logger log = LoggerFactory.getLogger(JwtVerification.class); + + public boolean isVerification(HttpServletRequest request) { + + // step 1. request header에서 토큰을 가져온다. + String jwtToken = EgovStringUtil.isNullToString(request.getHeader(Constants.JwtToken.HEADER_NAME.getCode())); + + return isVerification(jwtToken); + } + + public boolean isVerification(String jwtToken) { + // step 1. request header에서 토큰을 가져온다. + + // step 2.비교를 위해 loginVO를 가져옴 + LoginVO loginVO = (LoginVO) EgovUserDetailsHelper.getAuthenticatedUser(); + + // step 3. 토큰에 내용이 있는지 확인 & 토큰 기간이 자났는지를 확인해서 username값을 가져옴 + // Exception 핸들링 추가처리 + String username = null; + + try { + username = jwtTokenUtil.getUsernameFromToken(jwtToken); + } catch (IllegalArgumentException e) { + log.debug("Unable to get JWT Token"); + } catch (ExpiredJwtException e) { + log.debug("JWT Token has expired"); + } catch (MalformedJwtException e) { + log.debug("JWT strings must contain exactly 2 period characters"); + } catch (UnsupportedJwtException e) { + log.debug("not support JWT token."); + } + + // step 4. 가져온 username이랑 2에서 가져온 loginVO랑 비교해서 같은지 체크 & 이 과정에서 한번 더 기간 체크를 한다. + if (username == null || !(jwtTokenUtil.validateToken(jwtToken, loginVO))) { + log.debug("jwtToken not validate"); + return false; + } + return true; + } +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/jwt/config/package-info.java b/mens-core/src/main/java/egovframework/com/cmm/jwt/config/package-info.java new file mode 100644 index 0000000..54974ff --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/jwt/config/package-info.java @@ -0,0 +1,10 @@ +/** + * egov framework java web token package classes + *

    + * + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package egovframework.com.cmm.jwt.config; diff --git a/mens-core/src/main/java/egovframework/com/cmm/model/EgovFormBasedFileVo.java b/mens-core/src/main/java/egovframework/com/cmm/model/EgovFormBasedFileVo.java new file mode 100644 index 0000000..6d6ab4f --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/model/EgovFormBasedFileVo.java @@ -0,0 +1,104 @@ +package egovframework.com.cmm.model; + +import java.io.Serializable; + +/** + * @Class Name : EgovFormBasedFileVo.java + * @Description : Form-based File Upload VO + * @Modification Information + * + * 수정일 수정자 수정내용 + * ------- -------- --------------------------- + * 2009.08.26 한성곤 최초 생성 + * + * @author 공통컴포넌트 개발팀 한성곤 + * @since 2009.08.26 + * @version 1.0 + * @see + * + * Copyright (C) 2008 by MOPAS All right reserved. + */ +@SuppressWarnings("serial") +public class EgovFormBasedFileVo implements Serializable { + /** 파일명 */ + private String fileName = ""; + /** ContextType */ + private String contentType = ""; + /** 하위 디렉토리 지정 */ + private String serverSubPath = ""; + /** 물리적 파일명 */ + private String physicalName = ""; + /** 파일 사이즈 */ + private long size = 0L; + + /** + * fileName attribute를 리턴한다. + * @return the fileName + */ + public String getFileName() { + return fileName; + } + /** + * fileName attribute 값을 설정한다. + * @param fileName the fileName to set + */ + public void setFileName(String fileName) { + this.fileName = fileName; + } + /** + * contentType attribute를 리턴한다. + * @return the contentType + */ + public String getContentType() { + return contentType; + } + /** + * contentType attribute 값을 설정한다. + * @param contentType the contentType to set + */ + public void setContentType(String contentType) { + this.contentType = contentType; + } + /** + * serverSubPath attribute를 리턴한다. + * @return the serverSubPath + */ + public String getServerSubPath() { + return serverSubPath; + } + /** + * serverSubPath attribute 값을 설정한다. + * @param serverSubPath the serverSubPath to set + */ + public void setServerSubPath(String serverSubPath) { + this.serverSubPath = serverSubPath; + } + /** + * physicalName attribute를 리턴한다. + * @return the physicalName + */ + public String getPhysicalName() { + return physicalName; + } + /** + * physicalName attribute 값을 설정한다. + * @param physicalName the physicalName to set + */ + public void setPhysicalName(String physicalName) { + this.physicalName = physicalName; + } + /** + * size attribute를 리턴한다. + * @return the size + */ + public long getSize() { + return size; + } + /** + * size attribute 값을 설정한다. + * @param size the size to set + */ + public void setSize(long size) { + this.size = size; + } +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/model/EgovFormBasedUUID.java b/mens-core/src/main/java/egovframework/com/cmm/model/EgovFormBasedUUID.java new file mode 100644 index 0000000..b1a8625 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/model/EgovFormBasedUUID.java @@ -0,0 +1,525 @@ +package egovframework.com.cmm.model; + +import java.io.Serializable; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +/** + * + * A class that represents an immutable universally unique identifier (UUID). A + * UUID represents a 128-bit value. + * + *

    + * There exist different variants of these global identifiers. The methods of + * this class are for manipulating the Leach-Salz variant, although the + * constructors allow the creation of any variant of UUID (described below). + * + *

    + * The layout of a variant 2 (Leach-Salz) UUID is as follows: + * + * The most significant long consists of the following unsigned fields: + * + *

    + *   0xFFFFFFFF00000000 time_low
    + *   0x00000000FFFF0000 time_mid
    + *   0x000000000000F000 version
    + *   0x0000000000000FFF time_hi
    + * 
    + * + * The least significant long consists of the following unsigned fields: + * + *
    + *   0xC000000000000000 variant
    + *   0x3FFF000000000000 clock_seq
    + *   0x0000FFFFFFFFFFFF node
    + * 
    + * + *

    + * The variant field contains a value which identifies the layout of the + * UUID. The bit layout described above is valid only for a + * UUID with a variant value of 2, which indicates the Leach-Salz + * variant. + * + *

    + * The version field holds a value that describes the type of this UUID. + * There are four different basic types of UUIDs: time-based, DCE security, + * name-based, and randomly generated UUIDs. These types have a version value of + * 1, 2, 3 and 4, respectively. + * + *

    + * For more information including algorithms used to create UUIDs, + * see the Internet-Draft UUIDs + * and GUIDs or the standards body definition at ISO/IEC 11578:1996. + * + * @version 1.14, 07/12/04 + * @since 1.5 + */ +@SuppressWarnings("serial") +public class EgovFormBasedUUID implements Serializable { + /* + * The most significant 64 bits of this UUID. + * + * @serial + */ + private final long mostSigBits; + + /* + * The least significant 64 bits of this UUID. + * + * @serial + */ + private final long leastSigBits; + + /* + * The version number associated with this UUID. Computed on demand. + */ + private transient int version = -1; + + /* + * The variant number associated with this UUID. Computed on demand. + */ + private transient int variant = -1; + + /* + * The timestamp associated with this UUID. Computed on demand. + */ + private transient volatile long timestamp = -1; + + /* + * The clock sequence associated with this UUID. Computed on demand. + */ + private transient int sequence = -1; + + /* + * The node number associated with this UUID. Computed on demand. + */ + private transient long node = -1; + + /* + * The hashcode of this UUID. Computed on demand. + */ + private transient int hashCode = -1; + + /* + * The random number generator used by this class to create random based + * UUIDs. + */ + private static SecureRandom numberGenerator = null; + + private static final String UNSUPPORTED_MSG = "Not a time-based UUID"; + + private static SecureRandom makeSecureRandom() { + SecureRandom ng = numberGenerator; + if (ng == null) { + numberGenerator = ng = new SecureRandom(); + } + return ng; + } + + // Constructors and Factories + + /* + * Private constructor which uses a byte array to construct the new UUID. + */ + private EgovFormBasedUUID(byte[] data) { + long msb = 0; + long lsb = 0; + for (int i = 0; i < 8; i++) + msb = (msb << 8) | (data[i] & 0xff); + for (int i = 8; i < 16; i++) + lsb = (lsb << 8) | (data[i] & 0xff); + this.mostSigBits = msb; + this.leastSigBits = lsb; + } + + /** + * Constructs a new UUID using the specified data. + * mostSigBits is used for the most significant 64 bits of the + * UUID and leastSigBits becomes the least significant + * 64 bits of the UUID. + * + * @param mostSigBits + * @param leastSigBits + */ + public EgovFormBasedUUID(long mostSigBits, long leastSigBits) { + this.mostSigBits = mostSigBits; + this.leastSigBits = leastSigBits; + } + + /** + * Static factory to retrieve a type 4 (pseudo randomly generated) UUID. + * + * The UUID is generated using a cryptographically strong + * pseudo random number generator. + * + * @return a randomly generated UUID. + */ + public static EgovFormBasedUUID randomUUID() { + SecureRandom ng = makeSecureRandom(); + + byte[] randomBytes = new byte[16]; + ng.nextBytes(randomBytes); + randomBytes[6] &= 0x0f; /* clear version */ + randomBytes[6] |= 0x40; /* set to version 4 */ + randomBytes[8] &= 0x3f; /* clear variant */ + randomBytes[8] |= 0x80; /* set to IETF variant */ + + return new EgovFormBasedUUID(randomBytes); + } + + /** + * Static factory to retrieve a type 3 (name based) UUID based on + * the specified byte array. + * + * @param name + * a byte array to be used to construct a UUID. + * @return a UUID generated from the specified array. + */ + public static EgovFormBasedUUID nameUUIDFromBytes(byte[] name) { + MessageDigest md; + try { + // 2011.10.10 보안점검 후속조치 암호화 알고리즘 변경(MD5 -> SHA-256) + md = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException nsae) { + throw new InternalError("SHA-256 not supported"); + } + // 2011.10.10 보안점검 후속조치 + if (md == null) { + throw new RuntimeException("MessageDigest is null!!"); + } + // 2014.09.20 보안점검 후속 조치 + // Random 방식의 salt 추가 + SecureRandom ng = makeSecureRandom(); + byte[] randomBytes = new byte[16]; + ng.nextBytes(randomBytes); + + md.reset(); + md.update(randomBytes); + byte[] sha = md.digest(name); + + + byte[] md5Bytes = new byte[8]; + System.arraycopy(sha, 0, md5Bytes, 0, 8); + //2011.10.10 보안점검 후속조치 끝 + + md5Bytes[6] &= 0x0f; /* clear version */ + md5Bytes[6] |= 0x30; /* set to version 3 */ + md5Bytes[8] &= 0x3f; /* clear variant */ + md5Bytes[8] |= 0x80; /* set to IETF variant */ + + return new EgovFormBasedUUID(md5Bytes); + } + + /** + * Creates a UUID from the string standard representation as + * described in the {@link #toString} method. + * + * @param name + * a string that specifies a UUID. + * @return a UUID with the specified value. + * @throws IllegalArgumentException + * if name does not conform to the string representation as + * described in {@link #toString}. + */ + public static EgovFormBasedUUID fromString(String name) { + String[] components = name.split("-"); + if (components.length != 5) + throw new IllegalArgumentException("Invalid UUID string: " + name); + for (int i = 0; i < 5; i++) + components[i] = "0x" + components[i]; + + long mostSigBits = Long.decode(components[0]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[1]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[2]).longValue(); + + long leastSigBits = Long.decode(components[3]).longValue(); + leastSigBits <<= 48; + leastSigBits |= Long.decode(components[4]).longValue(); + + return new EgovFormBasedUUID(mostSigBits, leastSigBits); + } + + // Field Accessor Methods + + /** + * Returns the least significant 64 bits of this UUID's 128 bit value. + * + * @return the least significant 64 bits of this UUID's 128 bit value. + */ + public long getLeastSignificantBits() { + return leastSigBits; + } + + /** + * Returns the most significant 64 bits of this UUID's 128 bit value. + * + * @return the most significant 64 bits of this UUID's 128 bit value. + */ + public long getMostSignificantBits() { + return mostSigBits; + } + + /** + * The version number associated with this UUID. The version + * number describes how this UUID was generated. + * + * The version number has the following meaning: + *

    + *

      + *
    • 1 Time-based UUID + *
    • 2 DCE security UUID + *
    • 3 Name-based UUID + *
    • 4 Randomly generated UUID + *
    + * + * @return the version number of this UUID. + */ + public int version() { + if (version < 0) { + // Version is bits masked by 0x000000000000F000 in MS long + version = (int) ((mostSigBits >> 12) & 0x0f); + } + return version; + } + + /** + * The variant number associated with this UUID. The variant + * number describes the layout of the UUID. + * + * The variant number has the following meaning: + *

    + *

      + *
    • 0 Reserved for NCS backward compatibility + *
    • 2 The Leach-Salz variant (used by this class) + *
    • 6 Reserved, Microsoft Corporation backward compatibility + *
    • 7 Reserved for future definition + *
    + * + * @return the variant number of this UUID. + */ + public int variant() { + if (variant < 0) { + // This field is composed of a varying number of bits + if ((leastSigBits >>> 63) == 0) { + variant = 0; + } else if ((leastSigBits >>> 62) == 2) { + variant = 2; + } else { + variant = (int) (leastSigBits >>> 61); + } + } + return variant; + } + + /** + * The timestamp value associated with this UUID. + * + *

    + * The 60 bit timestamp value is constructed from the time_low, time_mid, + * and time_hi fields of this UUID. The resulting timestamp is + * measured in 100-nanosecond units since midnight, October 15, 1582 UTC. + *

    + * + * The timestamp value is only meaningful in a time-based UUID, which has + * version type 1. If this UUID is not a time-based UUID then + * this method throws UnsupportedOperationException. + * + * @throws UnsupportedOperationException + * if this UUID is not a version 1 UUID. + */ + public long timestamp() { + if (version() != 1) { + throw new UnsupportedOperationException(UNSUPPORTED_MSG); + } + long result = timestamp; + if (result < 0) { + result = (mostSigBits & 0x0000000000000FFFL) << 48; + result |= ((mostSigBits >> 16) & 0xFFFFL) << 32; + result |= mostSigBits >>> 32; + timestamp = result; + } + return result; + } + + /** + * The clock sequence value associated with this UUID. + * + *

    + * The 14 bit clock sequence value is constructed from the clock sequence + * field of this UUID. The clock sequence field is used to guarantee + * temporal uniqueness in a time-based UUID. + *

    + * + * The clockSequence value is only meaningful in a time-based UUID, which + * has version type 1. If this UUID is not a time-based UUID then this + * method throws UnsupportedOperationException. + * + * @return the clock sequence of this UUID. + * @throws UnsupportedOperationException + * if this UUID is not a version 1 UUID. + */ + public int clockSequence() { + if (version() != 1) { + throw new UnsupportedOperationException(UNSUPPORTED_MSG); + } + if (sequence < 0) { + sequence = (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48); + } + return sequence; + } + + /** + * The node value associated with this UUID. + * + *

    + * The 48 bit node value is constructed from the node field of this UUID. + * This field is intended to hold the IEEE 802 address of the machine that + * generated this UUID to guarantee spatial uniqueness. + *

    + * + * The node value is only meaningful in a time-based UUID, which has version + * type 1. If this UUID is not a time-based UUID then this method throws + * UnsupportedOperationException. + * + * @return the node value of this UUID. + * @throws UnsupportedOperationException + * if this UUID is not a version 1 UUID. + */ + public long node() { + if (version() != 1) { + throw new UnsupportedOperationException(UNSUPPORTED_MSG); + } + if (node < 0) { + node = leastSigBits & 0x0000FFFFFFFFFFFFL; + } + return node; + } + + // Object Inherited Methods + + /** + * Returns a String object representing this + * UUID. + * + *

    + * The UUID string representation is as described by this BNF : + * + *

    +     *    UUID                   = <time_low> "-" <time_mid> "-"
    +     *                             <time_high_and_version> "-"
    +     *                             <variant_and_sequence> "-"
    +     *                             <node>
    +     *    time_low               = 4*<hexOctet>
    +     *    time_mid               = 2*<hexOctet>
    +     *    time_high_and_version  = 2*<hexOctet>
    +     *    variant_and_sequence   = 2*<hexOctet>
    +     *    node                   = 6*<hexOctet>
    +     *    hexOctet               = <hexDigit><hexDigit>
    +     *    hexDigit               =
    +     *          "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
    +     *          | "a" | "b" | "c" | "d" | "e" | "f"
    +     *          | "A" | "B" | "C" | "D" | "E" | "F"
    +     * 
    + * + * @return a string representation of this UUID. + */ + @Override + public String toString() { + return (digits(mostSigBits >> 32, 8) + "-" + + digits(mostSigBits >> 16, 4) + "-" + digits(mostSigBits, 4) + + "-" + digits(leastSigBits >> 48, 4) + "-" + digits( + leastSigBits, 12)); + } + + /** Returns val represented by the specified number of hex digits. */ + private static String digits(long val, int digits) { + long hi = 1L << (digits * 4); + return Long.toHexString(hi | (val & (hi - 1))).substring(1); + } + + /** + * Returns a hash code for this UUID. + * + * @return a hash code value for this UUID. + */ + @Override + public int hashCode() { + if (hashCode == -1) { + hashCode = (int) ((mostSigBits >> 32) ^ mostSigBits + ^ (leastSigBits >> 32) ^ leastSigBits); + } + return hashCode; + } + + /** + * Compares this object to the specified object. The result is true + * if and only if the argument is not null, is a UUID + * object, has the same variant, and contains the same value, bit for bit, + * as this UUID. + * + * @param obj + * the object to compare with. + * @return true if the objects are the same; + * false otherwise. + */ + @Override + public boolean equals(Object obj) { + // 보안 취약점 점검 지적사항 반영 시작 + if (obj == null) + return false; + // 보안 취약점 점검 지적사항 반영 시작 끝 + if (!(obj instanceof EgovFormBasedUUID)) + return false; + if (((EgovFormBasedUUID) obj).variant() != this.variant()) + return false; + EgovFormBasedUUID id = (EgovFormBasedUUID) obj; + return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits); + } + + // Comparison Operations + + /** + * Compares this UUID with the specified UUID. + * + *

    + * The first of two UUIDs follows the second if the most significant field + * in which the UUIDs differ is greater for the first UUID. + * + * @param val + * UUID to which this UUID is to be + * compared. + * @return -1, 0 or 1 as this UUID is less than, equal to, or + * greater than val. + */ + public int compareTo(EgovFormBasedUUID val) { + // The ordering is intentionally set up so that the UUIDs + // can simply be numerically compared as two numbers + return (this.mostSigBits < val.mostSigBits ? -1 + : (this.mostSigBits > val.mostSigBits ? 1 + : (Long.compare(this.leastSigBits, val.leastSigBits)))); + } + + /** + * Reconstitute the UUID instance from a stream (that is, + * deserialize it). This is necessary to set the transient fields to their + * correct uninitialized value so they will be recomputed on demand. + */ + private void readObject(java.io.ObjectInputStream in) + throws java.io.IOException, ClassNotFoundException { + + in.defaultReadObject(); + + // Set "cached computation" fields to their initial values + version = -1; + variant = -1; + timestamp = -1; + sequence = -1; + node = -1; + hashCode = -1; + } +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/model/IncludedCompInfoVO.java b/mens-core/src/main/java/egovframework/com/cmm/model/IncludedCompInfoVO.java new file mode 100644 index 0000000..5eadf30 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/model/IncludedCompInfoVO.java @@ -0,0 +1,50 @@ +package egovframework.com.cmm.model; + +/** + * IncludedInfo annotation을 바탕으로 화면에 표시할 정보를 구성하기 위한 VO 클래스 + * @author 공통컴포넌트 정진오 + * @since 2011.08.26 + * @version 2.0.0 + * @see + * + *

    + * << 개정이력(Modification Information) >>
    + *   
    + *  수정일		수정자		수정내용
    + *  -------    	--------    ---------------------------
    + *  2011.08.26	정진오 		최초 생성
    + *
    + * 
    + */ +public class IncludedCompInfoVO { + + private String name; + private String listUrl; + private int order; + private int gid; + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getListUrl() { + return listUrl; + } + public void setListUrl(String listUrl) { + this.listUrl = listUrl; + } + public int getOrder() { + return order; + } + public void setOrder(int order) { + this.order = order; + } + public int getGid() { + return gid; + } + public void setGid(int gid) { + this.gid = gid; + } +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/model/LoginVO.java b/mens-core/src/main/java/egovframework/com/cmm/model/LoginVO.java new file mode 100644 index 0000000..a33e088 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/model/LoginVO.java @@ -0,0 +1,245 @@ +package egovframework.com.cmm.model; + +import java.io.Serializable; + +/** + * @Class Name : LoginVO.java + * @Description : Login VO class + * @Modification Information + * @ + * @ 수정일 수정자 수정내용 + * @ ------- -------- --------------------------- + * @ 2009.03.03 박지욱 최초 생성 + * + * @author 공통서비스 개발팀 박지욱 + * @since 2009.03.03 + * @version 1.0 + * @see + * + */ +public class LoginVO implements Serializable{ + + /** 아이디 */ + private String id; + /** 이름 */ + private String name; + /** 주민등록번호 */ + private String ihidNum; + /** 이메일주소 */ + private String email; + /** 비밀번호 */ + private String password; + /** 비밀번호 힌트 */ + private String passwordHint; + /** 비밀번호 정답 */ + private String passwordCnsr; + /** 사용자구분 */ + private String userSe; + /** 조직(부서)ID */ + private String orgnztId; + /** 조직(부서)명 */ + private String orgnztNm; + /** 고유아이디 */ + private String uniqId; + /** 로그인 후 이동할 페이지 */ + private String url; + /** 사용자 IP정보 */ + private String ip; + /** GPKI인증 DN */ + private String dn; + /** + * id attribute 를 리턴한다. + * @return String + */ + public String getId() { + return id; + } + /** + * id attribute 값을 설정한다. + * @param id String + */ + public void setId(String id) { + this.id = id; + } + /** + * name attribute 를 리턴한다. + * @return String + */ + public String getName() { + return name; + } + /** + * name attribute 값을 설정한다. + * @param name String + */ + public void setName(String name) { + this.name = name; + } + /** + * ihidNum attribute 를 리턴한다. + * @return String + */ + public String getIhidNum() { + return ihidNum; + } + /** + * ihidNum attribute 값을 설정한다. + * @param ihidNum String + */ + public void setIhidNum(String ihidNum) { + this.ihidNum = ihidNum; + } + /** + * email attribute 를 리턴한다. + * @return String + */ + public String getEmail() { + return email; + } + /** + * email attribute 값을 설정한다. + * @param email String + */ + public void setEmail(String email) { + this.email = email; + } + /** + * password attribute 를 리턴한다. + * @return String + */ + public String getPassword() { + return password; + } + /** + * password attribute 값을 설정한다. + * @param password String + */ + public void setPassword(String password) { + this.password = password; + } + /** + * passwordHint attribute 를 리턴한다. + * @return String + */ + public String getPasswordHint() { + return passwordHint; + } + /** + * passwordHint attribute 값을 설정한다. + * @param passwordHint String + */ + public void setPasswordHint(String passwordHint) { + this.passwordHint = passwordHint; + } + /** + * passwordCnsr attribute 를 리턴한다. + * @return String + */ + public String getPasswordCnsr() { + return passwordCnsr; + } + /** + * passwordCnsr attribute 값을 설정한다. + * @param passwordCnsr String + */ + public void setPasswordCnsr(String passwordCnsr) { + this.passwordCnsr = passwordCnsr; + } + /** + * userSe attribute 를 리턴한다. + * @return String + */ + public String getUserSe() { + return userSe; + } + /** + * userSe attribute 값을 설정한다. + * @param userSe String + */ + public void setUserSe(String userSe) { + this.userSe = userSe; + } + /** + * orgnztId attribute 를 리턴한다. + * @return String + */ + public String getOrgnztId() { + return orgnztId; + } + /** + * orgnztId attribute 값을 설정한다. + * @param orgnztId String + */ + public void setOrgnztId(String orgnztId) { + this.orgnztId = orgnztId; + } + /** + * uniqId attribute 를 리턴한다. + * @return String + */ + public String getUniqId() { + return uniqId; + } + /** + * uniqId attribute 값을 설정한다. + * @param uniqId String + */ + public void setUniqId(String uniqId) { + this.uniqId = uniqId; + } + /** + * url attribute 를 리턴한다. + * @return String + */ + public String getUrl() { + return url; + } + /** + * url attribute 값을 설정한다. + * @param url String + */ + public void setUrl(String url) { + this.url = url; + } + /** + * ip attribute 를 리턴한다. + * @return String + */ + public String getIp() { + return ip; + } + /** + * ip attribute 값을 설정한다. + * @param ip String + */ + public void setIp(String ip) { + this.ip = ip; + } + /** + * dn attribute 를 리턴한다. + * @return String + */ + public String getDn() { + return dn; + } + /** + * dn attribute 값을 설정한다. + * @param dn String + */ + public void setDn(String dn) { + this.dn = dn; + } + /** + * @return the orgnztNm + */ + public String getOrgnztNm() { + return orgnztNm; + } + /** + * @param orgnztNm the orgnztNm to set + */ + public void setOrgnztNm(String orgnztNm) { + this.orgnztNm = orgnztNm; + } + +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/model/SessionVO.java b/mens-core/src/main/java/egovframework/com/cmm/model/SessionVO.java new file mode 100644 index 0000000..1ce62b8 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/model/SessionVO.java @@ -0,0 +1,118 @@ +package egovframework.com.cmm.model; + +import java.io.Serializable; + +/** + * 세션 VO 클래스 + * @author 공통서비스 개발팀 박지욱 + * @since 2009.03.06 + * @version 1.0 + * @see + * + *
    + * << 개정이력(Modification Information) >>
    + *
    + *   수정일      수정자          수정내용
    + *  -------    --------    ---------------------------
    + *  2009.03.06  박지욱          최초 생성
    + *
    + *  
    + */ +public class SessionVO implements Serializable { + /** 아이디 */ + private String sUserId; + /** 이름 */ + private String sUserNm; + /** 이메일 */ + private String sEmail; + /** 사용자구분 */ + private String sUserSe; + /** 조직(부서)ID */ + private String orgnztId; + /** 고유아이디 */ + private String uniqId; + /** + * sUserId attribute 를 리턴한다. + * @return String + */ + public String getSUserId() { + return sUserId; + } + /** + * sUserId attribute 값을 설정한다. + * @param sUserId String + */ + public void setSUserId(String userId) { + sUserId = userId; + } + /** + * sUserNm attribute 를 리턴한다. + * @return String + */ + public String getSUserNm() { + return sUserNm; + } + /** + * sUserNm attribute 값을 설정한다. + * @param sUserNm String + */ + public void setSUserNm(String userNm) { + sUserNm = userNm; + } + /** + * sEmail attribute 를 리턴한다. + * @return String + */ + public String getSEmail() { + return sEmail; + } + /** + * sEmail attribute 값을 설정한다. + * @param sEmail String + */ + public void setSEmail(String email) { + sEmail = email; + } + /** + * sUserSe attribute 를 리턴한다. + * @return String + */ + public String getSUserSe() { + return sUserSe; + } + /** + * sUserSe attribute 값을 설정한다. + * @param sUserSe String + */ + public void setSUserSe(String userSe) { + sUserSe = userSe; + } + /** + * orgnztId attribute 를 리턴한다. + * @return String + */ + public String getOrgnztId() { + return orgnztId; + } + /** + * orgnztId attribute 값을 설정한다. + * @param orgnztId String + */ + public void setOrgnztId(String orgnztId) { + this.orgnztId = orgnztId; + } + /** + * uniqId attribute 를 리턴한다. + * @return String + */ + public String getUniqId() { + return uniqId; + } + /** + * uniqId attribute 값을 설정한다. + * @param uniqId String + */ + public void setUniqId(String uniqId) { + this.uniqId = uniqId; + } +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/model/package-info.java b/mens-core/src/main/java/egovframework/com/cmm/model/package-info.java new file mode 100644 index 0000000..46c503e --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/model/package-info.java @@ -0,0 +1,10 @@ +/** + * egov framework model package classes + *

    + * + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package egovframework.com.cmm.model; diff --git a/mens-core/src/main/java/egovframework/com/cmm/package-info.java b/mens-core/src/main/java/egovframework/com/cmm/package-info.java new file mode 100644 index 0000000..8aa54eb --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/package-info.java @@ -0,0 +1,10 @@ +/** + * egov framework package classes + *

    + * + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package egovframework.com.cmm; diff --git a/mens-core/src/main/java/egovframework/com/cmm/util/EgovDateUtil.java b/mens-core/src/main/java/egovframework/com/cmm/util/EgovDateUtil.java new file mode 100644 index 0000000..f87fbe3 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/util/EgovDateUtil.java @@ -0,0 +1,908 @@ +package egovframework.com.cmm.util; + +import java.security.SecureRandom; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.ibm.icu.util.ChineseCalendar; + +/** + * + * Date 에 대한 Util 클래스 + * @author 공통서비스 개발팀 이중호 + * @since 2009.02.01 + * @version 1.0 + * @see + * + *
    + * << 개정이력(Modification Information) >>
    + *
    + *   수정일      수정자           수정내용
    + *  -------    --------    ---------------------------
    + *   2009.02.01  이중호          최초 생성
    + *   2011.08.31  JJY            경량환경 템플릿 커스터마이징버전 생성
    + *
    + * 
    + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class EgovDateUtil { + + private static final Logger LOGGER = LoggerFactory.getLogger(EgovDateUtil.class); + private static final SecureRandom r = new SecureRandom(); + private static final String YMD_FMT = "yyyyMMdd"; + private static final String INVALID_DATE_MSG = "Invalid date format: "; + + /** + *

    yyyyMMdd 혹은 yyyy-MM-dd 형식의 날짜 문자열을 입력 받아 년, 월, 일을 + * 증감한다. 년, 월, 일은 가감할 수를 의미하며, 음수를 입력할 경우 감한다.

    + * + *
    +	 * DateUtil.addYearMonthDay("19810828", 0, 0, 19)  = "19810916"
    +	 * DateUtil.addYearMonthDay("20060228", 0, 0, -10) = "20060218"
    +	 * DateUtil.addYearMonthDay("20060228", 0, 0, 10)  = "20060310"
    +	 * DateUtil.addYearMonthDay("20060228", 0, 0, 32)  = "20060401"
    +	 * DateUtil.addYearMonthDay("20050331", 0, -1, 0)  = "20050228"
    +	 * DateUtil.addYearMonthDay("20050301", 0, 2, 30)  = "20050531"
    +	 * DateUtil.addYearMonthDay("20050301", 1, 2, 30)  = "20060531"
    +	 * DateUtil.addYearMonthDay("20040301", 2, 0, 0)   = "20060301"
    +	 * DateUtil.addYearMonthDay("20040229", 2, 0, 0)   = "20060228"
    +	 * DateUtil.addYearMonthDay("20040229", 2, 0, 1)   = "20060301"
    +	 * 
    + * + * @param sDate 날짜 문자열(yyyyMMdd, yyyy-MM-dd의 형식) + * @param year 가감할 년. 0이 입력될 경우 가감이 없다 + * @param month 가감할 월. 0이 입력될 경우 가감이 없다 + * @param day 가감할 일. 0이 입력될 경우 가감이 없다 + * @return yyyyMMdd 형식의 날짜 문자열 + * @throws IllegalArgumentException 날짜 포맷이 정해진 바와 다를 경우. + * 입력 값이 null인 경우. + */ + public static String addYearMonthDay(String sDate, int year, int month, int day) { + + String dateStr = validChkDate(sDate); + + Calendar cal = Calendar.getInstance(); + SimpleDateFormat sdf = new SimpleDateFormat(YMD_FMT, Locale.getDefault()); + try { + cal.setTime(sdf.parse(dateStr)); + } catch (ParseException e) { + throw new IllegalArgumentException(INVALID_DATE_MSG + dateStr); + } + + if (year != 0) + cal.add(Calendar.YEAR, year); + if (month != 0) + cal.add(Calendar.MONTH, month); + if (day != 0) + cal.add(Calendar.DATE, day); + return sdf.format(cal.getTime()); + } + + public static String addHourMinuteSecond(String sDate, int hour, int minute, int second) { + + String dateStr = validChkDateHMS(sDate); + + Calendar cal = Calendar.getInstance(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()); + try { + cal.setTime(sdf.parse(dateStr)); + } catch (ParseException e) { + throw new IllegalArgumentException(INVALID_DATE_MSG + dateStr); + } + + if (hour != 0) + cal.add(Calendar.HOUR_OF_DAY, hour); + if (minute != 0) + cal.add(Calendar.MINUTE, minute); + if (second != 0) + cal.add(Calendar.SECOND, second); + return sdf.format(cal.getTime()); + } + + /** + *

    yyyyMMdd 혹은 yyyy-MM-dd 형식의 날짜 문자열을 입력 받아 년을 + * 증감한다. year는 가감할 수를 의미하며, 음수를 입력할 경우 감한다.

    + * + *
    +	 * DateUtil.addYear("20000201", 62)  = "20620201"
    +	 * DateUtil.addYear("20620201", -62) = "20000201"
    +	 * DateUtil.addYear("20040229", 2)   = "20060228"
    +	 * DateUtil.addYear("20060228", -2)  = "20040228"
    +	 * DateUtil.addYear("19000101", 200) = "21000101"
    +	 * 
    + * + * @param dateStr 날짜 문자열(yyyyMMdd, yyyy-MM-dd의 형식) + * @param year 가감할 년. 0이 입력될 경우 가감이 없다 + * @return yyyyMMdd 형식의 날짜 문자열 + * @throws IllegalArgumentException 날짜 포맷이 정해진 바와 다를 경우. + * 입력 값이 null인 경우. + */ + public static String addYear(String dateStr, int year) { + return addYearMonthDay(dateStr, year, 0, 0); + } + + /** + *

    yyyyMMdd 혹은 yyyy-MM-dd 형식의 날짜 문자열을 입력 받아 월을 + * 증감한다. month는 가감할 수를 의미하며, 음수를 입력할 경우 감한다.

    + * + *
    +	 * DateUtil.addMonth("20010201", 12)  = "20020201"
    +	 * DateUtil.addMonth("19800229", 12)  = "19810228"
    +	 * DateUtil.addMonth("20040229", 12)  = "20050228"
    +	 * DateUtil.addMonth("20050228", -12) = "20040228"
    +	 * DateUtil.addMonth("20060131", 1)   = "20060228"
    +	 * DateUtil.addMonth("20060228", -1)  = "20060128"
    +	 * 
    + * + * @param dateStr 날짜 문자열(yyyyMMdd, yyyy-MM-dd의 형식) + * @param month 가감할 월. 0이 입력될 경우 가감이 없다 + * @return yyyyMMdd 형식의 날짜 문자열 + * @throws IllegalArgumentException 날짜 포맷이 정해진 바와 다를 경우. + * 입력 값이 null인 경우. + */ + public static String addMonth(String dateStr, int month) { + return addYearMonthDay(dateStr, 0, month, 0); + } + + /** + *

    yyyyMMdd 혹은 yyyy-MM-dd 형식의 날짜 문자열을 입력 받아 일(day)를 + * 증감한다. day는 가감할 수를 의미하며, 음수를 입력할 경우 감한다. + *

    + * 위에 정의된 addDays 메서드는 사용자가 ParseException을 반드시 처리해야 하는 불편함이 + * 있기 때문에 추가된 메서드이다.

    + * + *
    +	 * DateUtil.addDay("19991201", 62) = "20000201"
    +	 * DateUtil.addDay("20000201", -62) = "19991201"
    +	 * DateUtil.addDay("20050831", 3) = "20050903"
    +	 * DateUtil.addDay("20050831", 3) = "20050903"
    +	 * // 2006년 6월 31일은 실제로 존재하지 않는 날짜이다 -> 20060701로 간주된다
    +	 * DateUtil.addDay("20060631", 1) = "20060702"
    +	 * 
    + * + * @param dateStr 날짜 문자열(yyyyMMdd, yyyy-MM-dd의 형식) + * @param day 가감할 일. 0이 입력될 경우 가감이 없다 + * @return yyyyMMdd 형식의 날짜 문자열 + * @throws IllegalArgumentException 날짜 포맷이 정해진 바와 다를 경우. + * 입력 값이 null인 경우. + */ + public static String addDay(String dateStr, int day) { + return addYearMonthDay(dateStr, 0, 0, day); + } + + + /** + *

    yyyyMMddHHmmss 형식의 날짜 문자열을 입력 받아 분(minute)을 + * 증감한다. minute는 가감할 수를 의미하며, 음수를 입력할 경우 감한다. + *

    + * 위에 정의된 addMinute 메서드는 사용자가 ParseException을 반드시 처리해야 하는 불편함이 + * 있기 때문에 추가된 메서드이다.

    + * + *
    +	 * DateUtil.addDay("20230616100000", 3) = "20230616100300"
    +	 * DateUtil.addDay("20230616100000", -3) = "20230616095700"
    +	 * 
    + * + * @param dateStr 날짜 문자열(yyyyMMddHHmmss의 형식) + * @param minute 가감할 일. 0이 입력될 경우 가감이 없다 + * @return yyyyMMdd 형식의 날짜 문자열 + * @throws IllegalArgumentException 날짜 포맷이 정해진 바와 다를 경우. + * 입력 값이 null인 경우. + */ + public static String addMinute(String dateStr, int minute) { + return addHourMinuteSecond(dateStr, 0, minute, 0); + } + + /** + *

    yyyyMMdd 혹은 yyyy-MM-dd 형식의 날짜 문자열 dateStr1 + * dateStr2 사이의 일 수를 구한다.
    + * dateStr2dateStr1 보다 과거 날짜일 경우에는 + * 음수를 반환한다. 동일한 경우에는 0을 반환한다.

    + * + *
    +	 * DateUtil.getDaysDiff("20060228","20060310") = 10
    +	 * DateUtil.getDaysDiff("20060101","20070101") = 365
    +	 * DateUtil.getDaysDiff("19990228","19990131") = -28
    +	 * DateUtil.getDaysDiff("20060801","20060802") = 1
    +	 * DateUtil.getDaysDiff("20060801","20060801") = 0
    +	 * 
    + * + * @param dateStr1 날짜 문자열(yyyyMMdd, yyyy-MM-dd의 형식) + * @param dateStr2 날짜 문자열(yyyyMMdd, yyyy-MM-dd의 형식) + * @return 일 수 차이. + * @throws IllegalArgumentException 날짜 포맷이 정해진 바와 다를 경우. + * 입력 값이 null인 경우. + */ + public static int getDaysDiff(String sDate1, String sDate2) { + String dateStr1 = validChkDate(sDate1); + String dateStr2 = validChkDate(sDate2); + + if (!checkDate(sDate1) || !checkDate(sDate2)) { + throw new IllegalArgumentException("Invalid date format: args[0]=" + sDate1 + " args[1]=" + sDate2); + } + SimpleDateFormat sdf = new SimpleDateFormat(YMD_FMT, Locale.getDefault()); + + try { + Date date1; + Date date2; + + date1 = sdf.parse(dateStr1); + date2 = sdf.parse(dateStr2); + int days1 = (int) ((date1.getTime() / 3600000) / 24); + int days2 = (int) ((date2.getTime() / 3600000) / 24); + + return days2 - days1; + } catch (ParseException e) { + throw new IllegalArgumentException("Invalid date format: args[0]=" + dateStr1 + " args[1]=" + dateStr2); + } + + } + + /** + *

    yyyyMMdd 혹은 yyyy-MM-dd 형식의 날짜 문자열을 입력 받아 유효한 날짜인지 검사.

    + * + *
    +	 * DateUtil.checkDate("1999-02-35") = false
    +	 * DateUtil.checkDate("2000-13-31") = false
    +	 * DateUtil.checkDate("2006-11-31") = false
    +	 * DateUtil.checkDate("2006-2-28")  = false
    +	 * DateUtil.checkDate("2006-2-8")   = false
    +	 * DateUtil.checkDate("20060228")   = true
    +	 * DateUtil.checkDate("2006-02-28") = true
    +	 * 
    + * + * @param dateStr 날짜 문자열(yyyyMMdd, yyyy-MM-dd의 형식) + * @return 유효한 날짜인지 여부 + */ + public static boolean checkDate(String sDate) { + String dateStr = validChkDate(sDate); + + String year = dateStr.substring(0, 4); + String month = dateStr.substring(4, 6); + String day = dateStr.substring(6); + + return checkDate(year, month, day); + } + + /** + *

    입력한 년, 월, 일이 유효한지 검사.

    + * + * @param year 연도 + * @param month 월 + * @param day 일 + * @return 유효한 날짜인지 여부 + */ + public static boolean checkDate(String year, String month, String day) { + try { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy.MM.dd", Locale.getDefault()); + + Date result = formatter.parse(year + "." + month + "." + day); + String resultStr = formatter.format(result); + return resultStr.equalsIgnoreCase(year + "." + month + "." + day); + } catch (ParseException e) { + return false; + } + } + + /** + * 날짜형태의 String의 날짜 포맷 및 TimeZone을 변경해 주는 메서드 + * + * @param strSource 바꿀 날짜 String + * @param fromDateFormat 기존의 날짜 형태 + * @param toDateFormat 원하는 날짜 형태 + * @param strTimeZone 변경할 TimeZone(""이면 변경 안함) + * @return 소스 String의 날짜 포맷을 변경한 String + */ + public static String convertDate(String strSource, String fromDateFormat, String toDateFormat, String strTimeZone) { + SimpleDateFormat simpledateformat = null; + Date date = null; + String _fromDateFormat = ""; + String _toDateFormat = ""; + + if (EgovStringUtil.isNullToString(strSource).trim().equals("")) { + return ""; + } + if (EgovStringUtil.isNullToString(fromDateFormat).trim().equals("")) + _fromDateFormat = "yyyyMMddHHmmss"; // default값 + if (EgovStringUtil.isNullToString(toDateFormat).trim().equals("")) + _toDateFormat = "yyyy-MM-dd HH:mm:ss"; // default값 + + try { + simpledateformat = new SimpleDateFormat(_fromDateFormat, Locale.getDefault()); + date = simpledateformat.parse(strSource); + if (!EgovStringUtil.isNullToString(strTimeZone).trim().equals("")) { + simpledateformat.setTimeZone(TimeZone.getTimeZone(strTimeZone)); + } + simpledateformat = new SimpleDateFormat(_toDateFormat, Locale.getDefault()); + return simpledateformat.format(date); + + } catch (ParseException exception) { + LOGGER.error("{}", exception.getMessage()); + } + return StringUtils.EMPTY; + + } + + /** + * yyyyMMdd 형식의 날짜문자열을 원하는 캐릭터(ch)로 쪼개 돌려준다
    + *
    +	* ex) 20030405, ch(.) -> 2003.04.05
    +	* ex) 200304, ch(.) -> 2003.04
    +	* ex) 20040101,ch(/) --> 2004/01/01 로 리턴
    +	* 
    + * + * @param sDate yyyyMMdd 형식의 날짜문자열 + * @param ch 구분자 + * @return 변환된 문자열 + */ + public static String formatDate(String sDate, String ch) { + String dateStr = validChkDate(sDate); + + String str = dateStr.trim(); + String yyyy; + String mm; + String dd; + + switch (str.length()){ + case 8: + yyyy = str.substring(0, 4); + if (yyyy.equals("0000")) return ""; + + mm = str.substring(4, 6); + if (mm.equals("00")) return yyyy; + + dd = str.substring(6, 8); + if (dd.equals("00")) return yyyy + ch + mm; + + return yyyy + ch + mm + ch + dd; + + case 6: + yyyy = str.substring(0, 4); + if (yyyy.equals("0000")) return ""; + + mm = str.substring(4, 6); + if (mm.equals("00")) return yyyy; + + return yyyy + ch + mm; + + case 4: + yyyy = str.substring(0, 4); + if (yyyy.equals("0000")) return ""; + return yyyy; + + default: + return ""; + } + } + + /** + * HH24MISS 형식의 시간문자열을 원하는 캐릭터(ch)로 쪼개 돌려준다
    + *
    +	 *     ex) 151241, ch(/) -> 15/12/31
    +	 * 
    + * + * @param sTime HH24MISS 형식의 시간문자열 + * @param ch 구분자 + * @return 변환된 문자열 + */ + public static String formatTime(String sTime, String ch) { + String timeStr = validChkTime(sTime); + return timeStr.substring(0, 2) + ch + timeStr.substring(2, 4) + ch + timeStr.substring(4, 6); + } + + /** + * 연도를 입력 받아 해당 연도 2월의 말일(일수)를 문자열로 반환한다. + * + * @param year + * @return 해당 연도 2월의 말일(일수) + */ + public String leapYear(int year) { + if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) { + return "29"; + } + + return "28"; + } + + /** + *

    입력받은 연도가 윤년인지 아닌지 검사한다.

    + * + *
    +	 * DateUtil.isLeapYear(2004) = false
    +	 * DateUtil.isLeapYear(2005) = true
    +	 * DateUtil.isLeapYear(2006) = true
    +	 * 
    + * + * @param year 연도 + * @return 윤년 여부 + */ + public static boolean isLeapYear(int year) { + return (year % 4 != 0 || year % 100 == 0) && year % 400 != 0; + } + + /** + * 현재(한국기준) 날짜정보를 얻는다.
    + * 표기법은 yyyy-mm-dd
    + * @return String yyyymmdd형태의 현재 한국시간.
    + */ + public static String getToday() { + return getCurrentDate(""); + } + + /** + * 현재(한국기준) 날짜정보를 얻는다.
    + * 표기법은 yyyy-mm-dd
    + * @return String yyyymmdd형태의 현재 한국시간.
    + */ + public static String getCurrentDate(String dateType) { + Calendar aCalendar = Calendar.getInstance(); + + int year = aCalendar.get(Calendar.YEAR); + int month = aCalendar.get(Calendar.MONTH) + 1; + int date = aCalendar.get(Calendar.DATE); + String strDate = Integer.toString(year) + ((month < 10) ? "0" + Integer.toString(month) : Integer.toString(month)) + + ((date < 10) ? "0" + Integer.toString(date) : Integer.toString(date)); + + if (!"".equals(dateType)) + strDate = convertDate(strDate, YMD_FMT, dateType); + + return strDate; + } + + /** + * 날짜형태의 String의 날짜 포맷만을 변경해 주는 메서드 + * @param sDate 날짜 + * @param sTime 시간 + * @param sFormatStr 포멧 스트링 문자열 + * @return 지정한 날짜/시간을 지정한 포맷으로 출력 + * @See Letter Date or Time Component Presentation Examples + G Era designator Text AD + y Year Year 1996; 96 + M Month in year Month July; Jul; 07 + w Week in year Number 27 + W Week in month Number 2 + D Day in year Number 189 + d Day in month Number 10 + F Day of week in month Number 2 + E Day in week Text Tuesday; Tue + a Am/pm marker Text PM + H Hour in day (0-23) Number 0 + k Hour in day (1-24) Number 24 + K Hour in am/pm (0-11) Number 0 + h Hour in am/pm (1-12) Number 12 + m Minute in hour Number 30 + s Second in minute Number 55 + S Millisecond Number 978 + z Time zone General time zone Pacific Standard Time; PST; GMT-08:00 + Z Time zone RFC 822 time zone -0800 + + + + Date and Time Pattern Result + "yyyy.MM.dd G 'at' HH:mm:ss z" 2001.07.04 AD at 12:08:56 PDT + "EEE, MMM d, ''yy" Wed, Jul 4, '01 + "h:mm a" 12:08 PM + "hh 'o''clock' a, zzzz" 12 o'clock PM, Pacific Daylight Time + "K:mm a, z" 0:08 PM, PDT + "yyyyy.MMMMM.dd GGG hh:mm aaa" 02001.July.04 AD 12:08 PM + "EEE, d MMM yyyy HH:mm:ss Z" Wed, 4 Jul 2001 12:08:56 -0700 + "yyMMddHHmmssZ" 010704120856-0700 + + */ + public static String convertDate(String sDate, String sTime, String sFormatStr) { + String dateStr = validChkDate(sDate); + String timeStr = validChkTime(sTime); + + Calendar cal; + cal = Calendar.getInstance(); + + cal.set(Calendar.YEAR, Integer.parseInt(dateStr.substring(0, 4))); + cal.set(Calendar.MONTH, Integer.parseInt(dateStr.substring(4, 6)) - 1); + cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateStr.substring(6, 8))); + cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(timeStr.substring(0, 2))); + cal.set(Calendar.MINUTE, Integer.parseInt(timeStr.substring(2, 4))); + + SimpleDateFormat sdf = new SimpleDateFormat(sFormatStr, Locale.ENGLISH); + + return sdf.format(cal.getTime()); + } + + /** + * 입력받은 일자 사이의 임의의 일자를 반환 + * @param sDate1 시작일자 + * @param sDate2 종료일자 + * @return 임의일자 + */ + public static String getRandomDate(String sDate1, String sDate2) { + String dateStr1 = validChkDate(sDate1); + String dateStr2 = validChkDate(sDate2); + + int sYear; + int sMonth; + int sDay; + int eYear; + int eMonth; + int eDay; + + sYear = Integer.parseInt(dateStr1.substring(0, 4)); + sMonth = Integer.parseInt(dateStr1.substring(4, 6)); + sDay = Integer.parseInt(dateStr1.substring(6, 8)); + + eYear = Integer.parseInt(dateStr2.substring(0, 4)); + eMonth = Integer.parseInt(dateStr2.substring(4, 6)); + eDay = Integer.parseInt(dateStr2.substring(6, 8)); + + GregorianCalendar beginDate = new GregorianCalendar(sYear, sMonth - 1, sDay, 0, 0); + GregorianCalendar endDate = new GregorianCalendar(eYear, eMonth - 1, eDay, 23, 59); + + if (endDate.getTimeInMillis() < beginDate.getTimeInMillis()) { + throw new IllegalArgumentException("Invalid input date : " + sDate1 + "~" + sDate2); + } + + long rand = ((r.nextLong() >>> 1) % (endDate.getTimeInMillis() - beginDate.getTimeInMillis() + 1)) + beginDate.getTimeInMillis(); + + GregorianCalendar cal = new GregorianCalendar(); + SimpleDateFormat calformat = new SimpleDateFormat(YMD_FMT, Locale.ENGLISH); + cal.setTimeInMillis(rand); + return calformat.format(cal.getTime()); + } + + /** + * 입력받은 양력일자를 변환하여 음력일자로 반환 + * @param sDate 양력일자 + * @return 음력일자 + */ + public static Map toLunar(String sDate) { + String dateStr = validChkDate(sDate); + + Map hm = new HashMap<>(); + hm.put("day", ""); + hm.put("leap", "0"); + + if (dateStr.length() != 8) { + return hm; + } + + Calendar cal; + ChineseCalendar lcal; + + cal = Calendar.getInstance(); + lcal = new ChineseCalendar(); + + cal.set(Calendar.YEAR, Integer.parseInt(dateStr.substring(0, 4))); + cal.set(Calendar.MONTH, Integer.parseInt(dateStr.substring(4, 6)) - 1); + cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateStr.substring(6, 8))); + + lcal.setTimeInMillis(cal.getTimeInMillis()); + + String year = String.valueOf(lcal.get(ChineseCalendar.EXTENDED_YEAR) - 2637); + String month = String.valueOf(lcal.get(ChineseCalendar.MONTH) + 1); + String day = String.valueOf(lcal.get(ChineseCalendar.DAY_OF_MONTH)); + String leap = String.valueOf(lcal.get(ChineseCalendar.IS_LEAP_MONTH)); + + String pad4Str = "0000"; + String pad2Str = "00"; + + String retYear = (pad4Str + year).substring(year.length()); + String retMonth = (pad2Str + month).substring(month.length()); + String retDay = (pad2Str + day).substring(day.length()); + + String SDay = retYear + retMonth + retDay; + + hm.put("day", SDay); + hm.put("leap", leap); + + return hm; + } + + /** + * 입력받은 음력일자를 변환하여 양력일자로 반환 + * @param sDate 음력일자 + * @param iLeapMonth 음력윤달여부(IS_LEAP_MONTH) + * @return 양력일자 + */ + public static String toSolar(String sDate, int iLeapMonth) { + String dateStr = validChkDate(sDate); + + Calendar cal; + ChineseCalendar lcal; + + cal = Calendar.getInstance(); + lcal = new ChineseCalendar(); + + lcal.set(ChineseCalendar.EXTENDED_YEAR, Integer.parseInt(dateStr.substring(0, 4)) + 2637); + lcal.set(ChineseCalendar.MONTH, Integer.parseInt(dateStr.substring(4, 6)) - 1); + lcal.set(ChineseCalendar.DAY_OF_MONTH, Integer.parseInt(dateStr.substring(6, 8))); + lcal.set(ChineseCalendar.IS_LEAP_MONTH, iLeapMonth); + + cal.setTimeInMillis(lcal.getTimeInMillis()); + + String year = String.valueOf(cal.get(Calendar.YEAR)); + String month = String.valueOf(cal.get(Calendar.MONTH) + 1); + String day = String.valueOf(cal.get(Calendar.DAY_OF_MONTH)); + + String pad4Str = "0000"; + String pad2Str = "00"; + + String retYear = (pad4Str + year).substring(year.length()); + String retMonth = (pad2Str + month).substring(month.length()); + String retDay = (pad2Str + day).substring(day.length()); + + return retYear + retMonth + retDay; + } + + /** + * 입력받은 요일의 영문명을 국문명의 요일로 반환 + * @param sWeek 영문 요일명 + * @return 국문 요일명 + */ + public static String convertWeek(String sWeek) { + String retStr = null; + + if (sWeek.equals("SUN")) { + retStr = "일요일"; + } else if (sWeek.equals("MON")) { + retStr = "월요일"; + } else if (sWeek.equals("TUE")) { + retStr = "화요일"; + } else if (sWeek.equals("WED")) { + retStr = "수요일"; + } else if (sWeek.equals("THR")) { + retStr = "목요일"; + } else if (sWeek.equals("FRI")) { + retStr = "금요일"; + } else if (sWeek.equals("SAT")) { + retStr = "토요일"; + } + + return retStr; + } + + /** + * 입력일자의 유효 여부를 확인 + * @param sDate 일자 + * @return 유효 여부 + */ + public static boolean validDate(String sDate) { + String dateStr = validChkDate(sDate); + + Calendar cal; + boolean ret = false; + + cal = Calendar.getInstance(); + + cal.set(Calendar.YEAR, Integer.parseInt(dateStr.substring(0, 4))); + cal.set(Calendar.MONTH, Integer.parseInt(dateStr.substring(4, 6)) - 1); + cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateStr.substring(6, 8))); + + String year = String.valueOf(cal.get(Calendar.YEAR)); + String month = String.valueOf(cal.get(Calendar.MONTH) + 1); + String day = String.valueOf(cal.get(Calendar.DAY_OF_MONTH)); + + String pad4Str = "0000"; + String pad2Str = "00"; + + String retYear = (pad4Str + year).substring(year.length()); + String retMonth = (pad2Str + month).substring(month.length()); + String retDay = (pad2Str + day).substring(day.length()); + + String retYMD = retYear + retMonth + retDay; + + if (sDate.equals(retYMD)) { + ret = true; + } + + return ret; + } + + /** + * 입력일자, 요일의 유효 여부를 확인 + * @param sDate 일자 + * @param sWeek 요일 (DAY_OF_WEEK) + * @return 유효 여부 + */ + public static boolean validDate(String sDate, int sWeek) { + String dateStr = validChkDate(sDate); + + Calendar cal = Calendar.getInstance(); + + cal.set(Calendar.YEAR, Integer.parseInt(dateStr.substring(0, 4))); + cal.set(Calendar.MONTH, Integer.parseInt(dateStr.substring(4, 6)) - 1); + cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateStr.substring(6, 8))); + + int Week = cal.get(Calendar.DAY_OF_WEEK); + + if (validDate(sDate)) { + return sWeek == Week; + } + + return false; + } + + /** + * 입력시간의 유효 여부를 확인 + * @param sTime 입력시간 + * @return 유효 여부 + */ + public static boolean validTime(String sTime) { + String timeStr = validChkTime(sTime); + + Calendar cal; + boolean ret = false; + + cal = Calendar.getInstance(); + + cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(timeStr.substring(0, 2))); + cal.set(Calendar.MINUTE, Integer.parseInt(timeStr.substring(2, 4))); + + String HH = String.valueOf(cal.get(Calendar.HOUR_OF_DAY)); + String MM = String.valueOf(cal.get(Calendar.MINUTE)); + + String pad2Str = "00"; + + String retHH = (pad2Str + HH).substring(HH.length()); + String retMM = (pad2Str + MM).substring(MM.length()); + + String retTime = retHH + retMM; + + if (sTime.equals(retTime)) { + ret = true; + } + + return ret; + } + + /** + * 입력된 일자에 연, 월, 일을 가감한 날짜의 요일을 반환 + * @param sDate 날짜 + * @param year 연 + * @param month 월 + * @param day 일 + * @return 계산된 일자의 요일(DAY_OF_WEEK) + */ + public static String addYMDtoWeek(String sDate, int year, int month, int day) { + String dateStr = validChkDate(sDate); + + dateStr = addYearMonthDay(dateStr, year, month, day); + + Calendar cal = Calendar.getInstance(); + SimpleDateFormat sdf = new SimpleDateFormat(YMD_FMT, Locale.ENGLISH); + try { + cal.setTime(sdf.parse(dateStr)); + } catch (ParseException e) { + throw new IllegalArgumentException(INVALID_DATE_MSG + dateStr); + } + + SimpleDateFormat rsdf = new SimpleDateFormat("E", Locale.ENGLISH); + + return rsdf.format(cal.getTime()); + } + + /** + * 입력된 일자에 연, 월, 일, 시간, 분을 가감한 날짜, 시간을 포멧스트링 형식으로 반환 + * @param sDate 날짜 + * @param sTime 시간 + * @param year 연 + * @param month 월 + * @param day 일 + * @param hour 시간 + * @param minute 분 + * @param formatStr 포멧스트링 + * @return String + */ + public static String addYMDtoDayTime(String sDate, String sTime, String formatStr, int year, int month, int day, int hour, int minute) { + String dateStr = validChkDate(sDate); + String timeStr = validChkTime(sTime); + + dateStr = addYearMonthDay(dateStr, year, month, day); + + dateStr = convertDate(dateStr, timeStr, "yyyyMMddHHmm"); + + Calendar cal = Calendar.getInstance(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm", Locale.ENGLISH); + + try { + cal.setTime(sdf.parse(dateStr)); + } catch (ParseException e) { + throw new IllegalArgumentException(INVALID_DATE_MSG + dateStr); + } + + if (hour != 0) { + cal.add(Calendar.HOUR, hour); + } + + if (minute != 0) { + cal.add(Calendar.MINUTE, minute); + } + + SimpleDateFormat rsdf = new SimpleDateFormat(formatStr, Locale.ENGLISH); + + return rsdf.format(cal.getTime()); + } + + /** + * 입력된 일자를 int 형으로 반환 + * @param sDate 일자 + * @return int(일자) + */ + public static int datetoInt(String sDate) { + return Integer.parseInt(convertDate(sDate, "0000", YMD_FMT)); + } + + /** + * 입력된 시간을 int 형으로 반환 + * @param sTime 시간 + * @return int(시간) + */ + public static int timetoInt(String sTime) { + return Integer.parseInt(convertDate("00000101", sTime, "HHmm")); + } + + /** + * 입력된 일자 문자열을 확인하고 8자리로 리턴 + * @param sDate + * @return + */ + public static String validChkDate(String dateStr) { + String _dateStr = dateStr; + + if (dateStr == null || !(dateStr.trim().length() == 8 || dateStr.trim().length() == 10)) { + throw new IllegalArgumentException(INVALID_DATE_MSG + dateStr); + } + if (dateStr.length() == 10) { + _dateStr = EgovStringUtil.removeMinusChar(dateStr); + } + return _dateStr; + } + + /** + * 입력된 일자 문자열을 확인하고 8자리로 리턴 + * @param sDate + * @return + */ + public static String validChkTime(String timeStr) { + String _timeStr = timeStr; + + if (_timeStr == null){ + throw new IllegalArgumentException("Invalid time format: null"); + } + + if (_timeStr.length() == 5) { + _timeStr = EgovStringUtil.remove(_timeStr, ':'); + } + + if(_timeStr.trim().length() != 6) { + throw new IllegalArgumentException("Invalid time format: " + _timeStr); + } + + return _timeStr; + } + + /** + * 입력된 일자 문자열을 확인하고 14자리 체크 + * @param dateStr + * @return + */ + public static String validChkDateHMS(String dateStr) { + String _dateStr = dateStr; + + if (dateStr == null || dateStr.trim().length() < 14) { + throw new IllegalArgumentException(INVALID_DATE_MSG + dateStr); + } + return _dateStr; + } + +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/util/EgovFileScrty.java b/mens-core/src/main/java/egovframework/com/cmm/util/EgovFileScrty.java new file mode 100644 index 0000000..ac1cb05 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/util/EgovFileScrty.java @@ -0,0 +1,236 @@ +package egovframework.com.cmm.util; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.commons.codec.binary.Base64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import egovframework.com.cmm.EgovWebUtil; +import kr.xit.core.exception.BizRuntimeException; + +/** + * Base64인코딩/디코딩 방식을 이용한 데이터를 암호화/복호화하는 Business Interface class + * @author 공통서비스개발팀 박지욱 + * @since 2009.01.19 + * @version 1.0 + * @see + * + *
    + * << 개정이력(Modification Information) >>
    + *
    + *   수정일      수정자           수정내용
    + *  -------    --------    ---------------------------
    + *   2009.01.19  박지욱          최초 생성
    + *   2011.08.31  JJY            경량환경 템플릿 커스터마이징버전 생성
    + *
    + * 
    + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class EgovFileScrty { + // 파일구분자 + static final String FILE_SEPARATOR = System.getProperty("file.separator"); + // 버퍼사이즈 + static final int BUFFER_SIZE = 1024; + + private static final String SHA_256 = "SHA-256"; + + /** + * 파일을 암호화하는 기능 + * + * @param source 암호화할 파일 + * @param target 암호화된 파일 + * @return boolean result 암호화여부 True/False + * @exception Exception + */ + public static boolean encryptFile(String source, String target) throws Exception { + + // 암호화 여부 + boolean result = false; + + String sourceFile = EgovWebUtil.filePathBlackList(source.replace("\\", FILE_SEPARATOR).replace("/", FILE_SEPARATOR)); + String targetFile = EgovWebUtil.filePathBlackList(target.replace("\\", FILE_SEPARATOR).replace("/", FILE_SEPARATOR)); + File srcFile = new File(sourceFile); + + byte[] buffer = new byte[BUFFER_SIZE]; + + if (srcFile.exists() && srcFile.isFile()) { + + try(BufferedInputStream input = new BufferedInputStream(new FileInputStream(srcFile)); + BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(targetFile)); + ) { + + int length; + while ((length = input.read(buffer)) >= 0) { + byte[] data = new byte[length]; + System.arraycopy(buffer, 0, data, 0, length); + output.write(encodeBinary(data).getBytes()); + output.write(System.getProperty("line.separator").getBytes()); + } + + result = true; + } + } + return result; + } + + /** + * 파일을 복호화하는 기능 + * + * @param source 복호화할 파일 + * @param target 복호화된 파일 + * @return boolean result 복호화여부 True/False + * @exception Exception + */ + public static boolean decryptFile(String source, String target) throws Exception { + + // 복호화 여부 + boolean result = false; + + String sourceFile = source.replace("\\", FILE_SEPARATOR).replace("/", FILE_SEPARATOR); + String targetFile = target.replace("\\", FILE_SEPARATOR).replace("/", FILE_SEPARATOR); + File srcFile = new File(sourceFile); + + String line; + if (srcFile.exists() && srcFile.isFile()) { + + try(BufferedReader input = new BufferedReader(new InputStreamReader(new FileInputStream(srcFile))); + BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(targetFile)); + ) { + while ((line = input.readLine()) != null) { + byte[] data = line.getBytes(); + output.write(decodeBinary(new String(data))); + } + + result = true; + } + } + + return result; + } + + /** + * 데이터를 암호화하는 기능 + * + * @param data 암호화할 데이터 + * @return String result 암호화된 데이터 + * @exception Exception + */ + public static String encodeBinary(byte[] data) throws Exception { + if (data == null) { + return ""; + } + + return Arrays.toString(Base64.encodeBase64(data)); + } + + /** + * 데이터를 암호화하는 기능 + * + * @param data 암호화할 데이터 + * @return String result 암호화된 데이터 + * @exception Exception + */ + public static String encode(String data) throws Exception { + return encodeBinary(data.getBytes()); + } + + /** + * 데이터를 복호화하는 기능 + * + * @param data 복호화할 데이터 + * @return String result 복호화된 데이터 + * @exception Exception + */ + public static byte[] decodeBinary(String data) throws Exception { + return Base64.decodeBase64(data.getBytes()); + } + + /** + * 데이터를 복호화하는 기능 + * + * @param data String복호화할 데이터 + * @return String result 복호화된 데이터 + * @exception Exception + */ + public static String decode(String data) throws Exception { + return new String(decodeBinary(data)); + } + + /** + * 비밀번호를 암호화하는 기능(복호화가 되면 안되므로 SHA-256 인코딩 방식 적용) + * + * @param password 암호화될 패스워드 + * @param id salt로 사용될 사용자 ID 지정 + * @return + * @throws Exception + */ + public static String encryptPassword(final String password, final String id) { + + if (password == null) return ""; + + try { + MessageDigest md = MessageDigest.getInstance(SHA_256); + md.reset(); + md.update(id.getBytes()); + + return new String(Base64.encodeBase64( md.digest(password.getBytes()))); + } catch (NoSuchAlgorithmException nse) { + throw BizRuntimeException.create(nse.getMessage()); + } + } + + /** + * 비밀번호를 암호화하는 기능(복호화가 되면 안되므로 SHA-256 인코딩 방식 적용) + * @param data 암호화할 비밀번호 + * @param salt Salt + * @return 암호화된 비밀번호 + * @throws Exception + */ + public static String encryptPassword(final String data, final byte[] salt) { + + if (data == null) return ""; + + try { + MessageDigest md = MessageDigest.getInstance(SHA_256); + md.reset(); + md.update(salt); + + return new String(Base64.encodeBase64( md.digest(data.getBytes()))); + } catch (NoSuchAlgorithmException nse) { + throw BizRuntimeException.create(nse.getMessage()); + } + } + + /** + * 비밀번호를 암호화된 패스워드 검증(salt가 사용된 경우만 적용). + * + * @param data 원 패스워드 + * @param encoded 해쉬처리된 패스워드(Base64 인코딩) + * @return + * @throws Exception + */ + public static boolean checkPassword(String data, String encoded, byte[] salt) { + + try { + MessageDigest md = MessageDigest.getInstance(SHA_256); + md.reset(); + md.update(salt); + + return MessageDigest.isEqual( md.digest(data.getBytes()), Base64.decodeBase64(encoded.getBytes())); + } catch (NoSuchAlgorithmException nse) { + throw BizRuntimeException.create(nse.getMessage()); + } + } +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/util/EgovFileUploadUtil.java b/mens-core/src/main/java/egovframework/com/cmm/util/EgovFileUploadUtil.java new file mode 100644 index 0000000..10f7a79 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/util/EgovFileUploadUtil.java @@ -0,0 +1,151 @@ +package egovframework.com.cmm.util; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; + +import egovframework.com.cmm.EgovWebUtil; +import egovframework.com.cmm.model.EgovFormBasedFileVo; + +/** + * @Class Name : EgovFileUploadUtil.java + * @Description : Spring 기반 File Upload 유틸리티 + * @Modification Information + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2009.08.26 한성곤 최초 생성 + * 2018.08.17 신용호 uploadFilesExt(확장자 기록) 추가 + * 2019.12.06 신용호 checkFileExtension(), checkFileMaxSize() 추가 + * 2020.08.05 신용호 uploadFilesExt Parameter 수정 + * 2021.02.16 신용호 WebUtils.getNativeRequest(request,MultipartHttpServletRequest.class); + * + * @author 공통컴포넌트 개발팀 한성곤 + * @since 2009.08.26 + * @version 1.0 + * @see + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class EgovFileUploadUtil extends EgovFormBasedFileUtil { + + /** + * 파일을 Upload(확장명 저장 및 확장자 제한) 처리한다. + * + * @param mptRequest + * @param where + * @param extensionWhiteList + * @return List + * @throws Exception + */ + public static List uploadFilesExt(MultipartHttpServletRequest mptRequest, String where, + String extensionWhiteList) throws Exception { + List list = new ArrayList<>(); + if(mptRequest == null) return list; + + Iterator fileIter = mptRequest.getFileNames(); + + while (fileIter.hasNext()) { + MultipartFile mFile = mptRequest.getFile((String)fileIter.next()); + if(mFile == null) continue; + + String tmp = mFile.getOriginalFilename(); + if(tmp == null) continue; + + if (tmp.lastIndexOf("\\") >= 0) { + tmp = tmp.substring(tmp.lastIndexOf("\\") + 1); + } + + String ext; + if (tmp.lastIndexOf(".") > 0) { + ext = getFileExtension(tmp).toLowerCase(); + } else { + throw new SecurityException("Unacceptable file extension."); // 허용되지 않는 확장자 처리 + } + if (extensionWhiteList.indexOf(ext) < 0) { + throw new SecurityException("Unacceptable file extension."); // 허용되지 않는 확장자 처리 + } + + EgovFormBasedFileVo vo = new EgovFormBasedFileVo(); + vo.setFileName(tmp); + vo.setContentType(mFile.getContentType()); + vo.setServerSubPath(getTodayString()); + vo.setPhysicalName(getPhysicalFileName() + "." + ext); + vo.setSize(mFile.getSize()); + + if (tmp.lastIndexOf(".") >= 0) { + vo.setPhysicalName(vo.getPhysicalName()); // 2012.11 KISA 보안조치 + } + + if (mFile.getSize() > 0) { + try (InputStream is = mFile.getInputStream();){ + saveFile(is, new File(EgovWebUtil.filePathBlackList( + where + SEPERATOR + vo.getServerSubPath() + SEPERATOR + vo.getPhysicalName()))); + } + list.add(vo); + } + } + return list; + } + + /** + * 파일 확장자를 추출한다. + * + * @param fileNamePath + * @return 확장자 : "" 또는 추출된 확장자 + */ + public static String getFileExtension(String fileNamePath) { + + if (fileNamePath == null) { + return ""; + } + return fileNamePath.substring(fileNamePath.lastIndexOf(".") + 1, fileNamePath.length()); + } + + /** + * 파일 확장자의 허용유무를 검증한다. + * + * @param fileNamePath + * @param whiteListExtensions : ex) .png.pdf.txt + * @return true : 허용 + * @return true : 불가 + */ + public static boolean checkFileExtension(String fileNamePath, String whiteListExtensions) { + String extension = getFileExtension(fileNamePath); + + if ("".equals(extension)) { + return false; + } + + if (whiteListExtensions == null) { + return false; + } + if ("".equals(whiteListExtensions)) { + return false; + } + + return whiteListExtensions.contains("." + extension); + } + + /** + * 최대 파일 사이즈 허용유무를 검증한다. + * + * @param multipartFile + * @param maxFileSize : ex) 1048576 = 1M , 1K = 1024 + * @return true : 허용 + * @return true : 불가 + */ + public static boolean checkFileMaxSize(MultipartFile multipartFile, long maxFileSize) { + + if (multipartFile == null) return false; + + return (multipartFile.getSize() <= maxFileSize); + } + +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/util/EgovFormBasedFileUtil.java b/mens-core/src/main/java/egovframework/com/cmm/util/EgovFormBasedFileUtil.java new file mode 100644 index 0000000..af156c4 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/util/EgovFormBasedFileUtil.java @@ -0,0 +1,198 @@ +package egovframework.com.cmm.util; + +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +import javax.servlet.http.HttpServletResponse; + +import kr.xit.core.exception.BizRuntimeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import egovframework.com.cmm.EgovWebUtil; +import egovframework.com.cmm.model.EgovFormBasedUUID; + +/** + * @Class Name : EgovFormBasedFileUtil.java + * @Description : Form-based File Upload 유틸리티 + * @Modification Information + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2009.08.26 한성곤 최초 생성 + * 2017.03.03 조성원 시큐어코딩(ES)-부적절한 예외 처리[CWE-253, CWE-440, CWE-754] + * 2019.12.09 신용호 KISA 보안약점 조치 (위험한 형식 파일 업로드) : uploadFiles 삭제 => EgovFileUploadUtil.uploadFilesExt(확장자 기록) 대체 + * + * @author 공통컴포넌트 개발팀 한성곤 + * @since 2009.08.26 + * @version 1.0 + * @see + */ +public class EgovFormBasedFileUtil { + /** Buffer size */ + public static final int BUFFER_SIZE = 8192; + + public static final String SEPERATOR = File.separator; + + private static final Logger LOGGER = LoggerFactory.getLogger(EgovFormBasedFileUtil.class); + + /** + * 오늘 날짜 문자열 취득. + * ex) 20090101 + * @return String + */ + public static String getTodayString() { + SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd", Locale.getDefault()); + + return format.format(new Date()); + } + + /** + * 물리적 파일명 생성. + * @return String + */ + public static String getPhysicalFileName() { + return EgovFormBasedUUID.randomUUID().toString().replaceAll("/-/g", "").toUpperCase(); + } + + /** + * 파일명 변환. + * @param filename String + * @return String + * @throws Exception + */ + protected static String convert(String filename) { + try { + return java.net.URLEncoder.encode(filename, "utf-8"); + } catch (UnsupportedEncodingException e) { + throw BizRuntimeException.create(e.getMessage()); + } + } + + /** + * Stream으로부터 파일을 저장함. + * @param is InputStream + * @param file File + * @throws IOException + */ + public static long saveFile(InputStream is, File file) throws IOException { + //KISA 보안약점 조치 (2018-10-29, 윤창원) + if (file.getParentFile() == null) { + LOGGER.debug("file.getParentFile() is null"); + throw new RuntimeException("file.getParentFile() is null"); + } + + // 디렉토리 생성 + if (!file.getParentFile().exists()) { + //2017.03.03 조성원 시큐어코딩(ES)-부적절한 예외 처리[CWE-253, CWE-440, CWE-754] + if(file.getParentFile().mkdirs()){ + LOGGER.debug("[file.mkdirs] file : Directory Creation Success"); + }else{ + LOGGER.error("[file.mkdirs] file : Directory Creation Fail"); + } + } + + long size = 0L; + try(OutputStream os = new FileOutputStream(file);){ + int bytesRead; + byte[] buffer = new byte[BUFFER_SIZE]; + + while ((bytesRead = is.read(buffer, 0, BUFFER_SIZE)) != -1) { + size += bytesRead; + os.write(buffer, 0, bytesRead); + } + } + return size; + } + + /** + * 파일을 Download 처리한다. + * + * @param response + * @param where + * @param serverSubPath + * @param physicalName + * @param original + * @throws Exception + */ + public static void downloadFile(HttpServletResponse response, String where, String serverSubPath, String physicalName, String original) throws Exception { + String downFileName = where + SEPERATOR + serverSubPath + SEPERATOR + physicalName; + + File file = new File(EgovWebUtil.filePathBlackList(downFileName)); + + if (!file.exists()) { + throw new FileNotFoundException(downFileName); + } + + if (!file.isFile()) { + throw new FileNotFoundException(downFileName); + } + + byte[] b = new byte[BUFFER_SIZE]; + + original = original.replaceAll("(\r\n|\r|\n|\n\r)", ""); + response.setContentType("application/octet-stream"); + response.setHeader("Content-Disposition", "attachment; filename=\"" + convert(original) + "\";"); + response.setHeader("Content-Transfer-Encoding", "binary"); + response.setHeader("Pragma", "no-cache"); + response.setHeader("Expires", "0"); + + try(BufferedInputStream fin = new BufferedInputStream(new FileInputStream(file)); + BufferedOutputStream outs = new BufferedOutputStream(response.getOutputStream()); + ){ + int read; + while ((read = fin.read(b)) != -1) { + outs.write(b, 0, read); + } + } + } + + /** + * 이미지에 대한 미리보기 기능을 제공한다. + * + * mimeType의 경우는 JSP 상에서 다음과 같이 얻을 수 있다. + * getServletConfig().getServletContext().getMimeType(name); + * + * @param response + * @param where + * @param serverSubPath + * @param physicalName + * @param mimeTypeParam + * @throws Exception + */ + public static void viewFile(HttpServletResponse response, String where, String serverSubPath, String physicalName, String mimeTypeParam) throws Exception { + String mimeType = mimeTypeParam; + String downFileName = where + SEPERATOR + serverSubPath + SEPERATOR + physicalName; + + File file = new File(EgovWebUtil.filePathBlackList(downFileName)); + + if (!file.exists()) { + throw new FileNotFoundException(downFileName); + } + + if (!file.isFile()) { + throw new FileNotFoundException(downFileName); + } + + byte[] b = new byte[BUFFER_SIZE]; + + if (mimeType == null) { + mimeType = "application/octet-stream;"; + } + + response.setContentType(EgovWebUtil.removeCRLF(mimeType)); + response.setHeader("Content-Disposition", "filename=image;"); + + try( + BufferedInputStream fin = new BufferedInputStream(new FileInputStream(file)); + BufferedOutputStream outs = new BufferedOutputStream(response.getOutputStream()); + ){ + int read; + while ((read = fin.read(b)) != -1) { + outs.write(b, 0, read); + } + } + } +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/util/EgovNumberUtil.java b/mens-core/src/main/java/egovframework/com/cmm/util/EgovNumberUtil.java new file mode 100644 index 0000000..ef26fd9 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/util/EgovNumberUtil.java @@ -0,0 +1,202 @@ +/** + * @Class Name : EgovNumberUtil.java + * @Description : 숫자 데이터 처리 관련 유틸리티 + * @Modification Information + * + * 수정일 수정자 수정내용 + * ------- -------- --------------------------- + * 2009.02.13 이삼섭 최초 생성 + * + * @author 공통 서비스 개발팀 이삼섭 + * @since 2009. 02. 13 + * @version 1.0 + * @see + * + */ + +package egovframework.com.cmm.util; + +import java.security.SecureRandom; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class EgovNumberUtil { + + private static final Logger LOGGER = LoggerFactory.getLogger(EgovNumberUtil.class); + + private static SecureRandom rnd = new SecureRandom(); + + /** + * 특정숫자 집합에서 랜덤 숫자를 구하는 기능 시작숫자와 종료숫자 사이에서 구한 랜덤 숫자를 반환한다 + * + * @param startNum - 시작숫자 + * @param endNum - 종료숫자 + * @return 랜덤숫자 + * @see + */ + public static int getRandomNum(int startNum, int endNum) { + int randomNum; + + do { + // 종료숫자내에서 랜덤 숫자를 발생시킨다. + randomNum = rnd.nextInt(endNum + 1); + } while (randomNum < startNum); // 랜덤 숫자가 시작숫자보다 작을경우 다시 랜덤숫자를 발생시킨다. + + return randomNum; + } + + /** + * 특정 숫자 집합에서 특정 숫자가 있는지 체크하는 기능 12345678에서 7이 있는지 없는지 체크하는 기능을 제공함 + * + * @param sourceInt - 특정숫자집합 + * @param searchInt - 검색숫자 + * @return 존재여부 + * @see + */ + public static Boolean getNumSearchCheck(int sourceInt, int searchInt) { + String sourceStr = String.valueOf(sourceInt); + String searchStr = String.valueOf(searchInt); + + // 특정숫자가 존재하는지 하여 위치값을 리턴한다. 없을 시 -1 + return sourceStr.indexOf(searchStr) != -1; + } + + /** + * 숫자타입을 문자열로 변환하는 기능 숫자 20081212를 문자열 '20081212'로 변환하는 기능 + * + * @param srcNumber - 숫자 + * @return 문자열 + * @see + */ + public static String getNumToStrCnvr(int srcNumber) { + return String.valueOf(srcNumber); + } + + + /** + * 숫자타입을 데이트 타입으로 변환하는 기능 + * 숫자 20081212를 데이트타입 '2008-12-12'로 변환하는 기능 + * @param srcNumber - 숫자 + * @return String + * @see + */ + public static String getNumToDateCnvr(int srcNumber) { + + String pattern = null; + + String srcStr = String.valueOf(srcNumber); + + // Date 형태인 8자리 및 14자리만 정상처리 + if (srcStr.length() != 8 && srcStr.length() != 14) { + throw new IllegalArgumentException("Invalid Number: " + srcStr + " Length=" + srcStr.trim().length()); + } + + if (srcStr.length() == 8) { + pattern = "yyyyMMdd"; + } else { + pattern = "yyyyMMddhhmmss"; + } + + SimpleDateFormat dateFormatter = new SimpleDateFormat(pattern, Locale.KOREA); + + Date cnvrDate = null; + + try { + cnvrDate = dateFormatter.parse(srcStr); + } catch (ParseException e) { + LOGGER.error("ERROR parsing :: {}", e.getMessage()); + } + + return String.format("%1$tY-%1$tm-%1$td", cnvrDate); + } + + /** + * 체크할 숫자 중에서 숫자인지 아닌지 체크하는 기능 + * 숫자이면 True, 아니면 False를 반환한다 + * @param checkStr - 체크문자열 + * @return 숫자여부 + * @see + */ + public static Boolean getNumberValidCheck(String checkStr) { + + for (int i = 0; i < checkStr.length(); i++) { + // 아스키코드값( '0'-> 48, '9' -> 57) + if (checkStr.charAt(i) <= 47 || checkStr.charAt(i) >= 58) { + return false; + } + } + return true; + } + + /** + * 특정숫자를 다른 숫자로 치환하는 기능 숫자 12345678에서 123를 999로 변환하는 기능을 제공(99945678) + * + * @param srcNumber - 숫자집합 + * @param cnvrSrcNumber - 원래숫자 + * @param cnvrTrgtNumber - 치환숫자 + * @return 치환숫자 + * @see + */ + + public static int getNumberCnvr(int srcNumber, int cnvrSrcNumber, int cnvrTrgtNumber) { + + // 입력받은 숫자를 문자열로 변환 + String source = String.valueOf(srcNumber); + String subject = String.valueOf(cnvrSrcNumber); + String object = String.valueOf(cnvrTrgtNumber); + + StringBuilder rtnStr = new StringBuilder(); + String preStr; + String nextStr = source; + + // 원본숫자에서 변환대상숫자의 위치를 찾는다. + while (source.indexOf(subject) >= 0) { + preStr = source.substring(0, source.indexOf(subject)); // 변환대상숫자 위치까지 숫자를 잘라낸다 + nextStr = source.substring(source.indexOf(subject) + subject.length(), source.length()); + source = nextStr; + rtnStr.append(preStr).append(object); // 변환대상위치 숫자에 변환할 숫자를 붙여준다. + } + rtnStr.append(nextStr); // 변환대상 숫자 이후 숫자를 붙여준다. + + return Integer.parseInt(rtnStr.toString()); + } + + /** + * 특정숫자가 실수인지, 정수인지, 음수인지 체크하는 기능 123이 실수인지, 정수인지, 음수인지 체크하는 기능을 제공함 + * + * @param srcNumber - 숫자집합 + * @return -1(음수), 0(정수), 1(실수) + * @see + */ + public static int checkRlnoInteger(double srcNumber) { + + // byte 1바이트 ▶소수점이 없는 숫자로, 범위 -2^7 ~ 2^7 -1 + // short 2바이트 ▶소수점이 없는 숫자로, 범위 -2^15 ~ 2^15 -1 + // int 4바이트 ▶소수점이 없는 숫자로, 범위 -2^31 ~ 2^31 - 1 + // long 8바이트 ▶소수점이 없는 숫자로, 범위 -2^63 ~ 2^63-1 + + // float 4바이트 ▶소수점이 있는 숫자로, 끝에 F 또는 f 가 붙는 숫자 (예:3.14f) + // double 8바이트 ▶소수점이 있는 숫자로, 끝에 아무것도 붙지 않는 숫자 (예:3.14) + // ▶소수점이 있는 숫자로, 끝에 D 또는 d 가 붙는 숫자(예:3.14d) + + if (srcNumber < 0) { + return -1; + } else { + String cnvrString = String.valueOf(srcNumber); + + if (cnvrString.indexOf(".") == -1) { + return 0; + } else { + return 1; + } + } + } +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/util/EgovStringUtil.java b/mens-core/src/main/java/egovframework/com/cmm/util/EgovStringUtil.java new file mode 100644 index 0000000..2ccd482 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/util/EgovStringUtil.java @@ -0,0 +1,743 @@ +/** + * @Class Name : EgovStringUtil.java + * @Description : 문자열 데이터 처리 관련 유틸리티 + * @Modification Information + * + * 수정일 수정자 수정내용 + * ------- -------- --------------------------- + * 2009.01.13 박정규 최초 생성 + * 2009.02.13 이삼섭 내용 추가 + * + * @author 공통 서비스 개발팀 박정규 + * @since 2009. 01. 13 + * @version 1.0 + * @see + * + */ + +package egovframework.com.cmm.util; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.security.SecureRandom; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.Locale; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang3.StringUtils; + + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class EgovStringUtil { + + /** + * 빈 문자열 "". + */ + public static final String EMPTY = ""; + + /** + * 랜덤 객체 + */ + public static final SecureRandom RND = new SecureRandom(); + + /** + * 문자열이 지정한 길이를 초과했을때 지정한길이에다가 해당 문자열을 붙여주는 메서드. + * @param source 원본 문자열 배열 + * @param output 더할문자열 + * @param slength 지정길이 + * @return 지정길이로 잘라서 더할분자열 합친 문자열 + */ + public static String cutString(String source, String output, int slength) { + String returnVal = null; + if (source != null) { + if (source.length() > slength) { + returnVal = source.substring(0, slength) + output; + } else { + returnVal = source; + } + } + return returnVal; + } + + /** + * 문자열이 지정한 길이를 초과했을때 해당 문자열을 삭제하는 메서드 + * @param source 원본 문자열 배열 + * @param slength 지정길이 + * @return 지정길이로 잘라서 더할분자열 합친 문자열 + */ + public static String cutString(String source, int slength) { + String result = null; + if (source != null) { + if (source.length() > slength) { + result = source.substring(0, slength); + } else { + result = source; + } + } + return result; + } + + /** + *

    + * String이 비었거나("") 혹은 null 인지 검증한다. + *

    + * + *
    +	 *  StringUtil.isEmpty(null)      = true
    +	 *  StringUtil.isEmpty("")        = true
    +	 *  StringUtil.isEmpty(" ")       = false
    +	 *  StringUtil.isEmpty("bob")     = false
    +	 *  StringUtil.isEmpty("  bob  ") = false
    +	 * 
    + * + * @param str - 체크 대상 스트링오브젝트이며 null을 허용함 + * @return true - 입력받은 String 이 빈 문자열 또는 null인 경우 + */ + public static boolean isEmpty(String str) { + return str == null || str.length() == 0; + } + + /** + *

    기준 문자열에 포함된 모든 대상 문자(char)를 제거한다.

    + * + *
    +	 * StringUtil.remove(null, *)       = null
    +	 * StringUtil.remove("", *)         = ""
    +	 * StringUtil.remove("queued", 'u') = "qeed"
    +	 * StringUtil.remove("queued", 'z') = "queued"
    +	 * 
    + * + * @param str 입력받는 기준 문자열 + * @param remove 입력받는 문자열에서 제거할 대상 문자열 + * @return 제거대상 문자열이 제거된 입력문자열. 입력문자열이 null인 경우 출력문자열은 null + */ + public static String remove(String str, char remove) { + if (isEmpty(str) || str.indexOf(remove) == -1) { + return str; + } + char[] chars = str.toCharArray(); + int pos = 0; + for (int i = 0; i < chars.length; i++) { + if (chars[i] != remove) { + chars[pos++] = chars[i]; + } + } + return new String(chars, 0, pos); + } + + /** + *

    문자열 내부의 콤마 character(,)를 모두 제거한다.

    + * + *
    +	 * StringUtil.removeCommaChar(null)       = null
    +	 * StringUtil.removeCommaChar("")         = ""
    +	 * StringUtil.removeCommaChar("asdfg,qweqe") = "asdfgqweqe"
    +	 * 
    + * + * @param str 입력받는 기준 문자열 + * @return " , "가 제거된 입력문자열 + * 입력문자열이 null인 경우 출력문자열은 null + */ + public static String removeCommaChar(String str) { + return remove(str, ','); + } + + /** + *

    문자열 내부의 마이너스 character(-)를 모두 제거한다.

    + * + *
    +	 * StringUtil.removeMinusChar(null)       = null
    +	 * StringUtil.removeMinusChar("")         = ""
    +	 * StringUtil.removeMinusChar("a-sdfg-qweqe") = "asdfgqweqe"
    +	 * 
    + * + * @param str 입력받는 기준 문자열 + * @return " - "가 제거된 입력문자열 + * 입력문자열이 null인 경우 출력문자열은 null + */ + public static String removeMinusChar(String str) { + return remove(str, '-'); + } + + /** + *

    strsearchStr의 시작(index) 위치를 반환.

    + * + *

    입력값 중 null이 있을 경우 -1을 반환.

    + * + *
    +	 * StringUtil.indexOf(null, *)          = -1
    +	 * StringUtil.indexOf(*, null)          = -1
    +	 * StringUtil.indexOf("", "")           = 0
    +	 * StringUtil.indexOf("aabaabaa", "a")  = 0
    +	 * StringUtil.indexOf("aabaabaa", "b")  = 2
    +	 * StringUtil.indexOf("aabaabaa", "ab") = 1
    +	 * StringUtil.indexOf("aabaabaa", "")   = 0
    +	 * 
    + * + * @param str 검색 문자열 + * @param searchStr 검색 대상문자열 + * @return 검색 문자열 중 검색 대상문자열이 있는 시작 위치 검색대상 문자열이 없거나 null인 경우 -1 + */ + public static int indexOf(String str, String searchStr) { + if (str == null || searchStr == null) { + return -1; + } + return str.indexOf(searchStr); + } + + /** + *

    오라클의 decode 함수와 동일한 기능을 가진 메서드이다. + * sourStrcompareStr의 값이 같으면 + * returStr을 반환하며, 다르면 defaultStr을 반환한다. + *

    + * + *
    +	 * StringUtil.decode(null, null, "foo", "bar")= "foo"
    +	 * StringUtil.decode("", null, "foo", "bar") = "bar"
    +	 * StringUtil.decode(null, "", "foo", "bar") = "bar"
    +	 * StringUtil.decode("하이", "하이", null, "bar") = null
    +	 * StringUtil.decode("하이", "하이  ", "foo", null) = null
    +	 * StringUtil.decode("하이", "하이", "foo", "bar") = "foo"
    +	 * StringUtil.decode("하이", "하이  ", "foo", "bar") = "bar"
    +	 * 
    + * + * @param sourceStr 비교할 문자열 + * @param compareStr 비교 대상 문자열 + * @param returnStr sourceStr와 compareStr의 값이 같을 때 반환할 문자열 + * @param defaultStr sourceStr와 compareStr의 값이 다를 때 반환할 문자열 + * @return sourceStr과 compareStr의 값이 동일(equal)할 때 returnStr을 반환하며, + *
    다르면 defaultStr을 반환한다. + */ + public static String decode(String sourceStr, String compareStr, String returnStr, String defaultStr) { + if (sourceStr == null && compareStr == null) { + return returnStr; + } + + if (sourceStr != null && sourceStr.trim().equals(compareStr)) { + return returnStr; + } + + return defaultStr; + } + + /** + *

    오라클의 decode 함수와 동일한 기능을 가진 메서드이다. + * sourStrcompareStr의 값이 같으면 + * returStr을 반환하며, 다르면 sourceStr을 반환한다. + *

    + * + *
    +	 * StringUtil.decode(null, null, "foo") = "foo"
    +	 * StringUtil.decode("", null, "foo") = ""
    +	 * StringUtil.decode(null, "", "foo") = null
    +	 * StringUtil.decode("하이", "하이", "foo") = "foo"
    +	 * StringUtil.decode("하이", "하이 ", "foo") = "하이"
    +	 * StringUtil.decode("하이", "바이", "foo") = "하이"
    +	 * 
    + * + * @param sourceStr 비교할 문자열 + * @param compareStr 비교 대상 문자열 + * @param returnStr sourceStr와 compareStr의 값이 같을 때 반환할 문자열 + * @return sourceStr과 compareStr의 값이 동일(equal)할 때 returnStr을 반환하며, + *
    다르면 sourceStr을 반환한다. + */ + public static String decode(String sourceStr, String compareStr, String returnStr) { + return decode(sourceStr, compareStr, returnStr, sourceStr); + } + + /** + * 객체가 null인지 확인하고 null인 경우 "" 로 바꾸는 메서드 + * @param object 원본 객체 + * @return resultVal 문자열 + */ + public static String isNullToString(Object object) { + String string = ""; + + if (object != null) { + string = object.toString().trim(); + } + + return string; + } + + /** + *
    +	 * 인자로 받은 String이 null일 경우 ""로 리턴한다.
    +	 * @param src null값일 가능성이 있는 String 값.
    +	 * @return 만약 String이 null 값일 경우 ""로 바꾼 String 값.
    +	 *
    + */ + public static String nullConvert(Object src) { + if (src != null && src instanceof BigDecimal) { + return ((BigDecimal)src).toString(); + } + + if (src == null || src.equals("null")) { + return StringUtils.EMPTY; + } else { + return ((String)src).trim(); + } + } + + /** + *
    +	 * 인자로 받은 String이 null일 경우 ""로 리턴한다.
    +	 * @param src null값일 가능성이 있는 String 값.
    +	 * @return 만약 String이 null 값일 경우 ""로 바꾼 String 값.
    +	 *
    + */ + public static String nullConvert(String src) { + + if (src == null || src.equals("null") || "".equals(src) || " ".equals(src)) { + return ""; + } else { + return src.trim(); + } + } + + /** + *
    +	 * 인자로 받은 String이 null일 경우 "0"로 리턴한다.
    +	 * @param src null값일 가능성이 있는 String 값.
    +	 * @return 만약 String이 null 값일 경우 "0"로 바꾼 String 값.
    +	 *
    + */ + public static int zeroConvert(Object src) { + + if (src == null || src.equals("null")) { + return 0; + } else { + return Integer.parseInt(((String)src).trim()); + } + } + + /** + *
    +	 * 인자로 받은 String이 null일 경우 ""로 리턴한다.
    +	 * @param src null값일 가능성이 있는 String 값.
    +	 * @return 만약 String이 null 값일 경우 ""로 바꾼 String 값.
    +	 *
    + */ + public static int zeroConvert(String src) { + + if (src == null || src.equals("null") || "".equals(src) || " ".equals(src)) { + return 0; + } else { + return Integer.parseInt(src.trim()); + } + } + + /** + *

    문자열에서 {@link Character#isWhitespace(char)}에 정의된 + * 모든 공백문자를 제거한다.

    + * + *
    +	 * StringUtil.removeWhitespace(null)         = null
    +	 * StringUtil.removeWhitespace("")           = ""
    +	 * StringUtil.removeWhitespace("abc")        = "abc"
    +	 * StringUtil.removeWhitespace("   ab  c  ") = "abc"
    +	 * 
    + * + * @param str 공백문자가 제거도어야 할 문자열 + * @return the 공백문자가 제거된 문자열, null이 입력되면 null이 리턴 + */ + public static String removeWhitespace(String str) { + if (isEmpty(str)) { + return str; + } + int sz = str.length(); + char[] chs = new char[sz]; + int count = 0; + for (int i = 0; i < sz; i++) { + if (!Character.isWhitespace(str.charAt(i))) { + chs[count++] = str.charAt(i); + } + } + if (count == sz) { + return str; + } + + return new String(chs, 0, count); + } + + /** + * Html 코드가 들어간 문서를 표시할때 태그에 손상없이 보이기 위한 메서드 + * + * @param strString + * @return HTML 태그를 치환한 문자열 + */ + public static String checkHtmlView(String strString) { + StringBuilder strTxt = new StringBuilder(); + + char chrBuff; + int len = strString.length(); + + for (int i = 0; i < len; i++) { + chrBuff = strString.charAt(i); + + switch (chrBuff) { + case '<': + strTxt.append("<"); + break; + case '>': + strTxt.append(">"); + break; + case '"': + strTxt.append("""); + break; + case 10: + strTxt.append("
    "); + break; + case ' ': + strTxt.append(" "); + break; + + default: + strTxt.append(chrBuff); + } + } + return strTxt.toString(); + } + + /** + * 문자열을 지정한 분리자에 의해 배열로 리턴하는 메서드. + * @param source 원본 문자열 + * @param separator 분리자 + * @return result 분리자로 나뉘어진 문자열 배열 + */ + public static String[] split(String source, String separator) throws NullPointerException { + String[] returnVal; + int cnt = 1; + + int index = source.indexOf(separator); + int index0 = 0; + while (index >= 0) { + cnt++; + index = source.indexOf(separator, index + 1); + } + returnVal = new String[cnt]; + cnt = 0; + index = source.indexOf(separator); + while (index >= 0) { + returnVal[cnt] = source.substring(index0, index); + index0 = index + 1; + index = source.indexOf(separator, index + 1); + cnt++; + } + returnVal[cnt] = source.substring(index0); + + return returnVal; + } + + /** + *

    {@link String#toLowerCase()}를 이용하여 소문자로 변환한다.

    + * + *
    +	 * StringUtil.lowerCase(null)  = null
    +	 * StringUtil.lowerCase("")    = ""
    +	 * StringUtil.lowerCase("aBc") = "abc"
    +	 * 
    + * + * @param str 소문자로 변환되어야 할 문자열 + * @return 소문자로 변환된 문자열, null이 입력되면 null 리턴 + */ + public static String lowerCase(String str) { + if (str == null) { + return null; + } + + return str.toLowerCase(); + } + + /** + *

    {@link String#toUpperCase()}를 이용하여 대문자로 변환한다.

    + * + *
    +	 * StringUtil.upperCase(null)  = null
    +	 * StringUtil.upperCase("")    = ""
    +	 * StringUtil.upperCase("aBc") = "ABC"
    +	 * 
    + * + * @param str 대문자로 변환되어야 할 문자열 + * @return 대문자로 변환된 문자열, null이 입력되면 null 리턴 + */ + public static String upperCase(String str) { + if (str == null) { + return null; + } + + return str.toUpperCase(); + } + + /** + *

    입력된 String의 앞쪽에서 두번째 인자로 전달된 문자(stripChars)를 모두 제거한다.

    + * + *
    +	 * StringUtil.stripStart(null, *)          = null
    +	 * StringUtil.stripStart("", *)            = ""
    +	 * StringUtil.stripStart("abc", "")        = "abc"
    +	 * StringUtil.stripStart("abc", null)      = "abc"
    +	 * StringUtil.stripStart("  abc", null)    = "abc"
    +	 * StringUtil.stripStart("abc  ", null)    = "abc  "
    +	 * StringUtil.stripStart(" abc ", null)    = "abc "
    +	 * StringUtil.stripStart("yxabc  ", "xyz") = "abc  "
    +	 * 
    + * + * @param str 지정된 문자가 제거되어야 할 문자열 + * @param stripChars 제거대상 문자열 + * @return 지정된 문자가 제거된 문자열, null이 입력되면 null 리턴 + */ + public static String stripStart(String str, String stripChars) { + int strLen; + if (str == null || (strLen = str.length()) == 0) { + return str; + } + int start = 0; + if (stripChars == null) { + while ((start != strLen) && Character.isWhitespace(str.charAt(start))) { + start++; + } + } else if (stripChars.length() == 0) { + return str; + } else { + while ((start != strLen) && (stripChars.indexOf(str.charAt(start)) != -1)) { + start++; + } + } + + return str.substring(start); + } + + /** + *

    입력된 String의 뒤쪽에서 두번째 인자로 전달된 문자(stripChars)를 모두 제거한다.

    + * + *
    +	 * StringUtil.stripEnd(null, *)          = null
    +	 * StringUtil.stripEnd("", *)            = ""
    +	 * StringUtil.stripEnd("abc", "")        = "abc"
    +	 * StringUtil.stripEnd("abc", null)      = "abc"
    +	 * StringUtil.stripEnd("  abc", null)    = "  abc"
    +	 * StringUtil.stripEnd("abc  ", null)    = "abc"
    +	 * StringUtil.stripEnd(" abc ", null)    = " abc"
    +	 * StringUtil.stripEnd("  abcyx", "xyz") = "  abc"
    +	 * 
    + * + * @param str 지정된 문자가 제거되어야 할 문자열 + * @param stripChars 제거대상 문자열 + * @return 지정된 문자가 제거된 문자열, null이 입력되면 null 리턴 + */ + public static String stripEnd(String str, String stripChars) { + int end; + if (str == null || (end = str.length()) == 0) { + return str; + } + + if (stripChars == null) { + while ((end != 0) && Character.isWhitespace(str.charAt(end - 1))) { + end--; + } + } else if (stripChars.length() == 0) { + return str; + } else { + while ((end != 0) && (stripChars.indexOf(str.charAt(end - 1)) != -1)) { + end--; + } + } + + return str.substring(0, end); + } + + /** + *

    입력된 String의 앞, 뒤에서 두번째 인자로 전달된 문자(stripChars)를 모두 제거한다.

    + * + *
    +	 * StringUtil.strip(null, *)          = null
    +	 * StringUtil.strip("", *)            = ""
    +	 * StringUtil.strip("abc", null)      = "abc"
    +	 * StringUtil.strip("  abc", null)    = "abc"
    +	 * StringUtil.strip("abc  ", null)    = "abc"
    +	 * StringUtil.strip(" abc ", null)    = "abc"
    +	 * StringUtil.strip("  abcyx", "xyz") = "  abc"
    +	 * 
    + * + * @param str 지정된 문자가 제거되어야 할 문자열 + * @param stripChars 제거대상 문자열 + * @return 지정된 문자가 제거된 문자열, null이 입력되면 null 리턴 + */ + public static String strip(String str, String stripChars) { + if (isEmpty(str)) { + return str; + } + + String srcStr = str; + srcStr = stripStart(srcStr, stripChars); + + return stripEnd(srcStr, stripChars); + } + + /** + * 문자열을 지정한 분리자에 의해 지정된 길이의 배열로 리턴하는 메서드. + * @param source 원본 문자열 + * @param separator 분리자 + * @param length 배열 길이 + * @return 분리자로 나뉘어진 문자열 배열 + */ + public static String[] split(String source, String separator, int length) throws NullPointerException { + String[] returnVal = new String[length]; + int cnt = 0; + int index0 = 0; + int index = source.indexOf(separator); + while (index >= 0 && cnt < (length - 1)) { + returnVal[cnt] = source.substring(index0, index); + index0 = index + 1; + index = source.indexOf(separator, index + 1); + cnt++; + } + returnVal[cnt] = source.substring(index0); + if (cnt < (length - 1)) { + for (int i = cnt + 1; i < length; i++) { + returnVal[i] = ""; + } + } + + return returnVal; + } + + /** + * 문자열 A에서 Z사이의 랜덤 문자열을 구하는 기능을 제공 시작문자열과 종료문자열 사이의 랜덤 문자열을 구하는 기능 + * + * @param startChr + * - 첫 문자 + * @param endChr + * - 마지막문자 + * @return 랜덤문자 + */ + public static String getRandomStr(char startChr, char endChr) { + + int randomInt; + + // 시작문자 및 종료문자를 아스키숫자로 변환한다. + Integer startInt = Integer.valueOf(startChr); + Integer endInt = Integer.valueOf(endChr); + + // 시작문자열이 종료문자열보가 클경우 + if (startInt > endInt) { + throw new IllegalArgumentException("Start String: " + startChr + " End String: " + endChr); + } + + do { + // 시작문자 및 종료문자 중에서 랜덤 숫자를 발생시킨다. + randomInt = RND.nextInt(endInt + 1); + } while (randomInt < startInt); // 입력받은 문자 'A'(65)보다 작으면 다시 랜덤 숫자 발생. + + // 랜덤 숫자를 문자로 변환 후 스트링으로 다시 변환 + return (char)randomInt + ""; + } + + /** + * 문자열을 다양한 문자셋(EUC-KR[KSC5601],UTF-8..)을 사용하여 인코딩하는 기능 역으로 디코딩하여 원래의 문자열을 + * 복원하는 기능을 제공함 String temp = new String(문자열.getBytes("바꾸기전 인코딩"),"바꿀 인코딩"); + * String temp = new String(문자열.getBytes("8859_1"),"KSC5601"); => UTF-8 에서 + * EUC-KR + * + * @param srcString + * - 문자열 + * @param srcCharsetNm + * - 원래 CharsetNm + * @param cnvrCharsetNm + * - CharsetNm + * @return 인(디)코딩 문자열 + */ + public static String getEncdDcd(String srcString, String srcCharsetNm, String cnvrCharsetNm) { + + if (srcString == null) { + return null; + } + + try { + return new String(srcString.getBytes(srcCharsetNm), cnvrCharsetNm); + } catch (UnsupportedEncodingException e) { + return null; + } + } + + /** + * 특수문자를 웹 브라우저에서 정상적으로 보이기 위해 특수문자를 처리('<' -> & lT)하는 기능이다 + * @param srcString - '<' + * @return 변환문자열('<' -> "<" + */ + public static String getSpclStrCnvr(String srcString) { + StringBuilder strTxt = new StringBuilder(); + + char chrBuff; + int len = srcString.length(); + + for (int i = 0; i < len; i++) { + chrBuff = srcString.charAt(i); + + switch (chrBuff) { + case '<': + strTxt.append("<"); + break; + case '>': + strTxt.append(">"); + break; + case '&': + strTxt.append("&"); + break; + default: + strTxt.append(chrBuff); + } + } + return strTxt.toString(); + } + + /** + * 응용어플리케이션에서 고유값을 사용하기 위해 시스템에서17자리의TIMESTAMP값을 구하는 기능 + * + * @return String Timestamp 값 + */ + public static String getTimeStamp() { + + // 문자열로 변환하기 위한 패턴 설정(년도-월-일 시:분:초:초(자정이후 초)) + String pattern = "yyyyMMddhhmmssSSS"; + + SimpleDateFormat sdfCurrent = new SimpleDateFormat(pattern, Locale.KOREA); + Timestamp ts = new Timestamp(System.currentTimeMillis()); + + return sdfCurrent.format(ts.getTime()); + } + + /** + * html의 특수문자를 표현하기 위해 + * + * @param srcString + * @return String + */ + public static String getHtmlStrCnvr(String srcString) { + + return StringEscapeUtils.unescapeHtml(srcString); + } + + /** + *

    날짜 형식의 문자열 내부에 마이너스 character(-)를 추가한다.

    + * + *
    +	 *   StringUtil.addMinusChar("20100901") = "2010-09-01"
    +	 * 
    + * + * @param date 입력받는 문자열 + * @return " - "가 추가된 입력문자열 + */ + public static String addMinusChar(String date) { + if (date.length() == 8) { + return date.substring(0, 4).concat("-").concat(date.substring(4, 6)).concat("-") + .concat(date.substring(6, 8)); + } else { + return ""; + } + } +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/util/EgovUserDetailsHelper.java b/mens-core/src/main/java/egovframework/com/cmm/util/EgovUserDetailsHelper.java new file mode 100644 index 0000000..6b0902f --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/util/EgovUserDetailsHelper.java @@ -0,0 +1,74 @@ +package egovframework.com.cmm.util; + +import java.util.ArrayList; +import java.util.List; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import egovframework.com.cmm.LoginVO; +import kr.xit.core.consts.Constants; +import kr.xit.core.support.utils.Checks; + +import org.egovframe.rte.fdl.string.EgovObjectUtil; + +/** + * EgovUserDetails Helper 클래스 + * + * @author sjyoon + * @since 2009.06.01 + * @version 1.0 + * @see + * + *
    + * << 개정이력(Modification Information) >>
    + *
    + *   수정일      수정자           수정내용
    + *  -------    -------------    ----------------------
    + *   2009.03.10  sjyoon    최초 생성
    + *   2011.08.31  JJY            경량환경 템플릿 커스터마이징버전 생성
    + *
    + * 
    + */ + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class EgovUserDetailsHelper { + + /** + * 인증된 사용자객체를 VO형식으로 가져온다. + * @return Object - 사용자 ValueObject + */ + public static Object getAuthenticatedUser() { + return (LoginVO)RequestContextHolder.currentRequestAttributes().getAttribute(Constants.AuthSaveSession.LOGIN_VO.getCode(), RequestAttributes.SCOPE_SESSION)==null ? + new LoginVO() : (LoginVO) RequestContextHolder.currentRequestAttributes().getAttribute(Constants.AuthSaveSession.LOGIN_VO.getCode(), RequestAttributes.SCOPE_SESSION); + + } + + /** + * 인증된 사용자의 권한 정보를 가져온다. + * 예) [ROLE_ADMIN, ROLE_USER, ROLE_A, ROLE_B, ROLE_RESTRICTED, IS_AUTHENTICATED_FULLY, IS_AUTHENTICATED_REMEMBERED, IS_AUTHENTICATED_ANONYMOUSLY] + * @return List - 사용자 권한정보 목록 + */ + public static List getAuthorities() { + List listAuth = new ArrayList<>(); + + if (Checks.isNull(RequestContextHolder.currentRequestAttributes().getAttribute(Constants.AuthSaveSession.LOGIN_VO.getCode(), RequestAttributes.SCOPE_SESSION))) { + return null; + } + + return listAuth; + } + + /** + * 인증된 사용자 여부를 체크한다. + * @return Boolean - 인증된 사용자 여부(TRUE / FALSE) + */ + public static Boolean isAuthenticated() { + if (Checks.isNull(RequestContextHolder.currentRequestAttributes().getAttribute(Constants.AuthSaveSession.LOGIN_VO.getCode(), RequestAttributes.SCOPE_SESSION))) { + return Boolean.FALSE; + } + return Boolean.TRUE; + } +} diff --git a/mens-core/src/main/java/egovframework/com/cmm/util/package-info.java b/mens-core/src/main/java/egovframework/com/cmm/util/package-info.java new file mode 100644 index 0000000..a970140 --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/util/package-info.java @@ -0,0 +1,10 @@ +/** + * egov framework util package classes + *

    + * + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package egovframework.com.cmm.util; diff --git a/mens-core/src/main/java/egovframework/com/cmm/web/EgovMultipartResolver.java b/mens-core/src/main/java/egovframework/com/cmm/web/EgovMultipartResolver.java new file mode 100644 index 0000000..1f0f1da --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/web/EgovMultipartResolver.java @@ -0,0 +1,150 @@ +//package egovframework.com.cmm.web; +// +///* +// * Copyright 2001-2006 The Apache Software Foundation. +// * +// * Licensed under the Apache License, Version 2.0 (the ";License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS"; BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +//import java.io.UnsupportedEncodingException; +//import java.util.HashMap; +//import java.util.Iterator; +//import java.util.List; +//import java.util.Map; +// +//import javax.servlet.ServletContext; +// +//import org.apache.commons.fileupload.FileItem; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +//import org.springframework.util.LinkedMultiValueMap; +//import org.springframework.util.MultiValueMap; +//import org.springframework.util.StringUtils; +//import org.springframework.web.multipart.MultipartFile; +//import org.springframework.web.multipart.commons.CommonsMultipartFile; +//import org.springframework.web.multipart.commons.CommonsMultipartResolver; +// +//import egovframework.com.cmm.util.EgovFileUploadUtil; +//import kr.xit.core.spring.util.SpringUtils; +// +///** +// * 실행환경의 파일업로드 처리를 위한 기능 클래스 +// * +// * @author 공통서비스개발팀 이삼섭 +// * @since 2009.06.01 +// * @version 1.0 +// * @see +// * +// *
    +// * << 개정이력(Modification Information) >>
    +// *
    +// *  수정일                수정자             수정내용
    +// *  ----------   --------    ---------------------------
    +// *  2009.03.25   이삼섭              최초 생성
    +// *  2011.06.11   서준식              스프링 3.0 업그레이드 API변경으로인한 수정
    +// *  2020.10.27   신용호              예외처리 수정
    +// *  2020.10.29   신용호              허용되지 않는 확장자 업로드 제한 (globals.properties > Globals.fileUpload.Extensions)
    +// *
    +// *      
    +// */ +//public class EgovMultipartResolver extends CommonsMultipartResolver { +// +// private static final Logger LOGGER = LoggerFactory.getLogger(EgovMultipartResolver.class); +// +// public EgovMultipartResolver() { +// } +// +// /** +// * 첨부파일 처리를 위한 multipart resolver를 생성한다. +// * +// * @param servletContext +// */ +// public EgovMultipartResolver(ServletContext servletContext) { +// super(servletContext); +// } +// +// /** +// * multipart에 대한 parsing을 처리한다. +// */ +// @Override +// protected MultipartParsingResult parseFileItems(List fileItems, String encoding) { +// +// // 스프링 3.0변경으로 수정한 부분 +// MultiValueMap multipartFiles = new LinkedMultiValueMap<>(); +// Map multipartParameters = new HashMap<>(); +// String whiteListFileUploadExtensions = SpringUtils.getPropertiesConfiguration().getString("Globals.fileUpload.Extensions"); +// Map mpParamContentTypes = new HashMap<>(); +// +// // Extract multipart files and multipart parameters. +// for (Iterator it = fileItems.iterator(); it.hasNext();) { +// FileItem fileItem = it.next(); +// +// if (fileItem.isFormField()) { +// +// String value; +// if (encoding != null) { +// try { +// value = fileItem.getString(encoding); +// } catch (UnsupportedEncodingException ex) { +// LOGGER.warn("Could not decode multipart item '{}' with encoding '{}': using platform default", +// fileItem.getFieldName(), encoding); +// value = fileItem.getString(); +// } +// } else { +// value = fileItem.getString(); +// } +// String[] curParam = multipartParameters.get(fileItem.getFieldName()); +// if (curParam == null) { +// // simple form field +// multipartParameters.put(fileItem.getFieldName(), new String[] { value }); +// } else { +// // array of simple form fields +// String[] newParam = StringUtils.addStringToArray(curParam, value); +// multipartParameters.put(fileItem.getFieldName(), newParam); +// } +// +// //contentType 입력 +// mpParamContentTypes.put(fileItem.getFieldName(), fileItem.getContentType()); +// } else { +// +// CommonsMultipartFile file = createMultipartFile(fileItem); +// multipartFiles.add(file.getName(), file); +// +// LOGGER.debug("Found multipart file [{" + file.getName() + "}] of size {" + file.getSize() +// + "} bytes with original filename [{" + file.getOriginalFilename() + "}], stored {" +// + file.getStorageDescription() + "}"); +// +// String fileName = file.getOriginalFilename(); +// String fileExtension = EgovFileUploadUtil.getFileExtension(fileName); +// LOGGER.debug("Found File Extension = "+fileExtension); +// if (StringUtils.isEmpty(whiteListFileUploadExtensions)) { +// LOGGER.debug("The file extension whitelist has not been set."); +// continue; +// } +// if (StringUtils.isEmpty(fileName)) { +// LOGGER.debug("No file name."); +// continue; +// } +// if ("".equals(fileExtension)) { // 확장자 없는 경우 처리 불가 +// throw new SecurityException("[No file extension] File extension not allowed."); +// } +// if ((whiteListFileUploadExtensions+".").contains("."+fileExtension.toLowerCase()+".")) { +// LOGGER.debug("File extension allowed."); +// } else { +// throw new SecurityException("["+fileExtension+"] File extension not allowed."); +// } +// } +// } +// +// return new MultipartParsingResult(multipartFiles, multipartParameters, mpParamContentTypes);//2022.01. Method call passes null for non-null parameter 처리 +// } +//} diff --git a/mens-core/src/main/java/egovframework/com/cmm/web/package-info.java b/mens-core/src/main/java/egovframework/com/cmm/web/package-info.java new file mode 100644 index 0000000..4dc7ada --- /dev/null +++ b/mens-core/src/main/java/egovframework/com/cmm/web/package-info.java @@ -0,0 +1,10 @@ +/** + * egov framework web package classes + *

    + * + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package egovframework.com.cmm.web; diff --git a/mens-core/src/main/java/kr/xit/biz/common/ApiConstants.java b/mens-core/src/main/java/kr/xit/biz/common/ApiConstants.java new file mode 100644 index 0000000..b8c9c48 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/biz/common/ApiConstants.java @@ -0,0 +1,168 @@ +package kr.xit.biz.common; + +/** + *
    + * description :
    + *
    + * packageName : kr.xit.ens.support.common
    + * fileName    : KakaoConstants
    + * author      : xitdev
    + * date        : 2023-05-04
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-05-04    xitdev       최초 생성
    + *
    + * 
    + */ +public class ApiConstants { + + /** + *
    +     * 문서 조회 버튼의 명칭을 구분하기 위한 값
    +     * code(실제 파라미터로 보내는 값) : button_name(내문서함 내부에 표기되는 버튼 명칭)- name(내문서함 내부에서 구분하기 위한 명칭)
    +     * Default : 문서확인 - Default
    +     * BILL : 문서확인 - 고지서
    +     * BILL_PAY : 문서확인후 납부 - 납부가 포함된 고지서
    +     * NOTICE : 문서확인 - 안내문
    +     * CONTRACT : 문서확인 - 계약서
    +     * REPORT : 문서확인 - 리포트
    +     * 
    + */ + public enum Categories { + DEFAULT("Default") + , BILL("BILL") + , BILL_PAY("BILL_PAY") + , NOTICE("NOTICE") + , CONTRACT("CONTRACT") + , REPORT("REPORT") + ; + + private final String code; + + Categories(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + } + + /** + *
    +     * INVALID_VALUE : http status code : 400
    +     *                 파라미터가 형식에 맞지않음 혹은 필수파라미터 누락
    +     *                 {"error_code": "INVALID_VALUE", "error_message": "유효하지 않은 값입니다."
    +     * UNIDENTIFIED_USER : http status code : 400
    +     *                     받는이의 정보로 발송대상을 특정 할 수 없을때
    +     *                     {"error_code": "INVALID_VALUE", "error_message": "유효하지 않은 값입니다."
    +     * UNAUTHORIZED : http status code : 401
    +     *                access token이 유효하지 않거나 잘못된 경우
    +     *                {"error_code": "UNAUTHORIZED","error_message": "접근 권한이 없습니다."
    +     * FORBIDDEN : http status code : 403
    +     *             문서의 대상자가 내문서함에서 수신거부를 한 경우
    +     *             {"error_code": "FORBIDDEN","error_message": "허용되지 않는 요청입니다. 수신거부된 사용자 입니다."}
    +     * NOT_FOUND : http status code : 404
    +     *             "Contract-Uuid" or "document_binder_uuid"가 유효하지 않거나 잘못된 경우
    +     *             {"error_code": "NOT_FOUND","error_message": "요청 정보를 찾을 수 없습니다."
    +     * INTERNAL_ERROR : http status code : 500
    +     *                  카카오페이 서버에러
    +     *                  {"error_code": "INTERNAL_SERVER_ERROR","error_message": "서버 에러입니다. 다시 시도해 주세요."}
    +     *  
    + * 카카오페이 전자문서 발송 요청 에러 코드 + */ + public enum Error { + INVALID_VALUE("INVALID_VALUE") + , UNIDENTIFIED_USER("UNIDENTIFIED_USER") + , UNAUTHORIZED("UNAUTHORIZED") + , FORBIDDEN("FORBIDDEN") + , NOT_FOUND("NOT_FOUND") + , INTERNAL_ERROR("INTERNAL_ERROR") + ; + + private final String code; + + Error(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + } + + /** + * 카카오페이 문서 상태 + * SENT(송신) > RECEIVED(수신) > READ(열람)/EXPIRED(미열람자료의 기한만료) + */ + public enum DocBoxStatus { + SENT("SENT") + , RECEIVED("RECEIVED") + , READ("READ") + , EXPIRED("EXPIRED") + ; + + private final String code; + + DocBoxStatus(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + } + + /** + * 발송처리상태 : ENS003 + */ + public enum SndngProcessStatus { + ACCEPT("accept"), + ACCETP_OK("accept-ok"), + ACCETP_FAIL("accept-fail"), + MAKE_OK("make-ok"), + MAKE_FAIL1("make-fail1"), + MAKE_FAIL2("make-fail2"), + MAKE_FAIL3("make-fail3"), + SENDING1("sending1"), + SENDING2("sending2"), + SEND_OK("send-ok"), + SEND_FAIL1("send-fail1"), + SEND_FAIL2("send-fail2"), + SEND_FAIL3("send-fail3"), + CLOSE("close") + + ; + + private final String code; + + SndngProcessStatus(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + } + + /** + * 발송구분코드 + */ + public enum SndngSeCode { + SMS("SMS"), + KAKAO_MY_DOC("KKO-MY-DOC"), + E_GREEN("E-GREEN") + ; + + private final String code; + + SndngSeCode(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + } +} diff --git a/mens-core/src/main/java/kr/xit/biz/ens/model/kakao/KkopayDocAttrDTO.java b/mens-core/src/main/java/kr/xit/biz/ens/model/kakao/KkopayDocAttrDTO.java new file mode 100644 index 0000000..b7ee3ab --- /dev/null +++ b/mens-core/src/main/java/kr/xit/biz/ens/model/kakao/KkopayDocAttrDTO.java @@ -0,0 +1,270 @@ +package kr.xit.biz.ens.model.kakao; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import javax.validation.Valid; +import javax.validation.constraints.Digits; +import javax.validation.constraints.Size; +import kr.xit.core.model.IApiResponse; +import kr.xit.biz.common.ApiConstants; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + *
    + * description : 카카오페이 전자문서 DTO
    + *
    + * packageName : kr.xit.biz.ens.model.kakao
    + * fileName    : KkopayDocAttrDTO
    + * author      : xitdev
    + * date        : 2023-05-03
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-05-03    xitdev       최초 생성
    + *
    + * 
    + */ +public class KkopayDocAttrDTO { + + //------------------- requestSend ------------------------------------------------------------------------------------------------ + @Schema(name = "Send", description = "RequestSend(문서발송 요청 파라메터)의 요청 파라메터 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Send implements IApiResponse { + /** + * 발송할 문서의 제목 : 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "발송할 문서의 제목", example = "문서 제목") + @Size(min = 1, max = 40, message = "문서제목은 필수입니다(max:40)") + private String title; + + /** + * 처리마감시간(절대시간) - 카카오톡 메시지를 수신한 사용자가 전자문서를 열람을 할 수 있는 시간 + * read_expired_sec 미전송시 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "처리마감시간(절대시간)", example = "1617202800") + @Digits(integer = 10, fraction = 0, message = "처리마감시간(절대시간:max=10자리)") + private Long read_expired_at; + + /** + * 처리마감시간(상대시간) - 권장값: 30일 (2592000 sec) + * read_expired_at 미전송시 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "처리마감시간(상대시간)", example = " ") + @Digits(integer = 8, fraction = 0, message = "처리마감시간(절대시간:max=8자리)") + private Integer read_expired_sec; + + /** + * 문서 원문(열람정보)에 대한 hash 값 - 공인전자문서 유통정보 등록 시 필수 + */ + @Schema(title = "문서 원문(열람정보)에 대한 hash 값", example = "6EFE827AC88914DE471C621AE") + @Size(max = 99, message = "문서 원문(열람정보)에 대한 hash 값(max=99)") + private String hash; + + /** + * 문서의 메타정보 - 향후 문서 검색을 위해서 활용될 메타 정보(현재 미 제공) + */ + @Schema(title = "문서의 메타정보(현재 미 제공)", example = "[\"NOTICE\"]") + private List common_categories; + + /** + * 받는이에 대한 정보 - 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private Receiver receiver; + + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private Property property; + } + + @Schema(name = "Receiver", description = "RequestSend(문서발송 요청 파라메터)의 receiver(받는이)에 대한 정보 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Receiver { + /** + * 받는이 CI + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "받는이 CI", example = " ") + @Size(max = 88, message = "받는이 CI(max=88)") + private String ci; + + /** + * 받는이 전화번호 + * ci 미전송시 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "받는이 전화번호", example = "01012345678") + @Size(max = 11, message = "받는이 전화번호(max=11)") + private String phone_number; + + /** + * 받는 이 이름 + * ci 미전송시 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "받는이 이름", example = "김페이") + @Size(max = 20, message = "받는이 이름(max=20)") + private String name; + + /** + * 받는 이 생년월일 (YYYYMMDD 형식) + * ci 미전송시 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "받는이 생년월일 (YYYYMMDD 형식)", example = "19801101") + //@Pattern(regexp = "^(19[0-9][0-9]|20\\d{2})(0[0-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])$") + @Size(max = 8, message = "받는이 생년월일(YYYYMMDD:max=8)") + private String birthday; + + /** + * 성명 검증 옵션 + * CI 전송 시 생략 가능 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "성명 검증 옵션", example = "false") + private Boolean is_required_verify_name; + } + + @Schema(name = "Property", description = "RequestSend(문서발송 요청 파라메터)의 property(문서속성)에 대한 정보 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Property { + /** + * 본인인증 후 사용자에게 보여줄 웹페이지 주소 : 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "본인인증 후 사용자에게 보여줄 웹페이지 주소", example = "http://ipAddress/api/kakaopay/v1/ott") + //@NotBlank + @Size(min = 10, max = 100, message = "본인인증후 사용자에게 보여줄 페이지 주소는 필수입니다(max=100)") + private String link; + + /** + * 고객센터 전화번호 : 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "고객센터 전화번호", example = "02-123-4567") + @Size(min = 10, max = 20, message = "고객센터 전화번호는 필수입니다(max=20)") + private String cs_number; + + /** + * 고객센터 전화번호 : 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "고객센터 명", example = "콜센터") + //@NotBlank + @Size(min = 1, max = 10, message = "고객센터명은 필수입니다(max=10)") + private String cs_name; + + /** + * 이용기관에서 해당 값을 다시 받고자 할 내용의 값 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "이용기관에서 해당 값을 다시 받고자 할 내용의 값", example = "payload 파라미터 입니다.") + @Size(max = 200, message = "이용기관에서 해당 값을 다시 받고자 할 내용의 값(max=200)") + private String payload; + + /** + * 메세지 - 사용자에게 전송하는 문서에 대한 설명 + * 노출위치 : 문서수신 메시지(알림톡)가 도착했음을 알리는 카카오톡 메시지 내부 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "사용자에게 전송하는 문서에 대한 설명", example = "해당 안내문은 다음과 같습니다.") + @Size(max = 500, message = "메세지(max=500)") + private String message; + } + //------------------------------------------------------------------------------------------------------------------- + + + @Schema(name = "DocumentBinderUuid DTO", description = "카카오페이 문서식별번호") + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class DocumentBinderUuid implements IApiResponse { + /** + * 카카오페이 문서식별번호(max:40) - 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "카카오페이 문서식별번호(max:40)", example = "BIN-ff806328863311ebb61432ac599d6150") + @Size(min = 1, max = 40, message = "카카오페이 문서식별번호는 필수입니다(max:40)") + private String document_binder_uuid; + } + + @Schema(name = "DocStatus DTO", description = "카카오페이 전자문서 상태 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class DocStatus implements IApiResponse { + /** + *
    +         * 진행상태(max:20) : 필수
    +         * 송신|수신|열람|만료
    +         * SENT|RECEIVED|READ|EXPIRED
    +         * SENT - 문서발송요청 성공(실 사용자에게 문서가 도달되지 않은 상태, 알림톡은 수신했으나 페이회원이 아니어서 실 문서 발송이 않됨)
    +         * RECEIVED - 사용자에 실문서 도달 완료
    +         * READ - OTT 검증 완료후 문서 상태 변경 API 호출에 성공한 상태
    +         * EXPIRED - 미열람 문서에 대한 열람 만료 시간이 지난 상태
    +         * 
    + * @see ApiConstants.DocBoxStatus + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, maxLength = 20, title = "진행상태(max:20)", example = " ") + @Size(min = 1, max = 20, message = "진행상태는 필수입니다(max:20)") + private ApiConstants.DocBoxStatus doc_box_status; + + /** + * 송신 시간(long max:10) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "송신 시간(max:10)", example = " ") + @Digits(integer = 10, fraction = 0, message = "송신시간(max:10)") + private Long doc_box_sent_at; + + /** + * 수신 시간(long max:10) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "수신 시간(max:10)", example = " ") + @Digits(integer = 10, fraction = 0, message = "수신시간(max:10)") + private Long doc_box_received_at; + + /** + * 열람인증성공 최초 시간(long max:10) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "열람인증성공 최초 시간(max:10)", example = " ") + @Digits(integer = 10, fraction = 0, message = "열람인증성공 최초시간(max:10)") + private Long authenticated_at; + + /** + * OTT 검증 성공 최초 시간(long max:10) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "OTT 검증 성공 최초 시간(max:10)", example = " ") + @Digits(integer = 10, fraction = 0, message = "OTT 검증 성공 최초 시간(max:10)") + private Long token_used_at; + + /** + * 최초 열람 시간(long max:10) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "최초 열람 시간(max:10)", example = " ") + @Digits(integer = 10, fraction = 0, message = "최초 열람 시간(max:10)") + private Long doc_box_read_at; + + /** + * 송신 시간(long max:10) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "알림톡 수신 시간(max:10)", example = " ") + @Digits(integer = 10, fraction = 0, message = "알림톡 수신 시간(max:10)") + private Long user_notified_at; + + /** + * 이용기관 생성 payload(max:200) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "이용기관 생성 payload(max:200)", example = " ") + @Size(max = 200, message = "이용기관 생성 payload(max:200)") + private String payload; + } +} diff --git a/mens-core/src/main/java/kr/xit/biz/ens/model/kakao/KkopayDocBulkDTO.java b/mens-core/src/main/java/kr/xit/biz/ens/model/kakao/KkopayDocBulkDTO.java new file mode 100644 index 0000000..80271b4 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/biz/ens/model/kakao/KkopayDocBulkDTO.java @@ -0,0 +1,183 @@ +package kr.xit.biz.ens.model.kakao; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import javax.validation.Valid; +import javax.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + *
    + * description :
    + *
    + * packageName : kr.xit.biz.ens.model.kakao
    + * fileName    : KkopayDocBulkDTO
    + * author      : limju
    + * date        : 2023-05-12
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-05-12    limju       최초 생성
    + *
    + * 
    + */ +public class KkopayDocBulkDTO extends KkopayDocAttrDTO { + + //------------ BulkSendRequests ----------------------------------------------------------------------------- + @Schema(name = "BulkSendRequests DTO", description = "문서발송(bulk) 요청 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class BulkSendRequests { + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private List documents; + } + + @Schema(name = "BulkSendReq", description = "문서발송(bulk) 요청 파라메터 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = true) + public static class BulkSendReq extends Send { + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private PropertyBulk property; + } + + @Schema(name = "PropertyBulk", description = "SendRequestBulk(문서발송 요청 파라메터)의 property(문서속성)에 대한 정보 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class PropertyBulk extends Property { + /** + * 문서 아이디(외부)(max=40) - 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "외부 문서 아이디(max=40)", example = "A000001") + @Size(min = 1, max = 40, message = "문서 아이디(외부)는 필수 입니다(max=40)") + private String external_document_uuid; + } + //---------------------------------------------------------------------------------- + + //------------------- BulkSendResponses --------------------------------------------------------------- + @Schema(name = "BulkSendResponses DTO", description = "문서발송(bulk) 응답 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class BulkSendResponses { + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private List documents; + } + + @Schema(name = "BulkSendRes DTO", description = "문서발송(bulk) 요청 결과 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class BulkSendRes { + /** + * 문서 아이디(외부)(max=40) - 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "외부 문서 아이디(max=40)", example = " ") + @Size(min = 1, max = 40) + private String external_document_uuid; + + /** + * 카카오페이 문서식별 번호(max:40) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "카카오페이 문서식별 번호(max:40)", example = " ") + @Size(max = 40) + private String document_binder_uuid; + + /** + * 에러 코드(max:40) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 코드(max:40)", example = " ") + @Size(max = 40) + private String error_code; + + /** + * 에러 메세지(max:500) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 메세지(max:500)", example = " ") + @Size(max = 500) + private String error_message; + } + //--------------------------------------------------------------------------------------- + + + //----------------- BulkStatusRequests ---------------------------------------------------------------------- + @Schema(name = "BulkStatusRequests DTO", description = "문서상태(bulk) 조회 요청 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class BulkStatusRequests { + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private List document_binder_uuids; + } + //-------------------------------------------------------------------------------------- + + //-------------- BulkStatusResponses ------------------------------------------------------------------------ + @Schema(name = "BulkStatusResponses DTO", description = "문서상태(bulk) 조회 응답 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class BulkStatusResponses { + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private List documents; + } + + @Schema(name = "BulkStatus DTO", description = "BulkStatusReponse()의 문서상태(bulk)에 대한 정보 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class BulkStatus { + /** + * 카카오페이 문서식별 번호(max:40) - 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "카카오페이 문서식별번호(max:40)", example = " ") + @Size(min = 1, max = 40, message = "카카오페이 문서식별번호는 필수입니다(max:40)") + private String document_binder_uuid; + + /** + * 에러 코드(max:40) + * @see kr.xit.biz.common.ApiConstants.Error + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 코드(max:40)", example = " ") + @Size(max = 40, message = "에러 코드(max:40)") + private String error_code; + + /** + * 에러 메세지(max:500) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 메세지(max:500)", example = " ") + @Size(max = 500, message = "에러 메세지(max:500)") + private String error_message; + + /** + * 문서 상태 - 성공시 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO) + @Valid + private DocStatus status_data; + } + //----------------------------------------------------------------------------------------------- +} diff --git a/mens-core/src/main/java/kr/xit/biz/ens/model/kakao/KkopayDocDTO.java b/mens-core/src/main/java/kr/xit/biz/ens/model/kakao/KkopayDocDTO.java new file mode 100644 index 0000000..24c89de --- /dev/null +++ b/mens-core/src/main/java/kr/xit/biz/ens/model/kakao/KkopayDocDTO.java @@ -0,0 +1,215 @@ +package kr.xit.biz.ens.model.kakao; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import javax.validation.Valid; +import javax.validation.constraints.Digits; +import javax.validation.constraints.Size; +import kr.xit.core.model.IApiResponse; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.SuperBuilder; +import org.hibernate.validator.constraints.Length; + +/** + *
    + * description : 카카오페이 전자문서 요청 DTO
    + *
    + * packageName : kr.xit.ens.model.kakao
    + * fileName    : KkopayDocDTO
    + * author      : xitdev
    + * date        : 2023-05-03
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-05-03    xitdev       최초 생성
    + *
    + * 
    + */ +public class KkopayDocDTO extends KkopayDocAttrDTO { + + //------------------ SendRequest ---------------------------------------------------------------------- + @Schema(name = "SendRequest DTO", description = "문서발송 요청 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class SendRequest { + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private RequestSend document; + } + + @Schema(name = "RequestSend", description = "문서발송 요청 파라메터 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + @EqualsAndHashCode(callSuper = true) + public static class RequestSend extends KkopayDocAttrDTO.Send { + /** + * 문서 속성 정보 - 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private Property property; + } + //------------------------------------------------------------------------------------- + //------------------ SendResponse ---------------------------------------------------------------------- + @Schema(name = "SendResponse DTO", description = "문서발송 응답 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @SuperBuilder + public static class SendResponse { + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "카카오페이 문서식별번호(max:40)", example = " ") + @Size(min = 1, max = 40) + private String document_binder_uuid; + + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 코드(max:40)", example = " ") + @Size(max = 40, message = "에러 코드(max:40)") + private String error_code; + + /** + * 에러 메세지(max:500) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 메세지(max:500)", example = " ") + @Size(max = 500, message = "에러 메세지(max:500)") + private String error_message; + } + //------------------------------------------------------------------------------------- + + //------------------- ValidTokenRequest ------------------------------------------------------------------ + @Schema(name = "ValidTokenRequest DTO", description = "카카오페이 전자문서 토큰 유효성 검증 파라메터 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class ValidTokenRequest extends DocumentBinderUuid{ + /** + * 카카오페이 전자문서 서버에서 생성한 토큰(max:50) : 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "카카오페이 전자문서 서버로 부터 받은 토큰(max:50)", example = "CON-cc375944ae3d11ecb91e42193199ee3c") + //@Size(min = 1, max = 50, message = "카카오페이 전자문서 서버 토큰은 필수입니다(max:50)") + @Length(min = 1, max = 50, message = "카카오페이 전자문서 서버 토큰은 필수입니다(max:50)") + private String token; + } + //----------------------------------------------------------------------------------------- + + //----------------- ValidTokenResponse ----------------------------------------------------------------------- + @Schema(name = "ValidTokenResponse DTO", description = "카카오페이 전자문서 토큰 유효성 검증 결과 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class ValidTokenResponse implements IApiResponse { + /** + * 토큰상태값(성공시 USED) : 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "토큰상태값(성공시 USED)", example = " ") + @Size(min = 1, max = 10, message = "토큰상태값은 필수입니다(성공:USED)") + private String token_status; + + /** + * 토큰 만료일시(max:20) : 필수 + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "토큰 만료일시(max:20)", example = " ") + @Digits(integer = 20, fraction = 0, message = "토큰 만료일시는 필수입니다(max:20)") + private Long token_expires_at; + + /** + * 토큰 사용일시(long max:20) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "토큰 사용일시(max:20)", example = " ") + @Digits(integer = 20, fraction = 0, message = "토큰 사용일시는 필수입니다(max:20)") + private Long token_used_at; + + /** + * 송신시간(long max:20) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "송신시간(max:20)", example = " ") + @Digits(integer = 20, fraction = 0, message = "송신시간(max:20)") + private Long doc_box_sent_at; + + /** + * 수신시간(long max:20) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "수신시간(max:20)", example = " ") + @Digits(integer = 20, fraction = 0, message = "수신시간(max:20)") + private Long doc_box_received_at; + + /** + * 열람인증을 성공한 최초 시간(long max:20) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "열람인증을 성공한 최초 시간(max:20)", example = " ") + @Digits(integer = 20, fraction = 0, message = "열람인증 최초 시간(max:20)") + private Long authenticated_at; + + /** + * 토큰 사용일시(long max:20) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "알림톡 수신 시간(max:20)", example = " ") + @Digits(integer = 20, fraction = 0, message = "알림톡 수신 시간(max:20)") + private Long user_notified_at; + + /** + * 이용기관 생성 payload(max:200) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "이용기관 생성 payload(max:200)", example = " ") + @Size(max = 200, message = "이용기관 생성 payload(max:200)") + private String payload; + + /** + * 토큰 서명일시(long max:20) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "토큰 서명일시(max:20)", example = " ") + @Digits(integer = 20, fraction = 0, message = "토큰 서명일시(max:20)") + private Long signed_at; + + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 코드(max:40)", example = " ") + @Size(max = 40, message = "에러 코드(max:40)") + private String error_code; + + /** + * 에러 메세지(max:500) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 메세지(max:500)", example = " ") + @Size(max = 500, message = "에러 메세지(max:500)") + private String error_message; + } + //----------------------------------------------------------------------------------------------------- + + + //----------------------- OneTimeToken ----------------------------------------------------------------- + @Schema(name = "OneTimeToken DTO", description = "카카오페이 OTT 요청 파라메터 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + @ToString + @JsonInclude(JsonInclude.Include.NON_NULL) + @EqualsAndHashCode(callSuper = true) + public static class OneTimeToken extends ValidTokenRequest { + /** + * 문서 식별 번호(외부 - max:40) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "외부 문서 식별 번호(max:40)", example = " ") + @Length(max = 40, message = "외부 문서 식별 번호(max:40)") + private String external_document_uuid; + } + //----------------------------------------------------------------------------------------------- + + //----------------- DocStatusResponse ------------------------------------------------------------------------------ + @Schema(name = "DocStatusResponse DTO", description = "카카오페이 전자문서 상태 응답 DTO") + @Data + @SuperBuilder + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class DocStatusResponse extends DocStatus { + } +} diff --git a/mens-core/src/main/java/kr/xit/core/consts/Constants.java b/mens-core/src/main/java/kr/xit/core/consts/Constants.java new file mode 100644 index 0000000..da1a9ac --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/consts/Constants.java @@ -0,0 +1,119 @@ +package kr.xit.core.consts; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + *
    + * description : 상수 정의
    + * packageName : kr.xit.core.const
    + * fileName    : Constants
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +public class Constants { + public static final String API_URL_PATTERNS = "/*"; + public static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8; + public static final String DEFAULT_VIEW = "mappingJackson2JsonView"; + + public static final String PRIMARY_DATA_SOURCE = "primaryDataSource"; + public static final String SECONDARY_DATA_SOURCE = "secondaryDataSource"; + public static final String PRIMARY_SQL_SESSION = "primarySqlSession"; + public static final String SECONDARY_SQL_SESSION = "secondarySqlSession"; + + /** + * 인증정보 저장(세션) + */ + public enum AuthSaveSession { + LOGIN_VO("LoginVO") + ; + + private final String code; + + AuthSaveSession(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + } + + /** + * 인증정보 저장 방식 + * + */ + public enum AuthSaveType { + SECURITY("security"), // SessionCreationPolicy.STATELESS인 경우는 SecurityContext 사용불가 + SESSION("session"), // TOKEN도 사용 가능은 하지만... + HEADER("header"); // TOKEN + + private final String code; + + AuthSaveType(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + } + + public enum JwtToken { + // 토큰헤더명, + HEADER_NAME("Authorization"), + GRANT_TYPE("Bearer"), + ACCESS_TOKEN_NAME("accessToken"), + REFRESH_TOKEN_NAME("refreshToken"), + AUTHORITIES_KEY("role"), + TOKEN_USER_NAME("userName"), + TOKEN_USER_MAIL("userEmail"), + TOKEN_USER_ID("userId"); + + private final String code; + + JwtToken(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + } + + /** + * JWT Token 통신 방식 지정 + * COOKIE : accessToken - header, refreshToken - cookie + * HEADER : accessToken - header, refreshToken - DTO + * DTO : accessToken - header, refreshToken - DTO + */ + public enum JwtTokenParamType { + COOKIE, + HEADER, + DTO + } + + /** + * 카카오페이 전자문서 요청 헤더 필드명 + */ + public enum HeaderName { + TOKEN("Authorization"), //Token + UUID("Contract-Uuid"), //Contract-Uuid + ; // TOKEN + + private final String code; + + HeaderName(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + } +} diff --git a/mens-core/src/main/java/kr/xit/core/consts/ErrorCode.java b/mens-core/src/main/java/kr/xit/core/consts/ErrorCode.java new file mode 100644 index 0000000..46e92fa --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/consts/ErrorCode.java @@ -0,0 +1,115 @@ +package kr.xit.core.consts; + +import org.egovframe.rte.fdl.cmmn.exception.BaseRuntimeException; +import org.springframework.http.HttpStatus; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + *
    + * description : HttpStatus 기준 에러 코드 정의
    + * packageName : kr.xit.core.const
    + * fileName    : ErrorCode
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see HttpStatus + */ +@Getter +@RequiredArgsConstructor +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum +ErrorCode { + + /* +200 : OK, 요청 정상 처리 +201 : Created, 생성 요청 성공 +202 : Accepted, 비동기 요청 성공 +204 : No Content, 요청 정상 처리, 응답 데이터 없음. + +실패 +400 : Bad Request, 요청이 부적절 할 때, 유효성 검증 실패, 필수 값 누락 등. +401 : Unauthorized, 인증 실패, 로그인하지 않은 사용자 또는 권한 없는 사용자 처리 +402 : Payment Required +403 : Forbidden, 인증 성공 그러나 자원에 대한 권한 없음. 삭제, 수정시 권한 없음. +404 : Not Found, 요청한 URI에 대한 리소스 없을 때 사용. +405 : Method Not Allowed, 사용 불가능한 Method를 이용한 경우. +406 : Not Acceptable, 요청된 리소스의 미디어 타입을 제공하지 못할 때 사용. +408 : Request Timeout +409 : Conflict, 리소스 상태에 위반되는 행위 시 사용. +413 : Payload Too Large +423 : Locked +428 : Precondition Required +429 : Too Many Requests + +500 : 서버 에러 + + */ + + + BAD_REQUEST(HttpStatus.BAD_REQUEST, "요청 매개변수 오류 입니다."), + NOT_FOUND(HttpStatus.NOT_FOUND, "잘못된 요청 입니다"), + FILE_NOT_FOUND(HttpStatus.NOT_FOUND, "파일이 존재하지 않습니다"), + DATA_NOT_FOUND(HttpStatus.NOT_FOUND, "처리 데이타 오류(처리 요청 데이타 미존재)"), + + /* 400 BAD_REQUEST : 잘못된 요청 */ + CANNOT_FOLLOW_MYSELF(HttpStatus.BAD_REQUEST, "자기 자신은 팔로우 할 수 없습니다"), + + /* 401 UNAUTHORIZED : 인증되지 않은 사용자 */ + INVALID_AUTH_TOKEN(HttpStatus.FORBIDDEN, "인가된 사용자가 아닙니다"), + UN_AUTHORIZED_USER(HttpStatus.UNAUTHORIZED, "계정 정보가 존재하지 않습니다"), + AUTH_HEADER_NOT_EXISTS(HttpStatus.UNAUTHORIZED, "헤더에 인증 정보를 찾을 수 없습니다"), + LOGOUT_USER(HttpStatus.UNAUTHORIZED, "로그아웃된 사용자 입니다"), + NOT_EXISTS_SECURITY_AUTH(HttpStatus.UNAUTHORIZED, "Security Context 에 인증 정보가 없습니다"), + + NOT_EXISTS_TOKEN(HttpStatus.UNAUTHORIZED, "인증된 토큰이 없습니다"), + NOT_EXISTS_SAVED_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "저장된 인증 토큰이 없습니다"), + INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "유효한 토큰이 아닙니다"), + INVALID_ROLE_TOKEN(HttpStatus.UNAUTHORIZED, "사용 권한이 없는 토큰 입니다"), + EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "유효기간이 경과된 토큰 입니다"), + INVALID_SIGN_TOKEN(HttpStatus.UNAUTHORIZED, "잘못된 서명의 토큰 입니다"), + INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "리프레시 토큰이 유효하지 않습니다"), + MISMATCH_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "리프레시 토큰의 유저 정보가 일치하지 않습니다"), + MISMATCH_REFRESH_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "발급된 refresh token의 정보가 일치하지 않습니다"), + NOT_EXPIRED_TOKEN_YET(HttpStatus.UNAUTHORIZED, "토큰 유효기간이 경과되지 않았습니다"), + + /* 404 NOT_FOUND : Resource 를 찾을 수 없음 */ + USER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자 정보를 찾을 수 없습니다"), + REFRESH_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "로그아웃 된 사용자입니다"), + + /* 409 CONFLICT : Resource 의 현재 상태와 충돌. 보통 중복된 데이터 존재 */ + DUPLICATE_RESOURCE(HttpStatus.CONFLICT, "데이터가 이미 존재합니다"), + MEMBER_EXISTS(HttpStatus.CONFLICT, "가입되어 있는 회원 입니다"), + + // JPA query error + SQL_DATA_RESOURCE_INVALID(HttpStatus.CONFLICT, "SQL 오류(데이터가 이미 존재합니다)"), + + MPOWER_CONNECT_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "MPower DB 접속 에러 입니다"), + MPOWER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "MPower DB 에러 입니다"), + + FORBIDDEN(HttpStatus.FORBIDDEN, "FORBIDDEN"), + INVALID_CODE(HttpStatus.BAD_REQUEST, "유효하지 않은 HttpStatus 상태 코드 입니다"), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "INTERNAL_SERVER_ERROR"), + + MISMATCH_PASSWORD(HttpStatus.BAD_REQUEST, "비밀 번호가 일치하지 않습니다."), + + CONNECT_TIMEOUT(HttpStatus.REQUEST_TIMEOUT, "서버 접속 에러입니다(Connect timeout)") + ; + + + private HttpStatus httpStatus; + private String message; + + ErrorCode(HttpStatus httpStatus, String message){ + this.httpStatus = httpStatus; + this.message = message; + } +} diff --git a/mens-core/src/main/java/kr/xit/core/consts/package-info.java b/mens-core/src/main/java/kr/xit/core/consts/package-info.java new file mode 100644 index 0000000..77b636e --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/consts/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework constant package classes + *

    + * + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.consts; diff --git a/mens-core/src/main/java/kr/xit/core/exception/BizRuntimeException.java b/mens-core/src/main/java/kr/xit/core/exception/BizRuntimeException.java new file mode 100644 index 0000000..3d08921 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/exception/BizRuntimeException.java @@ -0,0 +1,192 @@ +package kr.xit.core.exception; + +import java.util.Locale; + +import org.egovframe.rte.fdl.cmmn.exception.BaseRuntimeException; +import org.springframework.context.MessageSource; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +import kr.xit.core.consts.ErrorCode; +import kr.xit.core.spring.annotation.SecurityPolicy; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +/** + *
    + * description : egov BaseRuntimeException 상속
    + * packageName : kr.xit.core.exception
    + * fileName    : BizRuntimeException
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see BaseRuntimeException + */ +@Getter +@Setter +@Slf4j +@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "Invalid parameter") +public class BizRuntimeException extends BaseRuntimeException { + private String code; + private ErrorCode errorCode; + + /** + * BizRuntimeException 생성자. + * @param errorCode ErrorCode 지정 + */ + public static BizRuntimeException create(ErrorCode errorCode) { + return new BizRuntimeException(errorCode); + } + + /** + * BizRuntimeException 생성자. + * @param errorCode ErrorCode 지정 + */ + private BizRuntimeException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.code = String.valueOf(errorCode.getHttpStatus().value()); + this.errorCode = errorCode; + } + + /** + * BizRuntimeException 생성자. + * @param defaultMessage 메세지 지정 + */ + public static BizRuntimeException create(String defaultMessage) { + return new BizRuntimeException(defaultMessage, null, null); + } + + /** + * BizRuntimeException 생성자. + * @param code + * @param defaultMessage 메세지 지정 + */ + public static BizRuntimeException create(String code, String defaultMessage) { + BizRuntimeException e = new BizRuntimeException(); + e.setCode(code); + e.setMessage(defaultMessage); + return e; + } + + /** + * BizRuntimeException 생성자. + * @param wrappedException 원인 Exception + */ + public static BizRuntimeException create(Throwable wrappedException) { + return new BizRuntimeException("BizRuntimeException without message", null, wrappedException); + } + + /** + * BizRuntimeException 생성자. + * @param defaultMessage 메세지 지정 + * @param wrappedException 원인 Exception + */ + public static BizRuntimeException create(String defaultMessage, Throwable wrappedException) { + return new BizRuntimeException(defaultMessage, null, wrappedException); + } + + /** + * BizRuntimeException 생성자. + * @param defaultMessage 메세지 지정(변수지정) + * @param messageParameters 치환될 메세지 리스트 + * @param wrappedException 원인 Exception + */ + private static BizRuntimeException create(String defaultMessage, Object[] messageParameters, Throwable wrappedException) { + return new BizRuntimeException(defaultMessage, messageParameters, wrappedException); + } + + /** + * BizRuntimeException 기본 생성자. + */ + private BizRuntimeException() { + this("BizRuntimeException without message", null, null); + } + + /** + * BizRuntimeException 생성자. + * @param defaultMessage 메세지 지정(변수지정) + * @param messageParameters 치환될 메세지 리스트 + * @param wrappedException 원인 Exception + */ + private BizRuntimeException(String defaultMessage, Object[] messageParameters, Throwable wrappedException) { + super(defaultMessage, messageParameters, wrappedException); + } + + /** + * BizRuntimeException 생성자. + * @param messageSource 메세지 리소스 + * @param messageKey 메세지키값 + */ + public static BizRuntimeException create(MessageSource messageSource, String messageKey) { + return new BizRuntimeException(messageSource, messageKey, null, null, Locale.getDefault(), null); + } + + /** + * BizRuntimeException 생성자. + * @param messageSource 메세지 리소스 + * @param messageKey 메세지키값 + */ + public static BizRuntimeException create(MessageSource messageSource, String messageKey, Throwable wrappedException) { + return new BizRuntimeException(messageSource, messageKey, null, null, Locale.getDefault(), wrappedException); + } + + /** + * BizRuntimeException 생성자. + * @param messageSource 메세지 리소스 + * @param messageKey 메세지키값 + * @param locale 국가/언어지정 + * @param wrappedException 원인 Exception + */ + public static BizRuntimeException create(MessageSource messageSource, String messageKey, Locale locale, Throwable wrappedException) { + return new BizRuntimeException(messageSource, messageKey, null, null, locale, wrappedException); + } + + /** + * BizRuntimeException 생성자. + * @param messageSource 메세지 리소스 + * @param messageKey 메세지키값 + * @param messageParameters 치환될 메세지 리스트 + * @param locale 국가/언어지정 + * @param wrappedException 원인 Exception + */ + public static BizRuntimeException create(MessageSource messageSource, String messageKey, Object[] messageParameters, Locale locale, Throwable wrappedException) { + return new BizRuntimeException(messageSource, messageKey, messageParameters, null, locale, wrappedException); + } + + /** + * BizRuntimeException 생성자. + * @param messageSource 메세지 리소스 + * @param messageKey 메세지키값 + * @param messageParameters 치환될 메세지 리스트 + * @param wrappedException 원인 Exception + */ + public static BizRuntimeException create(MessageSource messageSource, String messageKey, Object[] messageParameters, Throwable wrappedException) { + return new BizRuntimeException(messageSource, messageKey, messageParameters, null, Locale.getDefault(), wrappedException); + } + + /** + * BizRuntimeException 생성자. + * @param messageSource 메세지 리소스 + * @param messageKey 메세지키값 + * @param messageParameters 치환될 메세지 리스트 + * @param defaultMessage 메세지 지정(변수지정) + * @param wrappedException 원인 Exception + */ + public static BizRuntimeException create(MessageSource messageSource, String messageKey, Object[] messageParameters, String defaultMessage, Throwable wrappedException) { + return new BizRuntimeException(messageSource, messageKey, messageParameters, defaultMessage, Locale.getDefault(), wrappedException); + } + + public BizRuntimeException(MessageSource messageSource, String messageKey, Object[] messageParameters, String defaultMessage, Locale locale, Throwable wrappedException) { + super(messageSource, messageKey, messageParameters, defaultMessage, locale, wrappedException); + } + + public HttpStatus getHttpStatus(){ + return HttpStatus.BAD_REQUEST; + } +} diff --git a/mens-core/src/main/java/kr/xit/core/exception/package-info.java b/mens-core/src/main/java/kr/xit/core/exception/package-info.java new file mode 100644 index 0000000..977eb81 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/exception/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework Exception package classes + *

    + * + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.exception; diff --git a/mens-core/src/main/java/kr/xit/core/model/ApiResponseDTO.java b/mens-core/src/main/java/kr/xit/core/model/ApiResponseDTO.java new file mode 100644 index 0000000..69a0f42 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/model/ApiResponseDTO.java @@ -0,0 +1,261 @@ +package kr.xit.core.model; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import org.apache.commons.lang.StringUtils; +import org.egovframe.rte.ptl.mvc.tags.ui.pagination.PaginationInfo; +import org.springframework.http.HttpStatus; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; + +import com.google.gson.GsonBuilder; + +import io.swagger.v3.oas.annotations.media.Schema; +import kr.xit.core.consts.ErrorCode; +import kr.xit.core.exception.BizRuntimeException; +import kr.xit.core.support.utils.Checks; +import kr.xit.core.support.utils.ConvertHelper; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + *
    + * description : Api 응답
    + * packageName : kr.xit.core.model
    + * fileName    : ApiResponseDTO
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +@Schema(name = "ApiResponseDTO", description = "Restful API 결과") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ApiResponseDTO implements Serializable { + private static final String FAIL_STATUS = "fail"; + private static final String ERROR_STATUS = "error"; + + @Schema(example = "true", description = "에러인 경우 false", requiredMode = Schema.RequiredMode.REQUIRED) + private boolean success; + + @Schema(example = " ", description = "HttpStatus.OK", requiredMode = Schema.RequiredMode.REQUIRED) + private String code; + + @Schema(description = "결과 데이타, 오류시 null", example = " ") + private T data; + + @Schema(description = "오류 발생시 오류 메세지", example = " ", requiredMode = Schema.RequiredMode.AUTO) + @Setter + private String message; + + @Schema(example = " ", description = "HttpStatus.OK", requiredMode = Schema.RequiredMode.AUTO) + private HttpStatus httpStatus; + + @Schema(description = "API 실행 결과 데이타 수") + private int count; + + @Schema(description = "페이징 정보 - 결과값이 Collection type인 경우 사용됨", example = " ") + private PaginationInfo paginationInfo; + + /** + * 비동기 정상 데이타 ApiResponseDTO return + * @param future CompletableFuture + * @return ApiResponseDTO + */ + public static ApiResponseDTO of(final CompletableFuture future) { + try { + if(future.get() instanceof ApiResponseDTO) return (ApiResponseDTO)future.get(); + return new ApiResponseDTO(true, future.get(), String.valueOf(HttpStatus.OK.value()), HttpStatus.OK.name(), HttpStatus.OK); + + } catch (InterruptedException ie){ + // thread pool에 에러 상태 전송 + Thread.currentThread().interrupt(); + throw BizRuntimeException.create(ie); + + } catch (ExecutionException ee) { + throw BizRuntimeException.create(ee); + } + } + + /** + * 정상 데이타 ApiResponseDTO return + * @return ApiResponseDTO + */ + public static ApiResponseDTO success() { + return new ApiResponseDTO<>(true, null, String.valueOf(HttpStatus.OK.value()), HttpStatus.OK.name(), HttpStatus.OK); + } + + /** + * 정상 데이타 ApiResponseDTO return + * @param data T + * @return ApiResponseDTO + */ + public static ApiResponseDTO success(T data) { + return new ApiResponseDTO<>(true, data, String.valueOf(HttpStatus.OK.value()), HttpStatus.OK.name(), HttpStatus.OK); + } + + + /** + * 정상 데이타 ApiResponseDTO return + * @param httpStatus HttpStatus + * @return ApiResponseDTO + */ + public static ApiResponseDTO success(final HttpStatus httpStatus) { + return new ApiResponseDTO<>(true, null, String.valueOf(httpStatus.value()), httpStatus.name(), httpStatus); + } + + /** + * 정상 데이타 ApiResponseDTO return + * @param data T 데이타 + * @param httpStatus HttpStatus + * @return ApiResponseDTO + */ + public static ApiResponseDTO success(final T data, final HttpStatus httpStatus) { + return new ApiResponseDTO<>(true, data, String.valueOf(httpStatus.value()), httpStatus.name(), httpStatus); + } + + /** + * 정상 return - body empty + * @return ApiResponseDTO + */ + public static ApiResponseDTO empty() { + return new ApiResponseDTO<>(true, null, String.valueOf(HttpStatus.NO_CONTENT.value()), HttpStatus.NO_CONTENT.name(), HttpStatus.OK); + } + + /** + * Error ApiResponseDTO return + * Hibernate Validator에 의해 유효하지 않은 데이터로 인해 API 호출이 거부될때 반환 + * @param bindingResult BindingResult + * @return ApiResponseDTO + */ + public static ApiResponseDTO error(final BindingResult bindingResult) { + Map errors = new HashMap<>(); + + List allErrors = bindingResult.getAllErrors(); + for (ObjectError error : allErrors) { + if (error instanceof FieldError) { + errors.put(((FieldError) error).getField(), error.getDefaultMessage()); + } else { + errors.put( error.getObjectName(), error.getDefaultMessage()); + } + } + return new ApiResponseDTO<>(false, null, FAIL_STATUS, errors.toString(), null); + } + + /** + * Error ApiResponseDTO return + * @param message 에러 메세지 + * @return ApiResponseDTO + */ + public static ApiResponseDTO error(final String message) { + return new ApiResponseDTO<>(false, null, ERROR_STATUS, message, null); + } + + /** + * Error ApiResponseDTO return + * @param errorCode ErrorCode + * @return ApiResponseDTO + */ + public static ApiResponseDTO error(final ErrorCode errorCode) { + return new ApiResponseDTO<>(false, null, errorCode.name(), errorCode.getMessage(), errorCode.getHttpStatus()); + } + + /** + * Error ApiResponseDTO return + * @param e BizRuntimeException + * @return ApiResponseDTO + */ + public static ApiResponseDTO error(final BizRuntimeException e) { + + if (Checks.isNotEmpty(e.getErrorCode())) { + return ApiResponseDTO.error(e.getErrorCode()); + } + return new ApiResponseDTO<>( + false, + null, + org.apache.commons.lang3.StringUtils.defaultString(e.getCode(), org.apache.commons.lang3.StringUtils.EMPTY), + org.apache.commons.lang3.StringUtils.defaultString(e.getMessage(), org.apache.commons.lang3.StringUtils.EMPTY), + e.getHttpStatus()); + } + + /** + * Error ApiResponseDTO return + * @param code 에러코드 + * @param message 에러메세지 + * @return ApiResponseDTO + */ + public static ApiResponseDTO error(final String code, final String message) { + return new ApiResponseDTO<>(false, null, code, message, null); + } + + /** + * Error ApiResponseDTO return + * @param code 에러코드 + * @param message 에러메세지 + * @param httpStatus HttpStatus + * @return ApiResponseDTO + */ + public static ApiResponseDTO error(final String code, final String message, HttpStatus httpStatus) { + return new ApiResponseDTO<>(false, null, code, message, httpStatus); + } + + /** + * + * @param success true|false + * @param data data + * @param code 에러코드 + * @param message 메세지(에러 발생시 필수) + * @param httpStatus HttpStatus + */ + private ApiResponseDTO(final boolean success, final T data, final String code, final String message, final HttpStatus httpStatus) { + this.success = success; + this.data = data; + this.code = code; + this.message = message; + this.httpStatus = httpStatus; + + if(data == null){ + this.count = 0; + + }else { + + if (Collection.class.isAssignableFrom(data.getClass())) { + this.count = (((Collection) data).size()); + + } else { + this.count = 1; + } + } + + if(httpStatus == null){ + if(!success) this.httpStatus = HttpStatus.BAD_REQUEST; + else this.httpStatus = HttpStatus.OK; + } + } + + @Override + public String toString() { + // value가 null값인 경우도 생성 + GsonBuilder builder = new GsonBuilder().serializeNulls(); + builder.disableHtmlEscaping(); + return builder.setPrettyPrinting().create().toJson(this); + } + + public String convertToJson() { + return ConvertHelper.jsonToObject(this); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/model/ErrorDTO.java b/mens-core/src/main/java/kr/xit/core/model/ErrorDTO.java new file mode 100644 index 0000000..ac62c8e --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/model/ErrorDTO.java @@ -0,0 +1,49 @@ +package kr.xit.core.model; + +import javax.validation.constraints.Size; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + *
    + * description :
    + *
    + * packageName : kr.xit.core.model
    + * fileName    : ErrorDTO
    + * author      : limju
    + * date        : 2023-06-01
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-06-01    limju       최초 생성
    + *
    + * 
    + */ +@Schema(name = "ErrorDTO", description = "에러 DTO") +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class ErrorDTO implements IApiResponse { + /** + * 에러 코드(max:40) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 코드(max:40)", example = " ") + @Size(max = 40) + @JsonProperty("error_code") + private String errorCode; + + /** + * 에러 메세지(max:500) + */ + @Schema(requiredMode = Schema.RequiredMode.AUTO, title = "에러 메세지(max:500)", example = " ") + @Size(max = 500) + @JsonProperty("error_message") + private String errorMessage; +} diff --git a/mens-core/src/main/java/kr/xit/core/model/IApiResponse.java b/mens-core/src/main/java/kr/xit/core/model/IApiResponse.java new file mode 100644 index 0000000..f192bf6 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/model/IApiResponse.java @@ -0,0 +1,19 @@ +package kr.xit.core.model; + +/** + *
    + * description :
    + *
    + * packageName : kr.xit.core.model
    + * fileName    : IApiResponse
    + * author      : limju
    + * date        : 2023-05-31
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-05-31    limju       최초 생성
    + *
    + * 
    + */ +public interface IApiResponse { +} diff --git a/mens-core/src/main/java/kr/xit/core/model/package-info.java b/mens-core/src/main/java/kr/xit/core/model/package-info.java new file mode 100644 index 0000000..bcc6f32 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/model/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework model package classes + *

    + * + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.model; diff --git a/mens-core/src/main/java/kr/xit/core/package-info.java b/mens-core/src/main/java/kr/xit/core/package-info.java new file mode 100644 index 0000000..e854c31 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework package classes + *

    + * + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core; diff --git a/mens-core/src/main/java/kr/xit/core/spring/RequestBodyControllerAdvice.java b/mens-core/src/main/java/kr/xit/core/spring/RequestBodyControllerAdvice.java new file mode 100644 index 0000000..e7d9a1f --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/RequestBodyControllerAdvice.java @@ -0,0 +1,78 @@ +package kr.xit.core.spring; + +import java.lang.reflect.Type; + +import org.apache.ibatis.session.RowBounds; +import org.springframework.core.MethodParameter; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice; + +import lombok.extern.slf4j.Slf4j; + +/** + *
    + * description : RequestBodyAdvice는 요청 body를 커스터마이징
    + *               supports: 해당 RequestBodyAdvice를 적용할지 여부를 결정함
    + *               beforeBodyRead: body를 읽어 객체로 변환되기 전에 호출됨
    + *               afterBodyRead: body를 읽어 객체로 변환된 후에 호출됨
    + *               handleEmptyBody: body가 비어있을때 호출됨
    + * packageName : kr.xit.core.spring
    + * fileName    : equestBodyControllerAdvice
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +//TODO:: RequestBody cutomizing +//@RestControllerAdvice +@Slf4j +public class RequestBodyControllerAdvice implements RequestBodyAdvice { + + @Override + public boolean supports( + final MethodParameter methodParameter, + final Type targetType, + final Class> converterType) { + log.debug("{}", methodParameter.getParameterName()); + log.debug("{}", targetType); + log.debug("{}", converterType); + return methodParameter.getContainingClass() == RowBounds.class && targetType.getTypeName().equals(RowBounds.class.getTypeName()); + } + + @Override + public HttpInputMessage beforeBodyRead( + final HttpInputMessage inputMessage, + final MethodParameter parameter, + final Type targetType, + final Class> converterType) { + return inputMessage; + } + + @Override + public Object afterBodyRead( + final Object body, + final HttpInputMessage inputMessage, + final MethodParameter parameter, + final Type targetType, + final Class> converterType) { + log.debug("{}", body); + return body; + } + + @Override + public Object handleEmptyBody( + final Object body, + final HttpInputMessage inputMessage, + final MethodParameter parameter, + final Type targetType, + final Class> converterType) { + return body; + } + +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/annotation/Permission.java b/mens-core/src/main/java/kr/xit/core/spring/annotation/Permission.java new file mode 100644 index 0000000..7bacf8e --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/annotation/Permission.java @@ -0,0 +1,26 @@ +package kr.xit.core.spring.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *
    + * description : 권한 타입 체크 annotation
    + * packageName : kr.xit.core.spring.annotation
    + * fileName    : Permission
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +public @interface Permission { + String[] types() default {}; +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/annotation/Secured.java b/mens-core/src/main/java/kr/xit/core/spring/annotation/Secured.java new file mode 100644 index 0000000..5031805 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/annotation/Secured.java @@ -0,0 +1,28 @@ +package kr.xit.core.spring.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *
    + * description : 인증 정책 정의 annotation
    + *               Secured(policy = SecurityPolicy.DEFAUTL|TOKEN|SESSION|COOKIE|NONE)
    + * packageName : kr.xit.core.spring.annotation
    + * fileName    : Secured
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see SecurityPolicy + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(value = RetentionPolicy.RUNTIME) +public @interface Secured { + SecurityPolicy policy() default SecurityPolicy.DEFAULT; +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/annotation/SecurityPolicy.java b/mens-core/src/main/java/kr/xit/core/spring/annotation/SecurityPolicy.java new file mode 100644 index 0000000..2243a80 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/annotation/SecurityPolicy.java @@ -0,0 +1,43 @@ +package kr.xit.core.spring.annotation; + +/** + *
    + * description : 인증 정책 청의 enum
    + * packageName : kr.xit.core.spring.annotation
    + * fileName    : SecurityPolicy
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see Secured + */ +public enum SecurityPolicy { + /** + * 리소스의 아이피, 권한 등 기본 검증 처리 + */ + DEFAULT, + + /** + * 접근한 리소스의 토큰 기반 사용자가 접근할 수 있는 정책 적용 + */ + TOKEN, + + /** + * 접근한 리소스에 세션 기반 인증 사용자가 접근할 수 있는 정책 적용 + */ + SESSION, + + /** + * 접근한 리소스에 쿠키 기반 인증 사용자가 접근할 수 있는 정책 적용 + */ + COOKIE, + + /** + * 보안 검사를 수행하지 않도록 정책 적용 + */ + NONE +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/annotation/TraceLogging.java b/mens-core/src/main/java/kr/xit/core/spring/annotation/TraceLogging.java new file mode 100644 index 0000000..1a9bc02 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/annotation/TraceLogging.java @@ -0,0 +1,27 @@ +package kr.xit.core.spring.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *
    + * description : Method 단위 로깅 annotation(AOP 사용 예정)
    + *               Around("@annotation(kr.xit.core.spring.annotation.TraceLogging)")
    + * packageName : kr.xit.core.spring.annotation
    + * fileName    : classAop
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +//@Target({ElementType.TYPE, ElementType.METHOD}) +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface TraceLogging { +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/annotation/package-info.java b/mens-core/src/main/java/kr/xit/core/spring/annotation/package-info.java new file mode 100644 index 0000000..c60f7d4 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/annotation/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework spring customizing package classes + *

    + * annotation + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.spring.annotation; diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/ArgumentResolverConfig.java b/mens-core/src/main/java/kr/xit/core/spring/config/ArgumentResolverConfig.java new file mode 100644 index 0000000..5cb37ec --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/ArgumentResolverConfig.java @@ -0,0 +1,34 @@ +package kr.xit.core.spring.config; + +import java.util.List; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import kr.xit.core.spring.resolver.CustomArgumentResolver; +import kr.xit.core.spring.resolver.PageableArgumentResolver; + +/** + *
    + * description : HandlerMethodArgumentResolver를 구현한 resolver 등록
    + * packageName : kr.xit.core.spring.config
    + * fileName    : ArgumentResolverConfig
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +@Configuration +public class ArgumentResolverConfig implements WebMvcConfigurer { + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(new CustomArgumentResolver()); + resolvers.add(new PageableArgumentResolver()); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/AsyncExecutorConfig.java b/mens-core/src/main/java/kr/xit/core/spring/config/AsyncExecutorConfig.java new file mode 100644 index 0000000..c335d3a --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/AsyncExecutorConfig.java @@ -0,0 +1,93 @@ +package kr.xit.core.spring.config; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurerSupport; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import kr.xit.core.spring.config.support.ClonedTaskDecorator; +import kr.xit.core.spring.config.support.CustomAsyncExceptionHandler; + +/** + *
    + * description : Async 설정
    + *               - @Scheduler와 동시 사용시는 반드시 Bean name을 지정
    + *                 사용 서비스에서 지정해 줘야 한다 (@Async("asyncExecutor")
    + * packageName : kr.xit.core.spring.config
    + * fileName    : AsyncExecutorConfig
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see kr.xit.core.spring.util.ApiWebClient + */ +@Configuration +@EnableAsync +public class AsyncExecutorConfig extends AsyncConfigurerSupport { + + @Value("${contract.provider.connection.thread.corePoolSize:5}") + private int corePoolSize; + + @Value("${contract.provider.connection.thread.maxPoolSize:10}") + private int maxPoolSize; + + /** + *
    +     * 비동기 호출 Thread 설정
    +     * AsyncConfigurerSupport 상속
    +     * 서비스의 public 메소드에 @Async annotation 사용하여 적용(private 메소드는 불가)
    +     * corePoolSize: 지정한 사이즈 만큼 동시 처리
    +     * MaxPoolSise: 동시 동작하는, 최대 Thread 갯수
    +     * QueueCapacity : MaxPoolSize 초과 요청시 해당 내용을 Queue에 저장하고, 여유가 발생하면 하나씩 꺼내져서 동작
    +     * ThreadNamePrefix: spring이 생성하는 쓰레드의 접두사 지정
    +     * org.springframework.util.concurrent.ListenableFuture 타입으로 받아 처리
    +     *
    +     * TaskExecutor 사용시 executor는 새로운 Thread를 생성
    +     * -> 기존 쓰레드의 context가 넘어오지 않는다
    +     * -> TaskDecorator(spring4.3 이상에서 사용) TaskExecutor 생성시 MDC 전체 copy
    +     * @return Executor
    +     * 
    + * @see ClonedTaskDecorator + */ + @Override + @Bean(name = "asyncExecutor") + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setThreadNamePrefix("async-"); + executor.setCorePoolSize(corePoolSize); + executor.setMaxPoolSize(maxPoolSize); + executor.setQueueCapacity(100); + executor.setKeepAliveSeconds(30); + + // MDC copy + executor.setTaskDecorator(new ClonedTaskDecorator()); + + //shutdown 상태가 아니라면 ThreadPoolTaskExecutor에 요청한 thread에서 직접 처리 + //예외와 누락 없이 최대한 처리하려면 CallerRunsPolicy로 설정하는 것이 좋음 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + + //최대 종료 대기 시간 + executor.setWaitForTasksToCompleteOnShutdown(true); + executor.setAwaitTerminationSeconds(60); // shutdown 최대 60초 대기 + executor.setAllowCoreThreadTimeOut(true); + executor.initialize(); + + return executor; + //return threadPoolExecutor(); + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler(){ + return new CustomAsyncExceptionHandler(); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/EncodingFilterConfig.java b/mens-core/src/main/java/kr/xit/core/spring/config/EncodingFilterConfig.java new file mode 100644 index 0000000..f48ab2a --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/EncodingFilterConfig.java @@ -0,0 +1,40 @@ +package kr.xit.core.spring.config; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.CharacterEncodingFilter; + +/** + *
    + * description : 기본 UTF-8인데, encoding이 EUC-KR로 들어오는 경우가 있는 경우(외부에서 들어오는 - 결재 등 -) 사용
    + *               - 조건 : spring.http.encoding.force: false
    + * packageName : kr.xit.core.spring.config
    + * fileName    : EncodingFilterConfig
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +//FIXME :: 기본 UTF-8인데, encoding이 EUC-KR로 들어오는 경우가 있는 경우(외부에서 들어오는 - 결재 등 -) 사용 +@ConditionalOnProperty(value = "spring.http.encoding.force", havingValue = "false", matchIfMissing = false) +@Configuration +public class EncodingFilterConfig { + + @Bean + public FilterRegistrationBean encodingFilterBean() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); + filter.setForceEncoding(true); + filter.setEncoding("MS949"); + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns("/ms949filterUrl/*"); + return registrationBean; + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/JasyptConfig.java b/mens-core/src/main/java/kr/xit/core/spring/config/JasyptConfig.java new file mode 100644 index 0000000..4ffd26e --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/JasyptConfig.java @@ -0,0 +1,105 @@ +package kr.xit.core.spring.config; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.jasypt.encryption.StringEncryptor; +import org.jasypt.encryption.pbe.PooledPBEStringEncryptor; +import org.jasypt.encryption.pbe.StandardPBEStringEncryptor; +import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *
    + * description : properties 암호화 설정
    + * packageName : kr.xit.core.spring.config
    + * fileName    : JasyptConfig
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +//FIXME :: properties 암호화시 사용 +@Configuration +public class JasyptConfig { + @Value("${app.jasypt.secretKey:none}") + private String secretKey; + + @Value("${app.jasypt.alg:none}") + private String secretAlg; + + @Value("${app.jasypt.type:none}") + private String secretType; + + @Bean(name = "jasyptStringEncryptor") + public StringEncryptor jasyptStringEncryptor() { + SimpleStringPBEConfig config = new SimpleStringPBEConfig(); + // 암/복호화 키 + config.setPassword(secretKey); + // 암/복호화 알고리즘 + config.setAlgorithm(secretAlg); + // 반복할 해싱 회수 + config.setKeyObtentionIterations("1000"); + config.setProviderName("SunJCE"); + // salt 생성 클래스 + config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); + // 암/복호화 인스턴스 : 0보다 커야 + config.setPoolSize("1"); + config.setStringOutputType(secretType); + + PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); + encryptor.setConfig(config); + return encryptor; + } + +/* + @Bean(name = "jasyptStringEncryptor") + public StringEncryptor jasyptStringEncryptor() { + SimpleStringPBEConfig config = new SimpleStringPBEConfig(); + // 암/복호화 키 + config.setPassword(secretKey); + // 암/복호화 알고리즘 + config.setAlgorithm("PBEWithSHA256And128BitAES-CBC-BC"); + // 반복할 해싱 회수 + config.setKeyObtentionIterations("1000"); + config.setProvider(new BouncyCastleProvider()); + // salt 생성 클래스 + config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); + // 암/복호화 인스턴스 : 0보다 커야 + config.setPoolSize("1"); + config.setStringOutputType(secretType); + + PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); + encryptor.setConfig(config); + + return encryptor; + } +*/ + + + public static void main(String[] args) { + StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); + encryptor.setAlgorithm("PBEWithMD5AndDES"); + encryptor.setPassword("xit5811807!@"); + + String url = encryptor.encrypt("jdbc:mariadb://127.0.0.1:4407/ens?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true&autoReconnect=true"); + String id = encryptor.encrypt("root"); + String passwd = encryptor.encrypt("xit1807"); + String url2 = encryptor.encrypt("jdbc:oracle:thin:@211.119.124.115:1521:XITSMS"); + String id2 = encryptor.encrypt("xit_sms_lg"); + String passwd2 = encryptor.encrypt("xit_sms_lg"); + System.out.println(url); + System.out.println(id); + System.out.println(passwd); + System.out.println(encryptor.decrypt(url)); + System.out.println(url2); + System.out.println(id2); + System.out.println(passwd2); + System.out.println(encryptor.decrypt(url2)); + } + +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/SpringDocsConfig.java b/mens-core/src/main/java/kr/xit/core/spring/config/SpringDocsConfig.java new file mode 100644 index 0000000..ab11ec5 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/SpringDocsConfig.java @@ -0,0 +1,77 @@ +package kr.xit.core.spring.config; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.servers.Server; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; + +/** + *
    + * description : Springdoc(swagger) 설정
    + *               설정내용이 상이한 경우 동일한 파일로 재정의 하거나 상속받아 사용
    + * packageName : kr.xit.core.spring.config
    + * fileName    : SpringDocsConfig
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +@ConditionalOnProperty(value = "springdoc.swagger-ui.enabled", havingValue = "true", matchIfMissing = false) +@Configuration +public class SpringDocsConfig { + + @Bean + public OpenAPI openAPI( + @Value("${springdoc.version:v1}") String version, + @Value("${app.url}") String url, + @Value("${app.desc}") String desc, + @Value("${app.name}") String name, + @Value("${spring.profiles.active}") String active) { + + Info info = new Info() + .title(String.format("%s : %s 서버( %s )", desc, name, active)) // 타이틀 + .version(version) // 문서 버전 + .description("잘못된 부분이나 오류 발생 시 바로 말씀해주세요.") // 문서 설명 + .contact(new Contact() // 연락처 + .name("관리자") + .email("admin@xit.co.kr") + .url("http://www.xerotech.co.kr/")); + + List servers = Collections.singletonList(new Server().url(url).description(name + "(" + active + ")")); + + // Security 스키마 설정 + SecurityScheme securityScheme = new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + .in(SecurityScheme.In.HEADER) + // .name(HttpHeaders.AUTHORIZATION); + .name("Authorization"); + SecurityRequirement securityRequirement = new SecurityRequirement().addList("bearerAuth"); + + return new OpenAPI() + // Security 인증 컴포넌트 설정 + .components(new Components().addSecuritySchemes("bearerAuth", securityScheme)) + // API 마다 Security 인증 컴포넌트 설정 + //.addSecurityItem(new SecurityRequirement().addList("JWT")) + .security(Collections.singletonList(securityRequirement)) + .info(info) + .servers(servers); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/auth/AuthentificationInterceptor.java b/mens-core/src/main/java/kr/xit/core/spring/config/auth/AuthentificationInterceptor.java new file mode 100644 index 0000000..8a78651 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/auth/AuthentificationInterceptor.java @@ -0,0 +1,152 @@ +package kr.xit.core.spring.config.auth; + +import kr.xit.core.consts.Constants; +import kr.xit.core.consts.ErrorCode; +import kr.xit.core.exception.BizRuntimeException; +import kr.xit.core.spring.annotation.Secured; +import kr.xit.core.spring.annotation.SecurityPolicy; +import kr.xit.core.spring.config.auth.util.HeaderUtil; +import kr.xit.core.spring.util.SpringUtils; +import kr.xit.core.support.utils.Checks; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.AsyncHandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Objects; + +/** + *
    + * description : 인증 처리 Interceptor
    + *              - annotation 방식
    + *              - @Secured(policy = SecurityPolicy.TOKEN | SESSION | COOKIE)
    + * packageName : kr.xit.core.spring.config.auth
    + * fileName    : AuthentificationInterceptor
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +@Slf4j +public class AuthentificationInterceptor implements AsyncHandlerInterceptor {//AsyncHandlerInterceptor { //extends HandlerInterceptorAdapter{ // HandlerInterceptor{ + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{ + + // Request의 Method가 OPTIONS 인 경우 통과 : CORS 정책에 의한 preFlight pass + if (request.getMethod().equals("OPTIONS")) { + log.info("==== OPTIONS method AuthentificationInterceptor skip ===="); + return true; + } + + log.info("==== AuthentificationInterceptor checking ===="); + SecurityPolicy policy = null; + + if(handler instanceof HandlerMethod){ + policy = SecurityPolicy.DEFAULT; + + HandlerMethod method = (HandlerMethod)handler; + Secured secured = method.getMethod().getAnnotation(Secured.class); + + if(secured != null){ + policy = secured.policy(); + }else{ + secured = method.getBeanType().getAnnotation(Secured.class); + if(secured != null) + policy = secured.policy(); + } + } + + if(Checks.isNotEmpty(policy)){ + + // 토큰 인증 + if (Objects.equals(SecurityPolicy.TOKEN, policy)) { + log.debug("TOKEN 인증 start ==>> "); + + String tokenString = request.getHeader(Constants.JwtToken.HEADER_NAME.getCode()); + + if(Checks.isNotEmpty(tokenString)){ + + try{ + + // Spring Security 미사용 + tokenString = HeaderUtil.extractAccessToken(tokenString); + if(SpringUtils.getJwtVerification().isVerification(tokenString)){ + throw BizRuntimeException.create(ErrorCode.INVALID_AUTH_TOKEN); + }; + return true; + + // Spring Security 사용시 + // if(SpringUtils.getJwtTokenProvider().validateTokenExcludeExpired(tokenString, false, true)){ + // log.debug("<<==== 토큰인증성공"); + // return true; + // } + + + }catch(BizRuntimeException cbe){ + //TODO Refresh토큰 사용시 자동 재발급하는 경우 주석 제거 + // access token expired >> refresh 토큰 확인후 access 토큰 재발급 + // if(Objects.equals(ErrorConst.TOKEN_EXPIRED, be.getErrorCode())){ + // //TODO refresh 토큰유효기간 확인 + // // 1.GET refresh 토큰 + // + // // 2.refresh 토큰 유효성 검사 + // if(tokenService.isValidToken("", tokenString)){ + // tokenService.getRefreshToken(tokenString, response); + // return true; + // + // }else{ + // throw new BaseException("swork.token.expired", new String[]{}, ErrorConst.TOKEN_EXPIRED); + // } + // } + log.error("====토큰인증실패 :: {} ====", cbe.getLocalizedMessage()); + throw cbe; + } + }else{ + log.error("==== 토큰인증실패 :: 요청 헤더에 토큰이 존재하지 않습니다. ===="); + throw BizRuntimeException.create(ErrorCode.AUTH_HEADER_NOT_EXISTS); + } + + // 세션 인증 (cookie 테스트를 위한 redirect 설정) + }else if (Objects.equals(SecurityPolicy.SESSION, policy)) { + + return true; + // 쿠키 인증 데이터 생성 + }else if (Objects.equals(SecurityPolicy.COOKIE, policy)) { + + return true; + } + }else{ + //log.debug("<<== OpenApiTokenInterceptor end"); + return true; + } + + // TODO : 인증이 필요없는 Page?? 로 처리할지 에러(401:인가되지 않음)로 표시할지 결정 + // 접근불가로 처리하려면 주석처리 + if(Objects.equals(SecurityPolicy.DEFAULT, policy)){ + //log.debug("===================== 인증이 필요없는 페이지 ==========================="); + return true; + } + // 401 : 인가되지 않음으로 에러 처리 + log.error("====인가되지 않음===="); + //response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + throw BizRuntimeException.create(ErrorCode.INVALID_AUTH_TOKEN); + //return false; + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception{ + + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) throws Exception{ + + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/auth/package-info.java b/mens-core/src/main/java/kr/xit/core/spring/config/auth/package-info.java new file mode 100644 index 0000000..c397023 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/auth/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework spring customizing package classes + *

    + * config - authentification + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.spring.config.auth; diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/auth/util/CookieUtil.java b/mens-core/src/main/java/kr/xit/core/spring/config/auth/util/CookieUtil.java new file mode 100644 index 0000000..664dd86 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/auth/util/CookieUtil.java @@ -0,0 +1,82 @@ +package kr.xit.core.spring.config.auth.util; + +import java.util.Base64; +import java.util.Objects; +import java.util.Optional; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.util.SerializationUtils; + +/** + *
    + * description : CookieUtil
    + * packageName : kr.xit.core.spring.config.auth.util
    + * fileName    : CookieUtil
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CookieUtil { + + public static Optional getCookie(HttpServletRequest request, String name) { + Cookie[] cookies = request.getCookies(); + + if (cookies != null && cookies.length > 0) { + for (Cookie cookie : cookies) { + if (Objects.equals(name, cookie.getName())) { + return Optional.of(cookie); + } + } + } + return Optional.empty(); + } + + public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) { + Cookie cookie = new Cookie(name, value); + cookie.setPath("/"); + cookie.setHttpOnly(true); + cookie.setMaxAge(maxAge); + + response.addCookie(cookie); + } + + public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String name) { + Cookie[] cookies = request.getCookies(); + + if (cookies != null && cookies.length > 0) { + for (Cookie cookie : cookies) { + if (Objects.equals(name, cookie.getName())) { + cookie.setValue(""); + cookie.setPath("/"); + cookie.setMaxAge(0); + response.addCookie(cookie); + } + } + } + } + + public static String serialize(Object obj) { + return Base64.getUrlEncoder() + .encodeToString(SerializationUtils.serialize(obj)); + } + + public static T deserialize(Cookie cookie, Class cls) { + return cls.cast( + SerializationUtils.deserialize( + Base64.getUrlDecoder().decode(cookie.getValue()) + ) + ); + } + +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/auth/util/HeaderUtil.java b/mens-core/src/main/java/kr/xit/core/spring/config/auth/util/HeaderUtil.java new file mode 100644 index 0000000..5c9abf5 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/auth/util/HeaderUtil.java @@ -0,0 +1,97 @@ +package kr.xit.core.spring.config.auth.util; + +import javax.servlet.http.HttpServletRequest; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import kr.xit.core.consts.Constants; +import kr.xit.core.consts.ErrorCode; +import kr.xit.core.exception.BizRuntimeException; +//import kr.xit.core.spring.config.auth.jwt.JwtTokenProvider; +import kr.xit.core.spring.util.SpringUtils; +import kr.xit.core.support.utils.Checks; + +/** + *
    + * description : Header token utility
    + * packageName : kr.xit.core.spring.config.auth.util
    + * fileName    : HeaderUtil
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class HeaderUtil { + + public static String getAccessToken(){ + return getAccessToken(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()); + } + + public static String getAccessToken(HttpServletRequest request) { + return extractAccessToken(request.getHeader(Constants.JwtToken.HEADER_NAME.getCode())); + } + + /** + * get access token + * @param tokenStr String + * @return String + */ + public static String extractAccessToken(String tokenStr) { + if (tokenStr == null) { + return null; + } + + if (tokenStr.startsWith(Constants.JwtToken.GRANT_TYPE.getCode())) { + return tokenStr.substring(Constants.JwtToken.GRANT_TYPE.getCode().length() + 1); + } + + return null; + } + + /** + * + * @param request + * @return String userId + */ + public static String getUserId(HttpServletRequest request){ + return getUserIdFromToken(getAccessToken(request)); + } + + + public static String getUserId(){ + return getUserIdFromToken(getAccessToken(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest())); + } + + public static String getUserName(){ + return getUserNameFromToken(getAccessToken(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest())); + } + + private static String getUserIdFromToken(String accessToken){ + if(Checks.isEmpty(accessToken)) throw BizRuntimeException.create(ErrorCode.AUTH_HEADER_NOT_EXISTS); + + try { + String username = SpringUtils.getEgovJwtTokenUtil().getUsernameFromToken(accessToken); + return username; + }catch (Exception e){ + throw BizRuntimeException.create(ErrorCode.INVALID_TOKEN); + } + } + + private static String getUserNameFromToken(String accessToken){ + if(Checks.isEmpty(accessToken)) throw BizRuntimeException.create(ErrorCode.AUTH_HEADER_NOT_EXISTS); + + try { + return SpringUtils.getEgovJwtTokenUtil().getUsernameFromToken(accessToken); + }catch (Exception e){ + throw BizRuntimeException.create(ErrorCode.INVALID_TOKEN); + } + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/auth/util/package-info.java b/mens-core/src/main/java/kr/xit/core/spring/config/auth/util/package-info.java new file mode 100644 index 0000000..772f3b4 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/auth/util/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework spring customizing package classes + *

    + * config- auth - utitlity + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.spring.config.auth.util; diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/custom/bouncy/BouncyUtils.java b/mens-core/src/main/java/kr/xit/core/spring/config/custom/bouncy/BouncyUtils.java new file mode 100644 index 0000000..2b0e729 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/custom/bouncy/BouncyUtils.java @@ -0,0 +1,320 @@ +package kr.xit.core.spring.config.custom.bouncy; + +import kr.xit.core.exception.BizRuntimeException; +import kr.xit.core.support.utils.DateUtils; +import kr.xit.core.support.utils.FileUtil; +import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.engines.SEEDEngine; +import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; +import xit.core.init.custom.bouncy.BouncyDecUtils; + +import javax.crypto.Cipher; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.*; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +/** + *
    + * description : DSA 파일 생성 / 암/복호화 Utils
    + *               DSA(Digital Signature Algorithm) 파일 암/복호화
    + *              -> bouncycastle 암/복호화
    + * packageName : kr.xit.core.spring.config.custom.bouncy
    + * fileName    : BouncyUtils
    + * author      : limju
    + * date        : 2023-07-20
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-07-20    limju       최초 생성
    + *
    + * 
    + */ +public class BouncyUtils { + + private static final String PRIVATE_KEY_FILE_NAME = "private_key.pem"; + private static final String PUBLIC_KEY_FILE_NAME = "public_key.pem"; + private static final String KEY_ALG = "RSA"; + + /** + * RSA PublicKey and PrivateKey 생성 + * @return KeyPair + */ + public static KeyPair generateKeyPair() { + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALG); + keyPairGenerator.initialize(2048); + + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + return keyPair; + }catch(NoSuchAlgorithmException | InvalidParameterException e){ + throw BizRuntimeException.create(e.getMessage()); + } + } + + /** + * RSA PublicKey and PrivateKey file 생성 + * @param keyFilePath key 파일 생성 위치 + */ + public static void generateKeyPairFile(String keyFilePath) { + KeyPair keyPair = generateKeyPair(); + String privateKeyPem = keyToPem(keyPair.getPrivate(), "RSA PRIVATE KEY"); + String publicKeyPem = keyToPem(keyPair.getPublic(), "RSA PUBLIC KEY"); + + try { + Files.write(Paths.get(keyFilePath, PRIVATE_KEY_FILE_NAME), privateKeyPem.getBytes(StandardCharsets.UTF_8)); + Files.write(Paths.get(keyFilePath, PUBLIC_KEY_FILE_NAME), publicKeyPem.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e){ + throw BizRuntimeException.create(e.getMessage()); + } + } + + /** + * RSA PublicKey and PrivateKey file 생성 + * -> 파일 암호화 적용 생성 + * @param keyFilePath key 파일 생성 위치 + */ + public static void generateKeyPairFile(String keyFilePath, String key) { + KeyPair keyPair = generateKeyPair(); + String privateKeyPem = keyToPem(keyPair.getPrivate(), "RSA PRIVATE KEY"); + String publicKeyPem = keyToPem(keyPair.getPublic(), "RSA PUBLIC KEY"); + + try { + Files.write(Paths.get(keyFilePath, PRIVATE_KEY_FILE_NAME), encrypt(key, privateKeyPem.getBytes(StandardCharsets.UTF_8))); + Files.write(Paths.get(keyFilePath, PUBLIC_KEY_FILE_NAME), encrypt(key, publicKeyPem.getBytes(StandardCharsets.UTF_8))); + + } catch (IOException e){ + throw BizRuntimeException.create(e.getMessage()); + } + } + + /** + * 평문 암호화 + * @param publicKeyPath PublicKey file path + * @param plainText 암호화 대상 문자열 + * @return 암호화된 문자열 + */ + public static String encode(String publicKeyPath, String plainText) { + + try { + String publicKeyPem = new String(Files.readAllBytes(Paths.get(publicKeyPath)), StandardCharsets.UTF_8); + PublicKey publicKey = pemToPublicKey(publicKeyPem); + + Cipher cipher = Cipher.getInstance(KEY_ALG); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + byte[] bytePlain = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); + + return Base64.getEncoder().encodeToString(bytePlain); + }catch (Exception e){ + throw BizRuntimeException.create(e.getMessage()); + } + } + + /** + * 평문 암호화 + * @param publicKeyPath PublicKey file path + * @param plainText 암호화 대상 문자열 + * @return 암호화된 문자열 + */ + public static String encode(String publicKeyPath, String key, String plainText) { + + try { + byte[] encryptedFileBytes = FileUtil.getFileBytesFrom(publicKeyPath); + String publicKeyPem = new String(BouncyDecUtils.decrypt(key, encryptedFileBytes), StandardCharsets.UTF_8); + PublicKey publicKey = pemToPublicKey(publicKeyPem); + + Cipher cipher = Cipher.getInstance(KEY_ALG); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + byte[] bytePlain = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); + + return Base64.getEncoder().encodeToString(bytePlain); + }catch (Exception e){ + throw BizRuntimeException.create(e.getMessage()); + } + } + + public static void encryptionFile(String key, String filePath) throws IOException { + byte[] plainFileBytes = FileUtil.getFileBytesFrom(filePath); + FileUtil.saveFile(filePath, encrypt(key, plainFileBytes)); + } + + public static void decryptionFile(String key, String filePath) throws IOException { + byte[] encryptedFileBytes = FileUtil.getFileBytesFrom(filePath); + FileUtil.saveFile(filePath, BouncyDecUtils.decrypt(key, encryptedFileBytes)); + } + + /** + * 파일 객체 암호화(SEEDEngine 사용) + * @param key 128bits(String 16자리) + * @param plainText file byte 객체 + * @return + */ + public static byte[] encrypt(String key, byte[] plainText) { + byte[] keyBytes = key.getBytes(); + + // 블록 암호 운용 + // 블록보다 데이터가 짧을 경우 패딩을 사용함 + // 블록 암호 알고리즘으로는 SEED 알고리즘을 사용함 + BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new SEEDEngine()); + // 초기화 및 키 파라미터 생성 첫 번째 매개변수가 true 라면 암호화 모드 + cipher.init(true, new KeyParameter(keyBytes)); + + return getBytes(plainText, cipher); + } + + /** + * PublicKey and PrivateKey file로 부터 KeyPair(PublicKey and PrivateKey) return + * @param keyFilePath + * @return KeyPair(PublicKey and PrivateKey) + */ + public static KeyPair loadKeyPair(String keyFilePath) { + try{ + String privateKeyPem = new String(Files.readAllBytes(Paths.get(keyFilePath, PRIVATE_KEY_FILE_NAME)), StandardCharsets.UTF_8); + String publicKeyPem = new String(Files.readAllBytes(Paths.get(keyFilePath, PUBLIC_KEY_FILE_NAME )), StandardCharsets.UTF_8); + + PrivateKey privateKey = pemToPrivateKey(privateKeyPem); + PublicKey publicKey = pemToPublicKey(publicKeyPem); + + return new KeyPair(publicKey, privateKey); + }catch (Exception e){ + throw BizRuntimeException.create(e.getMessage()); + } + } + //---------------------------------------------------------------------------------- + + /** + * Key -> String 변환 + * @param key + * @param type + * @return + */ + private static String keyToPem(Key key, String type) { + StringWriter stringWriter = new StringWriter(); + try (JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { + pemWriter.writeObject(new PemObject(type, key.getEncoded())); + } catch (IOException e) { + throw BizRuntimeException.create(e.getMessage()); + } + return stringWriter.toString(); + } + + /** + * String PrivateKey -> PrivateKey + * - 파일에 저장한 키 read시 사용 + * @param privateKeyPem String + * @return PrivateKey + */ + private static PrivateKey pemToPrivateKey(String privateKeyPem) throws Exception { + PemReader pemReader = new PemReader(new StringReader(privateKeyPem)); + PemObject pemObject = pemReader.readPemObject(); + pemReader.close(); + + byte[] privateKeyBytes = pemObject.getContent(); + PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALG); + + return keyFactory.generatePrivate(privateKeySpec); + } + + /** + * String publicKey -> PublicKey + * - 파일에 저장한 키 read시 사용 + * @param publicKeyPem String + * @return PublicKey + */ + private static PublicKey pemToPublicKey(String publicKeyPem) throws Exception { + PemReader pemReader = new PemReader(new StringReader(publicKeyPem)); + PemObject pemObject = pemReader.readPemObject(); + pemReader.close(); + + byte[] publicKeyBytes = pemObject.getContent(); + X509EncodedKeySpec privateKeySpec = new X509EncodedKeySpec(publicKeyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALG); + + return keyFactory.generatePublic(privateKeySpec); + } + + private static byte[] getBytes(byte[] targetData, BufferedBlockCipher cipher) { + byte[] outputData = new byte[cipher.getOutputSize(targetData.length)]; + + int tam = cipher.processBytes(targetData, 0, targetData.length, outputData, 0); + + try { + cipher.doFinal(outputData, tam); + } catch (Exception e) { + e.printStackTrace(); + } + return outputData; + } + + /** + * 파일 객체 복호롸(SEEDEngine 사용) + * @param key 128bits(String 16자리) + * @param cipherText file byte 객체 + * @return + */ + public static byte[] decrypt(String key, byte[] cipherText) { + byte[] keyBytes = key.getBytes(); + + BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new SEEDEngine()); + cipher.init(false, new KeyParameter(keyBytes)); + + return getBytes(cipherText, cipher); + } + + // TODO::암호화할 속성 + public static void main(String[] args) throws Exception { + + String path = "d:/data/ens/.pem/"; + String macAddress = "00-21-5E-DB-59-36"; //dev mac IpMacUtils.getMacAddress("ip"); + //String macAddress = "FC-34-97-15-04-44"; //local mac + String key = "mxLAM1fAEDPWkFz8"; + + // enc public / private create + //BouncyUtils.encryptionFile(key, "C:/workspace/git/ens-parent/xit-init/target/xit-init.jar"); + //BouncyUtils.encryptionFile(key, path+"public_key.pem"); + // dec public / private create + //BouncyFileHelper.decryptionFile(key, path); + //BouncyFileHelper.decryptionFile(key, path); + + + long expiredDate = DateUtils.parseStringToLong("20991231135959", "yyyyMMddHHmmss"); + String ip = "211.119.124.9";// IpMacUtils.getIpAddress(); + //String ip = "211.119.124.73";// local ip + long curDate = DateUtils.getLongTodayAndNowTime(); + + String mariaAccount = "org.mariadb.jdbc.Driver;root;xit5811807;jdbc:mariadb://211.119.124.9:4407/ens?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true&autoReconnect=true;false"; + String oracleAccount = "oracle.jdbc.OracleDriver;xit_sms_lg;xit_sms_lg;jdbc:oracle:thin:@211.119.124.115:1521:XITSMS;false"; + + // 10 + 17 + ~ + String encData = String.format("%d%s%s", expiredDate, macAddress, ip, mariaAccount, oracleAccount); + + System.out.println(encData.substring(0, 10)); + System.out.println(encData.substring(10, 27)); + System.out.println(encData.substring(27)); + + BouncyUtils.generateKeyPairFile(path, key); + String license = encode(path+"public_key.pem", key, encData); + String db1 = encode(path+"public_key.pem", key, mariaAccount); + String db2 = encode(path+"public_key.pem", key, oracleAccount); + //String decStr = decode(path+"private_key.pem", key, license); + //String[] arrDec = decStr.split(";"); + System.out.println(license); + System.out.println(db1); + System.out.println(db2); + + System.out.println(BouncyDecUtils.decode(path+"private_key.pem", key, license)); + System.out.println(BouncyDecUtils.decode(path+"private_key.pem", key, db1)); + System.out.println(BouncyDecUtils.decode(path+"private_key.pem", key, db2)); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/custom/bouncy/package-info.java b/mens-core/src/main/java/kr/xit/core/spring/config/custom/bouncy/package-info.java new file mode 100644 index 0000000..f3e3c8b --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/custom/bouncy/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework spring customizing package classes + *

    + * encript / decript + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.spring.config.custom.bouncy; diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/package-info.java b/mens-core/src/main/java/kr/xit/core/spring/config/package-info.java new file mode 100644 index 0000000..d33b8c9 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework spring customizing package classes + *

    + * config + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.spring.config; diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/properties/CorsProperties.java b/mens-core/src/main/java/kr/xit/core/spring/config/properties/CorsProperties.java new file mode 100644 index 0000000..458981b --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/properties/CorsProperties.java @@ -0,0 +1,55 @@ +package kr.xit.core.spring.config.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import lombok.Getter; +import lombok.Setter; + +/** + *
    + * description : CORS Properties
    + *               - properties : cors 항목에 정의
    + * packageName : kr.xit.core.spring.config.properties
    + * fileName    : CorsProperties
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +@Getter +@Setter +@ConfigurationProperties(prefix = "cors") +public class CorsProperties { + /** + * 헤더에 작성된 출처만 브라우저가 리소스를 접근할 수 있도록 허용함. + * * 이면 모든 곳에 공개 + */ + private String allowedOrigins; + /** + * 리소스 접근을 허용하는 HTTP 메서드를 지정 + */ + private String allowedMethods; + /** + * 요청을 허용하는 해더 + */ + private String allowedHeaders; + /** + * 클라이언트에서 preflight 의 요청 결과를 저장할 기간을 지정 + * 해당 시간 동안 preflight 요청을 캐시하는 설정으로, 첫 요청 이후 60초 동안은 OPTIONS 메소드를 사용하는 예비 요청을 보내지 않는다. + */ + private Long maxAge; + /** + * 클라이언트 요청이 쿠키를 통해서 자격 증명을 해야 하는 경우에 true. + * 자바스크립트 요청에서 credentials가 include일 때 요청에 대한 응답을 할 수 있는지를 나타낸다. + */ + private Boolean allowCredentials; + /** + * 기본적으로 브라우저에게 노출이 되지 않지만, 브라우저 측에서 접근할 수 있게 허용해주는 헤더를 지정 + * Content-Length + */ + private String exposeHeader; +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/properties/JwtProperties.java b/mens-core/src/main/java/kr/xit/core/spring/config/properties/JwtProperties.java new file mode 100644 index 0000000..910559b --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/properties/JwtProperties.java @@ -0,0 +1,26 @@ +package kr.xit.core.spring.config.properties; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +@Getter +@Setter +@NoArgsConstructor +@ConfigurationProperties //(prefix = "app.token") +//@ConstructorBinding +public class JwtProperties { + + private String typ; + private String grant; + private String alg; + private String issuer; + private String audience; + // minute + private int tokenExpiry; + // day + private int refreshTokenExpiry; + private String saveType; +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/properties/package-info.java b/mens-core/src/main/java/kr/xit/core/spring/config/properties/package-info.java new file mode 100644 index 0000000..133c908 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/properties/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework spring customizing package classes + *

    + * properties + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.spring.config.properties; diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/support/ApplicationContextProvider.java b/mens-core/src/main/java/kr/xit/core/spring/config/support/ApplicationContextProvider.java new file mode 100644 index 0000000..0675234 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/support/ApplicationContextProvider.java @@ -0,0 +1,45 @@ +package kr.xit.core.spring.config.support; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + *
    + * description : ApplicationContext 를 이용하여 component 주입
    + *              - ApplicationContextAware 구현
    + *              - Bean으로 등록되는 클래스 내에서만 @Autowired / @Resource 등이 동작
    + *              - Filter / Interceptor 등에서 Bean 사용시 필요
    + * packageName : kr.xit.core.spring.config.support
    + * fileName    : ApplicationContextProvider
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see kr.xit.core.spring.util.SpringUtils + */ +@Component +public class ApplicationContextProvider implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + public ApplicationContextProvider(final ApplicationContext applicationContext){ + this.applicationContext = applicationContext; + } + + @Override + public void setApplicationContext(ApplicationContext ctx) throws BeansException { + this.applicationContext = ctx; + } + + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/support/ClonedTaskDecorator.java b/mens-core/src/main/java/kr/xit/core/spring/config/support/ClonedTaskDecorator.java new file mode 100644 index 0000000..3ccbf11 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/support/ClonedTaskDecorator.java @@ -0,0 +1,52 @@ +package kr.xit.core.spring.config.support; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import org.slf4j.MDC; +import org.springframework.core.task.TaskDecorator; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import lombok.extern.slf4j.Slf4j; + +/** + *
    + * description : 비동기 처리를 위해 TaskExecutor 사용시 executor 는 새로운 Thread 를 생성
    + *               -> 기존 쓰레드의 context 가 넘어가지 않는다
    + *               -> TaskDecorator(spring4.3 이상에서 사용) TaskExecutor 생성시
    + *               -> RequestContextHolder의 ThreadLocal attributes copy
    + *               -> MDC 전체 copy
    + *               -> TaskExecutor.setTaskDecorator()에 주입
    + * packageName : kr.xit.core.spring.config.support
    + * fileName    : ClonedTaskDecorator
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see kr.xit.core.spring.config.AsyncExecutorConfig + */ +@Slf4j +public class ClonedTaskDecorator implements TaskDecorator { + @Override + public Runnable decorate(Runnable task) { + Map mdcMap = MDC.getCopyOfContextMap(); + //MDC.clear(); + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + + return () -> { + if (attributes != null) { + RequestContextHolder.setRequestAttributes(attributes); + } + if (mdcMap != null) { + MDC.setContextMap(mdcMap); + log.info(">>>>>>>>>>>>>>>ClonedTaskDecorator: {}", mdcMap); + } + task.run(); + }; + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/support/CustomAsyncExceptionHandler.java b/mens-core/src/main/java/kr/xit/core/spring/config/support/CustomAsyncExceptionHandler.java new file mode 100644 index 0000000..342639c --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/support/CustomAsyncExceptionHandler.java @@ -0,0 +1,18 @@ +package kr.xit.core.spring.config.support; + +import java.lang.reflect.Method; + +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; + +public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler { + + @Override + public void handleUncaughtException(Throwable throwable, Method method, Object... obj) { + System.out.println("Exception message - " + throwable.getMessage()); + System.out.println("Method name - " + method.getName()); + for (Object param : obj) { + System.out.println("Parameter value - " + param); + } + } + +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/support/CustomBeanNameGenerator.java b/mens-core/src/main/java/kr/xit/core/spring/config/support/CustomBeanNameGenerator.java new file mode 100644 index 0000000..8cb23c8 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/support/CustomBeanNameGenerator.java @@ -0,0 +1,64 @@ +package kr.xit.core.spring.config.support; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.context.annotation.AnnotationBeanNameGenerator; +import org.springframework.lang.NonNull; + +/** + * Bean 이름 식별시 패키지를 포함하도록 지정 + * /v1/api, /v2/api 형태로 생성 가능하도록 + */ +/** + *
    + * description : Bean 이름 식별시 패키지를 포함하도록 지정
    + *              - /v1/api, /v2/api 형태로 생성 가능
    + * packageName : kr.xit.core.spring.config.support
    + * fileName    : CustomBeanNameGenerator
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +public class CustomBeanNameGenerator implements BeanNameGenerator { + + /** + * basePackages 외에 scaning된 beanGenerator + */ + private static final BeanNameGenerator DELEGATE = new AnnotationBeanNameGenerator(); + + /** + * VersioningBeanNameGenerator 대상 package 경로 + */ + private final List basePackages = new ArrayList<>(); + + @Override + public @NonNull String generateBeanName(@NonNull BeanDefinition definition, @NonNull BeanDefinitionRegistry registry) { + if(isTargetPackageBean(definition)) { + return getBeanName(definition); + } + + return DELEGATE.generateBeanName(definition, registry); + } + + private boolean isTargetPackageBean(BeanDefinition definition) { + String beanClassName = getBeanName(definition); + return basePackages.stream().anyMatch(beanClassName::startsWith); + } + + private String getBeanName(BeanDefinition definition) { + return definition.getBeanClassName(); + } + + public void addBasePackages(List basePackages) { + this.basePackages.addAll(basePackages); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/support/P6SpySqlMultilineFormat.java b/mens-core/src/main/java/kr/xit/core/spring/config/support/P6SpySqlMultilineFormat.java new file mode 100644 index 0000000..24c7b2e --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/support/P6SpySqlMultilineFormat.java @@ -0,0 +1,45 @@ +package kr.xit.core.spring.config.support; + +import javax.annotation.PostConstruct; + +import org.apache.commons.lang.StringUtils; +import org.springframework.context.annotation.Configuration; + +import com.p6spy.engine.spy.P6SpyOptions; +import com.p6spy.engine.spy.appender.MessageFormattingStrategy; + +import kr.xit.core.support.utils.Checks; + +/** + *
    + * description : P6Spy SQL 로그 Multiline 출력 포맷 Custom
    + *               - sql이 있는 경우만 출력
    + *               - prepared, url(DB), now 출력 제거
    + * packageName : kr.xit.core.spring.config.support
    + * fileName    : P6SpySqlMultilineFormat
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see com.p6spy.engine.spy.appender.MultiLineFormat + */ + +@Configuration +public class P6SpySqlMultilineFormat implements MessageFormattingStrategy { + + @PostConstruct + public void setLogMessageFormat() { + P6SpyOptions.getActiveInstance().setLogMessageFormat(this.getClass().getName()); + } + + @Override + public String formatMessage(final int connectionId, final String now, final long elapsed, final String category, final String prepared, final String sql, final String url) { + if(Checks.isEmpty(sql)) return StringUtils.EMPTY; + + return "connection " + connectionId + " | took " + elapsed + "ms | " + category + "\n" + sql +";"; + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/support/PropertiesConfig.java b/mens-core/src/main/java/kr/xit/core/spring/config/support/PropertiesConfig.java new file mode 100644 index 0000000..e562cd9 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/support/PropertiesConfig.java @@ -0,0 +1,59 @@ +package kr.xit.core.spring.config.support; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnResource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; + +import lombok.extern.slf4j.Slf4j; + +/** + *
    + * description :
    + *
    + * packageName : kr.xit.core.spring.config.support
    + * fileName    : PropertiesConfig
    + * author      : xitdev
    + * date        : 2023-05-01
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-05-01    xitdev       최초 생성
    + *
    + * 
    + */ +@ConditionalOnResource(resources = {"classpath:config/application-app"}) +@Slf4j +@Configuration +public class PropertiesConfig { + + //private static final String SERVER_FILE = "file:/data/dynamic"; + private static final String CLASSPATH_FILE = "classpath:config/application-app"; + private static final String SUFFIX = ".yml"; + + // @Bean + // ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource(){ + // ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); + // //messageSource.setBasenames(CLASSPATH_FILE, SERVER_FILE); + // messageSource.setBasenames(CLASSPATH_FILE); + // messageSource.setCacheMillis(5000); + // + // return messageSource; + // } + @Bean + PropertiesConfiguration propertiesConfiguration() throws Exception { + PropertiesConfiguration configuration = null; + try { + configuration = new PropertiesConfiguration(CLASSPATH_FILE + SUFFIX); + configuration.setReloadingStrategy(new FileChangedReloadingStrategy()); + } catch (ConfigurationException e) { + log.error(e.getMessage()); + //configuration = new PropertiesConfiguration(CLASSPATH_FILE + SUFFIX); + } + return configuration; + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/config/support/package-info.java b/mens-core/src/main/java/kr/xit/core/spring/config/support/package-info.java new file mode 100644 index 0000000..9fa4df4 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/config/support/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework spring customizing package classes + *

    + * 3rd-party config + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.spring.config.support; diff --git a/mens-core/src/main/java/kr/xit/core/spring/converter/LocalDateConverter.java b/mens-core/src/main/java/kr/xit/core/spring/converter/LocalDateConverter.java new file mode 100644 index 0000000..7574fff --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/converter/LocalDateConverter.java @@ -0,0 +1,43 @@ +package kr.xit.core.spring.converter; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; +import java.sql.Date; +import java.time.LocalDate; +import java.util.Optional; + +/** + *
    + * description : LocalDateTime > Date 변환
    + *               LocalDateTime(Object) > Date(DB Type)
    + *               - autoApply = true : 모든 LocalDateTime에 대해 String 적용
    + *               - autoApply = true 미사용시 필드에 @Convert(converter = LocalDateConverter.class)
    + * packageName : kr.xit.core.spring.converter
    + * fileName    : FromLocalDateTimeToStringConverter
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +@Converter//(autoApply = true) +public class LocalDateConverter implements AttributeConverter { + + @Override + public Date convertToDatabaseColumn(LocalDate localDate) { + return Optional.ofNullable(localDate) + .map(Date::valueOf) + .orElse(null); + } + + @Override + public LocalDate convertToEntityAttribute(Date date) { + return Optional.ofNullable(date) + .map(Date::toLocalDate) + .orElse(null); + } + +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/converter/LocalDateTimeConverter.java b/mens-core/src/main/java/kr/xit/core/spring/converter/LocalDateTimeConverter.java new file mode 100644 index 0000000..dfb1d9b --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/converter/LocalDateTimeConverter.java @@ -0,0 +1,43 @@ +package kr.xit.core.spring.converter; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.Optional; + +/** + *
    + * description : LocalDateTime > Timestamp 변환
    + *               LocalDateTime(Object) > Timestamp(DB Type)
    + *               - autoApply = true : 모든 LocalDateTime에 대해 String 적용
    + *               - autoApply = true 미사용시 필드에 @Convert(converter = LocalDateTimeConverter.class)
    + * packageName : kr.xit.core.spring.converter
    + * fileName    : LocalDateTimeConverter
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +@Converter//(autoApply = true) +public class LocalDateTimeConverter implements AttributeConverter { + + @Override + public Timestamp convertToDatabaseColumn(LocalDateTime localDateTime) { + return Optional.ofNullable(localDateTime) + .map(Timestamp::valueOf) + .orElse(null); + } + + @Override + public LocalDateTime convertToEntityAttribute(Timestamp timestamp) { + return Optional.ofNullable(timestamp) + .map(Timestamp::toLocalDateTime) + .orElse(null); + } + +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/converter/ToStringLocalDateTimeConverter.java b/mens-core/src/main/java/kr/xit/core/spring/converter/ToStringLocalDateTimeConverter.java new file mode 100644 index 0000000..b58093c --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/converter/ToStringLocalDateTimeConverter.java @@ -0,0 +1,53 @@ +package kr.xit.core.spring.converter; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import org.apache.commons.lang.StringUtils; + +/** + *
    + * description : LocalDateTime > String 변환
    + *               LocalDateTime(Object) > String(DB Type)
    + *               - autoApply = true : 모든 LocalDateTime에 대해 String 적용
    + *               - autoApply = true 미사용시 필드에 @Convert(converter = ToStringLocalDateTimeConverter.class)
    + * packageName : kr.xit.core.spring.converter
    + * fileName    : FromLocalDateTimeToStringConverter
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +@Converter//(autoApply = true) +public class ToStringLocalDateTimeConverter implements AttributeConverter { + + + @Override + public String convertToDatabaseColumn(LocalDateTime attribute) { + + if(attribute == null) { + return ""; + } else { + return attribute.toLocalDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } + } + + + @Override + public LocalDateTime convertToEntityAttribute(String dbData) { + + if(StringUtils.isEmpty(dbData)) { + return null; + } else { + return LocalDate.parse(dbData, DateTimeFormatter.ISO_LOCAL_DATE).atStartOfDay(); + } + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/converter/package-info.java b/mens-core/src/main/java/kr/xit/core/spring/converter/package-info.java new file mode 100644 index 0000000..b62a772 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/converter/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework spring customizing package classes + *

    + * converter + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.spring.converter; diff --git a/mens-core/src/main/java/kr/xit/core/spring/filter/ExceptionHandlerFilter.java b/mens-core/src/main/java/kr/xit/core/spring/filter/ExceptionHandlerFilter.java new file mode 100644 index 0000000..5ee0600 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/filter/ExceptionHandlerFilter.java @@ -0,0 +1,81 @@ +package kr.xit.core.spring.filter; + +import java.io.IOException; +import java.util.Arrays; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.core.env.Environment; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import kr.xit.core.consts.ErrorCode; +import kr.xit.core.exception.BizRuntimeException; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.core.support.utils.Checks; +import lombok.extern.slf4j.Slf4j; + +/** + *
    + * description : CustomExceptionHandler 와 함께 에러 처리
    + *               - Filter에서 발생한 오류 처리
    + * packageName : kr.xit.core.spring.filter
    + * fileName    : ExceptionHandlerFilter
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see OncePerRequestFilter + */ +@Slf4j +@Component +public class ExceptionHandlerFilter extends OncePerRequestFilter { + + private final Environment env; + + public ExceptionHandlerFilter(Environment env) { + this.env = env; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + try{ + filterChain.doFilter(request,response); + } catch (BizRuntimeException ex){ + log.error("exception exception handler filter"); + setErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR,response,ex); + }catch (RuntimeException ex){ + log.error("runtime exception exception handler filter"); + setErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR,response,ex); + } + } + + private void setErrorResponse(HttpStatus status, HttpServletResponse response,Throwable ex){ + response.setStatus(status.value()); + response.setContentType("application/json"); + ApiResponseDTO errorResponse = ApiResponseDTO.error(ErrorCode.INTERNAL_SERVER_ERROR); + + // 운영 환경인 경우는 상세 정보 미출력 + if(Arrays.asList(env.getActiveProfiles()).contains("prod")) + errorResponse.setMessage(Checks.isNotEmpty(ex.getCause())? ex.getLocalizedMessage() : ex.getCause().getLocalizedMessage()); + else + errorResponse.setMessage(Checks.isNotEmpty(ex.getCause())? ex.getLocalizedMessage() : ex.getCause().toString()); + try{ + String json = errorResponse.convertToJson(); + log.error(json); + response.getWriter().write(json); + }catch (IOException e){ + log.error("ExceptionHandlerFilter::setErrorResponse", e); + } + + } +} + diff --git a/mens-core/src/main/java/kr/xit/core/spring/filter/LoggingFilter.java b/mens-core/src/main/java/kr/xit/core/spring/filter/LoggingFilter.java new file mode 100644 index 0000000..7631a6a --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/filter/LoggingFilter.java @@ -0,0 +1,115 @@ +package kr.xit.core.spring.filter; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import org.json.simple.JSONObject; + +import kr.xit.core.support.utils.LogUtils; +import lombok.extern.slf4j.Slf4j; + +/** + *
    + * description :
    + *              Async 호출시
    + *              preHandle -> afterConcurrentHandlingStarted -> preHandle -> postHandle -> afterCompletion
    + * packageName : kr.xit.core.spring.interceptor
    + * fileName    : LoggingInterceptor
    + * author      : limju
    + * date        : 2023-06-01
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-06-01    limju       최초 생성
    + *
    + * 
    + * @see org.springframework.web.servlet.config.annotation.WebMvcConfigurer + */ + +@Slf4j +public class LoggingFilter implements Filter { + private List excludedUrls; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws + IOException, + ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; + + try { + String path = httpRequest.getServletPath(); + + if(DispatcherType.REQUEST.equals(request.getDispatcherType()) + && excludedUrls.stream().noneMatch(path::matches)){ + requestLog(httpRequest, getParams(httpRequest)); + } + chain.doFilter(request, response); + + } catch (Exception e) { + //FIXME:: 에러발생시 처리 추가 가능 + throw e; + } + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + String excludePattern = filterConfig.getInitParameter("excludedUrls"); + excludedUrls = Arrays.asList(excludePattern.split(",")); + } + + @Override + public void destroy() { + //Filter.super.destroy(); + } + + private void requestLog(HttpServletRequest request, JSONObject params) { + + if (log.isDebugEnabled()) { + Map map = new LinkedHashMap<>(); + //sb.append("Ajax Call : " + "XMLHttpRequest".equals(request.getHeader(Globals.AJAX_HEADER))).append("\n"); + map.put("URI", request.getRequestURI()); + map.put("URL", request.getRequestURL()); + map.put("IP", request.getRemoteAddr()); + map.put("Referer URI", request.getHeader("referer")); + map.put("Method", request.getMethod()); + map.put("User Agent", request.getHeader("User-Agent")); + map.put("Session", request.getSession().getId()); + map.put("Locale", request.getLocale().getCountry()); + map.put("ContentType", request.getContentType()); + map.put("Parameters", params); + + log.info("{}{}{}", + "\n//============================= Http Request ==============================", + LogUtils.toString(map), + "\n=========================================================================//" + ); + map.clear(); + } + } + + private JSONObject getParams(HttpServletRequest request) { + JSONObject jsonObject = new JSONObject(); + Enumeration params = request.getParameterNames(); + while (params.hasMoreElements()) { + String param = params.nextElement(); + String replaceParam = param.replaceAll("\\.", "-"); + jsonObject.put(replaceParam, request.getParameter(param)); + } + return jsonObject; + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/filter/ReadableRequestWrapper.java b/mens-core/src/main/java/kr/xit/core/spring/filter/ReadableRequestWrapper.java new file mode 100644 index 0000000..5977421 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/filter/ReadableRequestWrapper.java @@ -0,0 +1,178 @@ +package kr.xit.core.spring.filter; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.springframework.http.MediaType; +import org.springframework.web.filter.OncePerRequestFilter; + +import kr.xit.core.support.utils.JsonUtils; +import lombok.extern.slf4j.Slf4j; + +/** + *
    + * description : POST request parameter logging
    + * packageName : kr.xit.core.spring.filter
    + * fileName    : ReadableRequestWrapper
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see HttpServletRequestWrapper + */ +@Slf4j +public class ReadableRequestWrapper extends HttpServletRequestWrapper { + private final Charset encoding; + private byte[] rawData; + private Map params = new HashMap<>(); + + public ReadableRequestWrapper(HttpServletRequest request) { + super(request); + // 원래의 파라미터를 저장 + this.params.putAll(request.getParameterMap()); + + // 인코딩 설정 + String charEncoding = request.getCharacterEncoding(); + this.encoding = StringUtils.isBlank(charEncoding) ? StandardCharsets.UTF_8 : Charset.forName(charEncoding); + + try { + InputStream is = request.getInputStream(); + // InputStream 을 별도로 저장한 다음 getReader() 에서 새 스트림으로 생성 + this.rawData = IOUtils.toByteArray(is); + + // body 파싱 + String collect = this.getReader().lines().collect(Collectors.joining(System.lineSeparator())); + + // body 가 없을경우 로깅 제외 + if (StringUtils.isEmpty(collect)) { + return; + } + + // 파일 업로드시 로깅제외 + if (request.getContentType() != null && request.getContentType().contains( + MediaType.MULTIPART_FORM_DATA_VALUE)) { + return; + } + JSONParser jsonParser = new JSONParser(); + Object parse = jsonParser.parse(collect); + if (parse instanceof JSONArray) { + JSONArray jsonArray = (JSONArray)jsonParser.parse(collect); + setParameter("requestBody", jsonArray.toJSONString()); + } else if(parse instanceof JSONObject){ + setParameter(jsonParser, collect); + } else { + setParameter(jsonParser, parse.toString()); + } + } catch (Exception e) { + log.error("ReadableRequestWrapper init error", e); + } + } + + private void setParameter(JSONParser jsonParser, String parse) throws ParseException { + JSONObject jsonObject = (JSONObject)jsonParser.parse(parse); + Iterator iterator = jsonObject.keySet().iterator(); + while (iterator.hasNext()) { + String key = (String)iterator.next(); + setParameter(key, jsonObject.get(key).toString().replace("\"", "\\\"")); + } + } + + @Override + public String getParameter(String name) { + String[] paramArray = getParameterValues(name); + if (paramArray != null && paramArray.length > 0) { + return paramArray[0]; + } else { + return null; + } + } + + @Override + public Map getParameterMap() { + return Collections.unmodifiableMap(params); + } + + @Override + public Enumeration getParameterNames() { + return Collections.enumeration(params.keySet()); + } + + @Override + public String[] getParameterValues(String name) { + String[] result = null; + String[] dummyParamValue = params.get(name); + + if (dummyParamValue != null) { + result = new String[dummyParamValue.length]; + System.arraycopy(dummyParamValue, 0, result, 0, dummyParamValue.length); + } + return result; + } + + public void setParameter(String name, String value) { + String[] param = {value}; + setParameter(name, param); + } + + public void setParameter(String name, String[] values) { + params.put(name, values); + } + + @Override + public ServletInputStream getInputStream() { + final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.rawData); + + return new ServletInputStream() { + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + // Do nothing + } + + public int read() { + return byteArrayInputStream.read(); + } + }; + } + + @Override + public BufferedReader getReader() { + return new BufferedReader(new InputStreamReader(this.getInputStream(), this.encoding)); + } + + +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/filter/ReadableRequestWrapperFilter.java b/mens-core/src/main/java/kr/xit/core/spring/filter/ReadableRequestWrapperFilter.java new file mode 100644 index 0000000..83ff1b3 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/filter/ReadableRequestWrapperFilter.java @@ -0,0 +1,49 @@ +package kr.xit.core.spring.filter; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import lombok.extern.slf4j.Slf4j; + +/** + *
    + * description : POST request parameter logging
    + * packageName : kr.xit.core.spring.filter
    + * fileName    : ReadableRequestWrapperFilter
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see ReadableRequestWrapper + * @see Filter + */ +@Slf4j +public class ReadableRequestWrapperFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) { + // Do nothing + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + ReadableRequestWrapper wrapper = new ReadableRequestWrapper((HttpServletRequest)request); + chain.doFilter(wrapper, response); + } + + @Override + public void destroy() { + // Do nothing + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/filter/SimpleCORSFilter.java b/mens-core/src/main/java/kr/xit/core/spring/filter/SimpleCORSFilter.java new file mode 100644 index 0000000..ad21487 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/filter/SimpleCORSFilter.java @@ -0,0 +1,54 @@ +package kr.xit.core.spring.filter; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; + +import kr.xit.core.spring.config.properties.CorsProperties; +import kr.xit.core.spring.util.SpringUtils; + +/** + *
    + * description : Cors filter
    + * packageName : kr.xit.core.spring.filter
    + * fileName    : SimpleCORSFilter
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +public class SimpleCORSFilter implements Filter { + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + CorsProperties corsProperties = SpringUtils.getCorsProperties(); + HttpServletResponse httpResponse = (HttpServletResponse) response; + httpResponse.setHeader("Access-Control-Allow-Origin", corsProperties.getAllowedOrigins()); + httpResponse.setHeader("Access-Control-Allow-Methods", corsProperties.getAllowedMethods()); + httpResponse.setHeader("Access-Control-Allow-Headers", corsProperties.getAllowedHeaders()); + httpResponse.setHeader("Access-Control-Allow-Credentials", corsProperties.getAllowCredentials().toString()); + httpResponse.setHeader("Access-Control-Max-Age", corsProperties.getMaxAge().toString()); + httpResponse.setHeader("Access-Control-Expose-Headers", corsProperties.getExposeHeader()); + chain.doFilter(request, response); + } + + @Override + public void init(FilterConfig filterConfig) { + // Do nothing + } + + @Override + public void destroy() { + // Do nothing + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/filter/package-info.java b/mens-core/src/main/java/kr/xit/core/spring/filter/package-info.java new file mode 100644 index 0000000..47790a2 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/filter/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework spring customizing package classes + *

    + * filter + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.spring.filter; diff --git a/mens-core/src/main/java/kr/xit/core/spring/handler/CustomErrorAttributes.java b/mens-core/src/main/java/kr/xit/core/spring/handler/CustomErrorAttributes.java new file mode 100644 index 0000000..c2650ea --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/handler/CustomErrorAttributes.java @@ -0,0 +1,57 @@ +package kr.xit.core.spring.handler; + +import java.util.Map; + +import javax.servlet.Filter; + +import org.springframework.boot.web.error.ErrorAttributeOptions; +import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.WebRequest; + +import kr.xit.core.consts.ErrorCode; +import kr.xit.core.exception.BizRuntimeException; +import kr.xit.core.spring.filter.ReadableRequestWrapper; +import lombok.extern.slf4j.Slf4j; + +/** + *
    + * description :  Catch 되지 않은 에러인 경우 처리 되는 class
    + *                개발자가 처리한 예외처리중 오류 및 framework에서 처리되지 않은 오류 발생시 반드시 처리 필요
    + * packageName : kr.xit.core.spring.handler
    + * fileName    : ReadableRequestWrapperFilter
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see DefaultErrorAttributes + */ +@Slf4j +@Component +public class CustomErrorAttributes extends DefaultErrorAttributes { + + @Override + public Map getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) { + Map result = super.getErrorAttributes(webRequest, options); + + final Throwable error = super.getError(webRequest); + if(error instanceof BizRuntimeException){ + ErrorCode errorCode = ((BizRuntimeException) error).getErrorCode(); + result.put("status", errorCode.getHttpStatus()); + result.put("code", errorCode.getHttpStatus().toString()); + result.put("message", errorCode.getMessage()); + } + + log.error("====================== Exception handler 에서 catch하지 못한 에러 :: CustomErrorAttributes 에서 처리 =================================="); + log.error("========================== 처리되지 않은 에러 발생 =================================="); + log.error("{}", result); + log.error("========================== 반드시 처리해 주세요 ====================================="); + log.error("=================================================================================="); + + return result; + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/handler/CustomRestExceptionHandler.java b/mens-core/src/main/java/kr/xit/core/spring/handler/CustomRestExceptionHandler.java new file mode 100644 index 0000000..36750d2 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/handler/CustomRestExceptionHandler.java @@ -0,0 +1,210 @@ +package kr.xit.core.spring.handler; + +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.CompletionException; + +import javax.validation.ConstraintViolationException; + +import org.egovframe.rte.fdl.cmmn.exception.EgovBizException; +import org.slf4j.MDC; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import kr.xit.core.consts.ErrorCode; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.core.exception.BizRuntimeException; +import kr.xit.core.spring.util.error.ErrorParse; +import kr.xit.core.support.utils.Checks; +import lombok.extern.slf4j.Slf4j; + +/** + *
    + * description : ExceptionHandlerFilter(Filter에서 발생한 에러 처리)와 함께 에러 처리
    + *               ErrorCode 에서 해당 Exception 오류를 정의하여 사용
    + *               - spring boot의 기본 properties
    + *                server.error:
    + *                  include-exception: false  # 응답에 exception의 내용을 포함할지 여부
    + *                  include-stacktrace: never # 오류 응답에 stacktrace 내용을 포함할 지 여부
    + *                  path: '/error'            # 오류 응답을 처리할 Handler의 경로
    + *                  whitelabel.enabled: true  # 서버 오류 발생시 브라우저에 보여줄 기본 페이지 생성 여부
    + * packageName : kr.xit.core.spring.handler
    + * fileName    : CustomRestExceptionHandler
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see ResponseEntityExceptionHandler + */ +@SuppressWarnings("rawtypes") +@Slf4j +@RestControllerAdvice +public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler { + + /** + * MethodArgumentNotValidException 에러 메세지 처리 + * Valid 체크 에러 메세지 처리를 위한 ResponseEntityExceptionHandler#handleMethodArgumentNotValid override + * + * Customize the response for MethodArgumentNotValidException. + *

    This method delegates to {@link #handleExceptionInternal}. + * @param ex the exception + * @param headers the headers to be written to the response + * @param status the selected response status + * @param request the current request + * @return a {@code ResponseEntity} instance + */ + @Override + protected ResponseEntity handleMethodArgumentNotValid( + MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, + WebRequest request) { + log.error("==== MethodArgumentNotValidException override ===="); + Map validErrorMap = new HashMap<>(); + ex.getBindingResult().getFieldErrors() + .forEach(e -> validErrorMap.put(e.getField(), e.getDefaultMessage())); + return ResponseEntity + .ok() + .body(ApiResponseDTO.error(validErrorMap.toString())); +// return super.handleMethodArgumentNotValid(ex, headers, status, request); + } + + + /** + * Customize the response for HttpMessageNotReadableException. + *

    This method delegates to {@link #handleExceptionInternal}. + * @param ex the exception + * @param headers the headers to be written to the response + * @param status the selected response status + * @param request the current request + * @return a {@code ResponseEntity} instance + */ + @Override + protected ResponseEntity handleHttpMessageNotReadable( + HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, + WebRequest request) { + log.error("==== HttpMessageNotReadableException override ===="); + + return ResponseEntity + .ok() + .body(ApiResponseDTO.error(ex.getLocalizedMessage())); + //return super.handleHttpMessageNotReadable(ex, headers, status, request); + } + + /** + * BizRuntimeException + * + * @param e BizRuntimeException + * @return ErrorApiResponse + */ + @ExceptionHandler(value = {BizRuntimeException.class}) + protected ApiResponseDTO handleBizRutimeException(BizRuntimeException e) { + log.error("==== throw BizRutimeException===="); + return sendError(e); + } + + /** + * EgovBizException + * + * @param e EgovBizException + * @return ErrorApiResponse + */ + @ExceptionHandler(value = {EgovBizException.class}) + @ResponseStatus(value = HttpStatus.BAD_REQUEST) + protected ApiResponseDTO handleEgovBizException(EgovBizException e) { + log.error("==== throw EgovBizException ===================="); + return sendError(e); + } + + /** + * NoSuchElementException + * + * @return ErrorResponse + */ + @ExceptionHandler(value = {NoSuchElementException.class}) + @ResponseStatus(value = HttpStatus.BAD_REQUEST) + protected ApiResponseDTO handleNoSuchElementException(NoSuchElementException e) { + log.error("==== throw NoSuchElementException ===================="); + return sendError(e); + } + + /** + * NoSuchElementException + * + * @return ErrorResponse + */ + @ExceptionHandler(value = {IllegalArgumentException.class}) + @ResponseStatus(value = HttpStatus.BAD_REQUEST) + protected ApiResponseDTO handleIllegalArgumentException(IllegalArgumentException e) { + log.error("==== throw IllegalArgumentException ===================="); + return sendError(e); + } + + /** + * Data 중복 + * + * @return ErrorResponse + */ + @ExceptionHandler(value = {ConstraintViolationException.class, DataIntegrityViolationException.class}) + @ResponseStatus(value = HttpStatus.BAD_REQUEST) + protected ApiResponseDTO handleDataException() { + log.error("==== throw ConstraintViolationException, DataIntegrityViolationException ===================="); + return ApiResponseDTO.error(ErrorCode.SQL_DATA_RESOURCE_INVALID); + } + + /** + * 비동기 호출 에러 : timeout + * @return + */ + @ExceptionHandler(value = {CompletionException.class}) + @ResponseStatus(value = HttpStatus.BAD_REQUEST) + protected ApiResponseDTO handleCompletionException(CompletionException e) { + log.error("==== throw CompletionException ===================="); + return sendError(e); + } + + /** + * RuntimeException + * + * @param e RuntimeException + * @return ErrorResponse + */ + @ExceptionHandler(value = {RuntimeException.class}) + @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) + protected ApiResponseDTO handleRuntimeException(RuntimeException e) { + log.error("==== throw RuntimeException ===================="); + return sendError(e); + } + + /** + * Exception + * + * @param e Exception + * @return ErrorResponse + */ + @ExceptionHandler(value = {Exception.class}) + @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) + protected ApiResponseDTO handleException(Exception e) { + log.error("==== throw Exception ===================="); + return sendError(e); + } + + + private ApiResponseDTO sendError(Throwable e) { + Map map = ErrorParse.extractError(Checks.checkVal(e.getCause(), e)); + return ApiResponseDTO.error(String.valueOf(map.get("code")), String.valueOf(map.get("message")), (HttpStatus)map.get("httpStatus")); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/handler/package-info.java b/mens-core/src/main/java/kr/xit/core/spring/handler/package-info.java new file mode 100644 index 0000000..cc140f9 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/handler/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework spring customizing package classes + *

    + * handler + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.spring.handler; diff --git a/mens-core/src/main/java/kr/xit/core/spring/package-info.java b/mens-core/src/main/java/kr/xit/core/spring/package-info.java new file mode 100644 index 0000000..9bde63b --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework spring customizing package classes + *

    + * + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.spring; diff --git a/mens-core/src/main/java/kr/xit/core/spring/resolver/CustomArgumentResolver.java b/mens-core/src/main/java/kr/xit/core/spring/resolver/CustomArgumentResolver.java new file mode 100644 index 0000000..e39d738 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/resolver/CustomArgumentResolver.java @@ -0,0 +1,63 @@ +package kr.xit.core.spring.resolver; + +import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +import lombok.extern.slf4j.Slf4j; + +/** + *
    + * description :  Dispatchersevlet 이 요청 전달시 컨트롤러에서 필요로 하는 객체 생성 및 바인딩
    + *                아래의 어노테이션이 ArgumentResolver로 동작
    + *                * @RequestParam: 쿼리 파라미터 값 바인딩
    + *                * @ModelAttribute: 쿼리 파라미터 및 폼 데이터 바인딩
    + *                * @CookieValue: 쿠키값 바인딩
    + *                * @RequestHeader: 헤더값 바인딩
    + *                * @RequestBody: 바디값 바인딩
    + * packageName : kr.xit.core.spring.resolver
    + * fileName    : CustomArgumentResolver
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see HandlerMethodArgumentResolver + */ +// FIXME:: CustomArgumentResolver +@Slf4j +public class CustomArgumentResolver implements HandlerMethodArgumentResolver { + @Override + public boolean supportsParameter(MethodParameter parameter) { + return true; + //return parameter.getParameterType().equals(User.class); + + // parameter.hasParameterAnnotation(User.class) + } + + @Override + public Object resolveArgument( + MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory) { + // HttpServletRequest httpServletRequest = (HttpServletRequest) webRequest.getNativeRequest(); + // + // String token = JwtUtil.extract(httpServletRequest); + // JwtUtil.validateToken(token); + // + // String userId = JwtUtil.getPayload(token); + // String ipAddress = httpServletRequest.getRemoteAddr(); + // + // return new User(userId, ipAddress); + log.info("~~~CustomArgumentResolver~~~~"); + + return null; + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/resolver/PageableArgumentResolver.java b/mens-core/src/main/java/kr/xit/core/spring/resolver/PageableArgumentResolver.java new file mode 100644 index 0000000..484b979 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/resolver/PageableArgumentResolver.java @@ -0,0 +1,58 @@ +package kr.xit.core.spring.resolver; + +import org.egovframe.rte.ptl.mvc.tags.ui.pagination.PaginationInfo; +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +import lombok.extern.slf4j.Slf4j; + +/** + *
    + * description :  Dispatchersevlet 이 요청 전달시 컨트롤러에서 필요로 하는 객체 생성 및 바인딩
    + *                아래의 어노테이션이 ArgumentResolver로 동작
    + *                * @RequestParam: 쿼리 파라미터 값 바인딩
    + *                * @ModelAttribute: 쿼리 파라미터 및 폼 데이터 바인딩
    + *                * @CookieValue: 쿠키값 바인딩
    + *                * @RequestHeader: 헤더값 바인딩
    + *                * @RequestBody: 바디값 바인딩
    + * packageName : kr.xit.core.spring.resolver
    + * fileName    : PageableArgumentResolver
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see HandlerMethodArgumentResolver + */ +// FIXME:: PageableArgumentResolver +@Slf4j +public class PageableArgumentResolver implements HandlerMethodArgumentResolver { + @Override + public boolean supportsParameter(MethodParameter parameter) { + // 파라미터가 Pageable 타입이면 지원한다 + return PaginationInfo.class.equals(parameter.getParameterType()); + + // PaginationInfo paginationInfo = new PaginationInfo(); + // paginationInfo.setCurrentPageNo(boardVO.getPageIndex()); + // paginationInfo.setRecordCountPerPage(propertyService.getInt("pageUnit")); + // paginationInfo.setPageSize(propertyService.getInt("pageSize")); + // + // boardVO.setFirstIndex(paginationInfo.getFirstRecordIndex()); + // boardVO.setLastIndex(paginationInfo.getLastRecordIndex()); + // boardVO.setRecordCountPerPage(paginationInfo.getRecordCountPerPage()); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + + + //return new PageRequest(offset / limit, limit, new Sort(Sort.Direction.DESC, "seq")); + return null; + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/resolver/package-info.java b/mens-core/src/main/java/kr/xit/core/spring/resolver/package-info.java new file mode 100644 index 0000000..f511d05 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/resolver/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework spring customizing package classes + *

    + * resolver + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.spring.resolver; diff --git a/mens-core/src/main/java/kr/xit/core/spring/util/ApiWebClient.java b/mens-core/src/main/java/kr/xit/core/spring/util/ApiWebClient.java new file mode 100644 index 0000000..ed86c39 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/util/ApiWebClient.java @@ -0,0 +1,262 @@ +package kr.xit.core.spring.util; + +import java.time.Duration; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.http.codec.LoggingCodecSupport; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.ExchangeStrategies; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientRequestException; + +import io.netty.channel.ChannelOption; +import io.netty.channel.ConnectTimeoutException; +import io.netty.handler.timeout.ReadTimeoutException; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.handler.timeout.WriteTimeoutHandler; +import kr.xit.core.consts.Constants; +import kr.xit.core.exception.BizRuntimeException; +import kr.xit.core.model.ApiResponseDTO; +import kr.xit.core.model.ErrorDTO; +import kr.xit.core.spring.util.error.ClientError; +import kr.xit.core.spring.util.error.ErrorParse; +import kr.xit.core.spring.util.error.ServerError; +import kr.xit.core.support.utils.Checks; +import kr.xit.core.support.utils.JsonUtils; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Mono; +import reactor.netty.http.client.HttpClient; + +/** + *
    + * description : react Restfull Util
    + *
    + * packageName : kr.xit.core.spring.util
    + * fileName    : ApiWebClient
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see kr.xit.core.spring.config.AsyncExecutorConfig + * @see ClientError + * @see ServerError + */ +@Slf4j +@Component +public class ApiWebClient { + private final WebClient webClient; + + + public ApiWebClient( + @Value("${contract.provider.kakao.token:}") + String accessToken, + @Value("${contract.provider.kakao.uuid:}") + String contractUuid, + @Value("${contract.provider.connection.timeout:5000}") + int connectionTimeout, + @Value("${contract.provider.connection.readTimeout:5000}") + int readTimeout + ) { + ExchangeStrategies es = ExchangeStrategies.builder() + .codecs(configurer -> { + configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024); + //configurer.customCodecs().register(new Jackson2JsonDecoder()); + //configurer.customCodecs().register(new Jackson2JsonEncoder()); + }) + .build(); + + //FIXME::rest call async 로깅 + // ExchangeStrategies를 통해 setEnableLoggingRequestDetails(true)로 설정 + // boot에서 로깅 org.springframework.web.reactive.function.client.ExchangeFunctions: DEBUG 하여 활성 + es.messageWriters() + .stream() + .filter(LoggingCodecSupport.class::isInstance) + .forEach(writer -> ((LoggingCodecSupport)writer).setEnableLoggingRequestDetails(true)); + + + this.webClient = WebClient.builder() + .clientConnector(httpClient(connectionTimeout, readTimeout)) + .baseUrl("http://localhost:8081") // (1) 외부 API Base URl + .defaultHeaders((headers)->{ + headers.setContentType(new MediaType(MediaType.APPLICATION_JSON, Constants.CHARSET_UTF8)); + headers.set(Constants.HeaderName.TOKEN.getCode(), String.format("%s %s", Constants.JwtToken.GRANT_TYPE.getCode(), accessToken)); + headers.set(Constants.HeaderName.UUID.getCode(), contractUuid); + + }) + .exchangeStrategies(es) + .filters(exchangeFilterFunctions -> { + exchangeFilterFunctions.add(logRequest()); + exchangeFilterFunctions.add(logResponse()); + //TODO::에러발생시 점검필요 + //exchangeFilterFunctions.add(errorHandler()); + }) + .build(); + } + + //TODO:: ApiResponseDTO로 return 하도록 해야할 듯 + // CompletableFuture.supplyAsync().handle() / exceptionally() 사용을 위해 + /** + *
    +     * Async call - Response Entity(상태정보등) 가 필요한 경우
    +     * @param url call url
    +     * @param method POST|GET
    +     * @param body JSON String type
    +     * @param rtnClzz rtnClzz return type class
    +     *        (ex: new KkopayDocDTO.DocStatusResponse().getClass())
    +     * @return T rtnClzz return DTO
    +     * 
    + */ + public T exchange(String url, HttpMethod method, Object body, Class rtnClzz) { + + return webClient.method(method) + .uri(url) + .bodyValue(Objects.requireNonNullElse(body, "")) + .exchangeToMono(res -> res.bodyToMono(rtnClzz)) + .block(); + } + + public T exchange2(String url, HttpMethod method, Object body, Class rtnClzz) { + + return webClient.method(method) + .uri(url) + + .bodyValue(Objects.requireNonNullElse(body, "")) + .exchangeToMono(res -> res.bodyToMono(rtnClzz)) + + .block(); + } + + public ApiResponseDTO sendError(Throwable e) { + + Map map = ErrorParse.extractError(e.getCause()); + return ApiResponseDTO.error(String.valueOf(map.get("code")), String.valueOf(map.get("message")), (HttpStatus)map.get("httpStatus")); + } + + + /** + *
    +     * Async call - body 만 필요한 경우
    +     * @param url call url
    +     * @param method POST|GET
    +     * @param body JSON String type
    +     * @param rtnClzz rtnClzz return type class
    +     *        (ex: new KkopayDocDTO.DocStatusResponse().getClass())
    +     * @return T rtnClzz return DTO
    +     * 
    + */ + public T retrive(String url, HttpMethod method, Object body, Class rtnClzz) { + + return webClient.mutate() + .build() + .method(method) + .uri(url) + .bodyValue(Objects.requireNonNullElse(body, "")) + .retrieve() + .onStatus( + status -> status.is4xxClientError() || status.is5xxServerError(), + res -> res.bodyToMono(String.class).map(BizRuntimeException::create)) + .bodyToMono(rtnClzz) + .block(); + + } + + + + + private ReactorClientHttpConnector httpClient(int connectTimeout, int readTimeout){ + return new ReactorClientHttpConnector( + HttpClient.create() + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout) + .responseTimeout(Duration.ofMillis(connectTimeout)) + .doOnConnected(conn -> + conn.addHandlerLast(new ReadTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS)) + .addHandlerLast(new WriteTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS))) + ); + } + + + ExchangeFilterFunction logRequest() { + return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> { + if (log.isDebugEnabled()) { + StringBuilder sb = new StringBuilder("\n>>>>>>>>>> Http Rest Request <<<<<<<<<<<<<\n"); + clientRequest + .headers() + .forEach((name, values) -> values.forEach(value -> sb.append(name).append(": ").append(value).append("\n"))); + log.debug(sb.toString()); + } + return Mono.just(clientRequest); + }); + } + + ExchangeFilterFunction logResponse() { + return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> { + StringBuilder sb = new StringBuilder("\n>>>>>>>>>> Http Rest Response <<<<<<<<<<<<<\n"); + clientResponse.headers() + .asHttpHeaders() + .forEach((name, values) -> values.forEach(value -> sb.append(name).append(": ").append(value).append("\n"))); + log.debug(sb.toString()); + return Mono.just(clientResponse); + }); + } + + ExchangeFilterFunction errorHandler() { + return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> { + HttpStatus status = clientResponse.statusCode(); + + if (clientResponse.statusCode().is4xxClientError()) { + return clientResponse.bodyToMono(String.class) + .flatMap(errorBody -> Mono.error(new ClientError(status, errorBody))); + + } else if (clientResponse.statusCode().is5xxServerError()) { + return clientResponse.bodyToMono(String.class) + .flatMap(errorBody -> Mono.error(new ServerError(status, errorBody))); + + } + return Mono.just(clientResponse); + }); + } + + + public T exchange3(String url, HttpMethod method, Object body, Class rtnClzz) { + CountDownLatch cdl = new CountDownLatch(1); + AtomicReference result = new AtomicReference<>(); + + webClient.mutate() + .build() + .method(method) + .uri(url) + .bodyValue(body) + .exchangeToMono(res -> res.bodyToMono(rtnClzz)) + .onErrorContinue((e, i) -> { + log.error("{}", e); + }) + .doOnTerminate(() -> cdl.countDown()) + .subscribe(data -> result.set(data)); + try { + cdl.await(); + } catch (InterruptedException e) { + // thread pool에 에러상태 set + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + return result.get(); + } + +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/util/SpringUtils.java b/mens-core/src/main/java/kr/xit/core/spring/util/SpringUtils.java new file mode 100644 index 0000000..351937d --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/util/SpringUtils.java @@ -0,0 +1,89 @@ +package kr.xit.core.spring.util; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.core.env.Environment; + +import egovframework.com.cmm.jwt.config.EgovJwtTokenUtil; +import egovframework.com.cmm.jwt.config.JwtVerification; +import kr.xit.core.spring.config.properties.CorsProperties; +import kr.xit.core.spring.config.support.ApplicationContextProvider; + +/** + *
    + * description : Get Bean Object
    + *               Filter / Interceptor 등에서 Bean 사용시 필요
    + *               (Bean으로 등록되는 클래스 내에서만 @Autowired / @Resource 등이 동작)
    + * packageName : kr.xit.core.spring.util
    + * fileName    : SpringUtils
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see ApplicationContextProvider + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class SpringUtils { + public static ApplicationContext getApplicationContext() { + return ApplicationContextProvider.getApplicationContext(); + } + + public static boolean containsBean(String beanName) { + return getApplicationContext().containsBean(beanName); + } + + public static Object getBean(String beanName) { + return getApplicationContext().getBean(beanName); + } + + public static Object getBean(Class clazz) { + return getApplicationContext().getBean(clazz); + } + + /** + * + * @return MessageSourceAccessor + */ + public static MessageSourceAccessor getMessageSourceAccessor(){ + return (MessageSourceAccessor)getBean(MessageSourceAccessor.class); + } + + /** + * + * @return JwtVerification + */ + public static JwtVerification getJwtVerification(){ + return (JwtVerification)getBean(JwtVerification.class); + } + + /** + * + * @return EgovJwtTokenUtil + */ + public static EgovJwtTokenUtil getEgovJwtTokenUtil(){ + return (EgovJwtTokenUtil)getBean(EgovJwtTokenUtil.class); + } + + /** + * + * @return PropertiesConfiguration + */ + public static PropertiesConfiguration getPropertiesConfiguration(){ + return (PropertiesConfiguration)getBean(PropertiesConfiguration.class); + } + + public static Environment getEnvironment(){ + return (Environment)getBean(Environment.class); + } + + public static CorsProperties getCorsProperties(){ + return (CorsProperties)getBean(CorsProperties.class); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/util/error/ClientError.java b/mens-core/src/main/java/kr/xit/core/spring/util/error/ClientError.java new file mode 100644 index 0000000..f85e703 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/util/error/ClientError.java @@ -0,0 +1,33 @@ +package kr.xit.core.spring.util.error; + +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +/** + *
    + * description : WebClient 4xx 에러 : 에러 핸들러에서 사용
    + *
    + * packageName : kr.xit.core.spring.util.error
    + * fileName    : ClientError
    + * author      : limju
    + * date        : 2023-05-25
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-05-25    limju       최초 생성
    + *
    + * 
    + * @see kr.xit.core.spring.util.ApiWebClient + */ +@Getter +public class ClientError extends RuntimeException { + private final HttpStatus status; + private final String body; + + public ClientError(HttpStatus status, String body) { + this.status = status; + this.body = body; + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/util/error/ErrorParse.java b/mens-core/src/main/java/kr/xit/core/spring/util/error/ErrorParse.java new file mode 100644 index 0000000..0b09767 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/util/error/ErrorParse.java @@ -0,0 +1,110 @@ +package kr.xit.core.spring.util.error; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ExecutionException; + +import kr.xit.core.exception.BizRuntimeException; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatus; +import org.springframework.web.reactive.function.client.WebClientRequestException; + +import io.netty.channel.ConnectTimeoutException; +import io.netty.handler.timeout.ReadTimeoutException; +import kr.xit.core.model.ErrorDTO; +import kr.xit.core.support.utils.Checks; +import kr.xit.core.support.utils.JsonUtils; + +/** + *
    + * description :
    + *
    + * packageName : kr.xit.core.spring.util.error
    + * fileName    : ErrorParse
    + * author      : limju
    + * date        : 2023-06-01
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-06-01    limju       최초 생성
    + *
    + * 
    + */ +public class ErrorParse { + + public static Map extractError(final Throwable e){ + String errCode = String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()); + String message = Checks.isNotNull(e) ? e.getLocalizedMessage() : StringUtils.EMPTY; + HttpStatus httpStatus = null; + + if(e instanceof BizRuntimeException) { + BizRuntimeException be = (BizRuntimeException)e; + return getStringObjectMap(be.getCode(), be.getMessage(), HttpStatus.BAD_REQUEST); + } + + if(e instanceof ClientError) { + ClientError ce = (ClientError)e; + ErrorDTO error = JsonUtils.toObject(ce.getBody(), ErrorDTO.class); + return getStringObjectMap(Objects.requireNonNull(error).getErrorCode(), error.getErrorMessage(), ce.getStatus()); + } + + if(e instanceof ServerError) { + ServerError ce = (ServerError) e; + ErrorDTO error = JsonUtils.toObject(ce.getBody(), ErrorDTO.class); + return getStringObjectMap(Objects.requireNonNull(error).getErrorCode(), error.getErrorMessage(), ce.getStatus()); + } + + // Async(React) Exception 처리 + if(e instanceof WebClientRequestException) { + if (e.getCause() instanceof ConnectTimeoutException || e.getCause() instanceof ReadTimeoutException) { + return getTimeoutException(); + } + } + + if(e instanceof ExecutionException) { + if (e.getCause() instanceof WebClientRequestException) { + message = e.getCause().getMessage(); + + // Timeout 에러 + if (e.getCause().getCause() instanceof ReadTimeoutException || e.getCause().getCause() instanceof ConnectTimeoutException) { + return getTimeoutException(); + } + } + } + + // Async(React) Exception 처리 + if(e.getCause() instanceof WebClientRequestException){ + + // Timeout 에러 + if(e.getCause().getCause() instanceof ReadTimeoutException){ + return getTimeoutException(); + } + + } + + if(Checks.isNotEmpty(e.getCause())) { + message = e.getCause().getMessage(); + } + + return getStringObjectMap(errCode, message, httpStatus); + } + + @NotNull + private static Map getStringObjectMap(String errCode, String message, HttpStatus httpStatus) { + Map errorMap = new HashMap<>(); + errorMap.put("code", errCode); + errorMap.put("message", message); + errorMap.put("httpStatus", httpStatus); + return errorMap; + } + + private static Map getTimeoutException(){ + return getStringObjectMap( + String.valueOf(HttpStatus.REQUEST_TIMEOUT.value()), + HttpStatus.REQUEST_TIMEOUT.getReasonPhrase(), + HttpStatus.REQUEST_TIMEOUT); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/util/error/ServerError.java b/mens-core/src/main/java/kr/xit/core/spring/util/error/ServerError.java new file mode 100644 index 0000000..53ba638 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/util/error/ServerError.java @@ -0,0 +1,33 @@ +package kr.xit.core.spring.util.error; + +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +/** + *
    + * description : WebClient 5xx 에러 : 에러 핸들러에서 사용
    + *
    + * packageName : kr.xit.core.spring.util.error
    + * fileName    : ServerError
    + * author      : limju
    + * date        : 2023-05-25
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-05-25    limju       최초 생성
    + *
    + * 
    + * @see kr.xit.core.spring.util.ApiWebClient + */ +@Getter +public class ServerError extends RuntimeException { + private final HttpStatus status; + private final String body; + + public ServerError(HttpStatus status, String body) { + this.status = status; + this.body = body; + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/util/error/package-info.java b/mens-core/src/main/java/kr/xit/core/spring/util/error/package-info.java new file mode 100644 index 0000000..f2077e8 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/util/error/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework spring customizing package classes + *

    + * web Client error help classes + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.spring.util.error; diff --git a/mens-core/src/main/java/kr/xit/core/spring/util/package-info.java b/mens-core/src/main/java/kr/xit/core/spring/util/package-info.java new file mode 100644 index 0000000..b07ab43 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/util/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework spring customizing package classes + *

    + * utility + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.spring.util; diff --git a/mens-core/src/main/java/kr/xit/core/spring/validator/CustomCollectionValidator.java b/mens-core/src/main/java/kr/xit/core/spring/validator/CustomCollectionValidator.java new file mode 100644 index 0000000..729e3d0 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/validator/CustomCollectionValidator.java @@ -0,0 +1,51 @@ +package kr.xit.core.spring.validator; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.executable.ExecutableValidator; + +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; +import org.springframework.validation.beanvalidation.SpringValidatorAdapter; + +import lombok.RequiredArgsConstructor; + +/** + *
    + * description :
    + *
    + * packageName : kr.xit.core.spring.validator
    + * fileName    : CustomCollectionValidator
    + * author      : limju
    + * date        : 2023-05-12
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-05-12    limju       최초 생성
    + *
    + * 
    + */ +@RequiredArgsConstructor +@Component +public class CustomCollectionValidator implements Validator { + + private final SpringValidatorAdapter validator; + + @Override + public boolean supports(Class clazz) { + return List.class.equals(clazz); + //return Collection.class.equals(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + for (Object object : (Collection)target) { + validator.validate(object, errors); + } + } +} diff --git a/mens-core/src/main/java/kr/xit/core/spring/validator/package-info.java b/mens-core/src/main/java/kr/xit/core/spring/validator/package-info.java new file mode 100644 index 0000000..d4b5444 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/spring/validator/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework spring customizing package classes + *

    + * validator + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.spring.validator; diff --git a/mens-core/src/main/java/kr/xit/core/support/logback/ExcludeLogFilter.java b/mens-core/src/main/java/kr/xit/core/support/logback/ExcludeLogFilter.java new file mode 100644 index 0000000..6f17bc1 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/logback/ExcludeLogFilter.java @@ -0,0 +1,39 @@ +package kr.xit.core.support.logback; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.filter.Filter; +import ch.qos.logback.core.spi.FilterReply; + +/** + *
    + * description : logback log에서
    + *               1) SQL 기본로그중  "==>  Preparing: " 제외
    + *               2) 파라메터 출력 로그 "==> Parameters: " 제외
    + *               3) Hibernate SQL 로그 제외 : org.hibernate.SQL org.hibernate.type.descriptor.sql
    + * packageName : kr.xit.core.support.logback
    + * fileName    : ExcloudLogFilter
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see Filter + */ +public class ExcludeLogFilter extends Filter { + @Override + public FilterReply decide(ILoggingEvent event) { + if (event.getMessage().contains("==> Preparing:") + || event.getMessage().contains("==> Parameters:") + || event.getMessage().contains("org.hibernate.SQL") + || event.getMessage().contains("org.hibernate.type.descriptor.sql") + ) { + return FilterReply.DENY; //DENY | NEUTRAL | ACCEPT + } else { + return FilterReply.ACCEPT; + } + } +} + diff --git a/mens-core/src/main/java/kr/xit/core/support/logback/LogbackMaskingPatternLayout.java b/mens-core/src/main/java/kr/xit/core/support/logback/LogbackMaskingPatternLayout.java new file mode 100644 index 0000000..5583372 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/logback/LogbackMaskingPatternLayout.java @@ -0,0 +1,77 @@ +package kr.xit.core.support.logback; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + + +/** + * logback log masking + *
    + * 
    + *     
    + *         
    + * 	           \"SSN\"\s*:\s*\"(.*?)\" 
    + * 	           \"address\"\s*:\s*\"(.*?)\" 
    + * 	           (\d+\.\d+\.\d+\.\d+) 
    + * 	           (\w+@\w+\.\w+) 
    + * 	           %-5p [%d{ISO8601,UTC}] [%thread] %c: %m%n%rootException
    + *         
    + *     
    + * 
    + * 
    + */ +/** + *
    + * description : logback log에서
    + *               1) SQL 기본로그중  "==>  Preparing: " 제외
    + *               2) 파라메터 출력 로그 "==> Parameters: " 제외
    + *               3) Hibernate SQL 로그 제외 : org.hibernate.SQL org.hibernate.type.descriptor.sql
    + * packageName : kr.xit.core.support.logback
    + * fileName    : ExcloudLogFilter
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see ch.qos.logback.core.filter.Filter + */ +/* +public class LogbackMaskingPatternLayout extends PatternLayout { + + private Pattern multilinePattern; + private List maskPatterns = new ArrayList<>(); + + public void addMaskPattern(String maskPattern) { + maskPatterns.add(maskPattern); + multilinePattern = Pattern.compile(maskPatterns.stream().collect(Collectors.joining("|")), Pattern.MULTILINE); + } + + @Override + public String doLayout(ILoggingEvent event) { + return maskMessage(super.doLayout(event)); + } + + private String maskMessage(String message) { + if (multilinePattern == null) { + return message; + } + StringBuilder sb = new StringBuilder(message); + Matcher matcher = multilinePattern.matcher(sb); + while (matcher.find()) { + IntStream.rangeClosed(1, matcher.groupCount()).forEach(group -> { + if (matcher.group(group) != null) { + IntStream.range(matcher.start(group), matcher.end(group)).forEach(i -> sb.setCharAt(i, '*')); + } + }); + } + return sb.toString(); + } +} +*/ diff --git a/mens-core/src/main/java/kr/xit/core/support/logback/PatternLayoutWithUserContext.java b/mens-core/src/main/java/kr/xit/core/support/logback/PatternLayoutWithUserContext.java new file mode 100644 index 0000000..1a953a9 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/logback/PatternLayoutWithUserContext.java @@ -0,0 +1,15 @@ +package kr.xit.core.support.logback; + +//import ch.qos.logback.classic.PatternLayout; + +/** + * 사용자 및 세션별 로깅 처리시 + */ +/* +public class PatternLayoutWithUserContext extends PatternLayout { + static { + PatternLayout.defaultConverterMap.put("user", UserConverter.class.getName()); + PatternLayout.defaultConverterMap.put("session", SessionConverter.class.getName()); + } +} +*/ diff --git a/mens-core/src/main/java/kr/xit/core/support/logback/SessionConverter.java b/mens-core/src/main/java/kr/xit/core/support/logback/SessionConverter.java new file mode 100644 index 0000000..d3b36c6 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/logback/SessionConverter.java @@ -0,0 +1,21 @@ +package kr.xit.core.support.logback; +/* +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import ch.qos.logback.classic.pattern.ClassicConverter; +import ch.qos.logback.classic.spi.ILoggingEvent; + +// logback 에서 사용하기 위한 변수를 convert 해주는 class +// 해당 class는 request별 session을 출력하기 위한 용도임 +public class SessionConverter extends ClassicConverter { + @Override + public String convert(ILoggingEvent event) { + RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); + if (attrs != null) { + return "session: " + attrs.getSessionId(); + } + return ""; + } +} +*/ diff --git a/mens-core/src/main/java/kr/xit/core/support/logback/UserConverter.java b/mens-core/src/main/java/kr/xit/core/support/logback/UserConverter.java new file mode 100644 index 0000000..627e181 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/logback/UserConverter.java @@ -0,0 +1,22 @@ +package kr.xit.core.support.logback; +/* +import ch.qos.logback.classic.pattern.ClassicConverter; +import ch.qos.logback.classic.spi.ILoggingEvent; + +// logback 에서 사용하기 위한 변수를 convert 해주는 class +// 해당 class는 user 정보에 대해 출력하기 위한 용도임 +public class UserConverter extends ClassicConverter { + @Override + public String convert(ILoggingEvent event) { + // Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + // + // if (auth != null && auth.getPrincipal() instanceof Map) { + // Map authMap = (Map) auth.getPrincipal(); + // String userNo = authMap.get("USER_NO").toString(); + // return "userNo: " + userNo; + // } + + return "user-id"; + } +} +*/ diff --git a/mens-core/src/main/java/kr/xit/core/support/logback/package-info.java b/mens-core/src/main/java/kr/xit/core/support/logback/package-info.java new file mode 100644 index 0000000..bd84e3e --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/logback/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework 3rd-party library package classes + *

    + * logback 확장 + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.support.logback; diff --git a/mens-core/src/main/java/kr/xit/core/support/mybatis/CamelCaseLinkedMap.java b/mens-core/src/main/java/kr/xit/core/support/mybatis/CamelCaseLinkedMap.java new file mode 100644 index 0000000..69051f4 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/mybatis/CamelCaseLinkedMap.java @@ -0,0 +1,31 @@ +package kr.xit.core.support.mybatis; + +import java.util.LinkedHashMap; + +import org.springframework.jdbc.support.JdbcUtils; + +import ch.qos.logback.core.filter.Filter; + +/** + *
    + * description : CamelCase LinkedHashMap
    + * packageName : kr.xit.core.support.mybatis
    + * fileName    : CamelCaseLinkedMap
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see LinkedHashMap + */ +public class CamelCaseLinkedMap extends LinkedHashMap { + + @Override + public Object put(Object key, Object value){ + if(key != null && key.toString().contains("_")) return super.put(JdbcUtils.convertUnderscoreNameToPropertyName((String)key), value); + else return super.put(key, value); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/support/mybatis/CamelCaseMap.java b/mens-core/src/main/java/kr/xit/core/support/mybatis/CamelCaseMap.java new file mode 100644 index 0000000..d70a339 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/mybatis/CamelCaseMap.java @@ -0,0 +1,30 @@ +package kr.xit.core.support.mybatis; + +import java.util.HashMap; +import java.util.LinkedHashMap; + +import org.springframework.jdbc.support.JdbcUtils; + +/** + *
    + * description : CamelCase HashMap
    + * packageName : kr.xit.core.support.mybatis
    + * fileName    : CamelCaseLinkedMap
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see HashMap + */ +public class CamelCaseMap extends HashMap { + + @Override + public Object put(Object key, Object value){ + if(key != null && key.toString().contains("_")) return super.put(JdbcUtils.convertUnderscoreNameToPropertyName((String)key), value); + else return super.put(key, value); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/support/mybatis/ObjectTypeHandler.java b/mens-core/src/main/java/kr/xit/core/support/mybatis/ObjectTypeHandler.java new file mode 100644 index 0000000..1435634 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/mybatis/ObjectTypeHandler.java @@ -0,0 +1,91 @@ +package kr.xit.core.support.mybatis; + +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.Date; +import java.util.LinkedHashMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; + +/** + *
    + * description : Mybatis Object Handler
    + * packageName : kr.xit.core.support.mybatis
    + * fileName    : ObjectTypeHandler
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * + * @see BaseTypeHandler + */ +public class ObjectTypeHandler extends BaseTypeHandler { + private static final Log log = LogFactory.getLog(ObjectTypeHandler.class); + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) + throws SQLException { + ps.setObject(i, parameter); + } + + @Override + public Object getNullableResult(ResultSet rs, String columnName) throws SQLException { + Object object = rs.getObject(columnName); + + if (object instanceof Clob) { + Clob clob = (Clob) object; + try { + int size = (int) clob.length(); + return clob.getSubString(1, size); + } catch (Exception e) { + log.error(e.getMessage()); + } + } else if (object instanceof java.sql.Date) { + Timestamp sqlTimestamp = rs.getTimestamp(columnName); + if (sqlTimestamp != null) { + return new Date(sqlTimestamp.getTime()); + } + } + return object; + } + + @Override + public Object getNullableResult(ResultSet rs, int columnIndex) + throws SQLException { + Object object = rs.getObject(columnIndex); + + if (object instanceof Clob) { + + Clob clob = (Clob) object; + try { + int size = (int) clob.length(); + return clob.getSubString(1, size); + } catch (Exception e) { + log.error(e.getMessage()); + } + } else if (object instanceof java.sql.Date) { + Timestamp sqlTimestamp = rs.getTimestamp(columnIndex); + if (sqlTimestamp != null) { + return new Date(sqlTimestamp.getTime()); + } + } + return object; + } + + @Override + public Object getNullableResult(CallableStatement cs, int columnIndex) + throws SQLException { + return cs.getObject(columnIndex); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/support/mybatis/RefreshableSqlSessionFactoryBean.java b/mens-core/src/main/java/kr/xit/core/support/mybatis/RefreshableSqlSessionFactoryBean.java new file mode 100644 index 0000000..587a469 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/mybatis/RefreshableSqlSessionFactoryBean.java @@ -0,0 +1,183 @@ +package kr.xit.core.support.mybatis; + +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.type.BaseTypeHandler; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.core.io.Resource; + +/** + *
    + * description :  mybatis mapper 자동 감지 후 자동으로 서버 재시작이 필요 없이 반영
    + * packageName : kr.xit.core.support.mybatis
    + * fileName    : RefreshableSqlSessionFactoryBean
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + * @see SqlSessionFactoryBean + */ +public class RefreshableSqlSessionFactoryBean extends SqlSessionFactoryBean implements DisposableBean { + private static final Log log = LogFactory.getLog(RefreshableSqlSessionFactoryBean.class); + private SqlSessionFactory proxy; + private int interval = 10000; + private Timer timer; + private TimerTask task; + private Resource[] mapperLocations; + /** + * 파일 감시 쓰레드가 실행중인지 여부. + */ + private boolean running = false; + private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); + private final Lock r = rwl.readLock(); + private final Lock w = rwl.writeLock(); + + public void setMapperLocations(Resource[] mapperLocations) { + super.setMapperLocations(mapperLocations); + this.mapperLocations = mapperLocations; + } + + public void setInterval(int interval) { + this.interval = interval; + } + + /** + * @throws Exception + */ + public void refresh() throws Exception { + if (log.isInfoEnabled()) { + log.info("refreshing sqlMapClient."); + } + w.lock(); + try { + super.afterPropertiesSet(); + } finally { + w.unlock(); + } + } + + /** + * 싱글톤 멤버로 SqlMapClient 원본 대신 프록시로 설정하도록 오버라이드. + */ + public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + setRefreshable(); + } + + private void setRefreshable() { + proxy = (SqlSessionFactory) Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSessionFactory.class }, new InvocationHandler() { + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return method.invoke(getParentObject(), args); + } + }); + task = new TimerTask() { + private final Map map = new HashMap<>(); + + public void run() { + if (isModified()) { + try { + refresh(); + } catch (Exception e) { + log.error("caught exception", e); + } + } + } + + private boolean isModified() { + if(mapperLocations == null) return false; + + boolean retVal = false; + for (int i = 0; i < mapperLocations.length; i++) { + Resource mappingLocation = mapperLocations[i]; + retVal |= findModifiedResource(mappingLocation); + } + return retVal; + } + + private boolean findModifiedResource(Resource resource) { + boolean retVal = false; + List modifiedResources = new ArrayList<>(); + try { + long modified = resource.lastModified(); + if (map.containsKey(resource)) { + long lastModified = map.get(resource); + if (lastModified != modified) { + map.put(resource, modified); + modifiedResources.add(resource.getDescription()); + retVal = true; + } + } else { + map.put(resource, modified); + } + } catch (IOException e) { + log.error("caught exception", e); + } + return retVal; + } + }; + timer = new Timer(true); + resetInterval(); + } + + private Object getParentObject() throws Exception { + r.lock(); + try { + return super.getObject(); + } finally { + r.unlock(); + } + } + + public SqlSessionFactory getObject() { + return this.proxy; + } + + public Class getObjectType() { + return (this.proxy != null ? this.proxy.getClass() : SqlSessionFactory.class); + } + + public boolean isSingleton() { + return true; + } + + public void setCheckInterval(int ms) { + interval = ms; + if (timer != null) { + resetInterval(); + } + } + + private void resetInterval() { + if (running) { + timer.cancel(); + running = false; + } + if (interval > 0) { + timer.schedule(task, 0, interval); + running = true; + } + } + + public void destroy() throws Exception { + timer.cancel(); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/support/mybatis/package-info.java b/mens-core/src/main/java/kr/xit/core/support/mybatis/package-info.java new file mode 100644 index 0000000..02f6590 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/mybatis/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework 3rd-party library package classes + *

    + * mybatis + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.support.mybatis; diff --git a/mens-core/src/main/java/kr/xit/core/support/package-info.java b/mens-core/src/main/java/kr/xit/core/support/package-info.java new file mode 100644 index 0000000..51c7fa8 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework 3rd-party library package classes + *

    + * + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.support; diff --git a/mens-core/src/main/java/kr/xit/core/support/slack/SlackWebhookPush.java b/mens-core/src/main/java/kr/xit/core/support/slack/SlackWebhookPush.java new file mode 100644 index 0000000..e88ce4d --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/slack/SlackWebhookPush.java @@ -0,0 +1,163 @@ +package kr.xit.core.support.slack; + +import static com.slack.api.webhook.WebhookPayloads.*; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +import kr.xit.core.support.utils.Checks; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import com.slack.api.Slack; +import com.slack.api.model.Attachment; +import com.slack.api.model.Field; + +import lombok.extern.slf4j.Slf4j; + +/** + *
    + * description : Slack webhook push
    + *               Error 혹은 메세지를 slack에 push할 경우 사용
    + * packageName : kr.xit.core.spring.support
    + * fileName    : SlackWebhookPush
    + * author      : limju
    + * date        : 2023-05-11
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-05-11    limju       최초 생성
    + *
    + * 
    + * @see kr.xit.core.spring.annotation.TraceLogging + */ +@Slf4j +@Component +public class SlackWebhookPush { + + private final Slack slackClient = Slack.getInstance(); + + @Value("${app.slack-webhook.url}") + private String webhookUrl; + + private static final String DT_FMT = "yyyy-MM-dd HH:mm:ss.SSS"; + private static final String API_CALL_ERR_MSG = "API 호출 에러"; + private static final String SLACK_CALL_ERR_MSG = "Slack 호출 에러"; + private static final String IP_HEADER = "X-FORWARDED-FOR"; + private static final String MSG_COLOR = "0xff0000"; + + /** + * http call Exception Slack push + * @param e Exception + * @param request HttpServletRequest + */ + @Async + public void sendSlackAlertErrorLog(final Exception e, final HttpServletRequest request) { + try { + slackClient.send(webhookUrl, payload(p -> p + .text(API_CALL_ERR_MSG) + .attachments(List.of(generateSlackAttachment(e, request))) + )); + } catch (IOException slackError) { + log.error(SLACK_CALL_ERR_MSG); + } + } + + /** + * http call message slack push + * @param message String + * @param request HttpServletRequest + */ + @Async + public void sendSlackAlertLog(final String message, final HttpServletRequest request) { + sendSlackAlertLog(request.getRequestURL() + " " + request.getMethod(), message, getIp(request)); + try { + final String requestTime = DateTimeFormatter.ofPattern(DT_FMT).format(LocalDateTime.now()); + + slackClient.send(webhookUrl, payload(p -> p + .text(API_CALL_ERR_MSG) + .attachments( + List.of(Attachment.builder() + .color(MSG_COLOR) + .title(requestTime + " 발생 에러 로그") + .fields( + getFields(message, request.getRequestURL() + " " + request.getMethod(), getIp(request)) + ) + .build()) + ) + )); + } catch (IOException slackError) { + log.error(SLACK_CALL_ERR_MSG); + } + } + + /** + * 에러 내용 slack push + * @param message String + * @param url String + * @param ip String + */ + @Async + public void sendSlackAlertLog(final String message, final String url, String ip) { + try { + final String requestTime = DateTimeFormatter.ofPattern(DT_FMT).format(LocalDateTime.now()); + + slackClient.send(webhookUrl, payload(p -> p + .text(API_CALL_ERR_MSG) + .attachments( + List.of(Attachment.builder() + .color(MSG_COLOR) + .title(requestTime + " 발생 에러 로그") + .fields(getFields(message, url, ip) + ) + .build()) + ) + )); + } catch (IOException slackError) { + log.error(SLACK_CALL_ERR_MSG); + } + } + + private Attachment generateSlackAttachment(final Exception e, final HttpServletRequest request) { + String requestTime = DateTimeFormatter.ofPattern(DT_FMT).format(LocalDateTime.now()); + String xffHeader = request.getHeader(IP_HEADER); + return Attachment.builder() + .color(MSG_COLOR) + .title(requestTime + " 발생 에러 로그") + .fields( + getFields( + Checks.isEmpty(e.getCause()) ? e.getMessage() : e.getCause().getMessage(), + request.getRequestURL() + " " + request.getMethod(), + xffHeader == null ? request.getRemoteAddr() : xffHeader) + ) + .build(); + } + + private Field generateSlackField(final String title, final String value) { + return Field.builder() + .title(title) + .value(value) + .valueShortEnough(false) + .build(); + } + + @NotNull + private List getFields(String message, String url, String ip) { + return List.of( + generateSlackField("Request IP", ip), + generateSlackField("Request URL", url), + generateSlackField("Error Message", message) + ); + } + + private String getIp(final HttpServletRequest request) { + return request.getHeader(IP_HEADER) == null ? request.getRemoteAddr() : request.getHeader(IP_HEADER); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/support/slack/package-info.java b/mens-core/src/main/java/kr/xit/core/support/slack/package-info.java new file mode 100644 index 0000000..c9ab15e --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/slack/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework 3rd-party library package classes + *

    + * slack + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.support.slack; diff --git a/mens-core/src/main/java/kr/xit/core/support/utils/Checks.java b/mens-core/src/main/java/kr/xit/core/support/utils/Checks.java new file mode 100644 index 0000000..d74bb11 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/utils/Checks.java @@ -0,0 +1,164 @@ +package kr.xit.core.support.utils; + +import java.util.Iterator; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.mybatis.spring.SqlSessionFactoryBean; + +/** + *
    + * description : null or empty check utility
    + * packageName : kr.xit.core.support.utils
    + * fileName    : Checks
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class Checks { + + /** + * val이 null이 아니면 그대로 리턴, 아니면 defVal + * + * @param val T + * @param defVal T + * @return T + */ + public static < T > T checkVal( T val, T defVal ) { + return ( isNotNull( val ) ? val : defVal ); + } + + /** + * val이 Empty가 아니면 그대로 리턴, 아니면 defVal (Empty는 배열이나 Map도 가능) + * + * @param val T + * @param defVal T + * @return T + */ + public static < T > T checkEmptyVal( T val, T defVal ) { + return ( isNotEmpty( val ) ? val : defVal ); + } + + /** + * expression이 true이면 val, 아니면 defVal + * + * @param expression boolean + * @param tVal T + * @param fVal T + * @return T + */ + public static < T > T checkVal( boolean expression, T tVal, T fVal ) { + return ( expression ? tVal : fVal ); + } + + + public static boolean isNull( Object checkValue ) { + return !isNotNull(checkValue); + } + + public static boolean isNotNull( Object checkValue ) { + return checkValue != null; + } + + public static boolean isEmpty( Object checkValue ) { + return !isNotEmpty(checkValue); + } + + public static boolean isNotEmpty( Object checkValue ) { + if ( isNotNull( checkValue ) ) { + + if ( checkValue instanceof String ) { + return !( ( String ) checkValue ).isEmpty(); + } + else if ( checkValue instanceof Iterator< ? > ) { + return ( ( Iterator< ? > ) checkValue ).hasNext(); + } + else if ( checkValue instanceof Iterable< ? > ) { + return ( ( Iterable< ? > ) checkValue ).iterator().hasNext(); + } + else if ( checkValue instanceof Map< ?, ? > ) { + return !( ( Map< ?, ? > ) checkValue ).isEmpty(); + } +// else if ( checkValue instanceof DataSetMap ) { +// return !( ( DataSetMap ) checkValue ).isEmpty(); +// } + + if ( checkValue.getClass().isArray() ) { + return ( ( Object[] ) checkValue ).length != 0; + } + else { + // 체크하는 항목이 아니면서 not null 이면 not empty로 간주한다. + return true; + } + } + + return false; + } + + public static boolean isNumeric( String str ) + { + if ( Checks.isEmpty( str ) ) + return false; + + return str.matches( "-?\\d+(.\\d+)?" ); + } + + public static boolean isInstance( Object checkValue, Class< ? >... classes ) { + if ( isNotNull( checkValue ) ) { + for ( Class< ? > clazz : classes ) { + if ( clazz.isInstance( checkValue ) ) { + return true; + } + } + } + + return false; + } + + /** + * email형식 체크 + * + * @메소드 : isCheckEmail + * + * @param str + * @return + */ + public static boolean isCheckEmail( String str ) { + if ( Checks.isEmpty( str ) ) + return false; + + String regex = "^([\\w-]+(?:\\.[\\w-]+)*)@((?:[\\w-]+\\.)*\\w[\\w-]{0,66})\\.([a-z]{2,6}(?:\\.[a-z]{2})?)$"; + Pattern pattern = Pattern.compile( regex, Pattern.CASE_INSENSITIVE ); + + Matcher matcher = pattern.matcher( str ); + return matcher.find(); + } + + /** + * 전화형식 체크 + * + * @메소드 : isCheckTel + * + * @param str + * @return + */ + public static boolean isCheckTel( String str ) { + if ( Checks.isEmpty( str ) ) + return false; + + String regex = "^\\d[\\d-]+\\d$"; + Pattern pattern = Pattern.compile( regex, Pattern.MULTILINE ); + + Matcher matcher = pattern.matcher( str ); + return matcher.find(); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/support/utils/ConvertHelper.java b/mens-core/src/main/java/kr/xit/core/support/utils/ConvertHelper.java new file mode 100644 index 0000000..9c19fc7 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/utils/ConvertHelper.java @@ -0,0 +1,103 @@ +package kr.xit.core.support.utils; + +import java.io.StringWriter; +import java.util.Map; + +import lombok.AccessLevel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.joda.JodaModule; + +import lombok.NoArgsConstructor; + +/** + *
    + * description : 객제 변환 utility
    + * packageName : kr.xit.core.support.utils
    + * fileName    : ConvertHelper
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ConvertHelper { + private static final Logger log = LoggerFactory.getLogger(ConvertHelper.class); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final JsonFactory JSON_FACTORY = new JsonFactory(); + + /** + * Object -> Json String + * @param o + * @return + */ + public static String jsonToObject(Object o){ + JsonGenerator generator = null; + StringWriter writer = new StringWriter(); + + try{ + OBJECT_MAPPER.registerModule(new JodaModule()); + generator = JSON_FACTORY.createGenerator(writer); + OBJECT_MAPPER.writeValue(generator, o); + generator.flush(); + return writer.toString(); + }catch(Exception e){ + log.error("ConvertHelper::jsonToObject", e); + return null; + }finally{ + if(generator != null){ + try{ + writer.close(); + generator.close(); + }catch(Exception e){ + log.error("InternalServerError: {}", e.getLocalizedMessage()); + } + } + } + } + + /** + * json string -> Object + * @param json + * @param clazz + * @return T + */ + public static T objectToJson(String json, Class clazz){ + try{ + OBJECT_MAPPER.registerModule(new JodaModule()); + return OBJECT_MAPPER.readValue(json, clazz); + }catch(Exception e){ + log.error("ConvertHelper::objectToJson", e); + return null; + } + } + + /** + * Object -> MultiValueMap + * @param dto + * @return + */ + public static MultiValueMap toMultiValueMap(Object dto) { + try { + MultiValueMap params = new LinkedMultiValueMap<>(); + Map map = OBJECT_MAPPER.convertValue(dto, new TypeReference>() {}); + params.setAll(map); + + return params; + } catch (Exception e) { + log.error("Url Parameter 변환중 오류가 발생했습니다. requestDto={}", dto, e); + throw new IllegalStateException("Url Parameter 변환중 오류가 발생했습니다."); + } + } +} diff --git a/mens-core/src/main/java/kr/xit/core/support/utils/DateUtils.java b/mens-core/src/main/java/kr/xit/core/support/utils/DateUtils.java new file mode 100644 index 0000000..7c18172 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/utils/DateUtils.java @@ -0,0 +1,619 @@ +package kr.xit.core.support.utils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; + +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.Date; + +import lombok.extern.slf4j.Slf4j; + +/** + *
    + * description : Date uitility
    + *               자바 8부터 java.time 패키지 활용
    + *               localDate : 날짜 정보만 필요할때
    + *               localDateTime : 날짜와 시간 모두 필요할때
    + *               localtime : 시간 정보만 필요할때
    + * packageName : kr.xit.core.support.utils
    + * fileName    : Checks
    + * author      : julim
    + * date        : 2023-04-28
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-04-28    julim       최초 생성
    + *
    + * 
    + */ +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class DateUtils { + + private static final String DEFAULT_YMD_DT_FMT ="yyyy-MM-dd HH:mm:ss"; + + public static java.util.Date toDateFromLocalDate(LocalDate localDate){ + Instant instant = localDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); + return Date.from(instant); + //return java.sql.Date.valueOf(localDate); + } + + public static java.util.Date toDateFromLocalDateTime(LocalDateTime localDateTime){ + Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant(); + return Date.from(instant); + //return java.sql.Timestamp.valueOf(localDateTime); + } + +// public static LocalDate toLocalDateFromDate(Date date){ +// return LocalDate.ofInstant(date.toInstant(), ZoneId.systemDefault()); +// //return new java.sql.Date(date.getTime()).toLocalDate(); +// } + + public static LocalDateTime toLocalDateTimeFromDate(Date date){ + return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); + //return new java.sql.Timestamp(date.getTime()).toLocalDateTime(); + } + + /** + * LocalDate 객체 반환 + * + * @return LocalDate + */ + public static LocalDate localDate() { + return LocalDate.now(); + } + + /** + * LocalDate.parse 객체 반환 + * + * @param target yyyy-MM-dd + * @return LocalDate.parse(tartget); + */ + public static LocalDate parseLocalDate(String target) { + return LocalDate.parse(target); + } + + /** + * LocalDateTime 객체 반환 + * + * @return LocalDateTime + */ + public static LocalDateTime localDateTime() { + return LocalDateTime.now(); + } + + /** + * LocalDateTime.parse 객체 반환 + * + * @param target yyyy-MM-dd HH:mm:ss + * @return LocalDateTime.parse(tartget); + */ + public static LocalDateTime parseLocalDateTime(String target) { + return LocalDateTime.parse(target); + } + + /** + * LocalDateTime.parse 객체 반환 + * + * @param target yyyy-MM-dd HH:mm:ss + * @return LocalDateTime.parse(tartget); + */ + public static LocalDateTime parseLocalDateTimeForm(String target, String pattern) { + return LocalDateTime.parse(target, DateTimeFormatter.ofPattern(Checks.checkVal(pattern, DEFAULT_YMD_DT_FMT))); + } + + /** + * String format -> long + * @param target 20230610115959 + * @param fmt yyyyMMddHHmmss + * @return long long seconds + */ + public static long parseStringToLong(final String target, final String fmt) { + String format = Checks.checkVal(fmt, DEFAULT_YMD_DT_FMT); + LocalDateTime ldt = LocalDateTime.parse(target, DateTimeFormatter.ofPattern(format)); + return ldt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()/1000; + } + + /** + * LocalDateTime -> long + * @param ldt LocalDateTime + * @return long long seconds + */ + public static long parseToLong(final LocalDateTime ldt) { + return ldt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()/1000; + } + + /** + * Date -> long + * @param date Date + * @return long long seconds + */ + public static long parseToLong(final Date date) { + return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()) + .atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()/1000; + } + + /** + * 현재 년도 + * + * @return int + */ + public static int getCurrentYear() { + return localDate().getYear(); + } + + /** + * 현재 월 + * + * @return int + */ + public static int getCurrentMonth() { + return localDate().getMonthValue(); + } + + /** + * 현재 일 + * + * @return int + */ + public static int getCurrentDay() { + return localDate().getDayOfMonth(); + } + + /** + * 운년 여부 + * + * @return boolean + */ + public static boolean isLeapYear() { + return localDate().isLeapYear(); + } + + /** + * 오늘 날짜 : 년-월-일 + * BASIC_ISO_DATE : 20200621 리턴 + * + * @return LocalDate + */ + public static LocalDate getToday() { + return localDate(); + } + + /** + * 오늘 날짜 : 년-월-일 + * BASIC_ISO_DATE : 20200621 리턴 + * + * @param delimiter 년원일 사이 구분자 + * @return String + */ + public static String getToday(String delimiter) { + String result = ""; + String pattern = "yyyy" + delimiter + "MM" + delimiter + "dd"; + + try { + if (delimiter == null) { + result = localDate().format(DateTimeFormatter.BASIC_ISO_DATE); + } else { + result = localDate().format(DateTimeFormatter.ofPattern(pattern)); + } + } catch (final Exception e) { + log.error("DateUtils::getToday", e); + } + + return result; + } + + /** + * 오늘 날짜 : 년-월-일 시분초 + * + * @return LocalDate + */ + public static LocalDateTime getTodayAndNowTime() { + return localDateTime(); + } + + /** + * 현재시간 -> long return + * + * @return long + */ + public static long getLongTodayAndNowTime() { + return localDateTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()/1000; + } + + /** + * 오늘 날짜 : 년-월-일 시분초 + * + * @param delimiter 년원일 사이 구분자 + * @param isMillisecond 밀리 세컨드 여부 + * @return String + */ + public static String getTodayAndNowTime(String delimiter, boolean isMillisecond) { + String pattern = ""; + + try { + pattern = "yyyy" + delimiter + "MM" + delimiter + "dd" + " HH:mm:ss" + (isMillisecond ? ".SSS" : ""); + } catch (Exception e) { + log.error("DateUtils::getTodayAndNowTime", e); + } + + return localDateTime().format(DateTimeFormatter.ofPattern(pattern)); + } + + /** + * 오늘 날짜 : 년-월-일 시분초 + * + * @param userFormat 사용자가 원하는 포멧 + * @return String + */ + public static String getTodayAndNowTime(String userFormat) { + String pattern = ""; + + try { + pattern = DEFAULT_YMD_DT_FMT; + if (!StringUtils.isEmpty(userFormat)) { + pattern = userFormat; + } + } catch (Exception e) { + log.error("DateUtils::getTodayAndNowTime", e); + } + + return localDateTime().format(DateTimeFormatter.ofPattern(pattern)); + } + + /** + * 년 월 일 계산 + * 날짜만 계산 년-월-일 형태로 넘겨줘야함(안그럼 예외) + * 해당월에 존재하지 않는 일을 넘겨도 예외 + * 빼기는 음수를 붙여서 넘기면 된다. + * ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일 + * @param year 계산할 년 + * @param month 계산할 월 + * @param day 계산할 일 + * @return String + */ + public static String getCalculatorDate(String targetDate, int year, int month, int day) { + String result = ""; + + try { + LocalDate localDate = parseLocalDate(targetDate); + localDate = localDate.plusYears(year); + localDate = localDate.plusMonths(month); + localDate = localDate.plusDays(day); + result = localDate.toString(); + } catch (Exception e) { + log.error("DateUtils::getCalculatorDate", e); + } + + return result; + } + + /** + * 년 월 계산 + * 년-월-일 형태로 넘겨줘야함(안그럼 예외) + * 해당월에 존재하지 않는 일을 넘겨도 예외 + * 빼기는 음수를 붙여서 넘기면 된다. + * ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일 + * @param year 계산할 년 + * @param month 계산할 월 + * @return String + */ + public static String getCalculatorDate(String targetDate, int year, int month) { + return getCalculatorDate(targetDate, year, month, 0); + } + + /** + * 년 계산 + * 년-월-일 형태로 넘겨줘야함(안그럼 예외) + * 해당월에 존재하지 않는 일을 넘겨도 예외 + * 빼기는 음수를 붙여서 넘기면 된다. + * ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일 + * @param year 계산할 년 + * @return String + */ + public static String getCalculatorDate(String targetDate, int year) { + return getCalculatorDate(targetDate, year, 0, 0); + } + + /** + * 날짜&시간 계산 년-월-일THH:mm:ss 형태(안그럼 예외) + * 2020-06-22T23:20:32 ISO 시간 표기법에 따라 'T'를 꼭 붙여야 함. + * 빼기는 음수를 붙여서 ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일T시:분:초 + * @param year 계산할 년 + * @param month 계산할 월 + * @param day 계산할 일 + * @param hour 계산할 시간 + * @param minute 계산할 분 + * @param second 계산할 초 + * @return String + */ + public static String getCalculatorDateAndTime(String targetDate, int year, int month, int day, int hour, int minute, int second) { + String result = ""; + + try { + LocalDateTime localDateTime = parseLocalDateTime(targetDate); + localDateTime = localDateTime.plusYears(year); + localDateTime = localDateTime.plusMonths(month); + localDateTime = localDateTime.plusDays(day); + localDateTime = localDateTime.plusHours(hour); + localDateTime = localDateTime.plusMinutes(minute); + localDateTime = localDateTime.plusSeconds(second); + result = localDateTime.toString(); + } catch (Exception e) { + log.error("DateUtils::getCalculatorDateAndTime", e); + } + + return result; + } + + /** + * 년 월 일 시 분 계산 + * 날짜&시간 계산 년-월-일THH:mm:ss 형태(안그럼 예외) + * 2020-06-22T23:20:32 ISO 시간 표기법에 따라 'T'를 꼭 붙여야 함. + * 빼기는 음수를 붙여서 ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일T시:분:초 + * @param year 계산할 년 + * @param month 계산할 월 + * @param day 계산할 일 + * @param hour 계산할 시간 + * @param minute 계산할 분 + * @return String + */ + public static String getCalculatorDateAndTime(String targetDate, int year, int month, int day, int hour, int minute) { + return getCalculatorDateAndTime(targetDate, year, month, day, hour, minute, 0); + } + + /** + * 년 월 일 시 계산 + * 2020-06-22T23:20:32 ISO 시간 표기법에 따라 'T'를 꼭 붙여야 함. + * 빼기는 음수를 붙여서 ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일T시:분:초 + * @param year 계산할 년 + * @param month 계산할 월 + * @param day 계산할 일 + * @param hour 계산할 시간 + * @return String + */ + public static String getCalculatorDateAndTime(String targetDate, int year, int month, int day, int hour) { + return getCalculatorDateAndTime(targetDate, year, month, day, hour, 0, 0); + } + + /** + * 년 월 일 계산 + * 2020-06-22T23:20:32 ISO 시간 표기법에 따라 'T'를 꼭 붙여야 함. + * 빼기는 음수를 붙여서 ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일T시:분:초 + * @param year 계산할 년 + * @param month 계산할 월 + * @param day 계산할 일 + * @return String + */ + public static String getCalculatorDateAndTime(String targetDate, int year, int month, int day) { + return getCalculatorDateAndTime(targetDate, year, month, day, 0, 0, 0); + } + + /** + * 년 월 계산 + * 2020-06-22T23:20:32 ISO 시간 표기법에 따라 'T'를 꼭 붙여야 함. + * 빼기는 음수를 붙여서 ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일T시:분:초 + * @param year 계산할 년 + * @param month 계산할 월 + * @return String + */ + public static String getCalculatorDateAndTime(String targetDate, int year, int month) { + return getCalculatorDateAndTime(targetDate, year, month, 0, 0, 0, 0); + } + + /** + * 년 계산 + * 2020-06-22T23:20:32 ISO 시간 표기법에 따라 'T'를 꼭 붙여야 함. + * 빼기는 음수를 붙여서 ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일T시:분:초 + * @param year 계산할 년 + * @return String + */ + public static String getCalculatorDateAndTime(String targetDate, int year) { + return getCalculatorDateAndTime(targetDate, year, 0, 0, 0, 0, 0); + } + + /** + * 2020-06-22 + * 이전 날짜 확인 + * sourceDate < compareDate = true + * sourceDate > compareDate = false + * sourceDate == compareDate = false + * + * @param sourceDate 시작 년-월-일 + * @param compareDate 비교 년-월-일 + * @return boolean + */ + public static boolean isBeforeLocalDate(String sourceDate, String compareDate) { + boolean result = false; + + try { + LocalDate source = parseLocalDate(sourceDate); + result = source.isBefore(parseLocalDate(compareDate)); + } catch (Exception e) { + log.error("DateUtils::isBeforeLocalDate", e); + } + + return result; + } + + /** + * 2020-06-22 + * 지난 날짜 확인 + * sourceDate > compareDate = true + * sourceDate < compareDate = false + * sourceDate == compareDate = false + * + * @param sourceDate 시작 년-월-일 + * @param compareDate 비교 년-월-일 + * @return boolean + */ + public static boolean isAfterLocalDate(String sourceDate, String compareDate) { + boolean result = false; + + try { + LocalDate source = parseLocalDate(sourceDate); + result = source.isAfter(parseLocalDate(compareDate)); + } catch (Exception e) { + log.error("DateUtils::isAfterLocalDate", e); + } + + return result; + } + + /** + * 2020-06-22T22:57:33 + * 이전 날짜&시간 확인 + * sourceDate < compareDate = true + * sourceDate > compareDate = false + * sourceDate == compareDate = false + * + * @param sourceDate 시작 년월일 + * @param compareDate 비교 년월일 + * @return boolean + */ + public static boolean isBeforeLocalDateTime(String sourceDate, String compareDate) { + boolean result = false; + + try { + LocalDateTime source = parseLocalDateTime(sourceDate); + result = source.isBefore(parseLocalDateTime(compareDate)); + } catch (final Exception e) { + log.error("DateUtils::isBeforeLocalDateTime", e); + } + + return result; + } + + /** + * 20230620235733 (Default: 2023-06-20 23:57:33) + * pattern: yyyyMMddHHmmss (Default: yyyy-MM-dd HH:mm:ss) + * 이전 날짜&시간 확인 + * sourceDate < compareDate = true + * sourceDate > compareDate = false + * sourceDate == compareDate = false + * + * @param sourceDate 시작 년월일 + * @param compareDate 비교 년월일 + * @param pattern 년월일 패턴 + * @return boolean + */ + public static boolean isBeforeLocalDateTime(String sourceDate, String compareDate, String pattern) { + boolean result = false; + + try { + LocalDateTime source = parseLocalDateTimeForm(sourceDate,pattern); + result = source.isBefore(parseLocalDateTimeForm(compareDate,pattern)); + } catch (final Exception e) { + log.error("DateUtils::isBeforeLocalDateTime", e); + } + + return result; + } + + /** + * 2020-06-22T22:57:33 + * 지난 날짜&시간 확인 + * sourceDate > compareDate = true + * sourceDate < compareDate = false + * sourceDate == compareDate = false + * + * @param sourceDate 시작 년월일 + * @param compareDate 비교 년월일 + * @return boolean + */ + public static boolean isAfterLocalDateTime(String sourceDate, String compareDate) { + boolean result = false; + + try { + LocalDateTime source = parseLocalDateTime(sourceDate); + result = source.isAfter(parseLocalDateTime(compareDate)); + } catch (final Exception e) { + log.error("DateUtils::isAfterLocalDateTime", e); + } + + return result; + } + + /** + * 날짜 차이 계산 + * 2020-06-23 - 2020-06-22 = 1 + * 2020-06-23 - 2020-06-24 = -1 + * 앞에꺼 - 뒤에꺼 + * + * @param startDate String + * @param endDate String + * @return int + */ + public static int getDateDiffPeriodForLocalDate(String startDate, String endDate) { + int result = 0; + + try { + LocalDate source = parseLocalDate(endDate); + result = (int) ChronoUnit.DAYS.between(source, parseLocalDate(startDate)); + } catch (final Exception e) { + log.error("DateUtils::getDateDiffPeriodForLocalDate", e); + } + + return result; + } + + /** + * 날짜 차이 계산(시분초까지 계산해줌) + * 2020-06-23T23:12:45 - 2020-06-22T23:12:45 = 1 + * 2020-07-23T23:59:59 - 2020-07-24T23:59:58 = 0 + * 2020-07-23T23:59:58 - 2020-07-24T23:59:59 = -1 + * 앞에꺼 - 뒤에꺼 + * + * @param startDate String + * @param endDate String + * @return int + */ + public static int getDateDiffPeriodForLocalDateTime(String startDate, String endDate) { + int result = 0; + + try { + LocalDateTime source = parseLocalDateTime(endDate); + result = (int) ChronoUnit.DAYS.between(source, parseLocalDateTime(startDate)); + } catch (final Exception e) { + log.error("DateUtils::getDateDiffPeriodForLocalDateTime", e); + } + + return result; + } + + /** + * + * @param date + * @param sDateFormat + * @return + */ + public static String getFormatedDT(Date date, String sDateFormat) { + SimpleDateFormat sdf = new SimpleDateFormat(sDateFormat); + return sdf.format(date); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/support/utils/FileUtil.java b/mens-core/src/main/java/kr/xit/core/support/utils/FileUtil.java new file mode 100644 index 0000000..47e259f --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/utils/FileUtil.java @@ -0,0 +1,47 @@ +package kr.xit.core.support.utils; + +import kr.xit.core.exception.BizRuntimeException; + +import java.io.*; + +/** + *
    + * description : File Utils
    + *
    + * packageName : kr.xit.core.support.utils
    + * fileName    : FileUtil
    + * author      : limju
    + * date        : 2023-07-20
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-07-20    limju       최초 생성
    + *
    + * 
    + */ +public class FileUtil { + + public static byte[] getFileBytesFrom(String path) { + + // 파일 객체 생성 + File file = new File(path); + byte[] fileBytes = new byte[(int)file.length()]; + + try(FileInputStream fis = new FileInputStream(file);){ + fis.read(fileBytes); + + } catch (IOException e) { + throw BizRuntimeException.create(e.getMessage()); + } + return fileBytes; + } + + public static void saveFile(String path, byte[] fileData) { + + try(FileOutputStream fos = new FileOutputStream(path);){ + fos.write(fileData); + } catch (IOException e) { + throw BizRuntimeException.create(e.getMessage()); + } + } +} diff --git a/mens-core/src/main/java/kr/xit/core/support/utils/IpMacUtils.java b/mens-core/src/main/java/kr/xit/core/support/utils/IpMacUtils.java new file mode 100644 index 0000000..e799fb0 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/utils/IpMacUtils.java @@ -0,0 +1,132 @@ +package kr.xit.core.support.utils; + +import kr.xit.core.exception.BizRuntimeException; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.*; +import java.util.stream.Collectors; + +/** + *
    + * description : IP address and Mac address Utils
    + *
    + * packageName : kr.xit.core.support.utils
    + * fileName    : IpMacUtils
    + * author      : limju
    + * date        : 2023-07-20
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-07-20    limju       최초 생성
    + *
    + * 
    + */ +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class IpMacUtils { + /** + * Mac address List return + * + * @return {@code List} Mac address List + */ + public static List getMacAddress() { + List macs = new ArrayList<>(); + + try { + Enumeration e = NetworkInterface.getNetworkInterfaces(); + + while (e.hasMoreElements()) { + NetworkInterface network = e.nextElement(); + + byte[] bmac = network.getHardwareAddress(); + if (bmac != null) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < bmac.length; i++) { + sb.append(String.format("%02X%s", bmac[i], (i < bmac.length - 1) ? "-" : "")); + } + + if (sb.toString().isEmpty() == false) { + macs.add(sb.toString().toUpperCase()); + } + } + } + } catch (SocketException e){ + log.error("{}", e.getLocalizedMessage()); + throw BizRuntimeException.create(e.getLocalizedMessage()); + } + return macs; + } + + /** + * Mac address String return + * @param ip String + * @return {@code String} Mac address String + */ + public static String getMacAddress(String ip) { + StringBuilder sb = new StringBuilder(); + + try { + InetAddress add = InetAddress.getByName(ip); + + NetworkInterface ni = NetworkInterface.getByInetAddress(add); + if (ni != null) { + byte[] bmac = ni.getHardwareAddress(); + if (bmac != null) { + for (int i = 0; i < bmac.length; i++) { + sb.append(String.format("%02X%s", bmac[i], (i < bmac.length - 1) ? "-" : "")); + } + } + } + return sb.toString().toUpperCase(); + } catch (UnknownHostException | SocketException e) { + log.error("{}", e.getLocalizedMessage()); + throw BizRuntimeException.create(e.getLocalizedMessage()); + } + } + + /** + * IP address List return + * + * @return {@code List} IP address or "" + */ + public static List getIpAddress() { + List ips = new ArrayList<>(); + + try { + Enumeration e = NetworkInterface.getNetworkInterfaces(); + + while (e.hasMoreElements()) { + NetworkInterface ni = e.nextElement(); + Enumeration kk = ni.getInetAddresses(); + + while (kk.hasMoreElements()) { + InetAddress inetAddress = kk.nextElement(); + if(inetAddress.getAddress().length == 4) ips.add(inetAddress.getHostAddress().toString()); + } + } + return ips.stream().distinct().collect(Collectors.toList()); + } catch (SocketException e) { + log.error("{}", e.getLocalizedMessage()); + throw BizRuntimeException.create(e.getLocalizedMessage()); + } + } + + public static void main(String[] args) throws SocketException, UnknownHostException { + log.info("==>>{}", getIpAddress()); + log.info("==>>{}", getMacAddress()); + log.info("==>>{}", getMacAddress("211.119.124.73")); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/support/utils/JsonUtils.java b/mens-core/src/main/java/kr/xit/core/support/utils/JsonUtils.java new file mode 100644 index 0000000..c104645 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/utils/JsonUtils.java @@ -0,0 +1,188 @@ +package kr.xit.core.support.utils; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import kr.xit.core.exception.BizRuntimeException; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; + +/** + * JSON Util Class + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class JsonUtils { + + /** + * Object -> json string + * @return String + * @param obj Object + */ + public static String toJson(Object obj) { + ObjectMapper mapper = new ObjectMapper(); + // null 필드 제 + mapper.setSerializationInclusion(Include.NON_NULL); + // No serializer found for class 에러 - private 필드 + mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); +// mapper.setSerializationInclusion(Include.NON_EMPTY); + try { + return obj != null ? mapper.writeValueAsString(obj) : null; + } catch (JsonProcessingException e) { + throw BizRuntimeException.create(e.getLocalizedMessage()); + } + } + + /** + * Json string -> class로 변환 + * @return T + * @param str String + * @param cls Class + */ + public static T toObject(String str, Class cls) { + ObjectMapper om = new ObjectMapper(); + om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + try { + return str != null ? om.readValue(str, cls) : null; + } catch (JsonProcessingException e) { + throw BizRuntimeException.create(e.getLocalizedMessage()); + } + } + + /** + * Object -> class로 변환 + * @param obj Object + * @param cls Class + * @return T + */ + public static T toObjByObj(Object obj, Class cls) { + String str = toJson(obj); + ObjectMapper om = new ObjectMapper(); + om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + try { + return str != null ? om.readValue(str, cls) : null; + } catch (JsonProcessingException e) { + throw BizRuntimeException.create(e.getLocalizedMessage()); + } + } + + /** + * Json string -> class list로 변환 + * @return T + * @param str + * @param cls + * @throws IOException + */ + public static List toObjectList(String str, Class cls) { + ObjectMapper om = new ObjectMapper(); + om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + if(str != null){ + try { + return om.readValue(str, om.getTypeFactory().constructCollectionType(List.class, cls)); + } catch (JsonProcessingException e) { + throw BizRuntimeException.create(e.getLocalizedMessage()); + } + }else { + return null; + } + } + + /** + * JSON 문자열 -> Map 구조체로 변환 + * @param str String str + * @return Map + */ + public static Map toMap(String str) { + ObjectMapper om = new ObjectMapper(); + try { + return om.readValue(str, new TypeReference>(){}); + } catch (JsonProcessingException e) { + throw BizRuntimeException.create(e.getLocalizedMessage()); + } + } + + /** + * Json 데이터 보기 좋게 변환. + * @param obj Object json + * @return String + */ + public static String jsonEnterConvert(Object obj) { + + try { + return jsonEnterConvert((JsonUtils.toJson(obj))); + } catch(Exception e) { + return StringUtils.EMPTY; + } + } + + /** + * Json 데이터 보기 좋게 변환. + * @param json String json + * @return String + */ + private static String jsonEnterConvert(String json) { + + if( json == null || json.length() < 2 ) + return json; + + final int len = json.length(); + final StringBuilder sb = new StringBuilder(); + char c; + String tab = ""; + boolean beginEnd = true; + for( int i=0 ; i 0 ) + sb.insert(0, '\n'); + return sb.toString(); + } +} + diff --git a/mens-core/src/main/java/kr/xit/core/support/utils/LogUtils.java b/mens-core/src/main/java/kr/xit/core/support/utils/LogUtils.java new file mode 100644 index 0000000..9d6eccf --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/utils/LogUtils.java @@ -0,0 +1,32 @@ +package kr.xit.core.support.utils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class LogUtils { + + /** + * Json 데이터 보기 좋게 변환. + * @param obj Object obj + * @return String + */ + public static Object toString(Object obj){ + return JsonUtils.jsonEnterConvert(obj); + } + + public static String getClassNm(Throwable throwable) { + StackTraceElement[] stacks = throwable.getStackTrace(); + return stacks[0].getClassName(); + } + + public static String getMethodNm(Throwable throwable) { + StackTraceElement[] stacks = throwable.getStackTrace(); + return stacks[ 0 ].getMethodName(); + } + + public static String getMethodInfo(Throwable throwable) { + StackTraceElement[] stacks = throwable.getStackTrace(); + return stacks[0].getClassName() + "." + stacks[0].getMethodName(); + } +} diff --git a/mens-core/src/main/java/kr/xit/core/support/utils/SFTPUtils.java b/mens-core/src/main/java/kr/xit/core/support/utils/SFTPUtils.java new file mode 100644 index 0000000..8af5d30 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/utils/SFTPUtils.java @@ -0,0 +1,306 @@ +package kr.xit.core.support.utils; + +import java.io.*; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Vector; + +import com.jcraft.jsch.*; +import com.jcraft.jsch.ChannelSftp.LsEntry; +import kr.xit.core.exception.BizRuntimeException; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +/** + *
    + * description : SFTP Util class
    + *
    + * packageName : kr.xit.core.support.utils
    + * fileName    : SFTPUtils
    + * author      : limju
    + * date        : 2023-07-07
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-07-07    limju       최초 생성
    + *
    + * 
    + */ +@Slf4j +public class SFTPUtils { + + public Session session = null; + public Channel channel = null; + public ChannelSftp channelSftp = null; + private static final int TIME_OUT = 60000; + + /** + * SFTP 접속 + * + * @param host String + * @param port int + * @param id String + * @param passwd String + * @param sshKey String + */ + public void init(String host, int port, String id, String passwd, String sshKey) { + + JSch jsch = new JSch(); + + try { + //세션객체 생성 + session = jsch.getSession(id, host, port); + + // 인증 + if(StringUtils.isNotEmpty(sshKey)) jsch.addIdentity(sshKey); + else session.setPassword(passwd); + + //세션관련 설정정보 설정 + java.util.Properties config = new java.util.Properties(); + //호스트 정보 검사 skip. + config.put("StrictHostKeyChecking", "no"); + session.setConfig(config); + session.setTimeout(TIME_OUT); //타임아웃 설정 + + session.connect(); + + //sftp 채널 접속 + channel = session.openChannel("sftp"); + channel.connect(); + + } catch (JSchException e) { + throw BizRuntimeException.create(e.getMessage()); + } + channelSftp = (ChannelSftp) channel; + } + + /** + * SFTP 서버 파일 업로드 + * @param uploadPath String 서버업로드 절대경로 + * @param localPathOnly String 로컬 업로드 파일 경로(파일 미포함) + * @param uploadFileNm String 업로드할 파일명 + */ + public void fileUpload(String uploadPath, String localPathOnly, String uploadFileNm) { + + try(FileInputStream in = new FileInputStream(String.format("%s/%s", localPathOnly, uploadFileNm));) { + channelSftp.cd(uploadPath); + channelSftp.put(in, uploadFileNm); + + }catch(IOException | SftpException fe){ + throw BizRuntimeException.create(fe.getMessage()); + } + } + + /** + * SFTP 서버 여러 파일 업로드 + * @param uploadPath String 업로드할 서버 절대경로 + * @param localPath String 업로드할 로컬 파일 경로 + * @param uploadFiles ArrayList 업로드할 로컬파일명 목록 + */ + public void fileUploads(String uploadPath, String localPath, ArrayList uploadFiles) { + try { + channelSftp.cd(uploadPath); + for(String uploadFile : uploadFiles) { + try (FileInputStream in = new FileInputStream(localPath + String.valueOf(uploadFile))) { + channelSftp.put(in, String.valueOf(uploadFile)); + + } catch (IOException | SftpException fe) { + throw BizRuntimeException.create(fe.getMessage()); + } + } + } catch (SftpException e) { + throw BizRuntimeException.create(e.getMessage()); + } + } + + /** + * SFTP 서버 파일 다운로드 + * @param downloadPath String 서버 절대 경로 + * @param localFilePath String 다운로드 로컬경로 + */ + public void fileDownload(String downloadPath, String localFilePath) { + byte[] buffer = new byte[1024]; + OutputStream os = null; + + try { + String cdDir = downloadPath.substring(0, downloadPath.lastIndexOf("/") + 1); + String fileName = downloadPath.substring(downloadPath.lastIndexOf("/") + 1); + channelSftp.cd(cdDir); + + File newFile = new File(String.format("%s/%s", localFilePath, fileName)); + os = Files.newOutputStream(newFile.toPath()); + + //파일 다운로드 SFTP 서버 -> 다운로드 서버 + try(BufferedInputStream bis = new BufferedInputStream(channelSftp.get(fileName)); + BufferedOutputStream bos = new BufferedOutputStream(os);) { + int readCount; + while ((readCount = bis.read(buffer)) > 0) { + bos.write(buffer, 0, readCount); + } + } + + } catch (Exception e) { + throw BizRuntimeException.create(e.getMessage()); + + } finally { + try { + if(os != null) os.close(); + } catch (IOException e) { + log.error(e.getMessage()); + } + } + } + + /** + * SFTP 서버 파일 read(파일명) - 파일명(확장자 제외) 으로 search + * @param fullPath String + * @return String + */ + public String findFileName(String fullPath) { + try { + String cdDir = fullPath.substring(0, fullPath.lastIndexOf("/") + 1); + String fileName = fullPath.substring(fullPath.lastIndexOf("/") + 1); + + channelSftp.cd(cdDir); + Vector fileList = channelSftp.ls(cdDir); + + for (LsEntry files : fileList) { + if ((fileName + (".*")).matches(files.getFilename())) { + return files.getFilename(); + } + } + + } catch (Exception e) { + throw BizRuntimeException.create(e.getMessage()); + } + return null; + } + + /** + * SFTP 서버 경로의 파일 목록 조회 + * @param path String 서버 절대 경로 + * @return ArrayList + */ + public ArrayList findFileNameList(String path) { + ArrayList fileNameList = new ArrayList<>(); + + try { + Vector fileList = channelSftp.ls(path); + for (LsEntry le : fileList){ + if(".".equals(le.getFilename()) || "..".equals(le.getFilename())) { + continue; + } + fileNameList.add(le.getFilename()); + } + return fileNameList; + + } catch (Exception e) { + throw BizRuntimeException.create(e.getMessage()); + } + } + + /** + * SFTP 파일 move + * @param src String source file full path(파일명 포함) + * @param dest String target full path(파일명 포함) + */ + public void mv(String src, String dest) { + try { + String cdDir = dest.substring(0, dest.lastIndexOf("/")); + SftpATTRS attrs = null; + try { + attrs = channelSftp.stat(cdDir); + } catch (SftpException e) { + log.info("파일 move 대상 path 미존재 :: {}", cdDir); + } + if(attrs == null) channelSftp.mkdir(cdDir); + + channelSftp.rename(src, dest); + } catch (SftpException e) { + throw BizRuntimeException.create(e.getMessage()); + } + } + + /** + * SFTP 파일 삭제 + * @param fileName 파일명 + */ + public void rm(String fileName) { + try { + channelSftp.rm(fileName); + } catch (SftpException e) { + throw BizRuntimeException.create(e.getMessage()); + } + } + + public String command(String command) { + ArrayList fileNameList = new ArrayList<>(); + + try { + ChannelExec exec = (ChannelExec) session.openChannel("exec"); + exec.setCommand(command); + + try (ByteArrayOutputStream responseStream = new ByteArrayOutputStream()) { + exec.setOutputStream(responseStream); + exec.connect(); + + while (exec.isConnected()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // thread pool에 에러 상태 전송 + Thread.currentThread().interrupt(); + throw BizRuntimeException.create(e.getMessage()); + } + } + + return responseStream.toString(); + } + + } catch (JSchException | IOException e) { + throw BizRuntimeException.create(e.getMessage()); + } + } + + /** + * SFTP 서버 접속 종료 + */ + public void disconnect() { + if(channelSftp != null) { + channelSftp.quit(); + } + if(channel != null) { + channel.disconnect(); + } + if(session != null) { + session.disconnect(); + } + } + + public static void main(String[] args) { + /* + SFTPUtils sftpUtil = new SFTPUtils(); + final String downloadFile = "/data/ens/sg-pni-cctv/rcv/20230622111301_49고3736_174번카메라-UDP_101.jpg"; + final String uploadFile = "D:/doc/ens/04. 서광 CCTV/사전알리미 샘플자료/20230622111301_49고3736_174번카메라-UDP_101.jpg"; + try { + //SFTP 서버 접속 + //접속할 SFTP 서버 IP, SFTP 포트, 계정 ID, 계정비밀번호, Pem Key + sftpUtil.init("211.119.124.9", 22, "xituser", "xituser!@", null); + + ArrayList fileNameList = sftpUtil.findFileNameList("/data/ens/sg-pni-cctv/rcv/"); + System.out.println(fileNameList); + + sftpUtil.mv( + "/data/ens/sg-pni-cctv/err/20230622111301_49고3736_174번카메라-UDP_101.jpg", + "/data/ens/sg-pni-cctv/rcv/20230622111301_49고3736_174번카메라-UDP_101.jpg" + ); + + }catch(Exception e) { + System.out.println(e); + }finally { + sftpUtil.disconnect(); + } + + */ + } +} diff --git a/mens-core/src/main/java/kr/xit/core/support/utils/package-info.java b/mens-core/src/main/java/kr/xit/core/support/utils/package-info.java new file mode 100644 index 0000000..a2d83f5 --- /dev/null +++ b/mens-core/src/main/java/kr/xit/core/support/utils/package-info.java @@ -0,0 +1,10 @@ +/** + * xit core java framework 3rd-party library package classes + *

    + * utility + *

    + * @since 1.0 + * @author limju + * @version 1.0 + */ +package kr.xit.core.support.utils; diff --git a/mens-core/src/main/resources/META-INF/spring.factories b/mens-core/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..221d1bd --- /dev/null +++ b/mens-core/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +# META-INF/spring.factories : xit-init +org.springframework.context.ApplicationContextInitializer=xit.core.init.custom.CustomContextInitalizer diff --git a/mens-core/src/main/resources/config/application-auth.yml b/mens-core/src/main/resources/config/application-auth.yml new file mode 100644 index 0000000..d086da6 --- /dev/null +++ b/mens-core/src/main/resources/config/application-auth.yml @@ -0,0 +1,39 @@ +#----------------------------------------------------------------------- +# application 인증 설정 +#----------------------------------------------------------------------- + +app: + # 암호화 알고리즘 + encrypt: + alg: SHA-256 + passwd: gdyYs/IZqY86VcWhT8emCYfqY1ahw2vtLG+/FzNqtrQ= + jasypt: + secretKey: xit5811807!@ + alg: PBEWithMD5AndDES + type: base64 + token: + typ: JWT + alg: HS512 + grant: Bearer + issuer: xit + audience: ${app.name} + # day + tokenExpiry: 1 + # day + refreshTokenExpiry: 7 + apiKey: lf2McyT3V5gDu2pNNm4VxmX3C2mezX3s + #secretKey: 8sknjlO3NPTBqo319DHLNqsQAfRJEdKsETOds + secretKey: c3ByaW5nLWJvb3Qtc2VjdXJpdHktand0LWlucGl4LWNvcmUtamF2YWZyYW1ld29yay1qYXZhLXRva2VuLWtleQ + # 토큰 재발급시 토큰(refresh) 전달 방식 : COOKIE | HEADER | DTO + saveType: HEADER + + # mybatis config +# 사용자 ID 정보를 어디에 저장할 것인지 설정 +# JWT 토큰을 사용하고 SessionCreationPolicy.STATELESS인 경우는 SecurityContext 사용불가 +# --> session에 저장하는 방식은 가능 +# security | session | header +# jwt secret key 설정 +#jwt.secret: 8sknjlO3NPTBqo319DHLNqsQAfRJEdKsETOds +# 토큰 재발급시 토큰(refresh) 전달 방식 : COOKIE | HEADER | DTO +#jwt.refresh.save.type: COOKIE +#jwt.secret: c3ByaW5nLWJvb3Qtc2VjdXJpdHktand0LWlucGl4LWNvcmUtamF2YWZyYW1ld29yay1qYXZhLXRva2VuLWtleQ diff --git a/mens-core/src/main/resources/config/application-common.yml b/mens-core/src/main/resources/config/application-common.yml new file mode 100644 index 0000000..b08e840 --- /dev/null +++ b/mens-core/src/main/resources/config/application-common.yml @@ -0,0 +1,110 @@ +#----------------------------------------------------------------------- +# egovframework 설정 +# framework 공통 설정 +#----------------------------------------------------------------------- +# 1. key = value 구조입니다. +# 2. key값은 공백문자를 포함불가, value값은 공백문자를 가능 +# 3. key값으로 한글을 사용불가, value값은 한글사용이 가능 +# 4. 줄을 바꿀 필요가 있으면 '\'를 라인의 끝에 추가(만약 '\'문자를 사용해야 하는 경우는 '\\'를 사용) +# 5. Windows에서의 디렉토리 표시 : '\\' or '/' ('\' 사용하면 안됨) +# 6. Unix에서의 디렉토리 표시 : '/' +# 7. 주석문 처리는 #사용 +# 8. value값 뒤에 스페이스가 존재하는 경우 서블릿에서 참조할때는 에러발생할 수 있으므로 trim()하거나 마지막 공백없이 properties 값을 설정할것 +#----------------------------------------------------------------------- +Globals: + # 운영서버 타입(WINDOWS, UNIX) + OsType: UNIX + + # G4C 연결용 IP (localhost) + LocalIp: 127.0.0.1 + + # 파일 확장자 화이트리스트(허용목록) : 파일 확장자를 (.)과 함께 연이어서 사용하며 (.)로 시작한다. + fileUpload: + Extensions: .gif.jpg.jpeg.png.xls.xlsx + '[Images.Extensions]': .gif.jpg.jpeg.png + + # egov validator + validator: + rule: classpath:/egovframework/validator/validator-rules-let.xml + file: classpath:/egovframework/validator/biz/**/*.xml + +#server.servlet.context-path=/sht_boot_web +server: + tomcat: + uri-encoding: UTF-8 + # remote-ip-header: x-forwarded-for + # protocol-header: x-forwarded-proto + # internal-proxies: "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|20\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|192\\.168\\.\\d{1,3}\\.\\d{1,3}|169\\.254\\.\\d{1,3}\\.\\d{1,3}|127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}|0:0:0:0:0:0:0:1|::1" + servlet: + context-path: + session.timeout: 3600 #sec + encoding: + enabled: true + charset: utf-8 + force: true +# forward-headers-strategy: FRAMEWORK #NATIVE|FRAMEWORK + +spring: + mvc: + pathmatch: + matching-strategy: ant_path_matcher + + #----------------------------------------------------------------- + # xit framework 설정 + #----------------------------------------------------------------- + main: + allow-bean-definition-overriding: true + #allow-circular-references: true + + devtools: + livereload: + enabled: false + +springdoc: + api-docs: + enabled: false + path: /api-docs + swagger-ui: + enabled: false # default true + path: /swagger-ui.html + csrf: + enabled: false + version: 'v1' # API 문서 버전 + default-consumes-media-type: application/json # default consume media type + default-produces-media-type: application/json # default produce media type + model-and-view-allowed: true # ModelAndView 허용 - excel download시 필요 + +#----------------------------------------------------------------- +# logback-spring.xml 로 설정시 스프링의 환경변수를 로그백 시스템 환경변수로 사용 +# logging.file.name / path => LOG_FILE, LOG_PATH +# logging.charset.console / file => CONSOLE_LOG_CHARSET, FILE_LOG_CHARSET +# logging.pattern.console / file => CONSOLE_LOG_PATTERN, FILE_LOG_PATTERN +#----------------------------------------------------------------- +logging: + charset: + console: UTF-8 + file: UTF-8 + exception-conversion-word: '%wEx' + level: + root: error + pattern: + console: '%d{yyyy-MM-dd HH:mm:ss.SSS} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr([%18.18thread]){magenta}[traceId=%X{request_id}] %clr([%-35.35logger{35}::%-25.25method{25}:%4line]){cyan} %clr(%m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}){faint}' + file: '%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%5p} [%18.18thread][traceId=%X{request_id}] [%-35.35logger{35}::%-25.25method{25}:%4line] %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}' + +# Spring Security cors 설정 :: CorsConfiguration 설정 값 +cors: + # 헤더에 작성된 출처만 브라우저가 리소스를 접근할 수 있도록 허용함. + # * 이면 모든 곳에 공개 + allowed-origins: http://localhost:8080,http:127.0.0.1:8080 + # 리소스 접근을 허용하는 HTTP 메서드를 지정 + allowed-methods: GET,POST,PUT,DELETE + # 요청을 허용하는 해더 + allowed-headers: Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,X-Csrftoken,Authorization + # 클라이언트에서 preflight 의 요청 결과를 저장할 기간을 지정 + # 60초 동안 preflight 요청을 캐시하는 설정으로, 첫 요청 이후 60초 동안은 OPTIONS 메소드를 사용하는 예비 요청을 보내지 않는다. + max-Age: 60 + # 클라이언트 요청이 쿠키를 통해서 자격 증명을 해야 하는 경우에 true. + # 자바스크립트 요청에서 credentials가 include일 때 요청에 대한 응답을 할 수 있는지를 나타낸다. + allow-Credentials: true + # 기본적으로 브라우저에게 노출이 되지 않지만, 브라우저 측에서 접근할 수 있게 허용해주는 헤더를 지정 + expose-Headers: Content-Length diff --git a/mens-core/src/main/resources/egovframework/message/com/message-common.properties b/mens-core/src/main/resources/egovframework/message/com/message-common.properties new file mode 100644 index 0000000..4cdeb51 --- /dev/null +++ b/mens-core/src/main/resources/egovframework/message/com/message-common.properties @@ -0,0 +1,294 @@ +fail.common.msg=\uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! +fail.common.sql=sql \uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! error code: {0}, error msg: {1} +info.nodata.msg=\ud574\ub2f9 \ub370\uc774\ud130\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. + +#UI Common resource# +button.search=\uac80\uc0c9 +button.use=\uc0ac\uc6a9 +button.notUsed=\uc0ac\uc6a9\uc911\uc9c0 +button.inquire=\uc870\ud68c +button.update=\uc218\uc815 +button.create=\ub4f1\ub85d +button.delete=\uc0ad\uc81c +button.deleteDatabase=\uc644\uc804\uc0ad\uc81c +button.close=\ub2eb\uae30 +button.save=\uc800\uc7a5 +button.list=\ubaa9\ub85d +button.reset=\ucde8\uc18c +button.passwordUpdate=\uc554\ud638\ubcc0\uacbd +button.subscribe=\uac00\uc785\uc2e0\uccad +button.realname=\uc2e4\uba85\ud655\uc778 +button.moveToGpin=GPIN\uc2e4\uba85\ud655\uc778\uc73c\ub85c \uc774\ub3d9 +button.moveToIhidnum=\uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638 \uc2e4\uba85\ud655\uc778\uc73c\ub85c \uc774\ub3d9 +button.agree=\ub3d9\uc758 +button.disagree=\ube44\ub3d9\uc758 +button.possible = \uac00\ub2a5 +button.impossible = \ubd88\uac00\ub2a5 +button.qnaregist=Q&A\ub4f1\ub85d +button.cnsltregist=\uc0c1\ub2f4\ub4f1\ub85d +button.preview=\ubbf8\ub9ac\ubcf4\uae30 +button.next=\ub2e4\uc74c +button.add=\ubc14\ub85c\ucd94\uac00 +button.confirm=\ud655\uc778 + + +#UI Common Message# +common.save.msg=\uc800\uc7a5\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.regist.msg=\ub4f1\ub85d\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.delete.msg=\uc0ad\uc81c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.update.msg=\uc218\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.nodata.msg=\uc790\ub8cc\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\ub978 \uac80\uc0c9\uc870\uac74\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694 +common.required.msg=(\uc740)\ub294 \ud544\uc218\uc785\ub825\ud56d\ubaa9\uc785\ub2c8\ub2e4. +common.acknowledgement.msg=\uc2b9\uc778\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.acknowledgementcancel.msg=\uc2b9\uc778\ucde8\uc18c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? + +success.common.select=\uc815\uc0c1\uc801\uc73c\ub85c \uc870\ud68c\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.insert=\uc815\uc0c1\uc801\uc73c\ub85c \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.update=\uc815\uc0c1\uc801\uc73c\ub85c \uc218\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.delete=\uc815\uc0c1\uc801\uc73c\ub85c \uc0ad\uc81c\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +success.request.msg = \uc694\uccad\ucc98\ub9ac\uac00 \uc131\uacf5\uc801\uc73c\ub85c \uc218\ud589\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +common.imposbl.fileupload = \ub354 \uc774\uc0c1 \ud30c\uc77c\uc744 \ucca8\ubd80\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +common.isConfmDe.msg=\uc2b9\uc778\uc77c\uc790\ub97c \ud655\uc778 \ubc14\ub78d\ub2c8\ub2e4. + + +fail.common.insert = \uc0dd\uc131\uc774 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.update = \uc218\uc815\uc774 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.delete = \uc0ad\uc81c\uac00 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.delete.upperMenuExist = \ucc38\uc870\ub418\ub294 \uba54\ub274\uac00 \uc788\uc5b4 \uc0ad\uc81c\uac00 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.select = \uc870\ud68c\uc5d0 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +common.isExist.msg = \uc774\ubbf8 \uc874\uc7ac\ud558\uac70\ub098 \uacfc\uac70\uc5d0 \ub4f1\ub85d\uc774 \ub418\uc5c8\ub358 \uc0c1\ud0dc\uc785\ub2c8\ub2e4. +fail.common.login = \ub85c\uadf8\uc778 \uc815\ubcf4\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.common.idsearch = \uc544\uc774\ub514\ub97c \ucc3e\uc744\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +fail.common.pwsearch = \ube44\ubc00\ubc88\ud638\ub97c \ucc3e\uc744\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +fail.request.msg = \uc694\uccad\ucc98\ub9ac\ub97c \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. + +#UI User Message# +fail.user.passwordUpdate1=\ud604\uc7ac \ube44\ubc00\ubc88\ud638\uac00 \ub9de\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.user.passwordUpdate2=\ube44\ubc00\ubc88\ud638\uc640 \ube44\ubc00\ubc88\ud638 \ud655\uc778\uc774 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +info.user.rlnmCnfirm=\uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2e4\uba85\ud655\uc778\uc744 \ud558\uc2ed\uc2dc\uc624. +success.user.rlnmCnfirm=\ud589\uc815\uc548\uc804\ubd80\uc758 \uc8fc\ubbfc\ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud569\ub2c8\ub2e4. +fail.user.rlnmCnfirm=\ud589\uc815\uc548\uc804\ubd80\uc758 \uc8fc\ubbfc\ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.user.connectFail=\uc2dc\uc2a4\ud15c \uc7a5\uc560\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.(\uc778\uc99d\uc11c\ubc84 \uc5f0\uacb0 \uc2e4\ud328) +info.user.rlnmPinCnfirm=\uacf5\uacf5 \uc544\uc774\ud540 \uc544\uc774\ub514\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2e4\uba85\ud655\uc778\uc744 \ud558\uc2ed\uc2dc\uc624. +success.user.rlnmPinCnfirm=\uacf5\uacf5\uc544\uc774\ud540\uc758 \ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud569\ub2c8\ub2e4. +fail.user.rlnmPinCnfirm=\uacf5\uacf5\uc544\uc774\ud540\uc758 \ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + + +#UI Cop Message# +cop.extrlUser = \uc678\ubd80\uc0ac\uc6a9\uc790 +cop.intrlUser = \ub0b4\ubd80\uc0ac\uc6a9\uc790 +cop.private = \ube44\uacf5\uac1c +cop.public = \uacf5\uac1c + +cop.adbkNm = \uc8fc\uc18c\ub85d\uba85 +cop.othbcScope = \uacf5\uac1c\ubc94\uc704 +cop.company = \ud68c\uc0ac +cop.part = \ubd80\uc11c +cop.man = \uac1c\uc778 +cop.adbkUser = \uad6c\uc131\uc6d0 +cop.bbsNm = \uac8c\uc2dc\ud310\uba85 +cop.bbsIntrcn = \uac8c\uc2dc\ud310\uc18c\uac1c +cop.bbsTyCode = \uac8c\uc2dc\ud310 \uc720\ud615 +cop.bbsAttrbCode = \uac8c\uc2dc\ud310 \uc18d\uc131 +cop.replyPosblAt = \ub2f5\uc7a5\uac00\ub2a5\uc5ec\ubd80 +cop.fileAtchPosblAt = \ud30c\uc77c\ucca8\ubd80\uac00\ub2a5\uc5ec\ubd80 +cop.posblAtchFileNumber = \ucca8\ubd80\uac00\ub2a5\ud30c\uc77c \uc22b\uc790 +cop.tmplatId = \ud15c\ud50c\ub9bf \uc815\ubcf4 +cop.guestList.subject = \ubc29\uba85\ub85d \uac8c\uc2dc\uae00\uc785\ub2c8\ub2e4. +cop.nttSj = \uc81c\ubaa9 +cop.nttCn = \uae00\ub0b4\uc6a9 +cop.ntceBgnde = \uac8c\uc2dc\uc2dc\uc791\uc77c +cop.ntceEndde = \uac8c\uc2dc\uc885\ub8cc\uc77c +cop.ntcrNm = \uc791\uc131\uc790 +cop.password = \ud328\uc2a4\uc6cc\ub4dc +cop.atchFile = \ud30c\uc77c\ucca8\ubd80 +cop.guestList = \ubc29\uba85\ub85d +cop.guestListCn = \ubc29\uba85\ub85d \ub0b4\uc6a9 +cop.noticeTerm = \uac8c\uc2dc\uae30\uac04 +cop.atchFileList = \ucca8\ubd80\ud30c\uc77c\ubaa9\ub85d +cop.cmmntyNm = \ucee4\ubba4\ub2c8\ud2f0\uba85 +cop.cmmntyIntrcn = \ucee4\ubba4\ub2c8\ud2f0 \uc18c\uac1c +cop.cmmntyMngr = \ucee4\ubba4\ub2c8\ud2f0 \uad00\ub9ac\uc790 +cop.clbOprtr = \ub3d9\ud638\ud68c \uc6b4\uc601\uc790 +cop.clbIntrcn = \ub3d9\ud638\ud68c \uc18c\uac1c +cop.clbNm = \ub3d9\ud638\ud68c \uba85 +cop.tmplatNm = \ud15c\ud50c\ub9bf\uba85 +cop.tmplatSeCode = \ud15c\ud50c\ub9bf \uad6c\ubd84 +cop.tmplatCours = \ud15c\ud50c\ub9bf\uacbd\ub85c +cop.useAt = \uc0ac\uc6a9\uc5ec\ubd80 +cop.ncrdNm = \uc774\ub984 +cop.cmpnyNm = \ud68c\uc0ac\uba85 +cop.deptNm = \ubd80\uc11c\uba85 +cop.ofcpsNm = \uc9c1\uc704 +cop.clsfNm = \uc9c1\uae09 +cop.emailAdres = \uc774\uba54\uc77c\uc8fc\uc18c +cop.telNo = \uc804\ud654\ubc88\ud638 +cop.mbtlNum = \ud734\ub300\ud3f0\ubc88\ud638 +cop.adres = \uc8fc\uc18c +cop.extrlUserAt = \uc678\ubd80\uc0ac\uc6a9\uc790\uc5ec\ubd80 +cop.publicAt = \uacf5\uac1c\uc5ec\ubd80 +cop.remark = \ube44\uace0 +cop.trgetNm = \ucee4\ubba4\ub2c8\ud2f0/\ub3d9\ud638\ud68c \uc815\ubcf4 +cop.preview = \ubbf8\ub9ac\ubcf4\uae30 + +cop.withdraw.msg=\ud0c8\ud1f4\ucc98\ub9ac \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.reregist.msg=\uc7ac\uac00\uc785 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.registmanager.msg=\uc6b4\uc601\uc9c4\uc73c\ub85c \ub4f1\ub85d\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.use.msg=\uc0ac\uc6a9 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.unuse.msg=\uc0ac\uc6a9\uc911\uc9c0 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.delete.confirm.msg=\uc0ac\uc6a9\uc911\uc9c0\ub97c \uc120\ud0dd\ud558\uc2e4 \uacbd\uc6b0 \ub2e4\uc2dc \uc0ac\uc6a9\uc73c\ub85c \ubcc0\uacbd\uc774 \ubd88\uac00\ub2a5\ud569\ub2c8\ub2e4. +cop.ing.msg=\uc2b9\uc778\uc694\uccad \uc911\uc785\ub2c8\ub2e4. +cop.request.msg=\uac00\uc785\uc2e0\uccad\uc774 \uc815\uc0c1\uc801\uc73c\ub85c \uc694\uccad\ub418\uc5c8\uc2b5\ub2c8\ub2e4 +cop.password.msg=\ud328\uc2a4\uc6cc\ub4dc\ub97c \uc785\ub825\ud574 \uc8fc\uc2ed\uc2dc\uc624. +cop.password.not.same.msg=\ud328\uc2a4\uc6cc\ub4dc\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + +cop.comment.wrterNm = \uc791\uc131\uc790 +cop.comment.commentCn = \ub0b4\uc6a9 +cop.comment.commentPassword = \ud328\uc2a4\uc6cc\ub4dc + +cop.satisfaction.wrterNm = \uc791\uc131\uc790 +cop.satisfaction.stsfdgCn = \ub0b4\uc6a9 +cop.satisfaction.stsfdg = \ub9cc\uc871\ub3c4 +cop.satisfaction.stsfdgPassword = \ud328\uc2a4\uc6cc\ub4dc + +cop.scrap.scrapNm = \uc2a4\ud06c\ub7a9\uba85 + +#UI USS Message# +uss.ion.noi.ntfcSj=\uc81c\ubaa9 +uss.ion.noi.ntfcCn=\ub0b4\uc6a9 +uss.ion.noi.ntfcDate=\uc54c\ub9bc\uc77c\uc790 +uss.ion.noi.ntfcTime=\uc54c\ub9bc\uc2dc\uac04 +uss.ion.noi.ntfcHH=\uc54c\ub9bc\uc2dc\uac04 +uss.ion.noi.ntfcMM=\uc54c\ub9bc\ubd84 +uss.ion.noi.bhNtfcIntrvl=\uc0ac\uc804\uc54c\ub9bc\uac04\uaca9 +uss.ion.noi.bhNtfcIntrvl.msg=\uc0ac\uc804\uc54c\ub9bc\uac04\uaca9 \uc9c0\uc815\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. +uss.ion.noi.alertNtfcTime=\uc54c\ub9bc\uc77c\uc790 \ubc0f \uc2dc\uac04\uc774 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + +#UI COP Message# +cop.sms.trnsmitTelno=\ubc1c\uc2e0\uc804\ud654\ubc88\ud638 +cop.sms.trnsmitCn=\uc804\uc1a1\ub0b4\uc6a9 +cop.sms.recptnTelno=\uc218\uc2e0\uc804\ud654\ubc88\ud638 +cop.sms.send=\uc804\uc1a1 +cop.sms.addRecptn=\ucd94\uac00 +cop.sms.recptnTelno.msg=\uc218\uc2e0\uc804\ud654\ubc88\ud638 \uc9c0\uc815\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. + +#UI sym.log Message# +sym.log.histSeCode = \uc774\ub825\uad6c\ubd84 +sym.log.sysNm = \uc2dc\uc2a4\ud15c\uba85 +sym.log.histCn = \uc774\ub825\ub0b4\uc6a9 +sym.log.atchFile = \ucca8\ubd80\ud30c\uc77c +sym.log.atchFileList = \ucca8\ubd80\ud30c\uc77c\ubaa9\ub85d +sym.ems.receiver = \ubc1b\ub294\uc0ac\ub78c +sym.ems.title = \uc81c\ubaa9 +sym.ems.content = \ubc1c\uc2e0\ub0b4\uc6a9 + +#Vlidator Errors# +errors.prefix=
    +errors.suffix=

    + +errors.required={0}\uc740(\ub294) \ud544\uc218 \uc785\ub825\uac12\uc785\ub2c8\ub2e4. +errors.minlength={0}\uc740(\ub294) {1}\uc790 \uc774\uc0c1 \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.maxlength={0}\uc740(\ub294) {1}\uc790 \uc774\uc0c1 \uc785\ub825\ud560\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +errors.invalid={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uac12\uc785\ub2c8\ub2e4. +errors.minInteger={0}\uc740(\ub294) \uc720\ud6a8\ud55c \uac12\uc774 \uc544\ub2d9\ub2c8\ub2e4. 1 \uc774\uc0c1\uc758 \uac12\uc744 \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.byte={0}\uc740(\ub294) byte\ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.short={0}\uc740(\ub294) short\ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.integer={0}\uc740(\ub294) \uc815\uc218 \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.long={0}\uc740(\ub294) long \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.float={0}\uc740(\ub294) \uc2e4\uc218 \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.double={0}\uc740(\ub294) double \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. + +errors.date={0}\uc740(\ub294) \ub0a0\uc9dc \uc720\ud615\uc774 \uc544\ub2d9\ub2c8\ub2e4. +errors.range={0}\uc740(\ub294) {1}\uacfc {2} \uc0ac\uc774\uc758 \uac12\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.creditcard={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc2e0\uc6a9\uce74\ub4dc \ubc88\ud638\uc785\ub2c8\ub2e4. +errors.email={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc774\uba54\uc77c \uc8fc\uc18c\uc785\ub2c8\ub2e4. + +errors.ihidnum=\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638\uc785\ub2c8\ub2e4. +errors.korean={0}\uc740(\ub294) \ud55c\uae00\uc744 \uc785\ub825\ud558\uc154\uc57c \ud569\ub2c8\ub2e4. +errors.ip=\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 IP\uc8fc\uc18c\uc785\ub2c8\ub2e4. + +errors.password1={0}\uc740(\ub294) 8~20\uc790 \ub0b4\uc5d0\uc11c \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.password2={0}\uc740(\ub294) \ud55c\uae00,\ud2b9\uc218\ubb38\uc790,\ub744\uc5b4\uc4f0\uae30\ub294 \ud5c8\uc6a9\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +errors.password3={0}\uc740(\ub294) \uc21c\ucc28\uc801\uc778 \uc22b\uc790\ub97c 4\uac1c\uc774\uc0c1 \uc5f0\uc18d\ud574\uc11c \uc0ac\uc6a9\ud560\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +errors.password4={0}\uc740(\ub294) \ubc18\ubcf5\ub418\ub294 \ubb38\uc790\ub098 \uc22b\uc790\ub97c 4\uac1c\uc774\uc0c1 \uc5f0\uc18d\ud574\uc11c \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. + +errors.notKorean={0}\uc740(\ub294) \ud55c\uae00\uc744 \uc0ac\uc6a9\ud558\uc2e4\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +error.security.runtime.error = error +#Vlidator Errors- wordDicaryVO# +wordDicaryVO.wordNm=\uc6a9\uc5b4\uba85 +wordDicaryVO.engNm=\uc601\ubb38\uba85 +wordDicaryVO.wordDc=\uc6a9\uc5b4\uc124\uba85 +wordDicaryVO.synonm=\ub3d9\uc758\uc5b4 + +#Vlidator Errors- cnsltManageVO# +cnsltManageVO.cnsltSj=\uc0c1\ub2f4\uc81c\ubaa9 +cnsltManageVO.cnsltCn=\uc0c1\ub2f4\ub0b4\uc6a9 +cnsltManageVO.writngPassword=\uc791\uc131\ube44\ubc00\ubc88\ud638 +cnsltManageVO.areaNo=\uc9c0\uc5ed\ubc88\ud638 +cnsltManageVO.middleTelno=\uc911\uac04\uc804\ud654\ubc88\ud638 +cnsltManageVO.endTelno=\ub05d\uc804\ud654\ubc88\ud638 +cnsltManageVO.wrterNm=\uc791\uc131\uc790\uba85 +cnsltManageVO.managtCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- siteManageVO# +siteManageVO.siteNm=\uc0ac\uc774\ud2b8\uba85 +siteManageVO.siteUrl=\uc0ac\uc774\ud2b8 URL +siteManageVO.siteDc=\uc0ac\uc774\ud2b8\uc124\uba85 +siteManageVO.siteThemaClCode=\uc0ac\uc774\ud2b8\uc8fc\uc81c\ubd84\ub958 +siteManageVO.actvtyAt=\ud65c\uc131\uc5ec\ubd80 +siteManageVO.useAt=\uc0ac\uc6a9\uc5ec\ubd80 + +#Vlidator Errors- recomendSiteManageVO# +recomendSiteManageVO.recomendSiteNm=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uba85 +recomendSiteManageVO.recomendSiteUrl=\ucd94\ucc9c\uc0ac\uc774\ud2b8 URL +recomendSiteManageVO.recomendSiteDc=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uc124\uba85 +recomendSiteManageVO.recomendResnCn=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uc2b9\uc778\uc0ac\uc720 +recomendSiteManageVO.confmDe=\uc2b9\uc778\uc77c\uc790 + +#Vlidator Errors- hpcmManageVO# +hpcmManageVO.hpcmSeCode=\ub3c4\uc6c0\ub9d0\uad6c\ubd84 +hpcmManageVO.hpcmDf=\ub3c4\uc6c0\ub9d0\uc815\uc758 +hpcmManageVO.hpcmDc=\ub3c4\uc6c0\ub9d0\uc124\uba85 + +#Vlidator Errors- newsManageVO# +newsManageVO.newsSj=\ub274\uc2a4\uc81c\ubaa9 +newsManageVO.newsCn=\ub274\uc2a4\ub0b4\uc6a9 +newsManageVO.ntceDe=\uac8c\uc2dc\uc77c\uc790 + +#Vlidator Errors- faqManageVO# +faqManageVO.qestnSj=\uc9c8\ubb38\uc81c\ubaa9 +faqManageVO.qestnCn=\uc9c8\ubb38\ub0b4\uc6a9 +faqManageVO.answerCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- stplatManageVO# +stplatManageVO.useStplatNm=\uc774\uc6a9\uc57d\uad00\uba85 +stplatManageVO.useStplatCn=\uc774\uc6a9\uc57d\uad00\ub0b4\uc6a9 +stplatManageVO.infoProvdAgreCn=\uc815\ubcf4\uc81c\uacf5\ub3d9\uc758\ub0b4\uc6a9 + +#Vlidator Errors- cpyrhtPrtcPolicyVO# +cpyrhtPrtcPolicyVO.cpyrhtPrtcPolicyCn=\uc800\uc791\uad8c\ubcf4\ud638\uc815\ucc45\ub0b4\uc6a9 + +#Vlidator Errors- qnaManageVO# +qnaManageVO.qestnSj=\uc9c8\ubb38\uc81c\ubaa9 +qnaManageVO.qestnCn=\uc9c8\ubb38\ub0b4\uc6a9 +qnaManageVO.writngPassword=\uc791\uc131\ube44\ubc00\ubc88\ud638 +qnaManageVO.areaNo=\uc9c0\uc5ed\ubc88\ud638 +qnaManageVO.middleTelno=\uc911\uac04\uc804\ud654\ubc88\ud638 +qnaManageVO.endTelno=\ub05d\uc804\ud654\ubc88\ud638 +qnaManageVO.wrterNm=\uc791\uc131\uc790\uba85 +qnaManageVO.answerCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- ReprtStatsVO# +sts.reprtId = \ubcf4\uace0\uc11cID +sts.title = \ubcf4\uace0\uc11c\uba85 +sts.category = \ubcf4\uace0\uc11c\uc720\ud615 +sts.status = \uc9c4\ud589\uc0c1\ud0dc +sts.regDate = \ub4f1\ub85d\uc77c\uc2dc + +#Rest day messages# +sym.cal.restDay = \ud734\uc77c\uc77c\uc790 +sym.cal.restName = \ud734\uc77c\uba85 +sym.cal.restDetail = \ud734\uc77c\uc124\uba85 +sym.cal.restCategory = \ud734\uc77c\uad6c\ubd84 \ No newline at end of file diff --git a/mens-core/src/main/resources/egovframework/message/com/message-common_en.properties b/mens-core/src/main/resources/egovframework/message/com/message-common_en.properties new file mode 100644 index 0000000..1d5b3f8 --- /dev/null +++ b/mens-core/src/main/resources/egovframework/message/com/message-common_en.properties @@ -0,0 +1,196 @@ +fail.common.msg=error ocurred! +fail.common.sql=sql error ocurred! error code: {0}, error msg: {1} +info.nodata.msg=no data found. + +#UI Common resource# +button.search=Search +button.use=use +button.notUsed=Not used +button.inquire=inquire +button.update=update +button.create=create +button.delete=delete +button.close=close +button.save=save +button.list=list +button.reset=reset +button.passwordUpdate=password update +button.subscribe=subscribe +button.realname=realname confirm +button.moveToGpin=move to gpin confirm +button.moveToIhidnum=move to ihidnum confirm +button.agree=agree +button.disagree=disagree +button.possible = possible +button.impossible = impossible + +#UI Common Message# +common.save.msg=confirm save? +common.regist.msg=confirm regist? +common.delete.msg=confirm delete? +common.update.msg=confirm update? +common.nodata.msg=There is no data. please choose another seach keyword +common.required.msg=is required field +common.acknowledgement.msg=confirm acknowledgement? +common.acknowledgementcancel.msg=confirm acknowledgement cancel? + +success.request.msg=you're request successfully done + +success.common.select=successfully selected +success.common.insert=successfully inserted +success.common.update=successfully updated +success.common.delete=successfully deleted + +common.imposbl.fileupload = cannot upload files + +fail.common.insert = fail to insert. +fail.common.update = fail to update +fail.common.delete = fail to delete +fail.common.delete.upperMenuExist = fail to delete[upperMenuId foreign key error] +fail.common.select = fail to select +common.isExist.msg = already exist +fail.common.login = login information is not correct +fail.common.idsearch = can not find id +fail.common.pwsearch = can not find password + + +#UI User Message# +fail.user.passwordUpdate1=current password is not correct +fail.user.passwordUpdate2=password confirm is not correct +info.user.rlnmCnfirm=realname confirm ready +success.user.rlnmCnfirm=it is realname +fail.user.rlnmCnfirm=it is not realname +fail.user.connectFail=connection fail + +#UI Cop Message# +cop.extrlUser = External User +cop.intrlUser = Internal User +cop.private = private +cop.public = public + +cop.bbsNm = BBS Name +cop.bbsIntrcn = BBS Introduction +cop.bbsTyCode = BBS Type +cop.bbsAttrbCode = BBS Attribute +cop.replyPosblAt = Reply Possible Alternative +cop.fileAtchPosblAt = File Attach Possible Alternative +cop.posblAtchFileNumber = Possible Attach File Number +cop.tmplatId = Template Information +cop.guestList.subject = This article registered by Guest List +cop.nttSj = Notice Subject +cop.nttCn = Notice Contents +cop.ntceBgnde = Notice Start Date +cop.ntceEndde = Notice End Date +cop.ntcrNm = Noticer Name +cop.password = PassWord +cop.atchFile = Attach Files +cop.guestList = Guest List +cop.guestListCn = Guest List Contents +cop.noticeTerm = Notice term +cop.atchFileList = Attached File List +cop.cmmntyNm = Community Name +cop.cmmntyIntrcn = Community Introduction +cop.cmmntyMngr = Community Manager +cop.clbOprtr = Club Operator +cop.clbIntrcn = Club Introduction +cop.clbNm = Club Name +cop.tmplatNm = Template Name +cop.tmplatSeCode = Template Se Code +cop.tmplatCours = Template Cours +cop.useAt = Use Alternative +cop.ncrdNm = NameCard user name +cop.cmpnyNm = Company name +cop.deptNm = Department name +cop.ofcpsNm = OFCPS name +cop.clsfNm = Class Name +cop.emailAdres = E-mail +cop.telNo = Tel No. +cop.mbtlNum = Mobile +cop.adres = Address +cop.extrlUserAt = External User alternative +cop.publicAt = Public open alternative +cop.remark = Remark +cop.trgetNm = Company/Club Information +cop.preview = preview + +cop.withdraw.msg=confirm withdrawal memebership? +cop.reregist.msg=confirm re-registration? +cop.registmanager.msg=confirm registration of manager? +cop.use.msg=confirm use? +cop.unuse.msg=confirm stop using? +cop.delete.confirm.msg=If you choose to disable the re-use change is impossible. +cop.ing.msg=Approval is being requested. +cop.request.msg=Signup is normally requested. +cop.password.msg=Please enter your password. +cop.password.not.same.msg=Password do not match. + +cop.comment.wrterNm = Writer Name +cop.comment.commentCn = Comment +cop.comment.commentPassword = Password + +cop.satisfaction.wrterNm = Writer Name +cop.satisfaction.stsfdgCn = Satisfaction +cop.satisfaction.stsfdg = Satisfaction Degree +cop.satisfaction.stsfdgPassword = Password + +cop.scrap.scrapNm = Scrap Name + +#UI USS Message# +uss.ion.noi.ntfcSj=Subject +uss.ion.noi.ntfcCn=Contents +uss.ion.noi.ntfcDate=Notification Date +uss.ion.noi.ntfcTime=Notification Time +uss.ion.noi.ntfcHH=Notification Hour +uss.ion.noi.ntfcMM=Notification Minute +uss.ion.noi.bhNtfcIntrvl=Beforehand Interval +uss.ion.noi.bhNtfcIntrvl.msg=Beforehand Interval is required. +uss.ion.noi.alertNtfcTime=Date and time of notification is not valid. + +#UI COP Message# +cop.sms.trnsmitTelno=Sender +cop.sms.trnsmitCn=Contents +cop.sms.recptnTelno=Receiver(s) +cop.sms.send=Send +cop.sms.addRecptn=Add +cop.sms.recptnTelno.msg=The phone number of receiver is required. + +#UI sym.log Message# +sym.log.histSeCode = History Code +sym.log.sysNm = System Name +sym.log.histCn = History Contents +sym.log.atchFile = Attached File +sym.log.atchFileList = Attached File List +sym.ems.receiver = Receiver +sym.ems.title = Title +sym.ems.content = Content + +#Vlidator Errors# +errors.required={0} is required. +errors.minlength={0} can not be less than {1} characters. +errors.maxlength={0} can not be greater than {1} characters. +errors.invalid={0} is invalid. + +errors.byte={0} must be a byte. +errors.short={0} must be a short. +errors.integer={0} must be an integer. +errors.long={0} must be a long. +errors.float={0} must be a float. +errors.double={0} must be a double. + +errors.date={0} is not a date. +errors.range={0} is not in the range {1} through {2}. +errors.creditcard={0} is an invalid credit card number. +errors.email={0} is an invalid e-mail address. + +#Vlidator Errors- ReprtStatsVO# +sts.reprtId = Report ID +sts.title = Report Title +sts.category = Report Category +sts.status = Report Status +sts.regDate = Registration Date + +#Rest day messages# +sym.cal.restDay = Holiday Date +sym.cal.restName = Holiday Name +sym.cal.restDetail = Holiday Detail +sym.cal.restCategory = Holiday Category \ No newline at end of file diff --git a/mens-core/src/main/resources/egovframework/message/com/message-common_ko.properties b/mens-core/src/main/resources/egovframework/message/com/message-common_ko.properties new file mode 100644 index 0000000..4cdeb51 --- /dev/null +++ b/mens-core/src/main/resources/egovframework/message/com/message-common_ko.properties @@ -0,0 +1,294 @@ +fail.common.msg=\uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! +fail.common.sql=sql \uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! error code: {0}, error msg: {1} +info.nodata.msg=\ud574\ub2f9 \ub370\uc774\ud130\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. + +#UI Common resource# +button.search=\uac80\uc0c9 +button.use=\uc0ac\uc6a9 +button.notUsed=\uc0ac\uc6a9\uc911\uc9c0 +button.inquire=\uc870\ud68c +button.update=\uc218\uc815 +button.create=\ub4f1\ub85d +button.delete=\uc0ad\uc81c +button.deleteDatabase=\uc644\uc804\uc0ad\uc81c +button.close=\ub2eb\uae30 +button.save=\uc800\uc7a5 +button.list=\ubaa9\ub85d +button.reset=\ucde8\uc18c +button.passwordUpdate=\uc554\ud638\ubcc0\uacbd +button.subscribe=\uac00\uc785\uc2e0\uccad +button.realname=\uc2e4\uba85\ud655\uc778 +button.moveToGpin=GPIN\uc2e4\uba85\ud655\uc778\uc73c\ub85c \uc774\ub3d9 +button.moveToIhidnum=\uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638 \uc2e4\uba85\ud655\uc778\uc73c\ub85c \uc774\ub3d9 +button.agree=\ub3d9\uc758 +button.disagree=\ube44\ub3d9\uc758 +button.possible = \uac00\ub2a5 +button.impossible = \ubd88\uac00\ub2a5 +button.qnaregist=Q&A\ub4f1\ub85d +button.cnsltregist=\uc0c1\ub2f4\ub4f1\ub85d +button.preview=\ubbf8\ub9ac\ubcf4\uae30 +button.next=\ub2e4\uc74c +button.add=\ubc14\ub85c\ucd94\uac00 +button.confirm=\ud655\uc778 + + +#UI Common Message# +common.save.msg=\uc800\uc7a5\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.regist.msg=\ub4f1\ub85d\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.delete.msg=\uc0ad\uc81c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.update.msg=\uc218\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.nodata.msg=\uc790\ub8cc\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\ub978 \uac80\uc0c9\uc870\uac74\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694 +common.required.msg=(\uc740)\ub294 \ud544\uc218\uc785\ub825\ud56d\ubaa9\uc785\ub2c8\ub2e4. +common.acknowledgement.msg=\uc2b9\uc778\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.acknowledgementcancel.msg=\uc2b9\uc778\ucde8\uc18c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? + +success.common.select=\uc815\uc0c1\uc801\uc73c\ub85c \uc870\ud68c\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.insert=\uc815\uc0c1\uc801\uc73c\ub85c \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.update=\uc815\uc0c1\uc801\uc73c\ub85c \uc218\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.delete=\uc815\uc0c1\uc801\uc73c\ub85c \uc0ad\uc81c\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +success.request.msg = \uc694\uccad\ucc98\ub9ac\uac00 \uc131\uacf5\uc801\uc73c\ub85c \uc218\ud589\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +common.imposbl.fileupload = \ub354 \uc774\uc0c1 \ud30c\uc77c\uc744 \ucca8\ubd80\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +common.isConfmDe.msg=\uc2b9\uc778\uc77c\uc790\ub97c \ud655\uc778 \ubc14\ub78d\ub2c8\ub2e4. + + +fail.common.insert = \uc0dd\uc131\uc774 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.update = \uc218\uc815\uc774 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.delete = \uc0ad\uc81c\uac00 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.delete.upperMenuExist = \ucc38\uc870\ub418\ub294 \uba54\ub274\uac00 \uc788\uc5b4 \uc0ad\uc81c\uac00 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.select = \uc870\ud68c\uc5d0 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +common.isExist.msg = \uc774\ubbf8 \uc874\uc7ac\ud558\uac70\ub098 \uacfc\uac70\uc5d0 \ub4f1\ub85d\uc774 \ub418\uc5c8\ub358 \uc0c1\ud0dc\uc785\ub2c8\ub2e4. +fail.common.login = \ub85c\uadf8\uc778 \uc815\ubcf4\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.common.idsearch = \uc544\uc774\ub514\ub97c \ucc3e\uc744\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +fail.common.pwsearch = \ube44\ubc00\ubc88\ud638\ub97c \ucc3e\uc744\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +fail.request.msg = \uc694\uccad\ucc98\ub9ac\ub97c \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. + +#UI User Message# +fail.user.passwordUpdate1=\ud604\uc7ac \ube44\ubc00\ubc88\ud638\uac00 \ub9de\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.user.passwordUpdate2=\ube44\ubc00\ubc88\ud638\uc640 \ube44\ubc00\ubc88\ud638 \ud655\uc778\uc774 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +info.user.rlnmCnfirm=\uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2e4\uba85\ud655\uc778\uc744 \ud558\uc2ed\uc2dc\uc624. +success.user.rlnmCnfirm=\ud589\uc815\uc548\uc804\ubd80\uc758 \uc8fc\ubbfc\ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud569\ub2c8\ub2e4. +fail.user.rlnmCnfirm=\ud589\uc815\uc548\uc804\ubd80\uc758 \uc8fc\ubbfc\ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.user.connectFail=\uc2dc\uc2a4\ud15c \uc7a5\uc560\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.(\uc778\uc99d\uc11c\ubc84 \uc5f0\uacb0 \uc2e4\ud328) +info.user.rlnmPinCnfirm=\uacf5\uacf5 \uc544\uc774\ud540 \uc544\uc774\ub514\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2e4\uba85\ud655\uc778\uc744 \ud558\uc2ed\uc2dc\uc624. +success.user.rlnmPinCnfirm=\uacf5\uacf5\uc544\uc774\ud540\uc758 \ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud569\ub2c8\ub2e4. +fail.user.rlnmPinCnfirm=\uacf5\uacf5\uc544\uc774\ud540\uc758 \ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + + +#UI Cop Message# +cop.extrlUser = \uc678\ubd80\uc0ac\uc6a9\uc790 +cop.intrlUser = \ub0b4\ubd80\uc0ac\uc6a9\uc790 +cop.private = \ube44\uacf5\uac1c +cop.public = \uacf5\uac1c + +cop.adbkNm = \uc8fc\uc18c\ub85d\uba85 +cop.othbcScope = \uacf5\uac1c\ubc94\uc704 +cop.company = \ud68c\uc0ac +cop.part = \ubd80\uc11c +cop.man = \uac1c\uc778 +cop.adbkUser = \uad6c\uc131\uc6d0 +cop.bbsNm = \uac8c\uc2dc\ud310\uba85 +cop.bbsIntrcn = \uac8c\uc2dc\ud310\uc18c\uac1c +cop.bbsTyCode = \uac8c\uc2dc\ud310 \uc720\ud615 +cop.bbsAttrbCode = \uac8c\uc2dc\ud310 \uc18d\uc131 +cop.replyPosblAt = \ub2f5\uc7a5\uac00\ub2a5\uc5ec\ubd80 +cop.fileAtchPosblAt = \ud30c\uc77c\ucca8\ubd80\uac00\ub2a5\uc5ec\ubd80 +cop.posblAtchFileNumber = \ucca8\ubd80\uac00\ub2a5\ud30c\uc77c \uc22b\uc790 +cop.tmplatId = \ud15c\ud50c\ub9bf \uc815\ubcf4 +cop.guestList.subject = \ubc29\uba85\ub85d \uac8c\uc2dc\uae00\uc785\ub2c8\ub2e4. +cop.nttSj = \uc81c\ubaa9 +cop.nttCn = \uae00\ub0b4\uc6a9 +cop.ntceBgnde = \uac8c\uc2dc\uc2dc\uc791\uc77c +cop.ntceEndde = \uac8c\uc2dc\uc885\ub8cc\uc77c +cop.ntcrNm = \uc791\uc131\uc790 +cop.password = \ud328\uc2a4\uc6cc\ub4dc +cop.atchFile = \ud30c\uc77c\ucca8\ubd80 +cop.guestList = \ubc29\uba85\ub85d +cop.guestListCn = \ubc29\uba85\ub85d \ub0b4\uc6a9 +cop.noticeTerm = \uac8c\uc2dc\uae30\uac04 +cop.atchFileList = \ucca8\ubd80\ud30c\uc77c\ubaa9\ub85d +cop.cmmntyNm = \ucee4\ubba4\ub2c8\ud2f0\uba85 +cop.cmmntyIntrcn = \ucee4\ubba4\ub2c8\ud2f0 \uc18c\uac1c +cop.cmmntyMngr = \ucee4\ubba4\ub2c8\ud2f0 \uad00\ub9ac\uc790 +cop.clbOprtr = \ub3d9\ud638\ud68c \uc6b4\uc601\uc790 +cop.clbIntrcn = \ub3d9\ud638\ud68c \uc18c\uac1c +cop.clbNm = \ub3d9\ud638\ud68c \uba85 +cop.tmplatNm = \ud15c\ud50c\ub9bf\uba85 +cop.tmplatSeCode = \ud15c\ud50c\ub9bf \uad6c\ubd84 +cop.tmplatCours = \ud15c\ud50c\ub9bf\uacbd\ub85c +cop.useAt = \uc0ac\uc6a9\uc5ec\ubd80 +cop.ncrdNm = \uc774\ub984 +cop.cmpnyNm = \ud68c\uc0ac\uba85 +cop.deptNm = \ubd80\uc11c\uba85 +cop.ofcpsNm = \uc9c1\uc704 +cop.clsfNm = \uc9c1\uae09 +cop.emailAdres = \uc774\uba54\uc77c\uc8fc\uc18c +cop.telNo = \uc804\ud654\ubc88\ud638 +cop.mbtlNum = \ud734\ub300\ud3f0\ubc88\ud638 +cop.adres = \uc8fc\uc18c +cop.extrlUserAt = \uc678\ubd80\uc0ac\uc6a9\uc790\uc5ec\ubd80 +cop.publicAt = \uacf5\uac1c\uc5ec\ubd80 +cop.remark = \ube44\uace0 +cop.trgetNm = \ucee4\ubba4\ub2c8\ud2f0/\ub3d9\ud638\ud68c \uc815\ubcf4 +cop.preview = \ubbf8\ub9ac\ubcf4\uae30 + +cop.withdraw.msg=\ud0c8\ud1f4\ucc98\ub9ac \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.reregist.msg=\uc7ac\uac00\uc785 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.registmanager.msg=\uc6b4\uc601\uc9c4\uc73c\ub85c \ub4f1\ub85d\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.use.msg=\uc0ac\uc6a9 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.unuse.msg=\uc0ac\uc6a9\uc911\uc9c0 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.delete.confirm.msg=\uc0ac\uc6a9\uc911\uc9c0\ub97c \uc120\ud0dd\ud558\uc2e4 \uacbd\uc6b0 \ub2e4\uc2dc \uc0ac\uc6a9\uc73c\ub85c \ubcc0\uacbd\uc774 \ubd88\uac00\ub2a5\ud569\ub2c8\ub2e4. +cop.ing.msg=\uc2b9\uc778\uc694\uccad \uc911\uc785\ub2c8\ub2e4. +cop.request.msg=\uac00\uc785\uc2e0\uccad\uc774 \uc815\uc0c1\uc801\uc73c\ub85c \uc694\uccad\ub418\uc5c8\uc2b5\ub2c8\ub2e4 +cop.password.msg=\ud328\uc2a4\uc6cc\ub4dc\ub97c \uc785\ub825\ud574 \uc8fc\uc2ed\uc2dc\uc624. +cop.password.not.same.msg=\ud328\uc2a4\uc6cc\ub4dc\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + +cop.comment.wrterNm = \uc791\uc131\uc790 +cop.comment.commentCn = \ub0b4\uc6a9 +cop.comment.commentPassword = \ud328\uc2a4\uc6cc\ub4dc + +cop.satisfaction.wrterNm = \uc791\uc131\uc790 +cop.satisfaction.stsfdgCn = \ub0b4\uc6a9 +cop.satisfaction.stsfdg = \ub9cc\uc871\ub3c4 +cop.satisfaction.stsfdgPassword = \ud328\uc2a4\uc6cc\ub4dc + +cop.scrap.scrapNm = \uc2a4\ud06c\ub7a9\uba85 + +#UI USS Message# +uss.ion.noi.ntfcSj=\uc81c\ubaa9 +uss.ion.noi.ntfcCn=\ub0b4\uc6a9 +uss.ion.noi.ntfcDate=\uc54c\ub9bc\uc77c\uc790 +uss.ion.noi.ntfcTime=\uc54c\ub9bc\uc2dc\uac04 +uss.ion.noi.ntfcHH=\uc54c\ub9bc\uc2dc\uac04 +uss.ion.noi.ntfcMM=\uc54c\ub9bc\ubd84 +uss.ion.noi.bhNtfcIntrvl=\uc0ac\uc804\uc54c\ub9bc\uac04\uaca9 +uss.ion.noi.bhNtfcIntrvl.msg=\uc0ac\uc804\uc54c\ub9bc\uac04\uaca9 \uc9c0\uc815\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. +uss.ion.noi.alertNtfcTime=\uc54c\ub9bc\uc77c\uc790 \ubc0f \uc2dc\uac04\uc774 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + +#UI COP Message# +cop.sms.trnsmitTelno=\ubc1c\uc2e0\uc804\ud654\ubc88\ud638 +cop.sms.trnsmitCn=\uc804\uc1a1\ub0b4\uc6a9 +cop.sms.recptnTelno=\uc218\uc2e0\uc804\ud654\ubc88\ud638 +cop.sms.send=\uc804\uc1a1 +cop.sms.addRecptn=\ucd94\uac00 +cop.sms.recptnTelno.msg=\uc218\uc2e0\uc804\ud654\ubc88\ud638 \uc9c0\uc815\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. + +#UI sym.log Message# +sym.log.histSeCode = \uc774\ub825\uad6c\ubd84 +sym.log.sysNm = \uc2dc\uc2a4\ud15c\uba85 +sym.log.histCn = \uc774\ub825\ub0b4\uc6a9 +sym.log.atchFile = \ucca8\ubd80\ud30c\uc77c +sym.log.atchFileList = \ucca8\ubd80\ud30c\uc77c\ubaa9\ub85d +sym.ems.receiver = \ubc1b\ub294\uc0ac\ub78c +sym.ems.title = \uc81c\ubaa9 +sym.ems.content = \ubc1c\uc2e0\ub0b4\uc6a9 + +#Vlidator Errors# +errors.prefix=
    +errors.suffix=

    + +errors.required={0}\uc740(\ub294) \ud544\uc218 \uc785\ub825\uac12\uc785\ub2c8\ub2e4. +errors.minlength={0}\uc740(\ub294) {1}\uc790 \uc774\uc0c1 \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.maxlength={0}\uc740(\ub294) {1}\uc790 \uc774\uc0c1 \uc785\ub825\ud560\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +errors.invalid={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uac12\uc785\ub2c8\ub2e4. +errors.minInteger={0}\uc740(\ub294) \uc720\ud6a8\ud55c \uac12\uc774 \uc544\ub2d9\ub2c8\ub2e4. 1 \uc774\uc0c1\uc758 \uac12\uc744 \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.byte={0}\uc740(\ub294) byte\ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.short={0}\uc740(\ub294) short\ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.integer={0}\uc740(\ub294) \uc815\uc218 \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.long={0}\uc740(\ub294) long \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.float={0}\uc740(\ub294) \uc2e4\uc218 \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.double={0}\uc740(\ub294) double \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. + +errors.date={0}\uc740(\ub294) \ub0a0\uc9dc \uc720\ud615\uc774 \uc544\ub2d9\ub2c8\ub2e4. +errors.range={0}\uc740(\ub294) {1}\uacfc {2} \uc0ac\uc774\uc758 \uac12\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.creditcard={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc2e0\uc6a9\uce74\ub4dc \ubc88\ud638\uc785\ub2c8\ub2e4. +errors.email={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc774\uba54\uc77c \uc8fc\uc18c\uc785\ub2c8\ub2e4. + +errors.ihidnum=\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638\uc785\ub2c8\ub2e4. +errors.korean={0}\uc740(\ub294) \ud55c\uae00\uc744 \uc785\ub825\ud558\uc154\uc57c \ud569\ub2c8\ub2e4. +errors.ip=\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 IP\uc8fc\uc18c\uc785\ub2c8\ub2e4. + +errors.password1={0}\uc740(\ub294) 8~20\uc790 \ub0b4\uc5d0\uc11c \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.password2={0}\uc740(\ub294) \ud55c\uae00,\ud2b9\uc218\ubb38\uc790,\ub744\uc5b4\uc4f0\uae30\ub294 \ud5c8\uc6a9\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +errors.password3={0}\uc740(\ub294) \uc21c\ucc28\uc801\uc778 \uc22b\uc790\ub97c 4\uac1c\uc774\uc0c1 \uc5f0\uc18d\ud574\uc11c \uc0ac\uc6a9\ud560\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +errors.password4={0}\uc740(\ub294) \ubc18\ubcf5\ub418\ub294 \ubb38\uc790\ub098 \uc22b\uc790\ub97c 4\uac1c\uc774\uc0c1 \uc5f0\uc18d\ud574\uc11c \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. + +errors.notKorean={0}\uc740(\ub294) \ud55c\uae00\uc744 \uc0ac\uc6a9\ud558\uc2e4\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +error.security.runtime.error = error +#Vlidator Errors- wordDicaryVO# +wordDicaryVO.wordNm=\uc6a9\uc5b4\uba85 +wordDicaryVO.engNm=\uc601\ubb38\uba85 +wordDicaryVO.wordDc=\uc6a9\uc5b4\uc124\uba85 +wordDicaryVO.synonm=\ub3d9\uc758\uc5b4 + +#Vlidator Errors- cnsltManageVO# +cnsltManageVO.cnsltSj=\uc0c1\ub2f4\uc81c\ubaa9 +cnsltManageVO.cnsltCn=\uc0c1\ub2f4\ub0b4\uc6a9 +cnsltManageVO.writngPassword=\uc791\uc131\ube44\ubc00\ubc88\ud638 +cnsltManageVO.areaNo=\uc9c0\uc5ed\ubc88\ud638 +cnsltManageVO.middleTelno=\uc911\uac04\uc804\ud654\ubc88\ud638 +cnsltManageVO.endTelno=\ub05d\uc804\ud654\ubc88\ud638 +cnsltManageVO.wrterNm=\uc791\uc131\uc790\uba85 +cnsltManageVO.managtCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- siteManageVO# +siteManageVO.siteNm=\uc0ac\uc774\ud2b8\uba85 +siteManageVO.siteUrl=\uc0ac\uc774\ud2b8 URL +siteManageVO.siteDc=\uc0ac\uc774\ud2b8\uc124\uba85 +siteManageVO.siteThemaClCode=\uc0ac\uc774\ud2b8\uc8fc\uc81c\ubd84\ub958 +siteManageVO.actvtyAt=\ud65c\uc131\uc5ec\ubd80 +siteManageVO.useAt=\uc0ac\uc6a9\uc5ec\ubd80 + +#Vlidator Errors- recomendSiteManageVO# +recomendSiteManageVO.recomendSiteNm=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uba85 +recomendSiteManageVO.recomendSiteUrl=\ucd94\ucc9c\uc0ac\uc774\ud2b8 URL +recomendSiteManageVO.recomendSiteDc=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uc124\uba85 +recomendSiteManageVO.recomendResnCn=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uc2b9\uc778\uc0ac\uc720 +recomendSiteManageVO.confmDe=\uc2b9\uc778\uc77c\uc790 + +#Vlidator Errors- hpcmManageVO# +hpcmManageVO.hpcmSeCode=\ub3c4\uc6c0\ub9d0\uad6c\ubd84 +hpcmManageVO.hpcmDf=\ub3c4\uc6c0\ub9d0\uc815\uc758 +hpcmManageVO.hpcmDc=\ub3c4\uc6c0\ub9d0\uc124\uba85 + +#Vlidator Errors- newsManageVO# +newsManageVO.newsSj=\ub274\uc2a4\uc81c\ubaa9 +newsManageVO.newsCn=\ub274\uc2a4\ub0b4\uc6a9 +newsManageVO.ntceDe=\uac8c\uc2dc\uc77c\uc790 + +#Vlidator Errors- faqManageVO# +faqManageVO.qestnSj=\uc9c8\ubb38\uc81c\ubaa9 +faqManageVO.qestnCn=\uc9c8\ubb38\ub0b4\uc6a9 +faqManageVO.answerCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- stplatManageVO# +stplatManageVO.useStplatNm=\uc774\uc6a9\uc57d\uad00\uba85 +stplatManageVO.useStplatCn=\uc774\uc6a9\uc57d\uad00\ub0b4\uc6a9 +stplatManageVO.infoProvdAgreCn=\uc815\ubcf4\uc81c\uacf5\ub3d9\uc758\ub0b4\uc6a9 + +#Vlidator Errors- cpyrhtPrtcPolicyVO# +cpyrhtPrtcPolicyVO.cpyrhtPrtcPolicyCn=\uc800\uc791\uad8c\ubcf4\ud638\uc815\ucc45\ub0b4\uc6a9 + +#Vlidator Errors- qnaManageVO# +qnaManageVO.qestnSj=\uc9c8\ubb38\uc81c\ubaa9 +qnaManageVO.qestnCn=\uc9c8\ubb38\ub0b4\uc6a9 +qnaManageVO.writngPassword=\uc791\uc131\ube44\ubc00\ubc88\ud638 +qnaManageVO.areaNo=\uc9c0\uc5ed\ubc88\ud638 +qnaManageVO.middleTelno=\uc911\uac04\uc804\ud654\ubc88\ud638 +qnaManageVO.endTelno=\ub05d\uc804\ud654\ubc88\ud638 +qnaManageVO.wrterNm=\uc791\uc131\uc790\uba85 +qnaManageVO.answerCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- ReprtStatsVO# +sts.reprtId = \ubcf4\uace0\uc11cID +sts.title = \ubcf4\uace0\uc11c\uba85 +sts.category = \ubcf4\uace0\uc11c\uc720\ud615 +sts.status = \uc9c4\ud589\uc0c1\ud0dc +sts.regDate = \ub4f1\ub85d\uc77c\uc2dc + +#Rest day messages# +sym.cal.restDay = \ud734\uc77c\uc77c\uc790 +sym.cal.restName = \ud734\uc77c\uba85 +sym.cal.restDetail = \ud734\uc77c\uc124\uba85 +sym.cal.restCategory = \ud734\uc77c\uad6c\ubd84 \ No newline at end of file diff --git a/mens-core/src/main/resources/egovframework/validator/validator-rules-let.xml b/mens-core/src/main/resources/egovframework/validator/validator-rules-let.xml new file mode 100644 index 0000000..3529d20 --- /dev/null +++ b/mens-core/src/main/resources/egovframework/validator/validator-rules-let.xml @@ -0,0 +1,1530 @@ + + + + + + + + + + = 0) { + value = field.options[si].value; + } + } else if (field.type == 'radio' || field.type == 'checkbox') { + if (field.checked) { + value = field.value; + } + } else { + value = field.value; + } + if (trim(value).length == 0) { + if ((i == 0) && (field.type != 'hidden')) { + focusField = field; + } + fields[i++] = oRequired[x][1]; + isValid = false; + } + } else if (field.type == "select-multiple") { + var numOptions = field.options.length; + lastSelected=-1; + for(loop=numOptions-1;loop>=0;loop--) { + if(field.options[loop].selected) { + lastSelected = loop; + value = field.options[loop].value; + break; + } + } + if(lastSelected < 0 || trim(value).length == 0) { + if(i == 0) { + focusField = field; + } + fields[i++] = oRequired[x][1]; + isValid=false; + } + } else if ((field.length > 0) && (field[0].type == 'radio' || field[0].type == 'checkbox')) { + isChecked=-1; + for (loop=0;loop < field.length;loop++) { + if (field[loop].checked) { + isChecked=loop; + break; // only one needs to be checked + } + } + if (isChecked < 0) { + if (i == 0) { + focusField = field[0]; + } + fields[i++] = oRequired[x][1]; + isValid=false; + } + } + } + if (fields.length > 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return isValid; + } + + // Trim whitespace from left and right sides of s. + function trim(s) { + return s.replace( /^\s*/, "" ).replace( /\s*$/, "" ); + } + + ]]> + + + + + + + + + + + + 0) && (field.value.length < iMin)) { + if (i == 0) { + focusField = field; + } + fields[i++] = oMinLength[x][1]; + isValid = false; + } + } + } + if (fields.length > 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return isValid; + }]]> + + + + + + + + iMax) { + if (i == 0) { + focusField = field; + } + fields[i++] = oMaxLength[x][1]; + isValid = false; + } + } + } + if (fields.length > 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return isValid; + }]]> + + + + + + + + 0)) { + + if (!matchPattern(field.value, oMasked[x][2]("mask"))) { + if (i == 0) { + focusField = field; + } + fields[i++] = oMasked[x][1]; + isValid = false; + } + } + } + + if (fields.length > 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return isValid; + } + + function matchPattern(value, mask) { + return mask.exec(value); + }]]> + + + + + + + + = 0) { + value = field.options[si].value; + } + } else { + value = field.value; + } + + if (value.length > 0) { + if (!isAllDigits(value)) { + bValid = false; + if (i == 0) { + focusField = field; + } + fields[i++] = oByte[x][1]; + + } else { + + var iValue = parseInt(value); + if (isNaN(iValue) || !(iValue >= -128 && iValue <= 127)) { + if (i == 0) { + focusField = field; + } + fields[i++] = oByte[x][1]; + bValid = false; + } + } + } + + } + } + if (fields.length > 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return bValid; + }]]> + + + + + + + + = 0) { + value = field.options[si].value; + } + } else { + value = field.value; + } + + if (value.length > 0) { + if (!isAllDigits(value)) { + bValid = false; + if (i == 0) { + focusField = field; + } + fields[i++] = oShort[x][1]; + + } else { + + var iValue = parseInt(value); + if (isNaN(iValue) || !(iValue >= -32768 && iValue <= 32767)) { + if (i == 0) { + focusField = field; + } + fields[i++] = oShort[x][1]; + bValid = false; + } + } + } + } + } + if (fields.length > 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return bValid; + }]]> + + + + + + + + = 0) { + value = field.options[si].value; + } + } else { + value = field.value; + } + + if (value.length > 0) { + + if (!isAllDigits(value)) { + bValid = false; + if (i == 0) { + focusField = field; + } + fields[i++] = oInteger[x][1]; + + } else { + var iValue = parseInt(value); + if (isNaN(iValue) || !(iValue >= -2147483648 && iValue <= 2147483647)) { + if (i == 0) { + focusField = field; + } + fields[i++] = oInteger[x][1]; + bValid = false; + } + } + } + } + } + if (fields.length > 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return bValid; + } + + function isAllDigits(argvalue) { + argvalue = argvalue.toString(); + var validChars = "0123456789"; + var startFrom = 0; + if (argvalue.substring(0, 2) == "0x") { + validChars = "0123456789abcdefABCDEF"; + startFrom = 2; + } else if (argvalue.charAt(0) == "0") { + validChars = "01234567"; + startFrom = 1; + } else if (argvalue.charAt(0) == "-") { + startFrom = 1; + } + + for (var n = startFrom; n < argvalue.length; n++) { + if (validChars.indexOf(argvalue.substring(n, n+1)) == -1) return false; + } + return true; + }]]> + + + + + + + + + + + = 0) { + value = field.options[si].value; + } + } else { + value = field.value; + } + + if (value.length > 0) { + // remove '.' before checking digits + var tempArray = value.split('.'); + var joinedString= tempArray.join(''); + + if (!isAllDigits(joinedString)) { + bValid = false; + if (i == 0) { + focusField = field; + } + fields[i++] = oFloat[x][1]; + + } else { + var iValue = parseFloat(value); + if (isNaN(iValue)) { + if (i == 0) { + focusField = field; + } + fields[i++] = oFloat[x][1]; + bValid = false; + } + } + } + } + } + if (fields.length > 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return bValid; + }]]> + + + + + + + + + + + 0) && + (datePattern.length > 0)) { + var MONTH = "MM"; + var DAY = "dd"; + var YEAR = "yyyy"; + var orderMonth = datePattern.indexOf(MONTH); + var orderDay = datePattern.indexOf(DAY); + var orderYear = datePattern.indexOf(YEAR); + if ((orderDay < orderYear && orderDay > orderMonth)) { + var iDelim1 = orderMonth + MONTH.length; + var iDelim2 = orderDay + DAY.length; + var delim1 = datePattern.substring(iDelim1, iDelim1 + 1); + var delim2 = datePattern.substring(iDelim2, iDelim2 + 1); + if (iDelim1 == orderDay && iDelim2 == orderYear) { + dateRegexp = new RegExp("^(\\d{2})(\\d{2})(\\d{4})$"); + } else if (iDelim1 == orderDay) { + dateRegexp = new RegExp("^(\\d{2})(\\d{2})[" + delim2 + "](\\d{4})$"); + } else if (iDelim2 == orderYear) { + dateRegexp = new RegExp("^(\\d{2})[" + delim1 + "](\\d{2})(\\d{4})$"); + } else { + dateRegexp = new RegExp("^(\\d{2})[" + delim1 + "](\\d{2})[" + delim2 + "](\\d{4})$"); + } + var matched = dateRegexp.exec(value); + if(matched != null) { + if (!isValidDate(matched[2], matched[1], matched[3])) { + if (i == 0) { + focusField = form[oDate[x][0]]; + } + fields[i++] = oDate[x][1]; + bValid = false; + } + } else { + if (i == 0) { + focusField = form[oDate[x][0]]; + } + fields[i++] = oDate[x][1]; + bValid = false; + } + } else if ((orderMonth < orderYear && orderMonth > orderDay)) { + var iDelim1 = orderDay + DAY.length; + var iDelim2 = orderMonth + MONTH.length; + var delim1 = datePattern.substring(iDelim1, iDelim1 + 1); + var delim2 = datePattern.substring(iDelim2, iDelim2 + 1); + if (iDelim1 == orderMonth && iDelim2 == orderYear) { + dateRegexp = new RegExp("^(\\d{2})(\\d{2})(\\d{4})$"); + } else if (iDelim1 == orderMonth) { + dateRegexp = new RegExp("^(\\d{2})(\\d{2})[" + delim2 + "](\\d{4})$"); + } else if (iDelim2 == orderYear) { + dateRegexp = new RegExp("^(\\d{2})[" + delim1 + "](\\d{2})(\\d{4})$"); + } else { + dateRegexp = new RegExp("^(\\d{2})[" + delim1 + "](\\d{2})[" + delim2 + "](\\d{4})$"); + } + var matched = dateRegexp.exec(value); + if(matched != null) { + if (!isValidDate(matched[1], matched[2], matched[3])) { + if (i == 0) { + focusField = form[oDate[x][0]]; + } + fields[i++] = oDate[x][1]; + bValid = false; + } + } else { + if (i == 0) { + focusField = form[oDate[x][0]]; + } + fields[i++] = oDate[x][1]; + bValid = false; + } + } else if ((orderMonth > orderYear && orderMonth < orderDay)) { + var iDelim1 = orderYear + YEAR.length; + var iDelim2 = orderMonth + MONTH.length; + var delim1 = datePattern.substring(iDelim1, iDelim1 + 1); + var delim2 = datePattern.substring(iDelim2, iDelim2 + 1); + if (iDelim1 == orderMonth && iDelim2 == orderDay) { + dateRegexp = new RegExp("^(\\d{4})(\\d{2})(\\d{2})$"); + } else if (iDelim1 == orderMonth) { + dateRegexp = new RegExp("^(\\d{4})(\\d{2})[" + delim2 + "](\\d{2})$"); + } else if (iDelim2 == orderDay) { + dateRegexp = new RegExp("^(\\d{4})[" + delim1 + "](\\d{2})(\\d{2})$"); + } else { + dateRegexp = new RegExp("^(\\d{4})[" + delim1 + "](\\d{2})[" + delim2 + "](\\d{2})$"); + } + var matched = dateRegexp.exec(value); + if(matched != null) { + if (!isValidDate(matched[3], matched[2], matched[1])) { + if (i == 0) { + focusField = form[oDate[x][0]]; + } + fields[i++] = oDate[x][1]; + bValid = false; + } + } else { + if (i == 0) { + focusField = form[oDate[x][0]]; + } + fields[i++] = oDate[x][1]; + bValid = false; + } + } else { + if (i == 0) { + focusField = form[oDate[x][0]]; + } + fields[i++] = oDate[x][1]; + bValid = false; + } + } + } + if (fields.length > 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return bValid; + } + + function isValidDate(day, month, year) { + if (month < 1 || month > 12) { + return false; + } + if (day < 1 || day > 31) { + return false; + } + if ((month == 4 || month == 6 || month == 9 || month == 11) && + (day == 31)) { + return false; + } + if (month == 2) { + var leap = (year % 4 == 0 && + (year % 100 != 0 || year % 400 == 0)); + if (day>29 || (day == 29 && !leap)) { + return false; + } + } + return true; + }]]> + + + + + + + + + + + + + + + 0)) { + + var iMin = parseInt(oRange[x][2]("min")); + var iMax = parseInt(oRange[x][2]("max")); + var iValue = parseInt(field.value); + if (!(iValue >= iMin && iValue <= iMax)) { + if (i == 0) { + focusField = field; + } + fields[i++] = oRange[x][1]; + isValid = false; + } + } + } + if (fields.length > 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return isValid; + }]]> + + + + + + + 0)) { + + var fMin = parseFloat(oRange[x][2]("min")); + var fMax = parseFloat(oRange[x][2]("max")); + var fValue = parseFloat(field.value); + if (!(fValue >= fMin && fValue <= fMax)) { + if (i == 0) { + focusField = field; + } + fields[i++] = oRange[x][1]; + isValid = false; + } + } + } + if (fields.length > 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return isValid; + }]]> + + + + + + + 0)) { + if (!luhnCheck(form[oCreditCard[x][0]].value)) { + if (i == 0) { + focusField = form[oCreditCard[x][0]]; + } + fields[i++] = oCreditCard[x][1]; + bValid = false; + } + } + } + if (fields.length > 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return bValid; + } + + /** + * Reference: http://www.ling.nwu.edu/~sburke/pub/luhn_lib.pl + */ + function luhnCheck(cardNumber) { + if (isLuhnNum(cardNumber)) { + var no_digit = cardNumber.length; + var oddoeven = no_digit & 1; + var sum = 0; + for (var count = 0; count < no_digit; count++) { + var digit = parseInt(cardNumber.charAt(count)); + if (!((count & 1) ^ oddoeven)) { + digit *= 2; + if (digit > 9) digit -= 9; + }; + sum += digit; + }; + if (sum == 0) return false; + if (sum % 10 == 0) return true; + }; + return false; + } + + function isLuhnNum(argvalue) { + argvalue = argvalue.toString(); + if (argvalue.length == 0) { + return false; + } + for (var n = 0; n < argvalue.length; n++) { + if ((argvalue.substring(n, n+1) < "0") || + (argvalue.substring(n,n+1) > "9")) { + return false; + } + } + return true; + }]]> + + + + + + + + 0)) { + if (!checkEmail(form[oEmail[x][0]].value)) { + if (i == 0) { + focusField = form[oEmail[x][0]]; + } + fields[i++] = oEmail[x][1]; + bValid = false; + } + } + } + if (fields.length > 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return bValid; + } + + /** + * Reference: Sandeep V. Tamhankar (stamhankar@hotmail.com), + * http://javascript.internet.com + */ + function checkEmail(emailStr) { + if (emailStr.length == 0) { + return true; + } + var emailPat=/^(.+)@(.+)$/; + var specialChars="\\(\\)<>@,;:\\\\\\\"\\.\\[\\]"; + var validChars="\[^\\s" + specialChars + "\]"; + var quotedUser="(\"[^\"]*\")"; + var ipDomainPat=/^(\d{1,3})[.](\d{1,3})[.](\d{1,3})[.](\d{1,3})$/; + var atom=validChars + '+'; + var word="(" + atom + "|" + quotedUser + ")"; + var userPat=new RegExp("^" + word + "(\\." + word + ")*$"); + var domainPat=new RegExp("^" + atom + "(\\." + atom + ")*$"); + var matchArray=emailStr.match(emailPat); + if (matchArray == null) { + return false; + } + var user=matchArray[1]; + var domain=matchArray[2]; + if (user.match(userPat) == null) { + return false; + } + var IPArray = domain.match(ipDomainPat); + if (IPArray != null) { + for (var i = 1; i <= 4; i++) { + if (IPArray[i] > 255) { + return false; + } + } + return true; + } + var domainArray=domain.match(domainPat); + if (domainArray == null) { + return false; + } + var atomPat=new RegExp(atom,"g"); + var domArr=domain.match(atomPat); + var len=domArr.length; + if ((domArr[domArr.length-1].length < 2) || + (domArr[domArr.length-1].length > 3)) { + return false; + } + if (len < 2) { + return false; + } + return true; + }]]> + + + + + + + 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return bValid; + } + + /** + * Reference: JS Guide + * http://jsguide.net/ver2/articles/frame.php?artnum=002 + */ + function checkIhIdNum(ihidnum){ + + fmt = /^\d{6}[1234]\d{6}$/; + if(!fmt.test(ihidnum)){ + return false; + } + + birthYear = (ihidnum.charAt(7) <= "2") ? "19" : "20"; + birthYear += ihidnum.substr(0, 2); + birthMonth = ihidnum.substr(2, 2) - 1; + birthDate = ihidnum.substr(4, 2); + birth = new Date(birthYear, birthMonth, birthDate); + + if( birth.getYear() % 100 != ihidnum.substr(0, 2) || + birth.getMonth() != birthMonth || + birth.getDate() != birthDate) { + return false; + } + + var arrDivide = [2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5]; + var checkdigit = 0; + for(var i=0;i + + + + 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return bValid; + } + + function checkKorean(koreanStr){ + for(var i=0;i + + + + + + 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return bValid; + } + + function checkPassword1(pwd) { + p_pass = pwd.value; + + if (pwd.value.length < 8 || pwd.value.length > 20 ){ + + pwd.value =""; + pwd.focus(); + return false; + } + return pwd; + } + ]]> + + + + + 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return bValid; + } + + function checkPassword2(pwd) { + var str = pwd.value; + for (var i=0; i < str .length; i++) { + ch_char = str .charAt(i); + ch = ch_char.charCodeAt(); + if( (ch >= 33 && ch <= 47) || (ch >= 58 && ch <= 64) || (ch >= 91 && ch <= 96) || (ch >= 123 && ch <= 126) ) { + return false; + } + } + return pwd; + } + ]]> + + + + + 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return bValid; + } + + function checkPassword3(pwd) { + p_pass = pwd.value; + var cnt=0,cnt2=1,cnt3=1; + var temp=""; + + for(i=0;i < p_pass.length;i++){ + temp_pass1 = p_pass.charAt(i); + next_pass = (parseInt(temp_pass1.charCodeAt(0)))+1; + temp_p = p_pass.charAt(i+1); + temp_pass2 = (parseInt(temp_p.charCodeAt(0))); + if (temp_pass2 == next_pass) + cnt2 = cnt2 + 1; + else + cnt2 = 1; + if (temp_pass1 == temp_p) + cnt3 = cnt3 + 1; + else + cnt3 = 1; + if (cnt2 > 3) break; + if (cnt3 > 3) break; + } + if (cnt2 > 3){ + pwd.value =""; + pwd.focus(); + return false; + } + return pwd; + } + ]]> + + + + + 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return bValid; + } + + function checkPassword4(pwd) { + p_pass = pwd.value; + var cnt=0,cnt2=1,cnt3=1; + var temp=""; + + for(i=0;i < p_pass.length;i++){ + temp_pass1 = p_pass.charAt(i); + next_pass = (parseInt(temp_pass1.charCodeAt(0)))+1; + temp_p = p_pass.charAt(i+1); + temp_pass2 = (parseInt(temp_p.charCodeAt(0))); + if (temp_pass2 == next_pass) + cnt2 = cnt2 + 1; + else + cnt2 = 1; + if (temp_pass1 == temp_p) + cnt3 = cnt3 + 1; + else + cnt3 = 1; + if (cnt2 > 3) break; + if (cnt3 > 3) break; + } + if (cnt3 > 3){ + pwd.value =""; + pwd.focus(); + return false; + } + return pwd; + } + ]]> + + + + = 0) { + value = field.options[si].value; + } + } else { + value = field.value; + } + + if (value.length > 0) { + + if (!isOK(value)) { + bValid = false; + if (i == 0) { + focusField = field; + } + fields[i++] = oInteger[x][1]; + + } else { + var iValue = parseInt(value); + if (isNaN(iValue) || !(iValue >= -2147483648 && iValue <= 2147483647)) { + if (i == 0) { + focusField = field; + } + fields[i++] = oInteger[x][1]; + bValid = false; + } + } + } + } + } + if (fields.length > 0) { + focusField.focus(); + alert(fields.join('\n')); + } + return bValid; + } + + function isOK(vv){ + if (vv >0) return true; + else return false; + } + ]]> + + + + + diff --git a/mens-core/src/main/resources/spy.properties b/mens-core/src/main/resources/spy.properties new file mode 100644 index 0000000..22b7f15 --- /dev/null +++ b/mens-core/src/main/resources/spy.properties @@ -0,0 +1,233 @@ +#driverlist +#appender +#logMessageFormat +#filter +#exclude + + +################################################################# +# P6Spy Options File # +# See documentation for detailed instructions # +# http://p6spy.github.io/p6spy/2.0/configandusage.html # +################################################################# +################################################################# +# MODULES # +# # +# Module list adapts the modular functionality of P6Spy. # +# Only modules listed are active. # +# (default is com.p6spy.engine.logging.P6LogFactory and # +# com.p6spy.engine.spy.P6SpyFactory) # +# Please note that the core module (P6SpyFactory) can't be # +# deactivated. # +# Unlike the other properties, activation of the changes on # +# this one requires reload. # +################################################################# +#modulelist=com.p6spy.engine.spy.P6SpyFactory,com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory +################################################################ +# CORE (P6SPY) PROPERTIES # +################################################################ +# A comma separated list of JDBC drivers to load and register. +# (default is empty) +# +# Note: This is normally only needed when using P6Spy in an +# application server environment with a JNDI data source or when +# using a JDBC driver that does not implement the JDBC 4.0 API +# (specifically automatic registration). +#driverlist=org.mariadb.jdbc.Driver,com.p6spy.engine.spy.P6SpyDriver +driverlist=org.mariadb.jdbc.Driver +# for flushing per statement +# (default is false) +#autoflush=false +# sets the date format using Java's SimpleDateFormat routine. +# In case property is not set, milliseconds since 1.1.1970 (unix time) is used (default is empty) +#dateformat= +# prints a stack trace for every statement logged +#stacktrace=false +# if stacktrace=true, specifies the stack trace to print +#stacktraceclass= +# determines if property file should be reloaded +# Please note: reload means forgetting all the previously set +# settings (even those set during runtime - via JMX) +# and starting with the clean table +# (default is false) +#reloadproperties=false +# determines how often should be reloaded in seconds +# (default is 60) +#reloadpropertiesinterval=60 +# specifies the appender to use for logging +# Please note: reload means forgetting all the previously set +# settings (even those set during runtime - via JMX) +# and starting with the clean table +# (only the properties read from the configuration file) +# (default is com.p6spy.engine.spy.appender.FileLogger) +#appender=com.p6spy.engine.spy.appender.Slf4JLogger +#appender=com.p6spy.engine.spy.appender.StdoutLogger +#appender=com.p6spy.engine.spy.appender.FileLogger +# name of logfile to use, note Windows users should make sure to use forward slashes in their pathname (e:/test/spy.log) +# (used for com.p6spy.engine.spy.appender.FileLogger only) +# (default is spy.log) +#logfile=spy.log +# append to the p6spy log file. if this is set to false the +# log file is truncated every time. (file logger only) +# (default is true) +#append=true +# class to use for formatting log messages (default is: com.p6spy.engine.spy.appender.SingleLineFormat) +#logMessageFormat=com.p6spy.engine.spy.appender.SingleLineFormat +#logMessageFormat=kr.xit.core.spring.config.support.P6SpySqlMultilineFormat +logMessageFormat=com.p6spy.engine.spy.appender.MultiLineFormat +# Custom log message format used ONLY IF logMessageFormat is set to com.p6spy.engine.spy.appender.CustomLineFormat +# default is %(currentTime)|%(executionTime)|%(category)|connection%(connectionId)|%(sqlSingleLine) +# Available placeholders are: +# %(connectionId) the id of the connection +# %(currentTime) the current time expressing in milliseconds +# %(executionTime) the time in milliseconds that the operation took to complete +# %(category) the category of the operation +# %(effectiveSql) the SQL statement as submitted to the driver +# %(effectiveSqlSingleLine) the SQL statement as submitted to the driver, with all new lines removed +# %(sql) the SQL statement with all bind variables replaced with actual values +# %(sqlSingleLine) the SQL statement with all bind variables replaced with actual values, with all new lines removed +#customLogMessageFormat=%(currentTime)|%(executionTime)|%(category)|connection%(connectionId)|%(sqlSingleLine) +# format that is used for logging of the java.util.Date implementations (has to be compatible with java.text.SimpleDateFormat) +# (default is yyyy-MM-dd'T'HH:mm:ss.SSSZ) +#databaseDialectDateFormat=yyyy-MM-dd'T'HH:mm:ss.SSSZ +# format that is used for logging of the java.sql.Timestamp implementations (has to be compatible with java.text.SimpleDateFormat) +# (default is yyyy-MM-dd'T'HH:mm:ss.SSSZ) +#databaseDialectTimestampFormat=yyyy-MM-dd'T'HH:mm:ss.SSSZ +# format that is used for logging booleans, possible values: boolean, numeric +# (default is boolean) +#databaseDialectBooleanFormat=boolean +# Specifies the format for logging binary data. Not applicable if excludebinary is true. +# (default is com.p6spy.engine.logging.format.HexEncodedBinaryFormat) +#databaseDialectBinaryFormat=com.p6spy.engine.logging.format.PostgreSQLBinaryFormat +#databaseDialectBinaryFormat=com.p6spy.engine.logging.format.MySQLBinaryFormat +#databaseDialectBinaryFormat=com.p6spy.engine.logging.format.HexEncodedBinaryFormat +# whether to expose options via JMX or not +# (default is true) +#jmx=true +# if exposing options via jmx (see option: jmx), what should be the prefix used? +# jmx naming pattern constructed is: com.p6spy(.)?:name= +# please note, if there is already such a name in use it would be unregistered first (the last registered wins) +# (default is none) +#jmxPrefix= +# if set to true, the execution time will be measured in nanoseconds as opposed to milliseconds +# (default is false) +#useNanoTime=false +################################################################# +# DataSource replacement # +# # +# Replace the real DataSource class in your application server # +# configuration with the name com.p6spy.engine.spy.P6DataSource # +# (that provides also connection pooling and xa support). # +# then add the JNDI name and class name of the real # +# DataSource here # +# # +# Values set in this item cannot be reloaded using the # +# reloadproperties variable. Once it is loaded, it remains # +# in memory until the application is restarted. # +# # +################################################################# +#realdatasource=/RealMySqlDS +#realdatasourceclass=com.mysql.jdbc.jdbc2.optional.MysqlDataSource +################################################################# +# DataSource properties # +# # +# If you are using the DataSource support to intercept calls # +# to a DataSource that requires properties for proper setup, # +# define those properties here. Use name value pairs, separate # +# the name and value with a semicolon, and separate the # +# pairs with commas. # +# # +# The example shown here is for mysql # +# # +################################################################# +#realdatasourceproperties=port;3306,serverName;myhost,databaseName;jbossdb,foo;bar +################################################################# +# JNDI DataSource lookup # +# # +# If you are using the DataSource support outside of an app # +# server, you will probably need to define the JNDI Context # +# environment. # +# # +# If the P6Spy code will be executing inside an app server then # +# do not use these properties, and the DataSource lookup will # +# use the naming context defined by the app server. # +# # +# The two standard elements of the naming environment are # +# jndicontextfactory and jndicontextproviderurl. If you need # +# additional elements, use the jndicontextcustom property. # +# You can define multiple properties in jndicontextcustom, # +# in name value pairs. Separate the name and value with a # +# semicolon, and separate the pairs with commas. # +# # +# The example shown here is for a standalone program running on # +# a machine that is also running JBoss, so the JNDI context # +# is configured for JBoss (3.0.4). # +# # +# (by default all these are empty) # +################################################################# +#jndicontextfactory=org.jnp.interfaces.NamingContextFactory +#jndicontextproviderurl=localhost:1099 +#jndicontextcustom=java.naming.factory.url.pkgs;org.jboss.naming:org.jnp.interfaces +#jndicontextfactory=com.ibm.websphere.naming.WsnInitialContextFactory +#jndicontextproviderurl=iiop://localhost:900 +################################################################ +# P6 LOGGING SPECIFIC PROPERTIES # +################################################################ +# filter what is logged +# please note this is a precondition for usage of: include/exclude/sqlexpression +# (default is false) +# ?????? SQL ?? ?? ??? ?? filter ?? ?? +filter=true +# comma separated list of strings to include +# please note that special characters escaping (used in java) has to be done for the provided regular expression +# (default is empty) +#include= +# comma separated list of strings to exclude +# (default is empty) +# ?????? SQL ?? ?? ?? +exclude=BATCH_JOB_SEQ,BATCH_JOB_INSTANCE,BATCH_JOB_EXECUTION.*,BATCH_STEP_EXECUTION.* +# sql expression to evaluate if using regex +# please note that special characters escaping (used in java) has to be done for the provided regular expression +# (default is empty) +#sqlexpression= +#list of categories to exclude: error, info, batch, debug, statement, +#commit, rollback, result and resultset are valid values +# (default is info,debug,result,resultset,batch) +#excludecategories=info,debug,result,resultset,batch +#whether the binary values (passed to DB or retrieved ones) should be logged with placeholder: [binary] or not. +# (default is false) +#excludebinary=false +# Execution threshold applies to the standard logging of P6Spy. +# While the standard logging logs out every statement +# regardless of its execution time, this feature puts a time +# condition on that logging. Only statements that have taken +# longer than the time specified (in milliseconds) will be +# logged. This way it is possible to see only statements that +# have exceeded some high water mark. +# This time is reloadable. +# +# executionThreshold=integer time (milliseconds) +# (default is 0) +#executionThreshold= +################################################################ +# P6 OUTAGE SPECIFIC PROPERTIES # +################################################################ +# Outage Detection +# +# This feature detects long-running statements that may be indicative of +# a database outage problem. If this feature is turned on, it will log any +# statement that surpasses the configurable time boundary during its execution. +# When this feature is enabled, no other statements are logged except the long +# running statements. The interval property is the boundary time set in seconds. +# For example, if this is set to 2, then any statement requiring at least 2 +# seconds will be logged. Note that the same statement will continue to be logged +# for as long as it executes. So if the interval is set to 2, and the query takes +# 11 seconds, it will be logged 5 times (at the 2, 4, 6, 8, 10 second intervals). +# +# outagedetection=true|false +# outagedetectioninterval=integer time (seconds) +# +# (default is false) +#outagedetection=false +# (default is 60) +#outagedetectioninterval=30 diff --git a/mens-core/src/main/webapp/META-INF/MANIFEST.MF b/mens-core/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 0000000..254272e --- /dev/null +++ b/mens-core/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..8d937f4 --- /dev/null +++ b/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..c4586b5 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..fd56ac3 --- /dev/null +++ b/pom.xml @@ -0,0 +1,210 @@ + + + 4.0.0 + + kr.xit + mens-parent + 1.0.0 + pom + Mobile Electronic Notice System Root pom + + + xit-init + mens-core + mens-api + mens-batch + + + + 17 + 17 + UTF-8 + 5.3.20 + 4.1.0 + 1.18.28 + 1.2.11 + + 2.6.0 + 7.3.2 + 9.0.10 + + + + + mvn2s + https://repo1.maven.org/maven2/ + + true + + + true + + + + egovframe2 + https://maven.egovframe.go.kr/maven/ + + true + + + false + + + + local-repository + local repository + file://${project.basedir}/lib + + + maven-public + https://nas.xit.co.kr:8888/repository/maven-public + + true + + + false + + + + + + + + org.springframework.boot + spring-boot-starter-parent + 2.7.14 + + + + + maven-snapshot + https://nas.xit.co.kr:8888/repository/maven-snapshots/ + + true + always + + + false + + + + + maven-release + https://nas.xit.co.kr:8888/repository/maven-releases/ + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + ${maven.compiler.source} + ${maven.compiler.source} + UTF-8 + 1024m + + **/BouncyUtils.java + + + + + + org.apache.maven.plugins + maven-source-plugin + + + + attach-sources + package + + jar + + + + **/BouncyUtils.java + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + openframework.css + true + false + ko_KR + UTF-8 + private + true + UTF-8 + UTF-8 + + ${project.build.directory}/javadoc + ${project.build.directory} + + none + xit.core.init.custom.* + ${java.home}/bin/javadoc + + + + + + + + + + local + + local + + + true + + + + + + dev + + dev + + + false + + + + + + prod + + prod + + + false + + + + diff --git a/settings.xml b/settings.xml new file mode 100644 index 0000000..8078764 --- /dev/null +++ b/settings.xml @@ -0,0 +1,110 @@ + + + + C:/repo + + + maven-snapshot + xit-repo-user + xituser!@ + + + maven-release + xit-repo-user + xituser!@ + + + maven-3rdparty + xit-repo-user + xituser!@ + + + + + + + + + + + + + + nexus + + + + + + maven-snapshot + http://211.119.124.110:8081/repository/maven-snapshots/ + + false + + + true + + + + maven-releases + http://211.119.124.110:8081/repository/maven-releases/ + + true + + + false + + + + maven-3rdparty + http://211.119.124.110:8081/repository/maven-3rdparty/ + + true + + + false + + + + + + + \ No newline at end of file diff --git a/shell/deployEnsApi.sh b/shell/deployEnsApi.sh new file mode 100644 index 0000000..5295ad7 --- /dev/null +++ b/shell/deployEnsApi.sh @@ -0,0 +1,3 @@ +#!/bin/sh +mv /applications/mens/mens-api.jar /applications/mens/mens-api.jar_backup +cp /var/lib/jenkins/workspace/MENS-API/mens-api/target/mens-api.jar /applications/mens/ens-api.jar diff --git a/shell/startEnsApi.sh b/shell/startEnsApi.sh new file mode 100644 index 0000000..37c6bdd --- /dev/null +++ b/shell/startEnsApi.sh @@ -0,0 +1,7 @@ +#!/bin/sh +echo "======================" +echo "ens-api deamon start" +echo "======================" +JAVA_HOME=/lib/jvm/java-17-openjdk +export JAVA_HOME +nohup $JAVA_HOME/bin/java -Dspring.profiles.active=dev -jar /applications/mens/mens-api.jar > /dev/null & diff --git a/shell/stopEnsApi.sh b/shell/stopEnsApi.sh new file mode 100644 index 0000000..c08a9c3 --- /dev/null +++ b/shell/stopEnsApi.sh @@ -0,0 +1,13 @@ +#!/bin/sh +PID=`ps -ef | grep java | grep ens-api- | awk '{print $2}'` +echo "Process ID: $PID" +if [ -z $PID ]; then + echo "No process is running" +else + echo "Kill process" + kill $PID + echo "=========================" + echo "mens-api deamon stop" + echo "=========================" +fi + diff --git a/spy.log b/spy.log new file mode 100644 index 0000000..f5bc1b1 --- /dev/null +++ b/spy.log @@ -0,0 +1,9 @@ +#1692578675897 | took 1ms | rollback | connection 59| url jdbc:oracle:thin:@211.119.124.115:1521:XITSMS + +; +#1692578675898 | took 0ms | rollback | connection 58| url jdbc:mariadb://211.119.124.9:4407/ens?user=root&password=***&rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf-8&autoReconnect=true + +; +#1692578675899 | took 0ms | rollback | connection 56| url jdbc:mariadb://211.119.124.9:4407/ens?user=root&password=***&rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf-8&autoReconnect=true + +; \ No newline at end of file diff --git a/xit-init/.gitignore b/xit-init/.gitignore new file mode 100644 index 0000000..b56f1dd --- /dev/null +++ b/xit-init/.gitignore @@ -0,0 +1,33 @@ +README.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/xit-init/pom.xml b/xit-init/pom.xml new file mode 100644 index 0000000..e3095d1 --- /dev/null +++ b/xit-init/pom.xml @@ -0,0 +1,150 @@ + + + 4.0.0 + + + kr.xit + mens-parent + 1.0.0 + + + xit-init + 1.0.0 + jar + xit-init + xit-init + + + + org.springframework.boot + spring-boot-starter + + + + + org.springframework.boot + spring-boot-properties-migrator + runtime + + + + + org.bouncycastle + bcpkix-jdk15on + 1.70 + + + + com.guardsquare + proguard-base + ${proguard-base-version} + runtime + + + com.guardsquare + proguard-core + ${proguard-core-version} + runtime + + + org.projectlombok + lombok + ${lombok.version} + true + + + + + install + ${basedir}/target + ${project.name} + + + + com.github.wvengen + proguard-maven-plugin + ${proguard-plugin-version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + + com.github.wvengen + proguard-maven-plugin + + + package + + proguard + + + + + ${project.build.finalName}.jar + ${project.build.finalName}.jar + true + false + proguard.cfg + true + true + + + + + + + + + + + + + + + + + ${java.home}/jmods/java.base.jmod + + + + + + + + + + + + + + + diff --git a/xit-init/src/main/java/xit/core/init/custom/AppInitHelper.java b/xit-init/src/main/java/xit/core/init/custom/AppInitHelper.java new file mode 100644 index 0000000..8ecb021 --- /dev/null +++ b/xit-init/src/main/java/xit/core/init/custom/AppInitHelper.java @@ -0,0 +1,161 @@ +package xit.core.init.custom; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertiesPropertySource; +import xit.core.init.custom.bouncy.BouncyDecUtils; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Properties; +import java.util.stream.Collectors; + +/** + *
    + * description :
    + *
    + * packageName : kr.xit.core.spring.config.custom
    + * fileName    : AppInitHelper
    + * author      : limju
    + * date        : 2023-07-24
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-07-24    limju       최초 생성
    + *
    + * 
    + */ +@Slf4j +public class AppInitHelper { + private ConfigurableApplicationContext applicationContext; + + /** + * Instantiates a new App init helper. + * + * @param applicationContext the application context + */ + public AppInitHelper(ConfigurableApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + /** + * Parse init. + * + * @throws Exception the exception + */ + public void parseInit() throws Exception { + init(); + } + + private void init() throws Exception { + ConfigurableEnvironment env = applicationContext.getEnvironment(); + Properties props = new Properties(); + + String p = env.getProperty("app.license.path"); + String k = env.getProperty("app.license.key"); + // 10 + 17 + ~ + String license = env.getProperty("app.license.data1"); + long curDate = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()/1000; + String decLicense = BouncyDecUtils.decode(p+"private_key.pem", k, license); + + if(Long.parseLong(decLicense.substring(0,10)) < curDate){ + log.error("유효기간이 만료 되었습니다."); + System.exit(0); + } + + try { + if (!getMacAddress().contains(decLicense.substring(10, 27).toUpperCase())) { + log.info("Mac Address :: {}", getMacAddress()); + log.info("MAC Data :: {}", decLicense.substring(10, 27).toUpperCase()); + log.error("인증된 서버가 아닙니다."); + System.exit(0); + } + + if (!getIpAddress().contains(decLicense.substring(27))) { + log.error("IP :: {}", getIpAddress()); + log.error("IP Data :: {}", decLicense.substring(27)); + log.error("인증된 서버가 아닙니다."); + System.exit(0); + } + } catch (SocketException e) { + log.error(e.getLocalizedMessage()); + System.exit(0); + } + + String db = BouncyDecUtils.decode(p+"private_key.pem", k, env.getProperty("app.license.data2")); + String[] arrDb = db.split(";"); + props.put("spring.datasource.hikari.maria.driver-class-name", arrDb[0]); + props.put("spring.datasource.hikari.maria.read-only", arrDb[4]); + props.put("spring.datasource.hikari.maria.jdbc-url", arrDb[3]); + props.put("spring.datasource.hikari.maria.username", arrDb[1]); + props.put("spring.datasource.hikari.maria.password", arrDb[2]); + db = null; + arrDb = null; + + db = BouncyDecUtils.decode(p+"private_key.pem", k, env.getProperty("app.license.data3")); + arrDb = db.split(";"); + props.put("spring.datasource.hikari.oracle.driver-class-name", arrDb[0]); + props.put("spring.datasource.hikari.oracle.read-only", arrDb[4]); + props.put("spring.datasource.hikari.oracle.jdbc-url", arrDb[3]); + props.put("spring.datasource.hikari.oracle.username", arrDb[1]); + props.put("spring.datasource.hikari.oracle.password", arrDb[2]); + + env.getPropertySources().addFirst(new PropertiesPropertySource("decodeProps", props)); + } + + private List getMacAddress() throws SocketException { + List macs = new ArrayList<>(); + + try { + Enumeration e = NetworkInterface.getNetworkInterfaces(); + + while (e.hasMoreElements()) { + NetworkInterface network = e.nextElement(); + + byte[] bmac = network.getHardwareAddress(); + if (bmac != null) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < bmac.length; i++) { + sb.append(String.format("%02X%s", bmac[i], (i < bmac.length - 1) ? "-" : "")); + } + + if (sb.toString().isEmpty() == false) { + macs.add(sb.toString().toUpperCase()); + } + } + } + } catch (SocketException e){ + throw e; + } + return macs; + } + + private List getIpAddress() throws SocketException { + List ips = new ArrayList<>(); + + try { + Enumeration e = NetworkInterface.getNetworkInterfaces(); + + while (e.hasMoreElements()) { + NetworkInterface ni = e.nextElement(); + Enumeration kk = ni.getInetAddresses(); + + while (kk.hasMoreElements()) { + InetAddress inetAddress = kk.nextElement(); + if(inetAddress.getAddress().length == 4) ips.add(inetAddress.getHostAddress().toString()); + } + } + return ips.stream().distinct().collect(Collectors.toList()); + } catch (SocketException e) { + throw e; + } + } + +} diff --git a/xit-init/src/main/java/xit/core/init/custom/CustomContextInitalizer.java b/xit-init/src/main/java/xit/core/init/custom/CustomContextInitalizer.java new file mode 100644 index 0000000..2a7f4f6 --- /dev/null +++ b/xit-init/src/main/java/xit/core/init/custom/CustomContextInitalizer.java @@ -0,0 +1,45 @@ +package xit.core.init.custom; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; + +import java.util.Arrays; + +/** + *
    + * description : license 암호호 적용
    + *               -> local 환경이 아닌 경우만 적용 하도록
    + *
    + * packageName : kr.xit.core.spring.custom
    + * fileName    : CustomContextInitalizer
    + * author      : limju
    + * date        : 2023-07-21
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-07-21    limju       최초 생성
    + *
    + * 
    + */ +@Slf4j +public class CustomContextInitalizer implements ApplicationContextInitializer { + /** + * Initialize. + * + * @param applicationContext the application context + */ + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + if(Arrays.asList(applicationContext.getEnvironment().getActiveProfiles()).contains("dev")) { + try { + new AppInitHelper(applicationContext).parseInit(); + } catch (Exception e) { + log.error(e.getMessage()); + final String line = "===================================================================="; + log.error("{}\n{}\n {}", line, " >>>>>>>>>> 인증 되지 않은 서버 입니다(서버 인증 필요) <<<<<<<<<<", line); + System.exit(0); + } + } + } +} diff --git a/xit-init/src/main/java/xit/core/init/custom/bouncy/BouncyDecUtils.java b/xit-init/src/main/java/xit/core/init/custom/bouncy/BouncyDecUtils.java new file mode 100644 index 0000000..f7eda79 --- /dev/null +++ b/xit-init/src/main/java/xit/core/init/custom/bouncy/BouncyDecUtils.java @@ -0,0 +1,154 @@ +package xit.core.init.custom.bouncy; + +import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.engines.SEEDEngine; +import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; + +import javax.crypto.Cipher; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; + +/** + *
    + * description : 암호화(license) Utils
    + *              -> bouncycastle 암호화 파일 복호화
    + * packageName : kr.xit.core.spring.config.custom.bouncy
    + * fileName    : BouncyDecUtils
    + * author      : limju
    + * date        : 2023-07-20
    + * ======================================================================
    + * 변경일         변경자        변경 내용
    + * ----------------------------------------------------------------------
    + * 2023-07-20    limju       최초 생성
    + *
    + * 
    + */ +public class BouncyDecUtils { + private static final String KEY_ALG = "RSA"; + + /** + * 암호화된 문자열 -> decoding + * + * @param privateKeyPath PrivateKey file path + * @param encodeData decoding 대상 문자열 + * @return 암호해제된 문자열 + * @throws Exception the exception + */ + public static String decode(String privateKeyPath, String encodeData) throws Exception { + + try { + String privateKeyPem = new String(Files.readAllBytes(Paths.get(privateKeyPath)), StandardCharsets.UTF_8); + PrivateKey privateKey = pemToPrivateKey(privateKeyPem); + + Cipher cipher = Cipher.getInstance(KEY_ALG); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + byte[] decBytes = cipher.doFinal(Base64.getDecoder().decode(encodeData.getBytes())); + + return new String(decBytes, StandardCharsets.UTF_8); + }catch (Exception e){ + throw e; + } + } + + /** + * 암호화된 문자열 -> decoding + * + * @param privateKeyPath PrivateKey file path + * @param key the key + * @param encodeData decoding 대상 문자열 + * @return 암호해제된 문자열 + * @throws Exception the exception + */ + public static String decode(String privateKeyPath, String key, String encodeData) throws Exception { + + try { + byte[] encryptedFileBytes = getFileBytesFrom(privateKeyPath); + String privateKeyPem = new String(decrypt(key, encryptedFileBytes), StandardCharsets.UTF_8); + PrivateKey privateKey = pemToPrivateKey(privateKeyPem); + + Cipher cipher = Cipher.getInstance(KEY_ALG); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + byte[] decBytes = cipher.doFinal(Base64.getDecoder().decode(encodeData.getBytes())); + + return new String(decBytes, StandardCharsets.UTF_8); + }catch (Exception e){ + throw e; + } + } + + /** + * 파일 객체 복호롸(SEEDEngine 사용) + * + * @param key 128bits(String 16자리) + * @param cipherText file byte 객체 + * @return byte [ ] + */ + public static byte[] decrypt(String key, byte[] cipherText) { + byte[] keyBytes = key.getBytes(); + + BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new SEEDEngine()); + cipher.init(false, new KeyParameter(keyBytes)); + + return getBytes(cipherText, cipher); + } + + //---------------------------------------------------------------------------------- + + /** + * String PrivateKey -> PrivateKey + * - 파일에 저장한 키 read시 사용 + * @param privateKeyPem String + * @return PrivateKey + */ + private static PrivateKey pemToPrivateKey(String privateKeyPem) throws Exception { + PemReader pemReader = new PemReader(new StringReader(privateKeyPem)); + PemObject pemObject = pemReader.readPemObject(); + pemReader.close(); + + byte[] privateKeyBytes = pemObject.getContent(); + PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALG); + + return keyFactory.generatePrivate(privateKeySpec); + } + + private static byte[] getFileBytesFrom(String path) throws IOException { + + // 파일 객체 생성 + File file = new File(path); + byte[] fileBytes = new byte[(int)file.length()]; + + try(FileInputStream fis = new FileInputStream(file);){ + fis.read(fileBytes); + + } catch (IOException e) { + throw e; + } + return fileBytes; + } + + private static byte[] getBytes(byte[] targetData, BufferedBlockCipher cipher) { + byte[] outputData = new byte[cipher.getOutputSize(targetData.length)]; + + int tam = cipher.processBytes(targetData, 0, targetData.length, outputData, 0); + + try { + cipher.doFinal(outputData, tam); + } catch (Exception e) { + e.printStackTrace(); + } + return outputData; + } +}