cleandb.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "net"
  6. "time"
  7. "github.com/go-redis/redis/v8"
  8. "go.mongodb.org/mongo-driver/v2/mongo"
  9. "go.mongodb.org/mongo-driver/v2/mongo/options"
  10. "golang.org/x/crypto/ssh"
  11. )
  12. // SSHConfig SSH 连接配置
  13. type SSHConfig struct {
  14. Host string // SSH 转发服务器地址, e.g. "1.2.3.4"
  15. Port int // SSH 端口, e.g. 22
  16. User string // SSH 用户名
  17. Password string // SSH 密码 (若使用密钥则留空)
  18. }
  19. // DBConfig 数据库连接配置
  20. type DBConfig struct {
  21. Host string // 数据库地址 (通常是 127.0.0.1)
  22. Port int // 数据库端口
  23. User string // 数据库用户名
  24. Password string // 数据库密码
  25. AuthSource string // 认证数据库 (如 "admin")
  26. }
  27. // SSHTunnel SSH 隧道包装
  28. type SSHTunnel struct {
  29. client *ssh.Client
  30. }
  31. // Close 关闭 SSH 连接
  32. func (s *SSHTunnel) Close() error {
  33. if s.client != nil {
  34. return s.client.Close()
  35. }
  36. return nil
  37. }
  38. // createSSHTunnel 创建 SSH 客户端并返回拨号器
  39. func createSSHTunnel(sshConf SSHConfig) (*SSHTunnel, error) {
  40. config := &ssh.ClientConfig{
  41. User: sshConf.User,
  42. Auth: []ssh.AuthMethod{
  43. ssh.Password(sshConf.Password),
  44. },
  45. HostKeyCallback: ssh.InsecureIgnoreHostKey(),
  46. Timeout: 10 * time.Second,
  47. }
  48. addr := fmt.Sprintf("%s:%d", sshConf.Host, sshConf.Port)
  49. client, err := ssh.Dial("tcp", addr, config)
  50. if err != nil {
  51. return nil, fmt.Errorf("failed to dial ssh: %w", err)
  52. }
  53. return &SSHTunnel{client: client}, nil
  54. }
  55. // sshConn 包装 net.Conn 以支持 MongoDB 和 Redis 驱动所需的 Deadline 方法
  56. // 因为 SSH 通道 (channel) 默认不支持 SetDeadline
  57. type sshConn struct {
  58. net.Conn
  59. }
  60. func (c *sshConn) SetDeadline(t time.Time) error { return nil }
  61. func (c *sshConn) SetReadDeadline(t time.Time) error { return nil }
  62. func (c *sshConn) SetWriteDeadline(t time.Time) error { return nil }
  63. // mongoDialer 实现 options.ContextDialer 接口
  64. type mongoDialer struct {
  65. tunnel *SSHTunnel
  66. }
  67. func (m *mongoDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
  68. conn, err := m.tunnel.client.Dial(network, address)
  69. if err != nil {
  70. return nil, err
  71. }
  72. return &sshConn{conn}, nil
  73. }
  74. // ConnectMongoViaSSH 通过 SSH 隧道连接 MongoDB (若 sshConf.Host 为空则直连)
  75. func ConnectMongoViaSSH(sshConf SSHConfig, dbConf DBConfig) (*mongo.Client, *SSHTunnel, error) {
  76. var tunnel *SSHTunnel
  77. var dialer options.ContextDialer
  78. var err error
  79. if sshConf.Host != "" {
  80. tunnel, err = createSSHTunnel(sshConf)
  81. if err != nil {
  82. return nil, nil, err
  83. }
  84. dialer = &mongoDialer{tunnel: tunnel}
  85. }
  86. mongoAddr := fmt.Sprintf("%s:%d", dbConf.Host, dbConf.Port)
  87. uri := fmt.Sprintf("mongodb://%s", mongoAddr)
  88. clientOptions := options.Client().ApplyURI(uri)
  89. if dialer != nil {
  90. clientOptions.SetDialer(dialer)
  91. }
  92. if dbConf.User != "" {
  93. if dbConf.AuthSource == "" {
  94. dbConf.AuthSource = "admin"
  95. }
  96. clientOptions.SetAuth(options.Credential{
  97. Username: dbConf.User,
  98. Password: dbConf.Password,
  99. AuthSource: dbConf.AuthSource,
  100. })
  101. }
  102. client, err := mongo.Connect(clientOptions)
  103. if err != nil {
  104. if tunnel != nil {
  105. tunnel.Close()
  106. }
  107. return nil, nil, fmt.Errorf("failed to connect to mongo: %w", err)
  108. }
  109. return client, tunnel, nil
  110. }
  111. // ConnectRedisViaSSH 通过 SSH 隧道连接 Redis (若 sshConf.Host 为空则直连)
  112. func ConnectRedisViaSSH(sshConf SSHConfig, dbConf DBConfig) (*redis.Client, *SSHTunnel, error) {
  113. var tunnel *SSHTunnel
  114. var err error
  115. redisAddr := fmt.Sprintf("%s:%d", dbConf.Host, dbConf.Port)
  116. redisOptions := &redis.Options{
  117. Addr: redisAddr,
  118. Username: dbConf.User,
  119. Password: dbConf.Password,
  120. DB: 0, // 默认 DB
  121. }
  122. if sshConf.Host != "" {
  123. tunnel, err = createSSHTunnel(sshConf)
  124. if err != nil {
  125. return nil, nil, err
  126. }
  127. redisOptions.Dialer = func(ctx context.Context, network, addr string) (net.Conn, error) {
  128. conn, err := tunnel.client.Dial(network, addr)
  129. if err != nil {
  130. return nil, err
  131. }
  132. return &sshConn{conn}, nil
  133. }
  134. }
  135. rdb := redis.NewClient(redisOptions)
  136. // 验证连接
  137. if err := rdb.Ping(context.Background()).Err(); err != nil {
  138. rdb.Close()
  139. if tunnel != nil {
  140. tunnel.Close()
  141. }
  142. return nil, nil, fmt.Errorf("failed to ping redis: %w", err)
  143. }
  144. return rdb, tunnel, nil
  145. }