Go语言的web框架--gin
本章内容,会介绍一下gin的运用,以及gin框架底层的内容,话不多说,开始进入今天的主题吧!
一.基本使用
gin框架支持前后端不分离的形式,也就是直接使用模板的形式。
模板是什么?
这里可能有同学不太了解,其实就是指的html,直接调用html,在后续的介绍中会有所了解。
1.1 模板搭建
首先简单看一下引用模板的样子吧
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()//解析模板r.LoadHTMLFiles("templates/hello.html")r.GET("/hello", func(c *gin.Context) {//Http请求c.HTML(http.StatusOK, "hello.html", gin.H{ //模板渲染"title": "Hello World",})})r.Run() //启动server
}
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body>{{ .title }}<h1>haha</h1></body>
</html>
对应的文件就是hello.html,然后运行go代码,去默认端口查看即可。
上述对应的函数会在后续介绍,接下来看看静态文件(css,js)的引入
1.2 静态文件的引入
- 静态文件就是指的是css,js那一类的文件
r.Static("/static", "./static")
就是如果以static开头的文件,会去static下面去找
r.Static("/xxx", "./static")
也就是xxx开头的文件去static下面去找
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()//静态文件渲染r.Static("/static", "./static")//解析模板r.LoadHTMLFiles("templates/hello.html")r.GET("/hello", func(c *gin.Context) {//Http请求c.HTML(http.StatusOK, "hello.html", gin.H{ //模板渲染"title": "Hello World",})})r.Run() //启动server
}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><link rel="stylesheet" href="/static/css/hello.css"><!-- 这里其实就是去找的static开头的文件都去 /static下面取找把static改为xxx也是可以实现的 -->
</head>
<body>{{ .title }}<h1>haha</h1><script src="/static/js/hello.js"></script>
</body>
</html>
alert(123);
body{background-color: hotpink;
}
1.3 前后端分离的搭建
主要还是前后端分离的搭建,说一下流程
- 首先设置开发模式
- 创建实例
- 设置中间件
- 加载路由
- 最后启动
func InitRouter(cfg *config.Configuration) {gin.SetMode(gin.DebugMode)//r := gin.New() //获取一个gin的引擎实例r := gin.Default() //这个是创建了一个默认的gin实例对象r.Use(cor.Cors()) // 跨域中间件repo := repositories.NewUrlMapRepoUse()service := service2.NewUrlService(repo)UrlHandler := shortURL.NewURLHandler(service)r.GET("/:code", UrlHandler.RedirectURL)apiv1 := r.Group("/api/v1"){apiv1.POST("/shorturl", UrlHandler.CreateURL)}r.Run(":8080")
}
这个例子无法直接运行,他是我的一个案例
二.具体函数和步骤介绍
目前来看,大部分项目都是前后端分离的项目,所以我们采用的也是前后端分离的架构,下面会按照之前介绍的步骤,一一介绍对应的函数。
2.1 开发模式
gin.SetMode(gin.DebugMode)
在gin包下,它里面有默认的常量表示开发模式,就像上面的gin.DebugMode
也可以改为"debug"也是可以的,两者等价
除了上面的debug模式还有release模式
debug模式会展示更多细节内容,但是release模式就是一个运行,而不是测试
具体介绍一下:
- DebugMode:这是默认模式,适用于开发和调试阶段。在这种模式下,Gin 会输出详细的调试信息,帮助开发者快速定位问题。例如: gin.SetMode(gin.DebugMode)
- ReleaseMode:适用于生产环境。在这种模式下,Gin 会减少日志输出,提升性能和安全性。例如: gin.SetMode(gin.ReleaseMode)
- TestMode:主要用于单元测试,Gin 自身的测试会使用这种模式。对于一般开发者来说,这种模式并不常用。例如: gin.SetMode(gin.TestMode)
2.2 实例对象
r := gin.Default() //这个是创建了一个默认的gin实例对象或者r:= gin.New()
两者的主要区别就是
gin.default默认提供了两个中间件
但是gin.New()获得实例没有任何中间件
r.Use(gin.Logger()) // 日志中间件
r.Use(gin.Recovery()) //
gin.Recovery()中间件
gin.Recovery()
是一个内置的Gin中间件,它的主要目的是捕获和处理在请求处理过程中发生的任何panic。当一个panic发生时,如果没有适当的恢复机制,程序将会崩溃并终止运行。gin.Recovery()
中间件提供了一个安全网,它能够捕获这些panic,防止程序崩溃,并且返回一个500内部服务器错误的响应给客户端。
2.3 中间件的加入
r.Use(cor.Cors()) // 跨域中间件
主要就是通过r.Use来添加中间件,除了自带的中间件,我们也可以自己编写中间件,会在后续聊到中间件的时候具体介绍一下。
2.4 路由的加入
r.GET("/:code", UrlHandler.RedirectURL)apiv1 := r.Group("/api/v1"){apiv1.POST("/shorturl", UrlHandler.CreateURL)}
所谓的路由就是url对应的处理器,处理前端传送过来的请求,上述的写法是路由组的形式,很容易看出来,前缀都是api/v1
2.5 启动gin引擎
r.Run(":8080")
就是在8080端口启动这个程序
三.请求和响应
所谓的请求和响应就是客户端和服务器之间的一个信息沟通,他们之间交流的信息格式有很多,比如json,xml等,一般来说都是json比较多。
json就是一个前端和后端交互的一个媒介,换句话说就是一种数据格式。
3.0 前置补充
如果大家有学过http协议,应该对请求方法有所了解,比如Get,Post等等方法,在gin框架中大家不难发现它已经为这些不同的方法做好了封装处理。
package mainimport ("github.com/gin-gonic/gin""net/http"
)func Sayhello(c *gin.Context) {c.JSON(200, gin.H{"message": "hello world",})
}func main() {//创建一个默认的路由引擎r := gin.Default() //返回一个默认的路由引擎//指定用户使用GET请求访问r.GET("/ping", Sayhello)r.GET("/books", func(c *gin.Context) {c.JSON(200, gin.H{"message": "Get",})})r.POST("/books", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "post",})})r.PUT("/books", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "Put",})})r.DELETE("/books", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "DELETE",})})//启动服务器r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}
其中的func(c *gin.Context) { 函数体 }就是http里面的处理器函数
在这个案例中,也可以直接引用处理器函数,要求和http的处理器是一样的,必须是func(c *gin.Context)类型,这样写让路由更加简单,也可以更好的实现分层。
//指定用户使用GET请求访问
r.GET("/ping", Sayhello)
c.JSON(http.StatusOK, gin.H{ (json格式 ) })
说一下JSON这个函数,它的作用就是处理器处理完成之后,给前端的一个响应,第一个参数就是状态码,如果有同学不了解的,可以自行上网搜索一下。
第二个参数就是后续要将的响应数据,采用的是json格式。
3.1 响应
响应,其实就是服务器给客户端发送数据的操作,在gin框架中,对响应的渲染主要有两种方式:
一个是map,另一个就是结构体。
3.1.1 gin.H
map方式 (也就是gin.H),为了处理不同类型的值,一般都会采用接口作为value
但是定义map[]interface比较麻烦,所以gin框架就对其做了一个封装,改为了gin.H。
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()r.GET("json", func(c *gin.Context) {//方法一使用map//data := map[string]interface{}{// "name": "小王子",// "age": 18,// "message": "hello world",// }// c.JSON(http.StatusOK, data)//})//简化的方式//但是gin的作者考虑到了这个问题,如果一直写map麻烦,就有了gin.Hdata := gin.H{"name": "小王子","age": 18,"message": "hello world",}c.JSON(http.StatusOK, data)})r.Run(":9090") //启动server
}
3.1.2 *结构体方式(这一块的内容可以了解)
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()r.GET("json", func(c *gin.Context) {type msg struct {Name stringAge intMessage string}m := msg{"小王子",18,"hello world",}c.JSON(http.StatusOK, m)})r.Run(":9090") //启动server
}
这里注意结构体的访问问题,大小写是否可以访问呢??
如果就想他是小写的name呢??查看内容即可,我做一个简单的案例。
通过tag(标签)来实现一个这样的效果
type msg struct {Name string `json:"name"`Age int `json:"age"`Message string `json:"message"`}
3.2 请求
类比http包,如果大家对http有了解的话,一个知道,http可以对请求做处理,比如路径参数,表格参数等等。
接下来就是介绍一下gin框架中是如何处理这些内容的。
3.2.1 querystring参数
这里还是介绍一下query函数的作用:就是获取参数的作用
name := c.Query("query") //通过query获取请求中的query string参数
这里获取的是url上的参数,是在路由之后/web?参数
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()r.GET("/web", func(c *gin.Context) {//获取浏览器那边发请求携带的querystring参数name := c.Query("query") //通过query获取请求中的querystring参数c.JSON(http.StatusOK, gin.H{"name": name,})})r.Run(":9090") //启动server
}
http://localhost:9090/web?query=程潇,去浏览器搜索这个路径
除了Quety函数,还有DefaultQuery和GetQuery两个函数
DefaultQuery()则是如果没用该参数,返回一个设置的默认值
GetQuery()则会返回一个布尔值,来判断是否返回了一个值
除此之外还可以传递多个参数,不是只有一个参数哦
多个key-value使用&来连接
3.2.2.form参数
这个一般是使用在获取信息的界面。
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()r.LoadHTMLFiles("templates/hello.html", "templates/index.html")r.GET("/hello", func(c *gin.Context) {c.HTML(http.StatusOK, "hello.html", nil)})//接受请求r.POST("/hello", func(c *gin.Context) {user := c.PostForm("username")mm := c.PostForm("password")c.HTML(http.StatusOK, "index.html", gin.H{"Name": user,"Password": mm,})})r.Run(":9090") //启动server
}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><link rel="stylesheet" href="/static/css/hello.css"><!-- 这里其实就是去找的static开头的文件都去 /static下面取找把static改为xxx也是可以实现的 -->
</head>
<body><form action="/hello" method="post"><div><label for="username">username:</label><input type="text" name="username" id="username"></div><div><label for="password">password:</label><input type="text" name="password" id="password"></div><div><input type="submit" value="登录"></div></form><script src="/static/js/hello.js"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><h1>hello,{{ .Name}}</h1><h1>你的密码是{{.Password}}</h1>
</body>
</html>
这里我们可以发现,就是一个/hello可以对于不同的请求的界面,一个是get,一个是post
都是可以实现的
还要该清楚这里的逻辑是怎么实现的
他的逻辑就是你进入get的hello网址(不过一般都是/开头的),然后在登入的html里面把内容通过form表单在传递给他参数action规定的处理器上,然后看看他是什么方法,由于是post方法,所以会传给post方法对应的处理器上面,从而进行不同的操作啦。
还存在第二种方法
他和上面的query string差不多,他就是类似一个,如果没有的话就返回一个默认值,你应该是传入input标签里面的name,但是没有传入,就会取默认值。
还有第三种,和上面的大差不差
3.2.3.path参数的获取
path参数也被称为路径参数,主要通过/:key 来表示,获取这个key的值
package mainimport "github.com/gin-gonic/gin"func main() {r := gin.Default()r.GET("/:name/:age", func(c *gin.Context) {name := c.Param("name")age := c.Param("age")c.JSON(200, gin.H{"name": name,"age": age,})})r.Run(":9090")
}
看他的URL上,就可以将他传给我们的get,去处理
package mainimport "github.com/gin-gonic/gin"func main() {r := gin.Default()r.GET("/:name/:age", func(c *gin.Context) {name := c.Param("name")age := c.Param("age")c.JSON(200, gin.H{"name": name,"age": age,})})r.GET("/blog/:year/:month", func(c *gin.Context) {year := c.Param("year")month := c.Param("month")c.JSON(200, gin.H{"year": year,"month": month,})})r.Run(":9090")
}
可以通过加前缀的方式改变这个问题
3.2.4.参数的绑定
c.ShouldBind(&参数)
我们可以使用这个函数直接提取上述三种方式传入的数据,并且绑定到对应的结构体里面去。
非常的好用哦
比如我们的登入注册功能就可以不在使用页面做了,直接使用api来进行测试也是非常的快的。
接下展示一下json的例子,由于是前后端分离的形式,所以要认真学习者一块的内容
json发送和form选的是不一样的啦
package mainimport ("fmt""github.com/gin-gonic/gin""net/http"
)type User struct {Username string `json:"username" form:"username"`Password string `json:"password" form:"password"`
}func main() {r := gin.Default()r.LoadHTMLFiles("templates/hello.html")r.GET("/", func(c *gin.Context) {//username := c.Query("username")//password := c.Query("password")//u1 := User{// Username: username,// Password: password,//}var u1 Usererr := c.ShouldBind(&u1) //这里为什么要传&u1引用,因为你要去改变他的值/*这里就会还有一个问题,你怎么判断传入的字段和你的结构体字段符合呢?在结构体体后面加上form,就是tag*/if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error(),})} else {fmt.Println(u1)c.JSON(http.StatusOK, gin.H{"status": "ok",})}})r.GET("/hello", func(c *gin.Context) {c.HTML(http.StatusOK, "hello.html", nil)})r.POST("/form", func(c *gin.Context) {var u1 Usererr := c.ShouldBind(&u1)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error(),})} else {fmt.Println(u1)c.JSON(http.StatusOK, gin.H{"status": "ok",})}})r.POST("/json", func(c *gin.Context) {var u1 Usererr := c.ShouldBind(&u1)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error(),})} else {fmt.Println(u1)c.JSON(http.StatusOK, gin.H{"status": "ok",})}})r.Run(":9090")
}
通过观察,我们会发现不管是什么方式去获取,他们的大致代码都是差不多哟,你应该可以发现吧。
除了请求和URL的不同,其他大致都可以,
由此可见ShouldBind(&参数)的厉害,不管你是什么格式,都可以调用
其实这里一般只用来处理表单传入的数据,一般前端form表单的数据转化为json格式或者其他格式传给后端来解析。
除了ShouldBind,还有ShouldBindJSON专门用来解析josn数据。
3.2.5.获取Host
直接通过c.Host就可以获取路径的主机名
这些函数其实本质上都是对http的一个封装,简单看一下源码,大家应该就知道如何使用了。
四.文件的传输
4.1 介绍
4.2 操作案例
f, err := c.FormFile("f1") 首先获取文件
dst := fmt.Sprintf("%d", f.Filename)
dst := path.Join("./", f.Filename)
上述两个方法都是将个获取当前目录的方法
c.SaveUploadedFile(f, dst)
然后这里就是将这文件保存到指定目录下面
package mainimport ("github.com/gin-gonic/gin""net/http""path"
)func main() {r := gin.Default()r.LoadHTMLFiles("templates/index.html")r.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "index.html", nil)})r.POST("/upload", func(c *gin.Context) {//从请求中读取文件f, err := c.FormFile("f1")if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})} else {// 将读取的文件保存在本地// dst := fmt.Sprintf("%d", f.Filename)dst := path.Join("./", f.Filename)c.SaveUploadedFile(f, dst)c.JSON(http.StatusOK, gin.H{"message": "ok"})}})r.Run(":9090")
}
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><form action="/upload" method="post" enctype="multipart/form-data"><!-- 插入文件或者照片必须带上enctype="multipart/form-data"--><input type="file" name="f1"><input type="submit" value="上传"></form></body>
</html>
上面是单个文件进行的一个读取操作
下面介绍一下多个文件的读取操作
c.MultipartForm 获取多个文件
然后存入到这个数组里面去
再用一个for range循环上传到指定的文件去即可
五.请求重定向
5.1 概念介绍
关于重定向,在http的内容里面已经涉及过了,就不在过多介绍了。
5.2 案例
5.2.1 http 重定向
下面就是一个重定向,到百度的网址
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()r.GET("/", func(c *gin.Context) {//c.JSON(http.StatusOK, gin.H{// "status": "ok",//})c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com")})r.Run(":9090")
}
5.2.2 路由重定向
也就是你虽然进入的是a的路由,但是他最后却是指向b的路由,这就是所谓的路由重定向
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()r.GET("/a", func(c *gin.Context) {//跳转到bc.Request.URL.Path = "/b" //把请求的URL修改r.HandleContext(c)//继续后续的处理,跳转到b路由})r.GET("/b", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"massage": "ok",})})r.Run(":9090")
}
六.路由和路由组
6.1 基础介绍
any请求里面包含所有的请求哦,可以用switch来设置
NoRoute
处理函数允许开发者定义当请求没有匹配到任何路由时应该执行的操作。默认情况下,Gin框架会为NoRoute
情况返回一个404状态码
6.2 案例
6.2.1普通路由
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()//访问/index的r.GET("/test", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"method": "GET",})})r.POST("/test", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"method": "POST",})})r.DELETE("/test", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"method": "DELETE",})})r.PUT("/test", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"method": "PUT",})})//这个会处理所以的请求,懒得话就可以写这个anyr.Any("/user", func(c *gin.Context) {switch c.Request.Method {case "GET":c.JSON(http.StatusOK, gin.H{"method": "GET"})case http.MethodPost:c.JSON(http.StatusOK, gin.H{"method": "POST"})}c.JSON(http.StatusOK, gin.H{"method": "ANY",})})r.Run(":9090")
}
但是一般来说,我们设置的URL是有限制的,但是用户可能会随便输入各种各样的路径,为了解决如果是不存在的路径,我们就设置一个页面进行跳转
我们可以使用NoRoute来实现这个功能
r.NoRoute(func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"信息": "404,查找无果",})})
6.2.2路由组
如果当我们去写项目的时候,发现有很多的来自同一个URL下的多个不同的页面,如果一个一个写r.GET或者什么,写起来是相当麻烦的。
比如这样:
我们该如何去修改呢?
这个时候我们就可以使用路由组去解决这个问题
这样就可以了
6.3路由组的嵌套
七.中间件
7.1 概念介绍
除此之外,还有跨域中间件,在gin框架中提供的有跨域的库cor。
7.2 案例
要明白Next函数和Abort函数
下面展示一个案例
package mainimport ("fmt""github.com/gin-gonic/gin""net/http""time"
)func indexhandler(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "index",})
}// 定义一个中间件:统计耗时
func m1(c *gin.Context) {fmt.Println("m1进入")//计时start := time.Now()c.Next() //调用后续的处理函数//c.Abort() //阻止调用后续的处理函数cost := time.Since(start)fmt.Printf("m1 cost %v\n", cost)
}func main() {r := gin.Default()r.GET("/test", m1, indexhandler)//这里请求进来之后先走m1,之后才走index,这里的m1就相当于是一个中中间件r.Run(":9090")
}
如果存在一个情况就是多个请求都需要使用m1这个中间件,有没有什么快捷的方式呢?
当然有了:
就是Use这个函数
加上之后,后续的函数都会有这个m1中间件
如果存在多个中间件:
要明白他在执行的顺序:他不是一个一个调用,而是类似一个嵌套的那种方式
package mainimport ("fmt""github.com/gin-gonic/gin""net/http""time"
)func indexhandler(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "test",})
}// 定义一个中间件:统计耗时
func m1(c *gin.Context) {fmt.Println("m1进入")//计时start := time.Now()c.Next() //调用后续的处理函数,就是相当于是调用后面的indexhandler//c.Abort() //阻止调用后续的处理函数cost := time.Since(start)fmt.Printf("m1 cost %v\n", cost)
}func m2(c *gin.Context) {fmt.Println("m2 in")c.Next() //调用后续的处理函数fmt.Println("m2 out")
}func main() {r := gin.Default()r.Use(m1, m2)r.GET("/test", indexhandler)//这里请求进来之后先走m1,之后才走index,这里的m1就相当于是一个中中间件r.Run(":9090")
}
如果把m2里面的Next改为Abort,这样就会断掉,这样网页就会没有内容
如果想后面的m2 out 都没有
直接return即可
7.3 真正的使用案例
一般情况下是和闭包一起使用的
为路由准备中间件啦
7.4 如何实现传递数据(m2->indexhandler)
也就是使用函数GET和SET
package mainimport ("fmt""github.com/gin-gonic/gin""net/http""time"
)func indexhandler(c *gin.Context) {name, ok := c.Get("name")if !ok {name = "匿名用户"}c.JSON(http.StatusOK, gin.H{"message": name,})
}// 定义一个中间件:统计耗时
func m1(c *gin.Context) {fmt.Println("m1进入")//计时start := time.Now()c.Next() //调用后续的处理函数,就是相当于是调用后面的indexhandler//c.Abort() //阻止调用后续的处理函数cost := time.Since(start)fmt.Printf("m1 cost %v\n", cost)
}func m2(c *gin.Context) {fmt.Println("m2 in")c.Set("name", "qimi")c.Next() //调用后续的处理函数//c.Abort() //阻止调用后续的处理函数fmt.Println("m2 out")
}func main() {r := gin.Default()r.Use(m1, m2)r.GET("/test", indexhandler)//这里请求进来之后先走m1,之后才走index,这里的m1就相当于是一个中中间件r.Run(":9090")
}