Files
MyGoNavi/internal/app/db_proxy.go
Syngnat 7d5592d8d9 feat(db): 数据库连接新增 SOCKS5/HTTP 代理能力并兼容 SRV/SSH 场景
- 后端 ConnectionConfig 增加代理配置并完成规范化处理
- 普通 TCP 数据源通过本地转发接入代理
- MongoDB 使用 Dialer 支持代理连接(含 SRV)
- 前端连接配置新增代理 UI、字段清洗与数据回填
- refs #122
2026-02-27 09:31:24 +08:00

203 lines
5.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package app
import (
"fmt"
"net"
"strconv"
"strings"
"GoNavi-Wails/internal/connection"
proxytunnel "GoNavi-Wails/internal/proxy"
)
func resolveDialConfigWithProxy(raw connection.ConnectionConfig) (connection.ConnectionConfig, error) {
config := raw
if !config.UseProxy {
config.Proxy = connection.ProxyConfig{}
return config, nil
}
normalizedProxy, err := proxytunnel.NormalizeConfig(config.Proxy)
if err != nil {
return connection.ConnectionConfig{}, err
}
config.Proxy = normalizedProxy
if config.UseSSH {
sshPort := config.SSH.Port
if sshPort <= 0 {
sshPort = 22
}
forwardedSSH, err := buildProxyForwardAddress(normalizedProxy, strings.TrimSpace(config.SSH.Host), sshPort)
if err != nil {
return connection.ConnectionConfig{}, fmt.Errorf("代理连接 SSH 网关失败:%w", err)
}
config.SSH.Host = forwardedSSH.host
config.SSH.Port = forwardedSSH.port
config.UseProxy = false
config.Proxy = connection.ProxyConfig{}
return config, nil
}
normalizedType := strings.ToLower(strings.TrimSpace(config.Type))
if normalizedType == "sqlite" || normalizedType == "duckdb" || normalizedType == "custom" {
// 文件型/自定义 DSN 类型不走标准 host:port不在此层改写。
return config, nil
}
if normalizedType == "mongodb" && config.MongoSRV {
// Mongo SRV 由驱动侧 Dialer 处理代理,避免破坏 DNS SRV 拓扑发现。
return config, nil
}
targetPort := config.Port
if targetPort <= 0 {
targetPort = defaultPortByType(normalizedType)
}
forwardedPrimary, err := buildProxyForwardAddress(normalizedProxy, strings.TrimSpace(config.Host), targetPort)
if err != nil {
return connection.ConnectionConfig{}, err
}
config.Host = forwardedPrimary.host
config.Port = forwardedPrimary.port
if len(config.Hosts) > 0 {
rewritten := make([]string, 0, len(config.Hosts))
seen := make(map[string]struct{}, len(config.Hosts))
for _, rawEntry := range config.Hosts {
targetHost, targetPort, ok := parseAddressWithDefaultPort(rawEntry, defaultPortByType(normalizedType))
if !ok {
continue
}
forwarded, forwardErr := buildProxyForwardAddress(normalizedProxy, targetHost, targetPort)
if forwardErr != nil {
return connection.ConnectionConfig{}, forwardErr
}
rewrittenAddress := formatHostPort(forwarded.host, forwarded.port)
if _, exists := seen[rewrittenAddress]; exists {
continue
}
seen[rewrittenAddress] = struct{}{}
rewritten = append(rewritten, rewrittenAddress)
}
config.Hosts = rewritten
}
config.UseProxy = false
config.Proxy = connection.ProxyConfig{}
return config, nil
}
type hostPort struct {
host string
port int
}
func buildProxyForwardAddress(proxyConfig connection.ProxyConfig, targetHost string, targetPort int) (hostPort, error) {
host := strings.TrimSpace(targetHost)
if host == "" {
host = "localhost"
}
port := targetPort
if port <= 0 {
return hostPort{}, fmt.Errorf("目标端口无效:%d", targetPort)
}
forwarder, err := proxytunnel.GetOrCreateLocalForwarder(proxyConfig, host, port)
if err != nil {
return hostPort{}, err
}
localHost, localPort, splitOK := parseAddressWithDefaultPort(forwarder.LocalAddr, 0)
if !splitOK || localPort <= 0 {
return hostPort{}, fmt.Errorf("解析代理本地转发地址失败:%s", forwarder.LocalAddr)
}
return hostPort{host: localHost, port: localPort}, nil
}
func parseAddressWithDefaultPort(raw string, defaultPort int) (string, int, bool) {
text := strings.TrimSpace(raw)
if text == "" {
return "", 0, false
}
if strings.HasPrefix(text, "[") {
if host, portText, err := net.SplitHostPort(text); err == nil {
if port, convErr := strconv.Atoi(portText); convErr == nil && port > 0 && port <= 65535 {
return strings.TrimSpace(host), port, true
}
return "", 0, false
}
trimmed := strings.Trim(strings.TrimPrefix(text, "["), "]")
if trimmed != "" && defaultPort > 0 {
return trimmed, defaultPort, true
}
return "", 0, false
}
if strings.Count(text, ":") == 0 {
if defaultPort <= 0 {
return "", 0, false
}
return text, defaultPort, true
}
if strings.Count(text, ":") == 1 {
host, portText, err := net.SplitHostPort(text)
if err == nil {
port, convErr := strconv.Atoi(portText)
if convErr == nil && port > 0 && port <= 65535 {
return strings.TrimSpace(host), port, true
}
return "", 0, false
}
if defaultPort > 0 {
return strings.TrimSpace(text), defaultPort, true
}
return "", 0, false
}
// IPv6 地址未带端口,使用默认端口。
if defaultPort > 0 {
return text, defaultPort, true
}
return "", 0, false
}
func formatHostPort(host string, port int) string {
h := strings.TrimSpace(host)
if strings.Contains(h, ":") && !strings.HasPrefix(h, "[") {
return fmt.Sprintf("[%s]:%d", h, port)
}
return fmt.Sprintf("%s:%d", h, port)
}
func defaultPortByType(driverType string) int {
switch strings.ToLower(strings.TrimSpace(driverType)) {
case "mysql", "mariadb":
return 3306
case "diros":
return 9030
case "sphinx":
return 9306
case "postgres", "vastbase":
return 5432
case "redis":
return 6379
case "tdengine":
return 6041
case "oracle":
return 1521
case "dameng":
return 5236
case "kingbase":
return 54321
case "sqlserver":
return 1433
case "mongodb":
return 27017
case "highgo":
return 5866
default:
return 0
}
}