Go 实现一个简易的配置文件(ini)的解析器

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反射的理解,写得不好,请路过的大佬经喷~~

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值