嵌入式linux之go語言開發(八)存儲模塊的封裝(二)

接着上一篇的繼續,

使用go實現一個適用於嵌入式上的存儲模塊。簡單易用,使用簡單方便。

由於在終端上,不需要執行復雜的sql查詢,多表級聯查詢等。就是用來存儲記錄的,因此設計爲存儲到表裏的都爲二進制的字節流。

還有一個原因是終端上記錄字段變動頻繁,不適合動不動就更改數據庫的表結構吧。如果想要方便記錄的解析,可以結合protobuf把數據序列化爲字節流存儲進去。

以下爲按照這個思路的實現的存儲方案:

首先記錄分爲若干區,底層實現對應若干個表,表裏存儲記錄。

然後在單獨建一個表,作爲目錄表。目錄表裏只有若干條記錄。注意這個表不是用來插入數據的。是用來記錄記錄表裏的記錄寫到什麼位置了,上傳到什麼位置了。

表的結構如下:

如圖所示,tb_dir目錄表裏只有10條數據。不會增也不會減,只會更新。分別對應tb_rec01---tb_rec10這十個記錄表。

記錄了當前的記錄流水號,當前記錄寫的位置,當前記錄讀的位置等信息。

再看下記錄表裏有哪些內容。

分別有id,recNo(記錄流水),

recType(記錄類型),

recTime(記錄時間),

data(記錄二進制數據內容,比byte字節流,長度不限),

ext(預留擴展),res(預留)

 

操作有哪些接口?

都在recapi.go文件中,

package sqllite

// 配置項
const (
	// MAXRECDIRS 最大記錄目錄數量
	//(一個記錄目錄對應控制一個記錄表,它記錄了記錄表中的數據存儲和讀取的位置)
	MAXRECDIRS = 10
	// MAXRECAREAS 最大記錄區數量 10個(即記錄表的個數,必須跟記錄目錄數量保持一致)
	MAXRECAREAS = MAXRECDIRS
	// MAXRECCOUNTS 最大記錄條數(即一個表中允許存儲的最大記錄數,例100000條
	// 記錄存滿後且上傳標記已清除後,則從頭開始存儲覆蓋,存儲一條,覆蓋一條)
	MAXRECCOUNTS = 100000
)

//枚舉,記錄區定義(一個記錄區對應一個表)
const (
	//RecArea01 記錄區1
	RecArea01 = iota + 1
	//RecArea02 記錄區2
	RecArea02
	//RecArea03 記錄區3
	RecArea03
	//RecArea04 記錄區4
	RecArea04
	//RecArea05 記錄區5
	RecArea05
	//RecArea06 記錄區6
	RecArea06
	//RecArea07 記錄區7
	RecArea07
	//RecArea08 記錄區8
	RecArea08
	//RecArea09 記錄區9
	RecArea09
	//RecArea10 記錄區10
	RecArea10
)

// Recorder 操作記錄的接口聲明
type Recorder interface {
	// 初始化記錄區(會清空所有數據!)
	InitRecAreas() error
	// 打開記錄區(開機必須先打開一次)
	OpenRecAreas() (err error)
	// 保存記錄
	SaveRec(areaID int, buf []byte, recType int) (id int64, err error)
	// 刪除記錄
	DeleteRec(areaID int, num int64) (err error)
	// 獲取未上傳記錄數量
	GetNoUploadNum(areaID int) int
	// 按數據庫ID讀取一條記錄
	ReadRecByID(areaID int, id int) (p *Records, err error)
	// 順序讀取未上傳的記錄
	ReadRecNotServer(areaID int, sn int) (p *Records, err error)
	// 倒數讀取記錄(如sn=1代表最後一次寫入的記錄)
	ReadRecWriteNot(areaID int, sn int) (p *Records, err error)
	// 最後一條記錄流水
	GetLastRecNO(areaID int) int
}

// RecAPI 操作接口的類
type RecAPI struct {
	Recorder
}

// NewRecAPI 初始化操作接口
func NewRecAPI(debug bool) RecAPI {
	return RecAPI{Recorder: NewRecords(debug)}
}

如何使用?代碼存放在我的github,地址:https://github.com/yongzhena/go-sqllite

有個demo, main.go

package main

import (
	"log"

	rec "github.com/yongzhena/go-sqllite"
)

func checkErr(err error) {
	if err != nil {
		panic(err)
	}
}

func main() {

	log.Println("test sqllite...")

	log.Println("InitRecAreas...")
	opt := rec.NewRecAPI(true)
	err := opt.InitRecAreas()
	if err != nil {
		log.Fatal(err.Error())
	}
	log.Println("InitRecAreas ok!")

	err = opt.OpenRecAreas()
	if err != nil {
		log.Fatal(err.Error())
	}

	log.Println("OpenRecAreas ok!")

	id, err := opt.SaveRec(1, []byte("123456789011"), 0)
	if err != nil {
		log.Println(err.Error())
	}
	log.Printf("over,SaveRec ok!,area=%d,id=%d\n", 1, id)

	id, err = opt.SaveRec(1, []byte("1234567890221111"), 1)
	if err != nil {
		log.Println(err.Error())
	}
	id, err = opt.SaveRec(2, []byte("123456789022"), 1)
	if err != nil {
		log.Println(err.Error())
	}
	log.Printf("over,SaveRec ok!,area=%d,id=%d\n", 2, id)
	id, err = opt.SaveRec(2, []byte("123456789022"), 3)
	if err != nil {
		log.Println(err.Error())
	}
	log.Printf("over,SaveRec ok!,area=%d,id=%d\n", 2, id)

	num := opt.GetNoUploadNum(1)
	log.Printf("area=%d,NoUploadNum=%d\n", 1, num)

	recp, err := opt.ReadRecNotServer(1, 1)
	if err != nil {
		log.Fatal(err.Error())
	}
	log.Println(recp)

	err = opt.DeleteRec(1, 1)
	if err != nil {
		log.Fatal(err.Error())
	}

	num = opt.GetNoUploadNum(1)
	log.Printf("area=%d,NoUploadNum=%d\n", 1, num)

	num = opt.GetNoUploadNum(2)
	log.Printf("area=%d,NoUploadNum=%d\n", 2, num)

}

是不是很簡單?完成了記錄存儲和記錄獲取。完全看不到任何sql的影子。

記錄裏有日期和流水和記錄類型等簡單信息供查詢。

記錄的內容爲二進制byte流,想存什麼就存什麼,存多長也無所謂。至於解析記錄嘛,建議結合protobuf來用。

把記錄序列化後存儲進去。

這幾個接口,在嵌入式終端上足夠用了。可以寫入記錄,按順序讀取未上傳記錄。刪除記錄(並非真正的刪除記錄,若直接刪記錄在終端上是不安全的。而是改了目錄表裏的readID。並且記錄存儲滿後會從頭覆蓋。前提是該記錄已上傳完畢。是不是很安全?這在終端上操作是必須要考慮的。不能讓表裏記錄一直存下去,得指定大小。存滿了也不能刪,得從頭一條條覆蓋。)

這種思路是否成熟?

我們原來的c代碼,單片機的應用,就是這麼做的。只不過底層操作的是Flash。

以下爲內部實現:

recdir的實現:

package sqllite

import (
	"errors"
	"fmt"
	"log"
	db "sqllite/database"
)

// RecDir ...
type RecDir struct {
	ID      int   `json:"id"`
	RecNo   int   `json:"recno" `
	WriteID int64 `json:"writeid" `
	ReadID1 int64 `json:"readid1" `
	ReadID2 int64 `json:"readid2" `
	ReadID3 int64 `json:"readid3" `
	Rp      int   `json:"rp" `
	Res     int   `json:"res" `
	Flag    bool  `json:"flag" `
}

// InitRecDir ...
func InitRecDir() (err error) {
	//創建表
	sqlTable := `
	DROP TABLE IF EXISTS tb_dir;
    CREATE TABLE IF NOT EXISTS tb_dir(
        id INTEGER PRIMARY KEY AUTOINCREMENT,
		recNo INTEGER NOT NULL,
		writeID INTEGER NOT NULL,
		readID1 INTEGER  NOT NULL,
		readID2 INTEGER ,
		readID3 INTEGER ,
		rp INTEGER ,
		res INTEGER
    );
	`
	if db.SQLDB == nil {
		err = errors.New("db.SQLDB is null")
		log.Fatal(err.Error())
		return
	}
	log.Println("begin create dir table...")
	if IsDebug {
		log.Println("sql:" + sqlTable)
	}
	_, err = db.SQLDB.Exec(sqlTable)
	if err != nil {
		log.Fatal(err.Error())
	}
	log.Println("create dir table ok!")
	//清空數據
	// log.Println("begin truncate dir table...")
	// _, err = db.SQLDB.Exec(`UPDATE sqlite_sequence SET seq = 0 WHERE name = 'tb_dir' `)
	// if err != nil {
	// 	log.Fatal(err.Error())
	// }
	// log.Println("truncate dir table ok!")
	log.Println("begin init dir table...")
	for i := 0; i < MAXRECDIRS; i++ {
		_, err = db.SQLDB.Exec("INSERT INTO tb_dir(recNo, writeID,readID1,readID2,readID3,rp,res) VALUES (?, ?,?,?,?,?,?)", 0, 0, 0, 0, 0, 0, 0)
		if err != nil {
			log.Fatal(err.Error())
		}
	}
	log.Println("init dir table ok!")
	return err
}

// UpdateDirs 更新目錄
func (rd *RecDir) UpdateDirs(areaID int) error {

	strSQL := fmt.Sprintf("UPDATE tb_dir SET recNo=%d, writeID=%d, readID1=%d, readID2=%d, readID3=%d, rp=%d,res=%d WHERE id=%d",
		rd.RecNo, rd.WriteID, rd.ReadID1, rd.ReadID2, rd.ReadID3, rd.Rp, rd.Res, areaID)
	if IsDebug {
		log.Println(strSQL)
	}
	res, err := db.SQLDB.Exec(strSQL)
	if err != nil {
		log.Fatal(err.Error())
	}
	affect, err := res.RowsAffected()
	fmt.Println(affect)
	if IsDebug {
		log.Println(rd)
	}
	return err

}

// LoadDirs 加載(讀取)目錄
func (rd *RecDir) LoadDirs(areaID int) error {

	strSQL := fmt.Sprintf("SELECT * FROM tb_dir WHERE id=%d", areaID)
	if IsDebug {
		log.Println(strSQL)
	}
	rows, err := db.SQLDB.Query(strSQL)
	if err != nil {
		log.Fatal(err.Error())
		return err
	}
	if rows.Next() {
		err = rows.Scan(&rd.ID, &rd.RecNo, &rd.WriteID, &rd.ReadID1, &rd.ReadID2, &rd.ReadID3, &rd.Rp, &rd.Res)
		if err != nil {
			log.Fatal(err.Error())
			return err
		}
	} else {
		log.Fatal("no dir records")
	}

	rows.Close()
	if IsDebug {
		log.Println(rd)
	}
	return err
}

records.go實現:

package sqllite

import (
	"errors"
	"fmt"
	"log"
	db "sqllite/database"
	"strings"
	"time"
)

var (
	//IsDebug 是否調試
	IsDebug = true
	recDir  [MAXRECAREAS]RecDir
)

// Records ...
type Records struct {
	ID      int    `json:"id"`
	RecNo   int    `json:"recno" `
	RecType int    `json:"rectype" `
	RecTime string `json:"rectime" `
	Data    []byte `json:"data" `
	Ext     string `json:"ext" `
	Res     string `json:"res" `
}

// InitRecAreas 初始化記錄存儲區
func (rec Records) InitRecAreas() error {

	//初始化目錄表
	err := InitRecDir()
	if err != nil {
		log.Fatal(err.Error())
		return err
	}
	//創建記錄表
	sqlTable := `
	DROP TABLE IF EXISTS TB_NAME;
    CREATE TABLE IF NOT EXISTS TB_NAME (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
		recNo INTEGER NOT NULL,
		recType  INTEGER NOT NULL,
		recTime INTEGER NOT NULL,
		data BLOB ,
		ext TEXT ,
		res TEXT
    );
	`
	for i := 0; i < MAXRECAREAS; i++ {
		tbName := fmt.Sprintf("tb_rec%02d", i+1)
		log.Println("begin create rec table " + tbName)
		sqls := strings.Replace(sqlTable, "TB_NAME", tbName, -1)
		if IsDebug {
			log.Println("sql:" + sqls)
		}
		_, err = db.SQLDB.Exec(sqls)
		if err != nil {
			log.Fatal(err.Error())
			return err
		}
		log.Println("create rec table " + tbName + " ok!")

	}
	return err
}

// OpenRecAreas 打開記錄存儲區,每次開機,需要先打開一下
func (rec Records) OpenRecAreas() (err error) {
	//加載RecDir
	for i := 0; i < MAXRECAREAS; i++ {
		log.Printf("LoadDirs %02d \n", i+1)
		err = recDir[i].LoadDirs(i + 1)
		if err != nil {
			log.Println(err.Error())
			return
		}
		log.Printf("LoadDirs %02d ok!\n", i+1)
	}
	//log.Println(recDir)

	return err
}

// SaveRec 保存記錄
func (rec *Records) SaveRec(areaID int, buf []byte, recType int) (id int64, err error) {

	log.Printf("SaveRec,area=%02d \n", areaID)
	if (areaID <= 0) || (areaID > MAXRECAREAS) {
		err = fmt.Errorf("area id  %02d is not right,mast between 1 and %02d", areaID, MAXRECAREAS)
		log.Println(err.Error())
		return
	}
	rec.RecNo = recDir[areaID-1].RecNo
	t := time.Now()
	rec.RecTime = t.Format("20060102150405")
	rec.Data = buf
	rec.RecType = recType
	//記錄是否存儲滿,判斷
	if (recDir[areaID-1].WriteID + 1) > (int64)(MAXRECCOUNTS) {

		if recDir[areaID-1].ReadID1 == 0 {

			err = fmt.Errorf("rec area %02d is full", areaID)
			log.Println(err.Error())
			return
		}

		if (recDir[areaID-1].WriteID + 1 - int64(MAXRECCOUNTS)) == recDir[areaID-1].ReadID1 {
			err = fmt.Errorf("rec area %02d is full", areaID)
			log.Println(err.Error())
			return
		}

		//保存記錄
		strSQL := fmt.Sprintf(`UPDATE tb_rec%02x SET recNo=%d, recType=%d,recTime=%s,data=?,ext="%s",res="%s" WHERE id = 1`,
			areaID, rec.RecNo+1, rec.RecType, rec.RecTime, rec.Ext, rec.Res)
		if IsDebug {
			log.Println(strSQL)
		}
		_, err = db.SQLDB.Exec(strSQL, rec.Data)
		if err != nil {
			log.Fatal(err.Error())
			return
		}
		recDir[areaID-1].RecNo++
		recDir[areaID-1].WriteID = 1
		recDir[areaID-1].Flag = true
		id = 1
		err = recDir[areaID-1].UpdateDirs(areaID)
		if err != nil {
			log.Fatal(err.Error())
			return
		}
		log.Printf("SaveRec,area=%02d ok!\n", areaID)
		return id, err
	}

	if recDir[areaID-1].Flag {
		//記錄是否滿判斷
		if (recDir[areaID-1].WriteID + 1) == recDir[areaID-1].ReadID1 {
			err = fmt.Errorf("rec area %02d is full", areaID)
			log.Println(err.Error())
			return
		}
		id = recDir[areaID-1].WriteID + 1
		strSQL := fmt.Sprintf(`UPDATE tb_rec%02x SET recNo=%d, recType=%d,recTime=%s,data=?,ext="%s",res="%s" WHERE id = %d`,
			areaID, rec.RecNo+1, rec.RecType, rec.RecTime, rec.Ext, rec.Res, id)
		if IsDebug {
			log.Println(strSQL)
		}
		_, err = db.SQLDB.Exec(strSQL, rec.Data)
		if err != nil {
			log.Fatal(err.Error())
			return
		}
		recDir[areaID-1].RecNo++
		recDir[areaID-1].WriteID = id
		err = recDir[areaID-1].UpdateDirs(areaID)
		if err != nil {
			log.Fatal(err.Error())
			return 0, err
		}
		log.Printf("SaveRec,area=%02d ok!\n", areaID)
		return id, err

	}

	strSQL := fmt.Sprintf(`INSERT INTO tb_rec%02x(recNo, recType,recTime,data,ext,res) VALUES (%d,%d,%s,?,"%s","%s")`,
		areaID, rec.RecNo+1, rec.RecType, rec.RecTime, rec.Ext, rec.Res)
	if IsDebug {
		log.Println(strSQL)
	}
	rs, err := db.SQLDB.Exec(strSQL, rec.Data)
	if err != nil {
		log.Fatal(err.Error())
		return 0, err
	}
	id, err = rs.LastInsertId()
	if err != nil {
		log.Fatal(err.Error())
		return 0, err
	}
	recDir[areaID-1].RecNo++
	recDir[areaID-1].WriteID = id
	err = recDir[areaID-1].UpdateDirs(areaID)
	if err != nil {
		log.Fatal(err.Error())
		return 0, err
	}
	log.Printf("SaveRec,area=%02d ok!\n", areaID)
	return id, err

}

// DeleteRec 刪除記錄(並不是真正刪除表裏記錄,而是清除該記錄的上傳標記)
// areaID:記錄區 num:刪除的數量
func (rec Records) DeleteRec(areaID int, num int64) (err error) {
	if (areaID <= 0) || (areaID > MAXRECAREAS) {
		err = errors.New("area id is not right")
		log.Fatal(err.Error())
		return
	}

	id := recDir[areaID-1].ReadID1

	//如果寫的位置等於讀的位置,說明記錄已上傳完,沒有要刪除的了
	if recDir[areaID-1].WriteID == recDir[areaID-1].ReadID1 {
		return
	}

	//如果要刪除的數量大於了最大的記錄數
	if (id + num) > MAXRECCOUNTS {
		if (id + num - MAXRECCOUNTS) > recDir[areaID-1].WriteID {
			recDir[areaID-1].ReadID1 = recDir[areaID-1].WriteID
			err = recDir[areaID-1].UpdateDirs(areaID)
			if err != nil {
				log.Fatal(err.Error())
				return err
			}
			return
		}
		//更新讀指針(讀的位置)
		recDir[areaID-1].ReadID1 = id + num - MAXRECCOUNTS
		err = recDir[areaID-1].UpdateDirs(areaID)
		if err != nil {
			log.Fatal(err.Error())
			return err
		}
		return
	}

	//如果當前寫的位置大於讀的位置
	if recDir[areaID-1].WriteID > recDir[areaID-1].ReadID1 {
		if id+num > recDir[areaID-1].WriteID {
			//更新讀指針(讀的位置)
			recDir[areaID-1].ReadID1 = recDir[areaID-1].WriteID
			err = recDir[areaID-1].UpdateDirs(areaID)
			if err != nil {
				log.Fatal(err.Error())
				return err
			}
			return
		}
	}

	//更新讀指針(讀的位置)
	recDir[areaID-1].ReadID1 = id + num
	err = recDir[areaID-1].UpdateDirs(areaID)
	if err != nil {
		log.Fatal(err.Error())
		return err
	}
	return
}

//GetNoUploadNum 獲取未上傳記錄數量
func (rec Records) GetNoUploadNum(areaID int) int {

	num := 0
	if recDir[areaID-1].WriteID == recDir[areaID-1].ReadID1 {
		num = 0
		return num
	}
	if recDir[areaID-1].Flag == false {
		num = int(recDir[areaID-1].WriteID - recDir[areaID-1].ReadID1)
	} else {
		if recDir[areaID-1].WriteID > recDir[areaID-1].ReadID1 {
			num = int(recDir[areaID-1].WriteID - recDir[areaID-1].ReadID1)
		} else {
			num = int(MAXRECCOUNTS - recDir[areaID-1].ReadID1 + recDir[areaID-1].WriteID)
		}
	}
	return num
}

// ReadRecByID 按數據庫ID讀取記錄
func (rec Records) ReadRecByID(areaID int, id int) (p *Records, err error) {
	var rec1 Records
	if (areaID <= 0) || (areaID > MAXRECAREAS) {
		err = errors.New("area id is not right")
		log.Fatal(err.Error())
		return
	}
	strSQL := fmt.Sprintf("SELECT * FROM tb_rec%02d WHERE id=%d", areaID, id)
	if IsDebug {
		log.Println(strSQL)
	}
	rows, err := db.SQLDB.Query(strSQL)
	if err != nil {
		log.Fatal(err.Error())
		return nil, err
	}
	if rows.Next() {
		err = rows.Scan(&rec1.ID, &rec1.RecNo, &rec1.RecType, &rec1.RecTime, &rec1.Data, &rec1.Ext, &rec1.Res)
		if err != nil {
			log.Fatal(err.Error())
			return nil, err
		}
	} else {
		log.Println("no records")
		return nil, err
	}
	rows.Close()
	return &rec1, nil
}

//ReadRecNotServer 讀取未上傳的記錄數據,順序讀取第SN條未上傳的記錄
//sn取值 1-到-->未上傳記錄數目
func (rec Records) ReadRecNotServer(areaID int, sn int) (p *Records, err error) {
	if (areaID <= 0) || (areaID > MAXRECAREAS) {
		err = errors.New("area id is not right")
		log.Fatal(err.Error())
		return
	}
	id := recDir[areaID-1].ReadID1
	if (int(id) + sn) > MAXRECCOUNTS {
		if int(id)+sn-MAXRECCOUNTS > int(recDir[areaID-1].WriteID) {
			return nil, errors.New("no records")
		}
		p, err = rec.ReadRecByID(areaID, int(id)+sn-MAXRECCOUNTS)
	} else {
		if recDir[areaID-1].ReadID1 < recDir[areaID-1].WriteID {
			if (int(id) + sn) > int(recDir[areaID-1].WriteID) {
				return nil, errors.New("no records")
			}
			p, err = rec.ReadRecByID(areaID, int(recDir[areaID-1].ReadID1)+sn)
		}

	}
	return p, err
}

// ReadRecWriteNot 倒數讀取第SN條寫入的記錄
//讀取一條記錄  倒數讀取第SN條寫入的記錄
func (rec Records) ReadRecWriteNot(areaID int, sn int) (p *Records, err error) {
	id := int(recDir[areaID-1].WriteID)
	if (id - sn) < 0 {
		if recDir[areaID-1].Flag {
			p, err = rec.ReadRecByID(areaID, MAXRECCOUNTS-(sn-id-1))
		} else {
			return nil, errors.New("no records")
		}
	} else {
		p, err = rec.ReadRecByID(areaID, (id - sn + 1))
	}
	return
}

// GetLastRecNO 獲取最後一條記錄流水號
func (rec Records) GetLastRecNO(areaID int) int {
	if (areaID <= 0) || (areaID > MAXRECAREAS) {
		log.Println("area id is not right")
		return 0
	}
	id := recDir[areaID-1].RecNo
	return id
}

// NewRecords ...
func NewRecords(debug bool) *Records {
	IsDebug = debug
	records := new(Records)
	return records
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章