MSIPO技术圈 首页 IT技术 查看内容

【Go】八、Gin 入门使用简介

2024-03-29

GIN

GIN 是一个高性能,简单易用的轻量级 WEB 框架

快速尝试

package main

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

func pong(c *gin.Context) {
	// 这里的 gin.H 是 map[string]interface{} 的缩写
	c.JSON(http.StatusOK, gin.H{
		"message": "pong",
	})
	
	// 我们也可以写成
	//c.JSON(http.StatusOK, map[string]interface{
	//	"message": "pong",
	//	}{

}

func main() {
	// 实例化一个 gin 对象
	r := gin.Default()
	r.GET("/ping", pong)
	r.Run(":8080") // 默认是 8080
}

路由分组

对于 WEB 开发来讲,不同的接口需要被不同的模块调用,而我们一般把URL 路径的前缀作为区分模块的基础,例如:

localhost:8080/goods/list 和 localhost:8080/goods/detail 就同属于一个模块,而我们每次配置 GIN 时都需要写它的路径,这样无疑增加了我们的工作量,我们可以通过路由分组,来让相同模块的 URL 分到一起并设置一个共有前缀,进而解决这个问题

示例:

package main

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

func main() {

	router := gin.Default()
	goodsGroup := router.Group("/goods") // 这里传入的是公共前缀
	{
		goodsGroup.GET("/list", goodsList)
		goodsGroup.GET("/1", goodsDetail)
		goodsGroup.POST("/add")
	}
	router.Run(":8080")

}

func goodsDetail(context *gin.Context) {

}

func goodsList(context *gin.Context) {
	context.JSON(http.StatusAccepted, gin.H{
		"message": "WIN",
	})
}

如果我们有动态的变量需要传入:

使用冒号进行区分

 package main

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

func main() {

	router := gin.Default()
	goodsGroup := router.Group("/goods") // 这里传入的是公共前缀
	{
		goodsGroup.GET("/list", goodsList)
		goodsGroup.GET("/:id/:action", goodsDetail)
		// 注意这里有一个特例:*action:是一个特殊的路由规则,其会匹配所有的路径,例如我们访问:http://127.0.0.1:8080/goods/256/delete/asd/sad/asd
		// 就会返回 /256/delete/asd/sad/asd	这样的带有所有路径的字符串,很少使用,有时用来匹配静态资源
		goodsGroup.POST("/add")
	}
	router.Run(":8080")

}

func goodsDetail(context *gin.Context) {
	id := context.Param("id")
	action := context.Param("action")
	context.JSON(http.StatusOK, gin.H{
		"id":      id,
		"action":  action,
		"message": "OK",
	})
}

func goodsList(context *gin.Context) {

	context.JSON(http.StatusOK, gin.H{
		"message": "WIN",
	})
}

但是,我们还需要对传入的参数进行控制,对参数的类型进行约束

package main

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

type Person struct {
	ID   int    `uri:"id" binding:"required"` // 要求必填、必须是uuid格式
	Name string `uri:"name" binding:"required"`
}

func main() {
	router := gin.Default()
	router.GET("/:name/:id", func(c *gin.Context) {
		var person Person
		if err := c.ShouldBindUri(&person); err != nil {
			c.Status(404)
			return
		}
		c.JSON(http.StatusOK, gin.H{
			"name": person.Name,
			"id":   person.ID,
		})
	})
	router.Run(":8083")
}

参数获取

我们很一般会分为从 GET 和 POST 类型的请求中获取参数:

另外我们也有同时从 url 路径中以及 body 中获取参数的情况,这个时候就需要我们使用 POST 请求

package main

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

func main() {
	router := gin.Default()
	router.GET("/getname", GetName)
	router.POST("/getname", PostGetName)
	// 混合获取
	router.POST("bothgetname", GetPostname)
	router.Run(":8083")
}

// 通过 post 请求同时获取 url 与请求体中的数据
func GetPostname(context *gin.Context) {
	id := context.Query("id")
	page := context.DefaultQuery("page", "0")
	message := context.PostForm("message")
	nick := context.DefaultPostForm("nick", "anonymous")
	context.JSON(http.StatusOK, gin.H{
		"id":      id,
		"page":    page,
		"message": message,
		"nick":    nick,
	})
}

// 这里的要从 Body 中发送数据,而不是从 URL 中发送数据
func PostGetName(context *gin.Context) {
	message := context.PostForm("message")
	nick := context.DefaultPostForm("nick", "anonymous")
	context.JSON(http.StatusOK, gin.H{
		"message": message,
		"nick":    nick,
	})

}

// http://127.0.0.1:8080/getname?firstname=James&lastname=Yang
func GetName(context *gin.Context) {
	firstname := context.DefaultQuery("firstname", "Guest")
	lastname := context.DefaultQuery("lastname", "Guest")
	context.JSON(http.StatusOK, gin.H{
		"first_name": firstname,
		"last_name":  lastname,
	})
}

表单验证

GIN 引入了 validate 开源项目进行表单验证:

package main

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

type LoginForm struct {
	User     string `form:"user" binding:"required,min=3,max=10"`
	Password string `form:"password" binding:"required"`
}

type SignForm struct {
	Age        uint8  `json:"age" binding:"required,gte=1,lte=130"`
	Name       string `json:"name" binding:"required,min=2"`
	Email      string `json:"email" binding:"required,email"`
	Password   string `json:"password" binding:"required,min=6,max=20"`
	RePassword string `json:"repassword" binding:"required,eqfield=Password"` // 要求和 Password 字段相同,跨域校验,类似的其他跨域校验还有许多
}

func main() {
	router := gin.Default()
	router.POST("/loginJSON", func(context *gin.Context) {
		var loginForm LoginForm
		if err := context.ShouldBind(&loginForm); err != nil {
			fmt.Println(err.Error())
			context.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
			return
		}
		context.JSON(http.StatusOK, gin.H{
			"status": "验证成功",
		})
	})

	router.POST("/signUp", func(context *gin.Context) {
		var signForm SignForm
		if err := context.ShouldBind(&signForm); err != nil {
			context.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
			return
		}
		context.JSON(http.StatusOK, gin.H{
			"message": "成功!!!!!",
		})

	})

	router.Run(":8083")
}

错误信息配置成中文:

package main

import (
	"fmt"
	"net/http"
	"reflect"
	"strings"

	"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"
)

type LoginForm struct {
	User     string `form:"user" binding:"required,min=3,max=10"`
	Password string `form:"password" binding:"required"`
}

type SignForm struct {
	Age        uint8  `json:"age" binding:"required,gte=1,lte=130"`
	Name       string `json:"name" binding:"required,min=2"`
	Email      string `json:"email" binding:"required,email"`
	Password   string `json:"password" binding:"required,min=6,max=20"`
	RePassword string `json:"repassword" binding:"required,eqfield=Password"` // 要求和 Password 字段相同,跨域校验,类似的其他跨域校验还有许多
}

// 在最后返回错误时调用,用来将返回中的对象名去掉
func removeTopStruct(fields map[string]string) map[string]string {
	rsp := map[string]string{}
	for field, err := range fields {
		rsp[field[strings.Index(field, ".")+1:]] = err // 将map中的 key 中的 . 前面的信息去掉
	}
	return rsp
}

var trans ut.Translator

// 修改 gin 中的 validate,实现定制,这里处理语言问题
func InitTrans(locale string) (err error) {
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		// 这段会将 错误信息中的 字段改为 json 中的字段名,而不是结构体中的字段名
		v.RegisterTagNameFunc(func(fld reflect.StructField) string {
			name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
			if name == "-" {
				return ""
			}
			return name
		})

		zhT := zh.New()
		enT := en.New()
		uni := ut.New(enT, zhT, enT) // 这里的第一个参数是备用语言,后两个是支持的语言
		trans, ok = uni.GetTranslator(locale)
		if !ok {
			return fmt.Errorf("uni.GetTranslator(%s)", locale)
		}

		switch locale {
		case "en":
			en_translations.RegisterDefaultTranslations(v, trans)
		case "zh":
			zh_translations.RegisterDefaultTranslations(v, trans)
		default:
			en_translations.RegisterDefaultTranslations(v, trans)
		}
	}
	return
}

func main() {
	if err := InitTrans("zh"); err != nil {
		fmt.Println("初始化翻译器错误")
		fmt.Println(err)
		return
	}
	router := gin.Default()
	router.POST("/loginJSON", func(context *gin.Context) {
		var loginForm LoginForm
		if err := context.ShouldBind(&loginForm); err != nil {
			// 转换错误类型
			errs, ok := err.(validator.ValidationErrors)
			if !ok {
				context.JSON(http.StatusOK, gin.H{
					"msg": err.Error(),
				})
				return
			}

			fmt.Println(err.Error())
			context.JSON(http.StatusBadRequest, gin.H{
				// 去掉前面的结构体信息
				"error": removeTopStruct(errs.Translate(trans)),
			})
			return
		}
		context.JSON(http.StatusOK, gin.H{
			"status": "验证成功",
		})
	})

	router.POST("/signUp", func(context *gin.Context) {
		var signForm SignForm
		if err := context.ShouldBind(&signForm); err != nil {
			context.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
			return
		}
		context.JSON(http.StatusOK, gin.H{
			"message": "成功!!!!!",
		})

	})

	router.Run(":8083")
}

将配置翻译成中文的具体步骤:

配置如下函数与全局变量:

// 在最后返回错误时调用,用来将返回中的对象名去掉
func removeTopStruct(fields map[string]string) map[string]string {
	rsp := map[string]string{}
	for field, err := range fields {
		rsp[field[strings.Index(field, ".")+1:]] = err // 将map中的 key 中的 . 前面的信息去掉
	}
	return rsp
}

0var trans ut.Translator

// 修改 gin 中的 validate,实现定制,这里处理语言问题
func InitTrans(locale string) (err error) {
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		// 这段会将 错误信息中的 字段改为 json 中的字段名,而不是结构体中的字段名
		v.RegisterTagNameFunc(func(fld reflect.StructField) string {
			name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
			if name == "-" {
				return ""
			}
			return name
		})

		zhT := zh.New()
		enT := en.New()
		uni := ut.New(enT, zhT, enT) // 这里的第一个参数是备用语言,后两个是支持的语言
		trans, ok = uni.GetTranslator(locale)
		if !ok {
			return fmt.Errorf("uni.GetTranslator(%s)", locale)
		}

		switch locale {
		case "en":
			en_translations.RegisterDefaultTranslations(v, trans)
		case "zh":
			zh_translations.RegisterDefaultTranslations(v, trans)
		default:
			en_translations.RegisterDefaultTranslations(v, trans)
		}
	}
	return
}

其中,要注意可能需要添加的包:

	en_translations "github.com/go-playground/validator/v10/translations/en"
	zh_translations "github.com/go-playground/validator/v10/translations/zh"

在 main 函数的最一开始配置 翻译器的初始化:

	if err := InitTrans("zh"); err != nil {
		fmt.Println("初始化翻译器错误")
		fmt.Println(err)
		return
	}

在发生错误的地方进行配置:

if err := context.ShouldBind(&loginForm); err != nil {
    // 第一步:将错误类型进行转换
			// 转换错误类型
			errs, ok := err.(validator.ValidationErrors)
    		// 这里的 ok 是转换是否失败,转换失败不代表请求失败,故返回的是成功
			if !ok {
				context.JSON(http.StatusOK, gin.H{
					"msg": err.Error(),
				})
                // 语言转换失败就直接弹出了,不用再继续看了
				return
			}
		
    		// 在控制台输出错误信息,可选
			fmt.Println(err.Error())
    		// 将翻译之后的信息写入返回
			context.JSON(http.StatusBadRequest, gin.H{
                "error": removeTo

相关阅读

热门文章

    手机版|MSIPO技术圈 皖ICP备19022944号-2

    Copyright © 2024, msipo.com

    返回顶部