Files
Prism/internal/api/handlers_rules_io.go
T
renjue fe8ea784d0
CI / docker (push) Successful in 2m54s
feat: Prism HTTP/SOCKS5 代理网关及管理台
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 15:58:28 +08:00

143 lines
3.8 KiB
Go

package api
import (
"io"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/prism/proxy/internal/ruleio"
)
func (s *Server) exportRules(c *gin.Context) {
ctx := c.Request.Context()
items, err := s.app.Store.ListRules(ctx, "manual")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
format := strings.ToLower(c.DefaultQuery("format", "json"))
filename := "prism-rules"
switch format {
case "text", "txt", "clash":
c.Header("Content-Type", "text/plain; charset=utf-8")
c.Header("Content-Disposition", `attachment; filename="`+filename+`.txt"`)
c.String(http.StatusOK, ruleio.ExportText(items))
default:
data, err := ruleio.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 importRulesReq struct {
Mode string `json:"mode"`
Text string `json:"text"`
Rules []ruleio.ExportRule `json:"rules"`
}
func (s *Server) importRules(c *gin.Context) {
ctx := c.Request.Context()
mode := strings.ToLower(strings.TrimSpace(c.DefaultQuery("mode", "")))
var parsed []ruleio.ExportRule
var parseErrors []string
contentType := c.GetHeader("Content-Type")
if strings.Contains(contentType, "application/json") {
var req importRulesReq
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.Rules) > 0 {
parsed = req.Rules
} else if strings.TrimSpace(req.Text) != "" {
parsed, parseErrors = parseImportText(req.Text)
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": "rules 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"
}
parsed, parseErrors = parseImportText(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(parsed) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "no valid rules", "errors": parseErrors})
return
}
rules, valErrors := ruleio.ToStoreRules(parsed)
allErrors := append(parseErrors, valErrors...)
if len(rules) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "no valid rules", "errors": allErrors})
return
}
if mode == "append" {
start, _ := s.app.Store.NextManualRulePriority(ctx)
ruleio.AssignPriorities(rules, start)
if err := s.app.Store.AppendManualRules(ctx, rules); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
} else {
ruleio.AssignPriorities(rules, 100)
if err := s.app.Store.ReplaceManualRules(ctx, rules); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
}
_ = s.app.Store.AddAuditLog(ctx, "import", "rules", mode+": "+strconv.Itoa(len(rules)))
_ = s.app.ReloadEngine(ctx)
c.JSON(http.StatusOK, gin.H{
"ok": true,
"imported": len(rules),
"skipped": len(allErrors),
"errors": allErrors,
"mode": mode,
})
}
func parseImportText(text string) ([]ruleio.ExportRule, []string) {
text = strings.TrimSpace(text)
if text == "" {
return nil, nil
}
if strings.HasPrefix(text, "{") || strings.HasPrefix(text, "[") {
rules, err := ruleio.ParseJSON([]byte(text))
if err == nil && len(rules) > 0 {
return rules, nil
}
}
return ruleio.ParseText(text)
}