package db import ( "context" "crypto/sha256" "database/sql" "encoding/hex" "fmt" "net" "strings" "time" mysqldriver "github.com/go-sql-driver/mysql" ) func openMySQL(c ConnReq) (*sql.DB, error) { host := c.Host if host == "" { host = "127.0.0.1" } port := c.Port if port == 0 { port = 3306 } user := c.User if user == "" { user = "root" } cfg := mysqldriver.NewConfig() cfg.User = user cfg.Passwd = c.Password cfg.Net = "tcp" cfg.Addr = net.JoinHostPort(host, fmt.Sprintf("%d", port)) cfg.DBName = c.Database cfg.ParseTime = true cfg.Timeout = 5 * time.Second cfg.ReadTimeout = 30 * time.Second cfg.WriteTimeout = 30 * time.Second cfg.Loc = time.Local cfg.Params = map[string]string{"charset": "utf8mb4"} mode := EffectiveSSLMode(c) switch mode { case "disable": // no TLS case "skip-verify": if !hasTLSMaterial(c) { cfg.TLSConfig = "skip-verify" } else { name, err := registerMySQLTLS(c) if err != nil { return nil, err } cfg.TLSConfig = name } case "require", "verify-ca": if !hasTLSMaterial(c) && mode == "require" { cfg.TLSConfig = "true" } else { name, err := registerMySQLTLS(c) if err != nil { return nil, err } cfg.TLSConfig = name } default: return nil, fmt.Errorf("未知 SSL 模式: %s", mode) } return sql.Open("mysql", cfg.FormatDSN()) } // hasTLSMaterial 是否提供了任何 CA/客户端证书材料(路径或 PEM 内容)。 func hasTLSMaterial(c ConnReq) bool { return strings.TrimSpace(c.SSLCA) != "" || strings.TrimSpace(c.SSLCert) != "" || strings.TrimSpace(c.SSLKey) != "" || strings.TrimSpace(c.SSLCAContent) != "" || strings.TrimSpace(c.SSLCertContent) != "" || strings.TrimSpace(c.SSLKeyContent) != "" } func registerMySQLTLS(c ConnReq) (string, error) { tlsCfg, err := tlsConfigFrom(c) if err != nil { return "", err } if tlsCfg == nil { return "", fmt.Errorf("SSL 已启用但未能构建 TLS 配置") } sum := sha256.Sum256([]byte(EffectiveSSLMode(c) + "|" + c.SSLCA + "|" + c.SSLCert + "|" + c.SSLKey + "|" + c.SSLCAContent + "|" + c.SSLCertContent + "|" + c.SSLKeyContent + "|" + c.TLSServerName + "|" + c.Host)) name := "opsxdb-" + hex.EncodeToString(sum[:8]) mysqldriver.DeregisterTLSConfig(name) if err := mysqldriver.RegisterTLSConfig(name, tlsCfg); err != nil { return "", fmt.Errorf("注册 MySQL TLS 配置失败: %w", err) } return name, nil } func listDatabasesMySQL(ctx context.Context, db *sql.DB) ([]string, error) { rows, err := db.QueryContext(ctx, "SHOW DATABASES") if err != nil { return nil, err } defer rows.Close() var out []string for rows.Next() { var name string if err := rows.Scan(&name); err != nil { return nil, err } out = append(out, name) } return out, rows.Err() } func listTablesMySQL(ctx context.Context, db *sql.DB, database string) ([]string, error) { q := "SHOW TABLES" if database != "" { q = "SHOW TABLES FROM " + quoteMySQLIdent(database) } rows, err := db.QueryContext(ctx, q) if err != nil { return nil, err } defer rows.Close() var out []string for rows.Next() { var name string if err := rows.Scan(&name); err != nil { return nil, err } out = append(out, name) } return out, rows.Err() } func listColumnsMySQL(ctx context.Context, db *sql.DB, database, table string) ([]ColumnInfo, error) { ident := quoteMySQLIdent(table) if database != "" { ident = quoteMySQLIdent(database) + "." + ident } rows, err := db.QueryContext(ctx, "SHOW FULL COLUMNS FROM "+ident) if err != nil { return nil, err } defer rows.Close() var out []ColumnInfo for rows.Next() { var field, typ, null, key string var collation, extra, priv, comment sql.NullString var def sql.NullString if err := rows.Scan(&field, &typ, &collation, &null, &key, &def, &extra, &priv, &comment); err != nil { return nil, err } col := ColumnInfo{Name: field, Type: typ, Nullable: null == "YES", Key: key, Comment: comment.String, Extra: extra.String} if def.Valid { col.Default = def.String } out = append(out, col) } if out == nil { out = []ColumnInfo{} } return out, rows.Err() }