目录:
Gin 是一个用 Go 语言编写的轻量级、高性能的 Web 框架,主要用于构建 API 服务和微服务。由于其简洁的 API 设计和强大的路由功能,Gin 在 Go 社区中广受欢迎。
运行Web程序
创建一个目录web-service-gin
,初始化模块
go mod init example/web-service-gin
创建main.go
:
go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.Run("localhost:8080")
}
var albums = []album{
{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
将其运行起来: go run .
,就能访问http://lcoalhost:8080/albums
了。
再新增一个POST接口:
go
// add post handler
func postAlbums(c *gin.Context) {
var newAlbum album
if err := c.BindJSON(&newAlbum); err != nil {
return
}
albums = append(albums, newAlbum)
c.IndentedJSON(http.StatusCreated, newAlbum)
}
// update router
router.POST("/albums", postAlbums)
此时用POST方法来访问就可以来新增album了。
根据指定id返回
新增一个接口:
go
// add getById handler
func getAlbumByID(c *gin.Context) {
id := c.Param("id")
for _, a := range albums {
if a.ID == id {
c.IndentedJSON(http.StatusOK, a)
return
}
}
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}
// update router
router.GET("/albums/:id", getAlbumByID)
发起请求:curl http://localhost:8080/albums/1
即可获取第一个album了。
连接数据库
Web服务中访问数据库是必不可少的,下面以postgresql数据库为例来演示如何连接数据库。
先创建数据库和表:
CREATE DATABASE music;
CREATE TABLE album(
id SERIAL PRIMARY KEY,
title VARCHAR(36) NOT NULL,
artist VARCHAR(36) NOT NULL,
price NUMBRIC(10, 2) NOT NULL
) ;
INSERT INTO album(title, artist, price) VALUES
('Album A', 'Artist 1', 9.99),
('Album B', 'Artist 1', 14.99),
('Album C', 'Artist 2', 19.99);
创建main.go:
go
package main
import (
"database/sql"
"fmt"
"log"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/lib/pq"
)
type Album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
var db *sql.DB
func main() {
dsn := "user=postgres password=admin dbname=music sslmode=disable"
var err error
db, err = sql.Open("postgres", dsn)
if err != nil {
log.Fatal("Failed to connect to database", err)
}
pingErr := db.Ping()
if pingErr != nil {
log.Fatal("Failed to ping database", pingErr)
}
fmt.Println("Connected!")
router := gin.Default()
router.GET("/albums/:artist", getAlbumsByArtist)
router.Run("localhost:8080")
}
func getAlbumsByArtist(c *gin.Context) {
artist := c.Param("artist")
albums, err := queryAlbumsByArtist(artist)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if len(albums) == 0 {
c.JSON(http.StatusNotFound, gin.H{"message": "No albums found for artist"})
return
}
c.JSON(http.StatusOK, albums)
}
func queryAlbumsByArtist(artist string) ([]Album, error) {
rows, err := db.Query("SELECT * FROM album WHERE artist = $1", artist)
if err != nil {
return nil, err
}
defer rows.Close()
var albums []Album
for rows.Next() {
var album Album
if err := rows.Scan(&album.ID, &album.Title, &album.Artist, &album.Price); err != nil {
return nil, err
}
albums = append(albums, album)
}
if err = rows.Err(); err != nil {
return nil, err
}
return albums, nil
}
注意,在导入包时使用了 _ "github.com/lib/pq"
, 此处_
表示该包被导入,但不直接在代码中使用,其作用是执行该包的初始化函数。这种用法称为空白标识符导入,通常用于以下几种情况:
- 注册驱动程序或插件
- 执行包的初始化代码
访问curl http://localhost:8080/albums/Artist%201
,即可获取返回结果。
Gin的中间件
Gin的中间件函数可以在请求到达handler之前做一些前置处理或者在响应返回给客户端之前做后置处理。Gin提供了很多内置的中间件函数,比如常见的日志记录,CORS处理等。开发者也可以根据需要自己定制中间件函数。
日志中间件
下面以内置的日志中间件函数为例来说明如何做日志前处理:
go
package main
import (
"log"
"time"
"github.com/gin-gonic/gin"
)
func LoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
duration := time.Since(start)
log.Printf("Request - Method: %s | Status : %d | Duration: %v", c.Request.Method, c.Writer.Status(), duration)
}
}
func main() {
router := gin.Default()
router.Use(LoggerMiddleware())
router.GET("/", func(c *gin.Context) {
c.String(200, "Hello, World!")
})
router.Run(":8080")
}
访问 http://localhost:8080/, 会看到输出日志中打印日志:
Request - Method: GET | Status : 200 | Duration: 24.291µs
自定义中间件
假设我们需要在请求被处理之前对其进行鉴权,可以自定义中间件:
go
package main
import (
"github.com/gin-gonic/gin"
)
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
apiKey := c.GetHeader("X-API-Key")
if apiKey == "" {
c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"})
return
}
c.Next()
}
}
func main() {
router := gin.Default()
authGroup := router.Group("/api")
authGroup.Use(AuthMiddleware())
{
authGroup.GET("/data", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "Authenticated and authorized!"})
})
}
router.Run(":8080")
}
当访问http://localhost:8080/api/data
时,返回{"error":"Unauthorized"}
错误。
路由分组
上文中设置鉴权中间件时对路由进行了分组。Gin允许对路由进行分组,以便更好地组织和维护代码。
下面继续展示路由分组功能:
go
package main
import (
"github.com/gin-gonic/gin"
)
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
apiKey := c.GetHeader("X-API-Key")
if apiKey == "" {
c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"})
return
}
c.Next()
}
}
func main() {
router := gin.Default()
public := router.Group("/public")
{
public.GET("/info", func(c *gin.Context) {
c.String(200, "Public information")
})
public.GET("/products", func(c *gin.Context) {
c.String(200, "Public product list")
})
}
private := router.Group("/private")
private.Use(AuthMiddleware())
{
private.GET("/data", func(c *gin.Context) {
c.String(200, "Private data accessible after authentication")
})
private.POST("/create", func(c *gin.Context) {
c.String(200, "Create a new resource")
})
}
router.Run(":8080")
}
控制器与Handlers
当后端接口不断增加时,如果将所有的业务逻辑全部放在路由的handlers里面是不明智的。
最好是增加控制器来处理业务逻辑,如下所示:
go
package main
import (
"github.com/gin-gonic/gin"
)
type UserController struct{}
func (uc *UserController) GetUserInfo(c *gin.Context) {
userID := c.Param("id")
c.JSON(200, gin.H{"id": userID, "name": "John Doe", "email": "john@example.com"})
}
func main() {
router := gin.Default()
userController := &UserController{}
router.GET("/users/:id", userController.GetUserInfo)
router.Run(":8080")
}