-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[REFACTOR] 데이터 레이어 다른 계층 참조하지 않도록 코드 분리 #340
Changes from 8 commits
a2d3c2b
b609e6c
8b882b7
123e7e7
60adff3
3e8d971
1aebc18
2ac7be5
0c210d0
ead0369
60bbd81
a5eab3d
b3267c8
625d913
e9c245b
0af3516
a390166
7315ecc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package com.runnect.runnect.data.service | ||
|
||
import com.runnect.runnect.application.ApplicationClass | ||
import com.runnect.runnect.application.PreferenceManager | ||
import com.runnect.runnect.data.dto.response.ResponseGetRefreshToken | ||
import com.runnect.runnect.data.dto.response.base.BaseResponse | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.runBlocking | ||
import kotlinx.serialization.decodeFromString | ||
import kotlinx.serialization.json.Json | ||
import okhttp3.Interceptor | ||
import okhttp3.Request | ||
import okhttp3.RequestBody.Companion.toRequestBody | ||
import okhttp3.Response | ||
import timber.log.Timber | ||
import javax.inject.Inject | ||
|
||
class AuthInterceptor @Inject constructor( | ||
private val json: Json | ||
) : Interceptor { | ||
// access Header 에 보내고 이때 401(토큰 만료) 뜨면 액세스 재발급 요청 | ||
// 재발급 성공 : 저장 | ||
// 재발급 실패 : 재 로그인 토스트 메시지 띄우고 preference 빈 값 넣고 로그인 화면 이동 | ||
override fun intercept(chain: Interceptor.Chain): Response { | ||
runBlocking { Timber.e("AccessToken : ${getAccessToken()}, RefreshToken : ${getRefreshToken()}") } | ||
val originalRequest = chain.request() | ||
|
||
val headerRequest = originalRequest.newAuthTokenBuilder() | ||
.build() | ||
|
||
val response = headerRequest.let { chain.proceed(it) } | ||
|
||
when (response.code) { | ||
CODE_TOKEN_EXPIRED -> { | ||
try { | ||
Timber.e("Access Token Expired: getNewAccessToken") | ||
response.close() | ||
return handleTokenExpired(chain, originalRequest, headerRequest) | ||
} catch (t: Throwable) { | ||
Timber.e("Exception: ${t.message}") | ||
saveToken("", "") | ||
} | ||
} | ||
} | ||
return response | ||
} | ||
|
||
private fun Request.newAuthTokenBuilder() = | ||
runBlocking(Dispatchers.IO) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분 runBlocking 쓰신 이유가 있나요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저 때는 해당 작업이 완료될때까지 스레드를 막으려고 runBlocking을 사용하긴 했습니다 ! 다만 runBlocking이 아예 차단이라는 점에서 스레드를 점유하니까 좋진 않을거라고 작성할때도 생각하긴했습니다 .. .ㅎ ㅎ 처음에는 suspend로 작성했었는데 그러면 newAuthTokenBuilder를 사용하는 okhttp3 intercept까지 suspend func이 되어야 하는데, 이를 지원하지 않더라구요 😭 그래서 차선책으로 사용했습니다!! 지금 생각해보니 저 동작이 꼭 비동기여야할 필요는 없다고 생각이 드는데 어떻게 생각하시나요 ? 아니면 혹시 runBlocking을 쓰지 않고 다르게 처리할 방법이 있는지 궁금합니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혹시 runBlocking을 작성하지 않았을 때 제대로 동작하지 않았나요?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 처음에 구현할땐 저도 필요없다고 생각했어서 작성하지 않았는데 동작이 안됐어서 runBlocking을 사용했는데, 지금 제거하고 보니 또 잘 가져오네요 🥹 다른 부분이 잘못됐었는데 불필요하게 같이 고쳐준 것 같아요 저도 runBlocking이 여전히 불필요하다고 생각합니다 ㅎㅎㅎ 없애서 수정할게요 ! |
||
val accessToken = getAccessToken() | ||
val refreshToken = getRefreshToken() | ||
newBuilder().apply { | ||
addHeader(ACCESS_TOKEN, accessToken) | ||
addHeader(REFRESH_TOKEN, refreshToken) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분 더 쪼개서 구조를 아래처럼 바꿔보는 것은 어떨까요? 개인적으로 가독성을 더 높일 수 있다고 생각합니다.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵 처음에 구현할 때 응답코드에 따른 다른 처리가 있을까봐 미리 when문으로 짜둔 건데 일단 지금은 불필요한것 같네요 !! 감사합니당 다만 말씀하신 의도가 return if로 response를 직접 리턴해주자는 게 맞나요 ? 쪼갠다는 맥락에 해당하는지는 잘모르겠어서 (ead0369) 에 수정해 푸쉬했으니 한번 확인부탁드려요 ~! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. return if를 써보자는 것보다는 if문 안이 true일 때랑 false일 때 어떤 일이 일어나는지 간략하게 보여주고 구체적인 logic은 호출하는 함수 내부로 숨겨보자는 의미였습니다! (캡슐화) |
||
|
||
|
||
private fun getAccessToken(): String { | ||
return PreferenceManager.getString( | ||
ApplicationClass.appContext, | ||
TOKEN_KEY_ACCESS | ||
) ?: "" | ||
} | ||
|
||
private fun getRefreshToken(): String { | ||
return PreferenceManager.getString( | ||
ApplicationClass.appContext, | ||
TOKEN_KEY_REFRESH | ||
) ?: "" | ||
} | ||
|
||
private fun saveToken(accessToken: String, refreshToken: String) { | ||
PreferenceManager.setString(ApplicationClass.appContext, TOKEN_KEY_ACCESS, accessToken) | ||
PreferenceManager.setString(ApplicationClass.appContext, TOKEN_KEY_REFRESH, refreshToken) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분 AuthUtils라는 SharedPreference 값 set/get 하는 목적의 유틸 클래스 하나 만들어서 거기로 옮기는 건 어떨까요? Login 쪽에서 getAccessToken() 함수 호출하는 부분이 있던데 유틸로 만들면 각각이 쓰여지는 부분을 좀 더 간결하게 만들 수 있을 것 같아서요 |
||
|
||
private fun handleTokenExpired( | ||
chain: Interceptor.Chain, | ||
originalRequest: Request, | ||
headerRequest: Request | ||
): Response { | ||
val refreshTokenResponse = getRefreshToken(originalRequest, chain) | ||
return if (refreshTokenResponse.isSuccessful) { | ||
handleGetRefreshTokenSuccess(refreshTokenResponse, originalRequest, chain) | ||
} else { | ||
handleGetRefreshTokenFailure(refreshTokenResponse, headerRequest, chain) | ||
} | ||
} | ||
|
||
private fun getRefreshToken(originalRequest: Request, chain: Interceptor.Chain): Response { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. accessToken을 재발급 받는 api인데 개인적으로는 이름이 getRefreshToken으로 돼있어서 조금 헷갈리는 것 같아 혹시 이름을 api endpoint와 통일해서 getNewToken으로 해보는 건 어떨까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ㅎㅎㅎ 공감합니다 전 코드가 refresh길래 일단 남겨둔거였어요 : ) 수정했습니다 ! |
||
val baseUrl = ApplicationClass.getBaseUrl() | ||
val refreshToken = getRefreshToken() | ||
val refreshTokenRequest = originalRequest.newBuilder().post("".toRequestBody()) | ||
.url("$baseUrl/api/auth/getNewToken") | ||
.addHeader(REFRESH_TOKEN, refreshToken) | ||
.build() | ||
|
||
return chain.proceed(refreshTokenRequest) | ||
} | ||
|
||
private fun handleGetRefreshTokenSuccess( | ||
refreshTokenResponse: Response, | ||
originalRequest: Request, | ||
chain: Interceptor.Chain | ||
): Response { | ||
refreshTokenResponse.use { response -> | ||
val responseToken = json.decodeFromString<BaseResponse<ResponseGetRefreshToken>>( | ||
response.body?.string().orEmpty() | ||
) | ||
responseToken.data?.data?.let { | ||
Timber.e("New Refresh Token Success: ${it.refreshToken}") | ||
saveToken(it.accessToken, it.refreshToken) | ||
} | ||
} | ||
|
||
val newRequest = originalRequest.newAuthTokenBuilder().build() | ||
return chain.proceed(newRequest) | ||
} | ||
|
||
private fun handleGetRefreshTokenFailure( | ||
refreshTokenResponse: Response, | ||
headerRequest: Request, | ||
chain: Interceptor.Chain | ||
): Response { | ||
Timber.e("New Refresh Token Failure: ${refreshTokenResponse.code}") | ||
saveToken("", "") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 상황이 조금 더 직관적으로 파악될 수 있게 named argument 써보시는 건 어떨까요? |
||
return chain.proceed(headerRequest) | ||
} | ||
|
||
|
||
companion object { | ||
private const val ACCESS_TOKEN = "accessToken" | ||
private const val CODE_TOKEN_EXPIRED = 401 | ||
private const val REFRESH_TOKEN = "refreshToken" | ||
|
||
const val TOKEN_KEY_ACCESS = "access" | ||
const val TOKEN_KEY_REFRESH = "refresh" | ||
} | ||
|
||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
package com.runnect.runnect.di | ||
|
||
import com.google.android.gms.auth.api.Auth | ||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory | ||
import com.runnect.runnect.BuildConfig | ||
import com.runnect.runnect.application.ApplicationClass | ||
|
@@ -16,6 +17,7 @@ import dagger.hilt.components.SingletonComponent | |
import kotlinx.coroutines.InternalCoroutinesApi | ||
import kotlinx.serialization.ExperimentalSerializationApi | ||
import kotlinx.serialization.json.Json | ||
import okhttp3.Interceptor | ||
import okhttp3.MediaType.Companion.toMediaType | ||
import okhttp3.OkHttpClient | ||
import okhttp3.logging.HttpLoggingInterceptor | ||
|
@@ -34,14 +36,20 @@ object RetrofitModule { | |
@Retention(AnnotationRetention.BINARY) | ||
annotation class Tmap | ||
|
||
@Qualifier | ||
@Retention(AnnotationRetention.BINARY) | ||
annotation class Auth | ||
|
||
@Provides | ||
@Singleton | ||
fun provideOkHttpClient( | ||
logger: HttpLoggingInterceptor, | ||
appInterceptor: AppInterceptor, | ||
tokenAuthenticator: TokenAuthenticator | ||
): OkHttpClient = OkHttpClient.Builder().addInterceptor(logger).addInterceptor(appInterceptor) | ||
.authenticator(tokenAuthenticator).build() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 인터셉터 하나로 합치신 거 좋네요 굿 |
||
logger : HttpLoggingInterceptor, | ||
@Auth authInterceptor: Interceptor | ||
): OkHttpClient = OkHttpClient.Builder() | ||
.addInterceptor(logger) | ||
.addInterceptor(authInterceptor) | ||
.build() | ||
|
||
|
||
@Provides | ||
@Singleton | ||
|
@@ -51,12 +59,9 @@ object RetrofitModule { | |
|
||
@Provides | ||
@Singleton | ||
fun provideAppInterceptor(): AppInterceptor = AppInterceptor() | ||
@Auth | ||
fun provideAuthInterceptor(interceptor: AuthInterceptor): Interceptor = interceptor | ||
|
||
@Provides | ||
@Singleton | ||
fun provideTokenAuthenticator(): TokenAuthenticator = | ||
TokenAuthenticator(ApplicationClass.appContext) | ||
|
||
@OptIn(ExperimentalSerializationApi::class, InternalCoroutinesApi::class) | ||
@Provides | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
로그 찍는 이 부분을 runBlocking으로 감싸신 이유가 있나요?