package main import ( "fmt" "io/ioutil" "os" "reflect" "regexp" "strconv" "strings" ) type IniConfig struct { Mysql MysqlConfig `ini:"mysql"` Redis RedisConfig `ini:"redis"` } type MysqlConfig struct { Host string `ini:"host"` Port int `ini:"port"` User string `ini:"user"` Password string `ini:"password"` } type RedisConfig struct { Host string `ini:"host"` Port int `ini:"port"` Password string `ini:"password"` Db int `ini:"db"` } func loadIni(filePath string, v interface{}) (err error) { // 传入的 接收值 v 必须为指针类型 因为需要对齐进行赋值操作 t := reflect.TypeOf(v) if v == nil || t.Kind() != reflect.Ptr { return fmt.Errorf("接收者必须为指针类型\n") } el := t.Elem() if el.Kind() != reflect.Struct { return fmt.Errorf("接收者必须为一个结构体对象\n") } // 打开文件 f, err := os.OpenFile(filePath, os.O_RDONLY, 0644) if err != nil { return err } // 读取整个文件 b, _ := ioutil.ReadAll(f) // 释放文件占用 _ = f.Close() // ini 文件中 // ; 开头的为注释 // [xxx] 为节 // key=value 为键值对 // 读取文件 conf := string(b) // 替换换行符 \r\n 为 \n conf = strings.Replace(conf, "\r\n", "\n", -1) // 根据换行符 \n 分割行 confSplit := strings.Split(conf, "\n") //fmt.Printf("%#v\n", confSplit) var curSection string for _, line := range confSplit { // 去除 首尾 空格符 trimReg, _ := regexp.Compile("\\s") line = trimReg.ReplaceAllLiteralString(line, "") // 如果是以 ; 或 # 开头 的 视为注释 直接跳过 annotationReg, _ := regexp.Compile("^[;|#]") if annotationReg.MatchString(line) { continue } sectionReg, _ := regexp.Compile("^\\[(.*)]$") if sectionReg.MatchString(line) { sectionMatch := sectionReg.FindAllStringSubmatch(line, 1) if len(sectionMatch) < 1 { return fmt.Errorf("无效的 节 %s\n", line) } if len(sectionMatch[0]) < 2 { return fmt.Errorf("无效的 节 %s\n", line) } section := sectionMatch[0][1] //fmt.Printf("节: %s\n", line) value := reflect.ValueOf(v).Elem() for i := 0; i < el.NumField(); i++ { field := el.Field(i) if field.Tag.Get("ini") == section { item := value.FieldByName(field.Name) if item.Kind() == reflect.Ptr { return fmt.Errorf("接收者 %s 不能为指针\n", section) } //if item.Kind() == reflect.Struct { // fmt.Printf("%s 为结构体\n", section) //} curSection = field.Name break } } continue } if strings.EqualFold(curSection, "") { continue } keyValueReg, _ := regexp.Compile("^(.*)=(.*)$") if keyValueReg.MatchString(line) { keyValueMatch := keyValueReg.FindAllStringSubmatch(line, -1) if len(keyValueMatch) < 1 { //fmt.Println("未找到 匹配的 键值对") continue } keyValueSubMatch := keyValueMatch[0] if len(keyValueSubMatch) < 3 { //fmt.Println("未找到 匹配的 键值对") continue } k := keyValueSubMatch[1] val := keyValueSubMatch[2] //fmt.Printf("键值对: k: %#v => v: %#v\n", k, val) sectionReflectType, _ := reflect.TypeOf(v).Elem().FieldByName(curSection) sectionType := sectionReflectType.Type sectionVal := reflect.ValueOf(v).Elem().FieldByName(curSection) if sectionVal.Kind() != reflect.Struct { return fmt.Errorf("section 接收者 需为 struct 类型\n") } for i := 0; i < sectionVal.NumField(); i++ { selectionKey := sectionType.Field(i).Tag.Get("ini") if strings.EqualFold(selectionKey, k) { //fmt.Println(selectionKey, sectionVal.Field(i).Kind()) switch sectionVal.Field(i).Kind() { case reflect.Int: intVal, err := strconv.Atoi(val) if err != nil { return err } sectionVal.Field(i).SetInt(int64(intVal)) case reflect.String: sectionVal.Field(i).SetString(val) } break } } continue } fmt.Println(line) } return } func main() { config := new(IniConfig) err := loadIni("./base/reflect/ini/conf.ini", config) if err != nil { fmt.Println(err) } fmt.Printf("%#v\n", config) }