refactor: 重构代码结构,按业务分类组织 Controller 和 Service

- 重构 API 路径为 /api/业务/xxx 格式
- 将 Controller 和 Service 按业务分类组织:
  * copytrading: 跟单相关(configs, leaders, statistics, templates)
  * accounts: 账户管理(公共模块)
  * markets: 市场数据(公共模块)
  * system: 系统管理(users, notifications, proxy, health)
  * auth: 认证
  * announcement: 公告
- 将所有完整类名引用改为使用 import 导入
- 修复 calculatePositionValue 方法实现
- 更新前端 API 调用路径
This commit is contained in:
WrBug
2025-12-08 23:02:57 +08:00
parent ac4e1dde2b
commit 7173d24fe1
48 changed files with 310 additions and 234 deletions
+7
View File
@@ -94,6 +94,13 @@ coverage/
test-results/
*.test.log
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
# Misc
*.bak
*.backup
@@ -227,7 +227,7 @@ data class CreateOrderRequest(
@Deprecated("使用 NewOrderRequest 代替")
data class CreateOrdersBatchRequest(
val orders: List<CreateOrderRequest>
val orders: List<NewOrderRequest>
)
data class CancelOrdersBatchRequest(
@@ -1,6 +1,6 @@
package com.wrbug.polymarketbot.config
import com.wrbug.polymarketbot.service.ProxyConfigService
import com.wrbug.polymarketbot.service.system.ProxyConfigService
import jakarta.annotation.PostConstruct
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
@@ -1,8 +1,8 @@
package com.wrbug.polymarketbot.controller
package com.wrbug.polymarketbot.controller.accounts
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.enums.ErrorCode
import com.wrbug.polymarketbot.service.AccountService
import com.wrbug.polymarketbot.service.accounts.AccountService
import com.wrbug.polymarketbot.util.toSafeBigDecimal
import kotlinx.coroutines.runBlocking
import org.slf4j.LoggerFactory
@@ -15,7 +15,7 @@ import java.math.BigDecimal
* 账户管理控制器
*/
@RestController
@RequestMapping("/api/copy-trading/accounts")
@RequestMapping("/api/accounts")
class AccountController(
private val accountService: AccountService,
private val messageSource: MessageSource
@@ -1,8 +1,8 @@
package com.wrbug.polymarketbot.controller
package com.wrbug.polymarketbot.controller.announcement
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.enums.ErrorCode
import com.wrbug.polymarketbot.service.AnnouncementService
import com.wrbug.polymarketbot.service.announcement.AnnouncementService
import kotlinx.coroutines.runBlocking
import org.slf4j.LoggerFactory
import org.springframework.context.MessageSource
@@ -1,8 +1,8 @@
package com.wrbug.polymarketbot.controller
package com.wrbug.polymarketbot.controller.auth
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.enums.ErrorCode
import com.wrbug.polymarketbot.service.AuthService
import com.wrbug.polymarketbot.service.auth.AuthService
import jakarta.servlet.http.HttpServletRequest
import org.slf4j.LoggerFactory
import org.springframework.context.MessageSource
@@ -1,9 +1,9 @@
package com.wrbug.polymarketbot.controller
package com.wrbug.polymarketbot.controller.copytrading.configs
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.enums.ErrorCode
import com.wrbug.polymarketbot.service.CopyTradingService
import com.wrbug.polymarketbot.service.FilteredOrderService
import com.wrbug.polymarketbot.service.copytrading.configs.CopyTradingService
import com.wrbug.polymarketbot.service.copytrading.configs.FilteredOrderService
import org.slf4j.LoggerFactory
import org.springframework.context.MessageSource
import org.springframework.http.ResponseEntity
@@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.*
* 跟单配置管理控制器钱包-模板关联
*/
@RestController
@RequestMapping("/api/copy-trading")
@RequestMapping("/api/copy-trading/configs")
class CopyTradingController(
private val copyTradingService: CopyTradingService,
private val filteredOrderService: FilteredOrderService,
@@ -1,8 +1,8 @@
package com.wrbug.polymarketbot.controller
package com.wrbug.polymarketbot.controller.copytrading.leaders
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.enums.ErrorCode
import com.wrbug.polymarketbot.service.LeaderService
import com.wrbug.polymarketbot.service.copytrading.leaders.LeaderService
import org.slf4j.LoggerFactory
import org.springframework.context.MessageSource
import org.springframework.http.ResponseEntity
@@ -1,8 +1,8 @@
package com.wrbug.polymarketbot.controller
package com.wrbug.polymarketbot.controller.copytrading.statistics
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.enums.ErrorCode
import com.wrbug.polymarketbot.service.CopyTradingStatisticsService
import com.wrbug.polymarketbot.service.copytrading.statistics.CopyTradingStatisticsService
import kotlinx.coroutines.runBlocking
import org.slf4j.LoggerFactory
import org.springframework.context.MessageSource
@@ -1,8 +1,8 @@
package com.wrbug.polymarketbot.controller
package com.wrbug.polymarketbot.controller.copytrading.templates
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.enums.ErrorCode
import com.wrbug.polymarketbot.service.CopyTradingTemplateService
import com.wrbug.polymarketbot.service.copytrading.templates.CopyTradingTemplateService
import org.slf4j.LoggerFactory
import org.springframework.context.MessageSource
import org.springframework.http.ResponseEntity
@@ -1,10 +1,10 @@
package com.wrbug.polymarketbot.controller
package com.wrbug.polymarketbot.controller.markets
import com.wrbug.polymarketbot.api.LatestPriceResponse
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.enums.ErrorCode
import com.wrbug.polymarketbot.service.AccountService
import com.wrbug.polymarketbot.service.PolymarketClobService
import com.wrbug.polymarketbot.service.accounts.AccountService
import com.wrbug.polymarketbot.service.common.PolymarketClobService
import kotlinx.coroutines.runBlocking
import org.slf4j.LoggerFactory
import org.springframework.context.MessageSource
@@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*
* 提供市场相关的数据查询接口价格订单簿等
*/
@RestController
@RequestMapping("/api/copy-trading/markets")
@RequestMapping("/api/markets")
class MarketController(
private val accountService: AccountService,
private val clobService: PolymarketClobService,
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.controller
package com.wrbug.polymarketbot.controller.system
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
@@ -11,7 +11,7 @@ import java.util.*
* 用于 Docker 健康检查和监控
*/
@RestController
@RequestMapping("/api/health")
@RequestMapping("/api/system/health")
class HealthController {
@GetMapping
@@ -1,9 +1,9 @@
package com.wrbug.polymarketbot.controller
package com.wrbug.polymarketbot.controller.system
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.enums.ErrorCode
import com.wrbug.polymarketbot.service.NotificationConfigService
import com.wrbug.polymarketbot.service.TelegramNotificationService
import com.wrbug.polymarketbot.service.system.NotificationConfigService
import com.wrbug.polymarketbot.service.system.TelegramNotificationService
import kotlinx.coroutines.runBlocking
import org.slf4j.LoggerFactory
import org.springframework.context.MessageSource
@@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.*
* 消息推送配置控制器
*/
@RestController
@RequestMapping("/api/notifications")
@RequestMapping("/api/system/notifications")
class NotificationController(
private val notificationConfigService: NotificationConfigService,
private val telegramNotificationService: TelegramNotificationService,
@@ -1,9 +1,9 @@
package com.wrbug.polymarketbot.controller
package com.wrbug.polymarketbot.controller.system
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.enums.ErrorCode
import com.wrbug.polymarketbot.service.ApiHealthCheckService
import com.wrbug.polymarketbot.service.ProxyConfigService
import com.wrbug.polymarketbot.service.system.ApiHealthCheckService
import com.wrbug.polymarketbot.service.system.ProxyConfigService
import jakarta.servlet.http.HttpServletRequest
import kotlinx.coroutines.runBlocking
import org.slf4j.LoggerFactory
@@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.*
* 代理配置控制器
*/
@RestController
@RequestMapping("/api/proxy-config")
@RequestMapping("/api/system/proxy")
class ProxyConfigController(
private val proxyConfigService: ProxyConfigService,
private val apiHealthCheckService: ApiHealthCheckService,
@@ -1,9 +1,9 @@
package com.wrbug.polymarketbot.controller
package com.wrbug.polymarketbot.controller.system
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.enums.ErrorCode
import com.wrbug.polymarketbot.service.SystemConfigService
import com.wrbug.polymarketbot.service.RelayClientService
import com.wrbug.polymarketbot.service.system.SystemConfigService
import com.wrbug.polymarketbot.service.system.RelayClientService
import kotlinx.coroutines.runBlocking
import org.slf4j.LoggerFactory
import org.springframework.context.MessageSource
@@ -1,8 +1,8 @@
package com.wrbug.polymarketbot.controller
package com.wrbug.polymarketbot.controller.system
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.enums.ErrorCode
import com.wrbug.polymarketbot.service.UserService
import com.wrbug.polymarketbot.service.system.UserService
import jakarta.servlet.http.HttpServletRequest
import org.slf4j.LoggerFactory
import org.springframework.context.MessageSource
@@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.*
* 用户管理控制器
*/
@RestController
@RequestMapping("/api/users")
@RequestMapping("/api/system/users")
class UserController(
private val userService: UserService,
private val messageSource: MessageSource
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.accounts
import com.wrbug.polymarketbot.api.TradeResponse
import com.wrbug.polymarketbot.dto.*
@@ -8,6 +8,14 @@ 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 com.wrbug.polymarketbot.service.common.PolymarketClobService
import com.wrbug.polymarketbot.service.common.BlockchainService
import com.wrbug.polymarketbot.service.common.PolymarketApiKeyService
import com.wrbug.polymarketbot.service.copytrading.orders.OrderPushService
import com.wrbug.polymarketbot.service.copytrading.orders.OrderSigningService
import com.wrbug.polymarketbot.service.system.TelegramNotificationService
import com.wrbug.polymarketbot.service.system.RelayClientService
import com.wrbug.polymarketbot.util.CryptoUtils
import kotlinx.coroutines.*
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@@ -27,7 +35,7 @@ class AccountService(
private val apiKeyService: PolymarketApiKeyService,
private val orderPushService: OrderPushService,
private val orderSigningService: OrderSigningService,
private val cryptoUtils: com.wrbug.polymarketbot.util.CryptoUtils,
private val cryptoUtils: CryptoUtils,
private val telegramNotificationService: TelegramNotificationService? = null, // 可选,避免循环依赖
private val relayClientService: RelayClientService
) {
@@ -1070,7 +1078,7 @@ class AccountService(
/**
* 从订单表获取最优价用于市价单
* 支持多元市场二元三元及以上
* 委托给 PolymarketClobService.getOptimalPrice 方法
* 委托给 com.wrbug.polymarketbot.service.common.PolymarketClobService.getOptimalPrice 方法
*
* @param tokenId token ID通过 marketId outcomeIndex 计算得出
* @param isSellOrder 是否为卖出订单true: 卖单需要 bestBidfalse: 买单需要 bestAsk
@@ -1167,7 +1175,7 @@ class AccountService(
/**
* 获取可赎回仓位统计
*/
suspend fun getRedeemablePositionsSummary(accountId: Long? = null): Result<com.wrbug.polymarketbot.dto.RedeemablePositionsSummary> {
suspend fun getRedeemablePositionsSummary(accountId: Long? = null): Result<RedeemablePositionsSummary> {
return try {
val positionsResult = getAllPositions()
positionsResult.fold(
@@ -1223,7 +1231,7 @@ class AccountService(
* 赎回仓位
* 支持多账户多仓位赎回自动按账户和市场分组
*/
suspend fun redeemPositions(request: com.wrbug.polymarketbot.dto.PositionRedeemRequest): Result<com.wrbug.polymarketbot.dto.PositionRedeemResponse> {
suspend fun redeemPositions(request: PositionRedeemRequest): Result<PositionRedeemResponse> {
return try {
// 检查 Builder API Key 是否已配置
if (!relayClientService.isBuilderApiKeyConfigured()) {
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.accounts
import com.wrbug.polymarketbot.dto.AccountPositionDto
import com.wrbug.polymarketbot.entity.CopyOrderTracking
@@ -18,6 +18,9 @@ import jakarta.annotation.PostConstruct
import jakarta.annotation.PreDestroy
import org.springframework.context.MessageSource
import org.springframework.context.i18n.LocaleContextHolder
import com.wrbug.polymarketbot.service.system.SystemConfigService
import com.wrbug.polymarketbot.service.system.RelayClientService
import com.wrbug.polymarketbot.service.system.TelegramNotificationService
import org.springframework.stereotype.Service
import java.math.BigDecimal
import java.util.concurrent.ConcurrentHashMap
@@ -368,15 +371,15 @@ class PositionCheckService(
if (position == null) {
// 仓位不存在,更新所有订单状态为已卖出
val currentPrice = getCurrentMarketPrice(marketId, outcomeIndex)
updateOrdersAsSold(orders, currentPrice, copyTrading.id!!, marketId, outcomeIndex)
updateOrdersAsSold(orders, currentPrice, copyTrading.id, marketId, outcomeIndex)
} else {
// 有仓位,按订单下单顺序(FIFO)更新状态
// 如果仓位数量 >= 订单数量总和,所有订单完全成交
// 如果仓位数量 < 订单数量总和,按FIFO顺序部分成交
val totalUnmatchedQuantity = orders.sumOf { it.remainingQuantity.toSafeBigDecimal() }
val positionQuantity = position.quantity.toSafeBigDecimal()
val currentPrice = getCurrentMarketPrice(marketId, outcomeIndex)
updateOrdersAsSoldByFIFO(orders, positionQuantity, currentPrice, copyTrading.id!!, marketId, outcomeIndex)
updateOrdersAsSoldByFIFO(orders, positionQuantity, currentPrice,
copyTrading.id, marketId, outcomeIndex)
}
}
}
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.accounts
import com.wrbug.polymarketbot.dto.PositionListResponse
import jakarta.annotation.PostConstruct
@@ -1,6 +1,7 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.accounts
import com.wrbug.polymarketbot.dto.AccountPositionDto
import com.wrbug.polymarketbot.dto.PositionListResponse
import com.wrbug.polymarketbot.dto.PositionPushMessage
import com.wrbug.polymarketbot.dto.PositionPushMessageType
import com.wrbug.polymarketbot.dto.getPositionKey
@@ -155,7 +156,7 @@ class PositionPushService(
* 处理仓位更新事件
* 根据文档要求每次轮训完成后向订阅者发送全量数据
*/
private fun handlePositionUpdate(positions: com.wrbug.polymarketbot.dto.PositionListResponse) {
private fun handlePositionUpdate(positions: PositionListResponse) {
// 更新快照
lastCurrentPositions = positions.currentPositions.associateBy { it.getPositionKey() }
lastHistoryPositions = positions.historyPositions.associateBy { it.getPositionKey() }
@@ -1,6 +1,7 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.announcement
import com.wrbug.polymarketbot.api.GitHubApi
import com.wrbug.polymarketbot.api.GitHubCommentResponse
import com.wrbug.polymarketbot.dto.AnnouncementDto
import com.wrbug.polymarketbot.dto.AnnouncementListResponse
import com.wrbug.polymarketbot.util.RetrofitFactory
@@ -25,30 +26,30 @@ class AnnouncementService(
@Value("\${github.announcement.issue.number:1}")
private val issueNumber: Int
) {
private val logger = LoggerFactory.getLogger(AnnouncementService::class.java)
// GitHub API 客户端(懒加载)
private val githubApi: GitHubApi by lazy {
retrofitFactory.createGitHubApi()
}
// 需要排除的 Issue ID(从 issue_url 中提取)
private val excludedIssueId = "3703128976"
// 缓存数据(1分钟有效期)
private data class CachedData<T>(
val data: T,
val timestamp: Long
)
private var cachedList: CachedData<AnnouncementListResponse>? = null
private var cachedAssignees: CachedData<List<String>>? = null
private var cachedComments: CachedData<List<com.wrbug.polymarketbot.api.GitHubCommentResponse>>? = null
// 缓存有效期:10分钟(毫秒)
private val cacheExpiryTime = 10 * 60 * 1000L
/**
* 检查缓存是否有效
*/
@@ -57,7 +58,7 @@ class AnnouncementService(
val now = System.currentTimeMillis()
return (now - cached.timestamp) < cacheExpiryTime
}
/**
* 检查是否被限流
*/
@@ -70,7 +71,7 @@ class AnnouncementService(
val remaining = response.headers()["X-RateLimit-Remaining"]
return remaining == "0"
}
/**
* 获取 Issue assignees 列表通过 API 获取带缓存
* @return Pair<assignees列表, 是否使用了缓存>
@@ -81,14 +82,14 @@ class AnnouncementService(
logger.debug("使用缓存的 assignees")
return Pair(cachedAssignees!!.data, true)
}
return try {
val response = githubApi.getIssue(
owner = repoOwner,
repo = repoName,
issueNumber = issueNumber
)
// 如果被限流,使用缓存数据,不更新缓存
if (isRateLimited(response)) {
logger.warn("GitHub API 被限流,使用缓存的 assignees(不更新缓存)")
@@ -98,14 +99,14 @@ class AnnouncementService(
// 如果没有缓存,使用默认值
return Pair(listOf("WrBug"), false)
}
val assignees = if (response.isSuccessful && response.body() != null) {
response.body()!!.assignees.map { it.login }
} else {
logger.warn("获取 Issue assignees 失败,使用默认值: code=${response.code()}")
listOf("WrBug") // 默认值
}
// 更新缓存
cachedAssignees = CachedData(assignees, System.currentTimeMillis())
Pair(assignees, false) // 返回新数据,标记为未使用缓存
@@ -119,24 +120,24 @@ class AnnouncementService(
Pair(listOf("WrBug"), false) // 默认值
}
}
/**
* 获取 Issue 评论列表带缓存
* @return Pair<评论列表, 是否使用了缓存>
*/
private suspend fun getIssueComments(forceRefresh: Boolean = false): Pair<List<com.wrbug.polymarketbot.api.GitHubCommentResponse>, Boolean> {
private suspend fun getIssueComments(forceRefresh: Boolean = false): Pair<List<GitHubCommentResponse>, Boolean> {
// 检查缓存
if (!forceRefresh && isCacheValid(cachedComments)) {
logger.debug("使用缓存的评论列表")
return Pair(cachedComments!!.data, true)
}
val response = githubApi.getIssueComments(
owner = repoOwner,
repo = repoName,
issueNumber = issueNumber
)
// 如果被限流,使用缓存数据,不更新缓存
if (isRateLimited(response)) {
logger.warn("GitHub API 被限流,使用缓存的评论列表(不更新缓存)")
@@ -146,7 +147,7 @@ class AnnouncementService(
// 如果没有缓存,抛出异常
throw Exception("获取公告列表失败: GitHub API 被限流,且无缓存数据")
}
if (!response.isSuccessful || response.body() == null) {
logger.error("获取 GitHub Issue 评论失败: code=${response.code()}, message=${response.message()}")
// 如果缓存存在,使用缓存
@@ -156,14 +157,14 @@ class AnnouncementService(
}
throw Exception("获取公告列表失败: HTTP ${response.code()}")
}
val comments = response.body()!!
// 更新缓存
cachedComments = CachedData(comments, System.currentTimeMillis())
return Pair(comments, false) // 返回新数据,标记为未使用缓存
}
/**
* 获取公告列表最近10条
* @param forceRefresh 是否强制刷新缓存
@@ -174,12 +175,12 @@ class AnnouncementService(
logger.debug("使用缓存的公告列表")
return Result.success(cachedList!!.data)
}
return try {
// 强制刷新时,先尝试获取新数据
val (assigneeList, assigneesFromCache) = getAssignees(forceRefresh)
val (comments, commentsFromCache) = getIssueComments(forceRefresh)
// 如果强制刷新时使用了缓存(被限流),直接返回缓存数据,不更新缓存
if (forceRefresh && (assigneesFromCache || commentsFromCache)) {
logger.warn("强制刷新时被限流,返回缓存的公告列表(不更新缓存)")
@@ -187,48 +188,47 @@ class AnnouncementService(
return Result.success(cachedList!!.data)
}
}
// 筛选条件:
// 1. assignees 发布的评论
// 2. 排除 issueNumber 为 3703128976 的评论(从 issue_url 中提取)
val filteredComments = comments
.filter { comment ->
// 检查是否为 assignee
val isAssignee = assigneeList.contains(comment.user.login)
// 检查是否应该排除(从 issue_url 中提取 issue ID
val shouldExclude = comment.issue_url?.let { issueUrl ->
// issue_url 格式:https://api.github.com/repos/owner/repo/issues/3703128976
// 提取最后的数字
val issueId = issueUrl.split("/").lastOrNull()
issueId == excludedIssueId
} ?: false
isAssignee && !shouldExclude
}
val filteredComments = comments.filter { comment ->
// 检查是否为 assignee
val isAssignee = assigneeList.contains(comment.user.login)
// 检查是否应该排除(从 issue_url 中提取 issue ID
val shouldExclude = comment.issue_url?.let { issueUrl ->
// issue_url 格式:https://api.github.com/repos/owner/repo/issues/3703128976
// 提取最后的数字
val issueId = issueUrl.split("/").lastOrNull()
issueId == excludedIssueId
} ?: false
isAssignee && !shouldExclude
}
.sortedByDescending { comment ->
parseGitHubTime(comment.created_at)
}
val total = filteredComments.size
val hasMore = total > 10
// 取前10条
val latest10 = filteredComments.take(10).map { comment ->
toAnnouncementDto(comment)
}
val result = AnnouncementListResponse(
list = latest10,
hasMore = hasMore,
total = total
)
// 只有在数据正常返回时才更新缓存(不是从缓存获取的)
if (!assigneesFromCache && !commentsFromCache) {
cachedList = CachedData(result, System.currentTimeMillis())
}
Result.success(result)
} catch (e: Exception) {
logger.error("获取公告列表异常: ${e.message}", e)
@@ -240,7 +240,7 @@ class AnnouncementService(
Result.failure(e)
}
}
/**
* 获取公告详情
* @param id 评论ID如果为 null 则返回最新一条
@@ -249,19 +249,18 @@ class AnnouncementService(
suspend fun getAnnouncementDetail(id: Long?, forceRefresh: Boolean = false): Result<AnnouncementDto> {
return try {
// 获取 assignees
val (assigneeList, assigneesFromCache) = getAssignees(forceRefresh)
val (assigneeList, _) = getAssignees(forceRefresh)
// 获取评论列表
val (comments, commentsFromCache) = getIssueComments(forceRefresh)
val (comments, _) = getIssueComments(forceRefresh)
// 筛选条件:
// 1. assignees 发布的评论
// 2. 排除 issueNumber 为 3703128976 的评论(从 issue_url 中提取)
val filteredComments = comments
.filter { comment ->
val filteredComments = comments.filter { comment ->
// 检查是否为 assignee
val isAssignee = assigneeList.contains(comment.user.login)
// 检查是否应该排除(从 issue_url 中提取 issue ID
val shouldExclude = comment.issue_url?.let { issueUrl ->
// issue_url 格式:https://api.github.com/repos/owner/repo/issues/3703128976
@@ -269,37 +268,37 @@ class AnnouncementService(
val issueId = issueUrl.split("/").lastOrNull()
issueId == excludedIssueId
} ?: false
isAssignee && !shouldExclude
}
.sortedByDescending { comment ->
parseGitHubTime(comment.created_at)
}
val targetComment = if (id != null) {
filteredComments.find { it.id == id }
} else {
filteredComments.firstOrNull()
}
if (targetComment == null) {
return Result.failure(IllegalArgumentException("公告不存在"))
}
Result.success(toAnnouncementDto(targetComment))
} catch (e: Exception) {
logger.error("获取公告详情异常: ${e.message}", e)
Result.failure(e)
}
}
/**
* GitHub 评论转换为 AnnouncementDto
*/
private fun toAnnouncementDto(comment: com.wrbug.polymarketbot.api.GitHubCommentResponse): AnnouncementDto {
private fun toAnnouncementDto(comment: GitHubCommentResponse): AnnouncementDto {
// 提取标题(第一行,移除 Markdown 格式)
val title = extractTitle(comment.body)
// 转换 reactions 数据
val reactions = comment.reactions?.let { r ->
com.wrbug.polymarketbot.dto.ReactionsDto(
@@ -314,7 +313,7 @@ class AnnouncementService(
total = r.total_count
)
}
return AnnouncementDto(
id = comment.id,
title = title,
@@ -326,7 +325,7 @@ class AnnouncementService(
reactions = reactions
)
}
/**
* 从评论内容中提取标题第一行移除 Markdown 格式
* 支持的 Markdown 格式
@@ -342,49 +341,49 @@ class AnnouncementService(
if (body.isBlank()) {
return ""
}
// 获取第一行
val firstLine = body.lines().firstOrNull()?.trim() ?: ""
if (firstLine.isBlank()) {
return ""
}
// 移除 Markdown 格式
var title = firstLine
// 移除标题标记(# ## ### 等)
title = title.replace(Regex("^#{1,6}\\s+"), "")
// 移除粗体标记(**text** 或 __text__
title = title.replace(Regex("\\*\\*([^*]+)\\*\\*"), "$1")
title = title.replace(Regex("__([^_]+)__"), "$1")
// 移除斜体标记(*text* 或 _text_
title = title.replace(Regex("(?<!\\*)\\*([^*]+)\\*(?!\\*)"), "$1")
title = title.replace(Regex("(?<!_)_([^_]+)_(?!_)"), "$1")
// 移除代码标记(`code`
title = title.replace(Regex("`([^`]+)`"), "$1")
// 移除链接标记([text](url)
title = title.replace(Regex("\\[([^\\]]+)\\]\\([^\\)]+\\)"), "$1")
// 移除图片标记(![alt](url)
title = title.replace(Regex("!\\[([^\\]]*)\\]\\([^\\)]+\\)"), "$1")
// 移除删除线标记(~~text~~
title = title.replace(Regex("~~([^~]+)~~"), "$1")
// 移除引用标记(> text
title = title.replace(Regex("^>\\s+"), "")
// 移除列表标记(- * + 1. 等)
title = title.replace(Regex("^[-*+]\\s+"), "")
title = title.replace(Regex("^\\d+\\.\\s+"), "")
return title.trim()
}
/**
* 解析 GitHub 时间格式ISO 8601为时间戳毫秒
* GitHub API 返回的时间格式2025-12-07T14:30:00Z
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.auth
import com.wrbug.polymarketbot.dto.CheckFirstUseResponse
import com.wrbug.polymarketbot.dto.LoginResponse
@@ -10,6 +10,7 @@ import jakarta.servlet.http.HttpServletRequest
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import com.wrbug.polymarketbot.service.common.RateLimitService
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.common
import com.wrbug.polymarketbot.api.EthereumRpcApi
import com.wrbug.polymarketbot.api.JsonRpcRequest
@@ -11,6 +11,7 @@ import com.wrbug.polymarketbot.util.RetrofitFactory
import com.wrbug.polymarketbot.util.createClient
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import com.wrbug.polymarketbot.service.system.RelayClientService
import org.springframework.stereotype.Service
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.common
import com.wrbug.polymarketbot.api.ApiKeyResponse
import com.wrbug.polymarketbot.api.PolymarketClobApi
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.common
import com.wrbug.polymarketbot.api.*
import com.wrbug.polymarketbot.util.RetrofitFactory
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.common
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
@@ -1,9 +1,11 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.common
import com.wrbug.polymarketbot.dto.OrderPushMessage
import com.wrbug.polymarketbot.dto.PositionPushMessage
import com.wrbug.polymarketbot.dto.WebSocketMessage as WsMessage
import com.wrbug.polymarketbot.dto.WebSocketMessageType
import com.wrbug.polymarketbot.service.accounts.PositionPushService
import com.wrbug.polymarketbot.service.copytrading.orders.OrderPushService
import kotlinx.coroutines.*
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.copytrading.configs
import com.wrbug.polymarketbot.api.OrderbookResponse
import com.wrbug.polymarketbot.entity.CopyTrading
@@ -7,6 +7,7 @@ import com.wrbug.polymarketbot.util.lt
import com.wrbug.polymarketbot.util.multi
import com.wrbug.polymarketbot.util.toSafeBigDecimal
import org.slf4j.LoggerFactory
import com.wrbug.polymarketbot.service.common.PolymarketClobService
import org.springframework.stereotype.Service
import java.math.BigDecimal
@@ -1,11 +1,14 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.copytrading.configs
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.entity.Account
import com.wrbug.polymarketbot.entity.CopyTrading
import com.wrbug.polymarketbot.entity.Leader
import com.wrbug.polymarketbot.repository.AccountRepository
import com.wrbug.polymarketbot.repository.CopyTradingRepository
import com.wrbug.polymarketbot.repository.CopyTradingTemplateRepository
import com.wrbug.polymarketbot.repository.LeaderRepository
import com.wrbug.polymarketbot.service.copytrading.monitor.CopyTradingMonitorService
import com.wrbug.polymarketbot.util.toSafeBigDecimal
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@@ -339,7 +342,7 @@ class CopyTradingService(
fun getAccountTemplates(accountId: Long): Result<AccountTemplatesResponse> {
return try {
// 验证账户是否存在
val account = accountRepository.findById(accountId).orElse(null)
accountRepository.findById(accountId).orElse(null)
?: return Result.failure(IllegalArgumentException("账户不存在"))
val copyTradings = copyTradingRepository.findByAccountId(accountId)
@@ -380,8 +383,8 @@ class CopyTradingService(
*/
private fun toDto(
copyTrading: CopyTrading,
account: com.wrbug.polymarketbot.entity.Account,
leader: com.wrbug.polymarketbot.entity.Leader
account: Account,
leader: Leader
): CopyTradingDto {
return CopyTradingDto(
id = copyTrading.id!!,
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.copytrading.configs
import com.wrbug.polymarketbot.dto.FilteredOrderDto
import com.wrbug.polymarketbot.dto.FilteredOrderListRequest
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.copytrading.leaders
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.entity.Leader
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.copytrading.monitor
import com.wrbug.polymarketbot.entity.CopyTrading
import com.wrbug.polymarketbot.entity.Leader
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.copytrading.monitor
import com.wrbug.polymarketbot.api.TradeResponse
import com.wrbug.polymarketbot.api.UserActivityResponse
@@ -9,6 +9,7 @@ import jakarta.annotation.PreDestroy
import kotlinx.coroutines.*
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import com.wrbug.polymarketbot.service.copytrading.statistics.CopyOrderTrackingService
import org.springframework.stereotype.Service
import retrofit2.Response
import java.util.concurrent.ConcurrentHashMap
@@ -72,7 +73,7 @@ class CopyTradingPollingService(
return
}
val leaderId = leader.id!!
val leaderId = leader.id
monitoredLeaders[leaderId] = leader
// 初始化缓存的交易ID集合
cachedTradeIds[leaderId] = mutableSetOf()
@@ -178,7 +179,7 @@ class CopyTradingPollingService(
return
}
val leaderId = leader.id!!
val leaderId = leader.id
val leaderAddress = leader.leaderAddress
try {
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.copytrading.monitor
import com.google.gson.Gson
import com.google.gson.JsonObject
@@ -11,6 +11,7 @@ import jakarta.annotation.PreDestroy
import kotlinx.coroutines.*
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import com.wrbug.polymarketbot.service.copytrading.statistics.CopyOrderTrackingService
import org.springframework.stereotype.Service
import java.util.concurrent.ConcurrentHashMap
@@ -65,7 +66,7 @@ class CopyTradingWebSocketService(
return
}
val leaderId = leader.id!!
val leaderId = leader.id
val leaderAddress = leader.leaderAddress.lowercase()
leaderAddresses[leaderId] = leaderAddress
@@ -1,6 +1,7 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.copytrading.orders
import com.fasterxml.jackson.databind.ObjectMapper
import com.wrbug.polymarketbot.api.MarketResponse
import com.wrbug.polymarketbot.dto.OrderDetailDto
import com.wrbug.polymarketbot.dto.OrderMessageDto
import com.wrbug.polymarketbot.dto.OrderPushMessage
@@ -13,6 +14,11 @@ import jakarta.annotation.PreDestroy
import kotlinx.coroutines.*
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import com.wrbug.polymarketbot.service.common.PolymarketClobService
import com.wrbug.polymarketbot.util.CryptoUtils
import com.wrbug.polymarketbot.repository.CopyOrderTrackingRepository
import com.wrbug.polymarketbot.repository.CopyTradingRepository
import com.wrbug.polymarketbot.repository.LeaderRepository
import org.springframework.stereotype.Service
import java.util.concurrent.ConcurrentHashMap
@@ -26,10 +32,10 @@ class OrderPushService(
private val objectMapper: ObjectMapper,
private val clobService: PolymarketClobService,
private val retrofitFactory: RetrofitFactory, // 用于创建 Gamma API 客户端(不需要认证)
private val cryptoUtils: com.wrbug.polymarketbot.util.CryptoUtils,
private val copyOrderTrackingRepository: com.wrbug.polymarketbot.repository.CopyOrderTrackingRepository? = null, // 可选,避免循环依赖
private val copyTradingRepository: com.wrbug.polymarketbot.repository.CopyTradingRepository? = null, // 可选,避免循环依赖
private val leaderRepository: com.wrbug.polymarketbot.repository.LeaderRepository? = null // 可选,避免循环依赖
private val cryptoUtils: CryptoUtils,
private val copyOrderTrackingRepository: CopyOrderTrackingRepository? = null, // 可选,避免循环依赖
private val copyTradingRepository: CopyTradingRepository? = null, // 可选,避免循环依赖
private val leaderRepository: LeaderRepository? = null // 可选,避免循环依赖
) {
private val logger = LoggerFactory.getLogger(OrderPushService::class.java)
@@ -152,9 +158,9 @@ class OrderPushService(
return account.apiKey != null &&
account.apiSecret != null &&
account.apiPassphrase != null &&
account.apiKey!!.isNotBlank() &&
account.apiSecret!!.isNotBlank() &&
account.apiPassphrase!!.isNotBlank()
account.apiKey.isNotBlank() &&
account.apiSecret.isNotBlank() &&
account.apiPassphrase.isNotBlank()
}
/**
@@ -378,7 +384,7 @@ class OrderPushService(
/**
* 获取订单详情
* 通过 PolymarketClobService 获取订单详情
* 通过 com.wrbug.polymarketbot.service.common.PolymarketClobService 获取订单详情
*/
private suspend fun fetchOrderDetail(
account: Account,
@@ -391,7 +397,7 @@ class OrderPushService(
return null
}
// 通过 PolymarketClobService 获取订单详情(需要 L2 认证)
// 通过 com.wrbug.polymarketbot.service.common.PolymarketClobService 获取订单详情(需要 L2 认证)
// 解密 API 凭证
val apiSecret = try {
decryptApiSecret(account)
@@ -453,7 +459,7 @@ class OrderPushService(
* 使用 /markets 接口通过 condition_ids 查询参数获取市场信息
* 订单返回的 market 字段是 16 进制的 condition ID "0x..."
*/
private suspend fun fetchMarketInfo(conditionId: String): com.wrbug.polymarketbot.api.MarketResponse? {
private suspend fun fetchMarketInfo(conditionId: String): MarketResponse? {
return try {
// 创建 Gamma API 客户端(公开 API,不需要认证)
val gammaApi = retrofitFactory.createGammaApi()
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.copytrading.orders
import com.wrbug.polymarketbot.api.SignedOrderObject
import com.wrbug.polymarketbot.util.toSafeBigDecimal
@@ -77,10 +77,6 @@ class OrderSigningService {
): OrderAmounts {
val sizeDecimal = size.toSafeBigDecimal()
val priceDecimal = price.toSafeBigDecimal()
// 舍入价格
val roundedPrice = roundNormal(priceDecimal, roundConfig.price)
if (side.uppercase() == "BUY") {
// BUY: makerAmount = price * size (USDC), takerAmount = size (shares)
// makerAmount 是 USDC 金额,最多 2 位小数
@@ -1,6 +1,7 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.copytrading.statistics
import com.wrbug.polymarketbot.api.NewOrderRequest
import com.wrbug.polymarketbot.api.PolymarketClobApi
import com.wrbug.polymarketbot.api.TradeResponse
import com.wrbug.polymarketbot.entity.*
import com.wrbug.polymarketbot.repository.*
@@ -9,6 +10,11 @@ import com.wrbug.polymarketbot.util.*
import kotlinx.coroutines.*
import org.slf4j.LoggerFactory
import org.springframework.dao.DataIntegrityViolationException
import com.wrbug.polymarketbot.service.copytrading.configs.CopyTradingFilterService
import com.wrbug.polymarketbot.service.copytrading.orders.OrderSigningService
import com.wrbug.polymarketbot.service.common.BlockchainService
import com.wrbug.polymarketbot.service.system.TelegramNotificationService
import com.wrbug.polymarketbot.util.CryptoUtils
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.math.BigDecimal
@@ -33,7 +39,7 @@ class CopyOrderTrackingService(
private val orderSigningService: OrderSigningService,
private val blockchainService: BlockchainService,
private val retrofitFactory: RetrofitFactory,
private val cryptoUtils: com.wrbug.polymarketbot.util.CryptoUtils,
private val cryptoUtils: CryptoUtils,
private val telegramNotificationService: TelegramNotificationService? = null // 可选,避免循环依赖
) {
@@ -381,7 +387,7 @@ class CopyOrderTrackingService(
side = "BUY",
price = buyPrice.toString(),
size = finalBuyQuantity.toString(),
owner = account.apiKey!!,
owner = account.apiKey,
copyTradingId = copyTrading.id!!,
tradeId = trade.id
)
@@ -759,7 +765,7 @@ class CopyOrderTrackingService(
// 这样可以快速响应 Leader 的交易,避免订单长期挂单导致价格不匹配
val orderRequest = NewOrderRequest(
order = signedOrder,
owner = account.apiKey!!,
owner = account.apiKey,
orderType = "FAK", // Fill-And-Kill
deferExec = false
)
@@ -782,8 +788,8 @@ class CopyOrderTrackingService(
side = "SELL",
price = sellPrice.toString(),
size = totalMatched.toString(),
owner = account.apiKey!!,
copyTradingId = copyTrading.id!!,
owner = account.apiKey,
copyTradingId = copyTrading.id,
tradeId = leaderSellTrade.id
)
@@ -830,11 +836,11 @@ class CopyOrderTrackingService(
val totalRealizedPnl = matchDetails.sumOf { it.realizedPnl.toSafeBigDecimal() }
val matchRecord = SellMatchRecord(
copyTradingId = copyTrading.id!!,
copyTradingId = copyTrading.id,
sellOrderId = realSellOrderId, // 使用真实订单ID
leaderSellTradeId = leaderSellTrade.id,
marketId = leaderSellTrade.market,
side = leaderSellTrade.outcomeIndex?.toString() ?: "0", // 使用outcomeIndex作为side(兼容旧数据)
side = leaderSellTrade.outcomeIndex.toString(), // 使用outcomeIndex作为side(兼容旧数据)
outcomeIndex = leaderSellTrade.outcomeIndex, // 新增字段
totalMatchedQuantity = totalMatched,
sellPrice = sellPrice,
@@ -857,7 +863,7 @@ class CopyOrderTrackingService(
* 注意重试时会重新生成salt并重新签名确保每次重试都是新的订单
*/
private suspend fun createOrderWithRetry(
clobApi: com.wrbug.polymarketbot.api.PolymarketClobApi,
clobApi: PolymarketClobApi,
privateKey: String,
makerAddress: String,
tokenId: String,
@@ -914,7 +920,7 @@ class CopyOrderTrackingService(
delay(1000) // 重试前等待1秒
continue
}
return Result.failure(lastError!!)
return Result.failure(lastError)
}
val response = orderResponse.body()!!
@@ -928,7 +934,7 @@ class CopyOrderTrackingService(
delay(1000) // 重试前等待1秒
continue
}
return Result.failure(lastError!!)
return Result.failure(lastError)
}
// 成功
@@ -943,7 +949,7 @@ class CopyOrderTrackingService(
delay(1000) // 重试前等待1秒
continue
}
return Result.failure(lastError!!)
return Result.failure(lastError)
}
}
@@ -1105,7 +1111,7 @@ class CopyOrderTrackingService(
}
// 2. 检查每日亏损限制(需要计算今日已实现盈亏)
val todaySellRecords = sellMatchRecordRepository.findByCopyTradingId(copyTrading.id!!)
val todaySellRecords = sellMatchRecordRepository.findByCopyTradingId(copyTrading.id)
.filter { it.createdAt >= todayStart }
val todayRealizedPnl = todaySellRecords.sumOf { it.totalRealizedPnl.toSafeBigDecimal() }
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.copytrading.statistics
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.entity.*
@@ -13,6 +13,8 @@ import org.slf4j.LoggerFactory
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
import com.wrbug.polymarketbot.service.accounts.AccountService
import com.wrbug.polymarketbot.service.common.BlockchainService
import org.springframework.stereotype.Service
import java.math.BigDecimal
import java.math.RoundingMode
@@ -30,7 +32,7 @@ class CopyTradingStatisticsService(
private val accountRepository: AccountRepository,
private val leaderRepository: LeaderRepository,
private val accountService: AccountService,
private val blockchainService: com.wrbug.polymarketbot.service.BlockchainService
private val blockchainService: BlockchainService
) {
private val logger = LoggerFactory.getLogger(CopyTradingStatisticsService::class.java)
@@ -69,7 +71,10 @@ class CopyTradingStatisticsService(
// 9. 计算未实现盈亏(使用链上实际持仓,而不是 remainingQuantity
val unrealizedPnl = calculateUnrealizedPnl(buyOrders, currentPrice, actualPositions)
// 9. 构建响应
// 10. 计算持仓价值(使用链上实际持仓和当前价格)
val positionValue = calculatePositionValue(buyOrders, currentPrice, actualPositions)
// 11. 构建响应
val response = CopyTradingStatisticsResponse(
copyTradingId = copyTradingId,
accountId = copyTrading.accountId,
@@ -85,7 +90,7 @@ class CopyTradingStatisticsService(
totalSellOrders = statistics.totalSellOrders,
totalSellAmount = statistics.totalSellAmount,
currentPositionQuantity = statistics.currentPositionQuantity,
currentPositionValue = calculatePositionValue(statistics.currentPositionQuantity, currentPrice),
currentPositionValue = positionValue,
totalRealizedPnl = statistics.totalRealizedPnl,
totalUnrealizedPnl = unrealizedPnl,
totalPnl = (statistics.totalRealizedPnl.toSafeBigDecimal().add(unrealizedPnl.toSafeBigDecimal())).toString(),
@@ -105,7 +110,7 @@ class CopyTradingStatisticsService(
fun getOrderList(request: OrderTrackingRequest): Result<OrderListResponse> {
return try {
// 1. 验证跟单关系
val copyTrading = copyTradingRepository.findById(request.copyTradingId).orElse(null)
copyTradingRepository.findById(request.copyTradingId).orElse(null)
?: return Result.failure(IllegalArgumentException("跟单关系不存在: ${request.copyTradingId}"))
// 2. 根据类型查询
@@ -354,7 +359,7 @@ class CopyTradingStatisticsService(
* 获取链上实际持仓
* (marketId, outcomeIndex) 组合返回实际持仓数量
*/
private suspend fun getActualPositions(account: com.wrbug.polymarketbot.entity.Account?): Map<String, BigDecimal> {
private suspend fun getActualPositions(account: Account?): Map<String, BigDecimal> {
val positions = mutableMapOf<String, BigDecimal>()
if (account == null || account.proxyAddress.isBlank()) {
@@ -424,11 +429,41 @@ class CopyTradingStatisticsService(
/**
* 计算持仓价值
* 使用链上实际持仓数量和当前市场价格计算
*/
private fun calculatePositionValue(positionQuantity: String, currentPrices: Map<String, String>): String {
// 这里简化处理,实际应该根据每个市场的持仓分别计算
// 暂时返回0,因为需要知道每个市场的持仓数量
return "0"
private fun calculatePositionValue(
buyOrders: List<CopyOrderTracking>,
currentPrices: Map<String, String>,
actualPositions: Map<String, BigDecimal>
): String {
var totalPositionValue = BigDecimal.ZERO
for (order in buyOrders) {
// 如果没有 outcomeIndex,跳过(无法确定价格和持仓)
if (order.outcomeIndex == null) {
logger.warn("订单缺少 outcomeIndex,跳过持仓价值计算: orderId=${order.buyOrderId}, marketId=${order.marketId}")
continue
}
// 使用 "marketId:outcomeIndex" 作为 key
val key = "${order.marketId}:${order.outcomeIndex}"
// 获取链上实际持仓数量(如果存在),否则使用 remainingQuantity
val actualQty = actualPositions[key] ?: order.remainingQuantity.toSafeBigDecimal()
// 如果实际持仓 <= 0,说明已全部卖出(包括手动卖出),跳过持仓价值计算
if (actualQty.lte(BigDecimal.ZERO)) continue
// 获取当前市场价格
val currentPrice = currentPrices[key]?.toSafeBigDecimal()
?: continue // 如果没有当前价格,跳过
// 计算持仓价值:持仓数量 × 当前价格
val positionValue = actualQty.multi(currentPrice)
totalPositionValue = totalPositionValue.add(positionValue)
}
return totalPositionValue.toString()
}
/**
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.copytrading.templates
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.entity.CopyTradingTemplate
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.system
import com.wrbug.polymarketbot.dto.ApiHealthCheckDto
import com.wrbug.polymarketbot.dto.ApiHealthCheckResponse
@@ -12,6 +12,8 @@ import org.springframework.beans.BeansException
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware
import org.springframework.beans.factory.annotation.Value
import com.wrbug.polymarketbot.service.copytrading.orders.OrderPushService
import com.wrbug.polymarketbot.service.copytrading.monitor.CopyTradingWebSocketService
import org.springframework.stereotype.Service
import java.util.concurrent.TimeUnit
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.system
import com.fasterxml.jackson.databind.ObjectMapper
import com.wrbug.polymarketbot.dto.*
@@ -1,8 +1,10 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.system
import com.wrbug.polymarketbot.dto.*
import com.wrbug.polymarketbot.entity.ProxyConfig
import com.wrbug.polymarketbot.repository.ProxyConfigRepository
import com.wrbug.polymarketbot.service.copytrading.monitor.CopyTradingWebSocketService
import com.wrbug.polymarketbot.service.copytrading.orders.OrderPushService
import com.wrbug.polymarketbot.util.ProxyConfigProvider
import com.wrbug.polymarketbot.util.TrustAllHostnameVerifier
import com.wrbug.polymarketbot.util.createSSLSocketFactory
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.system
import com.wrbug.polymarketbot.api.BuilderRelayerApi
import com.wrbug.polymarketbot.api.EthereumRpcApi
@@ -802,10 +802,10 @@ class RelayClientService(
safeTxs: List<SafeTransaction>
): Result<String> {
// 批量执行:将多个交易合并为一个 execTransaction 调用
// 当前实现:委托给 BlockchainService
// 当前实现:委托给 com.wrbug.polymarketbot.service.common.BlockchainService
return Result.failure(
UnsupportedOperationException(
"批量 Gasless 执行暂未实现。请使用 BlockchainService.redeemPositions() 方法。"
"批量 Gasless 执行暂未实现。请使用 com.wrbug.polymarketbot.service.common.BlockchainService.redeemPositions() 方法。"
)
)
}
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.system
import com.wrbug.polymarketbot.dto.SystemConfigDto
import com.wrbug.polymarketbot.dto.SystemConfigUpdateRequest
@@ -1,7 +1,8 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.system
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.wrbug.polymarketbot.api.PolymarketClobApi
import com.wrbug.polymarketbot.dto.NotificationConfigData
import com.wrbug.polymarketbot.dto.TelegramConfigData
import com.wrbug.polymarketbot.util.createClient
@@ -65,7 +66,7 @@ class TelegramNotificationService(
side: String,
accountName: String? = null,
walletAddress: String? = null,
clobApi: com.wrbug.polymarketbot.api.PolymarketClobApi? = null,
clobApi: PolymarketClobApi? = null,
apiKey: String? = null,
apiSecret: String? = null,
apiPassphrase: String? = null,
@@ -1,4 +1,4 @@
package com.wrbug.polymarketbot.service
package com.wrbug.polymarketbot.service.system
import com.wrbug.polymarketbot.dto.UserCreateRequest
import com.wrbug.polymarketbot.dto.UserDto
@@ -3,7 +3,7 @@ package com.wrbug.polymarketbot.websocket
import com.fasterxml.jackson.databind.ObjectMapper
import com.wrbug.polymarketbot.dto.WebSocketMessage as WsMessage
import com.wrbug.polymarketbot.dto.WebSocketMessageType
import com.wrbug.polymarketbot.service.WebSocketSubscriptionService
import com.wrbug.polymarketbot.service.common.WebSocketSubscriptionService
import jakarta.annotation.PostConstruct
import jakarta.annotation.PreDestroy
import kotlinx.coroutines.*
+38 -38
View File
@@ -145,31 +145,31 @@ export const apiService = {
* 获取用户列表
*/
list: () =>
apiClient.post<ApiResponse<any[]>>('/users/list', {}),
apiClient.post<ApiResponse<any[]>>('/system/users/list', {}),
/**
* 创建用户
*/
create: (data: { username: string; password: string }) =>
apiClient.post<ApiResponse<any>>('/users/create', data),
apiClient.post<ApiResponse<any>>('/system/users/create', data),
/**
* 更新用户密码
*/
updatePassword: (data: { userId: number; newPassword: string }) =>
apiClient.post<ApiResponse<void>>('/users/update-password', data),
apiClient.post<ApiResponse<void>>('/system/users/update-password', data),
/**
* 删除用户
*/
delete: (data: { userId: number }) =>
apiClient.post<ApiResponse<void>>('/users/delete', data),
apiClient.post<ApiResponse<void>>('/system/users/delete', data),
/**
* 用户修改自己的密码
*/
updateOwnPassword: (data: { newPassword: string }) =>
apiClient.post<ApiResponse<void>>('/users/update-own-password', data)
apiClient.post<ApiResponse<void>>('/system/users/update-own-password', data)
},
/**
@@ -203,61 +203,61 @@ export const apiService = {
* 导入账户
*/
import: (data: any) =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/import', data),
apiClient.post<ApiResponse<any>>('/accounts/import', data),
/**
* 更新账户
*/
update: (data: any) =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/update', data),
apiClient.post<ApiResponse<any>>('/accounts/update', data),
/**
* 删除账户
*/
delete: (data: { accountId: number }) =>
apiClient.post<ApiResponse<void>>('/copy-trading/accounts/delete', data),
apiClient.post<ApiResponse<void>>('/accounts/delete', data),
/**
* 查询账户列表
*/
list: () =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/list', {}),
apiClient.post<ApiResponse<any>>('/accounts/list', {}),
/**
* 查询账户详情
*/
detail: (data: { accountId?: number }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/detail', data),
apiClient.post<ApiResponse<any>>('/accounts/detail', data),
/**
* 查询账户余额
*/
balance: (data: { accountId?: number }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/balance', data),
apiClient.post<ApiResponse<any>>('/accounts/balance', data),
/**
* 查询所有账户的仓位列表
*/
positionsList: () =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/positions/list', {}),
apiClient.post<ApiResponse<any>>('/accounts/positions/list', {}),
/**
* 卖出仓位
*/
sellPosition: (data: any) =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/positions/sell', data),
apiClient.post<ApiResponse<any>>('/accounts/positions/sell', data),
/**
* 获取可赎回仓位统计
*/
getRedeemableSummary: (data: { accountId?: number }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/positions/redeemable-summary', data),
apiClient.post<ApiResponse<any>>('/accounts/positions/redeemable-summary', data),
/**
* 赎回仓位
*/
redeemPositions: (data: any) =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/positions/redeem', data),
apiClient.post<ApiResponse<any>>('/accounts/positions/redeem', data),
},
@@ -269,13 +269,13 @@ export const apiService = {
* 获取市场价格(通过 Gamma API)
*/
getMarketPrice: (data: { marketId: string; outcomeIndex?: number }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/markets/price', data),
apiClient.post<ApiResponse<any>>('/markets/price', data),
/**
* 获取最新价(从订单表获取,供前端下单时显示)
*/
getLatestPrice: (data: { tokenId: string }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/markets/latest-price', data)
apiClient.post<ApiResponse<any>>('/markets/latest-price', data)
},
/**
@@ -365,37 +365,37 @@ export const apiService = {
* 2. 不提供 templateId:手动输入所有配置参数
*/
create: (data: any) =>
apiClient.post<ApiResponse<any>>('/copy-trading/create', data),
apiClient.post<ApiResponse<any>>('/copy-trading/configs/create', data),
/**
* 更新跟单配置
*/
update: (data: any) =>
apiClient.post<ApiResponse<any>>('/copy-trading/update', data),
apiClient.post<ApiResponse<any>>('/copy-trading/configs/update', data),
/**
* 查询跟单列表
*/
list: (data: { accountId?: number; leaderId?: number; enabled?: boolean } = {}) =>
apiClient.post<ApiResponse<any>>('/copy-trading/list', data),
apiClient.post<ApiResponse<any>>('/copy-trading/configs/list', data),
/**
* 更新跟单状态(兼容旧接口)
*/
updateStatus: (data: { copyTradingId: number; enabled: boolean }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/update-status', data),
apiClient.post<ApiResponse<any>>('/copy-trading/configs/update-status', data),
/**
* 删除跟单
*/
delete: (data: { copyTradingId: number }) =>
apiClient.post<ApiResponse<void>>('/copy-trading/delete', data),
apiClient.post<ApiResponse<void>>('/copy-trading/configs/delete', data),
/**
* 查询钱包绑定的跟单配置(兼容旧接口)
*/
getAccountTemplates: (data: { accountId: number }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/account-templates', data),
apiClient.post<ApiResponse<any>>('/copy-trading/configs/account-templates', data),
/**
* 查询被过滤订单列表
@@ -408,7 +408,7 @@ export const apiService = {
startTime?: number
endTime?: number
}) =>
apiClient.post<ApiResponse<any>>('/copy-trading/filtered-orders', data)
apiClient.post<ApiResponse<any>>('/copy-trading/configs/filtered-orders', data)
},
/**
@@ -476,13 +476,13 @@ export const apiService = {
* 获取当前代理配置
*/
get: () =>
apiClient.post<ApiResponse<any>>('/proxy-config/get', {}),
apiClient.post<ApiResponse<any>>('/system/proxy/get', {}),
/**
* 获取所有代理配置
*/
list: () =>
apiClient.post<ApiResponse<any[]>>('/proxy-config/list', {}),
apiClient.post<ApiResponse<any[]>>('/system/proxy/list', {}),
/**
* 保存 HTTP 代理配置
@@ -494,7 +494,7 @@ export const apiService = {
username?: string
password?: string
}) =>
apiClient.post<ApiResponse<any>>('/proxy-config/http/save', data),
apiClient.post<ApiResponse<any>>('/system/proxy/http/save', data),
/**
* 检查代理是否可用
@@ -504,13 +504,13 @@ export const apiService = {
success: boolean
message: string
responseTime?: number
}>>('/proxy-config/check', {}),
}>>('/system/proxy/check', {}),
/**
* 删除代理配置
*/
delete: (data: { id: number }) =>
apiClient.post<ApiResponse<void>>('/proxy-config/delete', data),
apiClient.post<ApiResponse<void>>('/system/proxy/delete', data),
/**
* 检查所有 API 的健康状态
@@ -524,7 +524,7 @@ export const apiService = {
message: string
responseTime?: number
}>
}>>('/proxy-config/api-health-check', {})
}>>('/system/proxy/api-health-check', {})
},
/**
@@ -535,49 +535,49 @@ export const apiService = {
* 获取配置列表
*/
list: (data?: { type?: string }) =>
apiClient.post<ApiResponse<NotificationConfig[]>>('/notifications/configs/list', data || {}),
apiClient.post<ApiResponse<NotificationConfig[]>>('/system/notifications/configs/list', data || {}),
/**
* 获取配置详情
*/
detail: (data: { id: number }) =>
apiClient.post<ApiResponse<NotificationConfig>>('/notifications/configs/detail', data),
apiClient.post<ApiResponse<NotificationConfig>>('/system/notifications/configs/detail', data),
/**
* 创建配置
*/
create: (data: NotificationConfigRequest) =>
apiClient.post<ApiResponse<NotificationConfig>>('/notifications/configs/create', data),
apiClient.post<ApiResponse<NotificationConfig>>('/system/notifications/configs/create', data),
/**
* 更新配置
*/
update: (data: NotificationConfigUpdateRequest) =>
apiClient.post<ApiResponse<NotificationConfig>>('/notifications/configs/update', data),
apiClient.post<ApiResponse<NotificationConfig>>('/system/notifications/configs/update', data),
/**
* 更新启用状态
*/
updateEnabled: (data: { id: number; enabled: boolean }) =>
apiClient.post<ApiResponse<NotificationConfig>>('/notifications/configs/update-enabled', data),
apiClient.post<ApiResponse<NotificationConfig>>('/system/notifications/configs/update-enabled', data),
/**
* 删除配置
*/
delete: (data: { id: number }) =>
apiClient.post<ApiResponse<void>>('/notifications/configs/delete', data),
apiClient.post<ApiResponse<void>>('/system/notifications/configs/delete', data),
/**
* 测试通知
*/
test: (data?: { message?: string }) =>
apiClient.post<ApiResponse<boolean>>('/notifications/test', data || {}),
apiClient.post<ApiResponse<boolean>>('/system/notifications/test', data || {}),
/**
* 获取 Telegram Chat IDs
*/
getTelegramChatIds: (data: { botToken: string }) =>
apiClient.post<ApiResponse<string[]>>('/notifications/telegram/get-chat-ids', data)
apiClient.post<ApiResponse<string[]>>('/system/notifications/telegram/get-chat-ids', data)
},
/**