编写GO的WEB开发框架 (十三): 配置文件读取

目前,常用作配置文件的格式有多种多样,包括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三个文件的配置内容

转载于:https://my.oschina.net/tim8670/blog/638893

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值