我们前面已经实现了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

【Gin-API系列】配置文件和数据库操作(三)的更多相关文章

  1. Java Web----Java Web的数据库操作(三)

    Java Web的数据库操作 前面介绍了JDBC技术和JDBC API及API的使用示例,下面详细介绍JDBC在Web中的应用. Java Web----Java Web的数据库操作(一) Java ...

  2. ThinkPHP 数据库操作(三) : 查询方法、查询语法、链式操作

    查询方法 条件查询方法 where 方法 可以使用 where 方法进行 AND 条件查询: Db::table('think_user') ->where('name','like','%th ...

  3. Django-website 程序案例系列-4 ORM数据库操作

    数据库表的创建: 使用mysql时注意,在setting.py中的设置: DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql' ...

  4. Java Web----Java Web的数据库操作(二)

    Java Web的数据库操作 三.JDBC操作数据库 上一篇介绍了JDBC API,之后就可以通过API来操作数据库,实现对数据库的CRUD操作了. http://blog.csdn.net/zhai ...

  5. 循序渐进学.Net Core Web Api开发系列【9】:常用的数据库操作

    系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇描述一 ...

  6. C#实现多级子目录Zip压缩解压实例 NET4.6下的UTC时间转换 [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程 asp.net core异步进行新增操作并且需要判断某些字段是否重复的三种解决方案 .NET Core开发日志

    C#实现多级子目录Zip压缩解压实例 参考 https://blog.csdn.net/lki_suidongdong/article/details/20942977 重点: 实现多级子目录的压缩, ...

  7. phoenix 开发API系列(三)phoenix api 结合数据库

    概述 介绍了 api 的各种写法之后,下面介绍构建 api 时与数据库连接的方式. 注 下面使用的工程的完整代码已经公开在: http://git.oschina.net/wangyubin/phoe ...

  8. ASP.NET实现二维码 ASP.Net上传文件 SQL基础语法 C# 动态创建数据库三(MySQL) Net Core 实现谷歌翻译ApI 免费版 C#发布和调试WebService ajax调用WebService实现数据库操作 C# 实体类转json数据过滤掉字段为null的字段

    ASP.NET实现二维码 using System;using System.Collections.Generic;using System.Drawing;using System.Linq;us ...

  9. 第三百零六节,Django框架,models.py模块,数据库操作——创建表、数据类型、索引、admin后台,补充Django目录说明以及全局配置文件配置

    Django框架,models.py模块,数据库操作——创建表.数据类型.索引.admin后台,补充Django目录说明以及全局配置文件配置 数据库配置 django默认支持sqlite,mysql, ...

随机推荐

  1. Python Ethical Hacking - The Lab and Needed Software

    The Lab and Needed Software Attacker Machine - Kali Linux https://www.kali.org/ 1. Install the softw ...

  2. ATX学习(一)-atx-server

    今天无意中发现了ATX手机设备管理平台,瞬间勾引起了我极大的兴趣,这里对学习过程中的情况做个记录. 1.搭建环境 先按照作者步骤搭建环境出来吧,哇,突然发现ATX搭建环境很方便(一会就搭建好了)   ...

  3. python环境搭建及配置

    我选择的是pycharm,这个对新手比较友好 我目前正在自学周志华的西瓜书,在做练习题3.3时需要用到python来实现,做这个练习需要numpy库和matplot库,最开始的时候忘了anaconda ...

  4. 搭建高可用kubernetes集群(keepalived+haproxy)

    序 由于单master节点的kubernetes集群,存在master节点异常之后无法继续使用的缺陷.本文参考网管流程搭建一套多master节点负载均衡的kubernetes集群.官网给出了两种拓扑结 ...

  5. 一张PDF了解JDK11 GC调优秘籍-附PDF下载

    目录 简介 废弃的VM选项 Source-File Mode Code Heap状态分析 AppCDS 总结 简介 JDK11相比JDK10,添加了一个新的Source-File Mode,可以直接通 ...

  6. Java数组(基本+内存分析)

    一.数组概念   数组即为多个相同数据类型数据的数据按一定顺序排列的集合. 二.数组的特点   1.数组有数组名.索引.元素.素组长度:   2.数组的元素可以是基本数据类型也可以是引用数据类型:   ...

  7. MyBatis Plus 导入IdType失败

    import com.baomidou.mybatisplus.annotation.IdType; 修正Entity模板IdType引入包名到com.baomidou.mybatisplus.enu ...

  8. 给自己挖坑——DateWay

    参考文章 官方手册 官方博客 填坑 目录 简介 使用 1. 引入相关依赖 2. 配置 Dataway,并初始化数据表 3. 配置数据源 4. 把数据源设置到 Hasor 容器中 5. 在SprintB ...

  9. Python File fileno() 方法

    概述 fileno() 方法返回一个整型的文件描述符(file descriptor FD 整型),可用于底层操作系统的 I/O 操作.高佣联盟 www.cgewang.com 语法 fileno() ...

  10. PHP preg_grep() 函数

    preg_grep 函数用于返回匹配模式的数组条目.高佣联盟 www.cgewang.com 语法 array preg_grep ( string $pattern , array $input [ ...