| 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, |
| }) |
| } |
|
|
| |
| if h.Pool != nil { |
| h.Pool.Reset() |
| } |
|
|
| writeJSON(w, http.StatusOK, map[string]any{"items": items, "total": len(items)}) |
| } |
|
|