1. Introduction to Gin
Gin is an HTTP web framework written in Go . It is an API framework similar to martini but with better performance, better than httprouter and nearly 40 times faster. Click here to access Gin official Chinese documentation.
2. Installation
1. Install Gin
go get -u github.com/gin-gonic/gin
2. Introduced into the code
import "github.com/gin-gonic/gin"
3. Simple example, verification
Create a new file main.go with the following content:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// 1.创建路由
r := gin.Default()
// 2.绑定路由规则,执行的函数
// gin.Context,封装了request和response
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "hello World!")
})
// 3.监听端口,默认在8080
// Run("")里面不指定端口号默认为8080
r.Run(":8000")
}
After running, visit: http://localhost:8000/
3. Cross-domain rendering front-end and configuration
1. Render html template
Gin supports loading HTML templates, then configuring them according to template parameters and returning corresponding data, which is essentially string replacement. The LoadHTMLGlob() method can load the template file (the parameter is the html template file to be rendered, if the parameter is a relative path, it is the relative path of the running path).
a. Render a single file
r.LoadHTMLGlob("web/index.html")
b. Render all files in the folder
r.LoadHTMLGlob("web/*")
c. Render all files with html suffix in the folder.
For example:
r.LoadHTMLGlob("web/*.html")
2. Define template segmentation
r.Delims("<<<", ">>>")
The first parameter is: template tag start tag.
The second parameter is: template tag end tag.
3. Render static files and directories
If you need to introduce static files, you need to define a static file directory
r.Static("/assets", "./static/assets")
If the directory structure of assets is
The specified
resource
If r is a routing group, the path of the routing group needs to be spliced before assets including its prefix.
4. Redirect
// 重定向两种默认应支持的首页访问方式
router.GET("/", func(c *gin.Context) {
//重定向到/index.html
c.Redirect(302, "/index.html")
})
router.GET("/index", func(c *gin.Context) {
//重定向到/index.html
c.Redirect(302, "/index.html")
})
router.GET("/index.html", func(c *gin.Context) {
//返回渲染的html模板中的index.html
c.HTML(http.StatusOK, "index.html", gin.H{
"baseUrl": "http://" + host,
})
})
5. Configure cross-domain
Next()
r.Use(Next())
Allow cross domain
// 允许跨域
func Next() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Headers", "Access-Control-Allow-Headers,Authorization,User-Agent, Keep-Alive, Content-Type, X-Requested-With,X-CSRF-Token,AccessToken,Token")
c.Header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, PATCH, OPTIONS")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")
// 放行所有OPTIONS方法
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusAccepted)
}
c.Next()
}
}
6. Response format c.String() c.JSON() c.JSONP() c.XML() c.HTML()
(1) Return a string
r.GET("/news", func(c *gin.Context) {
aid := c.Query("aid")
c.String(200, "aid=%s", aid)
})
(2) Return a JSON data
//方法一:自己拼接JSON
r.GET("/json", func(c *gin.Context) {
//返回json数据,使用 map[string]interface
//c.JSON(返回的状态码, 任意类型的数据(如:map,struct,...)
c.JSON(200, map[string]interface{}{
"success": true,
"msg": "你好",
})
})
//方法二:gin中的H函数
r.GET("/json2", func(c *gin.Context) {
//返回json数据,使用gin中的H函数, gin.H 是 map[string]interface{}的缩写
c.JSON(200, gin.H{
"success": true,
"msg": "你好gin",
})
})
//方法三:使用结构体
r.GET("/json3", func(c *gin.Context) {
//实例化一个结构体
a := &Article{
Title: "标题",
Desc: "说明",
Content: "内容",
}
c.JSON(200, a)
})
JSOPN: jsonp requests are mainly used to solve cross-domain problems
//jsonp请求 主要用来解决跨域问题
//http://127.0.0.1:8080/jsonp?callback=call
//call({"title":"标题-jsonp","desc":"说明-jsonp","content":"内容-jsonp"});
r.GET("/jsonp", func(c *gin.Context) {
//实例化一个结构体
a := &Article{
Title: "标题-jsonp",
Desc: "说明-jsonp",
Content: "内容-jsonp",
}
c.JSONP(200, a)
})
(3) Return XML data
//方法一:使用gin.H返回
r.GET("/xml", func(c *gin.Context) {
c.XML(http.StatusOK, gin.H{
"success": true,
"msg": "成功xml",
})
})
//方法二:使用结构体
r.GET("/xmlStruct", func(c *gin.Context) {
//实例化一个结构体
a := &Article{
Title: "标题-xmlStruct",
Desc: "说明-xmlStruct",
Content: "内容-xmlStruct",
}
c.XML(200, a)
})
(4) Return HTML data
//初始化路由
r := gin.Default()
//加载templates文件中所有模板文件,以便后续c.HTML()渲染文件时使用
r.LoadHTMLGlob("templates/*")
r.GET("/news", func(c *gin.Context) {
//使用模板文件渲染HTML文件
//前提: r.LoadHTMLGlob("templates/*")
//HTML(状态码, 要渲染的文件名, 加载的参数)
c.HTML(http.StatusOK, "news.html", gin.H{
"title": "我是一个news",
})
})
The complete code example is as follows:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type Article struct {
Title string `json:"title"`
Desc string `json:"desc"`
Content string `json:"content"`
}
func main() {
//初始化路由
r := gin.Default()
//加载templates文件中所有模板文件,以便后续c.HTML()渲染文件时使用
r.LoadHTMLGlob("templates/*")
//配置路由
r.GET("/", func(c *gin.Context) {
c.String(200, "首页")
})
r.GET("/json", func(c *gin.Context) {
//返回json数据,使用 map[string]interface
//c.JSON(返回的状态码, 任意类型的数据(如:map,struct,...)
c.JSON(200, map[string]interface{}{
"success": true,
"msg": "你好",
})
})
r.GET("/json2", func(c *gin.Context) {
//返回json数据,使用gin中的H函数
c.JSON(200, gin.H{
"success": true,
"msg": "你好gin",
})
})
r.GET("/json3", func(c *gin.Context) {
//实例化一个结构体
a := &Article{
Title: "标题",
Desc: "说明",
Content: "内容",
}
c.JSON(200, a)
})
//jsonp请求 主要用来解决跨域问题
//http://127.0.0.1:8080/jsonp?callback=call
//call({"title":"标题-jsonp","desc":"说明-jsonp","content":"内容-jsonp"});
r.GET("/jsonp", func(c *gin.Context) {
//实例化一个结构体
a := &Article{
Title: "标题-jsonp",
Desc: "说明-jsonp",
Content: "内容-jsonp",
}
c.JSONP(200, a)
})
r.GET("/xml", func(c *gin.Context) {
c.XML(http.StatusOK, gin.H{
"success": true,
"msg": "成功xml",
})
})
r.GET("/news", func(c *gin.Context) {
//使用模板文件渲染HTML文件
//前提: r.LoadHTMLGlob("templates/*")
//HTML(状态码, 要渲染的文件名, 加载的参数)
c.HTML(http.StatusOK, "news.html", gin.H{
"title": "我是一个news",
})
})
r.GET("/goods", func(c *gin.Context) {
//使用模板文件渲染HTML文件
//前提: r.LoadHTMLGlob("templates/*")
//HTML(状态码, 要渲染的文件名, 加载的参数)
c.HTML(http.StatusOK, "goods.html", gin.H{
"title": "我是一个goods",
"price": 12.99,
})
})
r.Run() // 启动一个web服务
}
4. Routing related
1. Create a routing group
// 创建路由组
// 根据需要,可以为这种多级的路由组:r.Group("/v1/user")
userApi:= r.Group("/user")
// 创建用户
// 匹配POST请求的 /user
userApi.POST("", userCreate)
// 修改用户
// 匹配PUT请求的 /user/1 但不会匹配 /user/ 或者 /user
userApi.PUT("/:id", userUpdate)
// 获取用户
// 匹配GET请求的 /user/1 但不会匹配 /user/ 或者 /user
userApi.GET("/:id", userGet)
// 查询用户
// 匹配GET请求的 /user/list
userApi.GET("/list", userQuery)
// 删除用户
// 匹配DELETE请求的 /user/1 但不会匹配 /user/ 或者 /user
userApi.DELETE("/:id", userDelete)
Restful style API
2. Get routing parameters
a. api parameters
Get api parameters through Context's Param method
userApi.GET("/:id/:name", userGet)
The first parameter: Get the value of url path parameters id and name
Second parameter: userGet function
func userGet(ctx *gin.Context) {
//api参数可以为单个或者多个也可以拼接多级
//ctx.Param()函数获取时参数需要与api中的名称一致才能获取到
id := ctx.Param("id")
name:= ctx.Param("name")
ctx.JSON(http.StatusOK, gin.H{"data": id,"name":name})
return
}
http://localhost:8000/user/1/admin
b. url parameters
Get url parameters through Context's Query method and DefaultQuery method
userApi.GET("/list", userQuery)
userQuery method
func userQuery(ctx *gin.Context) {
//获取
id := ctx.Query("id")
//获取,第二个参数为获取为空的默认值,如果参数不存在则放回第二个参数
name := ctx.DefaultQuery("name", "user")
ctx.JSON(http.StatusOK, gin.H{"data": id, "name": name})
return
}
Note: The default value is when the client does not bring this parameter in the request, the server will take the name and the default value is "user"
You can access this interface by splicing query parameters behind http://localhost:8000/user/list.
If the client does not pass parameters, the default value will be used.
If the client has passed parameters, the default value will not be taken.
c. Form parameters
To test form parameters and observe request effects, you need to install postman
to obtain form parameters through the PostForm method of Context.
userApi.POST("", userCreate)
The userCreate function is
func userCreate(ctx *gin.Context) {
id := ctx.PostForm("id")
name := ctx.PostForm("name")
ctx.JSON(http.StatusOK, gin.H{"data": id, "name": name})
return
}
As shown in the figure, access this interface
d. json parameters
To test json parameters and observe request effects, you need to install postman to obtain form parameters through the GetRawData
or ShouldBindJSON method of Context.
userApi.PUT("/:id", userUpdate)
userUpdate function is
GetRawData方法
func userUpdate(ctx *gin.Context) {
b, err := ctx.GetRawData() // 从ctx.Request.Body读取请求数据
if err !=nil{
fmt.print(err)
}
// 定义map或结构体
var m map[string]interface{}
// 反序列化
_ = json.Unmarshal(b, &m)
ctx.JSON(http.StatusOK, gin.H{"data": m["id"], "name": m["name"]})
return
}
ShouldBindJSON method
// 先定义结构
type User struct {
Id string `form:"id" json:"id" binding:"required"`
Name string `form:"name" json:"name" binding:"required"`
}
// 函数实现
func userUpdate(ctx *gin.Context) {
var user User
if err := ctx.ShouldBindJSON(&user); err == nil {
ctx.JSON(http.StatusOK, gin.H{"data": user.Id, "name": user.Name})
return
} else {
ctx.JSON(http.StatusOK, gin.H{"err": err.Error()})
return
}
}
e. Parameter binding
In order to obtain request-related parameters more conveniently and improve development efficiency, we can identify the request data type based on the Content-Type of the request and use the reflection mechanism to automatically extract form, JSON, XML and other parameters in the request into the structure.
Code example:
Define the structure to be obtained
type User struct {
Id string `form:"id" json:"id" binding:"required"`
Name string `form:"name" json:"name" binding:"required"`
}
userCreate function
func userCreate(ctx *gin.Context) {
// 实例化结构体对象
var user User
if err := ctx.ShouldBind(&user); err == nil {
// 响应体 json 格式
ctx.JSON(http.StatusOK, gin.H{"data": user.Id, "name": user.Name})
return
}else{
ctx.JSON(http.StatusOK, gin.H{"err": err.Error()})
return
}
}
userUpdate function
func userUpdate(ctx *gin.Context) {
// 实例化结构体对象
var user User
if err := ctx.ShouldBind(&user); err == nil {
ctx.JSON(http.StatusOK, gin.H{"data": user.Id, "name": user.Name})
return
}else{
ctx.JSON(http.StatusOK, gin.H{"err": err.Error()})
return
}
}
3. Upload files
Generally, parameters are passed in the post request form.
userApi.POST("/upload", userUpload)
userUpload function
// 先定义结构
type FileUpload struct {
File *multipart.FileHeader `form:"file"`
Type string `form:"type"`
}
// 函数实现
func userUpload(ctx *gin.Context) {
var fileUpload FileUpload
//
if err := ctx.ShouldBind(&fileUpload); err == nil {
//获取运行路径
ex, err := os.Executable()
//
if err != nil {
ctx.JSON(http.StatusOK, gin.H{"err": err.Error()})
return
}
//定义接收文件的存放地址
path := filepath.Dir(ex) + string(os.PathSeparator) + fileUpload.File.Filename
//接收文件并保存到指定path
err = ctx.SaveUploadedFile(fileUpload.File, path)
ctx.JSON(http.StatusOK, gin.H{"data": path, "type": fileUpload.Type})
return
} else {
ctx.JSON(http.StatusOK, gin.H{"err": err.Error()})
return
}
}
5. Routing group file
When using the Gin framework to write Go projects, if all routes are written in a .go file, as the number of routes increases, the routing file will become extremely bloated.
At this time, routes need to be grouped and managed in files, which is more conducive to code organization and maintenance.
When using the Gin framework to write Go projects, if all routes are written in a .go file, as the number of routes increases, the routing file will become extremely bloated.
At this time, routes need to be grouped and managed in files, which is more conducive to code organization and maintenance.
Implementation of routing grouping
Original ungrouped file
func main() {
router := gin.Default()
router.POST("/one/a", OneAFunc)
router.POST("/one/b", OneBFunc)
router.POST("/one/c", OneCFunc)
router.POST("/one/d", OneDFunc)
router.POST("/one/e", OneEFunc)
router.POST("/one/f", OneFFunc)
router.POST("/one/g", OneGFunc)
router.POST("/one/h", OneHFunc)
router.POST("/one/i", OneIFunc)
/*
省略n条路由
*/
router.POST("/one/x", OneXFunc)
router.POST("/one/y", OneYFunc)
router.POST("/one/z", OneZFunc)
router.POST("/two/a", TwoAFunc)
router.POST("/two/b", TwoBFunc)
router.POST("/two/c", TwoCFunc)
router.POST("/two/d", TwoDFunc)
router.POST("/two/e", TwoEFunc)
router.POST("/two/f", TwoFFunc)
router.POST("/two/g", TwoGFunc)
router.POST("/two/h", TwoHFunc)
router.POST("/two/i", TwoIFunc)
/*
省略n条路由
*/
router.POST("/two/x", TwoXFunc)
router.POST("/two/y", TwoYFunc)
router.POST("/two/z", TwoZFunc)
router.Run(":8080")
}
Route logical grouping
We first create a routing group one with the prefix /one.
Then, the POST method is used to define multiple routing processing functions oneAFunc to oneZFunc in the routing group one, which handle the HTTP POST requests of the /two/a to /two/z routes respectively.
Request path
/one/a/
/tow/a/
// 路由分组第一组
one := router.Group("/one")
{
one.POST("/a", OneAFunc)
one.POST("/b", OneBFunc)
one.POST("/c", OneCFunc)
one.POST("/d", OneDFunc)
one.POST("/e", OneEFunc)
one.POST("/f", OneFFunc)
one.POST("/g", OneGFunc)
one.POST("/h", OneHFunc)
one.POST("/i", OneIFunc)
/*
省略n条路由
*/
one.POST("/x", OneXFunc)
one.POST("/y", OneYFunc)
one.POST("/z", OneZFunc)
}
//路由分组第二组
two := router.Group("/two")
{
two.POST("/a", twoAFunc)
two.POST("/b", twoBFunc)
two.POST("/c", twoCFunc)
two.POST("/d", twoDFunc)
two.POST("/e", twoEFunc)
two.POST("/f", twoFFunc)
two.POST("/g", twoGFunc)
two.POST("/h", twoHFunc)
two.POST("/i", twoIFunc)
/*
省略n条路由
*/
two.POST("/x", twoXFunc)
two.POST("/y", twoYFunc)
two.POST("/z", twoZFunc)
}
After grouping the go code, although the routes implement route grouping and the routing logic appears clear, the routing files are still concentrated in one file and the file is still large.
Routing is grouped and divided into files for management
File structure
main.go
handlers/
├── one_handlers.go
└── two_handlers.go
main.go
import (
"github.com/gin-gonic/gin"
"your_project/routers"
)
func main() {
// 创建路由
router := gin.Default()
//
routers.SetupRouter(router)
// 路由分组第一组
routers.TwoRoutersInit(router)
// 路由分组第二组
routers.OneRoutersInit(router)
// 启动服务
router.Run(":8080")
}
TwoRoutersInit() and OneRoutersInit() correspond to the following two files:
one_handlers.go
package routers
import "github.com/gin-gonic/gin"
func OneRoutersInit(engine *gin.Engine) {
one := engine.Group("/one")
{
one.POST("/a", OneAFunc)
one.POST("/b", OneBFunc)
one.POST("/c", OneCFunc)
one.POST("/d", OneDFunc)
one.POST("/e", OneEFunc)
one.POST("/f", OneFFunc)
/*
省略n条路由
*/
one.POST("/x", OneXFunc)
one.POST("/y", OneYFunc)
one.POST("/z", OneZFunc)
}
}
func OneAFunc(context *gin.Context) {
}
/*
省略N多方法
*/
func OneZFunc(context *gin.Context) {
}
two_handlers.go
package routers
import "github.com/gin-gonic/gin"
func TwoRoutersInit(engine *gin.Engine) {
two := engine.Group("/two")
{
two.POST("/a", twoAFunc)
two.POST("/b", twoBFunc)
two.POST("/c", twoCFunc)
two.POST("/d", twoDFunc)
two.POST("/e", twoEFunc)
two.POST("/f", twoFFunc)
two.POST("/g", twoGFunc)
two.POST("/h", twoHFunc)
two.POST("/i", twoIFunc)
/*
省略n条路由
*/
two.POST("/x", twoXFunc)
two.POST("/y", twoYFunc)
two.POST("/z", twoZFunc)
}
}
func twoAFunc(context *gin.Context) {
}
/*
省略n多方法
*/
func twoZFunc(context *gin.Context) {
}
Note: Each route must be placed in {}, and the method corresponding to each route has a fixed context *gin.Context, where the context wind state request and response
Official example:
main.go
// main.go
package main
import (
"github.com/gin-gonic/gin"
"your_project/routers"
)
func main() {
r := gin.Default()
routers.SetupRouter(r)
r.Run() // listen and serve on 0.0.0.0:8080
}
routers/router.go
package routers
import (
"github.com/gin-gonic/gin"
)
func SetupRouter(r *gin.Engine) {
v1 := r.Group("/v1")
{
v1.GET("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "hello",
})
})
}
v2 := r.Group("/v2")
{
v2.GET("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "hello v2",
})
})
}
}
6. Middleware
1. Unified registration middleware
Register middleware uniformly for global routing or registered routing groups
r.Use(CheckToken())
2. Register middleware separately
userApi.POST("/upload",CheckToken(),userUpload)
3. Middleware function implementation
func CheckToken() gin.HandlerFunc {
return func(c *gin.Context) {
// 验证不通过直接跳出
//c.JSON(http.StatusBadRequest, gin.H{"msg": "need token"})
//c.Abort()
//return
// 验证通过继续
c.Next()
}
}
Middleware scenario
Go Gin JWT
https://blog.csdn.net/u013302168/article/details/132178429
7. How to use go module to import packages in vscode
Go version management uses go module
(1) Suppose we now have two packages moduleA and moduleB
The moduleA package will import the moduleB package and use its New method.
(Note: Function names can only be quoted if they start with a capital letter)
The contents of moduleB/moduleB.go are as follows:
package moduleB
import "fmt"
func New(){
fmt.Println("moduleB.New")
}
1. Discuss in two situations:
(1) Under the same project
Note: We can define multiple packages under one project.
The directory structure is as follows:
Then create the mod of moduleA, open it in the integrated terminal, and enter it on the command line
go mod init moduleA
After execution, a mod.go file will be generated in the project root directory, and execute go mod tidy to install dependencies.
The content of moduleA’s go.mod is as follows:
module moduleA
go 1.17
The main.go content of moudleA is as follows:
package main
import (
"fmt"
"moduleA/moduleB"
)
func main() {
moduleB.New()
fmt.Println("main")
}
(2) Executed under different projects
Here we have completed the first step, and now we have come to the second step;
First, pull the code of moduleB out, and let’s implement references to packages under different projects.
At this time, there is no mod file in moduleB's package (folder), so you need to mod init yourself.
Open the terminal and enter at the command line
go mod init moduleB
At this time, moduleB’s go.mod should be:
module moduleA
go 1.17
require "moduleB" v0.0.0
replace "moduleB" => "../moduleB"
The moduleB.go file in moduleB does not move and is the same as above.
Because these two packages are not in the same project path, you want to import the local package, and these packages are not published to a remote github or other code repository address.
At this time we need to use the replace directive in the go.mod file.
In the caller, that is, moduleA/go.mod, specify the relative path to find the moduleB package as follows. At this time, the go.mod file of moduleA should be:
module moduleA
go 1.17
require "moduleB" v0.0.0
replace "moduleB" => "../moduleB"
The main.go file of moduleA should be:
package main
import (
"fmt"
"moduleB"
)func main() {
moduleB.New()
fmt.Println("main")
}