package proxies import ( "context" "encoding/json" "net/http" "net/url" "strings" "github.com/go-chi/chi/v5" "ds2api/internal/config" dsclient "ds2api/internal/deepseek/client" "ds2api/internal/proxyhealth" ) var proxyConnectivityTester = func(ctx context.Context, proxy config.Proxy) map[string]any { return dsclient.TestProxyConnectivity(ctx, proxy) } func validateProxyMutation(cfg *config.Config) error { if cfg == nil { return nil } if err := config.ValidateProxyConfig(cfg.Proxies); err != nil { return err } return config.ValidateAccountProxyReferences(cfg.Accounts, cfg.Proxies) } func proxyResponse(proxy config.Proxy) map[string]any { proxy = config.NormalizeProxy(proxy) return map[string]any{ "id": proxy.ID, "name": proxy.Name, "type": proxy.Type, "host": proxy.Host, "port": proxy.Port, "username": proxy.Username, "has_password": strings.TrimSpace(proxy.Password) != "", "disabled": proxy.Disabled, } } func (h *Handler) listProxies(w http.ResponseWriter, _ *http.Request) { proxies := h.Store.Snapshot().Proxies healthResults := h.checkerResults() items := make([]map[string]any, 0, len(proxies)) for _, proxy := range proxies { proxy = config.NormalizeProxy(proxy) item := map[string]any{ "id": proxy.ID, "name": proxy.Name, "type": proxy.Type, "host": proxy.Host, "port": proxy.Port, "username": proxy.Username, "has_password": strings.TrimSpace(proxy.Password) != "", "disabled": proxy.Disabled, } if hr, ok := healthResults[proxy.ID]; ok { item["health"] = map[string]any{ "healthy": hr.Healthy, "disabled": hr.Disabled, "message": hr.Message, "response_time": hr.ResponseTime, "checked_at": hr.CheckedAt, "retries_used": hr.RetriesUsed, } } items = append(items, item) } writeJSON(w, http.StatusOK, map[string]any{"items": items, "total": len(items)}) } func (h *Handler) addProxy(w http.ResponseWriter, r *http.Request) { var req map[string]any _ = json.NewDecoder(r.Body).Decode(&req) proxy := toProxy(req) err := h.Store.Update(func(c *config.Config) error { c.Proxies = append(c.Proxies, proxy) return validateProxyMutation(c) }) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"detail": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "proxy": proxyResponse(proxy)}) } func (h *Handler) updateProxy(w http.ResponseWriter, r *http.Request) { proxyID := chi.URLParam(r, "proxyID") if decoded, err := url.PathUnescape(proxyID); err == nil { proxyID = decoded } var req map[string]any _ = json.NewDecoder(r.Body).Decode(&req) proxy := toProxy(req) proxy.ID = strings.TrimSpace(proxyID) err := h.Store.Update(func(c *config.Config) error { for i, existing := range c.Proxies { existing = config.NormalizeProxy(existing) if existing.ID != proxy.ID { continue } if proxy.Password == "" { proxy.Password = existing.Password } c.Proxies[i] = proxy return validateProxyMutation(c) } return newRequestError("代理不存在") }) if err != nil { if detail, ok := requestErrorDetail(err); ok { writeJSON(w, http.StatusNotFound, map[string]any{"detail": detail}) return } writeJSON(w, http.StatusBadRequest, map[string]any{"detail": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "proxy": proxyResponse(proxy)}) } func (h *Handler) deleteProxy(w http.ResponseWriter, r *http.Request) { proxyID := chi.URLParam(r, "proxyID") if decoded, err := url.PathUnescape(proxyID); err == nil { proxyID = decoded } err := h.Store.Update(func(c *config.Config) error { idx := -1 for i, existing := range c.Proxies { existing = config.NormalizeProxy(existing) if existing.ID == strings.TrimSpace(proxyID) { idx = i break } } if idx < 0 { return newRequestError("代理不存在") } c.Proxies = append(c.Proxies[:idx], c.Proxies[idx+1:]...) for i := range c.Accounts { if strings.TrimSpace(c.Accounts[i].ProxyID) == strings.TrimSpace(proxyID) { c.Accounts[i].ProxyID = "" } } return validateProxyMutation(c) }) if err != nil { if detail, ok := requestErrorDetail(err); ok { writeJSON(w, http.StatusNotFound, map[string]any{"detail": detail}) return } writeJSON(w, http.StatusBadRequest, map[string]any{"detail": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]any{"success": true}) } func (h *Handler) testProxy(w http.ResponseWriter, r *http.Request) { var req map[string]any _ = json.NewDecoder(r.Body).Decode(&req) proxyID := fieldString(req, "proxy_id") var proxy config.Proxy if proxyID != "" { var ok bool proxy, ok = findProxyByID(h.Store.Snapshot(), proxyID) if !ok { writeJSON(w, http.StatusNotFound, map[string]any{"detail": "代理不存在"}) return } } else { proxy = toProxy(req) } result := proxyConnectivityTester(r.Context(), proxy) writeJSON(w, http.StatusOK, result) } func (h *Handler) updateAccountProxy(w http.ResponseWriter, r *http.Request) { identifier := chi.URLParam(r, "identifier") if decoded, err := url.PathUnescape(identifier); err == nil { identifier = decoded } var req map[string]any _ = json.NewDecoder(r.Body).Decode(&req) proxyID := fieldString(req, "proxy_id") err := h.Store.Update(func(c *config.Config) error { if proxyID != "" { if _, ok := findProxyByID(*c, proxyID); !ok { return newRequestError("代理不存在") } } for i, acc := range c.Accounts { if !accountMatchesIdentifier(acc, identifier) { continue } c.Accounts[i].ProxyID = proxyID return validateProxyMutation(c) } return newRequestError("账号不存在") }) if err != nil { if detail, ok := requestErrorDetail(err); ok { writeJSON(w, http.StatusBadRequest, map[string]any{"detail": detail}) return } writeJSON(w, http.StatusBadRequest, map[string]any{"detail": err.Error()}) return } h.Pool.Reset() writeJSON(w, http.StatusOK, map[string]any{"success": true, "proxy_id": proxyID}) } func (h *Handler) unbanProxy(w http.ResponseWriter, r *http.Request) { proxyID := chi.URLParam(r, "proxyID") if decoded, err := url.PathUnescape(proxyID); err == nil { proxyID = decoded } proxyID = strings.TrimSpace(proxyID) if proxyID == "" { writeJSON(w, http.StatusBadRequest, map[string]any{"detail": "proxy_id 不能为空"}) return } if h.Checker == nil { writeJSON(w, http.StatusInternalServerError, map[string]any{"detail": "代理健康检查器未启用"}) return } if err := h.Checker.UnbanProxy(proxyID); err != nil { writeJSON(w, http.StatusNotFound, map[string]any{"detail": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "proxy_id": proxyID}) } func (h *Handler) proxyHealth(w http.ResponseWriter, _ *http.Request) { if h.Checker == nil { writeJSON(w, http.StatusOK, map[string]any{"items": []any{}, "total": 0}) return } results := h.Checker.Results() items := make([]map[string]any, 0, len(results)) for _, r := range results { items = append(items, map[string]any{ "proxy_id": r.ProxyID, "healthy": r.Healthy, "disabled": r.Disabled, "message": r.Message, "response_time": r.ResponseTime, "checked_at": r.CheckedAt, "retries_used": r.RetriesUsed, }) } writeJSON(w, http.StatusOK, map[string]any{"items": items, "total": len(items)}) } func (h *Handler) checkerResults() map[string]*proxyhealth.CheckResult { if h.Checker == nil { return nil } return h.Checker.Results() } func (h *Handler) checkAllProxies(w http.ResponseWriter, r *http.Request) { if h.Checker == nil { writeJSON(w, http.StatusInternalServerError, map[string]any{"detail": "代理健康检查器未启用"}) return } results := h.Checker.CheckAll(r.Context()) items := make([]map[string]any, 0, len(results)) for _, r := range results { items = append(items, map[string]any{ "proxy_id": r.ProxyID, "healthy": r.Healthy, "disabled": r.Disabled, "message": r.Message, "response_time": r.ResponseTime, "checked_at": r.CheckedAt, "retries_used": r.RetriesUsed, }) } // Refresh pool in case accounts were migrated during auto-ban. if h.Pool != nil { h.Pool.Reset() } writeJSON(w, http.StatusOK, map[string]any{"items": items, "total": len(items)}) }