refactor: 移除刷新代理钱包接口,增加copyRatio精度支持

- 移除刷新代理钱包相关接口和方法
  - AccountController: 移除 /refresh-proxy 和 /refresh-all-proxies 接口
  - AccountService: 移除 refreshProxyAddress 和 refreshAllProxyAddresses 方法

- 增加 copyRatio 字段精度支持
  - CopyTrading: copyRatio 精度从 DECIMAL(10,2) 增加到 DECIMAL(20,8)
  - CopyTradingTemplate: copyRatio 精度从 DECIMAL(10,2) 增加到 DECIMAL(20,8)
  - 新增数据库迁移脚本 V18__increase_copy_ratio_precision.sql

- 前端工具函数
  - 添加 formatNumber 函数用于格式化数字显示

修复的文件:
- AccountController.kt
- AccountService.kt
- CopyTrading.kt
- CopyTradingTemplate.kt
- frontend/src/utils/index.ts
- V18__increase_copy_ratio_precision.sql
This commit is contained in:
WrBug
2026-01-04 12:49:20 +08:00
parent 185cade11d
commit 95930332df
6 changed files with 47 additions and 140 deletions
@@ -95,65 +95,6 @@ class AccountController(
}
}
/**
* 刷新账户的代理地址
* 使用最新的代理地址计算逻辑(支持 Magic 和 Safe 两种类型)
*/
@PostMapping("/refresh-proxy")
fun refreshProxyAddress(@RequestBody request: AccountDetailRequest): ResponseEntity<ApiResponse<AccountDto>> {
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<ApiResponse<List<AccountDto>>> {
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))
}
}
/**
* 删除账户
*/
@@ -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)
@@ -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)
@@ -202,85 +202,6 @@ class AccountService(
}
}
/**
* 刷新账户的代理地址
* 使用最新的代理地址计算逻辑(支持 Magic 和 Safe 两种类型)
*/
@Transactional
fun refreshProxyAddress(accountId: Long): Result<AccountDto> {
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<List<AccountDto>> {
return try {
val accounts = accountRepository.findAll()
val updatedAccounts = mutableListOf<AccountDto>()
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)
}
}
/**
* 删除账户
*/
@@ -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时生效)';
+31
View File
@@ -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 位小数,自动去除尾随零(截断,不四舍五入)