From cf53d7c66f900d2fb7def546c46cfc98b46248a6 Mon Sep 17 00:00:00 2001 From: Liuxinyu176 <1041316040@qq.com> Date: Sat, 10 Oct 2026 14:39:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(proxy):=20=E6=A0=B9=E8=B7=AF=E7=94=B1/?= =?UTF-8?q?=E6=97=A0=20v1=20=E5=89=8D=E7=BC=80=E5=85=BC=E5=AE=B9=EF=BC=8C?= =?UTF-8?q?=E8=AE=BF=E9=97=AE=E8=A3=B8=E5=9C=B0=E5=9D=80=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=20404?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 4 +- .../token/data/proxy/KtorLocalProxyServer.kt | 173 +++++++++++------- 2 files changed, 108 insertions(+), 69 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index cc091e5..b56c3a0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,8 +16,8 @@ android { applicationId = "com.rainy.token" minSdk = 31 targetSdk = 35 - versionCode = 49 - versionName = "1.7.29" + versionCode = 50 + versionName = "1.7.30" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/com/rainy/token/data/proxy/KtorLocalProxyServer.kt b/app/src/main/java/com/rainy/token/data/proxy/KtorLocalProxyServer.kt index 907514e..55938a8 100644 --- a/app/src/main/java/com/rainy/token/data/proxy/KtorLocalProxyServer.kt +++ b/app/src/main/java/com/rainy/token/data/proxy/KtorLocalProxyServer.kt @@ -98,85 +98,124 @@ class KtorLocalProxyServer @Inject constructor( json() } routing { + get("/") { + call.respond( + mapOf( + "status" to "ok", + "endpoints" to listOf("GET /health", "GET /v1/models", "POST /v1/chat/completions"), + "base_url" to "http://<手机热点或局域网IP>:8787/v1" + ) + ) + } + get("/v1") { + call.respond( + mapOf( + "status" to "ok", + "endpoints" to listOf("GET /health", "GET /v1/models", "POST /v1/chat/completions"), + "base_url" to "http://<手机热点或局域网IP>:8787/v1" + ) + ) + } get("/health") { call.respond(mapOf("status" to "ok")) } get("/v1/models") { - if (!authorized(call, apiKey)) { - call.respond(HttpStatusCode.Unauthorized, errorBody("未授权")) - return@get - } - val result = sub2Api.forwardModels() - if (result == null) { - call.respond(HttpStatusCode.BadRequest, errorBody("Sub2API 未配置或未登录,请在设置中填写 API Key")) - } else { - call.respondBytes(result.body, contentTypeOf(result.contentType), HttpStatusCode(result.status, "")) - } + call.respondModels(apiKey, sub2Api) + } + // 部分客户端 Base URL 不带 /v1 时兼容 + get("/models") { + call.respondModels(apiKey, sub2Api) } post("/v1/chat/completions") { - if (!authorized(call, apiKey)) { - call.respond(HttpStatusCode.Unauthorized, errorBody("未授权")) - return@post - } - val rawBody = call.receiveText() - if (rawBody.length > MAX_REQUEST_BYTES) { - call.respond(HttpStatusCode(413, ""), errorBody("请求体过大")) - return@post - } - val conversationId = call.request.headers["X-Conversation-Id"] ?: extractUser(rawBody) - try { - if (extractStream(rawBody)) { - val stream = router.openStreamingChat(rawBody, conversationId) - if (stream == null) { - call.respond( - HttpStatusCode.BadRequest, - errorBody("所有上游均不可用或未配置,请检查设置") - ) - } else { - call.respondOutputStream( - contentType = contentTypeOf(stream.contentType), - status = HttpStatusCode(stream.status, "") - ) { - try { - val buffer = ByteArray(8192) - val input = stream.input - while (true) { - val read = input.read(buffer) - if (read < 0) break - write(buffer, 0, read) - flush() - } - } finally { - stream.close() - } + call.respondChatCompletion(apiKey, router) + } + post("/chat/completions") { + call.respondChatCompletion(apiKey, router) + } + } + } + + private suspend fun ApplicationCall.respondModels( + apiKey: String?, + sub2Api: Sub2ApiChatProxy, + ) { + if (!authorized(this, apiKey)) { + respond(HttpStatusCode.Unauthorized, errorBody("未授权")) + return + } + val result = sub2Api.forwardModels() + if (result == null) { + respond(HttpStatusCode.BadRequest, errorBody("Sub2API 未配置或未登录,请在设置中填写 API Key")) + } else { + respondBytes(result.body, contentTypeOf(result.contentType), HttpStatusCode(result.status, "")) + } + } + + private suspend fun ApplicationCall.respondChatCompletion( + apiKey: String?, + router: ProviderRouter, + ) { + if (!authorized(this, apiKey)) { + respond(HttpStatusCode.Unauthorized, errorBody("未授权")) + return + } + val rawBody = receiveText() + if (rawBody.length > MAX_REQUEST_BYTES) { + respond(HttpStatusCode(413, ""), errorBody("请求体过大")) + return + } + val conversationId = request.headers["X-Conversation-Id"] ?: extractUser(rawBody) + try { + if (extractStream(rawBody)) { + val stream = router.openStreamingChat(rawBody, conversationId) + if (stream == null) { + respond( + HttpStatusCode.BadRequest, + errorBody("所有上游均不可用或未配置,请检查设置") + ) + } else { + respondOutputStream( + contentType = contentTypeOf(stream.contentType), + status = HttpStatusCode(stream.status, "") + ) { + try { + val buffer = ByteArray(8192) + val input = stream.input + while (true) { + val read = input.read(buffer) + if (read < 0) break + write(buffer, 0, read) + flush() } - } - } else { - val result = router.forwardChat(rawBody, conversationId) - if (result == null) { - call.respond( - HttpStatusCode.BadRequest, - errorBody("所有上游均不可用或未配置,请检查设置") - ) - } else { - call.respondBytes(result.body, contentTypeOf(result.contentType), HttpStatusCode(result.status, "")) + } finally { + stream.close() } } - } catch (e: IOException) { - val detail = e.message ?: "未知错误" - call.respond( - HttpStatusCode.BadGateway, - errorBody("上游网络错误:$detail") - ) - } catch (e: Exception) { - if (e is kotlinx.coroutines.CancellationException) throw e - val detail = e.message ?: "未知错误" - call.respond( - HttpStatusCode.InternalServerError, - errorBody("网关内部错误:$detail") + } + } else { + val result = router.forwardChat(rawBody, conversationId) + if (result == null) { + respond( + HttpStatusCode.BadRequest, + errorBody("所有上游均不可用或未配置,请检查设置") ) + } else { + respondBytes(result.body, contentTypeOf(result.contentType), HttpStatusCode(result.status, "")) } } + } catch (e: IOException) { + val detail = e.message ?: "未知错误" + respond( + HttpStatusCode.BadGateway, + errorBody("上游网络错误:$detail") + ) + } catch (e: Exception) { + if (e is kotlinx.coroutines.CancellationException) throw e + val detail = e.message ?: "未知错误" + respond( + HttpStatusCode.InternalServerError, + errorBody("网关内部错误:$detail") + ) } }