|
@@ -0,0 +1,130 @@
|
|
|
|
|
+package config
|
|
|
|
|
+
|
|
|
|
|
+import (
|
|
|
|
|
+ "encoding/json"
|
|
|
|
|
+ "errors"
|
|
|
|
|
+ "fmt"
|
|
|
|
|
+
|
|
|
|
|
+ "dario.cat/mergo"
|
|
|
|
|
+ "git.ikuban.com/server/yaml"
|
|
|
|
|
+ "github.com/elliotchance/orderedmap/v3"
|
|
|
|
|
+ "github.com/go-kratos/kratos/v2/config"
|
|
|
|
|
+ "google.golang.org/protobuf/encoding/protojson"
|
|
|
|
|
+ "google.golang.org/protobuf/proto"
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+func Init(sources []Source, bc any) error {
|
|
|
|
|
+ configSources := make([]config.Source, 0)
|
|
|
|
|
+ sourceMap := orderedmap.NewOrderedMap[string, any]() // 有序map控制优先级
|
|
|
|
|
+
|
|
|
|
|
+ for _, source := range sources {
|
|
|
|
|
+ if !source.Validate() {
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+ s, err := source.NewSource()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return fmt.Errorf("%v, new source error: %v", source.String(), err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ kvs, err := s.Load()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return fmt.Errorf("%v, source load error: %v", source.String(), err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for _, v := range kvs {
|
|
|
|
|
+ sourceMap.Set(v.Key, map[string]any{})
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ configSources = append(configSources, s)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if sourceMap.Len() == 0 {
|
|
|
|
|
+ return errors.New("config source is empty")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ c := config.New(
|
|
|
|
|
+ config.WithSource(configSources...),
|
|
|
|
|
+ config.WithDecoder(func(kv *config.KeyValue, v map[string]interface{}) error {
|
|
|
|
|
+
|
|
|
|
|
+ ok := sourceMap.Has(kv.Key)
|
|
|
|
|
+ if !ok {
|
|
|
|
|
+ return errors.New("unknown config key")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ next := map[string]any{}
|
|
|
|
|
+ err := yaml.Unmarshal(kv.Value, next)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ sourceMap.Set(kv.Key, convertMap(next))
|
|
|
|
|
+
|
|
|
|
|
+ merged := make(map[string]any)
|
|
|
|
|
+ for c := range sourceMap.Values() {
|
|
|
|
|
+ err = mergo.Merge(&merged, c, mergo.WithOverride)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ b, err := marshalJSON(merged)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ err = unmarshalJSON(b, bc)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return nil
|
|
|
|
|
+ }),
|
|
|
|
|
+ )
|
|
|
|
|
+ if err := c.Load(); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func marshalJSON(v interface{}) ([]byte, error) {
|
|
|
|
|
+ if m, ok := v.(proto.Message); ok {
|
|
|
|
|
+ return protojson.MarshalOptions{EmitUnpopulated: true}.Marshal(m)
|
|
|
|
|
+ }
|
|
|
|
|
+ return json.Marshal(v)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func unmarshalJSON(data []byte, v interface{}) error {
|
|
|
|
|
+ if m, ok := v.(proto.Message); ok {
|
|
|
|
|
+ return protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal(data, m)
|
|
|
|
|
+ }
|
|
|
|
|
+ return json.Unmarshal(data, v)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func convertMap(src interface{}) interface{} {
|
|
|
|
|
+ switch m := src.(type) {
|
|
|
|
|
+ case map[string]interface{}:
|
|
|
|
|
+ dst := make(map[string]interface{}, len(m))
|
|
|
|
|
+ for k, v := range m {
|
|
|
|
|
+ dst[k] = convertMap(v)
|
|
|
|
|
+ }
|
|
|
|
|
+ return dst
|
|
|
|
|
+ case map[interface{}]interface{}:
|
|
|
|
|
+ dst := make(map[string]interface{}, len(m))
|
|
|
|
|
+ for k, v := range m {
|
|
|
|
|
+ dst[fmt.Sprint(k)] = convertMap(v)
|
|
|
|
|
+ }
|
|
|
|
|
+ return dst
|
|
|
|
|
+ case []interface{}:
|
|
|
|
|
+ dst := make([]interface{}, len(m))
|
|
|
|
|
+ for k, v := range m {
|
|
|
|
|
+ dst[k] = convertMap(v)
|
|
|
|
|
+ }
|
|
|
|
|
+ return dst
|
|
|
|
|
+ case []byte:
|
|
|
|
|
+ // there will be no binary data in the config data
|
|
|
|
|
+ return string(m)
|
|
|
|
|
+ default:
|
|
|
|
|
+ return src
|
|
|
|
|
+ }
|
|
|
|
|
+}
|