diff --git a/build.gradle b/build.gradle index d6c4770..09af3be 100644 --- a/build.gradle +++ b/build.gradle @@ -21,10 +21,23 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-devtools' + implementation 'org.apache.tomcat.embed:tomcat-embed-jasper' + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + + + /* =================================================================================== */ + /* External Jar.. */ + /* =================================================================================== */ + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.google.code.gson:gson:2.9.0' } tasks.named('test') { diff --git a/libs/VNOInterop.jar b/libs/VNOInterop.jar new file mode 100644 index 0000000..d46f04b Binary files /dev/null and b/libs/VNOInterop.jar differ diff --git a/src/main/java/cokr/xit/ci/IndexController.java b/src/main/java/cokr/xit/ci/IndexController.java new file mode 100644 index 0000000..7efc53c --- /dev/null +++ b/src/main/java/cokr/xit/ci/IndexController.java @@ -0,0 +1,21 @@ +package cokr.xit.ci; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Slf4j +@Controller +@RequiredArgsConstructor +public class IndexController { + + @GetMapping("/") + public String index(){ + log.info("Welcome to the CI system"); + + return "index"; + } + + +} diff --git a/src/main/java/cokr/xit/ci/api/code/ErrCd.java b/src/main/java/cokr/xit/ci/api/code/ErrCd.java new file mode 100644 index 0000000..9906ad6 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/code/ErrCd.java @@ -0,0 +1,58 @@ +package cokr.xit.ci.api.code; + +public enum ErrCd { + + OK("정상") + + /* ======================================================================= + * 공통(4xx: Client, 5xx: Server, 6xx: LinkService, 9xx: Etc..) + ======================================================================= */ + //클라이언트 요청 오류 + , ERR401("필수 파라미터가 없습니다.") + , ERR402("파라미터 유효성 검증 오류") + , ERR403("잘못된 파라미터 입니다.") + , ERR404("일치하는 자료가 없습니다.") + , ERR405("잘못된 요청 값.") + , ERR411("잘못된 JSON 포맷 문자열") + //서버 오류 + , ERR501("HttpServer 오류") + , ERR502("HttpClient 오류") + , ERR503("RestClient 오류") + , ERR504("요청 데이터 Json 파싱 오류") + , ERR505("응답 데이터 Json 파싱 오류") + , ERR506("Hash 생성 오류") + , ERR511("JSON 형식으로 변환 실패") + , ERR520("통신오류") + , ERR521("방화벽 설정 오류") + //외부서비스 오류 + , ERR600("API서버 요청 오류") + , ERR601("API서버 응답 오류") + , ERR602("API서버 내부 오류") + , ERR603("유효하지 않은 토큰(OTT) 값") + , ERR610("응답 데이터에 필수값이 없음") + , ERR620("API Response Error") + //기타오류 + , ERR999("기타 오류") + , ERR901("권한 없음") + , ERR902("유효하지 않은 데이터") + , ERR903("처리 완료된 데이터") + ; + + + + + private final String code; //코드 + private final String codeNm; //코드명 + ErrCd(String codeNm) { + this.code = this.name(); + this.codeNm = codeNm; + } + + public String getCode() { + return this.code; + } + + public String getCodeNm() { + return this.codeNm; + } +} diff --git a/src/main/java/cokr/xit/ci/api/model/ResponseVO.java b/src/main/java/cokr/xit/ci/api/model/ResponseVO.java new file mode 100644 index 0000000..33f9200 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/model/ResponseVO.java @@ -0,0 +1,15 @@ +package cokr.xit.ci.api.model; + +import cokr.xit.ci.api.code.ErrCd; +import lombok.*; + +@Builder +@Getter +@ToString +@EqualsAndHashCode +public class ResponseVO { + private ErrCd errCode; + private String errMsg; + private Object resultInfo; + +} diff --git a/src/main/java/cokr/xit/ci/api/presentation/NiceCiController.java b/src/main/java/cokr/xit/ci/api/presentation/NiceCiController.java new file mode 100644 index 0000000..8c7fb46 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/presentation/NiceCiController.java @@ -0,0 +1,48 @@ +package cokr.xit.ci.api.presentation; + +import cokr.xit.ci.api.code.ErrCd; +import cokr.xit.ci.api.model.ResponseVO; +import cokr.xit.ci.api.service.NiceCiService; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.google.gson.Gson; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.ui.ModelMap; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@RestController +@RequiredArgsConstructor +public class NiceCiController { + + private final NiceCiService diCiService; + + @Value("${nice.api.ci.site-code}") + private String SITE_CODE; + @Value("${nice.api.ci.site-pw}") + private String SITE_PW; + + @SuppressWarnings("deprecation") + @PostMapping("/nice/ci") +// public ResponseEntity ci(@RequestBody Map mParam) { + public ResponseEntity ci(@RequestBody String param) { + Gson gson = new Gson(); + List> params = gson.fromJson(param, ArrayList.class); + List jids = params.stream() + .map(row -> row.get("jid")) + .collect(Collectors.toList()); + + ResponseVO respVO = diCiService.findAllByJid(SITE_CODE, SITE_PW, jids); + + return new ResponseEntity(respVO, HttpStatus.OK); + } +} diff --git a/src/main/java/cokr/xit/ci/api/service/NiceCiService.java b/src/main/java/cokr/xit/ci/api/service/NiceCiService.java new file mode 100644 index 0000000..2cad255 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/NiceCiService.java @@ -0,0 +1,111 @@ +package cokr.xit.ci.api.service; + +import cokr.xit.ci.api.code.ErrCd; +import cokr.xit.ci.api.model.ResponseVO; +import cokr.xit.ci.api.service.suport.Interop; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Component +@RequiredArgsConstructor +public class NiceCiService { + + /** + * 주민번호로 CI를 취득 한다. + * -.CI: 연계정보(Connecting Information) + * -.국가표준 규격에 따라 사용자의 주민번호를 암호화한 개인 식별값. + * -.서비스에 상관없이 값이 일정 함. + * -.주민번호 -> 해쉬 -> CI 값 (88 byte) + * @param siteCode + * @param sitePw + * @param jids + * @return + */ + public ResponseVO findAllByJid(String siteCode, String sitePw, List jids) { + return ResponseVO.builder() + .errCode(ErrCd.OK) + .errMsg(ErrCd.OK.getCodeNm()) + .resultInfo( + jids.stream() +// jids.parallelStream() + .map(jid -> { + ResponseVO responseVO = null; + try { + /* ======================== + * validate + ======================== */ + if(StringUtils.isEmpty(siteCode)){ + responseVO = ResponseVO.builder().errCode(ErrCd.ERR401).errMsg("사이트코드(은)는 필수조건 입니다.").build(); + throw new RuntimeException(responseVO.getErrMsg()); + } + if(StringUtils.isEmpty(sitePw)){ + responseVO = ResponseVO.builder().errCode(ErrCd.ERR401).errMsg("사이트 패스워드(은)는 필수조건 입니다.").build(); + throw new RuntimeException(responseVO.getErrMsg()); + } + if(StringUtils.isEmpty(jid)){ + responseVO = ResponseVO.builder().errCode(ErrCd.ERR401).errMsg("서비스 구분값(주민번호:JID)(은)는 필수조건 입니다.").build(); + throw new RuntimeException(responseVO.getErrMsg()); + } + + /* ======================== + * api call + ======================== */ + responseVO = Interop.getCI(siteCode, sitePw, jid); + + } catch (Exception e){ + log.error(e.getMessage()); + } finally { + /* ======================== + * result set + ======================== */ + Map m = new HashMap<>(); + m.put("key", jid); + m.put("value", responseVO); + return m; + } + + }) + .collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> m.get("value"), (k1, k2)->k1))) + .build(); + } + + + + /** + * sha256 암호화 + * @param text + * @return + * @throws IOException + * @throws NoSuchAlgorithmException + */ + public static String hexSha256(String text) throws IOException, NoSuchAlgorithmException{ + StringBuffer sbuf = new StringBuffer(); + + MessageDigest mDigest = MessageDigest.getInstance("SHA-256"); + mDigest.update(text.getBytes()); + + byte[] msgStr = mDigest.digest() ; + + for(int i=0; i < msgStr.length; i++){ + byte tmpStrByte = msgStr[i]; + String tmpEncTxt = Integer.toString((tmpStrByte & 0xff) + 0x100, 16).substring(1); + + sbuf.append(tmpEncTxt) ; + } + + return sbuf.toString(); + } + + +} diff --git a/src/main/java/cokr/xit/ci/api/service/suport/Interop.java b/src/main/java/cokr/xit/ci/api/service/suport/Interop.java new file mode 100644 index 0000000..6bb887c --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/suport/Interop.java @@ -0,0 +1,97 @@ +package cokr.xit.ci.api.service.suport; + + +import KISINFO.VNO.VNOInterop; +import cokr.xit.ci.api.code.ErrCd; +import cokr.xit.ci.api.model.ResponseVO; + +public class Interop +{ + public Interop() + { + + } + + public static ResponseVO getCI(String siteCode, String sitePw, String jumin) { + + final String sSiteCode = siteCode; // NICE평가정보에서 발급한 서비스 사이트코드 + final String sSitePw = sitePw; // NICE평가정보에서 발급한 서비스 사이트패스워드 + final String sJumin = jumin.replaceAll("[^0-9]", ""); // 주민등록번호 13자리 + final String sFlag = "JID"; // 서비스 구분값 (JID:주민번호 이용) + + + + int iRtnCI = -1; + ErrCd errCode = null; + String errMsg = null; + String ci = null; + try { + // 모듈 객체 생성 + VNOInterop vnoInterop = new VNOInterop(); + + /* ──── CI 값을 추출하기 위한 부분 Start */ + // 인증요청처리 + iRtnCI = vnoInterop.fnRequestConnInfo(sSiteCode, sSitePw, sJumin, sFlag); + System.out.println("======================================================================="); + System.out.println("JID=" + sJumin); + System.out.println("iRtnCI=" + iRtnCI); + + // 인증결과코드에 따른 처리 + if (iRtnCI == 1) { + // CI 값 추출 (연계정보 확인값, 88Byte) + String sConnInfo = vnoInterop.getConnInfo(); + System.out.println("CONNINFO=[" + sConnInfo + "]"); + + // 결과설정 + errCode = ErrCd.OK; + errMsg = String.format("[%s]\n(응답코드 %s)", ErrCd.OK.getCodeNm(), iRtnCI); + ci = sConnInfo; + } else if (iRtnCI == 3) { + System.out.println("[사용자 정보와 서비스 구분값 매핑 오류]"); + System.out.println("사용자 정보와 서비스 구분값이 서로 일치하도록 매핑하여 주시기 바랍니다."); + + // 결과설정 + errCode = ErrCd.ERR405; + errMsg = String.format("[사용자 정보와 서비스 구분값 매핑 오류]\n사용자 정보와 서비스 구분값이 서로 일치하도록 매핑하여 주시기 바랍니다.\n(응답코드 %s)", iRtnCI); + } else if (iRtnCI == -9) { + System.out.println("[입력값 오류]"); + System.out.println("fnRequestConnInfo 함수 처리시, 필요한 4개의 파라미터값의 정보를 정확하게 입력해 주시기 바랍니다."); + + // 결과설정 + errCode = ErrCd.ERR403; + errMsg = String.format("[입력값 오류]\nfnRequestConnInfo 함수 처리시, 필요한 4개의 파라미터값의 정보를 정확하게 입력해 주시기 바랍니다.\n(응답코드 %s)", iRtnCI); + } else if (iRtnCI == -21 || iRtnCI == -31 || iRtnCI == -34) { + System.out.println("[통신오류]"); + System.out.println("방화벽 이용 시 아래 IP와 Port(총 5개)를 등록해주셔야 합니다."); + System.out.println("IP : 203.234.219.72 / Port : 81, 82, 83, 84, 85"); + + // 결과설정 + errCode = ErrCd.ERR521; + errMsg = String.format("[통신오류]\n방화벽 이용 시 아래 IP와 Port(총 5개)를 등록해주셔야 합니다.\nIP : 203.234.219.72 / Port : 81, 82, 83, 84, 85.\n(응답코드 %s)", iRtnCI); + } else { + System.out.println("[기타오류]"); + System.out.println("iRtnCI 값 확인 후 NICE평가정보 전산 담당자에게 문의"); + + // 결과설정 + errCode = ErrCd.ERR999; + errMsg = String.format("[기타오류]\niRtnCI 값 확인 후 NICE평가정보 전산 담당자에게 문의.\n(응답코드 %s)", iRtnCI); + } + /* ──── CI 값을 추출하기 위한 부분 End */ + + } catch (Exception e) { + // 결과설정 + errCode = ErrCd.ERR602; + errMsg = String.format("[나이스API 오류]\n%s\n(응답코드 %s)", e.getMessage(), iRtnCI); + } finally { + return ResponseVO.builder() + .errCode(errCode) + .errMsg(errMsg) + .resultInfo(ci) + .build(); + } + } + + + + +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 8b13789..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..b4fdabc --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,36 @@ + +spring: + profiles: + active: prod + mvc: + view: + prefix: /WEB-INF/jsp/ + suffix: .jsp + devtools: + livereload: + enabled: true #JSP ?? ? ?? ??? ?? ?? ?? + pid: + file: joa.pid + + logging: + file: + name: ./logs/logback.log + logback: + rollingpolicy: + file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}-%i.log + max-history: 30 #30??? ?? + max-file-size: + 100MB #????(100MB) + level: + root: info + '[org.hibernate.sql]': info + + +# ===================================================== +# NICE ???? +# ===================================================== +nice: + api: + ci: + site-code: + site-pw: diff --git a/src/main/webapp/WEB-INF/jsp/index.jsp b/src/main/webapp/WEB-INF/jsp/index.jsp new file mode 100644 index 0000000..3dc7272 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/index.jsp @@ -0,0 +1,326 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + +CI 변환 시스템 + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+

CI 변환

+
+ 메뉴 세부정보 +
    +
  • 주민번호에 대한 CI 를 취득 합니다.
  • +
  • 첨부파일은 엑셀문서(xls,xlsx)만 가능하며, 시트에는 `A열:성명, B:주민번호` 가 작성되어 있어야 합니다.
  • + 시트 작성 +
      +
    • 1행: 컬럼명(A열:성명, B열:주민번호)
    • +
    • 2~xxx행: 데이터
    • +
    +
+
+
+
+ +
+

첨부파일(xls,xlsx)을 이곳에 올려주세요

+
+ + + + + + + + +
+
+ +
+ +
+
+
+ + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/nav.jsp b/src/main/webapp/WEB-INF/jsp/nav.jsp new file mode 100644 index 0000000..43b585a --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/nav.jsp @@ -0,0 +1,24 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + + + + + + + + +
+ +
+ + + + \ No newline at end of file diff --git a/src/main/webapp/resource/css/file-drag-and-drop.css b/src/main/webapp/resource/css/file-drag-and-drop.css new file mode 100644 index 0000000..f421b3a --- /dev/null +++ b/src/main/webapp/resource/css/file-drag-and-drop.css @@ -0,0 +1,18 @@ + +.file-drag-and-drop{ + outline: 2px dashed #92b0b3 ; + outline-offset:-10px; + text-align: center; + transition: all .15s ease-in-out; + width: 300px; + height: 100px; + background-color: gray; + + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: center; + align-content: center; + flex-wrap: wrap; +} + diff --git a/src/main/webapp/resource/css/style.css b/src/main/webapp/resource/css/style.css new file mode 100644 index 0000000..ca11915 --- /dev/null +++ b/src/main/webapp/resource/css/style.css @@ -0,0 +1,31 @@ + +html{ + font-size: 15px; +} +div.app-container{ + display: flex; + flex-direction: row; + flex-wrap: wrap; +} +div.app-item.nav{ + padding: 10px; + background-color: burlywood; + width: 14rem; + flex-grow: 0; + +} +div.app-item.article{ + padding: 10px; + background-color: whitesmoke; + width: 50%; + flex-grow: 1; + +} + +details { + margin: 10px; +} +details > summary:hover { + cursor: pointer; + color: blue; +} diff --git a/src/main/webapp/resource/js/file-drag-and-drop.js b/src/main/webapp/resource/js/file-drag-and-drop.js new file mode 100644 index 0000000..c5b0e1b --- /dev/null +++ b/src/main/webapp/resource/js/file-drag-and-drop.js @@ -0,0 +1,73 @@ +const stateDragAndDrop = { + callback: ()=>{}, + attchFileCnt: 1 +} + + +$('.file-drag-and-drop') + .on("dragover", dragOver) + .on("dragleave", dragOver) + .on("drop", uploadFiles); + +// function dragOver(e){ +// e.stopPropagation(); +// e.preventDefault(); +// } +function dragOver(e) { + e.stopPropagation(); + e.preventDefault(); + if (e.type == "dragover") { + $(e.target).css({ + "background-color": "black", + "outline-offset": "-20px" + }); + } else { + $(e.target).css({ + "background-color": "gray", + "outline-offset": "-10px" + }); + } +} + + + +// function uploadFiles(e){ +// e.stopPropagation(); +// e.preventDefault(); +// } + +function uploadFiles(e) { + e.stopPropagation(); + e.preventDefault(); + dragOver(e); //1 + + e.dataTransfer = e.originalEvent.dataTransfer; //2 + let files = e.target.files || e.dataTransfer.files; + + if (files.length > stateDragAndDrop.attchFileCnt) { + alert('파일은 '+stateDragAndDrop.attchFileCnt+'개만 올리세요.'); + return; + } + + if (files[0].type.match(/image.*/)) { + $(e.target).css({ + "background-image": "url(" + window.URL.createObjectURL(files[0]) + ")", + "outline": "none", + "background-size": "100% 100%" + }); + }else{ + // alert('이미지가 아닙니다.'); + // return; + // debugger; + let event = { + target: { + files: files + } + } + // excelExportCommon(event, handleExcelDataAll); + stateDragAndDrop.callback(event); + } + +} + +