mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-08 23:49:34 +08:00
♻️ refactor(clickhouse): 使用结构化 Options 替代 DSN 连接构造
- 用 buildClickHouseOptions 收敛连接参数生成逻辑 - 将连接入口改为 clickhouse.OpenDB(Options) - 清理 DSN 中的 write_timeout/read_timeout/dial_timeout 透传路径 - 同步重写 ClickHouse 相关测试断言 - refs #138
This commit is contained in:
@@ -17,7 +17,7 @@ import (
|
|||||||
"GoNavi-Wails/internal/ssh"
|
"GoNavi-Wails/internal/ssh"
|
||||||
"GoNavi-Wails/internal/utils"
|
"GoNavi-Wails/internal/utils"
|
||||||
|
|
||||||
_ "github.com/ClickHouse/clickhouse-go/v2"
|
clickhouse "github.com/ClickHouse/clickhouse-go/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -100,25 +100,20 @@ func applyClickHouseURI(config connection.ConnectionConfig) connection.Connectio
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClickHouseDB) getDSN(config connection.ConnectionConfig) string {
|
func (c *ClickHouseDB) buildClickHouseOptions(config connection.ConnectionConfig) *clickhouse.Options {
|
||||||
u := &url.URL{
|
timeout := getConnectTimeout(config)
|
||||||
Scheme: "clickhouse",
|
return &clickhouse.Options{
|
||||||
Host: net.JoinHostPort(config.Host, strconv.Itoa(config.Port)),
|
Addr: []string{
|
||||||
Path: "/" + strings.TrimPrefix(strings.TrimSpace(config.Database), "/"),
|
net.JoinHostPort(config.Host, strconv.Itoa(config.Port)),
|
||||||
|
},
|
||||||
|
Auth: clickhouse.Auth{
|
||||||
|
Database: strings.TrimSpace(config.Database),
|
||||||
|
Username: strings.TrimSpace(config.User),
|
||||||
|
Password: config.Password,
|
||||||
|
},
|
||||||
|
DialTimeout: timeout,
|
||||||
|
ReadTimeout: timeout,
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(config.Password) != "" {
|
|
||||||
u.User = url.UserPassword(strings.TrimSpace(config.User), config.Password)
|
|
||||||
} else {
|
|
||||||
u.User = url.User(strings.TrimSpace(config.User))
|
|
||||||
}
|
|
||||||
|
|
||||||
timeoutSeconds := getConnectTimeoutSeconds(config)
|
|
||||||
query := u.Query()
|
|
||||||
query.Set("dial_timeout", fmt.Sprintf("%ds", timeoutSeconds))
|
|
||||||
query.Set("read_timeout", fmt.Sprintf("%ds", timeoutSeconds))
|
|
||||||
query.Set("write_timeout", fmt.Sprintf("%ds", timeoutSeconds))
|
|
||||||
u.RawQuery = query.Encode()
|
|
||||||
return u.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClickHouseDB) Connect(config connection.ConnectionConfig) error {
|
func (c *ClickHouseDB) Connect(config connection.ConnectionConfig) error {
|
||||||
@@ -165,11 +160,7 @@ func (c *ClickHouseDB) Connect(config connection.ConnectionConfig) error {
|
|||||||
logger.Infof("ClickHouse 通过本地端口转发连接:%s -> %s:%d", forwarder.LocalAddr, config.Host, config.Port)
|
logger.Infof("ClickHouse 通过本地端口转发连接:%s -> %s:%d", forwarder.LocalAddr, config.Host, config.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
dbConn, err := sql.Open("clickhouse", c.getDSN(runConfig))
|
c.conn = clickhouse.OpenDB(c.buildClickHouseOptions(runConfig))
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("打开数据库连接失败:%w", err)
|
|
||||||
}
|
|
||||||
c.conn = dbConn
|
|
||||||
|
|
||||||
if err := c.Ping(); err != nil {
|
if err := c.Ping(); err != nil {
|
||||||
_ = c.Close()
|
_ = c.Close()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package db
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"GoNavi-Wails/internal/connection"
|
"GoNavi-Wails/internal/connection"
|
||||||
)
|
)
|
||||||
@@ -115,7 +116,7 @@ func TestTDengineDSN_UsesWebSocketFormat(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClickHouseDSN_EscapesPasswordAndSetsTimeout(t *testing.T) {
|
func TestClickHouseOptions_UsesStructuredTimeoutAndAuth(t *testing.T) {
|
||||||
c := &ClickHouseDB{}
|
c := &ClickHouseDB{}
|
||||||
cfg := normalizeClickHouseConfig(connection.ConnectionConfig{
|
cfg := normalizeClickHouseConfig(connection.ConnectionConfig{
|
||||||
Type: "clickhouse",
|
Type: "clickhouse",
|
||||||
@@ -127,17 +128,35 @@ func TestClickHouseDSN_EscapesPasswordAndSetsTimeout(t *testing.T) {
|
|||||||
Timeout: 15,
|
Timeout: 15,
|
||||||
})
|
})
|
||||||
|
|
||||||
dsn := c.getDSN(cfg)
|
opts := c.buildClickHouseOptions(cfg)
|
||||||
if strings.Contains(dsn, cfg.Password) {
|
if opts == nil {
|
||||||
t.Fatalf("dsn 包含原始密码:%s", dsn)
|
t.Fatal("options 为空")
|
||||||
}
|
}
|
||||||
if !strings.Contains(dsn, "p%40ss%3Awo%2Frd") {
|
if len(opts.Addr) != 1 || opts.Addr[0] != "127.0.0.1:9000" {
|
||||||
t.Fatalf("dsn 未正确转义密码:%s", dsn)
|
t.Fatalf("addr 不符合预期:%v", opts.Addr)
|
||||||
}
|
}
|
||||||
if !strings.Contains(dsn, "dial_timeout=15s") {
|
if opts.Auth.Username != "default" {
|
||||||
t.Fatalf("dsn 缺少 dial_timeout 参数:%s", dsn)
|
t.Fatalf("username 不符合预期:%s", opts.Auth.Username)
|
||||||
}
|
}
|
||||||
if !strings.Contains(dsn, "/analytics") {
|
if opts.Auth.Password != cfg.Password {
|
||||||
t.Fatalf("dsn 缺少数据库路径:%s", dsn)
|
t.Fatalf("password 不符合预期:%s", opts.Auth.Password)
|
||||||
|
}
|
||||||
|
if opts.Auth.Database != "analytics" {
|
||||||
|
t.Fatalf("database 不符合预期:%s", opts.Auth.Database)
|
||||||
|
}
|
||||||
|
if opts.DialTimeout != 15*time.Second {
|
||||||
|
t.Fatalf("dial timeout 不符合预期:%s", opts.DialTimeout)
|
||||||
|
}
|
||||||
|
if opts.ReadTimeout != 15*time.Second {
|
||||||
|
t.Fatalf("read timeout 不符合预期:%s", opts.ReadTimeout)
|
||||||
|
}
|
||||||
|
if _, ok := opts.Settings["write_timeout"]; ok {
|
||||||
|
t.Fatalf("options 不应包含 write_timeout 设置:%v", opts.Settings)
|
||||||
|
}
|
||||||
|
if _, ok := opts.Settings["read_timeout"]; ok {
|
||||||
|
t.Fatalf("options 不应通过 settings 传递 read_timeout:%v", opts.Settings)
|
||||||
|
}
|
||||||
|
if _, ok := opts.Settings["dial_timeout"]; ok {
|
||||||
|
t.Fatalf("options 不应通过 settings 传递 dial_timeout:%v", opts.Settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user