add delta sync for dm-v21
This commit is contained in:
parent
5eb1125840
commit
177c888c84
4 changed files with 72 additions and 24 deletions
|
|
@ -22,10 +22,10 @@ data class RefreshRequest(
|
|||
)
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RefreshResponse(
|
||||
val access_token: String,
|
||||
val expires_in: Int,
|
||||
val refresh_token: String,
|
||||
val token: String? = null // 💡 設為可空並給預設值,防止後端沒吐時崩潰
|
||||
val access_token: String? = null,
|
||||
val expires_in: Int? = null,
|
||||
val refresh_token: String? = null,
|
||||
val token: String? = null // 💡 有些後端會直接吐 token 欄位
|
||||
)
|
||||
|
||||
interface TecnologiaApi {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ object NetworkModule {
|
|||
tokenManager.currentAccessToken.firstOrNull()
|
||||
}
|
||||
|
||||
android.util.Log.d("AuthDebug", "Interceptor - 路徑: $path, Token是否存在: ${!token.isNullOrEmpty()}")
|
||||
|
||||
val requestBuilder = originalRequest.newBuilder()
|
||||
if (!token.isNullOrEmpty()) {
|
||||
requestBuilder.addHeader("Authorization", "Bearer $token")
|
||||
|
|
|
|||
|
|
@ -25,7 +25,15 @@ class TokenAuthenticator @Inject constructor(
|
|||
}
|
||||
|
||||
override fun authenticate(route: Route?, response: Response): Request? {
|
||||
Log.d("AuthDebug", "--- 進入 Authenticator (觸發網址: ${response.request.url}) ---")
|
||||
val url = response.request.url
|
||||
Log.d("AuthDebug", "--- 進入 Authenticator (觸發網址: $url) ---")
|
||||
|
||||
// 🌟 防禦機制:如果刷新 Token 的請求本身又回傳了 401,代表 Refresh Token 已徹底過期
|
||||
// 必須直接回傳 null,否則會進入無窮迴圈(甚至死鎖,因為 Mutex 不可重入)
|
||||
if (url.encodedPath.contains("api/auth/refresh")) {
|
||||
Log.e("AuthDebug", "❌ 刷新 Token 的請求本身又回傳了 401!停止重試。")
|
||||
return null
|
||||
}
|
||||
|
||||
// 使用 runBlocking 配合 Mutex,讓同時間進來的 401 請求排隊
|
||||
return runBlocking {
|
||||
|
|
@ -35,13 +43,24 @@ class TokenAuthenticator @Inject constructor(
|
|||
val currentTokenInStore = tokenManager.currentAccessToken.first()
|
||||
val oldRequestToken = response.request.header("Authorization")?.removePrefix("Bearer ")
|
||||
|
||||
Log.d("AuthDebug", "DataStore中的Token: ${currentTokenInStore?.take(10)}...")
|
||||
Log.d("AuthDebug", "原始請求的Token: ${oldRequestToken?.take(10)}...")
|
||||
|
||||
// 🌟 防禦機制:如果 DataStore 裡的 Token 已經跟當初發起請求時的舊 Token 不同了
|
||||
// 代表排在你前面的請求已經幫忙刷新成功了!這裡直接拿新 Token 重試即可,不用再打一次 API。
|
||||
if (currentTokenInStore != oldRequestToken && !currentTokenInStore.isNullOrEmpty()) {
|
||||
Log.d("AuthDebug", "分流防禦:偵測到其他請求已刷新過 Token,直接重複利用新 Token")
|
||||
return@runBlocking response.request.newBuilder()
|
||||
.header("Authorization", "Bearer $currentTokenInStore")
|
||||
val requestBuilder = response.request.newBuilder()
|
||||
requestBuilder.header("Authorization", "Bearer $currentTokenInStore")
|
||||
|
||||
// 針對 WebSocket 更新 URL 中的 token 參數
|
||||
if (response.request.url.queryParameter("token") != null) {
|
||||
val newUrl = response.request.url.newBuilder()
|
||||
.setQueryParameter("token", currentTokenInStore)
|
||||
.build()
|
||||
requestBuilder.url(newUrl)
|
||||
}
|
||||
return@runBlocking requestBuilder.build()
|
||||
}
|
||||
|
||||
// 2. 真正準備去打後端刷新
|
||||
|
|
@ -54,33 +73,58 @@ class TokenAuthenticator @Inject constructor(
|
|||
}
|
||||
|
||||
// 3. 同步發送網路請求
|
||||
Log.d("AuthDebug", "正在向伺服器請求刷新 Token... RefreshToken: ${refreshToken.take(10)}...")
|
||||
val refreshResponse = try {
|
||||
apiService.get().refreshToken(RefreshRequest(refreshToken))
|
||||
} catch (e: Exception) {
|
||||
Log.e("AuthDebug", "打刷新 API 發生網路異常: ${e.message}")
|
||||
Log.e("AuthDebug", "打刷新 API 發生網路異常: ${e.message}", e)
|
||||
null
|
||||
}
|
||||
|
||||
// 4. 判斷刷新是否成功
|
||||
if (refreshResponse != null && refreshResponse.isSuccessful) {
|
||||
if (refreshResponse != null) {
|
||||
Log.d("AuthDebug", "刷新回應碼: ${refreshResponse.code()}, 是否成功: ${refreshResponse.isSuccessful}")
|
||||
if (refreshResponse.isSuccessful) {
|
||||
val newAuthData = refreshResponse.body()
|
||||
if (newAuthData != null) {
|
||||
Log.d("AuthDebug", "✅ Token 刷新成功,準備寫入 DataStore")
|
||||
val newAccessToken = newAuthData?.access_token ?: newAuthData?.token
|
||||
|
||||
if (!newAccessToken.isNullOrEmpty()) {
|
||||
Log.d("AuthDebug", "✅ Token 刷新成功,新 AccessToken: ${newAccessToken.take(10)}...")
|
||||
|
||||
// 如果後端沒給新的 refresh_token,就沿用舊的
|
||||
val nextRefreshToken = if (!newAuthData?.refresh_token.isNullOrEmpty()) {
|
||||
newAuthData?.refresh_token!!
|
||||
} else {
|
||||
refreshToken
|
||||
}
|
||||
|
||||
// 導正順序:確實執行完 saveAuthData
|
||||
tokenManager.saveAuthData(
|
||||
userId = currentUserId,
|
||||
accessToken = newAuthData.access_token,
|
||||
refreshToken = newAuthData.refresh_token,
|
||||
expiresInSec = newAuthData.expires_in
|
||||
accessToken = newAccessToken,
|
||||
refreshToken = nextRefreshToken,
|
||||
expiresInSec = newAuthData?.expires_in ?: 3600
|
||||
)
|
||||
|
||||
Log.d("AuthDebug", "✅ DataStore 寫入完成,重新發送原請求")
|
||||
|
||||
return@runBlocking response.request.newBuilder()
|
||||
.header("Authorization", "Bearer ${newAuthData.access_token}")
|
||||
// 修正:同步更新 Header 與 URL 中的 token 參數 (針對 WebSocket)
|
||||
val requestBuilder = response.request.newBuilder()
|
||||
requestBuilder.header("Authorization", "Bearer $newAccessToken")
|
||||
if (response.request.url.queryParameter("token") != null) {
|
||||
val newUrl = response.request.url.newBuilder()
|
||||
.setQueryParameter("token", newAccessToken)
|
||||
.build()
|
||||
requestBuilder.url(newUrl)
|
||||
}
|
||||
return@runBlocking requestBuilder.build()
|
||||
} else {
|
||||
Log.e("AuthDebug", "❌ 刷新成功但無法取得新 Token (Body: $newAuthData)")
|
||||
}
|
||||
} else {
|
||||
Log.e("AuthDebug", "❌ 刷新失敗,錯誤代碼: ${refreshResponse.code()}, 錯誤內容: ${refreshResponse.errorBody()?.string()}")
|
||||
}
|
||||
} else {
|
||||
Log.e("AuthDebug", "❌ refreshResponse 為 null (網路異常)")
|
||||
}
|
||||
|
||||
// 5. 失敗:連 Refresh Token 都失效,強制登出
|
||||
|
|
|
|||
|
|
@ -31,12 +31,14 @@ class TokenManager @Inject constructor(
|
|||
|
||||
// 儲存資料(維持原樣,這是安全的)
|
||||
suspend fun saveAuthData(userId: String, accessToken: String, refreshToken: String, expiresInSec: Int) {
|
||||
android.util.Log.d("TokenManager", "儲存 Token - User: $userId, AccessToken: ${accessToken.take(10)}..., RefreshToken: ${refreshToken.take(10)}...")
|
||||
dataStore.edit { preferences ->
|
||||
preferences[accessTokenKey(userId)] = accessToken
|
||||
preferences[refreshTokenKey(userId)] = refreshToken
|
||||
|
||||
val expiresAt = System.currentTimeMillis() + (expiresInSec * 1000L)
|
||||
preferences[expiresAtKey(userId)] = expiresAt
|
||||
android.util.Log.d("TokenManager", "Token 將於 ${(expiresInSec / 60)} 分鐘後過期 (timestamp: $expiresAt)")
|
||||
|
||||
preferences[CURRENT_USER_ID] = userId
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue