golang之Gin框架学习
Gin框架介绍及环境搭建
Gin特点和特性:
速度:之所以被很多企业和团队所采用,第一个原因是因为其速度快,性能表现初衷;
中间件:和iris类似,Gin在处理请求时,支持中间件操作,方便编码处理;
路由:在Gin中可以非常简单的实现路由解析的功能,并包含路由组解析功能;
内置渲染:Gin支持JSON、XML和HTML等多种数据格式的渲染,并提供了方便的操作API。
安装Gin框架:
go get -u github.com/gin-gonic/gin
Hello World:
package main import ( "fmt" "github.com/gin-gonic/gin" "log" ) func main() { engine := gin.Default() engine.GET("/hello", func(context *gin.Context) { fmt.Println(" 请求路径: ", context.FullPath()) context.Writer.Write([]byte("hello world!\n")) }) if err := engine.Run(":8090"); err != nil { log.Fatal(err.Error()) } }
Gin网络请求与路由处理
engine1 = gin.Default()
engine2 = gin.New()
gin.Default()也使用gin.New()创建engine实例,但是会默认使用Logger和Recovery中间件。
Logger负责进行打印并输出日志的中间件,方便开发者进行程序调试;
Recovery中间件的作用是如果程序执行过程中遇到panic中断了服务,则Recovery会恢复程序执行,并返回服务器500内部错误。
处理HTTP请求
通用处理:
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes
httpMethod:第一个参数表示要处理的HTTP的请求类型,是GET/POST/DELETE等请求类型中的一种。
relativePath:第二个参数表示要解析的接口,由开发想着进行定义。
handlers:第三个参数是处理对应的请求的代码的定义。
如:
engine.Handle("GET", "/hello", func(context *gin.Context) { fmt.Println(context.FullPath()) userName := context.Query("name") fmt.Println(userName) context.Writer.Write([]byte("hello: " + userName)) })
Context是gin框架中封装的一个结构体,这是gin框架中最重要,最基础的一个结构体对象。该结构体可以提供我们操作请求,处理请求,获取数据等相关的操作,通常称之为上下文对象,简单说为我们提供操作环境。
可以通过context.Query和context.DefaultQuery获取GET请求携带的参数。
分类处理:
engine.GET("/hello", func(context *gin.Context) {
username := context.Query("name")
context.Writer.Write([]byte("hello world!" + username))
})
context.DefaultQuery: 除了context.DefaultQuery方法获取请求携带的参数数据以外,还可以使用context.Query方法来获取Get请求携带的参数。
engine.POST("/login", func(context *gin.Context) {
username, exist := context.GetPostForm("username")
if exist {
fmt.Println(username)
}
context.Writer.Write([]byte("hello world!" + username))
})
context.GetPostForm获取表单数据:POST请求以表单的形式提交数据,除了可以使用context.PostForm获取表单数据意外,还可以使用context.GetPostForm来获取表单数据。
engine.DELETE("/user/:id", DeleteHandle) func DeleteHandle(context *gin.Context) { userID := context.Param("id") context.Writer.Write([]byte("Delete user's id : " + userID)) }
客户端的请求接口是DELETE类型,请求url为:http://localhost:8080/user/1。最后的1是要删除的用户的id,是一个变量。因此在服务端gin中,通过路由的:id来定义一个要删除用户的id变量值,同时使用context.Param进行获取。
请求参数绑定与多数据格式处理
Gin框架提供给开发者表单实体绑定的功能,可以将表单数据与结构体绑定。
表单实体绑定
使用PostForm这种单个获取属性和字段的方式,代码量较多,需要一个一个属性进行获取。而表单数据的提交,往往对应着完整的数据结构体定义,其中对应着表单的输入项。gin框架提供了数据结构体和表单提交数据绑定的功能,提高表单数据获取的效率。
以一个用户注册功能来进行讲解表单实体绑定操作。用户注册需要提交表单数据,假设注册时表单数据包含三项,分别为:username、phone和password。
type UserRegister struct { Username string form:"username" binding:"required" Phone string form:"phone" binding:"required" Password string form:"password" binding:"required" }
ShouldBindQuery
使用ShouldBindQuery可以实现Get方式的数据请求的绑定
func main() { engine := gin.Default() // http://localhost:8080/hello?name=davie&classes=软件工程 engine.GET("/hello", func(context *gin.Context) { fmt.Println(context.FullPath()) user := User{} if err := context.ShouldBindQuery(&user); err != nil { fmt.Println("信息有误!") } fmt.Println(user.UserName) fmt.Println(user.Classes) context.Writer.Write([]byte("username: " + user.UserName + " classes: " + user.Classes)) }) engine.Run() } type User struct { UserName string `form:"name"` Classes string `form:"classes"` }
ShouldBind
使用ShouldBind可以实现Post方式的提交数据的绑定工作
func main() { engine := gin.Default() engine.POST("register", func(context *gin.Context) { fmt.Println(context.FullPath()) var register Register if err := context.ShouldBind(®ister); err != nil { fmt.Println("注册失败!") } fmt.Println(register.UserName) fmt.Println(register.Phone) fmt.Println(register.Password) context.Writer.Write([]byte("register success! " + register.UserName)) }) engine.Run() } type Register struct { UserName string `form:"name"` Phone string `form:"phone"` Password string `form:"password"` }
ShouldBindJson
当客户端使用Json格式进行数据提交时,可以采用ShouldBindJson对数据进行绑定并自动解析
func main() { engine := gin.Default() engine.POST("/addstudent", func(context *gin.Context) { fmt.Println(context.FullPath()) var person Person if err := context.ShouldBindJSON(&person); err !=nil { //if err := context.BindJSON(&person); err != nil { fmt.Println("失败!") } fmt.Println(person.Name) fmt.Println(person.Sex) fmt.Println(person.Age) //context.Writer.Write([]byte(" 添加记录:" + person.Name)) context.Writer.WriteString(context.FullPath()) }) engine.Run() } type Person struct { Name string `form:"name"` Sex string `form:"sex"` Age int `form:"age"` }
gin还支持其他多种方式
多数据格式返回请求结果
在gin框架中,支持返回多种请求数据格式
[]byte
engine := gin.Default() engine.GET("/hello", func(context *gin.Context) { fullPath := "请求路径:" + context.FullPath() fmt.Println(fullPath) context.Writer.Write([]byte(fullPath)) }) engine.Run()
使用context.Writer.Write向客户端写入返回数据。Writer是gin框架中封装的一个ResponseWriter接口类型,其中的write方法就是http.ResponseWriter中包含的方法。
type ResponseWriter interface { http.ResponseWriter http.Hijacker http.Flusher http.CloseNotifier // Returns the HTTP response status code of the current request. Status() int // Returns the number of bytes already written into the response http body. // See Written() Size() int // Writes the string into the response body. WriteString(string) (int, error) // Returns true if the response body was already written. Written() bool // Forces to write the http header (status code + headers). WriteHeaderNow() // get the http.Pusher for server push Pusher() http.Pusher }
string
除了write方法以外,ResponseWriter自身还封装了WriteString方法返回数据
// Writes the string into the response body.
WriteString(string) (int, error)
和[]byte类型调用一样,可以通过Writer进行调用
engine.GET("/hello", func(context *gin.Context) { fullPath := "请求路径:" + context.FullPath() fmt.Println(fullPath) context.Writer.WriteString(fullPath) })
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")
调用JSON将map类型的数据转换成为json格式并返回给前端,第一个参数200表示设置请求返回的状态码。和http请求的状态码一致。
结构体类型
除了map以外,结构体也是可以直接转换为JSON格式进行返回的
//通用请求返回结构体定义 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格式的数据
engine := gin.Default() //设置html的目录 engine.LoadHTMLGlob("./html/*") engine.GET("/hellohtml", func(context *gin.Context) { fullPath := "请求路径:" + context.FullPath() context.HTML(http.StatusOK, "index.html", gin.H{ "title": "Gin框架", "fullpath": fullPath, }) }) engine.Run()
加载静态资源文件
如果需要再页面是添加一张img,需要将img所在的目录进行静态资源路径设置才可能会生效:
engine.Static("/img", "./img")
同理,在项目开发时,一些静态的资源文件如html、js、css等可以通过静态资源文件设置的方式来进行设置
使用路由组分类处理请求
在实际的项目开发中,均是模块化开发。同一模块内的功能接口,往往会有相同的接口前缀
注册:http://localhost:9000/user/register 登录:http://localhost:9000/user/login 用户信息:http://localhost:9000/user/info 删除:http://localhost:9000/user/1001
类似这种接口前缀统一,均属于相同模块的功能接口。可以使用路由组进行分类处理。
Group
gin框架中可以使用路由组来实现对路由的分类。
路由组是router.Group中的一个方法,用于对请求进行分组。
engine := gin.Default() userGroup := engine.Group("/user") userGroup.POST("/register", registerHandle) userGroup.POST("/login", loginHandle) userGroup.GET("/info", infoHandle) engine.Run()
Group返回一个RouterGroup指针对象,而RouterGroup是gin框架中的一个路由组结构体定义。
type RouterGroup struct { Handlers HandlersChain basePath string engine *Engine root bool }
RouterGroup实现了IRoutes中定义的方法,包含统一处理请求的Handle和分类型处理的GET、POST等。
type IRoutes interface {
Use(...HandlerFunc) IRoutes
Handle(string, string, ...HandlerFunc) IRoutes
Any(string, ...HandlerFunc) IRoutes
GET(string, ...HandlerFunc) IRoutes
POST(string, ...HandlerFunc) IRoutes
DELETE(string, ...HandlerFunc) IRoutes
PATCH(string, ...HandlerFunc) IRoutes
PUT(string, ...HandlerFunc) IRoutes
OPTIONS(string, ...HandlerFunc) IRoutes
HEAD(string, ...HandlerFunc) IRoutes
StaticFile(string, string) IRoutes
Static(string, string) IRoutes
StaticFS(string, http.FileSystem) IRoutes
}
middleware的编写与使用
中间件
在web应用服务中,完整的一个业务处理在技术上包含客户端操作、服务器端处理、返回处理结果给客户端三个步骤。
在实际的业务开发和处理中,会有更负责的业务和需求场景。一个完整的系统可能要包含鉴权认证、权限管理、安全检查、日志记录等多维度的系统支持。
鉴权认证、权限管理、安全检查、日志记录等这些保障和支持系统业务属于全系统的业务,和具体的系统业务没有关联,对于系统中的所有业务都适用。
由此,在业务开发过程中,为了更好的梳理系统架构,可以将上述描述所涉及的一些通用业务单独抽离并进行开发,然后以插件化的形式进行对接。这种方式既保证了系统功能的完整,同时又有效的将具体业务和系统功能进行解耦,并且,还可以达到灵活配置的目的。
这种通用业务独立开发并灵活配置使用的组件,一般称之为"中间件",因为其位于服务器和实际业务处理程序之间。其含义就是相当于在请求和具体的业务逻辑处理之间增加某些操作,这种以额外添加的方式不会影响编码效率,也不会侵入到框架中。
Gin的中间件
在gin中,中间件称之为middleware
// HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context)
HandlerFunc是一个函数类型,接收一个Context参数。用于编写程序处理函数并返回HandleFunc类型,作为中间件的定义。
中间件Use用法
使用gin.Default创建了gin引擎engins变量,其中,就使用了中间件。
func Default() *Engine { debugPrintWARNINGDefault() engine := New() engine.Use(Logger(), Recovery()) return engine } //Log中间件 func Logger() HandlerFunc { return LoggerWithConfig(LoggerConfig{}) } //Recovery中间件 func Recovery() HandlerFunc { return RecoveryWithWriter(DefaultErrorWriter) }
在Default函数中,engine调用Use方法设置了Logger中间件和Recovery中间件。Use函数接收一个可变参数,类型为HandlerFunc,恰为中间件的类型。
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { engine.RouterGroup.Use(middleware...) engine.rebuild404Handlers() engine.rebuild405Handlers() return engine }
自定义中间件
根据上文的介绍,可以自己定义实现一个特殊需求的中间件,中间件的类型是函数,有两条标准:
func函数
返回值类型为HandlerFunc
func main() { engine := gin.Default() //engine.Use(RequestInfos()) engine.GET("/hello", RequestInfos(), func(context *gin.Context) { fmt.Println("中间件的使用方法") context.JSON(200, map[string]interface{}{ "code": 1, "msg": "very good!", }) }) engine.POST("/register", func(context *gin.Context) { context.JSON(200, map[string]interface{}{ "code": 233, "msg": "you is dog!", }) }) engine.Run() } func RequestInfos() gin.HandlerFunc { return func(context *gin.Context) { path := context.FullPath() method := context.Request.Method fmt.Println("请求url:", path) fmt.Println("请求method:", method) context.Next() fmt.Println("状态码:", context.Writer.Status()) } }
context.Next函数
在上文自定义的中间件RequestInfos中,打印了请求了请求的path和method,接着去执行了正常的业务处理函数。如果我们想输出业务处理结果的信息,该如何实现呢。答案是使用context.Next函数。
context.Next函数可以将中间件代码的执行顺序一分为二,Next函数调用之前的代码在请求处理之前之前,当程序执行到context.Next时,会中断向下执行,转而先去执行具体的业务逻辑,执行完业务逻辑处理函数之后,程序会再次回到context.Next处,继续执行中间件后续的代码。
Next函数的作用及代码执行流程示意图如下图所示:
- 1、程序先执行①和②。
- 2、执行到③时,转而去执行业务处理程序。
- 3、返回到中间件中,执行④。
Gin框架中使用数据库
Gin链接和使用MySQL数据库
1、安装MySQL驱动
go get "github.com/go-sql-driver/mysql"
2、创建数据库
mysql -uroot -p
create database ginsql;
3、在gin中使用编程语言进行连接数据库
在gin中连接和操作mysql与在其他框架中连接操作mysql没有什么区别
a、引入mysql驱动程序:使用import将mysql驱动默认引入
import _ "github.com/go-sql-driver/mysql"
b、拼接链接字符:在程序中链接mysql,需要按照一定的规则进行用户名,密码等信息的组合
connStr := "root:password@tcp(127.0.0.1:3306)/ginsql"
c、使用sql.Open创建数据库连接
db, err := sql.Open("mysql", connStr) if err != nil { log.Fatal(err.Error()) return }