安装

安装gin框架

$ go get -u github.com/gin-gonic/gin

GET 函数第一个参数传入接口路径, 第二个参数可传入多个处理函数

// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodGet, relativePath, handlers)
}

// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)

创建服务

package main

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

// version 定义版本
func version(ctx *gin.Context) {
    ctx.AddParam("version", "v1")
}

func pong(ctx *gin.Context) {
    ctx.JSON(http.StatusOK, gin.H{
        "version": ctx.Param("version"),
        "message": "pong",
    })
}

func main() {
    // 实力化 gin server 对象. 相当于接口路由
    // default 包含 请求日志,异常拦截。 而 new 是不包含这些(panic是无法返回的
    r := gin.Default()
    r.GET("/ping", version, pong)
    // 运行,定义监听端口: 8081(默认8080
    r.Run(":8081")
}

访问测试

$ go run main.go

$ curl 127.0.0.1:8081/ping
{"message":"pong","version":"v1"}%

请求对象

GET

可通过 Query 函数获取, 或者使用强制带默认值的函数 DefaultQuery 获取

package main

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

func main() {
    // 初始化服务
    router := gin.Default()
    router.POST("/user", createUser) // POST /user

    router.Run(":8080")
}

func getUser(context *gin.Context) {
    // 获取 reset 参数
    id := context.Param("id")

    // 获取 get 参数, 不带默认值
    company := context.Query("company")

    // 获取 get 参数, 必须设置默认值为 Anonymity
    name := context.DefaultQuery("name", "Anonymity")
    context.JSON(http.StatusOK, gin.H{
        "id":      id,
        "company": company,
        "name":    name,
    })
}

请求测试

$ curl --request GET 'http://127.0.0.1:8080/user/1?company=魔王'

{
  "company": "魔王",
  "id": "1",
  "name": "Anonymity"
}

POST

Form表单请求方式可通过PostForm获取,或者使用强制带默认值函数DefaultPostForm获取

package main

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

func main() {
    // 初始化服务
    router := gin.Default()
    router.POST("/user", createUser) // POST /user
    router.GET("/user/:id", getUser) // GET /user/1

    router.Run(":8080")
}

func createUser(context *gin.Context) {
    // 获取 post 参数,不带默认值
    company := context.PostForm("company")

    // 获取 post 参数,必须设置默认值
    name := context.DefaultPostForm("name", "Gyi")

    context.JSON(http.StatusOK, gin.H{
        "company": company,
        "name":    name,
    })
}

请求测试

$ curl --location --request POST 'http://127.0.0.1:8080/user' \
--data-urlencode 'company=test'

company=test

响应对象

Json

可以使用 H 函数返回 json数据结构,还可以通过结构体打上标签 ,原样输出 html 字符串使用 PureJson 函数

package main

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

func main() {
    // 初始化服务
    router := gin.Default()
    router.GET("/user/:id", getUser) // GET /user/1

    router.Run(":8080")
}

func getUser(context *gin.Context) {
    var user struct {
        Id   string `json:"id"`
        Name string
    }
    // 获取 reset 参数
    id := context.Param("id")

    // 定义结构体
    user.Id = id
    user.Name = "Gyi"

    context.JSON(http.StatusOK, user)
}

Proto

定义响应protobuf

syntax = "proto3";
package demo;
option go_package="./response/proto;userPb";

message User{
  string id = 1;
  string name = 2;
  repeated string skill = 3;
}

生成文件

$ protoc -I=./response/proto user.proto --go_out ./

返回二进制数据

package main

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

func main() {
    route := gin.Default()
    route.GET("/user/:id", getUser)
    route.Run(":8080")
}

func getUser(context *gin.Context) {
    id := context.Param("id")
    user := userPb.User{
        Id:    id,
        Name:  "Gyi",
        Skill: []string{"Mysql", "Go"},
    }
    context.ProtoBuf(http.StatusOK, &user)
}

请求验证

简单验证

package main

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

type singUpForm struct {
    // form表单参数: user; json参数: user, 规则: 必传,最小长度,最大长度
    User       string `form:"user" json:"user" binding:"required,min=3,max=10"`
    Password   string `form:"password" json:"password" binding:"required"`
    // 验证 rePassword = Password 
    RePassword string `form:"re_password" json:"re_password" binding:"required,eqfield=Password"`
    Age        uint8  `json:"age" binding:"gte=1,lte=130"` // 验证年龄1~130
    Email      string `json:"email" binding:"required,email"` // 验证邮箱格式
}

func main() {
    router := gin.Default()
    router.POST("/singup", singUp)
    _ = router.Run(":8080")
}

func singUp(context *gin.Context) {
    var singUpForm singUpForm
    if err := context.ShouldBind(&singUpForm); err != nil {
        fmt.Println(err.Error())
        context.JSON(http.StatusBadRequest, gin.H{
            "error": err.Error(),
        })
        return
    }

    context.JSON(http.StatusOK, gin.H{
        "msg": "注册成功",
    })
}

错误提示

{
  "error": "Key: 'singUpForm.RePassword' Error:Field validation for 'RePassword' failed on the 'eqfield' tag"
}

提示信息

中文错误信息

package main

import (
    "fmt"
    "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"
    enTranslations "github.com/go-playground/validator/v10/translations/en"
    zhTranslations "github.com/go-playground/validator/v10/translations/zh"
    "net/http"
)

type singUpForm struct {
    // form表单参数: user; json参数: user, 规则: 必传,最小长度,最大长度
    User     string `form:"user" json:"user" binding:"required,min=3,max=10"`
    Password string `form:"password" json:"password" binding:"required"`
    // 验证 rePassword = Password
    RePassword string `form:"re_password" json:"re_password" binding:"required,eqfield=Password"`
    Age        uint8  `json:"age" binding:"gte=1,lte=130"`    // 验证年龄1~130
    Email      string `json:"email" binding:"required,email"` // 验证邮箱格式
}

// 定义全局翻译器 ut "github.com/go-playground/universal-translator"
var trans ut.Translator

func InitTran(locale string) error {
    // 修改 validator 引擎属性, 实现定制
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        zhT := zh.New() // 中文翻译器
        enT := en.New() // 英文翻译器

        // ut "github.com/go-playground/universal-translator" 第一个参数备用语言环境, 后面参数是应该支持的语言环境
        uni := ut.New(enT, zhT, enT)

        // 获取翻译. 使用 = 赋给全局变量, := 相当在函数内重新定义了局部变量
        trans, ok = uni.GetTranslator(locale)
        if !ok {
            return fmt.Errorf("uni GetTranslatir(%s)", locale)
        }

        switch locale {
        case "en":
            // enTranslations "github.com/go-playground/validator/v10/translations/en"
            _ = enTranslations.RegisterDefaultTranslations(v, trans)
        case "zh":
            // zhTranslations "github.com/go-playground/validator/v10/translations/zh"
            _ = zhTranslations.RegisterDefaultTranslations(v, trans)
        default:
            _ = enTranslations.RegisterDefaultTranslations(v, trans)
        }
    }
    return nil
}

func main() {
    // 初始化验证器语言
    if err := InitTran("zh"); err != nil {
        fmt.Printf("初始化翻译器异常: %s\n", err)
        return
    }

    router := gin.Default()
    router.POST("/singup", singUp)
    _ = router.Run(":8080")
}

func singUp(context *gin.Context) {
    var singUpForm singUpForm
    if err := context.ShouldBind(&singUpForm); err != nil {
        // 如果翻译错误
        errs, ok := err.(validator.ValidationErrors)
        if !ok { // 直接返回错误信息
            context.JSON(http.StatusOK, gin.H{
                "msg": err.Error(),
            })
        }

        // 翻译成功后返回翻译之后的错误信息
        context.JSON(http.StatusBadRequest, gin.H{
            "error": errs.Translate(trans), // 使用全局变量 trans 翻译中文内容
        })
        return
    }

    context.JSON(http.StatusOK, gin.H{
        "msg": "注册成功",
    })
}

错误信息

{
  "error": {
    "singUpForm.RePassword": "RePassword必须等于Password",
    "singUpForm.User": "User为必填字段"
  }
}

错误字段定义

使用标签中定义的 json 字段作为错误信息的 key
package main

import (
    "fmt"
    "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"
    enTranslations "github.com/go-playground/validator/v10/translations/en"
    zhTranslations "github.com/go-playground/validator/v10/translations/zh"
    "net/http"
    "reflect"
    "strings"
)

type singUpForm struct {
    // form表单参数: user; json参数: user, 规则: 必传,最小长度,最大长度
    User     string `form:"user" json:"user" binding:"required,min=3,max=10"`
    Password string `form:"password" json:"password" binding:"required"`
    // 验证 rePassword = Password
    RePassword string `form:"re_password" json:"re_password" binding:"required,eqfield=Password"`
    Age        uint8  `json:"age" binding:"gte=1,lte=130"`    // 验证年龄1~130
    Email      string `json:"email" binding:"required,email"` // 验证邮箱格式
}

// 定义全局翻译器 ut "github.com/go-playground/universal-translator"
var trans ut.Translator

func InitTran(locale string) error {
    // 修改 validator 引擎属性, 实现定制
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        // 注册获取 tag json 的自定义方法
        v.RegisterTagNameFunc(func(field reflect.StructField) string {
            name := strings.SplitN(field.Tag.Get("json"), ",", 2)[0]
            if name == "-" {
                return ""
            }
            return name
        })

        zhT := zh.New() // 中文翻译器
        enT := en.New() // 英文翻译器

        // ut "github.com/go-playground/universal-translator" 第一个参数备用语言环境, 后面参数是应该支持的语言环境
        uni := ut.New(enT, zhT, enT)

        // 获取翻译. 使用 = 赋给全局变量, := 相当在函数内重新定义了局部变量
        trans, ok = uni.GetTranslator(locale)
        if !ok {
            return fmt.Errorf("uni GetTranslatir(%s)", locale)
        }

        switch locale {
        case "en":
            // enTranslations "github.com/go-playground/validator/v10/translations/en"
            _ = enTranslations.RegisterDefaultTranslations(v, trans)
        case "zh":
            // zhTranslations "github.com/go-playground/validator/v10/translations/zh"
            _ = zhTranslations.RegisterDefaultTranslations(v, trans)
        default:
            _ = enTranslations.RegisterDefaultTranslations(v, trans)
        }
    }
    return nil
}

func main() {
    // 初始化验证器语言
    if err := InitTran("zh"); err != nil {
        fmt.Printf("初始化翻译器异常: %s\n", err)
        return
    }

    router := gin.Default()
    router.POST("/singup", singUp)
    _ = router.Run(":8080")
}

func singUp(context *gin.Context) {
    var singUpForm singUpForm
    if err := context.ShouldBind(&singUpForm); err != nil {
        // 如果翻译错误
        errs, ok := err.(validator.ValidationErrors)
        if !ok { // 直接返回错误信息
            context.JSON(http.StatusOK, gin.H{
                "msg": err.Error(),
            })
        }

        // 翻译成功后返回翻译之后的错误信息
        context.JSON(http.StatusBadRequest, gin.H{
            "error": errs.Translate(trans), // 使用全局变量 trans 翻译中文内容
        })
        return
    }

    context.JSON(http.StatusOK, gin.H{
        "msg": "注册成功",
    })
}

错误信息

{
  "error": {
    "singUpForm.re_password": "re_password必须等于Password",
    "singUpForm.user": "user为必填字段"
  }
}

去除字段前缀

// removeTopStruct 移除错误信息结构体前缀
func removeTopStruct(fields map[string]string) map[string]string {
    res := map[string]string{}

    // 循环处理传进来的错误信息
    for field, err := range fields {
        // 获取第一个 . 出现的位置
        position := strings.Index(field, ".")
        fmt.Println(field, position)

        // 获取截取后的 key 名
        key := field[position+1:]

        // 追加返回结果
        res[key] = err
    }
    return res
}

调用函数转换

// 翻译成功后返回翻译之后的错误信息
context.JSON(http.StatusBadRequest, gin.H{
        //"error": errs.Translate(trans), // 使用全局变量 trans 翻译中文内容
        // 使用自定义函数去除结构体前缀
        "error": removeTopStruct(errs.Translate(trans)), // 使用全局变量 trans 翻译中文内容
})

错误信息

{
  "error": {
    "re_password": "re_password必须等于Password",
    "user": "user为必填字段"
  }
}

自定义验证器

定义规则

package rule

import (
    "github.com/go-playground/validator/v10"
    "regexp"
)

func ValidateMobile(f validator.FieldLevel) bool {
    mobile := f.Field().String() // 获取获取手机号
    // 自定义表达式匹配
    if ok, _ := regexp.MatchString(`/^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}$/`, mobile); !ok {
        return false
    }

    return true
}

注册验证器

// 注册自定义验证器
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
  // 注册手机号验证(自定义名, 规则函数)
    _ = v.RegisterValidation("mobile", rule.ValidateMobile)
    // 自定义错误
    _ = v.RegisterTranslation("mobile", global.Trans, func(ut ut.Translator) error {
        return ut.Add("mobile", "{0} 非法手机号码", true)
    }, func(ut ut.Translator, fe validator.FieldError) string {
        t, _ := ut.T("mobile", fe.Field())
        return t
    })
}

绑定规则

package validate

// PasswordLoginValidate 密码表单验证
type PasswordLoginValidate struct {
    Mobile   string `form:"mobile" json:"mobile" binding:"required,mobile"`
    Password string `form:"password" json:"password" binding:"required,min=3,max=10"`
}

中间件

ginDefault 函数自带 LoggerRecovery 中间件

func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

自定义的中间件只要满足 HandleFunc 就可以使用, 也就是只需要定义函数并且返回值为 *Context

// logger 中间件
func Logger() HandlerFunc {
    return LoggerWithConfig(LoggerConfig{})
}

// recovery 中间件
func Recovery() HandlerFunc {
    return RecoveryWithWriter(DefaultErrorWriter)
}

// HandleFunc 类型
type HandlerFunc func(*Context)

注入中间件

func main() {
    router := gin.New()
  // 注入中间件Logger 和 Recovery
    router.Use(gin.Logger())
    router.Use(gin.Recovery())
  // 或者一次性传入多个
    router.Use(gin.Logger(), gin.Recovery())
}

终止请求

在中间见中需要使用 gin.Context.Abort 进行终止,return会继续执行

package main

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

// TokenRequired UseTime 定义使用时间中间件
func TokenRequired() gin.HandlerFunc {
    return func(context *gin.Context) {
        var token string
        // 获取 header
        for k, v := range context.Request.Header {
            if k == "Token" {
                token = v[0]
            }
            fmt.Println(k, v, token)
        }

        if token != "Gyi" {
            context.JSON(http.StatusUnauthorized, gin.H{
                "msg": "未登陆",
            })

            // 中间件内终止请求
            context.Abort()
        }
    }
}

func main() {
    router := gin.New()

    testRouter := router.Group("")
    testRouter.Use(gin.Logger())
    testRouter.Use(TokenRequired())
    testRouter.GET("/hello", hello)
    _ = router.Run(":8080")
}

func hello(context *gin.Context) {
    context.JSON(http.StatusOK, gin.H{
        "name": "Gyi",
    })
}

跨域设置

package middleware

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

func Cors() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        method := ctx.Request.Method // 获取请求方式

        ctx.Header("Access-Control-Allow-Origin", "*")
        ctx.Header("Access-Control-Allow-Methods", "*")
        ctx.Header("Access-Control-Allow-Method", "POST, PUT, GET, DELETE, PATCH, OPTIONS")
        ctx.Header("Access-Control-Allow-Credentials", "true")
        if method == "OPTIONS" {
            ctx.AbortWithStatus(http.StatusNoContent)
        }
    }
}

结束退出

使用channel接受系统退出信号,可以接收到ctr + ckill结束进程,但是无法接受kill -9 结束信号

package main

func main() {
   router := gin.Default()
   router.GET("/", func(context *gin.Context) {
      context.JSON(http.StatusOK, gin.H{
         "msg": "hello",
      })
   })

   // 使用协程启动服务
   go func() {
      _ = router.Run(":8080")
   }()

   quit := make(chan os.Signal)
   signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
   <-quit

   // 处理后续
   fmt.Println("关闭服务中...")
   fmt.Println("注销服务...")
}
Last modification:July 23rd, 2024 at 11:23 am