github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/framework/configmgr/nacos.go (about)

     1  package configmgr
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/joho/godotenv"
     6  	"github.com/pkg/errors"
     7  	"github.com/unionj-cloud/go-doudou/toolkit/dotenv"
     8  	"github.com/unionj-cloud/go-doudou/toolkit/maputils"
     9  	"github.com/unionj-cloud/go-doudou/toolkit/yaml"
    10  	logger "github.com/unionj-cloud/go-doudou/toolkit/zlogger"
    11  	"github.com/wubin1989/nacos-sdk-go/clients"
    12  	"github.com/wubin1989/nacos-sdk-go/clients/cache"
    13  	"github.com/wubin1989/nacos-sdk-go/clients/config_client"
    14  	"github.com/wubin1989/nacos-sdk-go/util"
    15  	"github.com/wubin1989/nacos-sdk-go/vo"
    16  	"io"
    17  	"os"
    18  	"strings"
    19  	"sync"
    20  )
    21  
    22  type nacosConfigType string
    23  
    24  const (
    25  	DotenvConfigFormat nacosConfigType = "dotenv"
    26  	YamlConfigFormat   nacosConfigType = "yaml"
    27  )
    28  
    29  type NacosConfigMgr struct {
    30  	dataIds     []string
    31  	group       string
    32  	format      nacosConfigType
    33  	namespaceId string
    34  	client      config_client.IConfigClient
    35  	listeners   cache.ConcurrentMap
    36  }
    37  
    38  func (m *NacosConfigMgr) Listeners() cache.ConcurrentMap {
    39  	return m.listeners
    40  }
    41  
    42  func NewNacosConfigMgr(dataIds []string, group string, format nacosConfigType, namespaceId string, client config_client.IConfigClient, listeners cache.ConcurrentMap) *NacosConfigMgr {
    43  	return &NacosConfigMgr{dataIds: dataIds, group: group, format: format, namespaceId: namespaceId, client: client, listeners: listeners}
    44  }
    45  
    46  var NacosClient *NacosConfigMgr
    47  
    48  type NacosChangeEvent struct {
    49  	Namespace, Group, DataId string
    50  	Changes                  map[string]maputils.Change
    51  }
    52  
    53  type NacosConfigListenerParam struct {
    54  	DataId   string
    55  	OnChange func(event *NacosChangeEvent)
    56  }
    57  
    58  func (m *NacosConfigMgr) fetchConfig(dataId string) (string, error) {
    59  	content, err := m.client.GetConfig(vo.ConfigParam{
    60  		DataId: dataId,
    61  		Group:  m.group,
    62  	})
    63  	if err != nil {
    64  		return "", err
    65  	}
    66  	return content, nil
    67  }
    68  
    69  func (m *NacosConfigMgr) loadDotenv(dataId string) error {
    70  	content, err := m.fetchConfig(dataId)
    71  	if err != nil {
    72  		return err
    73  	}
    74  	envMap, err := godotenv.Parse(StringReader(content))
    75  	if err != nil {
    76  		return err
    77  	}
    78  	currentEnv := map[string]bool{}
    79  	rawEnv := os.Environ()
    80  	for _, rawEnvLine := range rawEnv {
    81  		key := strings.Split(rawEnvLine, "=")[0]
    82  		currentEnv[key] = true
    83  	}
    84  	for key, value := range envMap {
    85  		if !currentEnv[key] {
    86  			_ = os.Setenv(key, value)
    87  		}
    88  	}
    89  	return nil
    90  }
    91  
    92  func (m *NacosConfigMgr) loadYaml(dataId string) error {
    93  	content, err := m.fetchConfig(dataId)
    94  	if err != nil {
    95  		return err
    96  	}
    97  	return yaml.LoadReader(strings.NewReader(content))
    98  }
    99  
   100  var onceNacos sync.Once
   101  var NewConfigClient = clients.NewConfigClient
   102  
   103  func InitialiseNacosConfig(param vo.NacosClientParam, dataId, format, group string) {
   104  	client, err := NewConfigClient(param)
   105  	if err != nil {
   106  		panic(errors.Wrap(err, "[go-doudou] failed to create nacos config client"))
   107  	}
   108  	dataIds := strings.Split(dataId, ",")
   109  	NacosClient = &NacosConfigMgr{
   110  		dataIds:     dataIds,
   111  		group:       group,
   112  		format:      nacosConfigType(format),
   113  		namespaceId: param.ClientConfig.NamespaceId,
   114  		client:      client,
   115  		listeners:   cache.NewConcurrentMap(),
   116  	}
   117  }
   118  
   119  func LoadFromNacos(param vo.NacosClientParam, dataId, format, group string) error {
   120  	onceNacos.Do(func() {
   121  		InitialiseNacosConfig(param, dataId, format, group)
   122  	})
   123  	switch nacosConfigType(format) {
   124  	case YamlConfigFormat:
   125  		for _, item := range NacosClient.dataIds {
   126  			if err := NacosClient.loadYaml(item); err != nil {
   127  				return errors.Wrap(err, "[go-doudou] failed to load yaml config")
   128  			}
   129  		}
   130  	case DotenvConfigFormat:
   131  		for _, item := range NacosClient.dataIds {
   132  			if err := NacosClient.loadDotenv(item); err != nil {
   133  				return errors.Wrap(err, "[go-doudou] failed to load dotenv config")
   134  			}
   135  		}
   136  	default:
   137  		return fmt.Errorf("[go-doudou] unknown config format: %s\n", format)
   138  	}
   139  	NacosClient.listenConfig()
   140  	return nil
   141  }
   142  
   143  var StringReader = func(s string) io.Reader {
   144  	return strings.NewReader(s)
   145  }
   146  
   147  func (m *NacosConfigMgr) CallbackOnChange(namespace, group, dataId, data, old string) {
   148  	var newData, oldData map[string]interface{}
   149  	var err error
   150  	switch m.format {
   151  	case YamlConfigFormat:
   152  		if newData, err = yaml.LoadReaderAsMap(StringReader(data)); err != nil {
   153  			logger.Error().Err(err).Msg("[go-doudou] error from nacos config listener")
   154  			return
   155  		}
   156  		if oldData, err = yaml.LoadReaderAsMap(StringReader(old)); err != nil {
   157  			logger.Error().Err(err).Msg("[go-doudou] error from nacos config listener")
   158  			return
   159  		}
   160  	case DotenvConfigFormat:
   161  		if newData, err = dotenv.LoadAsMap(StringReader(data)); err != nil {
   162  			logger.Error().Err(err).Msg("[go-doudou] error from nacos config listener")
   163  			return
   164  		}
   165  		if oldData, err = dotenv.LoadAsMap(StringReader(old)); err != nil {
   166  			logger.Error().Err(err).Msg("[go-doudou] error from nacos config listener")
   167  			return
   168  		}
   169  	}
   170  	changes := maputils.Diff(newData, oldData)
   171  	m.onChange("__"+dataId+"__"+"registry", group, namespace, changes)
   172  	m.onChange("__"+dataId+"__"+"ddhttp", group, namespace, changes)
   173  	m.onChange(dataId, group, namespace, changes)
   174  }
   175  
   176  func (m *NacosConfigMgr) listenConfig() {
   177  	for _, dataId := range m.dataIds {
   178  		if err := m.client.ListenConfig(vo.ConfigParam{
   179  			DataId:   dataId,
   180  			Group:    m.group,
   181  			OnChange: m.CallbackOnChange,
   182  		}); err != nil {
   183  			panic(err)
   184  		}
   185  	}
   186  }
   187  
   188  func (m *NacosConfigMgr) onChange(dataId, group, namespace string, changes map[string]maputils.Change) {
   189  	key := util.GetConfigCacheKey(dataId, group, namespace)
   190  	if v, ok := m.listeners.Get(key); ok {
   191  		listener := v.(NacosConfigListenerParam)
   192  		listener.OnChange(&NacosChangeEvent{
   193  			Namespace: namespace,
   194  			Group:     group,
   195  			DataId:    dataId,
   196  			Changes:   changes,
   197  		})
   198  	}
   199  }
   200  
   201  func (m *NacosConfigMgr) AddChangeListener(param NacosConfigListenerParam) {
   202  	key := util.GetConfigCacheKey(param.DataId, m.group, m.namespaceId)
   203  	if _, ok := m.listeners.Get(key); ok {
   204  		logger.Warn().Msgf("[go-doudou] you have already add a config change listener for dataId: %s, you cannot override it", param.DataId)
   205  		return
   206  	}
   207  	m.listeners.Set(key, param)
   208  }