Go 实现一个简易的[ ini | properties ]配置文件的解析
因为什么需要 ini 配置文件?
方便管理项目的各种配置,比如 mysql、redis等配置
用到比较重要的知识点:reflect(反射)
本文不进行详细介绍,请自行进行查询相关资料
1、分析 ini 文件
内容如下 (project_dev.ini)
** 关于 mysql 的配置块
[mysql]
## 数据库的连接地址
jdbc=jdbc:mysql://127.0.0.1:3306/test_db
// 数据库的登录账户
username=oukele
# 数据库的登录密码
password=oukele..
** 关于 redis 的配置块
[redis]
ip=127.0.0.1
port=1234
username=oukele
password=oukele..
当出现注释符号(**、##、//、#)或者 空白行时,不进行处理(这些数据对于程序来说不是必要的信息)
2、读取 ini 文件的内容,并使用 map 接口储存
// 跳过空白行、注释行
func ignoreComments(str string) bool {
if len(str) == 0 {
return true
}
if strings.HasPrefix(str, "**") || strings.HasPrefix(str, "##") || strings.HasPrefix(str, "//") || strings.HasPrefix(str, "#") {
return true
}
return false
}
// ReadFileData /**读取配置文件的信息,并且分好配置块的信息
func ReadFileData(filePath string) (map[string]map[string]string, error) {
file, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err
}
// 初始化map
var allMap = make(map[string]map[string]string, 5)
// 将 字节转成 字符串
str := string(file)
strRow := strings.Split(str, "\n")
var currentKey = ""
for _, row := range strRow {
row = strings.TrimSpace(row)
if ignoreComments(row) {
continue
}
// 获取到 [xxx] 的字符串
if strings.HasPrefix(row, "[") && strings.HasSuffix(row, "]") {
// 去除所有的 [ 和 ]
row = strings.ReplaceAll(row, "[", "")
row = strings.ReplaceAll(row, "]", "")
// 去除字符误打的空格 比如 这样的 [ m y s q l ]
row = strings.ReplaceAll(row, " ", "")
_, ok := allMap[row]
// 此键已经存在则跳过
if ok {
continue
}
// 将当前的字符作为map的键,并初始化里边的 map
currentKey = row
allMap[row] = make(map[string]string, 3)
continue
}
// 开头就找不到键...就全部跳过,直到找到键
if len(currentKey) == 0 {
continue
}
// 找到第一个=然后分割开,比如 port=3306 --> key:port, value:3306
index := strings.Index(row, "=")
// 没找到 = 也跳这一行的数据
if index == -1 {
continue
}
r := []rune(row)
key := string(r[:index])
key = strings.ReplaceAll(key, " ", "")
val := string(r[index+1:])
allMap[currentKey][key] = val
}
return allMap, nil
}
解析出来的结果如下
3、解析的数据,通过反射赋值到结构体中
1、增加一个 MysqlConfig 的结构体
// MysqlConfig 结构体
type MysqlConfig struct {
Jdbc string `ini:"jdbc"`
Username string `ini:"username"`
Password string `ini:"password"`
}
2、增加一个 RedisConfig 的结构体
// RedisConfig 结构体
type RedisConfig struct {
Ip string `ini:"ip"`
Port int `ini:"port"`
Username string `ini:"username"`
Password string `ini:"password"`
}
3、增加一个 Config 的结构体
type Config struct {
MysqlConfig `ini:"mysql"`
RedisConfig `ini:"redis"`
}
4、利用反射进行结构体的赋值 函数代码
// LoadConfigFile 加载配置文件
func LoadConfigFile(filePath string, dataType interface{}) error {
typeOf := reflect.TypeOf(dataType)
if typeOf.Kind() != reflect.Ptr {
return errors.New("请传入一个指针的类型变量")
}
valueOf := reflect.ValueOf(dataType)
// 指针类型数据 需要使用 Elem() 方法
if valueOf.Elem().Kind() != reflect.Struct {
return errors.New("请传入一个指针的类型结构体变量")
}
allMap, err := ReadFileData(filePath)
if err != nil {
return err
}
for i := 0; i < typeOf.Elem().NumField(); i++ {
field := typeOf.Elem().Field(i)
fieldIniTag := field.Tag.Get("ini")
// 跳过没有 ini Tag 的字段
if fieldIniTag == "" {
continue
}
itemMap, ok := allMap[fieldIniTag]
// 大的key不存在时,比如没有 mysql 配置项时 ,直接跳过
if !ok {
continue
}
// 取出这个字段的数据
structVal := valueOf.Elem().FieldByName(field.Name)
// 不是结构体的数据则跳过
if structVal.Kind() != reflect.Struct {
continue
}
// 获取这个数据类型的结构信息
structType := structVal.Type()
for i := 0; i < structType.NumField(); i++ {
structField := structType.Field(i)
structFieldIniTag := structField.Tag.Get("ini")
if structFieldIniTag == "" {
continue
}
s, ok := itemMap[structFieldIniTag]
// 没有此配置项时,跳过
if !ok {
continue
}
fieldObj := structVal.FieldByName(structField.Name)
// 使用反射进行赋值
err := setValue(&fieldObj, s)
if err != nil {
fmt.Printf("[warn]字段[%s]无法进行赋值,error: %v\n", structField.Name, err)
}
}
}
return nil
}
// 使用反射进行赋值
func setValue(fieldObj *reflect.Value, value interface{}) error {
value1 := value.(string)
switch fieldObj.Kind() {
case reflect.String:
fieldObj.SetString(value1)
case reflect.Int:
atoi, err := strconv.Atoi(value1)
if err != nil {
return err
}
fieldObj.SetInt(int64(atoi))
default:
return errors.New("缺少类型转换,请添加[ " + fmt.Sprintf("%v", fieldObj.Kind()) + " ]转换类型")
}
return nil
}
5、使用
结果
[warn]字段[DefaultQuery]无法进行赋值,error: 缺少类型转换,请添加[ bool ]转换类型
myini.MysqlConfig{Jdbc:"jdbc:mysql://127.0.0.1:3306/test_db", Username:"oukele", Password:"oukele..", DefaultQuery:false}
myini.RedisConfig{Ip:"127.0.0.1", Port:1234, Username:"oukele", Password:"oukele.."}
DefaultQuery 字段是新添加的,可以忽略(我测试用的)
结尾
小菜鸟一枚,本文的目的只是为了加强对Go反射的理解,写得不好,请路过的大佬经喷~~