gin学习笔记

本文详细介绍了Gin框架的优势,包括其简单易用、高并发处理能力、内存管理以及丰富的路由和参数绑定功能。通过实例展示了如何使用Gin进行URL参数处理、静态文件绑定、泛匹配路由,以及参数验证和多语言错误信息转换。同时涵盖了中间件的使用、优雅关机和自动化证书设置等内容。
摘要由CSDN通过智能技术生成

gin的优势

  • 简单原则
  • 并发高
  • 分配内存

请求路由

  • 请求类型:get、put、post等八种资源请求类型
  • 绑定静态文件夹
  • 参数作为URL:多用于Restfu请求中
  • 泛绑定:所有前缀的请求都定向到一个资源中

操作案例

package main

import (
	"bytes"
	"io/ioutil"
	"net/http"
	"time"

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

type Student struct {
	Name     string    `form:"name"`   结构体字段首字母要大写,反射时要去访问结构体
	Age      int       `form:"age"`
	Birthday time.Time `form:"birthday"`
}

func main() {
	r := gin.Default()  创建gin实例

    参数作为url               
	r.GET("/querry", func(c *gin.Context) {  创建测试路由
		name := c.Query("name")
		c.JSON(200, gin.H{
			"name": name,
		})
	})

    读取Body内的数据
	r.POST("/JHIN", func(c *gin.Context) {
		bodyByts, err := ioutil.ReadAll(c.Request.Body) 将c.Request.Body字节流取出
		if err != nil {
			c.String(http.StatusBadRequest, err.Error())
			c.Abort()
		}
		c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyByts)) 放回到c.Request.Body
		c.String(http.StatusOK, string(bodyByts))

		firstname := c.PostForm("firstname")
		lastname := c.DefaultPostForm("lastname", "y")
		c.String(http.StatusOK, "%s %s %s", firstname, lastname, string(bodyByts))

	})
	r.Handle("GET", "/GET", func(c *gin.Context) { 路由需要传入两个参数,一个为路径,另一个为路由执行的方法,做它处理器 Handler 
                                                  Handler 函数可以对前端返回 字符串,Json,Html 等多种格式或形式文件
		c.String(200, "delete")
	})
	r.DELETE("/", func(c *gin.Context) {

	})
	r.Any("/any", func(c *gin.Context) {    Any可以处理任意请求类型

	})

    绑定静态文件夹
	r.Static("/assets", "./assets")   文件的相对路径
	r.StaticFS("/static", http.Dir("static"))
	r.StaticFile("/favicon", "./document")   文件的相对路径

    泛绑定:所有前缀的请求都定向到一个资源中
	r.GET("/jianlai/*tion", func(c *gin.Context) {  
		c.String(200, "jian lai")
	})


	r.GET("/testing", testing)
	r.POST("/testing", testing)

	r.Run(":9091")
}

func testing(c *gin.Context) {
	var stu Student
	if ee := c.ShouldBind(&stu); ee == nil {
		c.String(http.StatusOK, "%v", stu)
		c.JSON(http.StatusOK, gin.H{
			"Status": "OK",
		})
	}
}

参数绑定:当数据的量比较大,一个一个的取出数据,并初始化json结构体会很繁琐,所以使用ShouldBind()参数绑定。

json是返回给前端的标签,form是从c.Context中取出的对应绑定参数

验证请求参数

  • 结构体验证(将验证规则定义在结构体tag里)
  • 自定义验证(当结构体验证无法满足需求时)
  • 升级验证-支持多语言错误信息转换
package main
 
import (
   "net/http"
   "time"
 
   "github.com/gin-gonic/gin"
   "github.com/gin-gonic/gin/binding"
   "github.com/go-playground/validator"
)
 


在前端请求的数据的字段名要和` `里的类型名对应的字段名一致
type Booking struct {
   CheckIn  time.Time `form:"check_in" validate:"required,bookabledate" time_format:"2006-01-02"`
   CheckOut time.Time `form:"check_out" validate:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}
 
var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
   date, ok := fl.Field().Interface().(time.Time)
   if ok {
      today := time.Now()
      if today.After(date) {
         return false
      }
   }
   return true
}
 
func main() {
   route := gin.Default()
 
   if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
      v.RegisterValidation("bookabledate", bookableDate)  将验证器注册到结构体中
   }
 
   route.GET("/bookable", getBookable)
   route.Run(":8085")
}
 
func getBookable(c *gin.Context) {
   var b Booking
   if err := c.ShouldBindWith(&b, binding.Query); err == nil {
      c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
   } else {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
   }
}

validator库参数校验

在web开发中一个不可避免的环节就是对请求参数进行校验,通常会在代码中定义与请求参数相对应的模型(结构体),借助模型绑定快捷地解析请求中的参数,例如 gin 框架中Bind和ShouldBind系列方法。

我们需要在定义结构体时使用binding tag标识相关校验规则。

type Booking struct{
	ChekIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
	CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn,datetime=2006-01-02"` //这里的CheckIn是原始的字段名
}

time_format和datetime是设置时间格式 required表示该参数是必要参数,如果不传或为空会报错
gtfield=CheckIn 表示check_out时间必须大于check_in ,这里的CheckIn是原始的字段名
bookabledate是自定义的结构体校验方法

自定义字段校验方法

validator支持为某个字段自定义校验方法,并使用RegisterValidation()注册到校验器实例中。

type SignUpParam struct {
	Age        uint8  `json:"age" binding:"gte=1,lte=130"`
	Name       string `json:"name" binding:"required"`
	Email      string `json:"email" binding:"required,email"`
	Password   string `json:"password" binding:"required"`
	RePassword string `json:"re_password" binding:"required,eqfield=Password"`
	// 需要使用自定义校验方法checkDate做参数校验的字段Date
	Date       string `json:"date" binding:"required,datetime=2006-01-02,checkDate"`
}


customFunc 自定义字段级别校验方法
func customFunc(fl validator.FieldLevel) bool {
	date, err := time.Parse("2006-01-02", fl.Field().String())
	if err != nil {
		return false
	}
	if date.Before(time.Now()) {
		return false
	}
	return true
}


 在校验器注册自定义的校验方法:把customFunc注册到结构体的binding中
if err := v.RegisterValidation("checkDate", customFunc); err != nil {
	return err
}

问题

type Booking struct {
   CheckIn  time.Time `form:"check_in" validate:"required,bookabledate" time_format:"2006-01-02"`
   CheckOut time.Time `form:"check_out" validate:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}

实现案例代码的时候一直报错,根据查找的资料,将type Booking struct里的binding改为validate,问题解决了,但没完全解决(不得甚解)可能是validator的版本兼容问题,早版本validator用binging,后来用validate。。。

翻译校验的错误信息

package main

import (
	/*"net/http"
	"time"
	*/
	"github.com/gin-gonic/gin"
	//"github.com/gin-gonic/gin/binding"
	en2 "github.com/go-playground/locales/en"
	zh2 "github.com/go-playground/locales/zh"
	"github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	en_translations "github.com/go-playground/validator/v10/translations/en"
	zh_translations "github.com/go-playground/validator/v10/translations/zh"
)

type Person struct {
	Age     int    `form:"age" validate:"required,gt=10"`
	Name    string `form:"name" validate:"required"`
	Address string `form:"address" validate:"required"`
}

var (
	Uni      *ut.UniversalTranslator
	Validate *validator.Validate
)

func main() {
	Validate = validator.New() //创建验证器
	zh := zh2.New()            //创建翻译器
	en := en2.New()
	Uni = ut.New(zh, en) //设置翻译器里支持的语言
	r := gin.Default()
	r.GET("/testing", func(c *gin.Context) {
		locale := c.DefaultQuery("locale", "zh")
		trans, _ := Uni.GetTranslator(locale) //返回对应的翻译器
		switch locale {
		case "zh":
			zh_translations.RegisterDefaultTranslations(Validate, trans) 把对应语言的翻译器注册到验证器里面
		case "en":
			en_translations.RegisterDefaultTranslations(Validate, trans) 把对应语言的翻译器注册到验证器里面
		default:
			zh_translations.RegisterDefaultTranslations(Validate, trans) 
		}
		person := Person{}
		if err := c.ShouldBind(&person); err != nil {  用shouldbind来实现参数绑定
			c.String(500, "%v", err)
			c.Abort()
			return
		}
		if err := Validate.Struct(person); err != nil {  
			errs := err.(validator.ValidationErrors)  搜集错误信息
			sliceErrs := []string{}
			for _, e := range errs {              
				sliceErrs = append(sliceErrs, e.Translate(trans))  将错误信息追加到切片里
			}
			c.String(500, "%v", sliceErrs)
			c.Abort()
		}
	})
	r.Run()

}

中间件

Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件位于gin框架和回调函数之间,中间件适合处理一些公共的业务逻辑,用来拦截请求,打印日志,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

package main

import "github.com/gin-gonic/gin"
import "os"
import "io"
import "time"
import "log"

func main() {
	f, _ := os.Create("gin.log")
	gin.DefaultWriter = io.MultiWriter(f)      把输出定向到文件f
	gin.DefaultErrorWriter = io.MultiWriter(f) 把错误定向到文件f

	r := gin.New()  创建一个没有任何默认中间件的路由
	r.Use(gin.Logger(), gin.Recovery(),StatCost())   注册全局中间件   Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release。 
      recovery()错误恢复,Recovery中间件会recover任何panic,使程序不至于被down掉如果有panic的话,会写入500响应码。
	r.GET("/test", func(c *gin.Context) {
		/*name := c.DefaultQuery("name", "default_name")
		c.String(200, "%s", name)*/
		name1 := c.MustGet("name").(string)
		log.Println(name1)
		c.JSON(200, gin.H{
			"message": "Hello world!",
		})
	})
	给/test2路由单独注册中间件(可注册多个)
	r.GET("/test2", StatCost(), func(c *gin.Context) {   单独注册
		name1 := c.MustGet("name").(string)        从上下文取值,用于中间件之间通讯
		log.Println(name1)
		c.JSON(200, gin.H{
			"message": "Hello world!",
		})
	})

	路由组注册中间件的方法1
	xxxGroup := r.Group("/xx",StatCost())
	{
		xxxGroup.GET("/index",func(x *gin.Context){
			c.JSON(200,gin.H{
				"mas" : "xxGroup"
			})
		})
	}
	路由组注册中间件的方法2
	xxx3Group := r.Group("/xx3")
	xxx3Group.Use(StatCost())
	{
		xxxGroup.GET("/index",func(x *gin.Context){
			c.JSON(200,gin.H{
				"mas" : "xxGroup"
			})
		})
	}

	r.Run()
}

func StatCost() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		c.Set("name", "xiaowangzi")
		c.Next()     调用后续的处理函数   c.Abort()阻止调用后续的处理函数
		cost := time.Since(start)
		log.Println(cost)

	}

}

中间件执行顺序

按照注册顺序执行

c.Next() 调用后续的处理函数 

整个调用执行顺序类似递归

gin中间件中使用goroutine

当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。因为在协程中对c进行改动后,其他的中间件或handler都会受到影响。

优雅关机

package main

import (
	"context"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

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

func main() {
	router := gin.Default()
	router.GET("/", func(c *gin.Context) {
		time.Sleep(10 * time.Second)   //get请求slepp 10s
		c.String(http.StatusOK, "Welcome Gin Server")
	})

	srv := &http.Server{
		Addr:    ":8080",
		Handler: router,
	}

	go func() {
		开启一个goroutine 来监听有没有正在运行的程序
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %s\n", err)
		}
	}()

	

    等待中断信号来优雅地关闭服务器,为关闭服务器操作设置一个5秒的超时
	

    quit := make(chan os.Signal, 1)    创建一个接收信号的通道

	//
    kill 默认会发送 syscall.SIGTERM 信号
    kill -2 发送 syscall.SIGINT 信号,我们常用的Ctrl+C就是触发系统SIGINT信号
	kill -9 发送 syscall.SIGKILL 信号,但是不能被捕获,所以不需要添加它
	//

   
    signal.Notify把收到的 syscall.SIGINT或syscall.SIGTERM 信号转发给quit

	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)    此处不会阻塞
	<-quit    阻塞在此,当接收到上述两种信号时才会往下执行
	log.Println("Shutdown Server ...")
	
    创建一个5秒超时的context
	
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	5秒内优雅关闭服务(将未处理完的请求处理完再关闭服务),当前没有运行中的程序或超过5秒就退出
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("Server Shutdown: ", err)
	}

	log.Println("Server exiting")
}

自动化证书设置

依赖包:"github.com/gin-gonic/gin"

autotls.Run(r,"网址地址")

实现原理:生成一个本地密钥,发送给这个目标网站,获取该网站的私钥,然后本地验证私钥,如果验证成功,就把私钥信息保存起来,下次请求的时候利用这个私钥加密。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值