From 89fb980da7fcb1416cab521176727bd3bbe70149 Mon Sep 17 00:00:00 2001 From: WrBug Date: Sun, 28 Dec 2025 04:51:07 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=20polygon.rpc.ur?= =?UTF-8?q?l=20=E9=85=8D=E7=BD=AE=EF=BC=8C=E4=BD=BF=E7=94=A8=20RpcNodeServ?= =?UTF-8?q?ice=20=E7=BB=9F=E4=B8=80=E7=AE=A1=E7=90=86=20RPC=20=E8=8A=82?= =?UTF-8?q?=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 application.properties 中的 polygon.rpc.url 配置 - 更新 ApiHealthCheckService 直接使用 RpcNodeService.getHttpUrl() - 删除所有 Docker Compose 配置中的 POLYGON_RPC_URL 环境变量 - 删除所有部署脚本中的 POLYGON_RPC_URL 环境变量 - 更新所有文档,移除 POLYGON_RPC_URL 相关说明 - 删除 application.properties 中无用的 position.push 配置项 - 修正日志配置中的包名(polyhermes -> polymarketbot) 现在系统通过 RpcNodeService 从数据库读取 RPC 节点配置,用户可以通过系统设置页面管理 RPC 节点,不再需要环境变量配置。 --- README.md | 2 - README_EN.md | 2 - backend/deploy.sh | 3 - backend/docker-compose.yml | 1 - .../statistics/CopyOrderTrackingService.kt | 144 +++++++++--------- .../service/system/ApiHealthCheckService.kt | 24 +-- .../src/main/resources/application.properties | 11 -- deploy.sh | 3 - docker-compose.prod.env.example | 6 - docker-compose.prod.yml | 1 - docker-compose.yml | 1 - docs/en/DEPLOYMENT.md | 3 - docs/en/DEVELOPMENT.md | 3 - docs/zh/DEPLOYMENT.md | 3 - docs/zh/DEVELOPMENT.md | 3 - 15 files changed, 75 insertions(+), 135 deletions(-) diff --git a/README.md b/README.md index 7be29a9..e447516 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,6 @@ DB_USERNAME=root DB_PASSWORD=your_password_here SPRING_PROFILES_ACTIVE=prod SERVER_PORT=80 -POLYGON_RPC_URL=https://polygon-rpc.com JWT_SECRET=your-jwt-secret-key-change-in-production ADMIN_RESET_PASSWORD_KEY=your-admin-reset-key-change-in-production EOF @@ -337,7 +336,6 @@ cd frontend | `DB_USERNAME` | 数据库用户名 | `root` | | `DB_PASSWORD` | 数据库密码 | - | | `SERVER_PORT` | 后端服务端口 | `8000` | -| `POLYGON_RPC_URL` | Polygon RPC 地址 | `https://polygon-rpc.com` | | `JWT_SECRET` | JWT 密钥 | - | | `ADMIN_RESET_PASSWORD_KEY` | 管理员密码重置密钥 | - | | `CRYPTO_SECRET_KEY` | 加密密钥(用于加密存储私钥和 API Key) | - | diff --git a/README_EN.md b/README_EN.md index 7267aa4..4c72814 100644 --- a/README_EN.md +++ b/README_EN.md @@ -270,7 +270,6 @@ DB_USERNAME=root DB_PASSWORD=your_password_here SPRING_PROFILES_ACTIVE=prod SERVER_PORT=80 -POLYGON_RPC_URL=https://polygon-rpc.com JWT_SECRET=your-jwt-secret-key-change-in-production ADMIN_RESET_PASSWORD_KEY=your-admin-reset-key-change-in-production EOF @@ -337,7 +336,6 @@ cd frontend | `DB_USERNAME` | Database username | `root` | | `DB_PASSWORD` | Database password | - | | `SERVER_PORT` | Backend service port | `8000` | -| `POLYGON_RPC_URL` | Polygon RPC address | `https://polygon-rpc.com` | | `JWT_SECRET` | JWT secret key | - | | `ADMIN_RESET_PASSWORD_KEY` | Admin password reset key | - | | `CRYPTO_SECRET_KEY` | Encryption key (for encrypting stored private keys and API Keys) | - | diff --git a/backend/deploy.sh b/backend/deploy.sh index bba3945..c9d18ca 100755 --- a/backend/deploy.sh +++ b/backend/deploy.sh @@ -154,9 +154,6 @@ SPRING_PROFILES_ACTIVE=prod # 服务器端口 SERVER_PORT=8000 -# Polygon RPC -POLYGON_RPC_URL=https://polygon-rpc.com - # JWT 密钥(已自动生成随机值,生产环境建议修改) JWT_SECRET=${JWT_SECRET} diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index 926fd2d..1e2b4fb 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -14,7 +14,6 @@ services: - DB_USERNAME=${DB_USERNAME:-root} - DB_PASSWORD=${DB_PASSWORD:-} - SERVER_PORT=8000 - - POLYGON_RPC_URL=${POLYGON_RPC_URL:-https://polygon-rpc.com} - JWT_SECRET=${JWT_SECRET:-change-me-in-production} - ADMIN_RESET_PASSWORD_KEY=${ADMIN_RESET_PASSWORD_KEY:-change-me-in-production} depends_on: diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/CopyOrderTrackingService.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/CopyOrderTrackingService.kt index 4e29087..e044c75 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/CopyOrderTrackingService.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/CopyOrderTrackingService.kt @@ -125,88 +125,88 @@ open class CopyOrderTrackingService( return mutex.withLock { try { - // 1. 检查是否已处理(去重,包括失败状态) - val existingProcessed = processedTradeRepository.findByLeaderIdAndLeaderTradeId(leaderId, trade.id) + // 1. 检查是否已处理(去重,包括失败状态) + val existingProcessed = processedTradeRepository.findByLeaderIdAndLeaderTradeId(leaderId, trade.id) - if (existingProcessed != null) { - if (existingProcessed.status == "FAILED") { + if (existingProcessed != null) { + if (existingProcessed.status == "FAILED") { return@withLock Result.success(Unit) - } + } return@withLock Result.success(Unit) - } + } - // 检查是否已记录为失败交易 - val failedTrade = failedTradeRepository.findByLeaderIdAndLeaderTradeId(leaderId, trade.id) - if (failedTrade != null) { + // 检查是否已记录为失败交易 + val failedTrade = failedTradeRepository.findByLeaderIdAndLeaderTradeId(leaderId, trade.id) + if (failedTrade != null) { return@withLock Result.success(Unit) - } + } - // 2. 处理交易逻辑 - val result = when (trade.side.uppercase()) { - "BUY" -> processBuyTrade(leaderId, trade) - "SELL" -> processSellTrade(leaderId, trade) - else -> { - logger.warn("未知的交易方向: ${trade.side}") - Result.failure(IllegalArgumentException("未知的交易方向: ${trade.side}")) - } + // 2. 处理交易逻辑 + val result = when (trade.side.uppercase()) { + "BUY" -> processBuyTrade(leaderId, trade) + "SELL" -> processSellTrade(leaderId, trade) + else -> { + logger.warn("未知的交易方向: ${trade.side}") + Result.failure(IllegalArgumentException("未知的交易方向: ${trade.side}")) } + } - if (result.isFailure) { - logger.error( - "处理交易失败: leaderId=$leaderId, tradeId=${trade.id}, side=${trade.side}", - result.exceptionOrNull() - ) + if (result.isFailure) { + logger.error( + "处理交易失败: leaderId=$leaderId, tradeId=${trade.id}, side=${trade.side}", + result.exceptionOrNull() + ) return@withLock result - } + } - // 3. 标记为已处理(成功状态) + // 3. 标记为已处理(成功状态) // 由于使用了 Mutex,这里理论上不会出现并发冲突,但保留异常处理作为兜底 - try { - val processed = ProcessedTrade( - leaderId = leaderId, - leaderTradeId = trade.id, - tradeType = trade.side.uppercase(), - source = source, - status = "SUCCESS", - processedAt = System.currentTimeMillis() - ) - processedTradeRepository.save(processed) - } catch (e: Exception) { - // 检查是否是唯一键冲突异常(理论上不会发生,但保留作为兜底) - if (isUniqueConstraintViolation(e)) { - val existing = processedTradeRepository.findByLeaderIdAndLeaderTradeId(leaderId, trade.id) - if (existing != null) { - if (existing.status == "FAILED") { - logger.debug("交易已标记为失败,跳过处理: leaderId=$leaderId, tradeId=${trade.id}") - return@withLock Result.success(Unit) - } - logger.debug("交易已处理(并发检测): leaderId=$leaderId, tradeId=${trade.id}, status=${existing.status}") - return@withLock Result.success(Unit) - } else { - // 如果检查不到,可能是事务隔离级别问题,等待一下再查询 - delay(100) - val existingAfterDelay = - processedTradeRepository.findByLeaderIdAndLeaderTradeId(leaderId, trade.id) - if (existingAfterDelay != null) { - logger.debug("延迟查询到记录(并发检测): leaderId=$leaderId, tradeId=${trade.id}, status=${existingAfterDelay.status}") - return@withLock Result.success(Unit) - } - logger.warn( - "保存ProcessedTrade时发生唯一约束冲突,但查询不到记录: leaderId=$leaderId, tradeId=${trade.id}", - e - ) - return@withLock Result.success(Unit) - } - } else { - // 其他类型的异常,重新抛出 - throw e - } - } - - Result.success(Unit) + try { + val processed = ProcessedTrade( + leaderId = leaderId, + leaderTradeId = trade.id, + tradeType = trade.side.uppercase(), + source = source, + status = "SUCCESS", + processedAt = System.currentTimeMillis() + ) + processedTradeRepository.save(processed) } catch (e: Exception) { - logger.error("处理交易异常: leaderId=$leaderId, tradeId=${trade.id}", e) - Result.failure(e) + // 检查是否是唯一键冲突异常(理论上不会发生,但保留作为兜底) + if (isUniqueConstraintViolation(e)) { + val existing = processedTradeRepository.findByLeaderIdAndLeaderTradeId(leaderId, trade.id) + if (existing != null) { + if (existing.status == "FAILED") { + logger.debug("交易已标记为失败,跳过处理: leaderId=$leaderId, tradeId=${trade.id}") + return@withLock Result.success(Unit) + } + logger.debug("交易已处理(并发检测): leaderId=$leaderId, tradeId=${trade.id}, status=${existing.status}") + return@withLock Result.success(Unit) + } else { + // 如果检查不到,可能是事务隔离级别问题,等待一下再查询 + delay(100) + val existingAfterDelay = + processedTradeRepository.findByLeaderIdAndLeaderTradeId(leaderId, trade.id) + if (existingAfterDelay != null) { + logger.debug("延迟查询到记录(并发检测): leaderId=$leaderId, tradeId=${trade.id}, status=${existingAfterDelay.status}") + return@withLock Result.success(Unit) + } + logger.warn( + "保存ProcessedTrade时发生唯一约束冲突,但查询不到记录: leaderId=$leaderId, tradeId=${trade.id}", + e + ) + return@withLock Result.success(Unit) + } + } else { + // 其他类型的异常,重新抛出 + throw e + } + } + + Result.success(Unit) + } catch (e: Exception) { + logger.error("处理交易异常: leaderId=$leaderId, tradeId=${trade.id}", e) + Result.failure(e) } } } @@ -594,7 +594,7 @@ open class CopyOrderTrackingService( ) copyOrderTrackingRepository.save(tracking) - + logger.info("买入订单已保存,等待轮询任务获取实际数据后发送通知: orderId=$realOrderId, copyTradingId=${copyTrading.id}") } catch (e: Exception) { logger.error("处理买入交易失败: copyTradingId=${copyTrading.id}, tradeId=${trade.id}", e) @@ -995,7 +995,7 @@ open class CopyOrderTrackingService( } val realSellOrderId = createOrderResult.getOrNull() ?: return - + // 12. 下单时直接使用下单价格保存,等待定时任务更新实际成交价 // priceUpdated 统一由定时任务更新,下单时统一设置为 false(非0x开头的除外) val priceUpdated = !realSellOrderId.startsWith("0x", ignoreCase = true) diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/system/ApiHealthCheckService.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/system/ApiHealthCheckService.kt index fe8e44b..094c0bf 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/system/ApiHealthCheckService.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/system/ApiHealthCheckService.kt @@ -28,8 +28,6 @@ class ApiHealthCheckService( private val dataApiBaseUrl: String, @Value("\${polymarket.gamma.base-url}") private val gammaBaseUrl: String, - @Value("\${polygon.rpc.url:}") - private val polygonRpcUrl: String, @Value("\${polymarket.rtds.ws-url}") private val polymarketWsUrl: String, @Value("\${polymarket.builder.relayer-url:}") @@ -187,27 +185,11 @@ class ApiHealthCheckService( /** * 检查 Polygon RPC - * 使用动态获取的可用节点,而不是固定的配置 + * 使用动态获取的可用节点(RpcNodeService 总是返回一个有效的 URL,包括默认节点) */ private suspend fun checkPolygonRpc(): ApiHealthCheckDto = withContext(Dispatchers.IO) { - // 优先使用动态获取的可用节点 - val rpcUrl = try { - rpcNodeService.getHttpUrl() - } catch (e: Exception) { - logger.debug("获取可用 RPC 节点失败,使用配置的默认值: ${e.message}") - // 如果获取失败,使用配置的默认值作为兜底 - if (polygonRpcUrl.isNotBlank()) { - polygonRpcUrl - } else { - return@withContext ApiHealthCheckDto( - name = "Polygon RPC", - url = "未配置", - status = "skipped", - message = "未配置 Polygon RPC URL 且没有可用的节点" - ) - } - } - + // 使用 RpcNodeService 获取可用节点(总是返回有效值,包括默认节点) + val rpcUrl = rpcNodeService.getHttpUrl() checkJsonRpcApi("Polygon RPC", rpcUrl) } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index dc75ad0..ef73a28 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -40,23 +40,12 @@ polymarket.rtds.ws-url=wss://ws-subscriptions-clob.polymarket.com polymarket.data-api.base-url=https://data-api.polymarket.com polymarket.gamma.base-url=https://gamma-api.polymarket.com -# Polygon RPC 配置(用于查询链上余额) -# 可选:如果未配置,将无acc法查询 USDC 余额,但仍可通过 Subgraph API 查询持仓 -# 示例:https://polygon-rpc.com 或 https://polygon-mainnet.infura.io/v3/YOUR_PROJECT_ID -polygon.rpc.url=${POLYGON_RPC_URL:https://polygon-rpc.com} - # Builder Relayer 配置(用于 Gasless 交易) # 从 polymarket.com/settings?tab=builder 获取 Builder API 凭证 # Builder API Key、Secret、Passphrase 现在通过系统设置页面配置,存储在数据库中 # 如果未配置,将使用手动发送交易的方式(需要用户支付 gas) polymarket.builder.relayer-url=${POLYMARKET_BUILDER_RELAYER_URL:https://relayer-v2.polymarket.com/} -# 仓位推送配置 -# 轮询间隔(毫秒),默认3秒 -position.push.polling-interval=${POSITION_PUSH_POLLING_INTERVAL:3000} -# 心跳超时时间(毫秒),默认60秒,超过此时间未收到心跳则清理连接 -position.push.heartbeat-timeout=${POSITION_PUSH_HEARTBEAT_TIMEOUT:60000} - # 跟单轮询配置 # 轮询间隔(毫秒),默认2秒 copy.trading.polling.interval=${COPY_TRADING_POLLING_INTERVAL:2000} diff --git a/deploy.sh b/deploy.sh index 41a1ebe..6fd5506 100755 --- a/deploy.sh +++ b/deploy.sh @@ -71,9 +71,6 @@ SERVER_PORT=80 # MySQL 端口(可选,用于外部连接,默认 3307 避免与本地 MySQL 冲突) MYSQL_PORT=3307 -# Polygon RPC -POLYGON_RPC_URL=https://polygon-rpc.com - # JWT 密钥(已自动生成随机值,生产环境建议修改) JWT_SECRET=${JWT_SECRET} diff --git a/docker-compose.prod.env.example b/docker-compose.prod.env.example index 912ecbd..040738a 100644 --- a/docker-compose.prod.env.example +++ b/docker-compose.prod.env.example @@ -38,12 +38,6 @@ SERVER_PORT=80 # Optional, for external connections, default 3307 to avoid conflicts with local MySQL MYSQL_PORT=3307 -# ============================================ -# Polygon RPC -# ============================================ -# Polygon 网络 RPC 地址 / Polygon network RPC address -POLYGON_RPC_URL=https://polygon-rpc.com - # ============================================ # 安全配置 / Security Configuration # ============================================ diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index e7b27bf..6ecb88c 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -24,7 +24,6 @@ services: - DB_USERNAME=${DB_USERNAME:-root} - DB_PASSWORD=${DB_PASSWORD:-} - SERVER_PORT=8000 - - POLYGON_RPC_URL=${POLYGON_RPC_URL:-https://polygon-rpc.com} # ⚠️ 安全警告:以下两个环境变量不能使用默认值,否则容器启动会失败 # 请在 .env 文件中设置,或通过环境变量传入 # 生成随机密钥:openssl rand -hex 32 (ADMIN_RESET_PASSWORD_KEY) 或 openssl rand -hex 64 (JWT_SECRET) diff --git a/docker-compose.yml b/docker-compose.yml index 6841b5d..2b7a0a8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,7 +22,6 @@ services: - DB_USERNAME=${DB_USERNAME:-root} - DB_PASSWORD=${DB_PASSWORD:-} - SERVER_PORT=8000 - - POLYGON_RPC_URL=${POLYGON_RPC_URL:-https://polygon-rpc.com} # ⚠️ 安全警告:以下两个环境变量不能使用默认值,否则容器启动会失败 # 请在 .env 文件中设置,或通过环境变量传入 # 生成随机密钥:openssl rand -hex 32 (ADMIN_RESET_PASSWORD_KEY) 或 openssl rand -hex 64 (JWT_SECRET) diff --git a/docs/en/DEPLOYMENT.md b/docs/en/DEPLOYMENT.md index 089e9f5..66cb8de 100644 --- a/docs/en/DEPLOYMENT.md +++ b/docs/en/DEPLOYMENT.md @@ -163,7 +163,6 @@ DB_USERNAME=root DB_PASSWORD=your_password_here SPRING_PROFILES_ACTIVE=prod SERVER_PORT=80 -POLYGON_RPC_URL=https://polygon-rpc.com JWT_SECRET=your-jwt-secret-key-change-in-production ADMIN_RESET_PASSWORD_KEY=your-admin-reset-key-change-in-production EOF @@ -393,7 +392,6 @@ DB_USERNAME=root DB_PASSWORD=your_password_here SPRING_PROFILES_ACTIVE=prod SERVER_PORT=8000 -POLYGON_RPC_URL=https://polygon-rpc.com JWT_SECRET=your-jwt-secret-key-change-in-production ADMIN_RESET_PASSWORD_KEY=your-admin-reset-key-change-in-production EOF @@ -554,7 +552,6 @@ serve -s dist -l 3000 | `DB_USERNAME` | Database username | `root` | Yes (Production) | | `DB_PASSWORD` | Database password | - | Yes (Production) | | `SERVER_PORT` | Server port | `8000` | No | -| `POLYGON_RPC_URL` | Polygon RPC address | `https://polygon-rpc.com` | No | | `JWT_SECRET` | JWT secret key | - | Yes (Production) | | `ADMIN_RESET_PASSWORD_KEY` | Admin password reset key | - | Yes (Production) | diff --git a/docs/en/DEVELOPMENT.md b/docs/en/DEVELOPMENT.md index 86516d3..6529684 100644 --- a/docs/en/DEVELOPMENT.md +++ b/docs/en/DEVELOPMENT.md @@ -100,9 +100,6 @@ spring.datasource.password=${DB_PASSWORD:password} # Server port server.port=${SERVER_PORT:8000} -# Polygon RPC -polygon.rpc.url=${POLYGON_RPC_URL:https://polygon-rpc.com} - # JWT secret jwt.secret=${JWT_SECRET:change-me-in-production} diff --git a/docs/zh/DEPLOYMENT.md b/docs/zh/DEPLOYMENT.md index f35ca62..1fdc385 100644 --- a/docs/zh/DEPLOYMENT.md +++ b/docs/zh/DEPLOYMENT.md @@ -165,7 +165,6 @@ DB_USERNAME=root DB_PASSWORD=your_password_here SPRING_PROFILES_ACTIVE=prod SERVER_PORT=80 -POLYGON_RPC_URL=https://polygon-rpc.com JWT_SECRET=your-jwt-secret-key-change-in-production ADMIN_RESET_PASSWORD_KEY=your-admin-reset-key-change-in-production EOF @@ -397,7 +396,6 @@ DB_USERNAME=root DB_PASSWORD=your_password_here SPRING_PROFILES_ACTIVE=prod SERVER_PORT=8000 -POLYGON_RPC_URL=https://polygon-rpc.com JWT_SECRET=your-jwt-secret-key-change-in-production ADMIN_RESET_PASSWORD_KEY=your-admin-reset-key-change-in-production EOF @@ -558,7 +556,6 @@ serve -s dist -l 3000 | `DB_USERNAME` | 数据库用户名 | `root` | 是(生产) | | `DB_PASSWORD` | 数据库密码 | - | 是(生产) | | `SERVER_PORT` | 服务器端口 | `8000` | 否 | -| `POLYGON_RPC_URL` | Polygon RPC 地址 | `https://polygon-rpc.com` | 否 | | `JWT_SECRET` | JWT 密钥 | - | 是(生产) | | `ADMIN_RESET_PASSWORD_KEY` | 管理员密码重置密钥 | - | 是(生产) | diff --git a/docs/zh/DEVELOPMENT.md b/docs/zh/DEVELOPMENT.md index a4e2771..b5fad19 100644 --- a/docs/zh/DEVELOPMENT.md +++ b/docs/zh/DEVELOPMENT.md @@ -93,9 +93,6 @@ spring.datasource.password=${DB_PASSWORD:password} # 服务器端口 server.port=${SERVER_PORT:8000} -# Polygon RPC -polygon.rpc.url=${POLYGON_RPC_URL:https://polygon-rpc.com} - # JWT 密钥 jwt.secret=${JWT_SECRET:change-me-in-production}