Gin框架入门实战系列教程之Gin环境搭建 Gin程序的热加载 Gin路由 GET POST PUT DELETE
Gin框架入门实战系列教程之Gin环境搭建 Gin程序的热加载 Gin路由 GET POST PUT DELETE
主讲教师:(大地) 在线文档见网盘下载:
百度网盘 请输入提取码 提取码:abcd
一、Gin介绍
Gin 是一个 Go (Golang) 编写的轻量级http web 框架,运行速度非常快,如果你是性能和高效的追求者,我们推荐你使用Gin框架。
Gin最擅长的就是Api接口的高并发,如果项目的规模不大,业务相对简单,这个时候我们也推荐您使用Gin。 当某个接口的性能遭到较大挑战的时候,这个还是可以考虑使用Gin重写接口。
Gin也是一个流行的golang Web框架,Github Strat量已经超过了50k。
Gin的官网:Gin Web Framework Gin Github地址:GitHub - gin-gonic/gin: Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.
二、Gin环境搭建
要安装 Gin 软件包,需要先安装 Go 并设置 Go 工作区。
1.下载并安装 gin:
$ go get -u github.com/gin-gonic/gin
2.将 gin 引入到代码中:
import "github.com/gin-gonic/gin"
3.(可选)如果使用诸如 http.StatusOK 之类的常量,则需要引入 net/http 包:
import "net/http"
4、新建Main.go配置路由
package mainimport ("github.com/gin-gonic/gin"
)func main() {// 创建一个默认的路由引擎r := gin.Default()// 配置路由r.GET("/", func(c *gin.Context) { c.JSON(200, gin.H{ // c.JSON:返回JSON格式的数据"message": "Hello world!",})})// 启动HTTP服务,默认在0.0.0.0:8080启动服务r.Run()
}
5、运行你的项目
$ go run main.go
6、要改变默认启动的端口
r.Run(":9000")
如果go get失败请参考: Golang Beego中没法下载第三方包解决办法
三、golang程序的热加载
所谓热加载就是当我们对代码进行修改时,程序能够自动重新加载并执行,这在我们开发中是非常便利的,可以快速进行代码测试,省去了每次手动重新编译
beego中我们可以使用官方给我们提供的bee工具来热加载项目,但是gin中并没有官方提供的热加载工具,这个时候我们要实现热加载就可以借助第三方的工具。
工具1(推荐):GitHub - gravityblast/fresh: Build and (re)start go web apps after saving/creating/deleting source files.
go get github.com/pilu/fresh
D:\gin_demo>fresh
工具2:GitHub - codegangsta/gin: Live reload utility for Go web servers
go get -u github.com/codegangsta/gin
D:\gin_demo>gin run main.go
四、Gin框架中的路由
4.1、路由概述
路由(Routing)是由一个 URI(或者叫路径)和一个特定的 HTTP 方法(GET、POST 等)组成的,涉及到应用如何响应客户端对某个网站节点的访问。
RESTful API是目前比较成熟的一套互联网应用程序的API设计理论,所以我们设计我们的路由的时候建议参考RESTful API指南。
在RESTful架构中,每个网址代表一种资源,不同的请求方式表示执行不同的操作: GET(SELECT) 从服务器取出资源(一项或多项) POST(CREATE) 在服务器新建一个资源 PUT(UPDATE) 在服务器更新资源(客户端提供改变后的完整资源) DELETE(DELETE) 从服务器删除资源
4.2、简单的路由配置
简单的路由配置(可以通过postman测试) 当用GET请求访问一个网址的时候,做什么事情:
r.GET("网址", func(c *gin.Context) { c.String(200, "Get")})
当用POST访问一个网址的时候,做什么事情:
r.POST("网址", func(c *gin.Context) { c.String(200, "POST")
})
当用PUT访问一个网址的时候,执行的操作:
r.PUT("网址", func(c *gin.Context) {c.String(200, "PUT")
})
当用DELETE访问一个网址的时候,执行的操作:
r.DELETE("网址", func(c *gin.Context) { c.String(200, "DELETE")
})
路由里面获取Get传值 域名/news?aid=20
r.GET("/news", func(c *gin.Context) {aid := c.Query("aid")c.String(200, "aid=%s", aid)
})
动态路由 域名/user/20
r.GET("/user/:uid", func(c *gin.Context) {uid := c.Param("uid")c.String(200, "userID=%s", uid)
})
4.3、 c.String() c.JSON() c.JSONP() c.XML() c.HTML()
返回一个字符串
r.GET("/news", func(c *gin.Context) {aid := c.Query("aid")c.String(200, "aid=%s", aid)
})
返回一个JSON数据
func main() {r := gin.Default()// gin.H 是map[string]interface{}的缩写r.GET("/someJSON", func(c *gin.Context) {// 方式一:自己拼接JSONc.JSON(http.StatusOK, gin.H{"message": "Hello world!"})})r.GET("/moreJSON", func(c *gin.Context) {// 方法二:使用结构体var msg struct {Name string `json:"user"`Message stringAge int}msg.Name = "IT营学院"msg.Message = "Hello world!"msg.Age = 18c.JSON(http.StatusOK, msg)})r.Run(":8080")
}
JSOPN
func main() {r := gin.Default()r.GET("/JSONP", func(c *gin.Context) {data := map[string]interface{}{"foo": "bar",}// /JSONP?callback=x// 将输出:x({\"foo\":\"bar\"})c.JSONP(http.StatusOK, data)})// 监听并在 0.0.0.0:8080 上启动服务r.Run(":8080")
}
返回XML数据
func main() {r := gin.Default()// gin.H 是map[string]interface{}的缩写r.GET("/someXML", func(c *gin.Context) {// 方式一:自己拼接JSONc.XML(http.StatusOK, gin.H{"message": "Hello world!"})})r.GET("/moreXML", func(c *gin.Context) {// 方法二:使用结构体type MessageRecord struct {Name stringMessage stringAge int}var msg MessageRecordmsg.Name = "IT营学院"msg.Message = "Hello world!"msg.Age = 18c.XML(http.StatusOK, msg)})r.Run(":8080")
}
渲染模板
router.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "default/index.html", map[string]interface{}{"title": "前台首页" })
})
五、Gin HTML模板渲染
5.1、全部模板放在一个目录里面的配置方法
1、我们首先在项目根目录新建templates文件夹,然后在文件夹中新建index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><h1>这是一个html模板</h1><h3>{{.title}}</h3>
</body>
</html>
2、Gin框架中使用c.HTML可以渲染模板,渲染模板前需要使用LoadHTMLGlob()或者LoadHTMLFiles()方法加载模板。
router.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "default/index.html", map[string]interface{}{"title": "前台首页" })
})
router.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "index.html", gin.H{"title": "Main website",})
})
package mainimport ("net/http""github.com/gin-gonic/gin"
)func main() {router := gin.Default()router.LoadHTMLGlob("templates/*")//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")router.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "index.html", gin.H{"title": "Main website",})})router.Run(":8080")
}
5.2、模板放在不同目录里面的配置方法
Gin框架中如果不同目录下面有同名模板的话我们需要使用下面方法加载模板 注意:定义模板的时候需要通过define定义名称 templates/admin/index.html
<!-- 相当于给模板定义一个名字 define end 成对出现-->
{{ define "admin/index.html" }}<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><h1>后台模板</h1><h3>{{.title}}</h3></body></html>
{{ end }}
templates/default/index.html
<!-- 相当于给模板定义一个名字 define end 成对出现-->
{{ define "default/index.html" }}<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><h1>前台模板</h1><h3>{{.title}}</h3></body></html>
{{end}}
业务逻辑
package mainimport ("net/http""github.com/gin-gonic/gin"
)func main() {router := gin.Default()router.LoadHTMLGlob("templates/**/*") router.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "default/index.html", gin.H{"title": "前台首页",})})router.GET("/admin", func(c *gin.Context) {c.HTML(http.StatusOK, "admin/index.html", gin.H{"title": "后台首页",})})router.Run(":8080")
}
注意:如果模板在多级目录里面的话需要这样配置r.LoadHTMLGlob(“templates///*”) /**表示目录
5.3、gin模板基本语法
1、{{.}} 输出数据 模板语法都包含在{{和}}中间,其中{{.}}中的点表示当前对象。 当我们传入一个结构体对象时,我们可以根据.来访问结构体的对应字段。例如: 业务逻辑
package mainimport ("net/http""github.com/gin-gonic/gin"
)type UserInfo struct {Name stringGender stringAge int
}func main() {router := gin.Default()router.LoadHTMLGlob("templates/**/*")user := UserInfo{Name: "张三",Gender: "男",Age: 18,}router.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "default/index.html", map[string]interface{}{"title": "前台首页","user": user,})})router.Run(":8080")
}
模板
<!-- 相当于给模板定义一个名字 define end 成对出现-->
{{ define "default/index.html" }}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><h1>前台模板</h1><h3>{{.title}}</h3><h4>{{.user.Name}}</h4><h4>{{.user.Age}}</h4>
</body>
</html>
{{end}}
2、注释
{{/* a comment */}}
注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止。 3、变量 我们还可以在模板中声明变量,用来保存传入模板的数据或其他语句生成的结果。具体语法如下:
<h4>{{$obj := .title}}</h4><h4>{{$obj}}</h4>
4、移除空格 有时候我们在使用模板语法的时候会不可避免的引入一下空格或者换行符,这样模板最终渲染出来的内容可能就和我们想的不一样,这个时候可以使用{{-语法去除模板内容左侧的所有空白符号, 使用-}}去除模板内容右侧的所有空白符号。 例如:
{{- .Name -}}
注意:-要紧挨{{和}},同时与模板值之间需要使用空格分隔。
5、比较函数 布尔函数会将任何类型的零值视为假,其余视为真。 下面是定义为函数的二元比较运算的集合: eq 如果arg1 == arg2则返回真 ne 如果arg1 != arg2则返回真 lt 如果arg1 < arg2则返回真 le 如果arg1 <= arg2则返回真 gt 如果arg1 > arg2则返回真 ge 如果arg1 >= arg2则返回真
6、条件判断 Go模板语法中的条件判断有以下几种:
{{if pipeline}} T1 {{end}}{{if pipeline}} T1 {{else}} T0 {{end}}{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}{{if gt .score 60}}
及格
{{else}}
不及格
{{end}}{{if gt .score 90}}
优秀
{{else if gt .score 60}}
及格
{{else}}
不及格
{{end}}
6、range Go的模板语法中使用range关键字进行遍历,有以下两种写法,其中pipeline的值必须是数组、切片、字典或者通道。
{{range $key,$value := .obj}} {{$value}}
{{end}}
如果pipeline的值其长度为0,不会有任何输出
{{range $key,$value := .obj}} {{$value}}
{{else}} pipeline的值其长度为0
{{end}}
如果pipeline的值其长度为0,则会执行T0。
router.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "default/index.html", map[string]interface{}{ "hobby": []string{"吃饭", "睡觉", "写代码"},})
})
{{range $key,$value := .hobby}}<p>{{$value}}</p>
{{end}}
7、With
user := UserInfo{Name: "张三",Gender: "男",Age: 18,}router.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "default/index.html", map[string]interface{}{"user": user,})})
以前要输出数据:
<h4>{{.user.Name}}</h4><h4>{{.user.Gender}}</h4><h4>{{.user.Age}}</h4>
现在要输出数据:
{{with .user}}<h4>姓名:{{.Name}}</h4><h4>性别:{{.user.Gender}}</h4><h4>年龄:{{.Age}}</h4>{{end}}
简单理解:相当于var .=.user
8、预定义函数 (了解)
执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用Funcs方法添加函数到模板里。 预定义的全局函数如下: and 函数返回它的第一个empty参数或者最后一个参数; 就是说"and x y"等价于"if x then y else x";所有参数都会执行; or 返回第一个非empty参数或者最后一个参数; 亦即"or x y"等价于"if x then x else y";所有参数都会执行; not 返回它的单个参数的布尔值的否定 len 返回它的参数的整数类型长度 index 执行结果为第一个参数以剩下的参数为索引/键指向的值; 如"index x 1 2 3"返回x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典。 print 即fmt.Sprint printf 即fmt.Sprintf println 即fmt.Sprintln html 返回与其参数的文本表示形式等效的转义HTML。 这个函数在html/template中不可用。 urlquery 以适合嵌入到网址查询中的形式返回其参数的文本表示的转义值。 这个函数在html/template中不可用。 js 返回与其参数的文本表示形式等效的转义JavaScript。 call 执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数; 如"call .X.Y 1 2"等价于go语言里的dot.X.Y(1, 2); 其中Y是函数类型的字段或者字典的值,或者其他类似情况; call的第一个参数的执行结果必须是函数类型的值(和预定义函数如print明显不同); 该函数类型值必须有1到2个返回值,如果有2个则后一个必须是error接口类型; 如果有2个返回值的方法返回的error非nil,模板执行会中断并返回给调用模板执行者该错误;
{{len .title}}{{index .hobby 2}}
9、自定义模板函数
router.SetFuncMap(template.FuncMap{"formatDate": formatAsDate,
})
package mainimport ("fmt""html/template""net/http""time""github.com/gin-gonic/gin"
)func formatAsDate(t time.Time) string {year, month, day := t.Date()return fmt.Sprintf("%d/%02d/%02d", year, month, day)
}
func main() {router := gin.Default()//注册全局模板函数 注意顺序,注册模板函数需要在加载模板上面router.SetFuncMap(template.FuncMap{"formatDate": formatAsDate,})//加载模板router.LoadHTMLGlob("templates/**/*") router.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "default/index.html", map[string]interface{}{"title": "前台首页", "now": time.Now(),})})router.Run(":8080")
}
模板里面的用法
{{.now | formatDate}}
或者
{{formatDate .now }}
5.4、嵌套template 1、新建templates/deafult/page_header.html {{ define “default/page_header.html” }} <h1>这是一个头部</h1> {{end}}
2、外部引入 注意: 1、引入的名字为page_header.html中定义的名字 2、引入的时候注意最后的点(.) {{template “default/page_header.html” .}}
<!-- 相当于给模板定义一个名字 define end 成对出现–>
{{ define “default/index.html” }} <!DOCTYPE html> <html lang=“en”> <head> <meta charset=“UTF-8”> <meta http-equiv=“X-UA-Compatible” content=“IE=edge”> <meta name=“viewport” content=“width=device-width, initial-scale=1.0”> <title>Document</title> </head> <body> {{template “default/page_header.html” .}}
</body> </html> {{end}}
六、静态文件服务 当我们渲染的HTML文件中引用了静态文件时,我们需要配置静态web服务 r.Static("/static", “./static”) 前面的/static表示路由 后面的./static表示路径 func main() { r := gin.Default() r.Static("/static", “./static”) r.LoadHTMLGlob(“templates/**/*”) // … r.Run(":8080") } <link rel=“stylesheet” href="/static/css/base.css" />
七、路由详解 路由(Routing)是由一个 URI(或者叫路径)和一个特定的 HTTP 方法(GET、POST 等)组成的,涉及到应用如何响应客户端对某个网站节点的访问。 前面章节我们给大家介绍了路由基础以及路由配置,这里我们详细给大家讲讲路由传值、路由返回值
7.1、GET POST 以及获取Get Post传值 7.1.1、Get请求传值 GET /user?uid=20&page=1 router.GET("/user", func(c *gin.Context) { uid := c.Query(“uid”) page := c.DefaultQuery(“page”, “0”) c.String(200, “uid=%v page=%v”, uid, page) })
7.1.2、动态路由传值 域名/user/20 r.GET("/user/:uid", func(c *gin.Context) { uid := c.Param(“uid”) c.String(200, “userID=%s”, uid) })
7.1.3、Post请求传值 获取form表单数据 定义一个add_user.html的页面 {{ define “default/add_user.html” }} <!DOCTYPE html> <html lang=“en”> <head> <meta charset=“UTF-8”> <meta http-equiv=“X-UA-Compatible” content=“IE=edge”> <meta name=“viewport” content=“width=device-width, initial-scale=1.0”> <title>Document</title> </head> <body> <form action="/doAddUser" method=“post”>
用户名:<input type="text" name="username" />密码: <input type="password" name="password" /><input type="submit" value="提交">
</form>
</body> </html> {{end}}
通过c.PostForm 接收表单传过来的数据
router.GET("/addUser", func(c *gin.Context) { c.HTML(200, “default/add_user.html”, gin.H{}) })
router.POST("/doAddUser", func(c *gin.Context) { username := c.PostForm(“username”) password := c.PostForm(“password”) age := c.DefaultPostForm(“age”, “20”)
c.JSON(200, gin.H{"usernmae": username,"password": password,"age": age,})
}) 7.1.4、获取GET POST传递的数据绑定到结构体 为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryString、form表单、JSON、XML等参数到结构体中。 下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取JSON、form表单和QueryString类型的数据,并把值绑定到指定的结构体对象。 //注意首字母大写 type Userinfo struct { Username string form:"username" json:"user"
Password string form:"password" json:"password"
}
Get传值绑定到结构体 /?username=zhangsan&password=123456 router.GET("/", func(c *gin.Context) { var userinfo Userinfo if err := c.ShouldBind(&userinfo); err == nil { c.JSON(http.StatusOK, userinfo) } else { c.JSON(http.StatusBadRequest, gin.H{“error”: err.Error()}) } })
返回数据 {“user”:“zhangsan”,“password”:“123456”}
Post传值绑定到结构体 router.POST("/doLogin", func(c *gin.Context) { var userinfo Userinfo if err := c.ShouldBind(&userinfo); err == nil { c.JSON(http.StatusOK, userinfo) } else { c.JSON(http.StatusBadRequest, gin.H{“error”: err.Error()}) } })
返回数据 {“user”:“zhangsan”,“password”:“123456”}
7.1.5、获取Post Xml数据 在 API 的开发中,我们经常会用到 JSON 或 XML 来作为数据交互的格式,这个时候我们可以在gin中使用c.GetRawData()获取数据。 <?xml version=“1.0” encoding=“UTF-8”?> <article> <content type=“string”>我是张三</content> <title type=“string”>张三</title> </article>
type Article struct { Title string xml:"title"
Content string xml:"content"
} router.POST("/xml", func(c *gin.Context) { b, _ := c.GetRawData() // 从c.Request.Body读取请求数据
article := &Article{}if err := xml.Unmarshal(b, &article); err == nil {c.JSON(http.StatusOK, article)} else {c.JSON(http.StatusBadRequest, err.Error())}
})
7.2、简单的路由组 func main() { router := gin.Default()
// 简单的路由组: v1
v1 := router.Group("/v1")
{v1.POST("/login", loginEndpoint)v1.POST("/submit", submitEndpoint)v1.POST("/read", readEndpoint)
}// 简单的路由组: v2
v2 := router.Group("/v2")
{v2.POST("/login", loginEndpoint)v2.POST("/submit", submitEndpoint)v2.POST("/read", readEndpoint)
}router.Run(":8080")
} 7.3、Gin路由文件 分组 8.2.1、新建routes文件夹,routes文件下面新建adminRoutes.go、apiRoutes.go、defaultRoutes.go 1、新建adminRoutes.go package routes
import ( “net/http”
"github.com/gin-gonic/gin"
)
func AdminRoutesInit(router *gin.Engine) { adminRouter := router.Group("/admin") { adminRouter.GET("/user", func(c *gin.Context) { c.String(http.StatusOK, “用户”) }) adminRouter.GET("/news", func(c *gin.Context) { c.String(http.StatusOK, “news”) }) } }
2、新建apiRoutes.go package routes
import ( “net/http”
"github.com/gin-gonic/gin"
)
func ApiRoutesInit(router *gin.Engine) { apiRoute := router.Group("/api") { apiRoute.GET("/user", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ “username”: “张三”, “age”: 20, }) }) apiRoute.GET("/news", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ “title”: “这是新闻”, }) }) } }
3、新建defaultRoutes.go package routes
import ( “github.com/gin-gonic/gin” )
func DefaultRoutesInit(router *gin.Engine) { defaultRoute := router.Group("/") { defaultRoute.GET("/", func(c *gin.Context) { c.String(200, “首页”) })
}
} 8.2.2 、配置main.go package main
import ( “gin_demo/routes”
"github.com/gin-gonic/gin"
)
//注意首字母大写 type Userinfo struct { Username string form:"username" json:"user"
Password string form:"password" json:"password"
}
func main() { r := gin.Default() routes.AdminRoutesInit® routes.ApiRoutesInit® routes.DefaultRoutesInit® r.Run(":8080") } 访问 /api/user /admin/user测试
八、Gin中自定义控制器
9.1、控制器分组 当我们的项目比较大的时候有必要对我们的控制器进行分组 新建controller/admin/NewsController.go package admin
import ( “net/http”
"github.com/gin-gonic/gin"
)
type NewsController struct { }
func (c NewsController) Index(ctx *gin.Context) { ctx.String(http.StatusOK, “新闻首页”) }
新建controller/admin/UserController.go package admin
import ( “net/http”
"github.com/gin-gonic/gin"
)
type UserController struct { }
func (c UserController) Index(ctx *gin.Context) { ctx.String(http.StatusOK, “这是用户首页”) }
func (c UserController) Add(ctx *gin.Context) { ctx.String(http.StatusOK, “增加用户”) }
…
配置对应的路由 --adminRoutes.go
其他路由的配置方法类似 package routes
import ( “gin_demo/controller/admin” “net/http”
"github.com/gin-gonic/gin"
)
func AdminRoutesInit(router *gin.Engine) { adminRouter := router.Group("/admin") { adminRouter.GET("/user", admin.UserController{}.Index) adminRouter.GET("/user/add", admin.UserController{}.Add) adminRouter.GET("/news", admin.NewsController{}.Add) } }
9.2、控制器的继承 1、新建controller/admin/BaseController.go package admin
import ( “net/http”
"github.com/gin-gonic/gin"
)
type BaseController struct { }
func (c BaseController) Success(ctx *gin.Context) { ctx.String(http.StatusOK, “成功”) }
func (c BaseController) Error(ctx *gin.Context) { ctx.String(http.StatusOK, “失败”) }
2、NewsController 继承BaseController 继承后就可以调用控制器里面的公共方法了 package admin
import ( “github.com/gin-gonic/gin” )
type NewsController struct { BaseController }
func (c NewsController) Index(ctx *gin.Context) { c.Success(ctx) }
九、Gin中间件 Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。 通俗的讲:中间件就是匹配路由前和匹配路由完成后执行的一系列操作 8.1、路由中间件 8.1.1、初识中间件 Gin中的中间件必须是一个gin.HandlerFunc类型,配置路由的时候可以传递多个func回调函数,最后一个func回调函数前面触发的方法都可以称为中间件。
package main
import ( “fmt”
"github.com/gin-gonic/gin"
)
func initMiddleware(ctx *gin.Context) { fmt.Println(“我是一个中间件”) } func main() { r := gin.Default() r.GET("/", initMiddleware, func(ctx *gin.Context) { ctx.String(200, “首页–中间件演示”) }) r.GET("/news", initMiddleware, func(ctx *gin.Context) { ctx.String(200, “新闻页面–中间件演示”) })
r.Run(":8080")
}
8.1.2、ctx.Next()调用该请求的剩余处理程序 中间件里面加上ctx.Next()可以让我们在路由匹配完成后执行一些操作。
比如我们统计一个请求的执行时间。
package main
import ( “fmt” “time”
"github.com/gin-gonic/gin"
)
func initMiddleware(ctx *gin.Context) { fmt.Println(“1-执行中中间件”) start := time.Now().UnixNano() // 调用该请求的剩余处理程序 ctx.Next() fmt.Println(“3-程序执行完成 计算时间”) // 计算耗时 Go语言中的Since()函数保留时间值,并用于评估与实际时间的差异 end := time.Now().UnixNano() fmt.Println(end - start)
} func main() { r := gin.Default() r.GET("/", initMiddleware, func(ctx *gin.Context) { fmt.Println(“2-执行首页返回数据”) ctx.String(200, “首页–中间件演示”) }) r.GET("/news", initMiddleware, func(ctx *gin.Context) { ctx.String(200, “新闻页面–中间件演示”) })
r.Run(":8080")
}
8.1.3、一个路由配置多个中间件的执行顺序
func initMiddlewareOne(ctx *gin.Context) { fmt.Println(“initMiddlewareOne–1-执行中中间件”)
// 调用该请求的剩余处理程序
ctx.Next()fmt.Println("initMiddlewareOne--2-执行中中间件")
} func initMiddlewareTwo(ctx *gin.Context) { fmt.Println(“initMiddlewareTwo–1-执行中中间件”)
// 调用该请求的剩余处理程序
ctx.Next()fmt.Println("initMiddlewareTwo--2-执行中中间件")
} func main() { r := gin.Default() r.GET("/", initMiddlewareOne, initMiddlewareTwo, func(ctx *gin.Context) { fmt.Println(“执行路由里面的程序”) ctx.String(200, “首页–中间件演示”) })
r.Run(":8080")
}
控制台内容:
initMiddlewareOne–1-执行中中间件 initMiddlewareTwo–1-执行中中间件 执行路由里面的程序 initMiddlewareTwo–2-执行中中间件 initMiddlewareOne–2-执行中中间件
8.1.4、 c.Abort()–(了解) Abort是终止的意思, c.Abort() 表示终止调用该请求的剩余处理程序 package main
import ( “fmt”
"github.com/gin-gonic/gin"
)
func initMiddlewareOne(ctx *gin.Context) { fmt.Println(“initMiddlewareOne–1-执行中中间件”)
// 调用该请求的剩余处理程序
ctx.Next()fmt.Println("initMiddlewareOne--2-执行中中间件")
} func initMiddlewareTwo(ctx *gin.Context) { fmt.Println(“initMiddlewareTwo–1-执行中中间件”)
// 终止调用该请求的剩余处理程序
ctx.Abort()fmt.Println("initMiddlewareTwo--2-执行中中间件")
} func main() { r := gin.Default() r.GET("/", initMiddlewareOne, initMiddlewareTwo, func(ctx *gin.Context) { fmt.Println(“执行路由里面的程序”) ctx.String(200, “首页–中间件演示”) }) r.Run(":8080") }
initMiddlewareOne–1-执行中间件 initMiddlewareTwo–1-执行中间件 initMiddlewareTwo–2-执行中间件 initMiddlewareOne–2-执行中间件
8.2、全局中间件 package main
import ( “fmt” “github.com/gin-gonic/gin” )
func initMiddleware(ctx *gin.Context) { fmt.Println(“全局中间件 通过 r.Use配置”) // 调用该请求的剩余处理程序 ctx.Next() }
func main() { r := gin.Default() r.Use(initMiddleware) r.GET("/", func(ctx *gin.Context) { ctx.String(200, “首页–中间件演示”) }) r.GET("/news", func(ctx *gin.Context) { ctx.String(200, “新闻页面–中间件演示”) }) r.Run(":8080") }
8.3、在路由分组中配置中间件 1、为路由组注册中间件有以下两种写法。 写法1: shopGroup := r.Group("/shop", StatCost()) { shopGroup.GET("/index", func(c *gin.Context) {…}) … } 写法2: shopGroup := r.Group("/shop") shopGroup.Use(StatCost()) { shopGroup.GET("/index", func(c *gin.Context) {…}) … }
2、分组路由AdminRoutes.go中配置中间件
package routes
import ( “fmt” “gin_demo/controller/admin” “net/http”
"github.com/gin-gonic/gin"
)
func initMiddleware(ctx *gin.Context) { fmt.Println(“路由分组中间件”)
// 调用该请求的剩余处理程序
ctx.Next()
}
func AdminRoutesInit(router *gin.Engine) { adminRouter := router.Group("/admin", initMiddleware) { adminRouter.GET("/user", admin.UserController{}.Index) adminRouter.GET("/user/add", admin.UserController{}.Add) adminRouter.GET("/news", func(c *gin.Context) { c.String(http.StatusOK, “news”) }) } }
8.4、中间件和对应控制器之间共享数据 设置值 ctx.Set(“username”, “张三”) 获取值 username, _ := ctx.Get(“username”)
中间件设置值 func InitAdminMiddleware(ctx *gin.Context) {
fmt.Println(“路由分组中间件”)
// 可以通过ctx.Set在请求上下文中设置值,后续的处理函数能够取到该值
ctx.Set(“username”, “张三”)
// 调用该请求的剩余处理程序
ctx.Next()
} 控制器获取值 func (c UserController) Index(ctx *gin.Context) { username, _ := ctx.Get(“username”) fmt.Println(username) ctx.String(http.StatusOK, “这是用户首页 111”) }
8.5、中间件注意事项 gin默认中间件 gin.Default()默认使用了Logger和Recovery中间件,其中: •Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release。 •Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。 如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。 gin中间件中使用goroutine 当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())
十、Gin中自定义Model
10.1、关于Model 如果我们的应用非常简单的话,我们可以在Controller 里面处理常见的业务逻辑。但是如果我们有一个功能想在多个控制器、或者多个模板里面复用的话,那么我们就可以把公共的功能单独抽取出来作为一个模块(Model)。 Model 是逐步抽象的过程,一般我们会在 Model 里面封装一些公共的方法让不同Controller 使用,也可以在Model中实现和数据库打交道 10.2、Model里面封装公共的方法 1、新建models/ tools.go package models
import ( “crypto/md5” “fmt” “time”
"github.com/astaxie/beego"
)
//时间戳间戳转换成日期 func UnixToDate(timestamp int) string {
t := time.Unix(int64(timestamp), 0)return t.Format("2006-01-02 15:04:05")
}
//日期转换成时间戳 2020-05-02 15:04:05 func DateToUnix(str string) int64 { template := “2006-01-02 15:04:05” t, err := time.ParseInLocation(template, str, time.Local) if err != nil { beego.Info(err) return 0 } return t.Unix() }
func GetUnix() int64 { return time.Now().Unix() } func GetDate() string { template := “2006-01-02 15:04:05” return time.Now().Format(template) } func GetDay() string { template := “20060102” return time.Now().Format(template) }
func Md5(str string) string { data := []byte(str) return fmt.Sprintf("%x\n", md5.Sum(data)) }
func Hello(in string) (out string) { out = in + “world” return } 10.3、控制器中调用Model package controllers
import ( “gin_demo/models” )
day := models.GetDay() 10.4、调用Model注册全局模板函数
models/tools.go //时间戳间戳转换成日期 func UnixToDate(timestamp int64) string {
t := time.Unix(timestamp, 0)return t.Format("2006-01-02 15:04:05")
} main.go //注册全局模板函数 注意顺序,注册模板函数需要在加载模板上面 r := gin.Default() r.SetFuncMap(template.FuncMap{ “unixToDate”: models.UnixToDate, })
控制器 func (c UserController) Add(ctx *gin.Context) { ctx.HTML(http.StatusOK, “admin/user/add.html”, gin.H{ “now”: models.GetUnix(), }) }
模板 <h2>{{.now | unixToDate}}</h2>
10.5、Golang Md5加密 打开golang包对应的网站:https://pkg.go.dev/,搜索md5 方法一: data := []byte(“123456”) has := md5.Sum(data) md5str := fmt.Sprintf("%x", has) fmt.Println(md5str) 方法二: h := md5.New() io.WriteString(h, “123456”) fmt.Printf("%x\n", h.Sum(nil))
十一、Gin文件上传
注意:需要在上传文件的form表单上面需要加入enctype=“multipart/form-data” 11.1、单文件上传 单文件 | Gin Web Framework
官方示例: func main() { router := gin.Default() // 为 multipart forms 设置较低的内存限制 (默认是 32 MiB) router.MaxMultipartMemory = 8 << 20 // 8 MiB router.POST("/upload", func(c *gin.Context) { // 单文件 file, _ := c.FormFile(“file”) log.Println(file.Filename)
// 上传文件至指定目录c.SaveUploadedFile(file, dst)c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
})
router.Run(":8080")
}
项目中实现文件上传: 1、定义模板 需要在上传文件的form表单上面需要加入enctype=“multipart/form-data” <!-- 相当于给模板定义一个名字 define end 成对出现–> {{ define “admin/user/add.html” }} <!DOCTYPE html> <html lang=“en”> <head> <meta charset=“UTF-8”> <meta http-equiv=“X-UA-Compatible” content=“IE=edge”> <meta name=“viewport” content=“width=device-width, initial-scale=1.0”> <title>Document</title> </head> <body> <form action="/admin/user/doAdd" method=“post” enctype=“multipart/form-data”> 用户名: <input type=“text” name=“username” placeholder=“用户名”> <br> <br> 头 像:<input type=“file” name=“face”><br> <br> <input type=“submit” value=“提交”> </form> </body> </html> {{ end }} 2、定义业务逻辑
func (c UserController) DoAdd(ctx *gin.Context) { username := ctx.PostForm(“username”) file, err := ctx.FormFile(“face”)
if err != nil {ctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error(),})return
}
// 上传文件到指定的目录
dst := path.Join("./static/upload", file.Filename)
fmt.Println(dst)
ctx.SaveUploadedFile(file, dst)
ctx.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("'%s' uploaded!", file.Filename),"username": username,
})
}
11.2、多文件上传–不同名字的多个文件
1、定义模板 需要在上传文件的form表单上面需要加入enctype=“multipart/form-data” <!-- 相当于给模板定义一个名字 define end 成对出现–> {{ define “admin/user/add.html” }} <!DOCTYPE html> <html lang=“en”> <head> <meta charset=“UTF-8”> <meta http-equiv=“X-UA-Compatible” content=“IE=edge”> <meta name=“viewport” content=“width=device-width, initial-scale=1.0”> <title>Document</title> </head> <body> <form action="/admin/user/doAdd" method=“post” enctype=“multipart/form-data”> 用户名: <input type=“text” name=“username” placeholder=“用户名”> <br> <br> 头 像1:<input type=“file” name=“face1”><br> <br> 头 像2:<input type=“file” name=“face2”><br> <br> <input type=“submit” value=“提交”> </form> </body> </html> {{ end }} 2、定义业务逻辑
func (c UserController) DoAdd(ctx *gin.Context) { username := ctx.PostForm(“username”) face1, err1 := ctx.FormFile(“face1”) face2, err2 := ctx.FormFile(“face2”) // 上传文件到指定的目录 if err1 == nil { dst1 := path.Join("./static/upload", face1.Filename) ctx.SaveUploadedFile(face1, dst1) } if err2 == nil { dst2 := path.Join("./static/upload", face2.Filename) ctx.SaveUploadedFile(face2, dst2) }
ctx.JSON(http.StatusOK, gin.H{"message": "文件上传成功","username": username,
})
// ctx.String(200, username)
}
11.3、多文件上传–相同名字的多个文件
参考:多文件 | Gin Web Framework
1、定义模板 需要在上传文件的form表单上面需要加入enctype=“multipart/form-data”
<!-- 相当于给模板定义一个名字 define end 成对出现–> {{ define “admin/user/add.html” }} <!DOCTYPE html> <html lang=“en”> <head> <meta charset=“UTF-8”> <meta http-equiv=“X-UA-Compatible” content=“IE=edge”> <meta name=“viewport” content=“width=device-width, initial-scale=1.0”> <title>Document</title> </head> <body> <form action="/admin/user/doAdd" method=“post” enctype=“multipart/form-data”> 用户名: <input type=“text” name=“username” placeholder=“用户名”> <br> <br> 头 像1:<input type=“file” name=“face[]”><br> <br> 头 像2:<input type=“file” name=“face[]”><br> <br> <input type=“submit” value=“提交”> </form> </body> </html> {{ end }}
2、定义业务逻辑 func (c UserController) DoAdd(ctx *gin.Context) { username := ctx.PostForm(“username”)
// Multipart form
form, _ := ctx.MultipartForm()
files := form.File["face[]"]// var dst;
for _, file := range files {// 上传文件至指定目录dst := path.Join("./static/upload", file.Filename)ctx.SaveUploadedFile(file, dst)
}ctx.JSON(http.StatusOK, gin.H{"message": "文件上传成功","username": username,
})
}
10.4、文件上传 按照日期存储 1、定义模板 需要在上传文件的form表单上面需要加入enctype=“multipart/form-data” <!-- 相当于给模板定义一个名字 define end 成对出现–> {{ define “admin/user/add.html” }} <!DOCTYPE html> <html lang=“en”> <head> <meta charset=“UTF-8”> <meta http-equiv=“X-UA-Compatible” content=“IE=edge”> <meta name=“viewport” content=“width=device-width, initial-scale=1.0”> <title>Document</title> </head> <body> <form action="/admin/user/doAdd" method=“post” enctype=“multipart/form-data”> 用户名: <input type=“text” name=“username” placeholder=“用户名”> <br> <br> 头 像: <input type=“file” name=“face”><br> <br>
<input type="submit" value="提交">
</form>
</body> </html> {{ end }}
2、定义业务逻辑 func (c UserController) DoAdd(ctx *gin.Context) { username := ctx.PostForm(“username”) //1、获取上传的文件 file, err1 := ctx.FormFile(“face”)
if err1 == nil {//2、获取后缀名 判断类型是否正确 .jpg .png .gif .jpegextName := path.Ext(file.Filename)allowExtMap := map[string]bool{".jpg": true,".png": true,".gif": true,".jpeg": true,}if _, ok := allowExtMap[extName]; !ok {ctx.String(200, "文件类型不合法")return}//3、创建图片保存目录 static/upload/20200623day := models.GetDay()dir := "./static/upload/" + dayif err := os.MkdirAll(dir, 0666); err != nil {log.Error(err)}//4、生成文件名称 144325235235.pngfileUnixName := strconv.FormatInt(models.GetUnix(), 10)//static/upload/20200623/144325235235.pngsaveDir := path.Join(dir, fileUnixName+extName)ctx.SaveUploadedFile(file, saveDir)
}
ctx.JSON(http.StatusOK, gin.H{"message": "文件上传成功","username": username,
})
// ctx.String(200, username)
} 3、models/tools.go package models
import ( “crypto/md5” “fmt” “time”
"github.com/astaxie/beego"
)
//时间戳间戳转换成日期 func UnixToDate(timestamp int) string {
t := time.Unix(int64(timestamp), 0)return t.Format("2006-01-02 15:04:05")
}
//日期转换成时间戳 2020-05-02 15:04:05 func DateToUnix(str string) int64 { template := “2006-01-02 15:04:05” t, err := time.ParseInLocation(template, str, time.Local) if err != nil { beego.Info(err) return 0 } return t.Unix() }
func GetUnix() int64 { return time.Now().Unix() } func GetDate() string { template := “2006-01-02 15:04:05” return time.Now().Format(template) } func GetDay() string { template := “20060102” return time.Now().Format(template) }
func Md5(str string) string { data := []byte(str) return fmt.Sprintf("%x\n", md5.Sum(data)) }
func Hello(in string) (out string) { out = in + “world” return }
十二、Gin中的Cookie
12.1、Cookie介绍 ● HTTP是无状态协议。简单地说,当你浏览了一个页面,然后转到同一个网站的另一个页面,服务器无法认识到这是同一个浏览器在访问同一个网站。每一次的访问,都是没有任何关系的。如果我们要实现多个页面之间共享数据的话我们就可以使用Cookie或者Session实现 ● cookie 是存储于访问者计算机的浏览器中。可以让我们用同一个浏览器访问同一个域名的时候共享数据。 12.2、Cookie能实现的功能 1、保持用户登录状态 2、保存用户浏览的历史记录 3、猜你喜欢,智能推荐 4、电商网站的加入购物车
12.3、设置和获取 Cookie 设置和获取 Cookie | Gin Web Framework
设置Cookie c.SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) 第一个参数 key 第二个参数 value 第三个参数 过期时间.如果只想设置Cookie的保存路径而不想设置存活时间,可以在第三个参数中传递nil 第四个参数 cookie的路径 第五个参数 cookie的路径Domain作用域 本地调试配置成 localhost , 正式上线配置成域名 第六个参数是secure ,当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效 第七个参数 httpOnly,是微软对COOKIE做的扩展。如果在COOKIE中设置了“httpOnly”属性,则通过程序(JS脚本、applet等)将无法读取到COOKIE信息,防止XSS攻击产生
获取Cookie cookie, err := c.Cookie(“name”)
完整demo package main
import ( “gin_demo/models” “html/template”
"github.com/gin-gonic/gin"
)
func main() { r := gin.Default() r.SetFuncMap(template.FuncMap{ “unixToDate”: models.UnixToDate, })
r.GET("/", func(c *gin.Context) { c.SetCookie("usrename", "张三", 3600, "/", "localhost", false, true)c.String(200, "首页")
})r.GET("/user", func(c *gin.Context) {username, _ := c.Cookie("usrename")c.String(200, "用户-"+username)
})r.Run(":8080")
} 12.4 、多个二级域名共享cookie 1、分别把a.itying.com 和 b.itying.com解析到我们的服务器 2、我们想的是用户在a.itying.com中设置Cookie信息后在b.itying.com中获取刚才设置的cookie,也就是实现多个二级域名共享cookie 这时候的话我们就可以这样设置cookie c.SetCookie(“usrename”, “张三”, 3600, “/”, “.itying.com”, false, true)
十三、Gin中的Session
13.1、Session简单介绍 session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而session保存在服务器上。 13.2、Session的工作流程 当客户端浏览器第一次访问服务器并发送请求时,服务器端会创建一个session对象,生成一个类似于key,value的键值对, 然后将key(cookie)返回到浏览器(客户)端,浏览器下次再访问时,携带key(cookie),找到对应的session(value)。 13.3、Gin中使用 Session Gin官方没有给我们提供Session相关的文档,这个时候我们可以使用第三方的Session中间件来实现 GitHub - gin-contrib/sessions: Gin middleware for session management
gin-contrib/sessions中间件支持的存储引擎: •cookie •memstore •redis •memcached •mongodb 13.4、基于Cookie存储Session
1、安装session包 go get github.com/gin-contrib/sessions 2、基本的session用法 package main
import ( “github.com/gin-contrib/sessions” “github.com/gin-contrib/sessions/cookie” “github.com/gin-gonic/gin” )
func main() { r := gin.Default() // 创建基于cookie的存储引擎,secret11111 参数是用于加密的密钥 store := cookie.NewStore([]byte(“secret11111”)) // 设置session中间件,参数mysession,指的是session的名字,也是cookie的名字 // store是前面创建的存储引擎,我们可以替换成其他存储引擎 r.Use(sessions.Sessions(“mysession”, store))
r.GET("/", func(c *gin.Context) {//初始化session对象session := sessions.Default(c)//设置过期时间session.Options(sessions.Options{MaxAge: 3600 * 6, // 6hrs})//设置Sessionsession.Set("username", "张三")session.Save()c.JSON(200, gin.H{"msg": session.Get("username")})
})
r.GET("/user", func(c *gin.Context) {// 初始化session对象session := sessions.Default(c)// 通过session.Get读取session值username := session.Get("username")c.JSON(200, gin.H{"username": username})
})r.Run(":8000")
} 13.5、基于Redis存储Session 如果我们想将session数据保存到redis中,只要将session的存储引擎改成redis即可。 使用redis作为存储引擎的例子: 首先安装redis存储引擎的包 go get github.com/gin-contrib/sessions/redis 例子: package main
import ( “github.com/gin-contrib/sessions” “github.com/gin-contrib/sessions/redis” “github.com/gin-gonic/gin” )
func main() { r := gin.Default() // 初始化基于redis的存储引擎 // 参数说明: // 第1个参数 - redis最大的空闲连接数 // 第2个参数 - 数通信协议tcp或者udp // 第3个参数 - redis地址, 格式,host:port // 第4个参数 - redis密码 // 第5个参数 - session加密密钥 store, _ := redis.NewStore(10, “tcp”, “localhost:6379”, “”, []byte(“secret”)) r.Use(sessions.Sessions(“mysession”, store))
r.GET("/", func(c *gin.Context) {session := sessions.Default(c)session.Set("username", "李四")session.Save()c.JSON(200, gin.H{"username": session.Get("username")})
})r.GET("/user", func(c *gin.Context) {// 初始化session对象session := sessions.Default(c)// 通过session.Get读取session值username := session.Get("username")c.JSON(200, gin.H{"username": username})
})
r.Run(":8000")
}
相关文章:
Gin框架入门实战系列教程之Gin环境搭建 Gin程序的热加载 Gin路由 GET POST PUT DELETE
Gin框架入门实战系列教程之Gin环境搭建 Gin程序的热加载 Gin路由 GET POST PUT DELETE 主讲教师:(大地) 在线文档见网盘下载: 百度网盘 请输入提取码 提取码:abcd 一、Gin介绍 Gin 是一个 Go (Golang) 编写的轻量级…...

浏览器自动播放音视频-前端实现方案
目录 前言 浏览器自动播放策略 策略详情: 实现方案 方案1: 互动后播放 方案2: 互动后出声 总结 前言 在开发中可能有遇到这样的需求,当用户打开页面后,需要自动播放视频或音频,按理说那就打开页面…...
HttpUtils工具类
作为Java开发程序员,需要我们经常写一些工具类来简化开发过程,我们自己肯定写过或者用过HttpUtils用来发送http请求,但是每次手写太繁琐了,于是就按照标准写了一个Http工具类,现在分享出来。 1.HTTP请求简介 HTTP(Hy…...

AI:59-基于深度学习的行人重识别
🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…...

TCP编程及基础知识
一、端口号 为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区分TCP端口号与UDP端口号独立端口用两个字节来表示 2byte(65535个) 众所周知端口:1~1023(1~255之间为众所周知端口ÿ…...

二百零一、Flink——Flink配置状态后端运行后报错:Can not create a Path from an empty string
一、目的 在尚硅谷学习用Flink配置状态后端的项目中,运行报错Exception in thread "main" java.lang.IllegalArgumentException: Can not create a Path from an empty string 二、Flink的状态后端(state backend)类型 (一)Memo…...

Python 爬虫基础
Python 爬虫基础 1.1 理论 在浏览器通过网页拼接【/robots.txt】来了解可爬取的网页路径范围 例如访问: https://www.csdn.net/robots.txt User-agent: * Disallow: /scripts Disallow: /public Disallow: /css/ Disallow: /images/ Disallow: /content/ Disallo…...

亚马逊云科技大语言模型的创新科技
陈老老老板🤴 🧙♂️本文专栏:生活(主要讲一下自己生活相关的内容)生活就像海洋,只有意志坚强的人,才能到达彼岸。 🧙♂️本文简述:亚马逊云科技大语言模型的创新科技 🧙♂️上…...

Qt 各种数据类型
目录 1. 基础类型 2. log 输出 3. 字符串类型 3.2 QByteArray 构造函数 数据操作 子字符串查找和判断 遍历 查看字节数 类型转换 3.3 QString 4. QVariant 4.1 标准类型 4.2 自定义类型 5. 位置和尺寸 5.1 QPoint 5.2 QLine 5.3 QSize 5.4 QRect 6. 日期和…...

电动车展示预约小程序的作用如何
电动车可以说是现在出行常见的方法,覆盖年龄广几乎是每家必备,也有不小大小品牌和经销商,市场需求较高,但在实际经营中,对经销商来时也面临着一些痛点: 1、品牌传播产品展示难 不同品牌竞争很大ÿ…...

「随笔」浅谈2023年云计算的发展趋势
在2023年,云计算的发展趋势将受到政治、经济、社会和科技四个维度的影响。以下是对这些维度的具体分析: 1.1 政治维度: 全球政策推动: 随着全球各国政策对云计算的重视程度不断提高,云计算服务将获得更广泛的市场准入…...

高性能三防工业平板电脑 防摔防爆电容屏工控平板
HT1000是一款高性能工业三防平板,10.1英寸超清大屏,厚度仅14.9mm,超薄机身,可轻松插入袋中,方便携带,搭载8核2.0GHz高性能CPU,行业领先的Android 11.0,设备性能大幅提升,…...
mac flutter pb解析报错:protoc-gen-dart: program not found or is not executable
在mac对pb文件转dart文件的时候报错:protoc-gen-dart: program not found or is not executable 原因是没有安装protoc-gen-dart或者protoc-gen-dart没有设置到环境变量中 解决办法: 1、安装protoc-gen-dart flutter pub global activate protoc_plu…...

PostgreSQL 连接是否要通过SSL,为什么使用SSL 连接后,业务部门会投诉我?
开头还是介绍一下群,如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,Redis, Oceanbase, Sql Server等有问题,有需求都可以加群群内有各大数据库行业大咖,CTO,可以解决你的问题。加群请联系 liuaustin3 ,(…...

Linux驱动开发——USB设备驱动
目录 一、 USB 协议简介 二、 Linux USB 驱动 三、 USB 设备驱动实例 一、 USB 协议简介 USB(Universal Serial Bus,通用串行总线)正如它的名字一样,是用来连接PC外设的一种通用串行总线,即插即用和易扩展是它最大的特点。所谓即插即用&am…...

微服务使用指南
微服务使用指南 1.初识微服务 微服务可以认为是一种分布式架构的解决方案,提供服务的独立性和完整性,做到服务的高内聚、低耦合。 目前服务架构主要包含:单体架构和分布式架构。 1.1 单体架构 单体架构:把所有业务功能模块都…...

MYSQL运维篇(已完结)
一、日志 1. 错误日志 2. 二进制日志 😎 介绍 😎 日志格式 😎 日志查看 😎 日志删除 3. 查询日志 4. 慢查询日志 二、主从复制 1. 概述 2. 原理 3. 搭建 4. 总结 三、分库分表 1. 介绍 🍤 问题分析 🍤…...

MapReduce性能优化之小文件问题和数据倾斜问题解决方案
文章目录 MapReduce性能优化小文件问题生成SequenceFileMapFile案例 :使用SequenceFile实现小文件的存储和计算 数据倾斜问题实际案例 MapReduce性能优化 针对MapReduce的案例我们并没有讲太多,主要是因为在实际工作中真正需要我们去写MapReduce代码的场…...

面向萌新的数学建模入门指南
时间飞逝,我的大一建模生涯也告一段落。感谢建模路上帮助过我的学长和学姐们,滴水之恩当涌泉相报,写下这篇感想,希望可以给学弟学妹们一丝启发,也就完成我的想法了。拙劣的文笔,也不知道写些啥,…...

基于 golang 从零到一实现时间轮算法 (二)
Go实现单机版时间轮 上一章介绍了时间轮的相关概念,接下来我们会使用 golang 标准库的定时器工具 time ticker 结合环状数组的设计思路,实现一个单机版的单级时间轮。 首先我们先运行一下下面的源码,看一下如何使用。 https://github.com/x…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...

ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...

20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...

【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...
HybridVLA——让单一LLM同时具备扩散和自回归动作预测能力:训练时既扩散也回归,但推理时则扩散
前言 如上一篇文章《dexcap升级版之DexWild》中的前言部分所说,在叠衣服的过程中,我会带着团队对比各种模型、方法、策略,毕竟针对各个场景始终寻找更优的解决方案,是我个人和我司「七月在线」的职责之一 且个人认为,…...