3. Gin RESTful API 開發(fā)

3. Gin RESTful API 開發(fā)

3.1 RESTful API簡介

3.1.1 RESTful API 定義

? ? REST(Representational State Transfer,表現(xiàn)層狀態(tài)轉換)是一種軟件架風格、設計風格,而不是一種標準。它提供了一組設計原則和約束條件,主要用于客戶端和服務端交互類的軟件。基于這個風格設計的軟件可以更簡潔、具有層次。

? ? REST 并沒有一個明確的標準,更像是一種設計風格,滿足這種設計風格的程序或接口稱之為RESTful,因此RESTful API就是滿足REST風格的接口。

? ? RESTful 風格的程序或API需要遵循一些約束條件和規(guī)則,其主要特點如下所示:

  • 以資源為基礎

? ? 資源可以是圖片、音樂、視頻等,它是網(wǎng)絡上的一個實體。

  • 統(tǒng)一接口

? ? 對資源的操作包括查詢、創(chuàng)建、修改和刪除,這些操作分別對應于HTTP協(xié)議中的GET、POST、PUT、DELETE方法。即使用RESTful風格的接口單從接口上只能定位其資源,但無法知道它具體進行了什么操作,需要具體了解其發(fā)生了什么操作,要從HTTP請求方法類型上進行判斷。

3.1.2 RESTful API 設計規(guī)范

? ? 遵循RESTful風格的設計規(guī)范主要如下所示:

  • 使用斜杠(/)表示層級關系且層級不要過深
  • 不要在URI尾部使用斜杠(/)
  • 使用連字符(-)來提高URI可讀性,而不使用橫線(-)
  • 在URI中使用小寫英文字母
  • 不要使用文件擴展名
  • URL中不要使用動詞,使用請求方式表示動作
  • 資源表示使用復數(shù)
  • 使用查詢參數(shù)過濾URI集合

3.2 API設計與實現(xiàn)

3.2.1 前后端分離

? ? 前后端分離是指軟件設計中用戶界面(前端)和后端邏輯、數(shù)據(jù)存儲等的分離,前后端通信通過API進行通信。

  • 前端可以看作是直接與用戶進行交互的部分,前端負責收集用戶的輸入和展現(xiàn)輸出結果
  • 后端一般是指在后臺服務器上運行的程序,包含后端邏輯處理、數(shù)據(jù)存儲和一些其他依賴的組件等。

? ? 前后端分離所帶來的好處如下所示:

  • 模塊化:前后端分離,可以獨立處理每個部分、從而實現(xiàn)模塊化,使得維護起來更容易
  • 可擴展性:前后端分離允許更大的可擴展性,使得每個部分都可以根據(jù)程序的需要獨立擴展
  • 可復用性:前后端分離可以使得每個部分在不同的程序或上下文中進行復用
  • 安全性:前后端分離有助于提高安全性,更好的控制對敏感數(shù)據(jù)或功能的訪問
  • 可屏蔽前后端技術棧所帶來的差異:前后端關注的技術領域不同,采用前后端分離后,可以使得各自關注自身領域內(nèi)的技術棧

3.2.2 設計RESTful API

? ? 在前后端分離的架構中,RESTful API是連接前后端的重要橋梁,一個清晰且規(guī)范的RESTful API可以有效幫助前后端開發(fā)人員進行查詢數(shù)據(jù)、執(zhí)行操作、相互通信等。主要包含以下幾個部分。

1.資源與URI設計

? ? 在RESTful API中,資源是要操作的對象,而URI是資源的唯一標識。一個良好的API通常使用名詞表示資源,并盡量保持簡潔清晰。

  • 資源名稱:應使用復數(shù)名詞來表示資源集合,例如使用/users表示所有用戶
  • 資源標識符:一個具體的資源應使用唯一標識符來表示,例如/users/{id}表示為特定ID的用戶

2.HTTP方法設計

? ? 不要在URI中使用動詞,而是要依賴于HTTP方法來表示資源的操作類型。

GET /users           # 獲取所有用戶
POST /users          # 創(chuàng)建一個新的用戶
PUT /users/{id}      # 更新指定ID的用戶
DELETE /users/{id}   # 刪除指定ID的用戶

3.請求與響應格式

? ? RESTful API 通常使用JSON來進行數(shù)據(jù)交換。前端可以通過API獲取數(shù)據(jù),后端則根據(jù)請求返回響應數(shù)據(jù)。

  • 請求格式:前端發(fā)送的請求數(shù)據(jù)需要放在請求體中傳遞
  • 響應格式:后端根據(jù)請求返回響應數(shù)據(jù),需要包含請求結果和相應的錯誤信息

4.狀態(tài)碼

? ? 狀態(tài)碼用于表示API請求的結果,遵循HTTP協(xié)議中的狀態(tài)碼。

5.分頁與過濾

? ? 當資源數(shù)據(jù)量比較大時,常常需要支持分頁和過濾功能。通過在API中加入分頁參數(shù)和過濾條件可以有效減小前端一次性加載大量數(shù)據(jù)的負擔。

GET /users?page_num=1&limit=20
GET /users?status=actived

6.版本管理

? ? 為保證新舊版本的API兼容性,通過在API的URI中加入相應的版本號以方便管理,示例如下所示:

GET /v1/users
GET /v2/users

7.安全性與身份驗證

? ? 在目前的Web環(huán)境中,API的安全性也日益重要,常用和身份驗證如下所示:

  • API Key: 通過請求頭可查詢參數(shù)傳遞API密鑰進行身份驗證
  • JWT(JSON Web Token):通過Bear Token機制在請求頭中傳遞JWT進行身份驗證
  • OAuth: 用于第三方授權驗證

8.錯誤響應

? ? API 應當能夠返回明確的錯誤信息,以幫助客戶端處理異常情況。通常錯誤信息需要包含狀態(tài)碼、錯誤信息錯誤詳細信息

3.3 RESTful API案例

? ? 以下將演示通過接口從數(shù)據(jù)庫中查詢、創(chuàng)建、更新和刪除用戶。

3.3.1 路由設計

{
    users := r.Group("/api/v2/users")
    users.GET("/", controller.GetUsers)
    users.GET("/:id", controller.GetUsersById)
    users.POST("/", controller.CreateUsers)
    users.PUT("/:id", controller.UpdateUsers)
    users.DELETE("/:id", controller.DeleteUsersById)
}

3.3.2 數(shù)據(jù)庫設計

CREATE DATABASE IF NOT EXISTS restful_api_db;
USE restful_api_db;
CREATE TABLE  IF NOT EXISTS users (
    id INT NOT NULL AUTO_INCREMENT,
    name varchar(255) NOT NULL ,
    age INT DEFAULT NULL,
    location varchar(255) DEFAULT NULL,
    passwd varchar(255) NOT NULL,
    PRIMARY KEY (id)
) 
ENGINE=InnoDB ,
CHARACTER SET utf8mb4,
COLLATE utf8mb4_0900_ai_ci;

3.3.3 模型代碼

package models

// 數(shù)據(jù)庫對應的結構體
type User struct {
    Id       int    `json:"id"`
    Name     string `json:"name"`
    Age      int    `json:"age"`
    Location string `json:"location"`
    Passwd   string `json:"passwd"`
}

3.3.4 邏輯代碼

  • 查詢用戶
package controller

func GetUsers(ctx *gin.Context) {
    var queryUsers []models.User
    var responseUsers []models.User
    // 從數(shù)據(jù)庫中查詢數(shù)據(jù)
    db.ConnectDB().Find(&queryUsers)

    if len(queryUsers) <= 0 {
        ctx.JSON(
            http.StatusNotFound,
            gin.H{
                "status_code": http.StatusNotFound,
                "message":     "查詢結果為空",
            })
        return
    }
    for _, v := range queryUsers {
        responseUsers = append(responseUsers, models.User{
            Id:       v.Id,
            Name:     v.Name,
            Age:      v.Age,
            Location: v.Location,
        })
    }

    ctx.JSON(
        http.StatusOK,
        gin.H{
            "status_code": http.StatusOK,
            "count":       len(responseUsers),
            "data":        responseUsers,
        })
}
  • 按用戶ID查詢
func GetUsersById(ctx *gin.Context) {
    var requestUsers models.User
    id := ctx.Param("id")
    // 從數(shù)據(jù)庫查詢指定ID的用戶
    db.ConnectDB().Where("id = ?", id).Find(&requestUsers)

    result := models.User{
        Id:       requestUsers.Id,
        Name:     requestUsers.Name,
        Age:      requestUsers.Age,
        Location: requestUsers.Location,
    }

    ctx.JSON(
        http.StatusOK,
        gin.H{
            "status_code": http.StatusOK,
            "data":        result,
        })

}
  • 創(chuàng)建用戶
func CreateUsers(ctx *gin.Context) {
    name := ctx.PostForm("name")
    age, err := strconv.Atoi(ctx.PostForm("age"))
    if err != nil {
        log.Panicf("解析年齡出錯:%v", err)
        return
    }
    location := ctx.PostForm("location")
    passwd := ctx.PostForm("passwd")

    requestUsers := models.User{
        Name:     name,
        Age:      age,
        Location: location,
        Passwd:   passwd,
    }
    // 保存數(shù)據(jù)到數(shù)據(jù)庫
    db.ConnectDB().Create(&requestUsers)

    ctx.JSON(
        http.StatusOK,
        gin.H{
            "status_code": http.StatusOK,
            "message":     fmt.Sprintf("用戶%s創(chuàng)建成功", requestUsers.Name),
        })
}
  • 更新用戶
func UpdateUsers(ctx *gin.Context) {
    var requestUsers models.User
    id := ctx.Param("id")
    age, err := strconv.Atoi(ctx.PostForm("age"))
    if err != nil {
        log.Printf("解析年齡出錯:%s", err)
    } else {
        db.ConnectDB().Model(&requestUsers).Where("id = ? ", id).Update("age", age)
    }
    location := ctx.PostForm("location")
    passwd := ctx.PostForm("passwd")

    if location != "" {
        db.ConnectDB().Model(&requestUsers).Where("id = ? ", id).Update("location", location)
    }

    if passwd != "" {
        db.ConnectDB().Model(&requestUsers).Where("id = ? ", id).Update("passwd", passwd)
    }

    ctx.JSON(
        http.StatusOK,
        gin.H{
            "status_code": http.StatusOK,
            "message":     fmt.Sprintf("ID=%s的用戶更新成功", id),
        })
}
  • 刪除用戶
func DeleteUsersById(ctx *gin.Context) {
    id := ctx.Param("id")
    // 刪除指定ID的用戶
    db.ConnectDB().Delete(&models.User{}, id)

    ctx.JSON(
        http.StatusOK,
        gin.H{
            "status_code": http.StatusOK,
            "message":     fmt.Sprintf("ID=%s的用戶刪除成功", id),
        })
}
  • 數(shù)據(jù)庫邏輯
func getDsn() string {
    mysqlHost := os.Getenv("MYSQL_HOST")
    mysqlPort := os.Getenv("MYSQL_PORT")
    mysqlUsername := os.Getenv("MYSQL_USERNAME")
    mysqlPasswd := os.Getenv("MYSQL_PASSWD")
    dbName := "restful_api_db"

    dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", mysqlUsername, mysqlPasswd, mysqlHost, mysqlPort, dbName)
    return dsn
}

var db *gorm.DB
var err error

func ConnectDB() *gorm.DB {
    dsn := getDsn()
    db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatalf("連接數(shù)據(jù)庫錯誤:%+v", err)
    }
    return db
}

3.4 RESTful API 測試

? ? 在以上接口完成后,即可以使用API接口工具,例如Postman、Apifox、cURL等工具進行測試。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容