package apollo import ( "bufio" "fmt" "io/ioutil" "os" "os/signal" "reflect" "sync" "time" agollo2 "git.shuncheng.lu/bigthing/gocommon/pkg/conf/remote/apollo/agollo" "git.shuncheng.lu/bigthing/gocommon/pkg/internal/util" "github.com/tidwall/gjson" ) var ( readLock sync.Mutex rootPath string lastConfig map[string]interface{} watchChannel = make(chan struct{}) nameSpaceConfig gjson.Result regularMonitorInterval = 30 * time.Second ) func Start() { rootPath = util.GetRootPath() + "/config" err := loadNameSpacesConfig(getNameSpacesConfigPath()) if err != nil { panic(fmt.Sprintf("apollo:load err: %s", err.Error())) } err = agollo2.InitWithConfigFile( getApolloConfigPath(), agollo2.WithLogger(agollo2.NewLogger(agollo2.LoggerWriter(os.Stdout))), // 打印日志信息 agollo2.PreloadNamespaces(getNameSpaces()...), // 预先加载的namespace列表,如果是通过配置启动,会在app.properties配置的基础上追加 agollo2.AutoFetchOnCacheMiss(), // 在配置未找到时,去apollo的带缓存的获取配置接口,获取配置 agollo2.FailTolerantOnBackupExists(), agollo2.BackupFile(getBackFilePath()), // 在连接apollo失败时,如果在配置的目录下存在.agollo备份配置,会读取备份在服务器无法连接的情况下 ) if err != nil { panic(fmt.Sprintf("apollo:init err: %s", err.Error())) } err = writeMapToFile() if err != nil { panic(fmt.Sprintf("apollo:write file err: %s", err.Error())) } go listen() } func GetConfigPath() string { return rootPath + "/apollo/env.ini" } func SetRefreshInterval(second int) { regularMonitorInterval = time.Duration(second) * time.Second } func Watch() <-chan struct{} { return watchChannel } func listen() { signals := make(chan os.Signal) signal.Notify(signals, os.Interrupt) lastConfig = deepCopy(agollo2.GetAll()) for { timer := time.NewTimer(regularMonitorInterval) select { case <-timer.C: err := agollo2.Reload() if err != nil { fmt.Printf("[apollo] reload err: %s\n", err) continue } newConfig := deepCopy(agollo2.GetAll()) if reflect.DeepEqual(lastConfig, newConfig) { continue } lastConfig = newConfig err = writeMapToFile() if err != nil { fmt.Printf("[apollo] write env err: %s\n", err) continue } watchChannel <- struct{}{} case <-signals: fmt.Println("[apollo] receive signal, exited") return } } select {} } func getBackFilePath() string { return rootPath + "/apollo/.agollo" } func getApolloConfigPath() string { return rootPath + "/apollo/apollo.json" } func getNameSpacesConfigPath() string { return rootPath + "/apollo/namespaces.json" } func getNameSpaces() []string { nameSpaceList := make([]string, 0) nameSpaceConfig.ForEach(func(key gjson.Result, value gjson.Result) bool { nameSpaceList = append(nameSpaceList, getNameSpace(value)) return true }) return nameSpaceList } func getNameSpace(nameSpace gjson.Result) string { return nameSpace.Get("namespace").String() } func getSections(sections gjson.Result) gjson.Result { return sections.Get("sections") } func getSectionName(section gjson.Result) string { return section.Get("name").String() } func getKeys(section gjson.Result) gjson.Result { return section.Get("keys") } func getKeyName(key gjson.Result) string { return key.Get("name").String() } func getKeyMapTo(key gjson.Result) string { return key.Get("mapTo").String() } func getKeysMap() map[string]map[string]map[string]string { maps := map[string]map[string]map[string]string{} nameSpaceConfig.ForEach(func(nameSpaceKey gjson.Result, value gjson.Result) bool { nameSpace := getNameSpace(value) _, ok := maps[nameSpace] if !ok { maps[nameSpace] = map[string]map[string]string{} } sections := getSections(value) sections.ForEach(func(sectionKey gjson.Result, section gjson.Result) bool { sectionName := getSectionName(section) keys := getKeys(section) keys.ForEach(func(keys gjson.Result, key gjson.Result) bool { keyName := getKeyName(key) keyMapTo := getKeyMapTo(key) _, ok := maps[nameSpace][keyName] if ok { return true } maps[nameSpace][keyName] = map[string]string{} maps[nameSpace][keyName]["section"] = sectionName keyRealName := keyMapTo if keyMapTo == "" { keyRealName = keyName } maps[nameSpace][keyName]["mapTo"] = keyRealName return true }) return true }) return true }) return maps } func loadNameSpacesConfig(configPath string) error { file, err := os.Open(configPath) if err != nil { return err } defer file.Close() contents, err := ioutil.ReadAll(file) if err != nil { return err } nameSpaceConfig = gjson.Get(string(contents), "namespaces") return nil } //将所有的配置信息写入标准的配置文件里面 func writeMapToFile() error { readLock.Lock() defer readLock.Unlock() f, err := os.Create(GetConfigPath()) if err != nil { fmt.Printf("apollo:create config file err, err: %s", err.Error()) return err } defer f.Close() w := bufio.NewWriter(f) keysMap := getKeysMap() for _, nameSpace := range getNameSpaces() { sections := map[string][]string{} _, ok := keysMap[nameSpace] if !ok { continue } ConfigInfo := agollo2.GetNameSpace(nameSpace) for key, val := range ConfigInfo { _, ok = keysMap[nameSpace][key] if !ok { continue } _, ok = sections[keysMap[nameSpace][key]["section"]] if !ok { sections[keysMap[nameSpace][key]["section"]] = make([]string, 0) } lineStr := fmt.Sprintf("%s=%s", keysMap[nameSpace][key]["mapTo"], val) sections[keysMap[nameSpace][key]["section"]] = append(sections[keysMap[nameSpace][key]["section"]], lineStr) } if len(sections) == 0 { continue } for section, lineStrs := range sections { lineSection := fmt.Sprintf("[%s]", section) fmt.Fprintln(w, lineSection) for _, lineStr := range lineStrs { fmt.Fprintln(w, lineStr) } } } err = w.Flush() if err == nil { return nil } return err } func deepCopy(value map[string]interface{}) map[string]interface{} { newMap := make(map[string]interface{}) for k, v := range value { newMap[k] = v } return newMap }