golang 开发 REST 风格的 API

434 阅读3分钟

⒈ 从 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 演示,所以设置成了 *