javaer快速入门 goweb框架 gin

gin 入门

前置条件 安装环境

配置代理

# 配置 GOPROXY 环境变量,以下三选一

# 1. 七牛 CDN
go env -w  GOPROXY=https://goproxy.cn,direct

# 2. 阿里云
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct

# 3. 官方
go env -w  GOPROXY=https://goproxy.io,direct

安装gin

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

进入项目

启动类
/*
*
和Springboot一样 一个项目有一个main函数作为启动类
*/
func main() {
	//创建路由
	engine := gin.Default()
   // 这里写的匿名方法实际可以在文件中写多个多个方法作为路由导入
	engine.GET("/hello", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Hello World",
			"status":  "success",
			"data": gin.H{
				"name": "张三",
				"age":  20,
			},
		})

	})
	//启动项目 0.0.0.0:4444 本质就是http.ListenAndServe(":4444", engine)的封装
	engine.Run(":4444") //监听4444端口  本机得任意ip的额4444端口都可以访问

	//也可以原生http的方式启动
	//http.ListenAndServe(":4444", engine)
}

这样本机项目就可以根指定端口启动了

其中gin.H 是 Gin 框架提供的一个便捷方式,用于将 Go 语言中的 map[string]interface{} 类型封装为 JSON 数据并返回给客户端。gin.H 本质上就是一个 map[string]interface{},但它提供了一种更简洁的语法来定义 JSON 对象。

源码:

type H map[string]any

当然也可以自定义一个通用返回类型

响应信息–自定义结构体
func helloworld(c *gin.Context) {
	//gin的上下文向客户端写入JSON数据
	c.JSON(http.StatusOK, gin.H{
		"message": "Hello World!",
		"code":    "0000",
	})
}

func main() {
	router := gin.Default()
	router.GET("/index", helloworld)
	router.GET("/index2", returnR)
	router.GET("/", Index)
	router.GET("/error", ReturnError)
	router.Run(":888")
}
func Index(context *gin.Context) {
	//响应字符串
	context.String(200, "Hello 枫枫!")

}
func ReturnError(c *gin.Context) {
	c.Error(errors.New("演示响应错误")) //输出在控制台的呃呃error信息
}
//这样就可以实现返回自定以json 而不需要复写
func returnR(c *gin.Context) {
	r := Result{Code: 0, Message: "success", Data: "data"}
	c.JSON(200, r)
}

// 定义返回给前端的通用类型  后面的是tag :跟客户端进行序列化时候对应的key 首字母大写 给包外访问权限
type Result struct {
	Code    int         `json:"code"`
	Message string      `json:"message"`
	Data    interface{} `json:"data"`
}

在这里插入图片描述

可以看到结构体字段的响应到前端 key 就转变为对应tag (下划线或者小驼峰命名),不写tag 的话就是默认字段名

tag 还可以做数据脱敏

type Userindo struct {
	Code     int         `json:"code"`
	Message  string      `json:"message"`
	Data     interface{} `json:"data"`
	Password string      `json:"password"` 
}

如果直接返回密码,那么就太危险,go中可以序列化为json时候不进行序列化,也就不会返回给前端

type Result struct {
	Code     int         `json:"code"`
	Message  string      `json:"message"`
	Data     interface{} `json:"data"`
	Password string      `json:"-"` //不会进行序列化忽略空值字段
}

但是确不影响反序列化 json 序列化成为对象 (结构体)

type User struct {
	Username string `json:"username"`
	Password string `json:"-"` //omitempty 忽略空值字段
}

func logincontroller(c *gin.Context) {

	var user User
	

	// 将请求体绑定到 User 结构体
	// 注意:这里的 ShouldBindJSON 仅适用于 JSON 格式的请求体 ShouldBind适用于表单格式的请求体
	if err := c.ShouldBindJSON(&user); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "无效请求"})
		return
	}
	fmt.Print(user.Username, user.Password)

	// 输出接收到的用户名和密码
	c.JSON(http.StatusOK, gin.H{
		"username": user.Username,
		"password": user.Password,
	})
}

//.....绑定路由
	router.POST("/login", logincontroller)

在这里插入图片描述

响应体并没有密码

同时反序列化也没办法接收到来自客户端的请求

type User struct {
	Username string `json:"username"`
	Password string `json:"-"`   //omitempty 忽略空值字段
	Age      string `json:"age"` //omitempty 忽略空值字段
}

func logincontroller(c *gin.Context) {

	var user User

	// 将请求体绑定到 User 结构体
	// 注意:这里的 ShouldBindJSON 仅适用于 JSON 格式的请求体 ShouldBind适用于表单格式的请求体
	if err := c.ShouldBindJSON(&user); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "无效请求"})
		return
	}
	// 打印接收到的用户名和密码
	fmt.Println("Received username:", user.Username, "password:", user.Password, "age:", user.Age)

	// 输出接收到的用户名和密码
	c.JSON(http.StatusOK, gin.H{
		"username": user.Username,
		"password": user.Password,
	})
}

密码的数据打印始终未空 所以还是返回前手动赋予空值 脱敏

返回其他类型
  • xml
func returnXML(c *gin.Context) {
	c.XML(http.StatusOK, gin.H{"user": "hanru", "message": "hey", "status": http.StatusOK})
}

只是使用的c.json变成了xml

在这里插入图片描述

  • YAML

    这里使用Springboot 连接redid的配置代码块

func returnyml(c *gin.Context) {
	config := gin.H{
		"spring": gin.H{
			"data": gin.H{
				"redis": gin.H{
					"database": 1,
					"host":     "localhost",
					"port":     6379,
					// "password": "",
					// "timeout":  "6000ms",
				},
			},
		},
	}
	c.YAML(http.StatusOK, config)
}

游览器对于无法直接游览的文件会执行下载策略

在这里插入图片描述

打开后发现就是像输出的内容

在这里插入图片描述

并且输出的上诉格式都可以使用字符串,拼接,然后返回字符串,改变响应体的方式返回比如

  • json

    func returnRj(c *gin.Context) {
    	str := `
    {
    	"code":"0000",
    	"message":"Hello World!",
    	"data":{
    		"name":"John Doe"
    		"age":30
    		"city":"New York"}
    }
    `
    	c.Header("Content-Type", "application/json")
    
    	c.String(200, str)
    }
    
  • yaml

    
    func returnYAML(c *gin.Context) {
    	// 你的 Java 配置文件内容
    	yamlContent := `
    spring:
      data:
        redis:
          database: 1
          host: localhost
          port: 6379
          #password:
          #timeout: 6000ms  # 连接超时时长(毫秒)
    `
    
    	// 设置响应头
    	c.Header("Content-Type", "application/x-yaml")
    
    	// 返回 YAML 内容
    	c.String(http.StatusOK, yamlContent)
    }
    
    
  • html

    返回html文件之前需要先加载模板文件

    //加载模板
    
    	router.LoadHTMLGlob("/templates/**/*")
    //定义路由
    router.GET("/tem", func(c *gin.Context) {
      //根据完整文件名渲染模板,并传递参数
      c.HTML(http.StatusOK, "index.html", gin.H{
        "title": "你好世界",
      })
    })
    

模板接收参数

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
   hello world! {{  .title  }}
</body>
</html>
  • 响应重定向

    router.GET("/redirect", func(c *gin.Context) {
        //支持内部和外部的重定向
        c.Redirect(http.StatusMovedPermanently, "http://www.bilibili.com/")  //外部则需要协议名写全,内部只需要写一个路由即可
    })
    
    

接收请求信息

作为一个web框架除了响应客户端 还需要重客户端接收信息

接收querry方式参数
func queryUserinfo(c *gin.Context) {
	fmt.Println(c.Query("user"))                   //只会拿到第一个查询参数
	value, exists := c.GetQuery("user")
	fmt.Printf("当前参数是否存在: %v, 值: %s\n", exists, value) //判断是否存在查询参数
	fmt.Println(c.QueryArray("user"))              // 拿到多个相同的查询参数
	fmt.Println(c.DefaultQuery("addr", "四川省"))
}
func main() {
	router := gin.Default()
	router.GET("/queryUser", queryUserinfo)
	router.Run(":8")
}

在这里插入图片描述

输出:

在这里插入图片描述

restful形式接收请求
func restful(c *gin.Context) {
	fmt.Println(c.Param("user_id"))
	fmt.Println(c.Param("addr_id"))
	c.JSON(http.StatusOK, gin.H{
		"code":    0,
		"message": "success",
		"data": gin.H{
			"user_id": c.Param("user_id"),
			"addr_id": c.Param("addr_id"),
		},
	})
}


//路由绑定
	router.GET("/restfulUser/:user_id/:addr_id", restful)

接收到参数并且返回

在这里插入图片描述

表单数据

一般和json一样都是post方式携带的,表单 PostForm

可以接收 multipart/form-data; application/x-www-form-urlencoded

func postForm(c *gin.Context) {
	fmt.Println(c.PostForm("name"))               //接收第一个参数
	fmt.Println(c.PostFormArray("name"))          //同名数组参数
	fmt.Println(c.DefaultPostForm("addr", "四川省")) // 如果用户没传,就使用默认值
	forms, err := c.MultipartForm()               // 接收所有的form参数,包括文件
	fmt.Println(forms, err)
}


在这里插入图片描述

原始数据 json
func _raw(c *gin.Context) {
    body, _ := c.GetRawData() //获取原始请求体

    fmt.Println(string(body))
    contentType := c.GetHeader("Content-Type")
    switch contentType {
    case "application/json":
       // json解析到结构体
       type User struct {
          Name string `json:"name"`
          Age  int    `json:"age"`
       }
       var user User
       //原始json包解析数据到结构体
       err := json.Unmarshal(body, &user)
       if err != nil {
          fmt.Println(err.Error())
       }
       //解析后结构体
       fmt.Println(user)
    }
}

这里使用的解析,再绑定到结构体的方法是go中的json包自带的方法,当然gin也有封装好自带的解析方法

json的序列化方法;jsonData, err := json.Marshal(person)

在这里插入图片描述

请求头信息

在Spring的系列框架中,过滤器,拦截器来实现的权限验证都需要使用到请求头信息,所以对应请求头的获取是很重要的

func getHeader(c *gin.Context) {

	// 首字母大小写不区分  单词与单词之间用 - 连接
	// 用于获取一个请求头
	fmt.Println(c.GetHeader("User-Agent"))
	//fmt.Println(c.GetHeader("user-agent"))
	//fmt.Println(c.GetHeader("user-Agent"))
	//fmt.Println(c.GetHeader("user-AGent"))

	// Header 是一个普通的 map[string][]string
	fmt.Println(c.Request.Header) //getheader就是获取一个具体数据,如果有多个值,则返回一个数组
	// 如果是使用 Get方法或者是 .GetHeader,那么可以不用区分大小写,并且返回第一个value
	fmt.Println(c.Request.Header.Get("User-Agent"))
	fmt.Println(c.Request.Header["User-Agent"])
	// 如果是用map的取值方式,请注意大小写问题
	fmt.Println(c.Request.Header["user-agent"])

	// 自定义的请求头,用Get方法也是免大小写
	fmt.Println(c.Request.Header.Get("Token"))
	fmt.Println(c.Request.Header.Get("token"))
	c.JSON(200, gin.H{
		"msg":  "获取请求头信息成功",
		"code": 0,
		"data": c.Request.Header,
	})

}
响应头信息
/**
 * 设置响应头de1api  就是上下文.header
 */
func setResHeaders(c *gin.Context) {
    // 设置响应头

    c.Header("Authorization", "jhgeu%hsgsasokasalsoaisaposa845jUIF83jh")
    //c.Header("Content-Type", "application/text; charset=utf-8")
    c.JSON(0, gin.H{"data": "看看响应头"})

}

数据绑定

如果是原生JSON绑定结构体使用的是,反序列化

type User struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
	Sex  string `json:"sex"`
}
       var user User
       //原始json包解析数据到结构体
       err := json.Unmarshal(body, &user)

gin中的接收请求并且绑定结构体的相关api

在这里插入图片描述

json绑定在结构体

采用ShouldBindJSON api

func getUser(c *gin.Context) {
	var user *User = new(User)
	//绑定json到结构体
	err := c.ShouldBindJSON(user)
	if err != nil {
		c.JSON(200, gin.H{"msg": "绑定失败"})
		return
	}
	c.JSON(200, user)
}
query参数绑定结构体

ShouldBindQuery api,注意需要给结构体在添加tags form:···· 表明 是序列化的表单 我这里测试的时候query 绑定api 也需要该参数才可以绑定

type User struct {
	Name string `json:"name" form:"name"`
	Age  int    `json:"age" form:"age"`
	Sex  string `json:"sex" form:"sex"`
}
func getUserBy(c *gin.Context) {
	user := User{}
	if err := c.ShouldBindQuery(&user); err != nil {
		c.JSON(200, gin.H{"msg": "绑定失败"})
		return
	}
	fmt.Printf("user:%v\n", user)
	c.JSON(200, user)
}
表单数据绑定

ShouldBind

会根据请求头中的content-type去自动绑定

form-data的参数也用这个,tag用form

默认的tag就是form


type User struct {
	Name string `json:"name" form:"name"`
	Age  int    `json:"age" form:"age"`
	Sex  string `json:"sex" form:"sex"`
}
//虽然接收数据用的form格式但是jsontag还是需要的 序列化给前端的时候 会根据tag序列化字段

func getUserByform(c *gin.Context) {
	u := new(User)
	if err := c.ShouldBind(u); err != nil {
		c.JSON(200, gin.H{"msg": "绑定失败"})
		return
	}
	fmt.Printf("user:%v\n", u)
	c.JSON(200, u)
}

在这里插入图片描述

绑定resful参数
type User struct {
    Name string `json:"name"  uri:"name"`
    Age  int    `json:"age"  uri:"age"`
    Sex  string `json:"sex"  uri:"sex"`
}
func getUserByResful(c *gin.Context) {
	u := new(User)
	if err := c.ShouldBindUri(u); err != nil {
		c.JSON(200, gin.H{"msg": "绑定失败"})
		return
	}
	fmt.Printf("user:%v\n", u)
	c.JSON(200, u)
}
//路由
router.GET("/info/:name/:age/:sex", getUserByResful)

利用tag 实现java中的参数校验

在java中实现参数java需要@valid注解,但是在go中有tag绑定即可实现

go内置绑定判断 bind绑定器

需要使用参数验证功能,需要加binding tag

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

// 针对字符串的长度
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:"-"

gin 附加tag

// 枚举  只能是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

使用校验字段
type User struct {
    Name string `json:"name" form:"name" uri:"name" binding:"required" msg:"姓名不能为空"`
    Age  int    `json:"age"   form:"age"  uri:"age" binding:"gte=0,lte=100" msg:"年龄信息不合法"`
    Sex  string `json:"sex"    form:"sex"  uri:"sex" binding:"oneof=男 女" msg:"性别不能为其他"`
}

此时书传递的参数不和binding的判断的话 就无法绑定在结构体上,并且msg的数据就不为nil

在这里插入图片描述

获取校验信息

利用反射自己实现 go中追求轻量级别的代码 对于过滤器,自定义响应处理等都需要手动实现

func GetValidMsg(err error, obj any) string {
	// 使用的时候,需要传obj的指针
	getObj := reflect.TypeOf(obj)
	// 将err接口断言为具体类型
	var errs validator.ValidationErrors
	if errors.As(err, &errs) {
		// 断言成功
		for _, e := range errs {
			// 循环每一个错误信息
			// 根据报错字段名,获取结构体的具体字段
			if f, exits := getObj.Elem().FieldByName(e.Field()); exits {
				msg := f.Tag.Get("msg")
				return msg
			}
		}
	}

	return err.Error()
}

func getUserByform(c *gin.Context) {
	u := new(User)
	if err := c.ShouldBind(u); err != nil {
		msg := GetValidMsg(err, u)
		c.JSON(200, gin.H{"msg": msg})
		return
	}
	fmt.Printf("user:%v\n", u)
	c.JSON(200, u)
}
自定义绑定校验规则

编写函数

func signValid(fl validator.FieldLevel) bool {
	//源码是Field() reflect.Value 反射包裹的字段值
	age := fl.Field().Interface().(int)//这个断言的类型 是一会绑定过的tag 类型
	if age != 18 {
		return false
	}
	return true
}

注册

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

使用

type User struct {
    Name string `json:"name" form:"name" uri:"name" binding:"required" msg:"姓名不能为空"`
    Age  int    `json:"age"   form:"age"  uri:"age" binding:"Agesign" msg:"年龄信息不合法"`
    Sex  string `json:"sex"    form:"sex"  uri:"sex" binding:"oneof=男 女" msg:"性别不能为其他"`
}

gin中的文件传输

获取请求体中的文件

c.FormFile(“file”) 单文件可以采用该方式

func main() {
    router := gin.Default()
    // 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
    // 单位是字节, << 是左移预算符号,等价于 8 * 2^20
    // gin对文件上传大小的默认值是32MB
    router.MaxMultipartMemory = 8 << 20 // 8 MiB
    router.POST("/upload", func(c *gin.Context) {
       // 单文件
       file, _ := c.FormFile("file")
       log.Println(file.Filename)

       path := "./" + file.Filename
       // 上传文件至指定的完整文件路径
       c.SaveUploadedFile(file, path)

       c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
    })
    router.Run(":8080")
}

一开始入门接收请求表单哪里也有个方法可以获取表单字段 但是返回的字符串

value := c.PostForm("file")

但是可以通过获取整个表单 然后再获取文件这个字段的文件数组 这样进行上传也是一样的效果

if form, err := c.MultipartForm(); err == nil {
    files := form.File["file"]
    for _, file := range files {
       log.Println(file.Filename)
       path := "./" + file.Filename
       // 上传文件至指定的完整文件路径
       c.SaveUploadedFile(file, path)
    }
}

multipartform的源码

在这里插入图片描述

FormFile(“file”)的源码

在这里插入图片描述

所以俩者都是一样的 主要是获取fileHeader的指针

保存文件接口

SaveUploadedFile

c.SaveUploadedFile(file, path)  // 文件对象  文件路径,注意要从项目根路径开始写

还有种方式就是go种io原生的打开文件

file, _ := c.FormFile("file")
log.Println(file.Filename)
// 读取文件中的数据,返回文件对象
fileRead, _ := file.Open()
dst := "./" + file.Filename
// 创建一个文件
out, err := os.Create(dst)
if err != nil {
  fmt.Println(err)
}
defer out.Close()
// 拷贝文件对象到out中
io.Copy(out, fileRead)

下载文件

在Springboot中,对于下载文件逻辑,一般采用在响应体中,把文件读取为二进制流在写入响应体 标清楚响应头

而go中也类似

c.File("文件地址")
func downloadFile(c *gin.Context) {
    c.Header("Content-Type", "application/octet-stream")                   // 表示是文件流,唤起浏览器下载,一般设置了这个,就要设置文件名
    c.Header("Content-Disposition", "attachment; filename="+"18禁的安装包.apk") // 用来指定下载下来的文件名
    c.Header("Content-Transfer-Encoding", "binary")                        // 表示传输过程中的编码形式,乱码问题可能就是因为它
    c.File("./da_1701653930625.apk")

}

在这里插入图片描述

中间件详解

对应java中的很多重要业务逻辑,验证权限,身份认证,错误处理都是有专门的过滤器链,以及handler 接口实现,gin作为轻量级别的框架,这些内容采用中间的形式实现

func indexHandler(c *gin.Context) {
    fmt.Println("index.....")
    c.JSON(http.StatusOK, gin.H{
       "msg": "index",
    })
}

// 定义一个中间件
func m1(c *gin.Context) {
    fmt.Println("在方法响应回json之前执行.........")
}
func m2(c *gin.Context) {
    fmt.Println("在方法响应回json之后执行.........")
    c.JSON(http.StatusOK, gin.H{
       "msg": "index之后",
    })
}
func main() {
    r := gin.Default()
    //m1处于indexHandler函数的前面,请求来之后,先走m1,再走index 在m2
    
    r.GET("/index", m1, indexHandler, m2)

    _ = r.Run()//默认端口8080
}

也就是路由匹配的这些函数都是中间件,并且根据放入的顺序不同 执行不同

拦截中间件

当调用abort 方法后 后续中间件不再执行

func indexHandler(c *gin.Context) {
	fmt.Println("中间件拦截后 后面的请求就不会执行了.....")
	c.Abort()
	c.JSON(http.StatusOK, gin.H{
		"msg": "index",
	})
}

// 定义一个中间件
func m1(c *gin.Context) {
	fmt.Println("在方法响应回json之前执行.........")
}
func m2(c *gin.Context) {
	fmt.Println("在方法响应回json之后执行.........")
	c.JSON(http.StatusOK, gin.H{
		"msg": "index之后",
	})
}
//路由绑定
r.GET("/index", m1, indexHandler, m2)

由于index中使用了abort会中断中间件链路的执行

控制中间件的执行流程

c.Next() ,确实如此。当你在中间件中调用 c.Next() 后,Gin 会将控制权交给下一个中间件或路由处理器,让它们按照顺序执行。然而,一旦这些后续中间件和处理器执行完毕,控制权会回到调用 c.Next() 的中间件中,继续执行 c.Next() 之后的代码。 主要用于控制流程 ,如果打印错误

func testNext(c *gin.Context) {
    fmt.Println("执行当前中间件逻辑,但是先让执行下一个中间件执行")//1
    c.Next()
    fmt.Println("下一个中间件执行完毕  继续执行当前中间件...")//4
}
func testNext2(c *gin.Context) {
    fmt.Println("第二个中间件执行  继续执行当前逻辑...")//2
    c.Next()
    fmt.Println("第二个执行完毕...")//3
}
//路由
	r.GET("/", testNext, testNext2)

这里顺序分析 先执行 1,然后next控制权给下一个中间件,到达输出2的位置,由于没有下一个中间件所以执行3,然后回到第一个next中间件处执行4

在这里插入图片描述

全局注册中间件

写一个中间件

func initfilter(c *gin.Context) {
    fmt.Println("全局中间件开始执行.........")
    c.Next() //交给下一个中间件执行
    fmt.Println("全局中间件结束执行.........")
}

//路由全局注册
	r.Use(initfilter)

访问任意路由 发现全局中间件是第一个执行的 那么有意思的就来了(过滤器请求执行前,拦截器执行后)

在这里插入图片描述

如果多个全局中间件 即是注册顺序

在这里插入图片描述

因为源码是添加到一个函数数组(包含gin封装的http上下文的)

在这里插入图片描述

在这里插入图片描述

中间件传递数据

采用set (key,value)的方式 由于每一个请求都是隔离的 上下文之之间就算有同名key也不担心数据冲突 ,是不是很想java的Threadlocal,和过滤器存入用户认证信息的过程。只能说各个设计之间都有相似性

func indexHandler(c *gin.Context) {
	fmt.Println("中间件拦截后 后面的请求就不会执行了.....")
	c.Abort() // 直接中止请求,不再执行后面的逻辑 但是当前后面的代码块会执行
	if id, exists := c.Get("userid"); exists {
		c.JSON(http.StatusOK, gin.H{
			"msg": "index",
			"data": gin.H{
				"userid": id,
			},
		})
	}
}
func initfilter(c *gin.Context) {
	fmt.Println("全局中间件开始执行.........")
	c.Set("userid", 237)
	c.Next() //交给下一个中间件执行
	fmt.Println("全局中间件结束执行.........")
}

路由分组

对于全局中间件 可能应用的范围不同 所以gin中支持分组 并且还是链式的

r.Group("/api").POST().GET()
r.Group("/api").Use().Use()

在这里插入图片描述

此时路由地址会变成组级

func main() {
	r := gin.Default()
	r.Use(initfilter)
	group1 := r.Group("/api")

	group2 := r.Group("/test")

	group2.Use(Case)
	group1.Use(initfilter2)
	group1.GET("/index", m1, indexHandler, m2)
	group2.GET("/index", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "test index",
		})
	})

	//m1处于indexHandler函数的前面,请求来之后,先走m1,再走index

	_ = r.Run()
}

中间件执行顺序 全局->分组

注意确保中间件的注册在路由或路由组定义之前进行,以确保中间件能够正确应用。中间件的作用范围是从它注册的位置开始到定义的路由或路由组为止,所以正确的顺序和位置非常重要。

路由源码
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default(opts ...OptionFunc) *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine.With(opts...)
}

源码部分也是启动了 俩个中间件 一个日志 日志这个就是输出的路由和各个log

主要是recovery 正常的go程序遇到panic会停止recovery做的就算回复并且捕捉

func main() {

    engine := gin.Default()
    engine.GET("/panic", func(c *gin.Context) {
       panic("演示错误")
    })
    engine.Run(":8080")
}

在这里插入图片描述

整合 swagger

作为一个web框架 已经可以做到和前端交互,orm和数据库交互的过程篇幅问题不做演示,但是可以提一下swagger 这个javaer都不会陌生 knife这些 既然目前可以完成了接口交互 那么接口文档就必不可少

先写一个简单的服务案列

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


func Ping(ctx *gin.Context) {
    ctx.JSON(200, gin.H{
       "message": fmt.Sprintf("Hello World!%s", ctx.Query("name")),
    })
}
安装 swaggo
#SWAGGER 命令行
go install github.com/swaggo/swag/cmd/swag@latest
##源码依赖
go get github.com/swaggo/swag
##打包的静态文件
go get github.com/swaggo/files@latest
#适配版本库
go get github.com/swaggo/gin-swagger@latest


目录分级 实际开发中有 main.go(swagger只会根据同目录的main 进行初始化)主要是负责启动

go-swagger-example/
├── go.mod
├── main.go # 入口文件
├── controllers/ # 控制器包
│ └── hello.go
└── routes/ # 路由配置包
└── routes.go

/ HelloHandler godoc
// @Summary Say Hello
// @Description Responds with a message "Hello, World!"
// @Tags example
// @Accept  json
// @Produce  json
// @Success 200 {string} string "Hello, World!"
// @Router /hello [get]
func HelloHandler(c *gin.Context) {
    c.JSON(200, gin.H{
        "message": "Hello, World!",
    })
}

在代码中,Swagger 注释使用 // @ 开头的特殊注释。关键的注释包括:

  • @title:API 的标题。
  • @version:API 版本。
  • @description:API 的描述。
  • @contact.name:联系人姓名。
  • @license.name@license.url:API 的许可信息。
  • @BasePath:API 的基础路径。
  • @Summary@Description:为特定的处理程序提供简要描述和详细描述。
  • @Success:定义成功响应的结构。
  • @Router:定义路由路径和 HTTP 方法。

控制台输出

swag init

访问

http://localhost:8080/swagger/index.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/870841.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

阻抗控制中的dynamic movement primitives(DMP) model

在阻抗控制中&#xff0c;Dynamic Movement Primitives (DMP) 模型被用于实现一种高度灵活且可泛化的轨迹模仿学习方法。DMP模型由美国南加州大学&#xff08;University of Southern California&#xff09;的Stefan Schaal教授团队于2002年提出&#xff0c;它通过将动态系统建…

FreeRTOS 快速入门(三)之任务管理

目录 一、任务创建与删除1、什么是任务2、创建任务3、任务的删除 二、任务优先级和 Tick1、任务优先级2、Tick3、 修改优先级 三、任务状态1、阻塞状态(Blocked)2、暂停状态(Suspended)3、就绪状态(Ready)4、状态转换 四、Delay 函数五、空闲任务及其钩子函数1、介绍2、使用钩子…

议题揭晓 | 8 月 24 日,deepin 23 Release Party 暨武汉 LUG 等你来!

查看原文 Hi&#xff0c;Linuxer&#xff01; 历经三年的精心打磨和无数次的迭代测试&#xff0c;deepin 23 已正式发布&#xff0c;不少伙伴已上手体验&#xff0c;我们也收到了诸多积极反馈。 为了庆祝这一里程碑&#xff0c;本次武汉LUG暨deepin 23 线下发布活动&#xf…

二叉树(四)

一、二叉树的性质 二、练习 1.某二叉树共有399个节点&#xff0c;其中有199个度为2的节点&#xff0c;则二叉树中的叶子节点数为&#xff08; &#xff09;。 A.不存在这样的树 B.200 C.198 D.199 答案&#xff1a;B 参考二叉树的性质第三条 2.在具有2…

单位严禁非授权设备接入网络,此方案高效防护,安心无忧!

MAC与IP地址绑定策略 MAC地址&#xff08;媒体访问控制地址&#xff09;与IP地址&#xff08;互联网协议地址&#xff09;作为计算机网络中的两大关键标识符&#xff0c;分别在数据链路层与网络层各司其职。将二者绑定&#xff0c;是网络安全领域的一项常用手段&#xff0c;旨…

零基础5分钟上手亚马逊云科技核心云开发知识 - 网络基础

简介&#xff1a; 欢迎来到小李哥全新亚马逊云科技AWS云计算知识学习系列&#xff0c;适用于任何无云计算或者亚马逊云科技技术背景的开发者&#xff0c;通过这篇文章大家零基础5分钟就能完全学会亚马逊云科技一个经典的服务开发架构方案。 我会每天介绍一个基于亚马逊云科技…

Spring发送邮件性能优化?如何集成发邮件?

Spring发送邮件安全性探讨&#xff01;Spring发送邮件功能有哪些&#xff1f; 邮件发送的性能逐渐成为影响用户体验的重要因素之一。AokSend将探讨如何在Spring框架中进行Spring发送邮件的性能优化&#xff0c;确保系统能够高效、稳定地处理大量邮件请求。 Spring发送邮件&am…

Chat App 项目之解析(三)

Chat App 项目介绍与解析&#xff08;一&#xff09;-CSDN博客文章浏览阅读76次。Chat App 是一个实时聊天应用程序&#xff0c;旨在为用户提供一个简单、直观的聊天平台。该应用程序不仅支持普通用户的注册和登录&#xff0c;还提供了管理员登录功能&#xff0c;以便管理员可以…

《黑神话:悟空》媒体评分解禁 M站均分82

《黑神话&#xff1a;悟空》媒体评分现已解禁&#xff0c;截止发稿时&#xff0c;M站共有43家媒体评测&#xff0c;均分为82分。 部分媒体评测&#xff1a; God is a Geek 100&#xff1a; 毫无疑问&#xff0c;《黑神话&#xff1a;悟空》是今年最好的动作游戏之一&#xff…

计算机网络部分基础知识

网络协议的意义 单台主机内部的设备之间需要发送和接收消息&#xff0c;那么和相隔很远的两台主机之间发送消息有什么区别呢&#xff1f;两台主机通过网络发送消息&#xff0c;相当于两个网卡设备之间进行通信&#xff0c;最大的区别在于距离变长了。而距离变长带来的结果就是&…

<Linux> 进程控制

目录 一、进程创建 1. fork函数 2. fork函数返回值 3. 写时拷贝 4. fork常规用法 5. fork调用失败原因 6. 如何创建多个子进程&#xff1f; 二、进程终止 1. 进程退出场景 2. 进程退出码 3. errno 4. 进程异常退出 5. 进程常见退出方法 5.1 return退出 5.2 exit退出 5.3 _ex…

【FPGA数字信号处理】- 数字信号处理如何入门?

​数字信号处理&#xff08;Digital Signal Processing&#xff0c;简称DSP&#xff09;是一种利用计算机或专用数字硬件对信号进行处理的技术&#xff0c;在通信、音频、视频、雷达等领域发挥着越来越重要的作用&#xff0c;也是FPGA主要应用领域之一。 本文将详细介绍数字信…

Web3链上聚合器声呐已全球上线,开启区块链数据洞察新时代

在全球区块链技术高速发展的浪潮中&#xff0c;在创新发展理念的驱动下&#xff0c;区块链领域的工具类应用备受资本青睐。 2024年8月20日&#xff0c;由生纳&#xff08;香港&#xff09;国际集团倾力打造的一款链上应用工具——“声呐链上聚合器”&#xff0c;即“声呐链上数…

ESP RainMaker OTA 自动签名功能的安全启动

【如果您之前有关注乐鑫的博客和新闻&#xff0c;那么应该对 ESP RainMaker 及其各项功能有所了解。如果不曾关注&#xff0c;建议先查看相关信息&#xff0c;知晓本文背景。】 在物联网系统的建构中&#xff0c;安全性是一项核心要素。乐鑫科技对系统安全给予了极高的重视。ES…

OpenCV学堂 | 汇总 | 深度学习图像去模糊技术与模型

本文来源公众号“OpenCV学堂”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;汇总 | 深度学习图像去模糊技术与模型 引言 深度学习在图像去模糊领域展现出了强大的能力&#xff0c;通过构建复杂的神经网络模型&#xff0c;可以自…

Golang | Leetcode Golang题解之第337题打家劫舍III

题目&#xff1a; 题解&#xff1a; func rob(root *TreeNode) int {val : dfs(root)return max(val[0], val[1]) }func dfs(node *TreeNode) []int {if node nil {return []int{0, 0}}l, r : dfs(node.Left), dfs(node.Right)selected : node.Val l[1] r[1]notSelected : …

Kotlin Multiplatform 跨平台开发的优化策略与实践

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 Kotlin Multiplatform 跨平台开发的优化策略与实践 在当今快速发展的软件开发领域&#xff0c;跨平台开发技术正变得越来越重要。Kotlin Multi…

Ubuntu中服务部署

Ubuntu中服务部署 一、root用户密码一、SSH远程连接二、JDK1.8安装1、解压上传的安装包2、配置jdk环境变量 三、minio安装1、官网下载安装包2、上传文件并授权3、书写启动脚本4、启动及说明5、启动异常 四、nacos安装1、下载上传安装包&#xff0c;并解压2、修改启动脚本3、配置…

[RCTF2015]EasySQL1

打开题目 点进去看看 注册个admin账户&#xff0c;登陆进去 一个个点开看&#xff0c;没发现flag 我们也可以由此得出结论&#xff0c;页面存在二次注入的漏洞&#xff0c;并可以大胆猜测修改密码的源代码 resoponse为invalid string的关键字是被过滤的关键字&#xff0c;Le…

2百多首胎教儿童音乐ACCESS\EXCEL数据包

还记录之前我搞到过一个《113个大自然声音助眠纯音乐白噪音数据包》吗&#xff1f;今天又遇到了一个胎教儿童音乐&#xff0c;辅助用于怀孕手册、胎教指南、儿童早教类产品是个很不错的数据包哦。 MP3文件对应记录数共258条&#xff0c;大小总容量为1GB&#xff0c;其中分类汇总…