当前位置: 首页 > news >正文

gin(结)

gin day1


今天的目标就是学懂,看懂每一步代码。


gin框架

gin框架就是go语言的web框架。框架你也可以理解成一个库。里面有一堆封装好的工具,帮你实现各种各样的功能,这样使得你可以关注业务本身,而在写代码上少费力。

快速入门:

先懂得这些概念:
1.什么是模块
Go模块(Go Module)是Go语言的包管理和依赖管理系统,自Go 1.11版本引入。它提供了一种管理Go项目依赖的机制,使得开发者可以在项目中明确指定所使用的外部依赖包的版本。这种机制确保了项目的构建是可重复的,同时也简化了包的共享和版本控制

Go模块的特点
版本控制:Go模块支持语义版本控制(Semantic Versioning),每个模块的版本号清晰地反映了该版本的变更性质(如向后兼容的更新、新特性添加或大变更)。

依赖管理通过go.mod文件,Go模块允许你为项目指定精确的依赖版本,包括直接依赖和间接依赖。

易于共享:Go模块使得共享和使用其他开发者编写的Go代码变得更加简单,只需通过import语句引入模块路径即可使用。

模块代理:Go 1.13引入了模块代理(Module Proxy)的概念,允许通过设置GOPROXY环境变量来指定模块下载源,加快模块下载速度,提高依赖管理的效率。

使用Go模块
要在项目中启用Go模块支持:

初始化模块:在项目根目录下执行go mod init 命令,这会创建一个go.mod文件。通常是项目的包名或代码库的路径。

添加依赖:通过import语句在代码中引入所需的包,然后运行go build、go test或go mod tidy等命令。Go工具会自动更新go.mod文件,并可能创建一个go.sum文件来存储依赖的确切版本和校验和。

升级和管理依赖可以使用go get命令来添加、更新或移除依赖go mod tidy命令会移除项目不再使用的依赖,并更新go.mod文件以反映当前的依赖状态。

Go模块的好处
项目隔离:每个项目可以有自己的依赖版本,不同项目之间的依赖版本不会相互冲突。

构建可重复性:由于所有依赖的版本都被精确记录,项目的构建结果在任何环境下都是一致的。

简化依赖管理:无需将第三方包的源代码直接包含在项目仓库中,通过go.mod文件管理依赖使项目结构更清晰,依赖更新更简单。

2.工作区是什么?
工作区(Workspace)是一个概念,它用于组织和管理一个或多个相关的项目或模块,使得这些项目或模块可以作为一个整体被开发和管理。在不同的编程环境和工具中,工作区的具体实现和用法可能有所不同,但基本概念是一致的。对于Go语言来说,工作区的概念在Go 1.18版本中通过go.work文件被引入主要用于管理和协调多个Go模块。

3.Go工作区的特点
多模块管理:Go工作区允许在一个共享的开发环境中同时处理多个Go模块。这对于开发大型项目或多个相互依赖的微服务非常有用。

本地协调:在工作区中,你可以本地修改多个模块,并且这些修改会被整个工作区中的其他模块所识别和使用,而不需要先将更改推送到远程仓库。

灵活的依赖管理:工作区使得跨模块的依赖管理变得更加灵活。你可以轻松地在本地模块之间进行更改,测试这些更改对其他模块的影响,然后再一次性提交所有的更改。

4.使用Go工作区
要在Go中创建一个工作区,你需要:

1.创建go.work文件:在工作区的根目录下创建一个名为go.work的文件。这个文件告诉Go工具链,哪些模块属于这个工作区。

2.指定工作区中的模块:在go.work文件中使用use关键字列出工作区包含的所有模块的路径。例如:

plaintext
Copy code
go 1.18

use (
./module-a
./module-b
)
3.开发和构建:在工作区根目录下执行Go命令(如go build、go test等)时,Go工具链会考虑到所有在go.work文件中指定的模块。

5.工作区的好处
简化本地开发:工作区简化了同时开发多个模块时的流程,特别是当这些模块相互依赖时。
提高生产力:无需频繁地推送和拉取远程仓库中的更改,开发者可以更快地测试和迭代。
灵活的测试:在提交更改之前,可以在本地环境中测试模块间的集成,确保所有更改都能协同工作。


快速入门解读:

D:\go\project>mkdir ginlearn
D:\go\project>cd ginlearn
D:\go\project\ginlearn>go work init
D:\go\project\ginlearn>mkdir helloworld
D:\go\project\ginlearn>cd helloworld
D:\go\project\ginlearn\helloworld>go mod init test.com/helloworld
go: creating new go.mod: module test.com/helloworld
D:\go\project\ginlearn\helloworld>cd ..
D:\go\project\ginlearn>go work use ./helloworld

解读:
创建一个新的项目目录
mkdir ginlearn:在D:\go\project目录下创建一个名为ginlearn的新目录,这将作为新项目的根目录。
cd ginlearn:切换到刚创建的ginlearn目录,后续的操作都将在这个目录下进行。

初始化Go工作区
go work init:在当前目录(D:\go\project\ginlearn)初始化一个新的Go工作区,这会创建一个go.work文件这个文件用于定义工作区内包含的Go模块,使得你可以在本地开发环境中轻松管理和协调多个模块。

创建并初始化一个新的Go模块
mkdir helloworld:在ginlearn项目目录下创建一个名为helloworld的子目录,用于存放一个新的Go模块。
cd helloworld:切换到helloworld目录,准备在这个目录下初始化新的Go模块。
go mod init test.com/helloworld:在当前目录(D:\go\project\ginlearn\helloworld)初始化一个新的Go模块,模块名为test.com/helloworld。这会创建一个go.mod文件,该文件描述了模块的名称和其他依赖信息。

将新模块添加到工作区
cd …:返回到ginlearn项目根目录。
go work use ./helloworld:将helloworld模块添加到工作区。这个命令更新go.work文件,包含对helloworld模块的引用。这样,当你在工作区根目录(D:\go\project\ginlearn)中运行Go命令时,Go工具链会考虑到helloworld模块。


安装gin

下载安装gin后,依赖是灰色的代表还没有使用它。

package mainimport ("github.com/gin-gonic/gin""log"
)func main() {r := gin.Default()r.GET("/hello", func(ctx *gin.Context) {ctx.JSON(200, gin.H{"name": "hello",})})err := r.Run(":8080")if err != nil {log.Fatalln(err)}
}

解读:
1.先导包
2.**gin.Default()**创建了一个Gin引擎实例r。Default方法返回一个默认的路由引擎,已经包括了Logger和Recovery中间件,这两个中间件可以帮助记录日志和管理panic,使得服务更加稳定。
3.

	r.GET("/hello", func(ctx *gin.Context) {ctx.JSON(200, gin.H{"name": "hello",})})

这是设置路由:
设置了一个路由/hello,用于处理GET请求。当访问这个路由时,会执行一个匿名函数,这个函数接受一个*gin.Context类型的参数ctx,它封装了请求的上下文信息。函数内部,使用ctx.JSON方法返回一个JSON响应,状态码为200,内容是一个包含"name": "hello"键值对的JSON对象。

光是这样的理解我觉得还是很困难:再进一步解读:
要搞懂匿名函数的意义和ctx是什么。
匿名函数:
在Gin框架中,路由处理函数通常以匿名函数的形式提供。匿名函数允许你直接在路由定义中实现处理逻辑,而不需要单独定义一个函数。这种方式使得代码简洁且易于理解,特别是当处理逻辑相对简单时。
这里,r.GET(“/hello”, …)定义了一个处理HTTP GET请求的路由/hello。当这个路由被访问时,紧随其后的匿名函数就会被执行。

ctx(Context)
ctx是*gin.Context类型的实例,它是Gin框架提供的一个上下文对象,封装了当前HTTP请求的所有信息,以及对请求的响应操作的方法。你可以将ctx视为当前请求的上下文,通过它可以访问请求的数据(如查询参数、表单值、JSON请求体等),设置响应的数据,以及执行其他与请求处理相关的操作。

访问请求信息:比如,使用ctx.Query(“param”)来获取URL查询参数param的值,或者ctx.PostForm(“field”)来获取表单中名为field的字段值。

设置响应:在你的例子中,ctx.JSON(200, gin.H{“name”: “hello”})是使用ctx设置响应的一个例子。这条语句发送了一个HTTP状态码为200的响应,内容是一个JSON对象{“name”: “hello”}gin.H是Gin提供的一个便利的方式来创建一个map,这个map会被自动转换为JSON。

最后启动HTTP服务器,监听8080端口。


路由 — 请求方法

package mainimport ("github.com/gin-gonic/gin""log"
)func main() {r := gin.Default()r.GET("/hello", func(ctx *gin.Context) {ctx.JSON(200, gin.H{"name": "hello",})})r.GET("/get", func(ctx *gin.Context) {ctx.JSON(200, "get")})r.POST("/post", func(ctx *gin.Context) {ctx.JSON(200, "post")})r.PUT("/put", func(ctx *gin.Context) {ctx.JSON(200, "update")})r.DELETE("/delete", func(ctx *gin.Context) {ctx.JSON(200, "delete")})r.Any("/any", func(ctx *gin.Context) {ctx.JSON(200, "Any")})err := r.Run(":8080")if err != nil {log.Fatalln(err)}
}

这个就说几个要点:
指定了路由的响应方法,你用其他的方法去访问这个路由,就会报404。
如果想一个路由可以支持所有的方法,那么就要用Any来进行设置。
如果想一个路由支持某几种方法,那就分别写这个路由的这几种方法的实现。


路由—URL

URL书写时,我们不需要关系scheme和authority这两部分,我们主要通过path和query两部分的书写来进行资源定位。
静态url:

r.POST("/user/find", func(ctx *gin.Context) {
})

路径参数,比如/user/find/:id

r.POST("/user/find/:id", func(ctx *gin.Context) {param := ctx.Param("id")ctx.JSON(200, param)})

创建了一个路由/user/find/:id,其中:id是一个路径参数(path parameter),用于在URL路径中动态地表示数据(在这个例子中是用户的ID)。这种路由定义方式允许服务器接受包含特定ID的请求,如/user/find/123,其中123会被识别为ID的值。

param := ctx.Param(“id”):这行代码通过调用ctx.Param方法,从请求的URL路径中提取名为id的路径参数的值,并将这个值存储在变量param中。例如,如果请求的URL是/user/find/123,那么param的值将会是"123"。

*模糊匹配,比如/user/path

r.POST("/user/*path", func(ctx *gin.Context) {param := ctx.Param("path")ctx.JSON(200, param)
})

r.POST("/user/path", …)定义了一个路由规则,它可以匹配如/user/action、/user/settings/profile等任意以/user/开头的POST请求。**这里的path是一个动态部分,它会匹配这个位置及之后的所有路径。**
param := ctx.Param(“path”):这行代码通过ctx.Param方法从请求的URL中提取名为path的参数值。例如,如果请求的URL是/user/settings/profile,则param将包含settings/profile。


有一个点需要注意,就是两种动态方式,不能一起用,这里我说的一起用是前缀不能相同,否则就会产生矛盾。

分组路由

v1 := r.Group("/v1"){v1.GET("find", func(ctx *gin.Context) {ctx.JSON(200, "v1 find")})v1.GET("save", func(ctx *gin.Context) {ctx.JSON(200, "v1 save")})}v2 := r.Group("/v2"){v2.GET("find", func(ctx *gin.Context) {ctx.JSON(200, "v2 find")})v2.GET("save", func(ctx *gin.Context) {ctx.JSON(200, "v2 save")})}

这个思想对于业务上是很直观的,
比如一个程序版本是v1,那么对其的所有程序的编写那就是v1版本,当想提升版本的时候,总不可能去对v1进行改动,这里就采用了组的形式,可以有效的分组进行版本管理,可以看到上述例子还是非常的直观。
对于用户来说就是去改了一点点域名,由访问/v1而改去访问/v2
这种思想在分模块里面也是很好用的,比如我可以进行分组/user 和/goods。


请求参数

http://localhost:8080/user/save?id=11&name=zhangsan
比如我要获取这个里面的参数:?号后面的就是参数,这个显然是键值对的形式。

r := gin.Default()//http://localhost:8080/user/save?id=11&name=zhangsanr.GET("user/save", func(ctx *gin.Context) {id := ctx.Query("id")name := ctx.Query("name")ctx.JSON(200, gin.H{"id":   id,"name": name,})})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

这里是通过Query,来获取里面的参数,传参传的是key,返回值是value。
还可以通过GetQuery,这种方式相比Query的好处就是它有一个返回值是判断,
GetQuery(“address”),实际上我要访问的这个url中,根本就没有这个参数,那么返回值就是""和flase。这种在处理如果没有该字段那就进行处理这种场景也是很好用的。
ctx.DefaultQuery(“address”,“beijing”),这个就是如果不存在就会给个默认值这种用法。这个用法也是很常见的。

上面这种方法有它的不足,使用这几个方法,得到的返回值value都是string类型的。那么有没有方法,可以直接返回上面参数,并且类型要符合要求,比如id那就是int,name那就是string。
回答是可以的,要通过结构体实现ctx.BindQuery(&struct)

type User struct {Id   int64  `form:"id"`Name string `form:"name"`
}r := gin.Default()r.GET("/user/save", func(ctx *gin.Context) {var user Usererr := ctx.BindQuery(&user)if err != nil {log.Println(err)}ctx.JSON(200, user)})err := r.Run()if err != nil {log.Fatalln(err)}

这个结构体中的tag标签也是非常重要,这样才能使得参数与结构体里面的属性绑定起来,这样返回到结构体中的数据,那不就是有数据类型了 ,也就达到了要求。

但是更推荐使用ShouldBindQuery
再来看看这个例子

type User struct {Id      int64  `form:"id"`Name    string `form:"name"`Address string `form:"address" binding:"required"`
}
这个多加的binding:required表示在绑定的时候这个字段是必须的。r := gin.Default()r.GET("/user/save", func(ctx *gin.Context) {var user Usererr := ctx.ShouldBindQuery(&user)if err != nil {log.Println(err)}ctx.JSON(200, user)})err := r.Run()if err != nil {log.Fatalln(err)}

现在调用的话,虽然结果不会错(状态码是对的),但是在ide环境上是会报错的,原因也是很简单,那就是Address属性是必须的,但是代码中并没有发生address的值绑定。
但是如果在加了这个标签的情况下,那就会发生错误,状态码直接400,而且返回值你会发现好像不是Json类型了。

总结:
一般用ShouldBindQuery,因为这种更方便,就算ide报了错,在结果上是不会产生影响的。ide上的错误处理又非常的容易。

两个方法如果属性都对应的上,那么确实是会出现两种方式都可以的情况。


数组参数

传数组类型的参数是这么来传
http://localhost:8080/user/save?address=Beijing&address=shanghai
传参的时候传相同的参数名 ,只是值不同,值相同也是可以的。

理解:
这个URL相当于前端,后端我们就是要用个数组去接收value,现在我们的关注点就是怎么用数组接。

r := gin.Default()r.GET("/user/save", func(ctx *gin.Context) {http: //localhost:8080/user/save?address=Beijing&address=shanghaiaddress := ctx.QueryArray("address")ctx.JSON(200, address)})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

这里采用了QueryArray,返回值是string类型的切片。
同理可以使用GetQueryArray,有个判断的功能。
注意这个就没有Default的功能。

仍然可以实现绑定功能。

type User struct {Id      int64    `form:"id"`Name    string   `form:"name"`Address []string `form:"address" binding:"required"`
}r := gin.Default()r.GET("/user/save", func(ctx *gin.Context) {var user Userctx.ShouldBindQuery(&user)ctx.JSON(200, user)})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

map参数

先看map传参时,url的格式是如何传的。
http://localhost:8080/user/save?addressMap[home]=Beijing&addressMap[company]=shanghai

现在我们要关注的就是后端怎么去接收这样的参数。
主要是通过ctx.QueryMap(map名)来实现的,对于上面参数,[]里面的就是key,=就是value。

r := gin.Default()r.GET("/user/save", func(ctx *gin.Context) {addressMap := ctx.QueryMap("addressMap")ctx.JSON(200, addressMap)})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

map不支持绑定的方式去拿。
只能通过QueryMap

Post请求参数

post一般针对的是表单参数和Json参数。
所以要发送的是Post请求。

这个例子是获取Form表单的参数
用的一般是PostForm,当然也有Get前缀的方法和Default前缀的方法,这个在前面也学习过,一种是带判断,一种是带默认值。

怎么理解数组参数,就是一个key有多个value。这就是数组。

r := gin.Default()r.POST("/user/save", func(ctx *gin.Context) {id := ctx.PostForm("id")name := ctx.PostForm("name")address := ctx.PostFormArray("address")addressMap := ctx.PostFormMap("addressMap")ctx.JSON(200, gin.H{"id":         id,"name":       name,"address":    address,"addressMap": addressMap,})})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

当然也是可以使用结构体绑定,这里绑定就是单纯的ShouldBind(&user)

r := gin.Default()r.POST("/user/save", func(ctx *gin.Context) {var user Userctx.ShouldBind(&user)ctx.JSON(200, user)})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

但是有同样的问题,map同样获取不到。如果你一定要这个user里面的map有值,你只能直接去通过QueryMap单独获取了然后对user结构体的map字段赋值,然后再进行响应设置。


Json参数

这种获取参数可以采用绑定的形式

r := gin.Default()r.POST("/user/save", func(ctx *gin.Context) {var user Userctx.ShouldBindJSON(&user)ctx.JSON(200, user)})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

这里在知道是Json数据的情况下最好使用ShouldBindJSON


路径参数

这里有一个问题:

type User struct {Id         int64                  `form:"id"`Name       string                 `form:"name"`Address    []string               `form:"address" binding:"required"`AddressMap map[string]interface{} `form:"addressMap"`
}

这里说的是关于映射关系:
比如我访问的时候:

{"Id": 11,"Name": "zhangsan","Address": ["beijing","shanghai"],"AddressMap": {"home":"beijing"}
}

我的json数据我突然改了Address改为Addressess,这显然肯定就映射不到了。
这里我们想当然的去更改结构体里面的标签,想通过form标签的修改从而使得Address和addressess又匹配上。但是你这么做你就会发现还是匹配不上:这里说说原理:
这是因为实际上这个匹配关系根form标签没什么关系,这里默认情况下Json是将字段名转成小写实现了匹配,这里要想实现匹配,那么用的标签就不是form而是使用json标签来做修改。
这是用json匹配的一个重点。

路径参数:

r := gin.Default()r.POST("/user/save/:id/:name", func(ctx *gin.Context) {id := ctx.Param("id")name := ctx.Param("name")ctx.JSON(200, gin.H{"id":   id,"name": name,})})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

这个其实也讲过,就是利用Param从url里提取参数,然后进行返回。

也可以用绑定的形式去获取。
这个有个要点,要加上uri的tag。不然ShouldBindUri(&user)进行绑定时找不到它要对应的属性。
uri:“id” 这样的tag。

简单情况下就用param。复杂用tag


文件参数

用的MultipartFor

r := gin.Default()r.POST("/user/save", func(ctx *gin.Context) {form, err1 := ctx.MultipartForm()if err1 != nil {log.Fatalln(err1)}value := form.Valuefiles := form.Filefor _, fileArray := range files {for _, v := range fileArray {ctx.SaveUploadedFile(v, "./"+v.Filename)}}ctx.JSON(200, value)})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

我看的时候心中还是有疑惑的。

form, err1 := ctx.MultipartForm():从请求中解析多部分表单数据。这个方法返回两个值:一个multipart.Form对象和一个可能的错误。如果解析过程中出现错误,错误会被记录,并通过log.Fatalln(err1)输出。

value := form.Value:form.Value包含了所有表单的文本字段(键值对)。这些是非文件字段。

files := form.File:form.File包含了所有上传的文件,这些文件被组织为一个映射,其中键是表单字段名,值是对应的一组文件。

一组文件的理解:
不是文件夹:在这个上下文中,“一组文件”指的是用户通过单个表单字段上传的多个文件,而不是指一个文件夹或目录。这些文件以列表(Slice)的形式存在,每个列表项代表一个文件。
这个就是非常字面的意思,比如一个file1(key),它可以有多个value(别问为什么,就是这样的),允许用户为同一个字段名(在这个例子中是file1)选择多个文件进行上传。这意味着对于这个字段名file1,可以上传多个文件,比如1.png, 2.png等。。所以一个key就代表了一个文件组,里面的文件以slice的形式存在,所以才需要先遍历for range 所有的文件组,然后才是遍历这个文件组里面的文件。

现在我正式来解读这段代码:
form, err1 := ctx.MultipartForm()
这个就是直接从请求中解析多部分得表单数据。会得到一给multipart.Form对象。
对这个对象调用这两个字段value := form.Value
files := form.File
两个都是获取得map。

value是map[string][]string类型的,代表了所有非文件的表单key和value的映射,并且这里也考虑到了key相同而且value有几个的情况,这也是很常见的,比如前面说到的json数组参数。

files是map[string]*[]FileHeader类型的,代表了文件组里面一层就是文件组中的文件。
这就是为什么要搞for循环的原因。

for _, fileArray := range files {for _, v := range fileArray {ctx.SaveUploadedFile(v, "./"+v.Filename)}}

外循环
for _, fileArray := range files:这个循环遍历files映射中的每个条目。每次迭代,fileArray会被赋值为与当前字段名相关联的文件数组(或说是切片)。这意味着,如果你的表单允许用户在多个不同的字段上传文件,这个循环会依次处理每个字段上传的所有文件。
内循环
for _, v := range fileArray:对于每个字段名对应的文件数组,这个内部循环会遍历数组中的每个文件。在每次迭代中,v被赋值为当前的文件,它是一个*multipart.FileHeader实例,代表一个上传的文件。
文件保存
ctx.SaveUploadedFile(v, “./”+v.Filename):对于每个文件v,这行代码调用SaveUploadedFile方法将它保存到服务器上。第一个参数是文件(*multipart.FileHeader类型),第二个参数是保存路径这里使用"./"+v.Filename作为保存路径,意味着文件将被保存在服务器应用程序的当前工作目录下,文件名与上传时使用的文件名相同。


响应

所谓响应就是客户端请求发过来了,服务器给客户端返回什么东西。

gin中响应有多种方式(看需求选择):

1.string方式:

r := gin.Default()r.GET("/user/save", func(ctx *gin.Context) {ctx.String(200, "this is %s", "ms string value")})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

2.Json方式,前面用的一直是json形式。

r := gin.Default()r.GET("/user/save", func(ctx *gin.Context) {ctx.JSON(200, gin.H{"name": "666",})})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

3.xml方式,这个结构上和json类似
相较于json,就只是改个函数调用,值得格式都是一样。
只是xml不像json搞得键值对,而是这种形式666,key直接括起value。

r := gin.Default()r.GET("/user/save", func(ctx *gin.Context) {ctx.XML(200, gin.H{"name": "666",})})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

当然也可以自行定义结构体,但是注意要 打标签xml:"id"

4.文件方式

r := gin.Default()r.GET("/user/save", func(ctx *gin.Context) {ctx.File("文件路径")})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

这个如果你用浏览器去访问,那其实就是下载
如果改成ctx.FileAttachment(”文件路径“,“下载的时候改文件的名字”)

5.设置http响应头

r := gin.Default()r.GET("/user/save", func(ctx *gin.Context) {ctx.Header("test","ms")})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

test是key,ms是value
这个就是用来在响应的时候更改头信息。

6.重定向
就是在访问的时候跳转到另一个地方去。
注意这个用状态码不能用200,只能用301,这个在http包下是http.StatusMovedPermanently

r := gin.Default()r.GET("/user/save", func(ctx *gin.Context) {ctx.Redirect(http.StatusMovedPermanently,"https://www.baidu.com")})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

后面第二个参数就是重定向后的地址。就实现跳转到百度了。

7.YAML方式
用法和json一样的。就是函数改成YAML
这种格式只是有时候对这种格式有需求的时候就可以用。

8.还可以返回protoBuf格式。

模板渲染

模板是golang语言的一个标准库,使用场景很多,gin框架同样支持模板。
1.基本使用

r := gin.Default()r.LoadHTMLFiles("./templates/index.tmpl") r.GET("/index", func(ctx *gin.Context) {ctx.HTML(200, "index.tmpl", gin.H{"title": "hello template",})})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

1.先加载模板,就是模板的路径
2.然后还是构造get请求,访问的路由是/index,处理函数。是在响应的时候返回HTML,第二个参数就是使用模板,直接写模板的名字,然后就是传入占位符的参数,这里串了一个结构体进去,与占位符title完成映射。

2.多个模板渲染
如果有多个模板。可以统一进行渲染。
就是templates这个文件夹下,可能有多个模板文件。
想要同时使用多个,比如两个,仍然可以使用r,LoadHTMLFiles函数。直接在后面打个逗号再多加载一个模板,然后再针对这个模板写一个Get请求时的响应方式即可。

r := gin.Default()r.LoadHTMLFiles("./templates/index.tmpl","./templates/user.tmpl")r.GET("/index", func(ctx *gin.Context) {ctx.HTML(200, "index.tmpl", gin.H{"title": "hello template",})})r.GET("/user", func(ctx *gin.Context) {ctx.HTML(200, "user.tmpl", gin.H{"title": "hello user template",})})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

如果模板再多一点,那加载模板就要写一堆,这里还有方法:
可以使用正则表达式:
加载模板的函数改成这个:r.LoadHTMLGlob("./templates/") 加载templates下面的所有模板。
这种方法有注意事项。如果templates里面还有一个文件夹里面还有模板,那么这种情况叫做加载子模板,这个时候就要用./templates/
/*代表子模版,不然就会报错

自定义模板函数

啥时候用:当我们想对模板进行一些处理时用。这样能够使用起来更加的便捷。

实现也非常容易,但是注意这个要在加载模板之前完成。要先定义后加载。
模板:

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>gin_templates</title>
</head>
<body>
{{.title | safe}}
</body>
</html>
r := gin.Default()r.SetFuncMap(template.FuncMap{"safe": func(str string) template.HTML {return template.HTML(str)},})// 模板解析,解析templates目录下的所有模板文件r.LoadHTMLGlob("templates/**")r.GET("/index", func(c *gin.Context) {// HTML请求// 模板的渲染c.HTML(http.StatusOK, "index.tmpl", gin.H{"title": "<a href='http://baidu.com'>跳转到其他地方</a>",})})err := r.Run(":8080")if err != nil {log.Fatalln(err)}
}

首先具体说说自定义模板函数有什么用:
从模板文件就可以看出来,主函数只是实现。
在提供的HTML模板文件中,{{.title | safe}}使用了一个占位符title。这里,title是模板渲染时传入的数据的一部分,可以被动态替换。| safe表示在渲染title变量时,会通过名为safe的函数进行处理。这个函数的目的是将传入的字符串标记为安全的HTML,不会被自动转义。

总的来说这个模板函数的作用就是在你使用模板的时候,你要传参,那么这个函数就可以对传入的这个参数进行处理,这个参数在模板中的体现就是这个占位符。
{{.title | safe}}这里就注意下这种格式。

然后就是函数实现:

r.SetFuncMap(template.FuncMap{"safe": func(str string) template.HTML {return template.HTML(str)},})

参数对于这个函数就是str,然后返回值是一个HTML类型的。
“title”: “跳转到其他地方”,
正常来说那就直接对这个进行打印了,因为这就是一个字符串。但是经过这个处理函数处理之后,就会进行转义变成HTML格式的。

静态文件处理:

如果在模板中引入静态文件,比如样式文件index.css
模板文件为这样:

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>gin_templates</title><link rel="stylesheet" href="/css/index.css">
</head>
<body>
{{.title}}
</body>
</html>

这里就引入了样式表,但是显然这个路径是不对的,正确的应该是./static/css/index.css才行
但是这里我就要这样做,看看有什么方法进行处理。
我要想办法让他能够找到这个正确的路径,这里的做法:
用了静态加载
r.Static("映射“,“映射到正确的目录”)

r.Static("/css", "./static/css")

这里就相当于对这个css做了映射。
简而言之就是前者是我在模板里面用的,后者就是实际这个文件所在的目录。

甚至可以直接指定正确的文件在哪个目录。

会话

会话涉及到cookie和session的使用。

cookie

1、设置cookie
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
name:cookie名
value:cookie值
maxAge:有效时间
path:cookie路径
domain:cookie作用域
secure:这个用来设置cookie只能用https发送给服务器
httpOnly:设置cookie不能被js获取到

r := gin.Default()r.GET("/cookie", func(c *gin.Context) {// 设置cookiec.SetCookie("site_cookie", "cookievalue", 3600, "/", "localhost", false, true)})err := r.Run(":8080")if err != nil {log.Fatalln(err)}

直接使用SetCookie函数,然后把那些参数都填好就完成了cookie的设置。

既然可以设置那就可以读取cookie。

2.读取cookie(根据cookie的名字读取)
就是通过data,err:=ctx.Cookie(“name”)来实现,err一般就是你访问的cookie根本就不存在,这个时候就会报错了。
读出来是cookie的value

3.删除cookie。
通过将cookie的MaxAge设置为-1达到删除cookie的目的,所以这还是用到了setcookie函数。别的都不用动,动这个Maxage字段就完成了实现。
调用之后你去浏览器里f12看,就会发现那个cookie不见了

Session

在gin框架中,我们可以依赖gin-contrib/sessions中间件处理session
安装session包

go get github.com/gin-contrib/sessions

看例子:

r := gin.Default()// 创建基于cookie的存储引擎,secret 参数是用于加密的密钥store := cookie.NewStore([]byte("secret"))// 设置session中间件,参数mysession,指的是session的名字,也是cookie的名字// store是前面创建的存储引擎,我们可以替换成其他存储引擎r.Use(sessions.Sessions("mysession", store))r.GET("/hello", func(c *gin.Context) {// 初始化session对象session := sessions.Default(c)// 通过session.Get读取session值// session是键值对格式数据,因此需要通过key查询数据if session.Get("hello") != "world" {fmt.Println("没读到")// 设置session数据session.Set("hello", "world")session.Save()}c.JSON(200, gin.H{"hello": session.Get("hello")})})r.Run(":8080")

虽然注释的很清楚了,但是我心中有一些疑问:
这里从知识方面再解读:
Session在Web应用中是用来持久化用户状态的一种机制。当用户与应用交互时,HTTP协议本身是无状态的,意味着服务器默认情况下不会记住用户之前的操作。Session通过在服务器端存储用户特定的信息来跨多个请求保持状态,而这些信息通过客户端的Cookie来引用。

当我们说“这些信息通过客户端的Cookie来引用”时,我们是指Session机制如何在客户端(例如,用户的浏览器)和服务器之间维持状态信息的关联。这个过程大致如下:
1. 服务器创建Session
当用户第一次访问应用时,服务器为该用户创建一个Session。这个Session包含一个唯一的标识符(通常称为Session ID)和与该用户相关的任何状态信息。

2. 发送Session ID到客户端
一旦Session创建完毕,服务器会将Session ID发送到客户端,方法是设置一个Cookie。这个Cookie包含Session ID,并随着HTTP响应一起发送回用户的浏览器。

3. 客户端存储Session ID
用户的浏览器接收到这个Cookie后,会将其存储起来。在之后的每次请求中,浏览器都会自动将这个Cookie(即包含Session ID的Cookie)随请求一起发送到服务器。

4.服务器识别Session
对于接下来的每一个请求,服务器通过请求中携带的Cookie中的Session ID,识别出是哪个用户发起的请求。服务器然后可以查找对应的Session数据,进而访问该用户的状态信息。

5. 状态信息的引用
这里,“通过客户端的Cookie来引用”实际上是指,客户端的Cookie(具体来说,是Cookie中的Session ID)被用作在服务器上查找特定Session数据的键。换句话说,客户端的Cookie(Session ID)为服务器提供了一个引用或索引,服务器用它来找到存储的状态信息,这些状态信息包含了用户特定的数据(如登录状态、购物车内容等)。

结论
因此,尽管状态信息(Session数据)存储在服务器端,客户端的Cookie(携带Session ID)起到了桥梁的作用,使得服务器能够为不同的用户请求匹配到正确的Session,从而“记住”用户的状态跨多个请求。这是一种在无状态的HTTP协议上实现状态保持的常用技术。

现在看这个代码压力就不大了。

cookie.NewStore([]byte(“secret”))创建一个基于Cookie的Session存储引擎,其中"secret"是用于加密Cookie的密钥,以保护Session数据的安全。
sessions.Sessions(“mysession”, store)中间件被添加到Gin应用中,其中"mysession"是Session(同时也是Cookie)的名字。这个中间件
负责处理所有通过Gin路由的请求,为它们提供Session支持。

处理请求和操作Session
sessions.Default©获取当前请求的Session对象。
session.Get(“hello”)尝试从Session中读取键为"hello"的值。如果这是用户的第一次请求或者Session中没有设置该值,结果将为nil。
如果session.Get(“hello”)的结果不是"world",则**通过session.Set(“hello”, “world”)在Session中设置值,并调用session.Save()**来确保更改被保存。这些更改将被存储在服务器端的Session存储中,并通过客户端的Cookie来引用。
c.JSON(200, gin.H{“hello”: session.Get(“hello”)})返回一个JSON响应,其中包含了从Session中读取的"hello"键的值。

多session

package mainimport ("github.com/gin-contrib/sessions""github.com/gin-contrib/sessions/cookie""github.com/gin-gonic/gin"
)func main() {r := gin.Default()store := cookie.NewStore([]byte("secret"))sessionNames := []string{"a", "b"}r.Use(sessions.SessionsMany(sessionNames, store))r.GET("/hello", func(c *gin.Context) {sessionA := sessions.DefaultMany(c, "a")sessionB := sessions.DefaultMany(c, "b")if sessionA.Get("hello") != "world!" {sessionA.Set("hello", "world!")sessionA.Save()}if sessionB.Get("hello") != "world?" {sessionB.Set("hello", "world?")sessionB.Save()}c.JSON(200, gin.H{"a": sessionA.Get("hello"),"b": sessionB.Get("hello"),})})r.Run(":8080")
}

配置多Session中间件:r.Use(sessions.SessionsMany(sessionNames, store))向Gin引擎添加一个中间件,该中间件配置了多个Session。sessionNames := []string{“a”, “b”}定义了两个Session的名称"a"和"b",这些Session将使用前面创建的cookie存储引擎。

设置路由和处理函数:
在GET /hello路由的处理函数中,使用sessions.DefaultMany(c, “a”)和sessions.DefaultMany(c, “b”)分别获取名为"a"和"b"的Session实例。然后就可以对Session进行进一步的操作 。
检查每个Session中是否存在键"hello",如果不存在或值不匹配,则分别设置它们的值为"world!“和"world?”,并保存更改。
最后,响应包含了从两个Session中获取到的"hello"键的值,展示了如何在同一个请求中独立管理多个Session。

也可以把Session存储在数据库中,比如redis。

中间件

在Gin框架中,中间件(Middleware)指的是可以拦截http请求-响应生命周期的特殊函数在请求-响应生命周期中可以注册多个中间件,每个中间件执行不同的功能,一个中间执行完再轮到下一个中间件执行。

换句话说就是:请求发过来要穿过中间件,响应返回也要通过穿过中间件。

中间件的常见应用场景如下:

请求限速
api接口签名处理
权限校验
统一错误处理

Gin支持设置全局中间件针对路由分组设置中间件,设置全局中间件意思就是会拦截所有请求,针对分组路由设置中间件,意思就是仅对这个分组下的路由起作用。

1.中间件的使用
从这个gin.Default的源码可以看出:

func Default() *Engine {debugPrintWARNINGDefault()engine := New()engine.Use(Logger(), Recovery())return engine
}

发现了创建中间件,然后调用中间件这样的操作。这里就使用了两个中间件一个Logger一个是Recovery

 r := gin.New()// 通过use设置全局中间件// 设置日志中间件,主要用于打印请求日志r.Use(gin.Logger())// 设置Recovery中间件,主要用于拦截paic错误,不至于导致进程崩掉r.Use(gin.Recovery())r.GET("/test", func(ctx *gin.Context) {panic(errors.New("test error"))})r.Run(":8080")

这里在请求中报了一个panic,如果用了中间件,那么就不会导致我们的程序启动不起来,会发现状态码是500,但是如果你没有用Recovery中间件,没有拦截panic错误,那么就会直接崩掉,你去访问这个路由会发现路由都是不存在的,因为服务器都没启动起来。

里面还用到了一个日志中间件,会打印日志。

中间件有啥好处:就是利用这些功能提供很大的便捷。

2.自定义中间件
可以去看看我们用的中间件有什么特征:

func Logger() HandlerFunc {return LoggerWithConfig(LoggerConfig{})
}

可以发现就是一个返回了HandlerFunc类型的函数。我们实际上就是去开发这个函数。可以看到内部返回了一个函数,我们自定义的目标就是去实现这样的内部返回函数。
比如我这里也来一个

func Logger() gin.HandleFunc{return func(c *gin.Context){fmt.Println("my custom midd)}
}

然后直接主函数里面调用use就可以了。这样就实现了一个简单的中间件的自定义。调用之后由于前面说过,请求会穿过中间件,那么我这个中间件在遇到请求的时候就会工作起来,然后打印一个my custom midd

package main
// 导入gin包
import (
"github.com/gin-gonic/gin""log""time"
)// 自定义个日志中间件
func Logger() gin.HandlerFunc {return func(c *gin.Context) {t := time.Now()// 可以通过上下文对象,设置一些依附在上下文对象里面的键/值数据c.Set("example", "12345")// 在这里处理请求到达控制器函数之前的逻辑// 调用下一个中间件,或者控制器处理函数,具体得看注册了多少个中间件。c.Next()// 在这里可以处理请求返回给用户之前的逻辑latency := time.Since(t)log.Print(latency)// 例如,查询请求状态吗status := c.Writer.Status()log.Println(status)}
}func main() {r := gin.New()// 注册上面自定义的日志中间件r.Use(Logger())r.GET("/test", func(c *gin.Context) {// 查询我们之前在日志中间件,注入的键值数据example := c.MustGet("example").(string)// it would print: "12345"log.Println(example)})// Listen and serve on 0.0.0.0:8080r.Run(":8080")
}

我觉得我第一次看不懂主要出现在纠结这个c.Next()的问题。下面详细解读:

调用c.Next()确实会将控制权传递给后续的处理链,通常是业务逻辑处理函数。然而,c.Next()的调用方式确实有点类似于"中断"或"钩子"的概念,但更准确地说,这是一种"中间件模式"的实现,它允许在请求的前后执行特定的代码。

如何理解c.Next()和中间件的工作机制?
当Gin处理一个请求时,它会按照中间件和路由处理函数注册的顺序构建一个调用栈。每个中间件会被依次调用,直到调用栈中的最后一个函数(通常是实际的业务逻辑处理函数)。c.Next()在中间件中的作用是将控制权传递给调用栈中的下一个函数。

在c.Next()之前的代码:这部分代码在请求达到业务逻辑处理函数之前执行。 可以用于设置一些预处理条件,比如记录请求开始的时间、检查请求头部等。

调用c.Next():这个调用启动了调用栈中的下一个中间件或者路由处理函数的执行。如果没有更多的中间件或处理函数,Gin将开始逐步回溯调用栈。

在c.Next()之后的代码:这部分代码在所有后续的中间件和业务逻辑处理完成之后执行。这是处理请求后的逻辑,如记录处理时间、发送监控指标、记录响应状态等。

中间件的执行流程
假设有中间件A和中间件B,以及最终的业务处理函数C:

中间件A的c.Next()之前的代码执行。
中间件B的c.Next()之前的代码执行。
业务处理函数C执行。
中间件B的c.Next()之后的代码执行。
中间件A的c.Next()之后的代码执行。

如果中间件B中没有c.Next这个流程会怎样?
中间件A的c.Next()之前的代码执行。
中间件B的c.Next()之前的代码执行。
在这一步,由于中间件B没有调用c.Next(),请求处理流程在此中断,不会继续传递到业务处理函数C或任何后续的中间件。
业务处理函数C不会被执行。
因为中间件B没有传递控制权,所以即使有业务逻辑定义在C中,它也不会得到执行。
中间件B的c.Next()之后的代码不会执行。
由于c.Next()未被调用,所以实际上并没有进入到B中间件的“之后”阶段。
中间件A的c.Next()之后的代码也不会执行。
同样,由于请求处理流程在B中被中断,所以A中间件的后续逻辑也不会被执行。

所以c.Next这个流程是很重要的,我也不用去考虑多了,因为一旦有这种情况,后面都指向终端。没有这个调用那么处理流程就会提前终止。

现在再来分析代码逻辑:

r.GET("/test", func(c *gin.Context) {// 查询我们之前在日志中间件,注入的键值数据example := c.MustGet("example").(string)// it would print: "12345"log.Println(example)})

1.这里先发送到/test路由的请求。
2.然后由于设置了中间件,所以会先到中间件函数。
中间件函数中的c.Next之前的就会先执行。

t := time.Now()
c.Set("example", "12345")

记录当前的时间,然后在gin的上下文中设置一个键值对"example": “12345”
然后调用c.Next():此时,控制权被传递给调用栈中的下一个中间件或处理函数。在这个例子中,下一个处理函数是/test路由的处理函数。

  1. 执行路由处理函数
    c.MustGet(“example”)从Gin的上下文中检索键名为"example"的值。这个值是在之前执行的日志中间件中设置的。MustGet方法会返回一个interface{}类型的值,所以我们通过类型断言(string)将其转换为string类型。如果键名"example"不存在,MustGet会引发panic,因此使用它时需要确保该键确实存在。

打印获取的值:如果一切顺利,example变量应该包含字符串"12345",这个值接着被打印到日志中。

4.中间件后置处理
计算延迟:计算从请求开始到现在的总耗时latency,并通过log.Print(latency)打印出来。
记录状态码:获取并打印响应的HTTP状态码status。

  1. 响应发送
    此时,所有处理均已完成,Gin框架将生成的响应发送给客户端。

相关文章:

gin(结)

gin day1 今天的目标就是学懂&#xff0c;看懂每一步代码。 gin框架 gin框架就是go语言的web框架。框架你也可以理解成一个库。里面有一堆封装好的工具&#xff0c;帮你实现各种各样的功能&#xff0c;这样使得你可以关注业务本身&#xff0c;而在写代码上少费力。 快速入门&…...

JavaScript 设计模式之桥接模式

桥接模式 通过桥接模式&#xff0c;我们可以将业务逻辑与元素的事件解耦&#xff0c;也可以更灵活的创建一些对象 倘若我们有如下代码 const dom document.getElementById(#test)// 鼠标移入移出事件 // 鼠标移入时改变背景色和字体颜色 dom.onmouseenter function() { th…...

B3651 [语言月赛202208] 数组调整

题目描述 给出一个长度为 n 的数组&#xff0c;第 i 个数为ai​。 为了调整这个数组&#xff0c;需要将第 k 个数改变为 −ak​。 请你求出调整后的数组中所有数的和。 输入格式 输入共两行。 输入的第一行为两个整数 n,k。 输入的第二行为 n 个整数&#xff0c;第 i 个…...

MessageQueue --- RabbitMQ

MessageQueue --- RabbitMQ RabbitMQ IntroRabbitMQ 核心概念RabbitMQ 分发类型Dead letter (死信)保证消息的可靠传递 RabbitMQ Intro 2007年发布&#xff0c;是一个在AMQP&#xff08;高级消息队列协议&#xff09;基础上完成的&#xff0c;可复用的企业消息系统&#xff0c;…...

WordPress作者页面链接的用户名自动变成16位字符串串插件Smart User Slug Hider

WordPress默认的作者页面URL链接地址格式为“你的域名/author/admin”&#xff0c;其中admin就是你的用户名&#xff0c;这样的话就会暴露我们的用户名。 为了解决这个问题&#xff0c;前面boke112百科跟大家分享了『如何将WordPress作者存档链接中的用户名改为昵称或ID』一文…...

Nvidia 携手 RTX 推出的本地运行 AI 聊天机器人

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

年假作业day2

1.打印字母图形 #include<stdio.h> #include<string.h> int main(int argc, const char *argv[]) { int i,j; char k; for(i1;i<7;i) { for(j1;j<i;j) { printf("%c",_); } for(j0,…...

HTML-多媒体嵌入-MDN文档学习笔记

HTML-多媒体与嵌入 查看更多学习笔记&#xff1a;GitHub&#xff1a;LoveEmiliaForever MDN中文官网 HTML-中的图片 将图片放入网页 可以使用<img/>来将图片嵌入网页&#xff0c;它是一个空元素&#xff0c;最少只需src属性即可工作 <img src"图片链接"…...

openJudge | 距离排序 C语言

总时间限制: 1000ms 内存限制: 65536kB 描述 给出三维空间中的n个点&#xff08;不超过10个&#xff09;,求出n个点两两之间的距离,并按距离由大到小依次输出两个点的坐标及它们之间的距离。 输入 输入包括两行&#xff0c;第一行包含一个整数n表示点的个数&#xff0c;第二…...

【教程】MySQL数据库学习笔记(三)——数据定义语言DDL(持续更新)

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 【MySQL数据库学习】系列文章 第一章 《认识与环境搭建》 第二章 《数据类型》 第三章 《数据定义语言DDL》 文章目录 【MyS…...

[leetcode]买卖股票的最佳时机 (动态规划)

121. 买卖股票的最佳时机 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从…...

隐函数的求导【高数笔记】

1. 什么是隐函数&#xff1f; 2. 隐函数的做题步骤&#xff1f; 3. 隐函数中的复合函数求解法&#xff0c;与求导中复合函数求解法有什么不同&#xff1f; 4. 隐函数求导的过程中需要注意什么&#xff1f;...

SG3225EEN晶体振荡器规格书

SG3225EEN 晶振是EPSON/爱普生的一款额定频率25 MHz至500 MHz的石英晶体振荡器&#xff0c;6脚贴片&#xff0c;LV-PECL输出&#xff0c;3225封装常规有源晶振&#xff0c;具有小尺寸&#xff0c;轻薄型&#xff0c;高稳定性&#xff0c;低相位抖动&#xff0c;低电源电压&…...

ESP8266 常用AT指令

一、ESP8266的AT指令要点、常见错误 AT指令要大写;以"\r\n"作结尾;串口通信&#xff0c;115200-None-8-1;支持2.4G频段&#xff0c;不支持5G频段 &#xff08;如果用手机创建热点&#xff0c;注意选择2.4G&#xff09;不支持中文的wifi名称工作模式&#xff0c;上电…...

esbuild 构建工具为什么很快?

esbuild 构建工具之所以很快&#xff0c;主要有以下几个原因&#xff1a; Go语言编写&#xff1a;esbuild 是用 Go 语言编写的&#xff0c;Go 语言以其高效的并发模型和编译速度而闻名。与一些其他构建工具相比&#xff0c;Go 语言在并发处理和内存管理方面表现出色&#xff0c…...

解决vscode报错,在赋值前使用了变量“XXX“

问题&#xff1a;如图所示 解决方法&#xff1a; 法一&#xff1a; 补全函数使其完整 法二&#xff1a; 使用断言...

python自动定时任务schedule库的使用方法

当你需要在 Python 中定期执行任务时&#xff0c;schedule 库是一个非常实用的工具。它可以帮助你自动化定时任务。以下是一些使用示例&#xff1a; 基本使用&#xff1a; import schedule import timedef job():print("Im working...")schedule.every(10).minutes.d…...

用机器学习方法重构期货商品板块

用机器学习方法重构期货商品板块 阿岛格 参考专栏:低门槛搭建个人量化平台 https://www.zhihu.com/column/c_1441014235068944386 摘 要 金融市场商品期货的板块分类,通常根据不同交易所、监管机构和证券商标准,按照期货标的属性、或产业链关系等进行分类,各自分类略有差…...

51单片机项目(29)——基于51单片机的避障跟随小车

1.功能设计 按键模式&#xff1a;按下按键&#xff0c;小车可以前后左右地运动 自动模式&#xff1a;根据红外传感器的状态&#xff0c;自行决定运动状态。检测到前方有物体时&#xff0c;车子移动&#xff0c;起到一个跟随的效果。 演示视频如下&#xff1a; 51单片机智能避障…...

人工智能学习与实训笔记(六):百度飞桨套件使用方法

目录 八、百度飞桨套件使用 8.1 飞桨预训练模型套件PaddleHub 8.1.1 一些本机CPU可运行的飞桨预训练简单模型&#xff08;亲测可用&#xff09; 8.1.1.1 人脸检测模型 8.1.1.2 中文分词模型 8.1.2 预训练模型Fine-tune 8.2 飞桨开发套件 8.2.1 PaddleSeg - 图像分割 8…...

Linux第一个小程序-进度条

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 一、回车和换行 二、行缓冲区概念 三、倒计时 四、进度条代码 版本一&#xff1a; ​编辑 版本二&#xff1a; 总结 前言 世上有两种耀眼的光芒&#xff0c;一…...

YoloV8改进策略:Block改进|Mamba-UNet改进YoloV8,打造全新的Yolo-Mamba网络

摘要 本文尝试使用Mamba的VSSBlock替换YoloV8的Bottleneck,打造最新的Yolo-Mamba网络。 论文:《Mamba-UNet:用于医学图像分割的类似UNet的纯视觉Mamba网络》 在医学图像分析的最新进展中,卷积神经网络(CNN)和视觉转换器(ViT)都取得了显著的基准成绩。前者通过其卷积…...

数据分析基础之《pandas(8)—综合案例》

一、需求 1、现在我们有一组从2006年到2016年1000部最流行的电影数据 数据来源&#xff1a;https://www.kaggle.com/damianpanek/sunday-eda/data 2、问题1 想知道这些电影数据中评分的平均分&#xff0c;导演的人数等信息&#xff0c;我们应该怎么获取&#xff1f; 3、问题…...

(17)Hive ——MR任务的map与reduce个数由什么决定?

一、MapTask的数量由什么决定&#xff1f; MapTask的数量由以下参数决定 文件个数文件大小blocksize 一般而言&#xff0c;对于每一个输入的文件会有一个map split&#xff0c;每一个分片会开启一个map任务&#xff0c;很容易导致小文件问题&#xff08;如果不进行小文件合并&…...

define和typedef

目录 一、define 二、typedef 三、二者之间的区别 一、define 在我们写代码的日常中&#xff0c;经常会用到define去配合数组的定义使用 #define N 10 arr[N]{0}; define不仅仅能做这些 #define是一种宏&#xff0c;我们首先来了解一下宏定义。 宏定义一般作用在C语言的预…...

SpringCloud之Nacos用法笔记

SpringCloud之Nacos注册中心 Nacos注册中心nacos启动服务注册到Nacosnacos服务分级模型NacosRule负载均衡策略根据集群负载均衡加权负载均衡Nacos环境隔离-namespace Nacos与eureka的对比临时实例与非临时实例设置 Nacos配置管理统一配置管理微服务配置拉取配置自动刷新远端配置…...

【c++】拷贝构造函数

1.特征 1.拷贝构造函数是构造函数的一个重载形式。 2.若显示定义了拷贝构造函数&#xff0c;编译器就不会自动生成构造函数了。 3.拷贝构造函数的参数只有一个且必须是类型对象的引用&#xff0c;使用传值方式编译器直接报错&#xff0c;因为会引发无穷递归调用。 4.若未显…...

17.3.1.2 曝光

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 基本算法&#xff1a;先定义一个阈值&#xff0c;通常取得是128 原图像&#xff1a;颜色值color&#xff08;R&#xff0c;G&#…...

【Win10 触摸板】在插入鼠标时禁用触摸板,并在没有鼠标时自动启用触摸板。取消勾选连接鼠标时让触摸板保持打开状态,但拔掉鼠标后触摸板依旧不能使用

出现这种问题我的第一反应就是触摸板坏了&#xff0c;但是无意间我换了一个账户发现触摸板可以用&#xff0c;因此推断触摸板没有坏&#xff0c;是之前的账户问题&#xff0c;跟系统也没有关系&#xff0c;不需要重装系统。 解决办法&#xff1a;与鼠标虚拟设备有关 然后又从知…...

排序算法---桶排序

原创不易&#xff0c;转载请注明出处。欢迎点赞收藏~ 桶排序&#xff08;Bucket Sort&#xff09;是一种排序算法&#xff0c;它将待排序的数据分到几个有序的桶中&#xff0c;每个桶再分别进行排序&#xff0c;最后将各个桶中的数据按照顺序依次取出&#xff0c;即可得到有序序…...

FPGA_工程_基于rom的vga显示

一 框图 二 代码修改 module Display #(parameter H_DISP 1280,parameter V_DISP 1024,parameter H_lcd 12d150,parameter V_lcd 12d150,parameter LCD_SIZE 15d10_000 ) ( input wire clk, input wire rst_n, input wire [11:0] lcd_xpos, //lcd horizontal coo…...

代码随想录算法训练营第31天|● 理论基础 ● 455.分发饼干 ● 376. 摆动序列 ● 53. 最大子序和

文章目录 理论基础分发饼干思路&#xff1a;代码&#xff1a; 摆动序列思路一 贪心算法&#xff1a;代码&#xff1a; 思路二&#xff1a;动态规划&#xff08;想不清楚&#xff09;代码&#xff1a; 最大子序和思路&#xff1a;代码&#xff1a; 理论基础 贪心算法其实就是没…...

无人机地面站技术,无人机地面站理论基础详解

地面站作为整个无人机系统的作战指挥中心&#xff0c;其控制内容包括:飞行器的飞行过程&#xff0c;飞行航迹&#xff0c; 有效载荷的任务功能&#xff0c;通讯链路的正常工作&#xff0c;以及 飞行器的发射和回收。 无人机地面站总述 地面站作为整个无人机系统的作战指挥中心…...

2024.2.13

21.C 22.D 23.B 5先出栈表示1&#xff0c;2&#xff0c;3&#xff0c;4已经入栈了&#xff0c;5出后4出&#xff0c;但之后想出1得先让3&#xff0c;2先后出栈&#xff0c;所以 B 不可能 24.10&#xff0c;12&#xff0c;120 25.2&#xff0c;5 26.可能会出现段错误…...

论文阅读:四足机器人对抗运动先验学习稳健和敏捷的行走

论文&#xff1a;Learning Robust and Agile Legged Locomotion Using Adversarial Motion Priors 进一步学习&#xff1a;AMP&#xff0c;baseline方法&#xff0c;TO 摘要&#xff1a; 介绍了一种新颖的系统&#xff0c;通过使用对抗性运动先验 (AMP) 使四足机器人在复杂地…...

.NET Core WebAPI中封装Swagger配置

一、创建相关文件 创建一个Utility/SwaggerExt文件夹&#xff0c;添加一个类 二、在Program中找到Swagger相关配置信息 三、添加方法&#xff0c;在Program中调用 在SwaggerExt类中添加方法&#xff0c;将相关配置添写入 /// <summary> /// swagger配置 /// </sum…...

28. 找出字符串中第一个匹配项的下标

Problem: 28. 找出字符串中第一个匹配项的下标 文章目录 思路解题方法复杂度Code 思路 这个问题可以通过使用KMP&#xff08;Knuth-Morris-Pratt&#xff09;算法来解决。KMP算法是一种改进的字符串匹配算法&#xff0c;它的主要思想是当子串与目标字符串不匹配时&#xff0c;能…...

宿舍|学生宿舍管理小程序|基于微信小程序的学生宿舍管理系统设计与实现(源码+数据库+文档)

学生宿舍管理小程序目录 目录 基于微信小程序的学生宿舍管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员模块的实现 &#xff08;1&#xff09;学生信息管理 &#xff08;2&#xff09;公告信息管理 &#xff08;3&#xff09;宿舍信息管理 &am…...

CVE-2022-25487 漏洞复现

漏洞描述&#xff1a;Atom CMS 2.0版本存在远程代码执行漏洞&#xff0c;该漏洞源于/admin/uploads.php 未能正确过滤构造代码段的特殊元素。攻击者可利用该漏洞导致任意代码执行。 其实这就是一个文件上传漏洞罢了。。。。 打开之后&#xff0c;/home路由是个空白 信息搜集&…...

C#面:强类型和弱类型

强类型 强类型是指在编程语言中&#xff0c;变量必须明确声明其数据类型&#xff0c;并且在编译时会进行类型检查的特性。它可以提高代码的可读性和可维护性&#xff0c;但有时需要显式地进行类型转换。换句话说&#xff0c;强类型语言要求变量的类型在编译时就要确定&#xf…...

nodejs和npm和vite

Nodejs 简单的说 Node.js 就是运行在服务端的 JavaScript。 Node.js 是一个基于 Chrome JavaScript 运行时建立的一个平台。 Node.js 是一个事件驱动 I/O 服务端 JavaScript 环境 用途&#xff1a; Node.js 可以被看作是一个 JavaScript 运行时环境&#xff0c;专门用于在服务…...

相机图像质量研究(24)常见问题总结:CMOS期间对成像的影响--摩尔纹

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…...

Redis -- 数据库管理

目录 前言 切换数据库(select) 数据库中key的数量&#xff08;dbsize&#xff09; 清除数据库&#xff08;flushall flushdb&#xff09; 前言 MySQL有一个很重要的概念&#xff0c;那就是数据库database&#xff0c;一个MySQL里面有很多个database&#xff0c;一个datab…...

蓝桥杯(Web大学组)2023省赛真题:视频弹幕

思路&#xff1a; 主要是要仔细阅读题目以及理解给出的已有代码&#xff0c;进行函数间的调用、定时器的使用、元素移除、清除定时器等&#xff0c;注意细节。 笔记&#xff1a; height不要写成hight设置left时,记得加单位px可以获取left的值进行计算&#xff0c;但要注意sp…...

真假难辨 - Sora(OpenAI)/世界模拟器的技术报告

目录 引言技术报告汉译版英文原版 引言 Sora是OpenAI在2024年2月15日发布的世界模拟器&#xff0c;功能是通过文本可以生成一分钟的高保真视频。由于较高的视频质量&#xff0c;引起了巨大关注。下面是三个示例&#xff0c;在示例之后给出了其技术报告&#xff1a; tokyo-wal…...

Linux第52步_移植ST公司的linux内核第4步_关闭内核模块验证和log信息时间戳_编译_并通过tftp下载测试

1、采用程序配置关闭“内核模块验证” 默认配置文件“stm32mp1_atk_defconfig”路径为“arch/arm/configs”; 使用VSCode打开默认配置文件“stm32mp1_atk_defconfg”&#xff0c;然后将下面的4条语句屏蔽掉&#xff0c;如下&#xff1a; CONFIG_MODULE_SIGy CONFIG_MODULE_…...

ctfshow-web21~28-WP

爆破(21-28) web21 题目给了一个zip文件,打开后解压是爆破的字典,我们抓包一下网址看看 发现账号和密码都被base64了,我们发送到intruder模块,给爆破的位置加上$符圈住 去base64解码一下看看格式...

鸿蒙开发系列教程(二十四)--List 列表操作(3)

列表编辑 1、新增列表项 定义列表项数据结构和初始化列表数据&#xff0c;构建列表整体布局和列表项。 提供新增列表项入口&#xff0c;即给新增按钮添加点击事件。 响应用户确定新增事件&#xff0c;更新列表数据。 2、删除列表项 列表的删除功能一般进入编辑模式后才可…...

线性代数笔记2--矩阵消元

0. 简介 矩阵消元 1. 消元过程 实例方程组 { x 2 y z 2 3 x 8 y z 12 4 y z 2 \begin{cases} x2yz2\\ 3x8yz12\\ 4yz2 \end{cases} ⎩ ⎨ ⎧​x2yz23x8yz124yz2​ 矩阵化 A [ 1 2 1 3 8 1 0 4 1 ] X [ x y z ] A \begin{bmatrix} 1 & 2 & 1 \\ 3 & …...

透光力之珠——光耦固态继电器的独特特点解析

光耦固态继电器作为现代电子控制领域中的重要组件&#xff0c;以其独特的特点在工业、通信、医疗等多个领域得到广泛应用。本文将深入剖析光耦固态继电器的特点&#xff0c;揭示其在电子控制中的卓越性能。 光耦固态继电器的光电隔离技术 光耦固态继电器以其光电隔离技术而脱颖…...