refactor: drop sqlite fallback and admin mode

This commit is contained in:
icy
2025-11-06 17:52:30 +08:00
parent 579c73a81b
commit 8e7e50ef5f
27 changed files with 293 additions and 2123 deletions

View File

@@ -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头"})

View File

@@ -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"`

View File

@@ -1,5 +1,4 @@
{
"admin_mode": true,
"beta_mode": false,
"leverage": {
"btc_eth_leverage": 5,
@@ -24,4 +23,4 @@
"log": {
"level": "info"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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()
}
}

View File

@@ -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);
CREATE INDEX IF NOT EXISTS idx_beta_codes_used ON beta_codes(used);

View File

@@ -105,4 +105,4 @@ networks:
volumes:
postgres_data:
redis_data:
redis_data:

View File

@@ -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 |

View File

@@ -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 的 2FAGoogle Authenticator
- Bcrypt 密码哈希
- 管理员模式(简化的单用户模式)
---

View File

@@ -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;"
```
---

View File

@@ -152,18 +152,17 @@ docker compose up -d
## 数据与隐私
### 我的数据存储在哪里?
所有数据都**本地存储**在您的机器上,使用 SQLite 数据库
- `config.db` - 交易员配置
- `trading.db` - 交易历史
所有数据都**本地存储**在 PostgreSQLDocker 卷 `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;"
```
---

9
go.mod
View File

@@ -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
)

38
go.sum
View File

@@ -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=

View File

@@ -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 "🎉 导入完成!"

27
main.go
View File

@@ -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()

View File

@@ -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()

View File

@@ -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;

View File

@@ -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;

View File

@@ -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}"

160
scripts/import_default_patch.sh Executable file
View File

@@ -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 "🎉 操作完成"

View File

@@ -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;

View File

@@ -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 <LandingPage />
}
@@ -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)

View File

@@ -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),

View File

@@ -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}
</div>
</div>
{!isAdminMode && onLogout && (
{onLogout && (
<button
onClick={() => {
onLogout()
@@ -763,7 +761,7 @@ export default function HeaderBar({
</div>
</div>
</div>
{!isAdminMode && onLogout && (
{onLogout && (
<button
onClick={() => {
onLogout()

View File

@@ -1,5 +1,4 @@
import React, { createContext, useContext, useState, useEffect } from 'react'
import { getSystemConfig } from '../lib/config'
interface User {
id: string
@@ -49,37 +48,15 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
// 先检查是否为管理员模式(使用带缓存的系统配置获取)
getSystemConfig()
.then((data) => {
if (data.admin_mode) {
// 管理员模式下模拟admin用户
setUser({ id: 'admin', email: 'admin@localhost' })
setToken('admin-mode')
} else {
// 非管理员模式检查本地存储中是否有token
const savedToken = localStorage.getItem('auth_token')
const savedUser = localStorage.getItem('auth_user')
const savedToken = localStorage.getItem('auth_token')
const savedUser = localStorage.getItem('auth_user')
if (savedToken && savedUser) {
setToken(savedToken)
setUser(JSON.parse(savedUser))
}
}
setIsLoading(false)
})
.catch((err) => {
console.error('Failed to fetch system config:', err)
// 发生错误时,继续检查本地存储
const savedToken = localStorage.getItem('auth_token')
const savedUser = localStorage.getItem('auth_user')
if (savedToken && savedUser) {
setToken(savedToken)
setUser(JSON.parse(savedUser))
}
if (savedToken && savedUser) {
setToken(savedToken)
setUser(JSON.parse(savedUser))
}
setIsLoading(false)
})
setIsLoading(false)
}, [])
const login = async (email: string, password: string) => {

View File

@@ -11,7 +11,6 @@ export const translations = {
competition: 'Competition',
running: 'RUNNING',
stopped: 'STOPPED',
adminMode: 'Admin Mode',
logout: 'Logout',
switchTrader: 'Switch Trader:',
view: 'View',
@@ -504,7 +503,6 @@ export const translations = {
competition: '竞赛',
running: '运行中',
stopped: '已停止',
adminMode: '管理员模式',
logout: '退出',
switchTrader: '切换交易员:',
view: '查看',

View File

@@ -1,6 +1,8 @@
export interface SystemConfig {
admin_mode: boolean
beta_mode: boolean
default_coins?: string[]
btc_eth_leverage?: number
altcoin_leverage?: number
}
let configPromise: Promise<SystemConfig> | null = null