📦 Go1.22.5Gin1.10.0

⏮ 前置课程 httpRouter

🤦下面这些知识都是我东拼西凑找教程学的,官方网站写的不太可读,b站上的课程就只是教你怎么用

🌟 请求参数和路由处理

⭐️ Gin的路由组件是使用的 httpRouter,可以学一下 httprouter

⭐️ Get请求的 Query

就是比如 http://httpbin.org/get?name=xxxx这种样式

⭕️ 有两种方法

  1. Query 需要自己输入值
  2. DefaultQuery 带默认值

1️⃣将此前是 someGet方法修改一下,给他添加一个 Query,在使用 String去处理 Query,现在只演示 valueQ

func someGet(context *gin.Context) {

	valueQ := context.Query("name")
    valueDQ := context.DefaultQuery("age", "tanc")
	//返回一个字符串
	context.String(200, "Hello %s", valueQ)
}

2️⃣ 会发现 Query加上去之后就会显示 Query的值,如果不加是不会显示的

image-20240620145511141

3️⃣ 试一下 DefaultQuery,修改代码

func someGet(context *gin.Context) {

	valueQ := context.Query("name")
	valueDQ := context.DefaultQuery("age", "tanc")
	//返回一个字符串
	context.String(200, "Query: Hello %s", valueQ)
	context.String(200, "DefaultQuery: Hello %s", valueDQ)
}

4️⃣ 即使没有添加 Query,添加了默认值

image-20240620150202797

⭐️PostForm表单数据

也就是提交的表单上下文

⭕️ 也是主要有两个方法

  1. Posrform 需要自己输入
  2. DefaultPostForm 默认参数

1️⃣ 将 somePost方法修改一下

func somePost(context *gin.Context) {
	formQ := context.PostForm("name")
	formDQ := context.DefaultPostForm("age", "tanc")
	context.JSON(200, gin.H{
		"message":         "somPost",
		"PostForm":        formQ,
		"DefaultPostForm": formDQ,
	})
}

2️⃣ 发送请求

image-20240620151208267

⭐️ Json请求模板

有三种方法

🍵 第一种方法,使用空结构体接收处理,

1️⃣ 通过创建一个 mapmap表示 keystringvalue可以为任意类型,然后使用 BindJson绑定这个 json

func main(){
    ....
    r.GET("/someJson", somJson)
    ....
}


func somJson(context *gin.Context) {
	json := make(map[string]interface{}) 
	context.BindJSON(&json)
	context.JSON(200, gin.H{
		"message": "somJson",
		"name":    json["name"],
		"password": json["password"],
	})
}

2️⃣ 在来查看一下 JSON方法接收的参数,接收一个 code状态码,然后是任何对象

func (c *Context) JSON(code int, obj any) {
	c.Render(code, render.JSON{Data: obj})
}

3️⃣ 再来看 gin.H,它 map[string]interface{}差不多

// H is a shortcut for map[string]any
type H map[string]any

4️⃣ 来测试一下

image-20240620155320477

🍵 第二种方法: 定义结构体

1️⃣ 定义一个需要接收的结构体,这样很像 SpringBootmodel,这个很像我学 http/net包的时候,的 json,这个后面内容表示 json接收的 key

// User 接收json结构体
type User struct {
	Name     string `json:"name"`
	Password string `json:"password"`
}

2️⃣ 在创建一个路由

func main() {
	//创建gin server服务
	r := gin.Default()
    ...
    r.GET("/structJson", structJson)

    ...
	//启动服务,默认是8080端口
	r.Run(":8082")
}

3️⃣ 创建方法

func structJson(context *gin.Context) {
	json := User{}
	context.BindJSON(&json)
	context.JSON(200, gin.H{
		"message":  "somJson",
		"name":     json.Name,
		"password": json.Password,
	})
}

4️⃣ 测试

image-20240620161105794

🍵第三种方法:数据绑定表单 FormBind

1️⃣ 通过 ShouldBind来绑定结构体变量 ,首先需要新建一个结构体,注意结构体后面必须是 form而不是 json

如果你是绑定复选框,那么就直接写一个数组即可

// Model 表单数据绑定接收的结构体
type Model struct {
	Name     string `form:"name"`
	Password string `form:"password"`
	// 绑定复选框
	Code []string `form:"code[]"`
}

2️⃣ 添加新的路由,由于是表单形式,所以需要 Post

r.POST("/FormBind", formBind)

3️⃣ 编写路由的方法,使用 ShouldBind绑定数组

func formBind(context *gin.Context) {
	var model Model
	context.ShouldBind(&model)
	context.String(200, "FormBind: Hello %s %s 常用编程语言: %s\n", model.Name, model.Password, getCode(model))
}

//格式化数组
func getCode(model Model) string {
	var s string
	for _, v := range model.Code {
		s += v + " "
	}
	//去除最后一空格
	s = strings.TrimSpace(s)
	return s
}

4️⃣ 编写前端表单,注意这里还是使用之前的 index页面

    <form action="/FormBind" method="post">
        <input type="text" name="name">
        <input type="text" name="password">
        <input type="checkbox" name="code[]" value="Golang">Golang<br>
        <input type="checkbox" name="code[]" value="Python">Python<br>
        <input type="checkbox" name="code[]" value="Java">Java<br>
        <input type="submit" value="submit">
    </form>

5️⃣ 测试

image-20240621162810545

点击 submit

image-20240621162829482

还有一种方法,这种方法是 binding:"required"。这意味着当 Gin 尝试从HTTP请求的表单数据中绑定这些字段时,这两个字段必须存在且非空。如果不满足条件,数据绑定将会失败,Gin 可能会返回一个错误响应,指出哪些字段是必填的。

// Model 表单数据绑定接收的结构体
type Model struct {
	Name     string `form:"name" binding:"required"`
	Password string `form:"password" binding:"required"`
	// 绑定复选框
	Code []string `form:"code[]"`
}
func formBind(context *gin.Context) {
	var model Model
	if context.ShouldBind(&model) == nil {
		if model.Name == "tanc" && model.Password == "123456" {
			context.JSON(200, gin.H{
				"status": "hello tanc!!",
			})
		}else {
			context.JSON(400, gin.H{
				"status": "请输入正确用户",
			})
		}
	}
}

☑️ 拓展 Json

此外还有两种拓展的 Json

  1. PureJson JSON 使用 unicode 替换特殊 HTML 字符,例如 < 变为 \ u003c。如果要按字面对这些字符进行编码,则可以使用 PureJSON
  2. SecureJson: 防止 json 劫持。如果给定的结构是数组值,则默认预置 "while(1)," 到响应体头部来防止劫持,如果你的响应体不是数组,那么这个前缀不会被自动添加,但在任何情况下,SecureJSON都会确保输出的 JSON符合安全规范,减少 JSON劫持的风险。
  3. AsciiJson: 生成具有转义的非 ASCII 字符的 ASCII-only JSON,当一个JSON需要是纯ASCII的,即可以被ASCII字符集兼容的系统正确处理时,所有非ASCII字符都会被这样转义

⭕️ PureJson

1️⃣ 首先介绍第一种 Json,它和普通 Json一样接收两个参数

func (c *Context) PureJSON(code int, obj any) {
	c.Render(code, render.PureJSON{Data: obj})
}

2️⃣ 编写代码方式也是一样,可以填写 html代码 <h1>检测是否会被替换成 Unicode代码

main(){
    ...
	r.GET("/PureJson", pureJson)
	...
}


func pureJson(context *gin.Context) {
	context.PureJSON(200, gin.H{
		"message": "<h1>This is my first Gin code<h1>",
	})
}

3️⃣ 测试,并没有替换成 Unicode代码,可以替换成普通 Json测试一下

image-20240621150431564

⭕️ SecureJson

1️⃣ 同样首先查看一下源代码

func (c *Context) SecureJSON(code int, obj any) {
	c.Render(code, render.SecureJSON{Prefix: c.engine.secureJSONPrefix, Data: obj})
}

2️⃣ 编写代码,首先测试一个如果给定的结构是数组

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

	// 你也可以使用自己的 SecureJSON 前缀
	// r.SecureJsonPrefix(")]}',\n")

	r.GET("/someJSON", func(c *gin.Context) {
		names := []string{"lena", "austin", "foo"}

		// 将输出:while(1);["lena","austin","foo"]
		c.SecureJSON(http.StatusOK, names)
	})

	// 监听并在 0.0.0.0:8080 上启动服务
	r.Run(":8080")
}

2️⃣ 浏览器测试

image-20240621152620923

⭕️ AsciiJson

1️⃣ 编写代码

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

	r.GET("/someJSON", func(c *gin.Context) {
		data := map[string]interface{}{
			"lang": "GO语言",
			"tag":  "<br>",
		}

		// 输出 : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
		c.AsciiJSON(http.StatusOK, data)
	})

	// 监听并在 0.0.0.0:8080 上启动服务
	r.Run(":8080")
}

⭐️ HTML模板渲染

可以实现类似 Djangotmplates的功能

⭕️ 有两种方法导入 Html文件

  1. LoadHTMLGlob 导入全部
  2. LoadHTMLFiles 传入单个

1️⃣ 两个方法都大差不差的,首先在你的代码目录创建一个 templates/html文件夹,用来保存 html文件

image-20240621133100229

2️⃣ 创建新的路由,和处理方法

	//HTML模板渲染,注意路径
	r.LoadHTMLGlob("D:/learnCode/Go/GinStudy/firstCode/templates/html/*")
	//或者
	//r.LoadHTMLFiles("templates/html/index.html")

	r.GET("/index", someHtml)

这里需要使用 HTML方法,它接收3个参数,分别是:状态码,html文件名、需要传递过去的值,

func someHtml(context *gin.Context) {
	context.HTML(http.StatusOK, "index.html", gin.H{
		"Hello": "This is first Gin code",
	})
}

3️⃣ 修改 html文件,类似于 Djangocontext,可以在 html中接收,这里使用 .Hello接收,名字需要和函数中定义的一样

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>This is first Gin code</title>
</head>
<body>
{{.Hello}}
</body>
</html>

4️⃣ 打开浏览器访问

image-20240621135336508

⭐️ 绑定路由

使用 ShouldBindUri来绑定 uri,这里的 uri是路由,不是

1️⃣ 同样创建结构体接收 uri的,这个 uri:"id" binding:"required,uuid"表示这个绑定到 id,是必须的,且需要按照 uuid格式


// UriModel 绑定Uri的Model
type UriModel struct {
	Name string `uri:"name" binding:"required"`
	ID   string `uri:"id" binding:"required,uuid"`
}

2️⃣ 创建新路由和,路由处理 :name表示根据路由捕获,使用了 httprouter

r.GET("/:name/:id", bindUri)

3️⃣ 判断是否捕获到或者捕获到的 uri按照要球

func bindUri(context *gin.Context) {
	var uri UriModel
	if err := context.ShouldBindUri(&uri); err != nil {
		context.JSON(200, gin.H{
			"msg": err.Error(),
		})
		return
	}
	context.JSON(200, gin.H{
		"name": uri.Name,
		"id":   uri.ID,
	})
}

4️⃣ 测试

输入 UUID

image-20240621174801932

不按照 uuid格式

image-20240621174948384

⭐️ binding内置规则

前面也提到了使用 binding校验器来 uuid,它还提供了很多校验,他需要配置 Shouldxxx使用,在Gin框架中,数据绑定和验证主要依赖于 github.com/go-playground/validator/v10 库,如果数据验证失败,Gin会利用 validator 这个库进行错误检查

// 不能为空,并且不能没有这个字段
required: 必填字段,如:binding:"required"  

// 允许为空
omitempty

// 针对字符串的长度
min 最小长度,如:binding:"min=5"
max 最大长度,如:binding:"max=10"
len 长度,如:binding:"len=6"

// 针对数字的大小
eq 等于,如:binding:"eq=3"
ne 不等于,如:binding:"ne=12"
gt 大于,如:binding:"gt=10"
gte 大于等于,如:binding:"gte=10"
lt 小于,如:binding:"lt=10"
lte 小于等于,如:binding:"lte=10"

// 针对同级字段的
eqfield 等于其他字段的值,如:PassWord string `binding:"eqfield=Password"`
nefield 不等于其他字段的值


- 忽略字段,如:binding:"-" 或者不写

// 枚举  只能是red 或green
oneof=red green 

// 字符串  
contains=fengfeng  // 包含fengfeng的字符串
excludes // 不包含
startswith  // 字符串前缀
endswith  // 字符串后缀

// 数组
dive  // dive后面的验证就是针对数组中的每一个元素

// 网络验证
ip
ipv4
ipv6
uri
url
// uri 在于I(Identifier)是统一资源标示符,可以唯一标识一个资源。
// url 在于Locater,是统一资源定位符,提供找到该资源的确切路径

// 日期验证  1月2号下午3点4分5秒在2006年
datetime=2006-01-02


//其他校验
email 邮箱
uuid  uuid校验

😄 当然也可以自己编写校验规则,使用 binding.Validator.Engine().(*validator.Validate)这段代码获取 Gin 框架默认使用的 validator 库的实例,并通过类型断言确保可以安全地将其当作 *validator.Validate 类型来使用,如果可以就使用 RegisterValidation注册一个验证器名为 sign,实现函数为 singValid

if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
   v.RegisterValidation("sign", signValid)
}

🍵 将 binding错误信息装换为中文

😢 将错误信息翻译成中文,主要是使用的 validator.ValidationErrors它是检测 binding错误类型的一个错误类型,然后将错误转换为中文,通过 RegisterTagNameFunc来获取标签

/**
 * @Author tanchang
 * @Description //TODO
 * @Date 2024/6/21 20:17
 * @File:  test
 * @Software: GoLand
 **/

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	zh_translations "github.com/go-playground/validator/v10/translations/zh"
	"net/http"
	"reflect"
	"strings"
)

var trans ut.Translator

func init() {
	// 创建翻译器
	uni := ut.New(zh.New())
	trans, _ = uni.GetTranslator("zh")

	// 自定义binding
	v, ok := binding.Validator.Engine().(*validator.Validate)
	if ok {
		_ = zh_translations.RegisterDefaultTranslations(v, trans)
	}
	//将字段名也切换为中文
	v.RegisterTagNameFunc(func(field reflect.StructField) string {
		//获取结构体中定义的label信息
		label := field.Tag.Get("label")
		//如果label为空则直接返回字段名
		if label == "" {
			return field.Name
		}
		return label
	})
}

// 公共方法,将错误信息翻译成中文
func validateErr(err error) string {
	// 判断是否为validator.ValidationErrors错误,如果是验证错误,则调用翻译器,
	errs, ok := err.(validator.ValidationErrors)
	// 如果不是验证错误,则直接返回错误信息
	if !ok {
		return err.Error()
	}
	//创建list存储翻译器翻译后的错误信息
	var list []string
	// 遍历错误列表,调用翻译器翻译,因为ValidationErrors放回的是一个切片所以需要遍历
	for _, e := range errs {
		list = append(list, e.Translate(trans))
	}
	// 将list拼接成字符串返回
	return strings.Join(list, ";")
}

type User struct {
	Name  string `json:"name" binding:"required" label:"用户名"`
	Email string `json:"email" binding:"required,email" label:"邮箱"`
}

func main() {
	r := gin.Default()
	// 注册路由
	r.POST("/user", func(c *gin.Context) {
		var user User
		if err := c.ShouldBindJSON(&user); err != nil {
			// 参数验证失败,调用翻译器
			c.String(200, validateErr(err))
			return
		}

		// 参数验证成功
		c.JSON(http.StatusOK, gin.H{
			"message": fmt.Sprintf("Hello, %s! Your email is %s.", user.Name, user.Email),
		})
	})

	// 启动HTTP服务器
	r.Run()
}

修改版本,改成 json格式,通过设置一个分隔符来分割达到 json效果

/**
 * @Author tanchang
 * @Description //TODO
 * @Date 2024/6/21 20:17
 * @File:  test
 * @Software: GoLand
 **/

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	zh_translations "github.com/go-playground/validator/v10/translations/zh"
	"net/http"
	"reflect"
	"strings"
)

var trans ut.Translator

func init() {
	// 创建翻译器
	uni := ut.New(zh.New())
	trans, _ = uni.GetTranslator("zh")

	// 注册翻译器
	v, ok := binding.Validator.Engine().(*validator.Validate)
	if ok {
		_ = zh_translations.RegisterDefaultTranslations(v, trans)
	}
	//将字段名也切换为中文,filed就是创建的对象,可以操作这个对象拿取tag
	v.RegisterTagNameFunc(func(field reflect.StructField) string {
		//获取结构体中定义的label信息
		label := field.Tag.Get("label")
		//如果label为空则直接返回字段名
		if label == "" {
			label = field.Name
		}
		//拿取json字段
		name := field.Tag.Get("json")
		return fmt.Sprintf("%s----%s", name, label)
	})
}

// 公共方法,将错误信息翻译成中文
func validateErr(err error) any {
	// 判断是否为validator.ValidationErrors错误,如果是验证错误,则调用翻译器,
	errs, ok := err.(validator.ValidationErrors)
	// 如果不是验证错误,则直接返回错误信息
	if !ok {
		return err.Error()
	}
	//创建list存储翻译器翻译后的错误信息
	m := map[string]any{}
	for _, e := range errs {
		// 获取翻译器翻译后的错误信息
		msg := e.Translate(trans)
		//通过冒号分割,获取字段名
		split := strings.Split(msg, "----")
		//存储
		m[split[0]] = split[1] 
		return  m
	}
}

type User struct {
	Name  string `json:"name" binding:"required" label:"用户名"`
	Email string `json:"email" binding:"required,email" label:"邮箱"`
}

func main() {
	r := gin.Default()
	// 注册路由
	r.POST("/user", func(c *gin.Context) {
		var user User
		if err := c.ShouldBindJSON(&user); err != nil {
			// 参数验证失败,调用翻译器
			c.JSON(200, validateErr(err))
			return
		}

		// 参数验证成功
		c.JSON(http.StatusOK, gin.H{
			"message": fmt.Sprintf("Hello, %s! Your email is %s.", user.Name, user.Email),
		})
	})

	// 启动HTTP服务器
	r.Run()
}

最终版本测试

image-20240621212013296

🍵 自定义验证器

🍅 这里我使用官方示例

/**
 * @Author tanchang
 * @Description //TODO
 * @Date 2024/6/21 23:33
 * @File:  Gin官网示例代码之自定义验证器
 * @Software: GoLand
 **/

package main

import (
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10"
)

// Booking 包含绑定和验证的数据。
type Booking struct {
	CheckIn  time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
	CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn,bookabledate" time_format:"2006-01-02"`
}

// 表示创建一个validator.Func的bookableDate的变量,validator.Func需要接收一个函数
var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
	//Field返回一个Value对象,然后使用Interface()方法获取,通过断言转换为time.Time类型
	date, ok := fl.Field().Interface().(time.Time)
	if ok {
		//获取当前时间
		today := time.Now()
		//判断当前时间是否大于传入的时间
		if today.After(date) {
			return false
		}
	}
	return true
}

func main() {
	route := gin.Default()
	//注册验证器
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		// 使用RegisterValidation注册自定义验证器名为bookabledate,实现方法为bookableDate
		v.RegisterValidation("bookabledate", bookableDate)
	}

	route.GET("/bookable", getBookable)
	route.Run(":8085")
}

func getBookable(c *gin.Context) {
	var b Booking
	//和ShouldBindQuery一样的功能
	if err := c.ShouldBindWith(&b, binding.Query); err == nil {
		c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
	} else {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
	}
}

1️⃣ 一步一步来看,先查看结构体

  • time.Time: 表明该字段存储的是一个时间点。
  • form:"check_in": 表示在表单数据中,这个字段对应于 check_in 键。
  • binding:"required,bookabledate"
    • required: 表示该字段是必填的,如果缺失则验证失败。
    • bookabledate: 应用了之前定义的自定义验证规则 bookableDate,确保日期是可预订的(即日期在未来)。
    • gtfield=CheckIn: 这是一个比较验证规则,要求 CheckOut 字段的值必须大于(在时间意义上)CheckIn 字段的值,即退房日期必须在入住日期之后。
  • time_format:"2006-01-02": 指定了日期字符串的预期格式,遵循Go的日期时间格式化约定,这里表示年月日格式(例如,2023-04-01)。
// Booking 包含绑定和验证的数据。
type Booking struct {
	CheckIn  time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
	CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn,bookabledate" time_format:"2006-01-02"`
}

2️⃣ 在查看 main函数,自定义了一个验证器

func main() {
	route := gin.Default()
	//注册验证器
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		// 使用RegisterValidation注册自定义验证器名为bookabledate,实现方法为bookableDate
		v.RegisterValidation("bookabledate", bookableDate)
	}

	route.GET("/bookable", getBookable)
	route.Run(":8085")
}

3️⃣ 查看自定义验证器内容


// 表示创建一个validator.Func的bookableDate的变量,validator.Func需要接收一个函数
var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
	//Field返回一个Value对象,然后使用Interface()方法获取,通过断言转换为time.Time类型
	date, ok := fl.Field().Interface().(time.Time)
	if ok {
		//获取当前时间
		today := time.Now()
		//判断当前时间是否大于传入的时间
		if today.After(date) {
			return false
		}
	}
	return true
}
这里其实可以简化,它加

validator.Func是为了告诉你它是一个自定义验证函数,其实不加也可以你只需在注册中添加这个函数即可

使用

fl.Field().Interface()调用 fl.Field() 方法会返回一个 reflect.Value 类型的对象。reflect.Value 是反射(Reflection)机制中的核心类型,它代表了运行时对象的值,然后将它转换为一个空接口,方便转换为 time.Time类型


// 表示创建一个validator.Func的bookableDate的变量,validator.Func需要接收一个函数
func bookableDate(fl validator.FieldLevel) bool {
	//Field返回一个Value对象,然后使用Interface()方法获取,通过断言转换为time.Time类型
	date, ok := fl.Field().Interface().(time.Time)
	if ok {
		//获取当前时间
		today := time.Now()
		//判断当前时间是否大于传入的时间
		if today.After(date) {
			return false
		}
	}
	return true
}

Filed()源代码

func (v *validate) Field() reflect.Value {
	return v.flField
}

4️⃣ 请求代码

func getBookable(c *gin.Context) {
	var b Booking
	//和ShouldBindQuery一样的功能
	if err := c.ShouldBindWith(&b, binding.Query); err == nil {
		c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
	} else {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
	}
}

4️⃣ 测试

image-20240622004114849

image-20240622004208670

⭐️ 路由组

1️⃣它作用是在你要请求的路由如果是在同一个页面或者是功能是差不多可以使用路由组在这些路由前面加一个统一的前缀(因人使用)

/**
 * @Author tanchang
 * @Description //TODO
 * @Date 2024/6/22 16:03
 * @File:  main.go
 * @Software: GoLand
 **/

package main

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

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

	ginServer.Use(gin.Logger(), gin.Recovery())

	//定义路由组
	v1 := ginServer.Group("/v1")
	{
		v1.GET("/home", homeHandleV1)
	}

	v2 := ginServer.Group("/v2")
	{
		v2.GET("/home", homeHandleV2)
	}
	ginServer.Run(":8083")

}

func homeHandleV1(context *gin.Context) {
	context.String(200, "Yes V1!!!")
}

func homeHandleV2(context *gin.Context) {
	context.String(200, "Yes V2!!!")
}

2️⃣ 测试:

image-20240622161552803

image-20240622161609009

3️⃣ Group和其他请求方法一样可以使用处理器,看一下它的源代码

func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
	return &RouterGroup{
		Handlers: group.combineHandlers(handlers),
		basePath: group.calculateAbsolutePath(relativePath),
		engine:   group.engine,
	}
}

添加一个处理器,它会显示在 home 路由器前面

	v1 := ginServer.Group("/v1", func(context *gin.Context) {
		context.JSON(200, gin.H{
			"Test": 123456,
		})
	})
	{
		v1.GET("/home", homeHandleV1)
	}

测试:

image-20240622163927169