安装
安装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"`
}
中间件
gin
中 Default
函数自带 Logger
和 Recovery
中间件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 + c
或 kill
结束进程,但是无法接受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("注销服务...")
}