feat: Implement initial Codex Pool backend server with comprehensive APIs for configuration, S2A integration, owner management, batch processing, monitoring, and corresponding frontend pages.
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -98,6 +99,7 @@ func startServer(cfg *config.Config) {
|
||||
// 基础 API
|
||||
mux.HandleFunc("/api/health", api.CORS(handleHealth))
|
||||
mux.HandleFunc("/api/config", api.CORS(handleConfig))
|
||||
mux.HandleFunc("/api/proxy/test", api.CORS(handleProxyTest)) // 代理测试
|
||||
|
||||
// 日志 API
|
||||
mux.HandleFunc("/api/logs", api.CORS(handleGetLogs))
|
||||
@@ -126,8 +128,8 @@ func startServer(cfg *config.Config) {
|
||||
mux.HandleFunc("/api/upload/validate", api.CORS(api.HandleUploadValidate))
|
||||
|
||||
// 母号封禁检查 API
|
||||
mux.HandleFunc("/api/db/owners/ban-check", api.CORS(api.HandleManualBanCheck)) // 手动触发检查
|
||||
mux.HandleFunc("/api/db/owners/ban-check/status", api.CORS(api.HandleBanCheckStatus)) // 检查状态
|
||||
mux.HandleFunc("/api/db/owners/ban-check", api.CORS(api.HandleManualBanCheck)) // 手动触发检查
|
||||
mux.HandleFunc("/api/db/owners/ban-check/status", api.CORS(api.HandleBanCheckStatus)) // 检查状态
|
||||
mux.HandleFunc("/api/db/owners/ban-check/settings", api.CORS(api.HandleBanCheckSettings)) // 配置
|
||||
|
||||
// 注册测试 API
|
||||
@@ -956,3 +958,84 @@ func handleCleanerSettings(w http.ResponseWriter, r *http.Request) {
|
||||
api.Error(w, http.StatusMethodNotAllowed, "不支持的方法")
|
||||
}
|
||||
}
|
||||
|
||||
// handleProxyTest POST /api/proxy/test - 测试代理连接
|
||||
func handleProxyTest(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
api.Error(w, http.StatusMethodNotAllowed, "仅支持 POST")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
ProxyURL string `json:"proxy_url"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
api.Error(w, http.StatusBadRequest, "请求格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
proxyURL := req.ProxyURL
|
||||
if proxyURL == "" {
|
||||
api.Error(w, http.StatusBadRequest, "代理地址不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info(fmt.Sprintf("测试代理连接: %s", proxyURL), "", "proxy")
|
||||
|
||||
// 解析代理 URL
|
||||
proxyParsed, err := parseProxyURL(proxyURL)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("代理地址格式错误: %v", err), "", "proxy")
|
||||
api.Error(w, http.StatusBadRequest, fmt.Sprintf("代理地址格式错误: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// 创建带代理的 HTTP 客户端
|
||||
client := &http.Client{
|
||||
Timeout: 15 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyParsed),
|
||||
},
|
||||
}
|
||||
|
||||
// 测试请求 - 使用 httpbin.org/ip 获取出口 IP
|
||||
testURL := "https://httpbin.org/ip"
|
||||
resp, err := client.Get(testURL)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("代理连接失败: %v", err), "", "proxy")
|
||||
api.Error(w, http.StatusBadGateway, fmt.Sprintf("代理连接失败: %v", err))
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logger.Error(fmt.Sprintf("代理测试失败: HTTP %d", resp.StatusCode), "", "proxy")
|
||||
api.Error(w, http.StatusBadGateway, fmt.Sprintf("代理测试失败: HTTP %d", resp.StatusCode))
|
||||
return
|
||||
}
|
||||
|
||||
// 解析响应获取出口 IP
|
||||
var ipResp struct {
|
||||
Origin string `json:"origin"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&ipResp); err != nil {
|
||||
logger.Warning(fmt.Sprintf("解析代理响应失败: %v", err), "", "proxy")
|
||||
}
|
||||
|
||||
logger.Success(fmt.Sprintf("代理连接成功, 出口IP: %s", ipResp.Origin), "", "proxy")
|
||||
|
||||
api.Success(w, map[string]interface{}{
|
||||
"connected": true,
|
||||
"message": "代理连接成功",
|
||||
"origin_ip": ipResp.Origin,
|
||||
})
|
||||
}
|
||||
|
||||
// parseProxyURL 解析代理 URL
|
||||
func parseProxyURL(proxyURL string) (*url.URL, error) {
|
||||
// 如果没有协议前缀,默认添加 http://
|
||||
if !strings.HasPrefix(proxyURL, "http://") && !strings.HasPrefix(proxyURL, "https://") && !strings.HasPrefix(proxyURL, "socks5://") {
|
||||
proxyURL = "http://" + proxyURL
|
||||
}
|
||||
return url.Parse(proxyURL)
|
||||
}
|
||||
|
||||
@@ -11,12 +11,13 @@ import (
|
||||
|
||||
// MonitorSettings 监控设置
|
||||
type MonitorSettings struct {
|
||||
Target int `json:"target"`
|
||||
AutoAdd bool `json:"auto_add"`
|
||||
MinInterval int `json:"min_interval"`
|
||||
CheckInterval int `json:"check_interval"` // 自动补号检查间隔(秒)
|
||||
PollingEnabled bool `json:"polling_enabled"`
|
||||
PollingInterval int `json:"polling_interval"`
|
||||
Target int `json:"target"`
|
||||
AutoAdd bool `json:"auto_add"`
|
||||
MinInterval int `json:"min_interval"`
|
||||
CheckInterval int `json:"check_interval"` // 自动补号检查间隔(秒)
|
||||
PollingEnabled bool `json:"polling_enabled"`
|
||||
PollingInterval int `json:"polling_interval"`
|
||||
ReplenishUseProxy bool `json:"replenish_use_proxy"` // 补号时使用代理
|
||||
}
|
||||
|
||||
// HandleGetMonitorSettings 获取监控设置
|
||||
@@ -32,12 +33,13 @@ func HandleGetMonitorSettings(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
settings := MonitorSettings{
|
||||
Target: 50,
|
||||
AutoAdd: false,
|
||||
MinInterval: 300,
|
||||
CheckInterval: 60,
|
||||
PollingEnabled: false,
|
||||
PollingInterval: 60,
|
||||
Target: 50,
|
||||
AutoAdd: false,
|
||||
MinInterval: 300,
|
||||
CheckInterval: 60,
|
||||
PollingEnabled: false,
|
||||
PollingInterval: 60,
|
||||
ReplenishUseProxy: false,
|
||||
}
|
||||
|
||||
if val, _ := database.Instance.GetConfig("monitor_target"); val != "" {
|
||||
@@ -66,6 +68,9 @@ func HandleGetMonitorSettings(w http.ResponseWriter, r *http.Request) {
|
||||
settings.PollingInterval = v
|
||||
}
|
||||
}
|
||||
if val, _ := database.Instance.GetConfig("monitor_replenish_use_proxy"); val == "true" {
|
||||
settings.ReplenishUseProxy = true
|
||||
}
|
||||
|
||||
Success(w, settings)
|
||||
}
|
||||
@@ -120,6 +125,9 @@ func HandleSaveMonitorSettings(w http.ResponseWriter, r *http.Request) {
|
||||
if err := database.Instance.SetConfig("monitor_polling_interval", strconv.Itoa(settings.PollingInterval)); err != nil {
|
||||
saveErrors = append(saveErrors, "polling_interval: "+err.Error())
|
||||
}
|
||||
if err := database.Instance.SetConfig("monitor_replenish_use_proxy", strconv.FormatBool(settings.ReplenishUseProxy)); err != nil {
|
||||
saveErrors = append(saveErrors, "replenish_use_proxy: "+err.Error())
|
||||
}
|
||||
|
||||
if len(saveErrors) > 0 {
|
||||
errMsg := "保存监控设置部分失败: " + saveErrors[0]
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// S2AAccountItem S2A 账号信息
|
||||
type S2AAccountItem struct {
|
||||
ID int `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Email string `json:"account"` // S2A API 返回的字段名是 account
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user