package service import ( "context" "fmt" "net/url" "strconv" "strings" ) // ImportResult 是批量导入的汇总结果。 type ImportResult struct { Created []ProxyView `json:"created"` Failed []ImportFail `json:"failed"` } // ImportFail 记录解析或入库失败的行;Line 已脱敏,凭据段以 *** 代替。 type ImportFail struct { LineNo int `json:"lineNo"` Line string `json:"line"` Reason string `json:"reason"` } // Import 逐行解析并创建代理;空行与 # 注释跳过,失败行不阻断后续。 func (s *ProxyService) Import(ctx context.Context, text string) (ImportResult, error) { res := ImportResult{Created: []ProxyView{}, Failed: []ImportFail{}} for i, raw := range strings.Split(text, "\n") { line := strings.TrimSpace(raw) if line == "" || strings.HasPrefix(line, "#") { continue } in, err := parseProxyLine(line) if err == nil { var view ProxyView if view, err = s.Create(ctx, in); err == nil { res.Created = append(res.Created, view) continue } } res.Failed = append(res.Failed, ImportFail{ LineNo: i + 1, Line: maskProxyLine(line), Reason: err.Error(), }) } return res, nil } // parseProxyLine 解析一行代理,支持两种格式: // // scheme://[user:pass@]host:port(scheme 为 socks5 / http / https) // host:port[:user:pass](缺省按 socks5) func parseProxyLine(line string) (ProxyInput, error) { if strings.Contains(line, "://") { return parseProxyURI(line) } parts := strings.Split(line, ":") if len(parts) != 2 && len(parts) != 4 { return ProxyInput{}, fmt.Errorf("格式应为 host:port 或 host:port:user:pass: %w", ErrProxyInvalid) } port, err := strconv.Atoi(parts[1]) if err != nil { return ProxyInput{}, fmt.Errorf("端口非数字: %w", ErrProxyInvalid) } in := ProxyInput{Type: "socks5", Host: parts[0], Port: port} if len(parts) == 4 { in.Username = parts[2] in.Password = &parts[3] } return in, nil } // parseProxyURI 解析 scheme://[user:pass@]host:port。 func parseProxyURI(line string) (ProxyInput, error) { u, err := url.Parse(line) if err != nil { return ProxyInput{}, fmt.Errorf("URI 解析失败: %w", ErrProxyInvalid) } port, err := strconv.Atoi(u.Port()) if err != nil { return ProxyInput{}, fmt.Errorf("缺少或非法端口: %w", ErrProxyInvalid) } in := ProxyInput{Type: u.Scheme, Host: u.Hostname(), Port: port} if u.User != nil { in.Username = u.User.Username() if pw, ok := u.User.Password(); ok { in.Password = &pw } } return in, nil } // maskProxyLine 把行内可能的凭据段替换为 ***,避免密码回显到响应。 func maskProxyLine(line string) string { if i := strings.Index(line, "://"); i >= 0 { if j := strings.LastIndex(line, "@"); j > i { return line[:i+3] + "***@" + line[j+1:] } return line } if parts := strings.Split(line, ":"); len(parts) > 2 { return strings.Join(parts[:2], ":") + ":***" } return line }