Gin框架笔记
前言
最近尝试用Gin框架写一个小程序后端,自己做了一些学习笔记备忘,也供大家参考。作者水平有限,有任何问题欢迎在文章下面留言交流! 主要参考资料:https://www.qfgolang.com/?special=ginkuangjia&pid=2606
安装Gin框架
需要go版本在1.6以上
查看自己go版本
$ go version
安装Gin框架
$ go get -u github.com/gin-gonic/gin
国内直接使用这条命令安装大概率会因网络超时安装失败。
解决办法1:更换阿里云的镜像源
// 启用 Go Modules 功能
$ go env -w GO111MODULE-on
// 配置 GOPROXY 环境变量
$ go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/
但是此方法开启了Go Modules,包将会下载到pkg/mod/目录下,且文件名将会带上版本号。但编辑器似乎不一定能够自动识别路径并导入。
解决办法2:终端翻墙下载,需要你有代理工具。
还需要注意,mac的终端默认是不走代理的,即使你的v2ray开了全局模式,终端也还是没翻出去。
mac终端翻墙的方法:
1、打开.zprofile(在旧版mac系统中则是.bash_profile)
$ vim ~/.zprofile
添加两个函数
function proxy_on{
export http_proxy=http://本机http监听host:本机http监听端口
export https_proxy=http://本机http监听host:本机http监听端口
}
function proxy_off{
unset http_proxy
unset https_proxy
}
本机http监听host和本机http监听端口可以去代理工具查看,这里以V2RayU为例子:打开V2RayU偏好设置,选择Advance就可以看到。
所以在我的电脑上,我添加的两个功能具体写为:
function proxy_on{
export http_proxy=http://127.0.0.1:1087
export https_proxy=http://127.0.0.1:1087
}
function proxy_off{
unset http_proxy
unset https_proxy
}
每次需要终端翻墙时,在终端输入以下命令开启代理:
$ proxy_on
使用完后输入以下命令关闭代理:
$ proxy_off
1、创建Engine
在gin框架中,Engine 被定义成为一个结构体,Engine 代表gin框架的一个结构体定义,其中包含了路由组、中间件、页面渲染接口、框架配置设置等相关内容。
默认的Engine 可以通过**gin.Default **进行创建,或者使用gin.New() 同样可以创建。两种方式如下所示:
engine1 = gin.Default()
engine2 = gin.New()
gin.Default() 和gin.New() 的区别在于:
gin.Default 也使用gin.New() 创建engine实例,但是会默认使用Logger 和Recovery 中间件。
Logger 是负责进行打印并输出日志的中间件,方便开发者进行程序调试。Recovery 中间件的作用是如果程序执行过程中遇到panic 中断了服务,则Recovery 会恢复程序执行,并返回服务器500内部错误。
通常情况下,我们使用默认的gin.Default创建Engine实例。
运行Engine
engine1.Run([主机地址:端口号])
engine1.Run()
engine1.Run("apphost:8090")
2、处理HTTP请求
在上面我们创建的engine实例中,包含很多方法可以直接处理不同类型的HTTP请求。
HTTP请求类型
http协议中一共定义了八种方法或者称之为类型来表明对请求网络资源(Request-URI)的不同的操作方式,分别是:OPTIONS、HEAD、GET、POST、PUT、DELETE、TRACE、CONNECT。
虽然一共有八种请求操作类型,但是实际开发中常用的就:GET、POST、DELETE等几种。
Context-上下文
Context 是gin框架中封装的一个结构体,这是gin框架中最重要,最基础的一个结构体对象。
该结构体可以提供我们操作请求,处理请求,获取数据等相关的操作,通常称之为上下文对象,简单说为我们提供操作环境。
可以通过context.Query 和context.DefaultQuery 获取GET请求携带的参数。
可以通过context.Writer.Write 向请求发起端返回数据。
通用处理
engine中可以直接进行HTTP请求的处理,在engine中使用Handle 方法进行http请求的处理。Handle 方法包含三个参数,具体如下所示:
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes
httpMethod :第一个参数表示要处理的HTTP的请求类型,是GET、POST、DELETE等8种请求类型中的一种。relativePath :第二个参数表示要解析的接口,由开发者进行定义。handlers :第三个参数是处理对应的请求的代码的定义。
实例:
Handle处理GET请求
...
engine.Handle("GET", "/hello", func(context *gin.Context) {
fmt.Println(context.FullPath())
name := context.DefaultQuery("name", "")
fmt.Println(name)
context.Writer.Write([]byte("Hello ," + name))
})
...
context.FullPath() :**返回请求接口地址。**上述代码返回的是"/hello"。
context.DefaultQuery(key(string), defaultValue(string)) :**读取GET请求附带的参数。**第一个参数指定要读取的参数的键值key,第二个参数是若读取不到要返回的默认字符串。
context.Writer.Write(([]byte)) :**给前端返回数据。**数据类型是byte字符数组。
Handle处理POST请求
...
engine.Handle("POST", "/login", func(context *gin.Context) {
fmt.Println(context.FullPath())
username := context.PostForm("username")
fmt.Println(username)
password := context.PostForm("pwd")
fmt.Println(password)
context.Writer.Write([]byte("User login"))
})
...
context.PostForm(key(string)) :**读取POST请求附带的参数。**参数指定要读取的参数的键值key。
分类处理
除了Engine中包含的通用的处理方法以外,engine还可以按类型进行直接解析。
Engine 中包含有get 方法、post 方法、delete 方法等与http请求类型对应的方法。
engine.GET()处理GET请求
engine中包含GET方法处理HTTP的GET类型的请求。engine的GET方法包含两个参数,编程使用如下所示:
...
engine.GET("/hello", func(context *gin.Context) {
fmt.Println(context.FullPath())
username := context.Query("name")
fmt.Println(username)
context.Writer.Write([]byte("Hello," + username))
})
...
context.Query(key(string)) :**读取GET请求附带的参数。**参数指定要读取的参数的键值key。
engine.POST()处理POST请求
...
engine.POST("/login", func(context *gin.Context) {
fmt.Println(context.FullPath())
username, exist := context.GetPostForm("username")
if exist {
fmt.Println(username)
}
password, exists := context.GetPostForm("pwd")
if exists {
fmt.Println(password)
}
context.Writer.Write([]byte("Hello , " + username))
})
...
context.GetPostForm(key(string)) :**读取POST请求附带的参数。**参数指定要读取的参数的键值key。
它与context.PostForm() 的区别在于:它除了返回读取的数据之外,还会返回一个bool值,表示所读取的参数是否存在。
engine.DELETE()处理DELETE请求
在项目开发中,通常都是遵循RESTful标准进行接口开发。除了GET、POST以外,还会有DELETE等操作。
比如要执行某个删除操作,会发送DELETE类型的请求,同时需要携带一些操作的参数。比如要删除用户,按照RESTful标准会进行如下所示:
...
engine.DELETE("/user/:id", DeleteHandle)
func DeleteHandle(context *gin.Context) {
fmt.Println(context.FullPath())
userID := context.Param("id")
fmt.Println(userID)
context.Writer.Write([]byte("Delete user's id : " + userID))
}
...
context.Param(key(string)) :**读取DELETE请求的参数。**参数指定要读取的参数的键值key。
3、使用路由组分组处理请求
使用场景
在实际的项目开发中,均是模块化开发。同一模块内的功能接口,往往会有相同的接口前缀。比如如下所示:
在系统中有用户模块,用户有不同注册、登录、用户信息、
注册:http://localhost:9000/user/register
登录:http://localhost:9000/user/login
用户信息:http://localhost:9000/user/info
删除:http://localhost:9000/user/1001
类似这种接口前缀统一,均属于相同模块的功能接口。可以使用路由组进行分类处理。
什么是路由
在web开发中,**“route”(路由)**是指根据url分配到对应的处理程序。
RouterGroup
之所以engine中包含通用型的Handle和分类处理的GET、POST等类型的方法,是因为Engine中有RouterGroup 作为匿名字段。
RouteGroup 可以称之为路由组,在gin中定义为结构体:
type RouterGroup struct {
Handlers HandlersChain
basePath string
engine *Engine
root bool
}
RouterGroup 的作用就是为每一个服务请求提供解析功能,并指定每一个请求对应的处理程序。
路由组的使用
gin框架中可以使用路由组来实现对路由的分类。
路由组是engine.Group 返回的一个结构体,用于对请求进行分组。
实例:
engine := gin.Default()
routerGroup := engine.Group("/user")
routerGroup.GET("/register", registerHandle)
routerGroup.GET("/login", loginHandle)
routerGroup.GET("/info", infoHandle)
engine.Run(":9000")
func registerHandle(context *gin.Context) {
fullPath := " 用户注册功能 " + context.FullPath()
fmt.Println(fullPath)
context.Writer.WriteString(fullPath)
}
func loginHandle(context *gin.Context) {
fullPath := " 用户登录功能 " + context.FullPath()
fmt.Println(fullPath)
context.Writer.WriteString(fullPath)
}
func infoHandle(context *gin.Context) {
fullPath := " 信息查看功能 " + context.FullPath()
fmt.Println(fullPath)
context.Writer.WriteString(fullPath)
}
4、请求参数绑定
但是如果表单数据较多时,使用PostForm和GetPostForm一次获取一个表单数据,开发效率较慢。
Gin框架提供给开发者表单实体绑定的功能,可以将表单数据与结构体绑定。
表单实体绑定
gin框架提供了数据结构体和表单提交数据绑定的功能,提高表单数据获取的效率。
实例:
以一个用户注册功能来进行讲解表单实体绑定操作。
用户注册需要提交表单数据,假设注册时表单数据包含三项,分别为:username、phone和password。
先创建UserRegister结构体用于接收表单数据,通过tag 标签的方式设置每个字段对应的form 表单中的属性名,通过binding 属性用于设置属性是否是必须。
type UserRegister struct {
Username string `form:"username" binding:"required"`
Phone string `form:"phone" binding:"required"`
Password string `form:"password" binding:"required"`
}
ShouldBindQuery-GET请求数据绑定
func main() {
engine := gin.Default()
engine.GET("/hello", func(context *gin.Context) {
fmt.Println(context.FullPath())
var student Student
err := context.ShouldBindQuery(&student)
if err != nil {
log.Fatal(err.Error())
}
fmt.Println(student.Name)
fmt.Println(student.Classes)
context.Writer.Write([]byte("hello," + student.Name))
})
engine.Run()
}
type Student struct {
Name string `form:"name"`
Classes string `form:"classes"`
}
context.ShouldBindQuery(&结构体) :**将GET请求附带的参数传入结构体中。**并返回一个error类型数据。
ShouldBind-POST请求数据绑定
func main(){
engine := gin.Default()
engine.POST("/register", func(context *gin.Context){
fmt.Println(context.FullPath())
var _register Register
err := context.ShouldBind(&_register)
if err != nil{
log.Fatal(err.Error())
return
}
fmt.Println(_register.UserName)
fmt.Println(_register.Phone)
context.Writer.Write([]byte(_register.UserName + " Register "))
})
engine.Run()
}
type Register struct {
UserName string `form:"name"`
Phone string `form:"phone"`
Password string `form:"pwd"`
}
context.ShouldBind(&结构体) :**将POST请求附带的参数传入结构体中。**并返回一个error类型数据。
Json格式数据绑定-ShouldBindJson
当客户端使用Json格式进行数据提交时,可以采用ShouldBindJson对数据进行绑定并自动解析。
实例:
func main() {
engine := gin.Default()
engine.POST("/addstudent", func(context *gin.Context) {
fmt.Println(context.FullPath())
var person Person
err := context.BindJSON(&person)
if err != nil {
log.Fatal(err.Error())
return
}
fmt.Println("姓名:" + person.Name)
fmt.Println("年龄:", person.Age)
context.Writer.Write([]byte(" 添加记录:" + person.Name))
})
engine.Run()
}
type Person struct {
Name string `form:"name"`
Sex string `form:"sex"`
Age int `form:"age"`
}
context.BindJSON(&结构体) :**将http请求附带的Json格式数据传入结构体中。**并返回一个error类型数据。
5、多数据格式返回前端
在gin框架中,支持返回给前端多种请求数据格式。
[]byte
context.Writer.Write(([]byte)) :给前端返回[]byte类型数据。
string
context.Writer.WriteString((string)) :给前端返回string类型数据。
JSON
gin框架中的context.JSON 方法可以将map 类型和struct 结构体类型的数据转换成JSON格式的结构化数据,然后返回给客户端。
map类型
...
engine := gin.Default()
engine.GET("/hellojson", func(context *gin.Context) {
fullPath := "请求路径:" + context.FullPath()
fmt.Println(fullPath)
context.JSON(200, map[string]interface{}{
"code": 1,
"message": "OK",
"data": fullPath,
})
})
engine.Run(":9000")
...
结构体类型
type Response struct {
Code int json:"code"
Message string json:"msg"
Data interface{} json:"data"
}
engine.GET("/jsonstruct", func(context *gin.Context) {
fullPath := "请求路径:" + context.FullPath()
fmt.Println(fullPath)
resp := Response{Code: 1, Message: "Ok", Data: fullPath}
context.JSON(200, &resp)
})
HTML模板
除了JSON格式以外,gin框架还支持返回HTML 格式的数据。可以直接渲染HTML页面。
实例:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func main(){
engine := gin.Default()
engine.LoadHTMLGlob("./html/*")
engine.Static("/img", "./img")
engine.GET("/hellohtml", func(context *gin.Context) {
fullPath := "请求路径:" + context.FullPath()
fmt.Println(fullPath)
context.HTML(http.StatusOK, "index.html", gin.H{
"fullPath":fullPath,
"title":"gin教程",
})
})
engine.Run()
}
index.html源代码:
<html>
<head>
<title>{{.title}}</title>
</head>
<h1 align="center">syb的Gin学习</h1>
{{.fullPath}}
<br />
<div align="center"><img src="../img/jiandang100zhounian.jpeg"></div>
</html>
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mHRaV0GV-1627695899916)(截屏2021-07-23 下午6.23.56.png)]
6、middleware中间件的编写与使用
中间件
在web应用服务中,完整的一个业务处理在技术上包含客户端操作、服务器端处理、返回处理结果给客户端三个步骤。
在实际的业务开发和处理中,会有更负责的业务和需求场景。一个完整的系统可能要包含鉴权认证、权限管理、安全检查、日志记录等多维度的系统支持。
鉴权认证、权限管理、安全检查、日志记录等这些保障和支持系统业务属于全系统的业务,和具体的系统业务没有关联,对于系统中的所有业务都适用。
由此,在业务开发过程中,为了更好的梳理系统架构,可以将上述描述所涉及的一些通用业务单独抽离并进行开发,然后以插件化的形式进行对接。这种方式既保证了系统功能的完整,同时又有效的将具体业务和系统功能进行解耦,并且,还可以达到灵活配置的目的。
这种通用业务独立开发并灵活配置使用的组件,一般称之为**“中间件”**,因为其位于服务器和实际业务处理程序之间。其含义就是相当于在请求和具体的业务逻辑处理之间增加某些操作,这种以额外添加的方式不会影响编码效率,也不会侵入到框架中。中间件的位置和角色示意图如下图所示:
engine.Use()方法使用中间件
engine.Use(<中间件>)
engine.Use(Logger(), Recovery())
自定义中间件
根据上文的介绍,可以自己定义实现一个特殊需求的中间件,中间件的类型是函数,有两条标准:
比如,我们自定义一个自己的中间件。在前面所学的内容中,我们在处理请求时,为了方便代码调试,通常都将请求的一些信息打印出来。有了中间件以后,为了避免代码多次重复编写,使用统一的中间件来完成。定义一个名为RequestInfos的中间件,在该中间件中打印请求的path和method。
具体代码实现如下所示:
func RequestInfos() gin.HandlerFunc {
return func(context *gin.Context) {
path := context.FullPath()
method := context.Request.Method
fmt.Println("请求Path:", path)
fmt.Println("请求Method:", method)
}
}
func main() {
engine := gin.Default()
engine.Use(RequestInfos())
engine.GET("/query", func(context *gin.Context) {
context.JSON(200, map[string]interface{}{
"code": 1,
"msg": context.FullPath(),
})
})
engine.Run(":9000")
}
运行程序,能够得到正确的返回JSON格式的数据:
{
"code": 1,
"msg": "/query"
}
context.Next函数
context.Next 函数可以将中间件代码的执行顺序一分为二。
context.Next 函数调用之前的代码在请求处理之前之前,当程序执行到context.Next 时,会中断向下执行,转而先去执行具体的业务逻辑,执行完业务逻辑处理函数之后,程序会再次回到context.Next 处,继续执行中间件后续的代码。具体用法如下:
func main() {
engine := gin.Default()
engine.Use(RequestInfos())
engine.GET("/query", func(context *gin.Context) {
fmt.Println("使用中间件")
context.JSON(200, map[string]interface{}{
"code": 1,
"msg": context.FullPath(),
})
})
engine.Run(":9000")
}
func RequestInfos() gin.HandlerFunc {
return func(context *gin.Context) {
path := context.FullPath()
method := context.Request.Method
fmt.Println("请求Path:", path)
fmt.Println("请求Method:", method)
context.Next()
fmt.Println(context.Writer.Status())
}
}
执行程序,输出结果如下:
请求Path: /query
请求Method: GET
使用中间件
200
|