Gin中文文档
本文参考 https://www.yoytang.com/go-gin-doc.html

Gin简单示例

GoLand为例:

  1. 新建项目,选择Go Module(vgo) 。 proxy设置为 https://goproxy.cn

  2. 生成的 go.mod 存放依赖信息

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

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

func main() {
	r := gin.Default() // 返回默认的引擎
	r.GET("/book", func(context *gin.Context) {
		//context.HTML(200,"hello",asn1.NullBytes)
		context.JSON(200, gin.H{
			"message": "GET book!",
		})
	})
	r.POST("/book", func(context *gin.Context) {
		context.JSON(200, gin.H{
			"message": "POST book!",
		})
	})
	_ = r.Run(":9090") // 启动服务
}

http/template

  1. 模板文件通常定义为.tmpl和.tpl为后缀(也可以使用其他的后缀),必须使用UTF8编码。
  2. 模板文件中使用{{和}}包裹和标识需要传入的数据。
  3. 传给模板这样的数据就可以通过点号(.)来访问,如果数据是复杂类型的数据,可以通过{ { .FieldName }}来访问它的字段。
  4. 除{{和}}包裹的内容外,其他内容均不做修改原样输出。
import (
	"fmt"
	"github.com/gin-gonic/gin"
	"html/template"
)

//模板: {{.}}
func main() {
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		temp, err := template.ParseFiles("./demo.templ")
		if err != nil {
			fmt.Println("template parser error:", err)
			return
		}
		//u1 := User{Name: "小王子", gender: "男", Age: 18}
		u2 := map[string]interface{}{
			"Name":   "小王子",
			"Gender": "男",
			"Age":    18,
		}
		err = temp.Execute(writer, u2)
		if err == nil {
			fmt.Println("render template failed,", err)
		}
	})
	err := http.ListenAndServe(":9090", nil)
	if err != nil {
		fmt.Println("HTTP server failed,err:", err)
		return
	}
}

传值

使用struct

模板文件:

<p>姓名 {{.Name}}</p>
<p>年龄 {{.Age}}</p>
<p>性别 {{.gender}}</p> (性别不会显示)
type User struct {
	Name   string
	gender string //首字母小写是私有,不会render到模板上
	Age    int
}

//...
u1 := User{Name: "小王子", gender: "男", Age: 18}
err = temp.Execute(context.Writer, u1)
if err == nil {
	fmt.Println("render template failed,", err)
}
//...		

使用map

map通过key访问,key的大小写均可以。

u2 := map[string]interface{}{
	"Name":   "小王子",
	"Gender": "男",
	"Age":    18,
}
err = temp.Execute(context.Writer, u2)
if err == nil {
    fmt.Println("render template failed,", err)
}

常用命令

1). 注释

{{/* 注释 支持换行 */}}

2). pipeline

支持管道指令,支持使用管道符号|链接多个命令。

3). 声明变量

$obj := {{.}}

4). 移除空格

{{- .Name -}}

5). 条件判断

{{if pipeline}} T1 {{end}}

{{if pipeline}} T1 {{else}} T0 {{end}}

{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}

等等不一一列举。 参考地址

Gin中使用template

func main() {
	r := gin.Default()
	// 1. 进行模板解析
	//   Gin框架中使用 LoadHTMLGlob()或者使用 loadHTMLFiles()进行html模板解析
	r.LoadHTMLFiles("templates/index.tmpl")
	r.GET("/index", func(context *gin.Context) {
		// 2. 进行模板渲染
		context.HTML(http.StatusOK, "index.tmpl", gin.H{
			"title": "tyard.cc",
		})
	})
	_ = r.Run(":9091") // 启动服务
}

define

转载自:原文地址

使用define定义模板名字,示例如下:

文件目录: templates/posts/index.tmpl

{{define "posts/index.tmpl"}}
    <!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}}

文件目录: templates/users/index.tmpl

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

渲染的代码:

func main() {
	r := gin.Default()
	// 1. 进行模板解析
	//   Gin框架中使用 LoadHTMLGlob()或者使用 loadHTMLFiles()进行html模板解析
	r.LoadHTMLGlob("templates/**/*")
	//r.LoadHTMLFiles("templates/posts/index.tmpl", "templates/users/index.tmpl")
	r.GET("/posts/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
			"title": "posts/index",
		})
	})

	r.GET("users/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
			"title": "users/index",
		})
	})
	_ = r.Run(":9091") // 启动服务
}

模板函数(管道)

例如:防止html函数转义,定义一个safe函数。

代码如下:

//0. 定义模板函数
r.SetFuncMap(template.FuncMap{
	"safe": func(str string) template.HTML {
		return template.HTML(str)
	},
})
// 1. 进行模板解析
//   Gin框架中使用 LoadHTMLGlob()或者使用 loadHTMLFiles()进行html模板解析
r.LoadHTMLGlob("templates/**/*")
r.GET("/posts/index", func(c *gin.Context) {
	c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
		"title":   "posts/index",
		"content": "<a href='http://baidu.com'>baidu.com</a>",
	})
})
_ = r.Run(":9091") // 启动服务

html模板代码中,通过管道使用{{.content | safe}}即可。

静态资源

r := gin.Default()
//定义静态资源,第一个目录为映射的目录 第二个为实际目录
//写到LoadHTML...代码前面
r.Static("/static", "./static/")

JSON

func main() {
	r := gin.Default()
	r.GET("/map", func(c *gin.Context) {
		mp := map[string]interface{}{
			"name": "xiaoming",
		}
		c.JSON(http.StatusOK, mp)
	})

	//方式1
	r.GET("json", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "hello world",
		})
	})

	//方式2 结构体
	r.GET("/json2", func(c *gin.Context) {
		var msg struct {
			Name    string
			Message string
			Age     int
		}
		msg.Name = "小明"
		msg.Message = "hello world"
		msg.Age = 18
		c.JSON(http.StatusOK, msg)
	})
	_ = r.Run(":9091") // 启动服务
}

querystring参数

r.GET("/query", func(c *gin.Context) {
	//获取浏览器发送的query string参数
	// name=ch&value=1&value=2
	name := c.Query("name")
	value := c.QueryArray("value")
	//DefaultQuery: 查不到就用默认的
	df := c.DefaultQuery("score", "100")
	//GetQuery 返回结果和bool值
	gf, success := c.GetQuery("GetQuery")
	if !success {
		gf = "sv"
	}

	c.JSON(http.StatusOK, gin.H{
		"name":  name,
		"value": value,
		"df":    df,
		"gf":    gf,
	})
})

form

r.POST("/login", func(c *gin.Context) {
	username := c.PostForm("username")
	password := c.PostForm("password")
	// 与 query类似,也有default
	//c.GetPostForm()
	//c.DefaultPostForm()
	c.JSON(http.StatusOK, gin.H{
		"username": username,
		"password": password,
	})
})

path参数(URI参数)

注意url匹配不要冲突。

r.GET("/username/:name", func(c *gin.Context) {
	name := c.Param("name")
	c.JSON(http.StatusOK, gin.H{
		"name": name,
	})
})

参数绑定到struct

注意:

  1. struct绑定的字段需要大写
  2. ShouldBind传递地址
  3. 注解
type UserInfo struct {
	Username string `form:"username"`
	Password string `form:"password"`
}
//...
r.POST("/loginRd", func(c *gin.Context) {
	// 1. 字段需要大写
	// 2. ShouldBind传递地址
	var u UserInfo //声明变量
	if err := c.ShouldBind(&u); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"err": err.Error(),
		})
	} else {
		c.JSON(http.StatusOK, &u)
	}
})

通过struct tag 可以自动解析QueryString、form表单、JSON、XML等参数到结构体中。 下面是解析JSON、form表单和QueryString参数的示例:

// 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.ShouldBind(&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示例 (/loginQuery?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")
}

ShouldBind会按照下面的顺序解析请求中的数据完成绑定:

  • 如果是 GET 请求,只使用 Form 绑定引擎(query)。
  • 如果是 POST 请求,首先检查 content-type 是否为 JSON 或 XML,然后再使用 Form(form-data)

其他的, 还可以进行参数校验功能,详情见官方文档

文件上传

// 处理multipart forms提交文件时默认的内存限制是32 MiB
// 可以通过下面的方式修改
// router.MaxMultipartMemory = 8 << 20  // 8 MiB
r.POST("upload", func(c *gin.Context) {
	if file, err := c.FormFile("f1"); err != nil {
		//请求失败
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": err.Error(),
		})
		return
	} else {
		// 需要提前目录
		dst := fmt.Sprintf("/tmp/fileUpload/%s", file.Filename)
		// 上传文件到指定的目录
		if e := c.SaveUploadedFile(file, dst); e != nil {
			c.JSON(http.StatusInternalServerError, gin.H{
				"message": e.Error(),
			})
		} else {
			c.JSON(http.StatusOK, gin.H{
				"message": fmt.Sprintf("'%s' uploaded!", file.Filename),
			})
		}
	}
})

多文件上传

// 多文件上传
	r.POST("mutiUpload", func(c *gin.Context) {
		// Multipart form
		if form, err := c.MultipartForm(); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{
				"message": err.Error(),
			})
		} else {
			files := form.File["file"]
			for index, file := range files {
				log.Println(file.Filename)
				dst := fmt.Sprintf("/tmp/fileUpload/%s_%d", file.Filename, index)
				// 上传文件到指定的目录
				if saveErr := c.SaveUploadedFile(file, dst); saveErr != nil {
					c.JSON(http.StatusInternalServerError, gin.H{
						"message": saveErr.Error(),
					})
					return
				}
			}
			c.JSON(http.StatusOK, gin.H{
				"message": fmt.Sprintf("%d files uploaded!", len(files)),
			})
		}
	})	

重定向

url重定向

r.GET("urlRedirect", func(c *gin.Context) {
	c.Redirect(http.StatusMovedPermanently, "http://www.sogo.com/")
})

路由重定向

r.GET("/redirectRouter", 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框架中的路由使用的是httprouter这个库。其基本原理就是构造一个路由地址的前缀树。

普通路由

r.GET("/test2", func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"hello": "world"})
})

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

// 匹配。get post等
r.Any("/test", func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"hello": "test"})
})

路由组,有相同前缀的可以统一用路由组处理,支持多级嵌套:

userGroup := r.Group("/user")
{
	userGroup.GET("/index", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"user": "index"})
	})
	userGroup.GET("/login", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"user": "login"})
	})
	userGroup.POST("/login", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"user": "login"})
	})

}
shopGroup := r.Group("/shop")
{
	shopGroup.GET("/index", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"shop": "index"})
	})
	shopGroup.GET("/cart", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"shop": "cart"})
	})
	shopGroup.POST("/checkout", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"shop": "checkout"})
	})

	// 嵌套路由组  /shop/xx/oo
	xx := shopGroup.Group("xx")
	xx.GET("/oo", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"shop/xx": "oo"})
	})
}