Gin Web 框架
Gin 是一个用 Go 语言编写的高性能 HTTP Web 框架。它基于 HttpRouter 实现,提供了类似 Martini 的 API,但性能比 Martini 快 40 倍。Gin 是目前 Go 社区最受欢迎的 Web 框架之一。
安装
go get -u github.com/gin-gonic/gin
快速开始
第一个 Gin 应用
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run(":8080")
}
运行后访问 http://localhost:8080/ping,将返回 {"message":"pong"}。
代码解释
gin.Default():创建一个默认的路由引擎,包含 Logger 和 Recovery 中间件r.GET():注册 GET 路由c *gin.Context:请求上下文,包含请求和响应信息c.JSON():返回 JSON 格式响应gin.H:map[string]interface{}的简写r.Run():启动 HTTP 服务器,默认端口 8080
路由
基本路由
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
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.Run(":8080")
}
func getUsers(c *gin.Context) {
c.JSON(200, gin.H{"method": "GET"})
}
func createUser(c *gin.Context) {
c.JSON(201, gin.H{"method": "POST"})
}
func updateUser(c *gin.Context) {
c.JSON(200, gin.H{"method": "PUT"})
}
func deleteUser(c *gin.Context) {
c.JSON(200, gin.H{"method": "DELETE"})
}
func patchUser(c *gin.Context) {
c.JSON(200, gin.H{"method": "PATCH"})
}
func headUsers(c *gin.Context) {
c.Status(200)
}
func optionsUsers(c *gin.Context) {
c.JSON(200, gin.H{"method": "OPTIONS"})
}
路径参数
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(200, "Hello %s", name)
})
r.GET("/user/:name/:action", func(c *gin.Context) {
name := c.Param("name")
action := c.Param("action")
c.String(200, "%s is %s", name, action)
})
r.GET("/files/*filepath", func(c *gin.Context) {
filepath := c.Param("filepath")
c.String(200, "File path: %s", filepath)
})
r.Run(":8080")
}
参数说明:
:name:匹配单个路径段,如/user/john*filepath:匹配剩余所有路径,如/files/a/b/c.txt
查询参数
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/search", func(c *gin.Context) {
keyword := c.Query("keyword")
page := c.DefaultQuery("page", "1")
c.JSON(200, gin.H{
"keyword": keyword,
"page": page,
})
})
r.GET("/filter", func(c *gin.Context) {
tags := c.QueryArray("tag")
c.JSON(200, gin.H{
"tags": tags,
})
})
r.GET("/map", func(c *gin.Context) {
params := c.QueryMap("filter")
c.JSON(200, gin.H{
"filter": params,
})
})
r.Run(":8080")
}
访问示例:
/search?keyword=go&page=2/filter?tag=go&tag=web&tag=api/map?filter[name]=张三&filter[age]=25
表单参数
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.POST("/form", func(c *gin.Context) {
name := c.PostForm("name")
email := c.DefaultPostForm("email", "[email protected]")
c.JSON(200, gin.H{
"name": name,
"email": email,
})
})
r.POST("/upload", func(c *gin.Context) {
tags := c.PostFormArray("tag")
c.JSON(200, gin.H{
"tags": tags,
})
})
r.Run(":8080")
}
路由分组
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
api := r.Group("/api")
{
api.GET("/status", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok"})
})
users := api.Group("/users")
{
users.GET("", listUsers)
users.POST("", createUser)
users.GET("/:id", getUser)
users.PUT("/:id", updateUser)
users.DELETE("/:id", deleteUser)
}
posts := api.Group("/posts")
{
posts.GET("", listPosts)
posts.POST("", createPost)
posts.GET("/:id", getPost)
}
}
admin := r.Group("/admin")
admin.Use(AuthMiddleware())
{
admin.GET("/dashboard", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "admin dashboard"})
})
}
r.Run(":8080")
}
func listUsers(c *gin.Context) {
c.JSON(200, []gin.H{{"id": 1, "name": "张三"}})
}
func createUser(c *gin.Context) {
c.JSON(201, gin.H{"id": 2, "name": "李四"})
}
func getUser(c *gin.Context) {
c.JSON(200, gin.H{"id": c.Param("id"), "name": "张三"})
}
func updateUser(c *gin.Context) {
c.JSON(200, gin.H{"id": c.Param("id"), "name": "张三更新"})
}
func deleteUser(c *gin.Context) {
c.Status(204)
}
func listPosts(c *gin.Context) {
c.JSON(200, []gin.H{{"id": 1, "title": "文章1"}})
}
func createPost(c *gin.Context) {
c.JSON(201, gin.H{"id": 2, "title": "文章2"})
}
func getPost(c *gin.Context) {
c.JSON(200, gin.H{"id": c.Param("id"), "title": "文章"})
}
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(401, gin.H{"error": "未授权"})
c.Abort()
return
}
c.Next()
}
}
请求处理
获取请求信息
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/info", func(c *gin.Context) {
c.JSON(200, gin.H{
"method": c.Request.Method,
"path": c.Request.URL.Path,
"host": c.Request.Host,
"remoteAddr": c.ClientIP(),
"headers": c.Request.Header,
"userAgent": c.GetHeader("User-Agent"),
"contentType": c.ContentType(),
})
})
r.Run(":8080")
}
绑定请求参数
Gin 支持多种绑定方式,可以自动将请求参数绑定到结构体:
package main
import (
"github.com/gin-gonic/gin"
)
type User struct {
Name string `form:"name" json:"name" binding:"required"`
Email string `form:"email" json:"email" binding:"required,email"`
Age int `form:"age" json:"age" binding:"gte=0,lte=130"`
}
func main() {
r := gin.Default()
r.GET("/query", func(c *gin.Context) {
var user User
if err := c.ShouldBindQuery(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, user)
})
r.POST("/form", func(c *gin.Context) {
var user User
if err := c.ShouldBind(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, user)
})
r.POST("/json", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, user)
})
r.POST("/bind", func(c *gin.Context) {
var user User
if err := c.ShouldBind(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, user)
})
r.Run(":8080")
}
绑定 URI 参数
package main
import (
"github.com/gin-gonic/gin"
)
type UserURI struct {
ID string `uri:"id" binding:"required"`
Name string `uri:"name" binding:"required"`
}
func main() {
r := gin.Default()
r.GET("/user/:id/:name", func(c *gin.Context) {
var user UserURI
if err := c.ShouldBindUri(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{
"id": user.ID,
"name": user.Name,
})
})
r.Run(":8080")
}
绑定 Header
package main
import (
"github.com/gin-gonic/gin"
)
type HeaderParams struct {
Token string `header:"Authorization" binding:"required"`
Rate int `header:"Rate"`
}
func main() {
r := gin.Default()
r.GET("/header", func(c *gin.Context) {
var h HeaderParams
if err := c.ShouldBindHeader(&h); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{
"token": h.Token,
"rate": h.Rate,
})
})
r.Run(":8080")
}
响应处理
JSON 响应
package main
import (
"github.com/gin-gonic/gin"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
r := gin.Default()
r.GET("/json", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "hello"})
})
r.GET("/json/struct", func(c *gin.Context) {
c.JSON(200, User{ID: 1, Name: "张三"})
})
r.GET("/json/array", func(c *gin.Context) {
users := []User{
{ID: 1, Name: "张三"},
{ID: 2, Name: "李四"},
}
c.JSON(200, users)
})
r.GET("/json/indent", func(c *gin.Context) {
c.IndentedJSON(200, User{ID: 1, Name: "张三"})
})
r.GET("/json/secure", func(c *gin.Context) {
c.SecureJSON(200, []string{"张三", "李四"})
})
r.Run(":8080")
}
其他响应类型
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/string", func(c *gin.Context) {
c.String(200, "Hello, %s", "World")
})
r.GET("/xml", func(c *gin.Context) {
c.XML(200, gin.H{"message": "hello"})
})
r.GET("/yaml", func(c *gin.Context) {
c.YAML(200, gin.H{"message": "hello"})
})
r.GET("/html", func(c *gin.Context) {
c.HTML(200, "index.html", gin.H{
"title": "主页",
})
})
r.GET("/file", func(c *gin.Context) {
c.File("./static/file.txt")
})
r.GET("/attachment", func(c *gin.Context) {
c.FileAttachment("./static/file.txt", "download.txt")
})
r.GET("/data", func(c *gin.Context) {
c.Data(200, "text/plain; charset=utf-8", []byte("Hello"))
})
r.GET("/redirect", func(c *gin.Context) {
c.Redirect(302, "/string")
})
r.Run(":8080")
}
中间件
内置中间件
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "pong"})
})
r.Run(":8080")
}
自定义中间件
package main
import (
"log"
"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("[%s] %s %d %v", c.Request.Method, path, status, latency)
}
}
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(401, gin.H{"error": "未授权"})
c.Abort()
return
}
c.Set("userID", "123")
c.Next()
}
}
func CORSMiddleware() 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, OPTIONS")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
func main() {
r := gin.New()
r.Use(Logger())
r.Use(gin.Recovery())
r.Use(CORSMiddleware())
public := r.Group("/api")
{
public.POST("/login", login)
}
protected := r.Group("/api")
protected.Use(Auth())
{
protected.GET("/profile", profile)
}
r.Run(":8080")
}
func login(c *gin.Context) {
c.JSON(200, gin.H{"token": "fake-token"})
}
func profile(c *gin.Context) {
userID := c.GetString("userID")
c.JSON(200, gin.H{"userID": userID})
}
中间件控制
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/normal", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "normal"})
})
r.GET("/abort", func(c *gin.Context) {
c.JSON(401, gin.H{"error": "unauthorized"})
c.Abort()
})
r.GET("/next", func(c *gin.Context) {
c.Next()
c.JSON(200, gin.H{"message": "after next"})
})
r.Run(":8080")
}
文件上传
单文件上传
package main
import (
"fmt"
"path/filepath"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.MaxMultipartMemory = 8 << 20
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
filename := filepath.Base(file.Filename)
dst := filepath.Join("./uploads", filename)
if err := c.SaveUploadedFile(file, dst); err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{
"filename": filename,
"size": file.Size,
"header": file.Header,
})
})
r.Run(":8080")
}
多文件上传
package main
import (
"net/http"
"path/filepath"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.POST("/uploads", func(c *gin.Context) {
form, _ := c.MultipartForm()
files := form.File["files"]
var results []gin.H
for _, file := range files {
filename := filepath.Base(file.Filename)
dst := filepath.Join("./uploads", filename)
if err := c.SaveUploadedFile(file, dst); err != nil {
results = append(results, gin.H{
"filename": filename,
"error": err.Error(),
})
continue
}
results = append(results, gin.H{
"filename": filename,
"size": file.Size,
})
}
c.JSON(http.StatusOK, gin.H{"files": results})
})
r.Run(":8080")
}
模板渲染
HTML 模板
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.LoadHTMLGlob("templates/**/*")
r.GET("/index", func(c *gin.Context) {
c.HTML(200, "index.html", gin.H{
"title": "主页",
"message": "欢迎来到 Gin",
})
})
r.GET("/posts/index", func(c *gin.Context) {
c.HTML(200, "posts/index.html", gin.H{
"title": "文章列表",
"posts": []gin.H{
{"id": 1, "title": "文章1"},
{"id": 2, "title": "文章2"},
},
})
})
r.Run(":8080")
}
静态文件
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.Static("/assets", "./assets")
r.StaticFile("/favicon.ico", "./favicon.ico")
r.StaticFS("/static", http.Dir("./static"))
r.Run(":8080")
}
数据验证
Gin 使用 validator 包进行数据验证:
package main
import (
"github.com/gin-gonic/gin"
)
type RegisterRequest struct {
Username string `json:"username" binding:"required,min=3,max=20"`
Password string `json:"password" binding:"required,min=6,max=20"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"gte=1,lte=120"`
Phone string `json:"phone" binding:"omitempty,len=11"`
Website string `json:"website" binding:"omitempty,url"`
}
func main() {
r := gin.Default()
r.POST("/register", func(c *gin.Context) {
var req RegisterRequest
if err := c.ShouldBind(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{
"username": req.Username,
"email": req.Email,
"age": req.Age,
})
})
r.Run(":8080")
}
常用验证标签:
| 标签 | 说明 |
|---|---|
required | 必填 |
min=3 | 最小长度/值 |
max=20 | 最大长度/值 |
gte=1 | 大于等于 |
lte=120 | 小于等于 |
email | 邮箱格式 |
url | URL 格式 |
len=11 | 精确长度 |
oneof=男 女 | 枚举值 |
dive | 深入切片/数组验证 |
错误处理
统一错误处理
package main
import (
"github.com/gin-gonic/gin"
)
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(200, Response{
Code: 0,
Message: "success",
Data: data,
})
}
func Error(c *gin.Context, code int, message string) {
c.JSON(code, Response{
Code: code,
Message: message,
})
}
func main() {
r := gin.Default()
r.GET("/success", func(c *gin.Context) {
Success(c, gin.H{"name": "张三"})
})
r.GET("/error", func(c *gin.Context) {
Error(c, 400, "参数错误")
})
r.NoRoute(func(c *gin.Context) {
Error(c, 404, "路由不存在")
})
r.NoMethod(func(c *gin.Context) {
Error(c, 405, "方法不允许")
})
r.Run(":8080")
}
Recovery 中间件
package main
import (
"github.com/gin-gonic/gin"
)
func RecoveryMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
c.JSON(500, gin.H{
"code": 500,
"message": "服务器内部错误",
"error": err,
})
c.Abort()
}
}()
c.Next()
}
}
func main() {
r := gin.New()
r.Use(RecoveryMiddleware())
r.GET("/panic", func(c *gin.Context) {
panic("故意触发的错误")
})
r.Run(":8080")
}
项目结构示例
project/
├── main.go
├── config/
│ └── config.go
├── controllers/
│ ├── user.go
│ └── post.go
├── models/
│ ├── user.go
│ └── post.go
├── services/
│ ├── user.go
│ └── post.go
├── middlewares/
│ ├── auth.go
│ └── logger.go
├── routes/
│ └── router.go
├── utils/
│ └── response.go
├── templates/
│ └── index.html
└── static/
├── css/
└── js/
路由分离
package routes
import (
"github.com/gin-gonic/gin"
"myapp/controllers"
"myapp/middlewares"
)
func SetupRouter() *gin.Engine {
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
api := r.Group("/api")
{
api.POST("/login", controllers.Login)
api.POST("/register", controllers.Register)
}
userGroup := api.Group("/users")
userGroup.Use(middlewares.Auth())
{
userGroup.GET("", controllers.ListUsers)
userGroup.GET("/:id", controllers.GetUser)
userGroup.PUT("/:id", controllers.UpdateUser)
userGroup.DELETE("/:id", controllers.DeleteUser)
}
return r
}
package main
import (
"myapp/routes"
)
func main() {
r := routes.SetupRouter()
r.Run(":8080")
}
小结
本章学习了 Gin 框架的核心内容:
- 路由:基本路由、路径参数、查询参数、路由分组
- 请求处理:获取请求信息、参数绑定
- 响应处理:JSON、XML、HTML、文件响应
- 中间件:内置中间件、自定义中间件
- 文件上传:单文件、多文件上传
- 模板渲染:HTML 模板、静态文件
- 数据验证:常用验证标签
- 错误处理:统一错误处理、Recovery
练习
- 实现一个完整的 RESTful API,包含 CRUD 操作
- 编写一个 JWT 认证中间件
- 实现文件上传和下载功能
- 使用 Gin 构建一个简单的博客系统