6.5. Gin Web 框架
6.5.1. Gin 概述
Gin 是 Go 语言中最流行的 Web 框架,以高性能和简洁 API 著称。
6.5.2. 安装
go get github.com/gin-gonic/gin
6.5.3. 基本用法
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default() // 包含 Logger 和 Recovery 中间件
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "pong"})
})
r.Run(":8080")
}
6.5.4. 路由
6.5.4.1. HTTP 方法
r.GET("/users", getUsers)
r.POST("/users", createUser)
r.PUT("/users/:id", updateUser)
r.DELETE("/users/:id", deleteUser)
r.PATCH("/users/:id", patchUser)
r.HEAD("/users", headUsers)
r.OPTIONS("/users", optionsUsers)
// 匹配所有方法
r.Any("/test", handleAny)
6.5.4.2. 路由参数
// 路径参数
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(200, gin.H{"id": id})
})
// 通配符
r.GET("/files/*filepath", func(c *gin.Context) {
path := c.Param("filepath")
c.JSON(200, gin.H{"path": path})
})
// 查询参数
r.GET("/search", func(c *gin.Context) {
query := c.Query("q") // ?q=xxx
page := c.DefaultQuery("page", "1") // 默认值
c.JSON(200, gin.H{"query": query, "page": page})
})
6.5.4.3. 路由分组
v1 := r.Group("/api/v1")
{
v1.GET("/users", getUsers)
v1.POST("/users", createUser)
}
v2 := r.Group("/api/v2")
{
v2.GET("/users", getUsersV2)
}
// 带中间件的分组
authorized := r.Group("/admin")
authorized.Use(AuthMiddleware())
{
authorized.GET("/dashboard", dashboard)
}
6.5.5. 请求处理
6.5.5.1. 绑定请求数据
type CreateUserRequest struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"gte=0,lte=130"`
}
func createUser(c *gin.Context) {
var req CreateUserRequest
// 绑定 JSON
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 处理请求...
c.JSON(201, gin.H{"user": req})
}
// 其他绑定方法
c.ShouldBindQuery(&req) // 查询参数
c.ShouldBindUri(&req) // 路径参数
c.ShouldBind(&req) // 自动推断
c.ShouldBindHeader(&req) // Header
6.5.5.2. 获取请求数据
// Header
auth := c.GetHeader("Authorization")
// Cookie
cookie, _ := c.Cookie("session")
// 表单数据
name := c.PostForm("name")
name := c.DefaultPostForm("name", "default")
// 文件上传
file, _ := c.FormFile("file")
c.SaveUploadedFile(file, "/tmp/"+file.Filename)
// 原始 Body
body, _ := c.GetRawData()
6.5.6. 响应
// JSON
c.JSON(200, gin.H{"status": "ok"})
c.JSON(200, user) // 结构体
// XML
c.XML(200, gin.H{"status": "ok"})
// YAML
c.YAML(200, gin.H{"status": "ok"})
// String
c.String(200, "Hello %s", name)
// HTML
c.HTML(200, "index.html", gin.H{"title": "Home"})
// 重定向
c.Redirect(301, "/new-path")
// 文件
c.File("/path/to/file")
c.FileAttachment("/path/to/file", "filename.txt")
// 流式响应
c.Stream(func(w io.Writer) bool {
w.Write([]byte("data"))
return true // 继续发送
})
6.5.7. 中间件
6.5.7.1. 内置中间件
// Logger - 日志
r.Use(gin.Logger())
// Recovery - panic 恢复
r.Use(gin.Recovery())
// 静态文件
r.Static("/static", "./public")
r.StaticFile("/favicon.ico", "./public/favicon.ico")
6.5.7.2. 自定义中间件
func LoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
c.Next() // 处理请求
latency := time.Since(start)
status := c.Writer.Status()
log.Printf("%s %s %d %v", c.Request.Method, path, status, latency)
}
}
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"})
return
}
// 验证 token...
user := validateToken(token)
c.Set("user", user) // 设置上下文
c.Next()
}
}
// 使用
r.Use(LoggerMiddleware())
r.GET("/protected", AuthMiddleware(), protectedHandler)
6.5.7.3. 从上下文获取数据
func protectedHandler(c *gin.Context) {
user, exists := c.Get("user")
if !exists {
c.JSON(500, gin.H{"error": "user not found"})
return
}
c.JSON(200, gin.H{"user": user})
}
6.5.8. ⚠️ 常见陷阱
6.5.8.1. 陷阱 1:goroutine 中使用 Context
// ❌ 在 goroutine 中直接使用 c
func handler(c *gin.Context) {
go func() {
time.Sleep(time.Second)
c.JSON(200, gin.H{}) // 危险!c 可能已失效
}()
}
// ✅ 复制需要的数据
func handler(c *gin.Context) {
cCopy := c.Copy() // 或者只复制需要的值
go func() {
time.Sleep(time.Second)
processAsync(cCopy)
}()
c.JSON(200, gin.H{"status": "processing"})
}
6.5.8.2. 陷阱 2:多次写入响应
// ❌ 多次写入
func handler(c *gin.Context) {
c.JSON(200, gin.H{"first": true})
c.JSON(200, gin.H{"second": true}) // 无效!
}
// ✅ 只写入一次
func handler(c *gin.Context) {
c.JSON(200, gin.H{"result": "done"})
return
}
6.5.8.3. 陷阱 3:Next 之后的代码
// 中间件中 Next 之后的代码在响应后执行
func middleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 请求前
c.Next()
// 响应后(可以用于记录日志、清理等)
}
}
6.5.9. 错误处理
// 全局错误处理
func ErrorHandler() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
if len(c.Errors) > 0 {
c.JSON(-1, gin.H{
"errors": c.Errors.Errors(),
})
}
}
}
// 在 handler 中记录错误
func handler(c *gin.Context) {
if err := doSomething(); err != nil {
c.Error(err)
c.AbortWithStatusJSON(500, gin.H{"error": err.Error()})
return
}
}
6.5.10. 优雅关闭
func main() {
r := gin.Default()
server := &http.Server{
Addr: ":8080",
Handler: r,
}
go func() {
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatal(err)
}
}
6.5.11. 参考资源
Go 微服务访问控制之 Casbin 实践指南 — Gin + Casbin + JWT 实现 RBAC