Files
nofx/main.go
Icyoung 89085173f9 Dev Crypto (#730)
* feat: remove admin mode
* feat: bugfix
* feat(crypto): 添加RSA-OAEP + AES-GCM混合加密服务
- 实现CryptoService加密服务,支持RSA-OAEP-2048 + AES-256-GCM混合加密
- 集成数据库层加密,自动加密存储敏感字段(API密钥、私钥等)
- 支持环境变量DATA_ENCRYPTION_KEY配置数据加密密钥
- 适配SQLite数据库加密存储(从PostgreSQL移植)
- 保持Hyperliquid代理钱包处理兼容性
- 更新.gitignore以正确处理crypto模块代码
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* feat(scripts): 添加加密环境一键设置脚本
- setup_encryption.sh: 一键生成RSA密钥对+数据加密密钥+JWT密钥
- generate_rsa_keys.sh: 专业的RSA-2048密钥对生成工具
- generate_data_key.sh: 生成AES-256数据加密密钥和JWT认证密钥
- ENCRYPTION_README.md: 详细的加密系统说明文档
- 支持自动检测现有密钥并只生成缺失的密钥
- 完善的权限管理和安全验证
- 兼容macOS和Linux的跨平台支持
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* feat(api): 添加加密API端点和Gin框架集成
- 新增CryptoHandler处理加密相关API请求
- 提供/api/crypto/public-key端点获取RSA公钥
- 提供/api/crypto/decrypt端点解密敏感数据
- 适配Gin框架的HTTP处理器格式
- 集成CryptoService到API服务器
- 支持前端加密数据传输和解密
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* feat(web): 添加前端加密服务和两阶段密钥输入组件
- CryptoService: Web Crypto API集成,支持RSA-OAEP加密
- TwoStageKeyModal: 安全的两阶段私钥输入组件,支持剪贴板混淆
- 完善国际化翻译支持加密相关UI文本
- 修复TypeScript类型错误和编译问题
- 支持前端敏感数据加密传输到后端
- 增强用户隐私保护和数据安全
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* feat(auth): 增强JWT认证安全性
- 优先使用环境变量JWT_SECRET而不是数据库配置
- 支持通过.env文件安全配置JWT认证密钥
- 保留数据库配置作为回退机制
- 改进JWT密钥来源日志显示
- 增强系统启动时的安全配置检查
- 支持运行时动态JWT密钥切换
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* feat(docker): 集成加密环境变量到Docker部署
- 添加DATA_ENCRYPTION_KEY环境变量传递到容器
- 添加JWT_SECRET环境变量支持
- 挂载secrets目录使容器可访问RSA密钥文件
- 确保容器内加密服务正常工作
- 解决容器启动失败和加密初始化问题
- 完善Docker Compose加密环境配置
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* feat(start): 集成自动加密环境检测和设置
- 增强check_encryption()函数检测JWT_SECRET和DATA_ENCRYPTION_KEY
- 自动运行setup_encryption.sh当检测到缺失密钥时
- 改进加密状态显示,包含RSA+AES+JWT全套加密信息
- 优化用户体验,提供清晰的加密配置反馈
- 支持一键设置完整加密环境
- 确保容器启动前加密环境就绪
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* feat: format fix
* fix(security): 修复前端模型和交易所配置敏感数据明文传输
- 在handleSaveModelConfig中对API密钥进行RSA-OAEP加密
- 在handleSaveExchangeConfig中对API密钥、Secret密钥和Aster私钥进行加密
- 只有非空敏感数据才进行加密处理
- 添加加密失败错误处理和用户友好提示
- 增加encryptionFailed翻译键的中英文支持
- 使用用户ID和会话ID作为加密上下文增强安全性
这修复了之前敏感数据在网络传输中以明文形式发送的安全漏洞。
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* fix(crypto): 修复后端加密服务集成和缺失的加密端点
- 添加Server结构体缺少的cryptoService字段
- 实现handleUpdateModelConfigsEncrypted处理器用于模型配置加密传输
- 修复handleUpdateExchangeConfigsEncrypted中的函数调用
- 在前端API中添加updateModelConfigsEncrypted方法
- 统一RSA密钥路径从secrets/rsa_key改为keys/rsa_private.key
- 确保前端可以使用加密端点安全传输敏感数据
- 兼容原有加密通信模式和二段输入私钥功能
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: icy <icyoung520@gmail.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-08 02:03:09 +08:00

356 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"encoding/json"
"fmt"
"log"
"nofx/api"
"nofx/auth"
"nofx/config"
"nofx/crypto"
"nofx/manager"
"nofx/market"
"nofx/pool"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"github.com/joho/godotenv"
)
// ConfigFile 配置文件结构,只包含需要同步到数据库的字段
// TODO 现在与config.Config相同未来会被替换 现在为了兼容性不得不保留当前文件
type ConfigFile struct {
BetaMode bool `json:"beta_mode"`
APIServerPort int `json:"api_server_port"`
UseDefaultCoins bool `json:"use_default_coins"`
DefaultCoins []string `json:"default_coins"`
CoinPoolAPIURL string `json:"coin_pool_api_url"`
OITopAPIURL string `json:"oi_top_api_url"`
MaxDailyLoss float64 `json:"max_daily_loss"`
MaxDrawdown float64 `json:"max_drawdown"`
StopTradingMinutes int `json:"stop_trading_minutes"`
Leverage config.LeverageConfig `json:"leverage"`
JWTSecret string `json:"jwt_secret"`
DataKLineTime string `json:"data_k_line_time"`
Log *config.LogConfig `json:"log"` // 日志配置
}
// loadConfigFile 读取并解析config.json文件
func loadConfigFile() (*ConfigFile, error) {
// 检查config.json是否存在
if _, err := os.Stat("config.json"); os.IsNotExist(err) {
log.Printf("📄 config.json不存在使用默认配置")
return &ConfigFile{}, nil
}
// 读取config.json
data, err := os.ReadFile("config.json")
if err != nil {
return nil, fmt.Errorf("读取config.json失败: %w", err)
}
// 解析JSON
var configFile ConfigFile
if err := json.Unmarshal(data, &configFile); err != nil {
return nil, fmt.Errorf("解析config.json失败: %w", err)
}
return &configFile, nil
}
// syncConfigToDatabase 将配置同步到数据库
func syncConfigToDatabase(database *config.Database, configFile *ConfigFile) error {
if configFile == nil {
return nil
}
log.Printf("🔄 开始同步config.json到数据库...")
// 同步各配置项到数据库
configs := map[string]string{
"beta_mode": fmt.Sprintf("%t", configFile.BetaMode),
"api_server_port": strconv.Itoa(configFile.APIServerPort),
"use_default_coins": fmt.Sprintf("%t", configFile.UseDefaultCoins),
"coin_pool_api_url": configFile.CoinPoolAPIURL,
"oi_top_api_url": configFile.OITopAPIURL,
"max_daily_loss": fmt.Sprintf("%.1f", configFile.MaxDailyLoss),
"max_drawdown": fmt.Sprintf("%.1f", configFile.MaxDrawdown),
"stop_trading_minutes": strconv.Itoa(configFile.StopTradingMinutes),
}
// 同步default_coins转换为JSON字符串存储
if len(configFile.DefaultCoins) > 0 {
defaultCoinsJSON, err := json.Marshal(configFile.DefaultCoins)
if err == nil {
configs["default_coins"] = string(defaultCoinsJSON)
}
}
// 同步杠杆配置
if configFile.Leverage.BTCETHLeverage > 0 {
configs["btc_eth_leverage"] = strconv.Itoa(configFile.Leverage.BTCETHLeverage)
}
if configFile.Leverage.AltcoinLeverage > 0 {
configs["altcoin_leverage"] = strconv.Itoa(configFile.Leverage.AltcoinLeverage)
}
// 如果JWT密钥不为空也同步
if configFile.JWTSecret != "" {
configs["jwt_secret"] = configFile.JWTSecret
}
// 更新数据库配置
for key, value := range configs {
if err := database.SetSystemConfig(key, value); err != nil {
log.Printf("⚠️ 更新配置 %s 失败: %v", key, err)
} else {
log.Printf("✓ 同步配置: %s = %s", key, value)
}
}
log.Printf("✅ config.json同步完成")
return nil
}
// loadBetaCodesToDatabase 加载内测码文件到数据库
func loadBetaCodesToDatabase(database *config.Database) error {
betaCodeFile := "beta_codes.txt"
// 检查内测码文件是否存在
if _, err := os.Stat(betaCodeFile); os.IsNotExist(err) {
log.Printf("📄 内测码文件 %s 不存在,跳过加载", betaCodeFile)
return nil
}
// 获取文件信息
fileInfo, err := os.Stat(betaCodeFile)
if err != nil {
return fmt.Errorf("获取内测码文件信息失败: %w", err)
}
log.Printf("🔄 发现内测码文件 %s (%.1f KB),开始加载...", betaCodeFile, float64(fileInfo.Size())/1024)
// 加载内测码到数据库
err = database.LoadBetaCodesFromFile(betaCodeFile)
if err != nil {
return fmt.Errorf("加载内测码失败: %w", err)
}
// 显示统计信息
total, used, err := database.GetBetaCodeStats()
if err != nil {
log.Printf("⚠️ 获取内测码统计失败: %v", err)
} else {
log.Printf("✅ 内测码加载完成: 总计 %d 个,已使用 %d 个,剩余 %d 个", total, used, total-used)
}
return nil
}
func main() {
fmt.Println("╔════════════════════════════════════════════════════════════╗")
fmt.Println("║ 🤖 AI多模型交易系统 - 支持 DeepSeek & Qwen ║")
fmt.Println("╚════════════════════════════════════════════════════════════╝")
fmt.Println()
// Load environment variables from .env file if present (for local/dev runs)
// In Docker Compose, variables are injected by the runtime and this is harmless.
_ = godotenv.Load()
// 初始化数据库配置
dbPath := "config.db"
if len(os.Args) > 1 {
dbPath = os.Args[1]
}
// 读取配置文件
configFile, err := loadConfigFile()
if err != nil {
log.Fatalf("❌ 读取config.json失败: %v", err)
}
log.Printf("📋 初始化配置数据库: %s", dbPath)
database, err := config.NewDatabase(dbPath)
if err != nil {
log.Fatalf("❌ 初始化数据库失败: %v", err)
}
defer database.Close()
// 初始化加密服务
log.Printf("🔐 初始化加密服务...")
cryptoService, err := crypto.NewCryptoService("secrets/rsa_key")
if err != nil {
log.Fatalf("❌ 初始化加密服务失败: %v", err)
}
database.SetCryptoService(cryptoService)
log.Printf("✅ 加密服务初始化成功")
// 同步config.json到数据库
if err := syncConfigToDatabase(database, configFile); err != nil {
log.Printf("⚠️ 同步config.json到数据库失败: %v", err)
}
// 加载内测码到数据库
if err := loadBetaCodesToDatabase(database); err != nil {
log.Printf("⚠️ 加载内测码到数据库失败: %v", err)
}
// 获取系统配置
useDefaultCoinsStr, _ := database.GetSystemConfig("use_default_coins")
useDefaultCoins := useDefaultCoinsStr == "true"
apiPortStr, _ := database.GetSystemConfig("api_server_port")
// 设置JWT密钥优先使用环境变量
jwtSecret := strings.TrimSpace(os.Getenv("JWT_SECRET"))
if jwtSecret == "" {
// 回退到数据库配置
jwtSecret, _ = database.GetSystemConfig("jwt_secret")
if jwtSecret == "" {
jwtSecret = "your-jwt-secret-key-change-in-production-make-it-long-and-random"
log.Printf("⚠️ 使用默认JWT密钥建议使用加密设置脚本生成安全密钥")
} else {
log.Printf("🔑 使用数据库中JWT密钥")
}
} else {
log.Printf("🔑 使用环境变量JWT密钥")
}
auth.SetJWTSecret(jwtSecret)
// 管理员模式下需要管理员密码,缺失则退出
log.Printf("✓ 配置数据库初始化成功")
fmt.Println()
// 从数据库读取默认主流币种列表
defaultCoinsJSON, _ := database.GetSystemConfig("default_coins")
var defaultCoins []string
if defaultCoinsJSON != "" {
// 尝试从JSON解析
if err := json.Unmarshal([]byte(defaultCoinsJSON), &defaultCoins); err != nil {
log.Printf("⚠️ 解析default_coins配置失败: %v使用硬编码默认值", err)
defaultCoins = []string{"BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT", "DOGEUSDT", "ADAUSDT", "HYPEUSDT"}
} else {
log.Printf("✓ 从数据库加载默认币种列表(共%d个: %v", len(defaultCoins), defaultCoins)
}
} else {
// 如果数据库中没有配置,使用硬编码默认值
defaultCoins = []string{"BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT", "DOGEUSDT", "ADAUSDT", "HYPEUSDT"}
log.Printf("⚠️ 数据库中未配置default_coins使用硬编码默认值")
}
pool.SetDefaultCoins(defaultCoins)
// 设置是否使用默认主流币种
pool.SetUseDefaultCoins(useDefaultCoins)
if useDefaultCoins {
log.Printf("✓ 已启用默认主流币种列表")
}
// 设置币种池API URL
coinPoolAPIURL, _ := database.GetSystemConfig("coin_pool_api_url")
if coinPoolAPIURL != "" {
pool.SetCoinPoolAPI(coinPoolAPIURL)
log.Printf("✓ 已配置AI500币种池API")
}
oiTopAPIURL, _ := database.GetSystemConfig("oi_top_api_url")
if oiTopAPIURL != "" {
pool.SetOITopAPI(oiTopAPIURL)
log.Printf("✓ 已配置OI Top API")
}
// 创建TraderManager
traderManager := manager.NewTraderManager()
// 从数据库加载所有交易员到内存
err = traderManager.LoadTradersFromDatabase(database)
if err != nil {
log.Fatalf("❌ 加载交易员失败: %v", err)
}
// 获取数据库中的所有交易员配置用于显示使用default用户
traders, err := database.GetTraders("default")
if err != nil {
log.Fatalf("❌ 获取交易员列表失败: %v", err)
}
// 显示加载的交易员信息
fmt.Println()
fmt.Println("🤖 数据库中的AI交易员配置:")
if len(traders) == 0 {
fmt.Println(" • 暂无配置的交易员请通过Web界面创建")
} else {
for _, trader := range traders {
status := "停止"
if trader.IsRunning {
status = "运行中"
}
fmt.Printf(" • %s (%s + %s) - 初始资金: %.0f USDT [%s]\n",
trader.Name, strings.ToUpper(trader.AIModelID), strings.ToUpper(trader.ExchangeID),
trader.InitialBalance, status)
}
}
// 创建初始化上下文
// TODO : 传入实际配置, 现在并未实际使用,未来所有模块初始化都将通过上下文传递配置
// ctx := bootstrap.NewContext(&config.Config{})
// // 执行所有初始化钩子
// if err := bootstrap.Run(ctx); err != nil {
// log.Fatalf("初始化失败: %v", err)
// }
fmt.Println()
fmt.Println("🤖 AI全权决策模式:")
fmt.Printf(" • AI将自主决定每笔交易的杠杆倍数山寨币最高5倍BTC/ETH最高5倍\n")
fmt.Println(" • AI将自主决定每笔交易的仓位大小")
fmt.Println(" • AI将自主设置止损和止盈价格")
fmt.Println(" • AI将基于市场数据、技术指标、账户状态做出全面分析")
fmt.Println()
fmt.Println("⚠️ 风险提示: AI自动交易有风险建议小额资金测试")
fmt.Println()
fmt.Println("按 Ctrl+C 停止运行")
fmt.Println(strings.Repeat("=", 60))
fmt.Println()
// 获取API服务器端口
apiPort := 8080 // 默认端口
if apiPortStr != "" {
if port, err := strconv.Atoi(apiPortStr); err == nil {
apiPort = port
}
}
// 创建并启动API服务器
apiServer := api.NewServer(traderManager, database, cryptoService, apiPort)
go func() {
if err := apiServer.Start(); err != nil {
log.Printf("❌ API服务器错误: %v", err)
}
}()
// 启动流行情数据 - 默认使用所有交易员设置的币种 如果没有设置币种 则优先使用系统默认
go market.NewWSMonitor(150).Start(database.GetCustomCoins())
//go market.NewWSMonitor(150).Start([]string{}) //这里是一个使用方式 传入空的话 则使用market市场的所有币种
// 设置优雅退出
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
// TODO: 启动数据库中配置为运行状态的交易员
// traderManager.StartAll()
// 等待退出信号
<-sigChan
fmt.Println()
fmt.Println()
log.Println("📛 收到退出信号正在停止所有trader...")
traderManager.StopAll()
fmt.Println()
fmt.Println("👋 感谢使用AI交易系统")
}