1、概述
利用 web 客户端调用远端服务是服务开发本实验的重要内容。其中,要点建立 API First 的开发理念,实现前后端分离,使得团队协作变得更有效率。
任务目标
- 选择合适的 API 风格,实现从接口或资源(领域)建模,到 API 设计的过程
- 使用 API 工具,编制 API 描述文件,编译生成服务器、客户端原型
- 使用 Github 建立一个组织,通过 API 文档,实现 客户端项目 与 RESTful 服务项目同步开发
- 使用 API 设计工具提供 Mock 服务,两个团队独立测试 API
- 使用 travis 测试相关模块
2、开发项目
本次项目我主要负责数据库 boltdb 的接口以及数据库的建立。
boltdb 数据库
boltdb 数据库是使用 Go 语言编写的开源的键值对数据库,Github 地址如下:https://github.com/boltdb/bolt
boltdb 设计源于 LMDB,具有以下特点:
- 直接使用 API 存取数据,没有查询语句;
- 支持完全可序列化的 ACID 事务,这个特性比 LevelDB 强;
- 数据保存在内存映射的文件里。没有 wal、线程压缩和垃圾回收;
- 通过 COW 技术,可实现无锁的读写并发,但是无法实现无锁的写写并发,即读性能超高,但写性能一般,适合于读多写少的场景。
安装 boltdb 数据库
使用如下命令进行安装
go get github.com/boltdb/bolt
常见 API 操作
database.go 即为数据库操作的接口。
swapi 即为官方提供的接口。
启动数据库
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
}
}
其中 Open
的第一个参数为路径,如果数据库不存在则会创建数据库,第二个参数为文件操作,第三个参数为可选参数,可以配置只读和超时时间等。
停止数据库
func Stop(){
if err := db.Close(); err != nil {
log.Fatal(err)
}
}
初始化数据库
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()
}
更新数据库
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)
}
}
boltdb 数据库的读写事务操作可用 Update
来完成。在闭包 fun 中,在结束时返回 nil 来提交事务。
根据桶和键名得到数值
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)
}
桶是数据库中键值对的集合。桶中所有的键必须是唯一的。可以使用 CreateBucket
来创建桶。
数据库的建立
main.go 即为数据库的建立,通过官方的 api,循环向 swapi 网站发送 get 请求获取数据,将数据存进数据库相应的 bucket 中的 key。具体实现如下:
package main
import (
"encoding/json"
"log"
"strconv"
"github.com/WebDevelopingHW12/server/database/database"
"github.com/WebDevelopingHW12/server/database/swapi"
)
var dbName = "./database/test.db"
func main() {
reSetDB := false
if reSetDB {
// initialize the database for the first time
// if it is already exist , do not initialize it again
/*database.Init(dbName)*/
database.Start(dbName)
reSetDatabase()
}else{
database.Start(dbName)
database.Stop()
}
}
func reSetDatabase() {
findFilm()
findPerson()
findPlanet()
findSpecies()
findVehicle()
findStarship()
}
func findFilm() {
c := swapi.DefaultClient
invalidTime := 0
for index := 1; ; index++ {
jsonStr := dump(c.Film(index))
indexStr := strconv.Itoa(index)
if len(database.GetValue([]byte("films"), []byte(indexStr))) == 0 {
if !putIntoDb([]byte("films"), []byte(indexStr), jsonStr) {
invalidTime ++
if invalidTime == 10{
break
}
}else{
invalidTime = 0
}
}else{
log.Printf("films/%d is already exit", index)
}
}
}
func findPerson() {
c := swapi.DefaultClient
invalidTime := 0
for index := 1; ; index++ {
jsonStr := dump(c.Person(index))
indexStr := strconv.Itoa(index)
if len(database.GetValue([]byte("people"), []byte(indexStr))) == 0 {
if !putIntoDb([]byte("people"), []byte(indexStr), jsonStr) {
invalidTime ++
if invalidTime == 10{
break
}
}else{
invalidTime = 0
}
}else{
log.Printf("person/%d is already exit", index)
}
}
}
func findPlanet() {
c := swapi.DefaultClient
invalidTime := 0
for index := 1; ; index++ {
jsonStr := dump(c.Planet(index))
indexStr := strconv.Itoa(index)
if len(database.GetValue([]byte("planets"), []byte(indexStr))) == 0 {
if !putIntoDb([]byte("planets"), []byte(indexStr), jsonStr) {
invalidTime ++
if invalidTime == 10{
break
}
}else{
invalidTime = 0
}
}else{
log.Printf("planets/%d is already exit", index)
}
}
}
func findSpecies() {
c := swapi.DefaultClient
invalidTime := 0
for index := 1; ; index++ {
jsonStr := dump(c.Species(index))
indexStr := strconv.Itoa(index)
if len(database.GetValue([]byte("species"), []byte(indexStr))) == 0 {
if !putIntoDb([]byte("species"), []byte(indexStr), jsonStr) {
invalidTime ++
if invalidTime == 10{
break
}
}else{
invalidTime = 0
}
}else{
log.Printf("species/%d is already exit", index)
}
}
}
func findStarship() {
c := swapi.DefaultClient
invalidTime := 0
for index := 1; ; index++ {
jsonStr := dump(c.Starship(index))
indexStr := strconv.Itoa(index)
if len(database.GetValue([]byte("starships"), []byte(indexStr))) == 0 {
if !putIntoDb([]byte("starships"), []byte(indexStr), jsonStr) {
invalidTime ++
if invalidTime == 10{
break
}
}else{
invalidTime = 0
}
}else{
log.Printf("starships/%d is already exit", index)
}
}
}
func findVehicle() {
c := swapi.DefaultClient
invalidTime := 0
for index := 1; ; index++ {
jsonStr := dump(c.Vehicle(index))
indexStr := strconv.Itoa(index)
if len(database.GetValue([]byte("vehicles"), []byte(indexStr))) == 0 {
if !putIntoDb([]byte("vehicles"), []byte(indexStr), jsonStr) {
invalidTime ++
if invalidTime == 10{
break
}
}else{
invalidTime = 0
}
}else{
log.Printf("vehicles/%d is already exit", index)
}
}
}
func dump(data interface{}, err error) []byte {
jsonStr, err := json.MarshalIndent(data, "", " ")
return jsonStr
}
func putIntoDb(bucketName []byte, index []byte, jsonStr []byte) bool {
stb := &swapi.Film{}
err := json.Unmarshal(jsonStr, &stb)
if err != nil {
log.Fatal(err)
return false
} else if len(stb.URL) == 0 {
log.Printf("%s/%s is invalid", bucketName, index)
return false
}
log.Printf("solve %s/%s", bucketName, index)
database.Update(bucketName, index, jsonStr)
return true
}