快速入门
官方文档
引入
import "github.com/gin-gonic/gin"
编写代码
package mainimport ("github.com/gin-gonic/gin""net/http"
)func pong(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "pong",})
}func main() {//实例化一个gin的对象r := gin.Default()r.GET("/ping", pong)r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}
访问
http://127.0.0.1:8080/ping
请求处理
//实例化一个gin的对象
//r := gin.Default() //默认会开启两个中间件,logger和recovery错误恢复
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)// 默认在 8080 端口启动服务,除非定义了一个 PORT 的环境变量。router.Run()// router.Run(":3000") hardcode 端口号
}
RESTFUL
路由分组
package mainimport "github.com/gin-gonic/gin"func main() {router := gin.Default()// 简单的路由组: v1v1 := router.Group("/v1"){v1.POST("/login", loginEndpoint)v1.POST("/submit", submitEndpoint)v1.POST("/read", readEndpoint)}// 简单的路由组: v2v2 := router.Group("/v2"){v2.POST("/login", loginEndpoint)v2.POST("/submit", submitEndpoint)v2.POST("/read", readEndpoint)}router.Run(":8080")
}
例如商品组
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {router := gin.Default()goodsGroup := router.Group("/goods"){goodsGroup.GET("/list", goodsList)goodsGroup.GET("/1", goodsDetail)goodsGroup.POST("/add", goodsAdd)} //方便查看,逻辑组织在一起router.Run(":8080")
}func goodsAdd(context *gin.Context) {context.JSON(http.StatusOK, gin.H{"message": "add successful",})
}func goodsDetail(context *gin.Context) {context.JSON(http.StatusOK, gin.H{"1": "details",})
}func goodsList(context *gin.Context) {context.JSON(http.StatusOK, gin.H{"Goods": []int{1, 2, 3},})
}
url参数解析
关键
router.GET("/user/:name", func(c *gin.Context) {}
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {router := gin.Default()goodsGroup := router.Group("/goods"){goodsGroup.GET("", goodsList)goodsGroup.GET("/:id", goodsDetail)goodsGroup.POST("", goodsAdd)} //方便查看,逻辑组织在一起router.Run(":8080")
}func goodsAdd(context *gin.Context) {context.JSON(http.StatusOK, gin.H{"message": "add successful",})
}func goodsDetail(context *gin.Context) {param := context.Param("id")context.JSON(http.StatusOK, gin.H{param: "details",})
}func goodsList(context *gin.Context) {context.JSON(http.StatusOK, gin.H{"Goods": []int{1, 2, 3},})
}
约束参数类型
package mainimport ("github.com/gin-gonic/gin""net/http"
)type Person struct {ID string `uri:"id" binding:"required,uuid"`Name string `uri:"name" binding:"required"`
}func main() {router := gin.Default()router.GET("/:name/:id", func(context *gin.Context) {var person Personif err := context.ShouldBindUri(&person); err != nil {context.Status(404)return}context.JSON(http.StatusOK, gin.H{"name": person.Name,"id": person.ID,})})router.Run(":8080")
}
如果使用
http://127.0.0.1:8080/tom/1
则报错404
如果使用
http://127.0.0.1:8080/tom/d4054e14-7479-a070-bf9f-04cfa7bf75c9
则正常
如果整数id,约束
type Person struct {ID int `uri:"id" binding:"required`Name string `uri:"name" binding:"required"`
}
Get参数
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {router := gin.Default()router.GET("/welcome", welcome)router.Run(":8080")
}func welcome(context *gin.Context) {firstName := context.DefaultQuery("firstname", "Tom")lastName := context.DefaultQuery("lastname", "Steve")context.JSON(http.StatusOK, gin.H{"firstName": firstName,"lastName": lastName,})
}
访问
http://127.0.0.1:8080/welcome?firstname=Jack&lastname=H
有默认值
POST参数
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {router := gin.Default()router.GET("/welcome", welcome)router.POST("/form_post", formPost)router.Run(":8080")
}func formPost(context *gin.Context) {message := context.PostForm("message")nick := context.DefaultPostForm("nick", "anonymous")context.JSON(http.StatusOK, gin.H{"message": message,"nick": nick,})
}func welcome(context *gin.Context) {firstName := context.DefaultQuery("firstname", "Tom")lastName := context.DefaultQuery("lastname", "Steve")context.JSON(http.StatusOK, gin.H{"firstName": firstName,"lastName": lastName,})
}
混合,即url带参数
func getPost(context *gin.Context) {id := context.Query("id")name := context.PostForm("name")context.JSON(http.StatusOK, gin.H{"id": id,"name": name,})
}
返回数据格式
package mainimport ("github.com/gin-gonic/gin""github.com/gin-gonic/gin/testdata/protoexample""net/http"
)func main() {r := gin.Default()// gin.H 是 map[string]interface{} 的一种快捷方式r.GET("/someJSON", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})})r.GET("/moreJSON", func(c *gin.Context) {// 你也可以使用一个结构体var msg struct {Name string `json:"user"`Message stringNumber int}msg.Name = "Lena"msg.Message = "hey"msg.Number = 123// 注意 msg.Name 在 JSON 中变成了 "user"// 将输出:{"user": "Lena", "Message": "hey", "Number": 123}c.JSON(http.StatusOK, msg)})r.GET("/someXML", func(c *gin.Context) {c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})})r.GET("/someYAML", func(c *gin.Context) {c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})})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)})// 监听并在 0.0.0.0:8080 上启动服务r.Run(":8080")
}
反解Protobuf
使用,proto.Unmarshal
表单验证
gin使用了validator
库,https://github.com/go-playground/validator
使用时,需要在要绑定的所有字段上,设置相应的tag。
Gin提供了两类绑定方法:
- Must bind
- Methods -
Bind
,BindJSON
,BindXML
,BindQuery
,BindYAML
- Behavior - 这些方法属于
MustBindWith
的具体调用。 响应状态码被设置为 400 。
- Methods -
- Should bind
- Methods -
ShouldBind
,ShouldBindJSON
,ShouldBindXML
,ShouldBindQuery
,ShouldBindYAML
- Behavior - 这些方法属于
ShouldBindWith
的具体调用。 如果发生绑定错误,Gin 会返回错误并由开发者处理错误和请求。
- Methods -
使用 Bind 方法时,Gin 会尝试根据 Content-Type 推断如何绑定。 如果你明确知道要绑定什么,可以使用 MustBindWith
或 ShouldBindWith
。
你也可以指定必须绑定的字段。 如果一个字段的 tag 加上了 binding:"required"
,但绑定时是空值, Gin 会报错。
需要支持什么格式,则指定什么约束
package mainimport ("fmt""github.com/gin-gonic/gin""net/http"
)// 绑定 JSON
type Login struct {User string `json:"user" binding:"required"`Password string `json:"password" binding:"required"`
}type SignUpForm struct {Age uint8 `json:"age" binding:"gte=1,lte=130"`Name string `json:"name" binding:"required,min=3"`Email string `json:"email" binding:"required,email"`Password string `json:"password" binding:"required"`RePassword string `json:"rePassword" binding:"required,eqfield=Password"`
}func main() {router := gin.Default()router.POST("/loginJSON", func(context *gin.Context) {var loginForm Loginif err := context.ShouldBind(&loginForm); err != nil {fmt.Println(err.Error())context.JSON(http.StatusBadRequest, gin.H{"error": err.Error(),})return}context.JSON(http.StatusOK, gin.H{"user": loginForm.User,"password": loginForm.Password,})})router.POST("/signup", func(context *gin.Context) {var signForm SignUpFormif err := context.ShouldBind(&signForm); err != nil {fmt.Println(err.Error())context.JSON(http.StatusBadRequest, gin.H{"error": err.Error(),})return}context.JSON(http.StatusOK, gin.H{"message": "注册成功",})})router.Run(":8080")
}
POST http://127.0.0.1:8080/signup
{"age":130,"name":"Tom","email":"123456789@163.com","password":"1234","rePassword":"1234"
}
中文提示信息;字段问题
package mainimport ("fmt""github.com/gin-gonic/gin""github.com/gin-gonic/gin/binding""github.com/go-playground/locales/en""github.com/go-playground/locales/zh"ut "github.com/go-playground/universal-translator""github.com/go-playground/validator/v10"en_translations "github.com/go-playground/validator/v10/translations/en"zh_translations "github.com/go-playground/validator/v10/translations/zh""net/http""reflect""strings"
)// 绑定 JSON
type Login struct {User string `json:"user" binding:"required,max=10"`Password string `json:"password" binding:"required"`
}type SignUpForm struct {Age uint8 `json:"age" binding:"gte=1,lte=130"`Name string `json:"name" binding:"required,min=3"`Email string `json:"email" binding:"required,email"`Password string `json:"password" binding:"required"`RePassword string `json:"rePassword" binding:"required,eqfield=Password"`
}var trans ut.Translatorfunc removeTopStruct(fileds map[string]string) map[string]string {res := map[string]string{}for filed, err := range fileds {res[filed[strings.IndexAny(filed, ".")+1:]] = err}return res
}func InitTrans(locale string) (err error) {//修改gin中的validatorif v, ok := binding.Validator.Engine().(*validator.Validate); ok {//注册一个获取json等待tag的自定义方法;用tag标签的名称v.RegisterTagNameFunc(func(field reflect.StructField) string {name := strings.SplitN(field.Tag.Get("json"), ",", 2)[0]if name == "-" {return ""}return name})zhT := zh.New()enT := en.New()uni := ut.New(enT, zhT, enT) //第一个是首选,第二个是备用trans, ok = uni.GetTranslator(locale)if !ok {return fmt.Errorf("uni.GetTranslator(%s)", locale)}switch locale {case "en":en_translations.RegisterDefaultTranslations(v, trans)case "zh":zh_translations.RegisterDefaultTranslations(v, trans)default:en_translations.RegisterDefaultTranslations(v, trans)}}return
}func main() {if err := InitTrans("zh"); err != nil {fmt.Println("初始化翻译器错误")return}router := gin.Default()router.POST("/loginJSON", func(context *gin.Context) {var loginForm Loginif err := context.ShouldBind(&loginForm); err != nil {errs, ok := err.(validator.ValidationErrors)if !ok {fmt.Println("not ok")context.JSON(http.StatusOK, gin.H{"error": err.Error(),})return}context.JSON(http.StatusOK, gin.H{"error": removeTopStruct(errs.Translate(trans)),})return}context.JSON(http.StatusOK, gin.H{"user": loginForm.User,"password": loginForm.Password,})})router.POST("/signup", func(context *gin.Context) {var signForm SignUpFormif err := context.ShouldBind(&signForm); err != nil {fmt.Println(err.Error())context.JSON(http.StatusBadRequest, gin.H{"error": err.Error(),})return}context.JSON(http.StatusOK, gin.H{"message": "注册成功",})})router.Run(":8080")
}
自定义中间件
中间件使用
router := gin.New()//全局
//使用logger中间件
router.Use(gin.Logger())
//使用recovery
router.Use(gin.Recovery())//局部使用
authorized := router.Group("/goods")
authorized.Use(AuthRequred)
有一个执行队列,return只是当前中间件结束,
所有要使用context.Abort()
package mainimport ("fmt""github.com/gin-gonic/gin""net/http""time"
)func MyLogger() gin.HandlerFunc {return func(context *gin.Context) {t := time.Now()context.Set("example", "123456")context.Next()end := time.Since(t)fmt.Println("时间消耗: ", end)status := context.Writer.Status()fmt.Println("状态 ", status)}
}
func TokenRequired() gin.HandlerFunc {return func(context *gin.Context) {var token stringfor k, v := range context.Request.Header {if k == "X-Token" {token = v[0]if token != "Tom" {context.JSON(http.StatusUnauthorized, gin.H{"msg": "未登录",})//return不能阻止context.Abort()}}}context.Next()}}
func main() {router := gin.Default()router.Use(TokenRequired())router.GET("/ping", func(context *gin.Context) {context.JSON(http.StatusOK, gin.H{"message": "pong",})})router.Run(":8080")
}
设置静态文件和HTML文件
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {router := gin.Default()//相对目录router.LoadHTMLFiles("templates/index.tmpl")router.GET("/", func(context *gin.Context) {context.HTML(http.StatusOK, "index.tmpl", gin.H{"Title": "Hello",})})router.Run(":8080")
}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>{{.Title}}</title>
</head>
<body><h1></h1>
</body>
</html>
在命令行访问。
二级目录,与同名模板
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {router := gin.Default()//相对目录//router.LoadHTMLFiles("templates/index.tmpl", "templates/goods.html")router.LoadHTMLGlob("templates/**/*")router.GET("/", func(context *gin.Context) {context.HTML(http.StatusOK, "index.tmpl", gin.H{"Title": "Hello",})})router.GET("/goods", func(context *gin.Context) {context.HTML(http.StatusOK, "goods.html", gin.H{"name": "一些商品",})})router.GET("/goods/list", func(context *gin.Context) {context.HTML(http.StatusOK, "goods/list.html", gin.H{})})router.GET("/user/list", func(context *gin.Context) {context.HTML(http.StatusOK, "user/list.html", gin.H{})})router.Run(":8080")
}
{{define "goods/list.html"}}{{end}}
静态文件
对应
router.Static("/static", "./static")<link rel="stylesheet" href="/static/style.css">
退出程序
用协程,管道
package mainimport ("context""log""net/http""os""os/signal""time""github.com/gin-gonic/gin"
)func main() {router := gin.Default()router.GET("/", func(c *gin.Context) {time.Sleep(5 * time.Second)c.String(http.StatusOK, "Welcome Gin Server")})srv := &http.Server{Addr: ":8080",Handler: router,}go func() {// 服务连接if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {log.Fatalf("listen: %s\n", err)}}()// 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间)quit := make(chan os.Signal)signal.Notify(quit, os.Interrupt)<-quitlog.Println("Shutdown Server ...")ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()if err := srv.Shutdown(ctx); err != nil {log.Fatal("Server Shutdown:", err)}log.Println("Server exiting")
}