Files
Prism/internal/api/handlers_proxies_io.go
T
renjue 59d1087cc9
CI / test (push) Failing after 1m46s
CI / docker (push) Has been skipped
feat: Prism HTTP/SOCKS5 代理网关及管理台
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 15:08:50 +08:00

125 lines
3.4 KiB
Go

package api
import (
"io"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/prism/proxy/internal/proxyio"
"github.com/prism/proxy/internal/store"
)
func (s *Server) exportProxies(c *gin.Context) {
ctx := c.Request.Context()
items, err := s.app.Store.ListProxyNodes(ctx, "manual", nil)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
format := strings.ToLower(c.DefaultQuery("format", "json"))
filename := "prism-proxies"
switch format {
case "text", "txt", "links":
c.Header("Content-Type", "text/plain; charset=utf-8")
c.Header("Content-Disposition", `attachment; filename="`+filename+`.txt"`)
c.String(http.StatusOK, proxyio.ExportLinks(items))
default:
data, err := proxyio.ExportJSON(items)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Header("Content-Type", "application/json; charset=utf-8")
c.Header("Content-Disposition", `attachment; filename="`+filename+`.json"`)
c.Data(http.StatusOK, "application/json; charset=utf-8", data)
}
}
type importProxiesReq struct {
Mode string `json:"mode"`
Text string `json:"text"`
Nodes []proxyio.ExportNode `json:"nodes"`
}
func (s *Server) importProxies(c *gin.Context) {
ctx := c.Request.Context()
mode := strings.ToLower(strings.TrimSpace(c.DefaultQuery("mode", "")))
var nodes []store.ProxyNode
var parseErrors []string
contentType := c.GetHeader("Content-Type")
if strings.Contains(contentType, "application/json") {
var req importProxiesReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if mode == "" {
mode = strings.ToLower(strings.TrimSpace(req.Mode))
}
if len(req.Nodes) > 0 {
var valErrors []string
nodes, valErrors = proxyio.ToStoreNodes(req.Nodes)
parseErrors = append(parseErrors, valErrors...)
} else if strings.TrimSpace(req.Text) != "" {
nodes, parseErrors = proxyio.ParseText(req.Text)
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": "nodes or text required"})
return
}
} else {
body, err := io.ReadAll(c.Request.Body)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if mode == "" {
mode = "append"
}
nodes, parseErrors = proxyio.ParseText(string(body))
}
if mode == "" {
mode = "append"
}
if mode != "append" && mode != "replace" {
c.JSON(http.StatusBadRequest, gin.H{"error": "mode must be append or replace"})
return
}
if len(nodes) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "no valid proxies", "errors": parseErrors})
return
}
if mode == "append" {
existing, _ := s.app.Store.ListProxyNodes(ctx, "manual", nil)
proxyio.AssignUniqueNames(nodes, proxyio.ReservedNames(existing))
if err := s.app.Store.AppendManualProxies(ctx, nodes); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
} else {
proxyio.AssignUniqueNames(nodes, nil)
if err := s.app.Store.ReplaceManualProxies(ctx, nodes); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
}
_ = s.app.Store.AddAuditLog(ctx, "import", "proxies", mode+": "+strconv.Itoa(len(nodes)))
_ = s.app.ReloadEngine(ctx)
c.JSON(http.StatusOK, gin.H{
"ok": true,
"imported": len(nodes),
"skipped": len(parseErrors),
"errors": parseErrors,
"mode": mode,
})
}