package api import ( "fmt" "log" "net/http" "nofx/auth" "nofx/config" "nofx/manager" "strings" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // Server HTTP API服务器 type Server struct { router *gin.Engine traderManager *manager.TraderManager database *config.Database port int } // NewServer 创建API服务器 func NewServer(traderManager *manager.TraderManager, database *config.Database, port int) *Server { // 设置为Release模式(减少日志输出) gin.SetMode(gin.ReleaseMode) router := gin.Default() // 启用CORS router.Use(corsMiddleware()) s := &Server{ router: router, traderManager: traderManager, database: database, port: port, } // 设置路由 s.setupRoutes() return s } // corsMiddleware CORS中间件 func corsMiddleware() gin.HandlerFunc { return func(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(http.StatusOK) return } c.Next() } } // setupRoutes 设置路由 func (s *Server) setupRoutes() { // 健康检查 s.router.Any("/health", s.handleHealth) // API路由组 api := s.router.Group("/api") { // 认证相关路由(无需认证) api.POST("/register", s.handleRegister) api.POST("/login", s.handleLogin) api.POST("/verify-otp", s.handleVerifyOTP) api.POST("/complete-registration", s.handleCompleteRegistration) // 系统支持的模型和交易所(无需认证) api.GET("/supported-models", s.handleGetSupportedModels) api.GET("/supported-exchanges", s.handleGetSupportedExchanges) // 系统配置(无需认证) api.GET("/config", s.handleGetSystemConfig) // 需要认证的路由 protected := api.Group("/", s.authMiddleware()) { // AI交易员管理 protected.GET("/traders", s.handleTraderList) protected.POST("/traders", s.handleCreateTrader) protected.DELETE("/traders/:id", s.handleDeleteTrader) protected.POST("/traders/:id/start", s.handleStartTrader) protected.POST("/traders/:id/stop", s.handleStopTrader) protected.PUT("/traders/:id/prompt", s.handleUpdateTraderPrompt) // AI模型配置 protected.GET("/models", s.handleGetModelConfigs) protected.PUT("/models", s.handleUpdateModelConfigs) // 交易所配置 protected.GET("/exchanges", s.handleGetExchangeConfigs) protected.PUT("/exchanges", s.handleUpdateExchangeConfigs) // 竞赛总览 protected.GET("/competition", s.handleCompetition) // 指定trader的数据(使用query参数 ?trader_id=xxx) protected.GET("/status", s.handleStatus) protected.GET("/account", s.handleAccount) protected.GET("/positions", s.handlePositions) protected.GET("/decisions", s.handleDecisions) protected.GET("/decisions/latest", s.handleLatestDecisions) protected.GET("/statistics", s.handleStatistics) protected.GET("/equity-history", s.handleEquityHistory) protected.GET("/performance", s.handlePerformance) } } } // handleHealth 健康检查 func (s *Server) handleHealth(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "ok", "time": c.Request.Context().Value("time"), }) } // handleGetSystemConfig 获取系统配置(客户端需要知道的配置) func (s *Server) handleGetSystemConfig(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "admin_mode": auth.IsAdminMode(), }) } // getTraderFromQuery 从query参数获取trader func (s *Server) getTraderFromQuery(c *gin.Context) (*manager.TraderManager, string, error) { userID := c.GetString("user_id") traderID := c.Query("trader_id") // 确保用户的交易员已加载到内存中 err := s.traderManager.LoadUserTraders(s.database, userID) if err != nil { log.Printf("⚠️ 加载用户 %s 的交易员失败: %v", userID, err) } if traderID == "" { // 如果没有指定trader_id,返回该用户的第一个trader ids := s.traderManager.GetTraderIDs() if len(ids) == 0 { return nil, "", fmt.Errorf("没有可用的trader") } // 获取用户的交易员列表,优先返回用户自己的交易员 userTraders, err := s.database.GetTraders(userID) if err == nil && len(userTraders) > 0 { traderID = userTraders[0].ID } else { traderID = ids[0] } } return s.traderManager, traderID, nil } // AI交易员管理相关结构体 type CreateTraderRequest struct { Name string `json:"name" binding:"required"` AIModelID string `json:"ai_model_id" binding:"required"` ExchangeID string `json:"exchange_id" binding:"required"` InitialBalance float64 `json:"initial_balance"` CustomPrompt string `json:"custom_prompt"` OverrideBasePrompt bool `json:"override_base_prompt"` IsCrossMargin *bool `json:"is_cross_margin"` // 指针类型,nil表示使用默认值true } type ModelConfig struct { ID string `json:"id"` Name string `json:"name"` Provider string `json:"provider"` Enabled bool `json:"enabled"` APIKey string `json:"apiKey,omitempty"` } type ExchangeConfig struct { ID string `json:"id"` Name string `json:"name"` Type string `json:"type"` // "cex" or "dex" Enabled bool `json:"enabled"` APIKey string `json:"apiKey,omitempty"` SecretKey string `json:"secretKey,omitempty"` Testnet bool `json:"testnet,omitempty"` } type UpdateModelConfigRequest struct { Models map[string]struct { Enabled bool `json:"enabled"` APIKey string `json:"api_key"` } `json:"models"` } type UpdateExchangeConfigRequest struct { Exchanges map[string]struct { Enabled bool `json:"enabled"` APIKey string `json:"api_key"` SecretKey string `json:"secret_key"` Testnet bool `json:"testnet"` HyperliquidWalletAddr string `json:"hyperliquid_wallet_addr"` AsterUser string `json:"aster_user"` AsterSigner string `json:"aster_signer"` AsterPrivateKey string `json:"aster_private_key"` } `json:"exchanges"` } // handleCreateTrader 创建新的AI交易员 func (s *Server) handleCreateTrader(c *gin.Context) { userID := c.GetString("user_id") var req CreateTraderRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 生成交易员ID traderID := fmt.Sprintf("%s_%s_%d", req.ExchangeID, req.AIModelID, time.Now().Unix()) // 设置默认值 isCrossMargin := true // 默认为全仓模式 if req.IsCrossMargin != nil { isCrossMargin = *req.IsCrossMargin } // 创建交易员配置(数据库实体) trader := &config.TraderRecord{ ID: traderID, UserID: userID, Name: req.Name, AIModelID: req.AIModelID, ExchangeID: req.ExchangeID, InitialBalance: req.InitialBalance, CustomPrompt: req.CustomPrompt, OverrideBasePrompt: req.OverrideBasePrompt, IsCrossMargin: isCrossMargin, ScanIntervalMinutes: 3, // 默认3分钟 IsRunning: false, } // 保存到数据库 err := s.database.CreateTrader(trader) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("创建交易员失败: %v", err)}) return } // 立即将新交易员加载到TraderManager中 err = s.traderManager.LoadUserTraders(s.database, userID) if err != nil { log.Printf("⚠️ 加载用户交易员到内存失败: %v", err) // 这里不返回错误,因为交易员已经成功创建到数据库 } log.Printf("✓ 创建交易员成功: %s (模型: %s, 交易所: %s)", req.Name, req.AIModelID, req.ExchangeID) c.JSON(http.StatusCreated, gin.H{ "trader_id": traderID, "trader_name": req.Name, "ai_model": req.AIModelID, "is_running": false, }) } // handleDeleteTrader 删除交易员 func (s *Server) handleDeleteTrader(c *gin.Context) { userID := c.GetString("user_id") traderID := c.Param("id") // 从数据库删除 err := s.database.DeleteTrader(userID, traderID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("删除交易员失败: %v", err)}) return } // 如果交易员正在运行,先停止它 if trader, err := s.traderManager.GetTrader(traderID); err == nil { status := trader.GetStatus() if isRunning, ok := status["is_running"].(bool); ok && isRunning { trader.Stop() log.Printf("⏹ 已停止运行中的交易员: %s", traderID) } } log.Printf("✓ 交易员已删除: %s", traderID) c.JSON(http.StatusOK, gin.H{"message": "交易员已删除"}) } // handleStartTrader 启动交易员 func (s *Server) handleStartTrader(c *gin.Context) { traderID := c.Param("id") trader, err := s.traderManager.GetTrader(traderID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在"}) return } // 检查交易员是否已经在运行 status := trader.GetStatus() if isRunning, ok := status["is_running"].(bool); ok && isRunning { c.JSON(http.StatusBadRequest, gin.H{"error": "交易员已在运行中"}) return } // 启动交易员 go func() { log.Printf("▶️ 启动交易员 %s (%s)", traderID, trader.GetName()) if err := trader.Run(); err != nil { log.Printf("❌ 交易员 %s 运行错误: %v", trader.GetName(), err) } }() // 更新数据库中的运行状态 userID := c.GetString("user_id") err = s.database.UpdateTraderStatus(userID, traderID, true) if err != nil { log.Printf("⚠️ 更新交易员状态失败: %v", err) } log.Printf("✓ 交易员 %s 已启动", trader.GetName()) c.JSON(http.StatusOK, gin.H{"message": "交易员已启动"}) } // handleStopTrader 停止交易员 func (s *Server) handleStopTrader(c *gin.Context) { traderID := c.Param("id") trader, err := s.traderManager.GetTrader(traderID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在"}) return } // 检查交易员是否正在运行 status := trader.GetStatus() if isRunning, ok := status["is_running"].(bool); ok && !isRunning { c.JSON(http.StatusBadRequest, gin.H{"error": "交易员已停止"}) return } // 停止交易员 trader.Stop() // 更新数据库中的运行状态 userID := c.GetString("user_id") err = s.database.UpdateTraderStatus(userID, traderID, false) if err != nil { log.Printf("⚠️ 更新交易员状态失败: %v", err) } log.Printf("⏹ 交易员 %s 已停止", trader.GetName()) c.JSON(http.StatusOK, gin.H{"message": "交易员已停止"}) } // handleUpdateTraderPrompt 更新交易员自定义Prompt func (s *Server) handleUpdateTraderPrompt(c *gin.Context) { traderID := c.Param("id") userID := c.GetString("user_id") var req struct { CustomPrompt string `json:"custom_prompt"` OverrideBasePrompt bool `json:"override_base_prompt"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 更新数据库 err := s.database.UpdateTraderCustomPrompt(userID, traderID, req.CustomPrompt, req.OverrideBasePrompt) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("更新自定义prompt失败: %v", err)}) return } // 如果trader在内存中,更新其custom prompt和override设置 trader, err := s.traderManager.GetTrader(traderID) if err == nil { trader.SetCustomPrompt(req.CustomPrompt) trader.SetOverrideBasePrompt(req.OverrideBasePrompt) log.Printf("✓ 已更新交易员 %s 的自定义prompt (覆盖基础=%v)", trader.GetName(), req.OverrideBasePrompt) } c.JSON(http.StatusOK, gin.H{"message": "自定义prompt已更新"}) } // handleGetModelConfigs 获取AI模型配置 func (s *Server) handleGetModelConfigs(c *gin.Context) { userID := c.GetString("user_id") log.Printf("🔍 查询用户 %s 的AI模型配置", userID) models, err := s.database.GetAIModels(userID) if err != nil { log.Printf("❌ 获取AI模型配置失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("获取AI模型配置失败: %v", err)}) return } log.Printf("✅ 找到 %d 个AI模型配置", len(models)) c.JSON(http.StatusOK, models) } // handleUpdateModelConfigs 更新AI模型配置 func (s *Server) handleUpdateModelConfigs(c *gin.Context) { userID := c.GetString("user_id") var req UpdateModelConfigRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 更新每个模型的配置 for modelID, modelData := range req.Models { err := s.database.UpdateAIModel(userID, modelID, modelData.Enabled, modelData.APIKey) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("更新模型 %s 失败: %v", modelID, err)}) return } } log.Printf("✓ AI模型配置已更新: %+v", req.Models) c.JSON(http.StatusOK, gin.H{"message": "模型配置已更新"}) } // handleGetExchangeConfigs 获取交易所配置 func (s *Server) handleGetExchangeConfigs(c *gin.Context) { userID := c.GetString("user_id") log.Printf("🔍 查询用户 %s 的交易所配置", userID) exchanges, err := s.database.GetExchanges(userID) if err != nil { log.Printf("❌ 获取交易所配置失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("获取交易所配置失败: %v", err)}) return } log.Printf("✅ 找到 %d 个交易所配置", len(exchanges)) c.JSON(http.StatusOK, exchanges) } // handleUpdateExchangeConfigs 更新交易所配置 func (s *Server) handleUpdateExchangeConfigs(c *gin.Context) { userID := c.GetString("user_id") var req UpdateExchangeConfigRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 更新每个交易所的配置 for exchangeID, exchangeData := range req.Exchanges { err := s.database.UpdateExchange(userID, exchangeID, exchangeData.Enabled, exchangeData.APIKey, exchangeData.SecretKey, exchangeData.Testnet, exchangeData.HyperliquidWalletAddr, exchangeData.AsterUser, exchangeData.AsterSigner, exchangeData.AsterPrivateKey) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("更新交易所 %s 失败: %v", exchangeID, err)}) return } } log.Printf("✓ 交易所配置已更新: %+v", req.Exchanges) c.JSON(http.StatusOK, gin.H{"message": "交易所配置已更新"}) } // handleTraderList trader列表 func (s *Server) handleTraderList(c *gin.Context) { userID := c.GetString("user_id") traders, err := s.database.GetTraders(userID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("获取交易员列表失败: %v", err)}) return } result := make([]map[string]interface{}, 0, len(traders)) for _, trader := range traders { // 获取实时运行状态 isRunning := trader.IsRunning if at, err := s.traderManager.GetTrader(trader.ID); err == nil { status := at.GetStatus() if running, ok := status["is_running"].(bool); ok { isRunning = running } } result = append(result, map[string]interface{}{ "trader_id": trader.ID, "trader_name": trader.Name, "ai_model": trader.AIModelID, "exchange_id": trader.ExchangeID, "is_running": isRunning, "initial_balance": trader.InitialBalance, }) } c.JSON(http.StatusOK, result) } // handleStatus 系统状态 func (s *Server) handleStatus(c *gin.Context) { _, traderID, err := s.getTraderFromQuery(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } trader, err := s.traderManager.GetTrader(traderID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } status := trader.GetStatus() c.JSON(http.StatusOK, status) } // handleAccount 账户信息 func (s *Server) handleAccount(c *gin.Context) { _, traderID, err := s.getTraderFromQuery(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } trader, err := s.traderManager.GetTrader(traderID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } log.Printf("📊 收到账户信息请求 [%s]", trader.GetName()) account, err := trader.GetAccountInfo() if err != nil { log.Printf("❌ 获取账户信息失败 [%s]: %v", trader.GetName(), err) c.JSON(http.StatusInternalServerError, gin.H{ "error": fmt.Sprintf("获取账户信息失败: %v", err), }) return } log.Printf("✓ 返回账户信息 [%s]: 净值=%.2f, 可用=%.2f, 盈亏=%.2f (%.2f%%)", trader.GetName(), account["total_equity"], account["available_balance"], account["total_pnl"], account["total_pnl_pct"]) c.JSON(http.StatusOK, account) } // handlePositions 持仓列表 func (s *Server) handlePositions(c *gin.Context) { _, traderID, err := s.getTraderFromQuery(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } trader, err := s.traderManager.GetTrader(traderID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } positions, err := trader.GetPositions() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": fmt.Sprintf("获取持仓列表失败: %v", err), }) return } c.JSON(http.StatusOK, positions) } // handleDecisions 决策日志列表 func (s *Server) handleDecisions(c *gin.Context) { _, traderID, err := s.getTraderFromQuery(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } trader, err := s.traderManager.GetTrader(traderID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } // 获取所有历史决策记录(无限制) records, err := trader.GetDecisionLogger().GetLatestRecords(10000) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": fmt.Sprintf("获取决策日志失败: %v", err), }) return } c.JSON(http.StatusOK, records) } // handleLatestDecisions 最新决策日志(最近5条,最新的在前) func (s *Server) handleLatestDecisions(c *gin.Context) { _, traderID, err := s.getTraderFromQuery(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } trader, err := s.traderManager.GetTrader(traderID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } records, err := trader.GetDecisionLogger().GetLatestRecords(5) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": fmt.Sprintf("获取决策日志失败: %v", err), }) return } // 反转数组,让最新的在前面(用于列表显示) // GetLatestRecords返回的是从旧到新(用于图表),这里需要从新到旧 for i, j := 0, len(records)-1; i < j; i, j = i+1, j-1 { records[i], records[j] = records[j], records[i] } c.JSON(http.StatusOK, records) } // handleStatistics 统计信息 func (s *Server) handleStatistics(c *gin.Context) { _, traderID, err := s.getTraderFromQuery(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } trader, err := s.traderManager.GetTrader(traderID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } stats, err := trader.GetDecisionLogger().GetStatistics() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": fmt.Sprintf("获取统计信息失败: %v", err), }) return } c.JSON(http.StatusOK, stats) } // handleCompetition 竞赛总览(对比所有trader) func (s *Server) handleCompetition(c *gin.Context) { userID := c.GetString("user_id") // 确保用户的交易员已加载到内存中 err := s.traderManager.LoadUserTraders(s.database, userID) if err != nil { log.Printf("⚠️ 加载用户 %s 的交易员失败: %v", userID, err) } competition, err := s.traderManager.GetCompetitionData(userID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": fmt.Sprintf("获取竞赛数据失败: %v", err), }) return } c.JSON(http.StatusOK, competition) } // handleEquityHistory 收益率历史数据 func (s *Server) handleEquityHistory(c *gin.Context) { _, traderID, err := s.getTraderFromQuery(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } trader, err := s.traderManager.GetTrader(traderID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } // 获取尽可能多的历史数据(几天的数据) // 每3分钟一个周期:10000条 = 约20天的数据 records, err := trader.GetDecisionLogger().GetLatestRecords(10000) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": fmt.Sprintf("获取历史数据失败: %v", err), }) return } // 构建收益率历史数据点 type EquityPoint struct { Timestamp string `json:"timestamp"` TotalEquity float64 `json:"total_equity"` // 账户净值(wallet + unrealized) AvailableBalance float64 `json:"available_balance"` // 可用余额 TotalPnL float64 `json:"total_pnl"` // 总盈亏(相对初始余额) TotalPnLPct float64 `json:"total_pnl_pct"` // 总盈亏百分比 PositionCount int `json:"position_count"` // 持仓数量 MarginUsedPct float64 `json:"margin_used_pct"` // 保证金使用率 CycleNumber int `json:"cycle_number"` } // 从AutoTrader获取初始余额(用于计算盈亏百分比) initialBalance := 0.0 if status := trader.GetStatus(); status != nil { if ib, ok := status["initial_balance"].(float64); ok && ib > 0 { initialBalance = ib } } // 如果无法从status获取,且有历史记录,则从第一条记录获取 if initialBalance == 0 && len(records) > 0 { // 第一条记录的equity作为初始余额 initialBalance = records[0].AccountState.TotalBalance } // 如果还是无法获取,返回错误 if initialBalance == 0 { c.JSON(http.StatusInternalServerError, gin.H{ "error": "无法获取初始余额", }) return } var history []EquityPoint for _, record := range records { // TotalBalance字段实际存储的是TotalEquity totalEquity := record.AccountState.TotalBalance // TotalUnrealizedProfit字段实际存储的是TotalPnL(相对初始余额) totalPnL := record.AccountState.TotalUnrealizedProfit // 计算盈亏百分比 totalPnLPct := 0.0 if initialBalance > 0 { totalPnLPct = (totalPnL / initialBalance) * 100 } history = append(history, EquityPoint{ Timestamp: record.Timestamp.Format("2006-01-02 15:04:05"), TotalEquity: totalEquity, AvailableBalance: record.AccountState.AvailableBalance, TotalPnL: totalPnL, TotalPnLPct: totalPnLPct, PositionCount: record.AccountState.PositionCount, MarginUsedPct: record.AccountState.MarginUsedPct, CycleNumber: record.CycleNumber, }) } c.JSON(http.StatusOK, history) } // handlePerformance AI历史表现分析(用于展示AI学习和反思) func (s *Server) handlePerformance(c *gin.Context) { _, traderID, err := s.getTraderFromQuery(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } trader, err := s.traderManager.GetTrader(traderID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } // 分析最近100个周期的交易表现(避免长期持仓的交易记录丢失) // 假设每3分钟一个周期,100个周期 = 5小时,足够覆盖大部分交易 performance, err := trader.GetDecisionLogger().AnalyzePerformance(100) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": fmt.Sprintf("分析历史表现失败: %v", err), }) return } c.JSON(http.StatusOK, performance) } // 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头"}) c.Abort() return } // 检查Bearer token格式 tokenParts := strings.Split(authHeader, " ") if len(tokenParts) != 2 || tokenParts[0] != "Bearer" { c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的Authorization格式"}) c.Abort() return } // 验证JWT token claims, err := auth.ValidateJWT(tokenParts[1]) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的token: " + err.Error()}) c.Abort() return } // 将用户信息存储到上下文中 c.Set("user_id", claims.UserID) c.Set("email", claims.Email) c.Next() } } // handleRegister 处理用户注册请求 func (s *Server) handleRegister(c *gin.Context) { var req struct { Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required,min=6"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 检查邮箱是否已存在 _, err := s.database.GetUserByEmail(req.Email) if err == nil { c.JSON(http.StatusConflict, gin.H{"error": "邮箱已被注册"}) return } // 生成密码哈希 passwordHash, err := auth.HashPassword(req.Password) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "密码处理失败"}) return } // 生成OTP密钥 otpSecret, err := auth.GenerateOTPSecret() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "OTP密钥生成失败"}) return } // 创建用户(未验证OTP状态) userID := uuid.New().String() user := &config.User{ ID: userID, Email: req.Email, PasswordHash: passwordHash, OTPSecret: otpSecret, OTPVerified: false, } err = s.database.CreateUser(user) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "创建用户失败: " + err.Error()}) return } // 返回OTP设置信息 qrCodeURL := auth.GetOTPQRCodeURL(otpSecret, req.Email) c.JSON(http.StatusOK, gin.H{ "user_id": userID, "email": req.Email, "otp_secret": otpSecret, "qr_code_url": qrCodeURL, "message": "请使用Google Authenticator扫描二维码并验证OTP", }) } // handleCompleteRegistration 完成注册(验证OTP) func (s *Server) handleCompleteRegistration(c *gin.Context) { var req struct { UserID string `json:"user_id" binding:"required"` OTPCode string `json:"otp_code" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 获取用户信息 user, err := s.database.GetUserByID(req.UserID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"}) return } // 验证OTP if !auth.VerifyOTP(user.OTPSecret, req.OTPCode) { c.JSON(http.StatusBadRequest, gin.H{"error": "OTP验证码错误"}) return } // 更新用户OTP验证状态 err = s.database.UpdateUserOTPVerified(req.UserID, true) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "更新用户状态失败"}) return } // 生成JWT token token, err := auth.GenerateJWT(user.ID, user.Email) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "生成token失败"}) return } // 初始化用户的默认模型和交易所配置 err = s.initUserDefaultConfigs(user.ID) if err != nil { log.Printf("初始化用户默认配置失败: %v", err) } c.JSON(http.StatusOK, gin.H{ "token": token, "user_id": user.ID, "email": user.Email, "message": "注册完成", }) } // handleLogin 处理用户登录请求 func (s *Server) handleLogin(c *gin.Context) { var req struct { Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 获取用户信息 user, err := s.database.GetUserByEmail(req.Email) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "邮箱或密码错误"}) return } // 验证密码 if !auth.CheckPassword(req.Password, user.PasswordHash) { c.JSON(http.StatusUnauthorized, gin.H{"error": "邮箱或密码错误"}) return } // 检查OTP是否已验证 if !user.OTPVerified { c.JSON(http.StatusUnauthorized, gin.H{ "error": "账户未完成OTP设置", "user_id": user.ID, "requires_otp_setup": true, }) return } // 返回需要OTP验证的状态 c.JSON(http.StatusOK, gin.H{ "user_id": user.ID, "email": user.Email, "message": "请输入Google Authenticator验证码", "requires_otp": true, }) } // handleVerifyOTP 验证OTP并完成登录 func (s *Server) handleVerifyOTP(c *gin.Context) { var req struct { UserID string `json:"user_id" binding:"required"` OTPCode string `json:"otp_code" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 获取用户信息 user, err := s.database.GetUserByID(req.UserID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"}) return } // 验证OTP if !auth.VerifyOTP(user.OTPSecret, req.OTPCode) { c.JSON(http.StatusBadRequest, gin.H{"error": "验证码错误"}) return } // 生成JWT token token, err := auth.GenerateJWT(user.ID, user.Email) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "生成token失败"}) return } c.JSON(http.StatusOK, gin.H{ "token": token, "user_id": user.ID, "email": user.Email, "message": "登录成功", }) } // initUserDefaultConfigs 为新用户初始化默认的模型和交易所配置 func (s *Server) initUserDefaultConfigs(userID string) error { // 注释掉自动创建默认配置,让用户手动添加 // 这样新用户注册后不会自动有配置项 log.Printf("用户 %s 注册完成,等待手动配置AI模型和交易所", userID) return nil } // handleGetSupportedModels 获取系统支持的AI模型列表 func (s *Server) handleGetSupportedModels(c *gin.Context) { // 返回系统支持的AI模型(从default用户获取) models, err := s.database.GetAIModels("default") if err != nil { log.Printf("❌ 获取支持的AI模型失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "获取支持的AI模型失败"}) return } c.JSON(http.StatusOK, models) } // handleGetSupportedExchanges 获取系统支持的交易所列表 func (s *Server) handleGetSupportedExchanges(c *gin.Context) { // 返回系统支持的交易所(从default用户获取) exchanges, err := s.database.GetExchanges("default") if err != nil { log.Printf("❌ 获取支持的交易所失败: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "获取支持的交易所失败"}) return } c.JSON(http.StatusOK, exchanges) } // Start 启动服务器 func (s *Server) Start() error { addr := fmt.Sprintf(":%d", s.port) log.Printf("🌐 API服务器启动在 http://localhost%s", addr) log.Printf("📊 API文档:") log.Printf(" • GET /health - 健康检查") log.Printf(" • GET /api/traders - AI交易员列表") log.Printf(" • POST /api/traders - 创建新的AI交易员") log.Printf(" • DELETE /api/traders/:id - 删除AI交易员") log.Printf(" • POST /api/traders/:id/start - 启动AI交易员") log.Printf(" • POST /api/traders/:id/stop - 停止AI交易员") log.Printf(" • GET /api/models - 获取AI模型配置") log.Printf(" • PUT /api/models - 更新AI模型配置") log.Printf(" • GET /api/exchanges - 获取交易所配置") log.Printf(" • PUT /api/exchanges - 更新交易所配置") log.Printf(" • GET /api/status?trader_id=xxx - 指定trader的系统状态") log.Printf(" • GET /api/account?trader_id=xxx - 指定trader的账户信息") log.Printf(" • GET /api/positions?trader_id=xxx - 指定trader的持仓列表") log.Printf(" • GET /api/decisions?trader_id=xxx - 指定trader的决策日志") log.Printf(" • GET /api/decisions/latest?trader_id=xxx - 指定trader的最新决策") log.Printf(" • GET /api/statistics?trader_id=xxx - 指定trader的统计信息") log.Printf(" • GET /api/equity-history?trader_id=xxx - 指定trader的收益率历史数据") log.Printf(" • GET /api/performance?trader_id=xxx - 指定trader的AI学习表现分析") log.Println() return s.router.Run(addr) }