简单 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,存了若干键值对。在建立数据库以及写数据库的接口还是有不少坑的,等下详细说。

数据库的项目结构

简单 web 服务与客户端开发实战 复制SWAPI网站 项目小结_第1张图片

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一次,那样可能也可以。

你可能感兴趣的:(服务计算)