From 6af76c4d8085793628b1bbb3d61dfc73bc5421a7 Mon Sep 17 00:00:00 2001 From: WrBug Date: Sun, 11 Jan 2026 14:33:30 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=E9=87=8D=E5=A4=8D=E5=8F=91=E9=80=81=E5=92=8C?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E6=98=BE=E7=A4=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 修复并发导致的重复通知问题 - 在 OrderStatusUpdateService 中实现双重检查机制 - 先保存订单标记为已发送,再重新查询数据库检查 - 防止定时任务并发时重复发送同一订单的通知 - 同时修复买入订单和卖出订单的通知逻辑 2. Telegram 通知时间改为订单创建时间 - TelegramNotificationService.sendOrderSuccessNotification 添加 orderTime 参数 - 使用订单的 createdAt 时间戳作为通知显示时间 - 而不是使用当前通知发送时间 - 更准确反映订单的实际创建时间 修改文件: - OrderStatusUpdateService.kt: 实现双重检查机制,防止并发重复通知 - TelegramNotificationService.kt: 添加 orderTime 参数支持订单时间显示 - AccountService.kt: 手动创建订单时传递订单创建时间 --- .../service/accounts/AccountService.kt | 6 +- .../statistics/OrderStatusUpdateService.kt | 142 ++++++++++++------ .../system/TelegramNotificationService.kt | 17 ++- 3 files changed, 112 insertions(+), 53 deletions(-) diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/accounts/AccountService.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/accounts/AccountService.kt index 369cda6..a401b4a 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/accounts/AccountService.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/accounts/AccountService.kt @@ -948,6 +948,9 @@ class AccountService( java.util.Locale("zh", "CN") // 默认简体中文 } + // 使用当前时间作为订单创建时间 + val orderTime = System.currentTimeMillis() + telegramNotificationService?.sendOrderSuccessNotification( orderId = orderId, marketTitle = marketTitle, @@ -963,7 +966,8 @@ class AccountService( apiSecret = try { cryptoUtils.decrypt(account.apiSecret!!) } catch (e: Exception) { null }, apiPassphrase = try { cryptoUtils.decrypt(account.apiPassphrase!!) } catch (e: Exception) { null }, walletAddressForApi = account.walletAddress, - locale = locale + locale = locale, + orderTime = orderTime // 使用订单创建时间 ) } catch (e: Exception) { logger.warn("发送订单成功通知失败: ${e.message}", e) 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 8ced0b7..0c9268c 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 @@ -344,7 +344,8 @@ class OrderStatusUpdateService( copyTrading = copyTrading, clobApi = clobApi, apiSecret = apiSecret, - apiPassphrase = apiPassphrase + apiPassphrase = apiPassphrase, + orderCreatedAt = record.createdAt ) } else { logger.debug("自动生成的订单,跳过发送通知: orderId=${record.sellOrderId}") @@ -429,19 +430,7 @@ class OrderStatusUpdateService( totalRealizedPnl = totalRealizedPnl.add(updatedRealizedPnl) } - // 发送通知(使用实际价格) - sendSellOrderNotification( - record = record, - actualPrice = actualSellPrice.toString(), - actualSize = record.totalMatchedQuantity.toString(), - account = account, - copyTrading = copyTrading, - clobApi = clobApi, - apiSecret = apiSecret, - apiPassphrase = apiPassphrase - ) - - // 更新卖出记录 + // 先更新卖出记录,标记 priceUpdated = true(在发送通知之前更新) // 注意:SellMatchRecord 的字段都是 val,需要创建新对象 val updatedRecord = SellMatchRecord( id = record.id, @@ -459,20 +448,31 @@ class OrderStatusUpdateService( ) sellMatchRecordRepository.save(updatedRecord) - logger.info("更新卖出订单价格成功并已发送通知: orderId=${record.sellOrderId}, 原价格=${record.sellPrice}, 新价格=$actualSellPrice") - } else { - // 价格相同,但已经查询过,发送通知并标记为已处理 - sendSellOrderNotification( - record = record, - actualPrice = actualSellPrice.toString(), - actualSize = record.totalMatchedQuantity.toString(), - account = account, - copyTrading = copyTrading, - clobApi = clobApi, - apiSecret = apiSecret, - apiPassphrase = apiPassphrase - ) + logger.info("更新卖出订单价格成功: orderId=${record.sellOrderId}, 原价格=${record.sellPrice}, 新价格=$actualSellPrice") + // 重新从数据库查询最新的记录状态,检查是否已被其他任务标记为已处理 + val latestRecord = sellMatchRecordRepository.findById(record.id!!).orElse(null) + if (latestRecord == null) { + logger.warn("卖出记录已被删除,跳过发送通知: orderId=${record.sellOrderId}, recordId=${record.id}") + } else if (latestRecord.priceUpdated) { + logger.debug("卖出记录已被标记为已处理,跳过重复发送通知: orderId=${record.sellOrderId}, recordId=${record.id}") + } else { + // 发送通知(使用实际价格) + sendSellOrderNotification( + record = updatedRecord, + actualPrice = actualSellPrice.toString(), + actualSize = record.totalMatchedQuantity.toString(), + account = account, + copyTrading = copyTrading, + clobApi = clobApi, + apiSecret = apiSecret, + apiPassphrase = apiPassphrase, + orderCreatedAt = record.createdAt + ) + logger.info("卖出订单通知已发送: orderId=${record.sellOrderId}") + } + } else { + // 价格相同,但已经查询过,先标记为已处理再发送通知 val updatedRecord = SellMatchRecord( id = record.id, copyTradingId = record.copyTradingId, @@ -488,7 +488,30 @@ class OrderStatusUpdateService( createdAt = record.createdAt ) sellMatchRecordRepository.save(updatedRecord) - logger.debug("卖出订单价格无需更新但已发送通知: orderId=${record.sellOrderId}, price=$actualSellPrice") + + logger.debug("卖出订单价格无需更新: orderId=${record.sellOrderId}, price=$actualSellPrice") + + // 重新从数据库查询最新的记录状态,检查是否已被其他任务标记为已处理 + val latestRecord = sellMatchRecordRepository.findById(record.id!!).orElse(null) + if (latestRecord == null) { + logger.warn("卖出记录已被删除,跳过发送通知: orderId=${record.sellOrderId}, recordId=${record.id}") + } else if (latestRecord.priceUpdated) { + logger.debug("卖出记录已被标记为已处理,跳过重复发送通知: orderId=${record.sellOrderId}, recordId=${record.id}") + } else { + // 发送通知 + sendSellOrderNotification( + record = updatedRecord, + actualPrice = actualSellPrice.toString(), + actualSize = record.totalMatchedQuantity.toString(), + account = account, + copyTrading = copyTrading, + clobApi = clobApi, + apiSecret = apiSecret, + apiPassphrase = apiPassphrase, + orderCreatedAt = record.createdAt + ) + logger.info("卖出订单通知已发送: orderId=${record.sellOrderId}") + } } } catch (e: Exception) { logger.warn("更新卖出订单价格失败: orderId=${record.sellOrderId}, error=${e.message}", e) @@ -521,7 +544,7 @@ class OrderStatusUpdateService( // 验证 orderId 格式(必须以 0x 开头的 16 进制) if (!isValidOrderId(order.buyOrderId)) { logger.warn("买入订单ID格式无效,直接标记为已发送通知: orderId=${order.buyOrderId}") - // 对于非 0x 开头的订单ID,直接标记为已发送,使用临时数据发送通知 + // 对于非 0x 开头的订单ID,先标记为已发送,再使用临时数据发送通知 val updatedOrder = CopyOrderTracking( id = order.id, copyTradingId = order.copyTradingId, @@ -542,7 +565,16 @@ class OrderStatusUpdateService( updatedAt = System.currentTimeMillis() ) copyOrderTrackingRepository.save(updatedOrder) - sendBuyOrderNotification(updatedOrder, useTemporaryData = true) + + // 重新从数据库查询最新的订单状态,检查是否已被其他任务标记为已发送通知 + val latestOrder = copyOrderTrackingRepository.findById(order.id!!).orElse(null) + if (latestOrder == null) { + logger.warn("订单已被删除,跳过发送通知: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}") + } else if (latestOrder.notificationSent) { + logger.debug("订单已被标记为已发送通知,跳过重复发送: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}") + } else { + sendBuyOrderNotification(updatedOrder, useTemporaryData = true, orderCreatedAt = order.createdAt) + } continue } @@ -630,7 +662,8 @@ class OrderStatusUpdateService( // 更新订单数据(如果实际数据与临时数据不同) val needUpdate = actualPrice != order.price || actualSize != order.quantity - // 创建更新后的订单对象 + // 先保存更新后的订单,标记 notificationSent = true + // 这样可以防止其他并发任务重复发送通知 val updatedOrder = CopyOrderTracking( id = order.id, copyTradingId = order.copyTradingId, @@ -651,7 +684,7 @@ class OrderStatusUpdateService( updatedAt = System.currentTimeMillis() ) - // 保存更新后的订单 + // 保存更新后的订单(在发送通知之前保存) copyOrderTrackingRepository.save(updatedOrder) if (needUpdate) { @@ -660,18 +693,27 @@ class OrderStatusUpdateService( logger.debug("买入订单数据无需更新: orderId=${order.buyOrderId}") } - // 发送通知(使用实际数据) - sendBuyOrderNotification( - order = updatedOrder, - actualPrice = actualPrice.toString(), - actualSize = actualSize.toString(), - actualOutcome = actualOutcome, - account = account, - copyTrading = copyTrading, - clobApi = clobApi, - apiSecret = apiSecret, - apiPassphrase = apiPassphrase - ) + // 重新从数据库查询最新的订单状态,检查是否已被其他任务标记为已发送通知 + val latestOrder = copyOrderTrackingRepository.findById(order.id!!).orElse(null) + if (latestOrder == null) { + logger.warn("订单已被删除,跳过发送通知: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}") + } else if (latestOrder.notificationSent) { + logger.debug("订单已被标记为已发送通知,跳过重复发送: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}") + } else { + // 发送通知(使用实际数据) + sendBuyOrderNotification( + order = updatedOrder, + actualPrice = actualPrice.toString(), + actualSize = actualSize.toString(), + actualOutcome = actualOutcome, + account = account, + copyTrading = copyTrading, + clobApi = clobApi, + apiSecret = apiSecret, + apiPassphrase = apiPassphrase, + orderCreatedAt = order.createdAt + ) + } } catch (e: Exception) { logger.warn("更新买入订单失败: orderId=${order.buyOrderId}, error=${e.message}", e) // 继续处理下一条记录 @@ -695,7 +737,8 @@ class OrderStatusUpdateService( copyTrading: CopyTrading? = null, clobApi: PolymarketClobApi? = null, apiSecret: String? = null, - apiPassphrase: String? = null + apiPassphrase: String? = null, + orderCreatedAt: Long? = null // 订单创建时间(毫秒时间戳) ) { if (telegramNotificationService == null) { return @@ -762,7 +805,8 @@ class OrderStatusUpdateService( walletAddressForApi = finalAccount.walletAddress, locale = locale, leaderName = leaderName, - configName = configName + configName = configName, + orderTime = orderCreatedAt // 使用订单创建时间 ) logger.info("买入订单通知已发送: orderId=${order.buyOrderId}, copyTradingId=${order.copyTradingId}") @@ -785,7 +829,8 @@ class OrderStatusUpdateService( copyTrading: CopyTrading? = null, clobApi: PolymarketClobApi? = null, apiSecret: String? = null, - apiPassphrase: String? = null + apiPassphrase: String? = null, + orderCreatedAt: Long? = null // 订单创建时间(毫秒时间戳) ) { if (telegramNotificationService == null) { return @@ -852,7 +897,8 @@ class OrderStatusUpdateService( walletAddressForApi = finalAccount.walletAddress, locale = locale, leaderName = leaderName, - configName = configName + configName = configName, + orderTime = orderCreatedAt // 使用订单创建时间 ) logger.info("卖出订单通知已发送: orderId=${record.sellOrderId}, copyTradingId=${record.copyTradingId}") diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/system/TelegramNotificationService.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/system/TelegramNotificationService.kt index 625c244..e02b2de 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/system/TelegramNotificationService.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/system/TelegramNotificationService.kt @@ -77,6 +77,7 @@ class TelegramNotificationService( * @param apiPassphrase API Passphrase(可选,用于查询订单详情) * @param walletAddressForApi 钱包地址(可选,用于查询订单详情) * @param locale 语言设置(可选,如果提供则使用,否则使用 LocaleContextHolder 获取) + * @param orderTime 订单时间(可选,如果提供则使用订单创建时间,否则使用当前通知时间) */ suspend fun sendOrderSuccessNotification( orderId: String?, @@ -96,7 +97,8 @@ class TelegramNotificationService( walletAddressForApi: String? = null, locale: java.util.Locale? = null, leaderName: String? = null, // Leader 名称(备注) - configName: String? = null // 跟单配置名 + configName: String? = null, // 跟单配置名 + orderTime: Long? = null // 订单创建时间(毫秒时间戳),用于通知中的时间显示 ) { // 1. 如果提供了 orderId,检查是否已发送过通知(去重) if (orderId != null) { @@ -189,7 +191,8 @@ class TelegramNotificationService( walletAddress = walletAddress, locale = currentLocale, leaderName = leaderName, - configName = configName + configName = configName, + orderTime = orderTime ) sendMessage(message) } @@ -699,7 +702,8 @@ class TelegramNotificationService( walletAddress: String?, locale: java.util.Locale, leaderName: String? = null, // Leader 名称(备注) - configName: String? = null // 跟单配置名 + configName: String? = null, // 跟单配置名 + orderTime: Long? = null // 订单创建时间(毫秒时间戳) ): String { // 获取多语言文本 @@ -749,7 +753,12 @@ class TelegramNotificationService( "" } - val time = DateUtils.formatDateTime() + // 使用订单时间(如果提供),否则使用当前通知时间 + val time = if (orderTime != null) { + DateUtils.formatDateTime(orderTime) + } else { + DateUtils.formatDateTime() + } // 转义 HTML 特殊字符 val escapedMarketTitle = marketTitle.replace("<", "<").replace(">", ">")