简介
Gin是一个用Go (Golang) 编写的web框架。它具有类似martini-like
的API,具有更好的性能,由于httprouter,速度提高了40倍。如果你需要性能和良好的生产力,你会喜欢它的。
如何使用
import "github.com/gin-gonic/gin"
r := gin.New()
// or
r := gin.Default()
// 创建仅接收Get请求方法
r.GET("/", IndexHandler)
// 创建仅接收Post请求方法
r.Post("/",IndexHandler)
// 创建接收所有请求方法
r.Any("/",IndexHandler)
// 绑定地址和端口
err := r.Run("127.0.0.1:8080")
if err != nil {
fmt.Printf("gin server start error : %s\n", err.Error())
}
func IndexHandler(ctx *gin.Context) {
// 返回字符串
ctx.String(200,"hello")
}
一些实践
路由
通常情况下,一个项目会有很多相似的路由,比如: /user/login``/user/registry``/user/logout
等,单个写起来会很麻烦,并且不利于管理,gin中提供路由组
来管理这些相同前缀的路由。
func main() {
r := gin.Default()
// 创建用户路由组
userRg := r.Group("/user")
userRg.GET("/login")
userRg.GET("/registry")
userRg.GET("/logout")
r.Run(":80")
}
合理利用路由组可以极大的简化声明路由时产生的问题
r.NoRoute(func(ctx *gin.Context) {
ctx.Header("Content-Type", "text/html")
ctx.String(200, "<h1>404</h1>")
})
参数获取
// 获取Query参数
key := ctx.Query("key")
fmt.Printf("key: %s\n", key)
// 获取Body-FromData参数
name := ctx.PostForm("name")
fmt.Printf("name: %s\n", name)
// 绑定到结构体
var p struct {
Key string `uri:"key"`
Name string `form:"name"`
}
// 根据Content-Type绑定值
_ = ctx.ShouldBind(&p)
fmt.Printf("p: %+v\n", p)
// 绑定到结构体
// req is Request
var req struct {
Key string `form:"key"` // FormData & QueryParam
Name string `form:"name"` // ...
Password string `json:"password"` // JsonData
Version string `header:"version"` // Header
}
// 根据Content-Type绑定值
_ = ctx.ShouldBind(&req)
// 绑定JsonData参数
_ = ctx.ShouldBindJSON(&req)
// 绑定Header
_ = ctx.ShouldBindHeader(&req)
fmt.Printf("request: %+v\n", req)
单独调用
ShouldBind
方法时会根据Content-Type
绑定值,所以在同时使用多种参数类型时需要单独调用具体
绑定方法,如ShouldBindJSON``ShouldBindQuery
QueryParam参数默认情况下是没有Content-Type
的,默认ShouldBind
方法会通过FromData
的Tag
绑定
import (
"fmt"
"net/http"
"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"
enTranslations "github.com/go-playground/validator/v10/translations/en"
chTranslations "github.com/go-playground/validator/v10/translations/zh"
)
var trans ut.Translator
// transInit
// local 通常取决于 http 请求头的 'Accept-Language'
func transInit(local string) (err error) {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
zhT := zh.New() // chinese
enT := en.New() // english
uni := ut.New(enT, zhT, enT)
var o bool
trans, o = uni.GetTranslator(local)
if !o {
return fmt.Errorf("uni.GetTranslator(%s) failed", local)
}
// register translate
// 注册翻译器
switch local {
case "zh":
err = chTranslations.RegisterDefaultTranslations(v, trans)
default:
err = enTranslations.RegisterDefaultTranslations(v, trans)
}
return
}
return
}
func main() {
if err := transInit("zh"); err != nil {
fmt.Printf("init trans failed, err:%v\n", err)
return
}
r := gin.Default()
r.GET("/", func(ctx *gin.Context) {
var req struct {
Username string `form:"username" binding:"required"` // 必传参数
Password string `form:"password" binding:"min=6,max=10"` // 长度: 最小6,最大15
Age int `form:"age" binding:"gt=10,lt=60"` // 数值: 大于10,小于60
Version string `header:"version"`
}
if err := ctx.ShouldBind(&req); err != nil {
errs, ok := err.(validator.ValidationErrors)
if !ok {
// 非validator.ValidationErrors错误直接返回
}
ctx.JSON(http.StatusOK, errs.Translate(trans))
}
})
r.Run(":80")
}
file, err := ctx.FormFile("file")
if err != nil {
ctx.String(http.StatusBadRequest, fmt.Sprintf("param error: %s", err.Error()))
return
}
fmt.Printf("filename: %s\n", file.Filename)
fmt.Printf("file_size: %d\n", file.Size)
f, err := file.Open()
if err != nil {
ctx.String(http.StatusBadRequest, fmt.Sprintf("open file error: %s", err.Error()))
}
defer func(f multipart.File) {
_ = f.Close()
}(f)
var fileByte []byte
buf := make([]byte, 1024)
for {
var num int
if num, err = f.Read(buf); err != nil {
if err == io.EOF {
break
}
}
fileByte = append(fileByte, buf[:num]...)
}
fmt.Printf("fileByte:\n%s\n", fileByte)
// 接收 multipart/form-data类型参数
// form 为FromData map
form, err := ctx.MultipartForm()
if err != nil {
ctx.String(http.StatusBadRequest, fmt.Sprintf("param error: %s", err.Error()))
return
}
files := form.File["files"]
if len(files) == 0 {
ctx.String(http.StatusBadRequest, "files is none")
return
}
for _, file := range files {
// 存储文件
if err := ctx.SaveUploadedFile(file, file.Filename); err != nil {
ctx.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error()))
return
}
}
ctx.String(200, fmt.Sprintf("upload ok %d files", len(files)))
中间件
func TokenMid() gin.HandlerFunc {
return func(ctx *gin.Context) {
token, _ := ctx.Cookie("token")
if token == "" {
ctx.String(http.StatusUnauthorized, "please login !")
ctx.Abort()
}
}
}
上面代码定义了Token中间件,可以在执行
Handler之前
检查Cookie
中token
是否存在,不存在则中断执行并返回
func main() {
r := gin.Default()
r.Use(TokenMid()).Get("/user/info",func (ctx *gin.Context) {
ctx.String(200,"success")
})
}