github.com/wfusion/gofusion@v1.1.14/config/registry.go (about) 1 package config 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "reflect" 8 "sort" 9 "strings" 10 "sync" 11 "syscall" 12 13 "github.com/iancoleman/strcase" 14 "github.com/mitchellh/mapstructure" 15 "github.com/pkg/errors" 16 "github.com/wfusion/gofusion/common/utils/serialize/json" 17 18 "github.com/wfusion/gofusion/common/di" 19 "github.com/wfusion/gofusion/common/utils" 20 "github.com/wfusion/gofusion/common/utils/clone" 21 ) 22 23 var ( 24 Registry = ®istry{di: di.Dig, initOnce: new(sync.Once), closeCh: make(chan struct{})} 25 26 initLocker sync.RWMutex 27 registryLock sync.RWMutex 28 registryMap = map[string]Configurable{"": Registry} 29 ) 30 31 const ( 32 componentConfigFieldName = "Base" 33 ) 34 35 func Use(appName string, opts ...utils.OptionExtender) Configurable { 36 registryLock.RLock() 37 defer registryLock.RUnlock() 38 cfg, ok := registryMap[appName] 39 if !ok { 40 panic(errors.Errorf("app register config not found: %s", appName)) 41 } 42 return cfg 43 } 44 45 func New(appName string) Configurable { 46 registryLock.Lock() 47 defer registryLock.Unlock() 48 if reg, ok := registryMap[appName]; ok { 49 return reg 50 } 51 52 reg := ®istry{ 53 di: di.NewDI(), 54 appName: appName, 55 initOnce: new(sync.Once), 56 closeCh: make(chan struct{}), 57 } 58 registryMap[appName] = reg 59 return reg 60 } 61 62 type registry struct { 63 di di.DI 64 appName string 65 debug bool 66 loadComponentsOnce sync.Once 67 initOnce *sync.Once 68 initWg sync.WaitGroup 69 closeCh chan struct{} 70 71 componentList []*Component 72 businessConfig any 73 businessConfigType reflect.Type 74 componentConfigs any 75 } 76 77 type initOption struct { 78 debug bool 79 bizCtx context.Context 80 customLoadFunc loadConfigFunc 81 filenames []string 82 } 83 84 func Ctx(ctx context.Context) utils.OptionFunc[initOption] { 85 return func(o *initOption) { 86 o.bizCtx = ctx 87 } 88 } 89 90 func Loader(fn func(any, ...utils.OptionExtender)) utils.OptionFunc[initOption] { 91 return func(o *initOption) { 92 o.customLoadFunc = fn 93 } 94 } 95 96 func Files(filenames []string) utils.OptionFunc[initOption] { 97 return func(o *initOption) { 98 o.filenames = filenames 99 } 100 } 101 102 func Debug() utils.OptionFunc[initOption] { 103 return func(o *initOption) { 104 o.debug = true 105 } 106 } 107 108 func (r *registry) Init(businessConfig any, opts ...utils.OptionExtender) (gracefully func()) { 109 initLocker.Lock() 110 defer initLocker.Unlock() 111 112 r.initWg.Add(1) 113 r.initOnce.Do(func() { 114 opt := utils.ApplyOptions[initOption](opts...) 115 r.debug = opt.debug 116 r.closeCh = make(chan struct{}) 117 118 // context 119 parent := context.Background() 120 if opt.bizCtx != nil { 121 parent = opt.bizCtx 122 } 123 124 // load config function 125 loadFn := loadConfig 126 if opt.customLoadFunc != nil { 127 loadFn = opt.customLoadFunc 128 } 129 130 gracefully = r.initByConfigFile(parent, businessConfig, loadFn, opts...) 131 }) 132 if gracefully == nil { 133 // give back 134 reflect.Indirect(reflect.ValueOf(businessConfig)).Set(reflect.ValueOf(r.businessConfig)) 135 136 once := new(sync.Once) 137 gracefully = func() { 138 once.Do(func() { 139 r.initWg.Done() 140 }) 141 } 142 } 143 return 144 } 145 146 func (r *registry) AddComponent(name string, constructor any, opts ...ComponentOption) { 147 if name[0] < 'A' || name[0] > 'Z' { 148 panic("component name should start with A-Z") 149 } 150 for idx, com := range r.componentList { 151 if com.name == name { 152 r.componentList = append(r.componentList[:idx], r.componentList[idx+1:]...) 153 } 154 } 155 opt := newOptions() 156 for _, fn := range opts { 157 fn(opt) 158 } 159 160 com := &Component{ 161 name: name, 162 isCore: opt.isCoreComponent, 163 flagString: opt.flagValue, 164 } 165 166 hasYamlTag := false 167 hasJsonTag := false 168 hasTomlTag := false 169 for _, tag := range opt.tagList { 170 hasYamlTag = strings.HasPrefix(tag, "`yaml:") 171 hasJsonTag = strings.HasPrefix(tag, "`json:") 172 hasTomlTag = strings.HasPrefix(tag, "`toml:") 173 } 174 lowerName := strcase.ToSnake(name) 175 if name == ComponentI18n { 176 lowerName = strings.ToLower(name) 177 } 178 if !hasYamlTag { 179 opt.tagList = append(opt.tagList, fmt.Sprintf(`yaml:"%s"`, lowerName)) 180 } 181 if !hasJsonTag { 182 opt.tagList = append(opt.tagList, fmt.Sprintf(`json:"%s"`, lowerName)) 183 } 184 if !hasTomlTag { 185 opt.tagList = append(opt.tagList, fmt.Sprintf(`toml:"%s"`, lowerName)) 186 } 187 if len(opt.tagList) > 0 { 188 com.tag = strings.Join(opt.tagList, " ") 189 } 190 191 com.constructor, com.constructorInputType = parseConstructor(constructor) 192 193 r.addComponent(com) 194 } 195 196 func (r *registry) LoadComponentConfig(name string, componentConfig any) (err error) { 197 val := reflect.ValueOf(componentConfig) 198 typ := val.Type() 199 if typ.Kind() != reflect.Ptr { 200 return errors.New("componentConfig should be pointer") 201 } 202 203 var found bool 204 for _, com := range r.componentList { 205 if com.name == name { 206 found = true 207 break 208 } 209 } 210 if !found { 211 return errors.Errorf("no such component [%s]", name) 212 } 213 214 // load config 215 if r.componentConfigs == nil { 216 return 217 } 218 componentConfigsValue := utils.IndirectValue(reflect.ValueOf(clone.Clone(r.componentConfigs))) 219 if !componentConfigsValue.IsValid() { 220 return errors.Errorf("component configs not initialize now [%s]", name) 221 } 222 componentConfigValue := componentConfigsValue.FieldByName(componentConfigFieldName).FieldByName(name) 223 224 if componentConfigValue.Type().Kind() == reflect.Ptr { 225 if componentConfigValue.IsNil() { 226 return 227 } 228 componentConfigValue = componentConfigValue.Elem() 229 } 230 if componentConfigValue.Type() == typ.Elem() || componentConfigValue.Type().ConvertibleTo(typ.Elem()) { 231 val.Elem().Set(reflect.ValueOf(clone.Clone(componentConfigValue.Convert(typ.Elem()).Interface()))) 232 return 233 } 234 235 decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 236 Metadata: nil, 237 Result: componentConfig, 238 TagName: "yaml", 239 WeaklyTypedInput: true, 240 }) 241 if err != nil { 242 return 243 } 244 return decoder.Decode(componentConfigValue.Interface()) 245 } 246 247 func (r *registry) GetAllConfigs() any { 248 val := reflect.New(r.makeAllConfigStruct()) 249 derefVal := reflect.Indirect(val) 250 251 // business configs 252 businessConfigsVal := reflect.Indirect(reflect.ValueOf(r.businessConfig)) 253 numFields := businessConfigsVal.NumField() 254 for i := 0; i < numFields; i++ { 255 derefVal.Field(i + 1).Set(businessConfigsVal.Field(i)) 256 } 257 258 // component configs 259 derefComponentConfigsVal := derefVal.FieldByName(componentConfigFieldName) 260 componentConfigsVal := reflect.Indirect(reflect.ValueOf(r.componentConfigs)).FieldByName(componentConfigFieldName) 261 numFields = componentConfigsVal.NumField() 262 for i := 0; i < numFields; i++ { 263 derefComponentConfigsVal.Field(i).Set(componentConfigsVal.Field(i)) 264 } 265 return clone.Clone(val.Interface()) 266 } 267 268 func (r *registry) initByConfigFile(parent context.Context, businessConfig any, 269 loadFn loadConfigFunc, opts ...utils.OptionExtender) func() { 270 r.loadComponents() 271 r.checkBusinessConfig(businessConfig) 272 273 businessConfigVal := reflect.ValueOf(businessConfig) 274 r.businessConfigType = utils.IndirectType(businessConfigVal.Type()) 275 r.businessConfig = reflect.New(r.businessConfigType).Interface() 276 r.componentConfigs = reflect.New(r.makeComponentsConfigStruct()).Interface() 277 278 r.initAllConfigByLoadFunc(loadFn, opts...) 279 r.initAllConfigByFlag() 280 281 appName := r.AppName() 282 registryLock.Lock() 283 if _, ok := registryMap[appName]; !ok { 284 registryMap[appName] = r 285 } 286 registryLock.Unlock() 287 288 // decrypt 289 CryptoDecryptByTag(r.businessConfig, AppName(r.AppName())) 290 CryptoDecryptByTag(r.componentConfigs, AppName(r.AppName())) 291 292 // give back 293 reflect.Indirect(reflect.ValueOf(businessConfig)).Set(reflect.ValueOf(r.businessConfig)) 294 295 return r.initComponents(parent) 296 } 297 298 func (r *registry) getBaseObject() reflect.Value { 299 return reflect.Indirect(reflect.ValueOf(r.componentConfigs)).FieldByName(componentConfigFieldName) 300 } 301 302 func (r *registry) makeComponentsConfigStruct() reflect.Type { 303 fieldList := r.makeComponentsConfigFields() 304 return reflect.StructOf([]reflect.StructField{ 305 { 306 Name: componentConfigFieldName, 307 Type: reflect.StructOf(fieldList), 308 Tag: `yaml:"base" json:"base" toml:"base"`, 309 Anonymous: true, 310 }, 311 }) 312 } 313 314 func (r *registry) makeComponentsConfigFields() []reflect.StructField { 315 fieldList := make([]reflect.StructField, len(r.componentList)) 316 for i := 0; i < len(r.componentList); i++ { 317 component := r.componentList[i] 318 fieldList[i] = reflect.StructField{ 319 Name: component.name, 320 Type: component.constructorInputType, 321 Tag: reflect.StructTag(component.tag), 322 } 323 } 324 325 return fieldList 326 } 327 328 func (r *registry) makeAllConfigStruct() reflect.Type { 329 /* AllConfig struct may look like below 330 type AllConfig struct { 331 XXXBase struct { 332 Debug bool 333 App string 334 DB map[string]*db.Conf 335 Redis map[string]*redis.Conf 336 Log *log.Conf 337 ... 338 } `yaml:"base" json:"base" toml:"base"` 339 340 BusinessConfigField1 341 BusinessConfigField2 342 BusinessConfigField3 343 344 ... 345 } 346 */ 347 348 numFields := r.businessConfigType.NumField() 349 fieldList := make([]reflect.StructField, 0, numFields+1) 350 fieldList = append(fieldList, reflect.StructField{ 351 Name: componentConfigFieldName, 352 Type: reflect.StructOf(r.makeComponentsConfigFields()), 353 Tag: `yaml:"base" json:"base" toml:"base"`, 354 Anonymous: true, 355 }) 356 for i := 0; i < numFields; i++ { 357 fieldList = append(fieldList, r.businessConfigType.Field(i)) 358 } 359 360 return reflect.StructOf(fieldList) 361 } 362 363 func (r *registry) loadComponents() { 364 r.loadComponentsOnce.Do(func() { 365 // app 366 r.AddComponent(ComponentApp, func(context.Context, string, ...utils.OptionExtender) func() { return nil }, 367 WithTag("yaml", "app"), WithTag("json", "app"), WithTag("toml", "app"), 368 WithFlag(utils.AnyPtr("null")), 369 ) 370 371 // debug 372 r.AddComponent(ComponentDebug, func(context.Context, bool, ...utils.OptionExtender) func() { return nil }, 373 WithTag("yaml", "debug"), WithTag("json", "debug"), WithTag("toml", "debug"), 374 WithFlag(utils.AnyPtr("null")), 375 ) 376 377 // crypto 378 r.AddComponent(ComponentCrypto, CryptoConstruct, 379 WithTag("yaml", "crypto"), WithTag("json", "crypto"), WithTag("toml", "crypto"), 380 WithFlag(&cryptoFlagString), 381 ) 382 383 for _, item := range getComponents() { 384 r.AddComponent(item.name, item.constructor, item.opt...) 385 } 386 387 /* example */ 388 // registry.AddComponent("ComponentExample", func(context.Context, string) func() { return nil }, 389 // WithTag("custom_tag", "val"), WithTag("yaml", "val")) 390 }) 391 } 392 393 func (r *registry) initAllConfigByLoadFunc(loadFn loadConfigFunc, opts ...utils.OptionExtender) { 394 if loadFn != nil { 395 loadFn(r.businessConfig, opts...) 396 loadFn(r.componentConfigs, opts...) 397 } 398 } 399 400 func (r *registry) initAllConfigByFlag() { 401 configVal := utils.IndirectValue(reflect.ValueOf(r.componentConfigs)).FieldByName(componentConfigFieldName) 402 for i := 0; i < len(r.componentList); i++ { 403 com := r.componentList[i] 404 if utils.IsStrPtrBlank(com.flagString) { 405 continue 406 } 407 switch com.name { 408 case ComponentApp: 409 if utils.IsStrNotBlank(appFlagString) { 410 configVal.FieldByName(com.name).SetString(appFlagString) 411 } 412 case ComponentDebug: 413 if debugFlag { 414 configVal.FieldByName(com.name).SetBool(debugFlag) 415 } 416 default: 417 comValp := configVal.FieldByName(com.name).Addr() 418 utils.MustSuccess(json.Unmarshal([]byte(*com.flagString), comValp.Interface())) 419 420 // process defaults 421 _ = utils.ParseTag(comValp.Interface(), utils.ParseTagName("default"), 422 utils.ParseTagUnmarshalType(utils.UnmarshalTypeYaml)) 423 } 424 } 425 426 if len(appBizFlagString) > 0 { 427 utils.MustSuccess(json.Unmarshal([]byte(appBizFlagString), &r.businessConfig)) 428 } 429 } 430 431 func (r *registry) initComponents(parent context.Context) func() { 432 ctx, cancel := context.WithCancel(parent) 433 ctxVal := reflect.ValueOf(ctx) 434 o1 := reflect.ValueOf(utils.OptionExtender(AppName(r.appName))) 435 o2 := reflect.ValueOf(utils.OptionExtender(DI(r.di))) 436 437 baseObject := r.getBaseObject() 438 destructors := make([]reflect.Value, 0, len(r.componentList)) 439 componentNames := make([]string, 0, len(r.componentList)) 440 hasCallbackComponentNames := make([]string, 0, len(r.componentList)) 441 for i := 0; i < len(r.componentList); i++ { 442 com := r.componentList[i] 443 comArgs := reflect.ValueOf(clone.Clone(baseObject.FieldByName(com.name).Interface())) 444 componentNames = append(componentNames, com.name) 445 if out := com.constructor.Call([]reflect.Value{ctxVal, comArgs, o1, o2}); len(out) > 0 && !out[0].IsNil() { 446 destructors = append(destructors, out[0]) 447 hasCallbackComponentNames = append(hasCallbackComponentNames, com.name) 448 } 449 } 450 451 /* print summary to stdout */ 452 pid := syscall.Getpid() 453 app := r.AppName() 454 log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds) 455 log.Printf("%v [Gofusion] %s initialized total %d components below: %s\n", 456 pid, app, len(componentNames), strings.Join(componentNames, ", ")) 457 458 once := new(sync.Once) 459 return func() { 460 once.Do(func() { 461 initLocker.Lock() 462 defer initLocker.Unlock() 463 464 defer close(r.closeCh) 465 466 r.initWg.Done() 467 r.initWg.Wait() 468 cancel() 469 for i := len(destructors) - 1; i >= 0; i-- { 470 log.Printf("%v [Gofusion] %s %s exiting...", pid, app, hasCallbackComponentNames[i]) 471 destructors[i].Call(nil) 472 log.Printf("%v [Gofusion] %s %s exited", pid, app, hasCallbackComponentNames[i]) 473 } 474 475 r.di.Clear() 476 r.businessConfig = nil 477 r.componentConfigs = nil 478 r.initOnce = new(sync.Once) 479 }) 480 } 481 } 482 483 func (r *registry) addComponent(com *Component) { 484 firstNonCoreComIndex := -1 485 for i, cp := range r.componentList { 486 if !cp.isCore { 487 firstNonCoreComIndex = i 488 break 489 } 490 } 491 if !com.isCore || firstNonCoreComIndex == -1 { 492 r.componentList = append(r.componentList, com) 493 sort.SliceStable(r.componentList, func(i, j int) bool { 494 // core component would not be sorted 495 if r.componentList[i].isCore || r.componentList[j].isCore { 496 return false 497 } 498 499 orderA := indexComponent(r.componentList[i].name) 500 if orderA == -1 { 501 return false 502 } 503 orderB := indexComponent(r.componentList[j].name) 504 if orderB == -1 { 505 return true 506 } 507 508 return orderA < orderB 509 }) 510 511 return 512 } 513 list := make([]*Component, len(r.componentList)+1) 514 for i := range list { 515 if i < firstNonCoreComIndex { 516 list[i] = r.componentList[i] 517 } else if i == firstNonCoreComIndex { 518 list[i] = com 519 } else { 520 list[i] = r.componentList[i-1] 521 } 522 } 523 524 r.componentList = list 525 } 526 527 func (r *registry) checkBusinessConfig(businessConfig any) { 528 typ := reflect.TypeOf(businessConfig) 529 if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Ptr { 530 panic(errors.New("businessConfig should be a **struct")) 531 } 532 }