Golang 开发 REST 风格的 API 实战

2022年3月27日 320点热度 0人点赞 0条评论

【导读】本文用详细例子介绍了 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"`
}
  • 初始化数据库连接(数据库连接信息配置在 .env 文件中,这里使用 dotenv 来读取配置信息,所以需要引入 autoload
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
}
  • 执行数据的 CURD 操作
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

 - EOF -

推荐阅读(点击标题可打开)

1、Golang 中的 RESTful API 最佳实践

2、Golang 分布式系统

3、Golang 互斥锁内部实现

Go 开发大全

参与维护一个非常全面的Go开源技术资源库。日常分享 Go, 云原生、k8s、Docker和微服务方面的技术文章和行业动态。

图片

关注后获取

回复 Go 获取6万star的Go资源库

分享、点赞和在看

支持我们分享更多好文章,谢谢!

9050Golang 开发 REST 风格的 API 实战

root

这个人很懒,什么都没留下

文章评论