Gin入门

Gin入门

声明:本博客为看李文周大佬gin入门视频笔记gin入门

我的代码仓库6月 · 沉着冷静/2023 - 码云 - 开源中国 (gitee.com)

安装

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

第一个Gin实例:

package main

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

func sayHello(c *gin.Context) {
	c.JSON(200, gin.H{
		"message": "hello go",
	})
}

func main() {
	//设置默认路由引擎
	r := gin.Default()
	// 指定用户使用Get访问/hello
	r.GET("/hello", sayHello)
	//启动服务器
	r.Run() //r.Run("9090")
}
RESTful API

REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。

简单来说,REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作。

  • GET用来获取资源
  • POST用来新建资源
  • PUT用来更新资源
  • DELETE用来删除资源。

只要API程序遵循了REST风格,那就可以称其为RESTful API。

实例

基于RESTful API的框架:图书管理系统

请求方法URL含义
GET/book查询书籍信息
POST/book创建书籍信息
PUT/book更新书籍信息
DELETE/book删除书籍信息
func main() {
	//设置默认路由引擎
	r := gin.Default()
	// 指定用户使用Get访问/hello
	r.GET("/hello", sayHello)
	//图书管理系统
	r.GET("/book", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"method":  "GET",
			"message": "查询书籍",
		})
	})
	r.POST("/book", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"method":  "POST",
			"message": "创建书籍",
		})
	})
	r.PUT("/book", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"method":  "PUT",
			"message": "更新书籍",
		})
	})
	r.DELETE("/book", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"method":  "DELETE",
			"message": "删除书籍",
		})
	})
	//启动服务器
	r.Run() //r.Run("9090")
}

使用ApiPost进行测试

GET:

image-20240601173500222

POST:

image-20240601173515559

PUT:

image-20240601173529425

DELETE:

image-20240601173546761

Go渲染

html渲染实例

{{ . }}

package main

import (
	"html/template"
	"net/http"
)

func sayHello(w http.ResponseWriter, r *http.Request) {
	//解析模版
	t, err := template.ParseFiles("./hello.tmpl")
	if err != nil {
		panic(err)
	}
	//渲染模版
	name := "张三"
	err = t.Execute(w, name)
	if err != nil {
		panic(err)
	}
}

func main() {
	http.HandleFunc("/", sayHello)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		panic(err)
	}
}
#hello.tmpl
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <tittle>Hello GO</tittle>
</head>
<body>
    <p>Hello {{ . }}</p>
</body>
</html>

image-20240601181600253

暂时简单理解为字符串替换。

解析结构体
type User struct {
	Name   string
	Gender string
	Age    int64
}

func sayHello(w http.ResponseWriter, r *http.Request) {
	//解析模版
	t, err := template.ParseFiles("./hello.tmpl")
	if err != nil {
		panic(err)
	}
	//渲染模版
	u1 := User{
		Name:   "张三",
		Gender: "男",
		Age:    18,
	}
	//name := "张三"
	err = t.Execute(w, u1)
	if err != nil {
		panic(err)
	}
}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>Hello Go Template</title>
</head>
<body>
    <p>Hello {{ .Name }}</p>
    <p>年龄:{{ .Age }}</p>
    <p>性别:{{ .Gender }}</p>
</body>
</html>

注意:结构体首字母需要大写

如果它的首字母是小写,那么它就是未导出的(即字段名首字母为小写)。

在 Go 语言中,未导出的字段和方法只能在同一个包内访问。

解析map
func sayHello(w http.ResponseWriter, r *http.Request) {
	//解析模版
	t, err := template.ParseFiles("./hello.tmpl")
	if err != nil {
		panic(err)
	}
	//渲染模版
	m1 := map[string]interface{}{
		"name":   "李四",
		"gender": "女",
		"age":    20,
	}
	//name := "张三"
	err = t.Execute(w, m1)
	if err != nil {
		panic(err)
	}
}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>Hello Go Template</title>
</head>
<body>
    <p>Hello {{ .name }}</p>
    <p>年龄:{{ .age }}</p>
    <p>性别:{{ .gender }}</p>
</body>
</html>

注意:首字母无需大写,因为map通过key寻找value

空接口自定义
func sayHello(w http.ResponseWriter, r *http.Request) {
	//解析模版
	t, err := template.ParseFiles("./hello.tmpl")
	if err != nil {
		panic(err)
	}
	//渲染模版
	u1 := User{
		Name:   "张三",
		Gender: "男",
		Age:    18,
	}
	m1 := map[string]interface{}{
		"name":   "李四",
		"gender": "女",
		"age":    20,
	}
	//name := "张三"
	err = t.Execute(w, map[string]interface{}{
		"u1": u1,
		"m1": m1,
	})
	if err != nil {
		panic(err)
	}
}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>Hello Go Template</title>
</head>
<body>
    <p>Hello {{ .m1.name }}</p>
    <p>年龄:{{ .m1.age }}</p>
    <p>性别:{{ .m1.gender }}</p>

    <p>Hello {{ .u1.Name }}</p>
    <p>年龄:{{ .u1.Age }}</p>
    <p>性别:{{ .u1.Gender }}</p>
</body>
</html>

image-20240601184409395

常用语法
注释
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>Hello Go Template</title>
</head>
<body>
    <p>Hello {{ .m1.name }}</p>
    <p>年龄:{{ .m1.age }}</p>
    <p>性别:{{ .m1.gender }}</p>
{{/*这是一条注释*/}}
    <p>Hello {{ .u1.Name }}</p>
    <p>年龄:{{ .u1.Age }}</p>
    <p>性别:{{ .u1.Gender }}</p>
</body>
</html>
变量
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>Hello Go Template</title>
</head>
<body>
    <p>Hello {{ .m1.name }}</p>
    <p>年龄:{{ .m1.age }}</p>
    <p>性别:{{ .m1.gender }}</p>
{{/*这是一条注释*/}}
    <p>Hello {{ .u1.Name }}</p>
    <p>年龄:{{ .u1.Age }}</p>
    <p>性别:{{ .u1.Gender }}</p>
<hr>
{{/*这是两个变量*/}}
{{ $v1 := 20 }}
{{ $age := .m1.age}}
</body>
</html>
移除空格
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>Hello Go Template</title>
</head>
<hr>
    {{/*name的空格被移除*/}}
    <p>Hello {{- .m1.name -}}</p>
</body>
</html>

image-20240601184907784

条件判断
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>Hello Go Template</title>
</head>
<hr>
    {{/*name的空格被移除*/}}
    <p>Hello {{- .m1.name -}}</p>
    <p>年龄:{{ .m1.age }}</p>
    <p>性别:{{ .m1.gender }}</p>
{{/*这是一条注释*/}}
    <p>Hello {{ .u1.Name }}</p>
    <p>年龄:{{ .u1.Age }}</p>
    <p>性别:{{ .u1.Gender }}</p>
<hr>
{{/*这是两个变量*/}}
{{ $v1 := 20 }}
{{ $age := .m1.age}}
{{ if $v1}}
{{ $v1 }}
{{else}}
啥也没有
{{end}}
</body>
</html>

image-20240601185151291

当然也有else if

比较函数

例如

lt:小于

gt:大于

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>Hello Go Template</title>
</head>
<hr>
    {{/*name的空格被移除*/}}
    <p>Hello {{- .m1.name -}}</p>
    <p>年龄:{{ .m1.age }}</p>
    <p>性别:{{ .m1.gender }}</p>
{{/*这是一条注释*/}}
    <p>Hello {{ .u1.Name }}</p>
    <p>年龄:{{ .u1.Age }}</p>
    <p>性别:{{ .u1.Gender }}</p>
<hr>
{{/*这是两个变量*/}}
{{ $v1 := 20 }}
{{ $age := .m1.age}}
{{ if $v1}}
{{ $v1 }}
{{else}}
啥也没有
{{end}}
{{ if lt .m1.age 20 }}
好好学习
{{ else if gt .m1.age 22 }}
好好工作
{{ else }}
666
{{end}}
</body>
</html>

image-20240601190056919

range
//...
hobbylist := []string{
		"篮球",
		"足球",
		"乒乓球",
	}
	//name := "张三"
	err = t.Execute(w, map[string]interface{}{
		"u1":    u1,
		"m1":    m1,
		"hobby": hobbylist,
	})
//...
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>Hello Go Template</title>
</head>
//...
<hr>
{{ range $idx, $hobby := .hobby }}
    <p>{{$idx}} - {{$hobby}}</p>
{{end}}
</body>
</html>

image-20240601191028678

也可以加else

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>Hello Go Template</title>
</head>
//...
<hr>
{{ range $idx, $hobby := .hobby }}
    <p>{{$idx}} - {{$hobby}}</p>
{{else}}
    空空如也
{{end}}
</body>
</html>
with

引一个作用域

//...
{{with .m1}}
<p>{{ .name }}</p>
<p>{{ .gender }}</p>
<p>{{ .age }}</p>
{{end}}
//...

方便替换

//...
<p>Hello {{- .m1.name -}}</p>
<p>年龄:{{ .m1.age }}</p>
<p>性别:{{ .m1.gender }}</p>
//...

例如:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>Hello Go Template</title>
</head>
<hr>
    {{/*name的空格被移除*/}}
    <p>Hello {{- .m1.name -}}</p>
    <p>年龄:{{ .m1.age }}</p>
    <p>性别:{{ .m1.gender }}</p>
{{/*这是一条注释*/}}
    <p>Hello {{ .u1.Name }}</p>
    <p>年龄:{{ .u1.Age }}</p>
    <p>性别:{{ .u1.Gender }}</p>
<hr>
{{/*这是两个变量*/}}
{{ $v1 := 20 }}
{{ $age := .m1.age}}
{{ if $v1}}
{{ $v1 }}
{{else}}
啥也没有
{{end}}
{{ if lt .m1.age 20 }}
好好学习
{{ else if gt .m1.age 22 }}
好好工作
{{ else }}
666
{{end}}
<hr>
{{ range $idx, $hobby := .hobby }}
    <p>{{$idx}} - {{$hobby}}</p>
{{else}}
    空空如也
{{end}}
<hr>
{{with .m1}}
<p>{{ .name }}</p>
<p>{{ .gender }}</p>
<p>{{ .age }}</p>
{{end}}

</body>
</html>

image-20240601191435873

自定义函数
func f1(w http.ResponseWriter, r *http.Request) {
	//定义函数
	kua := func(name string) (string, error) {
		return name + "年轻帅气", nil
	}
	//定义模版
	//解析模版
	t := template.New("f.tmpl")
	// 告诉引擎多了一个自定义函数kua
	t.Funcs(template.FuncMap{
		"kua": kua,
	})
	_, err := t.ParseFiles("./f.tmpl")
	if err != nil {
		panic(err)
	}
	name := "张三"
	//渲染模版
	t.Execute(w, name)
}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>Hello Go Template</title>
</head>
<body>
    {{ kua . }}
</body>
</html>

image-20240601193314321

模版嵌套

f.tmpl

<!DOCTYPE html>
<html lang="zh-CN">
<body>

<h1>测试嵌套模版</h1>
<hr>
{{/*嵌套了另外一个单独的模版文件*/}}
{{template "ul.tmpl"}}
<hr>
{{/*嵌套另一个define定义的模版*/}}
{{template "ol.tmpl"}}
<div>你好 {{ . }}</div>
</body>
</html>
{{/*通过define定义一个模版*/}}
{{ define "ol.tmpl"}}
    <ol>
        <li>吃饭</li>
        <li>睡觉</li>
        <li>编程</li>
    </ol>
{{end}}

ul.tmpl

<h3>
    <li>注册</li>
    <li>登录</li>
    <li>退出</li>
</h3>
func demo(w http.ResponseWriter, r *http.Request) {
	//被包含的写在后面
	t, err := template.ParseFiles("./f.tmpl", "./ul.tmpl")
	if err != nil {
		panic(err)
	}
	name := "李四"
	t.Execute(w, name)
}

func main() {
	http.HandleFunc("/", f1)
	http.HandleFunc("/tmpl", demo)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		panic(err)
	}
}

image-20240601195016273

模版继承:block

就相当于以下两个页面中,只有标题内容是不同的,我们完全没必要再写一个重复内容的前端页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>模版继承</title>
    <style>
        * {
            margin: 0;
        }
        .nav {
            height: 50px;
            width: 100%;
            position: fixed;
            top: 0;
            background-color: burlywood;
        }
        .main {
            margin-top: 50px;
            position: relative;
        }
        .menu {
            width: 20%;
            height: 100%;
            position: fixed;
            left: 0;
            background-color: cornflowerblue;
        }
        .center {
            text-align: center;
        }
    </style>
</head>
<body>

<div class="nav"></div>
<div class="main">
    <div class="menu"></div>
    <div class="content center">
        <h1>{{ . }}</h1>                  ///...不同
    </div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>模版继承</title>
    <style>
        * {
            margin: 0;
        }
        .nav {
            height: 50px;
            width: 100%;
            position: fixed;
            top: 0;
            background-color: burlywood;
        }
        .main {
            margin-top: 50px;
            position: relative;
        }
        .menu {
            width: 20%;
            height: 100%;
            position: fixed;
            left: 0;
            background-color: cornflowerblue;
        }
        .center {
            text-align: center;
        }
    </style>
</head>
<body>

<div class="nav"></div>
<div class="main">
    <div class="menu"></div>
    <div class="content center">
        <h1>{{ . }}</h1>                  ///...不同
    </div>
</div>
</body>
</html>
package main

import (
	"html/template"
	"net/http"
)

func index(w http.ResponseWriter, r *http.Request) {
	//定义模版
	//解析模版
	t, err := template.ParseFiles("./index.tmpl")
	if err != nil {
		panic(err)
	}
	msg := "index页面"
	//渲染模版
	t.Execute(w, msg)
}
func home(w http.ResponseWriter, r *http.Request) {
	//定义模版
	//解析模版
	t, err := template.ParseFiles("./home.tmpl")
	if err != nil {
		panic(err)
	}
	msg := "home页面"
	//渲染模版
	t.Execute(w, msg)
}

func main() {
	http.HandleFunc("/index", index)
	http.HandleFunc("/home", home)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		panic(err)
	}
}

此时我们就可以使用block了。

base

{{/*或者使用define重命名文件*/}}
{{/*{{define "xx"}}*/}}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>模版继承</title>
    <style>
        * {
            margin: 0;
        }
        .nav {
            height: 50px;
            width: 100%;
            position: fixed;
            top: 0;
            background-color: burlywood;
        }
        .main {
            margin-top: 50px;
            position: relative;
        }
        .menu {
            width: 20%;
            height: 100%;
            position: fixed;
            left: 0;
            background-color: cornflowerblue;
        }
        .center {
            text-align: center;
        }
    </style>
</head>
<body>

<div class="nav"></div>
<div class="main">
    <div class="menu"></div>
    <div class="content center">
        {{block "content" .}}{{end}}
    </div>
</div>
</body>
</html>

index2.tmpl

{{/*继承根模版*/}}
{{/*记得接收数据*/}}
{{template "base.tmpl" .}}//这个.就是接收来的数据
{{/*重新定义根模版*/}}
{{define "content"}}
    <h1>这是index2页面</h1>
    <p>Hello {{ . }}</p>
{{end}}

home2.tmpl

{{/*继承根模版*/}}
{{/*记得接收数据*/}}
{{template "base.tmpl" .}}
{{/*重新定义根模版*/}}
{{define "content"}}
    <h1>这是home2页面</h1>
    <p>Hello {{ . }}</p>
{{end}}

main

package main

import (
	"html/template"
	"net/http"
)

func index(w http.ResponseWriter, r *http.Request) {
	//定义模版
	//解析模版
	t, err := template.ParseFiles("./f.tmpl")
	if err != nil {
		panic(err)
	}
	msg := "index页面"
	//渲染模版
	t.Execute(w, msg)
}
func home(w http.ResponseWriter, r *http.Request) {
	//定义模版
	//解析模版
	t, err := template.ParseFiles("./f.tmpl")
	if err != nil {
		panic(err)
	}
	msg := "home页面"
	//渲染模版
	t.Execute(w, msg)
}

func index2(w http.ResponseWriter, r *http.Request) {
	//定义模版
	//解析模版
	t, err := template.ParseFiles("./template/base.tmpl", "./template/index.tmpl")
	if err != nil {
		panic(err)
	}
	msg := "张三"
	//渲染模版
	t.ExecuteTemplate(w, "index.tmpl", msg)
}
func home2(w http.ResponseWriter, r *http.Request) {
	//定义模版
	//解析模版
	t, err := template.ParseFiles("./template/base.tmpl", "./template/home.tmpl")
	if err != nil {
		panic(err)
	}
	msg := "李四"
	//渲染模版
	t.ExecuteTemplate(w, "home.tmpl", msg)
}

func main() {
	http.HandleFunc("/index", index)
	http.HandleFunc("/home", home)
	http.HandleFunc("/index2", index2)
	http.HandleFunc("/home2", home2)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		panic(err)
	}
}
模版补充
修改默认标识符
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>修改模拟引擎的标识符</title>
</head>
<body>
<div>Hello {[ . ]}</div>
</body>
</html>
package main

import (
	"html/template"
	"net/http"
)

func index(w http.ResponseWriter, r *http.Request) {
	//定义模版
	//解析模版
	t, err := template.New("index.tmpl").
		Delims("{[", "]}").
		ParseFiles("./index.tmpl")
	if err != nil {
		panic(err)
	}
	//渲染模版
	name := "张三"
	t.Execute(w, name)

}

func main() {
	http.HandleFunc("/index", index)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		panic(err)
	}
}
text/template和html/template的区别

html/template针对的是需要返回HTML内容的场景,在模板渲染过程中会对一些有风险的内容进行转义,以此来防范跨站脚本攻击。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>xss</title>
</head>
<body>
{{ . }}
</body>
</html>

这个时候传入一段JS代码并使用html/template去渲染该文件,会在页面上显示出转义后的JS内容。 <script>alert('123')</script> 这就是html/template为我们做的事。

但是在某些场景下,我们如果相信用户输入的内容,不想转义的话,可以自行编写一个safe函数,手动返回一个template.HTML类型的内容。示例如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>xss</title>
</head>
<body>
    {{ .str1 }}
    {{/* "|" 管道符*/}}
    {{ .str2 | safe }}
</body>
</html>
package main

import (
	"html/template"
	"net/http"
)

func index(w http.ResponseWriter, r *http.Request) {
	//定义模版
	//解析模版
	t, err := template.New("index.tmpl").
		Delims("{[", "]}").
		ParseFiles("./index.tmpl")
	if err != nil {
		panic(err)
	}
	//渲染模版
	name := "张三"
	err = t.Execute(w, name)
	if err != nil {
		panic(err)
	}
}

func xss(w http.ResponseWriter, r *http.Request) {
	//定义模版
	//解析模版前自定义一个函数
    
	t, err := template.New("xss.tmpl").Funcs(template.FuncMap{
		"safe": func(str string) template.HTML {
			return template.HTML(str)
		},
	}).ParseFiles("./xss.tmpl")//解析模版
	if err != nil {
		panic(err)
	}
	//渲染模版
	str1 := "<script></<script>>"
	str2 := "<a href='qq.com'>QQ</a>"
	t.Execute(w, map[string]string{
		"str1": str1,
		"str2": str2,
	})

}

func main() {
	http.HandleFunc("/index", index)
	http.HandleFunc("/xss", xss)

	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		panic(err)
	}
}

这样我们只需要在模板文件不需要转义的内容后面使用我们定义好的safe函数就可以了。

safe前:全部转义

image-20240601213622233

safe后:只转义,我们想转义的

image-20240601213708358

Gin渲染

单个模版渲染

模版解析

r.LoadHTMLFiles("template/index.tmpl") 

模版渲染

Gin框架中使用LoadHTMLGlob()或者LoadHTMLFiles()方法进行HTML模板渲染。

r.GET("/index", func(c *gin.Context) {
    c.HTML(http.StatusOK, "index.tmpl", gin.H{
        "tittle": "qq.com",
    })
})

结果

image-20240601215739100

package main

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

func main() {
	r := gin.Default()
	//模版解析
	r.LoadHTMLFiles("./templates/index.tmpl")
	//模版渲染
	r.GET("/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.tmpl", gin.H{
			"tittle": "qq.com",
		})
	})
	//运行
	r.Run()
}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>index</title>
</head>
<body>
{{ .tittle }}
</body>
</html>
多个模版渲染

image-20240601221432914

package main

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

func main() {
	r := gin.Default()
	//模版解析
	//r.LoadHTMLFiles("./templates/index.tmpl")
	r.LoadHTMLGlob("templates/**/*")
	//模版渲染
	r.GET("/posts/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
			"title": "/posts/index:qq.com",
		})
	})
	r.GET("/users/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
			"title": "/users/index:qq.com",
		})
	})
	//运行
	r.Run()
}

posts/index.tmpl

{{define "posts/index.tmpl" }}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>index</title>
</head>
<body>
{{ .title }}
</body>
</html>
{{end}}

image-20240601221544263

users/index.tmpl

{{define "users/index.tmpl" }}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>index</title>
</head>
<body>
{{ .title }}
</body>
</html>
{{end}}

image-20240601221516044

自定义模版函数
//gin框架中添加自定义函数
	r.SetFuncMap(template.FuncMap{
		"safe": func(str string) template.HTML {
			return template.HTML(str)
		},
	})
package main

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

func main() {
	r := gin.Default()
	//gin框架中添加自定义函数
	r.SetFuncMap(template.FuncMap{
		"safe": func(str string) template.HTML {
			return template.HTML(str)
		},
	})
	//模版解析
	//r.LoadHTMLFiles("./templates/index.tmpl")
	r.LoadHTMLGlob("templates/**/*")
	//模版渲染
	r.GET("/posts/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
			"title": "<a href='qq.com'>QQ</a>",
		})
	})
	r.GET("/users/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
			"title": "/users/index:qq.com",
		})
	})
	//运行
	r.Run()
}
<body>
{{ .title | safe }}
</body>

image-20240601234134563

静态文件的处理
//加载静态文件
	r.Static("/xxx", "./statics")
func main() {
	r := gin.Default()
	//加载静态文件
	r.Static("/xxx", "./statics")
	//gin框架中添加自定义函数
	r.SetFuncMap(template.FuncMap{
		"safe": func(str string) template.HTML {
			return template.HTML(str)
		},
	})
	//模版解析
	//r.LoadHTMLFiles("./templates/index.tmpl")
	r.LoadHTMLGlob("templates/**/*")
	//模版渲染
	r.GET("/posts/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
			"title": "<a href='qq.com'>QQ</a>",
		})
	})
	r.GET("/users/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
			"title": "/users/index:qq.com",
		})
	})
	//运行
	r.Run()
}

image-20240601235408996

css:

body {
    background-color: antiquewhite;
}

js:

alert(123);

tmpl:

{{define "posts/index.tmpl" }}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="/xxx/index.css">
    <title>index</title>
</head>
<body>
{{ .title | safe }}
<script src="/xxx/index.js"></script>
</body>
</html>
{{end}}

image-20240601235518665

模版继承

Gin框架默认都是使用单模板,如果需要使用block template功能,可以通过"github.com/gin-contrib/multitemplate"库实现。

例如:

在网上下载一个前端模版

把解压之后index.html改名为home.html,放在gin_demo7/templates/posts路径下,其他的放在gin_demo7/statics路径下。

image-20240602002616069

增加一个GET即可。

//从网上下载的模版
r.GET("/home", func(c *gin.Context) {
    c.HTML(http.StatusOK, "home.html", nil)
})

因为我们找静态资源是先去statics中寻找,但是我们设定了静态资源是在/xxx下寻找的,所以去下载的index.html中把一些css/改为/xxx/css

就可以呈现出:(当然,有点前端部分是不会显示的,ps:我不会改)

image-20240602003032014

Gin框架操作

返回JSON
package main

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

func main() {
	r := gin.Default()
	r.GET("/json", func(c *gin.Context) {
		data := map[string]interface{}{
			"name":   "张三",
			"age":    18,
			"gender": "男",
		}
		c.JSON(http.StatusOK, data)
	})
	r.Run()
}

image-20240602010120179

不用切片,也可以使用gin.H或者结构体

data := gin.H{
    "name":   "张三",
    "age":    18,
    "gender": "男",
}
--------------------------------------------
type msg struct {
    Name    string
    Message string
    Age     int
}
r.GET("/another_json", func(c *gin.Context) {
    data := msg{
        Name:    "张三",
        Message: "hello go",
        Age:     18,
    }
    c.JSON(http.StatusOK, data)
})

注意:结构体命名不能小写,比如Name不能写出name。

如果非得写小写,就使用tag即可。

type msg struct {
    Name    string `json:"name"`
    Message string
    Age     int
}
获取querystring参数
package main

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

func main() {
	r := gin.Default()
	r.GET("/web", func(c *gin.Context) {
		//获取浏览器那边发请求携带的query string参数
		name := c.Query("query")
		c.JSON(http.StatusOK, gin.H{
			"name": name,
		})
	})
	r.Run()
}

image-20240602013042495

设置默认值:

name := c.DefaultQuery("query", "李四")

image-20240602013303077

GetQuery

name, ok := c.GetQuery("query")
if !ok {
    name = "!ok"
}

image-20240602013535250

&英文AND符

func main() {
	r := gin.Default()
	r.GET("/web", func(c *gin.Context) {
		//获取浏览器那边发请求携带的query string参数
		//name := c.Query("query")
		//name := c.DefaultQuery("query", "李四")
		//name, ok := c.GetQuery("query")
		name := c.Query("query")
		age := c.Query("age")
		c.JSON(http.StatusOK, gin.H{
			"name": name,
			"age":  age,
		})
	})
	r.Run()
}

image-20240602014056574

获取form参数

PostForm

r.GET("/login", func(c *gin.Context) {
    c.HTML(http.StatusOK, "login.html", nil)
})
r.POST("/login", func(c *gin.Context) {
    username := c.PostForm("username")
    password := c.PostForm("password")
    c.HTML(http.StatusOK, "index.html", gin.H{
        "Name":     username,
        "Password": password,
    })
})

html

//login
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
<form action="/login" method="post" novalidate autocomplete="off">
    <div>
        <label for="username">username:</label>
        <input type="text" name="username" id="username">
    </div>

    <div>
        <label for="password">password:</label></input>
        <input type="password" name="password" id="password">
    </div>
    <div>
        <input type="submit" value="登录">
    </div>
</form>
</body>
</html>
//index
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
<p>Hello, {{ .Name }} !</p>
<p>密码:{{ .Password }} </p>
</body>
</html>

image-20240602025235632

DefaultPostForm

我此时没有填密码,就返回一个空

r.GET("/login", func(c *gin.Context) {
    c.HTML(http.StatusOK, "login.html", nil)
})
r.POST("/login", func(c *gin.Context) {
    username := c.DefaultPostForm("username", "somebody")
    password := c.DefaultPostForm("password", "***")
    c.HTML(http.StatusOK, "index.html", gin.H{
        "Name":     username,
        "Password": password,
    })
})

image-20240602030840285

我此时没有填密码,找不到对应表单,就使用默认的***

username := c.DefaultPostForm("username", "somebody")
password := c.DefaultPostForm("xxx", "***")

image-20240602030931171

GetPostForm

r.POST("/login", func(c *gin.Context) {
    username, ok := c.GetPostForm("username")
    if !ok {
        username = "李四"
    }
    password, ok := c.GetPostForm("password")
    if !ok {
        password = "20"
    }
    println(username, "-----------", password)
    c.HTML(http.StatusOK, "index.html", gin.H{
        "Name":     username,
        "Password": password,
    })
})

image-20240602031227760

获取URI路径参数
func main() {
	r := gin.Default()
	r.GET("/:name/:age", func(c *gin.Context) {
		//获取路径参数
		name := c.Param("name")
		age := c.Param("age")
		c.JSON(http.StatusOK, gin.H{
			"name": name,
			"age":  age,
		})
	})
	r.Run()
}

image-20240602161834052

防止路由匹配冲突:

//Param返回的都是string类型
func main() {
	r := gin.Default()
	r.GET("/user/:name/:age", func(c *gin.Context) {
		//获取路径参数
		name := c.Param("name")
		age := c.Param("age")
		c.JSON(http.StatusOK, gin.H{
			"name": name,
			"age":  age,
		})
	})
	r.GET("/blog/:year/:month", func(c *gin.Context) {
		year := c.Param("year")
		month := c.Param("month")
		c.JSON(http.StatusOK, gin.H{
			"year":  year,
			"month": month,
		})
	})
	r.Run()
}

image-20240602162111449

image-20240602162143012

参数绑定

之前获取参数的方法

type User struct {
	Name     string
	Password string
}

func main() {
	r := gin.Default()
	r.GET("/user", func(c *gin.Context) {
		username := c.Query("username")
		password := c.Query("password")
		u1 := User{
			Name:     username,
			Password: password,
		}
		fmt.Println(u1)
		c.JSON(http.StatusOK, gin.H{
			"message": "ok",
		})
	})
}

image-20240602163543007

image-20240602163705748

但是当请求中携带的量比较大,就需要使用gin框架中的绑定函数:ShouldBind

var u User
c.ShouldBind(&u)

注意:记得传指针,否则对形参的修改,不会影响到实参。

c.ShouldBind通过反射获取结构体中的字段,所以结构体内变量首字母大写。

可以使用tag,让别人查反射时,可以一一对应起来。

type User struct {
	Username string `form:"username"`
	Password string `form:"password"`
}
func main() {
	r := gin.Default()
	r.GET("/user", func(c *gin.Context) {
		//username := c.Query("username")
		//password := c.Query("password")
		//u1 := User{
		//	username: username,
		//	password: password,
		//}
		//fmt.Printf("%#v\n", u1)
		var u User
		err := c.ShouldBind(&u)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
		} else {
			c.JSON(http.StatusOK, gin.H{
				"status": "ok",
			})
		}
	})
	r.Run()
}

image-20240602164622118

我们也可以通过获取post方法的form表单

下面演示ApiPost:

image-20240602165216463

image-20240602165226551

再来处理最常处理的json格式的数据

type User struct {
	Username string `form:"username" json:"username"`
	Password string `form:"password" json:"password"`
}
r.POST("/json", func(c *gin.Context) {
    var u User
    err := c.ShouldBind(&u)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": err.Error(),
        })
    } else {
        c.JSON(http.StatusOK, gin.H{
            "status": "ok",
        })
    }
    fmt.Printf("%#v\n", u)
})

image-20240602165605234

image-20240602165626731

文件上传

单个文件上传

//SaveUploadedFile
func main() {
	r := gin.Default()
	r.LoadHTMLGlob("index.html")
	r.GET("/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", nil)
	})
	r.POST("/upload", func(c *gin.Context) {
		//从请求中读取文件
		f, err := c.FormFile("f1")
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
		} else {
			//把读取到的文件保存在服务器本地
			//filepath := fmt.Sprintf("./%s", f.Filename)
			filepath := path.Join("./", f.Filename)
			_ = c.SaveUploadedFile(f, filepath)
			c.JSON(http.StatusOK, gin.H{
				"status": "ok",
			})
		}
	})
	r.Run()
}
// 或者使用MultipartMemory
// 处理multipart forms提交文件时默认的内存限制是32 MiB
// 可以通过下面的方式修改
// router.MaxMultipartMemory = 8 << 20  // 8 MiB

image-20240602171326712

image-20240602171335078

服务器本地路径中就多了一个图片:

image-20240602171357685

多个文件上传

func main() {
	router := gin.Default()
	// 处理multipart forms提交文件时默认的内存限制是32 MiB
	// 可以通过下面的方式修改
	// router.MaxMultipartMemory = 8 << 20  // 8 MiB
	router.POST("/upload", func(c *gin.Context) {
		// Multipart form
		form, _ := c.MultipartForm()
		files := form.File["file"]
	
		for index, file := range files {
			log.Println(file.Filename)
			dst := fmt.Sprintf("C:/tmp/%s_%d", file.Filename, index)
			// 上传文件到指定的目录
			c.SaveUploadedFile(file, dst)
		}
		c.JSON(http.StatusOK, gin.H{
			"message": fmt.Sprintf("%d files uploaded!", len(files)),
		})
	})
	router.Run()
}
重定向
  • HTTP重定向

例1:

func main() {
	r := gin.Default()
	r.GET("/index", func(c *gin.Context) {
		//c.JSON(http.StatusOK, gin.H{
		//	"status": "ok",
		//})
		c.Redirect(http.StatusMovedPermanently, "http://www.qq.com")
	})
	r.Run()
}

image-20240602172256356

回车

image-20240602172309650

image-20240602172635065

  • 路由重定向

例2:

func main() {
	r := gin.Default()
	r.GET("/index", func(c *gin.Context) {
		//c.JSON(http.StatusOK, gin.H{
		//	"status": "ok",
		//})
		c.Redirect(http.StatusMovedPermanently, "http://www.qq.com")
	})
	r.GET("/a", func(c *gin.Context) {
		//从a跳到b
		c.Request.URL.Path = "/b" // 修改请求的URI
		r.HandleContext(c)        //继续后面的处理
	})
	r.GET("/b", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "你跳到了b",
		})
	})
	r.Run()
}

image-20240602172826371

image-20240602172856093

路由
  • 普通路由
func main() {
	r := gin.Default()
	r.GET("/index", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"method": "GET",
		})
	})
	r.POST("/index", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"method": "POST",
		})
	})
	r.DELETE("/index", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"method": "DELETE",
		})
	})
	r.PUT("/index", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"method": "PUT",
		})
	})
	r.Run()
}

image-20240602174640057

image-20240602174702688

  • 匹配所有的请求方法:
r.Any("/user", func(c *gin.Context) {
    switch c.Request.Method {
        case http.MethodGet:
        c.JSON(http.StatusOK, gin.H{
            "method": "GET",
        })
        case http.MethodPost:
        r.POST("/index", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{
                "method": "POST",
            })
        })
        case http.MethodDelete:
        r.DELETE("/index", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{
                "method": "DELETE",
            })
        })
        //...
    }
})
  • 匹配失败
r.NoRoute(func(c *gin.Context) {
    c.JSON(http.StatusNotFound, gin.H{
        "msg": "你访问的页面不存在",
    })
})

image-20240602174729647

image-20240602174759465

  • 路由组

假设一个视频网站的页面所需的路由:

r.GET("/video/a", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{ "msg": "/video/a" })
})
r.GET("/video/b", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{ "msg": "/video/b" })
})
r.GET("/video/c", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{ "msg": "/video/c" })
})
r.GET("/video/d", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{ "msg": "/video/d" })
})
//...

这样写,即繁琐又没有规矩,所以我们使用路由组:

//路由组
videoGroup := r.Group("/video")
{
    videoGroup.GET("/a", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"msg": "/video/a"})
    })
    videoGroup.GET("/b", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"msg": "/video/b"})
    })
    videoGroup.GET("/c", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"msg": "/video/c"})
    })
    videoGroup.GET("/d", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"msg": "/video/d"})
    })
}

image-20240602175822922

image-20240602175833574

image-20240602175857841

Gin中间件
中间件

Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

GET函数

// GET is a shortcut for router.Handle("GET", path, handlers).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodGet, relativePath, handlers)
}

第二个参数必须是HandlerFunc类型

// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)

所以说

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

func main() {
	r := gin.Default()
	r.GET("/index", indexHandler)
	r.Run()
}

定义一个中间件m1,记录接口耗时

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

// 中间件m1,统计耗时
func m1(c *gin.Context) {
	fmt.Println("m1 start...")
	// 计时
	start := time.Now()
	c.Next() // 调用该请求剩余的处理程序
	//c.Abort() // 不调用该请求的剩余处理程序
	cost := time.Since(start)
	fmt.Println(cost)
	fmt.Println("m1 end...")
}

func main() {
	r := gin.Default()
	r.GET("/home", m1, indexHandler)
	r.Run()
}

image-20240602190919424

中间件调用流程:

m1:start:time.Now() -> m1:c.Next() -> indexHandler() -> m1:cost:time.Since()

注册中间件
  • 全局注册中间接函数

例1:

func main() {
	r := gin.Default()
	r.Use(m1) //全局注册中间件函数m1
	r.GET("/home", m1, indexHandler)
	r.GET("/shop", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "shop",
		})
	})
	r.GET("/user", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "user",
		})
	})
	r.Run()
}

分别访问"/shop""/user"

image-20240602193737233

例2:

// 中间件m1,统计耗时
func m1(c *gin.Context) {
	fmt.Println("m1 start...")
	// 计时
	start := time.Now()
	c.Next() // 调用该请求剩余的处理程序
	//c.Abort() // 不调用该请求的剩余处理程序
	cost := time.Since(start)
	fmt.Println(cost)
	fmt.Println("m1 end...")
}
// 中间件m2,统计耗时
func m2(c *gin.Context) {
	fmt.Println("m2 start...")
	c.Next()
	fmt.Println("m2 end...")
}

func main() {
	r := gin.Default()
	r.Use(m1, m2) //全局注册中间件函数m1/m2
	r.GET("/home", m1, indexHandler)
	r.GET("/shop", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "shop",
		})
	})
	r.GET("/user", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "user",
		})
	})
	r.Run()
}

执行流程:main -> m1:Next() -> m2:Next() -> shop() -> m2 end.. -> m1 end..

image-20240602194516402

image-20240602195922743

abort()

// 中间件m2,统计耗时
func m2(c *gin.Context) {
	fmt.Println("m2 start...")
	//c.Next()
	c.Abort() //不调用该请求的剩余处理程序
	fmt.Println("m2 end...")
}

main -> m1:Next() -> m2:Abort -> m2 end.. -> m1 end..

image-20240602200615472

如果此时m2也不想执行,在Abort()后面加上return即可

// 中间件m2
func m2(c *gin.Context) {
	fmt.Println("m2 start...")
	//c.Next()
	c.Abort() //不调用该请求的剩余处理程序
	return
	fmt.Println("m2 end...")
}

image-20240602205457645

image-20240602205513542

  • 为某个路由单独注册
r.GET("/user", m1, func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "msg": "user",
    })
})
  • 为路由组注册中间件

写法1

shopGroup := r.Group("/shop", m1)
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

写法2

shopGroup := r.Group("/shop")
shopGroup.Use(m1)
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}
中间件一般用法

写法:

// doCheck开关控制
func outMiddleWare(doCheck bool) gin.HandlerFunc {
	//连接数据库
	//或者其他工作
	return func(c *gin.Context) {
		if doCheck {
			//是否登录
			//if 是登录用户
			//c.Next
			//else
			//c.Abort
		} else {
			c.Next()
		}
	}
}

func main() {
	r := gin.Default()
	r.Use(m1, m2, outMiddleWare(false)) //全局注册中间件函数m1/m2
	r.GET("/home", m1, indexHandler)
	r.GET("/shop", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "shop",
		})
	})
	r.GET("/user", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "user",
		})
	})
	r.Run()
}

用法:

从一个中间件设置信息,在另一个中间件拿到这个信息

c.Set("name", "张三")
///
name, ok := c.Get("name")
if !ok {
    name = "匿名用户"
}
// handlerFunc
func indexHandler(c *gin.Context) {
	fmt.Println("indexHandler ...")
	name, ok := c.Get("name")
	if !ok {
		name = "匿名用户"
	}
	c.JSON(http.StatusOK, gin.H{
		"name": name,
	})
}

// 中间件m1,统计耗时
func m1(c *gin.Context) {
	fmt.Println("m1 start...")
	// 计时
	start := time.Now()
	c.Next() // 调用该请求剩余的处理程序
	//c.Abort() // 不调用该请求的剩余处理程序
	cost := time.Since(start)
	fmt.Println(cost)
	fmt.Println("m1 end...")
}

// 中间件m2
func m2(c *gin.Context) {
	fmt.Println("m2 start...")
	c.Set("name", "张三")
	c.Next()
	//c.Abort() //不调用该请求的剩余处理程序
	return
	fmt.Println("m2 end...")
}

// doCheck开关控制
func outMiddleWare(doCheck bool) gin.HandlerFunc {
	//连接数据库
	//或者其他工作
	return func(c *gin.Context) {
		if doCheck {
			//是否登录
			//if 是登录用户
			//c.Next
			//else
			//c.Abort
		} else {
			c.Next()
		}
	}
}

func main() {
	r := gin.Default()
	r.Use(m1, m2, outMiddleWare(false)) //全局注册中间件函数m1/m2
	r.GET("/home", m1, indexHandler)
	r.Run()
}

image-20240602211334833

中间件注意事项

gin默认中间件

gin.Default()默认使用了LoggerRecovery中间件

  • Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release
  • Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。
// 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...)
}

如果想使用一个无中间件的路由:

r := gin.New()

gin中间件中使用goroutine

当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。(并发时不安全)

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

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

相关文章

llvm 3.5 源码分析 clang for x86 001 之搭环境

0&#xff0c;目标 编译 针对x86 的&#xff0c;debug 的 c语言的编译器 1&#xff0c;下载代码 git clone --recursive 。。。llvm-project.git $ cd llvm-project 2&#xff0c;预备代码 llvm 3.5 版本的源代码&#xff0c;早期版本&#xff0c;可能比较小比较容易debug $…

发送Http请求的两种方式

说明&#xff1a;在项目中&#xff0c;我们有时会需要调用第三方接口&#xff0c;获取调用结果&#xff0c;来实现自己的业务逻辑。调用第三方接口&#xff0c;通常是双方确定好&#xff0c;由对方开放一个接口&#xff0c;需要我们根据他们提供的接口文档&#xff0c;组装Http…

STM32(九):USART串口通信 (标准库函数)

前言 上一篇文章已经介绍了如何用STM32单片机中独立看门狗来实现检测按键点灯的程序。这篇文章我们来介绍一下如何用STM32单片机中USART通信协议来串口通信&#xff0c;并向XCOM发送信息。 一、实验原理 1.通信的介绍 首先&#xff0c;我们先介绍一下通信&#xff0c;何为通…

C语言 | Leetcode C语言题解之第128题最长连续序列

题目&#xff1a; 题解&#xff1a; typedef struct {int key;UT_hash_handle hh; }Hash; int longestConsecutive(int* nums, int numsSize) {Hash* headNULL;Hash* tempNULL;for(int i0;i<numsSize;i){int numnums[i];HASH_FIND_INT(head,&num,temp);if(!temp){temp…

数据结构与算法04-栈和队列

介绍 栈和队列。事实上它们并不是全新的东西&#xff0c;只不过是多加了一些约束条件的数组而已。但正是这些约束条件为它们赋予了巧妙的用法。 栈和队列都是处理临时数据的灵活工具。在操作系统、打印任务、数据遍历等各种需要临时容器才能构造出美妙算法的场景&#xff0c;…

SQL实验 带函数查询和综合查询

一、实验目的 1&#xff0e;掌握Management Studio的使用。 2&#xff0e;掌握带函数查询和综合查询的使用。 二、实验内容及要求 1&#xff0e;统计年龄大于30岁的学生的人数。 --统计年龄大于30岁的学生的人数。SELECT COUNT(*) AS 人数FROM StudentWHERE (datepart(yea…

Medieval Lowpoly City with Toon Shader

介绍中世纪低地城市,这是一个创造历史场景、城市和环境的杰作,带有中世纪时期的魔力。 该包拥有70多个精心制作的模型,包括模块化选项,并通过着色器进行了增强,捕捉到了乡村建筑和细节道具的精髓。 用精心挑选的色彩和材料,让自己沉浸在历史的魅力中,仿佛漫步在中世纪的…

YOLOv3深入解析与实战:实时目标检测的高效多尺度架构网络

参考&#xff1a; https://arxiv.org/pdf/1804.02767.pdf https://blog.csdn.net/weixin_43334693/article/details/129143961 网上有很多关于yolo的文章&#xff0c;有些东西没讲清楚&#xff0c;基于自己对论文的理解&#xff0c;也做一个按照自己的想法做的理解。 1. 预测…

Rustdesk 自建服务器教程

一、环境 阿里云轻量服务器、debian11 系统 二、服务端搭建 2.1、开放防火墙指定端口 TCP(21115, 21116, 21117, 21118, 21119)UDP(21116) 2.2、安装 rustdesk 服务器文件 在 github 下载页https://github.com/rustdesk/rustdesk-server/releases/&#xff0c;下载 rustde…

大饼在一个比较关键的转折点,等某个东风来。。。。

1、历史数据对比&#xff0c;看多 图上方指标为BTC价格&#xff1b; 下方链上指标为BTC长期持有者成本均价跟BTC短期持有者成本均价之比。 从历史来看&#xff0c;我们正在启动往顶部的路上&#xff0c;不要畏惧。 2、结构为下降趋势&#xff0c;看空 3、长期持有者MVRV&…

几种更新 npm 项目依赖的实用方法

引言 在软件开发的过程中&#xff0c;我们知道依赖管理是其中一个至关重要的环节。npm&#xff08;Node Package Manager&#xff09; 是 Node.js 的包管理器&#xff0c;它主要用于 Node.js 项目的依赖管理和包发布。随着项目的不断发展&#xff0c;依赖库的版本更新和升级成…

Windows 2000 Server:安全配置终极指南

"远古技术&#xff0c;仅供娱乐" &#x1f4ad; 前言&#xff1a;Windows 2000 服务器在当时的市场中占据了很大的比例&#xff0c;主要原因包括操作简单和易于管理&#xff0c;但也经常因为安全性问题受到谴责&#xff0c;Windows 2000 的安全性真的那么差吗&#x…

YOLOv9改进策略 | Conv篇 | 利用YOLOv10提出的SCDown魔改YOLOv9进行下采样(附代码 + 结构图 + 添加教程)

一、本文介绍 本文给大家带来的改进机制是利用YOLOv10提出的SCDown魔改YOLOv9进行下采样&#xff0c;其是更高效的下采样。具体而言&#xff0c;其首先利用点卷积调整通道维度&#xff0c;然后利用深度卷积进行空间下采样。这将计算成本减少到和参数数量减少到。同时&#xff…

【人工智能003】图像识别算法模型常见术语简单总结(已更新)

1.熟悉、梳理、总结数据分析实战中的AI图像识别等实战研发知识体系&#xff0c;这块领域很大&#xff0c;需要耗费很多精力&#xff0c;逐步总结、更新到位&#xff0c;&#xff0c;&#xff0c; 2.欢迎点赞、关注、批评、指正&#xff0c;互三走起来&#xff0c;小手动起来&am…

3、flex弹性盒布局(flex:1?、水平垂直居中、三栏布局)

一、flex布局 任何一个容器都可以指定为 Flex 布局。块元素&#xff0c;行内元素即可。 div{ display: flex; } span{ display: inline-flex; } 注意&#xff0c;设为 Flex 布局以后&#xff0c;子元素的float、clear和vertical-align属性将失效。 二、flex属性 父容器…

WordPress子比内容同步插件

1.支持分类替换 将主站同步过来的文章分类进行替换 2.支持本地化文章图片 &#xff08;使用储存桶可能会导致无法保存图片&#xff09; 3.支持自定义文章作者&#xff08;选择多个作者则同步到的文章作者将会随机分配&#xff09; 4.支持将同步过来的文章自定义文章状态&…

ThinkBook 14 G6+ IMH(21LD)原厂Win11系统oem镜像下载

lenovo联想笔记本电脑原装出厂Windows11系统安装包&#xff0c; 恢复开箱状态自带预装系统&#xff0c;含恢复重置还原功能 链接&#xff1a;https://pan.baidu.com/s/1WIPNagHrC0wqYC3HIcua9A?pwdhzqg 提取码&#xff1a;hzqg 联想原装出厂系统自带所有驱动、出厂主题壁…

基于Win11下的Wireshark的安装和使用

Wireshark的安装和使用 前言一、Wireshark是什么简介 二、下载Wireshark下载过程查看自己电脑配置 三、安装Wireshark安装过程安装组件创建快捷方式winPacpNpcap 打开检验 四、使用Wireshark实施抓包捕获数据包 五、基于Wireshark使用显示过滤器简介使用方法注意ICMP的请求和应…

dibbler-DHCPv6 的开源框架(C++ 实现)2

前置 在 dibbler-DHCPv6 的开源框架&#xff08;C 实现&#xff09;1 说了 dibbler 的安装和编译、使用。在这里说一下 server 的源码分析。 一、主函数文件 dibbler/Port-linux/dibbler-server.cpp 代码路径&#xff1a; 二、主要函数解释 1. 加载配置文件和设置 DUID …

【Python Cookbook】S01E12 根据字段将记录分组

目录 问题解决方案讨论 问题 如果有一系列的字典或对象实例&#xff0c;我们想根据某个特定的字段来分组迭代数据。 解决方案 假设有如下字典列表&#xff1a; rows [{address: 5412 N CLARK, date: 07/01/2012},{address: 5148 N CLARK, date: 07/04/2012},{address: 580…