1. 程式人生 > >簡單 web 服務與客戶端開發實戰 複製SWAPI網站 專案小結

簡單 web 服務與客戶端開發實戰 複製SWAPI網站 專案小結

專案github地址如下
https://github.com/BigBrother3

本次作業本人主要負責提供資料庫的介面以及資料庫的建立,也即將swapi網站的所有資源都存到資料庫中。

swapi網站如下
https://www.swapi.co/

資料庫按照規定使用boltdb這個資料庫。

boltdb的github地址
https://github.com/boltdb/bolt

boltdb的使用可以參考
https://segmentfault.com/a/1190000010098668

boltdb這個資料庫是鍵值對儲存的,簡單來說就是一個數據庫裡有很多個桶(bucket),這些桶都相當於c++中的map,存了若干鍵值對。在建立資料庫以及寫資料庫的介面還是有不少坑的,等下詳細說。

資料庫的專案結構

在這裡插入圖片描述

swapi為官方提供的介面,具體參考
https://github.com/peterhellberg/swapi

main.go為資料庫的建立檔案,通過官方的api,迴圈向swapi網站傳送get請求獲取資料,然後將資料存進資料庫相應的bucket中的key。

database.go就是資料庫操作的介面。

資料庫的主要介面

1.啟動資料庫

func Start(str string) {
	var err error
	dbName = str
	db, err = bolt.Open(dbName, 0666, &bolt.Options{
Timeout: 1 * time.Second}) if err != nil { log.Fatal(err) return } }

2.停止資料庫

func Stop(){
	if err := db.Close(); err != nil {
		log.Fatal(err)
	}
}

3.首次使用初始化資料庫

func Init(str string) {
	if _,err := os.Open(str)  ; err == nil{
		log.Println("database is already exist . If you want to initialze it , please delete it and try again"
) return } Start(str) if err := db.Update(func(tx *bolt.Tx) error { tx.CreateBucket([]byte("users")) tx.CreateBucket([]byte("films")) tx.CreateBucket([]byte("people")) tx.CreateBucket([]byte("planets")) tx.CreateBucket([]byte("species")) tx.CreateBucket([]byte("starships")) tx.CreateBucket([]byte("vehicles")) return nil }); err != nil { log.Fatal(err) } Stop() }

4.修改鍵值對

func Update(bucketName []byte, key []byte, value []byte) {
	if err := db.Update(func(tx *bolt.Tx) error {
		if err := tx.Bucket(bucketName).Put(key, value); err != nil {
			return err
		}
		return nil
	}); err != nil {
		log.Fatal(err)
	}
}

5.根據桶和鍵名得到數值

func GetValue(bucketName []byte, key []byte) string {
	var result []byte
	if err := db.View(func(tx *bolt.Tx) error {
		//value = tx.Bucket([]byte(bucketName)).Get(key)
		byteLen := len(tx.Bucket([]byte(bucketName)).Get(key))
		result = make([]byte, byteLen)
		copy(result[:], tx.Bucket([]byte(bucketName)).Get(key)[:])
		return nil
	}); err != nil {
		log.Fatal(err)
	}
	return string(result)
}

還有一些判斷鍵值對是否存在,以及得到一個桶的鍵值對數目等等不那麼重要的介面就不詳細描述了。

遇到的主要的坑

1.獲得資料庫某個桶的某個鍵的數值

開始這個getValue的函式我是這樣寫的。

func GetValue(bucketName []byte, key []byte) string {
    var result []byte
    db, err := bolt.Open(dbName, 0666, &bolt.Options{Timeout: 1 * time.Second})
    if err != nil {
        log.Fatal(err)
        return string(result)
    }

    if err := db.View(func(tx *bolt.Tx) error {
        result = tx.Bucket([]byte(bucketName)).Get(key)
        return nil
    }); err != nil {
        log.Fatal(err)
    }

    if err := db.Close(); err != nil {
        log.Fatal(err)
    }
    return string(result)
}

使用這個函式直接報錯。

unexpected fault address 0x7f8782136930
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x1 addr=0x7f8782136930 pc=0x459a55]

主要的原因是get()函式的返回值只能在一個事務開啟的時候有效,在官方的README有如下說明。

Please note that values returned from Get() are only valid while the transaction is open. If you need to use a value outside of the transaction then you must use copy() to copy it to another byte slice.

解決方式可以不關閉事務,也就是不進行db.Close(),我就特定加了一個介面Stop(),進行資料庫的關閉,也可以按照官方文件說的用copy()解決。具體可參考上面的GetValue()介面,我是兩種都採用了。
第一種方法我是後來重現bug的時候才發現的,原本我以為事務(transaction)指db.View的函式內,結果好像只要不把資料庫關掉也是不會報錯的。

2.swapi中的某些api並不連續

開始我建立資料庫的時候思路很簡單,就是對那六鍾資源(films,people,planets,species,starships,vehicles),按照給頂的官方api,從1開始遞增將資源一個個get回來,當get回來的東西為空時則結束,後來我發現,某些資源是不連續的,例如
https://swapi.co/api/people/17/

這個是get不到資源的,但people的資源有數十個,後面還有很多,還有的資源甚至不是從1開始的,例如
https://swapi.co/api/vehicles/1/

後來我就是判斷連續10個都get不到資源的話,那才意味著沒有資源了,別問為什麼不選3個,因為vehicles從1到3都get不到資源。

當然其實也有更優雅的方式,先去get總的api,例如
https://swapi.co/api/vehicles/

這裡json中有個counts表示這一類一共有多少個資源,但是我感覺這有點麻煩,就沒有這樣做。

還有要注意的一點就是,swapi不能頻繁訪問,一段時間內get的次數多了要等個一會(一分鐘左右?)再去get。因此我在建立資料庫的時候也要分開幾次建立,當然也可以設個定時器,每過幾秒才get一次,那樣可能也可以。