当前位置: 移动技术网 > IT编程>脚本编程>Go语言 > Gin框架介绍及使用

Gin框架介绍及使用

2019年10月26日  | 移动技术网IT编程  | 我要评论

gin框架介绍及使用

gin是一个用go语言编写的web框架。它是一个类似于martini但拥有更好性能的api框架, 由于使用了httprouter,速度提高了近40倍。 如果你是性能和高效的追求者, 你会爱上gin

gin框架介绍

go世界里最流行的web框架,github上有24k+star。 基于开发的web框架。 齐全,简单易用的轻量级框架。

gin框架安装与使用

安装

下载并安装gin:

go get -u github.com/gin-gonic/gin

第一个gin示例:

package main

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

func main() {
    // 创建一个默认的路由引擎
    r := gin.default()
    // get:请求方式;/hello:请求的路径
    // 当客户端以get方法请求/hello路径时,会执行后面的匿名函数
    r.get("/hello", func(c *gin.context) {
        // c.json:返回json格式的数据
        c.json(200, gin.h{
            "message": "hello world!",
        })
    })
    // 启动http服务,默认在0.0.0.0:8080启动服务
    r.run()
}

将上面的代码保存并编译执行,然后使用浏览器打开127.0.0.1:8080/hello就能看到一串json字符串。

restful api

rest与技术无关,代表的是一种软件架构风格,rest是representational state transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。

推荐阅读

简单来说,rest的含义就是客户端与web服务器之间进行交互的时候,使用http协议中的4个请求方法代表不同的动作。

  • get用来获取资源
  • post用来新建资源
  • put用来更新资源
  • delete用来删除资源。

只要api程序遵循了rest风格,那就可以称其为restful api。目前在前后端分离的架构中,前后端基本都是通过restful api来进行交互。

例如,我们现在要编写一个管理书籍的系统,我们可以查询对一本书进行查询、创建、更新和删除等操作,我们在编写程序的时候就要设计客户端浏览器与我们web服务端交互的方式和路径。按照经验我们通常会设计成如下模式:

请求方法 url 含义
get /book 查询书籍信息
post /create_book 创建书籍记录
post /update_book 更新书籍信息
post /delete_book 删除书籍信息

同样的需求我们按照restful api设计如下:

请求方法 url 含义
get /book 查询书籍信息
post /book 创建书籍记录
put /book 更新书籍信息
delete /book 删除书籍信息

gin框架支持开发restful api的开发。

func main() {
    r := gin.default()
    r.get("/book", func(c *gin.context) {
        c.json(200, gin.h{
            "message": "get",
        })
    })

    r.post("/book", func(c *gin.context) {
        c.json(200, gin.h{
            "message": "post",
        })
    })

    r.put("/book", func(c *gin.context) {
        c.json(200, gin.h{
            "message": "put",
        })
    })

    r.delete("/book", func(c *gin.context) {
        c.json(200, gin.h{
            "message": "delete",
        })
    })
}

开发restful api的时候我们通常使用postman来作为客户端的测试工具。

gin渲染

html渲染

我们首先定义一个存放模板文件的templates文件夹,然后在其内部按照业务分别定义一个posts文件夹和一个users文件夹。 posts/文件的内容如下:

{{define "posts/"}}
<!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>posts/index</title>
</head>
<body>

    {{.title}}

</body>
</html>
{{end}}

users/文件的内容如下:

{{define "users/"}}
<!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>users/index</title>
</head>

<body>
    {{.title}}
</body>
</html>
{{end}}

gin框架中使用loadhtmlglob()或者loadhtmlfiles()方法进行html模板渲染。

func main() {
    r := gin.default()
    r.loadhtmlglob("templates/**/*")
    //r.loadhtmlfiles("templates/posts/", "templates/users/")
    r.get("/posts/index", func(c *gin.context) {
        c.html(http.statusok, "posts/", gin.h{
            "title": "posts/index",
        })
    })

    r.get("users/index", func(c *gin.context) {
        c.html(http.statusok, "users/", gin.h{
            "title": "users/index",
        })
    })

    r.run(":8080")
}

静态文件处理

当我们渲染的html文件中引用了静态文件时,我们只需要按照以下方式在渲染页面前调用gin.static方法即可。

func main() {
    r := gin.default()
    r.static("/static", "./static")
    r.loadhtmlglob("templates/**/*")
   ...
    r.run(":8080")
}

补充文件路径处理

关于模板文件和静态文件的路径,我们需要根据公司/项目的要求进行设置。可以使用下面的函数获取当前执行程序的路径。

func getcurrentpath() string {
    if ex, err := os.executable(); err == nil {
        return filepath.dir(ex)
    }
    return "./"
}

json渲染

func main() {
    r := gin.default()

    // gin.h 是map[string]interface{}的缩写
    r.get("/somejson", func(c *gin.context) {
        // 方式一:自己拼接json
        c.json(http.statusok, gin.h{"message": "hello world!"})
    })
    r.get("/morejson", func(c *gin.context) {
        // 方法二:使用结构体
        var msg struct {
            name    string `json:"user"`
            message string
            age     int
        }
        msg.name = "小王子"
        msg.message = "hello world!"
        msg.age = 18
        c.json(http.statusok, msg)
    })
    r.run(":8080")
}

xml渲染

注意需要使用具名的结构体类型。

func main() {
    r := gin.default()
    // gin.h 是map[string]interface{}的缩写
    r.get("/somexml", func(c *gin.context) {
        // 方式一:自己拼接json
        c.xml(http.statusok, gin.h{"message": "hello world!"})
    })
    r.get("/morexml", func(c *gin.context) {
        // 方法二:使用结构体
        type messagerecord struct {
            name    string
            message string
            age     int
        }
        var msg messagerecord
        msg.name = "小王子"
        msg.message = "hello world!"
        msg.age = 18
        c.xml(http.statusok, msg)
    })
    r.run(":8080")
}

ymal渲染

r.get("/someyaml", func(c *gin.context) {
    c.yaml(http.statusok, gin.h{"message": "ok", "status": http.statusok})
})

protobuf渲染

r.get("/someprotobuf", func(c *gin.context) {
    reps := []int64{int64(1), int64(2)}
    label := "test"
    // protobuf 的具体定义写在 testdata/protoexample 文件中。
    data := &protoexample.test{
        label: &label,
        reps:  reps,
    }
    // 请注意,数据在响应中变为二进制数据
    // 将输出被 protoexample.test protobuf 序列化了的数据
    c.protobuf(http.statusok, data)
})

获取参数

获取querystring参数

querystring指的是url中?后面携带的参数,例如:/user/search?username=小王子&address=沙河。 获取请求的querystring参数的方法如下:

func main() {
    //default返回一个默认的路由引擎
    r := gin.default()
    r.get("/user/search", func(c *gin.context) {
        username := c.defaultquery("username", "小王子")
        //username := c.query("username")
        address := c.query("address")
        //输出json结果给调用方
        c.json(http.statusok, gin.h{
            "message":  "ok",
            "username": username,
            "address":  address,
        })
    })

    r.run()
}

获取form参数

请求的数据通过form表单来提交,例如向/user/search发送一个post请求,获取请求数据的方式如下:

func main() {
    //default返回一个默认的路由引擎
    r := gin.default()
    r.post("/user/search", func(c *gin.context) {
        // defaultpostform取不到值时会返回指定的默认值
        //username := c.defaultpostform("username", "小王子")
        username := c.postform("username")
        address := c.postform("address")
        //输出json结果给调用方
        c.json(http.statusok, gin.h{
            "message":  "ok",
            "username": username,
            "address":  address,
        })
    })
    r.run(":8080")
}

获取path参数

请求的参数通过url路径传递,例如:/user/search/小王子/沙河。 获取请求url路径中的参数的方式如下。

func main() {
    //default返回一个默认的路由引擎
    r := gin.default()
    r.get("/user/search/:username/:address", func(c *gin.context) {
        username := c.param("username")
        address := c.param("address")
        //输出json结果给调用方
        c.json(http.statusok, gin.h{
            "message":  "ok",
            "username": username,
            "address":  address,
        })
    })

    r.run(":8080")
}

参数绑定

为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的content-type识别请求数据类型并利用反射机制自动提取请求中querystring、form表单、json、xml等参数到结构体中。

// binding from json
type login struct {
    user     string `form:"user" json:"user" binding:"required"`
    password string `form:"password" json:"password" binding:"required"`
}

func main() {
    router := gin.default()

    // 绑定json的示例 ({"user": "q1mi", "password": "123456"})
    router.post("/loginjson", func(c *gin.context) {
        var login login

        if err := c.shouldbindjson(&login); err == nil {
            fmt.printf("login info:%#v\n", login)
            c.json(http.statusok, gin.h{
                "user":     login.user,
                "password": login.password,
            })
        } else {
            c.json(http.statusbadrequest, gin.h{"error": err.error()})
        }
    })

    // 绑定form表单示例 (user=q1mi&password=123456)
    router.post("/loginform", func(c *gin.context) {
        var login login
        // shouldbind()会根据请求的content-type自行选择绑定器
        if err := c.shouldbind(&login); err == nil {
            c.json(http.statusok, gin.h{
                "user":     login.user,
                "password": login.password,
            })
        } else {
            c.json(http.statusbadrequest, gin.h{"error": err.error()})
        }
    })

    // 绑定querystring示例 (user=q1mi&password=123456)
    router.get("/loginform", func(c *gin.context) {
        var login login
        // shouldbind()会根据请求的content-type自行选择绑定器
        if err := c.shouldbind(&login); err == nil {
            c.json(http.statusok, gin.h{
                "user":     login.user,
                "password": login.password,
            })
        } else {
            c.json(http.statusbadrequest, gin.h{"error": err.error()})
        }
    })

    // listen and serve on 0.0.0.0:8080
    router.run(":8080")
}

文件上传

单个文件上传

func main() {
    router := gin.default()
    // 处理multipart forms提交文件时默认的内存限制是32 mib
    // 可以通过下面的方式修改
    // router.maxmultipartmemory = 8 << 20  // 8 mib
    router.post("/upload", func(c *gin.context) {
        // 单个文件
        file, err := c.formfile("file")
        if err != nil {
            c.json(http.statusinternalservererror, gin.h{
                "message": err.error(),
            })
            return
        }

        log.println(file.filename)
        dst := fmt.sprintf("c:/tmp/%s", file.filename)
        // 上传文件到指定的目录
        c.saveuploadedfile(file, dst)
        c.json(http.statusok, gin.h{
            "message": fmt.sprintf("'%s' uploaded!", file.filename),
        })
    })
    router.run()
}

多个文件上传

func main() {
    router := gin.default()
    // 处理multipart forms提交文件时默认的内存限制是32 mib
    // 可以通过下面的方式修改
    // router.maxmultipartmemory = 8 << 20  // 8 mib
    router.post("/upload", func(c *gin.context) {
        // multipart form
        form, _ := c.multipartform()
        files := form.file["file"]

        for index, file := range files {
            log.println(file.filename)
            dst := fmt.sprintf("c:/tmp/%s_%d", file.filename, index)
            // 上传文件到指定的目录
            c.saveuploadedfile(file, dst)
        }
        c.json(http.statusok, gin.h{
            "message": fmt.sprintf("%d files uploaded!", len(files)),
        })
    })
    router.run()
}

gin中间件

gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录校验、日志打印、耗时统计等。

gin中的中间件必须是一个gin.handlerfunc类型。例如我们像下面的代码一样定义一个中间件。

// statcost 是一个统计耗时请求耗时的中间件
func statcost() gin.handlerfunc {
    return func(c *gin.context) {
        start := time.now()
        c.set("name", "小王子")
        // 执行其他中间件
        c.next()
        // 计算耗时
        cost := time.since(start)
        log.println(cost)
    }
}

然后注册中间件的时候,可以在全局注册。

func main() {
    // 新建一个没有任何默认中间件的路由
    r := gin.new()
    // 注册一个全局中间件
    r.use(statcost())
    
    r.get("/test", func(c *gin.context) {
        name := c.mustget("name").(string)
        log.println(name)
        c.json(http.statusok, gin.h{
            "message": "hello world!",
        })
    })
    r.run()
}

也可以给某个路由单独注册中间件。

// 给/test2路由单独注册中间件(可注册多个)
    r.get("/test2", statcost(), func(c *gin.context) {
        name := c.mustget("name").(string)
        log.println(name)
        c.json(http.statusok, gin.h{
            "message": "hello world!",
        })
    })

重定向

http重定向

http 重定向很容易。 内部、外部重定向均支持。

r.get("/test", func(c *gin.context) {
    c.redirect(http.statusmovedpermanently, "http://www.google.com/")
})

路由重定向

路由重定向,使用handlecontext

r.get("/test", func(c *gin.context) {
    // 指定重定向的url
    c.request.url.path = "/test2"
    r.handlecontext(c)
})
r.get("/test2", func(c *gin.context) {
    c.json(http.statusok, gin.h{"hello": "world"})
})

gin路由

普通路由

r.get("/index", func(c *gin.context) {...})
r.get("/login", func(c *gin.context) {...})
r.post("/login", func(c *gin.context) {...})

此外,还有一个可以匹配所有请求方法的any方法如下:

r.any("/test", func(c *gin.context) {...})

为没有配置处理函数的路由添加处理程序。默认情况下它返回404代码。

r.noroute(func(c *gin.context) {
        c.html(http.statusnotfound, "views/404.html", nil)
    })

路由组

我们可以将拥有共同url前缀的路由划分为一个路由组。

func main() {
    r := gin.default()
    usergroup := r.group("/user")
    {
        usergroup.get("/index", func(c *gin.context) {...})
        usergroup.get("/login", func(c *gin.context) {...})
        usergroup.post("/login", func(c *gin.context) {...})

    }
    shopgroup := r.group("/shop")
    {
        shopgroup.get("/index", func(c *gin.context) {...})
        shopgroup.get("/cart", func(c *gin.context) {...})
        shopgroup.post("/checkout", func(c *gin.context) {...})
    }
    r.run()
}

通常我们将路由分组用在划分业务逻辑或划分api版本时。

路由原理

gin框架中的路由使用的是这个库。

其基本原理就是构造一个路由地址的前缀树。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网