Files
oci-portal/internal/service/proxyimport.go
T

104 lines
2.9 KiB
Go

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
}