From 6e5ccdfe5ce5f490be97658f9aabb40339e998e5 Mon Sep 17 00:00:00 2001 From: WrBug Date: Sat, 3 Jan 2026 18:29:39 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20walletType=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E9=97=AE=E9=A2=98=E5=92=8C=20WebSocket=20?= =?UTF-8?q?=E5=86=85=E5=AD=98=E6=B3=84=E6=BC=8F=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=B4=A6=E6=88=B7=E5=AF=BC=E5=85=A5=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端修复: - 添加 walletType 字段到 Account 实体和数据库(V17 迁移) - 修复 refreshProxyAddress 方法使用正确的 walletType - 修复 WebSocketTicketService 内存泄漏(添加定时清理任务) 前端优化: - 账户导入改为 Modal 弹窗,提升用户体验 - 默认钱包类型改为 Web3 Wallet(safe),并将其选项排在首位 - 更新多语言文本:将 MetaMask(浏览器钱包)改为 Web3钱包 - 修复 walletTypeHelp Tooltip 换行显示问题 --- .../com/wrbug/polymarketbot/dto/AccountDto.kt | 1 + .../com/wrbug/polymarketbot/entity/Account.kt | 3 ++ .../service/accounts/AccountService.kt | 8 ++-- .../service/auth/WebSocketTicketService.kt | 10 +++++ .../V17__add_wallet_type_to_accounts.sql | 35 ++++++++++++++++ frontend/src/components/AccountImportForm.tsx | 13 +++--- frontend/src/locales/en/common.json | 4 +- frontend/src/locales/zh-CN/common.json | 4 +- frontend/src/locales/zh-TW/common.json | 4 +- frontend/src/pages/AccountList.tsx | 40 ++++++++++++++++++- frontend/src/types/index.ts | 2 + 11 files changed, 109 insertions(+), 15 deletions(-) create mode 100644 backend/src/main/resources/db/migration/V17__add_wallet_type_to_accounts.sql diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/dto/AccountDto.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/dto/AccountDto.kt index b21b818..193f0ca 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/dto/AccountDto.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/dto/AccountDto.kt @@ -73,6 +73,7 @@ data class AccountDto( val proxyAddress: String, // Polymarket 代理钱包地址 val accountName: String?, val isEnabled: Boolean, // 是否启用(用于订单推送等功能的开关) + val walletType: String = "magic", // 钱包类型:magic(邮箱/OAuth登录)或 safe(MetaMask浏览器钱包) val apiKeyConfigured: Boolean, // API Key 是否已配置(不返回实际 Key) val apiSecretConfigured: Boolean, // API Secret 是否已配置 val apiPassphraseConfigured: Boolean, // API Passphrase 是否已配置 diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/Account.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/Account.kt index fd5056f..d0cd8d1 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/Account.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/entity/Account.kt @@ -40,6 +40,9 @@ data class Account( @Column(name = "is_enabled", nullable = false) val isEnabled: Boolean = true, // 是否启用(用于订单推送等功能的开关) + @Column(name = "wallet_type", nullable = false, length = 20) + val walletType: String = "magic", // 钱包类型:magic(邮箱/OAuth登录)或 safe(MetaMask浏览器钱包) + @Column(name = "created_at", nullable = false) val createdAt: Long = System.currentTimeMillis(), 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 1b52f44..e60300e 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 @@ -151,6 +151,7 @@ class AccountService( accountName = accountName, isDefault = false, // 不再支持默认账户 isEnabled = request.isEnabled, + walletType = request.walletType, // 保存钱包类型 createdAt = System.currentTimeMillis(), updatedAt = System.currentTimeMillis() ) @@ -211,9 +212,9 @@ class AccountService( val account = accountRepository.findById(accountId) .orElse(null) ?: return Result.failure(IllegalArgumentException("账户不存在")) - // 重新获取代理地址 + // 重新获取代理地址(使用保存的钱包类型) val proxyAddress = runBlocking { - val proxyResult = blockchainService.getProxyAddress(account.walletAddress) + val proxyResult = blockchainService.getProxyAddress(account.walletAddress, account.walletType) if (proxyResult.isSuccess) { proxyResult.getOrNull() ?: throw IllegalStateException("获取代理地址返回空值") @@ -250,7 +251,7 @@ class AccountService( accounts.forEach { account -> try { val proxyAddress = runBlocking { - val proxyResult = blockchainService.getProxyAddress(account.walletAddress) + val proxyResult = blockchainService.getProxyAddress(account.walletAddress, account.walletType) if (proxyResult.isSuccess) { proxyResult.getOrNull() } else { @@ -443,6 +444,7 @@ class AccountService( proxyAddress = account.proxyAddress, accountName = account.accountName, isEnabled = account.isEnabled, + walletType = account.walletType, apiKeyConfigured = account.apiKey != null, apiSecretConfigured = account.apiSecret != null, apiPassphraseConfigured = account.apiPassphrase != null, diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/auth/WebSocketTicketService.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/auth/WebSocketTicketService.kt index 14b2c0b..12d6c06 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/auth/WebSocketTicketService.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/auth/WebSocketTicketService.kt @@ -1,5 +1,6 @@ package com.wrbug.polymarketbot.service.auth +import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Service import java.security.SecureRandom import java.util.concurrent.ConcurrentHashMap @@ -80,4 +81,13 @@ class WebSocketTicketService { val now = System.currentTimeMillis() tickets.entries.removeIf { it.value.expiresAt < now } } + + /** + * 定时清理过期票据(每分钟执行一次) + * 防止过期票据长时间占用内存 + */ + @Scheduled(fixedRate = 60_000) // 60秒 = 60000毫秒 + fun scheduledCleanup() { + cleanupExpiredTickets() + } } diff --git a/backend/src/main/resources/db/migration/V17__add_wallet_type_to_accounts.sql b/backend/src/main/resources/db/migration/V17__add_wallet_type_to_accounts.sql new file mode 100644 index 0000000..0fb5e92 --- /dev/null +++ b/backend/src/main/resources/db/migration/V17__add_wallet_type_to_accounts.sql @@ -0,0 +1,35 @@ +-- ============================================ +-- 添加 wallet_type 字段到 wallet_accounts 表 +-- 用于区分 Magic 和 Safe 两种钱包类型 +-- ============================================ + +-- 使用存储过程检查并添加字段(如果不存在) +DELIMITER $$ + +CREATE PROCEDURE IF NOT EXISTS add_wallet_type_if_not_exists() +BEGIN + DECLARE column_exists INT DEFAULT 0; + + SELECT COUNT(*) INTO column_exists + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'wallet_accounts' + AND COLUMN_NAME = 'wallet_type'; + + IF column_exists = 0 THEN + ALTER TABLE wallet_accounts + ADD COLUMN wallet_type VARCHAR(20) NOT NULL DEFAULT 'magic' COMMENT '钱包类型:magic(邮箱/OAuth登录)或 safe(MetaMask浏览器钱包)' AFTER is_enabled; + END IF; +END$$ + +DELIMITER ; + +-- 执行存储过程 +CALL add_wallet_type_if_not_exists(); + +-- 删除存储过程 +DROP PROCEDURE IF EXISTS add_wallet_type_if_not_exists; + +-- 为现有账户设置默认walletType为magic(如果值为NULL) +UPDATE wallet_accounts SET wallet_type = 'magic' WHERE wallet_type IS NULL OR wallet_type = ''; + diff --git a/frontend/src/components/AccountImportForm.tsx b/frontend/src/components/AccountImportForm.tsx index a541128..b1b0a60 100644 --- a/frontend/src/components/AccountImportForm.tsx +++ b/frontend/src/components/AccountImportForm.tsx @@ -35,7 +35,7 @@ const AccountImportForm: React.FC = ({ const isMobile = useMediaQuery({ maxWidth: 768 }) const { importAccount, loading } = useAccountStore() const [importType, setImportType] = useState('privateKey') - const [walletType, setWalletType] = useState('magic') + const [walletType, setWalletType] = useState('safe') const [derivedAddress, setDerivedAddress] = useState('') const [addressError, setAddressError] = useState('') @@ -211,7 +211,10 @@ const AccountImportForm: React.FC = ({ label={ {t('accountImport.walletType')}{' '} - + @@ -221,12 +224,12 @@ const AccountImportForm: React.FC = ({ value={walletType} onChange={(e) => setWalletType(e.target.value)} > - - {t('accountImport.walletTypeMagic')} - {t('accountImport.walletTypeSafe')} + + {t('accountImport.walletTypeMagic')} + diff --git a/frontend/src/locales/en/common.json b/frontend/src/locales/en/common.json index 2fae513..d2a9af9 100644 --- a/frontend/src/locales/en/common.json +++ b/frontend/src/locales/en/common.json @@ -201,9 +201,9 @@ "addressError": "Cannot derive address from private key", "addressErrorMnemonic": "Cannot derive address from mnemonic", "walletType": "Wallet Type", - "walletTypeHelp": "Magic: Polymarket accounts logged in via email or social accounts (Google, Twitter, etc.); MetaMask: Polymarket accounts connected via browser wallets like MetaMask", + "walletTypeHelp": "Web3 Wallet: Polymarket accounts connected via browser wallets like MetaMask\nMagic: Polymarket accounts logged in via email or social accounts (Google, Twitter, etc.)", "walletTypeMagic": "Magic (Email/Social Login)", - "walletTypeSafe": "MetaMask (Browser Wallet)" + "walletTypeSafe": "Web3 Wallet" }, "leader": { "title": "Leader Management", diff --git a/frontend/src/locales/zh-CN/common.json b/frontend/src/locales/zh-CN/common.json index cb706aa..ca201a1 100644 --- a/frontend/src/locales/zh-CN/common.json +++ b/frontend/src/locales/zh-CN/common.json @@ -201,9 +201,9 @@ "addressError": "无法从私钥推导地址", "addressErrorMnemonic": "无法从助记词推导地址", "walletType": "钱包类型", - "walletTypeHelp": "Magic:通过邮箱或社交账号(如 Google、Twitter)登录的 Polymarket 账户;MetaMask:使用 MetaMask 等浏览器钱包连接的 Polymarket 账户", + "walletTypeHelp": "Web3钱包:使用 MetaMask 等浏览器钱包连接的 Polymarket 账户\nMagic:通过邮箱或社交账号(如 Google、Twitter)登录的 Polymarket 账户", "walletTypeMagic": "Magic(邮箱/社交账号登录)", - "walletTypeSafe": "MetaMask(浏览器钱包)" + "walletTypeSafe": "Web3钱包" }, "leader": { "title": "Leader 管理", diff --git a/frontend/src/locales/zh-TW/common.json b/frontend/src/locales/zh-TW/common.json index 756df0b..5ee567f 100644 --- a/frontend/src/locales/zh-TW/common.json +++ b/frontend/src/locales/zh-TW/common.json @@ -201,9 +201,9 @@ "addressError": "無法從私鑰推導地址", "addressErrorMnemonic": "無法從助記詞推導地址", "walletType": "錢包類型", - "walletTypeHelp": "Magic:透過郵箱或社群帳號(如 Google、Twitter)登入的 Polymarket 帳戶;MetaMask:使用 MetaMask 等瀏覽器錢包連接的 Polymarket 帳戶", + "walletTypeHelp": "Web3錢包:使用 MetaMask 等瀏覽器錢包連接的 Polymarket 帳戶\nMagic:透過郵箱或社群帳號(如 Google、Twitter)登入的 Polymarket 帳戶", "walletTypeMagic": "Magic(郵箱/社群帳號登入)", - "walletTypeSafe": "MetaMask(瀏覽器錢包)" + "walletTypeSafe": "Web3錢包" }, "leader": { "title": "Leader 管理", diff --git a/frontend/src/pages/AccountList.tsx b/frontend/src/pages/AccountList.tsx index 0f34bd2..a1036e0 100644 --- a/frontend/src/pages/AccountList.tsx +++ b/frontend/src/pages/AccountList.tsx @@ -7,6 +7,7 @@ import { useAccountStore } from '../store/accountStore' import type { Account } from '../types' import { useMediaQuery } from 'react-responsive' import { formatUSDC } from '../utils' +import AccountImportForm from '../components/AccountImportForm' const { Title } = Typography @@ -25,11 +26,20 @@ const AccountList: React.FC = () => { const [editAccount, setEditAccount] = useState(null) const [editForm] = Form.useForm() const [editLoading, setEditLoading] = useState(false) + const [accountImportModalVisible, setAccountImportModalVisible] = useState(false) + const [accountImportForm] = Form.useForm() useEffect(() => { fetchAccounts() }, [fetchAccounts]) + const handleAccountImportSuccess = async () => { + message.success(t('accountImport.importSuccess')) + setAccountImportModalVisible(false) + accountImportForm.resetFields() + fetchAccounts() + } + // 加载所有账户的余额 useEffect(() => { const loadBalances = async () => { @@ -494,7 +504,7 @@ const AccountList: React.FC = () => {