Gin Web 框架 - HTTP 开发实战
Gin 是 Go 最流行的 Web 框架之一,以高性能、简洁 API 和丰富的中间件支持著称。掌握 Gin 是开发 Go Web 应用和 API 服务的基础。
📌 核心概念
🚀
高性能
基于 httprouter
50x 性能提升
🛣️
路由
URL 参数匹配
:param
🔌
中间件
请求处理链
Use()
📝
数据绑定
自动解析请求
ShouldBind()
快速开始
📝 Hello Gin
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
// 创建路由引擎
r := gin.Default()
// GET 请求
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello World",
})
})
// POST 请求
r.POST("/hello", func(c *gin.Context) {
name := c.PostForm("name")
c.JSON(http.StatusOK, gin.H{
"message": "Hello " + name,
})
})
// 启动服务
r.Run(":8080") // 监听 :8080
}
💡 Gin 要点
- gin.Default(): 包含 Logger 和 Recovery 中间件
- gin.New(): 纯净引擎,需手动添加中间件
- gin.Context: 请求上下文,核心对象
- gin.H: map[string]interface{} 快捷方式
路由处理
📝 路由参数和查询
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// URL 参数 /users/:id
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{"id": id})
})
// 可选参数 /users/:id/posts/:postID
r.GET("/users/:id/posts/:postID", func(c *gin.Context) {
userID := c.Param("id")
postID := c.Param("postID")
c.JSON(http.StatusOK, gin.H{
"userID": userID,
"postID": postID,
})
})
// 查询参数 /search?keyword=go&page=1
r.GET("/search", func(c *gin.Context) {
keyword := c.Query("keyword")
page := c.DefaultQuery("page", "1")
c.JSON(http.StatusOK, gin.H{
"keyword": keyword,
"page": page,
})
})
// 表单数据
r.POST("/form", func(c *gin.Context) {
name := c.PostForm("name")
email := c.DefaultPostForm("email", "")
c.JSON(http.StatusOK, gin.H{
"name": name,
"email": email,
})
})
r.Run(":8080")
}
数据绑定与验证
📝 自动绑定和验证
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
)
// 定义请求结构
type LoginRequest struct {
Username string `json:"username" binding:"required,min=3,max=20"`
Password string `json:"password" binding:"required,min=6"`
Email string `json:"email" binding:"email"`
}
type SignupRequest struct {
Username string `form:"username" binding:"required"`
Password string `form:"password" binding:"required"`
}
func main() {
r := gin.Default()
// JSON 绑定
r.POST("/login", func(c *gin.Context) {
var req LoginRequest
// ShouldBindJSON 自动解析并验证
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Login success",
"user": req.Username,
})
})
// Form 绑定
r.POST("/signup", func(c *gin.Context) {
var req SignupRequest
if err := c.ShouldBind(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Signup success",
})
})
// 自定义验证错误
if v, ok := r.Validator().Engine().(*validator.Validate); ok {
v.RegisterValidation("password", func(fl validator.FieldLevel) bool {
return len(fl.Field().String()) >= 8
})
}
r.Run(":8080")
}
中间件
📝 自定义中间件
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
// 日志中间件
func Logger() 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("[%d] %s %v\n", status, path, latency)
}
}
// 认证中间件
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Missing token",
})
c.Abort()
return
}
// 验证 token...
if token != "valid-token" {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Invalid token",
})
c.Abort()
return
}
// 设置用户信息
c.Set("userID", "123")
c.Next()
}
}
// CORS 中间件
func CORS() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer().Header().Set("Access-Control-Allow-Origin", "*")
c.Writer().Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
c.Writer().Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}
func main() {
r := gin.New()
// 全局中间件
r.Use(Logger(), CORS())
// 路由组
public := r.Group("/api")
{
public.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
}
// 需要认证的路由
auth := r.Group("/api")
auth.Use(Auth())
{
auth.GET("/profile", func(c *gin.Context) {
userID := c.GetString("userID")
c.JSON(http.StatusOK, gin.H{"userID": userID})
})
}
r.Run(":8080")
}
文件上传
📝 处理文件上传
func main() {
r := gin.Default()
// 单文件上传
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 保存到指定路径
dst := "./uploads/" + file.Filename
if err := c.SaveUploadedFile(file, dst); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "File uploaded successfully",
"filename": file.Filename,
})
})
// 多文件上传
r.POST("/upload/multi", func(c *gin.Context) {
form, _ := c.MultipartForm()
files := form.File["files"]
for _, file := range files {
dst := "./uploads/" + file.Filename
c.SaveUploadedFile(file, dst)
}
c.JSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("%d files uploaded", len(files)),
})
})
r.Run(":8080")
}
错误处理
📝 自定义错误处理
func main() {
r := gin.Default()
// 自定义 404
r.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{
"error": "Route not found",
"code": "NOT_FOUND",
})
})
// 自定义 405
r.NoMethod(func(c *gin.Context) {
c.JSON(http.StatusMethodNotAllowed, gin.H{
"error": "Method not allowed",
})
})
// 优雅的错误处理
r.GET("/error", func(c *gin.Context) {
err := doSomething()
if err != nil {
// 记录错误
c.Error(err)
// 返回错误响应
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal server error",
})
return
}
c.JSON(http.StatusOK, gin.H{"status": "success"})
})
r.Run(":8080")
}
func doSomething() error {
return fmt.Errorf("something went wrong")
}
最佳实践
✅ Gin 使用建议
- 路由分组: 使用 Group 组织相关路由
- 中间件复用: 提取通用逻辑为中间件
- 数据验证: 使用 binding tag 自动验证
- 错误处理: 统一错误响应格式
- 配置分离: 使用 Viper 管理配置
- 结构化日志: 使用 zap 等日志库