咱們前面已經實現了API的基礎版本,能對參數校驗和返回指定數據,這一章,咱們將對主機和交換機進行建模,存入數據庫。
考慮到數據庫安裝和使用的簡便性,咱們採用文檔存儲結構的MongoDB數據庫。前端
Mongo數據庫下載安裝,安裝後不用設置密碼,直接使用便可
下載連接 https://www.filehorse.com/download-mongodb/download/ 或者 https://www.mongodb.com/try/download/communitygit
使用數據庫以前須要配置數據庫地址和端口,因此咱們將配置信息存放到配置文件,採用
yaml
格式存儲解析github
這裏,咱們還將API監聽的地址和端口,日誌路徑也能夠都配上golang
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
// 通用 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中有多種優秀的orm庫,例如
xorm
,gorm
。因爲orm將數據庫模型和語言緊密封裝,使用起來很是方便,很適合web前端開發。
但與此同時,使用orm也會致使部分性能丟失(深度使用後會發現隱藏的坑也很多),有興趣的同窗能夠了解下。
本文主要使用golang官方mongodb庫mongo-driver
,直接經過sql
語句操做數據庫(這個過程能夠學習下如何explain和優化sql語句)。web
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個(本文選擇合併)sql
//|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"` }
隨機插入幾條測試數據便可mongodb
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{"小黃"}, }, }
最後咱們修改下參數的驗證規則,支持返回指定模型和返回全部模型。測試結果以下:數據庫
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。json
請訪問 Gin-IPs 或者搜索 Gin-IPsapi