README
¶
GDB
GDB is a real-time database encapsulated based on goleveldb it can be used to obtain and store large amount of historical data in various ways(including gettting raw data, filtered data with given condition, etc...),it provides rest, gRPC interface and desktop client , and it allows you to generate your own data based on existing data by coding js on desktop client.If you need deal with big data, you will love GDB.
Features
- High writing performance
- Fine control of historical data
- Simulate data based on existing data with js
- Token-based permission control
- restful and gRPC api
- support https api
- desktop client based on Electron
- fine system documentation and interface documentation
Contents
Quick Start
If you are familiar with go language, you call install gdb and then use it in your project to customize your own behavior,For more details,you can see document or examples The Base of gdb is group and item, item is the subset of group, you need to add group to gdb, then add item to group, after that, you can write realTime Data to item and get historical Data.
Installation
Gdb is a cgo project, to run or build it, you need gcc ,and install Go (version 1.16+ is required), then set GO111MODULE=ON
go get github.com/JustKeepSilence/gdb
Then import gdb in your own code
import "github.com/JustKeepSilence/gdb/db"
import (
"encoding/json"
"fmt"
"github.com/JustKeepSilence/gdb/db"
"log"
"io/ioutil"
"time"
)
func main() {
// initial gdb
if gdb, err := db.NewGdb("./leveldb", "./itemDb");err!=nil{
log.Fatal(err)
}else{
// add groups
if _, err := gdb.AddGroups(db.AddedGroupInfo{
GroupName: "1DCS",
ColumnNames: []string{"Column1", "Column2", "Column3"}, // column name can't be itemName
});err!=nil{
log.Fatal(err)
}
// add items
if _, err := gdb.AddItems(db.AddedItemsInfo{
GroupName: "1DCS",
ItemValues: []map[string]string{{"itemName": "x", "description": "x"}, {"itemName": "y", "description": "y"}, {"itemName": "z", "description": "z"},
{"itemName": "item1", "description": "item1"}, {"itemName": "item2", "description": "item2"}},
});err!=nil{
log.Fatal(err)
}
// batch write, y = 2 * x
if _, err := gdb.BatchWrite([]db.ItemValue{{ItemName: "x", Value: "1"}, {ItemName: "y", Value: "2"}}...);err!=nil{
log.Fatal(err)
}else{
// get latest updated value of given items
if r, err := gdb.GetRealTimeData("x", "y");err!=nil{
log.Fatal(err)
}else{
d, _ := json.Marshal(r)
fmt.Println(string(d))
}
}
// write historical data
// mock one hour historical data
var xData, yData, ts []string
now := time.Now()
fmt.Println("now: ", now.Format("2006-01-02 15:04:05"))
r := rand.New(rand.NewSource(99))
for i := 0; i < 3600; i++ {
x := r.Intn(3600)
y := 2 * x
t := now.Add(time.Second * time.Duration(i)).Unix() + 8 * 3600
xData = append(xData, fmt.Sprintf("%d", x))
yData = append(yData, fmt.Sprintf("%d", y))
ts = append(ts, fmt.Sprintf("%d", t))
}
if err := gdb.BatchWriteHistoricalData([]db.HistoricalItemValue{{ItemName: "x", Values: xData, TimeStamps: ts}, {ItemName: "y", Values: yData, TimeStamps: ts}}...);err!=nil{
log.Fatal(err)
}else{
// get raw historical data for debugging
if r, err := gdb.GetRawHistoricalData("x");err!=nil{
log.Fatal(err)
}else{
d, _ := json.Marshal(r)
_ = ioutil.WriteFile("./rawX.txt", d, 0644)
}
// get historical data with given itemName, startTime, endTime and intervals
stX := int(now.Add(time.Minute * 5).Unix() + 8 * 3600)
etX := int(now.Add(time.Minute * 25).Unix() + 8 * 3600)
stY := int(now.Add(time.Minute * 35).Unix() + 8 * 3600)
etY := int(now.Add(time.Minute * 55).Unix() + 8 * 3600)
if r, err := gdb.GetHistoricalData([]string{"x", "y"}, []int{stX, stY}, []int{etX, etY}, []int{2, 10});err!=nil{
log.Fatal(err)
}else{
d, _ := json.Marshal(r)
_ = ioutil.WriteFile("./hX.txt", d, 0644)
}
// get historical data with given itemName
if r, err := gdb.GetHistoricalDataWithStamp([]string{"x", "y"}, [][]int{{stX, etX}, {stY, etY}}...);err!=nil{
log.Fatal(err)
}else{
d, _ := json.Marshal(r)
fmt.Println(string(d))
}
// get historical data with condition
if r, err := gdb.GetHistoricalDataWithCondition([]string{"x", "y"}, []int{stX, stY}, []int{etX, etY}, []int{2, 10}, `item["x"] > 0 && item["y"] > 1000`, []db.DeadZone{}...);err!=nil{
log.Fatal(err)
}else{
d, _ := json.Marshal(r)
_ = ioutil.WriteFile("./f.txt", d, 0644)
}
}
}
}
Notes: In order to reduce the size of the entire project, in this case only the core functions of gdb are included, unless you use gdbClient tags when compiling
GdbServer
If you are not familiar with go, and want to use gdb as back-end database only, you can build-gdb, then run gdb in your server.Also, you can download the compiled installer and run it directly. In this way, you can't customize your own behavior, but you can use restful or grpc api provided by gdb, as well as token-control for every api we provided.For more details you can see restful-examples or grpc-examples or documents
Build GDB
you need to installGdb firstly, then change to gdb/main directory and run the following command
go build -tags=jsoniter -tags=gdbClient -o ../gdb
Notes: you must add gdbClient tags when building gdb Client, otherWise only core function without client will be compiled. After that, you can customize your own config in config.json.For more details about config you can see https://github.com/JustKeepSilence/gdb/blob/master/config.json
// Notes: you can use // single line comments in json file
{
"gdbConfigs": {
"ip": "",
"port": 8082,
"dbPath": "./leveldb",
"itemDbPath": "./itemDb",
"applicationName": "gdb",
"authorization": true,
"mode": "http",
"httpsConfigs": {
"ca": false,
"selfSignedCa": false,
"caCertificateName": "",
"serverCertificateName": "gdbServer"
}
},
"logConfigs": {
"logWriting": true,
"Level" : "Error",
"expiredTime": 86400
}
}
Notes: you need set gdb,config.json, and ssl folder in the same path to sure gdb work normally.
Download Gdb
if you are not familiar with go at all, you can also directly download the compiled installer we provided, the download url is: https://wws.lanzous.com/iHt95nkegha, download passWord is 7bv4
Run With HTTPS Mode
gdb support https mode for restful nad gRPC,if you want to run with https mode, you need to set mode filed in configs.json to https, and customize your own https configs.Then put your own certificate to ssl folder and put ssl folder the same path as gdb executable program. Or you can use default certificate we provided without ca root.
Notes: selfSignedCa is not allowed on windows at moment.And if you want to use ca root, you need to set ca field to true and set the caCertificateName field in config.json
Run With Authorization Mode
gdb support token authorization mode, if you want to run with authorization mode, you need to set authorization field to true.Then you need to add Authorization field to header if you use restful, or to context if you use gRPC.For more details, you can see document
Restful API Examples
If you use other language to access gdb, you can use resutful interface, before use you need build gdb or download, and after running the application,you can interact with gdb by the restful interface easily.Here is the examples of JS(ES6).For more details see document
Page
// userLogin, passWord is md5 of `${passWord}@seu`
axios.post("/page/userLogin", JSON.stringify({userName: "admin", passWord: "685a6b21dc732a9702a96e6731811ec9"}))
{"code":200,"message":"","data":{"token":"bc947ca95872df7993fb277072eaa12d"}}
// getUserInfo
axios.post("/page/getUserInfo", JSON.stringify({"name": "admin"}))
{"code":200,"message":"","data":{"name":"admin","role":["super_user"]}}
// userLogOut
axios.get("/page/userLogOut/admin")
{"code":200,"message":"","data":{"effectedRows":1}}
// upload excel file, you need to set Content-Type to multipart/form-data
const data = new FormData()
data.append('file', fileContent) // field must be 'file'
axios({url: "/page/uploadFile", headers:{"Content-Type": "multipart/form-data"}, data, method: "post"})
// addItemsByExcel
axios.post("/page/addItemsByExcel", JSON.stringify({"fileName": "item.xlsx", "groupName": "1DCS"}))
{"code":200,"message":"","data":{"effectedRows":18112}}
// importHistoryByExcel
axios.post("/page/importHistoryByExcel", JSON.stringify({"fileName": "data.xlsx", "itemNames": ["YFLDY"], "sheetNames": ["Sheet1"]}))
{"code":200,"message":"","data":""}
Group
// addGroups
axios.post("/group/addGroups", JSON.stringify({"groupInfos": [{"groupName": "1DCS", "columnNames": ["description", "unit"]}]}))
// deleteGroups
axios.post("/group/deleteGroups", JSON.stringify({"groupNames": ["1DCS"]}))
//getGroups
axios.post("/group/getGroups")
{"code":200,"message":"","data":{"groupNames":["calc","1DCS"]}}
// getGroupProperty
axios.post("/group/getGroupProperty", JSON.stringify({"groupName": "1DCS", "condition": "1=1"}))
{"code":200,"message":"","data":{"itemCount":"6387","itemColumnNames":["itemName","groupName","dataType","description","unit","source"]}}
// updateGroupColumnNames
axios.post("/group/updateGroupColumnNames", JSON.stringify({"groupName": "1DCS", "newColumnNames": ["unit1"], "oldColumnNames": ["unit"]}))
{"code":200,"message":"","data":{"effectedCols":1}}
// deleteGroupColumns
axios.post("/group/deleteGroupColumns", JSON.stringify({"groupName": "1DCS", "columnNames": ["unit"]}))
{"code":200,"message":"","data":{"effectedCols":1}}
// addGroupColumns
axios.post("/group/addGroupColumns", JSON.stringify({"groupName": "1DCS", "columnNames": ["unit"], "defaultValues": [""]}))
{"code":200,"message":"","data":{"effectedCols":1}}
// cleanGroupItems
axios.post("/group/cleanGroupItems", JSON.stringify({"groupNames": ["1DCS"]}))
Item
// addItems
axios.post("/item/addItems", JSON.stringify({"groupName": "1DCS", "itemValues": [{"itemName": "YFJDY", "description": "", "unit": ""}]}))
{"code":200,"message":"","data":{"effectedRows":1}}
// deleteItems
axios.post("/item/deleteItems", JSON.stringify({"groupName": "1DCS", "condition": "itemName='YFJDY'"}))
{"code":200,"message":"","data":{"effectedRows":1}}
// getItems
axois.post("/item/getItems", JSON.stringify({"columnNames": "*", "condition": "itemName like '%%'", "groupName": "1DCS", "rowCount": 10, startRow: 0}))
{"code":200,"message":"","data":{"itemValues":[{"dataType":"5","description":"","groupName":"1DCS","id":"1","itemName":"JL1_TURBTEST1:PTJ.OUT","source":"WP1007/TURBTEST1:PTJ.OUT","unit":""},{"dataType":"5","description":"","groupName":"1DCS","id":"2","itemName":"JL1_1ETS:L009_8.BO01","source":"WP1007/1ETS:L009_8.BO01","unit":""},{"dataType":"5","description":"","groupName":"1DCS","id":"3","itemName":"JL1_1ETS:L001_8.BO01","source":"WP1007/1ETS:L001_8.BO01","unit":""},{"dataType":"5","description":"","groupName":"1DCS","id":"4","itemName":"JL1_1OA2:L128_13.BO01","source":"WP1007/1OA2:L128_13.BO01","unit":""},{"dataType":"5","description":"","groupName":"1DCS","id":"5","itemName":"JL1_1OA2:L128_2.BO01","source":"WP1007/1OA2:L128_2.BO01","unit":""},{"dataType":"5","description":"","groupName":"1DCS","id":"6","itemName":"JL1_1OA2:L128_16.BO01","source":"WP1007/1OA2:L128_16.BO01","unit":""},{"dataType":"5","description":"","groupName":"1DCS","id":"7","itemName":"JL1_1ETS:L001_4.BO01","source":"WP1007/1ETS:L001_4.BO01","unit":""},{"dataType":"5","description":"","groupName":"1DCS","id":"8","itemName":"JL1_1ETS:L001_1.BO01","source":"WP1007/1ETS:L001_1.BO01","unit":""},{"dataType":"5","description":"","groupName":"1DCS","id":"9","itemName":"JL1_1ETS:GEN_TRIP.COUT","source":"WP1007/1ETS:GEN_TRIP.COUT","unit":""},{"dataType":"5","description":"","groupName":"1DCS","id":"10","itemName":"JL1_1OA2:L128_6.BO01","source":"WP1007/1OA2:L128_6.BO01","unit":""}]}}
// getItemsWithCount
axois.post("/item/getItemsWithCount")
{"code":200,"message":"","data":{"itemCount":6387,"itemValues":[{"dataType":"5","description":"汽机第一级压力","groupName":"1DCS","id":"1","itemName":"JL1_TURBTEST1:PTJ.OUT","source":"WP1007/TURBTEST1:PTJ.OUT","unit":""},{"dataType":"5","description":"汽机已复置","groupName":"1DCS","id":"2","itemName":"JL1_1ETS:L009_8.BO01","source":"WP1007/1ETS:L009_8.BO01","unit":""},{"dataType":"5","description":"ETS在线试验#3通道","groupName":"1DCS","id":"3","itemName":"JL1_1ETS:L001_8.BO01","source":"WP1007/1ETS:L001_8.BO01","unit":""},{"dataType":"5","description":"阀切换过程中","groupName":"1DCS","id":"4","itemName":"JL1_1OA2:L128_13.BO01","source":"WP1007/1OA2:L128_13.BO01","unit":""},{"dataType":"5","description":"顺序阀","groupName":"1DCS","id":"5","itemName":"JL1_1OA2:L128_2.BO01","source":"WP1007/1OA2:L128_2.BO01","unit":""},{"dataType":"5","description":"顺序阀运行","groupName":"1DCS","id":"6","itemName":"JL1_1OA2:L128_16.BO01","source":"WP1007/1OA2:L128_16.BO01","unit":""},{"dataType":"5","description":"ETS在线试验#1通道","groupName":"1DCS","id":"7","itemName":"JL1_1ETS:L001_4.BO01","source":"WP1007/1ETS:L001_4.BO01","unit":""},{"dataType":"5","description":"ETS在线试验进入试验","groupName":"1DCS","id":"8","itemName":"JL1_1ETS:L001_1.BO01","source":"WP1007/1ETS:L001_1.BO01","unit":""},{"dataType":"5","description":"发电机遮断","groupName":"1DCS","id":"9","itemName":"JL1_1ETS:GEN_TRIP.COUT","source":"WP1007/1ETS:GEN_TRIP.COUT","unit":""},{"dataType":"5","description":"单阀","groupName":"1DCS","id":"10","itemName":"JL1_1OA2:L128_6.BO01","source":"WP1007/1OA2:L128_6.BO01","unit":""}]}}
// updateItems
axios.post("/item/updateItems", JSON.stringify({"groupName": "1DCS", "condition": "id=1", "clause": "description=' ',unit='℃1'"}))
{"code":200,"message":"","data":{"effectedRows":1}}
// checkItems
axios.post("/item/checkItems", JSON.stringify({"groupName": "1DCS", "itemNames": ["NMJL.UNIT2.20ACS:MAG50AN001SV_MA"]}))
{"code":500,"message":"itemName: NMJL.UNIT2.20ACS:MAG50AN001SV_MAnot existed","data":""}
Data
// batchWrite
axios.post("/data/batchWrite", JSON.stringify({"itemValues": [{"itemName":"NMJL.UNIT2.20FSSS21B:HFY24AP002ZD","value":"382"},{"itemName":"NMJL.UNIT2.20SCS10B:MAL10AA566ZC","value":"97"},{"itemName":"NMJL.UNIT2.20ACS:MAG50AN001SV_MA","value":"314"},{"itemName":"NMJL.UNIT2.20SCS08B:LAV20AP001NA","value":"-326"},{"itemName":"NMJL.UNIT2.20ECSCOMM_3A:20BTM01YC28","value":"224"},{"itemName":"FZSIS.2DCSAI.20DAS05A:LAB10DP101.PNT","value":"-266"},{"itemName":"FZSIS.2DCSAI.20DAS05A:LAB10CP101.PNT","value":"253"},{"itemName":"FZSIS.2DCSAI.20DAS05A:LAB11DP101.PNT","value":"80"},{"itemName":"FZSIS.2DCSAI.20DAS05A:LAV10CE101.PNT","value":"-70"},{"itemName":"FZSIS.2DCSAI.20DAS05A:LAF11CP101.PNT","value":"21"},{"itemName":"FZSIS.2DCSAI.20MCS07B:LAH10AA101GT.PNT","value":"-348"},{"itemName":"FZSIS.2DCSAI.20MCS07A:LAB12CP101.PNT","value":"-328"},{"itemName":"FZSIS.2DCSAI.20DAS05A:LAC10CE101.PNT","value":"143"},{"itemName":"FZSIS.2DCSAI.20MCS07A:LAB11CF101.PNT","value":"143"},{"itemName":"FZSIS.2DCSAI.20DAS05A:LAK11CS101.PNT","value":"-489"},{"itemName":"FZSIS.2DCSAI.20DAS05B:MKF10CE001.PNT","value":"86"}]}))
{"code":200,"message":"","data":{"effectedRows":1}}
// batchWriteHistoricalData
axios.post("/data/batchWriteHistoricalData", JSON.stringify({"historicalItemValues":[{"itemName":"YFJDY","values":["57.760","57.811","57.801","57.781","57.801","57.807","57.773","57.523","57.508"],"timeStamps":["1602347580","1602347581","1602347582","1602347583","1602347584","1602347585","1602347586","1602347587","1602347588"]}]}))
{"code":200,"message":"","data":""}
// getRealTimeData
axios.post("/data/getRealTimeData", JSON.stringify({"groupName": "1DCS", "itemNames": ["testItem1", "testItem2"]))
{"code":200,"message":"","data":{"realTimeData":{"testItem1": "10", "testItem2": "20"}}}
// getHistoricalData
axios.post("/data/getHistoricalData", JSON.stringify({"itemNames":["NMJL.UNIT2.20ACS:MAG50AN001SV_MA"],"startTimes":[1618843574],"endTimes":[1618929974],"intervals":[60]}))
{"code":200,"message":"","data":{"historicalData":{"NMJL.UNIT2.20ACS:MAG50AN001SV_MA":[[1618921777,1618922077,1618922257,1618922437,1618923817,1618924657,1618924837,1618925137,1618925317,1618925497,1618925677,1618926577,1618926937,1618927177,1618927357,1618927537,1618927897,1618928197,1618928377,1618928557,1618928737,1618928917,1618929397,1618929577,1618929757,1618929937],["-218","-250","-72","387","-319","-438","-156","139","-81","-124","-251","218","224","-215","-94","-148","440","38","-78","-418","-59","-275","-279","83","-96","478"]]}}}
// getDbInfo
axios.post("/data/getDbInfo")
{"code":200,"message":"","data":{"info":{"currentTimeStamp":null,"ram":"30.44","speed":"0ms/0","writtenItems":"0"}}}
// getDbSpeedHistory
axios.post("/data/getDbInfoHistory", JSON.stringify({"starTimes": [1617861409547], "endTimes": [1617862009547], "intervale": 5}))
{"code":200,"message":"","data":{"historicalData":{"speed":[null,null]}}}
gRPC API Examples
gdb support gRPC both in http and https mode.In https mode, you need to provide a certificate corresponding to the configuration file(config.json).In authorization mode you also need to set authorization field in gRPC metaData.Here are examples of how to connect gdb client in go with gRPC.For more details you can see serviceExamples or about how to connect gdb in gRPC mode in NodeJs, you can see gdbUI
if cred, err := credentials.NewClientTLSFromFile("./ssl/gdbServer.crt", "gdb.com");err!=nil{
log.Fatal(err)
}else{
if conn, err := grpc.Dial(ip, grpc.WithTransportCredentials(cred));err!=nil{
log.Fatal(err)
}else{
client := model.NewPageClient(conn)
if r, err := client.UserLogin(context.Background(), &model.AuthInfo{
UserName: "admin",
PassWord: "685a6b21dc732a9702a96e6731811ec9",
});err!=nil{
log.Fatal(err)
}else{
fmt.Println(r.GetToken())
}
}
}
Desktop Application
see gdbUI for more details
FAQ
- How to obtain the timeStamp consistent with gdb
# The timestamp in gdb uses the unix timestamp that comes with go,timeZone is UTC
import (
"time"
)
n := time.Now
timeStamp := time.Date(n.Year(), n.Month(), n.Day(), n.Hour(), n.Minute(), n.Minute(), 0, time.UTC)
timeStamp := n.Unix() + 8 * 3600
So, above this, here are some examples to show how to get the timeStamp consistent with gdb
C#
var t1 = new DateTime(2021, 2, 11, 14, 26, 26);
long timeStamp1 = (long)(t1 - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;
Js(In China)
new Date(2021, 1, 11 , 14, 26, 26).getTime()/1000 + 8 * 3600
Python(In China)
from datetime import datetime
int(datetime(2021, 2, 11, 14, 26, 26).timestamp()) + 8 * 3600