目录:
(1)Go开发指南-Hello World
(2)Go开发指南-Gin与Web开发
Gin 是一个用 Go 语言编写的轻量级、高性能的 Web 框架,主要用于构建 API 服务和微服务。由于其简洁的 API 设计和强大的路由功能,Gin 在 Go 社区中广受欢迎。
运行Web程序
创建一个目录web-service-gin
,初始化模块
go mod init example/web-service-gin
创建main.go
:
package mainimport ("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接口:
// add post handler
func postAlbums(c *gin.Context) {var newAlbum albumif 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返回
新增一个接口:
// 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:
package mainimport ("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.DBfunc main() {dsn := "user=postgres password=admin dbname=music sslmode=disable"var err errordb, 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 []Albumfor rows.Next() {var album Albumif 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处理等。开发者也可以根据需要自己定制中间件函数。
日志中间件
下面以内置的日志中间件函数为例来说明如何做日志前处理:
package mainimport ("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
自定义中间件
假设我们需要在请求被处理之前对其进行鉴权,可以自定义中间件:
package mainimport ("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允许对路由进行分组,以便更好地组织和维护代码。
下面继续展示路由分组功能:
package mainimport ("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里面是不明智的。
最好是增加控制器来处理业务逻辑,如下所示:
package mainimport ("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")
}
参考资料
[1]. https://go.dev/doc/tutorial/web-service-gin