前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go语言(十七) 配置文件库项目

Go语言(十七) 配置文件库项目

作者头像
alexhuiwang
发布2020-09-23 11:39:21
4140
发布2020-09-23 11:39:21
举报
文章被收录于专栏:运维博客运维博客

配置文件库项目

简介

  • 用途: 解析ini类型的配置文件
  • 知识点: 主要基于反射
  • ini配置文件的解析,配置文件如下
代码语言:javascript
复制
;config file
[server]
host = www.baidu.com
port = 8080

[cartdb]
user = root.xxx
password = root
host = localhost
port = 3306
database = cartdb
rate = 1.2

需求分析

  • 拆离出动态配置
  • 映射数据到项目数据结构中 -包含配置文件读取(UnMarshalFile)和写入(MarsharFile)两个功能

代码部分

代码语言:javascript
复制
package oconfig

import (
   "fmt"
   "io/ioutil"
   "reflect"
   "strconv"
   "strings"
)

func UnMarshal(data []byte,result interface{}) (err error) {
   t := reflect.TypeOf(result)
   v := reflect.ValueOf(result)
   _ = v
   kind := t.Kind()

   //输入校验
   if kind != reflect.Ptr {
      panic("please input a address")
   }
   //解析配置文件
   var sectionName string
   lines := strings.Split(string(data),"\n")
   lineNo := 0
   for _,line := range lines {
      lineNo ++
      //去掉首位空串
      line = strings.Trim(line,"\t\n\r")
      if len(line) == 0 {
         //空行处理
         continue
      }
      //去掉注释
      if line[0] == '#' || line[0] == ';' {
         continue
      }
      // 解析group
      if line[0] == '[' {
         //解析group的名称,是否以[]包含校验
         if len(line)<=2 || line[len(line)-1] != ']' {
            tips := fmt.Sprintf("syntax err,invalid section:\"%s\",line:%d",line,lineNo)
            panic(tips)
         }
         //获取group名称,去空格
         sectionName = strings.TrimSpace(line[1:len(line)-1])
         if len(sectionName) == 0 {
            tips := fmt.Sprintf("syntax err,invalid section:\"%s\",line:%d",line,lineNo)
            panic(tips)
         }
      }else {
         //group的字段
         if len(sectionName) == 0 {
            tips := fmt.Sprintf("key-value:%s 不属于任何group,lineNo:%d",line,lineNo)
            panic(tips)
         }
         //找到第一个等于号所在的位置
         index := strings.Index(line,"=")
         if index == -1 {
            //等号之前没有配置的异常
            tips := fmt.Sprintf("syntax error,not found =,line:%s,lineNo:%d",line,lineNo)
            panic(tips)
         }
         key := strings.TrimSpace(line[0:index])  //去掉空格
         value := strings.TrimSpace(line[index+1:])  //获取到的value
         if len(key) == 0 {
            //key不存在异常
            tips := fmt.Sprintf("syntax error,not found key,line:%s,lineNo:%d",line,lineNo)
            panic(tips)
         }
         //通过Config找到所属的SectionName配置,需要查找两遍
         //1.找到sectionName在result中对应的结构体s1
         for i := 0;i < t.Elem().NumField();i++ {
            tfield := t.Elem().Field(i)
            vField := v.Elem().Field(i)
            if tfield.Tag.Get("ini") != sectionName {
               continue
            }
            //2.通过当前解析的key,找到对应的结构体s1中的对应字段
            tfieldType := tfield.Type
            if tfieldType.Kind() != reflect.Struct {
               tips := fmt.Sprintf("field %s is not a struct",tfield.Name)
               panic(tips)
            }
            //查找子结构体中的数据
            for j:=0;j<tfieldType.NumField();j++ {
               //找不到key对应field的时候就跳过
               tKeyField := tfieldType.Field(j)
               vKeyField := vField.Field(j)
               if tKeyField.Tag.Get("ini") != key {
                  continue
               }
               //找到了对应的字段,并将值映射到对应的字段
               switch tKeyField.Type.Kind() {
               case reflect.String:
                  vKeyField.SetString(value)
               case reflect.Int,reflect.Uint,reflect.Int16,reflect.Uint16:
                  fallthrough   //穿透到下个case
               case reflect.Int32,reflect.Uint32,reflect.Int64,reflect.Uint64:
                  valueInt,err := strconv.ParseInt(value,10,64) //十进制64字节int
                  if err != nil {
                     tips := fmt.Sprintf("value %s is not a convert to int,lineNo:%d",value,lineNo)
                     panic(tips)
                  }
                  vKeyField.SetInt(valueInt)
               case reflect.Float32,reflect.Float64:
                  valueFloat,err := strconv.ParseFloat(value,64)
                  if err != nil {
                     tips := fmt.Sprintf("value %s is not a convert to float,lineNo:%d",value,lineNo)
                     panic(tips)
                  }
                  vKeyField.SetFloat(valueFloat)
               default:
                  tips := fmt.Sprintf("key \"%s\" is not a convert to %v,lineNo:%d",key,tKeyField.Type.Kind(),lineNo)
                  panic(tips)
               }
            }
            break
         }
      }
   }
   return
}

func UnMarshalFile(filename string,result interface{}) (err error) {
   //读取配置文件
   data,err := ioutil.ReadFile(filename)
   if err != nil {
      return
   }
   return UnMarshal(data,result)
}

func Marshal(result interface{}) (data []byte,err error) {
   //序列化用户数据
   t := reflect.TypeOf(result)
   v := reflect.ValueOf(result)

   if t.Kind() != reflect.Struct {
      panic("please input struct type")
   }

   var strSlice []string
   //遍历结构体中的字段
   for i:=0;i<t.NumField();i++ {
      tField := t.Field(i)
      vField := v.Field(i)
      if tField.Type.Kind() != reflect.Struct {
         continue  //非结构体跳过
      }

      //sectionName的获取
      sectionName := tField.Name
      //subTFieldTagName := subTField.Tag.Get("ini")
      if len(tField.Tag.Get("ini")) > 0 {
         sectionName = tField.Tag.Get("ini")
      }
      //使用[]拼接sectionName
      sectionName = fmt.Sprintf("[%s]\n",sectionName)
      //保存sectionName信息
      strSlice = append(strSlice,sectionName)
      for j:=0;j<tField.Type.NumField();j++ {
         //1. 拿到类型信息
         subTField := tField.Type.Field(j)   //获取字段名称
         if subTField.Type.Kind() == reflect.Struct || subTField.Type.Kind() == reflect.Ptr{
            continue   //跳过结构体或者指针类型,正常是key=value格式
         }
         subTFieldName := subTField.Name
         subTFieldTagName := subTField.Tag.Get("ini")
         if len(subTFieldTagName) > 0 {
            subTFieldName = subTFieldTagName
         }
         //2. 拿到值信息
         subVField := vField.Field(j)
         fieldStr := fmt.Sprintf("%s=%v\n",subTFieldName,subVField.Interface())
         //fmt.Printf("conf%s\n",fieldStr)
         strSlice = append(strSlice,fieldStr) //保存sectionName对应的字段
      }
   }
   for _, v := range strSlice {
      //展开slice的结果
      data = append(data,[]byte(v)...)
   }
   return
}

func MarshalFile(filename string,result interface{}) (err error) {
   //将用户输入的数据写入配置文件
   data,err := Marshal(result)
   if err  != nil {
      return
   }
   err = ioutil.WriteFile(filename,data,0755)
   return
}

测试用例

  • 测试代码不需要和oconfig处于同一个包
代码语言:javascript
复制
package main

import (
   "fmt"
   "oldBoy/oconfig"
)

type Config struct {
   Server  ServerConf     `ini:"server"`
   CartDb  DbConf         `ini:"cartdb"`
}

type ServerConf struct {
   Host     string       `ini:"host"`
   Port     int          `ini:"port"`
}

type DbConf struct {
   User     string        `ini:"user"`
   Password string        `ini:"password"`
   Host     string        `ini:"host"`
   Port     int           `ini:"port"`
   Database string        `ini:"database"`
   Rate     float32       `ini:"rate"`
}

func main() {
   var config Config
   filename := "./example.ini"
   //config要传入地址(引用类型)
   err := oconfig.UnMarshalFile(filename,&config)
   if err != nil {
      fmt.Printf("unmarshal file faild,err:%v\n",err)
      return
   }
   fmt.Printf("conf=%#v\n",config)
   oconfig.MarshalFile("./aaa.ini",config)
}
  • example.ini (测试配置文件)
代码语言:javascript
复制
;config file
[server]
host = www.baidu.com
port = 8080
#DB Config
[cartdb]
user = root.xxx.sss
password = root
host = localhost
port = 3306
database = cartdb
rate = 1.23456
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-05-02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 配置文件库项目
    • 简介
      • 需求分析
        • 代码部分
          • 测试用例
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档