优化代理检查结果展示与地域检测文案

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WrBug
2026-05-27 00:54:22 +08:00
co-authored by Cursor
parent 727fa7ed58
commit 83a415fb7e
16 changed files with 844 additions and 111 deletions
@@ -3,6 +3,7 @@ package com.wrbug.polymarketbot.controller.system
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.enums.ErrorCode
import com.wrbug.polymarketbot.service.system.ApiHealthCheckService
import com.wrbug.polymarketbot.service.system.GeoblockService
import com.wrbug.polymarketbot.service.system.ProxyConfigService
import jakarta.servlet.http.HttpServletRequest
import kotlinx.coroutines.runBlocking
@@ -19,6 +20,7 @@ import org.springframework.web.bind.annotation.*
class ProxyConfigController(
private val proxyConfigService: ProxyConfigService,
private val apiHealthCheckService: ApiHealthCheckService,
private val geoblockService: GeoblockService,
private val messageSource: MessageSource
) {
@@ -123,6 +125,32 @@ class ProxyConfigController(
ResponseEntity.ok(ApiResponse.error(ErrorCode.SERVER_ERROR, "API 健康检查失败:${e.message}", messageSource))
}
}
/**
* 检查交易服务器出口 IP 的地域限制(Polymarket Geoblock
*/
@PostMapping("/geoblock-check")
fun checkGeoblock(): ResponseEntity<ApiResponse<GeoblockCheckDto>> {
return try {
val result = runBlocking { geoblockService.checkGeoblock() }
if (result.isSuccess) {
ResponseEntity.ok(ApiResponse.success(result.getOrNull()))
} else {
val error = result.exceptionOrNull()
logger.error("Geoblock 检查失败", error)
ResponseEntity.ok(
ApiResponse.error(
ErrorCode.SERVER_ERROR,
error?.message ?: "Geoblock 检查失败",
messageSource
)
)
}
} catch (e: Exception) {
logger.error("Geoblock 检查异常", e)
ResponseEntity.ok(ApiResponse.error(ErrorCode.SERVER_ERROR, "Geoblock 检查失败:${e.message}", messageSource))
}
}
}
@@ -0,0 +1,23 @@
package com.wrbug.polymarketbot.dto
/**
* Polymarket Geoblock API 原始响应
*/
data class PolymarketGeoblockApiResponse(
val blocked: Boolean = false,
val ip: String = "",
val country: String = "",
val region: String = ""
)
/**
* 地域限制检查结果(返回给前端)
*/
data class GeoblockCheckDto(
val blocked: Boolean,
val ip: String,
val country: String,
val region: String,
val checkedAt: Long,
val source: String = "server"
)
@@ -36,6 +36,18 @@ data class SubscriptionProxyConfigRequest(
val type: String // CLASH 或 SS
)
/**
* 代理检查时的 Polymarket 地域限制检测结果
*/
data class ProxyCheckGeoblockResult(
val checked: Boolean,
val blocked: Boolean? = null,
val ip: String? = null,
val country: String? = null,
val region: String? = null,
val message: String? = null
)
/**
* 代理检查响应
*/
@@ -43,15 +55,22 @@ data class ProxyCheckResponse(
val success: Boolean,
val message: String,
val responseTime: Long? = null, // 响应时间(毫秒)
val latency: Long? = null // 延迟(毫秒),与 responseTime 相同,用于前端显示
val latency: Long? = null, // 延迟(毫秒),与 responseTime 相同,用于前端显示
val geoblock: ProxyCheckGeoblockResult? = null
) {
companion object {
fun create(success: Boolean, message: String, responseTime: Long? = null): ProxyCheckResponse {
fun create(
success: Boolean,
message: String,
responseTime: Long? = null,
geoblock: ProxyCheckGeoblockResult? = null
): ProxyCheckResponse {
return ProxyCheckResponse(
success = success,
message = message,
responseTime = responseTime,
latency = responseTime // latency 和 responseTime 相同
latency = responseTime,
geoblock = geoblock
)
}
}
@@ -0,0 +1,67 @@
package com.wrbug.polymarketbot.service.system
import com.google.gson.Gson
import com.wrbug.polymarketbot.dto.GeoblockCheckDto
import com.wrbug.polymarketbot.dto.PolymarketGeoblockApiResponse
import com.wrbug.polymarketbot.util.createClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import java.util.concurrent.TimeUnit
@Service
class GeoblockService(
private val gson: Gson
) {
private val logger = LoggerFactory.getLogger(GeoblockService::class.java)
companion object {
private const val GEOBLOCK_URL = "https://polymarket.com/api/geoblock"
}
suspend fun checkGeoblock(): Result<GeoblockCheckDto> = withContext(Dispatchers.IO) {
val client = createClient()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build()
checkGeoblockWithClient(client)
}
fun checkGeoblockWithClient(client: OkHttpClient): Result<GeoblockCheckDto> {
return try {
val request = Request.Builder()
.url(GEOBLOCK_URL)
.get()
.header("Accept", "application/json")
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
return Result.failure(
IllegalStateException("HTTP ${response.code}: ${response.message}")
)
}
val body = response.body?.string()
?: return Result.failure(IllegalStateException("Empty response body"))
val apiResponse = gson.fromJson(body, PolymarketGeoblockApiResponse::class.java)
Result.success(
GeoblockCheckDto(
blocked = apiResponse.blocked,
ip = apiResponse.ip,
country = apiResponse.country,
region = apiResponse.region,
checkedAt = System.currentTimeMillis()
)
)
}
} catch (e: Exception) {
logger.warn("Geoblock 检查失败", e)
Result.failure(e)
}
}
}
@@ -24,7 +24,8 @@ import java.util.concurrent.TimeUnit
*/
@Service
class ProxyConfigService(
private val proxyConfigRepository: ProxyConfigRepository
private val proxyConfigRepository: ProxyConfigRepository,
private val geoblockService: GeoblockService
) : ApplicationContextAware {
private var applicationContext: ApplicationContext? = null
@@ -148,100 +149,153 @@ class ProxyConfigService(
}
/**
* 检查代理是否可用
* 使用配置的代理请求 Polymarket 健康检查接口
* 检查代理是否可用,并检测经该代理访问 Polymarket 时的地域限制
*/
fun checkProxy(): ProxyCheckResponse {
return try {
val config = proxyConfigRepository.findByEnabledTrue()
?: return ProxyCheckResponse.create(
if (config == null) {
return ProxyCheckResponse.create(
success = false,
message = "未配置代理或代理未启用"
message = "未配置代理或代理未启用",
geoblock = checkGeoblockForProxy(null)
)
}
if (config.type != "HTTP") {
return ProxyCheckResponse.create(
success = false,
message = "当前仅支持检查 HTTP 代理(订阅代理检查功能待实现)"
message = "当前仅支持检查 HTTP 代理(订阅代理检查功能待实现)",
geoblock = checkGeoblockForProxy(null)
)
}
if (config.host == null || config.port == null) {
return ProxyCheckResponse.create(
success = false,
message = "代理配置不完整:缺少主机或端口"
message = "代理配置不完整:缺少主机或端口",
geoblock = checkGeoblockForProxy(null)
)
}
// 创建代理
val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(config.host, config.port))
// 创建 OkHttpClient
val clientBuilder = OkHttpClient.Builder()
.proxy(proxy)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
// 配置 SSL:信任所有证书(用于代理连接)
clientBuilder.createSSLSocketFactory()
clientBuilder.hostnameVerifier(TrustAllHostnameVerifier())
// 如果配置了用户名和密码,添加代理认证
if (config.username != null && config.password != null) {
clientBuilder.proxyAuthenticator { _, response ->
val credential = okhttp3.Credentials.basic(config.username, config.password)
response.request.newBuilder()
.header("Proxy-Authorization", credential)
.build()
}
}
val client = clientBuilder.build()
// 请求 Polymarket 健康检查接口
val client = buildProxyHttpClient(config)
val geoblock = checkGeoblockForProxy(client)
val request = Request.Builder()
.url("https://data-api.polymarket.com/")
.get()
.build()
val startTime = System.currentTimeMillis()
val response = client.newCall(request).execute()
val responseTime = System.currentTimeMillis() - startTime
val responseBody = response.body?.string()
if (response.isSuccessful && responseBody != null) {
// 检查响应内容是否为 {"data": "OK"}
if (responseBody.contains("\"data\"") && responseBody.contains("OK")) {
logger.info("代理检查成功:host=${config.host}, port=${config.port}, responseTime=${responseTime}ms")
val message = buildProxyCheckMessage("代理连接成功", geoblock)
ProxyCheckResponse.create(
success = true,
message = "代理连接成功",
responseTime = responseTime
message = message,
responseTime = responseTime,
geoblock = geoblock
)
} else {
ProxyCheckResponse.create(
success = false,
message = "代理连接成功,但响应格式不正确:$responseBody",
responseTime = responseTime
message = buildProxyCheckMessage(
"代理连接成功,但响应格式不正确:$responseBody",
geoblock
),
responseTime = responseTime,
geoblock = geoblock
)
}
} else {
ProxyCheckResponse.create(
success = false,
message = "代理连接失败:HTTP ${response.code} ${response.message}",
responseTime = responseTime
message = buildProxyCheckMessage(
"代理连接失败:HTTP ${response.code} ${response.message}",
geoblock
),
responseTime = responseTime,
geoblock = geoblock
)
}
} catch (e: Exception) {
logger.error("代理检查异常", e)
ProxyCheckResponse.create(
success = false,
message = "代理检查失败:${e.message}"
message = "代理检查失败:${e.message}",
geoblock = checkGeoblockForProxy(null)
)
}
}
private fun buildProxyHttpClient(config: ProxyConfig): OkHttpClient {
val host = requireNotNull(config.host) { "代理主机不能为空" }
val port = requireNotNull(config.port) { "代理端口不能为空" }
val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(host, port))
val clientBuilder = OkHttpClient.Builder()
.proxy(proxy)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
clientBuilder.createSSLSocketFactory()
clientBuilder.hostnameVerifier(TrustAllHostnameVerifier())
if (config.username != null && config.password != null) {
clientBuilder.proxyAuthenticator { _, response ->
val credential = Credentials.basic(config.username, config.password)
response.request.newBuilder()
.header("Proxy-Authorization", credential)
.build()
}
}
return clientBuilder.build()
}
private fun checkGeoblockForProxy(client: OkHttpClient?): ProxyCheckGeoblockResult {
val httpClient = client ?: com.wrbug.polymarketbot.util.createClient()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build()
return geoblockService.checkGeoblockWithClient(httpClient).fold(
onSuccess = { dto ->
ProxyCheckGeoblockResult(
checked = true,
blocked = dto.blocked,
ip = dto.ip,
country = dto.country,
region = dto.region,
message = if (dto.blocked) {
"当前出口 IP${dto.country}/${dto.region})在 Polymarket 受限,无法下单"
} else {
"当前出口 IP${dto.country}/${dto.region})可向 Polymarket 下单"
}
)
},
onFailure = { e ->
ProxyCheckGeoblockResult(
checked = true,
message = "地域限制检测失败:${e.message}"
)
}
)
}
private fun buildProxyCheckMessage(baseMessage: String, geoblock: ProxyCheckGeoblockResult): String {
if (!geoblock.checked || geoblock.message.isNullOrBlank()) {
return baseMessage
}
return "$baseMessage${geoblock.message}"
}
/**
* 删除代理配置