javaer快速入门 goweb框架 gin

news2024/11/16 13:37:59

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【自动驾驶】自定义消息格式的话题通信(C++版本)

目录 新建消息文件更改包xml文件中的依赖关系更改cmakelist文件中的配置执行时依赖改变cmakelist编译顺序发布者程序调用者程序新建launch文件程序测试 新建消息文件 在功能包目录下&#xff0c;新建msg文件夹&#xff0c;下面新建mymsg.msg文件&#xff0c;其内容为 string …

机械行业数字化生产供应链产品解决方案(十六)

我们的机械行业数字化生产供应链产品解决方案通过全面应用物联网、人工智能和大数据技术&#xff0c;构建了一个高效的智能生产与供应链系统&#xff0c;能够在设计、生产和物流全环节中实现实时数据监控与动态优化。系统通过智能分析和预测&#xff0c;优化了生产计划和资源配…

Linux系统驱动(十九)块设备驱动

文章目录 一、块设备驱动简介&#xff08;一&#xff09;简介&#xff08;二&#xff09;块设备驱动相关概念 二、块设备驱动&#xff08;一&#xff09;框架图1. 虚拟文件系统&#xff08;VFS&#xff09;2. Disk Cache&#xff1a;硬盘的高速缓存3. 映射层&#xff08;mappin…

IP代理如何增强网络安全性?

在当今的数字时代&#xff0c;网络安全已成为一个关键问题&#xff0c;而使用 IP 代理可以成为增强网络安全的有效方法。根据请求信息的安全性&#xff0c;IP 代理服务器可分为三类&#xff1a;高级匿名代理、普通匿名代理和透明代理。此外&#xff0c;根据使用的用途&#xff…

NT35510的LCD函数详解01(洋桃电子-触摸屏开发者笔记)

NT35510的LCD函数详解01&#xff08;洋桃电子-触摸屏开发者笔记&#xff09; 资料下载&#xff1a; 洋桃电子 YoungTalk 探索最好的 STM32 教学 (doyoung.net) 接口类型 NT35510 数据手册&#xff08;英文&#xff09;.pdf NT35510 应用手册&#xff08;英文&#xff09;.…

Jenkins持续集成工具学习

一、从装修厨房看项目开发效率优化 二、持续集成工具 三、JavaEE项目部署方式对比 四、Jenkins+SVN持续集成环境搭建

WebGoC题解(18) 630.电线杆(2019NHOI小乙)

题目描述 小C在农场的附近看到有n颗电线杆排成一行&#xff0c;相邻之间距离为20。它们高度可能不一样&#xff0c;但高度相同的电线杆顶端有电线连接。如下面示意图中&#xff0c;电线杆用粗细为6的垂直直线画&#xff0c;电线用粗细为2的水平直线画。给定每个电线杆的高度&am…

Linux-Haproxy搭建Web群集

LVS在企业应用中抗负载能力强 不支持正则处理&#xff0c;不能实现动静分离对于大型网格&#xff0c;LVS的实施配置复杂&#xff0c;维护成本较高 Haproxy是一款可提供高可用性、负载均衡、及基于TCP和HTTP应用的代理的软件 适用于负载大的Web站点运行在硬件上可支持数以万计的…

AI大模型开发——4.transformer模型(0基础也可懂)(1)

无论是想怎样学习大模型&#xff0c;transformer都是一个绕不开的话题。transformer的出现彻底改变了nlp领域&#xff0c;进一步推动了大模型的产生&#xff0c;可以说&#xff0c;transformer就是大模型开发的鼻祖。 可能只通过说大家会有些不理解。大家可以看下方的大语言模型…

打卡第四十四天:最长公共子序列、不相交的线、最大子序和、判断子序列

一、最长公共子序列 题目 文章 视频 本题和最长重复子数组区别在于这里不要求是连续的了&#xff0c;但要有相对顺序&#xff0c;即&#xff1a;"ace" 是 "abcde" 的子序列&#xff0c;但 "aec" 不是 "abcde" 的子序列。 确定dp数…

4个快捷高效的ai在线写作工具推荐。

ai在线写作因其快速的创作方式&#xff0c;高效的写作效率以及能够为我们带来无限的灵感而被广泛应用。如果你还不会使用ai进行写作的话&#xff0c;就看看下面这4款AI写作工具吧。 1、笔灵在线创作 直通车 :https://ibiling.cn 这是个在线的AI工具网站&#xff0c;在内容创作…

个人可识别信息(PII) AI 去除 API 数据接口

个人可识别信息(PII) AI 去除 API 数据接口 ai / 隐私保护 基于 AI 模型自动去除个人识别信息&#xff08;PII&#xff09; 个人信息保护 / AI 模型 。 1. 产品功能 基于自有专业模型进行 PII 自动去除高效处理敏感信息全接口支持 HTTPS&#xff08;TLS v1.0 / v1.1 / v1.2 /…

地质灾害评估和治理工程勘查设计资质乙级资质办理标准

地质灾害评估和治理工程勘查设计资质乙级资质的办理标准主要包括单位条件、专业技术人员条件、仪器设备要求以及申请材料等方面。以下是详细的办理标准&#xff1a; 一、单位条件 **1、法人资格&#xff1a;**申请单位应具有企业法人或者事业单位法人资格。 **2、管理体系&a…

龙良曲pytorch课时1-课时13

前言 这篇是个人学习龙曲良老师的pytorch课程的笔记&#xff0c;疑惑地方自己加的内容 一、pytorch引入 1. 自动求导 在深度学习中&#xff0c;我们通常需要训练一个模型来最小化损失函数。这个过程可以通过梯度下降等优化算法来实现。梯度是函数在某一点上的变化率&#x…

Linux基础入门--目录结构之基本目录操作及注意事项

&#x1f600;前言 本篇博文是关于Linux基础入门–目录结构的基本介绍、基本目录和操作命令&#xff0c;希望你能够喜欢 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&…

滴答定时器笔记

SysTick介绍 1.1 什么是SysTick&#xff1f; Systick&#xff0c;即滴答定时器&#xff0c;是内核中的一个特殊定时器&#xff0c;用于提供系统级的定时服务。该定时器是一个24位的 递减计数器&#xff0c;具有自动重载值寄存器的功能。当计数器到达自动重载值时&#xff0c;它…

无人机中的温度/湿度/气压传感器详解!!!

一、温度传感器 温度传感器是一种用来测量物体或环境的温度变化的传感器。在无人机中&#xff0c;温度传感器通常采用红外线热成像技术&#xff0c;通过红外线相机获取物体表面的温度数据&#xff0c;实现对环境和物体温度的监测和测量。该技术具有响应速度快、无需接触、测量…

100个练习学习Rust!构文・整数・变量

前一篇文章 【0】准备 【1】构文・整数・变量 ← 本次全部文章列表 《100 Exercise To Learn Rust》第2回&#xff0c;也就是实际演习的第1回&#xff01;从这次开始&#xff0c;我们会适度减少前置说明&#xff0c;直接进入问题的解决&#xff01; 本次的相关页面 1.1. Syn…

SpringBoot入门第一篇

目录 SpringBootSpring Boot核心特性Spring Boot应用结构目录结构样例目录结构讲解 Spring Boot版本更新概览升级建议总结 常见使用场景社区和资源常见疑问**Spring Boot的自动配置机制是如何工作的&#xff1f;****Spring Boot支持哪些常见的配置文件格式&#xff1f;****为什…

硬件设计-1/f噪声、均方根(RMS)噪声与等效噪声带宽

简介&#xff1a; 学习下噪声的知识&#xff0c;为什么写这个呢&#xff0c;因为前几年有次面试和别人谈到这个问题。 定义解释&#xff1a; 1/F噪声 高频下的噪声为白噪声(即其频谱密度不会随频率而变化)。这种情况适用于运算放大器的大 部分频率范围&#xff0c;但在低频率…