初始提交:OCI 面板后端(含 GenAI 网关一期)
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user