mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-14 08:16:56 +08:00
feat: implement hybrid database architecture and frontend encryption
- Add PostgreSQL + SQLite hybrid database support with automatic switching - Implement frontend AES-GCM + RSA-OAEP encryption for sensitive data - Add comprehensive DatabaseInterface with all required methods - Fix compilation issues with interface consistency - Update all database method signatures to use DatabaseInterface - Add missing UpdateTraderInitialBalance method to PostgreSQL implementation - Integrate RSA public key distribution via /api/config endpoint - Add frontend crypto service with proper error handling - Support graceful degradation between encrypted and plaintext transmission - Add directory creation for RSA keys and PEM parsing fixes - Test both SQLite and PostgreSQL modes successfully 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: tinkle-community <tinklefund@gmail.com>
This commit is contained in:
@@ -23,9 +23,9 @@ type CompetitionCache struct {
|
||||
|
||||
// TraderManager 管理多个trader实例
|
||||
type TraderManager struct {
|
||||
traders map[string]*trader.AutoTrader // key: trader ID
|
||||
traders map[string]*trader.AutoTrader // key: trader ID
|
||||
competitionCache *CompetitionCache
|
||||
mu sync.RWMutex
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewTraderManager 创建trader管理器
|
||||
@@ -170,7 +170,7 @@ func (tm *TraderManager) LoadTradersFromDatabase(database config.DatabaseInterfa
|
||||
}
|
||||
|
||||
// 添加到TraderManager
|
||||
err = tm.addTraderFromDB(traderCfg, aiModelCfg, exchangeCfg, coinPoolURL, oiTopURL, maxDailyLoss, maxDrawdown, stopTradingMinutes, defaultCoins)
|
||||
err = tm.addTraderFromDB(traderCfg, aiModelCfg, exchangeCfg, coinPoolURL, oiTopURL, maxDailyLoss, maxDrawdown, stopTradingMinutes, defaultCoins, database, traderCfg.UserID)
|
||||
if err != nil {
|
||||
log.Printf("❌ 添加交易员 %s 失败: %v", traderCfg.Name, err)
|
||||
continue
|
||||
@@ -182,7 +182,7 @@ func (tm *TraderManager) LoadTradersFromDatabase(database config.DatabaseInterfa
|
||||
}
|
||||
|
||||
// addTraderFromConfig 内部方法:从配置添加交易员(不加锁,因为调用方已加锁)
|
||||
func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModelCfg *config.AIModelConfig, exchangeCfg *config.ExchangeConfig, coinPoolURL, oiTopURL string, maxDailyLoss, maxDrawdown float64, stopTradingMinutes int, defaultCoins []string) error {
|
||||
func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModelCfg *config.AIModelConfig, exchangeCfg *config.ExchangeConfig, coinPoolURL, oiTopURL string, maxDailyLoss, maxDrawdown float64, stopTradingMinutes int, defaultCoins []string, database config.DatabaseInterface, userID string) error {
|
||||
if _, exists := tm.traders[traderCfg.ID]; exists {
|
||||
return fmt.Errorf("trader ID '%s' 已存在", traderCfg.ID)
|
||||
}
|
||||
@@ -262,7 +262,7 @@ func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModel
|
||||
}
|
||||
|
||||
// 创建trader实例
|
||||
at, err := trader.NewAutoTrader(traderConfig)
|
||||
at, err := trader.NewAutoTrader(traderConfig, database, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建trader失败: %w", err)
|
||||
}
|
||||
@@ -286,7 +286,7 @@ func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModel
|
||||
// AddTrader 从数据库配置添加trader (移除旧版兼容性)
|
||||
|
||||
// AddTraderFromDB 从数据库配置添加trader
|
||||
func (tm *TraderManager) AddTraderFromDB(traderCfg *config.TraderRecord, aiModelCfg *config.AIModelConfig, exchangeCfg *config.ExchangeConfig, coinPoolURL, oiTopURL string, maxDailyLoss, maxDrawdown float64, stopTradingMinutes int, defaultCoins []string) error {
|
||||
func (tm *TraderManager) AddTraderFromDB(traderCfg *config.TraderRecord, aiModelCfg *config.AIModelConfig, exchangeCfg *config.ExchangeConfig, coinPoolURL, oiTopURL string, maxDailyLoss, maxDrawdown float64, stopTradingMinutes int, defaultCoins []string, database *config.Database, userID string) error {
|
||||
tm.mu.Lock()
|
||||
defer tm.mu.Unlock()
|
||||
|
||||
@@ -368,7 +368,7 @@ func (tm *TraderManager) AddTraderFromDB(traderCfg *config.TraderRecord, aiModel
|
||||
}
|
||||
|
||||
// 创建trader实例
|
||||
at, err := trader.NewAutoTrader(traderConfig)
|
||||
at, err := trader.NewAutoTrader(traderConfig, database, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建trader失败: %w", err)
|
||||
}
|
||||
@@ -506,19 +506,19 @@ func (tm *TraderManager) GetCompetitionData() (map[string]interface{}, error) {
|
||||
tm.competitionCache.mu.RUnlock()
|
||||
|
||||
tm.mu.RLock()
|
||||
|
||||
|
||||
// 获取所有交易员列表
|
||||
allTraders := make([]*trader.AutoTrader, 0, len(tm.traders))
|
||||
for _, t := range tm.traders {
|
||||
allTraders = append(allTraders, t)
|
||||
}
|
||||
tm.mu.RUnlock()
|
||||
|
||||
|
||||
log.Printf("🔄 重新获取竞赛数据,交易员数量: %d", len(allTraders))
|
||||
|
||||
|
||||
// 并发获取交易员数据
|
||||
traders := tm.getConcurrentTraderData(allTraders)
|
||||
|
||||
|
||||
// 按收益率排序(降序)
|
||||
sort.Slice(traders, func(i, j int) bool {
|
||||
pnlPctI, okI := traders[i]["total_pnl_pct"].(float64)
|
||||
@@ -531,14 +531,14 @@ func (tm *TraderManager) GetCompetitionData() (map[string]interface{}, error) {
|
||||
}
|
||||
return pnlPctI > pnlPctJ
|
||||
})
|
||||
|
||||
|
||||
// 限制返回前50名
|
||||
totalCount := len(traders)
|
||||
limit := 50
|
||||
if len(traders) > limit {
|
||||
traders = traders[:limit]
|
||||
}
|
||||
|
||||
|
||||
comparison := make(map[string]interface{})
|
||||
comparison["traders"] = traders
|
||||
comparison["count"] = len(traders)
|
||||
@@ -559,21 +559,21 @@ func (tm *TraderManager) getConcurrentTraderData(traders []*trader.AutoTrader) [
|
||||
index int
|
||||
data map[string]interface{}
|
||||
}
|
||||
|
||||
|
||||
// 创建结果通道
|
||||
resultChan := make(chan traderResult, len(traders))
|
||||
|
||||
|
||||
// 并发获取每个交易员的数据
|
||||
for i, t := range traders {
|
||||
go func(index int, trader *trader.AutoTrader) {
|
||||
// 设置单个交易员的超时时间为3秒
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
|
||||
// 使用通道来实现超时控制
|
||||
accountChan := make(chan map[string]interface{}, 1)
|
||||
errorChan := make(chan error, 1)
|
||||
|
||||
|
||||
go func() {
|
||||
account, err := trader.GetAccountInfo()
|
||||
if err != nil {
|
||||
@@ -582,10 +582,10 @@ func (tm *TraderManager) getConcurrentTraderData(traders []*trader.AutoTrader) [
|
||||
accountChan <- account
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
status := trader.GetStatus()
|
||||
var traderData map[string]interface{}
|
||||
|
||||
|
||||
select {
|
||||
case account := <-accountChan:
|
||||
// 成功获取账户信息
|
||||
@@ -634,18 +634,18 @@ func (tm *TraderManager) getConcurrentTraderData(traders []*trader.AutoTrader) [
|
||||
"error": "获取超时",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
resultChan <- traderResult{index: index, data: traderData}
|
||||
}(i, t)
|
||||
}
|
||||
|
||||
|
||||
// 收集所有结果
|
||||
results := make([]map[string]interface{}, len(traders))
|
||||
for i := 0; i < len(traders); i++ {
|
||||
result := <-resultChan
|
||||
results[result.index] = result.data
|
||||
}
|
||||
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
@@ -656,20 +656,20 @@ func (tm *TraderManager) GetTopTradersData() (map[string]interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
// 从竞赛数据中提取前5名
|
||||
allTraders, ok := competitionData["traders"].([]map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("竞赛数据格式错误")
|
||||
}
|
||||
|
||||
|
||||
// 限制返回前5名
|
||||
limit := 5
|
||||
topTraders := allTraders
|
||||
if len(allTraders) > limit {
|
||||
topTraders = allTraders[:limit]
|
||||
}
|
||||
|
||||
|
||||
result := map[string]interface{}{
|
||||
"traders": topTraders,
|
||||
"count": len(topTraders),
|
||||
@@ -832,7 +832,7 @@ func (tm *TraderManager) LoadUserTraders(database config.DatabaseInterface, user
|
||||
}
|
||||
|
||||
// 使用现有的方法加载交易员
|
||||
err = tm.loadSingleTrader(traderCfg, aiModelCfg, exchangeCfg, coinPoolURL, oiTopURL, maxDailyLoss, maxDrawdown, stopTradingMinutes, defaultCoins)
|
||||
err = tm.loadSingleTrader(traderCfg, aiModelCfg, exchangeCfg, coinPoolURL, oiTopURL, maxDailyLoss, maxDrawdown, stopTradingMinutes, defaultCoins, database, userID)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 加载交易员 %s 失败: %v", traderCfg.Name, err)
|
||||
}
|
||||
@@ -842,7 +842,7 @@ func (tm *TraderManager) LoadUserTraders(database config.DatabaseInterface, user
|
||||
}
|
||||
|
||||
// loadSingleTrader 加载单个交易员(从现有代码提取的公共逻辑)
|
||||
func (tm *TraderManager) loadSingleTrader(traderCfg *config.TraderRecord, aiModelCfg *config.AIModelConfig, exchangeCfg *config.ExchangeConfig, coinPoolURL, oiTopURL string, maxDailyLoss, maxDrawdown float64, stopTradingMinutes int, defaultCoins []string) error {
|
||||
func (tm *TraderManager) loadSingleTrader(traderCfg *config.TraderRecord, aiModelCfg *config.AIModelConfig, exchangeCfg *config.ExchangeConfig, coinPoolURL, oiTopURL string, maxDailyLoss, maxDrawdown float64, stopTradingMinutes int, defaultCoins []string, database config.DatabaseInterface, userID string) error {
|
||||
// 处理交易币种列表
|
||||
var tradingCoins []string
|
||||
if traderCfg.TradingSymbols != "" {
|
||||
@@ -889,6 +889,7 @@ func (tm *TraderManager) loadSingleTrader(traderCfg *config.TraderRecord, aiMode
|
||||
DefaultCoins: defaultCoins,
|
||||
TradingCoins: tradingCoins,
|
||||
SystemPromptTemplate: traderCfg.SystemPromptTemplate, // 系统提示词模板
|
||||
HyperliquidTestnet: exchangeCfg.Testnet, // Hyperliquid测试网
|
||||
}
|
||||
|
||||
// 根据交易所类型设置API密钥
|
||||
@@ -912,7 +913,7 @@ func (tm *TraderManager) loadSingleTrader(traderCfg *config.TraderRecord, aiMode
|
||||
}
|
||||
|
||||
// 创建trader实例
|
||||
at, err := trader.NewAutoTrader(traderConfig)
|
||||
at, err := trader.NewAutoTrader(traderConfig, database, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建trader失败: %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user