diff --git a/api/server.go b/api/server.go
index b068d5aa..a10a39f6 100644
--- a/api/server.go
+++ b/api/server.go
@@ -180,7 +180,6 @@ func (s *Server) handleGetSystemConfig(c *gin.Context) {
betaMode := betaModeStr == "true"
c.JSON(http.StatusOK, gin.H{
- "admin_mode": auth.IsAdminMode(),
"beta_mode": betaMode,
"default_coins": defaultCoins,
"btc_eth_leverage": btcEthLeverage,
@@ -1459,14 +1458,6 @@ func (s *Server) handlePerformance(c *gin.Context) {
// authMiddleware JWT认证中间件
func (s *Server) authMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
- // 如果是管理员模式,直接使用admin用户
- if auth.IsAdminMode() {
- c.Set("user_id", "admin")
- c.Set("email", "admin@localhost")
- c.Next()
- return
- }
-
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "缺少Authorization头"})
diff --git a/auth/auth.go b/auth/auth.go
index 89c58e5c..fb1ff822 100644
--- a/auth/auth.go
+++ b/auth/auth.go
@@ -14,9 +14,6 @@ import (
// JWTSecret JWT密钥,将从配置中动态设置
var JWTSecret []byte
-// AdminMode 管理员模式标志
-var AdminMode bool = false
-
// OTPIssuer OTP发行者名称
const OTPIssuer = "nofxAI"
@@ -25,16 +22,6 @@ func SetJWTSecret(secret string) {
JWTSecret = []byte(secret)
}
-// SetAdminMode 设置管理员模式
-func SetAdminMode(enabled bool) {
- AdminMode = enabled
-}
-
-// IsAdminMode 检查是否为管理员模式
-func IsAdminMode() bool {
- return AdminMode
-}
-
// Claims JWT声明
type Claims struct {
UserID string `json:"user_id"`
diff --git a/config.json.example b/config.json.example
index 820f39a7..6a169ff4 100644
--- a/config.json.example
+++ b/config.json.example
@@ -1,5 +1,4 @@
{
- "admin_mode": true,
"beta_mode": false,
"leverage": {
"btc_eth_leverage": 5,
@@ -24,4 +23,4 @@
"log": {
"level": "info"
}
-}
\ No newline at end of file
+}
diff --git a/config/database.go b/config/database.go
index f5b6cc8c..a1f8da45 100644
--- a/config/database.go
+++ b/config/database.go
@@ -1,31 +1,13 @@
package config
import (
- "crypto/rand"
- "database/sql"
- "encoding/base32"
- "encoding/json"
"fmt"
- "log"
- "nofx/market"
- "os"
- "slices"
- "strings"
"time"
-
- _ "github.com/lib/pq"
- _ "github.com/mattn/go-sqlite3"
)
-// Database 配置数据库
-type Database struct {
- db *sql.DB
-}
-
-// DatabaseInterface 数据库接口
+// DatabaseInterface 定义了数据库实现需要提供的方法集合
type DatabaseInterface interface {
CreateUser(user *User) error
- EnsureAdminUser() error
GetUserByEmail(email string) (*User, error)
GetUserByID(userID string) (*User, error)
GetAllUsers() ([]string, error)
@@ -57,362 +39,6 @@ type DatabaseInterface interface {
Close() error
}
-// NewDatabase 创建配置数据库
-func NewDatabase(dbPath string) (DatabaseInterface, error) {
- // 检查是否启用PostgreSQL
- if os.Getenv("POSTGRES_HOST") != "" {
- // 使用PostgreSQL
- pgDB, err := NewPostgreSQLDatabase()
- if err != nil {
- return nil, fmt.Errorf("创建PostgreSQL数据库失败: %w", err)
- }
- return pgDB, nil
- }
-
- // 使用SQLite(兼容模式)
- db, err := sql.Open("sqlite3", dbPath)
- if err != nil {
- return nil, fmt.Errorf("打开数据库失败: %w", err)
- }
-
- database := &Database{db: db}
- if err := database.createTables(); err != nil {
- return nil, fmt.Errorf("创建表失败: %w", err)
- }
-
- if err := database.initDefaultData(); err != nil {
- return nil, fmt.Errorf("初始化默认数据失败: %w", err)
- }
-
- return database, nil
-}
-
-// createTables 创建数据库表
-func (d *Database) createTables() error {
- queries := []string{
- // AI模型配置表
- `CREATE TABLE IF NOT EXISTS ai_models (
- id TEXT PRIMARY KEY,
- user_id TEXT NOT NULL DEFAULT 'default',
- name TEXT NOT NULL,
- provider TEXT NOT NULL,
- enabled BOOLEAN DEFAULT 0,
- api_key TEXT DEFAULT '',
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
- )`,
-
- // 交易所配置表
- `CREATE TABLE IF NOT EXISTS exchanges (
- id TEXT PRIMARY KEY,
- user_id TEXT NOT NULL DEFAULT 'default',
- name TEXT NOT NULL,
- type TEXT NOT NULL, -- 'cex' or 'dex'
- enabled BOOLEAN DEFAULT 0,
- api_key TEXT DEFAULT '',
- secret_key TEXT DEFAULT '',
- testnet BOOLEAN DEFAULT 0,
- -- Hyperliquid 特定字段
- hyperliquid_wallet_addr TEXT DEFAULT '',
- -- Aster 特定字段
- aster_user TEXT DEFAULT '',
- aster_signer TEXT DEFAULT '',
- aster_private_key TEXT DEFAULT '',
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
- )`,
-
- // 用户信号源配置表
- `CREATE TABLE IF NOT EXISTS user_signal_sources (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- user_id TEXT NOT NULL,
- coin_pool_url TEXT DEFAULT '',
- oi_top_url TEXT DEFAULT '',
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
- UNIQUE(user_id)
- )`,
-
- // 交易员配置表
- `CREATE TABLE IF NOT EXISTS traders (
- id TEXT PRIMARY KEY,
- user_id TEXT NOT NULL DEFAULT 'default',
- name TEXT NOT NULL,
- ai_model_id TEXT NOT NULL,
- exchange_id TEXT NOT NULL,
- initial_balance REAL NOT NULL,
- scan_interval_minutes INTEGER DEFAULT 3,
- is_running BOOLEAN DEFAULT 0,
- btc_eth_leverage INTEGER DEFAULT 5,
- altcoin_leverage INTEGER DEFAULT 5,
- trading_symbols TEXT DEFAULT '',
- use_coin_pool BOOLEAN DEFAULT 0,
- use_oi_top BOOLEAN DEFAULT 0,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
- FOREIGN KEY (ai_model_id) REFERENCES ai_models(id),
- FOREIGN KEY (exchange_id) REFERENCES exchanges(id)
- )`,
-
- // 用户表
- `CREATE TABLE IF NOT EXISTS users (
- id TEXT PRIMARY KEY,
- email TEXT UNIQUE NOT NULL,
- password_hash TEXT NOT NULL,
- otp_secret TEXT,
- otp_verified BOOLEAN DEFAULT 0,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
- )`,
-
- // 系统配置表
- `CREATE TABLE IF NOT EXISTS system_config (
- key TEXT PRIMARY KEY,
- value TEXT NOT NULL,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
- )`,
-
- // 内测码表
- `CREATE TABLE IF NOT EXISTS beta_codes (
- code TEXT PRIMARY KEY,
- used BOOLEAN DEFAULT 0,
- used_by TEXT DEFAULT '',
- used_at DATETIME DEFAULT NULL,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
- )`,
-
- // 触发器:自动更新 updated_at
- `CREATE TRIGGER IF NOT EXISTS update_users_updated_at
- AFTER UPDATE ON users
- BEGIN
- UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
- END`,
-
- `CREATE TRIGGER IF NOT EXISTS update_ai_models_updated_at
- AFTER UPDATE ON ai_models
- BEGIN
- UPDATE ai_models SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
- END`,
-
- `CREATE TRIGGER IF NOT EXISTS update_exchanges_updated_at
- AFTER UPDATE ON exchanges
- BEGIN
- UPDATE exchanges SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
- END`,
-
- `CREATE TRIGGER IF NOT EXISTS update_traders_updated_at
- AFTER UPDATE ON traders
- BEGIN
- UPDATE traders SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
- END`,
-
- `CREATE TRIGGER IF NOT EXISTS update_user_signal_sources_updated_at
- AFTER UPDATE ON user_signal_sources
- BEGIN
- UPDATE user_signal_sources SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
- END`,
-
- `CREATE TRIGGER IF NOT EXISTS update_system_config_updated_at
- AFTER UPDATE ON system_config
- BEGIN
- UPDATE system_config SET updated_at = CURRENT_TIMESTAMP WHERE key = NEW.key;
- END`,
- }
-
- for _, query := range queries {
- if _, err := d.db.Exec(query); err != nil {
- return fmt.Errorf("执行SQL失败 [%s]: %w", query, err)
- }
- }
-
- // 为现有数据库添加新字段(向后兼容)
- alterQueries := []string{
- `ALTER TABLE exchanges ADD COLUMN hyperliquid_wallet_addr TEXT DEFAULT ''`,
- `ALTER TABLE exchanges ADD COLUMN aster_user TEXT DEFAULT ''`,
- `ALTER TABLE exchanges ADD COLUMN aster_signer TEXT DEFAULT ''`,
- `ALTER TABLE exchanges ADD COLUMN aster_private_key TEXT DEFAULT ''`,
- `ALTER TABLE traders ADD COLUMN custom_prompt TEXT DEFAULT ''`,
- `ALTER TABLE traders ADD COLUMN override_base_prompt BOOLEAN DEFAULT 0`,
- `ALTER TABLE traders ADD COLUMN is_cross_margin BOOLEAN DEFAULT 1`, // 默认为全仓模式
- `ALTER TABLE traders ADD COLUMN use_default_coins BOOLEAN DEFAULT 1`, // 默认使用默认币种
- `ALTER TABLE traders ADD COLUMN custom_coins TEXT DEFAULT ''`, // 自定义币种列表(JSON格式)
- `ALTER TABLE traders ADD COLUMN btc_eth_leverage INTEGER DEFAULT 5`, // BTC/ETH杠杆倍数
- `ALTER TABLE traders ADD COLUMN altcoin_leverage INTEGER DEFAULT 5`, // 山寨币杠杆倍数
- `ALTER TABLE traders ADD COLUMN trading_symbols TEXT DEFAULT ''`, // 交易币种,逗号分隔
- `ALTER TABLE traders ADD COLUMN use_coin_pool BOOLEAN DEFAULT 0`, // 是否使用COIN POOL信号源
- `ALTER TABLE traders ADD COLUMN use_oi_top BOOLEAN DEFAULT 0`, // 是否使用OI TOP信号源
- `ALTER TABLE traders ADD COLUMN system_prompt_template TEXT DEFAULT 'default'`, // 系统提示词模板名称
- `ALTER TABLE ai_models ADD COLUMN custom_api_url TEXT DEFAULT ''`, // 自定义API地址
- `ALTER TABLE ai_models ADD COLUMN custom_model_name TEXT DEFAULT ''`, // 自定义模型名称
- }
-
- for _, query := range alterQueries {
- // 忽略已存在字段的错误
- d.db.Exec(query)
- }
-
- // 检查是否需要迁移exchanges表的主键结构
- err := d.migrateExchangesTable()
- if err != nil {
- log.Printf("⚠️ 迁移exchanges表失败: %v", err)
- }
-
- return nil
-}
-
-// initDefaultData 初始化默认数据
-func (d *Database) initDefaultData() error {
- // 初始化AI模型(使用default用户)
- aiModels := []struct {
- id, name, provider string
- }{
- {"deepseek", "DeepSeek", "deepseek"},
- {"qwen", "Qwen", "qwen"},
- }
-
- for _, model := range aiModels {
- _, err := d.db.Exec(`
- INSERT OR IGNORE INTO ai_models (id, user_id, name, provider, enabled)
- VALUES (?, 'default', ?, ?, 0)
- `, model.id, model.name, model.provider)
- if err != nil {
- return fmt.Errorf("初始化AI模型失败: %w", err)
- }
- }
-
- // 初始化交易所(使用default用户)
- exchanges := []struct {
- id, name, typ string
- }{
- {"binance", "Binance Futures", "binance"},
- {"hyperliquid", "Hyperliquid", "hyperliquid"},
- {"aster", "Aster DEX", "aster"},
- }
-
- for _, exchange := range exchanges {
- _, err := d.db.Exec(`
- INSERT OR IGNORE INTO exchanges (id, user_id, name, type, enabled)
- VALUES (?, 'default', ?, ?, 0)
- `, exchange.id, exchange.name, exchange.typ)
- if err != nil {
- return fmt.Errorf("初始化交易所失败: %w", err)
- }
- }
-
- // 初始化系统配置 - 创建所有字段,设置默认值,后续由config.json同步更新
- systemConfigs := map[string]string{
- "admin_mode": "true", // 默认开启管理员模式,便于首次使用
- "beta_mode": "false", // 默认关闭内测模式
- "api_server_port": "8080", // 默认API端口
- "use_default_coins": "true", // 默认使用内置币种列表
- "default_coins": `["BTCUSDT","ETHUSDT","SOLUSDT","BNBUSDT","XRPUSDT","DOGEUSDT","ADAUSDT","HYPEUSDT"]`, // 默认币种列表(JSON格式)
- "max_daily_loss": "10.0", // 最大日损失百分比
- "max_drawdown": "20.0", // 最大回撤百分比
- "stop_trading_minutes": "60", // 停止交易时间(分钟)
- "btc_eth_leverage": "5", // BTC/ETH杠杆倍数
- "altcoin_leverage": "5", // 山寨币杠杆倍数
- "jwt_secret": "", // JWT密钥,默认为空,由config.json或系统生成
- }
-
- for key, value := range systemConfigs {
- _, err := d.db.Exec(`
- INSERT OR IGNORE INTO system_config (key, value)
- VALUES (?, ?)
- `, key, value)
- if err != nil {
- return fmt.Errorf("初始化系统配置失败: %w", err)
- }
- }
-
- return nil
-}
-
-// migrateExchangesTable 迁移exchanges表支持多用户
-func (d *Database) migrateExchangesTable() error {
- // 检查是否已经迁移过
- var count int
- err := d.db.QueryRow(`
- SELECT COUNT(*) FROM sqlite_master
- WHERE type='table' AND name='exchanges_new'
- `).Scan(&count)
- if err != nil {
- return err
- }
-
- // 如果已经迁移过,直接返回
- if count > 0 {
- return nil
- }
-
- log.Printf("🔄 开始迁移exchanges表...")
-
- // 创建新的exchanges表,使用复合主键
- _, err = d.db.Exec(`
- CREATE TABLE exchanges_new (
- id TEXT NOT NULL,
- user_id TEXT NOT NULL DEFAULT 'default',
- name TEXT NOT NULL,
- type TEXT NOT NULL,
- enabled BOOLEAN DEFAULT 0,
- api_key TEXT DEFAULT '',
- secret_key TEXT DEFAULT '',
- testnet BOOLEAN DEFAULT 0,
- hyperliquid_wallet_addr TEXT DEFAULT '',
- aster_user TEXT DEFAULT '',
- aster_signer TEXT DEFAULT '',
- aster_private_key TEXT DEFAULT '',
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY (id, user_id),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
- )
- `)
- if err != nil {
- return fmt.Errorf("创建新exchanges表失败: %w", err)
- }
-
- // 复制数据到新表
- _, err = d.db.Exec(`
- INSERT INTO exchanges_new
- SELECT * FROM exchanges
- `)
- if err != nil {
- return fmt.Errorf("复制数据失败: %w", err)
- }
-
- // 删除旧表
- _, err = d.db.Exec(`DROP TABLE exchanges`)
- if err != nil {
- return fmt.Errorf("删除旧表失败: %w", err)
- }
-
- // 重命名新表
- _, err = d.db.Exec(`ALTER TABLE exchanges_new RENAME TO exchanges`)
- if err != nil {
- return fmt.Errorf("重命名表失败: %w", err)
- }
-
- // 重新创建触发器
- _, err = d.db.Exec(`
- CREATE TRIGGER IF NOT EXISTS update_exchanges_updated_at
- AFTER UPDATE ON exchanges
- BEGIN
- UPDATE exchanges SET updated_at = CURRENT_TIMESTAMP
- WHERE id = NEW.id AND user_id = NEW.user_id;
- END
- `)
- if err != nil {
- return fmt.Errorf("创建触发器失败: %w", err)
- }
-
- log.Printf("✅ exchanges表迁移完成")
- return nil
-}
-
// User 用户配置
type User struct {
ID string `json:"id"`
@@ -440,22 +66,20 @@ type AIModelConfig struct {
// ExchangeConfig 交易所配置
type ExchangeConfig struct {
- ID string `json:"id"`
- UserID string `json:"user_id"`
- Name string `json:"name"`
- Type string `json:"type"`
- Enabled bool `json:"enabled"`
- APIKey string `json:"apiKey"`
- SecretKey string `json:"secretKey"`
- Testnet bool `json:"testnet"`
- // Hyperliquid 特定字段
- HyperliquidWalletAddr string `json:"hyperliquidWalletAddr"`
- // Aster 特定字段
- AsterUser string `json:"asterUser"`
- AsterSigner string `json:"asterSigner"`
- AsterPrivateKey string `json:"asterPrivateKey"`
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
+ ID string `json:"id"`
+ UserID string `json:"user_id"`
+ Name string `json:"name"`
+ Type string `json:"type"`
+ Enabled bool `json:"enabled"`
+ APIKey string `json:"apiKey"`
+ SecretKey string `json:"secretKey"`
+ Testnet bool `json:"testnet"`
+ HyperliquidWalletAddr string `json:"hyperliquidWalletAddr"`
+ AsterUser string `json:"asterUser"`
+ AsterSigner string `json:"asterSigner"`
+ AsterPrivateKey string `json:"asterPrivateKey"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
}
// TraderRecord 交易员配置(数据库实体)
@@ -468,15 +92,15 @@ type TraderRecord struct {
InitialBalance float64 `json:"initial_balance"`
ScanIntervalMinutes int `json:"scan_interval_minutes"`
IsRunning bool `json:"is_running"`
- BTCETHLeverage int `json:"btc_eth_leverage"` // BTC/ETH杠杆倍数
- AltcoinLeverage int `json:"altcoin_leverage"` // 山寨币杠杆倍数
- TradingSymbols string `json:"trading_symbols"` // 交易币种,逗号分隔
- UseCoinPool bool `json:"use_coin_pool"` // 是否使用COIN POOL信号源
- UseOITop bool `json:"use_oi_top"` // 是否使用OI TOP信号源
- CustomPrompt string `json:"custom_prompt"` // 自定义交易策略prompt
- OverrideBasePrompt bool `json:"override_base_prompt"` // 是否覆盖基础prompt
- SystemPromptTemplate string `json:"system_prompt_template"` // 系统提示词模板名称
- IsCrossMargin bool `json:"is_cross_margin"` // 是否为全仓模式(true=全仓,false=逐仓)
+ BTCETHLeverage int `json:"btc_eth_leverage"`
+ AltcoinLeverage int `json:"altcoin_leverage"`
+ TradingSymbols string `json:"trading_symbols"`
+ UseCoinPool bool `json:"use_coin_pool"`
+ UseOITop bool `json:"use_oi_top"`
+ CustomPrompt string `json:"custom_prompt"`
+ OverrideBasePrompt bool `json:"override_base_prompt"`
+ SystemPromptTemplate string `json:"system_prompt_template"`
+ IsCrossMargin bool `json:"is_cross_margin"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
@@ -491,694 +115,11 @@ type UserSignalSource struct {
UpdatedAt time.Time `json:"updated_at"`
}
-// GenerateOTPSecret 生成OTP密钥
-func GenerateOTPSecret() (string, error) {
- secret := make([]byte, 20)
- _, err := rand.Read(secret)
+// NewDatabase 创建数据库连接(仅支持 PostgreSQL)
+func NewDatabase() (DatabaseInterface, error) {
+ pgDB, err := NewPostgreSQLDatabase()
if err != nil {
- return "", err
+ return nil, fmt.Errorf("创建PostgreSQL数据库失败: %w", err)
}
- return base32.StdEncoding.EncodeToString(secret), nil
-}
-
-// CreateUser 创建用户
-func (d *Database) CreateUser(user *User) error {
- _, err := d.db.Exec(`
- INSERT INTO users (id, email, password_hash, otp_secret, otp_verified)
- VALUES (?, ?, ?, ?, ?)
- `, user.ID, user.Email, user.PasswordHash, user.OTPSecret, user.OTPVerified)
- return err
-}
-
-// EnsureAdminUser 确保admin用户存在(用于管理员模式)
-func (d *Database) EnsureAdminUser() error {
- // 检查admin用户是否已存在
- var count int
- err := d.db.QueryRow(`SELECT COUNT(*) FROM users WHERE id = 'admin'`).Scan(&count)
- if err != nil {
- return err
- }
-
- // 如果已存在,直接返回
- if count > 0 {
- return nil
- }
-
- // 创建admin用户(密码为空,因为管理员模式下不需要密码)
- adminUser := &User{
- ID: "admin",
- Email: "admin@localhost",
- PasswordHash: "", // 管理员模式下不使用密码
- OTPSecret: "",
- OTPVerified: true,
- }
-
- return d.CreateUser(adminUser)
-}
-
-// GetUserByEmail 通过邮箱获取用户
-func (d *Database) GetUserByEmail(email string) (*User, error) {
- var user User
- err := d.db.QueryRow(`
- SELECT id, email, password_hash, otp_secret, otp_verified, created_at, updated_at
- FROM users WHERE email = ?
- `, email).Scan(
- &user.ID, &user.Email, &user.PasswordHash, &user.OTPSecret,
- &user.OTPVerified, &user.CreatedAt, &user.UpdatedAt,
- )
- if err != nil {
- return nil, err
- }
- return &user, nil
-}
-
-// GetUserByID 通过ID获取用户
-func (d *Database) GetUserByID(userID string) (*User, error) {
- var user User
- err := d.db.QueryRow(`
- SELECT id, email, password_hash, otp_secret, otp_verified, created_at, updated_at
- FROM users WHERE id = ?
- `, userID).Scan(
- &user.ID, &user.Email, &user.PasswordHash, &user.OTPSecret,
- &user.OTPVerified, &user.CreatedAt, &user.UpdatedAt,
- )
- if err != nil {
- return nil, err
- }
- return &user, nil
-}
-
-// GetAllUsers 获取所有用户ID列表
-func (d *Database) GetAllUsers() ([]string, error) {
- rows, err := d.db.Query(`SELECT id FROM users ORDER BY id`)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
-
- var userIDs []string
- for rows.Next() {
- var userID string
- if err := rows.Scan(&userID); err != nil {
- return nil, err
- }
- userIDs = append(userIDs, userID)
- }
- return userIDs, nil
-}
-
-// UpdateUserOTPVerified 更新用户OTP验证状态
-func (d *Database) UpdateUserOTPVerified(userID string, verified bool) error {
- _, err := d.db.Exec(`UPDATE users SET otp_verified = ? WHERE id = ?`, verified, userID)
- return err
-}
-
-// GetAIModels 获取用户的AI模型配置
-func (d *Database) GetAIModels(userID string) ([]*AIModelConfig, error) {
- rows, err := d.db.Query(`
- SELECT id, user_id, name, provider, enabled, api_key,
- COALESCE(custom_api_url, '') as custom_api_url,
- COALESCE(custom_model_name, '') as custom_model_name,
- created_at, updated_at
- FROM ai_models WHERE user_id = ? ORDER BY id
- `, userID)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
-
- // 初始化为空切片而不是nil,确保JSON序列化为[]而不是null
- models := make([]*AIModelConfig, 0)
- for rows.Next() {
- var model AIModelConfig
- err := rows.Scan(
- &model.ID, &model.UserID, &model.Name, &model.Provider,
- &model.Enabled, &model.APIKey, &model.CustomAPIURL, &model.CustomModelName,
- &model.CreatedAt, &model.UpdatedAt,
- )
- if err != nil {
- return nil, err
- }
- models = append(models, &model)
- }
-
- return models, nil
-}
-
-// UpdateAIModel 更新AI模型配置,如果不存在则创建用户特定配置
-func (d *Database) UpdateAIModel(userID, id string, enabled bool, apiKey, customAPIURL, customModelName string) error {
- // 先尝试精确匹配 ID(新版逻辑,支持多个相同 provider 的模型)
- var existingID string
- err := d.db.QueryRow(`
- SELECT id FROM ai_models WHERE user_id = ? AND id = ? LIMIT 1
- `, userID, id).Scan(&existingID)
-
- if err == nil {
- // 找到了现有配置(精确匹配 ID),更新它
- _, err = d.db.Exec(`
- UPDATE ai_models SET enabled = ?, api_key = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now')
- WHERE id = ? AND user_id = ?
- `, enabled, apiKey, customAPIURL, customModelName, existingID, userID)
- return err
- }
-
- // ID 不存在,尝试兼容旧逻辑:将 id 作为 provider 查找
- provider := id
- err = d.db.QueryRow(`
- SELECT id FROM ai_models WHERE user_id = ? AND provider = ? LIMIT 1
- `, userID, provider).Scan(&existingID)
-
- if err == nil {
- // 找到了现有配置(通过 provider 匹配,兼容旧版),更新它
- log.Printf("⚠️ 使用旧版 provider 匹配更新模型: %s -> %s", provider, existingID)
- _, err = d.db.Exec(`
- UPDATE ai_models SET enabled = ?, api_key = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now')
- WHERE id = ? AND user_id = ?
- `, enabled, apiKey, customAPIURL, customModelName, existingID, userID)
- return err
- }
-
- // 没有找到任何现有配置,创建新的
- // 推断 provider(从 id 中提取,或者直接使用 id)
- if provider == id && (provider == "deepseek" || provider == "qwen") {
- // id 本身就是 provider
- provider = id
- } else {
- // 从 id 中提取 provider(假设格式是 userID_provider 或 timestamp_userID_provider)
- parts := strings.Split(id, "_")
- if len(parts) >= 2 {
- provider = parts[len(parts)-1] // 取最后一部分作为 provider
- } else {
- provider = id
- }
- }
-
- // 获取模型的基本信息
- var name string
- err = d.db.QueryRow(`
- SELECT name FROM ai_models WHERE provider = ? LIMIT 1
- `, provider).Scan(&name)
- if err != nil {
- // 如果找不到基本信息,使用默认值
- if provider == "deepseek" {
- name = "DeepSeek AI"
- } else if provider == "qwen" {
- name = "Qwen AI"
- } else {
- name = provider + " AI"
- }
- }
-
- // 如果传入的 ID 已经是完整格式(如 "admin_deepseek_custom1"),直接使用
- // 否则生成新的 ID
- newModelID := id
- if id == provider {
- // id 就是 provider,生成新的用户特定 ID
- newModelID = fmt.Sprintf("%s_%s", userID, provider)
- }
-
- log.Printf("✓ 创建新的 AI 模型配置: ID=%s, Provider=%s, Name=%s", newModelID, provider, name)
- _, err = d.db.Exec(`
- INSERT INTO ai_models (id, user_id, name, provider, enabled, api_key, custom_api_url, custom_model_name, created_at, updated_at)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
- `, newModelID, userID, name, provider, enabled, apiKey, customAPIURL, customModelName)
-
- return err
-}
-
-// GetExchanges 获取用户的交易所配置
-func (d *Database) GetExchanges(userID string) ([]*ExchangeConfig, error) {
- rows, err := d.db.Query(`
- SELECT id, user_id, name, type, enabled, api_key, secret_key, testnet,
- COALESCE(hyperliquid_wallet_addr, '') as hyperliquid_wallet_addr,
- COALESCE(aster_user, '') as aster_user,
- COALESCE(aster_signer, '') as aster_signer,
- COALESCE(aster_private_key, '') as aster_private_key,
- created_at, updated_at
- FROM exchanges WHERE user_id = ? ORDER BY id
- `, userID)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
-
- // 初始化为空切片而不是nil,确保JSON序列化为[]而不是null
- exchanges := make([]*ExchangeConfig, 0)
- for rows.Next() {
- var exchange ExchangeConfig
- err := rows.Scan(
- &exchange.ID, &exchange.UserID, &exchange.Name, &exchange.Type,
- &exchange.Enabled, &exchange.APIKey, &exchange.SecretKey, &exchange.Testnet,
- &exchange.HyperliquidWalletAddr, &exchange.AsterUser,
- &exchange.AsterSigner, &exchange.AsterPrivateKey,
- &exchange.CreatedAt, &exchange.UpdatedAt,
- )
- if err != nil {
- return nil, err
- }
- exchanges = append(exchanges, &exchange)
- }
-
- return exchanges, nil
-}
-
-// GetExchangesForAPI 获取交易所配置(专用于API返回,排除敏感字段)
-func (d *Database) GetExchangesForAPI(userID string) ([]*ExchangeConfig, error) {
- rows, err := d.db.Query(`
- SELECT id, user_id, name, type, enabled,
- CASE
- WHEN type = 'hyperliquid' THEN ''
- ELSE COALESCE(api_key, '')
- END as api_key,
- '' as secret_key,
- testnet,
- COALESCE(hyperliquid_wallet_addr, '') as hyperliquid_wallet_addr,
- COALESCE(aster_user, '') as aster_user,
- COALESCE(aster_signer, '') as aster_signer,
- '' as aster_private_key,
- created_at, updated_at
- FROM exchanges WHERE user_id = ? ORDER BY id
- `, userID)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
-
- // 初始化为空切片而不是nil,确保JSON序列化为[]而不是null
- exchanges := make([]*ExchangeConfig, 0)
- for rows.Next() {
- var exchange ExchangeConfig
- err := rows.Scan(
- &exchange.ID, &exchange.UserID, &exchange.Name, &exchange.Type,
- &exchange.Enabled, &exchange.APIKey, &exchange.SecretKey, &exchange.Testnet,
- &exchange.HyperliquidWalletAddr, &exchange.AsterUser,
- &exchange.AsterSigner, &exchange.AsterPrivateKey,
- &exchange.CreatedAt, &exchange.UpdatedAt,
- )
- if err != nil {
- return nil, err
- }
- exchanges = append(exchanges, &exchange)
- }
-
- return exchanges, nil
-}
-
-// UpdateExchange 更新交易所配置,如果不存在则创建用户特定配置
-func (d *Database) UpdateExchange(userID, id string, enabled bool, apiKey, secretKey string, testnet bool, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey string) error {
- log.Printf("🔧 UpdateExchange: userID=%s, id=%s, enabled=%v", userID, id, enabled)
-
- // 首先尝试更新现有的用户配置
- result, err := d.db.Exec(`
- UPDATE exchanges SET enabled = ?, api_key = ?, secret_key = ?, testnet = ?,
- hyperliquid_wallet_addr = ?, aster_user = ?, aster_signer = ?, aster_private_key = ?, updated_at = datetime('now')
- WHERE id = ? AND user_id = ?
- `, enabled, apiKey, secretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey, id, userID)
- if err != nil {
- log.Printf("❌ UpdateExchange: 更新失败: %v", err)
- return err
- }
-
- // 检查是否有行被更新
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- log.Printf("❌ UpdateExchange: 获取影响行数失败: %v", err)
- return err
- }
-
- log.Printf("📊 UpdateExchange: 影响行数 = %d", rowsAffected)
-
- // 如果没有行被更新,说明用户没有这个交易所的配置,需要创建
- if rowsAffected == 0 {
- log.Printf("💡 UpdateExchange: 没有现有记录,创建新记录")
-
- // 根据交易所ID确定基本信息
- var name, typ string
- if id == "binance" {
- name = "Binance Futures"
- typ = "cex"
- } else if id == "hyperliquid" {
- name = "Hyperliquid"
- typ = "dex"
- } else if id == "aster" {
- name = "Aster DEX"
- typ = "dex"
- } else {
- name = id + " Exchange"
- typ = "cex"
- }
-
- log.Printf("🆕 UpdateExchange: 创建新记录 ID=%s, name=%s, type=%s", id, name, typ)
-
- // 创建用户特定的配置,使用原始的交易所ID
- _, err = d.db.Exec(`
- INSERT INTO exchanges (id, user_id, name, type, enabled, api_key, secret_key, testnet,
- hyperliquid_wallet_addr, aster_user, aster_signer, aster_private_key, created_at, updated_at)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
- `, id, userID, name, typ, enabled, apiKey, secretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey)
-
- if err != nil {
- log.Printf("❌ UpdateExchange: 创建记录失败: %v", err)
- } else {
- log.Printf("✅ UpdateExchange: 创建记录成功")
- }
- return err
- }
-
- log.Printf("✅ UpdateExchange: 更新现有记录成功")
- return nil
-}
-
-// CreateAIModel 创建AI模型配置
-func (d *Database) CreateAIModel(userID, id, name, provider string, enabled bool, apiKey, customAPIURL string) error {
- _, err := d.db.Exec(`
- INSERT OR IGNORE INTO ai_models (id, user_id, name, provider, enabled, api_key, custom_api_url)
- VALUES (?, ?, ?, ?, ?, ?, ?)
- `, id, userID, name, provider, enabled, apiKey, customAPIURL)
- return err
-}
-
-// CreateExchange 创建交易所配置
-func (d *Database) CreateExchange(userID, id, name, typ string, enabled bool, apiKey, secretKey string, testnet bool, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey string) error {
- _, err := d.db.Exec(`
- INSERT OR IGNORE INTO exchanges (id, user_id, name, type, enabled, api_key, secret_key, testnet, hyperliquid_wallet_addr, aster_user, aster_signer, aster_private_key)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- `, id, userID, name, typ, enabled, apiKey, secretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey)
- return err
-}
-
-// CreateTrader 创建交易员
-func (d *Database) CreateTrader(trader *TraderRecord) error {
- _, err := d.db.Exec(`
- INSERT INTO traders (id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running, btc_eth_leverage, altcoin_leverage, trading_symbols, use_coin_pool, use_oi_top, custom_prompt, override_base_prompt, system_prompt_template, is_cross_margin)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- `, trader.ID, trader.UserID, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance, trader.ScanIntervalMinutes, trader.IsRunning, trader.BTCETHLeverage, trader.AltcoinLeverage, trader.TradingSymbols, trader.UseCoinPool, trader.UseOITop, trader.CustomPrompt, trader.OverrideBasePrompt, trader.SystemPromptTemplate, trader.IsCrossMargin)
- return err
-}
-
-// GetTraders 获取用户的交易员
-func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) {
- rows, err := d.db.Query(`
- SELECT id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running,
- COALESCE(btc_eth_leverage, 5) as btc_eth_leverage, COALESCE(altcoin_leverage, 5) as altcoin_leverage,
- COALESCE(trading_symbols, '') as trading_symbols,
- COALESCE(use_coin_pool, 0) as use_coin_pool, COALESCE(use_oi_top, 0) as use_oi_top,
- COALESCE(custom_prompt, '') as custom_prompt, COALESCE(override_base_prompt, 0) as override_base_prompt,
- COALESCE(system_prompt_template, 'default') as system_prompt_template,
- COALESCE(is_cross_margin, 1) as is_cross_margin, created_at, updated_at
- FROM traders WHERE user_id = ? ORDER BY created_at DESC
- `, userID)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
-
- var traders []*TraderRecord
- for rows.Next() {
- var trader TraderRecord
- err := rows.Scan(
- &trader.ID, &trader.UserID, &trader.Name, &trader.AIModelID, &trader.ExchangeID,
- &trader.InitialBalance, &trader.ScanIntervalMinutes, &trader.IsRunning,
- &trader.BTCETHLeverage, &trader.AltcoinLeverage, &trader.TradingSymbols,
- &trader.UseCoinPool, &trader.UseOITop,
- &trader.CustomPrompt, &trader.OverrideBasePrompt, &trader.SystemPromptTemplate,
- &trader.IsCrossMargin,
- &trader.CreatedAt, &trader.UpdatedAt,
- )
- if err != nil {
- return nil, err
- }
- traders = append(traders, &trader)
- }
-
- return traders, nil
-}
-
-// UpdateTraderStatus 更新交易员状态
-func (d *Database) UpdateTraderStatus(userID, id string, isRunning bool) error {
- _, err := d.db.Exec(`UPDATE traders SET is_running = ? WHERE id = ? AND user_id = ?`, isRunning, id, userID)
- return err
-}
-
-// UpdateTrader 更新交易员配置
-func (d *Database) UpdateTrader(trader *TraderRecord) error {
- _, err := d.db.Exec(`
- UPDATE traders SET
- name = ?, ai_model_id = ?, exchange_id = ?, initial_balance = ?,
- scan_interval_minutes = ?, btc_eth_leverage = ?, altcoin_leverage = ?,
- trading_symbols = ?, custom_prompt = ?, override_base_prompt = ?,
- system_prompt_template = ?, is_cross_margin = ?, updated_at = CURRENT_TIMESTAMP
- WHERE id = ? AND user_id = ?
- `, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance,
- trader.ScanIntervalMinutes, trader.BTCETHLeverage, trader.AltcoinLeverage,
- trader.TradingSymbols, trader.CustomPrompt, trader.OverrideBasePrompt,
- trader.SystemPromptTemplate, trader.IsCrossMargin, trader.ID, trader.UserID)
- return err
-}
-
-// UpdateTraderCustomPrompt 更新交易员自定义Prompt
-func (d *Database) UpdateTraderCustomPrompt(userID, id string, customPrompt string, overrideBase bool) error {
- _, err := d.db.Exec(`UPDATE traders SET custom_prompt = ?, override_base_prompt = ? WHERE id = ? AND user_id = ?`, customPrompt, overrideBase, id, userID)
- return err
-}
-
-// UpdateTraderInitialBalance 更新交易员初始余额(用于自动同步交易所实际余额)
-func (d *Database) UpdateTraderInitialBalance(userID, id string, newBalance float64) error {
- _, err := d.db.Exec(`UPDATE traders SET initial_balance = ? WHERE id = ? AND user_id = ?`, newBalance, id, userID)
- return err
-}
-
-// DeleteTrader 删除交易员
-func (d *Database) DeleteTrader(userID, id string) error {
- _, err := d.db.Exec(`DELETE FROM traders WHERE id = ? AND user_id = ?`, id, userID)
- return err
-}
-
-// GetTraderConfig 获取交易员完整配置(包含AI模型和交易所信息)
-func (d *Database) GetTraderConfig(userID, traderID string) (*TraderRecord, *AIModelConfig, *ExchangeConfig, error) {
- var trader TraderRecord
- var aiModel AIModelConfig
- var exchange ExchangeConfig
-
- err := d.db.QueryRow(`
- SELECT
- t.id, t.user_id, t.name, t.ai_model_id, t.exchange_id, t.initial_balance, t.scan_interval_minutes, t.is_running, t.created_at, t.updated_at,
- a.id, a.user_id, a.name, a.provider, a.enabled, a.api_key, a.created_at, a.updated_at,
- e.id, e.user_id, e.name, e.type, e.enabled, e.api_key, e.secret_key, e.testnet,
- COALESCE(e.hyperliquid_wallet_addr, '') as hyperliquid_wallet_addr,
- COALESCE(e.aster_user, '') as aster_user,
- COALESCE(e.aster_signer, '') as aster_signer,
- COALESCE(e.aster_private_key, '') as aster_private_key,
- e.created_at, e.updated_at
- FROM traders t
- JOIN ai_models a ON t.ai_model_id = a.id AND t.user_id = a.user_id
- JOIN exchanges e ON t.exchange_id = e.id AND t.user_id = e.user_id
- WHERE t.id = ? AND t.user_id = ?
- `, traderID, userID).Scan(
- &trader.ID, &trader.UserID, &trader.Name, &trader.AIModelID, &trader.ExchangeID,
- &trader.InitialBalance, &trader.ScanIntervalMinutes, &trader.IsRunning,
- &trader.BTCETHLeverage, &trader.AltcoinLeverage, &trader.TradingSymbols,
- &trader.UseCoinPool, &trader.UseOITop,
- &trader.CustomPrompt, &trader.OverrideBasePrompt, &trader.SystemPromptTemplate,
- &trader.IsCrossMargin,
- &trader.CreatedAt, &trader.UpdatedAt,
- &aiModel.ID, &aiModel.UserID, &aiModel.Name, &aiModel.Provider, &aiModel.Enabled, &aiModel.APIKey,
- &aiModel.CustomAPIURL, &aiModel.CustomModelName,
- &aiModel.CreatedAt, &aiModel.UpdatedAt,
- &exchange.ID, &exchange.UserID, &exchange.Name, &exchange.Type, &exchange.Enabled,
- &exchange.APIKey, &exchange.SecretKey, &exchange.Testnet,
- &exchange.HyperliquidWalletAddr, &exchange.AsterUser, &exchange.AsterSigner, &exchange.AsterPrivateKey,
- &exchange.CreatedAt, &exchange.UpdatedAt,
- )
-
- if err != nil {
- return nil, nil, nil, err
- }
-
- return &trader, &aiModel, &exchange, nil
-}
-
-// GetSystemConfig 获取系统配置
-func (d *Database) GetSystemConfig(key string) (string, error) {
- var value string
- err := d.db.QueryRow(`SELECT value FROM system_config WHERE key = ?`, key).Scan(&value)
- return value, err
-}
-
-// SetSystemConfig 设置系统配置
-func (d *Database) SetSystemConfig(key, value string) error {
- _, err := d.db.Exec(`
- INSERT OR REPLACE INTO system_config (key, value) VALUES (?, ?)
- `, key, value)
- return err
-}
-
-// CreateUserSignalSource 创建用户信号源配置
-func (d *Database) CreateUserSignalSource(userID, coinPoolURL, oiTopURL string) error {
- _, err := d.db.Exec(`
- INSERT OR REPLACE INTO user_signal_sources (user_id, coin_pool_url, oi_top_url, updated_at)
- VALUES (?, ?, ?, CURRENT_TIMESTAMP)
- `, userID, coinPoolURL, oiTopURL)
- return err
-}
-
-// GetUserSignalSource 获取用户信号源配置
-func (d *Database) GetUserSignalSource(userID string) (*UserSignalSource, error) {
- var source UserSignalSource
- err := d.db.QueryRow(`
- SELECT id, user_id, coin_pool_url, oi_top_url, created_at, updated_at
- FROM user_signal_sources WHERE user_id = ?
- `, userID).Scan(
- &source.ID, &source.UserID, &source.CoinPoolURL, &source.OITopURL,
- &source.CreatedAt, &source.UpdatedAt,
- )
- if err != nil {
- return nil, err
- }
- return &source, nil
-}
-
-// UpdateUserSignalSource 更新用户信号源配置
-func (d *Database) UpdateUserSignalSource(userID, coinPoolURL, oiTopURL string) error {
- _, err := d.db.Exec(`
- UPDATE user_signal_sources SET coin_pool_url = ?, oi_top_url = ?, updated_at = CURRENT_TIMESTAMP
- WHERE user_id = ?
- `, coinPoolURL, oiTopURL, userID)
- return err
-}
-
-// GetCustomCoins 获取所有交易员自定义币种 / Get all trader-customized currencies
-func (d *Database) GetCustomCoins() []string {
- var symbol string
- var symbols []string
- _ = d.db.QueryRow(`
- SELECT GROUP_CONCAT(custom_coins , ',') as symbol
- FROM main.traders where custom_coins != ''
- `).Scan(&symbol)
- // 检测用户是否未配置币种 - 兼容性
- if symbol == "" {
- symbolJSON, _ := d.GetSystemConfig("default_coins")
- if err := json.Unmarshal([]byte(symbolJSON), &symbols); err != nil {
- log.Printf("⚠️ 解析default_coins配置失败: %v,使用硬编码默认值", err)
- symbols = []string{"BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT"}
- }
- }
- // filter Symbol
- for _, s := range strings.Split(symbol, ",") {
- if s == "" {
- continue
- }
- coin := market.Normalize(s)
- if !slices.Contains(symbols, coin) {
- symbols = append(symbols, coin)
- }
- }
- return symbols
-}
-
-// Close 关闭数据库连接
-func (d *Database) Close() error {
- return d.db.Close()
-}
-
-// LoadBetaCodesFromFile 从文件加载内测码到数据库
-func (d *Database) LoadBetaCodesFromFile(filePath string) error {
- // 读取文件内容
- content, err := os.ReadFile(filePath)
- if err != nil {
- return fmt.Errorf("读取内测码文件失败: %w", err)
- }
-
- // 按行分割内测码
- lines := strings.Split(string(content), "\n")
- var codes []string
- for _, line := range lines {
- code := strings.TrimSpace(line)
- if code != "" && !strings.HasPrefix(code, "#") {
- codes = append(codes, code)
- }
- }
-
- // 批量插入内测码
- tx, err := d.db.Begin()
- if err != nil {
- return fmt.Errorf("开始事务失败: %w", err)
- }
- defer tx.Rollback()
-
- stmt, err := tx.Prepare(`INSERT OR IGNORE INTO beta_codes (code) VALUES (?)`)
- if err != nil {
- return fmt.Errorf("准备语句失败: %w", err)
- }
- defer stmt.Close()
-
- insertedCount := 0
- for _, code := range codes {
- result, err := stmt.Exec(code)
- if err != nil {
- log.Printf("插入内测码 %s 失败: %v", code, err)
- continue
- }
-
- if rowsAffected, _ := result.RowsAffected(); rowsAffected > 0 {
- insertedCount++
- }
- }
-
- if err := tx.Commit(); err != nil {
- return fmt.Errorf("提交事务失败: %w", err)
- }
-
- log.Printf("✅ 成功加载 %d 个内测码到数据库 (总计 %d 个)", insertedCount, len(codes))
- return nil
-}
-
-// ValidateBetaCode 验证内测码是否有效且未使用
-func (d *Database) ValidateBetaCode(code string) (bool, error) {
- var used bool
- err := d.db.QueryRow(`SELECT used FROM beta_codes WHERE code = ?`, code).Scan(&used)
- if err != nil {
- if err == sql.ErrNoRows {
- return false, nil // 内测码不存在
- }
- return false, err
- }
- return !used, nil // 内测码存在且未使用
-}
-
-// UseBetaCode 使用内测码(标记为已使用)
-func (d *Database) UseBetaCode(code, userEmail string) error {
- result, err := d.db.Exec(`
- UPDATE beta_codes SET used = 1, used_by = ?, used_at = CURRENT_TIMESTAMP
- WHERE code = ? AND used = 0
- `, userEmail, code)
- if err != nil {
- return err
- }
-
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return err
- }
-
- if rowsAffected == 0 {
- return fmt.Errorf("内测码无效或已被使用")
- }
-
- return nil
-}
-
-// GetBetaCodeStats 获取内测码统计信息
-func (d *Database) GetBetaCodeStats() (total, used int, err error) {
- err = d.db.QueryRow(`SELECT COUNT(*) FROM beta_codes`).Scan(&total)
- if err != nil {
- return 0, 0, err
- }
-
- err = d.db.QueryRow(`SELECT COUNT(*) FROM beta_codes WHERE used = 1`).Scan(&used)
- if err != nil {
- return 0, 0, err
- }
-
- return total, used, nil
+ return pgDB, nil
}
diff --git a/config/database_pg.go b/config/database_pg.go
index ad11ca07..ef41bf07 100644
--- a/config/database_pg.go
+++ b/config/database_pg.go
@@ -52,6 +52,11 @@ func NewPostgreSQLDatabase() (*PostgreSQLDatabase, error) {
database := &PostgreSQLDatabase{db: db}
log.Printf("✅ PostgreSQL数据库连接成功")
+ // 初始化默认数据
+ if err := database.initDefaultData(); err != nil {
+ return nil, fmt.Errorf("初始化默认数据失败: %w", err)
+ }
+
return database, nil
}
@@ -72,32 +77,6 @@ func (d *PostgreSQLDatabase) CreateUser(user *User) error {
return err
}
-// EnsureAdminUser 确保admin用户存在(用于管理员模式)
-func (d *PostgreSQLDatabase) EnsureAdminUser() error {
- // 检查admin用户是否已存在
- var count int
- err := d.db.QueryRow(`SELECT COUNT(*) FROM users WHERE id = 'admin'`).Scan(&count)
- if err != nil {
- return err
- }
-
- // 如果已存在,直接返回
- if count > 0 {
- return nil
- }
-
- // 创建admin用户(密码为空,因为管理员模式下不需要密码)
- adminUser := &User{
- ID: "admin",
- Email: "admin@localhost",
- PasswordHash: "", // 管理员模式下不使用密码
- OTPSecret: "",
- OTPVerified: true,
- }
-
- return d.CreateUser(adminUser)
-}
-
// GetUserByEmail 通过邮箱获取用户
func (d *PostgreSQLDatabase) GetUserByEmail(email string) (*User, error) {
var user User
@@ -701,7 +680,67 @@ func (d *PostgreSQLDatabase) GetBetaCodeStats() (total, used int, err error) {
return total, used, nil
}
+// initDefaultData 初始化默认数据(AI模型和交易所)
+func (d *PostgreSQLDatabase) initDefaultData() error {
+ // 确保traders表存在custom_coins列,防止旧环境缺少字段
+ if _, err := d.db.Exec(`ALTER TABLE traders ADD COLUMN IF NOT EXISTS custom_coins TEXT DEFAULT ''`); err != nil {
+ return fmt.Errorf("添加custom_coins列失败: %w", err)
+ }
+
+ // 首先创建default用户(如果不存在)
+ _, err := d.db.Exec(`
+ INSERT INTO users (id, email, password_hash, otp_secret, otp_verified)
+ VALUES ('default', 'default@localhost', '', '', true)
+ ON CONFLICT (id) DO NOTHING
+ `)
+ if err != nil {
+ return fmt.Errorf("创建default用户失败: %w", err)
+ }
+
+ // 初始化AI模型(使用default用户)
+ aiModels := []struct {
+ id, name, provider string
+ }{
+ {"deepseek", "DeepSeek", "deepseek"},
+ {"qwen", "Qwen", "qwen"},
+ }
+
+ for _, model := range aiModels {
+ _, err := d.db.Exec(`
+ INSERT INTO ai_models (id, user_id, name, provider, enabled, api_key, custom_api_url, custom_model_name, created_at, updated_at)
+ VALUES ($1, 'default', $2, $3, false, '', '', '', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
+ ON CONFLICT (id) DO NOTHING
+ `, model.id, model.name, model.provider)
+ if err != nil {
+ return fmt.Errorf("初始化AI模型失败: %w", err)
+ }
+ }
+
+ // 初始化交易所(使用default用户)
+ exchanges := []struct {
+ id, name, typ string
+ }{
+ {"binance", "Binance Futures", "binance"},
+ {"hyperliquid", "Hyperliquid", "hyperliquid"},
+ {"aster", "Aster DEX", "aster"},
+ }
+
+ for _, exchange := range exchanges {
+ _, err := d.db.Exec(`
+ INSERT INTO exchanges (id, user_id, name, type, enabled, api_key, secret_key, testnet,
+ hyperliquid_wallet_addr, aster_user, aster_signer, aster_private_key, created_at, updated_at)
+ VALUES ($1, 'default', $2, $3, false, '', '', false, '', '', '', '', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
+ ON CONFLICT (id, user_id) DO NOTHING
+ `, exchange.id, exchange.name, exchange.typ)
+ if err != nil {
+ return fmt.Errorf("初始化交易所失败: %w", err)
+ }
+ }
+
+ return nil
+}
+
// Close 关闭数据库连接
func (d *PostgreSQLDatabase) Close() error {
return d.db.Close()
-}
\ No newline at end of file
+}
diff --git a/db/init.sql b/db/init.sql
index dbd9a335..4def4446 100644
--- a/db/init.sql
+++ b/db/init.sql
@@ -80,6 +80,7 @@ CREATE TABLE IF NOT EXISTS traders (
override_base_prompt BOOLEAN DEFAULT FALSE,
system_prompt_template TEXT DEFAULT 'default',
is_cross_margin BOOLEAN DEFAULT TRUE,
+ custom_coins TEXT DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
@@ -133,6 +134,11 @@ CREATE TRIGGER update_system_config_updated_at BEFORE UPDATE ON system_config
-- 插入默认数据
+-- 创建default用户(如果不存在)
+INSERT INTO users (id, email, password_hash, otp_secret, otp_verified) VALUES
+('default', 'default@localhost', '', '', TRUE)
+ON CONFLICT (id) DO NOTHING;
+
-- 初始化AI模型(使用default用户)
INSERT INTO ai_models (id, user_id, name, provider, enabled) VALUES
('deepseek', 'default', 'DeepSeek', 'deepseek', FALSE),
@@ -148,7 +154,6 @@ ON CONFLICT (id, user_id) DO NOTHING;
-- 初始化系统配置
INSERT INTO system_config (key, value) VALUES
-('admin_mode', 'true'),
('beta_mode', 'false'),
('api_server_port', '8080'),
('use_default_coins', 'true'),
@@ -166,4 +171,4 @@ CREATE INDEX IF NOT EXISTS idx_ai_models_user_id ON ai_models(user_id);
CREATE INDEX IF NOT EXISTS idx_exchanges_user_id ON exchanges(user_id);
CREATE INDEX IF NOT EXISTS idx_traders_user_id ON traders(user_id);
CREATE INDEX IF NOT EXISTS idx_traders_running ON traders(is_running);
-CREATE INDEX IF NOT EXISTS idx_beta_codes_used ON beta_codes(used);
\ No newline at end of file
+CREATE INDEX IF NOT EXISTS idx_beta_codes_used ON beta_codes(used);
diff --git a/docker-compose.yml b/docker-compose.yml
index 31d5a474..7dcf36da 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -105,4 +105,4 @@ networks:
volumes:
postgres_data:
- redis_data:
\ No newline at end of file
+ redis_data:
diff --git a/docs/architecture/README.md b/docs/architecture/README.md
index fb233a31..e5147770 100644
--- a/docs/architecture/README.md
+++ b/docs/architecture/README.md
@@ -93,7 +93,7 @@ nofx/
| `github.com/gin-gonic/gin` | HTTP API framework | v1.9+ |
| `github.com/adshao/go-binance/v2` | Binance API client | v2.4+ |
| `github.com/markcheno/go-talib` | Technical indicators (TA-Lib) | Latest |
-| `github.com/mattn/go-sqlite3` | SQLite database driver | v1.14+ |
+| `github.com/lib/pq` | PostgreSQL database driver | v1.10+ |
| `github.com/golang-jwt/jwt/v5` | JWT authentication | v5.0+ |
| `github.com/pquerna/otp` | 2FA/TOTP support | v1.4+ |
| `golang.org/x/crypto` | Password hashing (bcrypt) | Latest |
diff --git a/docs/architecture/README.zh-CN.md b/docs/architecture/README.zh-CN.md
index 4acc0f90..0ce428fc 100644
--- a/docs/architecture/README.zh-CN.md
+++ b/docs/architecture/README.zh-CN.md
@@ -93,7 +93,7 @@ nofx/
| `github.com/gin-gonic/gin` | HTTP API 框架 | v1.9+ |
| `github.com/adshao/go-binance/v2` | Binance API 客户端 | v2.4+ |
| `github.com/markcheno/go-talib` | 技术指标(TA-Lib) | 最新 |
-| `github.com/mattn/go-sqlite3` | SQLite 数据库驱动 | v1.14+ |
+| `github.com/lib/pq` | PostgreSQL 数据库驱动 | v1.10+ |
| `github.com/golang-jwt/jwt/v5` | JWT 认证 | v5.0+ |
| `github.com/pquerna/otp` | 2FA/TOTP 支持 | v1.4+ |
| `golang.org/x/crypto` | 密码哈希(bcrypt) | 最新 |
@@ -282,7 +282,6 @@ GET /api/decisions/latest # 最近决策
- 基于 JWT token 的认证
- 使用 TOTP 的 2FA(Google Authenticator)
- Bcrypt 密码哈希
-- 管理员模式(简化的单用户模式)
---
diff --git a/docs/guides/faq.en.md b/docs/guides/faq.en.md
index abe3dd5c..644de323 100644
--- a/docs/guides/faq.en.md
+++ b/docs/guides/faq.en.md
@@ -152,18 +152,17 @@ Yes, to some extent. NOFX provides historical performance feedback in each decis
## Data & Privacy
### Where is my data stored?
-All data is stored **locally** on your machine in SQLite databases:
-- `config.db` - Trader configurations
-- `trading.db` - Trade history
+All data is stored **locally** in PostgreSQL (Docker volume `postgres_data`) plus:
- `decision_logs/` - AI decision records
### Is my API key secure?
API keys are stored in local databases. Never share your databases or `.env` files. We recommend using API keys with IP whitelist restrictions.
### Can I export my trading history?
-Yes! Trading data is in SQLite format. You can query it directly:
+Yes! Use `pg_dump` or `psql` to export data:
```bash
-sqlite3 trading.db "SELECT * FROM trades;"
+docker compose exec postgres \
+ psql -U nofx -d nofx -c "SELECT * FROM trades;"
```
---
diff --git a/docs/guides/faq.zh-CN.md b/docs/guides/faq.zh-CN.md
index 2c74ca89..b33a4282 100644
--- a/docs/guides/faq.zh-CN.md
+++ b/docs/guides/faq.zh-CN.md
@@ -152,18 +152,17 @@ docker compose up -d
## 数据与隐私
### 我的数据存储在哪里?
-所有数据都**本地存储**在您的机器上,使用 SQLite 数据库:
-- `config.db` - 交易员配置
-- `trading.db` - 交易历史
+所有数据都**本地存储**在 PostgreSQL(Docker 卷 `postgres_data`)中,另有:
- `decision_logs/` - AI 决策记录
### API 密钥安全吗?
API 密钥存储在本地数据库中。永远不要分享您的数据库或 `.env` 文件。我们建议使用带 IP 白名单限制的 API 密钥。
### 可以导出交易历史吗?
-可以!交易数据是 SQLite 格式。您可以直接查询:
+可以!使用 `pg_dump` 或 `psql` 导出数据:
```bash
-sqlite3 trading.db "SELECT * FROM trades;"
+docker compose exec postgres \
+ psql -U nofx -d nofx -c "SELECT * FROM trades;"
```
---
diff --git a/go.mod b/go.mod
index 9b0f63e9..99f0147b 100644
--- a/go.mod
+++ b/go.mod
@@ -11,12 +11,10 @@ require (
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/lib/pq v1.10.9
- github.com/mattn/go-sqlite3 v1.14.32
github.com/pquerna/otp v1.4.0
github.com/sirupsen/logrus v1.9.3
github.com/sonirico/go-hyperliquid v0.17.0
golang.org/x/crypto v0.42.0
- modernc.org/sqlite v1.40.0
)
require (
@@ -31,7 +29,6 @@ require (
github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
- github.com/dustin/go-humanize v1.0.1 // indirect
github.com/elastic/go-sysinfo v1.15.4 // indirect
github.com/elastic/go-windows v1.0.2 // indirect
github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect
@@ -55,13 +52,11 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
- github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.54.0 // indirect
- github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rs/zerolog v1.34.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sonirico/vago v0.9.0 // indirect
@@ -77,7 +72,6 @@ require (
go.elastic.co/fastjson v1.5.1 // indirect
go.uber.org/mock v0.5.0 // indirect
golang.org/x/arch v0.20.0 // indirect
- golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/sync v0.17.0 // indirect
@@ -86,7 +80,4 @@ require (
golang.org/x/tools v0.36.0 // indirect
google.golang.org/protobuf v1.36.9 // indirect
howett.net/plist v1.0.1 // indirect
- modernc.org/libc v1.66.10 // indirect
- modernc.org/mathutil v1.7.1 // indirect
- modernc.org/memory v1.11.0 // indirect
)
diff --git a/go.sum b/go.sum
index 2494cec0..46f4473f 100644
--- a/go.sum
+++ b/go.sum
@@ -32,8 +32,6 @@ github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
-github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
-github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elastic/go-sysinfo v1.15.4 h1:A3zQcunCxik14MgXu39cXFXcIw2sFXZ0zL886eyiv1Q=
github.com/elastic/go-sysinfo v1.15.4/go.mod h1:ZBVXmqS368dOn/jvijV/zHLfakWTYHBZPk3G244lHrU=
github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI=
@@ -84,8 +82,6 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
-github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
@@ -126,8 +122,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
-github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
@@ -136,8 +130,6 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
-github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
@@ -154,8 +146,6 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
-github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
-github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
@@ -215,8 +205,6 @@ golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
-golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
-golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
@@ -246,29 +234,3 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
-modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
-modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
-modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
-modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q=
-modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
-modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
-modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
-modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
-modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
-modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
-modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
-modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
-modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
-modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
-modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
-modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
-modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
-modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
-modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
-modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
-modernc.org/sqlite v1.40.0 h1:bNWEDlYhNPAUdUdBzjAvn8icAs/2gaKlj4vM+tQ6KdQ=
-modernc.org/sqlite v1.40.0/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
-modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
-modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
-modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
-modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
diff --git a/import_default_patch.sh b/import_default_patch.sh
deleted file mode 100755
index 4a00d3d4..00000000
--- a/import_default_patch.sh
+++ /dev/null
@@ -1,150 +0,0 @@
-#!/bin/bash
-
-# 从SQLite导入default用户和系统默认数据到PostgreSQL
-echo "🔧 从SQLite导入default用户和系统默认数据"
-echo "========================================"
-
-# 检查SQLite数据库文件
-SQLITE_DB="config.db"
-if [ ! -f "$SQLITE_DB" ]; then
- echo "❌ 错误:找不到 SQLite 数据库文件 $SQLITE_DB"
- exit 1
-fi
-
-# 检测Docker Compose命令
-DOCKER_COMPOSE_CMD=""
-if command -v "docker-compose" &> /dev/null; then
- DOCKER_COMPOSE_CMD="docker-compose"
-elif command -v "docker" &> /dev/null && docker compose version &> /dev/null; then
- DOCKER_COMPOSE_CMD="docker compose"
-else
- echo "❌ 错误:找不到 docker-compose 或 docker compose 命令"
- exit 1
-fi
-
-echo "📋 使用命令: $DOCKER_COMPOSE_CMD"
-
-# 分析SQLite中的default用户数据
-echo "📊 分析SQLite中的default用户数据..."
-AI_MODEL_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM ai_models WHERE user_id = 'default';" 2>/dev/null || echo "0")
-EXCHANGE_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM exchanges WHERE user_id = 'default';" 2>/dev/null || echo "0")
-
-echo " 🤖 AI模型: $AI_MODEL_COUNT 个"
-echo " 🏢 交易所: $EXCHANGE_COUNT 个"
-
-if [ "$AI_MODEL_COUNT" -eq 0 ] && [ "$EXCHANGE_COUNT" -eq 0 ]; then
- echo "⚠️ SQLite中没有default用户的数据,将跳过导入"
- exit 0
-fi
-
-# 生成导入脚本
-IMPORT_SQL="import_default_data.sql"
-
-cat > $IMPORT_SQL << EOL
--- 从SQLite导入default用户和系统默认数据
--- 生成时间: $(date)
-
--- 设置时区
-SET timezone = 'Asia/Shanghai';
-
--- 1. 创建default用户(如果不存在)
-INSERT INTO users (id, email, password_hash, otp_secret, otp_verified, created_at, updated_at)
-VALUES ('default', 'default@localhost', '', '', true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
-ON CONFLICT (id) DO NOTHING;
-
-EOL
-
-# 导出AI模型数据
-if [ "$AI_MODEL_COUNT" -gt 0 ]; then
- echo "🤖 导出AI模型数据..."
- echo "-- AI模型数据 ($AI_MODEL_COUNT 条记录)" >> $IMPORT_SQL
- echo "INSERT INTO ai_models (id, user_id, name, provider, enabled, api_key, custom_api_url, custom_model_name, created_at, updated_at) VALUES" >> $IMPORT_SQL
-
- sqlite3 $SQLITE_DB "SELECT '(' || quote(id) || ', ' || quote(user_id) || ', ' || quote(name) || ', ' || quote(provider) || ', ' ||
- CASE WHEN enabled = 1 THEN 'true' ELSE 'false' END || ', ' || quote(COALESCE(api_key, '')) || ', ' || quote(COALESCE(custom_api_url, '')) || ', ' ||
- quote(COALESCE(custom_model_name, '')) || ', ' || quote(created_at) || ', ' || quote(updated_at) || '),'
- FROM ai_models WHERE user_id = 'default';" | sed '$ s/,$//' >> $IMPORT_SQL
-
- echo "ON CONFLICT (id) DO UPDATE SET user_id = EXCLUDED.user_id, name = EXCLUDED.name, provider = EXCLUDED.provider, enabled = EXCLUDED.enabled, api_key = EXCLUDED.api_key, custom_api_url = EXCLUDED.custom_api_url, custom_model_name = EXCLUDED.custom_model_name, updated_at = EXCLUDED.updated_at;" >> $IMPORT_SQL
- echo "" >> $IMPORT_SQL
-fi
-
-# 导出交易所数据
-if [ "$EXCHANGE_COUNT" -gt 0 ]; then
- echo "🏢 导出交易所数据..."
- echo "-- 交易所数据 ($EXCHANGE_COUNT 条记录)" >> $IMPORT_SQL
- echo "INSERT INTO exchanges (id, user_id, name, type, enabled, api_key, secret_key, testnet, hyperliquid_wallet_addr, aster_user, aster_signer, aster_private_key, created_at, updated_at) VALUES" >> $IMPORT_SQL
-
- sqlite3 $SQLITE_DB "SELECT '(' || quote(id) || ', ' || quote(user_id) || ', ' || quote(name) || ', ' || quote(type) || ', ' ||
- CASE WHEN enabled = 1 THEN 'true' ELSE 'false' END || ', ' || quote(COALESCE(api_key, '')) || ', ' || quote(COALESCE(secret_key, '')) || ', ' ||
- CASE WHEN testnet = 1 THEN 'true' ELSE 'false' END || ', ' || quote(COALESCE(hyperliquid_wallet_addr, '')) || ', ' ||
- quote(COALESCE(aster_user, '')) || ', ' || quote(COALESCE(aster_signer, '')) || ', ' || quote(COALESCE(aster_private_key, '')) || ', ' ||
- quote(created_at) || ', ' || quote(updated_at) || '),'
- FROM exchanges WHERE user_id = 'default';" | sed '$ s/,$//' >> $IMPORT_SQL
-
- echo "ON CONFLICT (id, user_id) DO UPDATE SET name = EXCLUDED.name, type = EXCLUDED.type, enabled = EXCLUDED.enabled, api_key = EXCLUDED.api_key, secret_key = EXCLUDED.secret_key, testnet = EXCLUDED.testnet, hyperliquid_wallet_addr = EXCLUDED.hyperliquid_wallet_addr, aster_user = EXCLUDED.aster_user, aster_signer = EXCLUDED.aster_signer, aster_private_key = EXCLUDED.aster_private_key, updated_at = EXCLUDED.updated_at;" >> $IMPORT_SQL
- echo "" >> $IMPORT_SQL
-fi
-
-# 添加验证查询
-cat >> $IMPORT_SQL << 'EOL'
--- 验证导入结果
-SELECT '=== 导入完成验证 ===' as status;
-SELECT 'default用户' as item, COUNT(*) as count FROM users WHERE id = 'default'
-UNION ALL SELECT 'AI模型', COUNT(*) FROM ai_models WHERE user_id = 'default'
-UNION ALL SELECT '交易所', COUNT(*) FROM exchanges WHERE user_id = 'default';
-EOL
-
-echo "✅ 生成导入脚本: $IMPORT_SQL"
-
-# 确认执行
-echo
-echo "⚠️ 准备导入default用户和系统默认数据,包括:"
-echo " 1. default用户账户"
-echo " 2. $AI_MODEL_COUNT 个AI模型"
-echo " 3. $EXCHANGE_COUNT 个交易所"
-echo
-read -p "确认执行导入? (y/N): " confirm
-
-if [[ $confirm != [yY] ]]; then
- echo "ℹ️ 已取消导入"
- echo "手动执行命令: $DOCKER_COMPOSE_CMD exec postgres psql -U nofx -d nofx -f /tmp/$IMPORT_SQL"
- exit 0
-fi
-
-# 检查PostgreSQL连接
-echo "🔌 检查数据库连接..."
-if ! $DOCKER_COMPOSE_CMD exec postgres pg_isready -U nofx > /dev/null 2>&1; then
- echo "❌ PostgreSQL连接失败,请确保服务正在运行"
- exit 1
-fi
-
-# 复制SQL脚本到容器
-echo "📦 复制导入脚本到容器..."
-POSTGRES_CONTAINER=$($DOCKER_COMPOSE_CMD ps -q postgres)
-if [ -z "$POSTGRES_CONTAINER" ]; then
- echo "❌ 找不到PostgreSQL容器"
- exit 1
-fi
-
-docker cp $IMPORT_SQL ${POSTGRES_CONTAINER}:/tmp/$IMPORT_SQL
-
-# 执行导入
-echo "🔄 执行数据导入..."
-if $DOCKER_COMPOSE_CMD exec postgres psql -U nofx -d nofx --pset pager=off -f /tmp/$IMPORT_SQL; then
- echo
- echo "✅ default用户和系统默认数据导入成功!"
- echo
- echo "📋 现在可以访问以下接口:"
- echo " - GET /api/supported-models ($AI_MODEL_COUNT 个AI模型)"
- echo " - GET /api/supported-exchanges ($EXCHANGE_COUNT 个交易所)"
- echo
- echo "🧹 清理导入文件..."
- rm -f $IMPORT_SQL
- $DOCKER_COMPOSE_CMD exec postgres rm -f /tmp/$IMPORT_SQL
-else
- echo "❌ 数据导入失败"
- exit 1
-fi
-
-echo "🎉 导入完成!"
diff --git a/main.go b/main.go
index 40ad8136..73dbab1b 100644
--- a/main.go
+++ b/main.go
@@ -25,7 +25,6 @@ type LeverageConfig struct {
// ConfigFile 配置文件结构,只包含需要同步到数据库的字段
type ConfigFile struct {
- AdminMode bool `json:"admin_mode"`
BetaMode bool `json:"beta_mode"`
APIServerPort int `json:"api_server_port"`
UseDefaultCoins bool `json:"use_default_coins"`
@@ -74,7 +73,6 @@ func syncConfigToDatabase(database config.DatabaseInterface, configFile *ConfigF
// 同步各配置项到数据库
configs := map[string]string{
- "admin_mode": fmt.Sprintf("%t", configFile.AdminMode),
"beta_mode": fmt.Sprintf("%t", configFile.BetaMode),
"api_server_port": strconv.Itoa(configFile.APIServerPort),
"use_default_coins": fmt.Sprintf("%t", configFile.UseDefaultCoins),
@@ -160,20 +158,14 @@ func main() {
fmt.Println("╚════════════════════════════════════════════════════════════╝")
fmt.Println()
- // 初始化数据库配置
- 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)
+ log.Printf("📋 初始化配置数据库 (PostgreSQL)")
+ database, err := config.NewDatabase()
if err != nil {
log.Fatalf("❌ 初始化数据库失败: %v", err)
}
@@ -194,10 +186,6 @@ func main() {
useDefaultCoins := useDefaultCoinsStr == "true"
apiPortStr, _ := database.GetSystemConfig("api_server_port")
- // 获取管理员模式配置
- adminModeStr, _ := database.GetSystemConfig("admin_mode")
- adminMode := adminModeStr != "false" // 默认为true
-
// 设置JWT密钥
jwtSecret, _ := database.GetSystemConfig("jwt_secret")
if jwtSecret == "" {
@@ -206,17 +194,6 @@ func main() {
}
auth.SetJWTSecret(jwtSecret)
- // 在管理员模式下,确保admin用户存在
- if adminMode {
- err := database.EnsureAdminUser()
- if err != nil {
- log.Printf("⚠️ 创建admin用户失败: %v", err)
- } else {
- log.Printf("✓ 管理员模式已启用,无需登录")
- }
- auth.SetAdminMode(true)
- }
-
log.Printf("✓ 配置数据库初始化成功")
fmt.Println()
diff --git a/manager/trader_manager.go b/manager/trader_manager.go
index 483421cb..e039b294 100644
--- a/manager/trader_manager.go
+++ b/manager/trader_manager.go
@@ -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, database *config.Database, userID 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 {
tm.mu.Lock()
defer tm.mu.Unlock()
diff --git a/migrate_actual_data.sql b/migrate_actual_data.sql
deleted file mode 100644
index 812594b3..00000000
--- a/migrate_actual_data.sql
+++ /dev/null
@@ -1,115 +0,0 @@
--- 实际数据迁移脚本 - 从SQLite迁移到PostgreSQL
--- 执行方式: psql -h localhost -p 5433 -U nofx -d nofx -f migrate_actual_data.sql
-
--- 首先插入default用户(满足外键约束)
-INSERT INTO users (id, email, password_hash, otp_secret, otp_verified, created_at, updated_at) VALUES
-('default', 'default@localhost', '', '', true, '2025-11-03 09:09:52', '2025-11-03 09:09:52')
-ON CONFLICT (id) DO NOTHING;
-
--- 插入AI模型数据(转换布尔值:0->false, 1->true)
-INSERT INTO ai_models (id, user_id, name, provider, enabled, api_key, custom_api_url, custom_model_name, created_at, updated_at) VALUES
-('deepseek', 'default', 'DeepSeek', 'deepseek', false, '', '', '', '2025-11-03 09:09:52', '2025-11-03 09:09:52'),
-('qwen', 'default', 'Qwen', 'qwen', false, '', '', '', '2025-11-03 09:09:52', '2025-11-03 09:09:52')
-ON CONFLICT (id) DO NOTHING;
-
--- 插入交易所数据(转换布尔值)
-INSERT INTO exchanges (id, user_id, name, type, enabled, api_key, secret_key, testnet, hyperliquid_wallet_addr, aster_user, aster_signer, aster_private_key, created_at, updated_at) VALUES
-('binance', 'default', 'Binance Futures', 'binance', false, '', '', false, '', '', '', '', '2025-11-03 09:09:52', '2025-11-03 09:09:52'),
-('hyperliquid', 'default', 'Hyperliquid', 'hyperliquid', false, '', '', false, '', '', '', '', '2025-11-03 09:09:52', '2025-11-03 09:09:52'),
-('aster', 'default', 'Aster DEX', 'aster', false, '', '', false, '', '', '', '', '2025-11-03 09:09:52', '2025-11-03 09:09:52')
-ON CONFLICT (id, user_id) DO NOTHING;
-
--- 插入系统配置数据
-INSERT INTO system_config (key, value, updated_at) VALUES
-('coin_pool_api_url', '', '2025-11-03 09:09:52'),
-('btc_eth_leverage', '5', '2025-11-03 09:09:52'),
-('api_server_port', '8080', '2025-11-03 09:09:52'),
-('oi_top_api_url', '', '2025-11-03 09:09:52'),
-('stop_trading_minutes', '60', '2025-11-03 09:09:52'),
-('default_coins', '["BTCUSDT","ETHUSDT","SOLUSDT","BNBUSDT","XRPUSDT","DOGEUSDT","ADAUSDT","HYPEUSDT"]', '2025-11-03 09:09:52'),
-('altcoin_leverage', '5', '2025-11-03 09:09:52'),
-('beta_mode', 'true', '2025-11-03 09:09:52'),
-('use_default_coins', 'true', '2025-11-03 09:09:52'),
-('max_daily_loss', '10.0', '2025-11-03 09:09:52'),
-('jwt_secret', 'Qk0kAa+d0iIEzXVHXbNbm+UaN3RNabmWtH8rDWZ5OPf+4GX8pBflAHodfpbipVMyrw1fsDanHsNBjhgbDeK9Jg==', '2025-11-03 09:09:52'),
-('admin_mode', 'false', '2025-11-03 09:09:52'),
-('max_drawdown', '20.0', '2025-11-03 09:09:52'),
-('encryption_public_key', '-----BEGIN PUBLIC KEY-----
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxDsGHRSFXqR2YFoWMNWC
-8s0FlVE2KglHjLnm1f+i5yPfuTYkTUbVDu6RZuqLJdvhX+UO0x1XnwFIhZqmEfro
-8Myr5+RnItl7QGqWWcKry4ZlPHroMwIK50WJt316KUKVUv7wUMMLoUUq7yctI8V/
-thRX+ZRaErJJU9DWkSqjYOVdc+KwsZnN9WifoYhp6veTKmJ1kJOd6AVtF+KJ/z0R
-hFarXjaQ89vf/oUgKahS/BUH7P6jpP+L+7z8G650oygp3Pn66eq+ttcUdc20WiBj
-K5eDBUJUUeNmdesqZXBafhJBhsQyilC0+LgI+3laSkGh3odMdY5Mf9lnke9mfX8E
-RQIDAQAB
------END PUBLIC KEY-----', '2025-11-03 09:09:52'),
-('encryption_public_key_version', 'mock-v1', '2025-11-03 09:09:52')
-ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = EXCLUDED.updated_at;
-
--- 插入内测码数据(转换布尔值:0->false, 1->true)
-INSERT INTO beta_codes (code, used, used_by, used_at, created_at) VALUES
-('2aw4wm', false, '', NULL, '2025-11-03 09:09:52'),
-('34cvds', false, '', NULL, '2025-11-03 09:09:52'),
-('3f39nc', false, '', NULL, '2025-11-03 09:09:52'),
-('3qmg67', false, '', NULL, '2025-11-03 09:09:52'),
-('5rjp6k', false, '', NULL, '2025-11-03 09:09:52'),
-('65a3e6', false, '', NULL, '2025-11-03 09:09:52'),
-('6hzgpr', false, '', NULL, '2025-11-03 09:09:52'),
-('6wruwb', false, '', NULL, '2025-11-03 09:09:52'),
-('8bdf7a', false, '', NULL, '2025-11-03 09:09:52'),
-('8jxnp5', false, '', NULL, '2025-11-03 09:09:52'),
-('8xp3xq', false, '', NULL, '2025-11-03 09:09:52'),
-('9r5uev', false, '', NULL, '2025-11-03 09:09:52'),
-('adbn7p', false, '', NULL, '2025-11-03 09:09:52'),
-('azm8y4', false, '', NULL, '2025-11-03 09:09:52'),
-('b6tfqu', false, '', NULL, '2025-11-03 09:09:52'),
-('bs32f9', false, '', NULL, '2025-11-03 09:09:52'),
-('ctz8gn', false, '', NULL, '2025-11-03 09:09:52'),
-('d8rmq8', false, '', NULL, '2025-11-03 09:09:52'),
-('dmf2yt', false, '', NULL, '2025-11-03 09:09:52'),
-('dz7e8d', false, '', NULL, '2025-11-03 09:09:52'),
-('e9ptrm', false, '', NULL, '2025-11-03 09:09:52'),
-('f25m8s', false, '', NULL, '2025-11-03 09:09:52'),
-('feuzgb', false, '', NULL, '2025-11-03 09:09:52'),
-('fnd7z7', false, '', NULL, '2025-11-03 09:09:52'),
-('h43s95', false, '', NULL, '2025-11-03 09:09:52'),
-('hgs7gq', false, '', NULL, '2025-11-03 09:09:52'),
-('huhkra', false, '', NULL, '2025-11-03 09:09:52'),
-('mhqch4', false, '', NULL, '2025-11-03 09:09:52'),
-('mqwkau', false, '', NULL, '2025-11-03 09:09:52'),
-('mwfssp', false, '', NULL, '2025-11-03 09:09:52'),
-('na7629', false, '', NULL, '2025-11-03 09:09:52'),
-('pb5c2n', false, '', NULL, '2025-11-03 09:09:52'),
-('q5k6jt', false, '', NULL, '2025-11-03 09:09:52'),
-('qrurb8', false, '', NULL, '2025-11-03 09:09:52'),
-('rssybm', false, '', NULL, '2025-11-03 09:09:52'),
-('s7hbk7', false, '', NULL, '2025-11-03 09:09:52'),
-('sj8rus', false, '', NULL, '2025-11-03 09:09:52'),
-('sxy53c', false, '', NULL, '2025-11-03 09:09:52'),
-('t8fjmk', false, '', NULL, '2025-11-03 09:09:52'),
-('udmqcb', false, '', NULL, '2025-11-03 09:09:52'),
-('um6xu6', false, '', NULL, '2025-11-03 09:09:52'),
-('uzwb4r', false, '', NULL, '2025-11-03 09:09:52'),
-('w2uh55', false, '', NULL, '2025-11-03 09:09:52'),
-('wejxcq', false, '', NULL, '2025-11-03 09:09:52'),
-('wtaama', false, '', NULL, '2025-11-03 09:09:52'),
-('x82qvu', false, '', NULL, '2025-11-03 09:09:52'),
-('ygg4d4', false, '', NULL, '2025-11-03 09:09:52'),
-('yv8hnn', false, '', NULL, '2025-11-03 09:09:52'),
-('z9ywv8', false, '', NULL, '2025-11-03 09:09:52'),
-('znpa5t', false, '', NULL, '2025-11-03 09:09:52')
-ON CONFLICT (code) DO NOTHING;
-
--- 数据迁移验证查询
-SELECT 'Migration Summary:' as status;
-SELECT 'ai_models' as table_name, COUNT(*) as count FROM ai_models
-UNION ALL
-SELECT 'exchanges', COUNT(*) FROM exchanges
-UNION ALL
-SELECT 'system_config', COUNT(*) FROM system_config
-UNION ALL
-SELECT 'beta_codes', COUNT(*) FROM beta_codes;
-
--- 显示当前配置
-SELECT 'Current System Config:' as status;
-SELECT key, value FROM system_config ORDER BY key;
\ No newline at end of file
diff --git a/migrate_data.sql b/migrate_data.sql
deleted file mode 100644
index 0f946cc1..00000000
--- a/migrate_data.sql
+++ /dev/null
@@ -1,49 +0,0 @@
--- PostgreSQL数据迁移脚本
--- 从SQLite导出的数据转换为PostgreSQL格式
-
--- 注意:这个脚本需要根据实际的SQLite导出数据进行调整
--- 主要差异:
--- 1. SQLite的AUTOINCREMENT -> PostgreSQL的SERIAL
--- 2. 布尔值:SQLite的0/1 -> PostgreSQL的false/true
--- 3. 日期时间格式可能需要调整
--- 4. 主键冲突处理:使用ON CONFLICT
-
--- 如果有实际数据,请在这里添加INSERT语句
--- 例如:
-
--- 插入用户数据(如果有)
--- INSERT INTO users (id, email, password_hash, otp_secret, otp_verified, created_at, updated_at)
--- VALUES (...) ON CONFLICT (id) DO NOTHING;
-
--- 插入AI模型配置(如果有自定义)
--- INSERT INTO ai_models (id, user_id, name, provider, enabled, api_key, custom_api_url, custom_model_name, created_at, updated_at)
--- VALUES (...) ON CONFLICT (id) DO NOTHING;
-
--- 插入交易所配置(如果有自定义)
--- INSERT INTO exchanges (id, user_id, name, type, enabled, api_key, secret_key, testnet, hyperliquid_wallet_addr, aster_user, aster_signer, aster_private_key, created_at, updated_at)
--- VALUES (...) ON CONFLICT (id, user_id) DO NOTHING;
-
--- 插入交易员配置(如果有)
--- INSERT INTO traders (id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running, btc_eth_leverage, altcoin_leverage, trading_symbols, use_coin_pool, use_oi_top, custom_prompt, override_base_prompt, system_prompt_template, is_cross_margin, created_at, updated_at)
--- VALUES (...) ON CONFLICT (id) DO NOTHING;
-
--- 插入系统配置(如果有自定义)
--- INSERT INTO system_config (key, value, updated_at)
--- VALUES (...) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;
-
--- 插入内测码(如果有)
--- INSERT INTO beta_codes (code, used, used_by, used_at, created_at)
--- VALUES (...) ON CONFLICT (code) DO NOTHING;
-
--- 数据迁移完成后的验证查询
--- SELECT 'users' as table_name, COUNT(*) as count FROM users
--- UNION ALL
--- SELECT 'ai_models', COUNT(*) FROM ai_models
--- UNION ALL
--- SELECT 'exchanges', COUNT(*) FROM exchanges
--- UNION ALL
--- SELECT 'traders', COUNT(*) FROM traders
--- UNION ALL
--- SELECT 'system_config', COUNT(*) FROM system_config
--- UNION ALL
--- SELECT 'beta_codes', COUNT(*) FROM beta_codes;
\ No newline at end of file
diff --git a/migrate_to_postgres.sh b/migrate_to_postgres.sh
deleted file mode 100755
index b2a00406..00000000
--- a/migrate_to_postgres.sh
+++ /dev/null
@@ -1,330 +0,0 @@
-#!/bin/bash
-
-# 生产环境 SQLite -> PostgreSQL 数据迁移脚本
-# 真实数据迁移工具 - 支持完整数据导出和迁移
-
-set -e
-
-# 颜色定义
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-BLUE='\033[0;34m'
-CYAN='\033[0;36m'
-NC='\033[0m'
-
-echo "╔══════════════════════════════════════════════════════════════╗"
-echo "║ 🚀 生产环境数据迁移工具 SQLite → PostgreSQL ║"
-echo "╚══════════════════════════════════════════════════════════════╝"
-echo
-
-# 检查必要文件
-SQLITE_DB="config.db"
-if [ ! -f "$SQLITE_DB" ]; then
- echo -e "${RED}❌ 错误:找不到 SQLite 数据库文件 $SQLITE_DB${NC}"
- exit 1
-fi
-
-# 检测Docker Compose命令
-DOCKER_COMPOSE_CMD=""
-if command -v "docker-compose" &> /dev/null; then
- DOCKER_COMPOSE_CMD="docker-compose"
-elif command -v "docker" &> /dev/null && docker compose version &> /dev/null; then
- DOCKER_COMPOSE_CMD="docker compose"
-else
- echo -e "${RED}❌ 错误:找不到 docker-compose 或 docker compose 命令${NC}"
- exit 1
-fi
-
-echo -e "${CYAN}📋 使用命令: ${DOCKER_COMPOSE_CMD}${NC}"
-
-# 分析当前SQLite数据
-echo -e "\n${BLUE}📊 分析当前SQLite数据库...${NC}"
-echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
-
-# 获取数据统计
-USER_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM users;" 2>/dev/null || echo "0")
-AI_MODEL_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM ai_models;" 2>/dev/null || echo "0")
-EXCHANGE_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM exchanges;" 2>/dev/null || echo "0")
-TRADER_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM traders;" 2>/dev/null || echo "0")
-CONFIG_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM system_config;" 2>/dev/null || echo "0")
-BETA_CODE_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM beta_codes;" 2>/dev/null || echo "0")
-
-echo "📈 数据库表统计:"
-echo " 👥 用户 (users): $USER_COUNT"
-echo " 🤖 AI模型 (ai_models): $AI_MODEL_COUNT"
-echo " 🏢 交易所 (exchanges): $EXCHANGE_COUNT"
-echo " 🔧 交易员 (traders): $TRADER_COUNT"
-echo " ⚙️ 系统配置 (system_config): $CONFIG_COUNT"
-echo " 🎟️ 内测码 (beta_codes): $BETA_CODE_COUNT"
-
-# 检查是否有exchange_secrets表
-if sqlite3 $SQLITE_DB "SELECT name FROM sqlite_master WHERE type='table' AND name='exchange_secrets';" | grep -q exchange_secrets; then
- SECRET_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM exchange_secrets;" 2>/dev/null || echo "0")
- echo " 🔐 交易所密钥 (exchange_secrets): $SECRET_COUNT"
-fi
-
-# 检查是否有user_signal_sources表
-if sqlite3 $SQLITE_DB "SELECT name FROM sqlite_master WHERE type='table' AND name='user_signal_sources';" | grep -q user_signal_sources; then
- SIGNAL_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM user_signal_sources;" 2>/dev/null || echo "0")
- echo " 📡 用户信号源 (user_signal_sources): $SIGNAL_COUNT"
-fi
-
-echo
-
-# 生成迁移时间戳
-TIMESTAMP=$(date '+%Y-%m-%d_%H-%M-%S')
-MIGRATION_FILE="migrate_production_${TIMESTAMP}.sql"
-
-echo -e "${BLUE}📤 生成数据迁移脚本: $MIGRATION_FILE${NC}"
-
-# 生成SQL迁移脚本
-cat > $MIGRATION_FILE << EOL
--- 生产环境数据迁移脚本
--- 从SQLite自动导出生成
--- 执行时间: $(date)
-
--- 设置时区
-SET timezone = 'Asia/Shanghai';
-
-EOL
-
-# 导出用户数据
-if [ "$USER_COUNT" -gt 0 ]; then
- echo -e "${CYAN}👥 导出用户数据...${NC}"
- echo "-- 用户数据 ($USER_COUNT 条记录)" >> $MIGRATION_FILE
- echo "INSERT INTO users (id, email, password_hash, otp_secret, otp_verified, created_at, updated_at) VALUES" >> $MIGRATION_FILE
-
- sqlite3 $SQLITE_DB "SELECT '(' || quote(id) || ', ' || quote(COALESCE(email, '')) || ', ' || quote(COALESCE(password_hash, '')) || ', ' || quote(COALESCE(otp_secret, '')) || ', ' ||
- CASE WHEN otp_verified = 1 THEN 'true' ELSE 'false' END || ', ' || quote(created_at) || ', ' || quote(updated_at) || '),'
- FROM users;" | sed '$ s/,$//' >> $MIGRATION_FILE
-
- echo "ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email, password_hash = EXCLUDED.password_hash, otp_secret = EXCLUDED.otp_secret, otp_verified = EXCLUDED.otp_verified, updated_at = EXCLUDED.updated_at;" >> $MIGRATION_FILE
- echo "" >> $MIGRATION_FILE
-fi
-
-# 导出AI模型数据
-if [ "$AI_MODEL_COUNT" -gt 0 ]; then
- echo -e "${CYAN}🤖 导出AI模型数据...${NC}"
- echo "-- AI模型数据 ($AI_MODEL_COUNT 条记录)" >> $MIGRATION_FILE
- echo "INSERT INTO ai_models (id, user_id, name, provider, enabled, api_key, custom_api_url, custom_model_name, created_at, updated_at) VALUES" >> $MIGRATION_FILE
-
- sqlite3 $SQLITE_DB "SELECT '(' || quote(id) || ', ' || quote(user_id) || ', ' ||
- quote(name) || ', ' || quote(provider) || ', ' ||
- CASE WHEN enabled = 1 THEN 'true' ELSE 'false' END || ', ' || quote(api_key) || ', ' || quote(COALESCE(custom_api_url, '')) || ', ' ||
- quote(COALESCE(custom_model_name, '')) || ', ' || quote(created_at) || ', ' || quote(updated_at) || '),'
- FROM ai_models WHERE user_id IS NOT NULL AND user_id != '' AND user_id != 'default'
- AND user_id IN (SELECT id FROM users);" | sed '$ s/,$//' >> $MIGRATION_FILE
-
- echo "ON CONFLICT (id) DO UPDATE SET user_id = EXCLUDED.user_id, name = EXCLUDED.name, provider = EXCLUDED.provider, enabled = EXCLUDED.enabled, api_key = EXCLUDED.api_key, custom_api_url = EXCLUDED.custom_api_url, custom_model_name = EXCLUDED.custom_model_name, updated_at = EXCLUDED.updated_at;" >> $MIGRATION_FILE
- echo "" >> $MIGRATION_FILE
-fi
-
-# 导出交易所数据
-if [ "$EXCHANGE_COUNT" -gt 0 ]; then
- echo -e "${CYAN}🏢 导出交易所数据...${NC}"
- echo "-- 交易所数据 ($EXCHANGE_COUNT 条记录)" >> $MIGRATION_FILE
- echo "INSERT INTO exchanges (id, user_id, name, type, enabled, api_key, secret_key, testnet, hyperliquid_wallet_addr, aster_user, aster_signer, aster_private_key, created_at, updated_at) VALUES" >> $MIGRATION_FILE
-
- sqlite3 $SQLITE_DB "SELECT '(' || quote(id) || ', ' || quote(user_id) || ', ' ||
- quote(name) || ', ' || quote(type) || ', ' ||
- CASE WHEN enabled = 1 THEN 'true' ELSE 'false' END || ', ' || quote(COALESCE(api_key, '')) || ', ' || quote(COALESCE(secret_key, '')) || ', ' ||
- CASE WHEN testnet = 1 THEN 'true' ELSE 'false' END || ', ' || quote(COALESCE(hyperliquid_wallet_addr, '')) || ', ' ||
- quote(COALESCE(aster_user, '')) || ', ' || quote(COALESCE(aster_signer, '')) || ', ' || quote(COALESCE(aster_private_key, '')) || ', ' ||
- quote(created_at) || ', ' || quote(updated_at) || '),'
- FROM exchanges WHERE user_id IS NOT NULL AND user_id != '' AND user_id != 'default'
- AND user_id IN (SELECT id FROM users);" | sed '$ s/,$//' >> $MIGRATION_FILE
-
- echo "ON CONFLICT (id, user_id) DO UPDATE SET name = EXCLUDED.name, type = EXCLUDED.type, enabled = EXCLUDED.enabled, api_key = EXCLUDED.api_key, secret_key = EXCLUDED.secret_key, testnet = EXCLUDED.testnet, hyperliquid_wallet_addr = EXCLUDED.hyperliquid_wallet_addr, aster_user = EXCLUDED.aster_user, aster_signer = EXCLUDED.aster_signer, aster_private_key = EXCLUDED.aster_private_key, updated_at = EXCLUDED.updated_at;" >> $MIGRATION_FILE
- echo "" >> $MIGRATION_FILE
-fi
-
-# 导出交易员数据
-if [ "$TRADER_COUNT" -gt 0 ]; then
- echo -e "${CYAN}🔧 导出交易员数据...${NC}"
- echo "-- 交易员数据 ($TRADER_COUNT 条记录)" >> $MIGRATION_FILE
- echo "INSERT INTO traders (id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running, btc_eth_leverage, altcoin_leverage, trading_symbols, use_coin_pool, use_oi_top, custom_prompt, override_base_prompt, system_prompt_template, is_cross_margin, created_at, updated_at) VALUES" >> $MIGRATION_FILE
-
- sqlite3 $SQLITE_DB "SELECT '(' || quote(id) || ', ' || quote(user_id) || ', ' ||
- quote(name) || ', ' || quote(ai_model_id) || ', ' ||
- quote(exchange_id) || ', ' || initial_balance || ', ' || scan_interval_minutes || ', ' ||
- CASE WHEN is_running = 1 THEN 'true' ELSE 'false' END || ', ' || btc_eth_leverage || ', ' || altcoin_leverage || ', ' ||
- quote(COALESCE(trading_symbols, '')) || ', ' ||
- CASE WHEN use_coin_pool = 1 THEN 'true' ELSE 'false' END || ', ' || CASE WHEN use_oi_top = 1 THEN 'true' ELSE 'false' END || ', ' ||
- quote(COALESCE(custom_prompt, '')) || ', ' || CASE WHEN override_base_prompt = 1 THEN 'true' ELSE 'false' END || ', ' ||
- quote(COALESCE(system_prompt_template, 'default')) || ', ' || CASE WHEN is_cross_margin = 1 THEN 'true' ELSE 'false' END || ', ' ||
- quote(created_at) || ', ' || quote(updated_at) || '),'
- FROM traders WHERE user_id IS NOT NULL AND user_id != '' AND user_id != 'default'
- AND user_id IN (SELECT id FROM users);" | sed '$ s/,$//' >> $MIGRATION_FILE
-
- echo "ON CONFLICT (id) DO UPDATE SET user_id = EXCLUDED.user_id, name = EXCLUDED.name, ai_model_id = EXCLUDED.ai_model_id, exchange_id = EXCLUDED.exchange_id, initial_balance = EXCLUDED.initial_balance, scan_interval_minutes = EXCLUDED.scan_interval_minutes, is_running = EXCLUDED.is_running, btc_eth_leverage = EXCLUDED.btc_eth_leverage, altcoin_leverage = EXCLUDED.altcoin_leverage, trading_symbols = EXCLUDED.trading_symbols, use_coin_pool = EXCLUDED.use_coin_pool, use_oi_top = EXCLUDED.use_oi_top, custom_prompt = EXCLUDED.custom_prompt, override_base_prompt = EXCLUDED.override_base_prompt, system_prompt_template = EXCLUDED.system_prompt_template, is_cross_margin = EXCLUDED.is_cross_margin, updated_at = EXCLUDED.updated_at;" >> $MIGRATION_FILE
- echo "" >> $MIGRATION_FILE
-fi
-
-# 导出系统配置
-if [ "$CONFIG_COUNT" -gt 0 ]; then
- echo -e "${CYAN}⚙️ 导出系统配置...${NC}"
- echo "-- 系统配置数据 ($CONFIG_COUNT 条记录)" >> $MIGRATION_FILE
- echo "INSERT INTO system_config (key, value, updated_at) VALUES" >> $MIGRATION_FILE
-
- sqlite3 $SQLITE_DB "SELECT '(' || quote(key) || ', ' || quote(value) || ', ' || quote(updated_at) || '),'
- FROM system_config;" | sed '$ s/,$//' >> $MIGRATION_FILE
-
- echo "ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = EXCLUDED.updated_at;" >> $MIGRATION_FILE
- echo "" >> $MIGRATION_FILE
-fi
-
-# 导出内测码数据
-if [ "$BETA_CODE_COUNT" -gt 0 ]; then
- echo -e "${CYAN}🎟️ 导出内测码数据...${NC}"
- echo "-- 内测码数据 ($BETA_CODE_COUNT 条记录)" >> $MIGRATION_FILE
- echo "INSERT INTO beta_codes (code, used, used_by, used_at, created_at) VALUES" >> $MIGRATION_FILE
-
- sqlite3 $SQLITE_DB "SELECT '(' || quote(code) || ', ' || CASE WHEN used = 1 THEN 'true' ELSE 'false' END || ', ' ||
- quote(COALESCE(used_by, '')) || ', ' || CASE WHEN used_at IS NULL THEN 'NULL' ELSE quote(used_at) END || ', ' ||
- quote(created_at) || '),'
- FROM beta_codes;" | sed '$ s/,$//' >> $MIGRATION_FILE
-
- echo "ON CONFLICT (code) DO UPDATE SET used = EXCLUDED.used, used_by = EXCLUDED.used_by, used_at = EXCLUDED.used_at;" >> $MIGRATION_FILE
- echo "" >> $MIGRATION_FILE
-fi
-
-# 导出用户信号源(如果存在)
-if sqlite3 $SQLITE_DB "SELECT name FROM sqlite_master WHERE type='table' AND name='user_signal_sources';" | grep -q user_signal_sources; then
- SIGNAL_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM user_signal_sources;" 2>/dev/null || echo "0")
- if [ "$SIGNAL_COUNT" -gt 0 ]; then
- echo -e "${CYAN}📡 导出用户信号源数据...${NC}"
- echo "-- 用户信号源数据 ($SIGNAL_COUNT 条记录)" >> $MIGRATION_FILE
- echo "INSERT INTO user_signal_sources (user_id, coin_pool_url, oi_top_url, created_at, updated_at) VALUES" >> $MIGRATION_FILE
-
- sqlite3 $SQLITE_DB "SELECT '(' || quote(user_id) || ', ' ||
- quote(COALESCE(coin_pool_url, '')) || ', ' ||
- quote(COALESCE(oi_top_url, '')) || ', ' || quote(created_at) || ', ' || quote(updated_at) || '),'
- FROM user_signal_sources WHERE user_id IS NOT NULL AND user_id != '' AND user_id != 'default'
- AND user_id IN (SELECT id FROM users);" | sed '$ s/,$//' >> $MIGRATION_FILE
-
- echo "ON CONFLICT (user_id) DO UPDATE SET coin_pool_url = EXCLUDED.coin_pool_url, oi_top_url = EXCLUDED.oi_top_url, updated_at = EXCLUDED.updated_at;" >> $MIGRATION_FILE
- echo "" >> $MIGRATION_FILE
- fi
-fi
-
-# 添加迁移验证查询
-cat >> $MIGRATION_FILE << 'EOL'
--- 迁移验证查询
-SELECT '=== 数据迁移完成验证 ===' as status;
-SELECT 'users' as table_name, COUNT(*) as record_count FROM users
-UNION ALL SELECT 'ai_models', COUNT(*) FROM ai_models
-UNION ALL SELECT 'exchanges', COUNT(*) FROM exchanges
-UNION ALL SELECT 'traders', COUNT(*) FROM traders
-UNION ALL SELECT 'system_config', COUNT(*) FROM system_config
-UNION ALL SELECT 'beta_codes', COUNT(*) FROM beta_codes
-UNION ALL SELECT 'user_signal_sources', COUNT(*) FROM user_signal_sources
-ORDER BY table_name;
-
--- 显示关键配置
-SELECT '=== 关键系统配置 ===' as info;
-SELECT key,
- CASE WHEN LENGTH(value) > 50 THEN LEFT(value, 50) || '...' ELSE value END as value
-FROM system_config
-WHERE key IN ('admin_mode', 'beta_mode', 'api_server_port', 'default_coins', 'jwt_secret')
-ORDER BY key;
-EOL
-
-echo -e "${GREEN}✅ 迁移脚本生成完成: $MIGRATION_FILE${NC}"
-
-# 确认是否执行迁移
-echo
-echo -e "${YELLOW}⚠️ 准备执行数据迁移,这将:${NC}"
-echo " 1. 停止现有服务"
-echo " 2. 启动PostgreSQL和Redis"
-echo " 3. 执行数据迁移"
-echo " 4. 验证迁移结果"
-echo
-read -p "确认执行迁移? (y/N): " confirm
-
-if [[ $confirm != [yY] ]]; then
- echo -e "${BLUE}ℹ️ 迁移脚本已生成,可稍后手动执行${NC}"
- echo "手动执行命令: $DOCKER_COMPOSE_CMD exec postgres psql -U nofx -d nofx -f /tmp/$MIGRATION_FILE"
- exit 0
-fi
-
-# 执行迁移
-echo -e "\n${BLUE}🚀 开始执行数据迁移...${NC}"
-echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
-
-# 停止现有服务
-echo -e "${YELLOW}🛑 停止现有服务...${NC}"
-$DOCKER_COMPOSE_CMD down 2>/dev/null || true
-
-# 启动PostgreSQL和Redis
-echo -e "${YELLOW}🚀 启动PostgreSQL和Redis服务...${NC}"
-$DOCKER_COMPOSE_CMD up postgres redis -d
-
-# 等待服务启动
-echo -e "${YELLOW}⏳ 等待服务启动...${NC}"
-sleep 15
-
-# 检查PostgreSQL连接
-echo -e "${BLUE}🔌 测试数据库连接...${NC}"
-max_retries=15
-retry_count=0
-
-while [ $retry_count -lt $max_retries ]; do
- if $DOCKER_COMPOSE_CMD exec postgres pg_isready -U nofx > /dev/null 2>&1; then
- echo -e "${GREEN}✅ PostgreSQL连接正常${NC}"
- break
- else
- retry_count=$((retry_count + 1))
- echo -e "${YELLOW}⏳ 等待PostgreSQL启动... (${retry_count}/${max_retries})${NC}"
- sleep 3
- fi
-done
-
-if [ $retry_count -eq $max_retries ]; then
- echo -e "${RED}❌ 无法连接到PostgreSQL,请检查服务状态${NC}"
- $DOCKER_COMPOSE_CMD logs postgres
- exit 1
-fi
-
-# 复制迁移脚本到容器
-echo -e "${BLUE}📦 复制迁移脚本到容器...${NC}"
-POSTGRES_CONTAINER=$($DOCKER_COMPOSE_CMD ps -q postgres)
-if [ -z "$POSTGRES_CONTAINER" ]; then
- echo -e "${RED}❌ 找不到PostgreSQL容器${NC}"
- exit 1
-fi
-
-docker cp $MIGRATION_FILE ${POSTGRES_CONTAINER}:/tmp/$MIGRATION_FILE
-
-# 验证文件复制成功
-if ! $DOCKER_COMPOSE_CMD exec postgres test -f /tmp/$MIGRATION_FILE; then
- echo -e "${RED}❌ 迁移脚本复制失败${NC}"
- exit 1
-fi
-
-# 执行数据迁移
-echo -e "${BLUE}🔄 执行数据迁移...${NC}"
-if $DOCKER_COMPOSE_CMD exec postgres psql -U nofx -d nofx --pset pager=off -f /tmp/$MIGRATION_FILE; then
- echo -e "${GREEN}✅ 数据迁移成功!${NC}"
-else
- echo -e "${RED}❌ 数据迁移失败${NC}"
- echo "查看错误日志: $DOCKER_COMPOSE_CMD exec postgres psql -U nofx -d nofx -c \"SELECT version();\""
- exit 1
-fi
-
-echo
-echo -e "${GREEN}🎉 生产环境数据迁移完成!${NC}"
-echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
-echo -e "${BLUE}📋 后续步骤:${NC}"
-echo -e "1. 备份原SQLite: ${YELLOW}mv config.db config.db.backup.$(date +%Y%m%d)${NC}"
-echo -e "2. 启动完整应用: ${YELLOW}$DOCKER_COMPOSE_CMD up${NC}"
-echo -e "3. 验证功能: 访问 ${YELLOW}http://localhost:3000${NC}"
-echo -e "4. 删除迁移文件: ${YELLOW}rm $MIGRATION_FILE${NC}"
-echo
-echo -e "${BLUE}🔧 如需回滚:${NC}"
-echo -e "1. 停止服务: ${YELLOW}$DOCKER_COMPOSE_CMD down${NC}"
-echo -e "2. 恢复SQLite: ${YELLOW}mv config.db.backup.$(date +%Y%m%d) config.db${NC}"
-echo -e "3. 删除环境变量或编辑 .env 文件注释掉 POSTGRES_HOST"
-echo -e "4. 重启: ${YELLOW}$DOCKER_COMPOSE_CMD up${NC}"
-echo
-echo -e "${GREEN}🚀 PostgreSQL生产环境迁移成功!${NC}"
diff --git a/scripts/import_default_patch.sh b/scripts/import_default_patch.sh
new file mode 100755
index 00000000..ef971edd
--- /dev/null
+++ b/scripts/import_default_patch.sh
@@ -0,0 +1,160 @@
+#!/bin/bash
+
+set -euo pipefail
+
+echo "🔧 同步默认用户与基础配置"
+echo "==============================="
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
+cd "$ROOT_DIR"
+
+# 检测 Docker Compose 命令
+if command -v docker-compose &> /dev/null; then
+ DOCKER_COMPOSE_CMD="docker-compose"
+elif docker compose version &> /dev/null; then
+ DOCKER_COMPOSE_CMD="docker compose"
+else
+ echo "❌ 无法找到 docker-compose 或 docker compose 命令"
+ exit 1
+fi
+
+echo "📋 使用命令: $DOCKER_COMPOSE_CMD"
+
+# 加载 .env 配置
+ENV_FILE=".env"
+if [ -f "$ENV_FILE" ]; then
+ echo "📁 加载 .env ..."
+ set -a
+ # shellcheck disable=SC1090
+ source "$ENV_FILE"
+ set +a
+else
+ echo "⚠️ 未找到 .env,使用默认数据库配置"
+fi
+
+POSTGRES_HOST=${POSTGRES_HOST:-postgres}
+POSTGRES_PORT=${POSTGRES_PORT:-5432}
+POSTGRES_DB=${POSTGRES_DB:-nofx}
+POSTGRES_USER=${POSTGRES_USER:-nofx}
+POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-}
+POSTGRES_SERVICE=${POSTGRES_SERVICE:-postgres}
+POSTGRES_CONTAINER_NAME=${POSTGRES_CONTAINER_NAME:-nofx-postgres}
+
+# 查找 PostgreSQL 容器
+POSTGRES_CONTAINER=$($DOCKER_COMPOSE_CMD ps -q "$POSTGRES_SERVICE" 2>/dev/null || true)
+if [ -z "$POSTGRES_CONTAINER" ]; then
+ POSTGRES_CONTAINER=$(docker ps -q --filter "name=$POSTGRES_CONTAINER_NAME" | head -n 1)
+fi
+
+if [ -z "$POSTGRES_CONTAINER" ]; then
+ echo "❌ 未找到 PostgreSQL 容器 (${POSTGRES_SERVICE}/${POSTGRES_CONTAINER_NAME})"
+ echo "💡 请先启动数据库容器: $DOCKER_COMPOSE_CMD up -d postgres"
+ exit 1
+fi
+
+PG_ENV_ARGS=()
+if [ -n "$POSTGRES_PASSWORD" ]; then
+ PG_ENV_ARGS=(-e "PGPASSWORD=$POSTGRES_PASSWORD")
+fi
+
+echo "🔌 检查数据库连接..."
+if ! docker exec "${PG_ENV_ARGS[@]}" "$POSTGRES_CONTAINER" pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" > /dev/null 2>&1; then
+ echo "❌ 无法连接到 PostgreSQL,请确认容器和凭据"
+ exit 1
+fi
+
+echo
+read -p "确认写入默认账号和基础配置? (y/N): " confirm
+if [[ $confirm != [yY] ]]; then
+ echo "ℹ️ 已取消操作"
+ exit 0
+fi
+
+echo "🚀 执行初始化 SQL..."
+if docker exec -i "${PG_ENV_ARGS[@]}" "$POSTGRES_CONTAINER" \
+ psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" <<'SQL'
+-- 确保 traders 表存在 custom_coins 字段
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'traders' AND column_name = 'custom_coins'
+ ) THEN
+ ALTER TABLE traders ADD COLUMN custom_coins TEXT DEFAULT '';
+ END IF;
+END
+$$;
+
+-- 创建 default 用户
+INSERT INTO users (id, email, password_hash, otp_secret, otp_verified, created_at, updated_at)
+VALUES ('default', 'default@localhost', '', '', true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
+ON CONFLICT (id) DO UPDATE
+ SET email = EXCLUDED.email,
+ updated_at = CURRENT_TIMESTAMP;
+
+-- 默认 AI 模型配置
+INSERT INTO ai_models (id, user_id, name, provider, enabled, api_key, custom_api_url, custom_model_name, created_at, updated_at) VALUES
+('deepseek', 'default', 'DeepSeek', 'deepseek', false, '', '', '', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
+('qwen', 'default', 'Qwen', 'qwen', false, '', '', '', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
+ON CONFLICT (id) DO UPDATE
+ SET user_id = EXCLUDED.user_id,
+ name = EXCLUDED.name,
+ provider = EXCLUDED.provider,
+ enabled = EXCLUDED.enabled,
+ api_key = EXCLUDED.api_key,
+ custom_api_url = EXCLUDED.custom_api_url,
+ custom_model_name = EXCLUDED.custom_model_name,
+ updated_at = CURRENT_TIMESTAMP;
+
+-- 默认交易所配置
+INSERT INTO exchanges (id, user_id, name, type, enabled, api_key, secret_key, testnet,
+ hyperliquid_wallet_addr, aster_user, aster_signer, aster_private_key,
+ created_at, updated_at) VALUES
+('binance', 'default', 'Binance Futures', 'binance', false, '', '', false, '', '', '', '', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
+('hyperliquid', 'default', 'Hyperliquid', 'hyperliquid', false, '', '', false, '', '', '', '', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
+('aster', 'default', 'Aster DEX', 'aster', false, '', '', false, '', '', '', '', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
+ON CONFLICT (id, user_id) DO UPDATE
+ SET name = EXCLUDED.name,
+ type = EXCLUDED.type,
+ enabled = EXCLUDED.enabled,
+ api_key = EXCLUDED.api_key,
+ secret_key = EXCLUDED.secret_key,
+ testnet = EXCLUDED.testnet,
+ hyperliquid_wallet_addr = EXCLUDED.hyperliquid_wallet_addr,
+ aster_user = EXCLUDED.aster_user,
+ aster_signer = EXCLUDED.aster_signer,
+ aster_private_key = EXCLUDED.aster_private_key,
+ updated_at = CURRENT_TIMESTAMP;
+
+-- 默认系统配置(不存在时写入)
+INSERT INTO system_config (key, value) VALUES
+('beta_mode', 'false'),
+('api_server_port', '8080'),
+('use_default_coins', 'true'),
+('default_coins', '["BTCUSDT","ETHUSDT","SOLUSDT","BNBUSDT","XRPUSDT","DOGEUSDT","ADAUSDT","HYPEUSDT"]'),
+('max_daily_loss', '10.0'),
+('max_drawdown', '20.0'),
+('stop_trading_minutes', '60'),
+('btc_eth_leverage', '5'),
+('altcoin_leverage', '5'),
+('jwt_secret', '')
+ON CONFLICT (key) DO NOTHING;
+
+-- 输出校验信息
+SELECT 'default_user' AS item, COUNT(*) AS count FROM users WHERE id = 'default'
+UNION ALL
+SELECT 'default_ai_models', COUNT(*) FROM ai_models WHERE user_id = 'default'
+UNION ALL
+SELECT 'default_exchanges', COUNT(*) FROM exchanges WHERE user_id = 'default';
+SQL
+then
+ echo
+ echo "✅ 默认数据写入完成"
+else
+ echo
+ echo "❌ 数据写入失败"
+ exit 1
+fi
+
+echo "🎉 操作完成"
diff --git a/sqlite_backup.sql b/sqlite_backup.sql
deleted file mode 100644
index 0abf0ebd..00000000
--- a/sqlite_backup.sql
+++ /dev/null
@@ -1,207 +0,0 @@
-PRAGMA foreign_keys=OFF;
-BEGIN TRANSACTION;
-CREATE TABLE ai_models (
- id TEXT PRIMARY KEY,
- user_id TEXT NOT NULL DEFAULT 'default',
- name TEXT NOT NULL,
- provider TEXT NOT NULL,
- enabled BOOLEAN DEFAULT 0,
- api_key TEXT DEFAULT '',
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, custom_api_url TEXT DEFAULT '', custom_model_name TEXT DEFAULT '',
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
- );
-INSERT INTO ai_models VALUES('deepseek','default','DeepSeek','deepseek',0,'','2025-11-03 09:09:52','2025-11-03 09:09:52','','');
-INSERT INTO ai_models VALUES('qwen','default','Qwen','qwen',0,'','2025-11-03 09:09:52','2025-11-03 09:09:52','','');
-CREATE TABLE exchange_secrets (
- exchange_id TEXT NOT NULL,
- user_id TEXT NOT NULL,
- credential_type TEXT NOT NULL,
- ciphertext BLOB NOT NULL,
- nonce BLOB NOT NULL,
- kms_ciphertext BLOB NOT NULL,
- kms_key_version TEXT NOT NULL,
- public_key_version TEXT NOT NULL,
- algorithm TEXT NOT NULL,
- aad BLOB NOT NULL,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY (exchange_id, user_id, credential_type),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
- );
-CREATE TABLE user_signal_sources (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- user_id TEXT NOT NULL,
- coin_pool_url TEXT DEFAULT '',
- oi_top_url TEXT DEFAULT '',
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
- UNIQUE(user_id)
- );
-CREATE TABLE traders (
- id TEXT PRIMARY KEY,
- user_id TEXT NOT NULL DEFAULT 'default',
- name TEXT NOT NULL,
- ai_model_id TEXT NOT NULL,
- exchange_id TEXT NOT NULL,
- initial_balance REAL NOT NULL,
- scan_interval_minutes INTEGER DEFAULT 3,
- is_running BOOLEAN DEFAULT 0,
- btc_eth_leverage INTEGER DEFAULT 5,
- altcoin_leverage INTEGER DEFAULT 5,
- trading_symbols TEXT DEFAULT '',
- use_coin_pool BOOLEAN DEFAULT 0,
- use_oi_top BOOLEAN DEFAULT 0,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, custom_prompt TEXT DEFAULT '', override_base_prompt BOOLEAN DEFAULT 0, is_cross_margin BOOLEAN DEFAULT 1, use_default_coins BOOLEAN DEFAULT 1, custom_coins TEXT DEFAULT '', system_prompt_template TEXT DEFAULT 'default',
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
- FOREIGN KEY (ai_model_id) REFERENCES ai_models(id),
- FOREIGN KEY (exchange_id) REFERENCES exchanges(id)
- );
-CREATE TABLE users (
- id TEXT PRIMARY KEY,
- email TEXT UNIQUE NOT NULL,
- password_hash TEXT NOT NULL,
- otp_secret TEXT,
- otp_verified BOOLEAN DEFAULT 0,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
- );
-CREATE TABLE system_config (
- key TEXT PRIMARY KEY,
- value TEXT NOT NULL,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
- );
-INSERT INTO system_config VALUES('coin_pool_api_url','','2025-11-03 09:09:52');
-INSERT INTO system_config VALUES('btc_eth_leverage','5','2025-11-03 09:09:52');
-INSERT INTO system_config VALUES('api_server_port','8080','2025-11-03 09:09:52');
-INSERT INTO system_config VALUES('oi_top_api_url','','2025-11-03 09:09:52');
-INSERT INTO system_config VALUES('stop_trading_minutes','60','2025-11-03 09:09:52');
-INSERT INTO system_config VALUES('default_coins','["BTCUSDT","ETHUSDT","SOLUSDT","BNBUSDT","XRPUSDT","DOGEUSDT","ADAUSDT","HYPEUSDT"]','2025-11-03 09:09:52');
-INSERT INTO system_config VALUES('altcoin_leverage','5','2025-11-03 09:09:52');
-INSERT INTO system_config VALUES('beta_mode','true','2025-11-03 09:09:52');
-INSERT INTO system_config VALUES('use_default_coins','true','2025-11-03 09:09:52');
-INSERT INTO system_config VALUES('max_daily_loss','10.0','2025-11-03 09:09:52');
-INSERT INTO system_config VALUES('jwt_secret','Qk0kAa+d0iIEzXVHXbNbm+UaN3RNabmWtH8rDWZ5OPf+4GX8pBflAHodfpbipVMyrw1fsDanHsNBjhgbDeK9Jg==','2025-11-03 09:09:52');
-INSERT INTO system_config VALUES('admin_mode','false','2025-11-03 09:09:52');
-INSERT INTO system_config VALUES('max_drawdown','20.0','2025-11-03 09:09:52');
-INSERT INTO system_config VALUES('encryption_public_key',unistr('-----BEGIN PUBLIC KEY-----\u000aMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxDsGHRSFXqR2YFoWMNWC\u000a8s0FlVE2KglHjLnm1f+i5yPfuTYkTUbVDu6RZuqLJdvhX+UO0x1XnwFIhZqmEfro\u000a8Myr5+RnItl7QGqWWcKry4ZlPHroMwIK50WJt316KUKVUv7wUMMLoUUq7yctI8V/\u000athRX+ZRaErJJU9DWkSqjYOVdc+KwsZnN9WifoYhp6veTKmJ1kJOd6AVtF+KJ/z0R\u000ahFarXjaQ89vf/oUgKahS/BUH7P6jpP+L+7z8G650oygp3Pn66eq+ttcUdc20WiBj\u000aK5eDBUJUUeNmdesqZXBafhJBhsQyilC0+LgI+3laSkGh3odMdY5Mf9lnke9mfX8E\u000aRQIDAQAB\u000a-----END PUBLIC KEY-----'),'2025-11-03 09:09:52');
-INSERT INTO system_config VALUES('encryption_public_key_version','mock-v1','2025-11-03 09:09:52');
-CREATE TABLE beta_codes (
- code TEXT PRIMARY KEY,
- used BOOLEAN DEFAULT 0,
- used_by TEXT DEFAULT '',
- used_at DATETIME DEFAULT NULL,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
- );
-INSERT INTO beta_codes VALUES('2aw4wm',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('34cvds',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('3f39nc',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('3qmg67',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('5rjp6k',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('65a3e6',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('6hzgpr',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('6wruwb',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('8bdf7a',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('8jxnp5',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('8xp3xq',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('9r5uev',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('adbn7p',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('azm8y4',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('b6tfqu',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('bs32f9',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('ctz8gn',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('d8rmq8',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('dmf2yt',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('dz7e8d',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('e9ptrm',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('f25m8s',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('feuzgb',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('fnd7z7',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('h43s95',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('hgs7gq',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('huhkra',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('mhqch4',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('mqwkau',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('mwfssp',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('na7629',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('pb5c2n',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('q5k6jt',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('qrurb8',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('rssybm',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('s7hbk7',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('sj8rus',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('sxy53c',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('t8fjmk',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('udmqcb',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('um6xu6',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('uzwb4r',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('w2uh55',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('wejxcq',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('wtaama',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('x82qvu',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('ygg4d4',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('yv8hnn',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('z9ywv8',0,'',NULL,'2025-11-03 09:09:52');
-INSERT INTO beta_codes VALUES('znpa5t',0,'',NULL,'2025-11-03 09:09:52');
-CREATE TABLE IF NOT EXISTS "exchanges" (
- id TEXT NOT NULL,
- user_id TEXT NOT NULL DEFAULT 'default',
- name TEXT NOT NULL,
- type TEXT NOT NULL,
- enabled BOOLEAN DEFAULT 0,
- api_key TEXT DEFAULT '',
- secret_key TEXT DEFAULT '',
- testnet BOOLEAN DEFAULT 0,
- hyperliquid_wallet_addr TEXT DEFAULT '',
- aster_user TEXT DEFAULT '',
- aster_signer TEXT DEFAULT '',
- aster_private_key TEXT DEFAULT '',
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY (id, user_id),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
- );
-INSERT INTO exchanges VALUES('binance','default','Binance Futures','binance',0,'','',0,'','','','','2025-11-03 09:09:52','2025-11-03 09:09:52');
-INSERT INTO exchanges VALUES('hyperliquid','default','Hyperliquid','hyperliquid',0,'','',0,'','','','','2025-11-03 09:09:52','2025-11-03 09:09:52');
-INSERT INTO exchanges VALUES('aster','default','Aster DEX','aster',0,'','',0,'','','','','2025-11-03 09:09:52','2025-11-03 09:09:52');
-CREATE TRIGGER update_users_updated_at
- AFTER UPDATE ON users
- BEGIN
- UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
- END;
-CREATE TRIGGER update_ai_models_updated_at
- AFTER UPDATE ON ai_models
- BEGIN
- UPDATE ai_models SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
- END;
-CREATE TRIGGER update_exchange_secrets_updated_at
- AFTER UPDATE ON exchange_secrets
- BEGIN
- UPDATE exchange_secrets
- SET updated_at = CURRENT_TIMESTAMP
- WHERE exchange_id = NEW.exchange_id AND user_id = NEW.user_id AND credential_type = NEW.credential_type;
- END;
-CREATE TRIGGER update_traders_updated_at
- AFTER UPDATE ON traders
- BEGIN
- UPDATE traders SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
- END;
-CREATE TRIGGER update_user_signal_sources_updated_at
- AFTER UPDATE ON user_signal_sources
- BEGIN
- UPDATE user_signal_sources SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
- END;
-CREATE TRIGGER update_system_config_updated_at
- AFTER UPDATE ON system_config
- BEGIN
- UPDATE system_config SET updated_at = CURRENT_TIMESTAMP WHERE key = NEW.key;
- END;
-CREATE TRIGGER update_exchanges_updated_at
- AFTER UPDATE ON exchanges
- BEGIN
- UPDATE exchanges SET updated_at = CURRENT_TIMESTAMP
- WHERE id = NEW.id AND user_id = NEW.user_id;
- END;
-COMMIT;
diff --git a/web/src/App.tsx b/web/src/App.tsx
index b57081c5..65f5a976 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -12,7 +12,6 @@ import AILearning from './components/AILearning'
import { LanguageProvider, useLanguage } from './contexts/LanguageContext'
import { AuthProvider, useAuth } from './contexts/AuthContext'
import { t, type Language } from './i18n/translations'
-import { useSystemConfig } from './hooks/useSystemConfig'
import { AlertTriangle } from 'lucide-react'
import type {
SystemStatus,
@@ -42,7 +41,7 @@ function getModelDisplayName(modelId: string): string {
function App() {
const { language, setLanguage } = useLanguage()
const { user, token, logout, isLoading } = useAuth()
- const { config: systemConfig, loading: configLoading } = useSystemConfig()
+ const configLoading = false
const [route, setRoute] = useState(window.location.pathname)
// 从URL路径读取初始页面状态(支持刷新保持页面)
@@ -243,7 +242,6 @@ function App() {
onLanguageChange={setLanguage}
user={user}
onLogout={logout}
- isAdminMode={systemConfig?.admin_mode}
onPageChange={(page) => {
console.log('Competition page onPageChange called with:', page)
console.log('Current route:', route, 'Current page:', currentPage)
@@ -286,7 +284,7 @@ function App() {
}
// Show main app for authenticated users on other routes
- if (!systemConfig?.admin_mode && (!user || !token)) {
+ if (!user || !token) {
// Default to landing page when not authenticated and no specific route
return
}
@@ -303,7 +301,6 @@ function App() {
onLanguageChange={setLanguage}
user={user}
onLogout={logout}
- isAdminMode={systemConfig?.admin_mode}
onPageChange={(page) => {
console.log('Main app onPageChange called with:', page)
diff --git a/web/src/components/landing/AboutSection.tsx b/web/src/components/landing/AboutSection.tsx
index 9ec4df0a..97b0c21f 100644
--- a/web/src/components/landing/AboutSection.tsx
+++ b/web/src/components/landing/AboutSection.tsx
@@ -104,8 +104,8 @@ export default function AboutSection({ language }: AboutSectionProps) {
lines={[
'$ git clone https://github.com/tinkle-community/nofx.git',
'$ cd nofx',
- '$ chmod +x start.sh',
- '$ ./start.sh start --build',
+ '$ chmod +x scripts/start.sh',
+ '$ ./scripts/start.sh start --build',
t('startupMessages1', language),
t('startupMessages2', language),
t('startupMessages3', language),
diff --git a/web/src/components/landing/HeaderBar.tsx b/web/src/components/landing/HeaderBar.tsx
index 37f2ae8b..665b0fd3 100644
--- a/web/src/components/landing/HeaderBar.tsx
+++ b/web/src/components/landing/HeaderBar.tsx
@@ -12,7 +12,6 @@ interface HeaderBarProps {
onLanguageChange?: (lang: Language) => void
user?: { email: string } | null
onLogout?: () => void
- isAdminMode?: boolean
onPageChange?: (page: string) => void
}
@@ -24,7 +23,6 @@ export default function HeaderBar({
onLanguageChange,
user,
onLogout,
- isAdminMode = false,
onPageChange,
}: HeaderBarProps) {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
@@ -363,7 +361,7 @@ export default function HeaderBar({
{user.email}
- {!isAdminMode && onLogout && (
+ {onLogout && (