欧陆新闻 分类
Go语言gin框架从入门到精通(1) 发布日期:2023-08-08 13:03:37 浏览次数:
  • Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,具有快速灵活,容错方便等特点
  • 对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错
  • 借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范

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

go mod init

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

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

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

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依赖。在浏览器输入 http://127.0.0.1:8080,出现以下页面说明运行成功:

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")
}
  • 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

  • 可以通过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")
}
  • URL参数可以通过DefaultQuery()或Query()方法获取
  • DefaultQuery()若参数不村则,返回默认值,Query()若不存在,返回空串

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

编译,请求输出如下:

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

带参数 name=thinks:

  • 表单传输为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="http://127.0.0.1:8080/form" 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()
}

编译请求输出:

  • 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="http://127.0.0.1:8080/upload" 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()
}

运行效果如下:

HTML页面加入multiple关键字

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>多文件上传</title>
</head>
<body>
<form action="http://127.0.0.1:8080/upload" 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")
}

效果展示:

  • 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\
", name))
}

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

在浏览器中分别输入:

127.0.0.1:8080/v1/login?

127.0.0.1:8080/v1/submi

输出 hello you

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

curl 127.0.0.1:8080/v2/login -X POST

输出 hello jack

下面最基础的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\
", err)
    }
}

当项目的规模增大后就不太适合继续在项目的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\
", 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
}

当我们的业务规模继续膨胀,单独的一个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\
", err)
    }
}

有时候项目规模实在太大,那么我们就更倾向于把业务拆分的更详细一些,例如把不同的业务代码拆分成不同的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\
", err)
    }
}


平台注册入口