1. 참고하면 좋은 이전 포스팅
https://growingsaja.tistory.com/953
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()
}
}