目前,常用作配置文件的格式有多种多样,包括xml,yaml,json,ini等, 个人还是比较习惯使用ini,无论是读写都比较直观,还能有详细的注释。
本篇介绍一下怎么读取和使用ini格式的配置文件。
解释配置文件,读取配置项
为方便处理,配置读取后返回key,value字典表,key,value都是字符串,如有需要,使用时再进行类型转换。
每组key、value代表一个配置项。
var(
confData = make(map[string]map[string]string) //配置数据
cMtime int64 //配置的最后读取的时间,初始化时为0,保证首次调用会读取
)
func LoadConf(files ...string) (map[string]string, error) {
for _, file := range files { //遍历要读取的配置文件,支持多个
f, err := os.Open(file)
if err != nil {
return nil, err //如果配置文件打开出错,直接返回
}
defer f.Close()
stat, _ := f.Stat()
//判断文件是否有更新,文件的修改时间在上次读取之前,不重复读
if stat.ModTime().Unix() <= cMtime {
continue
}
//读取文件内容,解释出配置项
//配置项先按文件来分组保存,
confData[f.Name()] = make(map[string]string) //重置当前文件的字典表,保证多次调用时不会出现脏数据
buf := bufio.NewReader(f)
section := ""
for ln := 1; ; ln++ {
line, err := buf.ReadBytes('\n') //按行读文件内容
if err != nil {
if err != io.EOF { //读取失败且未到行尾,返回错误
return nil, err
} else if len(line) == 0 { // 已到行尾,且已没有读到内容,完成读取
break
}
}
line = bytes.TrimSpace(line)
if line == nil || bytes.HasPrefix(line, bComment) { //空行或注释,跳过
continue
}
if bytes.HasPrefix(line, bSectionStart) && bytes.HasSuffix(line, bSectionEnd) { //切换到新的section
section = strings.ToLower(string(line[1 : len(line)-1]))
continue
}
keyValue := bytes.SplitN(line, bEqual, 2) //用"="分成key,value
if len(keyValue) != 2 { //不符合key=value的格式,返回错误
return nil, errors.New(fmt.Sprintf("Load conf file error: file=%s,line=%d", file, ln))
}
key := string(bytes.TrimSpace(keyValue[0]))
if section != "" {
key = section + "." + key //key加上section作为完整的key
}
//val去掉空格,引号等,保存到字典中
val := bytes.TrimSpace(keyValue[1])
val = bytes.Trim(val, `"'`)
confData[f.Name()][key] = string(val)
}
}
cMtime = time.Now().Unix() //将当前时间设为最后读取时间
//合并复制多个文件的值(相同的会覆盖)
//不能直接使用,因为map为引用,修改读取的值会影响原始值(比如设置默认值)
data := make(map[string]string)
for _, d := range confData {
for k, v := range d {
data[k] = v
}
}
return data, nil
}
框架中使用
- 在服务启动时,先读入配置
func NewServer() *App{
conf, err := LoadConf(confFile...) //confFile是配置文件数组
//check err
err = checkConf(conf) //检查配置是否有问题,并为某些配置项设置缺省值
//check err
app = &App{
conf : conf
//其它成员
}
return app
}
func checkConf(conf){
setConfDefault(conf, "listen", ":8080") //将listen的缺省值设为":8080",在配置中未有该项时使用缺省值
if _,exists:= conf["log.path"];!exists{ //缺少必填的配置,报错
return error.New("conf error: log.path is require")
}
//other
....
}
//设置默认值,当conf[key]未设置时,将值设为val
func setConfDefault(conf map[string]string, key string, val string) {
if _, exists := conf[key]; !exists {
conf[key] = val
}
}
//reload和载入时一样(有变化时,会实时加载新的配置项)
func (this *App) reload(){
conf, err := LoadConf(confFile...)
//check err
err = checkConf(conf)
this.conf = conf
}
- 在controller中使用
type Controller struct{
*ecgo.App
}
func (this *Controller) Say(){
path := this.conf["log.path"]
sessPath,_ := this.conf["session.path"]
}
独立使用
因为配置读取是作为一个子包的方法提供,所以,也可以单独在你的项目中使用。
conf, err := LoadConf("a.ini","b.ini")
//check err
//conf保存了配置项,可以直接读取,如有需要,可根据exists是否为true来判断有没有该项配置
v1, exists := conf["key"] //exists为false时,v1="" 空串,如果空串的意义和没有配置的意义一样,可不判断exists
v2, exists := conf["section.key"]
conf, _ = LoadConf("c.ini")
//此时,conf包括a,b,c三个文件的配置内容