基于Golang的监听读取配置文件的程序包开发——simpleConfig_v1
基于Golang的監(jiān)聽(tīng)&讀取配置文件的程序包開(kāi)發(fā)——simpleConfig_v1 【閱讀時(shí)間:約10分鐘】
- 一、配置文件概述
- 二、系統(tǒng)環(huán)境&項(xiàng)目介紹
- 1.系統(tǒng)環(huán)境
- 2.項(xiàng)目的任務(wù)要求
- 三、具體程序設(shè)計(jì)及Golang代碼實(shí)現(xiàn)
- 1. 數(shù)據(jù)結(jié)構(gòu)
- 2. init函數(shù)模塊
- 3.listen函數(shù)模塊
- 4.watch函數(shù)模塊
- 四、設(shè)置自定義錯(cuò)誤
- 五、程序測(cè)試
- 1.封裝并使用程序包
- 2.功能測(cè)試
- 3.單元測(cè)試
- 六、中文 api 文檔
- 七、完整代碼
- 八、References
一、配置文件概述
配置文件(Configuration File,CF)是一種文本文檔,為計(jì)算機(jī)系統(tǒng)或程序配置參數(shù)和初始設(shè)置。傳統(tǒng)的配置文件就是文本行,在 Unix 系統(tǒng)中隨處可見(jiàn),通常使用 .conf,.config,.cfg 作為后綴,并逐步形成了 key = value 的配置習(xí)慣。在 Windows 系統(tǒng)中添加了對(duì) section 支持,通常用 .ini 作為后綴。面向?qū)ο笳Z(yǔ)言的興起,程序員需要直接將文本反序列化成內(nèi)存對(duì)象作為配置,逐步提出了一些新的配置文件格式,包括 JSON,YAML,TOML 等。
本次監(jiān)聽(tīng)&讀取配置文件的程序包()開(kāi)發(fā),主要應(yīng)用于ini配置文件。
開(kāi)發(fā)過(guò)程中使用的配置文件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
二、系統(tǒng)環(huán)境&項(xiàng)目介紹
1.系統(tǒng)環(huán)境
操作系統(tǒng):CentOS7
硬件信息:使用virtual box配置虛擬機(jī)(內(nèi)存3G、磁盤(pán)30G)
編程語(yǔ)言:GO 1.15.2
2.項(xiàng)目的任務(wù)要求
-
核心任務(wù):包必須提供一個(gè)函數(shù)
Watch(filename,listener) (configuration, error)- 輸入 filename 是配置文件名
- 輸入 listener 一個(gè)特殊的接口,用來(lái)監(jiān)聽(tīng)配置文件是否被修改,讓開(kāi)發(fā)者自己決定如何處理配置變化
type ListenFunc func(string)type inteface Listener { listen(inifile string) }ListenFunc實(shí)現(xiàn)接口方法listen直接調(diào)用函數(shù)- 優(yōu)點(diǎn)
- 所有滿(mǎn)足簽名的函數(shù)、方法都可以作為參數(shù)
- 所有實(shí)現(xiàn) Listener 接口的數(shù)據(jù)類(lèi)型都可作為參數(shù)
- 輸出 configuration 數(shù)據(jù)類(lèi)型,可根據(jù) key 讀對(duì)應(yīng)的 value。 key 和 value 都是字符串
- 輸出 error 是錯(cuò)誤數(shù)據(jù),如配置文件不存在,無(wú)法打開(kāi)等
- 可選的函數(shù)
WatchWithOption(filename,listener,...) (configuration, error)
-
包必須包括以下內(nèi)容:
- 生成的中文 api 文檔
- 有較好的 Readme 文件,包括一個(gè)簡(jiǎn)單的使用案例
- 每個(gè)go文件必須有對(duì)應(yīng)的測(cè)試文件
- 必須提供自定義錯(cuò)誤
- 使有 init 函數(shù),使得 Unix 系統(tǒng)默認(rèn)采用
#作為注釋行,Windows 系統(tǒng)默認(rèn)采用;作為注釋行。
-
不能使用第三方包,但可以參考、甚至復(fù)制它們的代碼。例如:
-
ini 讀寫(xiě)包。 Github,中文支持
-
Viper 讀配置集成解決方案包。Github
live watching and re-reading of config files (optional) -
fsnotify 文件系統(tǒng)通知包。 Github
-
你可以參考這些代碼,但不能在你的包中 import
-
三、具體程序設(shè)計(jì)及Golang代碼實(shí)現(xiàn)
根據(jù)任務(wù)要求可知,simpleConfig_v1程序包中的watch函數(shù)相當(dāng)于load、read、listen這三個(gè)函數(shù)的結(jié)合。在調(diào)用該函數(shù)時(shí),首先會(huì)輸出配置文件的原始信息,然后會(huì)一直監(jiān)聽(tīng)配置文件有無(wú)改動(dòng),若有改動(dòng)則會(huì)提示并展示最新的配置文件信息。
simpleConfig_v1程序包的函數(shù)架構(gòu)如下:
下面按照simpleConfig_v1程序包的源碼順序來(lái)依次介紹數(shù)據(jù)結(jié)構(gòu)和相關(guān)函數(shù)。
1. 數(shù)據(jù)結(jié)構(gòu)
var sys string
var flag int//three layer, like [server] -> protocol -> http
type Config [](map[string](map[string]string))
sys用于標(biāo)記注釋行, Unix 系統(tǒng)默認(rèn)采用 # 作為注釋行,Windows 系統(tǒng)默認(rèn)采用 ; 作為注釋行。
flag用于標(biāo)記watch函數(shù)只能輸出一次原始配置文件的信息。
Config是配置文件的數(shù)據(jù)結(jié)構(gòu),一個(gè)簡(jiǎn)單的配置文件最多可有三層,比如:
FirstLayer->SecondLayer->ThirdLayer
[server] -> protocol -> http
2. init函數(shù)模塊
func init() {flag = 0if runtime.GOOS == "windows" {sys = ";"} else {sys = "#"}
}
使有 init 函數(shù),使得 Unix 系統(tǒng)默認(rèn)采用 # 作為注釋行,Windows 系統(tǒng)默認(rèn)采用 ; 作為注釋行。
3.listen函數(shù)模塊
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 + "文件發(fā)生改變,改變后的配置信息如下:")//fmt.Println(configuration)for key, value := range configuration {fmt.Println(key, ":", value)}fmt.Println("")}time.Sleep(time.Duration(2) * time.Second)}
}
輸入 listener 一個(gè)特殊的接口,用來(lái)監(jiān)聽(tīng)配置文件是否被修改,讓開(kāi)發(fā)者自己決定如何處理配置變化。
- type ListenFunc func(string)
- type inteface Listener { listen(inifile string) }
- ListenFunc 實(shí)現(xiàn)接口方法 listen 直接調(diào)用函數(shù)
- 優(yōu)點(diǎn)
- 所有滿(mǎn)足簽名的函數(shù)、方法都可以作為參數(shù)
- 所有實(shí)現(xiàn) Listener 接口的數(shù)據(jù)類(lèi)型都可作為參數(shù)
在上述例子中,listen函數(shù)的執(zhí)行流程為
watch函數(shù) -> listener.Listen(filename)函數(shù) -> OnConfigChange函數(shù)
其中OnConfigChange函數(shù)具體實(shí)現(xiàn)如下:
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}}}}}
}
每當(dāng)listen函數(shù)模塊監(jiān)聽(tīng)到配置文件由發(fā)生修改,便會(huì)提示修改信息和輸出修改后的配置文件信息。
4.watch函數(shù)模塊
//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函數(shù)相當(dāng)于load、read、listen這三個(gè)函數(shù)的結(jié)合。在調(diào)用該函數(shù)時(shí),首先會(huì)輸出配置文件的原始信息,然后會(huì)一直監(jiān)聽(tīng)配置文件有無(wú)改動(dòng),若有改動(dòng)則會(huì)提示并展示最新的配置文件信息。
其中l(wèi)oad&read的函數(shù)為ReadConfig函數(shù),其具體實(shí)現(xiàn)如下:
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
}
四、設(shè)置自定義錯(cuò)誤
利用errors包,可以在【三】的基礎(chǔ)上添加自定義錯(cuò)誤如下:
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}}...
}
五、程序測(cè)試
1.封裝并使用程序包
將項(xiàng)目simpleConfig_v1的simpleConfig_v1.go文件的main函數(shù)注釋掉,package改為package simpleConfig_v1,然后執(zhí)行如下指令:
go build
在其他路徑下建立main.go,內(nèi)容如下(listen函數(shù)可由用戶(hù)自定義設(shè)置,此處使用simpleConfig_v1自帶的OnConfigChange函數(shù)):
//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 + "文件發(fā)生改變,改變后的配置信息如下:")//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配置文件,內(nèi)容如下:
# 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.功能測(cè)試
功能測(cè)試主要從用戶(hù)角度測(cè)試程序包的功能,步驟如下:
[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文件發(fā)生改變,改變后的配置信息如下:
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文件發(fā)生改變,改變后的配置信息如下:
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
由此可知程序包的功能測(cè)試正常,調(diào)用程序包后首先會(huì)輸出配置文件的原始信息,然后會(huì)一直監(jiān)聽(tīng)配置文件有無(wú)改動(dòng),若有改動(dòng)則會(huì)提示并展示最新的配置文件信息。通過(guò)鍵盤(pán)ctrl+c可以終止監(jiān)聽(tīng)程序。
3.單元測(cè)試
單元測(cè)試主要從程序員角度,對(duì)程序包的具體函數(shù)進(jìn)行測(cè)試。
①init函數(shù)
測(cè)試代碼:
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)}
}
測(cè)試結(jié)果:
②ReadConfig函數(shù)
測(cè)試代碼:
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)}
}
測(cè)試結(jié)果:
③listen函數(shù)
測(cè)試代碼:
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)}
}
測(cè)試結(jié)果:
④watch函數(shù)
測(cè)試代碼:
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)}
}
測(cè)試結(jié)果:
通過(guò)簡(jiǎn)單的單元測(cè)試可知,程序包的函數(shù)均可正常調(diào)用。
六、中文 api 文檔
首先安裝godoc如下:
git clone https://github.com/golang/tools $GOPATH/src/golang.org/x/tools
go build golang.org/x/tools
將項(xiàng)目simpleConfig_v1的simpleConfig_v1.go文件的main函數(shù)注釋掉,package改為package simpleConfig_v1,然后執(zhí)行如下指令:
go install
go doc
godoc -url="pkg/github.com/user/simpleConfig_v1" > API.html
便會(huì)在當(dāng)前目錄下生成API.html文件:
七、完整代碼
具體代碼可見(jiàn)gitee倉(cāng)庫(kù):gitee
八、References
- ini 讀寫(xiě)包。 Github
- Viper 讀配置集成解決方案包。Github
- fsnotify 文件系統(tǒng)通知包。 Github
總結(jié)
以上是生活随笔為你收集整理的基于Golang的监听读取配置文件的程序包开发——simpleConfig_v1的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Test Reprot
- 下一篇: 【golang程序包推荐分享】go-in