• 【Gin-API系列】配置文件和数据库操作(三)


    我们前面已经实现了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
    }
    
    
    • 新增操作
    //插入单个
    func (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条记录(最旧的)
    如果匹配到了(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 bson.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:       "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=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

  • 相关阅读:
    iOS 色值 转换 干货
    控制器view及UIApplication的生命周期
    runtime认识及其用处
    block底层实现原理
    iOS开发,你不得不知的数据存储---干货
    UIImageView(UIImage)的那些细节
    UIButton那些应用细节
    使用gevent写异步代码
    数据分析三剑客之pandas
    数据分析之numpy
  • 原文地址:https://www.cnblogs.com/lxmhhy/p/13471256.html
Copyright © 2020-2023  润新知