参数配置和存储是经常用到的功能。
比如常用的可以用json文件,XML文件,或INI文件,YAML文件,properties文件等存储配置信息。
但是,这些都不够简单。
我想要的简单有多简单呢?整个互联网上没有比这更简单好用的。
例如,我想保存个IP地址和端口参数。理想是这样的:
syscfg.ip = "218.28.133.181"
syscfg.port = 22288
syscfg.saveCfg()
就这几步,就把需要的参数持久化的存储起来了。开机只需要syscfg.load() 就完成了配置信息的加载。
比起其他的方式,是不是没有比这更简单了。即便是小白,立马也会用。
之前在Android和嵌入式c上,都已封装好的有这样的功能很好用。
Android上的封装,参见:https://blog.csdn.net/yyz_1987/article/details/104122764
但是在go语言这块儿,计划也打造一个这样的参数配置存储的功能。
即使其他维护的人不懂yaml,不懂xml,不懂json解析,没关系,这种封装后谁都很快会用。
这样的好处是什么呢?这样实际上也是一种模块化和分层的思想。让应用层的人不关心底层是如何实现的。
有的说直接操作json或gob也很简单啊,几行代码而已。但是,如果哪天说想让你换种存储方式,应用里每处要扒拉改一遍吗?
再比如假如对配置文件分分类,有N多个配置文件要存储,要写N遍的解析json,加载json的方法吗?
好的封装可以做到事半功倍。
以下为简单的实现:
代码看着是有点儿长,其实这里面BaseConf以及它的相关操作代码可以单独抽出来。
package main import ( "bufio" "encoding/json" "fmt" "io" "os" "reflect" "strings" ) // BaseConf 配置文件操作的基础封装 type BaseConf struct { } // OthCfg 配置文件1 type OthCfg struct { BaseConf IP string Port int } // SysCfg 配置文件2 type SysCfg struct { BaseConf Name string Age int } // Save ... func (cfg *BaseConf) Save(v interface{}) { t := reflect.TypeOf(v) fname := fmt.Sprintf("%s.json", t) fname = strings.Replace(fname, "*", "r", -1) fmt.Println(t) fmt.Println(fname) //fmt.Printf("type of is:%v\n", t) jsond, err := json.Marshal(v) if err != nil { fmt.Println("生成json字符串错误") } fmt.Println(string(jsond)) SaveFile(fname, jsond) } // Load ... func (cfg *BaseConf) Load(v interface{}) { t := reflect.TypeOf(v) fname := fmt.Sprintf("%s.json", t) fname = strings.Replace(fname, "*", "r", -1) data, err := ReadFile(fname) if err != nil { fmt.Println(err) return } err = json.Unmarshal(data, v) if err != nil { fmt.Println(err) } fmt.Println(v) } // SaveFile 存文件 func SaveFile(filename string, data []byte) error { fi, err := os.Create(filename) if err != nil { fmt.Println(err) return err } defer fi.Close() w := bufio.NewWriter(fi) w.WriteString(string(data)) w.Flush() return nil } // ReadFile 读文件 func ReadFile(filename string) ([]byte, error) { fi, err := os.Open(filename) if err != nil { return nil, err } defer fi.Close() r := bufio.NewReader(fi) var data []byte buf := make([]byte, 1024) for { n, err := r.Read(buf) if err != nil && err != io.EOF { return nil, err } if 0 == n { break } data = append(data, buf[:n]...) } return data, nil } var sysCfg SysCfg func main() { fmt.Println("Hello Go") sysCfg.Load(&sysCfg) //sysCfg.Name = "yang" //sysCfg.Age = 21 //sysCfg.Save(&sysCfg) oth := OthCfg{} oth.IP = "192.168.1.97" oth.Port = 5057 oth.Save(&oth) }
而实际使用者有多简单呢?不用关系底层是什么存储,文件是什么名字。
使用者,即便有多个配置文件要存储,只需要像这样子:
package main import ( "fmt" "testconfig/config" ) // OthCfg 配置文件1 type OthCfg struct { BaseConf IP string Port int } // SysCfg 配置文件2 type SysCfg struct { BaseConf Name string Age int } var sysCfg SysCfg func main() { fmt.Println("Hello Go") //sysCfg.Load() sysCfg.Name = "yang" sysCfg.Age = 21 sysCfg.Save(&sysCfg) oth := OthCfg{} oth.IP = "192.168.1.97" oth.Port = 5057 oth.Save(&oth) }
唯一有点儿遗憾的是,你在操作Save的时候,必须把对应的自己传进去才行,不能传错了。
比如oth.Save(&oth) ,若搞成 oth.Save(&sysCfg)会是什么情况?
有没更好的办法?让实例化时就传进来呢?让使用者可以直接调用个oth.Save()即可。
有办法吗?欢迎讨论
最后感谢网友雨痕大神给出的解决方案。
雨痕,《go语言学习笔记》一书的作者。在这里推荐下他的书。真不错,讲的很细致。
他的解决办法如下:
另外发现一很有意思的事情,,有人知道原因吗?
如果把里面conf改为大写,则不行,还必须用小写
sysCfg.BaseConf.conf ,里面是 小写的conf正确
一个大小写,区别就这么大。
有时候学习劲头来了,挡都挡不住,本来问题解决了,但我就想搞明白,刨根究底。这问题影响我晚上没睡好觉。
不过最后还是想明白了。其实那地方为什么必须是小写,原因很简单啊,并没有什么玄机。
最后放出运行没问题的代码:
package main import ( "bufio" "encoding/json" "fmt" "io" "os" "reflect" "strings" ) // IConf 组合模式,父类 定义一个 接口 type IConf interface{ Save() Load() } // BaseConf 配置文件操作的基础封装 type BaseConf struct { conf IConf } // OthCfg 配置文件1 type OthCfg struct { BaseConf IP string Port int } // SysCfg 配置文件2 type SysCfg struct { BaseConf Name string Age int } // Save ... func (cfg *BaseConf) Save() { t := reflect.TypeOf(cfg.conf) fname := fmt.Sprintf("%s.json", t) fname = strings.Replace(fname, "*", "r", -1) fmt.Println(t) fmt.Println(fname) //fmt.Println(cfg.Conf) //fmt.Printf("type of is:%v\n", t) jsond, err := json.Marshal(cfg.conf) if err != nil { fmt.Println("生成json字符串错误") } fmt.Println(string(jsond)) SaveFile(fname, jsond) } // Load ... func (cfg *BaseConf) Load() { t := reflect.TypeOf(cfg.conf) fname := fmt.Sprintf("%s.json", t) fname = strings.Replace(fname, "*", "r", -1) fmt.Println(cfg.conf) data, err := ReadFile(fname) if err != nil { fmt.Println(err) return } err = json.Unmarshal(data, cfg.conf) if err != nil { fmt.Println(err) } fmt.Println(cfg) } // SaveFile 存文件 func SaveFile(filename string, data []byte) error { fi, err := os.Create(filename) if err != nil { fmt.Println(err) return err } defer fi.Close() w := bufio.NewWriter(fi) w.WriteString(string(data)) w.Flush() return nil } // ReadFile 读文件 func ReadFile(filename string) ([]byte, error) { fi, err := os.Open(filename) if err != nil { return nil, err } defer fi.Close() r := bufio.NewReader(fi) var data []byte buf := make([]byte, 1024) for { n, err := r.Read(buf) if err != nil && err != io.EOF { return nil, err } if 0 == n { break } data = append(data, buf[:n]...) } return data, nil } var sysCfg SysCfg func main() { fmt.Println("Hello Go") //sysCfg.BaseConf.Conf = &sysCfg //sysCfg.Load() //fmt.Println(sysCfg) sysCfg.BaseConf.conf = &sysCfg sysCfg.Name = "yang" sysCfg.Age = 188 sysCfg.Save() sysCfg.Load() fmt.Println(sysCfg) //sysCfg.Save(&sysCfg) // oth := OthCfg{} // oth.Conf = &oth // oth.IP = "192.168.1.97" // oth.Port = 5057 // oth.Save() }