GO GIN框架使用

140 阅读4分钟

前提概要

  • Gin 是一款高性能的、简单轻巧的HTTP Web 框架。
  • 安装方式go get -u github.com/gin-gonic/gin image.png

库重点概述

  • Gin Mode
        const (
              // 开发模式(默认模式)
              DebugMode = "debug"
              // 发行模式
              ReleaseMode = "release"
              // 测试模式
              TestMode = "test"
      )
    
    • 更改Gin 模式:gin.SetMode(gin.ReleaseMode)
  • type Engine struct:Gin框架的实例,包含复用器、中间件和配置设置。可使用New()或Default()创建Engine 实例
    engine := gin.Default() //默认的engine已自带了Logger和Recovery两个中间件
    engine := gin.New() //New 返回一个不附加任何中间件的新空白引擎实例 
    
  • type Context struct:Context 是Gin 最重要的部分。它允许我们在中间件之间传递变量,管理流程,验证请求的JSON并呈现JSON响应。你所需要的东西全都封装在了Context里面。

路由分组

  • (*gin.RouterGroup).Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup Group 创建一个新的路由分组,可以添加具有相同中间件或路由前缀的路由
  • (*gin.Context).String(code int, format string, values ...any)将给定的text/plain类型字符串写入响应正文。
    func boy(c *gin.Context) { //你所需要的东西全都封装在了gin.Context里面,包括http.Request和ResponseWriter
            c.String(http.StatusOK, "hi boy") 
    }

    func girl(c *gin.Context) {
            c.String(http.StatusOK, "hi girl")
    }

    func main() {
	engine := gin.Default()      //默认的engine已自带了Logger和Recovery两个中间件
	engine.GET("/", boy)
	engine.POST("/", girl)

	//路由分组
	oldVersion := engine.Group("/v1") 
	oldVersion.GET("/student", boy) //http://localhost:5656/v1/student
	oldVersion.GET("/teacher", boy) //http://localhost:5656/v1/teacher

	newVersion := engine.Group("/v2")
	newVersion.GET("/student", girl) //http://localhost:5656/v2/student
	newVersion.GET("/teacher", girl) //http://localhost:5656/v2/teacher

	engine.Run(":5656")
    }

参数获取

  • c.Query() 从GET 请求的URL 中获取参数。
     engine.GET("/student", func(ctx *gin.Context) {
        name := ctx.Query("name")
        addr := ctx.DefaultQuery("addr", "China") //如果没传addr参数,则默认为China
        ctx.String(http.StatusOK, name + " live in " + addr)
          })
    
  • c.Param()从Restful 风格的URL 中获取参数。
      engine.GET("/student/:name/*addr", func(ctx *gin.Context) {
          name := ctx.Param("name")
          addr := ctx.Param("addr")
          ctx.String(http.StatusOK, name+" live in "+addr)
          })
    
  • c.PostForm() 从POST 表单中获取参数。
     engine.POST("/student", func(ctx *gin.Context) {
                 name := ctx.PostForm("name")
                 addr := ctx.DefaultPostForm("addr", "China") //如果没传addr参数,则默认为China
                 ctx.String(http.StatusOK, name+" live in "+addr)
         })
    
    • 用postman 模拟一个POST 请求 image.png
  • c.FormFile() 获取上传的文件,消息类型为form-data。
      engine.MaxMultipartMemory = 8 << 20//限制表单上传大小为8M,默认上限是32M
      engine.POST("/upload", func(ctx *gin.Context) {
                 file, err := ctx.FormFile("file")
                 if err != nil {
                         fmt.Printf("get file error %v\n", err)
                         ctx.String(http.StatusInternalServerError, "upload file failed")
                 } else {
                         ctx.SaveUploadedFile(file, "./data/"+file.Filename) //把用户上传的文件存到data目录下
                         ctx.String(http.StatusOK, file.Filename)
                 }
         })
    
    image.png
  • c. MultipartForm() multipart/form-data 可以上传多个form-data 类消息并且用分隔符进行分割。多个key可以都叫files,value对应不同的文件
    engine.POST("/upload_files", func(ctx *gin.Context) {
      form, err := ctx.MultipartForm() //MultipartForm中不止包含多个文件
      if err != nil {
      	ctx.String(http.StatusBadRequest, err.Error())
      } else {
      	//从MultipartForm中获取上传的文件
      	files := form.File["files"]
      	for _, file := range files {
      		ctx.SaveUploadedFile(file, "./data/"+file.Filename) 
      	}
      	ctx.String(http.StatusOK, "upload "+strconv.Itoa(len(files))+" files")
      }
          })
    
    image.png
  • 提交JSON格式数据(XML、YAML同)
    engine.POST("/stu/json", func(ctx *gin.Context) {
       var stu Student
       if err := ctx.ShouldBindJSON(&stu); err != nil {
       	fmt.Println(err)
       	ctx.String(http.StatusBadRequest, "parse paramter failed")
       } else {
       	ctx.String(http.StatusOK, stu.Name+" live in "+stu.Addr)
       }
           })
    
    image.png

生成response

  • c.String(code int, format string, values ...any): response Content-Type=text/plain。
  • c.JSON(code int, obj any): response Content-Type= application/json。
  • c.JSONP(code int, obj any):如果请求参数里有callback=xxx,则response Content-Type为application/javascript,否则response Content-Type为application/json
  • c.XML(): response Content-Type= application/xml。
  • c.HTML(): 前端写好模板,后端往里面填值。
  • c.Redirect(): 重定向。

演示JSON()、HTML()、Redirect(),XML()和YAML()与JSON()类似不做演示

var stu struct{
	Name string
	Addr string
}

func json(engine *gin.Engine) {
	engine.GET("/user/json1", func(c *gin.Context) {
                c.JSON(http.StatusOK, stu)////response Content-Type:application/json
                //也可用自带的map[string]any “gin.H”来提交信息
		c.JSON(http.StatusOK, gin.H{"name": "lfd", "addr": "cd"}) 
	})
}
func html(engine *gin.Engine) {
	engine.LoadHTMLFiles("http/static/template.html")//此处填写自己html的路径
	engine.GET("/user/html", func(c *gin.Context) {
		//通过json往前端页面上传值
		c.HTML(http.StatusOK, "template.html", gin.H{"title": "用户信息", "name": "lfd", "addr": "cd"})
	})
}

func redirect(engine *gin.Engine) {
	engine.GET("/not_exists", func(c *gin.Context) {//重定向到html页面
		c.Redirect(http.StatusMovedPermanently, "http://localhost:5656/user/html")
	})
}
func main() {
	engine := gin.Default()
	stu.Name = "lfd"
	stu.Addr = "cd"
	json(engine)    //http://localhost:5656/user/json
	html(engine)     //http://localhost:5656/user/html
	redirect(engine) //http://localhost:5656/not_exists
	engine.Run(":5656")
}

参数检验

  • 结构体

    type Student struct {
            Name       string    `form:"name" binding:"required"`                  //required:必须上传name参数
            Score      int       `form:"score" binding:"gt=0"`                    //score必须为正数
            Enrollment time.Time `form:"enrollment" binding:"required,before_today" time_format:"2006-01-02" time_utc:"8"` //自定义验证before_today,日期格式东8区
            Graduation time.Time `form:"graduation" binding:"required,gtfield=Enrollment" time_format:"2006-01-02" time_utc:"8"` //毕业时间要晚于入学时间
    }
    
  • 自定义验证器

    • type Func func(fl FieldLevel) bool:Func 接受满足所有验证需求的字段级别接口。验证成功时,返回值应为 true。
    • type FieldLevel interface:FieldLevel包含用于验证字段的所有信息和帮助类函数
    • Field() reflect.Value:返回当前字段的reflect.value,对反射不了解的可查看go标准库中的reflect库
       var beforeToday validator.Func = func(fl validator.FieldLevel) bool {
            if date, ok := fl.Field().Interface().(time.Time); ok {//将reflect.Value转换为interface后强制转换为Time
                    today := time.Now()
                    if date.Before(today) {
                            return true
                    } else {
                            return false
                    }
          } else {
                    return false
            }
    }
    
  • 错误判断

    func processErr(err error) {
            if err == nil {
                    return
            }
    
            //给Validate.Struct()函数传了一个非法的参数
            invalid, ok := err.(*validator.InvalidValidationError)
            if ok {
                    fmt.Println("param error:", invalid)
                    return
            }
    
            //ValidationErrors是一个错误切片,它保存了每个字段违反的每个约束信息
            validationErrs := err.(validator.ValidationErrors)
            for _, validationErr := range validationErrs {
                    fmt.Printf("field %s 不满足条件 %s\n", validationErr.Field(), validationErr.Tag())
            }
    }
    
  • 注册验证器

    • var Validator StructValidator = &defaultValidator{}:Validator 是StructValidator 接口的默认Validator
    • type StructValidator interface:StructValidator 是需要实现的最小接口,以便将其用作验证器引擎,以确保请求的正确性。 Gin 为此提供了一个默认实现github.com/go-playgrou….
      • Engine()any:返回一个能实现StructValidator 的底层validator engine
    • (v *Validate) RegisterValidation(tag string, fn Func, callValidationEvenIfNull ...bool) error:添加具有给定tag的验证
    engine := gin.Default()
    
    //注册验证器
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
    	v.RegisterValidation("before_today", beforeToday)//tag为"before_today",验证时field只有Enrollment
    }
    
    engine.GET("/", func(ctx *gin.Context) {
    	var stu Student
    	if err := ctx.ShouldBind(&stu); err != nil {
    		processErr(err) //校验不符合时,打印出哪时不符合
    		ctx.String(http.StatusBadRequest, "parse parameter failed")
    	} else {
    		// ctx.JSON(http.StatusOK, stu)
    		bs, _ := json.Marshal(stu)
    		ctx.String(http.StatusOK, string(bs))
    	}
    }) //http://localhost:5656?name=lfd&score=100&enrollment=2021-08-23&graduation=2021-09-23
    
    engine.Run(":5656")
    

中间件

  • 计时中间件,返回gin.HandleFunc
func timeMiddleWare() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		begin := time.Now()
		ctx.Next() //执行业务逻辑
		timeElapsed := time.Since(begin)
		log.Printf("request %s use %d ms\n", ctx.Request.URL.Path, timeElapsed.Milliseconds())
	}
}
  • 可用engine.Use(timeMiddleWare())添加全局中间件
  • 也可在请求中添加局部的中间件
        engine.GET("/", limitMiddleWare(), func(ctx *gin.Context) { 
      	ctx.String(http.StatusOK, "welcome home!~")
      })
    

Cookie

  • HTTP 是无状态的,即服务端不知道两次请求是否来自于同一个客户端。
  • Cookie 由服务端生成,发送给客户端,客户端保存在本地。
  • 客户端每次发起请求时把Cookie 带上,以证明自己的身份
  • 。HTTP请求中的Cookie头只会包含name 和value 信息(服务端只能取到name 和value ),domain、path、expires 等Cookie 属性是由浏览器使用的,对服务器来说没有意义。
  • Cookie 可以被浏览器禁用。

实例

  • Cilent端通过login后得到Cookie 后登录cnter ,center带有Cookie 检测中间件。

Client端

func authLogin() {
	if resp, err := http.Post("http://127.0.0.1:5656/login", "text/plain", nil); err != nil {
		panic(err)
	} else {
		loginCookies := resp.Cookies() //读取服务端返回的Cookie
		if req, err := http.NewRequest("POST", "http://127.0.0.1:5656/center", nil); err != nil {
			panic(err)
		} else {
			//下次请求再带上cookie
			for _, cookie := range loginCookies {
				fmt.Printf("receive cookie %s = %s\n", cookie.Name, cookie.Value)
				// cookie.Value += "1" //修改cookie后认证不通过
				req.AddCookie(cookie)
			}
			client := &http.Client{}
			if resp, err := client.Do(req); err != nil {
				fmt.Println(err)
			} else {
				fmt.Println("成功登录用户中心")
			}
		}
	}
}

Server端

var (
	authMap sync.Map //并发安全的表,存储Cookie
)

//cookie name需要符合规则,否则该cookie会被Gin框架默默地丢弃掉
func genCookieName(ctx *gin.Context) string {
	return base64.StdEncoding.EncodeToString([]byte(ctx.Request.RemoteAddr))
}

//登录
func login(engine *gin.Engine) {
	engine.POST("/login", func(ctx *gin.Context) {
		//为客户端生成cookie
		cookie_key := genCookieName(ctx)//将Client 的地址当作Key
		cookie_value := strconv.Itoa(1)
		//服务端维护所有客户端的cookie,用于对客户端进行认证
		authMap.Store(cookie_key, cookie_value)
		//把cookie发给客户端
		ctx.SetCookie(cookie_key, cookie_value,
			3000,        //maxAge,cookie的有效时间,时间单位秒
			"/",         //path,cookie存放目录
			"localhost", //cookie从属的域名
			false,       //是否只能通过https访问
			true,        //是否允许别人通过js获取自己的cookie
		)
		fmt.Printf("set cookie_key %s \n cookie_value %s to client\n", cookie_key, cookie_value)
		ctx.String(http.StatusOK, "登录成功")
	})
}

//用户中心
func userCenter(engine *gin.Engine) {
	engine.POST("/center", authMiddleWare(), func(ctx *gin.Context) { //为"/center"加个认证中间件
		ctx.String(http.StatusOK, "您已通过身份认证,这里是你的私人空间")
	})
}

func authMiddleWare() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		cookie_key := genCookieName(ctx)
		var cookie_value string
		//读取客户端的cookie
		for _, cookie := range ctx.Request.Cookies() {
			if cookie.Name == cookie_key {
				cookie_value = cookie.Value
				break
			}
		}

		//验证Cookie Value是否正确
		if v, ok := authMap.Load(cookie_key); !ok {
			fmt.Printf("INVALID auth cookie %s = %s\n", cookie_key, cookie_value)
			ctx.JSON(http.StatusForbidden, gin.H{cookie_key: cookie_value})
			ctx.Abort() //验证不通过,调用Abort
		} else {
			if v.(string) == cookie_value {
				ctx.Next() //本中间件顺利通过
			} else {
				fmt.Printf("INVALID auth cookie %s = %s\n", cookie_key, cookie_value)
				ctx.JSON(http.StatusForbidden, gin.H{cookie_key: cookie_value})
				ctx.Abort() //验证不通过,调用Abort
			}
		}
	}
}

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

	//路由
	login(engine)
	userCenter(engine)

	engine.Run("127.0.0.1:5656") //测试方法,运行http/client/main.go里的authLogin()方法
}