Gin 
Gin 是一个 Go 语言写的 Web 框架。
1 安装 
go get -u github.com/gin-gonic/gin2 HelloWorld 
package main
import (
	"github.com/gin-gonic/gin"
	"net/http"
)
func helloWorld(context *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"message": "world",
	})
}
func main() {
	router := gin.Default()
	router.GET("/hello", helloWorld)
	router.Run("localhost:8000")
}3 路由分组 
假设现在有这些请求路径:/goods/list、goods/add、goods/del,在不使用路由分组的情况下,通常会这样写:
router := gin.Default()
router.GET("/goods/list", goodsList)
router.POST("/goods/add", addGoods)
router.POST("/goods/del", delGoods)这些路径都包含 /goods,可以通过路由分组进行管理:
router := gin.Default()
goodsGroup := router.Group("/goods")
goodsGroup.GET("/list", goodsList)
goodsGroup.POST("/add", addGoods)
goodsGroup.POST("/del", delGoods)4 获取 URL 上的变量 
假设现在需要通过 /goods/[商品ID] 访问对应商品的详情,通过如下方法可以识别 URL 中的 ID 变量:
router := gin.Default()
router.GET("/goods/:id", func(context *gin.Context) {
	id := context.Param("id")
	context.JSON(http.StatusOK, gin.H{
		"id": id,
	})
})假设现在想通过 /goods/[商品ID]/[动作] 来对商品进行操作,可以这样做:
router.GET("/goods/:id/:action", func(context *gin.Context) {
	id := context.Param("id")
	action := context.Param("action")
	context.JSON(http.StatusOK, gin.H{
		"id":     id,
		"action": action,
	})
})### 请求
GET http://localhost:8000/goods/123/delete
### 响应
{
  "action": "delete",
  "id": "123"
}通过 /goods/:id/*action 也能实现,但是和 /:action 有些区别:
router.GET("/goods/:id/*action", func(context *gin.Context) {
	id := context.Param("id")
	action := context.Param("action")
	context.JSON(http.StatusOK, gin.H{
		"id":     id,
		"action": action,
	})
})### 请求
GET http://localhost:8000/goods/123/delete
### 响应
{
  "action": "/delete",
  "id": "123"
}
### 请求
GET http://localhost:8000/goods/123/delete/test
### 响应
{
  "action": "/delete/test",
  "id": "123"
}
### 请求
GET http://localhost:8000/goods/123
### 响应
{
  "action": "/",
  "id": "123"
}
### 请求
GET http://localhost:8000/goods/123/
### 响应
{
  "action": "/",
  "id": "123"
}还可以直接绑定 Uri 到一个结构体:
type Goods struct {
	Id   int    `uri:"id"   binding:"required"`
	Name string `uri:"name" binding:"required"`
}
func main() {
	router := gin.Default()
	router.GET("/goods/:id/:name", func(context *gin.Context) {
		var goods Goods
		if err := context.ShouldBindUri(&goods); err != nil {
			context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		context.JSON(http.StatusOK, gin.H{
			"id":   goods.Id,
			"name": goods.Name,
		})
	})
	router.Run("localhost:8000")
}GET http://localhost:8000/goods/123/abc
# 响应
{
  "id": 123,
  "name": "abc"
}5 获取表单信息 
5.1 Get 请求 
func main() {
	router := gin.Default()
	router.GET("/hello", hello)
	router.Run("localhost:8000")
}
func hello(context *gin.Context) {
	// 如果参数没取到,则使用空字符串""
	lang := context.Query("lang")
	// 如果参数没取到,则使用默认值"Gin"
	framework := context.DefaultQuery("framework", "Gin")
	context.JSON(http.StatusOK, gin.H{
		"lang":      lang,
		"framework": framework,
	})
}测试:
GET http://localhost:8000/hello
# 响应
{
  "framework": "Gin",
  "lang": ""
}GET http://localhost:8000/hello?lang=Java&framework=Spring
# 响应
{
  "framework": "Spring",
  "lang": "Java"
}5.2 Post 请求 
func main() {
	router := gin.Default()
	router.GET("/hello", hello)   
	router.POST("/hello", hello)  
	router.Run("localhost:8000")
}
func hello(context *gin.Context) {
	lang := context.Query("lang")                             
	lang := context.PostForm("lang")                          
	framework := context.DefaultQuery("framework", "Gin")     
	framework := context.DefaultPostForm("framework", "Gin")  
	context.JSON(http.StatusOK, gin.H{
		"lang":      lang,
		"framework": framework,
	})
}测试:
### 请求
POST http://localhost:8000/hello
Content-Type: application/x-www-form-urlencoded
lang = Go &
framework = Gin
# 响应
{
  "framework": "Gin",
  "lang": "Go"
}6 Protobuf 渲染 
定义 Protobuf 消息:
syntax = "proto3";
option go_package = '.;proto';
message Teacher {
  string name = 1;
  repeated string courses = 2;
}生成 Go 包,在程序中导入使用:
func main() {
	router := gin.Default()
	router.GET("/hello", hello)
	router.Run("localhost:8000")
}
func hello(context *gin.Context) {
	user := &proto.Teacher{
		Name:    "zhang",
		Courses: []string{"Gin", "GoLang"},
	}
	context.ProtoBuf(http.StatusOK, user)
}7 表单验证 
type SignUpInfo struct {
	Username   string `json:"username" binding:"required,min=3,max=20"`        // 必传字段,要求 3 <= length <= 20
	Password   string `json:"password" binding:"required,min=8,max=20"`        // 必传字段,要求 3 <= length <= 20
	RePassword string `json:"rePassword" binding:"required,eqfield=Password"`  // 必传字段,要求和 password 字段一致
	Email      string `json:"email" binding:"required,email"`                  // 必传字段,要求符合邮箱格式
	Age        uint8  `json:"age" binding:"gte=0,lte=120"`                     // 要求 0 <= age <= 120
}
func main() {
	router := gin.Default()
	router.POST("/signUp", signUp)
	router.Run("localhost:8000")
}
func signUp(context *gin.Context) {
	var info SignUpInfo
	if err := context.ShouldBindJSON(&info); err != nil {
		context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	context.JSON(http.StatusOK, gin.H{"msg": "注册成功"})
}验证不通过时,返回的信息是英文的,如下所示:
{
    "error": "
		Key: 'SignUpInfo.Username' Error:Field validation for 'Username' failed on the 'min' tag\n
		Key: 'SignUpInfo.RePassword' Error:Field validation for 'RePassword' failed on the 'eqfield' tag\n
		Key: 'SignUpInfo.Email' Error:Field validation for 'Email' failed on the 'email' tag\n
		Key: 'SignUpInfo.Age' Error:Field validation for 'Age' failed on the 'lte' tag
	"
}可将错误信息转成中文:
package main
import (
	"errors"
	"fmt"
	"net/http"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/locales/en"
	"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"
)
func InitTrans(locale string) (err error) {
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		zhT := zh.New()
		enT := en.New()
		uni := ut.New(zhT, zhT, enT)
		trans, ok = uni.GetTranslator(locale)
		if !ok {
			return errors.New("translator not found")
		}
		switch locale {
		case "zh":
			if err := zh_translations.RegisterDefaultTranslations(v, trans); err != nil {
				return err
			}
		case "en":
			if err := en_translations.RegisterDefaultTranslations(v, trans); err != nil {
				return err
			}
		}
	}
	return nil
}
var trans ut.Translator
func main() {
	if err := InitTrans("zh"); err != nil {
		fmt.Println(err)
	}
	router := gin.Default()
	router.POST("/signUp", signUp)
	router.Run("localhost:8000")
}
func signUp(context *gin.Context) {
	var info SignUpInfo
	if err := context.ShouldBindJSON(&info); err != nil {
		errs, _ := err.(validator.ValidationErrors)
		context.JSON(http.StatusBadRequest, gin.H{"error": errs.Translate(trans)})
		return
	}
	context.JSON(http.StatusOK, gin.H{"msg": "注册成功"})
}此时,错误信息将转为中文:
{
    "error": {
        "SignUpInfo.Age": "Age必须小于或等于120",
        "SignUpInfo.Email": "Email必须是一个有效的邮箱",
        "SignUpInfo.RePassword": "RePassword必须等于Password",
        "SignUpInfo.Username": "Username长度必须至少为3个字符"
    }
}8 中间件 
func MyLogger() gin.HandlerFunc {
	return func(context *gin.Context) {
		t := time.Now()
		// context.Abort()  终止请求
		context.Next()   // 处理请求
		latency := time.Since(t)
		log.Print(latency)
	}
}
func main() {
	router := gin.New()
	router.Use(gin.Logger())  // 全局中间件
	router.GET("/signUp", MyLogger(), signUp)  // 路由中间件
	router.Run("localhost:8000")
}中间件后续逻辑的执行终止,必须使用 context.Abort(),直接 return 无法阻止执行:
func MyLogger() gin.HandlerFunc {
	return func(context *gin.Context) {
		return  // 后续逻辑依旧会被执行
		context.Next()
	}
}从添加中间件的 Use 函数源码上看:
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}本质上是把中间件追加到了 group.Handlers 切片的后面。
而在 GET/POST 函数内部执行了一个 combineHandlers 函数:
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
	finalSize := len(group.Handlers) + len(handlers)
	assert1(finalSize < int(abortIndex), "too many handlers")
	mergedHandlers := make(HandlersChain, finalSize)
	copy(mergedHandlers, group.Handlers)
	copy(mergedHandlers[len(group.Handlers):], handlers)
	return mergedHandlers
}这里将我们处理请求的函数和之前的 Handlers 拼在了一起。
而 context.Next() 的调用过程如下:
func (c *Context) Next() {
	c.index++
	for c.index < int8(len(c.handlers)) {
		c.handlers[c.index](c)
		c.index++
	}
}如果在自定义中间件里直接 return 了,只代表当前中间件的逻辑结束了,Handlers 中后续的函数仍然会依次执行。
从 context.Next() 的源码逻辑可以看出,真正决定 Handlers 中的函数调用的是 c.index。而 context.Abort() 的作用正是修改这个变量:
const abortIndex int8 = math.MaxInt8 >> 1
func (c *Context) Abort() {
	c.index = abortIndex
}9 优雅退出 
关闭程序的时候可能有请求还没有处理完,此时处理过程就会被迫中断。优雅退出其实就是在程序关闭时,不暴力关闭,而是要等待进程中的逻辑处理完成后,才关闭。
package main
import (
	"context"
	"errors"
	"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(5 * time.Second)
		c.String(http.StatusOK, "Welcome Gin Server")
	})
	srv := &http.Server{
		Addr:    ":8080",
		Handler: router.Handler(),
	}
	go func() {
		// service connections
		if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.Fatalf("listen: %s\n", err)
		}
	}()
	// Wait for interrupt signal to gracefully shutdown the server with
	// a timeout of 5 seconds.
	quit := make(chan os.Signal, 1)
	// kill (no params) by default sends syscall.SIGTERM
	// kill -2 is syscall.SIGINT
	// kill -9 is syscall.SIGKILL but can't be caught, so don't need add it
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	log.Println("Shutdown Server ...")
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.Println("Server Shutdown:", err)
	}
	log.Println("Server exiting")
}