简介
Gin是一个用Go (Golang) 编写的web框架。它具有类似martini-like的API,具有更好的性能,由于httprouter,速度提高了40倍。如果你需要性能和良好的生产力,你会喜欢它的。
如何使用
import "github.com/gin-gonic/gin"
r := gin.New()// orr := 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 Requestvar req struct {Key string `form:"key"` // FormData & QueryParamName string `form:"name"` // ...Password string `json:"password"` // JsonDataVersion 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``ShouldBindQueryQueryParam参数默认情况下是没有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() // chineseenT := en.New() // englishuni := ut.New(enT, zhT, enT)var o booltrans, 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,最大15Age int `form:"age" binding:"gt=10,lt=60"` // 数值: 大于10,小于60Version 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 []bytebuf := make([]byte, 1024)for {var num intif 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 mapform, 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")})}
