gin 订阅
琴酒,日本漫画《名侦探柯南》及其衍生作品中的角色。黑衣组织的重要成员,经常和伏特加一起出现。身穿黑色风衣,银色(早期动画为金色)长发,脸总被帽子和刘海半遮掩着,性格冷酷残忍,头脑冷静,似乎可以毫不犹豫地杀死任何人。经常执行各种暗杀和清除组织叛徒的任务,是给工藤新一灌下APTX4869使其身体变小的罪魁祸首,亦是柯南所面对的主要敌人之一。 展开全文
琴酒,日本漫画《名侦探柯南》及其衍生作品中的角色。黑衣组织的重要成员,经常和伏特加一起出现。身穿黑色风衣,银色(早期动画为金色)长发,脸总被帽子和刘海半遮掩着,性格冷酷残忍,头脑冷静,似乎可以毫不犹豫地杀死任何人。经常执行各种暗杀和清除组织叛徒的任务,是给工藤新一灌下APTX4869使其身体变小的罪魁祸首,亦是柯南所面对的主要敌人之一。
信息
座    驾
黑色保时捷356A(德国生产的古董车)
真实姓名
黑泽阵
饰    演
佐佐木藏之介
搭    档
伏特加
配    音
日语原版:堀之纪  、、大陆版:王明军、文涛、冯盛
身    份
黑衣组织成员
别    名
杜松子酒、金恩、阿进等
中文名
琴酒
性    别
外文名
Gin ジン(Jin)
年    龄
不详
惯用手
左手
登场作品
《名侦探柯南》及其衍生系列
琴酒人物信息
组织代号:Gin日文假名:ジン罗马音:Jin中文译名:琴酒其他译名:金恩、吉恩、阿进、杜松子酒等真实姓名:黑泽 阵 [1]  [2]  (日文姓名:黑泽 阵;日文假名:くろさわ じん;罗马音:Kurosawa Jin) 在小学馆出版的《LOVE·Conan-名侦探柯南Official fanbook》中,作者青山刚昌透露内部设定琴酒的名字为“黑泽阵”,原因是“阵”的日文片假名写法“ジン”与日文中的“琴酒”相同。 [1]  [2]  File.281中的名册上,宫野志保的名字旁边的名字为“鱼冢三郎”,疑为伏特加的名字,而“鱼冢三郎”边的名字被部分遮挡,仅露出“泽”一字;而在对应动画TV 223《人鱼失踪记(推理篇)》中,也只露出了“黑”字的一部分。不过据青山刚昌透露,“鱼冢三郎”的旁边写着的名字的确为“黑泽阵”。 [1]  另外在英文版漫画里,Gin和Vodka的真名被透露,分别是Melkior和Kaspar,不过后来这两个名字就没有下文了。性别:男年龄:不详出生地:不详血统:疑似混血儿身份:黑衣组织重要成员座驾:黑色保时捷356A(早期TV版中的车牌号为“新宿34-4869”,TV 345后的剧场版与TV版统一使用“新宿54 み43-68”)手机型号:iPhone 4S黑款(File.820)武器:伯莱塔M92F手枪爱好:开车时抽烟,并喜欢用汽车点烟器来点烟。 [3]  烟的品牌为“JILOISES”(虚构品牌)。 [4]  初登场:配音演员:真人版演员:佐佐木藏之介(真人版单发第二部)
收起全文
精华内容
参与话题
问答
  • Gin 入门实战

    千次阅读 2019-06-14 18:26:09
    #Gin 入门实战 Agenda 拨开云雾见天日:前置知识讲解 万丈高楼平地起:基础中的精髓 及 搭建企业级golang脚手架 秤砣虽小压千斤:实战学习开发用户管理系统 拨开云雾见天日 ##1-1 前置知识 Go开发web的优势 在 ...

    Gin 入门实战

    Agenda

    • 拨开云雾见天日:前置知识讲解
    • 万丈高楼平地起:基础中的精髓 及 搭建企业级golang脚手架
    • 秤砣虽小压千斤:实战学习开发用户管理系统

    拨开云雾见天日

    1-1 前置知识

    • Go开发web的优势
      在 Go 语言出现之前,开发者们总是面临非常艰难的抉择,
      究竟是使用执行速度快但是编译速度并不理想的语言(如:C++),还是
      使用编译速度较快但执行效率不佳的语言(如:.NET、Java),
      开发难度较低但执行速度一般的动态语言呢?(如:Python)
      显然,Go 语言在这 3 个条件之间做到了最佳的平衡:快速编译,高效执行,易于开发。
      在这里插入图片描述
    • gin是什么?
      Gin 框架 现在是 github 上 start 最多 Go 语言编写的 Web 框架,相比其他它的几个 start 数量差不多的框架,它更加的轻量,有更好的性能。
    • 性能对比

    在这里插入图片描述

    1. 在固定时间内重复完成的总数,数值越高的是越好的结果
    2. 单次重复持续时间(ns /op),越低越好
    3. 堆内存(B /op),越低越好
    4. 每次重复的平均分配(allocs /op),越低越好

    从内存分配、单词相应时间、Qps三个纬度分析。gin基本是碾压其他对手的。

    • httprouter 为gin插上了翅膀

    在这里插入图片描述

    https://chai2010.gitbooks.io/advanced-go-programming-book/ch5-web/ch5-02-router.html
    https://www.cnblogs.com/foxy/p/9469401.html

    1-2 golang开发环境准备

    下载与解压
    wget https://dl.google.com/go/go1.11.4.linux-amd64.tar.gz
    tar -xvf go1.11.4.linux-amd64.tar.gz
    
    修改环境变量
    vim ~/.bash_profile
    
    export GOROOT=/root/go
    export GOPATH=/root/go_path
    export GOBIN=$GOROOT/bin
    export PATH=$GOBIN:$GOROOT/bin:$GOPATH/bin:$PATH
    source ~/.bash_profile
    
    查看go的版本号
    go version
    

    1-2 go mod 包管理工具

    https://blog.csdn.net/e421083458/article/details/89762113

    万丈高楼平地起

    2-1 安装gin及快速开始

    测试使用版本:
    github.com/gin-gonic/gin v1.4.0

    go get -v github.com/gin-gonic/gin
    
    package main
    
    import "github.com/gin-gonic/gin"
    
    func main() {
    	r := gin.Default()
    	r.GET("/ping", func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message": "pong",
    		})
    	})
    	r.Run() // listen and serve on 0.0.0.0:8080
    }
    

    2-2 请求路由

    • 设置多种请求类型
    func main() {
    	// Creates a gin router with default middleware:
    	// logger and recovery (crash-free) middleware
    	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)
    
    	// By default it serves on :8080 unless a
    	// PORT environment variable was defined.
    	router.Run()
    	// router.Run(":3000") for a hard coded port
    }
    
    
    • 绑定静态文件夹
    func main() {
    	router := gin.Default()
    	router.Static("/assets", "./assets")
    	router.StaticFS("/more_static", http.Dir("my_file_system"))
    	router.StaticFile("/favicon.ico", "./resources/favicon.ico")
    
    	// Listen and serve on 0.0.0.0:8080
    	router.Run(":8080")
    }
    
    • 参数作为URL
    package main
    
    import "github.com/gin-gonic/gin"
    
    type Person struct {
    	ID string `uri:"id" binding:"required,uuid"`
    	Name string `uri:"name" binding:"required"`
    }
    
    func main() {
    	route := gin.Default()
    	route.GET("/:name/:id", func(c *gin.Context) {
    		var person Person
    		if err := c.ShouldBindUri(&person); err != nil {
    			c.JSON(400, gin.H{"msg": err})
    			return
    		}
    		c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})
    	})
    	route.Run(":8088")
    }
    
    • 正则绑定
    package main
    
    import (
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    
    func main() {
    	router := gin.Default()
    
    	router.GET("/user/*action", func(c *gin.Context) {
    		c.String(http.StatusOK, "Hello world")
    	})
    
    	router.Run(":8080")
    }
    

    2-3 获取请求参数

    • 获取GET请求参数
    func main() {
    	router := gin.Default()
    
    	// Query string parameters are parsed using the existing underlying request object.
    	// The request responds to a url matching:  /welcome?firstname=Jane&lastname=Doe
    	router.GET("/welcome", func(c *gin.Context) {
    		firstname := c.DefaultQuery("firstname", "Guest")
    		lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
    
    		c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
    	})
    	router.Run(":8080")
    }
    
    • 获取POST请求参数
    func main() {
    	router := gin.Default()
    
    	router.POST("/form_post", func(c *gin.Context) {
    		message := c.PostForm("message")
    		nick := c.DefaultPostForm("nick", "anonymous")
    
    		c.JSON(200, gin.H{
    			"status":  "posted",
    			"message": message,
    			"nick":    nick,
    		})
    	})
    	router.Run(":8080")
    }
    
    • 获取Body值
    package main
    
    import (
    	"github.com/gin-gonic/gin"
    	"io/ioutil"
    	"net/http"
    )
    
    func main() {
    	router := gin.Default()
    	
    	router.POST("/user/*action", func(c *gin.Context) {
    		bodyBytes, _ := ioutil.ReadAll(c.Request.Body)
    		c.String(http.StatusOK, string(bodyBytes))
    	})
    	router.Run(":8080")
    }
    

    测试效果:

    #form-urlencode
    curl 'http://127.0.0.1:8080/user/adsadsad' -F "key=value"
    --------------------------88baf4eac8fd4ba1
    Content-Disposition: form-data; name="key"
    
    value
    --------------------------88baf4eac8fd4ba1--
    
    #form-data
    curl 'http://127.0.0.1:8080/user/adsadsad' -d "key=value"
    key=value
    
    • bind绑定结构体获取参数(Get&POST&POST_BODY)

    只需要在结构体上设置form标签即可

    package main
    
    import (
    	"fmt"
    	"log"
    	"time"
    
    	"github.com/gin-gonic/gin"
    )
    
    type Person struct {
    	Name     string    `form:"name"`
    	Address  string    `form:"address"`
    	Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
    }
    
    func main() {
    	route := gin.Default()
    	route.GET("/testing", startPage)
    	route.POST("/testing", startPage)
    	route.Run(":8085")
    }
    
    func startPage(c *gin.Context) {
    	var person Person
    	if c.ShouldBind(&person) == nil {
    		log.Println(person.Name)
    		log.Println(person.Address)
    		log.Println(person.Birthday)
    	}
    	c.String(200, fmt.Sprintf("%#v",person))
    }
    

    测试

    curl 'http://127.0.0.1:8085/testing?name=1&address=2&birthday=2006-01-02'
    
    main.Person{Name:"1", Address:"2", Birthday:time.Time{wall:0x0, ext:63271756800, loc:(*time.Location)(nil)}}
    
    curl 'http://127.0.0.1:8085/testing' -d 'name=5566&address=3344&birthday=2006-01-02'
    main.Person{Name:"5566", Address:"3344", Birthday:time.Time{wall:0x0, ext:63271756800, loc:(*time.Location)(nil)}}%
    

    2-4 验证请求参数

    • 结构体binding验证

    gin默认使用validator.v8作为验证器。

    多种验证规则请参见:

    https://github.com/go-playground/validator/tree/v8

    https://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Using_Validator_Tags

    注意点:gin必须使用 binding tag 来设置校验规则,而不是 validate。

    package main
    
    import (
    	"fmt"
    	"time"
    
    	"github.com/gin-gonic/gin"
    )
    
    type Person struct {
    	Name     string    `form:"name" binding:"required"`
    	Address  string    `form:"address"`
    	Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
    }
    
    func main() {
    	route := gin.Default()
    	route.GET("/testing", startPage)
    	route.Run(":8085")
    }
    
    func startPage(c *gin.Context) {
    	var person Person
    	if err:=c.ShouldBind(&person);err!=nil {
    		c.String(500,fmt.Sprint(err))
    	}
    	c.String(200, fmt.Sprintf("%#v",person))
    }
    

    测试

    curl 'http://127.0.0.1:8085/testing' -d 'name=&address=3344&birthday=2006-01-02&age=1'
    Key: 'Person.Age' Error:Field validation for 'Age' failed on the 'gt' tag
    Key: 'Person.Name' Error:Field validation for 'Name' failed on the 'required' tagmain.Person{Age:1, Name:"", Address:"3344", Birthday:time.Time{wall:0x0, ext:63271756800, loc:(*time.Location)(nil)}}
    
    curl 'http://127.0.0.1:8085/testing' -d 'name=5566&address=3344&birthday=2006-01-02&age=1'
    Key: 'Person.Age' Error:Field validation for 'Age' failed on the 'gt' tagmain.Person{Age:1, Name:"5566", Address:"3344", Birthday:time.Time{wall:0x0, ext:63271756800, loc:(*time.Location)(nil)}}
    
    • 自定义验证
    package main
    
    import (
    	"net/http"
    	"reflect"
    	"time"
    
    	"github.com/gin-gonic/gin"
    	"github.com/gin-gonic/gin/binding"
    	"gopkg.in/go-playground/validator.v8"
    )
    
    // Booking contains binded and validated data.
    type Booking struct {
    	CheckIn  time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
    	CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
    }
    
    func bookableDate(
    	v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
    	field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
    ) bool {
    	if date, ok := field.Interface().(time.Time); ok {
    		today := time.Now()
    		if today.Year() > date.Year() || today.YearDay() > date.YearDay() {
    			return false
    		}
    	}
    	return true
    }
    
    func main() {
    	route := gin.Default()
    
    	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
    		v.RegisterValidation("bookabledate", bookableDate)
    	}
    
    	route.GET("/bookable", getBookable)
    	route.Run(":8085")
    }
    
    func getBookable(c *gin.Context) {
    	var b Booking
    	if err := c.ShouldBindWith(&b, binding.Query); err == nil {
    		c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
    	} else {
    		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    	}
    }
    
    $ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17"
    {"message":"Booking dates are valid!"}
    
    $ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09"
    {"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}
    
    • 升级v9验证,支持多语言错误信息

    注意v9验证的话,标签要使用 validate ,也是为了与v8的标签分开。

    package main
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    	"github.com/go-playground/locales/en"
    	"github.com/go-playground/locales/zh"
    	"github.com/go-playground/locales/zh_Hant_TW"
    	"github.com/go-playground/universal-translator"
    	"gopkg.in/go-playground/validator.v9"
    	zh_translations "gopkg.in/go-playground/validator.v9/translations/zh"
    	en_translations "gopkg.in/go-playground/validator.v9/translations/en"
    	zh_tw_translations "gopkg.in/go-playground/validator.v9/translations/zh_tw"
    )
    
    var (
    	Uni      *ut.UniversalTranslator
    	Validate *validator.Validate
    )
    
    type User struct {
    	Username string `form:"user_name" validate:"required"`
    	Tagline  string `form:"tag_line" validate:"required,lt=10"`
    	Tagline2 string `form:"tag_line2" validate:"required,gt=1"`
    }
    
    func main() {
    	en := en.New()
    	zh := zh.New()
    	zh_tw := zh_Hant_TW.New()
    	Uni = ut.New(en, zh, zh_tw)
    	Validate = validator.New()
    
    	route := gin.Default()
    	route.GET("/testing", startPage)
    	route.POST("/testing", startPage)
    	route.Run(":8085")
    }
    
    func startPage(c *gin.Context) {
    	//这部分应该放到中间件中
    	locale:=c.DefaultQuery("locale","zh")
    	trans, _ := Uni.GetTranslator(locale)
    	switch locale {
    	case "zh":
    		zh_translations.RegisterDefaultTranslations(Validate, trans)
    		break
    	case "en":
    		en_translations.RegisterDefaultTranslations(Validate, trans)
    		break
    	case "zh_tw":
    		zh_tw_translations.RegisterDefaultTranslations(Validate, trans)
    		break
    	default:
    		zh_translations.RegisterDefaultTranslations(Validate, trans)
    		break
    	}
    
    	//自定义错误内容
    	Validate.RegisterTranslation("required", trans, func(ut ut.Translator) error {
    		return ut.Add("required", "{0} must have a value!", true) // see universal-translator for details
    	}, func(ut ut.Translator, fe validator.FieldError) string {
    		t, _ := ut.T("required", fe.Field())
    		return t
    	})
    
    	//这块应该放到公共验证方法中
    	user := User{}
    	c.ShouldBind(&user)
    	fmt.Println(user)
    	err := Validate.Struct(user)
    	if err != nil {
    		errs := err.(validator.ValidationErrors)
    		sliceErrs:=[]string{}
    		for _, e := range errs {
    			sliceErrs=append(sliceErrs,e.Translate(trans))
    		}
    		c.String(200,fmt.Sprintf("%#v",sliceErrs))
    	}
    	c.String(200, fmt.Sprintf("%#v",""))
    }
    

    测试

    http://127.0.0.1:8085/testing?user_name=1111111&tag_line=1111122222222222222222&tag_line=111&locale=zh
    []string{"Tagline长度必须小于10个字符", "Tagline2 must have a value!"}""
    
    http://127.0.0.1:8085/testing?user_name=1111111&tag_line=1111122222222222222222&tag_line=111&locale=zh
    []string{"Tagline must be less than 10 characters in length", "Tagline2 must have a value!"}""
    

    2-5 中间件

    • 使用gin中间件
    func Logger() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		t := time.Now()
    
    		// Set example variable
    		c.Set("example", "12345")
    
    		// before request
    
    		c.Next()
    
    		// after request
    		latency := time.Since(t)
    		log.Print(latency)
    
    		// access the status we are sending
    		status := c.Writer.Status()
    		log.Println(status)
    	}
    }
    
    func main() {
    	r := gin.New()
    	r.Use(Logger())
    
    	r.GET("/test", func(c *gin.Context) {
    		example := c.MustGet("example").(string)
    
    		// it would print: "12345"
    		log.Println(example)
    	})
    
    	// Listen and serve on 0.0.0.0:8080
    	r.Run(":8080")
    }
    
    • 自定义ip白名单中间件
    package main
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    	"log"
    )
    
    func IPAuthMiddleware() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		ipList:=[]string{
    			"192.168.1.1",
    		}
    		isMatched:=false
    		for _, host := range ipList {
    			if c.ClientIP() == host {
    				isMatched = true
    			}
    		}
    		if !isMatched{
    			c.String(200,fmt.Sprintf("%v, not in iplist", c.ClientIP()))
    			c.Abort()
    		}
    	}
    }
    
    func main() {
    	r := gin.New()
    	r.Use(IPAuthMiddleware())
    
    	r.GET("/test", func(c *gin.Context) {
    		example := c.MustGet("example").(string)
    		log.Println(example)
    	})
    
    	r.Run(":8080")
    }
    

    2-6 其他补充

    • 优雅关停
      确保在 go1.8+ 下编译
    // +build go1.8
    package main
    import (
    	"context"
    	"log"
    	"net/http"
    	"os"
    	"os/signal"
    	"syscall"
    	"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() {
    		// service connections
    		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
    			log.Fatalf("listen: %s\n", err)
    		}
    	}()
    
    	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    	<-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)
    	}
    	
    	select {
    	case <-ctx.Done():
    		log.Println("timeout of 5 seconds.")
    	}
    	log.Println("Server exiting")
    }
    

    Linux Signal及Golang中的信号处理
    https://blog.csdn.net/leonpengweicn/article/details/52131313

    • 模板渲染
      gin在html/template基础之上,进行的文件解析预处理。模块文件使用方法与之前相同。
      LoadHTMLGlob 可以解析一个文件夹的模板。
    package main
    
    import (
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    func main() {
    	router := gin.Default()
    	//router.LoadHTMLFiles("templates/index.html",)
    	router.LoadHTMLGlob("templates/*")
    	router.GET("/index", func(c *gin.Context) {
    		c.HTML(http.StatusOK, "index.html", gin.H{
    			"title": "Main website",
    		})
    	})
    	router.Run(":8080")
    }
    

    index.html

    <html>
    <h1>
        {{ .title }}
    </h1>
    </html>
    

    template使用样例
    https://www.do1618.com/archives/893/go-template学习样例/
    https://www.calhoun.io/intro-to-templates-p3-functions/

    • Let’s Encrypt 自动证书下载
      无需配置任何证书即可自动下载证书,过期后自动续订。

    Let’s Encrypt原理
    在这里插入图片描述
    更多原理细节:
    https://www.cnblogs.com/esofar/p/9291685.html
    https://blog.csdn.net/canghaiguzhou/article/details/79945001

    gin使用方法超级简单:

    package main
    
    import (
    	"log"
    
    	"github.com/gin-gonic/autotls"
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	r := gin.Default()
    	r.GET("/ping", func(c *gin.Context) {
    		c.String(200, "pong")
    	})
    	log.Fatal(autotls.Run(r, "www.itpp.tk"))
    }
    

    测试

    https://www.itpp.tk/ping
    pong
    

    查看证书缓存

    [root@izrj998xcgvke9018fkvbvz autotls]# ls ~/.cache/golang-autocert/
    acme_account+key  www.itpp.tk
    

    福利彩蛋,免费域名注册:
    https://www.freenom.com/zh/index.html?lang=zh

    2-7 构建企业级脚手架

    代码地址:https://github.com/e421083458/gin_scaffold
    使用gin构建了企业级脚手架,代码简洁易读,可快速进行高效web开发。
    主要功能有:
    1、请求链路日志打印,涵盖mysql/redis/request_in/request_out
    2、接入validator.v9,支持多语言错误信息提示及自定义错误提示。
    3、借助golang_common,支持了多配置环境及log/redis/mysql/http.client

    现在开始

    go mod tidy
    
    • 运行脚本
    go run main.go
    
    ➜  gin_scaffold git:(master) ✗ go run main.go
    ------------------------------------------------------------------------
    [INFO]  config=./conf/dev/
    [INFO]  start loading resources.
    [INFO]  success loading resources.
    ------------------------------------------------------------------------
    [GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.
    
    [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
    
    [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
     - using env:	export GIN_MODE=release
     - using code:	gin.SetMode(gin.ReleaseMode)
    
    [GIN-debug] GET    /demo/index               --> github.com/e421083458/gin_scaffold/controller.(*Demo).Index-fm (6 handlers)
    [GIN-debug] GET    /demo/bind                --> github.com/e421083458/gin_scaffold/controller.(*Demo).Bind-fm (6 handlers)
    [GIN-debug] GET    /demo/dao                 --> github.com/e421083458/gin_scaffold/controller.(*Demo).Dao-fm (6 handlers)
    [GIN-debug] GET    /demo/redis               --> github.com/e421083458/gin_scaffold/controller.(*Demo).Redis-fm (6 handlers)
     [INFO] HttpServerRun::8880
    
    • 测试mysql与请求链路
    curl 'http://127.0.0.1:8880/demo/dao?id=1'
    {
        "errno": 0,
        "errmsg": "",
        "data": "[{\"id\":1,\"area_name\":\"area_name\",\"city_id\":1,\"user_id\":2,\"update_at\":\"2019-06-15T00:00:00+08:00\",\"create_at\":\"2019-06-15T00:00:00+08:00\",\"delete_at\":\"2019-06-15T00:00:00+08:00\"}]",
        "trace_id": "c0a8fe445d05b9eeee780f9f5a8581b0"
    }
    
    查看链路日志(确认是不是一次请求查询,都带有相同trace_id):
    tail -f gin_scaffold.inf.log
    
    [INFO][2019-06-16T11:39:26.802][log.go:58] _com_request_in||method=GET||from=127.0.0.1||traceid=c0a8fe445d05b9eeee780f9f5a8581b0||cspanid=||uri=/demo/dao?id=1||args=map[]||body=||spanid=9dad47aa57e9d186
    [INFO][2019-06-16T11:39:26.802][log.go:58] _com_mysql_success||affected_row=1||traceid=c0a8fe445d05b9ee07b80f9f66cb39b0||spanid=9dad47aa1408d2ac||source=/Users/niuyufu/go/src/github.com/e421083458/gin_scaffold/dao/demo.go:24||proc_time=0.000000000||sql=SELECT * FROM `area`  WHERE (id = '1')||level=sql||current_time=2019-06-16 11:39:26||cspanid=
    [INFO][2019-06-16T11:39:26.802][log.go:58] _com_request_out||method=GET||args=map[]||proc_time=0.025019164||traceid=c0a8fe445d05b9eeee780f9f5a8581b0||spanid=9dad47aa57e9d186||uri=/demo/dao?id=1||from=127.0.0.1||response={\"errno\":0,\"errmsg\":\"\",\"data\":\"[{\\\"id\\\":1,\\\"area_name\\\":\\\"area_name\\\",\\\"city_id\\\":1,\\\"user_id\\\":2,\\\"update_at\\\":\\\"2019-06-15T00:00:00+08:00\\\",\\\"create_at\\\":\\\"2019-06-15T00:00:00+08:00\\\",\\\"delete_at\\\":\\\"2019-06-15T00:00:00+08:00\\\"}]\",\"trace_id\":\"c0a8fe445d05b9eeee780f9f5a8581b0\"}||cspanid=
    
    • 测试参数绑定与多语言验证
    curl 'http://127.0.0.1:8880/demo/bind?name=name&locale=zh'
    {
        "errno": 500,
        "errmsg": "Age为必填字段,Passwd为必填字段",
        "data": "",
        "trace_id": "c0a8fe445d05badae8c00f9fb62158b0"
    }
    
    curl 'http://127.0.0.1:8880/demo/bind?name=name&locale=en'
    {
        "errno": 500,
        "errmsg": "Age is a required field,Passwd is a required field",
        "data": "",
        "trace_id": "c0a8fe445d05bb4cd3b00f9f3a768bb0"
    }
    

    文件分层

    ├── README.md
    ├── conf   配置文件夹
    │   └── dev
    │       ├── base.toml
    │       ├── mysql_map.toml
    │       └── redis_map.toml
    ├── controller 控制器
    │   └── demo.go
    ├── dao DB数据访问层
    │   └── demo.go
    ├── dto  Bind结构体层
    │   └── demo.go
    ├── gin_scaffold.inf.log  info日志
    ├── gin_scaffold.wf.log warning日志
    ├── go.mod go module管理文件
    ├── go.sum
    ├── main.go
    ├── middleware 中间件层
    │   ├── panic.go
    │   ├── response.go
    │   ├── token_auth.go
    │   └── translation.go
    ├── public  公共文件
    │   ├── log.go
    │   ├── mysql.go
    │   └── validate.go
    ├── router  路由层
    │   ├── httpserver.go
    │   └── route.go
    └── tmpl
    

    引入轻量级golang类库,支持 mysql、redis、http.client、log、支持多级环境配置、支持链路日志打印

    go get -v github.com/e421083458/golang_common
    

    测试日志打印功能:

    package main
    
    import (
    	"github.com/e421083458/golang_common/lib"
    	"log"
    	"time"
    )
    
    func main() {
    	if err := lib.InitModule("./conf/dev/",[]string{"base","mysql","redis",}); err != nil {
    		log.Fatal(err)
    	}
    	defer lib.Destroy()
    
    	//todo sth
    	lib.Log.TagInfo(lib.NewTrace(), lib.DLTagUndefind, map[string]interface{}{"message": "todo sth"})
    	time.Sleep(time.Second)
    }
    

    类库更多功能细节请查看:
    https://github.com/e421083458/golang_common

    输出格式统一封装

    func ResponseError(c *gin.Context, code ResponseCode, err error) {
    	trace, _ := c.Get("trace")
    	traceContext, _ := trace.(*lib.TraceContext)
    	traceId := ""
    	if traceContext != nil {
    		traceId = traceContext.TraceId
    	}
    	resp := &Response{ErrorCode: code, ErrorMsg: err.Error(), Data: "", TraceId: traceId}
    	c.JSON(200, resp)
    	response, _ := json.Marshal(resp)
    	c.Set("response", string(response))
    	c.AbortWithError(200, err)
    }
    
    func ResponseSuccess(c *gin.Context, data interface{}) {
    	trace, _ := c.Get("trace")
    	traceContext, _ := trace.(*lib.TraceContext)
    	traceId := ""
    	if traceContext != nil {
    		traceId = traceContext.TraceId
    	}
    	resp := &Response{ErrorCode: SuccessCode, ErrorMsg: "", Data: data, TraceId: traceId}
    	c.JSON(200, resp)
    	response, _ := json.Marshal(resp)
    	c.Set("response", string(response))
    }
    

    定义中间件链路日志打印

    package middleware
    
    import (
    	"bytes"
    	"errors"
    	"fmt"
    	"github.com/e421083458/gin_scaffold/public"
    	"github.com/e421083458/golang_common/lib"
    	"github.com/gin-gonic/gin"
    	"io/ioutil"
    	"time"
    )
    //链路请求日志
    func RequestInLog(c *gin.Context) {
    	traceContext := lib.NewTrace()
    	if traceId := c.Request.Header.Get("com-header-rid"); traceId != "" {
    		traceContext.TraceId = traceId
    	}
    	if spanId := c.Request.Header.Get("com-header-spanid"); spanId != "" {
    		traceContext.SpanId = spanId
    	}
    	c.Set("startExecTime", time.Now())
    	c.Set("trace", traceContext)
    	bodyBytes, _ := ioutil.ReadAll(c.Request.Body)
    	c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // Write body back
    
    	lib.Log.TagInfo(traceContext, "_com_request_in", map[string]interface{}{
    		"uri":    c.Request.RequestURI,
    		"method": c.Request.Method,
    		"args":   c.Request.PostForm,
    		"body":   string(bodyBytes),
    		"from":   c.ClientIP(),
    	})
    }
    //链路输出日志
    func RequestOutLog(c *gin.Context) {
    	endExecTime := time.Now()
    	response, _ := c.Get("response")
    	st, _ := c.Get("startExecTime")
    	startExecTime, _ := st.(time.Time)
    	public.ComLogNotice(c, "_com_request_out", map[string]interface{}{
    		"uri":       c.Request.RequestURI,
    		"method":    c.Request.Method,
    		"args":      c.Request.PostForm,
    		"from":      c.ClientIP(),
    		"response":  response,
    		"proc_time": endExecTime.Sub(startExecTime).Seconds(),
    	})
    }
    
    func TokenAuthMiddleware() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		RequestInLog(c)
    		defer RequestOutLog(c)
    		isMatched := false
    		for _, host := range lib.GetStringSliceConf("base.http.allow_ip") {
    			if c.ClientIP() == host {
    				isMatched = true
    			}
    		}
    		if !isMatched{
    			ResponseError(c, InternalErrorCode, errors.New(fmt.Sprintf("%v, not in iplist", c.ClientIP())))
    			c.Abort()
    			return
    		}
    		c.Next()
    	}
    }
    

    请求数据绑定到结构体与校验

    dto/demo.go

    package dto
    
    import (
    	"errors"
    	"github.com/e421083458/gin_scaffold/public"
    	"github.com/gin-gonic/gin"
    	"github.com/go-playground/universal-translator"
    	"gopkg.in/go-playground/validator.v9"
    	"strings"
    )
    
    type InStruct struct {
    	Name   string `form:"name" validate:"required"`
    	Age    int64  `form:"age" validate:"required"`
    	Passwd string `form:"passwd" validate:"required"`
    }
    
    func (o *InStruct) BindingValidParams(c *gin.Context)  error{
    	if err:=c.ShouldBind(o);err!=nil{
    		return err
    	}
    	v:=c.Value("trans")
    	trans,ok := v.(ut.Translator)
    	if !ok{
    		trans, _ = public.Uni.GetTranslator("zh")
    	}
    	err := public.Validate.Struct(o)
    	if err != nil {
    		errs := err.(validator.ValidationErrors)
    		sliceErrs:=[]string{}
    		for _, e := range errs {
    			sliceErrs=append(sliceErrs,e.Translate(trans))
    		}
    		return errors.New(strings.Join(sliceErrs,","))
    	}
    	return nil
    }
    

    controller/demo.go

    func (demo *Demo) Bind(c *gin.Context) {
    	st:=&dto.InStruct{}
    	if err:=st.BindingValidParams(c);err!=nil{
    		middleware.ResponseError(c,500,err)
    		return
    	}
    	middleware.ResponseSuccess(c, "")
    	return
    }
    

    秤砣虽小压千斤

    3-1 用户管理系统设计

    在这里插入图片描述
    在这里插入图片描述

    功能点

    • 管理员登陆及退出
    • 用户列表 翻页
    • 增、删、改

    数据库

    • 用户表设计
    CREATE TABLE `user` (
     `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
     `name` varchar(255) NOT NULL DEFAULT '' COMMENT '姓名',
     `addr` varchar(255) NOT NULL DEFAULT '' COMMENT '住址',
     `age` smallint(4) NOT NULL DEFAULT '0' COMMENT '年龄',
     `birth` varchar(100) NOT NULL DEFAULT '2000-01-01 00:00:00' COMMENT '生日',
     `sex` smallint(4) NOT NULL DEFAULT '0' COMMENT '性别',
     `update_at` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT '更新时间',
     `create_at` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT '创建时间',
     PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8 COMMENT='用户表'
    

    后端准备

    前端准备

    3-2 实战开发

    • 开发接口
    [GIN-debug] POST   /api/login                --> github.com/e421083458/gin_scaffold/controller.(*Api).Login-fm (7 handlers)
    [GIN-debug] GET    /api/loginout             --> github.com/e421083458/gin_scaffold/controller.(*Api).LoginOut-fm (7 handlers)
    [GIN-debug] GET    /api/user/listpage        --> github.com/e421083458/gin_scaffold/controller.(*Api).ListPage-fm (8 handlers)
    [GIN-debug] GET    /api/user/add             --> github.com/e421083458/gin_scaffold/controller.(*Api).AddUser-fm (8 handlers)
    [GIN-debug] GET    /api/user/edit            --> github.com/e421083458/gin_scaffold/controller.(*Api).EditUser-fm (8 handlers)
    [GIN-debug] GET    /api/user/remove          --> github.com/e421083458/gin_scaffold/controller.(*Api).RemoveUser-fm (8 handlers)
    [GIN-debug] GET    /api/user/batchremove     --> github.com/e421083458/gin_scaffold/controller.(*Api).RemoveUser-fm (8 handlers)
    
    • 整合前端调试
    npm run dev
    

    修改 config/index.js文件,作接口代理转发

    dev: {
            env: require('./dev.env'),
            port: 8080,
            autoOpenBrowser: true,
            assetsSubDirectory: 'static',
            assetsPublicPath: '/',
            proxyTable: {
                "/api/": {
                    target:"http://127.0.0.1:8880/",
                    changeOrigin: true,
                    pathRewrite: {
                    }
                }
            },
            cssSourceMap: false
        }
    
    • 前后端部署与整合
      正式部署时我们一般考虑使用nginx 作转发。
        server {
            listen       8882;
            server_name  localhost;
            root /Users/niuyufu/WebstormProjects/vue-admin/dist;
            index  index.html index.htm index.php;
            location / {
                try_files $uri $uri/ /index.html?$args;
            }
            location /api/ {
                proxy_pass http://127.0.0.1:8880/api/;
            }
        }
    

    课程总结

    随堂笔记
    https://blog.csdn.net/e421083458/article/details/91994788
    gin入门实战 - 基础精髓 随堂代码
    https://github.com/e421083458/hello_gin
    golang基础代码类库
    https://github.com/e421083458/golang_common
    gin开发脚手架
    https://github.com/e421083458/gin_scaffold
    整合vue.js的admin后台
    https://github.com/e421083458/vue-admin

    展开全文
  • Go语言Web框架--Gin介绍和使用

    万次阅读 2018-07-23 11:05:11
    所谓框架 框架一直是敏捷开发中的利器,能让开发者很快的上手并做出应用,甚至有的时候,脱离了框架,一些开发者都不会写程序了。成长总不会一蹴而就,从写出程序获取成就感,再到精通框架,快速构造应用,当这些...

    所谓框架

    框架一直是敏捷开发中的利器,能让开发者很快的上手并做出应用,甚至有的时候,脱离了框架,一些开发者都不会写程序了。成长总不会一蹴而就,从写出程序获取成就感,再到精通框架,快速构造应用,当这些方面都得心应手的时候,可以尝试改造一些框架,或是自己创造一个。

    曾经我以为Python世界里的框架已经够多了,后来发现相比golang简直小巫见大巫。golang提供的net/http库已经很好了,对于http的协议的实现非常好,基于此再造框架,也不会是难事,因此生态中出现了很多框架。既然构造框架的门槛变低了,那么低门槛同样也会带来质量参差不齐的框架。

    考察了几个框架,通过其github的活跃度,维护的team,以及生产环境中的使用率。发现Gin还是一个可以学习的轻巧框架。


    Gin

    Gin是一个golang的微框架封装比较优雅API友好,源码注释比较明确,已经发布了1.0版本。具有快速灵活容错方便等特点。其实对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错。框架更像是一些常用函数或者工具的集合。借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。

    下面就Gin的用法做一个简单的介绍。

    首先需要安装,安装比较简单,使用go get -u(更新) 即可:

    go get gopkg.in/gin-gonic/gin.v1

    gin的版本托管再 gopkg的网站上。我在安装的过程中,gokpg卡住了,后来不得不根据gin里的godep的文件,把响应的源码从github上下载,然后copy到对应的目录。


    Hello World

    使用Gin实现Hello world非常简单,创建一个router,然后使用其Run的方法:

    import ( 
        "gopkg.in/gin-gonic/gin.v1" 
        "net/http" 
    ) 
    func main(){ 
        router := gin.Default() 
        router.GET("/", func(c *gin.Context) { 
        c.String(http.StatusOK, "Hello World") 
        }) 
    router.Run(":8000") 
    }
    

    简单几行代码,就能实现一个web服务。使用gin的Default方法创建一个路由handler。然后通过HTTP方法绑定路由规则和路由函数。不同于net/http库的路由函数,gin进行了封装,把request和response都封装到gin.Context的上下文环境。最后是启动路由的Run方法监听端口。麻雀虽小,五脏俱全。当然,除了GET方法,gin也支持POST,PUT,DELETE,OPTION等常用的restful方法。


    restful路由

    gin的路由来自httprouter库。因此httprouter具有的功能,gin也具有,不过gin不支持路由正则表达式:

    func main(){ 
        router := gin.Default() 
        router.GET("/user/:name", func(c *gin.Context) { 
            name := c.Param("name") 
            c.String(http.StatusOK, "Hello %s", name) 
        }) 
    }
    
    

    冒号:加上一个参数名组成路由参数。可以使用c.Params的方法读取其值。当然这个值是字串string。诸如/user/rsj217,和/user/hello都可以匹配,而/user//user/rsj217/不会被匹配。

    ☁ ~ curl http://127.0.0.1:8000/user/rsj217 
    Hello rsj217%                  
    
    ☁ ~ curl http://127.0.0.1:8000/user/rsj217/ 
    404 page not found%            
    
    ☁ ~ curl http://127.0.0.1:8000/user/ 
    404 page not found%
    
    
    

    除了:,gin还提供了*号处理参数,*号能匹配的规则就更多。

    func main(){
        router := gin.Default() 
    
        router.GET("/user/:name/*action", func(c *gin.Context) { 
            name := c.Param("name") 
            action := c.Param("action") 
            message := name + " is " + action 
            c.String(http.StatusOK, message) 
        }) 
    }
    
    

    访问效果如下

    ☁  ~  curl http://127.0.0.1:8000/user/rsj217/
    rsj217 is /%                       
    
    ☁  ~  curl http://127.0.0.1:8000/user/rsj217/中国
    rsj217 is /中国%

    query string参数与body参数

    web提供的服务通常是client和server的交互。其中客户端向服务器发送请求,除了路由参数,其他的参数无非两种,查询字符串query string和报文体body参数。所谓query string,即路由用,用?以后连接的key1=value2&key2=value2的形式的参数。当然这个key-value是经过urlencode编码。

    query string

    对于参数的处理,经常会出现参数不存在的情况,对于是否提供默认值,gin也考虑了,并且给出了一个优雅的方案:

    func main(){ 
        router := gin.Default() 
    
        router.GET("/welcome", func(c *gin.Context) { 
            firstname := c.DefaultQuery("firstname", "Guest") 
            lastname := c.Query("lastname") 
            c.String(http.StatusOK, "Hello %s %s", firstname, lastname) 
            }) 
    
        router.Run() 
    }
    
    

    使用c.DefaultQuery方法读取参数,其中当参数不存在的时候,提供一个默认值。使用Query方法读取正常参数,当参数不存在的时候,返回空字串:

    ☁ ~ curl http://127.0.0.1:8000/welcome 
    Hello Guest % 
    
    ☁ ~ curl http://127.0.0.1:8000/welcome\?firstname\=中国 
    Hello 中国 % 
    
    ☁ ~ curl http://127.0.0.1:8000/welcome\?firstname\=中国\&lastname\=天朝 
    Hello 中国 天朝% 
    
    ☁ ~ curl http://127.0.0.1:8000/welcome\?firstname\=\&lastname\=天朝 
    Hello 天朝% 
    
    ☁ ~ curl http://127.0.0.1:8000/welcome\?firstname\=%E4%B8%AD%E5%9B%BD 
    Hello 中国 %
    
    

    之所以使用中文,是为了说明urlencode。注意,当firstname为空字串的时候,并不会使用默认的Guest值,空值也是值,DefaultQuery只作用于key不存在的时候,提供默认值。

    body

    http的报文体传输数据就比query string稍微复杂一点,常见的格式就有四种。例如application/jsonapplication/x-www-form-urlencoded, application/xmlmultipart/form-data。后面一个主要用于图片上传。json格式的很好理解,urlencode其实也不难,无非就是把query string的内容,放到了body体里,同样也需要urlencode。默认情况下,c.PostFROM解析的是x-www-form-urlencodedfrom-data的参数。

    func main(){ 
        router := gin.Default() 
        router.POST("/form_post", func(c *gin.Context) { 
            message := c.PostForm("message") 
            nick := c.DefaultPostForm("nick", "anonymous") 
            c.JSON(http.StatusOK, gin.H{ 
                "status": gin.H{ 
                    "status_code": http.StatusOK, 
                    "status": "ok", 
                }, 
                "message": message, 
                "nick": nick,         
            }) 
        }) 
    }

    与get处理query参数一样,post方法也提供了处理默认参数的情况。同理,如果参数不存在,将会得到空字串。

    ☁ ~ curl -X POST http://127.0.0.1:8000/form_post -H "Content-Type:application/x-www-form-urlencoded" -d "message=hello&nick=rsj217" | python -m json.tool % Total % Received % Xferd 
    
    Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 
    100 104 100 79 100 25 48555 15365 --:--:-- --:--:-- --:--:-- 79000 
    { 
        "message": "hello", 
        "nick": "rsj217", 
        "status": { 
            "status": "ok", 
            "status_code": 200 
        } 
    }

    前面我们使用c.String返回响应,顾名思义则返回string类型。content-type是plain或者text。调用c.JSON则返回json数据。其中gin.H封装了生成json的方式,是一个强大的工具。使用golang可以像动态语言一样写字面量的json,对于嵌套json的实现,嵌套gin.H即可。

    发送数据给服务端,并不是post方法才行,put方法一样也可以。同时querystring和body也不是分开的,两个同时发送也可以:

    func main(){ 
        router := gin.Default() 
        router.PUT("/post", func(c *gin.Context) { 
            id := c.Query("id") 
            page := c.DefaultQuery("page", "0") 
            name := c.PostForm("name") 
            message := c.PostForm("message") 
            fmt.Printf("id: %s; page: %s; name: %s; message: %s \n", id, page, name, message)                   
            c.JSON(http.StatusOK, gin.H{ "status_code": http.StatusOK, }) 
        }) 
    }
    
    

    上面的例子,展示了同时使用查询字串和body参数发送数据给服务器。


    文件上传

    前面介绍了基本的发送数据,其中multipart/form-data转用于文件上传。gin文件上传也很方便,和原生的net/http方法类似,不同在于gin把原生的request封装到c.Request中了。

    func main(){ 
        router := gin.Default() 
        router.POST("/upload", func(c *gin.Context) { 
            name := c.PostForm("name") 
            fmt.Println(name) 
            file, header, err := c.Request.FormFile("upload") if err != nil {     
                c.String(http.StatusBadRequest, "Bad request") 
                return 
            } 
            filename := header.Filename 
            fmt.Println(file, err, filename) 
            out, err := os.Create(filename) 
            if err != nil {
                log.Fatal(err) 
            } 
            defer out.Close() 
            _, err = io.Copy(out, file) 
            if err != nil 
            { 
                log.Fatal(err)
            } 
            c.String(http.StatusCreated, "upload successful") 
        }) 
        router.Run(":8000") 
    }

    使用c.Request.FormFile解析客户端文件name属性。如果不传文件,则会抛错,因此需要处理这个错误。一种方式是直接返回。然后使用os的操作,把文件数据复制到硬盘上。

    使用下面的命令可以测试上传,注意upload为c.Request.FormFile指定的参数,其值必须要是绝对路径:

    curl -X POST http://127.0.0.1:8000/upload -F "upload=@/Users/ghost/Desktop/pic.jpg" -H "Content-Type: multipart/form-data"

    上传多个文件

    单个文件上传很简单,别以为多个文件就会很麻烦。依葫芦画瓢,所谓多个文件,无非就是多一次遍历文件,然后一次copy数据存储即可。下面只写handler,省略main函数的初始化路由和开启服务器监听了:

    router.POST("/multi/upload", func(c *gin.Context) { 
        err := c.Request.ParseMultipartForm(200000) 
        if err != nil { 
            log.Fatal(err) 
        } 
        formdata := c.Request.MultipartForm 
        files := formdata.File["upload"] 
    
        for i, _ := range files { / 
            file, err := files[i].Open() 
            defer file.Close() 
            if err != nil { 
                log.Fatal(err) 
            } 
    
            out, err := os.Create(files[i].Filename) 
            defer out.Close() 
            if err != nil { 
                log.Fatal(err) 
            } 
    
            _, err = io.Copy(out, file) 
            if err != nil { 
                log.Fatal(err) 
            } 
    
            c.String(http.StatusCreated, "upload successful") 
        } 
    })

    与单个文件上传类似,只不过使用了c.Request.MultipartForm得到文件句柄,再获取文件数据,然后遍历读写。

    使用curl上传

    curl -X POST http://127.0.0.1:8000/multi/upload -F "upload=@/Users/ghost/Desktop/pic.jpg" -F "upload=@/Users/ghost/Desktop/journey.png" -H "Content-Type: multipart/form-data"

    表单上传

    上面我们使用的都是curl上传,实际上,用户上传图片更多是通过表单,或者ajax和一些requests的请求完成。下面展示一下web的form表单如何上传。

    我们先要写一个表单页面,因此需要引入gin如何render模板。前面我们见识了c.String和c.JSON。下面就来看看c.HTML方法。

    首先需要定义一个模板的文件夹。然后调用c.HTML渲染模板,可以通过gin.H给模板传值。至此,无论是String,JSON还是HTML,以及后面的XML和YAML,都可以看到Gin封装的接口简明易用。

    创建一个文件夹templates,然后再里面创建html文件upload.html:

    <!DOCTYPE html> 
    <html lang="en"> 
    <head> 
        <meta charset="UTF-8"> 
        <title>upload</title> 
    </head> 
    
    <body> 
    <h3>Single Upload</h3> 
    <form action="/upload", method="post" enctype="multipart/form-data"> 
        <input type="text" value="hello gin" /> 
        <input type="file" name="upload" /> 
        <input type="submit" value="upload" /> 
    </form> 
    
    <h3>Multi Upload</h3> 
    <form action="/multi/upload", method="post" enctype="multipart/form-data"> 
        <input type="text" value="hello gin" /> 
        <input type="file" name="upload" /> 
        <input type="file" name="upload" /> 
        <input type="submit" value="upload" /> 
    </form> 
    
    </body> 
    </html>

    upload 很简单,没有参数。一个用于单个文件上传,一个用于多个文件上传。

      router.LoadHTMLGlob("templates/*")
        router.GET("/upload", func(c *gin.Context) {
            c.HTML(http.StatusOK, "upload.html", gin.H{})
        })
    

    使用LoadHTMLGlob定义模板文件路径。


    参数绑定

    我们已经见识了x-www-form-urlencoded类型的参数处理,现在越来越多的应用习惯使用JSON来通信,也就是无论返回的response还是提交的request,其content-type类型都是application/json的格式。而对于一些旧的web表单页还是x-www-form-urlencoded的形式,这就需要我们的服务器能改hold住这多种content-type的参数了。

    Python的世界里很好解决,毕竟动态语言不需要实现定义数据模型。因此可以写一个装饰器将两个格式的数据封装成一个数据模型。golang中要处理并非易事,好在有gin,他们的model bind功能非常强大。

    type User struct { 
        Username string `form:"username" json:"username" binding:"required"` 
        Passwd string `form:"passwd" json:"passwd" bdinding:"required"` 
        Age int `form:"age" json:"age"` 
    } 
    
    func main(){ 
        router := gin.Default() 
    
        router.POST("/login", func(c *gin.Context) { 
            var user User 
            var err error 
            contentType := c.Request.Header.Get("Content-Type") 
    
            switch contentType { 
                case "application/json": 
                    err = c.BindJSON(&user) 
                case "application/x-www-form-urlencoded": 
                    err = c.BindWith(&user, binding.Form) 
            } 
            if err != nil { 
                fmt.Println(err) 
                log.Fatal(err) 
            } 
    
            c.JSON(http.StatusOK, gin.H{ 
                "user": user.Username, 
                "passwd": user.Passwd, 
                "age": user.Age, 
            }) 
        }) 
    }

    先定义一个User模型结构体,然后针对客户端的content-type,一次使BindJSONBindWith方法。

    ☁ ~ curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&passwd=123&age=21" | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 
    100 79 100 46 100 33 41181 29543 --:--:-- --:--:-- --:--:-- 46000
    { 
        "age": 21, 
        "passwd": "123", 
        "username": "rsj217" 
    }
    
     ☁ ~ curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&passwd=123&new=21" | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 
    100 78 100 45 100 33 37751 27684 --:--:-- --:--:-- --:--:-- 45000 
    { 
        "age": 0, 
        "passwd": "123", 
        "username": "rsj217" 
    } 
    
    ☁ ~ curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&new=21" | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 
    0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0curl: (52) Empty reply from server 
    No JSON object could be decoded

    可以看到,结构体中,设置了binding标签的字段(username和passwd),如果没传会抛错误。非banding的字段(age),对于客户端没有传,User结构会用零值填充。对于User结构没有的参数,会自动被忽略。

    改成json的效果类似:

    ☁ ~ curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": "123", "age": 21}' | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 
    100 96 100 46 100 50 32670 35511 --:--:-- --:--:-- --:--:-- 50000 
    { 
        "age": 21, 
        "passwd": "123", 
        "username": "rsj217" 
    } 
    
    ☁ ~ curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": "123", "new": 21}' | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 
    100 95 100 45 100 50 49559 55066 --:--:-- --:--:-- --:--:-- 50000 
    { 
        "age": 0, 
        "passwd": "123", 
        "username": "rsj217" 
    } 
    
    ☁ ~ curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "new": 21}' | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 
    0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0curl: (52) Empty reply from server 
    No JSON object could be decoded 
    
    ☁ ~ curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": 123, "new": 21}' | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 
    0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0curl: (52) Empty reply from server 
    No JSON object could be decoded
    

    使用json还需要注意一点,json是有数据类型的,因此对于 {"passwd": "123"}{"passwd": 123}是不同的数据类型,解析需要符合对应的数据类型,否则会出错。

    当然,gin还提供了更加高级方法,c.Bind,它会更加content-type自动推断是bind表单还是json的参数。

    router.POST("/login", func(c *gin.Context) 
    { 
        var user User err := c.Bind(&user) 
        if err != nil { 
            fmt.Println(err) 
            log.Fatal(err) 
        } 
        c.JSON(http.StatusOK, gin.H{ 
            "username": user.Username, 
            "passwd": user.Passwd, 
            "age": user.Age, 
        }) 
    })

    多格式渲染

    既然请求可以使用不同的content-type,响应也如此。通常响应会有html,text,plain,json和xml等。
    gin提供了很优雅的渲染方法。到目前为止,我们已经见识了c.String, c.JSON,c.HTML,下面介绍一下c.XML。

    router.GET("/render", func(c *gin.Context) { 
        contentType := c.DefaultQuery("content_type", "json") 
        if contentType == "json" { 
            c.JSON(http.StatusOK, gin.H{ 
            "user": "rsj217", 
            "passwd": "123", 
            }) 
        } else if contentType == "xml" { 
            c.XML(http.StatusOK, gin.H{ 
                "user": "rsj217", 
                "passwd": "123", 
            }) 
        } 
    })

    结果如下:

    ☁ ~ curl http://127.0.0.1:8000/render\?content_type\=json 
    {"passwd":"123","user":"rsj217"} 
    
    ☁ ~ curl http://127.0.0.1:8000/render\?content_type\=xml 
    <map><user>rsj217</user><passwd>123</passwd></map>%

    重定向

    gin对于重定向的请求,相当简单。调用上下文的Redirect方法:

    router.GET("/redict/google", func(c *gin.Context) {
            c.Redirect(http.StatusMovedPermanently, "https://google.com")
    })

    分组路由

    熟悉Flask的同学应该很了解蓝图分组。Flask提供了蓝图用于管理组织分组api。gin也提供了这样的功能,让你的代码逻辑更加模块化,同时分组也易于定义中间件的使用范围。

    v1 := router.Group("/v1") 
    v1.GET("/login", func(c *gin.Context) { 
        c.String(http.StatusOK, "v1 login") 
    }) 
    
    v2 := router.Group("/v2") 
    v2.GET("/login", func(c *gin.Context) { 
        c.String(http.StatusOK, "v2 login") 
    })

    访问效果如下:

    ☁  ~  curl http://127.0.0.1:8000/v1/login
    v1 login%                                                                                 
    ☁  ~  curl http://127.0.0.1:8000/v2/login
    v2 login%

    middleware中间件

    golang的net/http设计的一大特点就是特别容易构建中间件。gin也提供了类似的中间件。需要注意的是中间件只对注册过的路由函数起作用。对于分组路由,嵌套使用中间件,可以限定中间件的作用范围。中间件分为全局中间件,单个路由中间件和群组中间件。


    全局中间件

    先定义一个中间件函数:

    func MiddleWare() gin.HandlerFunc { 
        return func(c *gin.Context) { 
            fmt.Println("before middleware") 
            c.Set("request", "clinet_request") 
            c.Next() 
            fmt.Println("before middleware") 
        } 
    }

    该函数很简单,只会给c上下文添加一个属性,并赋值。后面的路由处理器,可以根据被中间件装饰后提取其值。需要注意,虽然名为全局中间件,只要注册中间件的过程之前设置的路由,将不会受注册的中间件所影响。只有注册了中间件一下代码的路由函数规则,才会被中间件装饰。

    router.Use(MiddleWare()) { 
        router.GET("/middleware", func(c *gin.Context) { 
            request := c.MustGet("request").(string) 
            req, _ := c.Get("request") 
            c.JSON(http.StatusOK, gin.H{ 
                "middile_request": request, "request": req, 
            }) 
        }) 
    }

    使用router装饰中间件,然后在/middlerware即可读取request的值,注意在router.Use(MiddleWare())代码以上的路由函数,将不会有被中间件装饰的效果。

    使用花括号包含被装饰的路由函数只是一个代码规范,即使没有被包含在内的路由函数,只要使用router进行路由,都等于被装饰了。想要区分权限范围,可以使用组返回的对象注册中间件。

    ☁  ~  curl  http://127.0.0.1:8000/middleware
    {"middile_request":"clinet_request","request":"clinet_request"}

    如果没有注册就使用MustGet方法读取c的值将会抛错,可以使用Get方法取而代之。

    上面的注册装饰方式,会让所有下面所写的代码都默认使用了router的注册过的中间件。


    单个路由中间件

    当然,gin也提供了针对指定的路由函数进行注册。

    router.GET("/before", MiddleWare(), func(c *gin.Context) { 
        request := c.MustGet("request").(string) 
        c.JSON(http.StatusOK, gin.H{ 
            "middile_request": request, 
        }) 
    })

    群组中间件

    群组的中间件也类似,只要在对于的群组路由上注册中间件函数即可:

    authorized := router.Group("/", MyMiddelware()) 
    // 或者这样用: 
    authorized := router.Group("/") 
    authorized.Use(MyMiddelware()) { 
        authorized.POST("/login", loginEndpoint) 
    }

    群组可以嵌套,因为中间件也可以根据群组的嵌套规则嵌套。


    中间件实践

    中间件最大的作用,莫过于用于一些记录log,错误handler,还有就是对部分接口的鉴权。下面就实现一个简易的鉴权中间件。

    router.GET("/auth/signin", func(c *gin.Context) { 
        cookie := &http.Cookie{ 
            Name: "session_id", 
            Value: "123", 
            Path: "/", 
            HttpOnly: true, 
        } 
        http.SetCookie(c.Writer, cookie) 
        c.String(http.StatusOK, "Login successful") 
    }) 
    
    router.GET("/home", AuthMiddleWare(), func(c *gin.Context) { 
        c.JSON(http.StatusOK, gin.H{"data": "home"}) 
    })

    登录函数会设置一个session_id的cookie,注意这里需要指定path为/,不然gin会自动设置cookie的path为/auth,一个特别奇怪的问题。/home的逻辑很简单,使用中间件AuthMiddleWare注册之后,将会先执行AuthMiddleWare的逻辑,然后才到/home的逻辑。

    AuthMiddleWare的代码如下:

    func AuthMiddleWare() gin.HandlerFunc { 
        return func(c *gin.Context) { 
            if cookie, err := c.Request.Cookie("session_id"); err == nil { 
                value := cookie.Value fmt.Println(value) 
                if value == "123" { c.Next() return } 
            } 
            c.JSON(http.StatusUnauthorized, gin.H{ 
                "error": "Unauthorized", 
            })
            c.Abort() 
            return 
        } 
    }

    从上下文的请求中读取cookie,然后校对cookie,如果有问题,则终止请求,直接返回,这里使用了c.Abort()方法。

    In [7]: resp = requests.get('http://127.0.0.1:8000/home') 
    
    In [8]: resp.json() 
    Out[8]: {u'error': u'Unauthorized'} 
    
    In [9]: login = requests.get('http://127.0.0.1:8000/auth/signin') 
    
    In [10]: login.cookies 
    Out[10]: <RequestsCookieJar[Cookie(version=0, name='session_id', value='123', port=None, port_specified=False, domain='127.0.0.1', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False)]> 
    
    In [11]: resp = requests.get('http://127.0.0.1:8000/home', cookies=login.cookies) 
    
    In [12]: resp.json() 
    Out[12]: {u'data': u'home'}
    

    异步协程

    golang的高并发一大利器就是协程。gin里可以借助协程实现异步任务。因为涉及异步过程,请求的上下文需要copy到异步的上下文,并且这个上下文是只读的。

    router.GET("/sync", func(c *gin.Context) { 
        time.Sleep(5 * time.Second) 
        log.Println("Done! in path" + c.Request.URL.Path) 
    }) 
    
    router.GET("/async", func(c *gin.Context) { 
        cCp := c.Copy() 
        go func() { 
            time.Sleep(5 * time.Second) 
            log.Println("Done! in path" + cCp.Request.URL.Path) 
        }() 
    })

    在请求的时候,sleep5秒钟,同步的逻辑可以看到,服务的进程睡眠了。异步的逻辑则看到响应返回了,然后程序还在后台的协程处理。


    自定义router

    gin不仅可以使用框架本身的router进行Run,也可以配合使用net/http本身的功能:

    func main() {
        router := gin.Default()
        http.ListenAndServe(":8080", router)
    }

    或者

    func main() { 
        router := gin.Default() 
        s := &http.Server{ 
            Addr: ":8000", 
            Handler: router, 
            ReadTimeout: 10 * time.Second, 
            WriteTimeout: 10 * time.Second, 
            MaxHeaderBytes: 1 << 20, 
        } 
        s.ListenAndServe() 
    }

    当然还有一个优雅的重启和结束进程的方案。后面将会探索使用supervisor管理golang的进程。


    总结

    Gin是一个轻巧而强大的golang web框架。涉及常见开发的功能,我们都做了简单的介绍。关于服务的启动,请求参数的处理和响应格式的渲染,以及针对上传和中间件鉴权做了例子。更好的掌握来自实践,同时gin的源码注释很详细,可以阅读源码了解更多详细的功能和魔法特性。


    End

    展开全文
  • 轻量级的Web框架Gin教程

    千人学习 2019-02-08 20:29:33
    Gin 是一个 go 写的 web 框架,封装比较优雅,API友好,源码注释比较明确,具有高性能的优点。 Golang的高并发一大利器就是协程。Gin里可以借助协程实现异步任务。
  • go语言gin框架教程

    2019-12-29 13:52:36
    Gin 是一个用 Go (Golang) 编写的 HTTP web 框架。 它是一个类似于 martini 但拥有更好性能的 API 框架, 由于 httprouter,速度提高了近 40 倍。如果你需要极好的性能,使用 Gin 吧。
  • Go语言web框架 gin

    万次阅读 2018-01-25 15:41:00
    Go语言web框架 GIN gin是go语言环境下的一个web框架, 它类似于Martini, 官方声称它比Martini有更好的性能, 比Martini快40倍, Ohhhh….看着不错的样子, 所以就想记录一下gin的学习. gin的github代码在这里: gin...

    Go语言web框架 GIN

    gin是go语言环境下的一个web框架, 它类似于Martini, 官方声称它比Martini有更好的性能, 比Martini快40倍, Ohhhh….看着不错的样子, 所以就想记录一下gin的学习. gin的github代码在这里: gin源码. gin的效率获得如此突飞猛进, 得益于另一个开源项目httprouter, 项目地址: httprouter源码. 下面主要记录一下gin的使用.

    1. 安装gin

    go get github.com/gin-gonic/gin

    使用时:
    import "github.com/gin-gonic/gin".

    2. 使用方法

    <1> 一种最简单的使用GET/POST方法

    gin服务端代码是:

    / func1: 处理最基本的GET
    func func1 (c *gin.Context)  {
        // 回复一个200OK,在client的http-get的resp的body中获取数据
        c.String(http.StatusOK, "test1 OK")
    }
    // func2: 处理最基本的POST
    func func2 (c *gin.Context) {
        // 回复一个200 OK, 在client的http-post的resp的body中获取数据
        c.String(http.StatusOK, "test2 OK")
    }
    func main(){
        // 注册一个默认的路由器
        router := gin.Default()
        // 最基本的用法
        router.GET("/test1", func1)
        router.POST("/test2", func2)
        // 绑定端口是8888
        router.Run(":8888")
    }
    

    客户端代码是:

    func main(){
        // 调用最基本的GET,并获得返回值
        resp,_ := http.Get("http://0.0.0.0:8888/test1")
        helpRead(resp)
    
        // 调用最基本的POST,并获得返回值
        resp,_ = http.Post("http://0.0.0.0:8888/test2", "",strings.NewReader(""))
        helpRead(resp)
    }
    

    在服务端, 实例化了一个router, 然后使用GET和POST方法分别注册了两个服务, 当我们使用HTTP GET方法的时候会使用GET注册的函数, 如果使用HTTP POST的方法, 那么会使用POST注册的函数. gin支持所有的HTTP的方法例如: GET, POST, PUT, PATCH, DELETE 和 OPTIONS等. 看客户端中的代码, 当调用http.Get(“http://0.0.0.0:8888/test1”)的时候, 服务端接收到请求, 并根据/test1将请求路由到func1函数进行 处理. 同理, 调用http.Post(“http://0.0.0.0:8888/test2”, “”,strings.NewReader(""))时候, 会使用func2函数处理. 在func1和func2中, 使用gin.Context填充了一个String的回复. 当然也支持JSON, XML, HTML等其他一些格式数据. 当执行c.String或者c.JSON时, 相当于向http的回复缓冲区写入了 一些数据. 最后调用router.Run(“:8888”)开始进行监听,Run的核心代码是:

    func (engine *Engine) Run(addr string) (err error) {
    	debugPrint("Listening and serving HTTP on %s\n", addr)
    	defer func() { debugPrintError(err) }()
    	// 核心代码
    	err = http.ListenAndServe(addr, engine)
    	return
    }
    

    其本质就是http.ListenAndServe(addr, engine).
    注意: helpRead函数是用于读取response的Body的函数, 你可以自己定义, 本文中此函数定义为:

    // 用于读取resp的body
    func helpRead(resp *http.Response)  {
        defer resp.Body.Close()
        body, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            fmt.Println("ERROR2!: ", err)
        }
        fmt.Println(string(body))
    }
    

    <2> 传递参数

    传递参数有几种方法, 对应到gin使用几种不同的方式来解析.

    第一种: 使用gin.Context中的Param方法解析

    对应的服务端代码为:

    // func3: 处理带参数的path-GET
    func func3(c *gin.Context)  {
        // 回复一个200 OK
        // 获取传入的参数
        name := c.Param("name")
        passwd := c.Param("passwd")
        c.String(http.StatusOK, "参数:%s %s  test3 OK", name, passwd)
    }
    // func4: 处理带参数的path-POST
    func func4(c *gin.Context)  {
        // 回复一个200 OK
        // 获取传入的参数
        name := c.Param("name")
        passwd := c.Param("passwd")
        c.String(http.StatusOK, "参数:%s %s  test4 OK", name, passwd)
    }
    // func5: 注意':'和'*'的区别
    func func5(c *gin.Context)  {
        // 回复一个200 OK
        // 获取传入的参数
        name := c.Param("name")
        passwd := c.Param("passwd")
        c.String(http.StatusOK, "参数:%s %s  test5 OK", name, passwd)
    }
    
    func main(){
        router := gin.Default()
        // TODO:注意':'必须要匹配,'*'选择匹配,即存在就匹配,否则可以不考虑
        router.GET("/test3/:name/:passwd", func3)
        router.POST("/test4/:name/:passwd", func4)
        router.GET("/test5/:name/*passwd", func5)
    
        router.Run(":8888")
    }
    

    客户端测试代码是:

    func main() {
        // GET传参数,使用gin的Param解析格式: /test3/:name/:passwd
        resp,_ = http.Get("http://0.0.0.0:8888/test3/name=TAO/passwd=123")
        helpRead(resp)
    
        // POST传参数,使用gin的Param解析格式: /test3/:name/:passwd
        resp,_ = http.Post("http://0.0.0.0:8888/test4/name=PT/passwd=456", "",strings.NewReader(""))
        helpRead(resp)
    
        // 注意Param中':'和'*'的区别
        resp,_ = http.Get("http://0.0.0.0:8888/test5/name=TAO/passwd=789")
        helpRead(resp)
        resp,_ = http.Get("http://0.0.0.0:8888/test5/name=TAO/")
        helpRead(resp)
    }
    

    注意上面定义参数的方法有两个辅助符号: ‘:’和’’. 如果使用’:’参数方法, 那么这个参数是必须要匹配的, 例如上面的router.GET(“/test3/:name/:passwd”, func3), 当请求URL是 类似于http://0.0.0.0:8888/test3/name=TAO/passwd=123这样的参会被匹配, 如果是http://0.0.0.0:8888/test3/name=TAO 或者http://0.0.0.0:8888/test3/passwd=123是不能匹配的. 但是如果使用’‘参数, 那么这个参数是可选的. router.GET(“/test5/:name/*passwd”, func5) 可以匹配http://0.0.0.0:8888/test5/name=TAO/passwd=789, 也可以匹配http://0.0.0.0:8888/test5/name=TAO/. 需要注意的一点是, 下面这个URL是不是能够 匹配呢? http://0.0.0.0:8888/test5/name=TAO, 注意TAO后面没有’/’, 这个其实就要看有没有一个路由是到http://0.0.0.0:8888/test5/name=TAO路径的, 如果有, 那么指定的那个函数进行处理, 如果没有http://0.0.0.0:8888/test5/name=TAO会被重定向到http://0.0.0.0:8888/test5/name=TAO/, 然后被当前注册的函数进行处理.

    第二种: 使用gin.Context中的Query方法解析

    这个类似于正常的URL中的参数传递, 先看服务端代码:

    // 使用Query获取参数
    func func6(c *gin.Context)  {
        // 回复一个200 OK
        // 获取传入的参数
        name := c.Query("name")
        passwd := c.Query("passwd")
        c.String(http.StatusOK, "参数:%s %s  test6 OK", name, passwd)
    }
    // 使用Query获取参数
    func func7(c *gin.Context)  {
        // 回复一个200 OK
        // 获取传入的参数
        name := c.Query("name")
        passwd := c.Query("passwd")
        c.String(http.StatusOK, "参数:%s %s  test7 OK", name, passwd)
    }
    
    func main(){
        router := gin.Default()
        // 使用gin的Query参数形式,/test6?firstname=Jane&lastname=Doe
        router.GET("/test6", func6)
        router.POST("/test7", func7)
    
        router.Run(":8888")
    }
    

    客户端测试代码是:

    func main() {
        // 使用Query获取参数形式/test6?firstname=Jane&lastname=Doe
        resp,_ = http.Get("http://0.0.0.0:8888/test6?name=BBB&passwd=CCC")
        helpRead(resp)
        resp,_ = http.Post("http://0.0.0.0:8888/test7?name=DDD&passwd=EEE", "",strings.NewReader(""))
        helpRead(resp)
    }
    

    这种方法的参数也是接在URL后面, 形如http://0.0.0.0:8888/test6?name=BBB&passwd=CCC. 服务器可以使用name := c.Query(“name”)这种 方法来解析参数.

    第三种: 使用gin.Context中的PostForm方法解析

    我们需要将参数放在请求的Body中传递, 而不是URL中. 先看服务端代码:

    // 参数是form中获得,即从Body中获得,忽略URL中的参数
    func func8(c *gin.Context)  {
        message := c.PostForm("message")
        extra := c.PostForm("extra")
        nick := c.DefaultPostForm("nick", "anonymous")
    
        c.JSON(200, gin.H{
            "status":  "test8:posted",
            "message": message,
            "nick":    nick,
            "extra": extra,
        })
    }
    
    func main(){
        router := gin.Default()
        // 使用post_form形式,注意必须要设置Post的type,
        // 同时此方法中忽略URL中带的参数,所有的参数需要从Body中获得
        router.POST("/test8", func8)
    
        router.Run(":8888")
    }
    

    客户端代码是:

    func main() {
        // 使用post_form形式,注意必须要设置Post的type,同时此方法中忽略URL中带的参数,所有的参数需要从Body中获得
        resp,_ = http.Post("http://0.0.0.0:8888/test8", "application/x-www-form-urlencoded",strings.NewReader("message=8888888&extra=999999"))
        helpRead(resp)
    }
    

    由于我们使用了request Body, 那么就需要指定Body中数据的形式, 此处是form格式, 即application/x-www-form-urlencoded. 常见的几种http提交数据方式有: application/x-www-form-urlencoded; multipart/form-data; application/json; text/xml. 具体使用请google.
    在服务端, 使用message := c.PostForm(“message”)方法解析参数, 然后进行处理.

    <3> 传输文件
    下面测试从client传输文件到server. 传输文件需要使用multipart/form-data格式的数据, 所有需要设定Post的类型是multipart/form-data.
    首先看服务端代码:

    // 接收client上传的文件
    // 从FormFile中获取相关的文件data!
    // 然后写入本地文件
    func func9(c *gin.Context) {
        // 注意此处的文件名和client处的应该是一样的
        file, header , err := c.Request.FormFile("uploadFile")
        filename := header.Filename
        fmt.Println(header.Filename)
        // 创建临时接收文件
        out, err := os.Create("copy_"+filename)
        if err != nil {
            log.Fatal(err)
        }
        defer out.Close()
        // Copy数据
        _, err = io.Copy(out, file)
        if err != nil {
            log.Fatal(err)
        }
        c.String(http.StatusOK, "upload file success")
    }
    
    func main(){
        router := gin.Default()
        // 接收上传的文件,需要使用
        router.POST("/upload", func9)
    
        router.Run(":8888")
    }
    

    客户端代码是:

    func main() {
        // 上传文件POST
        // 下面构造一个文件buf作为POST的BODY
        buf := new(bytes.Buffer)
        w := multipart.NewWriter(buf)
        fw,_ := w.CreateFormFile("uploadFile", "images.png") //这里的uploadFile必须和服务器端的FormFile-name一致
        fd,_ := os.Open("images.png")
        defer fd.Close()
        io.Copy(fw, fd)
        w.Close()
        resp,_ = http.Post("http://0.0.0.0:8888/upload", w.FormDataContentType(), buf)
        helpRead(resp)
    }
    

    首先客户端本地需要有一张”images.png”图片, 同时需要创建一个Form, 并将field-name命名为”uploadFile”, file-name命名为”images.png”. 在服务端, 通过”uploadFile”可以得到文件信息. 客户端继续将图片数据copy到创建好的Form中, 将数据数据Post出去, 注意数据的类型指定! 在服务端, 通过file, header , err := c.Request.FormFile(“uploadFile”)获得文件信息, file中就是文件数据, 将其拷贝到本地文件, 完成文件传输.

    <4> binding数据

    gin内置了几种数据的绑定例如JSON, XML等. 简单来说, 即根据Body数据类型, 将数据赋值到指定的结构体变量中. (类似于序列化和反序列化)
    看服务端代码:

    // Binding数据
    // 注意:后面的form:user表示在form中这个字段是user,不是User, 同样json:user也是
    // 注意:binding:"required"要求这个字段在client端发送的时候必须存在,否则报错!
    type Login struct {
        User     string `form:"user" json:"user" binding:"required"`
        Password string `form:"password" json:"password" binding:"required"`
    }
    // bind JSON数据
    func funcBindJSON(c *gin.Context) {
        var json Login
        // binding JSON,本质是将request中的Body中的数据按照JSON格式解析到json变量中
        if c.BindJSON(&json) == nil {
            if json.User == "TAO" && json.Password == "123" {
                c.JSON(http.StatusOK, gin.H{"JSON=== status": "you are logged in"})
            } else {
                c.JSON(http.StatusUnauthorized, gin.H{"JSON=== status": "unauthorized"})
            }
        } else {
            c.JSON(404, gin.H{"JSON=== status": "binding JSON error!"})
        }
    }
    
    // 下面测试bind FORM数据
    func funcBindForm(c *gin.Context) {
        var form Login
        // 本质是将c中的request中的BODY数据解析到form中
    
        // 方法一: 对于FORM数据直接使用Bind函数, 默认使用使用form格式解析,if c.Bind(&form) == nil
        // 方法二: 使用BindWith函数,如果你明确知道数据的类型
        if c.BindWith(&form, binding.Form) == nil{
            if form.User == "TAO" && form.Password == "123" {
                c.JSON(http.StatusOK, gin.H{"FORM=== status": "you are logged in"})
            } else {
                c.JSON(http.StatusUnauthorized, gin.H{"FORM=== status": "unauthorized"})
            }
        } else {
            c.JSON(404, gin.H{"FORM=== status": "binding FORM error!"})
        }
    }
    
    func main(){
        router := gin.Default()
        // 下面测试bind JSON数据
        router.POST("/bindJSON", funcBindJSON)
    
        // 下面测试bind FORM数据
        router.POST("/bindForm", funcBindForm)
    
        // 下面测试JSON,XML等格式的rendering
        router.GET("/someJSON", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"message": "hey, budy", "status": http.StatusOK})
        })
    
        router.GET("/moreJSON", func(c *gin.Context) {
            // 注意:这里定义了tag指示在json中显示的是user不是User
            var msg struct {
                Name    string `json:"user"`
                Message string
                Number  int
            }
            msg.Name = "TAO"
            msg.Message = "hey, budy"
            msg.Number = 123
            // 下面的在client的显示是"user": "TAO",不是"User": "TAO"
            // 所以总体的显示是:{"user": "TAO", "Message": "hey, budy", "Number": 123
            c.JSON(http.StatusOK, msg)
        })
    
        //  测试发送XML数据
        router.GET("/someXML", func(c *gin.Context) {
            c.XML(http.StatusOK, gin.H{"name":"TAO", "message": "hey, budy", "status": http.StatusOK})
        })
    
        router.Run(":8888")
    

    客户端代码:

    func main() {
        // 下面测试binding数据
        // 首先测试binding-JSON,
        // 注意Body中的数据必须是JSON格式
        resp,_ = http.Post("http://0.0.0.0:8888/bindJSON", "application/json", strings.NewReader("{\"user\":\"TAO\", \"password\": \"123\"}"))
        helpRead(resp)
    
        // 下面测试bind FORM数据
        resp,_ = http.Post("http://0.0.0.0:8888/bindForm", "application/x-www-form-urlencoded", strings.NewReader("user=TAO&password=123"))
        helpRead(resp)
    
        // 下面测试接收JSON和XML数据
        resp,_ = http.Get("http://0.0.0.0:8888/someJSON")
        helpRead(resp)
        resp,_ = http.Get("http://0.0.0.0:8888/moreJSON")
        helpRead(resp)
        resp,_ = http.Get("http://0.0.0.0:8888/someXML")
        helpRead(resp)
    }
    

    客户端发送请求, 在服务端可以直接使用c.BindJSON绑定到Json结构体上. 或者使用BindWith函数也可以, 但是需要指定绑定的数据类型, 例如JSON, XML, HTML等. Bind*函数的本质是读取request中的body数据, 拿BindJSON为例, 其核心代码是:

    func (_ jsonBinding) Bind(req *http.Request, obj interface{}) error {
    	// 核心代码: decode请求的body到obj中
    	decoder := json.NewDecoder(req.Body)
    	if err := decoder.Decode(obj); err != nil {
    		return err
    	}
    	return validate(obj)
    }
    

    <5> router group

    router group是为了方便前缀相同的URL的管理, 其基本用法如下.
    首先看服务端代码:

    // router GROUP - GET测试
    func func10(c *gin.Context)  {
        c.String(http.StatusOK, "test10 OK")
    }
    func func11(c *gin.Context)  {
        c.String(http.StatusOK, "test11 OK")
    }
    
    // router GROUP - POST测试
    func func12(c *gin.Context)  {
        c.String(http.StatusOK, "test12 OK")
    }
    func func13(c *gin.Context)  {
        c.String(http.StatusOK, "test13 OK")
    }
    
    func main(){
        router := gin.Default()
        // router Group是为了将一些前缀相同的URL请求放在一起管理
        group1 := router.Group("/g1")
        group1.GET("/read1", func10)
        group1.GET("/read2", func11)
    
        group2 := router.Group("/g2")
        group2.POST("/write1", func12)
        group2.POST("/write2", func13)
    
        router.Run(":8888")
    }
    

    客户端测试代码:

    func main() {
        // 下面测试router 的GROUP
        resp,_ = http.Get("http://0.0.0.0:8888/g1/read1")
        helpRead(resp)
        resp,_ = http.Get("http://0.0.0.0:8888/g1/read2")
        helpRead(resp)
        resp,_ = http.Post("http://0.0.0.0:8888/g2/write1", "", strings.NewReader(""))
        helpRead(resp)
        resp,_ = http.Post("http://0.0.0.0:8888/g2/write2", "", strings.NewReader(""))
        helpRead(resp)
    }
    

    在服务端代码中, 首先创建了一个组group1 := router.Group(“/g1”), 并在这个组下注册了两个服务, group1.GET(“/read1”, func10) 和group1.GET(“/read2”, func11), 那么当使用http://0.0.0.0:8888/g1/read1和http://0.0.0.0:8888/g1/read2访问时, 是可以路由 到上面注册的位置的. 同理对于group2 := router.Group(“/g2”)也是一样的.

    <6> 静态文件服务

    可以向客户端展示本地的一些文件信息, 例如显示某路径下地文件. 服务端代码是:

    func main(){
        router := gin.Default()
        // 下面测试静态文件服务
        // 显示当前文件夹下的所有文件/或者指定文件
        router.StaticFS("/showDir", http.Dir("."))
        router.Static("/files", "/bin")
        router.StaticFile("/image", "./assets/1.png")
    
        router.Run(":8888")
    }
    

    首先你需要在服务器的路径下创建一个assert文件夹, 并且放入1.png文件. 如果已经存在, 请忽略.
    测试代码: 请在浏览器中输入0.0.0.0:8888/showDir, 显示的是服务器当前路径下地文件信息:

    1这里写图片描述

    输入0.0.0.0:8888/files, 显示的是/bin目录下地文件信息:

    2这里写图片描述

    输入0.0.0.0:8888/image, 显示的是服务器下地./assets/1.png图片:

    3这里写图片描述

    <7> 加载模板templates

    gin支持加载HTML模板, 然后根据模板参数进行配置并返回相应的数据.
    看服务端代码

    func main(){
        router := gin.Default()
        // 下面测试加载HTML: LoadHTMLTemplates
        // 加载templates文件夹下所有的文件
        router.LoadHTMLGlob("templates/*")
        // 或者使用这种方法加载也是OK的: router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
        router.GET("/index", func(c *gin.Context) {
            // 注意下面将gin.H参数传入index.tmpl中!也就是使用的是index.tmpl模板
            c.HTML(http.StatusOK, "index.tmpl", gin.H{
                "title": "GIN: 测试加载HTML模板",
            })
        })
    
        router.Run(":8888")
    }
    

    客户端测试代码是:

    func main() {
        // 测试加载HTML模板
        resp,_ = http.Get("http://0.0.0.0:8888/index")
        helpRead(resp)
    }
    

    在服务端, 我们需要加载需要的templates, 这里有两种方法: 第一种使用LoadHTMLGlob加载所有的正则匹配的模板, 本例中使用的是*, 即匹配所有文件, 所以加载的是 templates文件夹下所有的模板. 第二种使用LoadHTMLFiles加载指定文件. 在本例服务器路径下有一个templates目录, 下面有一个index.tmpl模板, 模板的 内容是:

    <html>
        <h1>
           { { .title } }
        </h1>
    </html>
    

    当客户端请求/index时, 服务器使用这个模板, 并填充相应的参数, 此处参数只有title, 然后将HTML数据返回给客户端.
    你也可以在浏览器请求0.0.0.0:8888/index, 效果如下图所示:
    这里写图片描述

    <8> 重定向

    重定向相对比较简单, 服务端代码是:

    func main(){
        router := gin.Default()
        // 下面测试重定向
        router.GET("/redirect", func(c *gin.Context) {
            c.Redirect(http.StatusMovedPermanently, "http://shanshanpt.github.io/")
        })
    
        router.Run(":8888")
    }
    

    客户端测试代码是:

    func main() {
        // 下面测试重定向
        resp,_ = http.Get("http://0.0.0.0:8888/redirect")
        helpRead(resp)
    }
    

    当我们请求http://0.0.0.0:8888/redirect的时候, 会重定向到http://shanshanpt.github.io/这个站点.

    <9> 使用middleware

    这里使用了两个例子, 一个是logger, 另一个是BasiAuth, 具体看服务器代码:

    func Logger() gin.HandlerFunc {
        return func(c *gin.Context) {
            t := time.Now()
            // 设置example变量到Context的Key中,通过Get等函数可以取得
            c.Set("example", "12345")
            // 发送request之前
            c.Next()
            // 发送request之后
            latency := time.Since(t)
            log.Print(latency)
    
            // 这个c.Write是ResponseWriter,我们可以获得状态等信息
            status := c.Writer.Status()
            log.Println(status)
        }
    }
    
    
    func main(){
        router := gin.Default()
        // 1
        router.Use(Logger())
        router.GET("/logger", func(c *gin.Context) {
            example := c.MustGet("example").(string)
            log.Println(example)
        })
    
        // 2
        // 下面测试BasicAuth()中间件登录认证
        //
        var secrets = gin.H{
            "foo":    gin.H{"email": "foo@bar.com", "phone": "123433"},
            "austin": gin.H{"email": "austin@example.com", "phone": "666"},
            "lena":   gin.H{"email": "lena@guapa.com", "phone": "523443"},
        }
        // Group using gin.BasicAuth() middleware
        // gin.Accounts is a shortcut for map[string]string
        authorized := router.Group("/admin", gin.BasicAuth(gin.Accounts{
            "foo":    "bar",
            "austin": "1234",
            "lena":   "hello2",
            "manu":   "4321",
        }))
        // 请求URL: 0.0.0.0:8888/admin/secrets
        authorized.GET("/secrets", func(c *gin.Context) {
            // get user, it was set by the BasicAuth middleware
            user := c.MustGet(gin.AuthUserKey).(string)
            if secret, ok := secrets[user]; ok {
                c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
            } else {
                c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
            }
        })
    
        router.Run(":8888")
    }
    

    客户端测试代码是:

    func main() {
        // 下面测试使用中间件
        resp,_ = http.Get("http://0.0.0.0:8888/logger")
        helpRead(resp)
    
        // 测试验证权限中间件BasicAuth
        resp,_ = http.Get("http://0.0.0.0:8888/admin/secrets")
        helpRead(resp)
    }
    

    服务端使用Use方法导入middleware, 当请求/logger来到的时候, 会执行Logger(), 并且我们知道在GET注册的时候, 同时注册了匿名函数, 所有请看Logger函数中存在一个c.Next()的用法, 它是取出所有的注册的函数都执行一遍, 然后再回到本函数中, 所以, 本例中相当于是先执行了 c.Next()即注册的匿名函数, 然后回到本函数继续执行. 所以本例的Print的输出顺序是:
    log.Println(example)
    log.Print(latency)
    log.Println(status)
    如果将c.Next()放在log.Print(latency)后面, 那么log.Println(example)和log.Print(latency)执行的顺序就调换了. 所以一切都取决于c.Next()执行的位置. c.Next()的核心代码如下:

    // Next should be used only in the middlewares.
    // It executes the pending handlers in the chain inside the calling handler.
    // See example in github.
    func (c *Context) Next() {
    	c.index++
    	s := int8(len(c.handlers))
    	for ; c.index < s; c.index++ {
    		c.handlers[c.index](c)
    	}
    }
    

    它其实是执行了后面所有的handlers.
    关于使用gin.BasicAuth() middleware, 可以直接使用一个router group进行处理, 本质和logger一样.

    <10> 绑定http server

    之前所有的测试中, 我们都是使用router.Run(":8888")开始执行监听, 其实还有两种方法:

    // 方法二
    http.ListenAndServe(":8888", router)
    
    // 方法三:
    server := &http.Server{
        Addr:           ":8888",
        Handler:        router,
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }
    server.ListenAndServe()
    
    

    至此, gin最基本的一些应用都整理完了, 下面就具体看看代码中的一些实现. 有时间再记录吧.
    写几个注意事项把:
    1.中间件的使用插拔可以这样写:router.Use(MymiddleWare),写在该语句后面的路由转发,都会经过中间件的过滤,之前的不受影响,对单个路由转发使用一个或者多个中间件可以这样写 router.GET("/",MymiddleWare1,MymiddleWare2,HandleFunc)
    2.对c *gin.Context的c.Query(),c.Param()都只适用于GET请求的地址后的参数,如果要接收POST数据,必须使用c.Bind(&buf)

    ###3.参考:
    gin-github

    展开全文
  • gin离线安装包

    2018-10-15 22:58:09
    $ go get gopkg.in/gin-gonic/gin.v1由于被墙,会下载失败,这里提供下载好的包。 Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,已经发布了1.0版本。具有快速灵活,容错方便等特点。其实对于...
  • Gin框架常用内容

    2020-11-13 10:12:16
    文章目录安装-go mod版Hello World路由标准路由any路由静态资源路由泛绑定请求中间件gin.Context操作日志 安装-go mod版 go get github.com/gin-gonic/gin Hello World package main import ( "github....

    安装-go mod版

    go get github.com/gin-gonic/gin
    

    Hello World

    package main
    
    import (
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	r := gin.Default()
    	r.GET("/gin", func(c *gin.Context) {
    		c.JSON(200, gin.H{"Hello": "Gin"})
    	})
    	r.Run()
    }
    
    

    路由

    标准路由

    package main
    
    import "github.com/gin-gonic/gin"
    
    func main() {
    	r := gin.Default()
    	r.GET("/get", func(c *gin.Context) {
    		c.String(200, "get")
    	})
    	r.POST("/post", func(c *gin.Context) {
    		c.String(200, "post")
    	})
    	r.Run()
    }
    

    any路由

    8种常见请求类型

    r.Any("/any", func(c *gin.Context) {
    		c.String(200, "any")
    	})
    

    静态资源路由

    第一个参数是请求匹配路径,第二个参数为实际目录

    package main
    
    import (
    	"net/http"
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	r := gin.Default()
        #第一种 
    	r.Static("/ass", "../ass")
        #第二种
    	r.StaticFS("/aas", http.Dir("aas"))
        #三种效果一样,第三种路由单个文件
    	r.StaticFile("/a.txt", "../a.txt")
    	r.Run()
    }
    
    

    泛绑定请求

    e.g.:r.GET("/url/*action",func)
    #将会匹配所有/url/开头的请求
    

    中间件

    r.Use(func() gin.HandlerFunc{
    	return func(c *gin.Context){
    		//拦住代码序后的请求
    		/*先一步对请求作出处理*/
    		c.Next()	//执行请求匹配路由或下一个中间间
    	}
    })
    

    gin.Context操作

    c *gin.Context
    c.Request.FormValue("js对象属性") //返回String
    //请求返回数据 参数:状态码,数据
    c.JSON(,)
    c.String(,)
    c.Data(状态码,数据类型(e.g.:"application/json"),数据)
    

    日志

    // 禁用控制台颜色
    gin.DisableConsoleColor()
    f, _ := os.Create("gin.log")
    gin.DefaultWriter = io.MultiWriter(f)
    
    展开全文
  • GIN安装以及初步入门

    2019-10-24 20:22:08
    0.介绍 1.下载 go get -u github.com/gin-gonic/gin 2.引入 import "github.com/gin-gonic/gin" ... r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "...
  • gin阶段学习(入门)

    2020-04-10 14:47:55
    文章目录1.环境配置2.测试运行3.请求路由3.1 多种请求类型3.2 静态文件夹3.3 参数作为URL4.获取请求参数4.1 获取get请求4.2 泛绑定4.3 获取body内容值4.4 获取bind参数4....1.环境配置 假设你的go环境已经配置好了 ...
  • 文章目录Gin介绍安装快速开始路径参数构造参数post传参get+post混合形式构造Map格式 Gin介绍 Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,已经发布了1.0版本。具有快速灵活,容错方便等...
  • gin 入门教程

    千次阅读 2018-12-16 16:59:01
    周日花时间研究了gin框架的使用,基本上还算满意。 参考:https://blog.csdn.net/u010649766/article/details/79458004 代码参考:https://github.com/onebig32/gin-learn 教程代码:...
  • Gin框架一】Gin简介

    千次阅读 2019-06-18 20:08:49
    Gin是一个golang的微框架,封装比较优雅,API友好。具有快速灵活,容错方便等特点。Gin自身的net/http足够简单,性能也非常不错 Gin下载: https://github.com/gin-gonic/gin 英文文档:...
  • gin框架

    千次阅读 2018-12-24 09:54:50
    gin是用Go语言写的后端web框架,简洁、轻量、支持高并发, 官方网站:https://gin-gonic.github.io/gin/ Github地址:https://github.com/gin-gonic/gin 下载 go get -u github.com/gin-gonic/gin 源码结构如下 ...
  • Go:gin框架

    千次阅读 2019-08-28 14:58:19
    Gin框架介绍 1. 简介 A. 基于httprouter开发的web框架。http://github.com/julienschmidt/httprouter B. 提供Martini风格的API,但比Martini要快40倍 C. 非常轻量级,使用起来非常简洁 2. Gin框架安装与使用...
  • golang中的gin框架学习

    千次阅读 2019-03-29 21:00:24
    gin框架中常用方法 gin.H{ } 有这么一行c.JSON(200, gin.H{“message”: “use get method”})  这其中有一个gin.H{ },看样子,这像是一个结构体struct,查看gin框架的源码,声明如下:  所以,这只是一个map结构...
  • 深入Gin框架内幕(一)

    千次阅读 2020-01-11 18:58:39
    Gin框架介绍 Gin是一个用 Go (Golang) 编写的 web 框架。它是一个类似于martini但性能更好的API框架,不同于谢大主导的Beegoweb框架,后者更像是Python语言中的Django框架,内部包含了开发一个web程序所需的各种组件...
  • Gin 框架

    2020-11-03 11:40:02
    Gin框架介绍 Gin是一个用 Go (Golang) 编写的 web 框架。它是一个类似于martini但性能更好的API框架,不同于谢大主导的Beegoweb框架,后者更像是Python语言中的Django框架,内部包含了开发一个web程序所需的各种组件...
  • Gin框架

    2020-02-13 19:19:45
    使用 特性 路由基于radix tree,解析URL速度快 支持中间件,如日志、鉴权 panic自动recover ...把request和response都封装到gin.Context的上下文环境 获取参数 val := c.Query(<name>) 不存参数时...
  • Gin框架安装渲染HTML渲染静态文件补充文件路径处理JSON渲染获取参数获取querystring参数获取form参数获取path参数参数绑定文件上传单个文件上传多个文件上传重定向HTTP重定向路由重定向Gin路由 安装 使用go mod来...
  • 封装了 Gin Web 服务配置、数据库/连接池配置、视图配置,方便快速构建 Go Web 工程。自带一套用于体验及演示的 Restful Api 代码示例。
  • Golang 入门-Gin框架深入了解使用

    多人点赞 热门讨论 2020-03-19 17:23:01
    框架架构 HTTP 服务器 1.默认服务器 router.Run() 2.HTTP 服务器 除了默认服务器中router.Run()的方式外,还可以用http.ListenAndServe(),比如 func main() { router := gin.Default() ...
  • 我在搜索的时候被误导了一下,有知乎网友说,Golang就不用框架 的确,书上或者网上的DEMO都比较简单,对于有编程基础的人而言,的确很容易上手,但是对于长期做后端的人而言,其实并不会深刻感觉到有坑 开发中遇到的...
  • gin框架之中间件

    千次阅读 2019-07-27 01:49:04
    需求是有些方法或操作需要在所有路由之前执行,这里就需要用到gin框架的中间件了。准确地说是构造自己的中间件,供gin框架调用、执行。还是先从main.go代码看起: package main import ( "study/route" ) func ...
  • go语言Gin框架教程

    2020-08-05 18:28:16
    本文作者:陈进坚 个人博客:https://jian1098.github.io CSDN博客:https://blog.csdn.net/c_jian ... 下载使用 $ go get -u github.com/gin-gonic/gin import "github.com/gin-gonic/gin" ... router := gin..
  • Gin框架学习笔记

    2020-06-29 17:34:12
    Gin基础知识 安装gin以及快速开始 首先在GOPATH目录src下创建所需的项目文件 MacdeMacBook-pro-3:~ mac$ mkdir -p /Users/mac/go/src/github.com/Threadalive/gin_test_project MacdeMacBook-pro-3:~ mac$ cd ...
  • 深入Gin框架内幕(二)

    2020-01-13 07:32:13
    在上一篇文章深入gin框架内幕(一)中,主要介绍了Gin框架中是如何创建一个HTTP服务以及内部的核心结构和常用的一些结构体方法,并在最后以一个简单的示例来详细讲解Gin框架内部具体是如何运行的,但是在最后我们会...
  • Go语言 两种企业型语言, 一种是Java 另一种就是 Go语言,Java 和 Go 虽然都能实现并发,但是从底层而言,Java 作为上世纪90年代诞生的语言,并不是原生支持高并发,而Go语言不同,在2005年时, ...gin框架 与pyt...
  • Gin框架介绍及使用

    2020-03-03 08:31:40
    Gin是一个用Go语言编写的web框架。...Gin框架介绍 Go世界里最流行的Web框架,Github上有32K+star。 基于httprouter开发的Web框架。 中文文档齐全,简单易用的轻量级框架。 Gin框架安装与使用 安装 下载并安...
  • gin框架的简单使用

    2020-04-03 16:51:55
    gin框架是基于golang语言的web框架。如果用gin实现服务,有下述的场景需求,那么可以继续阅览: 服务针对不同的路由,有不同的验证规则。比如服务对应PC管理端和移动端,分别有不同的验证规则,涉及路由组、中间件...
  • gin框架安装

    千次阅读 2018-12-05 21:15:27
    为了研究gin框架,真是废了大劲了,因此做下记录,在安装前,烧香拜佛保佑顺利。 执行 go get -x github.com/gin-gonic/gin   如果顺利,真要感谢老天爷 如果不顺利碰到: (1)package gopkg.in/go-...

空空如也

1 2 3 4 5 ... 20
收藏数 16,375
精华内容 6,550
关键字:

gin