공공데이터 활용 API 작성

2024. 12. 6. 16:50TIL

 

최초 1회 실행하는 Open API를 이용하여 내 DB에 데이터 저장하기

package teamhp.alarm_open.domain.service

import com.fasterxml.jackson.dataformat.xml.XmlMapper
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate
import java.net.URI
import org.springframework.jdbc.core.JdbcTemplate
import teamhp.alarm_open.domain.dto.Performance
import teamhp.alarm_open.domain.entity.PerformanceDB
import teamhp.alarm_open.domain.model.OpenApiReposiitory

@Service
class OpenApiService(
    private val restTemplate: RestTemplate,
    private val openApiReposiitory: OpenApiReposiitory,
) {

    fun getAllRegionPerformanceList(): String {

        var pageNo:Int = 1
        while(pageNo <=74){

            val url =
                "http://api.data.go.kr/openapi/tn_pubr_public_pblprfr_event_info_api?serviceKey=내 서비스 키&pageNo=$pageNo&numOfRows=100&type=xml"

            val uri = URI(url)

            val response = restTemplate.getForObject(uri, String::class.java)

            val xmlResponse = response ?: return "Failed to fetch data"

            val xmlMapper = XmlMapper()
            val rootNode = xmlMapper.readTree(xmlResponse)

            println("XML Response: $xmlResponse")

            val items = rootNode["body"]?.get("items")?.get("item")

            if (items == null) {
                return "No items found in the response."
            }

            val performances = mutableListOf<PerformanceDB>()
            items.forEach {
                val performanceDB = PerformanceDB(
                    eventName = it["eventNm"]?.asText() ?: "Unknown Event",  // 값이 없을 경우 기본값 처리
                    location = it["opar"]?.asText(),
                    startDate = it["eventStartDate"]?.asText(),
                    endDate = it["eventEndDate"]?.asText(),
                    startTime = it["eventStartTime"]?.asText(),
                    endTime = it["eventEndTime"]?.asText(),
                    chargeInfo = it["chrgeInfo"]?.asText(),
                    seatNumber = it["seatNumber"]?.asText(),
                    admissionFee = it["admfee"]?.asText(),
                    ageLimit = it["entncAge"]?.asText(),
                    homePageUrl = it["homepageUrl"]?.asText(),
                    address = it["rdnmadr"]?.asText(),
                    latitude = parseDoubleSafe(it["latitude"]?.asText()),
                    longitude = parseDoubleSafe(it["longitude"]?.asText()),
                    region = 200,
                )
                performances.add(performanceDB)
            }

            openApiReposiitory.saveAll(performances)

            pageNo++
        }
        return "Data fetched and saved successfully"
    }

    private fun parseDoubleSafe(value: String?): Double? {
        return try {
            value?.takeIf { it.isNotEmpty() }?.toDouble()
        } catch (e: NumberFormatException) {
            null
        }
    }
}

 

현재 활성화 되어있는 행사와 앞으로 예정된 행사를 조회하는 서비스 작성

@Service
class PerformanceService(
    private val queryDslOpenApi: QueryDslOpenApi
) {


    fun getAllPerformanceList(): List<PerformanceDB> {
        return queryDslOpenApi.findCurrentAndUpcomingEvents()
    }
}
package teamhp.alarm_open.domain.model

import com.querydsl.core.types.dsl.Expressions
import org.springframework.stereotype.Repository
import teamhp.alarm_open.domain.dto.Performance
import teamhp.alarm_open.domain.entity.PerformanceDB
import teamhp.alarm_open.domain.entity.QPerformanceDB
import teamhp.alarm_open.infra.querydsl.QueryDslSupport
import java.time.LocalDate


@Repository
class QueryDslOpenApi: QueryDslSupport() {

    private val qPerformance = QPerformanceDB.performanceDB

    val today = LocalDate.now()

    fun findCurrentAndUpcomingEvents(): List<PerformanceDB> {
        return queryFactory.selectFrom(qPerformance)
            .where(
                Expressions.dateTemplate(
                    LocalDate::class.java,
                    "TO_DATE({0}, 'YYYY-MM-DD')", qPerformance.startDate
                ).loe(today)
                    .and(
                        Expressions.dateTemplate(
                            LocalDate::class.java,
                            "TO_DATE({0}, 'YYYY-MM-DD')", qPerformance.endDate
                        ).goe(today)
                    )
                    .or(
                        Expressions.dateTemplate(
                            LocalDate::class.java,
                            "TO_DATE({0}, 'YYYY-MM-DD')", qPerformance.startDate
                        ).gt(today)
                    )
            )
            .fetch()
    }
}

Open API를 통해서 받아온 start_date값과 end_date값이 모두 String값으로 설정되어 있었기 때문에 그대로 String 타입을 유지하면서 날짜 비교를 하였다.

 

클라이언트에서 각 지역에 대한 즐겨찾기 기능을 제공하고, 해당 지역에 대한 리스트를 보여줄 것이기 때문에 지역 정보가 있는 address 컬럼에서 따로 region이라는 지역코드 컬럼을 생성하였다.

클라이언트에서 지역코드를 통해서 각 지역에 대한 데이터접근이 편해지게 설계하였다.

 
ALTER TABLE performance_list ADD COLUMN region integer;

UPDATE performance_list
SET region =
CASE
    WHEN address LIKE '서울%' THEN 1
    WHEN address LIKE '부산%' THEN 2
    WHEN address LIKE '대구%' THEN 3
    WHEN address LIKE '인천%' THEN 4
    WHEN address LIKE '광주%' THEN 5
    WHEN address LIKE '대전%' THEN 6
    WHEN address LIKE '울산%' THEN 7
    WHEN address LIKE '세종%' THEN 8
    WHEN address LIKE '경기%' THEN 9
    WHEN address LIKE '강원%' THEN 10
    WHEN address LIKE '충청북도%' THEN 11
    WHEN address LIKE '충북%' THEN 11
    WHEN address LIKE '경상남도%' THEN 12
    WHEN address LIKE '경남%' THEN 12
    WHEN address LIKE '전북%' THEN 13
    WHEN address LIKE '전라남도%' THEN 14
    WHEN address LIKE '전남%' THEN 14
    WHEN address LIKE '경상북도%' THEN 15
    WHEN address LIKE '경북%' THEN 15
    WHEN address LIKE '제주%' THEN 16
    WHEN address LIKE '충청남도%' THEN 17
    WHEN address LIKE '충남%' THEN 17
    ELSE 100
END;
 

 

7천개가 넘는 데이터가 있기 때문에 성능개선을 위하여  2024년 이전의 데이터와 address가 없는 데이터를 삭제하였다.

DELETE FROM performance_list
WHERE address IS NULL OR address = '' OR address = '미정';

DELETE FROM performance_list
WHERE TO_DATE(end_date,'YYYY-MM-DD') < '2024-01-01';

 

DB

 

서버가 클라이언트로 보내는 json 데이터 형태

[
  {
    "eventName": "삼례문화예술촌 상설공연 운영",
    "location": "삼례문화예술촌 일원",
    "startDate": "2024-06-01",
    "endDate": "2024-12-28",
    "startTime": "14:00",
    "endTime": "15:00",
    "chargeInfo": "무료",
    "seatNumber": "108",
    "admissionFee": "",
    "ageLimit": "전연령",
    "homePageUrl": "http://www.samnyecav.kr/board/index.php",
    "address": "전북특별자치도 완주군 삼례읍 삼례역로 81-13",
    "latitude": 35.90623513,
    "longitude": 127.0658535,
    "region": 13,
    "id": 14627
  },
  {
    "eventName": "사라장 리사이틀",
    "location": "대공연장",
    "startDate": "2024-12-15",
    "endDate": "2024-12-15",
    "startTime": "16:00",
    "endTime": "17:40",
    "chargeInfo": "유료",
    "seatNumber": "1103",
    "admissionFee": "",
    "ageLimit": "초등학생 이상 관람가",
    "homePageUrl": "http://arts.iksan.go.kr",
    "address": "전북특별자치도 익산시 동서로 490",
    "latitude": null,
    "longitude": null,
    "region": 13,
    "id": 14663
  },

 

필요한 데이터는 클라이언트에서 정하여 사용하는 식으로 설계하였다.

GitHub: https://github.com/kotlin2024/alarm_open/tree/develop

 

GitHub - kotlin2024/alarm_open: 오픈데이터 활용 API

오픈데이터 활용 API. Contribute to kotlin2024/alarm_open development by creating an account on GitHub.

github.com

 

'TIL' 카테고리의 다른 글

내 로컬에 있는 컨테이너 EC2로 업로드하기  (0) 2024.12.08
docker 사용해보기  (0) 2024.12.07
공공API 사용  (1) 2024.12.05
공공데이터 활용 프로젝트  (0) 2024.12.04
Docker 설치 및 세팅  (2) 2024.12.03