From 61c010a0cf05ea9bc0ab0327dba71d4ff5391f49 Mon Sep 17 00:00:00 2001 From: shiaho <222622008+shiaho777@users.noreply.github.com> Date: Wed, 21 Oct 2026 18:00:29 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=8F=96=E6=B6=88=E7=BA=AF=20HTTP=20?= =?UTF-8?q?=E4=BB=A3=E7=90=86=E6=9C=80=E5=B0=8F=E5=89=A9=E4=BD=99=E6=97=B6?= =?UTF-8?q?=E9=95=BF=E9=97=A8=E6=A7=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 已扣额度的随机 IP 只要尚未过期就继续使用。对标官方 aca7b90(#103),避免客户端 60 秒预留把可用 IP 丢掉。 --- SurveyController.xcodeproj/project.pbxproj | 16 ++++++++-------- SurveyController/App/AppVersion.swift | 2 +- SurveyController/Core/Proxy/ProxyPool.swift | 11 +++++++---- SurveyControllerTests/RunEngineTests.swift | 14 ++++++++++++++ 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/SurveyController.xcodeproj/project.pbxproj b/SurveyController.xcodeproj/project.pbxproj index 1c84a26..cfa5fc8 100644 --- a/SurveyController.xcodeproj/project.pbxproj +++ b/SurveyController.xcodeproj/project.pbxproj @@ -317,7 +317,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 10101; + CURRENT_PROJECT_VERSION = 10102; DEAD_CODE_STRIPPING = YES; ENABLE_APP_SANDBOX = NO; ENABLE_PREVIEWS = YES; @@ -331,7 +331,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 0.1.1; + MARKETING_VERSION = 0.1.2; PRODUCT_BUNDLE_IDENTIFIER = "com.surveycontroller.mac"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -346,7 +346,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 10101; + CURRENT_PROJECT_VERSION = 10102; DEAD_CODE_STRIPPING = YES; ENABLE_APP_SANDBOX = NO; ENABLE_PREVIEWS = NO; @@ -360,7 +360,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 0.1.1; + MARKETING_VERSION = 0.1.2; PRODUCT_BUNDLE_IDENTIFIER = "com.surveycontroller.mac"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -373,9 +373,9 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 10101; + CURRENT_PROJECT_VERSION = 10102; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 0.1.1; + MARKETING_VERSION = 0.1.2; PRODUCT_BUNDLE_IDENTIFIER = "com.surveycontroller.mac.tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; @@ -389,9 +389,9 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 10101; + CURRENT_PROJECT_VERSION = 10102; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 0.1.1; + MARKETING_VERSION = 0.1.2; PRODUCT_BUNDLE_IDENTIFIER = "com.surveycontroller.mac.tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; diff --git a/SurveyController/App/AppVersion.swift b/SurveyController/App/AppVersion.swift index 84dc84f..9b9970c 100644 --- a/SurveyController/App/AppVersion.swift +++ b/SurveyController/App/AppVersion.swift @@ -6,7 +6,7 @@ import Foundation /// 应用版本与 GitHub 仓库信息。 public enum AppVersion { /// 当前版本(与 MARKETING_VERSION 同步)。 - public static let version = "0.1.1" + public static let version = "0.1.2" /// 当前对齐的官方桌面端版本。 public static let alignedOfficialVersion = "4.0.6" diff --git a/SurveyController/Core/Proxy/ProxyPool.swift b/SurveyController/Core/Proxy/ProxyPool.swift index d37a9b3..1258f9b 100644 --- a/SurveyController/Core/Proxy/ProxyPool.swift +++ b/SurveyController/Core/Proxy/ProxyPool.swift @@ -47,7 +47,7 @@ public struct OfficialProxyProvider: ProxyProvider { leases.append(ProxyLease( address: proxy.address, expireAt: expireAt, - ttlSufficient: expireAt <= 0 || expireAt - Date().timeIntervalSince1970 > 60 + ttlSufficient: expireAt <= 0 || expireAt - Date().timeIntervalSince1970 >= ProxyPool.minTtlSeconds )) } return leases @@ -134,7 +134,9 @@ public struct CustomProxyProvider: ProxyProvider { /// 对标 session_policy 的代理池(actor 保证并发安全,替代 Python 锁)。 public actor ProxyPool { public static let cooldownSeconds: TimeInterval = 45 - public static let minTtlSeconds: TimeInterval = 60 + /// 纯 HTTP 链路不设置预留时长门槛;仍由 expireAt 判断代理是否已经过期。 + /// 对标 HTTP_PROXY_MIN_REMAINING_TTL_SECONDS(官方 aca7b90,#103)。 + public static let minTtlSeconds: TimeInterval = 0 private var available: [ProxyLease] = [] private var inUse: Set = [] @@ -203,8 +205,9 @@ public actor ProxyPool { if inUse.contains(lease.address) || blocked.contains(lease.address) { return false } if let until = cooldowns[lease.address], until > Date() { return false } if lease.expireAt > 0 { - if Date().timeIntervalSince1970 >= lease.expireAt { return false } - if lease.expireAt - Date().timeIntervalSince1970 < Self.minTtlSeconds { return false } + // 对标 proxy_lease_has_sufficient_ttl:剩余时长 >= 门槛才可用。门槛为 0 时,未过期即保留。 + let remaining = lease.expireAt - Date().timeIntervalSince1970 + if remaining < Self.minTtlSeconds { return false } } return true } diff --git a/SurveyControllerTests/RunEngineTests.swift b/SurveyControllerTests/RunEngineTests.swift index 01e2ce7..9f1fb34 100644 --- a/SurveyControllerTests/RunEngineTests.swift +++ b/SurveyControllerTests/RunEngineTests.swift @@ -63,6 +63,20 @@ final class ProxyPoolTests: XCTestCase { XCTAssertEqual(lease.address, "5.5.5.5:80") } + /// 对标 test_merge_prefetched_proxy_leases_keeps_future_proxy_without_ttl_floor: + /// 已提取、尚未过期的代理必须使用,不能因客户端预留时长被丢弃。 + func test_future_lease_is_kept_without_ttl_floor() async throws { + XCTAssertEqual(ProxyPool.minTtlSeconds, 0) + let now = Date().timeIntervalSince1970 + let provider = ScriptedProxyProvider(batches: [[ + ProxyLease(address: "1.1.1.1:8000", expireAt: now + 5), + ProxyLease(address: "2.2.2.2:8000", expireAt: now - 1), + ]]) + let pool = ProxyPool(provider: provider) + let lease = try await pool.acquire() + XCTAssertEqual(lease.address, "1.1.1.1:8000") + } + func test_empty_provider_raises_unavailable() async { let pool = ProxyPool(provider: ScriptedProxyProvider(batches: [])) do {