一. 框架介绍
Gin是一个轻量级的Go语言Web框架,它具有高性能和简洁的设计。由于其快速的路由匹配和处理性能,Gin成为Go语言中最受欢迎的Web框架之一。
特点:
- 快速和轻量:Gin框架的设计注重性能和效率,采用了一些优化措施,使其成为一个快速而轻量级的框架。
- 路由和中间件:Gin提供了强大的路由功能,支持参数传递,路由分组等特性。同时,它支持中间件的使用,可以方便的在请求处理过程中执行一系列的操作,比如身份验证,日志记录等。
- json解析:Gin内置了对json的解析和序列化支持,使得处理json数据变得简单而高效。
- 支持插件:Gin允许开发者通过插件来扩展框架的功能,这样可以根据项目的需求进行灵活定制。
文档:
- Github地址:https://github.com/gin-gonic/gin
- 中文文档:https://gin-gonic.com/zh-cn/docs/
二. 安装
要安装Gin软件包,您需要安装Go并首先设置Go工作区。
- 命令安装Gin
go get github.com/gin-gonic/gin@latest
- 导入代码
import "github.com/gin-gonic/gin"
三. 第一个Gin应用
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
//将应用切换到“发布模式”以提升性能
gin.SetMode(gin.ReleaseMode)
//创建路由
r := gin.Default()
//绑定路由规则,执行函数
//gin.Context,封装了request和response
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "hello world")
})
//监听端口,默认绑定端口8080
r.Run(":8080")
}
代码解释:
- gin.Default:创建一个Gin引擎。gin.Default()返回一个带有默认中间件的Gin引擎,包括Logger和Recovery中间件,用于日志记录和恢复。
- r.Get("/", func(c *gin.Context){...}):定义了一个GET方法的路由,当访问路径是"/"时,执行后面的回调函数。
- c.String(http.StatusOK, "hello world"):在回调函数中,通过c.String方法返回一个字符串"hello world"并设置HTTP状态码为200 OK。
- s.Run(":8080"):启动Web服务,监听在0.0.0.0:8080。如果不指定端口号,默认使用8080端口。此时,你可以通过浏览器或HTTP客户端访问http://localhost:8080,将会得到"hello world"的响应。
四. 应用举例
以下项目都是使用Gin框架开发的:
- gorush:Go 编写的通知推送服务器。
- fnproject:容器原生,云 serverless 平台。
- photoprism:基于 Go 和 Google TensorFlow 实现的个人照片管理工具。
- krakend:拥有中间件的超高性能 API 网关。
- picfit:Go 编写的图像尺寸调整服务器。
- gotify:基于 WebSocket 进行实时消息收发的简单服务器。
- cds:企业级持续交付和 DevOps 自动化开源平台。
五. Gin入门
- gin.Engine
在Gin里面,一个Web服务器被抽象成了Engnie。你可以在一个应用里面创建多个Engine实例,监听不同的端口。Engine承担了路由注册,接入中间件的核心职责。
它组合了RouterGroup,RouterGroup才是实现路由功能的核心组件。
- gin.Context
gin.Context是Gin里面的核心类型。字面意思是"上下文",在Gin里面的核心职责是:
- 处理请求
- 返回响应
六. 路由
6.1 介绍
- gin框架中采用的路由库是基于httprouter做的
- 地址为:https://github.com/julienschmidt/httprouter
- 支持Restful风格的API,意思是"表面层状态转化",是一个互联网应用程序的API设计理念:URL定位资源。
- 可以创建路由组,为了管理相同的URL。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
//将应用切换到“发布模式”以提升性能
gin.SetMode(gin.ReleaseMode)
//创建路由
//默认使用了两个中间件Logger(),Recovery()
r := gin.Default()
r.GET("/hello1", func(c *gin.Context) {
c.String(http.StatusOK, "hello1")
})
r.GET("/hello2", func(c *gin.Context) {
c.String(http.StatusOK, "hello2")
})
//路由组1
v1 := r.Group("/v1")
{
v1.GET("/hellov1", func(c *gin.Context) {
c.String(http.StatusOK, "hello v1")
})
v1.GET("hellov11", func(c *gin.Context) {
c.String(http.StatusOK, "hello v11")
})
}
//路由组2
v2 := r.Group("/v2")
{
v2.GET("/hellov2", func(c *gin.Context) {
c.String(http.StatusOK, "hello v2")
})
v2.POST("/hellov22", func(c *gin.Context) {
c.String(http.StatusOK, "hello v22")
})
}
//监听端口,默认绑定端口8080
r.Run(":8080")
}
6.2 API参数
- 可以通过Context的Param方法来获取API参数
package main
import (
"fmt"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/user/:name/*action", func(c *gin.Context) {
name := c.Param("name")
action := c.Param("action")
fmt.Println(name, ":", action) //对于url /user/wy/aa 打印 wy : /aa
//去除/
action = strings.Trim(action, "/")
c.String(http.StatusOK, name+" is "+action)
})
r.Run()
}
6.3 URL参数
- URL参数可以通过DefaultQuery()或Query方法获取
- DefaultQuery()若参数不存在,返回默认值。Query()若参数不存在,返回空串。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/user", func(c *gin.Context) {
name := c.Query("name")
action := c.DefaultQuery("action", "")
c.String(http.StatusOK, name+" is "+action)
})
r.Run()
}
6.4 表单参数
-
表单传输参数为POST请求,http常见的传输格式为四种:
-
application/json
-
application/x-www-form-urlencode
-
application/xml
-
mulipart/form-data
-
-
表单参数可以通过PostForm()方法获取,该方法默认解析的是x-www-form-urlencode或form-data格式的参数。
POST请求的html代码:
<!--test.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<form action="/form" method="post"
action="application/x-www-form-urlencoded">
用户名:<input type="text" name="username" placeholder="请输入你的用户名">
<br>密 码:<input type="password" name="userpassword"
placeholder="请输入你的密码"> <br>
<input type="submit" value="提交">
</form>
</body>
</html>
服务端代码
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
//设置HTML文件所在目录
r.LoadHTMLGlob("./*.html")
//设置GET方法路由,成功返回test.html文件
r.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "test.html", nil)
})
//设置POST方法路由
r.POST("/form", func(c *gin.Context) {
//设置没有传的参数的默认值
types := c.DefaultPostForm("type", "post")
username := c.PostForm("username")
password := c.PostForm("userpassword")
c.String(http.StatusOK, "username:%s, userpassword:%s, types:%s", username, password, types)
})
r.Run()
}
演示:
6.5 上传文件
上传单个文件:
- multipart/form-data格式用于上传文件
- gin文件上传与原生的net/http方法类似,不同在于gin吧原生的request封装到了c.Request中。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
//设置HTML文件所在目录
r.LoadHTMLGlob("./*.html")
//设置GET方法路由,成功返回test.html文件
r.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "test.html", nil)
})
//设置POST方法路由
r.MaxMultipartMemory = 8 << 20 //限制上传最大尺寸
r.POST("/upload", func(c *gin.Context) {
//用于获取表单信息中file格式的参数,并且返回一个文件流
file, err := c.FormFile("file") //html中的name
if err != nil {
c.String(500, "上次图片错误")
}
//参数1为指定需要保存操作的文件,参数2为指定保存路径。
c.SaveUploadedFile(file, file.Filename)
//返回文件名
c.String(http.StatusOK, file.Filename)
})
r.Run()
}
演示:
上传特定文件:
有的用户上传文件需要限制文件的类型以及上传文件的大小,可以基于原生的函数写法自己写一个可以限制大小以及文件类型的上传函数。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.LoadHTMLGlob("./*.html")
r.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "test.html", nil)
})
r.POST("/upload", func(c *gin.Context) {
_, header, err := c.Request.FormFile("file")
if err != nil {
c.String(405, "文件错误")
return
}
if header.Size > 1024*1024*2 {
c.String(406, "文件太大")
return
}
if header.Header.Get("Content-Type") != "image/png" {
c.String(407, "只允许上传图片")
return
}
c.SaveUploadedFile(header, header.Filename)
c.String(http.StatusOK, header.Filename)
})
r.Run()
}
上传多个文件:
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.LoadHTMLGlob("./*.html")
r.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "test.html", nil)
})
//限制表单上传大小8MB 默认32MB
r.MaxMultipartMemory = 8 << 20 * 2
r.POST("/upload", func(c *gin.Context) {
//用于获取multipart表单。当用户通过表单上传文件时,浏览器通常会将表单编码为multipart/form-data格式。
form, err := c.MultipartForm()
if err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("get a %v", err))
return
}
//获取所有文件
files := form.File["files"]
//遍历所有文件
for _, file := range files {
if err := c.SaveUploadedFile(file, file.Filename); err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("upload get a %v", err))
return
}
}
c.String(http.StatusOK, fmt.Sprintf("upload %d files ok", len(files)))
})
r.Run()
}
6.6 路由原理
httprouter会将所有路由规则构造一棵前缀树。
例如有root,and,as,at,cn,com
6.7 路由拆分与注册
- 基本的路由注册
下面最基础的gin路由注册方式,使用于路由条目比较少的简单项目或者项目demo。
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func sayHello(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "hello go",
})
}
func main() {
r := gin.Default()
r.GET("/hello", sayHello)
if err := r.Run(); err != nil {
fmt.Printf("startup server err : %v", err)
}
}
- 路由拆分成单独文件或包
当项目的规模增大后,就不适合继续在项目的main.go文件中实现路由注册相关逻辑了,我们会倾向于把路由部分的代码都拆分出来,形成一个单独的文件或包。
- 形成单独文件
- 形成独立的包
- 拆分成多个文件
当业务规模继续膨胀,单独的一个router文件或包已经满足不了我们的需求了。 因为我们把所有的路由注册都写在一个SetRouter函数中的话会很复杂。
我们可以将其拆分为多个文件。
- 路由拆分到不同APP
有时候项目规模太大,那么我们就更倾向于把业务拆分的更加详细,例如把不同的业务代码拆分成不同的APP。
因此我们在项目目录下单独定义一个app目录,用来存放我们不同业务线的代码文件,这样就很容易进行横向扩展。
目录结构:
app/blog:
代码如下:
gin/app/shop:
gin/router/router.go
gin/main.go