2016년 10월 25일

커리어넷 Open API 를 이용한 학교 검색

커리어넷 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 를 호출하여 결과를 클라이언트가 손쉽게 사용할 수 있는 형식을 테이터로 변경하여 응답하도록 구현하였다.


spring 컨트롤러 : AccountsDataController.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
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("&amp;gubun=").append(gubun);
  if( StringUtils.isNotEmpty(region)){
   sb.append("&amp;region=").append(region);
  }
 
  if( StringUtils.isNotEmpty(name)){
   sb.append("&amp;searchSchulNm=").append(name);
  }  
 
  sb.append("&amp;thisPage=").append(page);
  sb.append("&amp;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기능을 사용하여 쉽게 구현하였다.
검색화면

댓글 5개:

  1. 안녕하세요. 질문이 있어서 글을 남겨봅니다.
    위 java파일에서 ItemList부분에 에러가 나오고 jsonnode에서도 에러가 나오는데
    어떻게 해야하는지 알수 있을까요?

    답글삭제
    답글
    1. 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

      삭제
  2. 음... seq는 어디에서 사용되는 용도인가요?

    답글삭제
    답글
    1. 학교정보 OpenAPI에는 명확하게 기술되어있지않지만 결과 데이터의 단순 순번으로 추정하고 있습니다.
      저는 화면에서 데이터를 출력할때 유일 키로 사용하였습니다. 큰 의미는 없다고 생각하셔도 무방합니다

      삭제
  3. jquery에서 학교를 검색하려면 여기있는 과정이 다 필요한 건가요?ㅠㅠ

    답글삭제