一、gin快速入门

1 - gin简介

2 - gin快速入门

  • 下载并安装gin(可选)go get -u github.com/gin-gonic/gin
    • 如果我们使用的是go module的方式,就不需要手动下载,import后自动同步即可
  • gin的importimport "github.com/gin-gonic/gin"
  • 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 上启动服务
}

在这里插入图片描述

3 - gin示例原型优化

  • r.GET原型
    • param1 -> 相对访问路径
    • param2 -> HandlerFunc类型,可以传入多个
// 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原型
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
  • gin.H原型type H map[string]any,因为在gin太经常使用了,所以定义了H
// any is an alias for interface{} and is equivalent to interface{} in all ways.
type any = interface{}
  • 根据原型改造示例代码
    • 返回状态码200修改为http.StatusOK
    • 自定义HandlerFunc
    • 修改监听的端口号为8088
    • gin.H体验
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func pong(c *gin.Context) {
	c.JSON(http.StatusOK, map[string]string{
		"message": "pong",
	})
}

func main() {
	r := gin.Default()
	r.GET("/ping", pong)
	r.Run(":8088") // 监听并在 0.0.0.0:8080 上启动服务
}

4 - gin的Default和New

  • gin.Default()源码
    • 可以看到Default会默认帮我添加上Logger和Recovery的中间件,然后再New()
    • 也就是说gin.New()是不会添加Logger和Recovery的中间件的
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine
}

5 - Gin的请求方法

  • 只要为对应的请求配置访问路径与HandlerFunc即可:与之前介绍的Get请求一样的实现
package main

import "github.com/gin-gonic/gin"

func main() {
	// 使用默认中间件创建一个gin路由器
	// logger and recovery (crash-free) 中间件
	router := gin.Default()

	//restful 的开发中
	router.GET("/someGet", getting)
	router.POST("/somePost", posting)
	router.PUT("/somePut", putting)
	router.DELETE("/someDelete", deleting)
	router.PATCH("/somePatch", patching)
	router.HEAD("/someHead", head)
	router.OPTIONS("/someOptions", options)

	// 默认启动的是 8080端口,也可以自己定义启动端口
	router.Run()
	// router.Run(":3000") for a hard coded port
}

6 - 路由分组

  • 路由分组:当多个请求都有相同前缀的时候就可以使用路由分组来实现
func main() {
	router := gin.Default()
	// Simple group: v1
	v1 := router.Group("/v1")
	{
		v1.POST("/login", loginEndpoint)
		v1.POST("/submit", submitEndpoint)
		v1.POST("/read", readEndpoint)
	}
	// Simple group: v2
	v2 := router.Group("/v2")
	{
		v2.POST("/login", loginEndpoint)
		v2.POST("/submit", submitEndpoint)
		v2.POST("/read", readEndpoint)
	}
	router.Run(":8082")
}

7 - 从url中获取参数

  • 提取变量:以冒号开头的方式,如/:id//:id/:action
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	goodsGroup := router.Group("/goods")
	{
		//获取商品id为1的详细信息 模式
		goodsGroup.GET("/:id/", goodsDetail)
		goodsGroup.GET("/:id/:action", goodsDetailX)
		goodsGroup.GET("/:id/:action/add", goodsDetailY)
	}

	router.Run(":8088")
}

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

func goodsDetailX(c *gin.Context) {
	id := c.Param("id")
	action := c.Param("action")
	c.JSON(http.StatusOK, gin.H{
		"id":     id,
		"action": action,
	})
}

func goodsDetailY(c *gin.Context) {
	id := c.Param("id")
	action := c.Param("action")
	c.JSON(http.StatusOK, gin.H{
		"id":     id,
		"action": action,
	})
}

在这里插入图片描述

8 - required标记

  • 从测试结果分析:添加了required的TAG,如果参数不是uuid返回的是404错误
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

type Person struct {
	ID   string `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(":8088")
}

在这里插入图片描述

  • 删除掉uuid的限制,并修改ID为int属性ID int uri:"id" binding:"required"
type Person struct {
	ID   int `uri:"id" binding:"required"`
	Name string `uri:"name" binding:"required"`
}

在这里插入图片描述


二、获取表单参数

1 - get获取参数

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	router.GET("/welcome", welcome)
	router.Run(":8088")
}

func welcome(c *gin.Context) {
	firstName := c.DefaultQuery("firstname", "zhp")
	lastName := c.DefaultQuery("lastname", "test")
	// 不加默认值的话就直接使用Query
	//lastName := c.Query("lastname", "test")
	c.JSON(http.StatusOK, gin.H{
		"first_name": firstName,
		"last_name":  lastName,
	})
}

在这里插入图片描述

2- post获取参数

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	router.POST("/form_post", formPost)
	router.Run(":8088")
}

func formPost(c *gin.Context) {
	message := c.PostForm("message")
	nick := c.DefaultPostForm("nick", "anonymous")
	c.JSON(http.StatusOK, gin.H{
		"message": message,
		"nick":    nick,
	})
}

在这里插入图片描述

3 - get、post混合获取

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	router.POST("/post", getPost)
	router.Run(":8088")
}

func getPost(c *gin.Context) {
	id := c.Query("id")
	page := c.DefaultQuery("page", "0")
	name := c.PostForm("name")
	message := c.DefaultPostForm("message", "信息")
	c.JSON(http.StatusOK, gin.H{
		"id":      id,
		"page":    page,
		"name":    name,
		"message": message,
	})
}

在这里插入图片描述
在这里插入图片描述


三、json与protobuf渲染

1 - json渲染

  • 增加了json的TAG后:输出中就不是Name了,而是user
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

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

	router.GET("/moreJSON", moreJSON)

	router.Run(":8088")
}

func moreJSON(c *gin.Context) {
	var msg struct {
		Name    string `json:"user"`
		Message string
		Number  int
	}
	msg.Name = "bobby"
	msg.Message = "这是一个测试json"
	msg.Number = 20

	c.JSON(http.StatusOK, msg)
}

在这里插入图片描述

2 - protobuf渲染

  • main.go
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"

	"test_project/gin_start/proto"
)

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

	router.GET("/someProtoBuf", returnProto)
	router.Run(":8088")
}

func returnProto(c *gin.Context) {
	course := []string{"python", "go", "微服务"}
	user := &proto.Teacher{
		Name:   "bobby",
		Course: course,
	}
	c.ProtoBuf(http.StatusOK, user)
}
  • user.protoprotoc --go_out=. --go_opt=paths=source_relative *.proto
syntax = "proto3";

option go_package = ".;proto";

message Teacher {
    string name = 1;
    repeated string course = 2;
}

在这里插入图片描述


四、表单验证

1 - 登录表单验证

  • gin的表单验证使用的是第三方库github.com/go-playground/validator
  • gin提供了两套方法实现表单验证:gin实际上是基于第三库的基础上进行了封装
    • Must bind:如果不符合要求,就会抛出400的异常
      • Method:Bind(这个仍然调用的是ShouldBind)、BindJSON。。。
    • Should bind:如果不符合要求,会返回给开发人员
      • Method:ShouldBind、ShouldBindJSON。。。
  • 登录表单验证
package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
)

// required:字段必填
// min:最小长度
// max:最大长度
type LoginForm struct {
	User     string `form:"user" json:"user" xml:"user"  binding:"required,min=3,max=10"`
	Password string `form:"password" json:"password" xml:"password"  binding:"required"`
}

func main() {
	router := gin.Default()
	router.POST("/loginJSON", func(c *gin.Context) {

		var loginForm LoginForm
		if err := c.ShouldBind(&loginForm); err != nil {
			fmt.Println(err.Error())
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
			return
		}

		c.JSON(http.StatusOK, gin.H{
			"msg": "登录成功",
		})
	})
	router.Run(":8088")
}

在这里插入图片描述

2 - 注册表单验证

在这里插入图片描述

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
)

// gte:>=
// lte:<=
// eqfield:密码一致
type SignUpForm struct {
	Age        uint8  `json:"age" binding:"gte=1,lte=130"`
	Name       string `json:"name" binding:"required,min=3"`
	Email      string `json:"email" binding:"required,email"`
	Password   string `json:"password" binding:"required"`
	RePassword string `json:"re_password" binding:"required,eqfield=Password"` //跨字段
}

func main() {
	router := gin.Default()
	router.POST("/signup", func(c *gin.Context) {
		var signUpFrom SignUpForm
		if err := c.ShouldBind(&signUpFrom); err != nil {
			fmt.Println(err.Error())
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
			return
		}

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

	router.Run(":8088")
}

3 - 表单验证错误翻译成中文

  • 表单验证翻译实现:实现返回的错误是中文,并且是json格式
    • InitTrans:主要完成翻译的初始化,将提示的对象转换成json的小写
    • removeTopStruct:删除掉结构体的前缀,如LoginForm.user -> user
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 `json:"user" binding:"required,min=3,max=10"`
	Password string `json:"password" binding:"required"`
}

var trans ut.Translator

func InitTrans(locale string) (err error) {
	//修改gin框架中的validator引擎属性, 实现定制
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		//注册一个获取json的tag的自定义方法
		v.RegisterTagNameFunc(func(fld reflect.StructField) string {
			name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
			if name == "-" { // json TAG如果是"-"不处理
				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
	}

	return
}

func removeTopStruct(fileds map[string]string) map[string]string {
	rsp := map[string]string{}
	for field, err := range fileds {
		rsp[field[strings.Index(field, ".")+1:]] = err
	}
	return rsp
}

func main() {
	//代码侵入性很强 中间件
	if err := InitTrans("zh"); err != nil {
		fmt.Println("初始化翻译器错误")
		return
	}
	router := gin.Default()

	router.POST("/loginJSON", func(c *gin.Context) {

		var loginForm LoginForm
		if err := c.ShouldBind(&loginForm); err != nil {
			errs, ok := err.(validator.ValidationErrors)
			if !ok {
				c.JSON(http.StatusOK, gin.H{
					"msg": err.Error(),
				})
			}
			c.JSON(http.StatusBadRequest, gin.H{
				"error": removeTopStruct(errs.Translate(trans)),
			})
			return
		}

		c.JSON(http.StatusOK, gin.H{
			"msg": "登录成功",
		})
	})

	router.Run(":8088")
}

在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐