This commit is contained in:
Eric85710 2026-06-30 16:05:50 +08:00
parent 89da510a3f
commit f71cb04704

View file

@ -2,6 +2,7 @@ package com.example.login_v3.data.di
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.example.login_v3.data.api.TecnologiaApi import com.example.login_v3.data.api.TecnologiaApi
import com.example.login_v3.data.repository.basic.TokenAuthenticator
import com.example.login_v3.data.repository.basic.TokenManager import com.example.login_v3.data.repository.basic.TokenManager
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
@ -12,7 +13,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.converter.moshi.MoshiConverterFactory
@ -32,39 +35,49 @@ object NetworkModule {
.build() .build()
} }
// TokenInterceptor
@Provides @Provides
@Singleton @Singleton
fun provideOkHttpClient(tokenManager: TokenManager): OkHttpClient { fun provideTokenInterceptor(tokenManager: TokenManager): Interceptor {
return Interceptor { chain ->
val originalRequest = chain.request()
val path = originalRequest.url.encodedPath
// 如果是認證相關的 API,直接放行,不附加 Token Header
if (path.contains("api/auth/login") ||
path.contains("api/auth/register") ||
path.contains("api/auth/refresh") ||
path.contains("api/health")) {
return@Interceptor chain.proceed(originalRequest)
}
// 在背景執行緒中安全地獲取當前 Token
val token = runBlocking(Dispatchers.IO) {
tokenManager.currentAccessToken.firstOrNull()
}
val requestBuilder = originalRequest.newBuilder()
if (!token.isNullOrEmpty()) {
requestBuilder.addHeader("Authorization", "Bearer $token")
}
// ⚠️ 注意:這裡不再手動處理 401 logout,純粹放行交給 Authenticator 處理
chain.proceed(requestBuilder.build())
}
}
// 2. 配置 OkHttpClient,把 Interceptor 和 Authenticator 綁進去
@Provides
@Singleton
fun provideOkHttpClient(
tokenInterceptor: Interceptor,
tokenAuthenticator: TokenAuthenticator // 👈 注入前面實作好的 Authenticator
): OkHttpClient {
return OkHttpClient.Builder() return OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS) .connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS)
.addInterceptor { chain -> .addInterceptor(tokenInterceptor) // 負責幫一般請求加上 Token
// 在 OkHttp 的背景執行緒中安全地進行同步等待 .authenticator(tokenAuthenticator) // 負責在 401 時自動刷新 Token
val (token, userId) = runBlocking(Dispatchers.IO) {
val t = tokenManager.currentAccessToken.first()
val id = tokenManager.currentUserId.first()
t to id
}
val requestBuilder = chain.request().newBuilder()
token?.let {
requestBuilder.addHeader("Authorization", "Bearer $it")
}
val response = chain.proceed(requestBuilder.build())
// 處理 Token 失效 (401)
if (response.code == 401) {
userId?.let { id ->
// 使用 Dispatchers.IO 確保不會卡死主執行緒
runBlocking(Dispatchers.IO) {
tokenManager.logout(id)
}
}
}
response
}
.build() .build()
} }