基于Golang的监听读取配置文件的程序包开发——simpleConfig_v1
基于Golang的監聽&讀取配置文件的程序包開發——simpleConfig_v1 【閱讀時間:約10分鐘】
- 一、配置文件概述
- 二、系統環境&項目介紹
- 1.系統環境
- 2.項目的任務要求
 
- 三、具體程序設計及Golang代碼實現
- 1. 數據結構
- 2. init函數模塊
- 3.listen函數模塊
- 4.watch函數模塊
 
- 四、設置自定義錯誤
- 五、程序測試
- 1.封裝并使用程序包
 
- 2.功能測試
- 3.單元測試
- 六、中文 api 文檔
- 七、完整代碼
- 八、References
一、配置文件概述
配置文件(Configuration File,CF)是一種文本文檔,為計算機系統或程序配置參數和初始設置。傳統的配置文件就是文本行,在 Unix 系統中隨處可見,通常使用 .conf,.config,.cfg 作為后綴,并逐步形成了 key = value 的配置習慣。在 Windows 系統中添加了對 section 支持,通常用 .ini 作為后綴。面向對象語言的興起,程序員需要直接將文本反序列化成內存對象作為配置,逐步提出了一些新的配置文件格式,包括 JSON,YAML,TOML 等。
本次監聽&讀取配置文件的程序包()開發,主要應用于ini配置文件。
 開發過程中使用的配置文件config.ini格式案例如下:
# possible values : production, development
app_mode = development[paths]
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
data = /home/git/grafana[server]
# Protocol (http or https)
protocol = http# The http port  to use
http_port = 9999# Redirect to correct domain if host header does not match domain
# Prevents DNS rebinding attacks
enforce_domain = true
二、系統環境&項目介紹
1.系統環境
操作系統:CentOS7
 硬件信息:使用virtual box配置虛擬機(內存3G、磁盤30G)
 編程語言:GO 1.15.2
2.項目的任務要求
-  核心任務:包必須提供一個函數 Watch(filename,listener) (configuration, error)- 輸入 filename 是配置文件名
- 輸入 listener 一個特殊的接口,用來監聽配置文件是否被修改,讓開發者自己決定如何處理配置變化 - type ListenFunc func(string)
- type inteface Listener { listen(inifile string) }
- ListenFunc實現接口方法- listen直接調用函數
- 優點 - 所有滿足簽名的函數、方法都可以作為參數
- 所有實現 Listener 接口的數據類型都可作為參數
 
 
- 輸出 configuration 數據類型,可根據 key 讀對應的 value。 key 和 value 都是字符串
- 輸出 error 是錯誤數據,如配置文件不存在,無法打開等
- 可選的函數 WatchWithOption(filename,listener,...) (configuration, error)
 
-  包必須包括以下內容: - 生成的中文 api 文檔
- 有較好的 Readme 文件,包括一個簡單的使用案例
- 每個go文件必須有對應的測試文件
- 必須提供自定義錯誤
- 使有 init 函數,使得 Unix 系統默認采用 #作為注釋行,Windows 系統默認采用;作為注釋行。
 
-  不能使用第三方包,但可以參考、甚至復制它們的代碼。例如: -  ini 讀寫包。 Github,中文支持 
-  Viper 讀配置集成解決方案包。Github live watching and re-reading of config files (optional)
-  fsnotify 文件系統通知包。 Github 
-  你可以參考這些代碼,但不能在你的包中 import 
 
-  
三、具體程序設計及Golang代碼實現
根據任務要求可知,simpleConfig_v1程序包中的watch函數相當于load、read、listen這三個函數的結合。在調用該函數時,首先會輸出配置文件的原始信息,然后會一直監聽配置文件有無改動,若有改動則會提示并展示最新的配置文件信息。
simpleConfig_v1程序包的函數架構如下:
 
下面按照simpleConfig_v1程序包的源碼順序來依次介紹數據結構和相關函數。
1. 數據結構
var sys string
var flag int//three layer, like [server] -> protocol -> http
type Config [](map[string](map[string]string))
sys用于標記注釋行, Unix 系統默認采用 # 作為注釋行,Windows 系統默認采用 ; 作為注釋行。
 flag用于標記watch函數只能輸出一次原始配置文件的信息。
 Config是配置文件的數據結構,一個簡單的配置文件最多可有三層,比如:
FirstLayer->SecondLayer->ThirdLayer
[server] -> protocol -> http
2. init函數模塊
func init() {flag = 0if runtime.GOOS == "windows" {sys = ";"} else {sys = "#"}
}
使有 init 函數,使得 Unix 系統默認采用 # 作為注釋行,Windows 系統默認采用 ; 作為注釋行。
 
3.listen函數模塊
type Listener interface {Listen(filename string)
}type ListenFunc func(filename string) (Config, error)func (fun ListenFunc) Listen(filename string) (Config, error) {return fun(filename)
}func Watch(filename string, listener ListenFunc) (Config, error) {...//listenreturn listener.Listen(filename)
}func main() {var listener ListenFunc = OnConfigChangefilename := "config.ini"for {configuration, err := Watch(filename, listener)if err != nil {fmt.Println(err.Error())} else {fmt.Println(filename + "文件發生改變,改變后的配置信息如下:")//fmt.Println(configuration)for key, value := range configuration {fmt.Println(key, ":", value)}fmt.Println("")}time.Sleep(time.Duration(2) * time.Second)}
}
輸入 listener 一個特殊的接口,用來監聽配置文件是否被修改,讓開發者自己決定如何處理配置變化。
 - type ListenFunc func(string)
 - type inteface Listener { listen(inifile string) }
 - ListenFunc 實現接口方法 listen 直接調用函數
 - 優點
 - 所有滿足簽名的函數、方法都可以作為參數
 - 所有實現 Listener 接口的數據類型都可作為參數
在上述例子中,listen函數的執行流程為
watch函數 -> listener.Listen(filename)函數 -> OnConfigChange函數
其中OnConfigChange函數具體實現如下:
func OnConfigChange(filename string) (Config, error) {temp := new(Config)config1, err := temp.ReadConfig(filename)if err != nil {return config1, err}flag2 := falsefor {temp2 := new(Config)config2, err := temp2.ReadConfig(filename)if err != nil {return config2, err}if len(config1) != len(config2) {return config2, nil}for _, i := range config2 {for j, k := range i {for l, m := range k {flag2 = falsefor _, n := range config1 {map1 := n[j]map2 := map1[l]if map2 == m {flag2 = true}}if flag2 == false {return config2, nil}}}}}
}
每當listen函數模塊監聽到配置文件由發生修改,便會提示修改信息和輸出修改后的配置文件信息。
 
4.watch函數模塊
//Watch = load + read + listen
func Watch(filename string, listener ListenFunc) (Config, error) {//load + readif flag == 0 {config := new(Config)configuration, err := config.ReadConfig(filename)if err != nil {fmt.Println(err.Error())} else {fmt.Println("")fmt.Println(filename + "文件原始的的配置信息如下:")//fmt.Println(configuration)for key, value := range configuration {fmt.Println(key, ":", value)}fmt.Println("")}flag = 1}//listenreturn listener.Listen(filename)
}
watch函數相當于load、read、listen這三個函數的結合。在調用該函數時,首先會輸出配置文件的原始信息,然后會一直監聽配置文件有無改動,若有改動則會提示并展示最新的配置文件信息。
其中load&read的函數為ReadConfig函數,其具體實現如下:
func (c *Config) ReadConfig(filename string) (Config, error) {file, err := os.Open(filename)if err != nil {return nil, err}defer file.Close()var element map[string]map[string]stringvar FirstLayer stringbuf := bufio.NewReader(file)for {l, err := buf.ReadString('\n')line := strings.TrimSpace(l)if err != nil {if err != io.EOF {return nil, err}if len(line) == 0 {break}}switch {case len(line) == 0:case string(line[0]) == sys:case line[0] == '[' && line[len(line)-1] == ']':FirstLayer = strings.TrimSpace(line[1 : len(line)-1])element = make(map[string]map[string]string)element[FirstLayer] = make(map[string]string)default:index := strings.IndexAny(line, "=")value := strings.TrimSpace(line[index+1 : len(line)])if FirstLayer == "" {FirstLayer = "FirstLayer"}element = make(map[string]map[string]string)element[FirstLayer] = make(map[string]string)valmap := strings.TrimSpace(line[0:index])element[FirstLayer][valmap] = value*c = append(*c, element)}}return *c, nil
}
四、設置自定義錯誤
利用errors包,可以在【三】的基礎上添加自定義錯誤如下:
func OnConfigChange(filename string) (Config, error) {temp := new(Config)config1, err := temp.ReadConfig(filename)if err != nil {err2 := errors.New("Could not read the config file.")return config1, err2}...temp2 := new(Config)config2, err := temp2.ReadConfig(filename)if err != nil {err2 := errors.New("Could not read the config file.")return config2, err2}...
}func (c *Config) ReadConfig(filename string) (Config, error) {file, err := os.Open(filename)if err != nil {err2 := errors.New("Could not open the config file.")return nil, err2}...l, err := buf.ReadString('\n')line := strings.TrimSpace(l)if err != nil {if err != io.EOF {err2 := errors.New("Could not read the config element.")return nil, err2}if len(line) == 0 {break}}...
}
五、程序測試
1.封裝并使用程序包
將項目simpleConfig_v1的simpleConfig_v1.go文件的main函數注釋掉,package改為package simpleConfig_v1,然后執行如下指令:
go build
在其他路徑下建立main.go,內容如下(listen函數可由用戶自定義設置,此處使用simpleConfig_v1自帶的OnConfigChange函數):
//main.go
package mainimport ("fmt""time""github.com/user/simpleConfig_v1"
)func main() {var listener simpleConfig_v1.ListenFunc = simpleConfig_v1.OnConfigChangefilename := "config.ini"for {configuration, err := simpleConfig_v1.Watch(filename, listener)if err != nil {fmt.Println(err.Error())} else {fmt.Println(filename + "文件發生改變,改變后的配置信息如下:")//fmt.Println(configuration)for key, value := range configuration {fmt.Println(key, ":", value)}fmt.Println("")}time.Sleep(time.Duration(2) * time.Second)}}并在main.go的目錄下存放config.ini配置文件,內容如下:
# possible values : production, development
app_mode = development[paths]
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
data = /home/git/grafana[server]
# Protocol (http or https)
protocol = http# The http port  to use
http_port = 9999# Redirect to correct domain if host header does not match domain
# Prevents DNS rebinding attacks
enforce_domain = true
2.功能測試
功能測試主要從用戶角度測試程序包的功能,步驟如下:
 
 
 
[henryhzy@localhost user]$ go run main.goconfig.ini文件原始的的配置信息如下:
0 : map[FirstLayer:map[app_mode:development]]
1 : map[paths:map[data:/home/git/grafana]]
2 : map[server:map[protocol:http]]
3 : map[server:map[http_port:9999]]
4 : map[server:map[enforce_domain:true]]config.ini文件發生改變,改變后的配置信息如下:
0 : map[FirstLayer:map[app_mode:development]]
1 : map[paths:map[data:/home/git/grafana]]
2 : map[server:map[protocol:http]]
3 : map[server:map[http_port:18342026]]
4 : map[server:map[enforce_domain:true]]config.ini文件發生改變,改變后的配置信息如下:
0 : map[FirstLayer:map[app_mode:henryhzy]]
1 : map[paths:map[data:/home/git/grafana]]
2 : map[server:map[protocol:http]]
3 : map[server:map[http_port:18342026]]
4 : map[server:map[enforce_domain:true]]^Csignal: interrupt由此可知程序包的功能測試正常,調用程序包后首先會輸出配置文件的原始信息,然后會一直監聽配置文件有無改動,若有改動則會提示并展示最新的配置文件信息。通過鍵盤ctrl+c可以終止監聽程序。
 
3.單元測試
單元測試主要從程序員角度,對程序包的具體函數進行測試。
①init函數
 測試代碼:
func Test_init(t *testing.T) {var test_sys string = "#"got := syswant := test_sysif got != want {t.Errorf("\n got %s\n want %s\n", got, want)}
}
測試結果:
 
②ReadConfig函數
 測試代碼:
func Test_ReadConfig(t *testing.T) {filename := "config.ini"temp := new(Config)_, err := temp.ReadConfig(filename)if err != nil {t.Errorf("ReadConfig function failed\n%s\n", err)}
}
測試結果:
 
③listen函數
 測試代碼:
func Test_listen(t *testing.T) {var listener ListenFunc = OnConfigChangefilename := "error_name.ini"_, err := listener(filename)got := fmt.Sprintf("%s", err)err2 := errors.New("Could not read the config file.")want := fmt.Sprintf("%s", err2)if got != want {t.Errorf("\nListen function failed\n%s\n%s", err, err2)}
}
測試結果:
 
 ④watch函數
 測試代碼:
func Test_watch(t *testing.T) {var listener ListenFunc = OnConfigChangefilename := "error_name.ini"_, err := Watch(filename, listener)got := fmt.Sprintf("%s", err)err2 := errors.New("Could not read the config file.")want := fmt.Sprintf("%s", err2)if got != want {t.Errorf("\nListen function failed\n%s\n%s", err, err2)}
}測試結果:
 
 通過簡單的單元測試可知,程序包的函數均可正常調用。
 
六、中文 api 文檔
首先安裝godoc如下:
git clone https://github.com/golang/tools $GOPATH/src/golang.org/x/tools
go build golang.org/x/tools
將項目simpleConfig_v1的simpleConfig_v1.go文件的main函數注釋掉,package改為package simpleConfig_v1,然后執行如下指令:
go install
go doc
godoc -url="pkg/github.com/user/simpleConfig_v1" > API.html
便會在當前目錄下生成API.html文件:
 
七、完整代碼
具體代碼可見gitee倉庫:gitee
 
八、References
- ini 讀寫包。 Github
- Viper 讀配置集成解決方案包。Github
- fsnotify 文件系統通知包。 Github
總結
以上是生活随笔為你收集整理的基于Golang的监听读取配置文件的程序包开发——simpleConfig_v1的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: Test Reprot
- 下一篇: 【golang程序包推荐分享】go-in
