feat: implement batch team owner pooling functionality with dedicated upload, processing, logging, and results pages.
This commit is contained in:
@@ -3,11 +3,13 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"codex-pool/internal/api"
|
||||
"codex-pool/internal/config"
|
||||
@@ -19,23 +21,30 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("============================================================")
|
||||
fmt.Println(" Codex Pool - HTTP API Server")
|
||||
fmt.Println("============================================================")
|
||||
fmt.Println()
|
||||
// ANSI 颜色代码
|
||||
colorReset := "\033[0m"
|
||||
colorCyan := "\033[36m"
|
||||
colorGreen := "\033[32m"
|
||||
colorYellow := "\033[33m"
|
||||
colorGray := "\033[90m"
|
||||
colorBold := "\033[1m"
|
||||
|
||||
fmt.Printf("%s%s============================================================%s\n", colorBold, colorCyan, colorReset)
|
||||
fmt.Printf("%s%s Codex Pool - HTTP API Server%s\n", colorBold, colorCyan, colorReset)
|
||||
fmt.Printf("%s%s============================================================%s\n\n", colorBold, colorCyan, colorReset)
|
||||
|
||||
// 确定数据目录
|
||||
dataDir := "data"
|
||||
// 确保数据目录存在
|
||||
if err := os.MkdirAll(dataDir, 0755); err != nil {
|
||||
fmt.Printf("[警告] 创建数据目录失败: %v, 使用当前目录\n", err)
|
||||
fmt.Printf("%s[WARN]%s 创建数据目录失败: %v, 使用当前目录\n", colorYellow, colorReset, err)
|
||||
dataDir = "."
|
||||
}
|
||||
|
||||
// 初始化数据库 (先于配置)
|
||||
dbPath := filepath.Join(dataDir, "codex-pool.db")
|
||||
if err := database.Init(dbPath); err != nil {
|
||||
fmt.Printf("[错误] 数据库初始化失败: %v\n", err)
|
||||
fmt.Printf("%s[ERROR]%s 数据库初始化失败: %v\n", "\033[31m", colorReset, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -46,25 +55,25 @@ func main() {
|
||||
// 初始化邮箱服务
|
||||
if len(cfg.MailServices) > 0 {
|
||||
mail.Init(cfg.MailServices)
|
||||
fmt.Printf("[邮箱] 已加载 %d 个邮箱服务\n", len(cfg.MailServices))
|
||||
fmt.Printf("%s[邮箱]%s 已加载 %d 个邮箱服务\n", colorGreen, colorReset, len(cfg.MailServices))
|
||||
}
|
||||
|
||||
fmt.Printf("[配置] 数据库: %s\n", dbPath)
|
||||
fmt.Printf("[配置] 端口: %d\n", cfg.Port)
|
||||
fmt.Printf("%s[配置]%s 数据库: %s\n", colorGray, colorReset, dbPath)
|
||||
fmt.Printf("%s[配置]%s 端口: %d\n", colorGray, colorReset, cfg.Port)
|
||||
if cfg.S2AApiBase != "" {
|
||||
fmt.Printf("[配置] S2A API: %s\n", cfg.S2AApiBase)
|
||||
fmt.Printf("%s[配置]%s S2A API: %s\n", colorGray, colorReset, cfg.S2AApiBase)
|
||||
} else {
|
||||
fmt.Println("[配置] S2A API: 未配置 (请在Web界面配置)")
|
||||
fmt.Printf("%s[配置]%s S2A API: %s未配置%s (请在Web界面配置)\n", colorGray, colorReset, colorYellow, colorReset)
|
||||
}
|
||||
if cfg.ProxyEnabled {
|
||||
fmt.Printf("[配置] 代理: %s (已启用)\n", cfg.DefaultProxy)
|
||||
fmt.Printf("%s[配置]%s 代理: %s (已启用)\n", colorGray, colorReset, cfg.DefaultProxy)
|
||||
} else {
|
||||
fmt.Println("[配置] 代理: 已禁用")
|
||||
fmt.Printf("%s[配置]%s 代理: 已禁用\n", colorGray, colorReset)
|
||||
}
|
||||
if web.IsEmbedded() {
|
||||
fmt.Println("[前端] 嵌入模式")
|
||||
fmt.Printf("%s[前端]%s 嵌入模式\n", colorGreen, colorReset)
|
||||
} else {
|
||||
fmt.Println("[前端] 开发模式 (未嵌入)")
|
||||
fmt.Printf("%s[前端]%s 开发模式 (未嵌入)\n", colorYellow, colorReset)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
@@ -85,6 +94,7 @@ func startServer(cfg *config.Config) {
|
||||
|
||||
// S2A 代理 API
|
||||
mux.HandleFunc("/api/s2a/test", api.CORS(handleS2ATest))
|
||||
mux.HandleFunc("/api/s2a/proxy/", api.CORS(handleS2AProxy)) // 通配代理
|
||||
|
||||
// 邮箱服务 API
|
||||
mux.HandleFunc("/api/mail/services", api.CORS(handleMailServices))
|
||||
@@ -123,16 +133,22 @@ func startServer(cfg *config.Config) {
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf(":%d", cfg.Port)
|
||||
|
||||
// ANSI 颜色代码
|
||||
colorReset := "\033[0m"
|
||||
colorGreen := "\033[32m"
|
||||
colorCyan := "\033[36m"
|
||||
|
||||
// 显示访问地址
|
||||
fmt.Println("[服务] 启动于:")
|
||||
fmt.Printf(" - 本地: http://localhost:%d\n", cfg.Port)
|
||||
fmt.Printf("%s[服务]%s 启动于:\n", colorGreen, colorReset)
|
||||
fmt.Printf(" - 本地: %shttp://localhost:%d%s\n", colorCyan, cfg.Port, colorReset)
|
||||
if ip := getOutboundIP(); ip != "" {
|
||||
fmt.Printf(" - 外部: http://%s:%d\n", ip, cfg.Port)
|
||||
fmt.Printf(" - 外部: %shttp://%s:%d%s\n", colorCyan, ip, cfg.Port, colorReset)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
if err := http.ListenAndServe(addr, mux); err != nil {
|
||||
fmt.Printf("[错误] 服务启动失败: %v\n", err)
|
||||
fmt.Printf("\033[31m[ERROR]\033[0m 服务启动失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -241,6 +257,59 @@ func handleS2ATest(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// handleS2AProxy 代理 S2A API 请求
|
||||
func handleS2AProxy(w http.ResponseWriter, r *http.Request) {
|
||||
if config.Global == nil || config.Global.S2AApiBase == "" || config.Global.S2AAdminKey == "" {
|
||||
api.Error(w, http.StatusBadRequest, "S2A 配置未设置")
|
||||
return
|
||||
}
|
||||
|
||||
// 提取路径: /api/s2a/proxy/xxx -> /api/v1/admin/xxx
|
||||
path := strings.TrimPrefix(r.URL.Path, "/api/s2a/proxy")
|
||||
targetURL := config.Global.S2AApiBase + "/api/v1/admin" + path
|
||||
if r.URL.RawQuery != "" {
|
||||
targetURL += "?" + r.URL.RawQuery
|
||||
}
|
||||
|
||||
// 创建代理请求
|
||||
proxyReq, err := http.NewRequest(r.Method, targetURL, r.Body)
|
||||
if err != nil {
|
||||
api.Error(w, http.StatusInternalServerError, "创建请求失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 复制请求头
|
||||
for key, values := range r.Header {
|
||||
for _, value := range values {
|
||||
proxyReq.Header.Add(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// 设置认证头
|
||||
proxyReq.Header.Set("Authorization", "Bearer "+config.Global.S2AAdminKey)
|
||||
proxyReq.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// 发送请求
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(proxyReq)
|
||||
if err != nil {
|
||||
api.Error(w, http.StatusBadGateway, fmt.Sprintf("请求 S2A 失败: %v", err))
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 复制响应头
|
||||
for key, values := range resp.Header {
|
||||
for _, value := range values {
|
||||
w.Header().Add(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// 复制响应状态和内容
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
io.Copy(w, resp.Body)
|
||||
}
|
||||
|
||||
func handleMailServices(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
|
||||
@@ -73,23 +73,44 @@ func log(level, message, email, module string) {
|
||||
|
||||
broadcast(entry)
|
||||
|
||||
// 打印到控制台
|
||||
// 打印到控制台 (带时间戳和颜色)
|
||||
timestamp := entry.Timestamp.Format("15:04:05")
|
||||
|
||||
// ANSI 颜色代码
|
||||
colorReset := "\033[0m"
|
||||
colorGray := "\033[90m"
|
||||
colorGreen := "\033[32m"
|
||||
colorRed := "\033[31m"
|
||||
colorYellow := "\033[33m"
|
||||
colorCyan := "\033[36m"
|
||||
|
||||
prefix := ""
|
||||
color := ""
|
||||
switch level {
|
||||
case "info":
|
||||
prefix = "[INFO]"
|
||||
prefix = "INFO"
|
||||
color = colorCyan
|
||||
case "success":
|
||||
prefix = "[SUCCESS]"
|
||||
prefix = "SUCCESS"
|
||||
color = colorGreen
|
||||
case "error":
|
||||
prefix = "[ERROR]"
|
||||
prefix = "ERROR"
|
||||
color = colorRed
|
||||
case "warning":
|
||||
prefix = "[WARN]"
|
||||
prefix = "WARN"
|
||||
color = colorYellow
|
||||
}
|
||||
|
||||
if email != "" {
|
||||
fmt.Printf("%s [%s] %s - %s\n", prefix, module, email, message)
|
||||
fmt.Printf("%s%s%s %s[%s]%s [%s] %s - %s\n",
|
||||
colorGray, timestamp, colorReset,
|
||||
color, prefix, colorReset,
|
||||
module, email, message)
|
||||
} else {
|
||||
fmt.Printf("%s [%s] %s\n", prefix, module, message)
|
||||
fmt.Printf("%s%s%s %s[%s]%s [%s] %s\n",
|
||||
colorGray, timestamp, colorReset,
|
||||
color, prefix, colorReset,
|
||||
module, message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user