diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/controller/accounts/AccountController.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/controller/accounts/AccountController.kt index 152ba85..44ac456 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/controller/accounts/AccountController.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/controller/accounts/AccountController.kt @@ -95,65 +95,6 @@ class AccountController( } } - /** - * 刷新账户的代理地址 - * 使用最新的代理地址计算逻辑(支持 Magic 和 Safe 两种类型) - */ - @PostMapping("/refresh-proxy") - fun refreshProxyAddress(@RequestBody request: AccountDetailRequest): ResponseEntity> { - return try { - if (request.accountId == null || request.accountId <= 0) { - return ResponseEntity.ok(ApiResponse.error(ErrorCode.PARAM_ACCOUNT_ID_INVALID, messageSource = messageSource)) - } - - val result = accountService.refreshProxyAddress(request.accountId) - result.fold( - onSuccess = { account -> - ResponseEntity.ok(ApiResponse.success(account)) - }, - onFailure = { e -> - logger.error("刷新代理地址失败: ${e.message}", e) - when (e) { - is IllegalArgumentException -> ResponseEntity.ok( - ApiResponse.error( - ErrorCode.PARAM_ERROR, - e.message, - messageSource - ) - ) - - else -> ResponseEntity.ok(ApiResponse.error(ErrorCode.SERVER_ERROR, e.message, messageSource)) - } - } - ) - } catch (e: Exception) { - logger.error("刷新代理地址异常: ${e.message}", e) - ResponseEntity.ok(ApiResponse.error(ErrorCode.SERVER_ERROR, e.message, messageSource)) - } - } - - /** - * 批量刷新所有账户的代理地址 - */ - @PostMapping("/refresh-all-proxies") - fun refreshAllProxyAddresses(): ResponseEntity>> { - return try { - val result = accountService.refreshAllProxyAddresses() - result.fold( - onSuccess = { accounts -> - ResponseEntity.ok(ApiResponse.success(accounts)) - }, - onFailure = { e -> - logger.error("批量刷新代理地址失败: ${e.message}", e) - ResponseEntity.ok(ApiResponse.error(ErrorCode.SERVER_ERROR, e.message, messageSource)) - } - ) - } catch (e: Exception) { - logger.error("批量刷新代理地址异常: ${e.message}", e) - ResponseEntity.ok(ApiResponse.error(ErrorCode.SERVER_ERROR, e.message, messageSource)) - } - } - /** * 删除账户 */ diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/CopyTrading.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/CopyTrading.kt index ec2aa46..22da6b4 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/CopyTrading.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/CopyTrading.kt @@ -32,7 +32,7 @@ data class CopyTrading( @Column(name = "copy_mode", nullable = false, length = 10) val copyMode: String = "RATIO", // "RATIO" 或 "FIXED" - @Column(name = "copy_ratio", nullable = false, precision = 10, scale = 2) + @Column(name = "copy_ratio", nullable = false, precision = 20, scale = 8) val copyRatio: BigDecimal = BigDecimal.ONE, // 仅在 copyMode="RATIO" 时生效 @Column(name = "fixed_amount", precision = 20, scale = 8) diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/CopyTradingTemplate.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/CopyTradingTemplate.kt index 9a3d355..866ce86 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/CopyTradingTemplate.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/CopyTradingTemplate.kt @@ -20,7 +20,7 @@ data class CopyTradingTemplate( @Column(name = "copy_mode", nullable = false, length = 10) val copyMode: String = "RATIO", // "RATIO" 或 "FIXED" - @Column(name = "copy_ratio", nullable = false, precision = 10, scale = 2) + @Column(name = "copy_ratio", nullable = false, precision = 20, scale = 8) val copyRatio: BigDecimal = BigDecimal.ONE, // 仅在 copyMode="RATIO" 时生效 @Column(name = "fixed_amount", precision = 20, scale = 8) 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 e60300e..f9f7048 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 @@ -202,85 +202,6 @@ class AccountService( } } - /** - * 刷新账户的代理地址 - * 使用最新的代理地址计算逻辑(支持 Magic 和 Safe 两种类型) - */ - @Transactional - fun refreshProxyAddress(accountId: Long): Result { - return try { - val account = accountRepository.findById(accountId) - .orElse(null) ?: return Result.failure(IllegalArgumentException("账户不存在")) - - // 重新获取代理地址(使用保存的钱包类型) - val proxyAddress = runBlocking { - val proxyResult = blockchainService.getProxyAddress(account.walletAddress, account.walletType) - if (proxyResult.isSuccess) { - proxyResult.getOrNull() - ?: throw IllegalStateException("获取代理地址返回空值") - } else { - val error = proxyResult.exceptionOrNull() - throw IllegalStateException("获取代理地址失败: ${error?.message}") - } - } - - // 更新账户 - val updated = account.copy( - proxyAddress = proxyAddress, - updatedAt = System.currentTimeMillis() - ) - val saved = accountRepository.save(updated) - - logger.info("刷新代理地址成功: accountId=${accountId}, oldProxy=${account.proxyAddress}, newProxy=${proxyAddress}") - Result.success(toDto(saved)) - } catch (e: Exception) { - logger.error("刷新代理地址失败: accountId=${accountId}", e) - Result.failure(e) - } - } - - /** - * 刷新所有账户的代理地址 - */ - @Transactional - fun refreshAllProxyAddresses(): Result> { - return try { - val accounts = accountRepository.findAll() - val updatedAccounts = mutableListOf() - - accounts.forEach { account -> - try { - val proxyAddress = runBlocking { - val proxyResult = blockchainService.getProxyAddress(account.walletAddress, account.walletType) - if (proxyResult.isSuccess) { - proxyResult.getOrNull() - } else { - null - } - } - - if (proxyAddress != null && proxyAddress != account.proxyAddress) { - val updated = account.copy( - proxyAddress = proxyAddress, - updatedAt = System.currentTimeMillis() - ) - val saved = accountRepository.save(updated) - logger.info("刷新代理地址成功: accountId=${account.id}, oldProxy=${account.proxyAddress}, newProxy=${proxyAddress}") - updatedAccounts.add(toDto(saved)) - } - } catch (e: Exception) { - logger.warn("刷新账户 ${account.id} 代理地址失败: ${e.message}") - } - } - - logger.info("批量刷新代理地址完成: 更新了 ${updatedAccounts.size} 个账户") - Result.success(updatedAccounts) - } catch (e: Exception) { - logger.error("批量刷新代理地址失败", e) - Result.failure(e) - } - } - /** * 删除账户 */ diff --git a/backend/src/main/resources/db/migration/V18__increase_copy_ratio_precision.sql b/backend/src/main/resources/db/migration/V18__increase_copy_ratio_precision.sql new file mode 100644 index 0000000..361de07 --- /dev/null +++ b/backend/src/main/resources/db/migration/V18__increase_copy_ratio_precision.sql @@ -0,0 +1,14 @@ +-- ============================================ +-- V18: 增加 copy_ratio 字段的精度,支持更小的小数值 +-- ============================================ + +-- 修改 copy_trading 表的 copy_ratio 字段精度 +-- 从 DECIMAL(10, 2) 改为 DECIMAL(20, 8),支持更小的跟单比例值(如 0.001) +ALTER TABLE copy_trading +MODIFY COLUMN copy_ratio DECIMAL(20, 8) NOT NULL DEFAULT 1.00000000 COMMENT '跟单比例(仅在copyMode=RATIO时生效)'; + +-- 修改 copy_trading_templates 表的 copy_ratio 字段精度 +-- 从 DECIMAL(10, 2) 改为 DECIMAL(20, 8),支持更小的跟单比例值(如 0.001) +ALTER TABLE copy_trading_templates +MODIFY COLUMN copy_ratio DECIMAL(20, 8) NOT NULL DEFAULT 1.00000000 COMMENT '跟单比例(仅在copyMode=RATIO时生效)'; + diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts index acfab40..7acea1a 100644 --- a/frontend/src/utils/index.ts +++ b/frontend/src/utils/index.ts @@ -1,3 +1,34 @@ +/** + * 格式化数字,自动去除尾随零 + * @param value - 数字值(字符串或数字) + * @param maxDecimals - 最大小数位数(默认不限制) + * @returns 格式化后的字符串,如果值为空或无效则返回 '' + * @example + * formatNumber(100.00) => "100" + * formatNumber(100.50) => "100.5" + * formatNumber(100.55) => "100.55" + */ +export const formatNumber = (value: string | number | undefined | null, maxDecimals?: number): string => { + if (value === undefined || value === null || value === '') { + return '' + } + + const num = typeof value === 'string' ? parseFloat(value) : value + if (isNaN(num)) { + return '' + } + + // 如果有最大小数位数限制,先截断 + if (maxDecimals !== undefined) { + const multiplier = Math.pow(10, maxDecimals) + const truncated = Math.floor(num * multiplier) / multiplier + return truncated.toFixed(maxDecimals).replace(/\.?0+$/, '') + } + + // 直接转换为字符串,然后去除尾随零 + return num.toString().replace(/\.?0+$/, '') +} + /** * 格式化 USDC 金额 * 最多显示 4 位小数,自动去除尾随零(截断,不四舍五入)