func (client *Client) GetKeyValue(ctx context.Context, key string) (kv *mvccpb.KeyValue, err error) {
	rp, err := client.Client.Get(ctx, key)
	if err != nil {
		return nil, err
	}
	if len(rp.Kvs) > 0 {
		return rp.Kvs[0], nil
	}
	return
}
GetKeyValue queries etcd key, returns mvccpb.KeyValue
package etcd
import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"os"
	"strings"
	"time"
	"github.com/abulo/ratel/v2/logger"
	"github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/clientv3/concurrency"
	"github.com/coreos/etcd/mvcc/mvccpb"
	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
	"google.golang.org/grpc"
)
type Config struct {
	Endpoints        []string      `json:"endpoints"`
	CertFile         string        `json:"certFile"`
	KeyFile          string        `json:"keyFile"`
	CaCert           string        `json:"caCert"`
	BasicAuth        bool          `json:"basicAuth"`
	UserName         string        `json:"userName"`
	Password         string        `json:"-"`
	ConnectTimeout   time.Duration `json:"connectTimeout"`  
	Secure           bool          `json:"secure"`
	AutoSyncInterval time.Duration `json:"autoAsyncInterval"`  
	TTL              int            
}
type Client struct {
	*clientv3.Client
	config *Config
}
func (config *Config) Build() *Client {
	cc := newClient(config)
	return cc
}
func newClient(config *Config) *Client {
	conf := clientv3.Config{
		Endpoints:            config.Endpoints,
		DialTimeout:          config.ConnectTimeout,
		DialKeepAliveTime:    10 * time.Second,
		DialKeepAliveTimeout: 3 * time.Second,
		DialOptions: []grpc.DialOption{
			grpc.WithBlock(),
			grpc.WithUnaryInterceptor(grpc_prometheus.UnaryClientInterceptor),
			grpc.WithStreamInterceptor(grpc_prometheus.StreamClientInterceptor),
		},
		AutoSyncInterval: config.AutoSyncInterval,
	}
	if config.Endpoints == nil {
		logger.Logger.Panic("client etcd endpoints empty", config)
	}
	if !config.Secure {
		conf.DialOptions = append(conf.DialOptions, grpc.WithInsecure())
	}
	if config.BasicAuth {
		conf.Username = config.UserName
		conf.Password = config.Password
	}
	tlsEnabled := false
	tlsConfig := &tls.Config{
		InsecureSkipVerify: false,
	}
	if config.CaCert != "" {
		certBytes, err := os.ReadFile(config.CaCert)
		if err != nil {
			logger.Logger.Panic("parse CaCert failed", err)
		}
		caCertPool := x509.NewCertPool()
		ok := caCertPool.AppendCertsFromPEM(certBytes)
		if ok {
			tlsConfig.RootCAs = caCertPool
		}
		tlsEnabled = true
	}
	if config.CertFile != "" && config.KeyFile != "" {
		tlsCert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
		if err != nil {
			logger.Logger.Panic("load CertFile or KeyFile failed", err)
		}
		tlsConfig.Certificates = []tls.Certificate{tlsCert}
		tlsEnabled = true
	}
	if tlsEnabled {
		conf.TLS = tlsConfig
	}
	client, err := clientv3.New(conf)
	if err != nil {
		logger.Logger.Panic("client etcd start panic", err)
	}
	cc := &Client{
		Client: client,
		config: config,
	}
	logger.Logger.Info("dial etcd server")
	return cc
}