Golang | Web开发之Gin框架快速入门基础实践

news2024/11/19 15:57:55

欢迎关注「全栈工程师修炼指南」公众号

点击 👇 下方卡片 即可关注我哟!

设为星标⭐每天带你 基础入门 到 进阶实践 再到 放弃学习

专注 企业运维实践、网络安全、系统运维、应用开发、物联网实战、全栈文章 等知识分享

  花开堪折直须折,莫待无花空折枝 


作者主页:[ https://www.weiyigeek.top ]  

博客:[ https://blog.weiyigeek.top ]

作者<开发安全运维>学习交流群,回复【学习交流群】即可加入


文章目录:

5a5c58f66c9e492cac2a702366b05238.png


0x00 前言简述

描述: 通过上一阶段的Go语言的基础学习,相信各位看友有一定的Go语言开发的基础,今天将给大家引入Go语言的Web框架,但是在介绍此之前,我们先了解前面我们学习的后端语言通常都有相依赖的Web框架, 例如 PHP 的 Laravel、THinkPHP、Yii等框架 ,Java 的 Spring Boot 、Quarkus、Micronaut、Jakarta EEVert.x 等框架,作为一名Google推出的Go语言来说也拥有众多的Web框架,例如今天讲解的Gin框架,以及 Aero、Beego 、Iris、Echo、Revel、Martini 等众多优秀的Web框架, 其各有各的特点。

总之,选择正确的Go Web Framework对于交付高质量和高效率的网络应用程序至关重要。

1.什么是Gin?

描述: Gin 是一个用 Go (Golang) 编写的 web 框架, 它提供类似Martini的API,但性能更佳,速度提升高达40倍, 号称是Go语言最快的全功能Web框架,所以如果你是性能和高效的追求者,你会爱上 Gin.

f794cfca8ffd938c1a4bd876b4f84073.jpeg

官网地址: https://gin-gonic.com/
帮助文档: https://gin-gonic.com/zh-cn/docs/
Go Web框架基准测试: https://github.com/gin-gonic/gin/blob/master/BENCHMARKS.md

7c48b9d4cce450ea0991056267d19066.png

2.有啥特点Gin?

描述: 其主要特点可以归纳终结为一下所述。
快速 : 基于 Radix 树的路由,小内存占用、没有反射、可预测的 API 性能。
路由组 : 帮助您更好地组织您的路由,例如,按照需要授权和不需要授权和不同API版本进行分组,此外路由分组可以无限嵌套而不降低性能。
内置渲染: 其为 JSON,XML 和 HTML 渲染提供了易于使用的 API。
JSON 解析: 解析并验证请求的 JSON,例如检查所需值的存在。
中间件 处理 : 传入的 HTTP 请求可以由一系列中间件和最终操作来处理, 例如:Logger,Authorization,GZIP,最终操作 DB。
Crash 处理 : 可以 catch 一个发生在 HTTP 请求中的 panic 并 recover 它。这样你的服务器将始终可用例如,你可以向 Sentry 报告这个 panic!
Error 处理: 支持收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件,数据库并通过网络发送。
可扩展性: 新建一个中间件非常简单,去查看示例代码吧

3.如何下载安装Gin?

描述: 由于Gin是基于Golang开发的所以必须得先安装Go环境,如果你还没安装此环境可以参考博主此系列教程文章【1.Go编程快速入门学习】( https://blog.weiyigeek.top/2020/4-23-283.html#0x01-Go语言开发环境搭建),下述操作都是假设你已经安装配置好Go语言相关环境情况下进行的。

温馨提示: Gin 必须是在Go 1.13 及以上版本上运行,当然我相信大家用的不会这么老的版本的。

在 Windows 操作系统开发

# 启用模块以及配置模块拉取镜像
PS C:\Users\WeiyiGeek> $env:GO111MODULE = "on"
PS C:\Users\WeiyiGeek> $env:GOPROXY = "https://goproxy.cn,direct"
# 拉取最新的gin模块包
PS C:\Users\WeiyiGeek> go get -u -v github.com/gin-gonic/gin

在 Linux 操作系统开发

# 启用模块以及配置模块拉取镜像
$ GO111MODULE = "on"
$ GOPROXY = "https://goproxy.cn,direct"
# 拉取最新的gin模块包
$ go get -u -v github.com/gin-gonic/gin

0x01 快速上手

描述: 作者为了方便看友们快速上手,此处将其常规使用方法函数进行示例演示,为加深学习印象与成果。

1.Hello-World 示例

描述: 下载安装 Gin 软件包后创建一个 hello-gin 项目文件夹以及main.go文件,此处作者以一个简单的hello woirld示例来讲解。

创建文件夹: $ mkdir -p $GOPATH/src/github.com/weiyigeek/hello-gin && cd "$_"

// hello-gin/main.go
// 声明当前文件属于哪个包,如果是主文件则写成main 
package main 
 
// 导入gin包
import "github.com/gin-gonic/gin" 

func main() { 
    // 生成了一个实例,这个实例即 WSGI 应用程序 
    r := gin.Default() 

    // 声明了一个GET方法路由 / 及对应的处理函数 
    r.GET("/", func(c *gin.Context) { 
      c.String(200, "Hello, Go Gin Web!") 
    })
    
    // 返回以JSON格式的字符串
    r.GET("/test", func(c *gin.Context) { 
      c.JSON(200, gin.H{
        "msg": "Test, Go Gin Web!",
      }) 
    })

    // 除了GET方法以外,GIN还支持的其他HTTP方法如下;
    r.POST("/somePost", func(c *gin.Context) { c.JSON(200, gin.H{ "msg": "posting",}) })
    r.PUT("/somePut", func(c *gin.Context) { c.JSON(200, gin.H{ "msg": "putting",}) })
    r.DELETE("/someDelete", func(c *gin.Context) { c.JSON(200, gin.H{ "msg": "deleting",}) })
    r.PATCH("/somePatch", func(c *gin.Context) { c.JSON(200, gin.H{ "msg": "patching",}) })
    r.HEAD("/someHead", func(c *gin.Context) { c.JSON(200, gin.H{ "msg": "head",}) })
    r.OPTIONS("/someOptions", func(c *gin.Context) { c.JSON(200, gin.H{ "msg": "options",}) })
    
    // 特殊的 Any 函数即响应所有HTTP方法
    r.Any("/testing", func(c *gin.Context) { c.JSON(200, gin.H{ "msg": "Any registers a route that matches all the HTTP methods. EX: GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.",}) })

    // 让应用运行在本地服务器上,默认监听端口是 8090
    g.Run(":8080") // listen and serve on 0.0.0.0:8080 
}

初始化及编写和执行 Go 代码

# 初始化
go mod init hello-gin
go mod tidy   # 英 / 'taɪdi'
# 运行
$ go run main.go

1647f50a23ef7a8e825d40365bd5fcaf.png

2.JSON/XML/YAML/ProtoBuf格式渲染示例

描述: 本小节演示了AsciiJSON、JSON、purejson以及JSONP与SecureJSON示例以及XML/YAML/ProtoBuf渲染显示。

package main

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

// 开辟一块内存空间
var data = make(map[string]interface{})

func setupRouter() *gin.Engine {
  // Disable Console Color
  gin.DisableConsoleColor()

  // Default返回一个Engine实例,该实例已连接Logger和Recovery中间件。
  r := gin.Default()

  // 示例1.AsciiJSON 生成具有转义的非 ASCII 字符的 ASCII-only JSON。
  r.GET("/AsciiJSON", func(c *gin.Context) {
    data = map[string]interface{}{
      "lang": "GO语言",
      "tag":  "<br>",
    }
    // 针对中文以及特殊符号进行的是Unicode编码
    // 输出 : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
    c.AsciiJSON(http.StatusOK, data)
  })

  // 示例2.通常,JSON 使用 unicode 替换特殊 HTML 字符,例如 < 变为 \ u003c。如果要按字面对这些字符进行编码,则可以使用 PureJSON。
  // 提供 unicode 实体
  r.GET("/json", func(c *gin.Context) {
    // 温馨提示: gin.H 是 map[string]interface{} 的一种快捷方式
    c.JSON(http.StatusOK, gin.H{
      "html": "<b>Hello, world! Go-Gin 框架</b>",
    })
  })
  // 提供字面字符(即原始数据未经过编码处理)
  r.GET("/purejson", func(c *gin.Context) {
    c.PureJSON(http.StatusOK, gin.H{
      "html": "<b>Hello, world! Go-Gin 框架</b>",
    })
  })

  // 示例3.使用 JSONP 向不同域的服务器请求数据,如果查询参数存在回调,则将回调添加到响应体中。
  r.GET("/JSONP", func(ctx *gin.Context) {
    data = map[string]interface{}{
      "name": "WeiyiGeek",
      "site": "weiyigeek.top",
    }
    // /JSONP?callback=u 其默认参数是callback
    // 将输出:u({\"name\":\"WeiyiGeek\",\"site\":\"weiyigeek.top\"})
    ctx.JSONP(http.StatusOK, data)
  })

  // 示例4.使用 SecureJSON 防止 json 劫持,如果给定的结构是数组值,则默认预置 "while(1)," 到响应体。
  r.GET("/SecureJSON", func(c *gin.Context) {
    // 你也可以使用自己的 SecureJSON 前缀,据说Google采用的是while的方法,facebook采用的是for的方法。
    r.SecureJsonPrefix(")]}',\n")

    names := []string{"Go", "Java", "PHP"}
    // 若不设置SecureJsonPrefix,默认输出:for(;;);["Go", "Java", "PHP"]
    c.SecureJSON(http.StatusOK, names)
  })

  return r
}

func main() {
  r := setupRouter()
  // Listen and Server in 0.0.0.0:8080
  r.Run(":8080")
}

代码运行访问结果:
bfae4147f02eeec5cabef1c9280a2239.png

补充说明: Gin请求响应XML/JSON/YAML/ProtoBuf格式输出示例片段

// 方式1.JSON 格式方法1, 温馨提示 gin.H 是 map[string]interface{} 的一种快捷方式
r.GET("/someJSON", func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
})
// 方式2.JSON 格式方法2,
r.GET("/AsciiJSON", func(c *gin.Context) {
  data = map[string]interface{}{
    "lang": "Golang",
    "tag":  "gin",
  }
  c.AsciiJSON(http.StatusOK, data)
})
// 方式3.JSON 格式方法3
r.GET("/moreJSON", func(c *gin.Context) {
  // 你也可以使用一个结构体
  var msg struct {
    Name    string `json:"user"`
    Message string
    Number  int
  }
  msg.Name = "Lena"
  msg.Message = "hey"
  msg.Number = 123
  // 注意 msg.Name 在 JSON 中变成了 "user"
  // 将输出:{"user": "Lena", "Message": "hey", "Number": 123}
  c.JSON(http.StatusOK, msg)
})

// XML 格式输出
r.GET("/someXML", func(c *gin.Context) {
  c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
})

// YAML 格式输出
r.GET("/someYAML", func(c *gin.Context) {
  c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
})

// ProtoBuf 格式输出
r.GET("/someProtoBuf", func(c *gin.Context) {
  reps := []int64{int64(1), int64(2)}
  label := "test"
  // protobuf 的具体定义写在 testdata/protoexample 文件中。
  data := &protoexample.Test{
    Label: &label,
    Reps:  reps,
  }
  // 请注意,数据在响应中变为二进制数据
  // 将输出被 protoexample.Test protobuf 序列化了的数据
  c.ProtoBuf(http.StatusOK, data)
})

3.Basic-Auth 方法示例

代码示例:

package main

import (
  "fmt"
  "net/http"

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

var db = make(map[string]string)

// 或者模拟一些私人数据
var secrets = gin.H{
  "foo":       gin.H{"email": "foo@bar.com", "phone": "123433"},
  "weiyigeek": gin.H{"email": "master@weiyigeek.top", "phone": "666"},
}

func setupRouter() *gin.Engine {
  // Disable Console Color
  // gin.DisableConsoleColor()
  r := gin.Default()

  // 1.Query 和 POST form 请求示例
  // Ping test
  r.GET("/ping", func(c *gin.Context) {
    c.String(http.StatusOK, "pong")
  })

  // 方式1.Get user value, example : /user/weiyigeek
  r.GET("/user/:name", func(c *gin.Context) {
    user := c.Params.ByName("name")
    value, ok := db[user]
    fmt.Println(user)
    if ok {
      c.JSON(http.StatusOK, gin.H{"user": user, "value": value})
    } else {
      c.JSON(http.StatusOK, gin.H{"user": user, "status": "no value"})
    }
  })

  // 方式2.Get user value, example : /username?id=1&user=weiyigeek&page=1
  r.GET("/username", func(c *gin.Context) {
    id := c.Query("id")
    user := c.Query("user")
    page := c.DefaultQuery("page", "0")
    if id != "" && user != "" || page != "" {
      c.JSON(http.StatusOK, gin.H{"code": "200", "data": gin.H{"id": id, "user": user, "page": page}})
    } else {
      c.JSON(http.StatusBadRequest, gin.H{"code": "0", "status": "err"})
    }
  })

  // 方式3.POST user value, example :
  // POST /username
  // id=1&user=weiyigeek&page=10
  r.POST("/username", func(c *gin.Context) {
    id := c.PostForm("id")
    user := c.PostForm("user")              // 表单数据
    page := c.DefaultPostForm("page", "10") // 默认值
    if secret, ok := secrets[user]; ok {
      c.JSON(http.StatusOK, gin.H{"id": id, "user": user, "page": page, "secret": secret})
    } else {
      c.JSON(http.StatusForbidden, gin.H{"user": user, "secret": "NO SECRET :("})
    }
  })

  // 路由组使用 gin.BasicAuth() 中间件设置 Auth 认证访问
  // gin.Accounts 是 map[string]string 的一种快捷方式

  // 方式1
  // authorized := r.Group("/create")
  // authorized.Use(gin.BasicAuth(gin.Credentials{
  //    "foo":  "bar",
  //    "weiyieek": "123456",
  //}))

  // 方式2
  authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
    "foo":       "bar",    // user:foo password:bar
    "weiyigeek": "123456", // user:manu password:123
  }))

  // admin/secrets 端点 触发 "localhost:8080/admin/secrets
  /* example curl for /admin with basicauth header
         Zm9vOmJhcg== is base64("weiyieek:123456")
         JS -> btoa("weiyigeek:123456") => 'd2VpeWlnZWVrOjEyMzQ1Ng=='

      curl -X POST http://localhost:8080/admin/secrets \
  -H 'authorization: Basic d2VpeWlnZWVrOjEyMzQ1Ng=='   \
  -H 'content-type: application/json'  \
  -d '{"value":"weiyigeek.top"}'
  */
  authorized.POST("secrets", func(c *gin.Context) {
    // 获取用户,它是由 BasicAuth 中间件设置的
    user := c.MustGet(gin.AuthUserKey).(string)
    // 解析提交的JSON数据(Parse JSON)
    var json struct {
      Value string `json:"value" binding:"required"`
    }
    // 将提交的数据与对应用户绑定
    if c.Bind(&json) == nil {
      db[user] = json.Value
      c.JSON(http.StatusOK, gin.H{"status": "ok", "code": "200"})
    } else {
      c.JSON(http.StatusBadRequest, gin.H{"status": "err", "code": "0"})
    }
  })

  // authorized POST username value
  authorized.POST("/user", func(c *gin.Context) {
    // 获取用户,它是由 BasicAuth 中间件设置的
    user := c.MustGet(gin.AuthUserKey).(string)
    // 根据authorization头,获取自定义数据
    if secret, ok := secrets[user]; ok {
      c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
    } else {
      c.JSON(http.StatusForbidden, gin.H{"user": user, "secret": "NO SECRET :("})
    }
  })

  return r
}

func main() {
  r := setupRouter()
  // Listen and Server in 0.0.0.0:8080
  r.Run(":8080")
}

执行结果:

# 正式环境设置环境变量与代码
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> main.setupRouter.func1 (3 handlers)
[GIN-debug] GET    /user/:name               --> main.setupRouter.func2 (3 handlers)
[GIN-debug] GET    /username                 --> main.setupRouter.func3 (3 handlers)
[GIN-debug] POST   /username                 --> main.setupRouter.func4 (3 handlers)
[GIN-debug] POST   /admin/secrets            --> main.setupRouter.func5 (4 handlers)
[GIN-debug] POST   /admin/user               --> main.setupRouter.func6 (4 handlers)
[GIN-debug] Listening and serving HTTP on :8080

# 1.在终端中运行curl进行POST请求写入值到auth用户中
$ curl --location 'http://127.0.0.1:8080/admin/secrets' \
--header 'authorization: Basic d2VpeWlnZWVrOjEyMzQ1Ng==' \
--header 'content-type: application/json' \
--data '{"value":"weiyigeek.top"}'
  # {"code":"200","status":"ok"}

# 2.GET请求通过url方法Params获取用户字段信息并返回该auth用户写入信息。
curl --location 'http://127.0.0.1:8080/user/weiyigeek'
  # {"user": "weiyigeek", "value": "weiyigeek.top" }

# 3.GET请求通过url参数获取用户字段并返回其kv。
curl --location 'http://127.0.0.1:8080/username?id=1&user=weiyigeek&page=10' \
--header 'authorization: Basic Zm9vOmJhcg=='
  # {"code":"200","data":{"id":"1","page":"10","user":"weiyigeek"}}

# 4.POST请求通过获取发送的表单数据获取用户字段并模拟一些私人数据。
curl --location 'http://127.0.0.1:8080/username' \
--header 'authorization: Basic Zm9vOmJhcg==' \
--form 'id="1"' \
--form 'user="weiyigeek"' \
--form 'page="10"'
  # {"id":"1","page":"10","secret":{"email":"master@weiyigeek.top","phone":"666"},"user":"weiyigeek"}
  
# 5.通过POST请求通过认证auth后获取用户字段并模拟一些私人数据
curl --location --request POST 'http://127.0.0.1:8080/admin/user' \
--header 'authorization: Basic d2VpeWlnZWVrOjEyMzQ1Ng=='
  # {"secret":{"email":"master@weiyigeek.top","phone":"666"},"user":"weiyigeek"}

2d0274adfc1ee002562a173e3f332305.png

4.路由、表单参数绑定示例

描述: 在网页中往往需要从URL 路由参数、URL 请求参数、表单中提交数据到后端进行处理,例如最常见的就是登录时需要传递用户名与密码以及验证码,所以作为Gin这么优秀的Web框架也是支持处理表单数据的。

请求参数获取总结:

  • Param("参数名") 方法: 获取路由参数

  • Query("参数名") 方法:获取URL参数

  • DefaultQuery("参数名","默认值") 方法:获取URL参数值,当不存在该参数时自动填充默认值

  • QueryMap("参数名") 方法:映射URL查询字符串

  • PostForm("参数名") 方法: 获取 POST 提交表单参数值

  • DefaultPostForm("参数名","默认值") 方法:获取 POST 提交表单参数值,当不存在该参数时自动填充默认值

  • PostFormMap("参数名") 方法:映射表单参数

  • ShouldBind(&结构体实例化对象) 方法:针对提交的GET请求或者POST请求参数自动进行绑定。

    // 如果是 GET 请求,只使用 Form 绑定引擎(query)。
    // 如果是 POST 请求,首先检查 content-type 是否为 JSON 或 XML,然后再使用 Form 或(form-data)。
    // 查看更多:https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88

代码示例:

package main

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

func setupRouter() *gin.Engine {
	// Disable Console Color
	gin.DisableConsoleColor()

	// Default返回一个Engine实例,该实例已连接Logger和Recovery中间件。
	r := gin.Default()

	// 示例1. 使用现有的基础请求对象解析查询字符串参数。
	// 示例 URL:/param_get?firstname=Weiyi&lastname=Geek
	r.GET("/param_get", func(c *gin.Context) {
		firstname := c.DefaultQuery("firstname", "Guest")
		lastname := c.Query("lastname") // 注意: c.Request.URL.Query().Get("lastname") 的一种快捷方式
		c.String(http.StatusOK, "Hello %s %s, Welcome To Study Go Gin!", firstname, lastname)
	})

	// 示例2.提交常规类型的数据表单并解析POST请求数据字符串参数。
	// Content-Type: application/x-www-form-urlencoded
	r.POST("/form_post", func(c *gin.Context) {
		name := c.PostForm("name")
		msg := c.DefaultPostForm("message", "empty")
		c.JSON(http.StatusOK, gin.H{"code": 200, "data": gin.H{"name": name, "message": msg}})
	})

	// 示例3.使用QueryMap、PostFormMap 函数映射查询字符串或表单参数
	// Content-Type: application/x-www-form-urlencoded
	r.POST("/form_getpostmap", func(c *gin.Context) {
		post_id := c.QueryMap("id")
		post_info := c.PostFormMap("user")
		c.JSON(http.StatusOK, gin.H{"code": 200, "id": post_id, "post": post_info})
	})

	// 示例4.获取URL路由中的参数
	// 此 handler 将匹配 /router_get/john (但不会匹配 /router_get/ 或者 /router_get)
	r.GET("/router_get/:name", func(c *gin.Context) {
		name := c.Param("name")
		c.String(http.StatusOK, "Hello %s", name)
	})
	// 此 handler 将匹配 /router_get/john/ 和 /router_get/john/send (若如果没有其他路由匹配 /router_get/john,它将重定向到 /router_get/john/)
	r.GET("/router_get/:name/*action", func(c *gin.Context) {
		name := c.Param("name")
		action := c.Param("action")
		message := name + " is " + action
		c.String(http.StatusOK, message)
	})

	// 示例6.c.ShouldBind 函数:针对提交URL参数进行自动选择合适的绑定。
	//GET  /router_get/login?user=weiyigeek&password=123456
	r.GET("/router_get/login", func(c *gin.Context) {
		// 你可以使用显式绑定声明绑定 Query Param:
		// c.ShouldBindWith(&form, binding.Query)
		// 或者简单地使用 ShouldBind 方法自动绑定:
		// POST请求:登录表单的结构体
		type LoginForm struct {
			User     string `form:"user" binding:"required"`
			Password string `form:"password" binding:"required"`
		}
		var login LoginForm
		// 在这种情况下,将自动选择合适的绑定 (值得学习。)
		if c.ShouldBind(&login) == nil {
			if login.User == "user" && login.Password == "password" {
				c.JSON(http.StatusOK, gin.H{"code": 200, "message": "you are logged in"})
			} else {
				c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "stmessageatus": "unauthorized"})
			}
		}
	})

	// 示例6.c.ShouldBind 函数:针对提交数据表单进行自动选择合适的绑定。
	// POST /router_get/login
	// ......
	// user=weiyigeek&password=123456
	r.POST("/login", func(c *gin.Context) {
		// 简单地使用 ShouldBind 方法自动绑定:
		// POST请求:登录表单的结构体
		type LoginForm struct {
			User     string `form:"user" binding:"required"`
			Password string `form:"password" binding:"required"`
		}
		var login LoginForm

		// 在这种情况下,将自动选择合适的绑定 (值得学习。)
		if c.ShouldBind(&login) == nil {
			if login.User == "weiyigeek" && login.Password == "123456" {
				c.JSON(http.StatusOK, gin.H{"code": 200, "message": "you are logged in"})
			} else {
				c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "stmessageatus": "unauthorized"})
			}
		}
	})

	// 示例7.c.ShouldBind 函数绑定 HTML 复选框表单数据
	// <form action="/" method="POST">
	// 	<p>Check some colors</p>
	// 	<label for="red">Red</label>
	// 	<input type="checkbox" name="colors[]" value="red" id="red">
	// 	<label for="green">Green</label>
	// 	<input type="checkbox" name="colors[]" value="green" id="green">
	// 	<label for="blue">Blue</label>
	// 	<input type="checkbox" name="colors[]" value="blue" id="blue">
	// 	<input type="submit">
	// </form>
	r.POST("/mutil_color", func(c *gin.Context) {
		// 绑定 HTML 复选框
		type myForm struct {
			Colors []string `form:"colors[]"`
		}
		var fakeForm myForm
		// 解析 form 表单
		if c.ShouldBind(&fakeForm) == nil {
			// 输出结果: {"code": 200,"color":["red","green","blue"]}
			c.JSON(http.StatusOK, gin.H{"code": 200, "color": fakeForm.Colors})
		}

	})

	return r
}

func main() {
	r := setupRouter()
	// Listen and Server in 0.0.0.0:8080
	r.Run(":8080")
}

运行结果:

[GIN-debug] GET    /param_get                --> main.setupRouter.func1 (3 handlers)
[GIN-debug] POST   /form_post                --> main.setupRouter.func2 (3 handlers)
[GIN-debug] POST   /form_getpostmap          --> main.setupRouter.func3 (3 handlers)
[GIN-debug] GET    /router_get/:name         --> main.setupRouter.func4 (3 handlers)
[GIN-debug] GET    /router_get/:name/*action --> main.setupRouter.func5 (3 handlers)
[GIN-debug] GET    /router_get/login         --> main.setupRouter.func6 (3 handlers)
[GIN-debug] POST   /login                    --> main.setupRouter.func7 (3 handlers)
[GIN-debug] POST   /mutil_color              --> main.setupRouter.func8 (3 handlers)

执行结果

# 示例1
curl --location 'http://127.0.0.1:8080/param_get?firstname=Weiyi&lastname=Geek'
  # Hello Weiyi Geek, Welcome To Study Go Gin!

# 示例2
curl --location 'http://127.0.0.1:8080/form_post' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'name=WeiyiGeek' \
--data-urlencode 'message=一个想成为全栈的男人'
  # {"code":200,"data":{"message":"一个想成为全栈的男人","name":"WeiyiGeek"}}

# 示例3
curl --location --globoff 'http://127.0.0.1:8080/form_getpostmap?id[name]=weiyigeek&id[site]=www.weiyigeel.top' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data 'user[name]=weiyigeek&user[message]=一个想成为全栈的男人'
  # {"code":200,"id":{"name":"weiyigeek","site":"www.weiyigeel.top"},"post":{"message":"一个想成为全栈的男人","name":"weiyigeek"}}

# 示例4
curl --location 'http://127.0.0.1:8080/router_get/weiyigeek' 
  # Hello weiyigeek

# 示例5
curl --location 'http://127.0.0.1:8080/router_get/weiyigeek/devops'
  # weiyigeek is /devops

# 示例6 & 示例7
curl --location 'http://127.0.0.1:8080/router_get/login?user=user&password=password'
curl --location 'http://127.0.0.1:8080/login' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data 'user=user&password=password'
  # {"code":200,"message":"you are logged in"}

5.Cookie、Header 获取和设置

描述: 在 Gin 中我们可以使用 c.Cookie 和 c.SetCookie方法 以及 c.GetHeader 和 c.Header 方法快速的获取设置 Cookie与Header值。

代码示例:

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

// 示例1.设置与获取 Cookie Handler
func getCookieHandler(c *gin.Context) {
	// 获取 cookies 中 secret 字段信息
	secret, err := c.Cookie("secret")
	// 判断 获取cookies 是否有误
	if err == nil {
		// 设置 Cookies:SetCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool)
		c.SetCookie("gin_cookie", "test", 3600, "/", "blog.weiyigeek.top", false, true)
		c.JSON(http.StatusOK, gin.H{
			"code": 200,
			"data": gin.H{
				"secret": secret,
			},
		})
	} else {
		c.JSON(http.StatusBadRequest, gin.H{
			"code":    0,
			"message": "请求参数有误!",
		})
	}
}

// 示例2.设置与获取 header Handler
func getHeaderHandler(c *gin.Context) {
	// 获取 header 中 secret 字段信息
	secret := c.GetHeader("secret")
	// 判断 获取 header 是否为空
	if secret != "" {
		// 设置 header (key string, value string)
		c.Header("gin_header", "blog.weiyigeek.top")
		c.JSON(http.StatusOK, gin.H{
			"code": 200,
			"data": gin.H{
				"secret": secret,
			},
		})
	} else {
		c.JSON(http.StatusBadRequest, gin.H{
			"code":    0,
			"message": "请求参数有误!",
		})
	}
}

func main() {
	// Default返回一个Engine实例,该实例已连接Logger和Recovery中间件。
	r := gin.Default()
	// 示例1.设置与获取 Cookie
	r.GET("/cookie_get", getCookieHandler)
	// 示例2.设置与获取 Header
	r.GET("/header_get", getHeaderHandler)
	// Listen and Server in 0.0.0.0:8080
	r.Run(":8080")
}

执行结果:

# Cookie 获取与设置 
curl --location 'http://10.20.172.106:8080/cookie_get' \
--header 'Cookie: secret=weiyigeek.top;'

# Header 获取与设置 
curl --location 'http://10.20.172.106:8080/header_get' \
--header 'secret: weiyigeek.top'

5f64627912eeb6330f58b90fa7acf7ab.png

偷偷的告诉你哟?极客全栈修炼】微信小程序已经上线了,

可直接在微信里面直接浏览博主博客了哟,后续将上线更多有趣的小工具。


6.路由组及模型绑定和验证

描述: 在Gin中若要将请求体绑定到结构体中可使用模型绑定,其目前支持JSON、XML、YAML和标准表单值的绑定(foo=bar&boo=baz),但是使用时,必须要在要绑定的所有字段上,设置相应的tag。

例如,使用 JSON 绑定时,设置字段标签为 json:"参数名称",如果一个字段的 tag 加上了 binding:"required",但绑定时是空值, Gin 会报错。

Gin提供了两类绑定方法, 在使用 Bind 方法时,Gin 会尝试根据 Content-Type 推断如何绑定。
Must bind

Methods - Bind, BindJSON, BindXML, BindQuery, BindYAML
说明: 上述方法属于 MustBindWith 的具体调用,如果发生绑定错误则请求终止,并触发 c.AbortWithError(400, err).SetType(ErrorTypeBind)。

Should bind

Methods - ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML
说明: Behavior - 这些方法属于 ShouldBindWith 的具体调用,如果发生绑定错误,Gin 会返回错误并由开发者处理错误和请求。

代码示例

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

// 绑定 FORM / JSON / XML
type Login struct {
	User     string `form:"user" json:"user" xml:"user"  binding:"required"`
	Password string `form:"password" json:"password" xml:"password" binding:"required"`
	// Birthday time.Time `form:"birthday" json:"birthday" xml:"birthday" time_format:"2006-01-02" time_utc:"1"`

}

// 你可以使用显式绑定声明绑定 Uri:
// c.ShouldBindWith(&uri, binding.Uri)
// 使用 ShouldBindUri 函数绑定 Uri 路由参数
func bindUri(c *gin.Context) {
	// 自定义结构体进行数据绑定
	type Person struct {
		ID   string `uri:"uuid" binding:"required"`
		Name string `uri:"name" binding:"required"`
	}
	var person Person
	// 绑定路由参数
	if err := c.ShouldBindUri(&person); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"name": person.Name, "uuid": person.ID})
}

// 使用 ShouldBindQuery 函数只绑定 url 查询参数而忽略 post 数据
func bindQuery(c *gin.Context) {
	// GET请求:url 查询参数的结构体
	type BlogUrl struct {
		Name string `form:"name" binding:"required"`
		Blog string `form:"blog" binding:"required"`
	}
	var site BlogUrl
	// 绑定 URL 请求参数
	if c.ShouldBindQuery(&site) == nil {
		c.JSON(http.StatusOK, gin.H{"code": 200, "data": gin.H{"name": site.Name, "blog": site.Blog}})
	} else {
		c.JSON(http.StatusOK, gin.H{"code": 0, "message": "Query Param Error!"})
	}
}

// 使用 ShouldBindJSON 绑定 JSON 数据
func bindJson(c *gin.Context) {
	var json Login
	if err := c.ShouldBindJSON(&json); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if json.User != "weiyigeek" || json.Password != "123456" {
		c.JSON(http.StatusUnauthorized, gin.H{"code": 0, "status": "unauthorized"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"code": 200, "status": "you are logged in"})
}

// 使用 ShouldBindXML 绑定 xml 数据
func bindXml(c *gin.Context) {
	var xml Login
	if err := c.ShouldBindXML(&xml); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if xml.User != "weiyigeek" || xml.Password != "123456" {
		c.JSON(http.StatusUnauthorized, gin.H{"code": 0, "status": "unauthorized"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"code": 200, "status": "you are logged in"})
}

// 使用 ShouldBindForm 绑定 表单 数据
func bindForm(c *gin.Context) {
	var form Login
	if err := c.ShouldBindWith(&form, binding.Form); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if form.User != "weiyigeek" || form.Password != "123456" {
		c.JSON(http.StatusUnauthorized, gin.H{"code": 0, "status": "unauthorized"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"code": 200, "status": "you are logged in"})
}

// 绑定表单数据至自定义结构体
type StructA struct {
	Name string `form:"name" json:"name" xml:"name" text:"name"`
}
type StructB struct {
	// 方式1
	NestedStruct StructA
	// 方式2
	// NestedStructPointer *StructA
	// 方式3
	// NestedAnonyStruct struct {
	// 		FieldX string `form:"field_x"`
	// }
	Blog string `form:"blog" json:"blog" xml:"blog" text:"name"`
}

func bindCustom(c *gin.Context) {
	var person StructB
	if c.Bind(&person) == nil {
		c.JSON(http.StatusOK, gin.H{
			"person": person.NestedStruct,
			"blog":   person.Blog,
		})
	} else {
		c.JSON(http.StatusBadRequest, gin.H{
			"message": "BadRequest",
		})
	}
}

func main() {
	// Default返回一个Engine实例,该实例已连接Logger和Recovery中间件。
	r := gin.Default()

	// 简单的路由组: v1
	v1 := r.Group("/v1")
	{
		// 示例1.使用 ShouldBindUri 函数绑定 Uri 路由参数
		v1.GET("/person/:name/:uuid", bindUri)

		// 示例2.使用 ShouldBindQuery 函数只绑定 url 查询参数而忽略 post 数据
		v1.Any("/site", bindQuery)
	}

	// 简单的路由组: v2
	v2 := r.Group("/v2")
	{
		// 示例3.使用 ShouldBindJSON 绑定 JSON 数据
		v2.POST("/loginJSON", bindJson)

		// 示例4.使用 ShouldBindXML 绑定 xml 数据
		v2.POST("/loginXML", bindXml)

		// 示例5.使用 ShouldBindForm 绑定 表单 数据
		v2.POST("/loginFORM", bindForm)
	}

	// 简单的路由组: v3
	v3 := r.Group("/v3")
	{
		v3.POST("/person", bindCustom)
	}

	// Listen and Server in 0.0.0.0:8080
	r.Run(":8080")
}

运行结果:

[GIN-debug] GET    /v1/person/:name/:uuid    --> main.bindUri (3 handlers)
[GIN-debug] GET    /v1/site                  --> main.bindQuery (3 handlers)
[GIN-debug] POST   /v1/site                  --> main.bindQuery (3 handlers)
[GIN-debug] PUT    /v1/site                  --> main.bindQuery (3 handlers)
[GIN-debug] PATCH  /v1/site                  --> main.bindQuery (3 handlers)
[GIN-debug] HEAD   /v1/site                  --> main.bindQuery (3 handlers)
[GIN-debug] OPTIONS /v1/site                  --> main.bindQuery (3 handlers)
[GIN-debug] DELETE /v1/site                  --> main.bindQuery (3 handlers)
[GIN-debug] CONNECT /v1/site                  --> main.bindQuery (3 handlers)
[GIN-debug] TRACE  /v1/site                  --> main.bindQuery (3 handlers)
[GIN-debug] POST   /v2/loginJSON             --> main.bindJson (3 handlers)
[GIN-debug] POST   /v2/loginXML              --> main.bindXml (3 handlers)
[GIN-debug] POST   /v2/loginFORM             --> main.bindForm (3 handlers)
[GIN-debug] POST   /v3/person                --> main.bindCustom (3 handlers)

访问结果:

# 示例1
http://10.20.172.106:8080/v1/person/weiyigeek/d932949f-6653-419b-977f-57e1ec1ec52d
  # {"name":"weiyigeek","uuid":"d932949f-6653-419b-977f-57e1ec1ec52d"}

# 示例2
http://10.20.172.106:8080/v1/site?name=weiyigeek&blog=blog.weiyigeek.top
curl --location --request POST 'http://10.20.172.106:8080/v1/site?name=weiyigeek&blog=blog.weiyigeek.top' \
--header 'Content-Type: application/x-www-form-urlencoded'
  # {"code":200,"data":{"blog":"blog.weiyigeek.top","name":"weiyigeek"}}

# 示例3.Content-Type: application/json
curl --location 'http://10.20.172.106:8080/v2/loginJSON' \
--header 'Content-Type: application/json' \
--data '{"user":"weiyigeek","password":"123456"}'

# 示例4.Content-Type: application/xml
curl --location 'http://10.20.172.106:8080/v2/loginXML' \
--header 'Content-Type: application/xml' \
--data '<?xml version="1.0" encoding="UTF-8"?>
<root>
<user>weiyigeek</user>
<password>123</password>
</root>'

# 示例5.application/x-www-form-urlencoded
curl --location 'http://10.20.172.106:8080/v2/loginFORM' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'user=weiyigeek' \
--data-urlencode 'password=123456'

# 示例6.
curl --location 'http://10.20.172.106:8080/v3/person' \
--form 'name="WeiyiGeek"' \
--form 'blog="blog.weiyigeek.top"'
  # {"blog":"blog.weiyigeek.top","person":{"name":"WeiyiGeek"}}

3d32c41f1c9bf118a1178fedc044be68.png

补充示例: 将 request body 绑定到不同的结构体中, 此处需要注意使用 c.ShouldBindBodyWith 会在绑定之前将 body 存储到上下文中, 其对性能造成轻微影响,如果调用一次就能完成绑定的话,建议不要用这个方法而是使用c.ShouldBind方法。

type formA struct {
  Foo string `json:"foo" xml:"foo" binding:"required"`
}

type formB struct {
  Bar string `json:"bar" xml:"bar" binding:"required"`
}

func SomeHandler(c *gin.Context) {
  objA := formA{}
  objB := formB{}
  // 读取 c.Request.Body 并将结果存入上下文。
  if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
    c.String(http.StatusOK, `the body should be formA`)
  // 这时, 复用存储在上下文中的 body。
  } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
    c.String(http.StatusOK, `the body should be formB JSON`)
  // 可以接受其他格式
  } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
    c.String(http.StatusOK, `the body should be formB XML`)
  } else {
    ...
  }
}

7.HTTP重定向的几种方式

描述: 在Gin中要实现重定向是很容易内部、外部重定向均支持,通常情况下有三种重定向,一种是 301 重定向,另一种是 302 重定向,最后一种是路由重定向(即访问的路由地址不会发生改变,请求内部其路由返回数据的,有点反代的感觉)

代码示例:

package main

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

func main() {
  gin.SetMode(gin.DebugMode)
	g := gin.Default()
  	// HTTP 重定向很容易
	// 通过 GET 方法进行 HTTP 301 重定向
	g.GET("/redirect-test-1", func(c *gin.Context) {
		c.Redirect(http.StatusMovedPermanently, "http://www.weiyigeek.top/")
	})

	//  GET 方法进行 HTTP 302 重定向
	g.POST("/redirect-test-2", func(c *gin.Context) {
		c.Redirect(http.StatusFound, "/test2")
	})

	// 通过使用 HandleContext 进行路由重定向
	g.GET("/test1", func(c *gin.Context) {
		c.Request.URL.Path = "/test2"
		g.HandleContext(c) // 返回的是 /test2 路由的数据
	})
	g.GET("/test2", func(c *gin.Context) {
		c.JSON(200, gin.H{"hello": "world"})
	})

	// 默认监听并在 0.0.0.0:8080 上启动服务
	g.Run()
}

执行效果:

curl -i http://10.20.172.106:8080/redirect-test-1
curl -X POST -i http://10.20.172.106:8080/redirect-test-2
curl -i  http://10.20.172.106:8080/test1

c1b130f2aec0d4cd04d5a2bd85b2ae82.png

亲,文章就要看完了,不关注一下【全栈工程师修炼指南】吗?

1c7e0b5605659ad523bfcdec41f259a3.jpeg

8.外部请求转发及数据验证

描述: 我们可以通过DataFromReader方法将指定的读取器写入主体流并更新HTTP代码,并返回给客户端,其次在提交数据时可以针对数据类型进行验证。

示例代码:

package main

import (
	"fmt"
	"net/http"
	"time"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator"
)

// 示例1.SomeDataFromReader 将外部URL请求响应给指定用户
func getSomeDataFromReader(c *gin.Context) {
	// GET 请求外部地址
	response, err := http.Get("https://blog.weiyigeek.top/img/avatar.jpg")
	if err != nil || response.StatusCode != http.StatusOK {
		c.Status(http.StatusServiceUnavailable)
		return
	}
	// 外部地址请求响应结果
	reader := response.Body
	contentLength := response.ContentLength
	contentType := response.Header.Get("Content-Type")
	extraHeaders := map[string]string{
		"Content-Disposition": `attachment; filename="avatar.jpg"`,
	}
	// DataFromReader 将指定的读取器写入主体流并更新HTTP代码,并返回给客户端
	// DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string)
	c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
}

// 示例2. Validator Handler 自定义验证器
// User 结构体包含绑定和验证的数据。
type User struct {
	Name  string    `json:"name"`
	Phone string    `json:"phone"`
	Birth time.Time `json:"birth" binding:"required" time_format:"2006-01-02"`
	Login time.Time `json:"login" binding:"required" time_format:"2006-01-02"`
}

// 验证用户输入基础信息是否为空
func UserStructLevelValidation(sl validator.StructLevel) {
	user := sl.Current().Interface().(User)
	if len(user.Name) == 0 && len(user.Phone) == 0 {
		sl.ReportError(user.Name, "Name", "name", "fnameorlname", "")
		sl.ReportError(user.Phone, "Phone", "phone", "fnameorlphone", "")
	}
}

// 验证用户输入日期是否有误
var userDateValida validator.Func = func(fl validator.FieldLevel) bool {
	// 使用了反射机制
	date, ok := fl.Field().Interface().(time.Time)
	fmt.Println(date)
	if ok {
		// 判断输入的时间是否在当前时间之后
		today := time.Now()
		if today.After(date) {
			return false
		}
	}
	return true
}

func postValidatorHandler(c *gin.Context) {
	// 实例化结构体对象,解析并绑定对应数据
	var user User
	if err := c.ShouldBindWith(&user, binding.JSON); err == nil {
		c.JSON(http.StatusOK, gin.H{"code": "200", "data": gin.H{"name": user.Name, "phone": user.Phone, "birthday": user.Birth, "logintime": user.Login}})
	} else {
		c.JSON(http.StatusBadRequest, gin.H{"code": "0", "message": err.Error()})
	}

}

func main() {
	// Default返回一个Engine实例,该实例已连接Logger和Recovery中间件。
	r := gin.Default()

	// 示例1.SomeDataFromReader 将外部URL请求响应给指定用户
	r.GET("/someDataFromReader", getSomeDataFromReader)

	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		v.RegisterStructValidation(UserStructLevelValidation, User{})
		//v.RegisterValidation("datevalida", userDateValida)
	}

	// 示例2.Validator Handler 自定义验证器 Header
	r.POST("/ValidatorHandler", postValidatorHandler)

	// Listen and Server in 0.0.0.0:8080
	r.Run(":8080")
}

执行结果:

6cbb1bdb8054e765f1d148c73c60ca6a.png


0x0n 入坑出坑

错误1.在VScode中无法安装Go插件中相关依赖模块

解决办法:

Tools environment: GOPATH=D:\Study\Go\package
Installing 7 tools at D:\Study\Go\package\bin in module mode.
  gotests
  gomodifytags
  impl
  goplay
  dlv
  staticcheck
  gopls

错误2.在Windows10中使用rotatelogs模块时创建软连接报A required privilege is not held by the client.错误。

错误信息: failed to rotate: failed to create new symlink: symlink \system.log.20230214.log s/system.log.20230214.log_symlink: A required privilege is not held by the client.
问题原因: 命令行终端问题或者未开启Windows10系统开发人员选项。
解决办法:

# 1.提升执行程序命令终端权限
- 在Windows10桌面,右键点击桌面左下角的开始按钮 ,在弹出的菜单中选择CMD “命令提示符 (管理员)”一项
- 在PowerShell中执行 `Start-Process -verb runas "C:\Windows\System32\cmd.exe" 命令

# 2.开启Windows10系统开发人员选项
- 点击【开始】-> 搜索开发者模式 -> 打开开发人员模式

828e78f9d76706f6ad07144886bb6b6f.png

本文至此完毕,更多技术文章,尽情等待下篇好文!

原文地址: https://blog.weiyigeek.top/2020/4-23-602.html

如果此篇文章对你有帮助,请你将它分享给更多的人! 

ed708c5b461224005bbe4de7b5653987.gif

469cad09f86073e89794b1274bdf36b9.png 学习书籍推荐 往期发布文章 6e92d0bff32dabf570ca8334deb75ad3.png

公众号回复【0008】获取【Ubuntu22.04安装与加固脚本】

公众号回复【10001】获取【WinServer安全加固脚本】

公众号回复【10002】获取【KylinOS银河麒麟安全加固脚本】

公众号回复【0011】获取【k8S二进制安装部署教程】

公众号回复【0014】获取【Nginx学习之路汇总】

公众号回复【0015】获取【Jenkins学习之路汇总】

公众号回复【10005】获取【adb工具刷抖音赚米】

 热文推荐  

  • 容灾恢复 | 记一次K8S集群中etcd数据快照的备份恢复实践

欢迎长按(扫描)二维码 取更多渠道哟!

08b634fd471bb6672eacc78a997e45aa.jpeg5c0a858a40961a8120cd6d2a02064d80.jpegbbd88d5a56d88f7ac91d099f1e4f2e23.jpeg

3553aa70b203046abcfcaf965ffdb6c2.gif

欢迎关注 【全栈工程师修炼指南】(^U^)ノ~YO

添加作者微信【weiyigeeker 】 一起学习交流吧!

关注回复【学习交流群】即可加入【安全运维沟通交流小群

温馨提示: 由于作者水平有限,本章错漏缺点在所难免,希望读者批评指正,若有问题或建议请在文章末尾留下您宝贵的经验知识,或联系邮箱地址

master@weiyigeek.top 或 关注公众号 [全栈工程师修炼指南] 留言。

点个【赞 + 在看】吧!

点击【"阅读原文"】获取更多有趣的知识!   

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

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

相关文章

Learning C++ No.30 【lambda表达式实战】

引言&#xff1a; 北京时间&#xff1a;2023/6/9/9:13&#xff0c;今天8:15起床&#xff0c;可能是最近课非常少&#xff0c;导致写博客没什么压力&#xff0c;什么时间都能写&#xff0c;导致7点起不来&#xff0c;哈哈哈&#xff0c;习惯睡懒觉了&#xff0c;但是问题不大&a…

【二十七】springboot之通过threadLocal+参数解析器实现同session一样保存当前登录信息的功能

springboot篇章整体栏目&#xff1a; 【一】springboot整合swagger&#xff08;超详细 【二】springboot整合swagger&#xff08;自定义&#xff09;&#xff08;超详细&#xff09; 【三】springboot整合token&#xff08;超详细&#xff09; 【四】springboot整合mybatis…

绿豆影视系统5.1.8反编译版源码:PC+WAP+APP端【附搭建教程+软件】

简介&#xff1a; 绿豆影视系统5.1.8反编译版源码&#xff1a;PCWAPAPP端【附搭建教程软件】 优化内容 1.专题类目&#xff0c;在后台进行设置 2.短视频类目 &#xff0c;需要有信天翁id 3.优化首页栏目不显示问题 4.去除我的页面 不常用功能 5.修复自定义密码只能输入数字的…

二阳竟然是这样的~

今天周六&#xff0c;家人都出去玩了&#xff0c;把我自己扔家里了&#xff0c;因为我2阳了&#xff0c;出门都不带我玩了。 正好趁这个时间简单写下2阳的症状&#xff0c;和1阳有什么不一样。 先说结论&#xff1a; 我的亲身感受是2阳比1阳轻的多&#xff0c;没有浑身关节疼&a…

MySQL-索引详解(三)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有回报&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️树高千尺&#xff0c;落叶归根人生不易&…

第64篇:史上最严重的APT供应链攻击事件,借助Solarwinds攻击欧美的流程图梳理和分析(上篇)...

Part1 前言 大家好&#xff0c;我是ABC_123&#xff0c;公众号正式更名为”希潭实验室”&#xff0c;敬请关注。本期分享一个堪称史上影响最大、危害最大的供应链攻击APT案例——Solarwinds供应链攻击事件&#xff0c;SolarWinds的旗下有数万家客户公司&#xff0c;包括了”财…

h.264 h.265 协议基本概念记录

区分一些概念 MPEG-4 是一套用于音频、视频信息的压缩编码标准H.264&#xff0c;AVC&#xff0c;编码格式&#xff0c;是MPEG-4 的第10部分H.265&#xff0c;HEVC&#xff0c;编码格式&#xff0c;是MPEG-H的第2部分mp4&#xff0c;rmvb&#xff0c;mkv&#xff0c;avi是容器&…

【ChatGPT】数据科学 ChatGPT Cheat Sheet 书籍分享(阿里云盘下载)

封皮 以下为书中部分内容的机器翻译 我们的重要提示指南 1. 以 AI 角色的描述开始提示。 例如&#xff0c;“你是{x}”或“我希望你扮演{x}”。如果您不确定&#xff0c;请尝试“你是一个有帮助的助手”。 例如&#xff0c;您是 OpenAI 的数据科学家&#xff0c;您正在研究大型…

测量项目总结

和朋友合作开发一个测量机产品, 用于测量汽车零件形位公差, 客户的客户是电动汽车第一品牌, 我负责上位机开发, 历时2个月, 完成上百次的commit. 时间虽紧, 但代码质量上没有妥协, 软件层次划分合理, 后续考虑做成系列产品. 开发利器 感恩这个时代, 现在的软件开发开发体验真好…

git hook

hook hook 翻译为钩子&#xff0c;简单说就是监听某个事件&#xff08;操作&#xff09;&#xff0c;然后触发自定义逻辑 在 git 中可以监听 commit&#xff0c;push 等操作&#xff0c;在操作之前或之后触发对应的 hook&#xff0c;在 hook 中写自定义的逻辑&#xff0c;比如…

nginx扩展篇之nginx.conf 详细文档

nginx.conf 配置文件 基本说明 Nginx 的配置文件位置 文件位置 ​ 安装目录\conf\nginx.conf ​ 安装目录\nginx.conf 两个文件是一样的 多说一句&#xff1a;使用/usr/local/nginx/sbin/nginx 启动Nginx &#xff0c;默认用的是安装目录\nginx.conf 配置文件 作用&…

网络安全学术顶会——SP 2023 议题清单、摘要与总结(上)

总结 本文总结了196篇近期涉及网络安全领域的研究论文。主要可分为以下几类: 隐私保护,涉及到匿名认证、隐私保护机器学习等机器学习安全,主要研究对抗样本和隐蔽后门等问题浏览器和网络安全,涉及指纹识别、端到端加密、网站选择标志等嵌入式系统安全,主要针对 IOT 安全操作系统…

java并发编程:LinkedBlockingQueue详解

文章目录 简介源码分析属性构造函数入队方法put(E e)offer(E e)offer(E e, long timeout, TimeUnit unit) 出队方法take()poll()获取元素方法删除元素方法 问题总结 简介 在集合框架里&#xff0c;想必大家都用过ArrayList和LinkedList&#xff0c;也经常在面试中问到他们之间…

光线追踪RayTracing,基本原理,判断物体与光线相交

光线的三点假设&#xff1a; 光线按直线传播光线之间不会发生碰撞光线会经过一系列折射反射进入摄像机 可以从摄像机发出光线&#xff0c;推出可逆的光路 上图中&#xff0c;透明球在与相机直连的线条处&#xff0c;需要将折射和反射的着色点结果相加&#xff0c;如果有光源直…

Neuroimage | LMDA-Net第一作者亲自讲述其设计思想

近期&#xff0c; 天津大学精仪学院和医工院联合&#xff0c;在神经科学和神经成像顶刊 Neuroimage中发表题为《LMDA-Net: 一种具有通用性和可解释性的轻量级EEG解码网络》的学术论文, 为解决人工神经网络模型在EEG解码时面临的跨数据集泛化性差、预测波动性高和模型可解释性差…

【2023】Redis cluster集群模式搭建

目录 1.cluster集群介绍2.搭建cluster集群2.1.架构图2.2.搭建集群2.2.1.创建所需配置文件2.2.2.创建集群所需容器2.2.3.创建集群&#xff1a;master1节点连接其他节点2.2.4.配置从节点&#xff0c;完成三主三从 3.在cluster集群内读写数据 1.cluster集群介绍 Redis Cluster是R…

每个程序员都必须知道的8种通用数据结构

8种常用数据结构 数据结构是一种特殊的组织和存储数据的方式&#xff0c;可以使我们可以更高效地对存储的数据执行操作。数据结构在计算机科学和软件工程领域具有广泛而多样的用途。 几乎所有已开发的程序或软件系统都使用数据结构。此外&#xff0c;数据结构属于计算机科学和…

GitOps多环境部署问题及解决方案

大型组织应用GitOps难免会遇到在多环境中部署的问题&#xff0c;本文分析了应用环境分支策略会遇到到问题&#xff0c;介绍了应用文件夹策略解决这些问题的方案。原文&#xff1a;Stop Using Branches for Deploying to Different GitOps Environments[1], How to Model Your G…

STC8比较器功能案例介绍

STC8比较器功能案例介绍 &#x1f4cc;相关篇《STC8功能脚切换相关寄存器P_SW1和P_SW2以及MCLKOCR介绍》 &#x1f4cb;基于内部参考电压源&#xff0c;使用外部端口P3.7作为比较器正极输入源&#xff0c;比较器结果输出到P3.4 或者P4.1&#xff08;由P_SW2 中的CMPO_S 进行设定…

Oracle的最高认证并不是OCM,而是......

什么是Oracle数据库的最高认证呢&#xff1f;大家注意&#xff0c;不是Oracle OCM&#xff0c;而是Oracle高可用大师认证&#xff0c;全称叫&#xff1a;Maxium Availability Certified Master&#xff0c;要获得这一张证书&#xff0c;你需要同时有5个认证。 关于博主&#x…