Gin框架入门

Gin框架入门_第1张图片
环境:
vscode,go1.14.3,gin1.4.0,

[root@go ~]# cat /etc/profile|tail -5
# go enviroment
export GOROOT=/usr/local/go
export GOPATH=~/golib:~/goproject
export GOBIN=~/gobin
export PATH=$PATH:$GOROOT/bin:$GOBIN

一、安装Gin及快速开始
1.1 安装及环境配置

[root@go ~]# mkdir $GOPATH/src/github/jstang007/gin_test_project
[root@go ~]# cd $GOPATH/src/github/jstang007/gin_test_project 
[root@go gin_test_project]# export GO111MODULE=on #开启go module环境变量 
[root@go gin_test_project]# go mod init 
[root@go gin_test_project]# go get -v github.com/gin-goniv/gin@v1.4

1.2 快速启动

[root@go gin_test_project]# mkdir start
[root@go gin_test_project]# touch start/main.go
[root@go gin_test_project]# vim start/main.go
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() // listen and serve on 0.0.0.0:8080
}
[root@go gin_test_project]# go run start/main.go
[root@go gin_test_project]# curl -X GET 127.0.0.1:8080/ping
pong

二、请求路由
2.1 设置多种请求类型
在项目目录下创建route_type/main.go文件,并编辑

package main
import "github.com/gin-gonic/gin"
func main() {
	r := gin.Default()
	r.GET("/get", func(c *gin.Context) {
		c.String(200, "GET")
	})
	r.POST("/post", func(c *gin.Context) {
		c.String(200, "post")
	})
	r.Handle("DELETE", "/delete", func(c *gin.Context) {
		c.String(200, "delete")
	})
	r.Any("/any", func(c *gin.Context) { //支持get\post\del\put\alter
		c.String(200, "any")
	})
	r.Run()
}

测试:

[root@go gin_test_project]# curl -X DELETE127.0.0.1:8080/delete
delete
[root@go gin_test_project]# curl -X PUT 127.0.0.1:8080/any
any

2.2 绑定静态文件夹
创建文件夹route_static,该文件夹树形结构:

- route_static
	- accets
   		a.html
	- static
   		b.html
	favicon.ico
	main.go

编辑main.go文件

package main
import (
	"net/http"
	"github.com/gin-gonic/gin"
)
func main() {
	r := gin.Default()
	r.Static("/accets", "./accets")
	r.StaticFS("/static", http.Dir("static"))
	r.StaticFile("/favicon.ico", "./favicon.ico") //设置单个静态文件
	r.Run()
}

2.3 参数作为URI
创建目录route_uri,route_uri/main.go

package main
import "github.com/gin-gonic/gin"
func main() {
	r := gin.Default()
	r.GET("/:name/:id", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"name": c.Param("name"), //value -key
			"id":   c.Param("id"),  //末尾一定要由逗号
		})
	})
	r.Run()
} //http://192.168.27.145:8080/golang/1

2.4 泛绑定
创建route_generic目录和route_generic/main.go文件

package main

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

func main() {
	r := gin.Default()
	r.GET("/user/*action", func(c *gin.Context) {
		c.String(200, "hello world")
	})
	r.Run()
} //http://192.168.27.145:8080/user/111
//http://192.168.27.145:8080/user/xxx

三、获取请求参数
3.1 获取GET的请求参数
创建param_get文件夹和param_get/main.go文件

package main
import (
	"net/http"
	"github.com/gin-gonic/gin"
)
func main() {
	r := gin.Default()
	r.GET("/test", func(c *gin.Context) {
		firstName := c.Query("first_name")                           //key
		lastName := c.DefaultQuery("last_name", "last_default_name") //key, 默认value
		c.String(http.StatusOK, "%s, %s", firstName, lastName)
	})
	r.Run()
} //http://192.168.27.145:8080/test?first_name=xxx&last_name=yyy
//http://192.168.27.145:8080/test?first_name=xxx

3.2 获取POST的请求
创建param_post文件夹和param_post/main.go文件

package main
import (
	"net/http"
	"github.com/gin-gonic/gin"
)
func main() {
	r := gin.Default()
	r.POST("/test", func(c *gin.Context) {
		firstName := c.DefaultPostForm("first_name","golang")//key, 默认value
		lastName := c.PostFrom("last_name")
		c.String(http.StatusOK, "%s, %s", firstName, lastName)
	})
	r.Run()
}//curl -X POST 127.0.0.1:8080/test?first_name=xxx

3.3 获取Body请求参数

package main
import (
	"bytes"
	"io/ioutil"
	"net/http"
	"github.com/gin-gonic/gin"
)
func main() {
	r := gin.Default()
	r.POST("/test", func(c *gin.Context) {
		bodyBytes, err := ioutil.ReadAll(c.Request.Body)
		if err != nil {
			c.String(http.StatusBadRequest, err.Error())
			c.Abort()
		}
		//调用完read之后,PostFrom里数值被清空,需要重新写入到c.Request.Body
		c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
		firstName := c.PostForm("first_name")
		lastName := c.DefaultPostForm("last_name", "last_default_name")
		c.String(http.StatusOK, "%s, %s, %s", firstName, lastName, string(bodyBytes))
	})
	r.Run()
} // curl -X POST 127.0.0.1:8080/test -d '{"name":"golang"}'
//curl -X POST "127.0.0.1:8080/test" -d 'first_name=xxx&last_name=yyy'
//xxx, yyy, first_name=xxx&last_name=yyy

3.4 获取参数bind绑定结构

package main
import (
	"fmt"
	"time"
	"github.com/gin-gonic/gin"
)
//Person 人
type Person struct {
	Name     string    `form:"name"`
	Address  string    `form:"address"`
	Birthday time.Time `form:"birthday" time_format:"2006-01-02"`
}
func main() {
	r := gin.Default()
	r.GET("/testing", testing)
	r.POST("/testing", testing)
	r.Run()
}
func testing(c *gin.Context) {
	var person Person
	//这里是根据请求content-type来做不同的binding操作
	if err := c.ShouldBind(&person); err == nil {
		fmt.Printf("====%v====", person)
		c.String(200, "%v", person)
	} else {
		c.String(200, ":%v", err.Error())
	}
}
//curl -X GET "http://127.0.0.1:8080/testing?name=xxx&address=China&birthday=2000-04-25"
//{xxx China 2000-04-25 00:00:00 +0800 CST}
//curl -X POST 127.0.0.1:8080/testing -d "name=jack&address=China&birthday=2020-02-02"
//{jack China 2020-02-02 00:00:00 +0800 CST}
//curl -H "Content-type:application/json" -X POST 127.0.0.1:8080/testing -d '{"name":"rose","address":"US"}'
//{rose US}

四、验证请求参数
获取的参数是否合法,如何定义验证规则
4.1 结构体binding验证

package main
import "github.com/gin-gonic/gin"
//Person A
type Person struct {
	Age     int    `form:"age" binding:"required,gt=10"`
	Name    string `form:"name" binding:"required"`
	Address string `form:"address" binding:"required"`
}
func main() {
	r := gin.Default()
	r.GET("/testing", func(c *gin.Context) {
		var person Person
		if err := c.ShouldBind(&person); err != nil {
			c.String(500, "%v", err.Error())
			c.Abort()
			return
		}
		c.String(200, "%v", person)
	})
	r.Run()
}

// curl -X GET "http://127.0.0.1:8080/testing?age=11&name=knho&address=cn"
//{11 knho cn}
// curl -X GET "http://127.0.0.1:8080/testing?age=10&name=knho&address=cn"
//Key: 'Person.Age' Error:Field validation for 'Age' failed on the 'gt' tag{10 knho cn}
// curl -X GET "http://127.0.0.1:8080/testing?age=12&name=knho"
//Key: 'Person.Address' Error:Field validation for 'Address' failed on the 'required' tag
//https://godoc.org/gopkg.in/go-playground/validator.v8

4.2 自定义验证

package main
import (
	"net/http"
	"reflect"
	"time"
	"gopkg.in/go-playground/validator.v8"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
)
//Booking x
type Booking struct {
	CheckIn  time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
	CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}

func bookableDate(
	v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
	field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
) bool {
	if date, ok := field.Interface().(time.Time); ok {
		today := time.Now()
		if today.Unix() > date.Unix() {
			return false
		}
	}
	return true
}
func main() {
	r := gin.Default()
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		v.RegisterValidation("bookabledate", bookableDate)
	}
	r.GET("/bookable", getBookable)
	r.Run()
}
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()})
	}
}

//1、当check_in是未来日期,并比check_out早时
// curl -X GET "127.0.0.1:8080/bookable?check_in=2020-07-01&check_out=2020-07-08"
// {"message":"Booking dates are valid!"}
//2、当check_in是未来日期,并比check_out晚时
//curl -X GET "127.0.0.1:8080/bookable?check_in=2020-07-09&check_out=2020-07-08"
// {"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
//3、当check_in是已过日期
//curl -X GET "127.0.0.1:8080/bookable?check_in=2020-05-09&check_out=2020-07-08"
// {"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}

4.3 支持多语言错误信息

package main
import (
	"github.com/gin-gonic/gin"
	"github.com/go-playground/locales/en"
	"github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"gopkg.in/go-playground/validator.v9"
	en_translations "gopkg.in/go-playground/validator.v9/translations/en"
	zh_translations "gopkg.in/go-playground/validator.v9/translations/zh"
)
// Person x
type Person struct {
	Age     int    `form:"age" validate:"required,gt=10"`
	Name    string `form:"name" validate:"required"`
	Address string `form:"address" validate:"required"`
}
// ut
var (
	Uni      *ut.UniversalTranslator
	Validate *validator.Validate
)
//验证信息多语言化
func main() {
	Validate = validator.New() //创建验证器
	en := en.New()
	zh := zh.New()
	Uni = ut.New(zh, en) //创建翻译器
	r := gin.Default()
	r.GET("/test", startPage)
	r.POST("/test", startPage)
	r.Run()
}
func startPage(c *gin.Context) {
	locale := c.DefaultQuery("locale", "zh") //获取uri中locale的值
	trans, _ := Uni.GetTranslator(locale)    //在翻译器中挑选locale
	switch locale {
	case "zh":
		zh_translations.RegisterDefaultTranslations(Validate, trans) //将对应翻译器注册到验证器中
	case "en":
		en_translations.RegisterDefaultTranslations(Validate, trans)
	default:
		zh_translations.RegisterDefaultTranslations(Validate, trans)
	}
	var person Person
	if err := c.ShouldBind(&person); err != nil { //绑定结构体
		c.String(500, "%v\n", err.Error())
		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\n", sliceErrs)
		c.Abort()
		return
	}
	c.String(200, "%v\n", person)
}

五、中间件
基于gin服务器和执行的回调函数的中间层,可以作为请求拦截、日志打印等功能
5.1 使用Gin中间件

package main
import (
	"io"
	"os"
	"github.com/gin-gonic/gin"
)
func main() {
	f, _ := os.Create("gin.log")
	gin.DefaultWriter = io.MultiWriter(f)
	gin.DefaultErrorWriter = io.MultiWriter(f)
	r := gin.New()                      //default默认实现两个中间件: Logger+Recovery
	r.Use(gin.Logger(), gin.Recovery()) //设置中间件
	r.GET("/test", func(c *gin.Context) {
		name := c.DefaultQuery("name", "middleware_gin")
		panic("主动触发错误\n") //没有使用Recovery中间间将会死掉服务, Recovery会捕获错误,让服务不至于挂掉
		c.String(200, "%s", name)
	})
	r.Run()
}

5.2 自定义ip白名单中间件

package main
import "github.com/gin-gonic/gin"
//IPAuthMiddleware x
func IPAuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		ipList := []string{"127.0.0.1"}
		flag := false
		clientIP := c.ClientIP()
		for _, host := range ipList {
			if clientIP == host {
				flag = true
				break
			}
		}
		if !flag {
			c.String(401, "%s, not in iplist", clientIP)
			c.Abort()
		}
	}
}
func main() {
	r := gin.Default()
	r.Use(IPAuthMiddleware())
	r.GET("/test", func(c *gin.Context) {
		c.String(200, "golang")
	})
	r.Run()
}
//curl -X GET "http://192.168.27.145:8080/test"
// 192.168.27.145, not in iplist
//curl -X GET "http://127.0.0.1:8080/test"
//golang

六、其他补充
6.1 优雅关停
指服务停止时,如果请求未处理完毕,会有一个超时等待时间,去完成后续的处理,保证服务不在中间中断停止
原始服务器关闭和优雅关闭区别:
Gin框架入门_第2张图片

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

	"github.com/gin-gonic/gin"
)
func main() {
	r := gin.Default()
	r.GET("test", func(c *gin.Context) {
		time.Sleep(10 * time.Second)
		c.String(200, "golang")
	})
	srv := &http.Server{
		Addr:    ":8085",
		Handler: r,
	}
	go func() { //将srv放入协程
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %s\n", err)
		}
	}()
	quit := make(chan os.Signal)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) //无法捕获kill -9
	<-quit                                               //一直等待关闭
	log.Println("shutdown server ...")

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel() //直到cancel方法执行完
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatalf("server shutdown:", err)
	}
	log.Println("server exiting")
}

访问url后立即结束服务(ctrl+c)
6.2 模板渲染

目录:
- teamplate_drawing
	- teamplate
		index.html
  	main.go
package main
import "github.com/gin-gonic/gin"
func main() {
	r := gin.Default()
	r.LoadHTMLGlob("teamplate/*") //将模板文件夹载入到gin框架
	r.GET("/", func(c *gin.Context) {
		c.HTML(200, "index.html", gin.H{
			"title": "index.html",
		})
	})
	r.Run()
}

6.3 自动证书配置
指无需配置任何证书,即可自动下载证书,过期自动续约的功能

package main

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

/*
* 废弃
engine := gin.Default()
//证书位置使用的是相对路径
_ = http.ListenAndServeTLS(":443", "cert/www.domain.cn.crt",
		"cert/www.domain.cn.key", engine)
*/
func main() {
	r := gin.Default()
	r.GET("/test", func(c *gin.Context) {
		c.String(200, "golang")
	})
	autotls.Run(r, "zan71.com")
}

你可能感兴趣的:(Go)