统一 Gson 使用,改为依赖注入方式

- 在 GsonConfig 中统一配置 Gson Bean(lenient 模式)
- 所有 Service 类通过构造函数注入 Gson 实例
- RetrofitFactory、BlockchainService、PolymarketApiKeyService 等统一使用注入的 Gson
- AccountService、PositionCheckService 添加 JsonUtils 注入
- OnChainWsUtils(object 单例)使用私有 Gson 实例(与 GsonConfig 配置一致)
- 移除所有 GsonConverterFactory.create() 无参调用,统一使用注入的 Gson
This commit is contained in:
WrBug
2026-01-01 12:01:17 +08:00
parent 8357546f3c
commit 5c18cbd95d
11 changed files with 107 additions and 74 deletions
@@ -0,0 +1,26 @@
package com.wrbug.polymarketbot.config
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* Gson 配置类
* 统一配置 Gson 实例,使用 lenient 模式允许解析格式不严格的 JSON
*/
@Configuration
class GsonConfig {
/**
* 创建 Gson Bean
* 使用 lenient 模式,允许解析格式不严格的 JSON
*/
@Bean
fun gson(): Gson {
return GsonBuilder()
.setLenient()
.create()
}
}
@@ -1,5 +1,6 @@
package com.wrbug.polymarketbot.config
import com.google.gson.Gson
import com.wrbug.polymarketbot.api.PolymarketClobApi
import com.wrbug.polymarketbot.util.createClient
import org.springframework.beans.factory.annotation.Value
@@ -18,7 +19,9 @@ import retrofit2.converter.gson.GsonConverterFactory
* - 账户 API Key 在调用时动态设置,不在此处配置
*/
@Configuration
class RetrofitConfig {
class RetrofitConfig(
private val gson: Gson
) {
@Value("\${polymarket.clob.base-url}")
private lateinit var clobBaseUrl: String
@@ -37,7 +40,7 @@ class RetrofitConfig {
return Retrofit.Builder()
.baseUrl(clobBaseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
.create(PolymarketClobApi::class.java)
}
@@ -37,7 +37,8 @@ class AccountService(
private val orderSigningService: OrderSigningService,
private val cryptoUtils: CryptoUtils,
private val telegramNotificationService: TelegramNotificationService? = null, // 可选,避免循环依赖
private val relayClientService: RelayClientService
private val relayClientService: RelayClientService,
private val jsonUtils: JsonUtils
) {
private val logger = LoggerFactory.getLogger(AccountService::class.java)
@@ -1121,7 +1122,7 @@ class AccountService(
// 如果目标 outcome 不是第一个(index != 0),需要转换价格
// 对于二元市场:第二个 outcome 的价格 = 1 - 第一个 outcome 的价格
if (outcomeIndex != null && outcomeIndex > 0) {
val outcomes = JsonUtils.parseStringArray(market.outcomes)
val outcomes = jsonUtils.parseStringArray(market.outcomes)
// 只对二元市场进行价格转换
if (outcomes.size == 2) {
// 保存原始第一个 outcome 的价格
@@ -48,7 +48,8 @@ class PositionCheckService(
private val accountRepository: AccountRepository,
private val messageSource: MessageSource,
private val retrofitFactory: RetrofitFactory,
private val blockchainService: BlockchainService
private val blockchainService: BlockchainService,
private val jsonUtils: JsonUtils
) {
private val logger = LoggerFactory.getLogger(PositionCheckService::class.java)
@@ -582,7 +583,7 @@ class PositionCheckService(
// 优先使用 outcomePrices(结算价格数组)
val outcomePrices = market.outcomePrices
if (outcomePrices != null && outcomePrices.isNotBlank()) {
val prices = JsonUtils.parseStringArray(outcomePrices)
val prices = jsonUtils.parseStringArray(outcomePrices)
if (outcomeIndex < prices.size) {
val price = prices[outcomeIndex].toSafeBigDecimal()
// 如果价格 >= 0.99,认为赢了
@@ -1,5 +1,6 @@
package com.wrbug.polymarketbot.service.common
import com.google.gson.Gson
import com.wrbug.polymarketbot.api.EthereumRpcApi
import com.wrbug.polymarketbot.api.JsonRpcRequest
import com.wrbug.polymarketbot.api.JsonRpcResponse
@@ -29,7 +30,8 @@ class BlockchainService(
private val dataApiBaseUrl: String,
private val retrofitFactory: RetrofitFactory,
private val relayClientService: RelayClientService,
private val rpcNodeService: RpcNodeService
private val rpcNodeService: RpcNodeService,
private val gson: Gson
) {
private val logger = LoggerFactory.getLogger(BlockchainService::class.java)
@@ -64,7 +66,7 @@ class BlockchainService(
Retrofit.Builder()
.baseUrl("$baseUrl/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
.create(PolymarketDataApi::class.java)
}
@@ -1,5 +1,6 @@
package com.wrbug.polymarketbot.service.common
import com.google.gson.Gson
import com.wrbug.polymarketbot.api.ApiKeyResponse
import com.wrbug.polymarketbot.api.PolymarketClobApi
import com.wrbug.polymarketbot.util.PolymarketL1AuthInterceptor
@@ -19,7 +20,8 @@ import retrofit2.converter.gson.GsonConverterFactory
@Service
class PolymarketApiKeyService(
@Value("\${polymarket.clob.base-url}")
private val clobBaseUrl: String
private val clobBaseUrl: String,
private val gson: Gson
) {
private val logger = LoggerFactory.getLogger(PolymarketApiKeyService::class.java)
@@ -222,7 +224,7 @@ class PolymarketApiKeyService(
return Retrofit.Builder()
.baseUrl(clobBaseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
.create(PolymarketClobApi::class.java)
}
@@ -236,7 +238,7 @@ class PolymarketApiKeyService(
return Retrofit.Builder()
.baseUrl(clobBaseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
.create(PolymarketClobApi::class.java)
}
@@ -22,15 +22,14 @@ import java.util.concurrent.ConcurrentHashMap
@Service
class CopyTradingWebSocketService(
private val copyOrderTrackingService: CopyOrderTrackingService,
private val templateRepository: CopyTradingTemplateRepository
private val templateRepository: CopyTradingTemplateRepository,
private val gson: Gson
) {
private val logger = LoggerFactory.getLogger(CopyTradingWebSocketService::class.java)
@Value("\${polymarket.websocket.url:wss://ws-live-data.polymarket.com}")
private var websocketUrl: String = "wss://ws-live-data.polymarket.com"
private val gson = Gson()
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
// 存储每个Leader的WebSocket客户端:leaderId -> WebSocketClient
@@ -1,5 +1,7 @@
package com.wrbug.polymarketbot.service.copytrading.monitor
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.reflect.TypeToken
import com.wrbug.polymarketbot.api.*
@@ -19,6 +21,29 @@ object OnChainWsUtils {
private val logger = LoggerFactory.getLogger(OnChainWsUtils::class.java)
// 创建 Gson 实例(与 GsonConfig 中的配置一致,使用 lenient 模式)
private val gson: Gson = GsonBuilder()
.setLenient()
.create()
/**
* 解析 JSON 字符串数组
* @param jsonString JSON 字符串 "[\"Yes\", \"No\"]"
* @return 字符串列表如果解析失败返回空列表
*/
private fun parseStringArray(jsonString: String?): List<String> {
if (jsonString.isNullOrBlank()) {
return emptyList()
}
return try {
val listType = object : TypeToken<List<String>>() {}.type
gson.fromJson<List<String>>(jsonString, listType) ?: emptyList()
} catch (e: Exception) {
emptyList()
}
}
// 合约地址
const val USDC_CONTRACT = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
const val ERC1155_CONTRACT = "0x4d97dcd97ec945f40cf65f87097ace5ea0476045"
@@ -240,24 +265,17 @@ object OnChainWsUtils {
val clobTokenIds = when {
clobTokenIdsRaw == null -> null
else -> {
try {
// 尝试解析 JSON 字符串
val gson = com.google.gson.Gson()
val listType = object : com.google.gson.reflect.TypeToken<List<String>>() {}.type
gson.fromJson<List<String>>(clobTokenIdsRaw, listType)
} catch (e: Exception) {
// 如果不是 JSON 字符串,可能是其他格式,返回 null
null
}
// 解析 JSON 字符串
parseStringArray(clobTokenIdsRaw)
}
}
// 解析 outcomes(可能是 JSON 字符串或数组)
val outcomes = com.wrbug.polymarketbot.util.JsonUtils.parseStringArray(market.outcomes)
val outcomes = parseStringArray(market.outcomes)
// 查找 tokenId 在 clobTokenIds 中的索引
val outcomeIndex = clobTokenIds?.indexOfFirst {
it.equals(tokenId, ignoreCase = true)
val outcomeIndex = clobTokenIds?.indexOfFirst { token ->
token.equals(tokenId, ignoreCase = true)
}?.takeIf { it >= 0 }
// 获取 outcome 名称
@@ -27,14 +27,12 @@ import java.util.concurrent.ConcurrentHashMap
@Service
class UnifiedOnChainWsService(
private val rpcNodeService: RpcNodeService,
private val retrofitFactory: RetrofitFactory
private val retrofitFactory: RetrofitFactory,
private val gson: Gson
) {
private val logger = LoggerFactory.getLogger(UnifiedOnChainWsService::class.java)
// Gson 实例,用于解析 JSON
private val gson = Gson()
@Value("\${copy.trading.onchain.ws.reconnect.delay:3000}")
private var reconnectDelay: Long = 3000 // 重连延迟(毫秒),默认3秒
@@ -2,14 +2,16 @@ package com.wrbug.polymarketbot.util
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import org.springframework.stereotype.Component
/**
* JSON 工具类
* 用于解析 JSON 字符串
*/
object JsonUtils {
private val gson = Gson()
@Component
class JsonUtils(
private val gson: Gson
) {
/**
* 解析 JSON 字符串数组
@@ -1,7 +1,6 @@
package com.wrbug.polymarketbot.util
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.wrbug.polymarketbot.api.BuilderRelayerApi
import com.wrbug.polymarketbot.api.EthereumRpcApi
import com.wrbug.polymarketbot.api.GitHubApi
@@ -34,7 +33,8 @@ class RetrofitFactory(
@Value("\${polymarket.clob.base-url}")
private val clobBaseUrl: String,
@Value("\${polymarket.gamma.base-url}")
private val gammaBaseUrl: String
private val gammaBaseUrl: String,
private val gson: Gson
) {
/**
@@ -61,11 +61,6 @@ class RetrofitFactory(
.addInterceptor(responseLoggingInterceptor)
.build()
// 创建 lenient 模式的 Gson,允许解析格式不严格的 JSON
val gson = GsonBuilder()
.setLenient()
.create()
return Retrofit.Builder()
.baseUrl(clobBaseUrl)
.client(okHttpClient)
@@ -87,11 +82,6 @@ class RetrofitFactory(
.addInterceptor(responseLoggingInterceptor)
.build()
// 创建 lenient 模式的 Gson,允许解析格式不严格的 JSON
val gson = GsonBuilder()
.setLenient()
.create()
return Retrofit.Builder()
.baseUrl(clobBaseUrl)
.client(okHttpClient)
@@ -129,11 +119,6 @@ class RetrofitFactory(
.addInterceptor(urlReplaceInterceptor)
.build()
// 创建 lenient 模式的 Gson
val gson = GsonBuilder()
.setLenient()
.create()
return Retrofit.Builder()
.baseUrl(fixedBaseUrl)
.client(okHttpClient)
@@ -228,11 +213,6 @@ class RetrofitFactory(
}
val okHttpClient = createClient().build()
// 创建 lenient 模式的 Gson
val gson = GsonBuilder()
.setLenient()
.create()
return Retrofit.Builder()
.baseUrl("$baseUrl/")
.client(okHttpClient)
@@ -253,11 +233,6 @@ class RetrofitFactory(
.followSslRedirects(true)
.build()
// 创建 lenient 模式的 Gson
val gson = GsonBuilder()
.setLenient()
.create()
return Retrofit.Builder()
.baseUrl("$baseUrl/")
.client(okHttpClient)
@@ -292,10 +267,6 @@ class RetrofitFactory(
.addInterceptor(builderAuthInterceptor)
.build()
val gson = GsonBuilder()
.setLenient()
.create()
return Retrofit.Builder()
.baseUrl("$baseUrl/")
.client(okHttpClient)
@@ -327,10 +298,6 @@ class RetrofitFactory(
.addInterceptor(githubInterceptor)
.build()
val gson = GsonBuilder()
.setLenient()
.create()
return Retrofit.Builder()
.baseUrl("$baseUrl/")
.client(okHttpClient)
@@ -393,18 +360,32 @@ class ResponseLoggingInterceptor : Interceptor {
val responseBody = response.peekBody(2048)
val responseBodyString = responseBody.string()
// 检查是否是有效的 JSON
val isJson = responseBodyString.trim().startsWith("{") ||
responseBodyString.trim().startsWith("[")
// 检查响应体是否为空
val isEmpty = responseBodyString.isBlank()
if (!isJson || !response.isSuccessful) {
// 检查是否是有效的 JSON
val trimmedBody = responseBodyString.trim()
val isJson = !isEmpty && (
trimmedBody.startsWith("{") ||
trimmedBody.startsWith("[")
)
// 如果响应体为空或不是 JSON,记录警告
if (isEmpty || !isJson) {
val bodyPreview = if (isEmpty) {
"(空响应体)"
} else {
trimmedBody.take(500)
}
logger.warn(
"API 响应异常: method=${request.method}, url=${request.url}, " +
"code=${response.code}, isJson=$isJson, " +
"responseBody=${responseBodyString.take(500)}"
"code=${response.code}, isJson=$isJson, isEmpty=$isEmpty, " +
"responseBody=$bodyPreview"
)
}
} catch (e: Exception) {
// 如果读取响应体失败,记录异常但不影响响应
logger.debug("读取响应体失败: ${e.message}")
}
}