1. 참고하면 좋은 이전 포스팅

 

https://growingsaja.tistory.com/953

 

[Kopring] Kotlin Spring Boot 서버 실습 01 - intelliJ 설치부터 간단한 프로젝트 실행까지

1. IntelliJ 다운로드 및 설치 https://www.jetbrains.com/ko-kr/idea/download 최고의 Java 및 Kotlin IDE인 IntelliJ IDEA를 다운로드하세요 www.jetbrains.com 2. IntelliJ 실행 Starter로 진행 3. New Project 생성 시작 4. 의존성 추

growingsaja.tistory.com

 

 

 

 

 

2. 새 프로젝트 생성

 

Kotlin

Gradle - Kotlin

Name : practiceKopring

Group : com.example

 

 

 

 

 

3. Dependencies 추가

 

Developer Tools -> Sptring Boot DevTools

Web -> Spring Web

SQL -> Spring Data JPA

SQL -> H2 Database

 

 

 

 

 

3. app 가동

 

Crtl + R

 - 아래와 같이 나올 경우 정상작동 확인 완료

 - 참조

// vim build.gradle.kts

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "3.1.2"
    id("io.spring.dependency-management") version "1.1.2"
    kotlin("jvm") version "1.8.22"
    kotlin("plugin.spring") version "1.8.22"
    kotlin("plugin.jpa") version "1.8.22"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"

java {
    sourceCompatibility = JavaVersion.VERSION_17
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    developmentOnly("org.springframework.boot:spring-boot-devtools")
    runtimeOnly("com.h2database:h2")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs += "-Xjsr305=strict"
        jvmTarget = "17"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

 

 

 

 

 

4. 폴더 및 파일 만들기

 

 구조 초기 세팅

 

 - 파일 3개 생성

ApiService.kt

Models.kt

MyApiController.kt

 

 

 

 

 

 

5. bybit에서 제공하는 public api 결과값을 가져와 return하는 api 구현 실습 전에, 사용할 api 확인

 

아래 public api에 대한 정보 확인

https://api.bybit.com/v5/market/tickers?category=spot

// bybit의 public api return값 예시

{
    "retCode": 0,
    "retMsg": "OK",
    "result": {
        "category": "spot",
        "list": [
            {
                "symbol": "APEXUSDC",
                "bid1Price": "0.1656",
                "bid1Size": "1427.15",
                "ask1Price": "0.1673",
                "ask1Size": "89.59",
                "lastPrice": "0.1657",
                "prevPrice24h": "0.1664",
                "price24hPcnt": "-0.0042",
                "highPrice24h": "0.1689",
                "lowPrice24h": "0.1645",
                "turnover24h": "8460.917772",
                "volume24h": "50750.86"
            },
            {
                "symbol": "ENSUSDT",
                "bid1Price": "8.015",
                "bid1Size": "14.75",
                "ask1Price": "8.028",
                "ask1Size": "41.32",
                "lastPrice": "8.028",
                "prevPrice24h": "8.133",
                "price24hPcnt": "-0.0129",
                "highPrice24h": "8.143",
                "lowPrice24h": "7.81",
                "turnover24h": "14887.48266",
                "volume24h": "1865.31",
                "usdIndexPrice": "8.0275839"
            },
            {
                "symbol": "TRIBEUSDT",
                "bid1Price": "0.278",
                "bid1Size": "24631.63",
                "ask1Price": "0.2817",
                "ask1Size": "30",
                "lastPrice": "0.2805",
                "prevPrice24h": "0.278",
                "price24hPcnt": "0.0090",
                "highPrice24h": "0.329",
                "lowPrice24h": "0.278",
                "turnover24h": "1506.056215",
                "volume24h": "5137.87"
            },
            {
                "symbol": "ECOXUSDT",
                "bid1Price": "0.297",
                "bid1Size": "241.66",
                "ask1Price": "0.2971",
                "ask1Size": "790.99",
                "lastPrice": "0.2971",
                "prevPrice24h": "0.2971",
                "price24hPcnt": "0",
                "highPrice24h": "0.2971",
                "lowPrice24h": "0.2971",
                "turnover24h": "0",
                "volume24h": "0"
            },
            {
                "symbol": "BTCUSDC",
                "bid1Price": "26046.01",
                "bid1Size": "0.000655",
                "ask1Price": "26048.18",
                "ask1Size": "0.28217",
                "lastPrice": "26048.18",
                "prevPrice24h": "26151.08",
                "price24hPcnt": "-0.0039",
                "highPrice24h": "26255.91",
                "lowPrice24h": "25802.25",
                "turnover24h": "148993709.40893579",
                "volume24h": "5720.9569",
                "usdIndexPrice": "26048.67000001"
            },
            ...
            
            {
                "symbol": "SLGUSDT",
                "bid1Price": "0.012357",
                "bid1Size": "7053.85",
                "ask1Price": "0.012485",
                "ask1Size": "35.06",
                "lastPrice": "0.012485",
                "prevPrice24h": "0.013213",
                "price24hPcnt": "-0.0551",
                "highPrice24h": "0.013578",
                "lowPrice24h": "0.011652",
                "turnover24h": "320208.86033018",
                "volume24h": "24354959.2"
            },
            {
                "symbol": "LINKUSDT",
                "bid1Price": "6.1608",
                "bid1Size": "48.724",
                "ask1Price": "6.1609",
                "ask1Size": "23.882",
                "lastPrice": "6.1595",
                "prevPrice24h": "6.243",
                "price24hPcnt": "-0.0134",
                "highPrice24h": "6.2489",
                "lowPrice24h": "6.0057",
                "turnover24h": "1657196.9441261",
                "volume24h": "269309.763",
                "usdIndexPrice": "6.16014595"
            }
        ]
    },
    "retExtInfo": {},
    "time": 1692670060498
}

 

 

 

 

 

6.  bybit에서 제공하는 public api 결과값 데이터 모델 정의

 

// vim Models.kt

package com.example.practicekopring

data class ApiResponse(
    val retCode: Int,
    val retMsg: String,
    val result: Result,
    val retExtInfo: Map<String, Any>,
    val time: Long
)

data class Result(
    val category: String,
    val list: List<TickerDetail>
)

data class TickerDetail(
    val symbol: String,
    val bid1Price: String,
    val bid1Size: String,
    val ask1Price: String,
    val ask1Size:String,

    // 아래 필드들은 모든 ticker에 포함되지 않을 수 있으므로 nullable로 선언합니다.
    val lastPrice:String?,
    val prevPrice24h:String?,
    val price24hPcnt:String?,
    val highPrice24h:String?,
    val lowPrice24h:String?,
    val turnover24h:String?,
    val volume24h:String?,

    // 일부 ticker에만 존재하는 필드입니다.
    val usdIndexPrice :String?
)

 

 

 

 

 

7. bybit public api에 call하는 기능 구현

 

// vim ApiService.kt

package com.example.practicekopring

import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

@Service
class ApiService {

    private var restTemplate = RestTemplate()

    fun getTickers(): ApiResponse {
        return restTemplate.getForObject("https://api.bybit.com/v5/market/tickers?category=spot", ApiResponse::class.java)!!
    }
}

 

 

 

 

 

8. 확보한 데이터를 return하는 api 구현

 

// vim MyController.kt

package com.example.practicekopring

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class ApiController(private var apiService : ApiService) {

    @GetMapping("/tickers")
    fun getTickers(): ApiResponse {
        return apiService.getTickers()
    }
}

 

 

 

 

 

9. 서버 실행 및 api end point 접속 시도

 

 

 

 

 

 

10. 폴더 구조 변경 및 이름 변경

 

 

// vim BybitTickerManager.kt

package com.example.practicekopring.controllers

import com.example.practicekopring.models.BybitTickerRawData
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

@Service
class BybitTickerManager {

    private var restTemplate = RestTemplate()

    fun getTickers(): BybitTickerRawData {
        return restTemplate.getForObject("https://api.bybit.com/v5/market/tickers?category=spot", BybitTickerRawData::class.java)!!
    }
}

 

// vim BybitTickerRawData.kt

package com.example.practicekopring.models

data class BybitTickerRawData(
    val retCode: Int,
    val retMsg: String,
    val result: Result,
    val retExtInfo: Map<String, Any>,
    val time: Long
)

data class Result(
    val category: String,
    val list: List<TickerDetail>
)

data class TickerDetail(
    val symbol: String,
    val bid1Price: String,
    val bid1Size: String,
    val ask1Price: String,
    val ask1Size:String,

    // 아래 필드들은 모든 ticker에 포함되지 않을 수 있으므로 nullable로 선언합니다.
    val lastPrice:String?,
    val prevPrice24h:String?,
    val price24hPcnt:String?,
    val highPrice24h:String?,
    val lowPrice24h:String?,
    val turnover24h:String?,
    val volume24h:String?,

    // 일부 ticker에만 존재하는 필드입니다.
    val usdIndexPrice :String?
)

 

// vim BybitTicker.kt

package com.example.practicekopring.services

import com.example.practicekopring.controllers.BybitTickerManager
import com.example.practicekopring.models.BybitTickerRawData
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class BybitTicker(private var bybitSpotTicker : BybitTickerManager) {

    @GetMapping("/tickers")
    fun getTickers(): BybitTickerRawData {
        return bybitSpotTicker.getTickers()
    }
}

 

 - 이후 실행해도 정상적으로 기능 작동함을 확인합니다.

 

 

 

 

 

 

11. 사용하는 open api url을 잘못된 것으로 바꿔보서 콜해보기

 

 - 일부터 잘못된 value를 넣어 call하도록 소스코드 수정 후 콜을 해도 정상적으로 잘 작동합니다.

https://api.bybit.com/v5/market/tickers?category=wow

 

// vim BybitTickerManager.kt

package com.example.practicekopring.services

import com.example.practicekopring.models.BybitTickerRawData
import com.example.practicekopring.models.TickerDetail
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

@Service
class BybitTickerManager {

    private var restTemplate = RestTemplate()

    fun getTickers(): ApiResponse {
        return restTemplate.getForObject("https://api.bybit.com/v5/market/tickers?category=wow", ApiResponse::class.java)!!
    }
}

 

https://api.bybit999.com/v5/market/tickers?category=spot

 

 

 

 

 

12. category를 spot 외에 linear도 정보 가져오는 기능 구현하기

 

linear 정보를 가져오는 api 기능은, result 안에 있는 list 데이터만 노출하도록 해봅니다.

 

api로부터 가져오는 데이터 형태는 달라지지 않지만, 별도로 만들게 될 /bybit/linear api의 return 데이터 형태는 전체 데이터의 일부만 list로 가져오기 때문에 return되는 데이터셋 정의가 달라집니다.

 

 

// vim BybitTickerManager.kt

package com.example.practicekopring.controllers

import com.example.practicekopring.models.BybitTickerRawData
import com.example.practicekopring.models.TickerDetail
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

@Service
class BybitTickerManager {

    private var restTemplate = RestTemplate()

    private var exchangeBasicApi: String = "https://api.bybit.com/v5/market/tickers"

    fun getSpotTickers(): BybitTickerRawData {
        return restTemplate.getForObject("$exchangeBasicApi?category=spot", BybitTickerRawData::class.java)!!
    }

    fun getLinearTickers(): List<TickerDetail> {
        val response = restTemplate.getForObject("$exchangeBasicApi?category=linear", BybitTickerRawData::class.java)!!
        return response.result.list
    }
}

 

// vim BybitTicker.kt

package com.example.practicekopring.services

import com.example.practicekopring.controllers.BybitTickerManager
import com.example.practicekopring.models.BybitTickerRawData
import com.example.practicekopring.models.TickerDetail
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class BybitTicker(private var bybitTicker : BybitTickerManager) {

    @GetMapping("/tickers")
    fun getTickers(): BybitTickerRawData {
        return bybitTicker.getSpotTickers()
    }

    @GetMapping("/bybit/linear")
    fun getLinearTickers(): List<TickerDetail> {
        return bybitTicker.getLinearTickers()
    }
}

 

 

 

 

 

 

13.  endpoint 앞에 공통 api 추가 기능 구현

 

기존 /tickers 또는 /bybit/linear api가 아니라 

/v1/tickers 또는 /v1/bybit/linear api만 정상적으로 작동합니다.

 

// vim BybitTicker.kt

package com.example.practicekopring.services

import com.example.practicekopring.controllers.BybitTickerManager
import com.example.practicekopring.models.BybitTickerRawData
import com.example.practicekopring.models.TickerDetail
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.bind.annotation.RequestMapping

@RestController
@RequestMapping(value=["/v1"])
class BybitTicker(private var bybitTicker : BybitTickerManager) {

    @GetMapping("/tickers")
    fun getTickers(): BybitTickerRawData {
        return bybitTicker.getSpotTickers()
    }

    @GetMapping("/bybit/linear")
    fun getLinearTickers(): List<TickerDetail> {
        return bybitTicker.getLinearTickers()
    }
}

 

 

 

 

+ Recent posts