mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-15 00:36:56 +08:00
Merge branch 'main' into beta
This commit is contained in:
@@ -166,8 +166,13 @@ func (s *Server) handleGetSystemConfig(c *gin.Context) {
|
|||||||
altcoinLeverage = val
|
altcoinLeverage = val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取内测模式配置
|
||||||
|
betaModeStr, _ := s.database.GetSystemConfig("beta_mode")
|
||||||
|
betaMode := betaModeStr == "true"
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"admin_mode": auth.IsAdminMode(),
|
"admin_mode": auth.IsAdminMode(),
|
||||||
|
"beta_mode": betaMode,
|
||||||
"default_coins": defaultCoins,
|
"default_coins": defaultCoins,
|
||||||
"btc_eth_leverage": btcEthLeverage,
|
"btc_eth_leverage": btcEthLeverage,
|
||||||
"altcoin_leverage": altcoinLeverage,
|
"altcoin_leverage": altcoinLeverage,
|
||||||
@@ -1168,6 +1173,7 @@ func (s *Server) handleRegister(c *gin.Context) {
|
|||||||
var req struct {
|
var req struct {
|
||||||
Email string `json:"email" binding:"required,email"`
|
Email string `json:"email" binding:"required,email"`
|
||||||
Password string `json:"password" binding:"required,min=6"`
|
Password string `json:"password" binding:"required,min=6"`
|
||||||
|
BetaCode string `json:"beta_code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
@@ -1175,6 +1181,27 @@ func (s *Server) handleRegister(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查是否开启了内测模式
|
||||||
|
betaModeStr, _ := s.database.GetSystemConfig("beta_mode")
|
||||||
|
if betaModeStr == "true" {
|
||||||
|
// 内测模式下必须提供有效的内测码
|
||||||
|
if req.BetaCode == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "内测期间,注册需要提供内测码"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证内测码
|
||||||
|
isValid, err := s.database.ValidateBetaCode(req.BetaCode)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "验证内测码失败"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !isValid {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "内测码无效或已被使用"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 检查邮箱是否已存在
|
// 检查邮箱是否已存在
|
||||||
_, err := s.database.GetUserByEmail(req.Email)
|
_, err := s.database.GetUserByEmail(req.Email)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -1212,6 +1239,18 @@ func (s *Server) handleRegister(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果是内测模式,标记内测码为已使用
|
||||||
|
betaModeStr2, _ := s.database.GetSystemConfig("beta_mode")
|
||||||
|
if betaModeStr2 == "true" && req.BetaCode != "" {
|
||||||
|
err := s.database.UseBetaCode(req.BetaCode, req.Email)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("⚠️ 标记内测码为已使用失败: %v", err)
|
||||||
|
// 这里不返回错误,因为用户已经创建成功
|
||||||
|
} else {
|
||||||
|
log.Printf("✓ 内测码 %s 已被用户 %s 使用", req.BetaCode, req.Email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 返回OTP设置信息
|
// 返回OTP设置信息
|
||||||
qrCodeURL := auth.GetOTPQRCodeURL(otpSecret, req.Email)
|
qrCodeURL := auth.GetOTPQRCodeURL(otpSecret, req.Email)
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -125,6 +126,15 @@ func (d *Database) createTables() error {
|
|||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
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
|
// 触发器:自动更新 updated_at
|
||||||
`CREATE TRIGGER IF NOT EXISTS update_users_updated_at
|
`CREATE TRIGGER IF NOT EXISTS update_users_updated_at
|
||||||
AFTER UPDATE ON users
|
AFTER UPDATE ON users
|
||||||
@@ -246,6 +256,7 @@ func (d *Database) initDefaultData() error {
|
|||||||
// 初始化系统配置 - 创建所有字段,设置默认值,后续由config.json同步更新
|
// 初始化系统配置 - 创建所有字段,设置默认值,后续由config.json同步更新
|
||||||
systemConfigs := map[string]string{
|
systemConfigs := map[string]string{
|
||||||
"admin_mode": "true", // 默认开启管理员模式,便于首次使用
|
"admin_mode": "true", // 默认开启管理员模式,便于首次使用
|
||||||
|
"beta_mode": "false", // 默认关闭内测模式
|
||||||
"api_server_port": "8080", // 默认API端口
|
"api_server_port": "8080", // 默认API端口
|
||||||
"use_default_coins": "true", // 默认使用内置币种列表
|
"use_default_coins": "true", // 默认使用内置币种列表
|
||||||
"default_coins": `["BTCUSDT","ETHUSDT","SOLUSDT","BNBUSDT","XRPUSDT","DOGEUSDT","ADAUSDT","HYPEUSDT"]`, // 默认币种列表(JSON格式)
|
"default_coins": `["BTCUSDT","ETHUSDT","SOLUSDT","BNBUSDT","XRPUSDT","DOGEUSDT","ADAUSDT","HYPEUSDT"]`, // 默认币种列表(JSON格式)
|
||||||
@@ -943,4 +954,106 @@ func (d *Database) UpdateUserSignalSource(userID, coinPoolURL, oiTopURL string)
|
|||||||
// Close 关闭数据库连接
|
// Close 关闭数据库连接
|
||||||
func (d *Database) Close() error {
|
func (d *Database) Close() error {
|
||||||
return d.db.Close()
|
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
|
||||||
}
|
}
|
||||||
221
generate_beta_code.sh
Executable file
221
generate_beta_code.sh
Executable file
@@ -0,0 +1,221 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 内测码生成脚本
|
||||||
|
# 生成6位不重复的内测码并写入 beta_codes.txt
|
||||||
|
|
||||||
|
BETA_CODES_FILE="beta_codes.txt"
|
||||||
|
COUNT=1
|
||||||
|
LIST_ONLY=false
|
||||||
|
CODE_LENGTH=6
|
||||||
|
|
||||||
|
# 字符集(避免易混淆字符:0/O, 1/I/l)
|
||||||
|
CHARSET="23456789abcdefghjkmnpqrstuvwxyz"
|
||||||
|
|
||||||
|
# 显示帮助信息
|
||||||
|
show_help() {
|
||||||
|
cat << EOF
|
||||||
|
用法: $0 [选项]
|
||||||
|
|
||||||
|
选项:
|
||||||
|
-c COUNT 生成内测码数量 (默认: 1)
|
||||||
|
-l 列出现有内测码
|
||||||
|
-f FILE 内测码文件路径 (默认: beta_codes.txt)
|
||||||
|
-h 显示此帮助信息
|
||||||
|
|
||||||
|
示例:
|
||||||
|
$0 -c 10 # 生成10个内测码
|
||||||
|
$0 -l # 列出现有内测码
|
||||||
|
$0 -f custom.txt -c 5 # 在自定义文件中生成5个内测码
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# 生成随机内测码
|
||||||
|
generate_beta_code() {
|
||||||
|
local length="$1"
|
||||||
|
local charset="$2"
|
||||||
|
local code=""
|
||||||
|
|
||||||
|
for ((i=0; i<length; i++)); do
|
||||||
|
local random_index=$((RANDOM % ${#charset}))
|
||||||
|
code+="${charset:$random_index:1}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$code"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 读取现有内测码
|
||||||
|
read_existing_codes() {
|
||||||
|
local file="$1"
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
grep -v '^$' "$file" 2>/dev/null | tr -d ' \t' | grep -v '^#' || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查内测码是否已存在
|
||||||
|
code_exists() {
|
||||||
|
local code="$1"
|
||||||
|
local file="$2"
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
grep -Fxq "$code" "$file" 2>/dev/null
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 添加内测码到文件
|
||||||
|
add_code_to_file() {
|
||||||
|
local code="$1"
|
||||||
|
local file="$2"
|
||||||
|
echo "$code" >> "$file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 验证内测码格式
|
||||||
|
validate_code() {
|
||||||
|
local code="$1"
|
||||||
|
# 检查长度
|
||||||
|
if [ ${#code} -ne $CODE_LENGTH ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
# 检查字符是否都在允许的字符集中
|
||||||
|
if [[ ! "$code" =~ ^[$CHARSET]+$ ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 去重并排序内测码
|
||||||
|
dedupe_and_sort_codes() {
|
||||||
|
local file="$1"
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
# 过滤空行和注释,去重并排序
|
||||||
|
grep -v '^$' "$file" | grep -v '^#' | sort -u > "${file}.tmp" && mv "${file}.tmp" "$file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 解析命令行参数
|
||||||
|
while getopts "c:lf:h" opt; do
|
||||||
|
case $opt in
|
||||||
|
c)
|
||||||
|
COUNT="$OPTARG"
|
||||||
|
if ! [[ "$COUNT" =~ ^[0-9]+$ ]] || [ "$COUNT" -lt 1 ]; then
|
||||||
|
echo "错误: count 必须是正整数" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
l)
|
||||||
|
LIST_ONLY=true
|
||||||
|
;;
|
||||||
|
f)
|
||||||
|
BETA_CODES_FILE="$OPTARG"
|
||||||
|
;;
|
||||||
|
h)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
\?)
|
||||||
|
echo "无效选项: -$OPTARG" >&2
|
||||||
|
echo "使用 -h 查看帮助信息" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# 如果是列出现有内测码
|
||||||
|
if [ "$LIST_ONLY" = true ]; then
|
||||||
|
if [ -f "$BETA_CODES_FILE" ]; then
|
||||||
|
existing_codes=$(read_existing_codes "$BETA_CODES_FILE")
|
||||||
|
if [ -z "$existing_codes" ]; then
|
||||||
|
echo "内测码列表为空"
|
||||||
|
else
|
||||||
|
count=$(echo "$existing_codes" | wc -l | tr -d ' ')
|
||||||
|
echo "当前内测码 ($count 个):"
|
||||||
|
echo "$existing_codes" | nl -w3 -s'. '
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "内测码文件不存在: $BETA_CODES_FILE"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 读取现有内测码
|
||||||
|
existing_codes=$(read_existing_codes "$BETA_CODES_FILE")
|
||||||
|
|
||||||
|
# 生成新内测码
|
||||||
|
new_codes=()
|
||||||
|
max_attempts=1000 # 防止无限循环
|
||||||
|
|
||||||
|
echo "正在生成 $COUNT 个内测码..."
|
||||||
|
|
||||||
|
for ((i=1; i<=COUNT; i++)); do
|
||||||
|
attempts=0
|
||||||
|
while [ $attempts -lt $max_attempts ]; do
|
||||||
|
code=$(generate_beta_code $CODE_LENGTH "$CHARSET")
|
||||||
|
|
||||||
|
# 验证格式
|
||||||
|
if ! validate_code "$code"; then
|
||||||
|
((attempts++))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查是否已存在
|
||||||
|
if code_exists "$code" "$BETA_CODES_FILE"; then
|
||||||
|
((attempts++))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查是否与本次生成的重复
|
||||||
|
duplicate=false
|
||||||
|
for existing_code in "${new_codes[@]}"; do
|
||||||
|
if [ "$code" = "$existing_code" ]; then
|
||||||
|
duplicate=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$duplicate" = false ]; then
|
||||||
|
new_codes+=("$code")
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
((attempts++))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $attempts -eq $max_attempts ]; then
|
||||||
|
echo "警告: 生成第 $i 个内测码时达到最大尝试次数,可能字符空间不足" >&2
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 检查是否成功生成了内测码
|
||||||
|
if [ ${#new_codes[@]} -eq 0 ]; then
|
||||||
|
echo "未能生成任何新的内测码"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 添加到文件
|
||||||
|
for code in "${new_codes[@]}"; do
|
||||||
|
add_code_to_file "$code" "$BETA_CODES_FILE"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 去重并排序
|
||||||
|
dedupe_and_sort_codes "$BETA_CODES_FILE"
|
||||||
|
|
||||||
|
echo "成功生成 ${#new_codes[@]} 个内测码:"
|
||||||
|
printf ' %s\n' "${new_codes[@]}"
|
||||||
|
echo
|
||||||
|
echo "内测码文件: $BETA_CODES_FILE"
|
||||||
|
|
||||||
|
# 显示当前总数
|
||||||
|
if [ -f "$BETA_CODES_FILE" ]; then
|
||||||
|
total_count=$(read_existing_codes "$BETA_CODES_FILE" | wc -l | tr -d ' ')
|
||||||
|
echo "当前内测码总计: $total_count 个"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 显示文件头部信息(如果是新文件)
|
||||||
|
if [ ! -s "$BETA_CODES_FILE" ] || [ $(wc -l < "$BETA_CODES_FILE") -eq ${#new_codes[@]} ]; then
|
||||||
|
echo
|
||||||
|
echo "内测码规则:"
|
||||||
|
echo "- 长度: $CODE_LENGTH 位"
|
||||||
|
echo "- 字符集: 数字 2-9, 小写字母 a-z (排除 0,1,i,l,o 避免混淆)"
|
||||||
|
echo "- 每个内测码唯一且不重复"
|
||||||
|
fi
|
||||||
42
main.go
42
main.go
@@ -25,6 +25,7 @@ type LeverageConfig struct {
|
|||||||
// ConfigFile 配置文件结构,只包含需要同步到数据库的字段
|
// ConfigFile 配置文件结构,只包含需要同步到数据库的字段
|
||||||
type ConfigFile struct {
|
type ConfigFile struct {
|
||||||
AdminMode bool `json:"admin_mode"`
|
AdminMode bool `json:"admin_mode"`
|
||||||
|
BetaMode bool `json:"beta_mode"`
|
||||||
APIServerPort int `json:"api_server_port"`
|
APIServerPort int `json:"api_server_port"`
|
||||||
UseDefaultCoins bool `json:"use_default_coins"`
|
UseDefaultCoins bool `json:"use_default_coins"`
|
||||||
DefaultCoins []string `json:"default_coins"`
|
DefaultCoins []string `json:"default_coins"`
|
||||||
@@ -62,6 +63,7 @@ func syncConfigToDatabase(database *config.Database) error {
|
|||||||
// 同步各配置项到数据库
|
// 同步各配置项到数据库
|
||||||
configs := map[string]string{
|
configs := map[string]string{
|
||||||
"admin_mode": fmt.Sprintf("%t", configFile.AdminMode),
|
"admin_mode": fmt.Sprintf("%t", configFile.AdminMode),
|
||||||
|
"beta_mode": fmt.Sprintf("%t", configFile.BetaMode),
|
||||||
"api_server_port": strconv.Itoa(configFile.APIServerPort),
|
"api_server_port": strconv.Itoa(configFile.APIServerPort),
|
||||||
"use_default_coins": fmt.Sprintf("%t", configFile.UseDefaultCoins),
|
"use_default_coins": fmt.Sprintf("%t", configFile.UseDefaultCoins),
|
||||||
"coin_pool_api_url": configFile.CoinPoolAPIURL,
|
"coin_pool_api_url": configFile.CoinPoolAPIURL,
|
||||||
@@ -105,6 +107,41 @@ func syncConfigToDatabase(database *config.Database) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadBetaCodesToDatabase 加载内测码文件到数据库
|
||||||
|
func loadBetaCodesToDatabase(database *config.Database) error {
|
||||||
|
betaCodeFile := "beta_codes.txt"
|
||||||
|
|
||||||
|
// 检查内测码文件是否存在
|
||||||
|
if _, err := os.Stat(betaCodeFile); os.IsNotExist(err) {
|
||||||
|
log.Printf("📄 内测码文件 %s 不存在,跳过加载", betaCodeFile)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件信息
|
||||||
|
fileInfo, err := os.Stat(betaCodeFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("获取内测码文件信息失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("🔄 发现内测码文件 %s (%.1f KB),开始加载...", betaCodeFile, float64(fileInfo.Size())/1024)
|
||||||
|
|
||||||
|
// 加载内测码到数据库
|
||||||
|
err = database.LoadBetaCodesFromFile(betaCodeFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("加载内测码失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示统计信息
|
||||||
|
total, used, err := database.GetBetaCodeStats()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("⚠️ 获取内测码统计失败: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("✅ 内测码加载完成: 总计 %d 个,已使用 %d 个,剩余 %d 个", total, used, total-used)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println("╔════════════════════════════════════════════════════════════╗")
|
fmt.Println("╔════════════════════════════════════════════════════════════╗")
|
||||||
fmt.Println("║ 🤖 AI多模型交易系统 - 支持 DeepSeek & Qwen ║")
|
fmt.Println("║ 🤖 AI多模型交易系统 - 支持 DeepSeek & Qwen ║")
|
||||||
@@ -129,6 +166,11 @@ func main() {
|
|||||||
log.Printf("⚠️ 同步config.json到数据库失败: %v", err)
|
log.Printf("⚠️ 同步config.json到数据库失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载内测码到数据库
|
||||||
|
if err := loadBetaCodesToDatabase(database); err != nil {
|
||||||
|
log.Printf("⚠️ 加载内测码到数据库失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 获取系统配置
|
// 获取系统配置
|
||||||
useDefaultCoinsStr, _ := database.GetSystemConfig("use_default_coins")
|
useDefaultCoinsStr, _ := database.GetSystemConfig("use_default_coins")
|
||||||
useDefaultCoins := useDefaultCoinsStr == "true"
|
useDefaultCoins := useDefaultCoinsStr == "true"
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
import { useLanguage } from '../contexts/LanguageContext';
|
import { useLanguage } from '../contexts/LanguageContext';
|
||||||
import { t } from '../i18n/translations';
|
import { t } from '../i18n/translations';
|
||||||
|
import { getSystemConfig } from '../lib/config';
|
||||||
|
|
||||||
export function RegisterPage() {
|
export function RegisterPage() {
|
||||||
const { language } = useLanguage();
|
const { language } = useLanguage();
|
||||||
@@ -10,12 +11,23 @@ export function RegisterPage() {
|
|||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [confirmPassword, setConfirmPassword] = useState('');
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
|
const [betaCode, setBetaCode] = useState('');
|
||||||
const [otpCode, setOtpCode] = useState('');
|
const [otpCode, setOtpCode] = useState('');
|
||||||
const [userID, setUserID] = useState('');
|
const [userID, setUserID] = useState('');
|
||||||
const [otpSecret, setOtpSecret] = useState('');
|
const [otpSecret, setOtpSecret] = useState('');
|
||||||
const [qrCodeURL, setQrCodeURL] = useState('');
|
const [qrCodeURL, setQrCodeURL] = useState('');
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [betaMode, setBetaMode] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 获取系统配置,检查是否开启内测模式
|
||||||
|
getSystemConfig().then(config => {
|
||||||
|
setBetaMode(config.beta_mode || false);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('Failed to fetch system config:', err);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleRegister = async (e: React.FormEvent) => {
|
const handleRegister = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -31,9 +43,14 @@ export function RegisterPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (betaMode && !betaCode.trim()) {
|
||||||
|
setError('内测期间,注册需要提供内测码');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const result = await register(email, password);
|
const result = await register(email, password, betaCode.trim() || undefined);
|
||||||
|
|
||||||
if (result.success && result.userID) {
|
if (result.success && result.userID) {
|
||||||
setUserID(result.userID);
|
setUserID(result.userID);
|
||||||
@@ -137,6 +154,27 @@ export function RegisterPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{betaMode && (
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
|
||||||
|
内测码 *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={betaCode}
|
||||||
|
onChange={(e) => setBetaCode(e.target.value.replace(/[^a-z0-9]/gi, '').toLowerCase())}
|
||||||
|
className="w-full px-3 py-2 rounded font-mono"
|
||||||
|
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
|
||||||
|
placeholder="请输入6位内测码"
|
||||||
|
maxLength={6}
|
||||||
|
required={betaMode}
|
||||||
|
/>
|
||||||
|
<p className="text-xs mt-1" style={{ color: '#848E9C' }}>
|
||||||
|
内测码由6位字母数字组成,区分大小写
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="text-sm px-3 py-2 rounded" style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }}>
|
<div className="text-sm px-3 py-2 rounded" style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }}>
|
||||||
{error}
|
{error}
|
||||||
@@ -145,7 +183,7 @@ export function RegisterPage() {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading}
|
disabled={loading || (betaMode && !betaCode.trim())}
|
||||||
className="w-full px-4 py-2 rounded text-sm font-semibold transition-all hover:scale-105 disabled:opacity-50"
|
className="w-full px-4 py-2 rounded text-sm font-semibold transition-all hover:scale-105 disabled:opacity-50"
|
||||||
style={{ background: '#F0B90B', color: '#000' }}
|
style={{ background: '#F0B90B', color: '#000' }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ interface AuthContextType {
|
|||||||
user: User | null;
|
user: User | null;
|
||||||
token: string | null;
|
token: string | null;
|
||||||
login: (email: string, password: string) => Promise<{ success: boolean; message?: string; userID?: string; requiresOTP?: boolean }>;
|
login: (email: string, password: string) => Promise<{ success: boolean; message?: string; userID?: string; requiresOTP?: boolean }>;
|
||||||
register: (email: string, password: string) => Promise<{ success: boolean; message?: string; userID?: string; otpSecret?: string; qrCodeURL?: string }>;
|
register: (email: string, password: string, betaCode?: string) => Promise<{ success: boolean; message?: string; userID?: string; otpSecret?: string; qrCodeURL?: string }>;
|
||||||
verifyOTP: (userID: string, otpCode: string) => Promise<{ success: boolean; message?: string }>;
|
verifyOTP: (userID: string, otpCode: string) => Promise<{ success: boolean; message?: string }>;
|
||||||
completeRegistration: (userID: string, otpCode: string) => Promise<{ success: boolean; message?: string }>;
|
completeRegistration: (userID: string, otpCode: string) => Promise<{ success: boolean; message?: string }>;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
@@ -89,14 +89,19 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
return { success: false, message: '未知错误' };
|
return { success: false, message: '未知错误' };
|
||||||
};
|
};
|
||||||
|
|
||||||
const register = async (email: string, password: string) => {
|
const register = async (email: string, password: string, betaCode?: string) => {
|
||||||
try {
|
try {
|
||||||
|
const requestBody: { email: string; password: string; beta_code?: string } = { email, password };
|
||||||
|
if (betaCode) {
|
||||||
|
requestBody.beta_code = betaCode;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch('/api/register', {
|
const response = await fetch('/api/register', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ email, password }),
|
body: JSON.stringify(requestBody),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|||||||
Reference in New Issue
Block a user