概述
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: %sn", 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,"网址地址")
实现原理:生成一个本地密钥,发送给这个目标网站,获取该网站的私钥,然后本地验证私钥,如果验证成功,就把私钥信息保存起来,下次请求的时候利用这个私钥加密。
最后
以上就是淡淡猫咪为你收集整理的gin学习笔记gin的优势请求路由验证请求参数问题中间件优雅关机的全部内容,希望文章能够帮你解决gin学习笔记gin的优势请求路由验证请求参数问题中间件优雅关机所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复