# Day11 Gin框架之快速指南

Gin轻量级Web框架,简单易用,中文文档 (opens new window)齐全

# Gin框架入门

  • 安装依赖
go get -u github.com/gin-gonic/gin
  • 快速入门
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() // 监听并在 0.0.0.0:8080 上启动服务
}

# Gin框架参数校验

package blog

import (
	"achilles/logger"
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

func querystringHandler(c *gin.Context) {
	username := c.DefaultQuery("username", "linda") // 默认 "linda"
	address := c.Query("address")                   // 默认零值

	c.JSON(http.StatusOK, gin.H{
		"username": username,
		"address":  address,
	})
}

func formHandler(c *gin.Context) {
	// DefaultPostForm取不到值时会返回指定的默认值
	username := c.DefaultPostForm("username", "linda") // 默认 "linda"
	address := c.PostForm("address")                   // 默认零值

	c.JSON(http.StatusOK, gin.H{
		"username": username,
		"address":  address,
	})
}

func jsonHandler(c *gin.Context) {
	// 注意:下面为了举例子方便,暂时忽略了错误处理
	b, _ := c.GetRawData() // 从c.Request.Body读取请求数据

	// 定义map或结构体
	var m map[string]interface{}
	// 反序列化
	_ = json.Unmarshal(b, &m)

	fmt.Printf("type: %T\nvalue:%#v\n", m, m)
	logger.Logger.Info("JSON数据", zap.Any("m", m))

	c.JSON(http.StatusOK, m)
}

func pathHandler(c *gin.Context) {
	username := c.Param("username")
	address := c.Param("address")

	c.JSON(http.StatusOK, gin.H{
		"username": username,
		"address":  address,
	})
}

type Login struct {
	// form:"user" 校验 FormPost提交数据
	// json:"user" 校验json提交数据
	// binding:"required"  如果没有改选项,默认零值
	Uesr     string `form:"user" json:"user" binding:"required"`
	Password string `json:"password"`
}

func shoudbindHandler(c *gin.Context) {

	var login Login
	// ShouldBind()会根据请求的Content-Type自行选择绑定器
	if err := c.ShouldBind(&login); err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": 1,
			"msg":  "参数错误",
		})

		logger.Logger.Error("参数错误",
			zap.String("path", c.Request.URL.Path),
			zap.String("error", err.Error()))
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"username": login.Uesr,
		"password": login.Password,
	})
}

ShouldBind会按照下面的顺序解析请求中的数据完成绑定:

  • 如果是 GET 请求,只使用 Form 绑定引擎(query);
  • 如果是 POST 请求,首先检查 content-type 是否为 JSON 或 XML,然后再使用 Form(form-data);

# 路由组与路由重定向

func Routers(e *gin.Engine) {
	// 路由组
	group := e.Group("/blog")
	{
		group.GET("/querystring", querystringHandler)
		group.GET("/path/:username/:address", pathHandler)
		group.POST("/form", formHandler)
		group.POST("/json", jsonHandler)
		group.GET("/shoudbind", shoudbindHandler)

		// HTTP重定向
		group.GET("/redirect1", func(c *gin.Context) {
			c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/")
		})
		// 路由重定向
		group.GET("/redirect2", func(c *gin.Context) {
			c.Request.URL.Path = "/querystring"
			e.HandleContext(c)
		})
	}
}

# Gin框架中间件

  • Gin框架可以在处理请求的过程中,加入钩子(Hook)函数,这个钩子函数就叫中间件;
  • 中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等;

# 定义中间件

Gin中的中间件必须是一个gin.HandlerFunc类型

package middleware

import (
	"achilles/logger"
	"bytes"
	"time"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

// MiddlewareRequetCost 记录接口耗时的中间件
func MiddlewareRequetCost(c *gin.Context) {
	start := time.Now()
	c.Set("name", "linda") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值

	// 调用该请求的剩余处理程序
	c.Next()

	// 不调用该请求的剩余处理程序
	// c.Abort()

	// 计算耗时
	cost := time.Since(start)
	logger.Logger.Info("耗时统计", zap.Any("cost", cost), zap.String("path", c.Request.URL.Path))

}

type bodyLogWriter struct {
	gin.ResponseWriter               // 嵌入gin框架ResponseWriter
	body               *bytes.Buffer // 我们记录用的response
}

// Write 写入响应体数据
func (w bodyLogWriter) Write(b []byte) (int, error) {
	w.body.Write(b)                  // 我们记录一份
	return w.ResponseWriter.Write(b) // 真正写入响应
}

// GinBodyLogMiddleware 一个记录返回给客户端响应体的中间件
// https://stackoverflow.com/questions/38501325/how-to-log-response-body-in-gin
func GinBodyLogMiddleware(c *gin.Context) {
	blw := &bodyLogWriter{body: bytes.NewBuffer([]byte{}), ResponseWriter: c.Writer}
	c.Writer = blw // 使用我们自定义的类型替换默认的

	c.Next() // 执行业务逻辑

	// 事后按需记录返回的响应
	logger.Logger.Info("响应数据", zap.String("body", blw.body.String()))
}

# 注册中间

// 注册一个全局中间件
func Init() *gin.Engine {
	// r := gin.Default()
	r := gin.New()
	// 注册一个全局中间件
	r.Use(middleware.MiddlewareRequetCost, middleware.GinBodyLogMiddleware)
	
	for _, opt := range options {
		opt(r)
	}
	return r
}

// 还可以注册到 group 或者 具体的handler
上次更新: 12/12/2022, 8:55:46 PM