安装
go install github.com/gin-gonic/gin@latest
// 或 go get -u github.com/gin-gonic/gin
引入
import "github.com/gin-gonic/gin"
开始
生成实例:
// 使用默认中间件(logger 和 recovery 中间件)创建 gin 路由
r := gin.Default()
运行
r.Run()
// 默认在 8080 端口启动服务,除非定义了一个 PORT 的环境变量。
// r.Run(":8888") 传入参数切换端口
路由
r.GET("/someGet", getting)
r.POST("/somePost", posting)
r.PUT("/somePut", putting)
r.DELETE("/someDelete", deleting)
r.PATCH("/somePatch", patching)
r.HEAD("/someHead", head)
r.OPTIONS("/someOptions", options)
注:
对于每一种路由都是传入两个参数,一个是路由的路径,第二个是处理请求的函数
对于请求函数例如:
func(c *gin.Context) {
// 其他处理代码
...
// 其他处理代码
// 响应
c.String(http.StatusOK, "hello world")
}
需要传入一个参数 c 为gin的上下文类型
gin中存在多种不同的响应类型
- 返回JSON数据
func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "hello world",
"status": "success",
})
}
- 返回HTML模板
func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "Main website",
})
}
// gin.H{} 传递给模板,模板中使用{{.title}}使用,其他的更多用法比如循环,分支,之类可以查找go官方文档
// https://pkg.go.dev/text/template
// https://pkg.go.dev/html/template
需要先设置模板目录:
router := gin.Default()
router.LoadHTMLGlob("templates/*")
- 返回XML数据
func(c *gin.Context) {
c.XML(http.StatusOK, gin.H{
"message": "hello world",
"status": "success",
})
}
- 返回YAML数据
func(c *gin.Context) {
c.YAML(http.StatusOK, gin.H{
"message": "hello world",
"status": "success",
})
}
- 返回ProtoBuf数据
func(c *gin.Context) {
data := &protoexample.Test{
Label: proto.String("hello"),
Reps: []int64{1, 2, 3},
}
c.ProtoBuf(http.StatusOK, data)
}
- 返回文件
func(c *gin.Context) {
c.File("/path/to/file")
}
- 返回文件流
func(c *gin.Context) {
c.FileFromFS("/path/to/file", http.Dir("/var/www"))
}
- 返回数据流
func(c *gin.Context) {
reader := strings.NewReader("some data")
c.DataFromReader(http.StatusOK, int64(reader.Len()), "text/plain", reader, nil)
}
- 重定向
func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "<https://www.google.com/>")
}
func(c *gin.Context) {
c.Request.URL.Path = "/"
r.HandleContext(c)
}
- 返回纯文本
func(c *gin.Context) {
c.String(http.StatusOK, "hello world")
}
关于Gin框架中的gin.H
gin.H
是Gin框架中一个非常实用的快捷方式,它实际上是一个类型别名(type alias),定义如下:
type H map[string]interface{}
详细解释
本质:
gin.H
就是一个map[string]interface{}
的别名string
是键的类型interface{}
是值的类型,表示可以接受任何类型的值
用途:
- 主要用于构建JSON/XML/YAML等格式的响应数据
- 提供了一种简洁的方式来创建动态数据结构
- 示例:
// 创建一个简单的JSON响应
data := gin.H{
"name": "John",
"age": 30,
"tags": []string{"golang", "gin"},
}
c.JSON(http.StatusOK, data)
优点:
- 比直接使用
map[string]interface{}
更简洁 - 提高了代码可读性
- 是Gin框架中的惯用写法
- 比直接使用
- 与其他方式的对比:
// 使用gin.H
gin.H{"key": "value"}
// 等同于
map[string]interface{}{"key": "value"}
// 也等同于
struct {
Key string `json:"key"`
}{Key: "value"}
- 嵌套使用:
data := gin.H{
"user": gin.H{
"name": "Alice",
"age": 25,
},
"status": "active",
}
注意事项:
- 虽然方便,但对于复杂数据结构,有时使用自定义结构体可能更合适
- 在性能敏感的场景,直接使用结构体可能比
gin.H
更高效
gin.H
是Gin框架中一个简单但强大的工具,它简化了动态数据的构建过程,特别是在构建API响应时非常有用。
在Gin中使用自定义结构体的示例
当然可以。在Gin框架中,使用自定义结构体作为响应通常比gin.H
更规范,特别是在处理复杂数据结构时。以下是几个示例:
- 基本结构体示例
// 定义自定义结构体
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
}
func getUser(c *gin.Context) {
user := User{
ID: 1,
Username: "john_doe",
Email: "[email protected]",
}
c.JSON(http.StatusOK, user)
}
- 嵌套结构体示例
type Address struct {
City string `json:"city"`
Country string `json:"country"`
}
type UserProfile struct {
Name string `json:"name"`
Age int `json:"age"`
Address Address `json:"address"`
}
func getProfile(c *gin.Context) {
profile := UserProfile{
Name: "Alice",
Age: 28,
Address: Address{
City: "Shanghai",
Country: "China",
},
}
c.JSON(http.StatusOK, profile)
}
- 带切片的结构体
type Post struct {
ID int `json:"id"`
Title string `json:"title"`
Tags []string `json:"tags"`
}
func getPosts(c *gin.Context) {
posts := []Post{
{
ID: 1,
Title: "Gin框架入门",
Tags: []string{"golang", "web"},
},
{
ID: 2,
Title: "Go语言并发编程",
Tags: []string{"golang", "concurrency"},
},
}
c.JSON(http.StatusOK, posts)
}
- 作为请求体和响应体
type LoginRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
type LoginResponse struct {
Token string `json:"token"`
Exp int64 `json:"exp"`
}
func login(c *gin.Context) {
var req LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 验证逻辑...
resp := LoginResponse{
Token: "generated_token_here",
Exp: time.Now().Add(24 * time.Hour).Unix(),
}
c.JSON(http.StatusOK, resp)
}
- 带omitempty标签的示例
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
Description string `json:"description,omitempty"` // 为空时不显示
Stock int `json:"-"` // 永远不序列化
}
func getProduct(c *gin.Context) {
product := Product{
ID: 101,
Name: "Laptop",
Price: 999.99,
// Description和Stock未赋值
}
c.JSON(http.StatusOK, product)
// 输出: {"id":101,"name":"Laptop","price":999.99}
}
Gin框架中的参数传递方式
一、路由传入的参数类型
- 路径参数 (Path Parameters)
router.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id") // 获取路径参数
})
- 查询参数 (Query Parameters)
// 请求: /search?q=gin&page=1
q := c.Query("q") // "gin"
page := c.DefaultQuery("page", "1") // 带默认值
- 表单参数 (Form Data)
// Content-Type: application/x-www-form-urlencoded
name := c.PostForm("name")
age := c.DefaultPostForm("age", "18") // 带默认值
- 多部分表单 (Multipart/Form-Data)
用于文件上传:
file, _ := c.FormFile("file")
// 处理文件上传
if file, err := c.FormFile("avatar"); err == nil {
// 保存文件逻辑
user.Avatar = "/uploads/" + file.Filename
}
- Header 参数
token := c.GetHeader("Authorization")
- Cookie 参数
value, err := c.Cookie("cookie_name")
- 原始Body (Raw Body)
body, _ := ioutil.ReadAll(c.Request.Body)
二、POST请求可传递的参数类型
POST请求可以通过以下Content-Type传递不同类型的参数:
application/x-www-form-urlencoded
- 传统的表单提交格式
- 通过
c.PostForm()
获取 - 示例:
// 客户端发送: name=John&age=30
name := c.PostForm("name")
multipart/form-data
- 用于文件上传和表单混合数据
- 通过
c.FormFile()
获取文件 - 通过
c.PostForm()
获取普通字段 - 示例:
file, _ := c.FormFile("avatar")
name := c.PostForm("username")
application/json
- 最常用的API数据格式
- 通过
c.ShouldBindJSON()
绑定到结构体 - 示例:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
var user User
if err := c.ShouldBindJSON(&user); err != nil {
// 错误处理
}
//例如:
// 绑定JSON或Form数据
//var user User
//if err := c.ShouldBind(&user); err != nil {
// c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
// return
//}
text/xml
/application/xml
- XML格式数据
- 通过
c.ShouldBindXML()
绑定 - 示例:
if err := c.ShouldBindXML(&obj); err != nil {
// 错误处理
}
application/protobuf
- Protocol Buffers格式
- 高性能二进制格式
- 示例:
var message pb.Message
if err := proto.Unmarshal(c.Request.Body, &message); err != nil {
// 错误处理
}
- 其他自定义格式
可以通过c.GetRawData()
获取原始数据后自行解析:
data, _ := c.GetRawData()
// 自定义解析逻辑
三、参数绑定最佳实践
- 对于简单参数:直接使用
Query()
,PostForm()
等方法 - 对于复杂结构:使用
ShouldBind
系列方法绑定到结构体 - 文件上传:使用
FormFile()
- API开发:优先使用JSON格式
- 性能敏感场景:考虑使用protobuf
四、完整示例
type User struct {
ID int `json:"id" form:"id"`
Username string `json:"username" form:"username"`
Avatar string `json:"avatar"` // 文件上传单独处理
}
func updateUser(c *gin.Context) {
// 路径参数
id := c.Param("id")
// 绑定JSON或Form数据
var user User
if err := c.ShouldBind(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 处理文件上传
if file, err := c.FormFile("avatar"); err == nil {
// 保存文件逻辑
user.Avatar = "/uploads/" + file.Filename
}
// 处理其他逻辑...
}
分组路由(Grouping Routes)
如果有一组路由,前缀都是/api/v1
开头,是否每个路由都需要加上/api/v1
这个前缀呢?答案是不需要,分组路由可以解决这个问题。利用分组路由还可以更好地实现权限控制,例如将需要登录鉴权的路由放到同一分组中去,简化权限控制。
// group routes 分组路由
defaultHandler := func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"path": c.FullPath(),
})
}
// group: v1
v1 := r.Group("/v1")
{
v1.GET("/posts", defaultHandler)
v1.GET("/series", defaultHandler)
}
// group: v2
v2 := r.Group("/v2")
{
v2.GET("/posts", defaultHandler)
v2.GET("/series", defaultHandler)
}
中间件使用
示例:
// 作用于全局
r.Use(gin.Logger())
r.Use(gin.Recovery())
// 作用于单个路由
r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
// 作用于某个组
authorized := r.Group("/")
authorized.Use(AuthRequired())
{
authorized.POST("/login", loginEndpoint)
authorized.POST("/submit", submitEndpoint)
}
参考文档: