【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
【Gin-API系列】配置文件和数据库操作(三)的更多相关文章
- Java Web----Java Web的数据库操作(三)
Java Web的数据库操作 前面介绍了JDBC技术和JDBC API及API的使用示例,下面详细介绍JDBC在Web中的应用. Java Web----Java Web的数据库操作(一) Java ...
- ThinkPHP 数据库操作(三) : 查询方法、查询语法、链式操作
查询方法 条件查询方法 where 方法 可以使用 where 方法进行 AND 条件查询: Db::table('think_user') ->where('name','like','%th ...
- Django-website 程序案例系列-4 ORM数据库操作
数据库表的创建: 使用mysql时注意,在setting.py中的设置: DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql' ...
- Java Web----Java Web的数据库操作(二)
Java Web的数据库操作 三.JDBC操作数据库 上一篇介绍了JDBC API,之后就可以通过API来操作数据库,实现对数据库的CRUD操作了. http://blog.csdn.net/zhai ...
- 循序渐进学.Net Core Web Api开发系列【9】:常用的数据库操作
系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇描述一 ...
- 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 重点: 实现多级子目录的压缩, ...
- phoenix 开发API系列(三)phoenix api 结合数据库
概述 介绍了 api 的各种写法之后,下面介绍构建 api 时与数据库连接的方式. 注 下面使用的工程的完整代码已经公开在: http://git.oschina.net/wangyubin/phoe ...
- 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 ...
- 第三百零六节,Django框架,models.py模块,数据库操作——创建表、数据类型、索引、admin后台,补充Django目录说明以及全局配置文件配置
Django框架,models.py模块,数据库操作——创建表.数据类型.索引.admin后台,补充Django目录说明以及全局配置文件配置 数据库配置 django默认支持sqlite,mysql, ...
随机推荐
- 转载一篇关于kafka零拷贝(zero-copy)通俗易懂的好文
原文地址 https://www.cnblogs.com/yizhou35/p/12026263.html 零拷贝就是一种避免CPU 将数据从一块存储拷贝到另外一块存储的技术. DMA技术是Direc ...
- Python 实现图像快速傅里叶变换和离散余弦变换
图像的正交变换在数字图像的处理与分析中起着很重要的作用,被广泛应用于图像增强.去噪.压缩编码等众多领域.本文手工实现了二维离散傅里叶变换和二维离散余弦变换算法,并在多个图像样本上进行测试,以探究二者的 ...
- 雨云CDN - 好用的CDN服务
注册雨云 点我 创建CDN 解析CDN 解析完后去试试快了吗?
- 10种常见OOM分析——手把手教你写bug
点赞+收藏 就学会系列,文章收录在 GitHub JavaKeeper ,N线互联网开发必备技能兵器谱,笔记自取 在<Java虚拟机规范>的规定里,除了程序计数器外,虚拟机内存的其他几个运 ...
- Echarts柱状图顶部加数量显示
//加在series中itemStyle: { normal: { label: { show: true, position: 'top', textStyle: { color: '#615a5a ...
- 面试题十七:打印从1到最大的n位数
输入数字n,按顺序打印到最大的n位数 注意:没有规定类型,无论int或long 都会有可能溢出. 应当选择其他类型如String 方法一:定义长度与位数相同的字符数组,从0开始进行加一操作打印 pub ...
- HttpServletRequest、HttpServletResponse
doGet()/doPost()方法都有两个参数,一个为代表请求的request,另一个代表响应response. request是获取前台传递的内容,response是反馈给前台数据 HttpSer ...
- jQuery与javascript
jQuery 是一个 JavaScript 库,jQuery 极大地简化了 JavaScript 编程. javaScript(js)和jQuery(jq) 都是找元素.操作元素 Dom操作的区别: ...
- 《精通Python网络爬虫》|百度网盘免费下载|Python爬虫实战
<精通Python网络爬虫>|百度网盘免费下载|Python爬虫实战 提取码:7wr5 内容简介 为什么写这本书 网络爬虫其实很早就出现了,最开始网络爬虫主要应用在各种搜索引擎中.在搜索引 ...
- 自定义placeholder样式
::-webkit-input-placeholder { /* WebKit, Blink, Edge */ color: #909; } :-moz-placeholder { /* Mozill ...