본문 바로가기

프로그래밍

나이스 교육 정보 OpenApi 사용 방법 및 후기

4/30 교육 공공데이터 활용 공모전 서비스 부문에 참가작을 제출 완료 하였다.

 

우리팀은 고교학점제 실시로 인한 학생들의 교육 정보 탐색을 주제로 서비스를 하였다.

 

Spring을 활용해서 백엔드를 구축하였기 때문에 오늘은 neis open api를 spring에 연동하는 방법을 리뷰하려 한다.

 

앞서 이 글은 openApi 활용이 완전 처음인 분들을 기준으로 소개하는 듯한 흐름의 글이니, 당연한 말을 왜할까 혹은 용어의 사용이 불편한 사람들이 있더라도 넓은 마음으로 스크롤을 내려주길 바라는 마음이길 바랍니다.

 

https://open.neis.go.kr/portal/mainPage.do

 

나이스 교육정보 개방 포털

OPEN API 활용신청 제공되는 데이터를 활용하기 위해 인증키를 발급 받으세요. 교육정보개방 소개 <!-- 2017.10.25 kty 정부3.0제거 교육부 정부 3.0 - 교육정보 개방 포털은 어떤 시스템인지 알아보세요.

open.neis.go.kr

 

 

 


먼저 Resttemplate을 사용하였다. 

 

빠른 개발과 기존에 많이 사용했던 방식으로 개발기간이 짧은 우리 팀에 적합한 방식이라 생각하였다.

OpenFeign라는 방식을 알게되었고, 가독성이 더 좋다고하여 리팩토링을 할 예정이다.

 

 

먼저 openApi 활용하기 위해서는 인증키가 필요하다. 발급은 포털 내에 인증키 발급을 통해 복잡한 절차없이 이용 가능하다.

인증키 발급 신청 화면

 

키를 발급 받았다면 데이터 탭 에서 내가 필요한 정보를 찾아보자.

 

내가 필요한 정보와 완전 핏하지 않더라도 api를 통해 정보를 받고 서비스 DB내에서 가공을 하면 되기 때문에 필요한 요소가 있는지 찾는게 주요하다.

 

 

 

 

 

 

 

예를 들어 학교 기본정보가 필요하다 가정하고 흐름을 살펴보자.

 

 

 

엑셀로 데이터 셋을 먼저 살펴볼수도 있다.

 

OpenApi탭에 들어가면 필요인자와 반환값들을 상세히 살펴볼 수 있다.

 

기존에 유로 openapi에 익숙해졌던 내 시야에서는 너무 혜자이고, 친절한 설명들..

 

 

 

샘플 test를 굉장히 많이 활용한 것 같다!

 

예를 들어 학교 기본 정보에 서울 지역코드인 B10을 넣고 검색하면

기본값인 xml로 정보를 보여준다. 나는 json으로 데이터를 받을 예정이기에 type에 json 추가가 필요하다.

 

내가 필요한 정보가 정해졌다면 먼저 내가 필요로 하는 정보들이 잘 넘어오는지 확인이 필요하다.

 

 

 

 

 

 

 

나는 Postman을 사용해서 실제 url로 요청을 보내 확인을 했었다.

 

실제 요청으로 확인하는 과정

아직까지는 백 포트에서 나이스로 요청을 보내는 과정이다.

 

 

 

하지만 서비스 입장에서는 클라이언트가 서비스로 요청을 보냈을때 혹은 비동기로 서비스가 나이스에 요청을 보내줘야 하기 떄문에 spring내에서 따로 컨트롤러 혹은 서비스 로직 구현이 필요하다.

 

 

 

 

나이스에서 정보를 받아오는 목적은 해당 데이터를 활용하기 위함이니 아마 서비스 DB에 데이터 적재가 주요 목적이지 않을까 싶다.

 

 

 

 

 

그렇기에 아래와 같은 목적이 있을것이다. 

클라이언트 요청을 통해 나이스openapi를 호출해 필요한 정보를 반환하는 과정

 

 

어떻게 하는지 간단하게 알아보자.

 

 

 

 

 

 

 

실제로 사용했던 디버그중 코드이다.

아래는 spring service단에서 openapi를 호출하는 주요 로직이다.

for (int i = 1; i <= 2; i++) { // pindex 1과 2 두 번 반복
            String url = UriComponentsBuilder.fromHttpUrl(testBaseUrl)
                    .queryParam("KEY", openApiKey)
                    .queryParam("Type", "json")
                    .queryParam("ATPT_OFCDC_SC_CODE", req.getAtptOfcdcScCode())
                    .queryParam("SD_SCHUL_CODE", req.getSdSchulCode())
                    .queryParam("TI_FROM_YMD", "20250407")
                    .queryParam("TI_TO_YMD", "20250411")
                    .queryParam("pSize", "750")
                    .queryParam("pindex", i)
                    .toUriString();

            ResponseEntity<String> response = restTemplate.exchange(
                    url,
                    HttpMethod.GET,
                    HttpEntity.EMPTY,
                    String.class
            );


            System.out.println("[DEBUG] NEIS 요청: " + url);
            System.out.println("[DEBUG] NEIS 응답: " + response.getBody());

            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
                ObjectMapper mapper = new ObjectMapper();
                JsonNode root = mapper.readTree(response.getBody());

                JsonNode rowArray = root.path("hisTimetable").get(1).path("row");
                System.out.println("[DEBUG] JSON 응답: " + rowArray);

                if (rowArray.isArray()) {
                    for (JsonNode node : rowArray) {
                        String courseName = node.path("ITRT_CNTNT").asText();
                        String grade = node.path("GRADE").asText(); // 학년 정보

                        String courseKey = courseName + "_" + grade;

                        if (courseNameSet.contains(courseKey)) continue;
                        courseNameSet.add(courseKey);
                        System.out.println(courseNameSet);

                        Course course = Course.builder()
                                .school(schoolRepository.findBySchoolId(node.path("SD_SCHUL_CODE").asLong()))
                                .courseName(courseName)
                                .courseType("공통")
                                .courseArea(node.path("ORD_SC_NM").asText()) // 예: 일반계
                                .semester(node.path("GRADE").asText() + "학년 " + node.path("SEM").asText() + "학기") // 예: 1학년 1학기
                                .description(node.path("DGHT_CRSE_SC_NM").asText() + " " + node.path("GRADE").asText() + "학년") // 예: 주간 1학년
                                .updatedAt(LocalDateTime.now())
                                .maxStudents(0) // 임의 초기값
                                .build();
                        System.out.println(course);
                        courseList.add(course);
                    }
                }
            } else {
                throw new RuntimeException("NEIS API 호출 실패");
            }
        }

        return courseRepository.saveAll(courseList);
    }

 

해당 로직은 학교의 일별 시간표를 불러와 중복제거를 통해 해당 학교에 어떤 수업이 있는지 튜닝하는 코드이다.

 

 

 

 

호출에 제일 중요한건 아래의 코드이다. openapi에 Post는 거의 없다시피 하니, 해당 코드에서 url을 정해주는게 중요하다.

ResponseEntity<String> response = restTemplate.exchange(
        url,
        HttpMethod.GET,
        HttpEntity.EMPTY,
        String.class
);

 

 

사실 요즘은 생성형 api를 너무 많이 쓰다보니, 위의 해당코드를 넣어놓고 내가 필요한 정보가 뭔지 그게 따라 코드를 바꿔달라고 하고 

해당 코드와 비교해서 보면 빠르게 습득할 수 있지 않을까?

 

 

 

 

 

중요한건 RestTemplate사용에 Config 설정이 필요하니 이를 미리 정의해서 만들어줘야한다. 아니면 RestTemplate사용이 불가능할것이다.

 

 

뭐 이런식으로..

 

이렇게 Service를 만들어 놓고 컨트롤러를 통해 Postman이나 테스트를 해보면서 테스트 하면 된다.

 

 

 

예선 통과했으면 좋겠당