读写ini配置文件

INI 文件由多行文本组成,整个配置由[ ]拆分为多个“段”(section)。每个段中又以分割为“键”和“值”。

INI 文件以;置于行首视为注释,注释后将不会被处理和识别

[sectionl]
key1=value1
;key2=value2
[section2]

反射实现

/demo/config/ini.go

package config

import (
	"bufio"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"reflect"
	"strings"
)

type Ini struct {
	Path string
}

//根据段名,读取配置到结构体
func (i *Ini) Read(duan string, conf any) error {
	if i.Path == "" {
		return errors.New("path not defined")
	}
	val := reflect.ValueOf(conf)
	if val.Kind() != reflect.Ptr {
		return errors.New("conf must be a Struct")
	}
	val = val.Elem()
	typ := reflect.TypeOf(conf).Elem()
	if val.Kind() != reflect.Struct {
		return errors.New("conf must be a Struct")
	}
	m, err := ParseFile(i.Path)
	if err != nil {
		return err
	}
	data := m[duan]
	if len(data) == 0 {
		return nil
	}
	for num := 0; num < typ.NumField(); num++ {
		field := typ.Field(num)
		tag := field.Tag.Get("ini")
		val.Field(num).SetString(data[tag])
	}
	return nil
}

//将结构体以指定的段名写入ini
func (i *Ini) Write(duan string, conf any) error {
	if i.Path == "" {
		return errors.New("path not defined")
	}
	typ := reflect.TypeOf(conf)
	val := reflect.ValueOf(conf)
	//如果以指针传入
	if typ.Kind() == reflect.Ptr {
		typ = typ.Elem()
		if typ.Kind() != reflect.Struct {
			return errors.New("conf must a struct")
		}
		val = val.Elem()
	} else if typ.Kind() != reflect.Struct {
		return errors.New("conf must a struct")
	}
	m := make(map[string]string)
	for i := 0; i < typ.NumField(); i++ {
		tag := typ.Field(i).Tag.Get("ini")
		value := val.Field(i).String()
		m[tag] = value
	}
	return WriteFile(i.Path, duan, m)
}

// ParseFile 将ini文件中的数据以map切片形式读出
func ParseFile(path string) (map[string]map[string]string, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	reader := bufio.NewReader(f)
	m := make(map[string]map[string]string)
	//表示当前记录的段名
	duanName := ""
	for {
		s, err := reader.ReadString('\n')
		if err != nil {
			break
		}
		// 去掉行两边的空格
		s = strings.TrimSpace(s)
		//如果是空行或者注释行跳过
		if s == "" || s[0] == ';' {
			continue
		}
		//如果以[开头并以]结尾,说明是段
		if s[0] == '[' && s[len(s)-1] == ']' {
			duanName = s[1 : len(s)-1]
			m[duanName] = make(map[string]string)
			continue
		}
		kv := strings.Split(s, "=")
		if duanName != "" {
			key := strings.TrimSpace(kv[0])
			val := strings.TrimSpace(kv[1])
			m[duanName][key] = val
		}
	}
	return m, nil
}

// WriteFile 将map作为指定段的内容写入ini,其余的配置不变
func WriteFile(path string, duan string, m map[string]string) error {
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}
	data := string(b)
	//将数据按照段切分为切片
	dataSlice := strings.Split(data, "[")
	dataSlice = dataSlice[1:]
	fmt.Println(dataSlice)
	duan = fmt.Sprintf("[%s]", duan)
	index := -1
	//判断该段是否已经存在
	for i := 0; i < len(dataSlice); i++ {
		dataSlice[i] = "[" + dataSlice[i]
		fmt.Println(dataSlice[i])
		if strings.Index(dataSlice[i], duan) != -1 {
			index = i
		}
	}
	duanData := "\n" + duan + "\n"
	//将参数封装为字符串
	for k, v := range m {
		duanData += fmt.Sprintf("%s = %s\n", k, v)
	}
	//判断该段是否已经存在,存在就覆写,不存在就追加一个
	if index != -1 {
		dataSlice[index] = duanData
	} else {
		dataSlice = append(dataSlice, duanData)
	}
	data = strings.Join(dataSlice, "")
	err = ioutil.WriteFile(path, []byte(data), 0777)
	if err != nil {
		return err
	}
	return nil
}

/demo/config/model.go

package config

type Mysql struct {
	Driver   string `ini:"driver"`
	UserName string `ini:"username"`
	PassWord string `ini:"password"`
}

type Oracle struct {
	UserName string `ini:"username"`
	PassWord string `ini:"password"`
}

/demo/db.ini

[mysql]
username = root
password = 123456
driver = com.jdbc.mysql
[oracle]
username = orl
password = 123456

/demo/main.go

package main

import (
	"demo/config"
	"fmt"
)

func main() {
	i := &config.Ini{"./db.ini"}

	mysql := config.Mysql{}
	oracle := config.Oracle{}
	err := i.Read("mysql", &mysql)
	if err != nil {
		fmt.Println(err)
	}
	err = i.Read("oracle", &oracle)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(mysql)  //{com.jdbc.mysql root 123456}
	fmt.Println(oracle) //{orl 123456}
}

package main

import (
	"demo/config"
	"fmt"
)

func main() {
	m := config.Mysql{"con.test.mysql", "mysql", "78787878"}
	ini := config.Ini{"./db.ini"}
	err := ini.Write("mysql", m)
	if err != nil {
		fmt.Println(err)
	}
}