快速入门
引入
import "github.com/gin-gonic/gin"
编写代码
package main
import (
"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 main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
// 简单的路由组: v1
v1 := router.Group("/v1")
{
v1.POST("/login", loginEndpoint)
v1.POST("/submit", submitEndpoint)
v1.POST("/read", readEndpoint)
}
// 简单的路由组: v2
v2 := router.Group("/v2")
{
v2.POST("/login", loginEndpoint)
v2.POST("/submit", submitEndpoint)
v2.POST("/read", readEndpoint)
}
router.Run(":8080")
}
例如商品组
package main
import (
"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 main
import (
"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 main
import (
"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 Person
if 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 main
import (
"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 main
import (
"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 main
import (
"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 string
Number 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 main
import (
"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 Login
if 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 SignUpForm
if 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 main
import (
"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.Translator
func 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中的validator
if 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 Login
if 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 SignUpForm
if 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 main
import (
"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 string
for 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 main
import (
"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 main
import (
"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 main
import (
"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)
<-quit
log.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")
}