Gin 框架实战指南:从入门到生产级 API
Gin 是目前 GitHub Star 最多的 Go Web 框架(7万+),以其极高的性能和简洁的 API 著称。基准测试中,Gin 的路由性能比标准库
net/http快约 40 倍。
一、安装与快速开始
bash
go get -u github.com/gin-gonic/gingo
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default() // 包含 Logger + Recovery 中间件
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
"code": 200,
})
})
r.Run(":8080") // 启动服务
}二、路由
2.1 HTTP 方法
go
r.GET("/users", listUsers)
r.POST("/users", createUser)
r.PUT("/users/:id", updateUser)
r.DELETE("/users/:id", deleteUser)
r.PATCH("/users/:id", patchUser)
// 任意方法
r.Any("/any", anyHandler)2.2 路径参数与查询参数
go
// 路径参数
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})
})
// 查询参数
// GET /search?q=golang&page=2&size=10
r.GET("/search", func(c *gin.Context) {
q := c.Query("q")
page := c.DefaultQuery("page", "1")
size := c.DefaultQuery("size", "20")
c.JSON(200, gin.H{"q": q, "page": page, "size": size})
})2.3 路由分组
go
// API 版本分组
v1 := r.Group("/api/v1")
{
users := v1.Group("/users")
{
users.GET("", listUsers)
users.POST("", createUser)
users.GET("/:id", getUser)
users.PUT("/:id", updateUser)
users.DELETE("/:id", deleteUser)
}
articles := v1.Group("/articles")
{
articles.GET("", listArticles)
articles.POST("", createArticle)
}
}
v2 := r.Group("/api/v2")
{
// v2 接口...
}三、请求绑定与验证
Gin 集成了 go-playground/validator,支持丰富的验证规则。
go
// 定义请求结构体
type CreateUserRequest struct {
Name string `json:"name" binding:"required,min=2,max=50"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=8"`
Age int `json:"age" binding:"gte=1,lte=150"`
Role string `json:"role" binding:"oneof=admin user guest"`
}
// 处理器
func createUser(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
// 业务逻辑...
c.JSON(http.StatusCreated, gin.H{
"message": "创建成功",
"data": req,
})
}
// 表单绑定
type LoginForm struct {
Username string `form:"username" binding:"required"`
Password string `form:"password" binding:"required"`
}
func login(c *gin.Context) {
var form LoginForm
if err := c.ShouldBind(&form); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
}
// 查询参数绑定
type PageQuery struct {
Page int `form:"page" binding:"gte=1"`
Size int `form:"size" binding:"gte=1,lte=100"`
}
func listItems(c *gin.Context) {
var q PageQuery
q.Page = 1
q.Size = 20
if err := c.ShouldBindQuery(&q); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
}自定义验证器
go
import "github.com/go-playground/validator/v10"
// 注册自定义验证器
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("phone", validatePhone)
}
func validatePhone(fl validator.FieldLevel) bool {
phone := fl.Field().String()
matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone)
return matched
}
// 使用自定义验证器
type PhoneRequest struct {
Phone string `json:"phone" binding:"required,phone"`
}四、中间件
4.1 内置中间件
go
r := gin.New() // 不带任何中间件
r := gin.Default() // 带 Logger + Recovery
// Logger:记录请求日志
r.Use(gin.Logger())
// Recovery:从 panic 恢复
r.Use(gin.Recovery())4.2 自定义中间件
go
// 请求 ID 中间件
func RequestID() gin.HandlerFunc {
return func(c *gin.Context) {
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
}
c.Set("requestID", requestID)
c.Header("X-Request-ID", requestID)
c.Next()
}
}
// 耗时统计中间件
func Timing() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
duration := time.Since(start)
log.Printf("[%s] %s %v", c.Request.Method, c.Request.URL.Path, duration)
}
}
// 跨域中间件
func CORS() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,PATCH,OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type,Authorization,X-Request-ID")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
// 限流中间件(令牌桶)
func RateLimit(qps float64) gin.HandlerFunc {
limiter := rate.NewLimiter(rate.Limit(qps), int(qps))
return func(c *gin.Context) {
if !limiter.Allow() {
c.AbortWithStatusJSON(429, gin.H{
"error": "请求过于频繁,请稍后再试",
})
return
}
c.Next()
}
}
// 注册全局中间件
r.Use(RequestID())
r.Use(CORS())
r.Use(Timing())
// 仅对某个路由组生效
api := r.Group("/api")
api.Use(RateLimit(100))4.3 JWT 认证中间件
go
import "github.com/golang-jwt/jwt/v5"
type Claims struct {
UserID uint `json:"userId"`
Role string `json:"role"`
jwt.RegisteredClaims
}
var jwtSecret = []byte("your-secret-key")
// 生成 Token
func GenerateToken(userID uint, role string) (string, error) {
claims := Claims{
UserID: userID,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "friday-go.icu",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtSecret)
}
// JWT 中间件
func JWTAuth() gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
c.AbortWithStatusJSON(401, gin.H{"error": "缺少认证 Token"})
return
}
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(t *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if err != nil || !token.Valid {
c.AbortWithStatusJSON(401, gin.H{"error": "Token 无效或已过期"})
return
}
claims := token.Claims.(*Claims)
c.Set("userID", claims.UserID)
c.Set("role", claims.Role)
c.Next()
}
}
// 使用
authGroup := r.Group("/api/v1")
authGroup.Use(JWTAuth())
{
authGroup.GET("/profile", getProfile)
authGroup.PUT("/profile", updateProfile)
}五、统一响应格式
go
// 定义统一响应结构
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
func Success(c *gin.Context, data interface{}) {
c.JSON(http.StatusOK, Response{
Code: 0,
Message: "success",
Data: data,
})
}
func Error(c *gin.Context, httpCode int, code int, msg string) {
c.JSON(httpCode, Response{
Code: code,
Message: msg,
})
}
// 使用
func getUser(c *gin.Context) {
id := c.Param("id")
user, err := userService.FindByID(id)
if err != nil {
if errors.Is(err, ErrNotFound) {
Error(c, 404, 10004, "用户不存在")
return
}
Error(c, 500, 50000, "服务器内部错误")
return
}
Success(c, user)
}六、文件上传
go
// 单文件上传
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(400, gin.H{"error": "文件获取失败"})
return
}
// 校验文件大小(10MB 限制)
if file.Size > 10*1024*1024 {
c.JSON(400, gin.H{"error": "文件不能超过 10MB"})
return
}
// 校验文件类型
ext := filepath.Ext(file.Filename)
allowed := map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".gif": true}
if !allowed[strings.ToLower(ext)] {
c.JSON(400, gin.H{"error": "不支持的文件类型"})
return
}
// 生成唯一文件名
filename := fmt.Sprintf("%d%s", time.Now().UnixNano(), ext)
dst := filepath.Join("uploads", filename)
if err := c.SaveUploadedFile(file, dst); err != nil {
c.JSON(500, gin.H{"error": "文件保存失败"})
return
}
c.JSON(200, gin.H{
"url": "/uploads/" + filename,
"filename": filename,
})
})
// 多文件上传
r.POST("/uploads", func(c *gin.Context) {
form, _ := c.MultipartForm()
files := form.File["files[]"]
var urls []string
for _, file := range files {
filename := filepath.Base(file.Filename)
dst := filepath.Join("uploads", filename)
c.SaveUploadedFile(file, dst)
urls = append(urls, "/uploads/"+filename)
}
c.JSON(200, gin.H{"urls": urls})
})七、优雅关机
go
func main() {
r := gin.Default()
// ... 注册路由
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("启动失败: %v\n", err)
}
}()
log.Println("服务器启动,监听 :8080")
// 等待中断信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("正在关闭服务器...")
// 给正在处理的请求 5 秒完成时间
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("服务器强制关闭:", err)
}
log.Println("服务器已关闭")
}八、项目结构推荐
project/
├── main.go
├── config/
│ └── config.go # 配置管理
├── router/
│ └── router.go # 路由注册
├── middleware/
│ ├── auth.go # JWT 中间件
│ ├── cors.go # 跨域
│ └── ratelimit.go # 限流
├── handler/ # HTTP 处理器(薄层)
│ ├── user.go
│ └── article.go
├── service/ # 业务逻辑
│ ├── user.go
│ └── article.go
├── repository/ # 数据库操作
│ ├── user.go
│ └── article.go
├── model/ # 数据模型
│ ├── user.go
│ └── article.go
└── pkg/ # 公共工具
├── response/
└── logger/总结
Gin 的核心优势:
| 特性 | 说明 |
|---|---|
| 性能 | 基于 httprouter,路由极快 |
| 中间件 | 链式调用,扩展方便 |
| 参数绑定 | 自动反序列化 + 验证 |
| 错误处理 | Recovery 防止 panic 崩溃 |
| 生态 | 丰富的第三方扩展 |
作者:PFinal南丞 | 更新时间:2026-04-21

