【Gin-API系列】配置文件和数据库操作(三)
2020-08-10 18:10
1086 查看
我们前面已经实现了API的基础版本,能对参数校验和返回指定数据,这一章,我们将对主机和交换机进行建模,存入数据库。
考虑到数据库安装和使用的简便性,我们采用文档存储结构的MongoDB数据库。
Mongo数据库下载安装,安装后不用设置密码,直接使用即可
下载链接 https://www.filehorse.com/download-mongodb/download/ 或者 https://www.mongodb.com/try/download/community
配置文件
使用数据库之前需要配置数据库地址和端口,所以我们将配置信息存放到配置文件,采用
yaml格式存储解析
- 配置文件内容
这里,我们还将API监听的地址和端口,日志路径也可以都配上
api_server: env: prod host: 127.0.0.1 port: 9000 mgo: uri: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb database: gin_ips pool_size: 100 log: path: log/gin_ips level: DEBUG name: gin.log count: 180
- Golang Yaml文件解析
// 通用 Config 接口 type Config interface { InitError(msg string) error } // 根据yaml文件初始化通用配置, 无需输出日志 func InitYaml(filename string, config Config) error { fp, err := os.Open(filename) if err != nil { msg := fmt.Sprintf("configure file [ %s ] not found", filename) return config.InitError(msg) } defer func() { _ = fp.Close() }() if err := yaml.NewDecoder(fp).Decode(config); err != nil { msg := fmt.Sprintf("configure file [ %s ] initialed failed", filename) return config.InitError(msg) } return nil }
Golang 使用Mongo数据库
golang中有多种优秀的orm库,例如
xorm,gorm。由于orm将数据库模型和语言紧密封装,使用起来非常方便,很适合web前端开发。
但与此同时,使用orm也会导致部分性能丢失(深度使用后会发现隐藏的坑也不少),有兴趣的同学可以了解下。
本文主要使用golang官方mongodb库mongo-driver,直接通过sql语句操作数据库(这个过程可以学习下如何explain和优化sql语句)。
- 模型定义
type Collection struct { client *mongo.Collection database string // 数据库 collection string // 集合 }
- 创建连接池
// 连接池创建 func CreatePool(uri string, size uint64) (pool *mongo.Client, e error) { defer func() { if err := recover(); err != nil { e = errors.New(fmt.Sprintf("%v", err)) } }() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) // 10s超时 defer cancel() var err error pool, err = mongo.Connect(ctx, options.Client().ApplyURI(uri).SetMinPoolSize(size)) // 连接池 if err != nil { return pool, err } err = pool.Ping(context.Background(), nil) // 检查连接 if err != nil { return pool, err } return pool, nil }
- 销毁连接池
func DestroyPool(client *mongo.Client) error { err := client.Disconnect(context.Background()) if err != nil { return err } return nil }
- 查询操作
// 查找单个文档, sort 等于1表示 返回最旧的,sort 等于-1 表示返回最新的 /* BSON(二进制编码的JSON) D家族 bson.D D:一个BSON文档。这种类型应该在顺序重要的情况下使用,比如MongoDB命令。 M:一张无序的map。它和D是一样的,只是它不保持顺序。 A:一个BSON数组。 E:D里面的一个元素。 */ func (m *Collection) FindOne(filter bson.D, sort, projection bson.M) (bson.M, error) { findOptions := options.FindOne().SetProjection(projection) if sort != nil { findOptions = findOptions.SetSort(sort) } singleResult := m.client.FindOne(context.Background(), filter, findOptions) var result bson.M if err := singleResult.Decode(&result); err != nil { return result, err } return result, nil } /* 查询多个 sort 等于1表示 返回最旧的,sort 等于-1 表示返回最新的 每次只返回1页 page size 大小的数据 project 不能混合 True 和 False */ func (m *Collection) FindLimit(filter bson.D, page, pageSize uint64, sort, projection bson.M) ([]bson.M, error) { var resultArray []bson.M if page == 0 || pageSize == 0 { return resultArray, errors.New("page or page size can't be 0") } skip := int64((page - 1) * pageSize) limit := int64(pageSize) if projection == nil { projection = bson.M{} } findOptions := options.Find().SetProjection(projection).SetSkip(skip).SetLimit(limit) if sort != nil { findOptions = findOptions.SetSort(sort) } cur, err := m.client.Find(context.Background(), filter, findOptions) if err != nil { return resultArray, err } defer func() { _ = cur.Close(context.Background()) }() for cur.Next(context.Background()) { var result bson.M err := cur.Decode(&result) if err != nil { return resultArray, err } resultArray = append(resultArray, result) } //err = cur.All(context.Background(), &resultArray) if err := cur.Err(); err != nil { return resultArray, err } return resultArray, nil } // 返回查找条件的全部文档记录 // project 不能混合 True 和 False func (m *Collection) FindAll(filter bson.D, sort, projection bson.M) ([]bson.M, error) { var resultArray []bson.M if projection == nil { projection = bson.M{} } findOptions := options.Find().SetProjection(projection) if sort != nil { findOptions = findOptions.SetSort(sort) } cur, err := m.client.Find(context.Background(), filter, findOptions) if err != nil { return resultArray, err } defer func() { _ = cur.Close(context.Background()) }() for cur.Next(context.Background()) { // fmt.Println(cur.Current) var result bson.M err := cur.Decode(&result) if err != nil { return resultArray, err } resultArray = append(resultArray, result) } if err := cur.Err(); err != nil { return resultArray, err } return resultArray, nil }
- 新增操作
//插入单个 fun ad8 c (m *Collection) InsertOne(document interface{}) (primitive.ObjectID, error) { insertResult, err := m.client.InsertOne(context.Background(), document) var objectId primitive.ObjectID if err != nil { return objectId, err } objectId = insertResult.InsertedID.(primitive.ObjectID) return objectId, nil } //插入多个文档 func (m *Collection) InsertMany(documents []interface{}) ([]primitive.ObjectID, error) { var insertDocs []interface{} for _, doc := range documents { insertDocs = append(insertDocs, doc) } insertResult, err := m.client.InsertMany(context.Background(), insertDocs) var objectIds []primitive.ObjectID if err != nil { return objectIds, err } for _, oid := range insertResult.InsertedIDs { objectIds = append(objectIds, oid.(primitive.ObjectID)) } return objectIds, nil }
- 修改操作
/* 更新 filter 返回的第一条记录 如果匹配到了(matchCount >= 1) 并且 objectId.IsZero()= false 则是新插入, objectId.IsZero()=true则是更新(更新没有获取到id) ObjectID("000000000000000000000000") document 修改为 interface 表示支持多个字段更新,使用bson.D ,即 []bson.E */ func (m *Collection) UpdateOne(filter bson.D, document interface{}, insert bool) (int64, primitive.ObjectID, error) { updateOption := options.Update().SetUpsert(insert) updateResult, err := m.client.UpdateOne(context.Background(), filter, document, updateOption) var objectId primitive.ObjectID if err != nil { return 0, objectId, err } if updateResult.UpsertedID != nil { objectId = updateResult.UpsertedID.(primitive.ObjectID) } // fmt.Println(objectId.IsZero()) return updateResult.MatchedCount, objectId, nil } /* 更新 filter 返回的所有记录,返回的匹配是指本次查询匹配到的所有数量,也就是最后更新后等于新的值的数量 如果匹配到了(matchCount >= 1) 并且 objectId.IsZero()= false 则是新插入, objectId.IsZero()=true则是更新(更新没有获取到id) ObjectID("000000000000000000000000") docAction 修改为 interface 表示支持多个字段更新,使用bson.D ,即 []bson.E */ func (m *Collection) UpdateMany(filter bson.D, docAction interface{}, insert bool) (int64, primitive.ObjectID, error) { updateOption := options.Update().SetUpsert(insert) updateResult, err := m.client.UpdateMany(context.Background(), filter, docAction, updateOption) var objectId primitive.ObjectID if err != nil { return 0, objectId, err } if updateResult.UpsertedID != nil { objectId = updateResult.UpsertedID.(primitive.ObjectID) } // fmt.Println(objectId.IsZero()) return updateResult.MatchedCount, objectId, nil } /* 替换 filter 返回的1条 56c 记录(最旧的) 如果匹配到了(matchCount >= 1) 并且 objectId.IsZero()= false 则是新插入, objectId.IsZero()=true则是更新(更新没有获取到id) ObjectID("000000000000000000000000") 采用 FindOneAndReplace 在查找不到但正确插入新的数据会有"mongo: no documents in result" 的错误 */ func (m *Collection) Replace(filter bson.D, document interface{}, insert bool) (int64, primitive.ObjectID, error) { option := options.Replace().SetUpsert(insert) replaceResult, err := m.client.ReplaceOne(context.Background(), filter, document, option) var objectId primitive.ObjectID if err != nil { return 0, objectId, err } if replaceResult.UpsertedID != nil { objectId = replaceResult.UpsertedID.(primitive.ObjectID) } // fmt.Println(objectId.IsZero()) return replaceResult.MatchedCount, objectId, nil }
- 删除操作
/* 查找并删除一个 sort 等于1表示 删除最旧的,sort 等于-1 表示删除最新的 一般根据 id 查找就会保证删除正确 */ func (m *Collection) DeleteOne(filter bson.D, sort bson.M) (bson.M, error) { findOptions := options.FindOneAndDelete() if sort != nil { findOptions = findOptions.SetSort(sort) } singleResult := m.client.FindOneAndDelete(context.Background(), filter, findOptions) var result bs ad8 on.M if err := singleResult.Decode(&result); err != nil { return result, err } return result, nil } /* 根据条件删除全部 */ func (m *Collection) DeleteAll(filter bson.D) (int64, error) { count, err := m.client.DeleteMany(context.Background(), filter) if err != nil { return 0, err } return count.DeletedCount, nil }
- 创建索引
// 创建索引,重复创建不会报错 func (m *Collection) CreateIndex(index string, unique bool) (string, error) { indexModel := mongo.IndexModel{Keys: bson.M{index: 1}, Options: options.Index().SetUnique(unique)} name, err := m.client.Indexes().CreateOne(context.Background(), indexModel) return name, err }
数据模型设计和返回
- 模型设计
根据需求,我们将主机和交换机的各个字段提前定义好,这时候可以考虑各个模型分开存储到多个集合,也可以合并成1个(本文选择合并)
//|ID|主机名|IP|内存大小|磁盘大小|类型|负责人| type HostModel struct { Oid configure.Oid `json:"oid"` // 考虑所有实例存放在同一个集合中,需要一个字段来区分 Id string `json:"id"` Ip string `json:"ip"` Hostname string `json:"hostname"` MemSize int64 `json:"mem_size"` DiskSize int64 `json:"disk_size"` Class string `json:"class"` // 主机类型 Owner []string `json:"owner"` } //|ID|设备名|管理IP|虚IP|带外IP|厂家|负责人| type SwitchModel struct { Oid configure.Oid `json:"oid"` // 考虑所有实例存放在同一个集合中,需要一个字段来区分 Id string `json:"id"` Name string `json:"name"` Ip string `json:"ip"` Vip []string `json:"vip"` ConsoleIp string `json:"console_ip"` Manufacturers string `json:"manufacturers"` // 厂家 Owner []string `json:"owner"` }
- 手工录入数据
随机插入几条测试数据即可
hmArr := []HostModel{ { Oid: configure.OidHost, Id: "H001", Ip: "10.1.162.18", Hostname: "10-1-162-18", MemSize: 1024000, DiskSize: 102400000000, Class: "物理机", Owner: []string{"小林"}, }, { Oid: configure.OidHost, Id: "H002", Ip: &quo 564 t;10.1.162.19", Hostname: "10-1-162-19", MemSize: 1024000, DiskSize: 102400000000, Class: "虚拟机", Owner: []string{"小黄"}, }, }
- API调用返回
最后我们修改下参数的验证规则,支持返回指定模型和返回所有模型。测试结果如下:
curl "http://127.0.0.1:8080?ip=10.1.162.18" {"code":0,"message":"","data":{"page":1,"page_size":2,"size":2,"total":2,"list":[{"class":"物理机","disksize":102400000000,"hostname":"10-1-162-18","id":"H001","ip":"10.1.162.18"," memsize":1024000,"oid":"HOST","owner":["小林"]},{"consoleip":"10.3.32.11","id":"S001","ip":"10.2.32.11","manufacturers":"华为","name":"上海集群交换机","oid":"SWITCH","owner":["老马 ","老曹"],"vip":["10.2.20.1","10.2.20.13","10.1.162.18"]}]}} curl "http://127.0.0.1:8080?ip= ad8 10.1.162.18&oid=HOST" {"code":0,"message":"","data":{"page":1,"page_size":1,"size":1,"total":1,"list":[{"class":"物理机","disksize":102400000000,"hostname":"10-1-162-18","id":"H001","ip":"10.1.162.18"," memsize":1024000,"oid":"HOST","owner":["小林"]}]}}
本文模拟生产环境完成了数据库的设计和数据配置,下一章,我们将配置Gin Log,同时开始使用Gin中间件完善API。
Github 代码
请访问 Gin-IPs 或者搜索 Gin-IPs
相关文章推荐
- Html5 学习系列(四)文件操作API
- Html5 学习系列(四)文件操作API
- Windows文件操作的API(CreateFile系列)
- ET之JDBC 数据库驱动、连接 配置文件操作
- eXtremeDB在VS中配置,自动由mco文件生成数据库API(vs中指定文件的编译器)
- Mybatis系列之-连接数据库的配置单独放在一个properties文件中
- springboot上传下载文件(3)--java api 操作HDFS集群+集群配置
- 数据库中的原有配置3个redolog文件,由于切换频繁,想扩大大小,操作简介
- C++操作数据库写入到json配置文件中
- Hibernate动态建表,通过hbm.xml配置文件创建数据表,进行数据库操作, 动态模型
- Hadoop系列-HDFS文件操作的JAVA API用法(七)
- TC官方文档翻译11----文件操作API(Tokyo Cabinet/Tokyo Tyarnt 文档系列)
- EF 学习系列二 数据库表的创建和表关系配置(Fluent API、Data Annotations、约定)
- java操作数据库的类——SqlHelper(读取properties配置文件)
- JAVA读取XML文件并利用该文件对数据库进行配置操作
- spring-通过JdbcTemplate进行数据库操作-代码实现,无配置文件
- Linux下oracle11gR2系统安装到数据库建立配置及最后oracle的dmp文件导入一站式操作记录(转)
- Spark RDD/Core 编程 API入门系列之动手实战和调试Spark文件操作、动手实战操作搜狗日志文件、搜狗日志文件深入实战(二)
- spring-使用配置文件完成JdbcTemplate操作数据库-dbcp
- 通过配置文件ini配置数据库,操作数据库,以mysql为示例