From abcc004606d521231eab5cd8f3b3bea666cec1c8 Mon Sep 17 00:00:00 2001 From: WrBug Date: Wed, 14 Jan 2026 14:55:05 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E5=A4=B1=E8=B4=A5=E7=9A=84=E5=85=9C=E5=BA=95?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E9=81=BF=E5=85=8D=E5=9B=A0=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=E5=BC=82=E5=B8=B8=E8=AF=AF=E5=88=A0=E8=AE=A2=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 orderNullDetectionTime 缓存,记录订单详情首次检测为 null 的时间戳 - 订单详情为 null 时,首次检测不删除,等待 1 分钟重试窗口 - 超过重试窗口仍为 null 才删除订单 - 订单详情正常或已部分卖出时清除缓存 - 避免 ResponseLoggingInterceptor 导致的响应体被消费问题 修改文件: - OrderStatusUpdateService.kt 相关 issue: 订单 0xd245a852... 因响应体为 null 被误删 --- .../statistics/OrderStatusUpdateService.kt | 61 ++++++++++++++++--- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/OrderStatusUpdateService.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/OrderStatusUpdateService.kt index b5269c6..65d6b27 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/OrderStatusUpdateService.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/OrderStatusUpdateService.kt @@ -18,6 +18,7 @@ import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.math.BigDecimal +import java.util.concurrent.ConcurrentHashMap /** * 订单状态更新服务 @@ -42,6 +43,12 @@ class OrderStatusUpdateService( private val updateScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + // 缓存首次检测到订单详情为 null 的时间戳(订单ID -> 首次检测时间) + private val orderNullDetectionTime = ConcurrentHashMap() + + // 订单详情为 null 的重试时间窗口(1分钟) + private val ORDER_NULL_RETRY_WINDOW_MS = 60000L + @EventListener(ApplicationReadyEvent::class) fun onApplicationReady() { logger.info("订单状态更新服务已启动,将每5秒轮询一次") @@ -219,25 +226,44 @@ class OrderStatusUpdateService( // 响应体也可能返回字符串 "null",Gson 解析时会返回 null val orderDetail = orderResponse.body() if (orderDetail == null) { - // HTTP 200 且响应体为 null(或字符串 "null"),表示订单不存在 + // HTTP 200 且响应体为 null(或字符串 "null"),可能是网络异常或 API 暂时不可用 + // 使用兜底逻辑:首次检测不删除,1分钟后仍为 null 才删除 + val firstDetectionTime = orderNullDetectionTime.getOrPut(order.buyOrderId) { System.currentTimeMillis() } + val currentTime = System.currentTimeMillis() + // 检查订单是否已部分卖出,如果已部分卖出则保留订单用于统计 val hasMatchedDetails = sellMatchDetailRepository.findByTrackingId(order.id!!).isNotEmpty() if (hasMatchedDetails || order.matchedQuantity > BigDecimal.ZERO) { - logger.debug("订单不存在但已部分卖出,保留订单用于统计: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, matchedQuantity=${order.matchedQuantity}") + logger.debug("订单详情为 null 但已部分卖出,保留订单用于统计: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, matchedQuantity=${order.matchedQuantity}") + // 清除缓存,下次重新检测 + orderNullDetectionTime.remove(order.buyOrderId) continue } - // 订单不存在且未部分卖出,删除本地订单 - logger.info("订单不存在(HTTP 200 但响应体为空),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}") + // 检查是否超过重试时间窗口 + if (currentTime - firstDetectionTime < ORDER_NULL_RETRY_WINDOW_MS) { + // 未超过重试窗口,记录日志并等待下次轮询 + val elapsedSeconds = ((currentTime - firstDetectionTime) / 1000).toInt() + logger.debug("订单详情为 null(可能是网络异常),等待重试: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, 已等待=${elapsedSeconds}s, 重试窗口=${ORDER_NULL_RETRY_WINDOW_MS / 1000}s") + continue + } + + // 超过重试窗口,删除本地订单 + logger.warn("订单详情为 null 超过重试窗口,删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, 已等待=$((currentTime - firstDetectionTime) / 1000}s") try { copyOrderTrackingRepository.deleteById(order.id!!) logger.info("已删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}") + // 清除缓存 + orderNullDetectionTime.remove(order.buyOrderId) } catch (e: Exception) { logger.error("删除本地订单失败: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, error=${e.message}", e) } continue } + // 订单详情不为 null,清除缓存 + orderNullDetectionTime.remove(order.buyOrderId) + // 检查订单是否成交 // 如果订单状态不是 FILLED 且已成交数量为0,说明未成交,删除 val sizeMatched = orderDetail.sizeMatched?.toSafeBigDecimal() ?: BigDecimal.ZERO @@ -611,25 +637,44 @@ class OrderStatusUpdateService( // 响应体也可能返回字符串 "null",Gson 解析时会返回 null val orderDetail = orderResponse.body() if (orderDetail == null) { - // HTTP 200 且响应体为 null(或字符串 "null"),表示订单不存在 + // HTTP 200 且响应体为 null(或字符串 "null"),可能是网络异常或 API 暂时不可用 + // 使用兜底逻辑:首次检测不删除,1分钟后仍为 null 才删除 + val firstDetectionTime = orderNullDetectionTime.getOrPut(order.buyOrderId) { System.currentTimeMillis() } + val currentTime = System.currentTimeMillis() + // 检查订单是否已部分卖出,如果已部分卖出则保留订单用于统计 val hasMatchedDetails = sellMatchDetailRepository.findByTrackingId(order.id!!).isNotEmpty() if (hasMatchedDetails || order.matchedQuantity > BigDecimal.ZERO) { - logger.debug("订单不存在但已部分卖出,保留订单用于统计: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, matchedQuantity=${order.matchedQuantity}") + logger.debug("订单详情为 null 但已部分卖出,保留订单用于统计: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, matchedQuantity=${order.matchedQuantity}") + // 清除缓存,下次重新检测 + orderNullDetectionTime.remove(order.buyOrderId) continue } - // 订单不存在且未部分卖出,删除本地订单 - logger.info("订单不存在(HTTP 200 但响应体为空),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}") + // 检查是否超过重试时间窗口 + if (currentTime - firstDetectionTime < ORDER_NULL_RETRY_WINDOW_MS) { + // 未超过重试窗口,记录日志并等待下次轮询 + val elapsedSeconds = ((currentTime - firstDetectionTime) / 1000).toInt() + logger.debug("订单详情为 null(可能是网络异常),等待重试: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, 已等待=${elapsedSeconds}s, 重试窗口=${ORDER_NULL_RETRY_WINDOW_MS / 1000}s") + continue + } + + // 超过重试窗口,删除本地订单 + logger.warn("订单详情为 null 超过重试窗口,删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, 已等待=$((currentTime - firstDetectionTime) / 1000)s") try { copyOrderTrackingRepository.deleteById(order.id!!) logger.info("已删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}") + // 清除缓存 + orderNullDetectionTime.remove(order.buyOrderId) } catch (e: Exception) { logger.error("删除本地订单失败: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, error=${e.message}", e) } continue } + // 订单详情不为 null,清除缓存 + orderNullDetectionTime.remove(order.buyOrderId) + // 获取实际价格和数量 val actualPrice = orderDetail.price?.toSafeBigDecimal() ?: order.price val actualSize = orderDetail.originalSize?.toSafeBigDecimal() ?: order.quantity