커리어넷 Open API
학교 정보가 필요한 경우 무료로 제공되는 커리어넷 Open API 를 활용하면 쉽게 서비스 구현이 가능하다. API 사용을 위하여 커리어넷 오픈 API 내용을 확인하고 가) 회원 가입 후 나)오픈 API 인증키 신청 후 발급된 키를 사용하여 이용이 가능하다. 이글에서는 학교정보 API 사용하여 학교 검색을 구현하였다.
한가지 아쉬운 점은 제공되는 Open API 가 jsonp 를 지원하고 있지 않아 자바스크립트에서 직접 호출이 어려워 별도의 서버를 통하여 구현이 가능했다는 점이다.
사용된 주요 기술은 아래와 같다.
- server framework : spring-webmvc 4.3.2.RELEASE
- json framework: jackson 2.8.2
- client : jquery 1.12.4 , kendoui 2015.3.930
서버 프로그램
spring mvc 를 사용하여 클라이어트의 요청을 받아 커리어넷 Open API 를 호출하여 결과를 클라이언트가 손쉽게 사용할 수 있는 형식을 테이터로 변경하여 응답하도록 구현하였다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; @Controller("user-data-controller") @RequestMapping("/accounts") public class AccountsDataController { private Logger logger = LoggerFactory.getLogger(getClass()); /** Json 데이터 파싱을 위한 매퍼 정의 */ private static final ObjectMapper MAPPER = new ObjectMapper(); /** * 학교검색 (커리어넷 Open API 를 사용 ) * * * 파라메터 gubun 학교구분 값 [필수]: * <ul> *<li>초등학교 : elem_list</li> *<li>중학교 :midd_list</li> *<li>고등학교 : high_list</li> *<li>대학교 : univ_list</li> *<li>특수/각종기타학교 : seet_list</li> *<li>대안학교 : alte_list</li> * </ul> * * @param gubun 학교구분 * @param request * @param response * @return */ @RequestMapping(value="/school/list.json",method={RequestMethod.POST} ) @ResponseBody public ItemList findSchool(@RequestParam(value="gubun", defaultValue="elem_list" ) String gubun, @RequestParam(value="name", required = false ) String name, @RequestParam(value="region", required = false ) String region, @RequestParam(value="page", defaultValue="1" ) Integer page, @RequestParam(value="pageSize", defaultValue="15" ) Integer pageSize, HttpServletRequest request, HttpServletResponse response){ ItemList items = new ItemList(Collections.EMPTY_LIST, 0); StringBuilder sb = new StringBuilder("http://www.career.go.kr/cnet/openapi/getOpenApi.json?apiKey=발급받은키값&svcType=api&svcCode=SCHOOL&contentType=json"); sb.append("&gubun=").append(gubun); if( StringUtils.isNotEmpty(region)){ sb.append("&region=").append(region); } if( StringUtils.isNotEmpty(name)){ sb.append("&searchSchulNm=").append(name); } sb.append("&thisPage=").append(page); sb.append("&perPage=").append(pageSize); try { URL url = new URL(sb.toString()); URLConnection connection = url.openConnection(); InputStream is = connection.getInputStream(); JsonNode jn = MAPPER.readTree(is); JsonNode jn2 = jn.get("dataSearch").get("content"); Iterator<jsonnode> iter = jn2.elements(); List<school> list = new ArrayList<school>(); while(iter.hasNext()){ JsonNode jn3 = iter.next(); if( items.getTotalCount() == 0 ) items.setTotalCount(jn3.get("totalCount").asInt()); list.add(new School( jn3.get("seq").asInt(0), jn3.get("schoolName").textValue(), jn3.get("region").textValue(), jn3.get("adres").textValue() )); } items.setItems(list); } catch (Exception e) { logger.error("CAREER API ERROR", e); } return items; } } | cs |
학교 정보 객체 : School.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | import java.io.Serializable; public class School implements Serializable { private Integer seq; private String region; private String name; private String address; public School() { } public School(Integer seq, String name, String region, String address) { super(); this.seq = seq; this.name = name; this.region = region; this.address = address; } public Integer getSeq() { return seq; } public void setSeq(Integer seq) { this.seq = seq; } public String getRegion() { return region; } public void setRegion(String region) { this.region = region; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } } | cs |
결과 객체( 배열의 경우 전체 데이터 수 가 없어 구현한 객체 ) : ItemList.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | import java.util.List; public class ItemList { private List<?> items; private int totalCount; /** * @param items * @param totalCount */ public ItemList(List<?> items, int totalCount) { super(); this.items = items; this.totalCount = totalCount; } /** * @return items */ public List<?> getItems() { return items; } /** * @param items * 설정할 items */ public void setItems(List<?> items) { this.items = items; } /** * @return totalCount */ public int getTotalCount() { return totalCount; } /** * @param totalCount * 설정할 totalCount */ public void setTotalCount(int totalCount) { this.totalCount = totalCount; } } | cs |
클라이언트 HTML 소스
학교와 지역 등 코드값들은 OpenApi 매뉴얼 내용을 참고하여 구현하였다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | <script type="text/javascript"> yepnope([{ load: [ '<@spring.url "/js/kendo/2015.3.930/kendo.web.min.js"/>', '<@spring.url "/js/kendo.extension/kendo.ko_KR.js"/>', '<@spring.url "/js/bootstrap/3.3.7/bootstrap.min.js"/>', '<@spring.url "/js/podo.ui/podo.ui.core.js"/>', '<@spring.url "/js/honeycomb/honeycomb.data.js"/>' ], complete: function() { createSchoolSearch(); } }]); function createSchoolSearch(){ var renderTo = $("#school-grid"); if(!podo.ui.exists(renderTo)){ var observable = kendo.observable({ gubun : "elem_list", region : "100260", name : "", search : function(){ var $this = this; podo.ui.grid(renderTo).dataSource.read({ name : $this.get('name'), gubun: $this.get('gubun'), region : $this.get('region') }); } }); kendo.bind($('#search-block'), observable); podo.ui.grid(renderTo, { autoBind: false, dataSource: { transport: { read: { // the remote service url url: "<@spring.url "/accounts/school/list.json"/>", type: "post", // the data type of the returned result dataType: "json" } }, // describe the result format schema: { // the data, which the data source will be bound to is in the "list" field of the response data : "items", total: "totalCount", model : { id : "seq", fields: { seq : {type: "number"}, address: { type: "string" }, region: { type: "string" }, name: { type: "string" }, } } }, pageSize: 15, serverPaging: true, error: podo.ui.handleAjaxError }, pageable: { refresh:true }, filterable : false, height: 300, columns: [ { field: "name", title: "이름", width:160}, { field: "region", title: "지역", width:100 }, { field: "address", title: "주소" } ] }); } } </script> <div class="form-horizontal" id="search-block"> <div class="margin-bottom-5"> <select class="form-control form-group-margin" data-bind="value: gubun" name="gubun" placeholder="구분"> <option value="elem_list">초등학교</option> <option value="midd_list">중학교</option> <option value="high_list">고등학교</option> </select> </div> <div class="margin-bottom-5"> <select class="form-control form-group-margin" data-bind="value: region" name="region" placeholder="지역"> <option value="">전체</option> <option value="100260">서울특별시</option> <option value="100267">부산광역시</option> <option value="100269">인천광역시</option> <option value="100271">대전광역시</option> <option value="100272">대구광역시</option> <option value="100273">울산광역시</option> <option value="100275">광주광역시</option> <option value="100276">경기도</option> <option value="100278">강원도</option> <option value="100280">충청북도</option> <option value="100281">충청남도</option> <option value="100282">전라북도</option> <option value="100283">전라남도</option> <option value="100285">경상북도</option> <option value="100291">경상남도</option> <option value="100292">제주도</option> </select> </div> <div class="margin-bottom-5"> <input class="form-control" data-bind="value: name" placeholder="학교이름" type="text"> </div> <div class="margin-bottom-5"> <button class="btn btn-default" data-bind="click:search" type="button">검색</button> </div> </div> <div id="school-grid"> </div> | cs |
kendoui 사용하니 UI 구현이 아주 간편해졌다. grid기능을 사용하여 쉽게 구현하였다.
검색화면 |
안녕하세요. 질문이 있어서 글을 남겨봅니다.
답글삭제위 java파일에서 ItemList부분에 에러가 나오고 jsonnode에서도 에러가 나오는데
어떻게 해야하는지 알수 있을까요?
ItemList 클래스는 전체 수 와 리턴할 데이터를 포함하는 객체 클래스입니다. 원 글에 소스를 포함했습니다.
삭제그리고 jsonnode 에서 에러가 나는것은 아마도 사용하는 json 파서의 버전의 문제가 아닐지 제가 사용한 버전은 아래와 같습니다. (pom.xml 파일의 의존성, maven 을 사용하지 않는 경우는 해당 라이브러리를 다운하여 사용하면 됩니다.)
com.fasterxml.jackson.core
jackson-core
2.8.2
com.fasterxml.jackson.core
jackson-databind
2.8.2
com.fasterxml.jackson.core
jackson-annotations
2.8.2
음... seq는 어디에서 사용되는 용도인가요?
답글삭제학교정보 OpenAPI에는 명확하게 기술되어있지않지만 결과 데이터의 단순 순번으로 추정하고 있습니다.
삭제저는 화면에서 데이터를 출력할때 유일 키로 사용하였습니다. 큰 의미는 없다고 생각하셔도 무방합니다
jquery에서 학교를 검색하려면 여기있는 과정이 다 필요한 건가요?ㅠㅠ
답글삭제