Gin基础入门

293 阅读7分钟

Gin 简介

Gin官网

https://gin-gonic.com/

gin简介

Gin是一个golang的微框架,基于httprouter,封装比较优雅,API友好,源码注释比较明确,具有快速灵活,容错方便等特点。

gin特征

速度快

基于基数树的路由,内存占用小。没有反射。可预测的 API 性能。

中间件支持

传入的 HTTP 请求可以由中间件链和最终操作处理。例如:Logger、Authorization、GZIP 最后在 DB 中发布一条消息。

Crash-free

Gin 可以捕获 HTTP 请求期间发生的panic并恢复它。这样,你的服务器将始终可用。

JSON 验证

Gin 可以解析和验证请求的 JSON - 例如,检查所需值的存在。

路由分组

更好地组织您的路线。需要授权与不需要授权,不同的 API 版本……此外,组可以无限嵌套,而不会降低性能。

错误管理

Gin 提供了一种方便的方法来收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件、数据库并通过网络发送它们。

内置渲染

Gin 为 JSON、XML 和 HTML 渲染提供了一个易于使用的 API。

可扩展

创建一个新的中间件非常简单,只需查看示例代码即可。

第一个gin

安装gin

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

实现代码

package main

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

func Hello(c *gin.Context) {
	// c.String(200, "hello,%s", "zs")
	c.JSON(200, gin.H{
		"name": "tom",
		"age":  "20",
	})
}

func main() {
	e := gin.Default()
	e.GET("/hello", Hello)
	// 默认在8080端口
	e.Run()
}

运行

浏览器输入:http://localhost:8080/hello

{
  "age": "20",
  "name": "tom"
}

Gin实现用户登录

实现步骤

创建一个文件tempates

在项目跟目录下面创建给文件夹tempates,用来保存静态文件

创建一个登录html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login</title>
</head>
<body>
    <form action="/login" method="post">
        Username: <input type="text" name="username"><br>
        Password: <input type="password" name="password"><br>
        <input type="submit" value="Login">
    </form>
    
</body>
</html>

创建一个欢迎html页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Welcome</title>
</head>
<body>
    Welcome, {{.username}}
    
</body>
</html>

使用Gin处理逻辑

package main
import "github.com/gin-gonic/gin"
func MyHandler(c *gin.Context) {
    c.JSON(200, gin.H{
        "hello": "hello world",
    })
}
func Login(c *gin.Context) {
    // 跳转到login.html页面
    c.HTML(200, "login.html", nil)
}
func DoLogin(c *gin.Context) {
    // 获取表单的值
    username := c.PostForm("username")
    password := c.PostForm("password")
    c.HTML(200, "welcome.html", gin.H{
        "username": username,
        "password": password,
    })
}
func main() {
    e := gin.Default()
    // 加载templates目录下的文件
    e.LoadHTMLGlob("templates/*")
    e.GET("/login", Login)
    e.POST("/login", DoLogin)
    e.Run()
}

Gin请求参数

Get请求参数

使用c.Query("key")、或者 c.DefaultQuery("key")方法

package main
import "github.com/gin-gonic/gin"
func TestQueryString(c *gin.Context) {
    username := c.Query("username")
    // 第二个参数为默认值 如果没有就使用它
    site := c.DefaultQuery("site", "默认.com")
    c.String(200, "username:%s, site:%s", username, site)
}
func main() {
    e := gin.Default()
    // url : http://localhost:8080/testQueryString?username=zs&site=zs.com
    e.GET("/testQueryString", TestQueryString)
    e.Run()
}

运行结果

username:zs, site:zs.com

Post参数

使用c.PostForm("key")、或者 c.DefaultQuery("key")方法

func DoLogin(c *gin.Context) {
    username := c.PostForm("username")
    // 第二个参数为默认值 如果没有就使用它
    password := c.DefaultPostForm("password", "123")
    c.HTML(200, "welcome.html", gin.H{
        "username": username,
        "password": password,
    })
}

使用Postman或者Post表单测试

路劲参数(restful风格)

使用c.Param("key")方法

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

func TestPathParam(c *gin.Context) {
    s := c.Param("username")
    c.String(200, "Username:%s", s)
    // 输出:Username:ghz
}
func main() {
    e := gin.Default()
    // http://localhost:8080/hello/ghz
    e.GET("/hello/:username", TestPathParam)
    e.Run()
}

既有Get也有Post

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

func TestGetAndPost(c *gin.Context) {
    page := c.DefaultQuery("page", "0")
    key := c.PostForm("key")
    c.String(200, "Page:%s, Key:%s", page, key)
}
func main() {
    e := gin.Default()
    // http://localhost:8080/query?page=1
    e.POST("/query", TestGetAndPost)
    e.Run()
}

使用Postman或者Post表单测试

Gin表单处理

创建一个HTML表单

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>多课网,听老郭讲golang</title>
</head>
<body>
    <h1>用户注册页面</h1>
    <form action="/register" method="post">
        用户名: <input type="text" name="username"><br>
        密码: <input type="password" name="password"><br>
        爱好:
        <input type="checkbox" name="hobby" value="swiming">游泳  
        <input type="checkbox" name="hobby" value="basketball">篮球
        <br>
        性别:<input type="radio" name="gender" id="1" value="m">男
        <input type="radio" name="gender" id="2" value="f">女
        <br>
        城市: <select name="city">
            <option value="beijing">北京</option>
            <option value="shanghai">上海</option>
        </select>
        <br>
        <input type="submit" value="注册">
    </form>
    
</body>
</html>

Go code

package main
import "github.com/gin-gonic/gin"
func Regsiter(c *gin.Context) {
    username := c.PostForm("username")
    password := c.PostForm("password")
    // 获取多选框的内容 数组类型
    hobby := c.PostFormArray("hobby")
    gender := c.PostForm("gender")
    city := c.PostForm("city")
    c.String(200, "Username:%s, Password:%s, hobby:%s, gender:%s, city:%s", username, password, hobby, gender, city)
}
func GoRegister(c *gin.Context) {
    c.HTML(200, "register.html", nil)
}
func main() {
    e := gin.Default()
    e.LoadHTMLGlob("templates/*")
    e.POST("/register", Regsiter)
    e.GET("/register", GoRegister)
    e.Run()
}

运行结果

Username:ghz, Password:123, hobby:[swiming basketball], gender:m, city:beijing

Gin数据绑定

绑定Form表单

package main
import (
    "github.com/gin-gonic/gin"
)
type User struct {
    Username string   `form:"username"`
    Password string   `form:"password"`
    Hobby    []string `form:"hobby"`
    Gender   string   `form:"gender"`
    City     string   `form:"city"`
}
func Regsiter(c *gin.Context) {
    var user User
    c.ShouldBind(&user)
    c.String(200, "User:%s", user)
}
func GoRegister(c *gin.Context) {
    c.HTML(200, "register.html", nil)
}
func main() {
    e := gin.Default()
    e.LoadHTMLGlob("templates/*")
    e.POST("/register", Regsiter)
    e.GET("/register", GoRegister)
    e.Run()
}

绑定查询参数

package main
import (
    "log"
    "github.com/gin-gonic/gin"
)
type User struct {
    Username string `form:"username"`
    Password string `form:"password"`
}
func TestGetBind(c *gin.Context) {
    var user User
    err := c.ShouldBind(&user)
    if err != nil {
        log.Fatal(err)
    }
    c.String(200, "User:%s", user)
}
func main() {
    e := gin.Default()
    // http://localhost:8080/testGetBind?username=ghz&password=123
    e.GET("/testGetBind", TestGetBind)
    e.Run()
}

路径请求参数绑定

package main
import (
    "log"
    "github.com/gin-gonic/gin"
)
type User struct {
    Username string `uri:"username"`
    Password string `uri:"password"`
}
func TestGetBind(c *gin.Context) {
    var user User
    err := c.ShouldBindUri(&user)
    if err != nil {
        log.Fatal(err)
    }
    c.String(200, "User:%s", user)
}
func main() {
    e := gin.Default()
    // http://localhost:8080/testGetBind/ghz/123
    e.GET("/testGetBind/:username/:password", TestGetBind)
    e.Run()
}

注意:结构体和绑定方法的变化

Gin访问静态文件集成BootStrap框架

下载BootStrap

下载地址:https://getbootstrap.com/

添加bootstrap css和js文件

创建一个assets文件夹,将css和js文件添加到该文件夹

创建html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/assets/css/bootstrap.min.css">
    
    <title>Login</title>
</head>
<body>
   <div class="container">
    
    <form>
        <div class="mb-3">
          <label for="exampleInputEmail1" class="form-label">Email address</label>
          <input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp">
          <div id="emailHelp" class="form-text">We'll never share your email with anyone else.</div>
        </div>
        <div class="mb-3">
          <label for="exampleInputPassword1" class="form-label">Password</label>
          <input type="password" class="form-control" id="exampleInputPassword1">
        </div>
        <div class="mb-3 form-check">
          <input type="checkbox" class="form-check-input" id="exampleCheck1">
          <label class="form-check-label" for="exampleCheck1">Check me out</label>
        </div>
        <button type="submit" class="btn btn-primary">Submit</button>
      </form>
   </div>
    
</body>
</html>

Go Code

package main
import (
    "net/http"
    "github.com/gin-gonic/gin"
)
func Login(c *gin.Context) {
    c.HTML(200, "login.html", nil)
}
func main() {
    e := gin.Default()
    e.LoadHTMLGlob("templates/*")
    e.Static("/assets", "./assets")
    e.StaticFS("/croot", http.Dir("c:/"))
    e.StaticFile("/favicon.ico", "./assets/favicon.ico")
    e.GET("/login", Login)
    e.POST("/login", DoLogin)
    e.Run()
}

Gin使用中间件

中间件听起来非常高大上的名字,实际非常简单,就是在请求中间起到拦截作用的处理函数。

Gin默认中间件

如果你使用Gin.Default()实例化gin引擎,默认有两个中间件,LoggerRecovery,分别用来处理日志和处理错误。如果使用gin.New()需要重新添加。

// 新建一个没有任何默认中间件的路由
r := gin.New()
// 全局中间件
// Logger 中间件将日志写入 gin.DefaultWriter,即使你将 GIN_MODE 设置为 release。
// By default gin.DefaultWriter = os.Stdout
r.Use(gin.Logger())
// Recovery 中间件会 recover 任何 panic。如果有 panic 的话,会写入 500。
r.Use(gin.Recovery())

自定义中间件

  1. 自定义中间件非常简单,定义一个符合下面格式的处理函数
type HandlerFunc func(*Context)
  1. 使用Use方法调用
package main
import (
    "fmt"
    "github.com/gin-gonic/gin"
)
func TestMW(c *gin.Context) {
    c.String(200, "hello,%s", "ghz")
}
func MyMiddleware1(c *gin.Context) {
    fmt.Println("我的第一个中间件")
}
func MyMiddleware2(c *gin.Context) {
    fmt.Println("我的第二个中间件")
}
func main() {
    /*  func Default() *Engine {
        debugPrintWARNINGDefault()
        engine := New()
        engine.Use(Logger(), Recovery())
        return engine
    } */
    // e := gin.Default()
    // e := gin.New()
    e := gin.Default()
    e.Use(MyMiddleware1, MyMiddleware2)
    e.GET("testmw", TestMW)
    e.Run()
}

使用Gin BasicAuth中间件

Gin提供了BasicAuth中间件,用来对网站资源的访问保护。

示例

package main
import (
    "fmt"
    "net/http"
    "github.com/gin-gonic/gin"
)
// 模拟一些私人数据
var secrets = gin.H{
    "foo":    gin.H{"email": "foo@bar.com", "phone": "123433"},
    "austin": gin.H{"email": "austin@example.com", "phone": "666"},
    "lena":   gin.H{"email": "lena@guapa.com", "phone": "523443"},
}
func main() {
    r := gin.Default()
    // 路由组使用 gin.BasicAuth() 中间件
    // gin.Accounts 是 map[string]string 的一种快捷方式
    authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
        "foo":    "bar",
        "austin": "1234",
        "lena":   "hello2",
        "manu":   "4321",
    }))
    // /admin/secrets 端点
    // 触发 "localhost:8080/admin/secrets
    authorized.GET("/secrets", func(c *gin.Context) {
        // 获取用户,它是由 BasicAuth 中间件设置的
        user := c.MustGet(gin.AuthUserKey).(string)
        fmt.Println(user)
        if secret, ok := secrets[user]; ok {
            c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
        } else {
            c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
        }
    })
    // 监听并在 0.0.0.0:8080 上启动服务
    r.Run(":8080")
}

测试

在浏览器输入localhost:8080/admin/secrets时,会弹出一个对话框,要求输入正确的用户名和密码,才能访问资源。

Gin cookie的使用

cookie是服务器向客户端写的一些数据,可以实现像自动登录等功能。

Gin cookie的使用

package main
import "github.com/gin-gonic/gin"
func Handler(c *gin.Context) {
    // 获得cookie
    s, err := c.Cookie("username")
    if err != nil {
        s = "zs"
        // 设置cookie 
        // security为true 只能https访问 false http也能访问
        // 参数:cookie名称 cookie值 存活时间s 当前根目录 域 security 是否为http(js能否读到cookie)
        c.SetCookie("username", s, 60*60, "/", "localhost", false, true)
    }
    c.String(200, "测试cookie")
}
func main() {
    e := gin.Default()
    e.GET("/test", Handler)
    e.Run()
}

基于安全的考虑,需要给cookie加上SecureHttpOnly属性,HttpOnly比较好理解,设置HttpOnly=true的cookie不能被js获取到,无法用document.cookie打出cookie的内容。

Secure属性是说如果一个cookie被设置了Secure=true,那么这个cookie只能用https协议发送给服务器,用http协议是不发送的。

Gin 使用Session

因为http是无状态、短连接,如何保存客户端和服务器直接的会话状态呢?可以使用session。

使用gin session中间件

gin 本身没有对session的支持,可以使用第三方中间件。

go get github.com/gin-contrib/sessions
import "github.com/gin-contrib/sessions"

该中间件提供了很多后端支持:

  • cookie-based
  • Redis
  • memcached
  • MongoDB
  • memstore
  • PostgreSQL

实例

package main
import (
  "github.com/gin-contrib/sessions"
  "github.com/gin-contrib/sessions/cookie"
  "github.com/gin-gonic/gin"
)
func main() {
  r := gin.Default()
  store := cookie.NewStore([]byte("secret"))
  // 注入中间件
  r.Use(sessions.Sessions("mysession", store))
  r.GET("/hello", func(c *gin.Context) {
    session := sessions.Default(c)
    if session.Get("hello") != "world" {
      session.Set("hello", "world")
      session.Save()
    }
    c.JSON(200, gin.H{"hello": session.Get("hello")})
  })
  r.Run(":8000")
}