获取gingo get -u github.com/gin-gonic/gin
路由
gin使用RESTful API,所以在设计时gin的路由请求方式通过HTTP动词进行分发。如:
func main() {
// 禁用控制台颜色
// gin.DisableConsoleColor()
// 使用默认中间件(logger 和 recovery 中间件)创建 gin 路由
router := gin.Default()
router.GET("/someGet", getting)
router.POST("/somePost", posting)
router.PUT("/somePut", putting)
router.DELETE("/someDelete", deleting)
router.PATCH("/somePatch", patching)
router.HEAD("/someHead", head)
router.OPTIONS("/someOptions", options)
// 匹配所有的请求方式
router.Any("/test", any)
// 默认在 8080 端口启动服务,除非定义了一个 PORT 的环境变量。
router.Run()
// router.Run(":3000") hardcode 端口号
}
需要注意的是,这些GET、POST等函数的第二个参数是回调函数,这个回调函数有一个参数的类型为*gin.Context,如以下这个简单的例子:
package main
import (
"github.com/gin-gonic/gin"
"log"
"net/http"
)
func main() {
router := gin.Default()
router.GET("/book", bookGet)
//router.Any("/book", bookGet)
err := router.Run(":9000")
if err != nil {
log.Fatalln("start run error", err)
}
}
func bookGet(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": 200,
"msg": "",
"data": []string{
"xxx", "abb",
},
})
}
回调函数也可以写成匿名函数
在测试出其他请求方式时,可以使用Postman工具验证。
路由组
一个路由组就是拥有相同的url前缀而划分的组,定义好路由组后,我们在为请求方式绑定url时,只需要写后面的路径即可。
要实现这个功能,使用的是gin.Rroup这个方法。注意要接收返回值,后面要用到。比如以下这个例子:
package main
import (
"github.com/gin-gonic/gin"
"log"
"net/http"
)
func main() {
router := gin.Default()
bookRouter := router.Group("/book")
{
bookRouter.GET("/index", bookGet)
}
musicRouter := router.Group("/music")
{
musicRouter.GET("/index", musicGet)
}
err := router.Run(":9000")
if err != nil {
log.Fatalln("start run error", err)
}
}
func bookGet(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": 200,
"msg": "",
"data": []string{
"MySQL", "斗气化马",
},
})
}
func musicGet(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": 200,
"msg": "",
"data": []string{
"aaa", "bbb",
},
})
}
Group下面的
{}只是为了更加明显划分group,可加可不加。
返回
字符串
假如想要返回的结果是一个字符串,就是使用c.String方法
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/", index)
_ = r.Run(":9000")
}
func index(c *gin.Context) {
c.String(http.StatusOK, "ok")
}
JSON
返回一个json数据,在上面的例子中也有用到,调用的是gin.Context的JSON方法:func (c *Context) JSON(code int, obj interface{})。其第一个参数是状态码,可以使用http中的状态码;第二个参数是要转成json格式的数据。
举个例子:
package main
import (
"github.com/gin-gonic/gin"
"log"
"net/http"
)
func main() {
router := gin.Default()
router.GET("/index", index)
router.POST("/login", login)
err := router.Run(":9000")
if err != nil {
log.Fatalln("start run error", err)
}
}
func index(c *gin.Context) {
// 方式一, 自定义数据,使用gin.H包裹
c.JSON(http.StatusOK, gin.H{
"status": 200,
"msg": "ok",
})
}
func login(c *gin.Context) {
// 方式二,使用结构体,注意首字母大写,可以添加tag
res := struct {
Status int `json:"status"`
Msg string `json:"msg"`
Data []string `json:"data"`
}{Status: http.StatusOK, Msg: "success", Data: []string{"aaa", "bbb"}}
c.JSON(http.StatusOK, res)
}
HTML
返回一个HTML文件需要做一下步骤:
- 创建一个HTML文件,最后把文件根据业务逻辑放在templates中的不同文件夹下
- 使用
gin.LoadHTMLGlob和gin.LoadHTMLFiles引入刚才的文件 - 最后
c.HTML返回数据
用到方法的签名:
func (engine *Engine) LoadHTMLFiles(files ...string)
func (engine *Engine) LoadHTMLGlob(pattern string)
func (c *Context) HTML(code int, name string, obj interface{})
1
比如在templates下创建文件:
../templates/
├── music
│ └── index.tmpl
└── videos
└── index.tmpl
分别写入利用html/template中的语法进行定义:
music/index.tmpl:{{define "music/index.tmpl"}} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{ .title }}</title> </head> <body> <h1>music</h1> </body> </html> {{end}}videos/index.tmpl:
{{define "videos/index.tmpl"}} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>videos</title> </head> <body> <h1>video</h1> </body> </html> {{end}}
2
文件较多的情况下,建议使用LoadHTMLGlob,
func main() {
r := gin.Default()
r.LoadHTMLGlob("templates/**/*")
//r.LoadHTMLFiles("templates/music/index.tmpl", "templates/videos/index.tmpl")
r.GET("/music/index", musicIndex)
r.GET("/videos/index", videoIndex)
_ = r.Run(":9000")
}
3c.HTML的第二个参数传define后面的字符串,否则找不到文件,第三个参数是模板中的.,里面的键可以小写,因为gin.H是map[string]interface{}。
func musicIndex (c *gin.Context) {
c.HTML(http.StatusOK, "music/index.tmpl", gin.H{
"title": "music/index",
})
}
func videoIndex(c *gin.Context) {
c.HTML(http.StatusOK, "videos/index.tmpl", gin.H{})
}
添加模板函数
调用SetFuncMap即可,使用方式和html/template很像:
router := gin.Default()
router.SetFuncMap(template.FuncMap{
"safe": func(str string) template.HTML{
return template.HTML(str)
},
})
静态文件
只需要为当前路由绑定一个静态文件目录即可
r.Static("/static", "./static")
第一个参数对应url的路径,第二个路径对应文件夹的路径
其他渲染(XML/YAML/ProtoBuf)查看官方文档
重定向
HTTP重定向
HTTP 重定向很容易。 内部、外部重定向均支持。比如下面这个例子:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
router := gin.Default()
router.GET("/user", user)
router.GET("/login", login)
_ = router.Run(":9000")
}
func user(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "/login")
}
func login(c *gin.Context) {
c.String(http.StatusOK, "ok!")
}
路由重定向
路由重定向,通过c.Request.URL.Path设置跳转的指定的路径。再使用HandleContext完成重定向:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// 使用匿名函数就没必要定义全局的router
var router *gin.Engine
func main() {
router = gin.Default()
router.GET("/user", user)
router.GET("/login", login)
_ = router.Run(":9000")
}
func user(c *gin.Context) {
c.Request.URL.Path = "/login"
router.HandleContext(c)
}
func login(c *gin.Context) {
c.String(http.StatusOK, "ok!")
}
获取客户端的数据
get参数
要获取url的get参数一般有两个方法:c.Query和c.DefaultQuery两个方法,既然有两个方法,那么这两个方法有什么不同呢?前者在url中没有参数时返回的时一个空字符串,而后者返回的是你自定义的一个默认值。
如这样一个例子:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/music", func(c *gin.Context) {
name := c.Query("name")
id := c.Query("id")
age := c.DefaultQuery("age", "18")
addr := c.DefaultQuery("addr", "beijing")
fmt.Printf("name = %v\n", name)
fmt.Printf("id = %v\n", id)
fmt.Printf("age = %v\n", age)
fmt.Printf("addr = %v\n", addr)
c.JSON(http.StatusOK, gin.H{"status": 200, "msg": "succeed"})
})
_ = r.Run(":9000")
}
访问http://127.0.0.1:9000/music?name=123&age=12,结果:
name = 123
id =
age = 12
addr = beijing
post参数
以上在获取url参数的是GET请求,假如要获取form表单或ajax的POST请求时需要用到c.PostForm和c.DefaultPostForm,这两个的区别就和c.Query和c.DefaultQuery的区别一样。
如下面这个例子:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.POST("/music", func(c *gin.Context) {
name := c.PostForm("name")
id := c.PostForm("id")
age := c.DefaultPostForm("age", "18")
addr := c.DefaultPostForm("addr", "beijing")
c.JSON(http.StatusOK, gin.H{
"status": 200, "msg": "succeed",
"data": map[string]string{
"name": name,
"id": id,
"age": age,
"addr": addr}})
})
_ = r.Run(":9000")
}
使用Postman发送POST请求,带上参数:
动态url
在一些场景中,我们的url有可能是动态的,所以url的一部分也应该可以获取,gin提供了:和*来解决问题。
:假如绑定url使用:xxx,表示xxx是一个键,我们可以在代码中,用c.Param("xxx")来接收url的值,如:package main import ( "fmt" "github.com/gin-gonic/gin" "net/http" ) func main() { r := gin.Default() r.GET("/user/:name", user) _ = r.Run(":9000") } func user(c *gin.Context) { name := c.Param("name") fmt.Println("name", name) c.String(http.StatusOK, "success") }以上这个例子可以处理形如这样的url:
/user/xxx和/user/xxx/
但是不能处理/user/或/user或/user/xxx/xxx*使用*绑定url就可以解决上面这个问题,如*xxx,其表示/*后边的一切url都归属xxx(包含/)
如这样绑定:r.GET("/user/*name", user)那么其可以匹配:
/user/name或/user/name/xxx等以/user/开头的url
注意:
在没有绑定/user时,访问/user会跳转成/user/
参数绑定
为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中get参数、post参数、form表单、url、JSON、XML等参数到结构体中。
定义结构体的tag的意思:
- form: get参数的key或post参数的key(form表单的name)
- json:json数据的key
- uri:uri参数
binding:"required":表示当前字段是必须的,数据中没有该tag对应的值时,就会panic
实现这个功能需要用到ShouldBind方法 **(绑定uri时使用的是c.ShouldBindUri)**,c.ShouldBind:
func (c *Context) ShouldBind(obj interface{}) error {
b := binding.Default(c.Request.Method, c.ContentType())
return c.ShouldBindWith(obj, b)
}
获取上传的文件
gin中上传文件有上传一个文件和上传多个文件之分
上传一个文件时使用:
func (c *Context) FormFile(name string) (*multipart.FileHeader, error)
它会返回name对应值的第一个文件和error类型
上传多个文件时使用:
一个文件
package main import ( "fmt" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.MaxMultipartMemory = 10 << 20 // 允许最大上传10MB r.POST("/user", userPost) _ = r.Run(":9000") } func userPost(c *gin.Context) { file, _ := c.FormFile("file") fmt.Println(file.Filename) c.String(200, file.Filename) }多个文件
上传多个文件需要用到c.MultipartForm:func (c *Context) MultipartForm() (*multipart.Form, error)
得到的*multipart.Form是一个字典,拿到后可以遍历,最终拿到文件。package main import ( "fmt" "github.com/gin-gonic/gin" "net/http" ) func main() { r := gin.Default() r.MaxMultipartMemory = 10 << 20 // 允许最大上传 10MB r.POST("/user", userPost) _ = r.Run(":9000") } func userPost(c *gin.Context) { form, _ := c.MultipartForm() files := form.File["file"] for index, file := range files { fmt.Printf("index:%d file name: %s\n", index, file.Filename) } c.String(http.StatusOK, fmt.Sprintf("uploaded %d files", len(files))) }
文件保存
把文件保存到磁盘的方法是SaveUploadedFilefunc (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
- file:单个文件
- dst:路径
如:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.MaxMultipartMemory = 10 << 20 // 10MB
r.POST("/user", userPost)
_ = r.Run(":9000")
}
/*
// 单个文件
func userPost(c *gin.Context) {
file, _ := c.FormFile("file")
err := c.SaveUploadedFile(file, fmt.Sprintf("./temp/%s", file.Filename))
if err != nil {
c.String(500, "error: save file failed",)
}
fmt.Println(file.Filename)
c.String(200, file.Filename)
}
*/
// 多个文件
func userPost(c *gin.Context) {
form, _ := c.MultipartForm()
files := form.File["file"]
for index, file := range files {
fmt.Printf("index:%d file name: %s\n", index, file.Filename)
// 注意./temp/需要存在
err := c.SaveUploadedFile(file, fmt.Sprintf("./temp/%s", file.Filename))
if err != nil{
fmt.Println("error: save file failed", err)
}
}
c.String(http.StatusOK, fmt.Sprintf("uploaded %d file", len(files)))
}
cookies
获取cookie和设置cookie分别使用c.Cookie和c.SetCookie,一下为这两个方法的签名:
// 找到就返回对应的值和nil,没有找到返回""和error
func (c *Context) Cookie(name string) (string, error)
// 设置cookie
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
// 参数
// name, value: 对应的键和值
// maxAge(源码的内容):
// MaxAge=0 means no 'Max-Age' attribute specified.
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
// MaxAge>0 means Max-Age attribute present and given in seconds
// 其他参数的作用见:https://tools.ietf.org/html/rfc6265#page-8
例子:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/index", index)
_ = r.Run(":9000")
}
func index(c *gin.Context) {
// 获取cookie
name, err := c.Cookie("name")
fmt.Println("name", name)
if err != nil {
// 设置cookie
c.SetCookie("name", "lczmx", 0, "/", "127.0.0.1", false, true)
c.String(http.StatusOK, "set cookie")
}
c.String(http.StatusOK, fmt.Sprintf("get cookie %s", name))
}