Gin 的基本操作(实用篇)

150 阅读8分钟

安装

go get github.com/gin-gonic/gin

HelloWorld

package main

import (
	"net/http"

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

func main() {
	// 创建一个默认的路由
	router := gin.Default()
	// 设置路由
	router.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Hello World!")
	})
	// 启动方式一
	// router.Run(":8080")
	// 启动方式二
	http.ListenAndServe(":8080", router)
    // 注意:可以指定IP地址,router.Run("0.0.0.0:8000")
}

响应

字符串

router.GET("/txt", func(c *gin.Context) {
  c.String(http.StatusOK, "这是一个字符串")
})

Json


router.GET("/", func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "success", "data": "hello world"})
})
	router.GET("/", func(c *gin.Context) {
		type Resp struct {
			Name string `json:"name"`
			Age  int    `json:"age"`
			City string `json:"city"`
		}
		resp := Resp{"Tom", 25, "Beijing"}
		c.JSON(http.StatusOK, resp)
	})

文件

    // 创建路由
    router := gin.Default()
    // 方法一:指定静态文件目录
    // 第一个参数:网页请求这个静态目录的前缀, 第二个参数:指定静态文件目录
    // 注意,前缀不要重复
    // router.StaticFS("/static", http.Dir("static"))
    // 方法二:指定静态文件
    router.StaticFile("/img1.png", "static/img1.png")
    router.Run(":8080")

重定向

	// 创建路由
	router := gin.Default()
	// 301重定向
	// router.GET("/old_path", func(c *gin.Context) {
	// 	c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
	// })
	// 302重定向
	router.GET("/old_path", func(c *gin.Context) {
		c.Redirect(http.StatusFound, "https://www.baidu.com")
	})
	router.Run(":8080")

设置响应头

func main() {
	// 创建路由
	router := gin.Default()
	// 设置响应头
	router.GET("/respnse/header", func(c *gin.Context) {
		c.Header("lemon", "440886666")
		c.JSON(200, gin.H{
			"code":    http.StatusOK,
			"message": "请看看响应头",
		})
	})
	router.Run(":8080")
}

请求

请求参数获取

Query 查询参数

func queryGet(c *gin.Context) {
	// 获取查询参数
	// 返回: 参数值
	fmt.Println(c.Query("name"))
	// 返回: 参数值;是否存在(bool)
	fmt.Println(c.GetQuery("name"))
	// 返回: 参数值数组(上面的方法只会返回第一个参数值(如果有多个))
	fmt.Println(c.QueryArray("name"))
	// 设置默认值
	fmt.Println(c.DefaultQuery("age", "18"))
}

func main() {
	// 创建路由
	router := gin.Default()
	// 获取查询参数
	router.GET("/query", queryGet)
	router.Run(":8080")
}

Parms 路径参数(动态参数)

func paramGet(c *gin.Context) {
	// 获取动态路径参数
	fmt.Println(c.Param("user_id"))
	fmt.Println(c.Param("username"))
}

func main() {
	// 创建路由
	router := gin.Default()
	// 获取查询参数
	router.GET("/param/:user_id/:username", paramGet)
	router.Run(":8080")
}

获取表单数据

func formdataGet(c *gin.Context) {
	fmt.Println(c.PostForm("name"))
	// 获取多个值(数组)
	fmt.Println(c.PostFormArray("hobby"))
	// 设置默认值
	fmt.Println(c.DefaultPostForm("email", "123@163.com"))
	// 获取整个表单
	form, err := c.MultipartForm()
	fmt.Println(form, err)
	// 通过 form 获取参数值
	fmt.Println(form.Value["hobby"])
}

func main() {
	// 创建路由
	router := gin.Default()
	// 获取查询参数
	router.POST("/formdata", formdataGet)
	router.Run(":8080")
}

获取请求体数据(原生)

func rowBodyGet(c *gin.Context) {
	data, err := c.GetRawData()
	fmt.Println(string(data), err)
}

func main() {
	// 创建路由
	router := gin.Default()
	// 获取查询参数
	router.POST("/raw", rowBodyGet)
	router.Run(":8080")
}

获取请求头

func main() {
	// 创建路由
	router := gin.Default()
	// 获取查询参数
	router.POST("/header", func(c *gin.Context) {
		// 不区分大小写
		// 获取请求头
		fmt.Println(c.GetHeader("User-Agent"))
		// 获取 header, 通过 header 获取头信息
		header := c.Request.Header
		fmt.Println(header)
		fmt.Println(header.Get("User-Agent"))
		fmt.Println(header["User-Agent"])
		// 区分大小写
		fmt.Println(header["Token"])
		// 不区分大小写
		fmt.Println(header.Get("Token"))
		c.JSON(200, gin.H{
			"msg": "success",
		})
	})
	router.Run(":8080")
}

请求参数绑定与校验

Json Bind

type User struct {
	Name    string   `json:"name"`
	Age     int      `json:"age"`
	City    string   `json:"city"`
	Hobbies []string `json:"hobbies"`
}

func main() {
	router := gin.Default()
	router.POST("/bind/json", func(c *gin.Context) {
		var user User
		err := c.ShouldBindJSON(&user)
		if err != nil {
			c.JSON(200, gin.H{"error": err.Error()})
			return
		}
		c.JSON(200, gin.H{"user": user})
	})
	router.Run(":8080")
}

Query Bind

type User1 struct {
	Name    string   `json:"name" form:"name"`
	Age     int      `json:"age" form:"age"`
	City    string   `json:"city" form:"city"`
	Hobbies []string `json:"hobbies" form:"hobbies"`
}

func main() {
	router := gin.Default()
	router.POST("/bind/query", func(c *gin.Context) {
		var user User1
		err := c.ShouldBindQuery(&user)
		if err != nil {
			c.JSON(200, gin.H{"error": err.Error()})
			return
		}
		c.JSON(200, gin.H{"user": user})
	})
	router.Run(":8080")
}

Uri Bind

type User2 struct {
	Name    string   `json:"name" form:"name" uri:"name"`
	Age     int      `json:"age" form:"age" uri:"age"`
	City    string   `json:"city" form:"city" uri:"city"`
}

func main() {
	router := gin.Default()
	router.POST("/bind/param/:name/:age/:city", func(c *gin.Context) {
		var user User2
		err := c.ShouldBindUri(&user)
		if err != nil {
			c.JSON(200, gin.H{"error": err.Error()})
			return
		}
		c.JSON(200, gin.H{"user": user})
	})
	router.Run(":8080")
}

Auto Bind

可以使用 ShouldBind 进行绑定,该方法会自动根据 content-type 类型来选择对应的绑定方式进行绑定,包括 form-data、 x-www-form-urlencode。

type User4 struct {
	Name    string   `json:"name" form:"name" uri:"name"`
	Age     int      `json:"age" form:"age" uri:"age"`
	City    string   `json:"city" form:"city" uri:"city"`
	Hobbies []string `json:"hobbies" form:"hobbies"`
}

func main() {
	router := gin.Default()
	router.POST("/bind/auto", func(c *gin.Context) {
		var user User4
		err := c.ShouldBind(&user)
		if err != nil {
			c.JSON(200, gin.H{"error": err.Error()})
			return
		}
		c.JSON(200, gin.H{"user": user})
	})
	router.Run(":8080")
}

参数校验

参数校验使用 binding 标签来设置,多个校验规则之间使用 逗号 分隔。

demo

type User5 struct {
	Name    string   `json:"name" form:"name" binding:"required,min=2,max=10"`
	Age     int      `json:"age" form:"age"`
	City    string   `json:"city" form:"city"`
	Hobbies []string `json:"hobbies" form:"hobbies"`
}

func main() {
	router := gin.Default()
	router.POST("/param/valid", func(c *gin.Context) {
		var user User5
		err := c.ShouldBindQuery(&user)
		if err != nil {
			c.JSON(200, gin.H{"error": err})
			return
		}
		c.JSON(200, gin.H{"user": user})
	})
	router.Run(":8080")
}

常见校验器

// 不能为空,并且不能没有这个字段
required: 必填字段,如:binding:"required"  

// 针对字符串的长度
min 最小长度,如:binding:"min=5"
max 最大长度,如:binding:"max=10"
len 长度,如:binding:"len=6"

// 针对数字的大小
eq 等于,如:binding:"eq=3"
ne 不等于,如:binding:"ne=12"
gt 大于,如:binding:"gt=10"
gte 大于等于,如:binding:"gte=10"
lt 小于,如:binding:"lt=10"
lte 小于等于,如:binding:"lte=10"

// 针对同级字段的
eqfield 等于其他字段的值,如:PassWord string `binding:"eqfield=Password"`
nefield 不等于其他字段的值


- 忽略字段,如:binding:"-"

gin 内置校验器

// 枚举  只能是red 或green
oneof=red green 

// 字符串  
contains=fengfeng  // 包含fengfeng的字符串
excludes // 不包含
startswith  // 字符串前缀
endswith  // 字符串后缀

// 数组
dive  // dive后面的验证就是针对数组中的每一个元素

// 网络验证
ip
ipv4
ipv6
uri
url
// uri 在于I(Identifier)是统一资源标示符,可以唯一标识一个资源。
// url 在于Locater,是统一资源定位符,提供找到该资源的确切路径

// 日期验证  1月2号下午3点4分5秒在2006年
datetime=2006-01-02

自定义错误提示信息

当校验失败,默认的错误消息不是很友好,可以自定义错误信息标签,具体的做法这里就不展开介绍了。

自定义校验器

如果现成的校验器无法满足使用,可以根据需求自定义校验器。

if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
   v.RegisterValidation("sign", signValid)
}

文件操作

上传文件

上传单个文件

func main() {
	router := gin.Default()
	router.MaxMultipartMemory = 8 << 20 // 8 MiB
	router.POST("/upload/single", func(c *gin.Context) {
		file, _ := c.FormFile("file")
		log.Println(file.Filename)
		// 文件存储目标路径
		dst := "./static/" + file.Filename
		// 上传文件
		// 这里保存文件的方法有多种,例如也可以先创建好目标文件,再使用 io.Copy(out, fileRead) 拷贝文件内容
		c.SaveUploadedFile(file, dst)
		c.JSON(http.StatusOK, gin.H{"message": "upload success"})
	})
	router.Run(":8080")
}

上传多个文件

func main() {
	router := gin.Default()
	router.MaxMultipartMemory = 8 << 20 // 8 MiB
	router.POST("/upload/multi", func(c *gin.Context) {
		form, _ := c.MultipartForm()
		files := form.File["upload"]

		for _, file := range files {
			log.Printf(file.Filename)
			// 文件存储目标路径
			dst := "./static/" + file.Filename
			// 上传文件
			// 这里保存文件的方法有多种,例如也可以先创建好目标文件,再使用 io.Copy(out, fileRead) 拷贝文件内容
			c.SaveUploadedFile(file, dst)
		}
		c.JSON(http.StatusOK, gin.H{"message": "upload success"})
	})
	router.Run(":8080")

}

下载文件

func main() {
	router := gin.Default()
	router.GET("/download", func(c *gin.Context) {
		value := c.Query("filename")
		log.Println(value)
		dst := "./static/" + value
		// 设置响应头
		// 表明是文件流
		c.Header("Content-Type", "application/octet-stream")
		c.Header("Content-Disposition", "attachment; filename="+value)
		c.Header("Content-Transfer-Encoding", "binary")
		c.File(dst)
	})
	router.Run(":8080")
}

中间件(钩子函数)与路由分组

注册中间件

func handler(c *gin.Context) {
	fmt.Println("Hello, World!")
	c.JSON(http.StatusOK, gin.H{"message": "Hello World!"})
}

// 定义钩子函数
func m1(c *gin.Context) {
	fmt.Println("钩子函数m1执行...")
}

// 定义钩子函数
func m2(c *gin.Context) {
	fmt.Println("钩子函数m2执行...")
}

func main() {
	router := gin.Default()
	router.GET("/hook", m1, handler, m2)
	router.Run(":8080")
}

执行结果:

钩子函数m1执行...
Hello, World!
钩子函数m2执行...

拦截( Abort )与放行( Next )

调用 Abort 后,不会继续执行当前中间后面的中间件,不过会继续执行完 Abort 后面的语句(如果有); 调用 Next 后,就会执行下一个中间件,直到后面所有中间件完成了执行返回时再继续执行 Next 后面的语句。

func handler(c *gin.Context) {
	fmt.Println("Hello, World!...in")
	c.JSON(http.StatusOK, gin.H{"message": "Hello World!"})
	// 拦截
	c.Abort()
	fmt.Println("Hello, World!...out")
}

// 定义钩子函数 m1
func m1(c *gin.Context) {
	fmt.Println("钩子函数m1执行...in")
	// 放行:调用下一个钩子函数
	c.Next()
	fmt.Println("钩子函数m1执行...out")
}

// 定义钩子函数 m2
func m2(c *gin.Context) {
	fmt.Println("钩子函数m2执行...in")
	// 放行:调用下一个钩子函数
	c.Next()
	fmt.Println("钩子函数m2执行...out")
}

// 定义钩子函数 m3
func m3(c *gin.Context) {
	fmt.Println("钩子函数m3执行...in")
	// 放行:调用下一个钩子函数
	c.Next()
	fmt.Println("钩子函数m3执行...out")
}

func main() {
	router := gin.Default()
	router.GET("/hook", m1, m2, handler, m3)
	router.Run(":8080")
}

执行结果:

钩子函数m1执行...in
钩子函数m2执行...in
Hello, World!...in
Hello, World!...out
钩子函数m2执行...out
钩子函数m1执行...out

全局注册中间件

除了上面给出的局部中间件,还可以注册全局中间件,如下:

func handler01(c *gin.Context) {
	fmt.Println("Hello, World!")
	c.JSON(http.StatusOK, gin.H{"message": "Hello World!"})
}

// 定义钩子函数
func m01(c *gin.Context) {
	fmt.Println("钩子函数m1执行...")
}

// 定义钩子函数
func m02(c *gin.Context) {
	fmt.Println("钩子函数m2执行...")
}

func main() {
	router := gin.Default()
	// 注册全局中间件
	router.Use(m01, m02)
	router.GET("/hook/global", handler01)
	router.Run(":8080")
}

中间件数据传递

type User struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func m10(c *gin.Context) {
	fmt.Println("m10_in")
	var user User
	c.ShouldBindJSON(&user)
	// 设置一个上下文变量,供后续中间件使用
	c.Set("user", user)
	c.Next()
	fmt.Println("m10_out")
}

func main() {
	router := gin.Default()
	router.Use(m10)
	router.POST("/hook_data_transfer", func(context *gin.Context) {
		// 从上下文变量中获取 user 数据
		value, exists := context.Get("user")
		// 判断是否存在
		if exists {
			user, _ := value.(User)
			fmt.Println("获取到了 m10 传递过来的 user 数据:", user)
			context.JSON(200, gin.H{"message": "success"})
		}
	})
	router.Run(":8080")
}

路由分组

func middle(c *gin.Context) {
	fmt.Println("This is a middleware")
}

func main() {
	rouer := gin.Default()
	// 将中间件应用到 /api 组下
	r := rouer.Group("/api").Use(middle)
	// 基于 /api 组下的路由
	r.GET("/users", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "This is a users API",
		})
	})
	r.GET("/posts", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "This is a posts API",
		})
	})
	rouer.Run(":8080")
}

参考:
docs.fengfengzhidao.com/#/README
(https://www.kancloud.cn/shuangdeyu/gin_book