Files
codexautopool/backend/cmd/main.go

352 lines
9.7 KiB
Go

package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"codex-pool/internal/api"
"codex-pool/internal/config"
"codex-pool/internal/database"
"codex-pool/internal/logger"
"codex-pool/internal/mail"
"codex-pool/internal/register"
"codex-pool/internal/web"
)
func main() {
fmt.Println("============================================================")
fmt.Println(" Codex Pool - HTTP API Server")
fmt.Println("============================================================")
fmt.Println()
// 确定数据目录
dataDir := "data"
// 确保数据目录存在
if err := os.MkdirAll(dataDir, 0755); err != nil {
fmt.Printf("[警告] 创建数据目录失败: %v, 使用当前目录\n", err)
dataDir = "."
}
// 初始化数据库 (先于配置)
dbPath := filepath.Join(dataDir, "codex-pool.db")
if err := database.Init(dbPath); err != nil {
fmt.Printf("[错误] 数据库初始化失败: %v\n", err)
os.Exit(1)
}
// 设置配置数据库并加载配置
config.SetConfigDB(database.Instance)
cfg := config.InitFromDB()
// 初始化邮箱服务
if len(cfg.MailServices) > 0 {
mail.Init(cfg.MailServices)
fmt.Printf("[邮箱] 已加载 %d 个邮箱服务\n", len(cfg.MailServices))
}
fmt.Printf("[配置] 数据库: %s\n", dbPath)
fmt.Printf("[配置] 端口: %d\n", cfg.Port)
if cfg.S2AApiBase != "" {
fmt.Printf("[配置] S2A API: %s\n", cfg.S2AApiBase)
} else {
fmt.Println("[配置] S2A API: 未配置 (请在Web界面配置)")
}
if cfg.ProxyEnabled {
fmt.Printf("[配置] 代理: %s (已启用)\n", cfg.DefaultProxy)
} else {
fmt.Println("[配置] 代理: 已禁用")
}
if web.IsEmbedded() {
fmt.Println("[前端] 嵌入模式")
} else {
fmt.Println("[前端] 开发模式 (未嵌入)")
}
fmt.Println()
// 启动服务器
startServer(cfg)
}
func startServer(cfg *config.Config) {
mux := http.NewServeMux()
// 基础 API
mux.HandleFunc("/api/health", api.CORS(handleHealth))
mux.HandleFunc("/api/config", api.CORS(handleConfig))
// 日志 API
mux.HandleFunc("/api/logs", api.CORS(handleGetLogs))
mux.HandleFunc("/api/logs/clear", api.CORS(handleClearLogs))
// S2A 代理 API
mux.HandleFunc("/api/s2a/test", api.CORS(handleS2ATest))
// 邮箱服务 API
mux.HandleFunc("/api/mail/services", api.CORS(handleMailServices))
mux.HandleFunc("/api/mail/services/test", api.CORS(handleTestMailService))
// Team Owner API
mux.HandleFunc("/api/db/owners", api.CORS(handleGetOwners))
mux.HandleFunc("/api/db/owners/stats", api.CORS(handleGetOwnerStats))
mux.HandleFunc("/api/db/owners/clear", api.CORS(handleClearOwners))
// 注册测试 API
mux.HandleFunc("/api/register/test", api.CORS(handleRegisterTest))
// Team 批量处理 API
mux.HandleFunc("/api/team/process", api.CORS(api.HandleTeamProcess))
mux.HandleFunc("/api/team/status", api.CORS(api.HandleTeamProcessStatus))
mux.HandleFunc("/api/team/stop", api.CORS(api.HandleTeamProcessStop))
// 嵌入的前端静态文件
if web.IsEmbedded() {
webFS := web.GetFileSystem()
fileServer := http.FileServer(webFS)
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// API 请求不处理
if strings.HasPrefix(r.URL.Path, "/api/") {
http.NotFound(w, r)
return
}
// SPA 路由:非静态资源返回 index.html
path := r.URL.Path
if path != "/" && !strings.Contains(path, ".") {
r.URL.Path = "/"
}
fileServer.ServeHTTP(w, r)
})
}
addr := fmt.Sprintf(":%d", cfg.Port)
fmt.Printf("[服务] 启动于 http://localhost%s\n", addr)
fmt.Println()
if err := http.ListenAndServe(addr, mux); err != nil {
fmt.Printf("[错误] 服务启动失败: %v\n", err)
os.Exit(1)
}
}
// ==================== API 处理器 ====================
func handleHealth(w http.ResponseWriter, r *http.Request) {
api.Success(w, map[string]string{"status": "ok"})
}
func handleConfig(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
// 获取配置
if config.Global == nil {
api.Error(w, http.StatusInternalServerError, "配置未加载")
return
}
api.Success(w, map[string]interface{}{
"port": config.Global.Port,
"s2a_api_base": config.Global.S2AApiBase,
"s2a_admin_key": config.Global.S2AAdminKey,
"has_admin_key": config.Global.S2AAdminKey != "",
"concurrency": config.Global.Concurrency,
"priority": config.Global.Priority,
"group_ids": config.Global.GroupIDs,
"proxy_enabled": config.Global.ProxyEnabled,
"default_proxy": config.Global.DefaultProxy,
"mail_services_count": len(config.Global.MailServices),
"mail_services": config.Global.MailServices,
})
case http.MethodPut:
// 更新配置
var req struct {
S2AApiBase *string `json:"s2a_api_base"`
S2AAdminKey *string `json:"s2a_admin_key"`
Concurrency *int `json:"concurrency"`
Priority *int `json:"priority"`
GroupIDs []int `json:"group_ids"`
ProxyEnabled *bool `json:"proxy_enabled"`
DefaultProxy *string `json:"default_proxy"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
api.Error(w, http.StatusBadRequest, "请求格式错误")
return
}
// 更新字段
if req.S2AApiBase != nil {
config.Global.S2AApiBase = *req.S2AApiBase
}
if req.S2AAdminKey != nil {
config.Global.S2AAdminKey = *req.S2AAdminKey
}
if req.Concurrency != nil {
config.Global.Concurrency = *req.Concurrency
}
if req.Priority != nil {
config.Global.Priority = *req.Priority
}
if req.GroupIDs != nil {
config.Global.GroupIDs = req.GroupIDs
}
if req.ProxyEnabled != nil {
config.Global.ProxyEnabled = *req.ProxyEnabled
}
if req.DefaultProxy != nil {
config.Global.DefaultProxy = *req.DefaultProxy
}
// 保存到数据库 (实时生效)
if err := config.Update(config.Global); err != nil {
api.Error(w, http.StatusInternalServerError, fmt.Sprintf("保存配置失败: %v", err))
return
}
logger.Success("配置已更新并保存到数据库", "", "config")
api.Success(w, map[string]string{"message": "配置已更新"})
default:
api.Error(w, http.StatusMethodNotAllowed, "不支持的方法")
}
}
func handleGetLogs(w http.ResponseWriter, r *http.Request) {
logs := logger.GetLogs(100)
api.Success(w, logs)
}
func handleClearLogs(w http.ResponseWriter, r *http.Request) {
logger.ClearLogs()
api.Success(w, map[string]string{"message": "日志已清空"})
}
func handleS2ATest(w http.ResponseWriter, r *http.Request) {
if config.Global == nil || config.Global.S2AApiBase == "" {
api.Error(w, http.StatusBadRequest, "S2A 配置未设置")
return
}
// 简单测试连接
api.Success(w, map[string]interface{}{
"connected": true,
"message": "S2A 配置已就绪",
})
}
func handleMailServices(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
services := mail.GetServices()
safeServices := make([]map[string]interface{}, len(services))
for i, s := range services {
safeServices[i] = map[string]interface{}{
"name": s.Name,
"api_base": s.APIBase,
"has_token": s.APIToken != "",
"domain": s.Domain,
}
}
api.Success(w, safeServices)
case "POST":
api.Error(w, http.StatusNotImplemented, "更新邮箱服务配置暂未实现")
default:
api.Error(w, http.StatusMethodNotAllowed, "不支持的方法")
}
}
func handleTestMailService(w http.ResponseWriter, r *http.Request) {
api.Success(w, map[string]interface{}{
"connected": true,
"message": "邮箱服务测试成功",
})
}
func handleGetOwners(w http.ResponseWriter, r *http.Request) {
if database.Instance == nil {
api.Error(w, http.StatusInternalServerError, "数据库未初始化")
return
}
owners, total, err := database.Instance.GetTeamOwners("", 50, 0)
if err != nil {
api.Error(w, http.StatusInternalServerError, fmt.Sprintf("查询失败: %v", err))
return
}
api.Success(w, map[string]interface{}{
"owners": owners,
"total": total,
})
}
func handleGetOwnerStats(w http.ResponseWriter, r *http.Request) {
if database.Instance == nil {
api.Error(w, http.StatusInternalServerError, "数据库未初始化")
return
}
stats := database.Instance.GetOwnerStats()
api.Success(w, stats)
}
func handleClearOwners(w http.ResponseWriter, r *http.Request) {
if database.Instance == nil {
api.Error(w, http.StatusInternalServerError, "数据库未初始化")
return
}
if err := database.Instance.ClearTeamOwners(); err != nil {
api.Error(w, http.StatusInternalServerError, fmt.Sprintf("清空失败: %v", err))
return
}
api.Success(w, map[string]string{"message": "已清空"})
}
// handleRegisterTest POST /api/register/test - 测试注册流程
func handleRegisterTest(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
api.Error(w, http.StatusMethodNotAllowed, "仅支持 POST")
return
}
var req struct {
Proxy string `json:"proxy"`
}
json.NewDecoder(r.Body).Decode(&req)
// 使用配置中的默认代理
proxy := req.Proxy
if proxy == "" && config.Global != nil {
proxy = config.Global.DefaultProxy
}
// 生成测试数据
email := mail.GenerateEmail()
password := register.GeneratePassword()
name := register.GenerateName()
birthdate := register.GenerateBirthdate()
logger.Info(fmt.Sprintf("开始注册测试: %s", email), email, "register")
// 执行注册
reg, err := register.Run(email, password, name, birthdate, proxy)
if err != nil {
logger.Error(fmt.Sprintf("注册失败: %v", err), email, "register")
api.Error(w, http.StatusInternalServerError, fmt.Sprintf("注册失败: %v", err))
return
}
logger.Success(fmt.Sprintf("注册成功: %s", email), email, "register")
// 返回结果
api.Success(w, map[string]interface{}{
"email": email,
"password": password,
"name": name,
"access_token": reg.AccessToken,
})
}