1. 简介 1.1. 介绍
  • Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,具有快速灵活,容错方便等特点
  • 对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的 net/http 足够简单,性能也非常不错
  • 借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范
1.2. 安装

使用Mod依赖管理工具,在项目根目录执行 :

 go mod init
  

然后项目根目录下会生成 go.mod文件,修改文件添加gin相关依赖

 require github.com/gin-gonic/gin v1.6.3
  

运行项目时会自动下载相关依赖。

1.3. 第一个程序Hello World!
 package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func main() {
    //1.创建路由
    r := gin.Default()
    //2.绑定路由规则,执行的函数
    r.GET("/", func(context *gin.Context) {
        context.String(http.StatusOK, "Hello World!")
    })
    //3.监听端口,默认8080
    r.Run(":8080")
}
  

编译运行,项目会自动下载gin依赖。在浏览器输入 ,出现以下页面说明运行成功:

2 Gin的路由 2.1基本路由
  • gin 框架中采用的路由库是基于httprouter做的
  • 地址为:
 func main() {

    r := gin.Default()
    r.GET("/", func(context *gin.Context) {
        context.String(http.StatusOK, "Hello")
    })
    r.POST("/xxxpost",func())
    r.PUT("/xxxput",func())

    r.Run(":8080")
}
  
2.1 Restful风格的API
  • gin支持Restful风格的API
  • 即Representational State Transfer的缩写。直接翻译的意思是”表现层状态转化”,是一种互联网应用程序的API设计理念:URL定位资源,用HTTP描述操作。如:

1.获取文章 /blog/getXxx Get blog/Xxx

2.添加 /blog/addXxx POST blog/Xxx

3.修改 /blog/updateXxx PUT blog/Xxx

4.删除 /blog/delXxxx DELETE blog/Xxx

2.2 API参数
  • 可以通过Context的Param方法获取API参数
  • localhost:8080/xxx/you
 func main() {
    r := gin.Default()
    r.GET("user/:name/*action", func(context *gin.Context) {
        name := context.Param("name")
        action := context.Param("action")

        fmt.Println(action)
        //  截取/
        action = strings.Trim(action, "/")
        context.String(http.StatusOK, name+" is "+action)
    })

    r.Run(":8080")
}
  
2.3 URL参数
  • URL参数可以通过DefaultQuery()或Query()方法获取
  • DefaultQuery()若参数不村则,返回默认值,Query()若不存在,返回空串

go func main() { r := gin.Default() r.GET(“user”, func(context *gin.Context) { //指定默认值 // name := context.DefaultQuery(“name”, “you”) context.String(http.StatusOK, fmt.Sprintf(“hello %s”, name)) }) //默认为:8080 r.Run() }

编译,请求输出如下:

不带参数,显示默认参数:

带参数 name=thinks:

2.4 表单参数
  • 表单传输为post请求,http常见的传输格式为四种:
  • application/json
  • application/x-www-form-urlencoded
  • application/xml
  • multipart/form-data
  • 表单参数可以通过PostForm()方法获取,该方法默认解析的是x-www-form-urlencoded或from-data格式的参数

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="#34; method="post">
    用户名:<input type="text" name="username" placeholder="请输入你的用户名"> <br>
    密   码:<input type="password" name="password" placeholder="请输入你的密码"> <br>
    <input type="submit" value="提交">
</form>
</body>
</html>  

Go语言代码如下:

 func main() {

    r := gin.Default()
    r.POST("/form", func(context *gin.Context) {
        types := context.DefaultPostForm("type", "post")
        username := context.PostForm("username")
        password := context.PostForm("password")

        context.String(http.StatusOK, fmt.Sprintf("username:%s , password:%s , types:%s", username, password, types))
    })

    r.Run()
}
  

编译请求输出:

2.5 单文件上传
  • multipart/form-data格式用于文件上传
  • gin文件上传与原生的net/http方法类似,不同在于gin把原生的request封装到c.Request中

代码如下:

 <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>单文件上传</title>
</head>
<body>
<form action="#34; method="post" enctype="multipart/form-data">
    上传文件:<input type="file" name="file">
    <input type="submit" value="提交">
</form>

</body>
</html>
func main() {

    r := gin.Default()
    //限制上传文件大小  8M
    r.MaxMultipartMemory = 8 << 20
    r.POST("/upload", func(context *gin.Context) {
        file, err := context.FormFile("file")
        if err != nil {
            context.String(500, "上传图片出错")
        }
        // c.JSON(200, gin.H{"message": file.Header.Context})
        context.SaveUploadedFile(file, file.Filename)
        context.String(http.StatusOK, file.Filename)
    })

    r.Run()
}  

运行效果如下:

2.6 多文件上传

HTML页面加入multiple关键字

 <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>多文件上传</title>
</head>
<body>
<form action="#34; method="post" enctype="multipart/form-data">
    上传文件:<input type="file" name="files" multiple>
    <input type="submit" value="提交">
</form>

</body>
</html>  

go语言代码

 func main() {
    // 创建路由
    r := gin.Default()
    // 限制表单上传大小8MB,默认为32MB
    r.MaxMultipartMemory = 8 << 20
    r.POST("/upload", func(context *gin.Context) {
        form, err := context.MultipartForm()
        if err != nil {
            context.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error()))
        }
        //获取所有文件
        files := form.File["files"]
        //遍历所有文件
        for _, file := range files {
            if err := context.SaveUploadedFile(file, file.Filename); err != nil {
                context.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error()))
                return
            }
        }

        context.String(200, fmt.Sprintf("upload ok %d files", len(files)))
    })

    r.Run(":8080")
}
  

效果展示:

2.7 routes group
  • routes group是为了管理一些相同的URL

举个例子:

 func main() {

    //创建路由
    r := gin.Default()
    //路由组1,处理get请求
    v1 := r.Group("/v1")
    {
        v1.GET("/login", login)
        v1.GET("/submit", submit)
    }
    v2 := r.Group("/v2")
    {
        v2.POST("/login", login)
        v2.POST("/submit", submit)
    }
    r.Run(":8080")
}

func login(c *gin.Context) {
    name := c.DefaultQuery("name", "jack")
    c.String(200, fmt.Sprintf("hello %s\n", name))
}

func submit(c *gin.Context) {
    name := c.DefaultQuery("name", "lily")
    c.String(200, fmt.Sprintf("hello %s\n", name))
}
  

在浏览器中分别输入:

输出 hello you

使用windows自带的curl工具进行post请求,在cmd中输入

curl -X POST

输出 hello jack

2.8 路由拆分与注册 2.8.1 基本的路由注册

下面最基础的gin路由注册方式,适用于路由条目比较少的简单项目或者项目demo。以上列子都是基本路由注册

 func helloHandler(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "message": "Hello www.topgoer.com!",
    })
}

func main() {
    r := gin.Default()
    r.GET("/topgoer", helloHandler)
    if err := r.Run(); err != nil {
        fmt.Println("startup service failed, err:%v\n", err)
    }
}
  
2.8.2 路由拆分成单独的文件或包

当项目的规模增大后就不太适合继续在项目的main.go文件中去实现路由注册相关逻辑了,我们会倾向于把路由部分的代码都拆分出来,形成一个单独的文件或包。

我我们在 routers.go 文件中定义并注册路由信息:

 package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func helloHandler(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "message": "Hello Gin",
    })
}

func setupRouter() *gin.Engine {
    r := gin.Default()
    r.GET("/user", helloHandler)
    return r
}
  

此时main.go中调用上面定义好的setupRouter函数:

 func main() {
    r := setupRouter()
    if err := r.Run(); err != nil {
        fmt.Println("startup service failed, err:%v\n", err)
    }
}
  

此时的目录结构:

 LearnGin
├── go.mod
├── go.sum
├── main.go
└── routers.go
  

把路由部分的代码单独拆分成包的话也是可以的,拆分后的目录结构如下:

 LearnGin
├── go.mod
├── go.sum
├── main.go
└── routers
    └── routers.go
  

routers/routers.go 需要注意此时setupRouter需要改成首字母大写:

 // SetupRouter 配置路由信息
func SetupRouter() *gin.Engine {
    r := gin.Default()
    r.GET("/topgoer", helloHandler)
    return r
}
  
2.8.3 路由拆分成多个文件

当我们的业务规模继续膨胀,单独的一个routers文件或包已经满足不了我们的需求了,

 func SetupRouter() *gin.Engine {
    r := gin.Default()
    r.GET("/user", helloHandler)
    r.GET("/login", xxHandler1)
    ...
    r.GET("/register", xxHandler30)
    return r
}
  

因为我们把所有路由注册都写在一个SetupRouter函数中的话就会太复杂了。

我们可以分开定义多个路由文件,例如

 LearnGin
├── go.mod
├── go.sum
├── main.go
└── routers
    ├── blog.go
    └── shop.go
  

routers/shop.go中添加一个LoadShop的函数,将shop相关的路由注册到指定的路由器

 package routers

import "github.com/gin-gonic/gin"

func LoadShop(e *gin.Engine)  {
    e.GET("/hello", helloHandler)
    e.GET("/goods", goodsHandler)
    e.GET("/checkout", checkoutHandler)
}
func helloHandler(c *gin.Context)  {}
func goodsHandler(c*gin.Context)  {}
func checkoutHandler(c*gin.Context)  {}
  

routers/blog.go中添加一个LoadBlog的函数,将blog相关的路由注册到指定的路由器

 package routers

import "github.com/gin-gonic/gin"

func LoadBlog(e *gin.Engine) {
    e.GET("/post", postHandler)
    e.GET("/comment", commentHandler)
}
func postHandler(c *gin.Context) {}
func commentHandler(c *gin.Context) {}
  

在main函数中实现最终的注册逻辑如下:

 func main() {
    r := gin.Default()
    routers.LoadBlog(r)
    routers.LoadShop(r)
    if err := r.Run(); err != nil {
        fmt.Println("startup service failed, err:%v\n", err)
    }
}
  
2.8.4 路由拆分到不同的APP

有时候项目规模实在太大,那么我们就更倾向于把业务拆分的更详细一些,例如把不同的业务代码拆分成不同的APP。

因此我们在项目目录下单独定义一个app目录,用来存放我们不同业务线的代码文件,这样就很容易进行横向扩展。大致目录结构如下:

 LearnGin
├── app
│   ├── blog
│   │   ├── handler.go
│   │   └── router.go
│   └── shop
│       ├── handler.go
│       └── router.go
├── go.mod
├── go.sum
├── main.go
└── routers
    └── routers.go  

其中app/blog/router.go用来定义post相关路由信息,具体内容如下:

 func Routers(e *gin.Engine) {
    e.GET("/post", postHandler)
    e.GET("/comment", commentHandler)
}
  

app/shop/router.go用来定义shop相关路由信息,具体内容如下:

 func Routers(e *gin.Engine) {
    e.GET("/goods", goodsHandler)
    e.GET("/checkout", checkoutHandler)
}
  

routers/routers.go中根据需要定义Include函数用来注册子app中定义的路由,Init函数用来进行路由的初始化操作:

 type Option func(*gin.Engine)

var options = []Option{}

// 注册app的路由配置
func Include(opts ...Option) {
    options = append(options, opts...)
}

// 初始化
func Init() *gin.Engine {
    r := gin.New()
    for _, opt := range options {
        opt(r)
    }
    return r
}
  

main.go中按如下方式先注册子app中的路由,然后再进行路由的初始化:

 func main() {
    // 加载多个APP的路由配置
    routers.Include(shop.Routers, blog.Routers)
    // 初始化路由
    r := routers.Init()
    if err := r.Run(); err != nil {
        fmt.Println("startup service failed, err:%v\n", err)
    }
}