任務概述
利用 web 客戶端調用遠端服務是服務開發本實驗的重要內容。其中,要點建立 API First 的開發理念,實現前後端分離,使得團隊協作變得更有效率。
任務目標
- 選擇合適的 API 風格,實現從接口或資源(領域)建模,到 API 設計的過程
- 使用 API 工具,編制 API 描述文件,編譯生成服務器、客戶端原型
- 使用 Github 建立一個組織,通過 API 文檔,實現 客戶端項目 與 RESTful 服務項目同步開發
- 使用 API 設計工具提供 Mock 服務,兩個團隊獨立測試 API
使用 travis 測試相關模塊
API風格
我們的API設計遵循了項目文檔中的api.yaml
{
"ArticlePost": "/openapi101/users/{username}/article",
"ArticleArticleIdGet":"/openapi101/users/{username}/article/{article_id}",
"CreateComment":"/openapi101/users/{username}/article/{article_id}/comment",
"GetCommentsOfArticle":"/openapi101/users/{username}/article/{article_id}/comment",
"AuthSigninPost":"/openapi101/auth/signin",
"AuthSignupPost": "/openapi101/auth/signup"
}
使用API生成原型
使用Swagger Edit工具自動生成框架:
API文檔如下:
swagger: "2.0"
info:
description: "A simple API to learn how to write OpenAPI Specification"
version: "1.0.0"
title: "Simple API"
host: "simple.api"
basePath: "/openapi101"
schemes:
- "https"
paths:
/auth/signin:
post:
summary: "sign in"
parameters:
- name: "username"
in: "path"
required: true
type: "string"
x-exportParamName: "Username"
responses:
"200":
description: "A list of Person"
schema:
required:
- "username"
properties:
username:
type: "string"
"404":
description: "The user does not exists"
/auth/signup:
post:
summary: "sign up a user"
description: "sign up a user"
parameters:
- in: "body"
name: "user"
required: false
schema:
$ref: "#/definitions/user"
x-exportParamName: "User"
responses:
"204":
description: "OK"
"400":
description: "Wrong"
/article/{article_id}:
get:
summary: "get post"
parameters:
- name: "article_id"
in: "path"
description: "article id"
required: true
type: "string"
x-exportParamName: "ArticleId"
responses:
"200":
description: "A post"
schema:
properties:
name:
type: "string"
data:
type: "string"
/article:
post:
summary: "Create article"
parameters:
- in: "body"
name: "content"
required: true
schema:
$ref: "#/definitions/content"
x-exportParamName: "Content"
responses:
"204":
description: "A post"
definitions:
user:
required:
- "password"
- "username"
properties:
username:
type: "string"
password:
type: "string"
content:
required:
- "article_content"
properties:
article_content:
type: "string"
author:
type: "string"
後端框架
在Swagger Edit編輯器中點擊go-server生成後端框架,結構如下:
- go-server
- .swagger-codegen
- VERSION
- api
- swagger.yaml #api設計
- go
- routers.go #路由匹配
- logger.go #日誌
- api_default.go #api框架
- user.go
- user_api.go
- ...
- README.md
- main.go
API實現
框架搭建好後,就需要關注實現部分
註冊實現
登陸實現
數據庫使用到boltdb,相關資料
開啓數據庫:
db, err := bolt.Open("my.db", 0600, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
數據庫的讀操作:
err = db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("User"))
if b != nil {
v := b.Get([]byte(user.Username))
if ByteSliceEqual(v, []byte(user.Password)) {
return nil
} else {
return errors.New("Wrong Username or Password")
}
} else {
return errors.New("Wrong Username or Password")
}
})
寫操作:
err = db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte("User"))
if err != nil {
return err
}
return b.Put([]byte(user.Username), []byte(user.Password))
})
json的序列化和反序列化:
當服務端收到請求後,需要使用json解析器反序列化爲結構體
當服務端發送響應時,需要將結構體序列化爲字符串
- json反序列化:
comment := &Comment{
Date: time.Now().Format("2006-01-02 15:04:05"),
Content: "",
Author: "",
ArticleId: Id,
}
err = json.NewDecoder(r.Body).Decode(&comment)
JWT認證
JWT是Json Web Token的縮寫
token的意思是“令牌”,是用戶身份的驗證方式,最簡單的token組成:
- uid(用戶唯一的身份標識)
- time(當前時間的時間戳)
- sign(簽名,由token的前幾位+用哈希算法壓縮成一定長的十六進制字符串,可以防止惡意第三方拼接token請求服務器)。
還可以把不變的參數也放進token,避免多次查庫time(當前時間的時間戳)
JWT認證:
用戶註冊之後, 服務器生成一個 JWT token返回給瀏覽器, 瀏覽器向服務器請求數據時將 JWT token 發給服務器, 服務器用 signature 中定義的方式解碼 JWT 獲取用戶信息.
一個JWT token包含3部分:
- header: 告訴我們使用的算法和 token 類型
- Payload:必須使用 sub key 來指定用戶 ID, 還可以包括其他信息比如 email, username 等.
- Signature: 用來保證 JWT 的真實性. 可以使用不同算法