gin 框架基础知识总结

5 篇文章 0 订阅
本文详细介绍了Gin框架,它是GitHub上最受欢迎的Go Web框架。Gin以其简洁、高性能和内存效率著称。文章通过快速启动示例展示了如何创建基本路由,接着探讨了请求路由、绑定静态文件夹、参数获取、泛绑定、请求参数验证(包括结构体验证和自定义验证)、中间件使用、优雅服务关闭、模板渲染以及自动化证书配置等功能。内容全面,适合Go Web开发初学者及进阶者参考。
摘要由CSDN通过智能技术生成

gin 是什么

目前Github上Star最多的Go Web 框架

优势
  • 简单原则
  • 并发高
  • 分配内存少
快速开始
package main // 声明包

/// 程序执行顺序 导入包 ( 导入包 (...) -> 变量/常量 ->init() ) --> 变量/常量初始化 --> init() --> main()

// 导入包
import "github.com/gin-gonic/gin"

// 程序入口, 主函数
func main() {
	// 快速开始
	r := gin.Default()
	// 路由设置, 回调方法
	r.GET("/ping", func(ctx *gin.Context) {
		ctx.JSON(200, gin.H{
			"message": "pong",
		})
	})

	// 开启
	r.Run() // 默认 :8080
}

gin 基础

1. 请求路由

多种请求类型
package main

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

func main() {

	// 路由, 回调方法
	r := gin.Default()
	// GET 方法
	r.GET("/get", func(c *gin.Context) {
		c.String(200, "get")
	})
	// POST 方法
	r.POST("/post", func(c *gin.Context) {
		c.String(200, "post")
	})
	// DELETE 方法
	r.Handle("DELETE", "/delete", func(c *gin.Context) {
		c.String(200, "delete")
	})
	
	r.DELETE("/del", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "DELETE",
		})
	})
	// Any方法, 支持 GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE
	r.Any("/any", func(c *gin.Context) {
		c.String(200, "any")
	})

	r.Run()
}
绑定静态文件夹
package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {

	// 静态文件夹
	r := gin.Default()

	// 路由 文件夹
	r.Static("/assets", "./assets") // 访问 http://127.0.0.1:8080/assets/a.html
	r.StaticFS("/static", http.Dir("static")) // 访问 http://127.0.0.1:8080/static/b.html
	r.StaticFile("/favicon.ico", "./favicon.ico")

	r.Run()
}
参数作为URL
package main

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

func main() {

	// 开启实例
	r := gin.Default()

	// 参数 访问 http://127.0.0.1:8080/assets/2 返回 {"id":"2","name":"assets"}
	r.GET("/:name/:id", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"name": c.Param("name"), // 获取URL参数
			"id": c.Param("id"), // 获取URL参数
		})
	})

	// 启动
	r.Run()
}
泛绑定
package main

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

func main() {

	// 开启实例
	r := gin.Default()

	// 匹配刀所有的user前缀的请求
	// http://127.0.0.1:8080/user/****
	// http://127.0.0.1:8080/user/ashdjka
	// http://127.0.0.1:8080/user
	r.GET("/user/*action", func(c *gin.Context) {
		c.String(200, "hello")
	})

	// 启动
	r.Run()
}

2. 获取请求参数

获取GET请求参数
package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {

	// 创建实例
	r := gin.Default()

	// 请求 http://127.0.0.1:8080/test?first_name=long 返回 long:wang
	// 请求 http://127.0.0.1:8080/test?first_name=long&last_name=wei 返回 long:wei
	r.GET("/test", func(c *gin.Context) {
		firstName := c.Query("first_name")
		lastName := c.DefaultQuery("last_name", "wang") // 默认值
		c.String(http.StatusOK, "%s:%s", firstName, lastName)
	})

	// 启动
	r.Run(":8080")
}
获取POST请求参数
package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {

	// 创建实例
	r := gin.Default()

	r.POST("/test", func(c *gin.Context) {

		// 从POST请求中获取参数值
		firstName := c.PostForm("first_name")
		lastName := c.DefaultPostForm("last_name", "wei")

		c.String(http.StatusOK, "%s:%s", firstName, lastName)
	})
	// 启动
	r.Run(":8080")
}
获取Body值
package main

import (
	"bytes"
	"github.com/gin-gonic/gin"
	"io/ioutil"
	"net/http"
)

func main() {

	// 创建实例
	r := gin.Default()

	r.POST("/test", func(c *gin.Context) {
		// 数据流中获取Body
		all, err := ioutil.ReadAll(c.Request.Body)
		if err != nil {
			c.String(http.StatusBadRequest, err.Error())
			// 结束, 将程序中止
			c.Abort()
		}

		// 从数据流取出body后, 无法再通过post获取到POST请求中参数的值

		// 需要将读到的结果再回存, 再写到c.Request.Body中
		c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(all))

		firstName := c.PostForm("first_name")
		lastName := c.DefaultPostForm("last_name", "wei")

		c.String(http.StatusOK, "%s:%s:%s", firstName, lastName, string(all))
	})
	// 启动
	r.Run(":8080")
}
获取参数绑定到结构体
package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
	"time"
)

// 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("/test", handle)
	r.POST("/test", handle)

	r.Run(":8080")
}

func handle(c *gin.Context)  {

	var user Person

	// 这里是根据请求的content-type来做不同的binding操作
	err := c.ShouldBind(&user)
	// 结构体要传指针, 因为要修改值, 并且结构体字段首字母大写, 因为要包外可见
	if err != nil {
		c.String(http.StatusBadRequest, "error: %v", err)
		c.Abort()
	}

	c.String(http.StatusOK, "user: %v", user)

}

2. 验证请求参数

结构体验证

更多验证规则官方验证规则

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

// Person 定义结构体
type Person struct {
	// 验证规则 多个条件都须满足用,分割 多个条件任意满足用|分割, 多个条件时,和|两边不要有空格
	Name string `form:"name" binding:"required"` // 结构体绑定通过form标签, 结构体字段验证通过binding标签
	Address string `form:"address" binding:"required"` // 结构体绑定通过form标签, 结构体字段验证通过binding标签
	Age int `form:"age" binding:"required,gt=10"` // 结构体绑定通过form标签, 结构体字段验证通过binding标签
}

func main() {

	// 创建实例
	r := gin.Default()

	// 请求 http://127.0.0.1:8080/testing?name=chao&age=10&address=hangzhou
	// 返回 error: Key: 'Person.Age' Error:Field validation for 'Age' failed on the 'gt' tag
	r.GET("/testing", handle)

	r.Run(":8080")
}

func handle(c *gin.Context)  {

	var user Person

	// 结构体要传指针, 因为要修改值, 并且结构体字段首字母大写, 因为要包外可见
	if err := c.ShouldBind(&user); err != nil {
		// 这里是根据请求的content-type来做不同的binding操作
		c.String(http.StatusInternalServerError, "error: %v", err)
		c.Abort() // Note that this will not stop the current handler, call Abort to ensure the remaining handlers for this request are not called.
		return
	}

	c.String(http.StatusOK, "user: %v", user)

}
自定义验证
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10"
	"net/http"
	"time"
)

// Booking 定义结构体
type Booking struct {
	// 验证规则 多个条件都须满足用,分割 多个条件任意满足用|分割 多个条件时,和|两边不要有空格
	CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` // 结构体绑定通过form标签, 结构体字段验证通过binding标签
	CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` // 结构体绑定通过form标签, 结构体字段验证通过binding标签
}

// 自定义验证规则
func bookableDate(fl validator.FieldLevel) bool {

	// 类型断言, 是否 time.Time 类型
	if date, ok := fl.Field().Interface().(time.Time); ok {
		// 当前时间, 格式为 Time
		today := time.Now()
		// 转成时间戳格式
		if today.Unix() < date.Unix() {
			return true
		}
	}

	return false
}

func main() {

	// 创建实例
	r := gin.Default()

	// 注册验证器
	if validate, ok := binding.Validator.Engine().(*validator.Validate); ok {
		// 将 tag 中的 key, 与自定义规则方法绑定
		err := validate.RegisterValidation("bookabledate", bookableDate)
		if err != nil {
			fmt.Println(err)
			return
		}
	}

	// 请求 http://127.0.0.1:8080/bookable?check_in=2021-11-04&check_out=2021-11-05
	// 返回 {"error": "Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}
	// 请求 http://127.0.0.1:8080/bookable?check_in=2021-11-08&check_out=2021-11-02
	// 返回 {"error": "Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
	r.GET("/bookable", handle)

	r.Run(":8080")
}

func handle(c *gin.Context)  {

	var booking Booking

	// 结构体要传指针, 因为要修改值, 并且结构体字段首字母大写, 因为要包外可见
	if err := c.ShouldBind(&booking); err != nil {
		// 这里是根据请求的content-type来做不同的binding操作
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": err.Error(),
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message": "ok",
		"booking": booking,
	})

}
升级验证-支持多语言错误信息
package main

import (
	"github.com/gin-gonic/gin"
	en2 "github.com/go-playground/locales/en" // 英文语言包
	zh2 "github.com/go-playground/locales/zh" // 中文语言包
	ut "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"
	"net/http"
)


// Person 定义结构体
type Person struct {
	// 验证规则 多个条件都须满足用,分割 多个条件任意满足用|分割 多个条件时,和|两边不要有空格
	// 使用多语言包, 结构体验证 tag 不再是 binding, 而是 validate
	Name string `form:"name" validate:"required" json:"name"`
	Age int `form:"age" validate:"required,gt=10" json:"age"`
}

func main() {

	// 创建验证器
	v := validator.New()

	// 支持的语言
	zh := zh2.New()
	en := en2.New()
	// 创建翻译器
	translator := ut.New(zh, en)

	// 创建实例
	r := gin.Default()

	// 请求 http://127.0.0.1:8080/bookable?name=chao&age=20, 返回 {"data":{"name":"chao","age":20},"message":"ok"}
	// 请求 http://127.0.0.1:8080/bookable?age=20, 返回 {"error":["Name为必填字段"]}
	// 请求 http://127.0.0.1:8080/bookable?name=chao&age=10&locale=en, 返回 {"error":["Age must be greater than 10"]}
	// 请求 http://127.0.0.1:8080/bookable, 返回 {"error":["Name为必填字段","Age为必填字段"]}
	
	r.GET("/bookable", func(c *gin.Context) {
		// 接收指定语言参数, 设置默认语言
		locale := c.DefaultQuery("locale", "zh")
		trans, _ := translator.GetTranslator(locale)
		switch locale {
		case "zh":
			zh_translations.RegisterDefaultTranslations(v, trans)
		case "en":
			en_translations.RegisterDefaultTranslations(v, trans)
		default:
			zh_translations.RegisterDefaultTranslations(v, trans)
		}

		var p Person

		// 绑定结构体
		if err := c.ShouldBind(&p); err != nil {
			// 结构体要传指针, 因为要修改值, 并且结构体字段首字母大写, 因为要包外可见
			c.JSON(http.StatusInternalServerError, gin.H{
				"error": err.Error(),
			})
			c.Abort()
			return
		}

		// 验证结构体
		if err := v.Struct(p); err != nil {
			errs := err.(validator.ValidationErrors)
			var sliceErrors []string
			for _, e := range errs {
				// 将错误翻译成相应的语言
				sliceErrors = append(sliceErrors, e.Translate(trans))
			}
			c.JSON(http.StatusInternalServerError, gin.H{
				"error": sliceErrors,
			})
			c.Abort()
			return
		}

		c.JSON(http.StatusOK, gin.H{
			"message": "ok",
			"data": p,
		})
	})

	r.Run(":8080")
}

3. 中间件

中间件是介于 gin 服务器和执行的回调函数之间的中间层, 可以作为请求拦截和日志打印

使用 gin 中间件
package main

import (
	"github.com/gin-gonic/gin"
	"io"
	"net/http"
	"os"
)

func main() {

	// 指定日志写入文件
	file, _ := os.Create("gin.log")
	gin.DefaultWriter = io.MultiWriter(file)
	gin.DefaultErrorWriter = io.MultiWriter(file)

	// 创建实例, gin.Default()方法默认已经实现了Logger()和Recovery()两个中间件
	r := gin.New()
	// 设置中间件
	// gin.Logger() 添加日志中间件 打印到终端, 或者写入文件
	// gin.Recovery() 添加 panic 恢复中间件, 遇到 panic 不会中断程序, 如果不加该中间件会直接中断程序结束运行
	r.Use(gin.Logger(), gin.Recovery())

	// [GIN] 2021/11/05 - 22:45:53 | 200 |       997.8µs |       127.0.0.1 | GET      "/get"
	r.GET("/get", func(c *gin.Context) {

		name := c.DefaultQuery("name", "chao")

		c.JSON(http.StatusOK, gin.H{
			"message": "OK",
			"data": name,
		})
	})

	r.Run(":8080")
}

自定义 ip 白名单中间件
package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

// 白名单
var ipList = []string{
	"127.0.0.2",
}

// IPAuthMiddleware 验证IP
func IPAuthMiddleware() gin.HandlerFunc {

	return func(c *gin.Context) {
		// 获取客户端IP
		clientIP := c.ClientIP()

		flag := false

		for _, ip := range ipList {
			if ip == clientIP {
				// 在列表中
				flag = true
				break
			}
		}

		// 不在白名单中
		if !flag {
			c.JSON(http.StatusUnauthorized, gin.H{
				"message": "no auth",
			})
			c.Abort()
		}

	}

}
func main() {

	// 创建实例
	r := gin.Default()

	// 设置自定义中间件
	r.Use(IPAuthMiddleware())

	// 请求 http://127.0.0.1:8080/get
	// 返回 {"message":"no auth"}
	r.GET("/get", func(c *gin.Context) {

		name := c.DefaultQuery("name", "chao")

		c.JSON(http.StatusOK, gin.H{
			"message": "OK",
			"data": name,
		})
	})

	r.Run(":8080")
}

4. 优雅关停服务器

在这里插入图片描述
传统的 gin.Run 是阻塞的, 一直监听端口, 关闭后服务直接结束, 而优雅关闭服务中 server.ListenAndServer是不阻塞的, 用 os.Signal 去阻塞, 当监听到 os.Signal 信号, 将超时的上下文传送到 Shutdown 方法中, 然后退出, 在监听到信号之后到正式退出之前, 会关闭这个时间段内重新进来的连接请求, 另外在超时时间内把之前已经接收到的请求执行完毕

package main

import (
	"context"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {

	// 创建实例
	r := gin.Default()

	// 优雅关停, 即当服务器关闭时, 不会立即关闭对请求链接的响应, 而是将超时时间内的重新链接关闭, 之前的链接会执行完
	r.GET("/test", func(c *gin.Context) {
		time.Sleep(10 * time.Second)
		c.JSON(http.StatusOK, gin.H{
			"message": "OK",
		})
	})

	// 创建 http服务
	server := &http.Server{
		Addr:              ":8085",
		Handler:           r,
	}

	// 开启协程, 监听服务
	go func() {
		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalln("listen error: ", err)
		}
	}()

	// 设置请求拦截, 将信号写入管道
	quit := make(chan os.Signal)
	// 捕获退出和中止信号, 并写入管道中
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	// 阻塞, 直到通道中有值
	<-quit

	log.Println("shutdown server")

	// 创建超时的上下文, 即监听到关闭的信号后, 设置10秒的超时缓冲段
	ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
	defer cancel()

	// 真正关闭服务的操作
	if err := server.Shutdown(ctx); err != nil {
		log.Fatalln("shutdown error: ", err)
	}

	log.Fatalln("server exiting")
}

5. 模板渲染

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {

	// 创建实例
	r := gin.Default()

	// 载入模板
	r.LoadHTMLGlob("template/*")

	// 路由回调
	r.GET("/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", gin.H{
			"title": "welcome",
		})
	})

	// 阻塞监听
	r.Run()

}

6. 自动化证书配置

package main

import (
	"github.com/gin-gonic/autotls"
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {

	// 创建实例
	r := gin.Default()

	// 路由回调
	r.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "test")
	})

	// 给域名 (外网可访问) 自动配置SSL证书
	autotls.Run(r, "www.***.**")
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敲代码der

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值