Skip to content

Commit 68b75c4

Browse files
authored
Merge pull request #211 from Jusicool-Ver-2-0/feature/182-stock-current-price-connect
🔀 :: (#182) - stock current price connect
2 parents de2c036 + 744fa10 commit 68b75c4

12 files changed

Lines changed: 255 additions & 137 deletions

File tree

‎data/model/src/main/java/com/jusicool/model/koreaInvestment/toCandleEntity.kt‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ package com.jusicool.model.koreaInvestment
22

33
import StockMinuteCandle
44
import com.jusicool.entity.price.MinuteCandleEntity
5-
import com.jusicool.utils.parseDateTime
5+
import com.jusicool.utils.parseStockDateTime
66

77
fun StockMinuteCandle.toCandleEntity(): MinuteCandleEntity? {
88
return try {
9-
val dateTime = parseDateTime(date, time)
9+
val dateTime = parseStockDateTime(date, time)
1010
MinuteCandleEntity(
1111
dateTime = dateTime,
1212
openPrice = openingPrice.toLong().toDouble(),
@@ -23,7 +23,7 @@ fun StockMinuteCandle.toCandleEntity(): MinuteCandleEntity? {
2323

2424
fun StockMinuteDetail.toCandleEntity(): MinuteCandleEntity? {
2525
return try {
26-
val dateTime = parseDateTime(baseDate, baseHour)
26+
val dateTime = parseStockDateTime(baseDate, baseHour)
2727
MinuteCandleEntity(
2828
dateTime = dateTime,
2929
openPrice = openPrice.toLong().toDouble(),

‎data/model/src/main/java/com/jusicool/model/mapper/crypto/CurrentMinuteCandleMapper.kt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ package com.jusicool.model.mapper.crypto
22

33
import com.jusicool.entity.price.MinuteCandleEntity
44
import com.jusicool.model.crypto.CurrentMinuteCandleResponse
5-
import com.jusicool.utils.parseDateTime
5+
import com.jusicool.utils.parseUpbitDateTime
66

77
fun CurrentMinuteCandleResponse.toModel(): MinuteCandleEntity =
88
MinuteCandleEntity(
9-
dateTime = parseDateTime(candleDateTimeKst),
9+
dateTime = parseUpbitDateTime(candleDateTimeKst),
1010
openPrice = this.openingPrice,
1111
highPrice = this.highPrice,
1212
lowPrice = this.lowPrice,

‎data/model/src/main/java/com/jusicool/model/mapper/crypto/MinuteCandleMapper.kt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ package com.jusicool.model.mapper.crypto
22

33
import com.jusicool.entity.price.MinuteCandleEntity
44
import com.jusicool.model.crypto.MinuteCandleResponse
5-
import com.jusicool.utils.parseDateTime
5+
import com.jusicool.utils.parseUpbitDateTime
66

77
fun MinuteCandleResponse.toModel(): MinuteCandleEntity =
88
MinuteCandleEntity(
9-
dateTime = parseDateTime(candleDateTimeKst),
9+
dateTime = parseUpbitDateTime(candleDateTimeKst),
1010
openPrice = openingPrice,
1111
highPrice = highPrice,
1212
lowPrice = lowPrice,

‎data/network/src/main/java/com/jusicool/network/util/KoreaInvestmentAuthManager.kt‎

Lines changed: 76 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.jusicool.network.util
22

33
import android.content.Context
4+
import android.util.Log
45
import com.jusicool.model.koreaInvestment.AccessKeyRequest
56
import com.jusicool.model.koreaInvestment.AccessTokenRequest
67
import com.jusicool.network.BuildConfig
@@ -18,87 +19,100 @@ class KoreaInvestmentAuthManager @Inject constructor(
1819
) {
1920
private val prefs = context.getSharedPreferences("auth_prefs", Context.MODE_PRIVATE)
2021

21-
// Mutex 객체 생성
2222
private val tokenMutex = Mutex()
2323
private val approvalKeyMutex = Mutex()
2424

25-
private var accessToken: String? = null
26-
private var accessTokenExpiresAt: Long = 0L
27-
28-
private var approvalKey: String? = null
29-
private var approvalKeyExpiresAt: Long = 0L
30-
3125
suspend fun getValidAccessToken(forceRefresh: Boolean = false): String {
3226
return tokenMutex.withLock {
33-
val now = System.currentTimeMillis()
34-
35-
if (!forceRefresh && accessToken != null && now < accessTokenExpiresAt) {
36-
return accessToken!!
37-
}
38-
39-
val storedToken = prefs.getString("access_token", null)
40-
val storedExpires = prefs.getLong("access_token_expires_at", 0L)
27+
try {
28+
val now = System.currentTimeMillis()
29+
30+
val storedToken = prefs.getString("access_token", null)
31+
val storedExpires = prefs.getLong("access_token_expires_at", 0L)
32+
33+
if (!forceRefresh && storedToken != null && now < storedExpires) {
34+
Log.d("AuthManager", "✅ SharedPreferences 캐시된 AccessToken 사용: $storedToken")
35+
return storedToken
36+
}
37+
38+
Log.d("AuthManager", "🔄 API 요청으로 AccessToken 갱신")
39+
val response = koreaInvestmentApi.getAccessToken(
40+
AccessTokenRequest(
41+
grantType = "client_credentials",
42+
appKey = BuildConfig.KOREAINVESTMENT_API_KEY,
43+
secretKey = BuildConfig.KOREAINVESTMENT_APP_SECRET,
44+
)
45+
)
4146

42-
if (!forceRefresh && storedToken != null && now < storedExpires) {
43-
accessToken = storedToken
44-
accessTokenExpiresAt = storedExpires
45-
return storedToken
46-
}
47+
val newToken = response.accessToken
48+
val expiresAt = (now + (response.expiresIn * 1000L) - 5 * 60 * 1000L).toLong()
4749

48-
val response = koreaInvestmentApi.getAccessToken(
49-
AccessTokenRequest(
50-
grantType = "client_credentials",
51-
appKey = BuildConfig.KOREAINVESTMENT_API_KEY,
52-
secretKey = BuildConfig.KOREAINVESTMENT_APP_SECRET,
53-
)
54-
)
50+
prefs.edit().apply {
51+
putString("access_token", newToken)
52+
putLong("access_token_expires_at", expiresAt)
53+
}.apply()
5554

56-
accessToken = response.accessToken
57-
accessTokenExpiresAt = (now + (response.expiresIn * 1000L) - 5 * 60 * 1000L).toLong()
55+
Log.d("AuthManager", "✅ 새 AccessToken 저장 및 반환: $newToken, 만료시간: $expiresAt")
56+
return newToken
57+
} catch (e: Exception) {
58+
Log.e("AuthManager", "❌ AccessToken 갱신 실패: ${e.message}", e)
5859

59-
prefs.edit().apply {
60-
putString("access_token", accessToken)
61-
putLong("access_token_expires_at", accessTokenExpiresAt)
62-
}.apply()
60+
// 기존 저장된 토큰이라도 있으면 반환
61+
val fallbackToken = prefs.getString("access_token", null)
62+
if (fallbackToken != null) {
63+
Log.w("AuthManager", "⚠️ API 실패, 캐시된 AccessToken 반환: $fallbackToken")
64+
return fallbackToken
65+
}
6366

64-
return accessToken!!
67+
throw e // 진짜로 토큰이 없으면 예외 던짐
68+
}
6569
}
6670
}
71+
6772
suspend fun getApprovalKey(forceRefresh: Boolean = false): String {
6873
return approvalKeyMutex.withLock {
69-
val now = System.currentTimeMillis()
74+
try {
75+
val now = System.currentTimeMillis()
76+
77+
val storedKey = prefs.getString("approval_key", null)
78+
val storedExpires = prefs.getLong("approval_key_expires_at", 0L)
79+
80+
if (!forceRefresh && storedKey != null && now < storedExpires) {
81+
Log.d("AuthManager", "✅ SharedPreferences 캐시된 ApprovalKey 사용: $storedKey")
82+
return storedKey
83+
}
84+
85+
Log.d("AuthManager", "🔄 API 요청으로 ApprovalKey 갱신")
86+
val response = koreaInvestmentApi.getWebSocketAccessToken(
87+
AccessKeyRequest(
88+
grantType = "client_credentials",
89+
appKey = BuildConfig.KOREAINVESTMENT_API_KEY,
90+
secretKey = BuildConfig.KOREAINVESTMENT_APP_SECRET,
91+
)
92+
)
7093

71-
if (!forceRefresh && approvalKey != null && now < approvalKeyExpiresAt) {
72-
return approvalKey!!
73-
}
94+
val newKey = response.approvalKey
95+
val expiresAt = now + 24 * 60 * 60 * 1000L
7496

75-
val storedKey = prefs.getString("approval_key", null)
76-
val storedExpires = prefs.getLong("approval_key_expires_at", 0L)
97+
prefs.edit().apply {
98+
putString("approval_key", newKey)
99+
putLong("approval_key_expires_at", expiresAt)
100+
}.apply()
77101

78-
if (!forceRefresh && storedKey != null && now < storedExpires) {
79-
approvalKey = storedKey
80-
approvalKeyExpiresAt = storedExpires
81-
return storedKey
82-
}
102+
Log.d("AuthManager", "✅ 새 ApprovalKey 저장 및 반환: $newKey, 만료시간: $expiresAt")
103+
return newKey
104+
} catch (e: Exception) {
105+
Log.e("AuthManager", "❌ ApprovalKey 갱신 실패: ${e.message}", e)
83106

84-
val response = koreaInvestmentApi.getWebSocketAccessToken(
85-
AccessKeyRequest(
86-
grantType = "client_credentials",
87-
appKey = BuildConfig.KOREAINVESTMENT_API_KEY,
88-
secretKey = BuildConfig.KOREAINVESTMENT_APP_SECRET,
89-
)
90-
)
107+
// 기존 저장된 키라도 있으면 반환
108+
val fallbackKey = prefs.getString("approval_key", null)
109+
if (fallbackKey != null) {
110+
Log.w("AuthManager", "⚠️ API 실패, 캐시된 ApprovalKey 반환: $fallbackKey")
111+
return fallbackKey
112+
}
91113

92-
approvalKey = response.approvalKey
93-
approvalKeyExpiresAt = now + 24 * 60 * 60 * 1000L
94-
95-
prefs.edit().apply {
96-
putString("approval_key", approvalKey)
97-
putLong("approval_key_expires_at", approvalKeyExpiresAt)
98-
}.apply()
99-
100-
return approvalKey!!
114+
throw e // 진짜로 키가 없으면 예외 던짐
115+
}
101116
}
102117
}
103118
}
104-

‎data/utils/src/main/java/com/jusicool/utils/parseDateTime.kt‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,12 @@ fun parseDateTime(dateTime: String): LocalDateTime {
1212
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
1313
return LocalDateTime.parse(dateTime, formatter)
1414
}
15+
16+
fun parseUpbitDateTime(dateTime: String): LocalDateTime {
17+
return LocalDateTime.parse(dateTime, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
18+
}
19+
20+
fun parseStockDateTime(date: String, time: String): LocalDateTime {
21+
val formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss")
22+
return LocalDateTime.parse(date + time.padStart(6, '0'), formatter)
23+
}

domain/usecase/src/main/java/com/jusicool/usecase/crypto/GetCurrentMinuteCandleUseCase.kt renamed to domain/usecase/src/main/java/com/jusicool/usecase/crypto/GetCurrentCryptoMinuteCandleUseCase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import java.time.LocalDateTime
77
import java.time.format.DateTimeFormatter
88
import javax.inject.Inject
99

10-
class GetCurrentMinuteCandleUseCase @Inject constructor(
10+
class GetCurrentCryptoMinuteCandleUseCase @Inject constructor(
1111
private val cryptoRepository: CryptoRepository
1212
) {
1313
operator fun invoke(market: String): Flow<List<MinuteCandleEntity>> {

‎domain/usecase/src/main/java/com/jusicool/usecase/holding/GetHoldingWithCurrentPriceUseCase.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class GetHoldingWithCurrentPriceUseCase @Inject constructor(
7171
private fun getCryptoPriceFlow(cryptoMarkets: List<String>): Flow<List<AssetsCurrentPrice>> {
7272
if (cryptoMarkets.isEmpty()) return flowOf(emptyList())
7373

74-
return tickerFlow(200)
74+
return tickerFlow(500)
7575
.flatMapLatest {
7676
getCurrentCryptoPriceUseCase(cryptoMarkets)
7777
}

domain/usecase/src/main/java/com/jusicool/usecase/koreaInvestment/GetTodayMinuteChartUseCase.kt renamed to domain/usecase/src/main/java/com/jusicool/usecase/koreaInvestment/GetCurrentStockMinuteChartUseCase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import java.time.LocalTime
99
import java.time.format.DateTimeFormatter
1010
import javax.inject.Inject
1111

12-
class GetTodayMinuteChartUseCase @Inject constructor(
12+
class GetCurrentStockMinuteChartUseCase @Inject constructor(
1313
private val koreaInvestmentRepository: KoreaInvestmentRepository
1414
) {
1515
operator fun invoke(

‎domain/usecase/src/main/java/com/jusicool/usecase/koreaInvestment/ObserveRealtimeStockPriceUseCase.kt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ import javax.inject.Inject
88
class ObserveRealtimeStockPriceUseCase @Inject constructor(
99
private val wsKoreaInvestmentRepository: WsKoreaInvestmentRepository
1010
) {
11-
operator fun invoke(stockCode: List<String>): Flow<List<AssetsCurrentPrice>> =
12-
wsKoreaInvestmentRepository.observeStockTicker(stockCode)
11+
operator fun invoke(stockCodes: List<String>): Flow<List<AssetsCurrentPrice>> =
12+
wsKoreaInvestmentRepository.observeStockTicker(stockCodes)
1313
}

‎feature/account/src/main/java/com/jusicool/account/viewModel/AccountViewModel.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ internal class AccountViewModel @Inject constructor(
7272
}
7373
.stateIn(
7474
scope = viewModelScope,
75-
started = SharingStarted.WhileSubscribed(5000),
75+
started = SharingStarted.WhileSubscribed(),
7676
initialValue = GetHoldingsPriceUiState.Loading
7777
)
7878
}

0 commit comments

Comments
 (0)