fix: 修复 walletType 字段问题和 WebSocket 内存泄漏,优化账户导入 UI
后端修复: - 添加 walletType 字段到 Account 实体和数据库(V17 迁移) - 修复 refreshProxyAddress 方法使用正确的 walletType - 修复 WebSocketTicketService 内存泄漏(添加定时清理任务) 前端优化: - 账户导入改为 Modal 弹窗,提升用户体验 - 默认钱包类型改为 Web3 Wallet(safe),并将其选项排在首位 - 更新多语言文本:将 MetaMask(浏览器钱包)改为 Web3钱包 - 修复 walletTypeHelp Tooltip 换行显示问题
This commit is contained in:
@@ -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 是否已配置
|
||||
|
||||
@@ -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(),
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
+10
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = '';
|
||||
|
||||
@@ -35,7 +35,7 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||
const { importAccount, loading } = useAccountStore()
|
||||
const [importType, setImportType] = useState<ImportType>('privateKey')
|
||||
const [walletType, setWalletType] = useState<WalletType>('magic')
|
||||
const [walletType, setWalletType] = useState<WalletType>('safe')
|
||||
const [derivedAddress, setDerivedAddress] = useState<string>('')
|
||||
const [addressError, setAddressError] = useState<string>('')
|
||||
|
||||
@@ -211,7 +211,10 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
||||
label={
|
||||
<span>
|
||||
{t('accountImport.walletType')}{' '}
|
||||
<Tooltip title={t('accountImport.walletTypeHelp')}>
|
||||
<Tooltip
|
||||
title={t('accountImport.walletTypeHelp')}
|
||||
overlayInnerStyle={{ whiteSpace: 'pre-line', maxWidth: '300px' }}
|
||||
>
|
||||
<QuestionCircleOutlined style={{ color: '#999' }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
@@ -221,12 +224,12 @@ const AccountImportForm: React.FC<AccountImportFormProps> = ({
|
||||
value={walletType}
|
||||
onChange={(e) => setWalletType(e.target.value)}
|
||||
>
|
||||
<Radio value="magic">
|
||||
{t('accountImport.walletTypeMagic')}
|
||||
</Radio>
|
||||
<Radio value="safe">
|
||||
{t('accountImport.walletTypeSafe')}
|
||||
</Radio>
|
||||
<Radio value="magic">
|
||||
{t('accountImport.walletTypeMagic')}
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 管理",
|
||||
|
||||
@@ -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 管理",
|
||||
|
||||
@@ -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<Account | null>(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 = () => {
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => navigate('/accounts/import')}
|
||||
onClick={() => setAccountImportModalVisible(true)}
|
||||
size={isMobile ? 'middle' : 'large'}
|
||||
block={isMobile}
|
||||
style={isMobile ? { minHeight: '44px' } : undefined}
|
||||
@@ -836,6 +846,34 @@ const AccountList: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
{/* 导入账户 Modal */}
|
||||
<Modal
|
||||
title={t('accountImport.title')}
|
||||
open={accountImportModalVisible}
|
||||
onCancel={() => {
|
||||
setAccountImportModalVisible(false)
|
||||
accountImportForm.resetFields()
|
||||
}}
|
||||
footer={null}
|
||||
width={isMobile ? '95%' : 600}
|
||||
style={{ top: isMobile ? 20 : 50 }}
|
||||
bodyStyle={{ padding: '24px', maxHeight: 'calc(100vh - 150px)', overflow: 'auto' }}
|
||||
destroyOnClose
|
||||
maskClosable
|
||||
closable
|
||||
>
|
||||
<AccountImportForm
|
||||
form={accountImportForm}
|
||||
onSuccess={handleAccountImportSuccess}
|
||||
onCancel={() => {
|
||||
setAccountImportModalVisible(false)
|
||||
accountImportForm.resetFields()
|
||||
}}
|
||||
showAlert={true}
|
||||
showCancelButton={true}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface Account {
|
||||
proxyAddress: string // Polymarket 代理钱包地址
|
||||
accountName?: string
|
||||
isEnabled?: boolean // 是否启用
|
||||
walletType?: string // 钱包类型:magic(邮箱/OAuth登录)或 safe(MetaMask浏览器钱包)
|
||||
apiKeyConfigured: boolean
|
||||
apiSecretConfigured: boolean
|
||||
apiPassphraseConfigured: boolean
|
||||
@@ -42,6 +43,7 @@ export interface AccountImportRequest {
|
||||
privateKey: string
|
||||
walletAddress: string
|
||||
accountName?: string
|
||||
walletType?: string // 钱包类型:magic(邮箱/OAuth登录)或 safe(MetaMask浏览器钱包)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user