Gin框架学习

198 阅读5分钟

Gin-Vue:www.gin-vue-admin.com/guide/serve…

文档参考: www.kancloud.cn/shuangdeyu/…

1、一个简单的实例

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  配置格式r.Run(":2022")  远程ip访问"0.0.0.0:2022"
}

2、创建路由

	// 使用默认中间件创建一个gin路由器
	// 携带基础中间件启动 logger and recovery (crash-free) 中间件
	router := gin.Default()
        
        // 使用无中间件默认启动
        r := gin.New()

3、获取接口参数

  • GET 参数在url中 url进行传参
  1. c.Param("key")
  2. c.Query("key")
  3. c.DefaultQuery("key", "defaultVal")
	// /path/foo?user=gogo&pwd=123456
	r.GET("/path/:id", func(c *gin.Context) {
		id := c.Param("id")
		user := c.Query("user")
		pwd := c.Query("pwd")
		c.JSON(200, gin.H{
			"id":   id,
			"user": user,
			"pwd":  pwd,
		})
	})





  • POST 参数在form body中 或者url中
  1. c.DefaultPostForm("key", "defaultVal")
  2. c.PostForm("key")
	r.POST("/path", func(c *gin.Context) {
		user := c.DefaultPostForm("user", "gogo")
		pwd := c.PostForm("pwd")
		c.JSON(200, gin.H{
			"user": user,
			"pwd":  pwd,
		})
	})
  • DELETE 一般情况为url 也可以用body

  • PUT 参数在form body或者url中

4、使用bind绑定参数和参数验证

Gin使用 go-playground/validator.v8 验证参数,查看完整文档

需要在绑定的字段上设置tag,比如,绑定格式为json,需要这样设置 json:"fieldname"

Bind模式获取参数和表单验证

bind默认如何使用

bind模式一定要设置tag json form url

MustBind(弃用)

不推荐使用,对返回code不好控制

ShouldBind

表单验证 自定义验证

使用tag进行参数绑定器设置

// 1、简单实例  使用ShouldBindJSON()和参数结构体字段tag配置json:参数名 进行绑定
type PostParams struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
	Sex  bool   `json:"sex"`
}

func main() {
	r := gin.Default()

	r.POST("/testBind", func(c *gin.Context) {
		var p PostParams
		err := c.ShouldBindJSON(&p)
		if err != nil {
			c.JSON(200, gin.H{
				"msg":  "接口报错",
				"data": gin.H{},
			})
		} else {
			c.JSON(200, gin.H{
				"msg":  "接口成功",
				"data": p,
			})
		}
	})
	r.Run(":2000")
}

// 2、简单实例  使用ShouldBinduri()和参数结构体字段tag配置uri:参数名 进行绑定
type PostParams struct {
	Name string `uri:"name"`
	Age  int    `uri:"age"`
	Sex  bool   `uri:"sex"`
}

func main() {
	r := gin.Default()

	r.POST("/testBind/:name/:age/:sex", func(c *gin.Context) {
		var p PostParams
		err := c.ShouldBindUri(&p)
		if err != nil {
			c.JSON(200, gin.H{
				"msg":  "接口报错",
				"data": gin.H{},
			})
		} else {
			c.JSON(200, gin.H{
				"msg":  "接口成功",
				"data": p,
			})
		}
	})
	r.Run(":2000")
}

// 3、简单实例  使用ShouldBinduri()和参数结构体字段tag配置uri:参数名 进行绑定
type PostParams struct {
	Name string `form:"name"`
	Age  int    `form:"age"`
	Sex  bool   `form:"sex"`
}

func main() {
	r := gin.Default()

	r.POST("/testBind", func(c *gin.Context) {
		var p PostParams
		err := c.ShouldBindQuery(&p)
		if err != nil {
			c.JSON(200, gin.H{
				"msg":  "接口报错",
				"data": gin.H{},
			})
		} else {
			c.JSON(200, gin.H{
				"msg":  "接口成功",
				"data": p,
			})
		}
	})
	r.Run(":2000")
}

// 4、简单实例  使用ShouldBindQuery()和参数结构体字段tag配置form:参数名 进行绑定
type PostParams struct {
	Name string `form:"name"`
	Age  int    `form:"age"`
	Sex  bool   `form:"sex"`
}

func main() {
	r := gin.Default()

	r.POST("/testBind", func(c *gin.Context) {
		var p PostParams
		err := c.ShouldBindQuery(&p)
		if err != nil {
			c.JSON(200, gin.H{
				"msg":  "接口报错",
				"data": gin.H{},
			})
		} else {
			c.JSON(200, gin.H{
				"msg":  "接口成功",
				"data": p,
			})
		}
	})
	r.Run(":2000")
}

// 思考
// 如果是Get,name接收不到请求中的Post的数据??
// 如果是Post,首先判断`content-type`的类型是json或者是xml,然后使用对应的绑定器获取数据

ShouldBind()表单验证

必填项配置: binding:"required"

type PostParams struct {
    Name string  `json:"name" binding:"required"`
    Age  int    `json:"age" binding:"required"`
    Sex  bool   `json:"sex" binding:"required"` 
} 

自定义配置器

package main

import (
	"fmt"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10"
)

// 1、自定义校验器 需要binding:"required,函数名"
type PostParams struct {
	Name string `json:"name"`
	Age  int    `json:"age" binding:"required,mustBig"`
	Sex  bool   `json:"sex"`
}

// 2、配置校验器函数
func mustBig(fl validator.FieldLevel) bool {
	if fl.Field().Interface().(int) <= 18 {
		return false
	} else {
		return true
	}
}

func main() {
	r := gin.Default()
        // 3、注册校验器
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		v.RegisterValidation("mustBig", mustBig)
	}

	r.POST("/testBind", func(c *gin.Context) {
		var p PostParams
		err := c.ShouldBindJSON(&p)
		if err != nil {
			fmt.Println(err.Error())
			c.JSON(200, gin.H{
				"msg":  "接口报错",
				"data": gin.H{},
			})
		} else {
			c.JSON(200, gin.H{
				"msg":  "接口成功",
				"data": p,
			})
		}
	})
	r.Run(":2000")
}

5、上传文件

参考文档:www.kancloud.cn/shuangdeyu/…

PostMan接口:http://127.0.0.1:2000/upload Header:{"Content-Type":"multipart/form-data"} Body:选文件类型{"file":"点击按钮选择文件"}

package main

import (
	"io"
	"os"

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

func main() {
	r := gin.Default()

	r.POST("/upload", func(c *gin.Context) {
		// 实现二: 实现c.SaveUploadedFile(file, "./"+file.Filename)
		file, _ := c.FormFile("file")
		in, _ := file.Open()
		defer in.Close()
		out, _ := os.Create("./" + file.Filename)
		io.Copy(out, in)
		//实现一:  c.SaveUploadedFile(file, "./"+file.Filename)
		c.JSON(200, gin.H{
			"msg": file,
		})

	})
	r.Run(":2000")
}



// 文件上传保存并且再传给前端的实现
	r.POST("/upload", func(c *gin.Context) {
		// 实现c.SaveUploadedFile(file, "./"+file.Filename)
		file, _ := c.FormFile("file")
		in, _ := file.Open()
		defer in.Close()
		out, _ := os.Create("./" + file.Filename)
		io.Copy(out, in)
		c.Writer.Header().Add("Content-Disposition", fmt.Sprint("attachment; filename=%s", file.Filename))
		c.File("./" + file.Filename)
	})
	r.Run(":2000")

多文件上传

image.png

	r.POST("/upload", func(c *gin.Context) {
		// 实现c.SaveUploadedFile(file, "./"+file.Filename)
		form, _ := c.MultipartForm()
		file, _ := form.File["file"]
		for _, file := range file {
                
                
			log.Println(file.Filename)

		}
	})
	r.Run(":2000")
        
// 终端打印
[GIN-debug] POST   /upload                   --> main.main.func1 (3 handlers)
[GIN-debug] Listening and serving HTTP on :2000
2023/04/18 22:44:52 2023-04-18_22-39.png
2023/04/18 22:44:52 计划方案.doc
[GIN] 2023/04/18 - 22:44:52 | 200 |     585.946µs |       127.0.0.1 | POST     "/upload"

6、 中间件和路由分组

路由

路由分组:对router创建Group就是分组,同一个分组会拥有同一前缀和同一中间件 好处:路由结构更加清晰、更加方便管理路由

	v1 := r.Group("v1")
	v1.GET("test", func(c *gin.Context) {
		fmt.Println("我在分组方法内部")
		c.JSON(200, gin.H{
			"success": true,
		})
	})
	r.Run(":2000")
        
        // http://127.0.0.1:2000/v1/test
        // 返回:{ "success": true }

中间件

Q:什么是中间件? A:在请求到达路由的方法的前和后进行的一系列操作

Q:如何使用中间件 A:在路由器(路由组)上进行user操作 后面传入中间件函数即可 * 中间件操作函数: c.Next() c.Abort() c.Set() c.Get() blog.csdn.net/qq_37767455…

r := gin.Default()
// 使用gin.Default()底层会给我们默认加上两个中间件

// 源码
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine
}

自定义中间件创建

// 一个中间件
func middle() gin.HandlerFunc {
	return func(c *gin.Context) {
		//c.Next() 是否通过

		fmt.Println("我在方法前......")
		c.Next()
		fmt.Println("我在方法后......")
	}
}

func main() {
	r := gin.Default()

	v1 := r.Group("v1").Use(middle())
	v1.GET("test", func(c *gin.Context) {
		fmt.Println("我在分组方法内部")
		c.JSON(200, gin.H{
			"success": true,
		})
	})
	r.Run(":2000")
}

// 终端打印
我在方法前......
我在分组方法内部
我在方法后......


// 两个中间件
func middle() gin.HandlerFunc {
	return func(c *gin.Context) {
		//c.Next() 是否通过

		fmt.Println("我在方法前......")
		c.Next()
		fmt.Println("我在方法后......")
	}
}

func middleTwo() gin.HandlerFunc {
	return func(c *gin.Context) {
		//c.Next() 是否通过

		fmt.Println("我在方法前 Two......")
		c.Next()
		fmt.Println("我在方法后 Two......")
	}
}

func main() {
	r := gin.Default()

        // 两种写法都可以
	// v1 := r.Group("v1").Use(middle(), middleTwo())
        v1 := r.Group("v1").Use(middle()).Use(middleTwo())
	v1.GET("test", func(c *gin.Context) {
		fmt.Println("我在分组方法内部")
		c.JSON(200, gin.H{
			"success": true,
		})
	})
	r.Run(":2000")
}

// 终端
我在方法前......
我在方法前 Two......
我在分组方法内部
我在方法后 Two......
我在方法后......

具体使用可以参考: gin-vue-admin的middleware/jwt.go和casbin_rcba.go【鉴权】

7、日志

  • 为什么使用日志? 记录参数信息、猜测用户行为、复现系统Bug并修复

  • in自带日志写入中间件:自定义比较麻烦 第三方日志中工具:go-logging、logrus

  • 日志切割:自行根据时间在写入时间判断进行切割日志、借助成品的日志包:go-file-rotatelogs、file-rotatelogs

文档参考: gin自带的日志格式实例【不推荐使用,灵活度不够】 www.kancloud.cn/shuangdeyu/… www.kancloud.cn/shuangdeyu/…

实例参考:gin-vue-admin日志模块

8、GORM

9、参考文档

learnku.com/docs/gorm/v… gorm.io/zh_CN/docs/

www.kancloud.cn/shuangdeyu/… learnku.com/docs/the-wa… golang-china.github.io/gopl-zh/ gin-gonic.com/zh-cn/docs/ gorm.io/zh_CN/ juejin.cn/post/716923… github.com/uber-go/gui… github.com/fatih/vim-g…

juejin.cn/post/684490… coolshell.cn/articles/21…

入门

learnku.com/docs/the-wa…

Go语言圣经 - Go语言圣经

web框架

文档 | Gin Web Framework

Gorm框架

GORM - The fantastic ORM library for Golang, aims to be developer friendly.

编码规范

送给学Go或者转Go同学的一套编码规范 - 掘金

github.com/uber-go/gui…

vim-IDE(使用vs-code忽略)

vim-go/vim-go.txt at master · fatih/vim-go · GitHub