1. 이전 포스팅

 

https://growingsaja.tistory.com/981

 

[Kotlin][SpringBoot3] Kopring 서버 응용 실습 01 - 새로운 프로젝트 하면 좋은 사전 세팅

1. 이전 포스팅 https://growingsaja.tistory.com/984 2. 목표 실시간 환율 정보 및 가상자산 정보를 수집해 저장하고, 데이터를 가공해 제공하는 api가 구현된 백엔드 서버 개발 - 개발하는 백엔드 시스템 기

growingsaja.tistory.com

 

 

 

 

 

2. 목표

 

 - MongoDB 연동

 - MongoDB에 Dunamu에서 제공해주는 api 활용해 환율 및 주요 원자재 등 가격 정보 조회

 

 

 

 

 

3. api 확인하기

 

https://openexchangerates.org/api/latest.json
https://openexchangerates.org/api/latest.json?app_id={api_key}

 - api 문서 페이지

https://docs.openexchangerates.org/reference/latest-json

 

/latest.json

Get the latest exchange rates available from the Open Exchange Rates API.The most simple route in our API, latest.json provides a standard response object containing all the conversion rates for all of the currently available symbols/currencies, labeled by

docs.openexchangerates.org

 

 - api 사용량 확인 페이지

https://openexchangerates.org/account/usage

 

Login - Open Exchange Rates

14 June 2022: We have updated our Terms & Conditions and Privacy Policy. Please read and familiarise yourself with these changes, as they apply to your continued use of our site and services.

openexchangerates.org

 

 

 

 

 

4. api call에 대한 response 데이터 확인

 

{
    "disclaimer": "Usage subject to terms: https://openexchangerates.org/terms",
    "license": "https://openexchangerates.org/license",
    "timestamp": 1695265200,
    "base": "USD",
    "rates": {
        "AED": 3.672915,
        "AFN": 79.365677,
        "ALL": 99.985896,
        "AMD": 387.647516,
        "ANG": 1.803099,
        "AOA": 830,
        "ARS": 349.9553,
        "AUD": 1.5608,
        "AWG": 1.8025,
        "AZN": 1.7,
        "BAM": 1.82815,
        "BBD": 2,
        "BDT": 109.803022,
        "BGN": 1.83836,
        "BHD": 0.376954,
        "BIF": 2837.370027,
        "BMD": 1,
        "BND": 1.363868,
        "BOB": 6.91328,
        "BRL": 4.8804,
        "BSD": 1,
        "BTC": 0.000037076379,
        "BTN": 83.277732,
        "BWP": 13.639614,
        "BYN": 2.525366,
        "BZD": 2.016685,
        "CAD": 1.349857,
        "CDF": 2497.786322,
        "CHF": 0.900497,
        "CLF": 0.031986,
        "CLP": 882.6,
        "CNH": 7.31192,
        "CNY": 7.3003,
        "COP": 3958.212951,
        "CRC": 531.4893,
        "CUC": 1,
        "CUP": 25.75,
        "CVE": 103.068245,
        "CZK": 22.958754,
        "DJF": 179.262281,
        "DKK": 7.012777,
        "DOP": 57.084536,
        "DZD": 137.0047,
        "EGP": 30.8909,
        "ERN": 15,
        "ETB": 55.26,
        "EUR": 0.940977,
        "FJD": 2.27125,
        "FKP": 0.812396,
        "GBP": 0.812396,
        "GEL": 2.655,
        "GGP": 0.812396,
        "GHS": 11.529075,
        "GIP": 0.812396,
        "GMD": 61.65,
        "GNF": 8644.92804,
        "GTQ": 7.868577,
        "GYD": 209.307769,
        "HKD": 7.82372,
        "HNL": 24.82,
        "HRK": 7.091352,
        "HTG": 135.40636,
        "HUF": 362.056766,
        "IDR": 15405.05,
        "ILS": 3.806885,
        "IMP": 0.812396,
        "INR": 83.193949,
        "IQD": 1318.560282,
        "IRR": 42250,
        "ISK": 135.6,
        "JEP": 0.812396,
        "JMD": 154.78232,
        "JOD": 0.7092,
        "JPY": 148.27483333,
        "KES": 146.71,
        "KGS": 88.71,
        "KHR": 4148.050726,
        "KMF": 459.949574,
        "KPW": 900,
        "KRW": 1340.865511,
        "KWD": 0.308883,
        "KYD": 0.833781,
        "KZT": 477.09184,
        "LAK": 20190.751797,
        "LBP": 15036.104936,
        "LKR": 325.174754,
        "LRD": 186.499991,
        "LSL": 19.036538,
        "LYD": 4.864291,
        "MAD": 10.307691,
        "MDL": 18.009997,
        "MGA": 4492.5,
        "MKD": 57.775104,
        "MMK": 2100.997679,
        "MNT": 3450,
        "MOP": 8.061616,
        "MRU": 38.21048,
        "MUR": 44.85816,
        "MVR": 15.375,
        "MWK": 1113.865075,
        "MXN": 17.142443,
        "MYR": 4.6915,
        "MZN": 63.950001,
        "NAD": 18.93,
        "NGN": 781.092745,
        "NIO": 36.59,
        "NOK": 10.821776,
        "NPR": 133.244208,
        "NZD": 1.693568,
        "OMR": 0.384982,
        "PAB": 1,
        "PEN": 3.728724,
        "PGK": 3.7125,
        "PHP": 56.958504,
        "PKR": 291.964072,
        "PLN": 4.340868,
        "PYG": 7279.706823,
        "QAR": 3.641,
        "RON": 4.6772,
        "RSD": 110.387,
        "RUB": 96.061479,
        "RWF": 1207.260873,
        "SAR": 3.751441,
        "SBD": 8.415589,
        "SCR": 13.009171,
        "SDG": 600.5,
        "SEK": 11.187843,
        "SGD": 1.367849,
        "SHP": 0.812396,
        "SLL": 20969.5,
        "SOS": 575.360442,
        "SRD": 38.221,
        "SSP": 130.26,
        "STD": 22281.8,
        "STN": 23.166981,
        "SVC": 8.754703,
        "SYP": 2512.53,
        "SZL": 19.0293,
        "THB": 36.2415,
        "TJS": 10.990396,
        "TMT": 3.51,
        "TND": 3.144,
        "TOP": 2.388828,
        "TRY": 27.036547,
        "TTD": 6.783352,
        "TWD": 32.131299,
        "TZS": 2505,
        "UAH": 36.949595,
        "UGX": 3746.414199,
        "USD": 1,
        "UYU": 38.145239,
        "UZS": 12195,
        "VES": 33.791109,
        "VND": 24310.10068,
        "VUV": 118.722,
        "WST": 2.7185,
        "XAF": 617.240311,
        "XAG": 0.04326944,
        "XAU": 0.00051902,
        "XCD": 2.70255,
        "XDR": 0.758722,
        "XOF": 617.240311,
        "XPD": 0.00079465,
        "XPF": 112.2884,
        "XPT": 0.00108194,
        "YER": 250.375049,
        "ZAR": 18.95355,
        "ZMW": 20.965996,
        "ZWL": 322
    }
}

 

 

 

 

 

5. mongoDB 연동

 

# vim /main/resources/application.properties

# ...

# mongodb
spring.data.mongodb.uri=mongodb://localhost:27017/test00

# ...

 

 

 

 

 

6. 프로젝트 구조 설계

 

크게 아래 두 구조 형태를 가지고 가는 것을 권장합니다.

 

entity - repository - service - controller

entity - repository - service - scheduler

 

 

 

 

 

 

7. entity로 data class 만들기

 

// vim OpenexchangeRateData_raw.kt

package com.dev.kopring00.external.fiat.entities

data class OpenexchangeRateData_raw(
    val timestamp: Long,        // 정보의 기준 unix timestamp
    val disclaimer: String,
    val license: String,
    val base: String,
    val rates: Map<String, Double>
)

 

 - 수집한 모든 데이터를 그대로 저장하는 것이 아니라 필요 형태로 가공해 저장합니다.

// vim OpenexchangeRateData.kt

package com.dev.kopring00.external.fiat.entities

import org.springframework.data.mongodb.core.mapping.Document

@Document(collection = "openexchangeRateData")
data class OpenexchangeRateData(
    var timestamp: Long,    // 정보의 기준 datetime
    val createdAt: Long?,   // 정보 저장 일시
    val rates: Map<String, Double>
)

 

 

 

 

 

8. repository 만들기

 

// vim OpenexchangeRateDataRepository.kt

package com.dev.kopring00.external.fiat.repositories

import com.dev.kopring00.external.fiat.entities.OpenexchangeRateData
import org.springframework.data.mongodb.repository.MongoRepository

interface OpenexchangeRateDataRepository: MongoRepository<OpenexchangeRateData, String> {

    fun findFirstByOrderByTimestampDesc(): OpenexchangeRateData?
}

 

 

 

 

 

9. service 만들기

 

 - api call limit이 있기 때문에 isNeedUpdate 로 확인

 

// vim OpenexchangeRateDataService.kt

package com.dev.kopring00.external.fiat.services

import com.dev.kopring00.external.fiat.entities.OpenexchangeRateData_raw
import com.dev.kopring00.external.fiat.entities.OpenexchangeRateData
import com.dev.kopring00.external.fiat.repositories.OpenexchangeRateDataRepository
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate
import java.time.Instant
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter

@Service
class OpenexchangeRateDataService(private val openexchangeRateDataRepository: OpenexchangeRateDataRepository) {

    // 외부 api call
    fun getDataFromApi(apiUrl: String): OpenexchangeRateData_raw? {
        val restTemplate = RestTemplate()
        val response = restTemplate.getForEntity(apiUrl, OpenexchangeRateData_raw::class.java)
        return response.body
    }

    // 데이터 저장하기
    fun saveDataFromData(openexchangeRateDataRaw: OpenexchangeRateData_raw) {
        val unixTimestamp: Long = openexchangeRateDataRaw.timestamp    // 정보 기준 일시 unix timestamp
        val openexchangeRateData = OpenexchangeRateData(
            unixTimestamp,
            Instant.now().epochSecond,
            openexchangeRateDataRaw.rates
        )
        openexchangeRateDataRepository.save(openexchangeRateData)
    }

    // 데이터 최신화 필요 여부 확인
    // 날짜, 시간까지만 일치 여부 확인 : "yyyy-MM-dd HH"
    fun isNeedUpdate(): Boolean {
        val currentTimestamp = Instant.now().epochSecond
        val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH")
        val currentDateHour = formatter.format(Instant.ofEpochSecond(currentTimestamp).atOffset(ZoneOffset.UTC))
        val lastSavedDatetime: OpenexchangeRateData? = openexchangeRateDataRepository.findFirstByOrderByTimestampDesc()
        println("currentDateHour : $currentDateHour")
        // 데이터가 있는 경우, 데이터 최신화 필요한지 체크
        if (lastSavedDatetime != null) {
            val instant = Instant.ofEpochSecond(lastSavedDatetime.timestamp)
            val lastSavedDateHour: String = formatter.format(instant.atOffset(ZoneOffset.UTC))
            println("lastSavedDateHour : $lastSavedDateHour")
            return lastSavedDateHour != currentDateHour
        }
        // 데이터가 없으면 즉시 update 실행
        return true
    }
}

 

 

 

 

 

10. scheduler 만들기

// vim OpenexchangeRateDataScheduler.kt

package com.dev.kopring00.external.fiat.schedulers

import com.dev.kopring00.external.fiat.services.OpenexchangeRateDataService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component

@Component
class OpenexchangeRateDataScheduler @Autowired constructor(private val openexchangeRateDataService: OpenexchangeRateDataService) {

    @Value("\${api.url.openexchangeRate}")
    private lateinit var apiUrl: String

    @Value("\${api.url.openexchangeKey}")
    private lateinit var apiKey: String

    fun updateRateData() {
        val fullApiUrl = apiUrl+apiKey
        val openexchangeRateData = openexchangeRateDataService.getDataFromApi(fullApiUrl)
        if (openexchangeRateData != null) {
            openexchangeRateDataService.saveDataFromData(openexchangeRateData)
        } else {

        }
    }

    // 3600초=1시간마다 실행
    @Scheduled(fixedRate = 3600000)
    fun scheduledUpdateOpenexchangeRates() {
        if (openexchangeRateDataService.isNeedUpdate()) {
            updateRateData()
        }
    }

}

 

 

 

 

 

11. 변수 설정

 

# vim /main/resources/application.properties

# ...

# rates
api.url.openexchangeRate=https://openexchangerates.org/api/latest.json?app_id=
api.url.openexchangeKey=k2p9qt483uvy899qptv28tcuale2

 

 

 

 

 

12. 결과 확인

 

 정상적으로 의도된 데이터들이 잘 저장된 것을 확인합니다.

 

 

 

+ Recent posts