Golang 開發 REST 風格的 API 實戰

【導讀】本文用詳細例子介紹了 Rest API 的項目實現。

⒈ 從 0 開始

在實現動態 API 之前,先來實現一個簡單的靜態頁面渲染

package main

import (
    "fmt"
    "log"
    "net/http"
)

func homePage(w http.ResponseWriter, r *http.Request)  {
    fmt.Fprint(w, "HomePage")
    fmt.Println("加載 HomePage 完成")
}

func handleRequests()  {
    http.HandleFunc("/", homePage)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func main() {
    handleRequests()
}

其中,函數 homePage 負責渲染靜態頁面,而 handleRequests 負責將所有對 URL 根路徑的請求路由到 homePage

⒉ 路由

在實際應用中,同一服務中會包含多個 API,針對不同的功能進行響應,所以必須用到路由模塊。這裏我們使用第三方包 gorilla/mux 來實現路由功能。

func handleRequests()  {
    myRouter := mux.NewRouter()

    myRouter.HandleFunc("/", homePage)
    // 列表
    myRouter.HandleFunc("/all", listAdx)
    // 詳情
    myRouter.HandleFunc("/adx/{id}", detailAdx).Methods("GET")
    // 新建
    myRouter.HandleFunc("/adx", createAdx).Methods("POST")
    // 編輯
    myRouter.HandleFunc("/adx/{id}", updateAdx).Methods("POST")
    // 刪除
    myRouter.HandleFunc("/adx/{id}", deleteAdx). Methods("DELETE")

    log.Fatal(http.ListenAndServe(":8080", myRouter))
}

解析路由中的參數

通常在一些特定功能的 API 中,都會通過路由傳參(如上例中的詳情 API),此時我們首先需要從 API 中解析參數。gorilla/mux 中爲我們提供瞭解析路由方式傳參的方法

vars := mux.Vars(r)
id := vars["id"]

⒊ ORM

golang 與數據庫交互常用的第三方包爲 go-sql-driver,但這裏我們使用另一個第三方包gormgorm 的使用方法與 PHP 中的 ORM 的使用方法非常相似,既可以運行原生的 SQL 語句,還可以進行鏈式調用。

package main

type Adx struct {
    Id        int        `json:"id,omitempty"`
    Name    string    `json:"name,omitempty"`
}
package main

import (
    "fmt"
    _ "github.com/joho/godotenv/autoload"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "log"
    "os"
)

var (
    host        string
    port        string
    dbName        string
    username    string
    password    string
    timeout        string
    DB            *gorm.DB
)

func init(){
    host := os.Getenv("HOST")
    port := os.Getenv("PORT")
    dbName := os.Getenv("DATABASE")
    username := os.Getenv("USERNAME")
    password := os.Getenv("PASSWORD")
    timeout := os.Getenv("TIMEOUT")

    dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?timeout=%s&charset=utf8mb4&parseTime=True&loc=Local", username, password, host, port, dbName, timeout)

    fmt.Println(dsn)

    db, err := gorm.Open(mysql.Open(dsn)&gorm.Config{})

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

    DB = db
}
package main

import (
    "encoding/json"
    "fmt"
    "github.com/gorilla/mux"
    "io/ioutil"
    "net/http"
    "strconv"
)

func listAdx(w http.ResponseWriter, r *http.Request) {
    var records []Adx

    DB.Table("adx").
        Scan(&records)

    json.NewEncoder(w).Encode(records)
}

func detailAdx(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id := vars["id"]

    var adx Adx
    DB.Table("adx").
        Where("id = ?", id).
        First(&adx)

    json.NewEncoder(w).Encode(adx)
}

func createAdx(w http.ResponseWriter, r *http.Request) {
    var adxs []Adx
    request, _ := ioutil.ReadAll(r.Body)
    json.Unmarshal(request, &adxs)

    fmt.Printf("%+v\n", adxs)

    DB.Table("adx").
        Create(&adxs)

    json.NewEncoder(w).Encode(adxs)
}

func updateAdx(w http.ResponseWriter, r *http.Request) {
    var adx Adx
    vars := mux.Vars(r)
    id, _ := strconv.Atoi(vars["id"])
    fmt.Printf("id = %d\n", id)

    request,_ := ioutil.ReadAll(r.Body)
    json.Unmarshal(request, &adx)
    fmt.Printf("%+v\n", adx)

    DB.Table("adx").
        Where("id = ?", id).
        Updates(adx)

    if DB.Error != nil {
        json.NewEncoder(w).Encode(DB.Error)
    } else {
        adx.Id = id
        json.NewEncoder(w).Encode(adx)
    }
}

func deleteAdx(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id, _ := strconv.Atoi(vars["id"])
    // 判斷記錄是否存在
    var count int64
    DB.Table("adx").
        Where("id = ?", id).
        Count(&count)

    if count == 0 {
        json.NewEncoder(w).Encode("記錄不存在")
        return
    }

    DB.Table("adx").
        Delete(&Adx{}, id)

    if DB.Error != nil {
        json.NewEncoder(w).Encode(DB.Error)
    } else {
        json.NewEncoder(w).Encode("操作成功")
    }
}

上述代碼中,路由傳參通過 gorilla/mux 包中的方法解析,但 POST 傳參則需要通過 ioutiljson 來解析。

⒋ API 跨域

在目前 web 應用前後端分離的背景下,要求後端 API 支持跨域。在 go 語言開發的 API 中要實現跨域,仍然需要藉助第三方包,這裏使用 github.com/rs/cors

package main

import (
    "github.com/gorilla/mux"
    "github.com/rs/cors"
    "log"
    "net/http"
)

func handleRequests()  {
    myRouter := mux.NewRouter()

    myRouter.HandleFunc("/", homePage)
    // 列表
    myRouter.HandleFunc("/all", listAdx)
    // 詳情
    myRouter.HandleFunc("/adx/{id}", detailAdx).Methods("GET")
    // 新建
    myRouter.HandleFunc("/adx", createAdx).Methods("POST")
    // 編輯
    myRouter.HandleFunc("/adx/{id}", updateAdx).Methods("POST")
    // 刪除
    myRouter.HandleFunc("/adx/{id}", deleteAdx). Methods("DELETE")

        c := cors.New(cors.Options{
        AllowedOrigins: []string{"*"},
    })

    handler := c.Handler(myRouter)

    log.Fatal(http.ListenAndServe(":8080", handler))
}

在實際應用中,爲了安全性考慮,AllowedOrigins 通常設置爲實際使用的域名,這裏只是作爲 DEMO 演示,所以設置成了 *

轉自:

juejin.cn/post/6945246548208910344

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/vzdeONeqKRO5dZ4yEbhxkA