fix(proxy): INTL 401 自动 refresh token 重试;Trae 重复签到文案判定为已签
Some checks are pending
Release / build (push) Waiting to run
Some checks are pending
Release / build (push) Waiting to run
This commit is contained in:
parent
7ecb7b3246
commit
1a0bb2b3fd
@ -16,8 +16,8 @@ android {
|
||||
applicationId = "com.rainy.token"
|
||||
minSdk = 31
|
||||
targetSdk = 35
|
||||
versionCode = 45
|
||||
versionName = "1.7.25"
|
||||
versionCode = 46
|
||||
versionName = "1.7.26"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.rainy.token.data.proxy
|
||||
|
||||
import com.rainy.token.data.repository.CredentialRepository
|
||||
import com.rainy.token.data.repository.WorkBuddyRepository
|
||||
import com.rainy.token.domain.model.Credential
|
||||
import com.rainy.token.domain.service.ServiceType
|
||||
import java.util.UUID
|
||||
@ -28,6 +29,7 @@ import kotlinx.serialization.json.contentOrNull
|
||||
class WorkBuddyChatProxy @Inject constructor(
|
||||
private val okHttpClient: OkHttpClient,
|
||||
private val credentialRepository: CredentialRepository,
|
||||
private val workBuddyRepository: WorkBuddyRepository,
|
||||
) {
|
||||
|
||||
suspend fun forwardChat(
|
||||
@ -53,23 +55,39 @@ class WorkBuddyChatProxy @Inject constructor(
|
||||
val upstreamBody = prepareChatBody(requestBody, effectiveRegion == ProviderRegion.INTL)
|
||||
val intl = effectiveRegion == ProviderRegion.INTL
|
||||
val paths = if (intl) listOf("/console/chat/completions", "/v2/chat/completions") else listOf("/v2/chat/completions")
|
||||
var current = credential
|
||||
var token = accessToken
|
||||
for (path in paths) {
|
||||
val request = buildChatRequest(base, path, upstreamBody, accessToken, intl)
|
||||
val response = try {
|
||||
okHttpClient.newCall(request).execute()
|
||||
} catch (e: java.io.IOException) {
|
||||
throw e
|
||||
}
|
||||
val status = response.code
|
||||
// 国际站 console 路由返回 404/405 时再退回共享 v2 路径
|
||||
if (status == 404 || status == 405) {
|
||||
var attempt = 0
|
||||
while (true) {
|
||||
val request = buildChatRequest(base, path, upstreamBody, token, intl)
|
||||
val response = try {
|
||||
okHttpClient.newCall(request).execute()
|
||||
} catch (e: java.io.IOException) {
|
||||
throw e
|
||||
}
|
||||
val status = response.code
|
||||
// 国际站 console 路由返回 404/405 时再退回共享 v2 路径
|
||||
if (status == 404 || status == 405) {
|
||||
response.close()
|
||||
break
|
||||
}
|
||||
if ((status == 401 || status == 403) && attempt == 0) {
|
||||
response.close()
|
||||
val refreshed = workBuddyRepository.refreshCredential(current)
|
||||
if (refreshed != null) {
|
||||
credentialRepository.save(refreshed, accountId)
|
||||
current = refreshed
|
||||
token = refreshed.accessToken
|
||||
attempt++
|
||||
continue
|
||||
}
|
||||
}
|
||||
val bytes = try { response.body?.bytes() ?: ByteArray(0) } catch (_: Throwable) { ByteArray(0) }
|
||||
val contentType = response.header("Content-Type") ?: "application/json"
|
||||
response.close()
|
||||
continue
|
||||
return@withContext ProxyUpstreamResponse(status, contentType, bytes)
|
||||
}
|
||||
val bytes = try { response.body?.bytes() ?: ByteArray(0) } catch (_: Throwable) { ByteArray(0) }
|
||||
val contentType = response.header("Content-Type") ?: "application/json"
|
||||
response.close()
|
||||
return@withContext ProxyUpstreamResponse(status, contentType, bytes)
|
||||
}
|
||||
return@withContext null
|
||||
}
|
||||
@ -95,27 +113,43 @@ class WorkBuddyChatProxy @Inject constructor(
|
||||
val upstreamBody = prepareChatBody(requestBody, effectiveRegion == ProviderRegion.INTL)
|
||||
val intl = effectiveRegion == ProviderRegion.INTL
|
||||
val paths = if (intl) listOf("/console/chat/completions", "/v2/chat/completions") else listOf("/v2/chat/completions")
|
||||
var current = credential
|
||||
var token = accessToken
|
||||
for (path in paths) {
|
||||
val request = buildChatRequest(base, path, upstreamBody, accessToken, intl)
|
||||
val response = try {
|
||||
okHttpClient.newCall(request).execute()
|
||||
} catch (e: java.io.IOException) {
|
||||
throw e
|
||||
var attempt = 0
|
||||
while (true) {
|
||||
val request = buildChatRequest(base, path, upstreamBody, token, intl)
|
||||
val response = try {
|
||||
okHttpClient.newCall(request).execute()
|
||||
} catch (e: java.io.IOException) {
|
||||
throw e
|
||||
}
|
||||
if (response.code == 404 || response.code == 405) {
|
||||
response.close()
|
||||
break
|
||||
}
|
||||
if ((response.code == 401 || response.code == 403) && attempt == 0) {
|
||||
response.close()
|
||||
val refreshed = workBuddyRepository.refreshCredential(current)
|
||||
if (refreshed != null) {
|
||||
credentialRepository.save(refreshed, accountId)
|
||||
current = refreshed
|
||||
token = refreshed.accessToken
|
||||
attempt++
|
||||
continue
|
||||
}
|
||||
}
|
||||
val input = response.body?.byteStream() ?: run {
|
||||
response.close()
|
||||
return@withContext null
|
||||
}
|
||||
return@withContext ProxyUpstreamStream(
|
||||
status = response.code,
|
||||
contentType = response.header("Content-Type") ?: "text/event-stream",
|
||||
input = input,
|
||||
close = { response.close() },
|
||||
)
|
||||
}
|
||||
if (response.code == 404 || response.code == 405) {
|
||||
response.close()
|
||||
continue
|
||||
}
|
||||
val input = response.body?.byteStream() ?: run {
|
||||
response.close()
|
||||
return@withContext null
|
||||
}
|
||||
return@withContext ProxyUpstreamStream(
|
||||
status = response.code,
|
||||
contentType = response.header("Content-Type") ?: "text/event-stream",
|
||||
input = input,
|
||||
close = { response.close() },
|
||||
)
|
||||
}
|
||||
return@withContext null
|
||||
}
|
||||
|
||||
@ -719,9 +719,11 @@ class TraeRepository constructor(
|
||||
// 权威口径幂等兜底码是 10001(trae-checkin-helper 逆向);历史实现用 1001,
|
||||
// 两者都认,避免把已签误判为 FAILED 硬错误。
|
||||
val already = (code == 1001L || code == 10001L) || (!deviceBlock && (
|
||||
msg.contains("已签到") || msg.contains("已经签到") || msg.contains("明日再来") ||
|
||||
msg.contains("今日已完成") || msg.contains("已领取") ||
|
||||
lowerMsg.contains("already") || lowerMsg.contains("checked") || lowerMsg.contains("claimed")
|
||||
msg.contains("已签到") || msg.contains("已经签到") || msg.contains("重复签到") ||
|
||||
msg.contains("重复") || msg.contains("不能重复") ||
|
||||
msg.contains("明日再来") || msg.contains("今日已完成") || msg.contains("已领取") ||
|
||||
lowerMsg.contains("already") || lowerMsg.contains("checked") || lowerMsg.contains("claimed") ||
|
||||
lowerMsg.contains("duplicate") || lowerMsg.contains("repeat")
|
||||
))
|
||||
// 服务端临时限流/排队(账号、设备或活动人数限制):提示可稍后重试,不算硬错误。
|
||||
val retryable = !already && !deviceBlock && (
|
||||
|
||||
@ -253,6 +253,21 @@ class WorkBuddyRepository constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/** 供 Chat 代理等非余额路径使用的 token 刷新入口:成功返回新凭据(由调用方落库)。 */
|
||||
fun refreshCredential(credential: Credential.WorkBuddyCredential): Credential.WorkBuddyCredential? {
|
||||
val refreshed = tryRefesh(credential, credential.refreshToken) ?: return null
|
||||
return credential.copy(
|
||||
accessToken = refreshed.accessToken,
|
||||
refreshToken = refreshed.refreshToken,
|
||||
expiresAt = if (refreshed.expiresIn > 0L) {
|
||||
System.currentTimeMillis() + refreshed.expiresIn * 1000L
|
||||
} else {
|
||||
credential.expiresAt
|
||||
},
|
||||
lastVerifiedAt = System.currentTimeMillis()
|
||||
)
|
||||
}
|
||||
|
||||
private fun JsonObject.readToken(vararg keys: String): String? {
|
||||
for (key in keys) {
|
||||
this[key]?.jsonPrimitive?.contentOrNull?.takeIf { it.isNotBlank() }?.let { return it }
|
||||
|
||||
@ -245,8 +245,9 @@ object NetworkModule {
|
||||
@Singleton
|
||||
fun provideWorkBuddyChatProxy(
|
||||
okHttpClient: OkHttpClient,
|
||||
credentialRepository: CredentialRepository
|
||||
): WorkBuddyChatProxy = WorkBuddyChatProxy(okHttpClient, credentialRepository)
|
||||
credentialRepository: CredentialRepository,
|
||||
workBuddyRepository: WorkBuddyRepository,
|
||||
): WorkBuddyChatProxy = WorkBuddyChatProxy(okHttpClient, credentialRepository, workBuddyRepository)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
|
||||
Loading…
Reference in New Issue
Block a user