前天刚把 Python 版的爬虫用 Go 重写了,数据放到了 Postgres 里,现在用 Go 来写点 API 玩玩吧。

选择 Web 框架

Go 的 Web 框架非常丰富,Github 上 start 过万的就有好几个:

因为我准备写 API,所以最终比较下来选择了 Gin。期间发现了一个 Web 框架性能 的网站,可以查看主流 Web 框架的性能数据,Gin 的性能在所有 Go 的 Web 框架中还算不错的。

起步

写个 Hello World 很简单,使用 Gin 比直接用原生的 net/http 更简洁一些:

app := gin.Default()
app.GET(“/”, func(c *gin.Context) {
		c.JSON(200, gin.H{
			“message”: “Hello World”,
		})
	})
app.Run()

数据模型、数据库

既然是 API,数据总不能写死吧,拿上次爬的数据来试试,数据库和模型都不变。

type Movie struct {
	gorm.Model
	Name   string `json:"name";gorm:"not_null"`
	Rank   string `json:"rank";gorm:"not_null"`
	Poster string `json:"poster";gorm:"not_null"`
	Detail string `json:"detail";gorm:"not_null"`
}

初始化数据库连接,这次我把数据库作为全局变量,因为在很多函数中都要调用数据库来查询。

const (
	host     = "localhost"
	port     = 5432
	user     = "test"
	password = "lllldddd"
	dbname   = "douban"
)

var db *gorm.DB
var err error

func InitDatabase() {
	pqInfo := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
	db, err = gorm.Open("postgres", pqInfo)

	if err != nil {
		fmt.Println(err)
		return
	}
	db.AutoMigrate(&Movie{})
}

获取前 50 个电影

先添加一个路由:

app.GET("/", GetTop50Movie)

然后实现函数 GetTop50Movie,可以直接在数据库查询时 Limit 设置为 50:

func GetTop50Movie(c *gin.Context) {
	var movie []Movie
	if err := db.Limit(50).Find(&movie).Error; err != nil {
		c.AbortWithStatus(404)
	} else {
		c.JSON(200, movie)
	}
}

按 ID 获取电影

先添加一个路由:

app.GET("/movie/:id", GetMovieByID)

然后实现函数 GetMovieByID,从 *gin.Context 里可以拿到 Query Params

func GetMovieByID(c *gin.Context) {
	id := c.Params.ByName("id")
	var movie Movie

	if err := db.Where("id = ?", id).First(&movie).Error; err != nil {
		c.AbortWithStatusJSON(404, gin.H{
			"error": "not found",
		})
	} else {
		c.JSON(200, movie)
	}
}

数据分页

先添加一个路由:

app.GET("/movies", GetMovieByPage)

然后实现函数 GetMovieByPage,分页参数为 page。目前分页实现的逻辑是,根据 page 和 pageSize 查找出相应数量的记录,然后取最后一页的记录。之前没做过后端,不知道是不是常规的做法 🤣,如果你有更好的建议,欢迎一起讨论。

func GetMovieByPage(c *gin.Context) {
	var movie []Movie
	pageSize := 20
	page, err := strconv.Atoi(c.Query("page"))
	if err != nil {
		fmt.Println(err)
		c.AbortWithStatusJSON(404, gin.H{
			"error": "params error",
		})
	} else {
		if page < 1 {
			page = 1
		}
		if err := db.Limit(pageSize * page).Find(&movie).Error; err != nil {
			c.AbortWithStatusJSON(404, gin.H{
				"error": "not found",
			})
		} else {
			if len(movie) < pageSize*(page-1) {
				empty := []Movie{}
				c.JSON(200, empty)
			}
			fmt.Println(len(movie))
			c.JSON(200, movie[pageSize*(page-1):len(movie)])
		}
	}
}

今天就先写到这里吧,用 Gin 写 API 还是蛮爽的,这三个接口才 100 行代码!后面可以再把 Login,Form 表单等等加上玩玩!