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

1217 lines
43 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package config
import (
"crypto/rand"
"database/sql"
"encoding/base32"
"encoding/json"
"fmt"
"log"
"nofx/crypto"
"nofx/market"
"os"
"slices"
"strings"
"time"
_ "modernc.org/sqlite"
)
// DatabaseInterface 定义了数据库实现需要提供的方法集合
type DatabaseInterface interface {
SetCryptoService(cs *crypto.CryptoService)
CreateUser(user *User) error
GetUserByEmail(email string) (*User, error)
GetUserByID(userID string) (*User, error)
GetAllUsers() ([]string, error)
UpdateUserOTPVerified(userID string, verified bool) error
GetAIModels(userID string) ([]*AIModelConfig, error)
UpdateAIModel(userID, id string, enabled bool, apiKey, customAPIURL, customModelName string) error
GetExchanges(userID string) ([]*ExchangeConfig, error)
UpdateExchange(userID, id string, enabled bool, apiKey, secretKey string, testnet bool, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey string) error
CreateAIModel(userID, id, name, provider string, enabled bool, apiKey, customAPIURL string) error
CreateExchange(userID, id, name, typ string, enabled bool, apiKey, secretKey string, testnet bool, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey string) error
CreateTrader(trader *TraderRecord) error
GetTraders(userID string) ([]*TraderRecord, error)
UpdateTraderStatus(userID, id string, isRunning bool) error
UpdateTrader(trader *TraderRecord) error
UpdateTraderInitialBalance(userID, id string, newBalance float64) error
UpdateTraderCustomPrompt(userID, id string, customPrompt string, overrideBase bool) error
DeleteTrader(userID, id string) error
GetTraderConfig(userID, traderID string) (*TraderRecord, *AIModelConfig, *ExchangeConfig, error)
GetSystemConfig(key string) (string, error)
SetSystemConfig(key, value string) error
CreateUserSignalSource(userID, coinPoolURL, oiTopURL string) error
GetUserSignalSource(userID string) (*UserSignalSource, error)
UpdateUserSignalSource(userID, coinPoolURL, oiTopURL string) error
GetCustomCoins() []string
LoadBetaCodesFromFile(filePath string) error
ValidateBetaCode(code string) (bool, error)
UseBetaCode(code, userEmail string) error
GetBetaCodeStats() (total, used int, err error)
Close() error
}
// Database 配置数据库
type Database struct {
db *sql.DB
cryptoService *crypto.CryptoService
}
// NewDatabase 创建配置数据库
func NewDatabase(dbPath string) (*Database, error) {
db, err := sql.Open("sqlite", 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{
"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"`
Email string `json:"email"`
PasswordHash string `json:"-"` // 不返回到前端
OTPSecret string `json:"-"` // 不返回到前端
OTPVerified bool `json:"otp_verified"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// AIModelConfig AI模型配置
type AIModelConfig struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Name string `json:"name"`
Provider string `json:"provider"`
Enabled bool `json:"enabled"`
APIKey string `json:"apiKey"`
CustomAPIURL string `json:"customApiUrl"`
CustomModelName string `json:"customModelName"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// 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"` // For Binance: API Key; For Hyperliquid: Agent Private Key (should have ~0 balance)
SecretKey string `json:"secretKey"` // For Binance: Secret Key; Not used for Hyperliquid
Testnet bool `json:"testnet"`
// Hyperliquid Agent Wallet configuration (following official best practices)
// Reference: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/nonces-and-api-wallets
HyperliquidWalletAddr string `json:"hyperliquidWalletAddr"` // Main Wallet Address (holds funds, never expose private key)
// 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"`
}
// TraderRecord 交易员配置(数据库实体)
type TraderRecord struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Name string `json:"name"`
AIModelID string `json:"ai_model_id"`
ExchangeID string `json:"exchange_id"`
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=逐仓)
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// UserSignalSource 用户信号源配置
type UserSignalSource struct {
ID int `json:"id"`
UserID string `json:"user_id"`
CoinPoolURL string `json:"coin_pool_url"`
OITopURL string `json:"oi_top_url"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// GenerateOTPSecret 生成OTP密钥
func GenerateOTPSecret() (string, error) {
secret := make([]byte, 20)
_, err := rand.Read(secret)
if err != nil {
return "", 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
}
// UpdateUserPassword 更新用户密码
func (d *Database) UpdateUserPassword(userID, passwordHash string) error {
_, err := d.db.Exec(`
UPDATE users
SET password_hash = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`, passwordHash, 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
}
// 解密API Key
model.APIKey = d.decryptSensitiveData(model.APIKey)
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),更新它
encryptedAPIKey := d.encryptSensitiveData(apiKey)
_, 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, encryptedAPIKey, 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)
encryptedAPIKey := d.encryptSensitiveData(apiKey)
_, 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, encryptedAPIKey, 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)
encryptedAPIKey := d.encryptSensitiveData(apiKey)
_, 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, encryptedAPIKey, 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
}
// 解密敏感字段
exchange.APIKey = d.decryptSensitiveData(exchange.APIKey)
exchange.SecretKey = d.decryptSensitiveData(exchange.SecretKey)
exchange.AsterPrivateKey = d.decryptSensitiveData(exchange.AsterPrivateKey)
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)
// 加密敏感字段
encryptedAPIKey := d.encryptSensitiveData(apiKey)
encryptedSecretKey := d.encryptSensitiveData(secretKey)
encryptedAsterPrivateKey := d.encryptSensitiveData(asterPrivateKey)
// 首先尝试更新现有的用户配置
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, encryptedAPIKey, encryptedSecretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, encryptedAsterPrivateKey, 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 {
// 加密敏感字段
encryptedAPIKey := d.encryptSensitiveData(apiKey)
encryptedSecretKey := d.encryptSensitiveData(secretKey)
encryptedAsterPrivateKey := d.encryptSensitiveData(asterPrivateKey)
_, 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, encryptedAPIKey, encryptedSecretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, encryptedAsterPrivateKey)
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,
COALESCE(t.btc_eth_leverage, 5) as btc_eth_leverage,
COALESCE(t.altcoin_leverage, 5) as altcoin_leverage,
COALESCE(t.trading_symbols, '') as trading_symbols,
COALESCE(t.use_coin_pool, 0) as use_coin_pool,
COALESCE(t.use_oi_top, 0) as use_oi_top,
COALESCE(t.custom_prompt, '') as custom_prompt,
COALESCE(t.override_base_prompt, 0) as override_base_prompt,
COALESCE(t.system_prompt_template, 'default') as system_prompt_template,
COALESCE(t.is_cross_margin, 1) as is_cross_margin,
t.created_at, t.updated_at,
a.id, a.user_id, a.name, a.provider, a.enabled, a.api_key,
COALESCE(a.custom_api_url, '') as custom_api_url,
COALESCE(a.custom_model_name, '') as custom_model_name,
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
}
// SetCryptoService 设置加密服务
func (d *Database) SetCryptoService(cs *crypto.CryptoService) {
d.cryptoService = cs
}
// encryptSensitiveData 加密敏感数据用于存储
func (d *Database) encryptSensitiveData(plaintext string) string {
if d.cryptoService == nil || plaintext == "" {
return plaintext
}
encrypted, err := d.cryptoService.EncryptForStorage(plaintext)
if err != nil {
log.Printf("⚠️ 加密失败: %v", err)
return plaintext // 返回明文作为降级处理
}
return encrypted
}
// decryptSensitiveData 解密敏感数据
func (d *Database) decryptSensitiveData(encrypted string) string {
if d.cryptoService == nil || encrypted == "" {
return encrypted
}
// 如果不是加密格式,直接返回
if !d.cryptoService.IsEncryptedStorageValue(encrypted) {
return encrypted
}
decrypted, err := d.cryptoService.DecryptFromStorage(encrypted)
if err != nil {
log.Printf("⚠️ 解密失败: %v", err)
return encrypted // 返回加密文本作为降级处理
}
return decrypted
}