东莞智通人才招聘网下载/长沙网站托管seo优化公司
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…...

【系统架构设计】架构核心知识: 5 系统安全性与保密性设计
目录 一 信息安全基础 1 信息安全的基本要素 2 信息安全的范围 3 网络安全...

无人零售奶柜:革新牛奶购买体验
无人零售奶柜:革新牛奶购买体验 无人零售奶柜的投放地点覆盖了社区、写字楼等靠近居民的场所,大大提升了消费者购买牛奶的体验。这一创新不仅令消费者能够享受到与电商平台相媲美的直供价格优势,还让他们能够购买更多、更丰富的知名品牌牛奶。…...

【Mybatis小白从0到90%精讲】15: Mybatis配置打印SQL日志
文章目录 前言配置日志实现前言 日志(Log)是每个程序都不可或缺的一部分,它可以帮助开发人员诊断和调试问题。Mybatis,作为一款备受赞誉的ORM框架,自然也提供了强大的日志功能。 它不仅提供了内置的标准实现,还支持集成各种主流的日志框架,让我们可以轻松地查看最终执行…...

vue3-video-play视频播放组件
安装: npm i vue3-video-play --save使用说明: https://codelife.cc/vue3-video-play/guide/install.html...

vue项目中页面遇到404报错
vue页面访问正常,但是一刷新就会404的问题解决办法: 1.解决方法: 将vue的路由模式 mode: history 修改为 mode: hash模式 //router.js文件 const router new Router({//mode: history, mode: hash,routes: [{ path: /, redirect: /login …...

快手直播弹幕websocket protobuf序列化与反序列化
系列文章目录 websocket训练地址:https://www.qiulianmao.com,正在搭建中 基础-websocket逆向基础-http拦截基础-websocket拦截基础-base64编码与解码基础-python实现protobuf序列化与反序列化基础-前端js实现protobuf序列化与反序列化基础-protobufjs实现protobuf序列化与反…...

viple入门(三)
(1)条件循环活动 条件循环活动中,必须给定条件,条件成立,则执行条件循环的后续程序。 条件不成立,则不执行后续程序。 从报错信息来看,程序提示:条件循环要和结束循环活动一起使用。…...

Vue渲染函数渲染html
版本 vue2.6 使用 domProps属性 domProps: {innerHTML: xxx},官方文档...

Odoo|“视图”和“模型”之间的数据传输
01前言 今天带领大家学习Odoo系统中“视图”与“模型”之间的数据传输。看题目我们可以知道,这篇文章是面向的是Odoo的初学者。Odoo作为当前最普遍的二开ERP系统,其开源,模块化,灵活开发的属性使得它在ERP相关领域十分受青睐。 …...

Electron进程通信的另一种方式
上一篇讲述了主进程和渲染进程之间的通信,其中是通过调用 ipcMain 和 ipcRenderer 来完成的。比如渲染进程给主进程发送一个消息,然后主进程再返回一个消息给渲染进程: 主进程的逻辑: ipcMain.on(selectDate,(e,date)>{conso…...