dm-180
This commit is contained in:
parent
7e936e4e05
commit
89da510a3f
2 changed files with 77 additions and 0 deletions
|
|
@ -1,6 +1,7 @@
|
|||
package com.example.login_v3.data.api
|
||||
|
||||
import com.example.login_v3.data.api.api_class.*
|
||||
import com.squareup.moshi.JsonClass
|
||||
import okhttp3.MultipartBody
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.Body
|
||||
|
|
@ -15,6 +16,11 @@ import retrofit2.http.Part
|
|||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RefreshRequest(
|
||||
val refresh_token: String
|
||||
)
|
||||
|
||||
interface TecnologiaApi {
|
||||
|
||||
@GET("api/health")
|
||||
|
|
@ -28,6 +34,9 @@ interface TecnologiaApi {
|
|||
@POST("api/auth/login")
|
||||
suspend fun login(@Body body: LoginRequest): Response<LoginResponse>
|
||||
|
||||
@POST("api/auth/refresh")
|
||||
suspend fun refreshToken(@Body body: RefreshRequest): Response<LoginResponse>
|
||||
|
||||
// 取得個人資料:通常需要 Token
|
||||
@GET("api/me")
|
||||
suspend fun getUserProfile(): Response<UserProfile>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
package com.example.login_v3.data.repository.basic
|
||||
|
||||
import com.example.login_v3.data.api.RefreshRequest
|
||||
import com.example.login_v3.data.api.TecnologiaApi
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.Authenticator
|
||||
import okhttp3.Request
|
||||
import okhttp3.Route
|
||||
import okhttp3.Response
|
||||
import javax.inject.Inject
|
||||
|
||||
class TokenAuthenticator @Inject constructor(
|
||||
private val tokenManager: TokenManager,
|
||||
// 💡 關鍵:使用 dagger.Lazy 避免 Hilt 的循環依賴 (Circular Dependency)
|
||||
private val apiService: dagger.Lazy<TecnologiaApi>
|
||||
) : Authenticator {
|
||||
|
||||
override fun authenticate(route: Route?, response: Response): Request? {
|
||||
// 1. 取得 DataStore 中的當前帳號資訊
|
||||
val refreshToken = runBlocking { tokenManager.currentRefreshToken.first() }
|
||||
val currentUserId = runBlocking { tokenManager.currentUserId.first() }
|
||||
|
||||
if (refreshToken.isNullOrEmpty() || currentUserId.isNullOrEmpty()) {
|
||||
return null // 沒有憑證資訊,不重試,直接拋出原 401 錯誤
|
||||
}
|
||||
|
||||
// 2. 同步發送網路請求,拿 refresh_token 去換新 Token
|
||||
val refreshResponse = runBlocking {
|
||||
try {
|
||||
// 用 apiService.get() 來取得真正需要用的實例
|
||||
apiService.get().refreshToken(RefreshRequest(refreshToken))
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 判斷刷新是否成功
|
||||
if (refreshResponse != null && refreshResponse.isSuccessful) {
|
||||
val newAuthData = refreshResponse.body()
|
||||
if (newAuthData != null) {
|
||||
|
||||
// 4. 成功:更新本地 DataStore 資料
|
||||
runBlocking {
|
||||
tokenManager.saveAuthData(
|
||||
userId = currentUserId,
|
||||
accessToken = newAuthData.access_token,
|
||||
refreshToken = newAuthData.refresh_token,
|
||||
expiresInSec = newAuthData.expires_in
|
||||
)
|
||||
}
|
||||
|
||||
// 5. 成功:用新的 Access Token 重新建構剛才失敗的 API 請求
|
||||
return response.request.newBuilder()
|
||||
.header("Authorization", "Bearer ${newAuthData.access_token}")
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 失敗:如果連 Refresh Token 都失效(例如過期被後端拒絕),則強制登出,導回登入頁
|
||||
runBlocking {
|
||||
tokenManager.logout(currentUserId)
|
||||
// 這裡可以透過 EventBus、SharedFlow 或 LiveData 貼出全域通知,要求 UI 跳轉到 LoginActivity/LoginScreen
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue