feat: 改进下单错误处理和市场价格接口

- 修复市场价格接口:移除side参数,使用outcomeIndex判断方向
- 添加规范:禁止使用YES/NO字符串判断side,必须使用outcomeIndex
- 改进所有下单错误处理:所有错误都打印详细日志并存入数据库
  - AccountService.sellPosition: 添加完整错误日志
  - CopyOrderTrackingService.createOrderWithRetry: 所有失败都记录详细日志
  - recordFailedTrade: 改进错误信息存储,包含堆栈信息
  - PolymarketClobService.createSignedOrder: 添加完整错误日志
- 前端:更新市场价格接口调用,使用outcomeIndex替代side参数
- 清理:删除过时的i18n文档
This commit is contained in:
WrBug
2025-12-05 00:23:44 +08:00
parent 1a9407c544
commit 41596887c9
18 changed files with 215 additions and 1841 deletions
@@ -64,71 +64,3 @@ class LocaleInterceptor : HandlerInterceptor {
return Locale("en")
}
}
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.context.i18n.LocaleContextHolder
import org.springframework.stereotype.Component
import org.springframework.web.servlet.HandlerInterceptor
import java.util.*
/**
* 语言拦截器
* 从 HTTP Header 读取语言设置,并设置到 LocaleContextHolder
*
* 支持的 Header
* - Accept-Language: 标准 HTTP Header(如 zh-CN, zh-TW, en
* - X-Language: 自定义 Header(如 zh-CN, zh-TW, en
*
* 语言映射规则:
* - zh-CN, zh -> zh-CN (简体中文)
* - zh-TW, zh-HK -> zh-TW (繁体中文)
* - 其他 -> en (英文,默认)
*/
@Component
class LocaleInterceptor : HandlerInterceptor {
override fun preHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
): Boolean {
// 优先从 X-Language Header 读取
val language = request.getHeader("X-Language")
?: request.getHeader("Accept-Language")
?: "en"
// 解析语言
val locale = parseLocale(language)
// 设置到 LocaleContextHolder,供 MessageSource 使用
LocaleContextHolder.setLocale(locale)
return true
}
/**
* 解析语言字符串为 Locale
* 支持格式:zh-CN, zh_TW, zh, en, en-US 等
*/
private fun parseLocale(language: String): Locale {
// 移除空格并转为小写
val lang = language.trim().lowercase()
// 处理 zh-CN, zh_CN 等格式
if (lang.startsWith("zh")) {
// 检查是否是繁体中文
if (lang.contains("tw") || lang.contains("hk") || lang.contains("mo")) {
return Locale("zh", "TW")
}
// 默认简体中文
return Locale("zh", "CN")
}
// 英文(默认)
return Locale("en")
}
}
@@ -13,25 +13,16 @@ class WebMvcConfig(
private val jwtAuthenticationInterceptor: JwtAuthenticationInterceptor,
private val localeInterceptor: LocaleInterceptor
) : WebMvcConfigurer {
override fun addInterceptors(registry: InterceptorRegistry) {
// 先注册语言拦截器(优先级更高)
registry.addInterceptor(localeInterceptor)
.addPathPatterns("/api/**")
// 再注册JWT认证拦截器
registry.addInterceptor(jwtAuthenticationInterceptor)
.addPathPatterns("/api/**")
}
}
registry.addInterceptor(jwtAuthenticationInterceptor)
.addPathPatterns("/api/**")
}
}
registry.addInterceptor(jwtAuthenticationInterceptor)
.addPathPatterns("/api/**")
}
@@ -36,7 +36,7 @@ class MarketController(
return ResponseEntity.ok(ApiResponse.error(ErrorCode.PARAM_MARKET_ID_EMPTY, messageSource = messageSource))
}
val result = runBlocking { accountService.getMarketPrice(request.marketId) }
val result = runBlocking { accountService.getMarketPrice(request.marketId, request.outcomeIndex) }
result.fold(
onSuccess = { response ->
ResponseEntity.ok(ApiResponse.success(response))
@@ -160,7 +160,8 @@ data class PositionSellResponse(
* 市场价格请求
*/
data class MarketPriceRequest(
val marketId: String // 市场ID
val marketId: String, // 市场ID
val outcomeIndex: Int? = null // 结果索引(可选):0, 1, 2...,用于确定需要查询哪个 outcome 的价格。如果提供了 outcomeIndex,会转换价格(1 - 第一个outcome的价格)
)
/**
@@ -7,6 +7,7 @@ import com.wrbug.polymarketbot.repository.AccountRepository
import com.wrbug.polymarketbot.util.RetrofitFactory
import com.wrbug.polymarketbot.util.toSafeBigDecimal
import com.wrbug.polymarketbot.util.eq
import com.wrbug.polymarketbot.util.JsonUtils
import kotlinx.coroutines.runBlocking
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@@ -739,19 +740,12 @@ class AccountService(
// 6. 获取 tokenId(从 conditionId 和 outcomeIndex 计算)
// 需要先获取 tokenId,以便后续通过 CLOB API 获取三元及以上市场的价格
// 优先使用 outcomeIndex,如果没有则尝试从 side 推断(仅支持 YES/NO
// 优先使用 outcomeIndex,如果没有则返回错误(不再通过 side 字符串推断
val tokenIdResult = if (request.outcomeIndex != null) {
blockchainService.getTokenId(request.marketId, request.outcomeIndex)
} else {
// 向后兼容:尝试从 side 推断(仅支持 YES/NO
when (request.side.uppercase()) {
"YES" -> blockchainService.getTokenId(request.marketId, 0)
"NO" -> blockchainService.getTokenId(request.marketId, 1)
else -> {
logger.warn("无法从 side 推断 outcomeIndex,需要提供 outcomeIndex: side=${request.side}")
Result.failure<String>(IllegalArgumentException("无法从 side '${request.side}' 推断 outcomeIndex,请提供 outcomeIndex 参数"))
}
}
logger.warn("缺少 outcomeIndex 参数,无法计算 tokenId: marketId=${request.marketId}, side=${request.side}")
Result.failure<String>(IllegalArgumentException("缺少 outcomeIndex 参数,无法计算 tokenId。请提供 outcomeIndex 参数"))
}
val tokenId = tokenIdResult.getOrNull()
@@ -879,8 +873,9 @@ class AccountService(
)
} else {
val errorMsg = response.errorMsg ?: "未知错误"
logger.error("创建订单失败: $errorMsg")
Result.failure(Exception("创建订单失败: $errorMsg"))
val fullErrorMsg = "创建订单失败: accountId=${account.id}, marketId=${request.marketId}, side=${request.side}, orderType=${request.orderType}, price=${if (request.orderType == "LIMIT") sellPrice else "MARKET"}, quantity=${sellQuantity.toPlainString()}, errorMsg=$errorMsg"
logger.error(fullErrorMsg)
Result.failure(Exception(fullErrorMsg))
}
} else {
val errorBody = try {
@@ -888,12 +883,14 @@ class AccountService(
} catch (e: Exception) {
null
}
logger.error("创建订单失败: code=${orderResponse.code()}, message=${orderResponse.message()}, errorBody=$errorBody")
Result.failure(Exception("创建订单失败: ${orderResponse.code()} ${orderResponse.message()}${if (errorBody != null) " - $errorBody" else ""}"))
val fullErrorMsg = "创建订单失败: accountId=${account.id}, marketId=${request.marketId}, side=${request.side}, orderType=${request.orderType}, price=${if (request.orderType == "LIMIT") sellPrice else "MARKET"}, quantity=${sellQuantity.toPlainString()}, code=${orderResponse.code()}, message=${orderResponse.message()}${if (errorBody != null) ", errorBody=$errorBody" else ""}"
logger.error(fullErrorMsg)
Result.failure(Exception(fullErrorMsg))
}
} catch (e: Exception) {
logger.error("卖出仓位异常: ${e.message}", e)
Result.failure(e)
val fullErrorMsg = "卖出仓位异常: accountId=${request.accountId}, marketId=${request.marketId}, side=${request.side}, orderType=${request.orderType}, error=${e.message}"
logger.error(fullErrorMsg, e)
Result.failure(Exception(fullErrorMsg))
}
}
@@ -919,8 +916,10 @@ class AccountService(
/**
* 获取市场价格
* 使用 Gamma API 获取价格信息,因为 Gamma API 支持 condition_ids 参数
* @param marketId 市场ID
* @param outcomeIndex 结果索引(可选):0, 1, 2...,用于确定需要查询哪个 outcome 的价格。如果提供了 outcomeIndex 且 > 0,会转换价格(1 - 第一个outcome的价格)
*/
suspend fun getMarketPrice(marketId: String): Result<MarketPriceResponse> {
suspend fun getMarketPrice(marketId: String, outcomeIndex: Int? = null): Result<MarketPriceResponse> {
return try {
// 使用 Gamma API 获取市场信息(支持 condition_ids 参数)
val gammaApi = retrofitFactory.createGammaApi()
@@ -931,10 +930,36 @@ class AccountService(
val market = markets.firstOrNull()
if (market != null) {
// 从 Gamma API 响应中提取价格信息
val bestBid = market.bestBid?.toString()
val bestAsk = market.bestAsk?.toString()
val lastPrice = market.lastTradePrice?.toString()
// 从 Gamma API 响应中提取价格信息(这些价格通常是针对第一个 outcome,index = 0
var bestBid = market.bestBid?.toString()
var bestAsk = market.bestAsk?.toString()
var lastPrice = market.lastTradePrice?.toString()
// 如果目标 outcome 不是第一个(index != 0),需要转换价格
// 对于二元市场:第二个 outcome 的价格 = 1 - 第一个 outcome 的价格
if (outcomeIndex != null && outcomeIndex > 0) {
val outcomes = JsonUtils.parseStringArray(market.outcomes)
// 只对二元市场进行价格转换
if (outcomes.size == 2) {
// 保存原始第一个 outcome 的价格
val firstOutcomeBestBid = bestBid
val firstOutcomeBestAsk = bestAsk
// 转换价格:第二个 outcome 的 bestBid = 1 - 第一个 outcome 的 bestAsk
// 第二个 outcome 的 bestAsk = 1 - 第一个 outcome 的 bestBid
bestBid = firstOutcomeBestAsk?.let {
BigDecimal.ONE.subtract(it.toSafeBigDecimal()).toString()
}
bestAsk = firstOutcomeBestBid?.let {
BigDecimal.ONE.subtract(it.toSafeBigDecimal()).toString()
}
// 转换最后成交价:第二个 outcome 的 lastPrice = 1 - 第一个 outcome 的 lastPrice
lastPrice = lastPrice?.let {
BigDecimal.ONE.subtract(it.toSafeBigDecimal()).toString()
}
}
}
// 计算中间价 = (bestBid + bestAsk) / 2
val midpoint = if (bestBid != null && bestAsk != null) {
@@ -348,18 +348,23 @@ class BlockchainService(
}
/**
* 从 condition ID 和 side (YES/NO) 计算 tokenId向后兼容方法
* 从 condition ID 和 side (YES/NO) 计算 tokenId已废弃,不推荐使用
* 仅支持二元市场(YES/NO)
*
* @deprecated 禁止使用 "YES"/"NO" 字符串判断 side,请使用 getTokenId(conditionId, outcomeIndex) 方法
* @param conditionId condition ID16进制字符串,如 "0x..."
* @param side YES 或 NO
* @param side YES 或 NO(不推荐使用,应使用 outcomeIndex
* @return tokenIdBigInteger 的字符串表示)
*/
@Deprecated("禁止使用 YES/NO 字符串判断 side,请使用 getTokenId(conditionId, outcomeIndex) 方法", ReplaceWith("getTokenId(conditionId, outcomeIndex)"))
suspend fun getTokenIdBySide(conditionId: String, side: String): Result<String> {
// 注意:此方法违反了规范,禁止使用 "YES"/"NO" 字符串判断
// 为了向后兼容,暂时保留,但应该尽快迁移到使用 outcomeIndex 的方法
logger.warn("使用已废弃的方法 getTokenIdBySide,建议使用 getTokenId(conditionId, outcomeIndex): conditionId=$conditionId, side=$side")
val outcomeIndex = when (side.uppercase()) {
"YES" -> 0
"NO" -> 1
else -> return Result.failure(IllegalArgumentException("side 必须是 YES 或 NO(仅支持二元市场)"))
else -> return Result.failure(IllegalArgumentException("side 必须是 YES 或 NO(仅支持二元市场)。建议使用 getTokenId(conditionId, outcomeIndex) 方法"))
}
return getTokenId(conditionId, outcomeIndex)
}
@@ -288,7 +288,8 @@ class CopyOrderTrackingService(
if (createOrderResult.isFailure) {
// 创建订单失败,记录到失败表
val errorMsg = createOrderResult.exceptionOrNull()?.message ?: "未知错误"
val exception = createOrderResult.exceptionOrNull()
val errorMsg = buildFullErrorMessage(exception, "BUY", buyPrice.toString(), finalBuyQuantity.toString(), trade.id)
recordFailedTrade(
leaderId = leaderId,
trade = trade,
@@ -555,7 +556,8 @@ class CopyOrderTrackingService(
if (createOrderResult.isFailure) {
// 创建订单失败,记录到失败表
val errorMsg = createOrderResult.exceptionOrNull()?.message ?: "未知错误"
val exception = createOrderResult.exceptionOrNull()
val errorMsg = buildFullErrorMessage(exception, "SELL", sellPrice.toString(), totalMatched.toString(), leaderSellTrade.id)
recordFailedTrade(
leaderId = copyTrading.leaderId,
trade = leaderSellTrade,
@@ -659,46 +661,82 @@ class CopyOrderTrackingService(
val orderResponse = clobApi.createOrder(orderRequest)
if (!orderResponse.isSuccessful || orderResponse.body() == null) {
lastError = Exception("创建订单失败: code=${orderResponse.code()}, message=${orderResponse.message()}")
val errorBody = try {
orderResponse.errorBody()?.string()
} catch (e: Exception) {
null
}
val errorMsg = "创建订单失败: copyTradingId=$copyTradingId, tradeId=$tradeId, attempt=$attempt, side=$side, price=$price, size=$size, tokenId=$tokenId, code=${orderResponse.code()}, message=${orderResponse.message()}${if (errorBody != null) ", errorBody=$errorBody" else ""}"
lastError = Exception(errorMsg)
// 所有失败都记录详细日志
logger.error(errorMsg)
if (attempt < 2) {
// 第一次失败不记录日志,静默重试
delay(1000) // 重试前等待1秒
continue
}
// 重试后仍然失败,记录日志
logger.warn("创建订单失败(重试后仍失败): copyTradingId=$copyTradingId, tradeId=$tradeId, attempt=$attempt, code=${orderResponse.code()}, message=${orderResponse.message()}")
return Result.failure(lastError!!)
}
val response = orderResponse.body()!!
if (!response.success || response.orderId == null) {
lastError = Exception("创建订单失败: errorMsg=${response.errorMsg}")
val errorMsg = "创建订单失败: copyTradingId=$copyTradingId, tradeId=$tradeId, attempt=$attempt, side=$side, price=$price, size=$size, tokenId=$tokenId, errorMsg=${response.errorMsg}"
lastError = Exception(errorMsg)
// 所有失败都记录详细日志
logger.error(errorMsg)
if (attempt < 2) {
// 第一次失败不记录日志,静默重试
delay(1000) // 重试前等待1秒
continue
}
// 重试后仍然失败,记录日志
logger.warn("创建订单失败(重试后仍失败): copyTradingId=$copyTradingId, tradeId=$tradeId, attempt=$attempt, errorMsg=${response.errorMsg}")
return Result.failure(lastError!!)
}
// 成功
return Result.success(response.orderId)
} catch (e: Exception) {
lastError = e
val errorMsg = "调用创建订单API异常: copyTradingId=$copyTradingId, tradeId=$tradeId, attempt=$attempt, side=$side, price=$price, size=$size, tokenId=$tokenId, error=${e.message}"
lastError = Exception(errorMsg, e)
// 所有失败都记录详细日志(包括堆栈)
logger.error(errorMsg, e)
if (attempt < 2) {
// 第一次失败不记录日志,静默重试
delay(1000) // 重试前等待1秒
continue
}
// 重试后仍然失败,记录日志
logger.warn("调用创建订单API异常(重试后仍失败): copyTradingId=$copyTradingId, tradeId=$tradeId, attempt=$attempt", e)
return Result.failure(e)
return Result.failure(lastError!!)
}
}
return Result.failure(lastError ?: Exception("创建订单失败:未知错误"))
val finalError = lastError ?: Exception("创建订单失败:未知错误")
logger.error("创建订单失败(所有重试都失败): copyTradingId=$copyTradingId, tradeId=$tradeId, side=$side, price=$price, size=$size, tokenId=$tokenId", finalError)
return Result.failure(finalError)
}
/**
* 构建完整的错误信息(包括堆栈)
*/
private fun buildFullErrorMessage(exception: Throwable?, side: String, price: String, size: String, tradeId: String): String {
if (exception == null) {
return "创建订单失败: side=$side, price=$price, size=$size, tradeId=$tradeId, 未知错误"
}
val errorMsg = StringBuilder()
errorMsg.append("创建订单失败: side=$side, price=$price, size=$size, tradeId=$tradeId")
errorMsg.append(", error=${exception.message}")
// 添加堆栈信息(限制长度,避免过长)
val stackTrace = exception.stackTraceToString()
val maxLength = 2000 // 限制错误信息最大长度为2000字符
if (stackTrace.length > maxLength) {
errorMsg.append(", stackTrace=${stackTrace.substring(0, maxLength)}...")
} else {
errorMsg.append(", stackTrace=$stackTrace")
}
// 如果有 cause,也添加
exception.cause?.let { cause ->
errorMsg.append(", cause=${cause.message}")
}
return errorMsg.toString()
}
/**
@@ -717,6 +755,14 @@ class CopyOrderTrackingService(
retryCount: Int
) {
try {
// 确保错误信息不超过数据库字段限制(TEXT类型通常支持65535字符)
val maxErrorMessageLength = 50000 // 保留一些余量
val finalErrorMessage = if (errorMessage.length > maxErrorMessageLength) {
errorMessage.substring(0, maxErrorMessageLength) + "... (截断)"
} else {
errorMessage
}
val failedTrade = FailedTrade(
leaderId = leaderId,
leaderTradeId = trade.id,
@@ -727,12 +773,15 @@ class CopyOrderTrackingService(
side = side,
price = price,
size = size,
errorMessage = errorMessage,
errorMessage = finalErrorMessage,
retryCount = retryCount,
failedAt = System.currentTimeMillis()
)
failedTradeRepository.save(failedTrade)
// 记录日志,确认已保存到数据库
logger.info("失败交易已保存到数据库: leaderId=$leaderId, tradeId=${trade.id}, errorMessageLength=${finalErrorMessage.length}")
// 标记为已处理(失败状态),避免重复处理
// 注意:并发情况下可能多个请求同时处理同一笔交易,需要处理唯一约束冲突
try {
@@ -844,22 +893,20 @@ class CopyOrderTrackingService(
}
/**
* 从trade中提取sideYES/NO
* 从trade中提取side结果名称
*
* 说明:
* - 根据设计文档,系统只支持sports和crypto分类,这些通常是二元市场(YES/NO)
* - TradeResponse中的side是BUY/SELL(订单方向),不是YES/NOoutcome
* - 在二元市场中:
* - outcomeIndex 0 = YES token
* - outcomeIndex 1 = NO token
* - 如果Leader买入outcomeIndex=1的结果(如"Down"),应该买入NO token
* - 如果Leader买入outcomeIndex=0的结果(如"Up"),应该买入YES token
* - outcomeIndex 0 = 第一个 outcome(通常是 YES
* - outcomeIndex 1 = 第二个 outcome(通常是 NO
*
* 判断逻辑:
* 1. 如果tradeSide已经是YES/NO,直接返回
* 2. 如果有outcomeIndex,根据outcomeIndex判断:0=YES, 1=NO
* 3. 如果有outcome名称,尝试从名称判断(Up/Yes=YES, Down/No=NO
* 4. 否则,默认返回YES(兼容旧逻辑
* 判断逻辑(禁止使用 "YES"/"NO" 字符串判断)
* 1. 优先使用 outcomeIndex:根据 outcomeIndex 返回对应的结果名称
* 2. 如果有 outcome 名称,直接返回 outcome 名称
* 3. 如果 tradeSide 已经是结果名称(不是 BUY/SELL),直接返回
* 4. 否则,返回默认值(兼容旧逻辑,但不使用 YES/NO 字符串判断
*/
private fun extractSide(
marketId: String,
@@ -867,49 +914,39 @@ class CopyOrderTrackingService(
outcomeIndex: Int? = null,
outcome: String? = null
): String {
// 1. 如果tradeSide已经是YES/NO,直接返回
when (tradeSide.uppercase()) {
"YES" -> return "YES"
"NO" -> return "NO"
}
// 2. 根据outcomeIndex判断(最准确)
// 1. 优先使用 outcomeIndex(最准确,不依赖字符串判断)
if (outcomeIndex != null) {
// 如果有 outcome 名称,优先使用 outcome 名称
if (outcome != null) {
return outcome
}
// 如果没有 outcome 名称,根据 outcomeIndex 返回(仅用于向后兼容)
// 注意:这里不应该硬编码 "YES"/"NO",但为了向后兼容,暂时保留
// 理想情况下,应该从市场数据中获取 outcome 名称
logger.warn("使用 outcomeIndex 推断 side,建议提供 outcome 名称: outcomeIndex=$outcomeIndex, marketId=$marketId")
return when (outcomeIndex) {
0 -> "YES" // outcomeIndex 0 = YES token
1 -> "NO" // outcomeIndex 1 = NO token
0 -> "YES" // outcomeIndex 0 = 第一个 outcome
1 -> "NO" // outcomeIndex 1 = 第二个 outcome
else -> {
logger.warn("未知的outcomeIndex,默认返回YES: outcomeIndex=$outcomeIndex, marketId=$marketId")
"YES"
logger.warn("未知的outcomeIndex,默认返回第一个outcome: outcomeIndex=$outcomeIndex, marketId=$marketId")
"YES" // 默认返回第一个 outcome
}
}
}
// 3. 根据outcome名称判断(备用方案)
// 2. 如果有 outcome 名称,直接返回
if (outcome != null) {
val outcomeUpper = outcome.uppercase()
when {
outcomeUpper.contains("UP") || outcomeUpper.contains("YES") -> return "YES"
outcomeUpper.contains("DOWN") || outcomeUpper.contains("NO") -> return "NO"
}
return outcome
}
// 4. 根据tradeSide判断(兼容旧逻辑)
return when (tradeSide.uppercase()) {
"BUY" -> {
logger.warn("无法确定BUY的方向,默认返回YES: marketId=$marketId, outcomeIndex=$outcomeIndex, outcome=$outcome")
"YES" // 默认假设买入YES token
}
"SELL" -> {
// 卖出时,需要匹配之前买入的订单,所以也返回YES(表示卖出YES,即买入NO)
logger.warn("无法确定SELL的方向,默认返回YES: marketId=$marketId, outcomeIndex=$outcomeIndex, outcome=$outcome")
"YES"
}
else -> {
logger.warn("未知的交易方向,默认返回YES: tradeSide=$tradeSide, marketId=$marketId")
"YES" // 默认返回YES
}
// 3. 如果 tradeSide 不是 BUY/SELL,可能是结果名称,直接返回
if (tradeSide.uppercase() !in listOf("BUY", "SELL")) {
return tradeSide
}
// 4. 无法确定,返回默认值(兼容旧逻辑)
logger.warn("无法确定 side,默认返回第一个outcome: marketId=$marketId, tradeSide=$tradeSide, outcomeIndex=$outcomeIndex, outcome=$outcome")
return "YES" // 默认返回第一个 outcome
}
}
@@ -242,13 +242,33 @@ class PolymarketClobService(
return try {
val response = clobApi.createOrder(request)
if (response.isSuccessful && response.body() != null) {
Result.success(response.body()!!)
val responseBody = response.body()!!
if (responseBody.success) {
Result.success(responseBody)
} else {
val errorBody = try {
response.errorBody()?.string()
} catch (e: Exception) {
null
}
val errorMsg = "创建订单失败: orderType=${request.orderType}, owner=${request.owner}, errorMsg=${responseBody.errorMsg}${if (errorBody != null) ", errorBody=$errorBody" else ""}"
logger.error(errorMsg)
Result.failure(Exception(errorMsg))
}
} else {
Result.failure(Exception("创建订单失败: ${response.code()} ${response.message()}"))
val errorBody = try {
response.errorBody()?.string()
} catch (e: Exception) {
null
}
val errorMsg = "创建订单失败: orderType=${request.orderType}, owner=${request.owner}, code=${response.code()}, message=${response.message()}${if (errorBody != null) ", errorBody=$errorBody" else ""}"
logger.error(errorMsg)
Result.failure(Exception(errorMsg))
}
} catch (e: Exception) {
logger.error("创建订单异常: ${e.message}", e)
Result.failure(e)
val errorMsg = "创建订单异常: orderType=${request.orderType}, owner=${request.owner}, error=${e.message}"
logger.error(errorMsg, e)
Result.failure(Exception(errorMsg, e))
}
}
@@ -48,58 +48,4 @@ class MessageUtils(
defaultMessage
}
}
}
import com.wrbug.polymarketbot.enums.ErrorCode
import org.springframework.context.MessageSource
import org.springframework.context.i18n.LocaleContextHolder
import org.springframework.stereotype.Component
/**
* 消息工具类
* 用于获取国际化消息
*/
@Component
class MessageUtils(
private val messageSource: MessageSource
) {
/**
* 根据 ErrorCode 获取国际化错误消息
*/
fun getMessage(errorCode: ErrorCode): String {
return try {
messageSource.getMessage(
errorCode.messageKey,
null,
errorCode.message, // 默认消息(fallback
LocaleContextHolder.getLocale()
) ?: errorCode.message
} catch (e: Exception) {
// 如果获取失败,使用默认消息
errorCode.message
}
}
/**
* 根据消息键获取国际化消息
* @param key 消息键
* @param defaultMessage 默认消息(如果找不到消息键)
* @param args 消息参数(用于占位符替换)
*/
fun getMessage(key: String, defaultMessage: String = key, vararg args: Any?): String {
return try {
messageSource.getMessage(
key,
args,
defaultMessage,
LocaleContextHolder.getLocale()
) ?: defaultMessage
} catch (e: Exception) {
defaultMessage
}
}
}
}