一小时上手gin框架

gin是go最受欢迎的web框架之一。有必要熟练掌握,老规矩,我们还是按照一步步探索的方式,从零开始一起学习它,不要怕,一切都很简单!加油!,整个过程我们遵循先详细后简略的方式, 希望对您有帮助。

1. 项目搭建

  1. mkdir gin_practice
  2. cd gin_practice
  3. go mod init gin_practice 初始化go mod
  4. touch main.go创建main文件

main.go内容如下,我们先实现一个简单的hello world展示吧

go 复制代码
package main

import "github.com/gin-gonic/gin"

func main() {
	// 创建一个默认的路由器
	r := gin.Default()

	// 注册一个hello路由
	r.GET("/hello", func(c *gin.Context) {
		// 向客户端返回hello world
		c.String(200, "hello world")
	})
	r.Run() // 启动服务 默认在8080端口
}

执行go mod tidy 更新需要必须的包(这里会拉取gin包) 然后执行go run .跑起来

shell 复制代码
dongmingyan@pro ⮀ ~/go_playground/gin_practice ⮀ go run .
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /hello                    --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

好啦,浏览器打开http://localhost:8080/hello就能看到输出的"hello world"了。

到这里我们已经实现了一个最最基本的web请求,🎉🎉🎉

2. 基本的json响应

在大多数时候,前后端分离项目,响应的是json而不是字符串,因此我们来看看如何响应一个json.

go 复制代码
func main() {
	// 省略...

	// 响应json的hello路由
	r.GET("/hellojson", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"code":    200,
			"message": "hello world",
		})
	})

	r.Run() // 启动服务
}

这个时候,ctrl + c终止掉,重新go run . 启动服务(PS:每次更新都需要重新启动) 浏览器输入http://localhost:8080/hellojson

3. html页面

要响应html页面,当然我们需要创建html页面,首先给它一个存放html页面的目录

  • mkdir templates
  • touch templates/index.html 新建index文件

index.html

html 复制代码
<html>
<head>
	<title>Welcome to Gin Practice</title>
</head>
<body>
	<h1>
    <!-- 通过 {{ .变量 }} 使用参数-->
    {{ .title }}欢迎您来到Gin Practice
  </h1>
	<p>这里是我们的第一个gin页面</p>
</body>
</html>

修改main.go文件

go 复制代码
func mian {
  // ...

  // 响应html页面
	r.LoadHTMLGlob("templates/*") // 加载模板文件
	//r.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
	r.GET("/index.html", func(c *gin.Context) {
		c.HTML(200, "index.html", gin.H{
			"title": "dmy", // 传递给模板的数据
		})
	})

	r.Run() // 启动服务
}

重启服务后浏览器输入http://localhost:8080/index.html

可以响应html也可以传参到页面上,理论上不区分前后端一起开发也是支持的。

4. POST/PUT/Delete请求

前面我们快速的上手了响应了json和html页面,但是我们请求的方式都是GET,这里一起学习下其它的请求方式。

4.1 POST

go 复制代码
func main {
  // ...

  // POST请求
	r.POST("/login", func(c *gin.Context) {
		name := c.PostForm("name")         // 获取表单数据
		password := c.PostForm("password") // 获取表单数据

		type any map[string]interface{}
		c.JSON(200, gin.H{
			"code":    200,
			"message": "login success",
			"data":    any{"name": name, "password": password}, // 传递给客户端的数据
		})
	})

	r.Run() // 启动服务
}

您可以在postman或者apifox这样的工具中测试

shell 复制代码
 dongmingyan@pro ⮀ ~ ⮀ curl --location --request POST 'http://localhost:8080/login' \
--form 'name="dmy"' \
--form 'password="123456"'

{"code":200,"data":{"name":"dmy","password":"123456"},"message":"login success"}

4.2 PUT

go 复制代码
func main {
  // ...

  // PUT更新请求
	r.PUT("/user/:id", func(c *gin.Context) {
		id := c.Param("id") // 获取路径参数
		name := c.PostForm("name")
		password := c.PostForm("password")

		type any map[string]interface{}
		c.JSON(200, gin.H{
			"code":    200,
			"message": "update success",
			"data":    any{"id": id, "name": name, "password": password},
		})
	})
	r.Run() // 启动服务
}

为简单起见我们直接命令行执行吧。

shell 复制代码
curl --location --request PUT 'http://localhost:8080/user/12' \
  --form 'name="dmy"' \
  --form 'password="123456"'

{"code":200,"data":{"id":"12","name":"dmy","password":"123456"},"message":"update success"}

4.3 Delete

修改main.go

go 复制代码
func main(){
  // ...

	// DELETE删除请求
	r.DELETE("/user/:id", func(c *gin.Context) {
		id := c.Param("id") // 获取路径参数
		c.JSON(200, gin.H{
			"code":    200,
			"message": "delete success",
			"data":    id,
		})
	})

	r.Run() // 启动服务
}

测试

shell 复制代码
curl --location --request DELETE 'http://localhost:8080/user/12'

{"code":200,"data":"12","message":"delete success"}

5. 获取参数值

gin针对各种参数的获取都写了响应的各种方法,前面我们使用了一些比如:form、路径参数,这里我们进一步探索其它参数的获取方式。

5.1 查询参数

go 复制代码
  // 查询参数
	// curl "http://localhost:8080/query?name=dmy&age=20&ids=1&ids=2&ids=3"
	// 返回结果:{"code":200,"data":{"age":"20","ids":["1","2","3"],"name":"dmy"},"message":"query success"}
	r.GET("/query", func(c *gin.Context) {
		name := c.Query("name")            // 获取查询参数
		age := c.DefaultQuery("age", "18") // 获取查询参数,如果不存在则返回默认值
		ids := c.QueryArray("ids")         // 获取查询参数数组

		fmt.Printf("%#v\n", ids)
		c.JSON(200, gin.H{
			"code":    200,
			"message": "query success",
			"data":    gin.H{"name": name, "age": age, "ids": ids},
		})
	})

5.2 json绑定参数

这种用的比较多,参数进来后,绑定到一个结构体上

go 复制代码
  type User struct {
		Name string `form:"name" json:"name" xml:"name" binding:"required"`
		Age  int    `form:"age" json:"age" xml:"age" binding:"required"`
	}

  // curl -X POST -H "Content-Type: application/json" -d '{"name": "dmy", "age": 20}' http://localhost:8080/users
	r.POST("/users", func(c *gin.Context) {
		var user User
		// 自动决定绑定类型,默认是JSON绑定 也可以是xml
		if err := c.ShouldBind(&user); err == nil {
			fmt.Println(user.Name, user.Age)
			c.JSON(200, gin.H{
				"code":    200,
				"message": "user created",
				"data":    user,
			})
		} else {
			c.JSON(400, gin.H{
				"code":    400,
				"message": "invalid request",
				"error":   err.Error(),
			})
		}
	})

5.3 表单form参数

go 复制代码
  // 表单参数
	//  curl -X POST  -d "ids=1&ids=2&ids=3" -d "name=dmy" -d "password=6789" http://localhost:8080/form
	// 返回结果: {"code":200,"data":{"ids":["1","2","3"],"name":"dmy","password":"6789"},"message":"form success"}
	// 表单参数
	//  curl -X POST  -d "ids=1&ids=2&ids=3" -d "name=dmy&password=456" -d "account[id]=111&account[name]=dmy" http://localhost:8080/form
	// 返回结果: {"code":200,"data":{"account":{"id":"111","name":"dmy"},"ids":["1","2","3"],"name":"dmy","password":"456"},"message":"form success"}
	r.POST("/form", func(c *gin.Context) {
		name := c.PostForm("name")
		password := c.DefaultPostForm("password", "123456") // 获取表单参数,如果不存在则返回默认值
		ids := c.PostFormArray("ids")                       // 获取表单参数数组
		// 表单map
		account := c.PostFormMap("account") // account[id]=111&account[name]=dmy

		fmt.Println(name, password)
		c.JSON(200, gin.H{
			"code":    200,
			"message": "form success",
			"data":    gin.H{"name": name, "password": password, "ids": ids, "account": account},
		})
	})

5.4 文件上传

go 复制代码
// 文件上传
	// curl -X POST -F "file=@/Users/dongmingyan/Desktop/test.txt" http://localhost:8080/upload
	r.POST("/upload", func(c *gin.Context) {
		file, _ := c.FormFile("file") // 获取上传的文件

		c.SaveUploadedFile(file, "./uploads/"+file.Filename) // 保存文件到指定路径
		c.JSON(200, gin.H{
			"code":    200,
			"message": "upload success",
			"data":    file.Filename,
		})
	})

5.5 其它

go 复制代码
// /user/:id
id := c.Param("id") // 获取路径参数

// 请求头
token := c.GetHeader("Authorization")

6. 路由组

根据api区分v1和v2的路由

go 复制代码
//========================== 路由组 ==========================//
	apiGroup := r.Group("/api")
	{
		// api/v1路由组
		v1Group := apiGroup.Group("/v1")
		{
			v1Group.GET("/users", func(c *gin.Context) {
				c.JSON(200, gin.H{"code": 200, "message": "v1 users"})
			})
		}

		// api/v2路由组
		v2Group := apiGroup.Group("/v2")
		{
			v2Group.GET("/users", func(c *gin.Context) {
				c.JSON(200, gin.H{"code": 200, "message": "v2 users"})
			})
		}
	}

7. 中间件

  • 定义中间件
go 复制代码
// 定义用户中间件
func userMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 做一些用户的获取和验证操作

		currentUser := "dmy"              // 获取当前用户信息
		c.Set("currentUser", currentUser) // 设置当前用户信息到上下文

		c.Next() // 继续处理请求
		//c.JSON(404, gin.H{"code": 404, "message": "not found"})
		//c.Abort() // 中止请求 终止前要写响应不然啥也没有
	}
}
  • 注册中间件
go 复制代码
func main() {
	// 创建一个默认的路由器
	r := gin.Default()

	r.Use(userMiddleware()) // 注册用户中间件 这里注册后对所有的路由都能获取到当前用户信息
  
  // ...
}
  • 使用中间件
go 复制代码
// 测试当前用户信息
	r.GET("/current_user", func(c *gin.Context) {
		// 获取当前用户信息 这是中间件中存下的
		currentUser, exists := c.Get("currentUser")

		if exists {
			c.JSON(200, gin.H{
				"code":         200,
				"message":      "current user",
				"current_user": currentUser,
			})
		} else {
			c.JSON(401, gin.H{
				"code":    401,
				"message": "unauthorized",
			})
		}
	})

8. 日志记录到文件

默认情况下gin的日志没有写到文件中,所以需要手动配置下

go 复制代码
func main() {
	//日志记录到当前目录下development.log文件
	f, _ := os.Create("development.log")
	// 同时保留了控制台输出
	gin.DefaultWriter = io.MultiWriter(f, os.Stdout)

  //...
}

9. 完整代码

main.go完整代码如下

go 复制代码
package main

import (
	"fmt"
	"io"
	"os"

	"github.com/gin-gonic/gin"
)

func main() {
	//日志记录到当前目录下development.log文件
	f, _ := os.Create("development.log")
	// 同时保留了控制台输出
	gin.DefaultWriter = io.MultiWriter(f, os.Stdout)

	// 创建一个默认的路由器
	r := gin.Default()

	r.Use(userMiddleware()) // 注册用户中间件 这里注册后对所有的路由都能获取到当前用户信息
	// 注册一个hello路由
	r.GET("/hello", func(c *gin.Context) {
		// 向客户端返回hello world
		c.String(200, "hello world")
	})

	// 响应json的hello路由
	r.GET("/hellojson", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"code":    200,
			"message": "hello world",
		})
	})

	// 响应html页面
	r.LoadHTMLGlob("templates/*") // 加载模板文件
	//r.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
	r.GET("/index.html", func(c *gin.Context) {
		c.HTML(200, "index.html", gin.H{
			"title": "dmy", // 传递给模板的数据
		})
	})

	// POST请求
	r.POST("/login", func(c *gin.Context) {
		name := c.PostForm("name")         // 获取表单数据
		password := c.PostForm("password") // 获取表单数据

		type any map[string]interface{}
		c.JSON(200, gin.H{
			"code":    200,
			"message": "login success",
			"data":    any{"name": name, "password": password}, // 传递给客户端的数据
		})
	})

	// PUT更新请求
	r.PUT("/user/:id", func(c *gin.Context) {
		id := c.Param("id") // 获取路径参数
		name := c.PostForm("name")
		password := c.PostForm("password")

		type any map[string]interface{}
		c.JSON(200, gin.H{
			"code":    200,
			"message": "update success",
			"data":    any{"id": id, "name": name, "password": password},
		})
	})

	// DELETE删除请求
	r.DELETE("/user/:id", func(c *gin.Context) {
		id := c.Param("id") // 获取路径参数
		c.JSON(200, gin.H{
			"code":    200,
			"message": "delete success",
			"data":    id,
		})
	})

	//========================== 参数部分 ==========================//
	// 查询参数
	// curl "http://localhost:8080/query?name=dmy&age=20&ids=1&ids=2&ids=3"
	// 返回结果:{"code":200,"data":{"age":"20","ids":["1","2","3"],"name":"dmy"},"message":"query success"}
	r.GET("/query", func(c *gin.Context) {
		name := c.Query("name")            // 获取查询参数
		age := c.DefaultQuery("age", "18") // 获取查询参数,如果不存在则返回默认值
		ids := c.QueryArray("ids")         // 获取查询参数数组

		fmt.Printf("%#v\n", ids)
		c.JSON(200, gin.H{
			"code":    200,
			"message": "query success",
			"data":    gin.H{"name": name, "age": age, "ids": ids},
		})
	})

	type User struct {
		Name string `form:"name" json:"name" xml:"name" binding:"required"`
		Age  int    `form:"age" json:"age" xml:"age" binding:"required"`
	}

	// curl -X POST -H "Content-Type: application/json" -d '{"name": "dmy", "age": 20}' http://localhost:8080/users
	r.POST("/users", func(c *gin.Context) {
		var user User
		// 自动决定绑定类型,默认是JSON绑定 也可以是xml
		if err := c.ShouldBind(&user); err == nil {
			fmt.Println(user.Name, user.Age)
			c.JSON(200, gin.H{
				"code":    200,
				"message": "user created",
				"data":    user,
			})
		} else {
			c.JSON(400, gin.H{
				"code":    400,
				"message": "invalid request",
				"error":   err.Error(),
			})
		}
	})

	// 表单参数
	//  curl -X POST  -d "ids=1&ids=2&ids=3" -d "name=dmy&password=456" -d "account[id]=111&account[name]=dmy" http://localhost:8080/form
	// 返回结果: {"code":200,"data":{"account":{"id":"111","name":"dmy"},"ids":["1","2","3"],"name":"dmy","password":"456"},"message":"form success"}
	r.POST("/form", func(c *gin.Context) {
		name := c.PostForm("name")
		password := c.DefaultPostForm("password", "123456") // 获取表单参数,如果不存在则返回默认值
		ids := c.PostFormArray("ids")                       // 获取表单参数数组
		// 表单map
		account := c.PostFormMap("account") // account[id]=111&account[name]=dmy

		fmt.Println(name, password)
		c.JSON(200, gin.H{
			"code":    200,
			"message": "form success",
			"data":    gin.H{"name": name, "password": password, "ids": ids, "account": account},
		})
	})

	// 文件上传
	// curl -X POST -F "file=@/Users/dongmingyan/Desktop/test.txt" http://localhost:8080/upload
	r.POST("/upload", func(c *gin.Context) {
		file, _ := c.FormFile("file") // 获取上传的文件

		c.SaveUploadedFile(file, "./uploads/"+file.Filename) // 保存文件到指定路径
		c.JSON(200, gin.H{
			"code":    200,
			"message": "upload success",
			"data":    file.Filename,
		})
	})

	//========================== 路由组 ==========================//
	apiGroup := r.Group("/api")
	{
		// api/v1路由组
		v1Group := apiGroup.Group("/v1")
		{
			v1Group.GET("/users", func(c *gin.Context) {
				c.JSON(200, gin.H{"code": 200, "message": "v1 users"})
			})
		}

		// api/v2路由组
		v2Group := apiGroup.Group("/v2")
		{
			v2Group.GET("/users", func(c *gin.Context) {
				c.JSON(200, gin.H{"code": 200, "message": "v2 users"})
			})
		}
	}

	// 测试当前用户信息
	r.GET("/current_user", func(c *gin.Context) {
		// 获取当前用户信息
		currentUser, exists := c.Get("currentUser")

		if exists {
			c.JSON(200, gin.H{
				"code":         200,
				"message":      "current user",
				"current_user": currentUser,
			})
		} else {
			c.JSON(401, gin.H{
				"code":    401,
				"message": "unauthorized",
			})
		}
	})

	r.Run() // 启动服务
}

// 定义用户中间件
func userMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 做一些用户的获取和验证操作

		currentUser := "dmy"              // 获取当前用户信息
		c.Set("currentUser", currentUser) // 设置当前用户信息到上下文

		c.Next() // 继续处理请求
		//c.JSON(404, gin.H{"code": 404, "message": "not found"})
		//c.Abort() // 中止请求 终止前要写响应不然啥也没有
	}
}
相关推荐
研究司马懿17 分钟前
【云原生】Gateway API高级功能
云原生·go·gateway·k8s·gateway api
梦想很大很大14 小时前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰19 小时前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘1 天前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤1 天前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt111 天前
AI DDD重构实践
go
Grassto3 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto5 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室6 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题6 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo