From 1361db0274c338af5719c7c71057ed32480c9f6d Mon Sep 17 00:00:00 2001 From: Huangzj Date: Wed, 9 Dec 2020 09:45:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(Go-StudyExample):=202020/12/9=20=EF=BC=9A?= =?UTF-8?q?=E6=96=B0=E5=A2=9EGin=E6=A1=86=E6=9E=B6=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/gin/1-AsciiJson_test.go | 34 +++++++ example/gin/10-Render_test.go | 86 +++++++++++++++++ example/gin/11-OneFile_test.go | 40 ++++++++ example/gin/12-MultiFile_test.go | 42 ++++++++ example/gin/15-GeneralSwitch_test.go | 52 ++++++++++ example/gin/2-HtmlRender_test.go | 7 ++ example/gin/20-ShouldBind_test.go | 60 ++++++++++++ example/gin/22-BindValid_test.go | 133 ++++++++++++++++++++++++++ example/gin/24-BindUri_test.go | 38 ++++++++ example/gin/28-DiyHttpConfig_test.go | 29 ++++++ example/gin/29-DiyMiddleWare_test.go | 51 ++++++++++ example/gin/3-Http2Server_test.go | 7 ++ example/gin/30-DiyStructValid_test.go | 64 +++++++++++++ example/gin/31-ReadCookie_test.go | 32 +++++++ example/gin/32-RouterMatch_test.go | 42 ++++++++ example/gin/33-RouteGroup_test.go | 48 ++++++++++ example/gin/35-Redirect_test.go | 25 +++++ example/gin/4-Jsonp_test.go | 34 +++++++ example/gin/7-PureJson_test.go | 7 ++ example/gin/8-QueryPostForm_test.go | 34 +++++++ example/gin/readme.md | 15 +++ readme.md | 1 + 22 files changed, 881 insertions(+) create mode 100644 example/gin/1-AsciiJson_test.go create mode 100644 example/gin/10-Render_test.go create mode 100644 example/gin/11-OneFile_test.go create mode 100644 example/gin/12-MultiFile_test.go create mode 100644 example/gin/15-GeneralSwitch_test.go create mode 100644 example/gin/2-HtmlRender_test.go create mode 100644 example/gin/20-ShouldBind_test.go create mode 100644 example/gin/22-BindValid_test.go create mode 100644 example/gin/24-BindUri_test.go create mode 100644 example/gin/28-DiyHttpConfig_test.go create mode 100644 example/gin/29-DiyMiddleWare_test.go create mode 100644 example/gin/3-Http2Server_test.go create mode 100644 example/gin/30-DiyStructValid_test.go create mode 100644 example/gin/31-ReadCookie_test.go create mode 100644 example/gin/32-RouterMatch_test.go create mode 100644 example/gin/33-RouteGroup_test.go create mode 100644 example/gin/35-Redirect_test.go create mode 100644 example/gin/4-Jsonp_test.go create mode 100644 example/gin/7-PureJson_test.go create mode 100644 example/gin/8-QueryPostForm_test.go create mode 100644 example/gin/readme.md diff --git a/example/gin/1-AsciiJson_test.go b/example/gin/1-AsciiJson_test.go new file mode 100644 index 0000000..a9e180d --- /dev/null +++ b/example/gin/1-AsciiJson_test.go @@ -0,0 +1,34 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 9:18 + * @Description: + */ + +package gin + +import ( + "fmt" + "net/http" + "testing" +) +import "github.com/gin-gonic/gin" + +func TestAsciiJson(t *testing.T) { + r := gin.Default() + + r.GET("/someJSON", func(c *gin.Context) { + data := map[string]interface{}{ + "lang": "GO语言", + "tag": "
", + } + + // 输出 : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} + c.AsciiJSON(http.StatusOK, data) + }) + + // 监听并在 0.0.0.0:8080 上启动服务 + err := r.Run(":8080") + if err != nil { + fmt.Println(err.Error()) + } +} diff --git a/example/gin/10-Render_test.go b/example/gin/10-Render_test.go new file mode 100644 index 0000000..e0f6507 --- /dev/null +++ b/example/gin/10-Render_test.go @@ -0,0 +1,86 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 9:46 + * @Description: + */ + +package gin + +import ( + "github.com/gin-gonic/gin" + "net/http" + "testing" +) + +type Test struct { + Label *string + Reps []int64 +} + +func TestRender(t *testing.T) { + r := gin.Default() + + byMap(r) //直接通过map返回 + + byStructWithAlias(r) //通过结构体返回,并设置结构体别名 + + byXml(r) //返回xml报文 + + byYaml(r) //返回YAML报文 + + byProtoBuf(r) //返回序列化的二进制数据 + + // 监听并在 0.0.0.0:8080 上启动服务 + r.Run(":8080") +} + +func byProtoBuf(r *gin.Engine) { + r.GET("/someProtoBuf", func(c *gin.Context) { + reps := []int64{int64(1), int64(2)} + label := "test" + // protobuf 的具体定义写在 testdata/protoexample 文件中。 + data := &Test{ + Label: &label, + Reps: reps, + } + // 请注意,数据在响应中变为二进制数据 + // 将输出被 protoexample.Test protobuf 序列化了的数据 + c.ProtoBuf(http.StatusOK, data) + }) +} + +func byYaml(r *gin.Engine) { + r.GET("/someYAML", func(c *gin.Context) { + c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) + }) +} + +func byXml(r *gin.Engine) { + r.GET("/someXML", func(c *gin.Context) { + c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) + }) +} + +func byStructWithAlias(r *gin.Engine) { + 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) + }) +} + +func byMap(r *gin.Engine) { + // gin.H 是 map[string]interface{} 的一种快捷方式 + r.GET("/someJSON", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) + }) +} diff --git a/example/gin/11-OneFile_test.go b/example/gin/11-OneFile_test.go new file mode 100644 index 0000000..8fe0807 --- /dev/null +++ b/example/gin/11-OneFile_test.go @@ -0,0 +1,40 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 10:51 + * @Description:单文件上传 + */ + +package gin + +import ( + "fmt" + "github.com/gin-gonic/gin" + "log" + "net/http" + "testing" +) + +func TestOneFile(t *testing.T) { + router := gin.Default() + // 为 multipart forms 设置较低的内存限制 (默认是 32 MiB) + router.MaxMultipartMemory = 8 << 20 // 8 MiB + router.POST("/upload", func(c *gin.Context) { + // 单文件 + fileHeader, _ := c.FormFile("file") + log.Println(fileHeader.Filename) + + var content []byte + file, _ := fileHeader.Open() + defer file.Close() + _, _ = file.Read(content) + fmt.Println(string(content)) //输出文件内容 + + //上传文件至指定目录 + err := c.SaveUploadedFile(fileHeader, "C://Users//admin//Desktop") + if err != nil { + panic(err) + } + c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", fileHeader.Filename)) + }) + router.Run(":8080") +} diff --git a/example/gin/12-MultiFile_test.go b/example/gin/12-MultiFile_test.go new file mode 100644 index 0000000..59e7b2f --- /dev/null +++ b/example/gin/12-MultiFile_test.go @@ -0,0 +1,42 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 11:10 + * @Description: + */ + +package gin + +import ( + "fmt" + "github.com/gin-gonic/gin" + "log" + "net/http" + "testing" +) + +//上传命令,指定多文件数组 +// curl -X POST http://localhost:8080/upload \ +// -F "upload[]=@/Users/appleboy/test1.zip" \ +// -F "upload[]=@/Users/appleboy/test2.zip" \ +// -H "Content-Type: multipart/form-data" +func TestMultiFile(t *testing.T) { + router := gin.Default() + // 为 multipart forms 设置较低的内存限制 (默认是 32 MiB) + // router.MaxMultipartMemory = 8 << 20 // 8 MiB + router.POST("/upload", func(c *gin.Context) { + // Multipart form + form, _ := c.MultipartForm() + fileHeaders := form.File["upload[]"] + + for _, header := range fileHeaders { + log.Println(header.Filename) + file, _ := header.Open() + var content []byte + file.Read(content) //读取文件 + + // c.SaveUploadedFile(file, dst) //上传文件至指定目录 + } + c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(fileHeaders))) + }) + router.Run(":8080") +} diff --git a/example/gin/15-GeneralSwitch_test.go b/example/gin/15-GeneralSwitch_test.go new file mode 100644 index 0000000..303735c --- /dev/null +++ b/example/gin/15-GeneralSwitch_test.go @@ -0,0 +1,52 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 11:18 + * @Description: + */ + +package gin + +import ( + "context" + "github.com/gin-gonic/gin" + "log" + "net/http" + "os" + "os/signal" + "testing" + "time" +) + +func TestGeneralSwitch(t *testing.T) { + 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") + +} diff --git a/example/gin/2-HtmlRender_test.go b/example/gin/2-HtmlRender_test.go new file mode 100644 index 0000000..444cbe5 --- /dev/null +++ b/example/gin/2-HtmlRender_test.go @@ -0,0 +1,7 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 9:27 + * @Description: + */ + +package gin diff --git a/example/gin/20-ShouldBind_test.go b/example/gin/20-ShouldBind_test.go new file mode 100644 index 0000000..8daa24f --- /dev/null +++ b/example/gin/20-ShouldBind_test.go @@ -0,0 +1,60 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 11:33 + * @Description: + */ + +package gin + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "testing" +) + +type formA struct { + Foo string `json:"foo" xml:"foo" binding:"required"` +} + +type formB struct { + Bar string `json:"bar" xml:"bar" binding:"required"` +} + +func TestBind(t *testing.T) { + router := gin.Default() + + bindOnce(router) //只能绑定一次 + bindMore(router) //可以多次绑定 +} + +func bindMore(router *gin.Engine) { + objA := formA{} + objB := formB{} + + // 读取 c.Request.Body 并将结果存入上下文,所以下一次的读取可以从上下文中拿到,达到重复读取的目的 + // 会对性能造成轻微影响,如果调用一次就能完成绑定的话,那就不要用这个方法。 + router.GET("bindMany", func(context *gin.Context) { + if errA := context.ShouldBindBodyWith(&objA, binding.JSON); errA == nil { + fmt.Println(objA.Foo) + //绑定不同的格式,这边演示xml的格式进行绑定 + } else if errB := context.ShouldBindBodyWith(&objB, binding.XML); errB == nil { + fmt.Println(objB.Bar) + } + }) +} + +func bindOnce(router *gin.Engine) { + objA := formA{} + objB := formB{} + + //c.ShouldBind 使用了 c.Request.Body,不可重用。请求是数据流,没有存储下来则只能使用一次 + router.GET("bind", func(context *gin.Context) { + //绑定Body数据到结构体. + if errA := context.ShouldBind(&objA); errA == nil { + fmt.Println(objA.Foo) + } else if errB := context.ShouldBind(&objB); errB == nil { + fmt.Println(objB.Bar) + } + }) +} diff --git a/example/gin/22-BindValid_test.go b/example/gin/22-BindValid_test.go new file mode 100644 index 0000000..ead101b --- /dev/null +++ b/example/gin/22-BindValid_test.go @@ -0,0 +1,133 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 11:58 + * @Description:模型绑定和验证 + */ + +package gin + +import ( + "fmt" + "github.com/gin-gonic/gin" + "net/http" + "testing" +) + +func doc() string { + return ` + 要将请求体绑定到结构体中,使用模型绑定。 Gin 目前支持 JSON、XML、YAML 和标准表单值的绑定(foo=bar&boo=baz)。 + + Gin 使用 go-playground/validator.v8 进行验证。 查看标签用法的全部文档(https://github.com/go-playground/validator) + + 使用时,需要在要绑定的所有字段上,设置相应的 tag。 例如,使用 JSON 绑定时,设置字段标签为 json:"fieldname"。 + Gin 提供了两类绑定方法: + Type - Must bind + Methods - Bind, BindJSON, BindXML, BindQuery, BindYAML + Behavior - 这些方法属于 MustBindWith 的具体调用。 如果发生绑定错误,则请求终止,并触发 c.AbortWithError(400, err).SetType(ErrorTypeBind)。响应状态码被设置为 400 并且 Content-Type 被设置为 text/plain; charset=utf-8。 如果您在此之后尝试设置响应状态码,Gin 会输出日志 [GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422。 如果您希望更好地控制绑定,考虑使用 ShouldBind 等效方法。 + + Type - Should bind + Methods - ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML + Behavior - 这些方法属于 ShouldBindWith 的具体调用。 如果发生绑定错误,Gin 会返回错误并由开发者处理错误和请求。 + + + 使用 Bind 方法时,Gin 会尝试根据 Content-Type 推断如何绑定。 如果你明确知道要绑定什么,可以使用 MustBindWith 或 ShouldBindWith。 + 你也可以指定必须绑定的字段。 如果一个字段的 tag 加上了 binding:"required",但绑定时是空值,Gin 会报错。 +` +} + +type Login struct { + User string `form:"user" json:"user" xml:"user" binding:"required"` + Password string `form:"password" json:"password" xml:"password" binding:"required"` +} + +func TestBindValid(t *testing.T) { + router := gin.Default() + BindJson(router) //对结构体中的json设置字段名进行绑定,如果没有对应字段则会报错 + + BindXml(router) //对结构体中的Xml设置字段名进行绑定,如果没有对应字段则会报错 + + BindHtml(router) //对html输入的地址栏参数进行绑定 + + //请求示例 + _ = `$ curl -v -X POST \ + http://localhost:8080/loginJSON \ + -H 'content-type: application/json' \ + -d '{ "user": "manu" }' + > POST /loginJSON HTTP/1.1 + > Host: localhost:8080 + > User-Agent: curl/7.51.0 + > Accept: */* + > content-type: application/json + > Content-Length: 18 + > + * upload completely sent off: 18 out of 18 bytes + < HTTP/1.1 400 Bad Request + < Content-Type: application/json; charset=utf-8 + < Date: Fri, 04 Aug 2017 03:51:31 GMT + < Content-Length: 100 + < + {"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} + ` + fmt.Println("上述为请求示例,因为在结构体的tag上设置了必须校验,所以当一个字段没有进行输入的时候,就会报错,除非是去掉对应的tag设置或者是传输正确的字段") +} + +func BindHtml(router *gin.Engine) { + // 绑定 HTML 表单 (user=manu&password=123) + router.POST("/loginForm", func(c *gin.Context) { + var form Login + // 根据 Content-Type Header 推断使用哪个绑定器。 + if err := c.ShouldBind(&form); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if form.User != "manu" || form.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) +} + +func BindXml(router *gin.Engine) { + // 绑定 XML ( + // + // + // user + // 123 + // ) + router.POST("/loginXML", func(c *gin.Context) { + var xml Login + if err := c.ShouldBindXML(&xml); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if xml.User != "manu" || xml.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) +} + +func BindJson(router *gin.Engine) { + // 绑定 JSON ({"user": "manu", "password": "123"}) + router.POST("/loginJSON", func(c *gin.Context) { + var json Login + if err := c.ShouldBindJSON(&json); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if json.User != "manu" || json.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + +} diff --git a/example/gin/24-BindUri_test.go b/example/gin/24-BindUri_test.go new file mode 100644 index 0000000..8026acf --- /dev/null +++ b/example/gin/24-BindUri_test.go @@ -0,0 +1,38 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 14:06 + * @Description:直接绑定Uri参数 + */ + +package gin + +import ( + "github.com/gin-gonic/gin" + "testing" +) + +type Person struct { + ID string `uri:"id" binding:"required,uuid"` + Name string `uri:"name" binding:"required"` +} + +func TestBindUri(t *testing.T) { + route := gin.Default() + //这边的【:name】和【:id】说明了对应的该Uri,传入的参数分别是叫做name和id.在下面的Bind直接进行映射 + route.GET("/uri/: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") + + //请求示例 + _ = ` + 这边可以看到uri后面的两个参数就是 name 和 id,直接不用声明名称. + $ curl -v localhost:8088/uri/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 + $ curl -v localhost:8088/uri/thinkerou/not-uuid + ` +} diff --git a/example/gin/28-DiyHttpConfig_test.go b/example/gin/28-DiyHttpConfig_test.go new file mode 100644 index 0000000..f2eb258 --- /dev/null +++ b/example/gin/28-DiyHttpConfig_test.go @@ -0,0 +1,29 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 14:14 + * @Description:自定义Http配置 + */ + +package gin + +import ( + "github.com/gin-gonic/gin" + "net/http" + "testing" + "time" +) + +func TestHttpConfig(t *testing.T) { + router := gin.Default() + + //最重要的是可以通过自定义Handler来处理不同类型的请求,这边的请求是说监听的不同端口. + //而不同的请求方法,可以处理对应的不同路径的请求,可以把一个类型的路径用一个相应的方法进行处理. + s := &http.Server{ + Addr: ":8080", //监听地址 + Handler: router, //处理类可自定义 + ReadTimeout: 10 * time.Second, //读超时 + WriteTimeout: 10 * time.Second, //写超时 + MaxHeaderBytes: 1 << 20, + } + s.ListenAndServe() +} diff --git a/example/gin/29-DiyMiddleWare_test.go b/example/gin/29-DiyMiddleWare_test.go new file mode 100644 index 0000000..2f3438f --- /dev/null +++ b/example/gin/29-DiyMiddleWare_test.go @@ -0,0 +1,51 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 14:45 + * @Description: + */ + +package gin + +import ( + "fmt" + "github.com/gin-gonic/gin" + "log" + "testing" + "time" +) + +func Logger() gin.HandlerFunc { + return func(c *gin.Context) { + t := time.Now() + + // 请求前 + fmt.Println(fmt.Sprintf("记录日志,本次请求的参数为:%v", c.Params)) + + c.Next() + + // 请求后 + latency := time.Since(t) + log.Print(latency) + + // 获取发送的 status + status := c.Writer.Status() + log.Println(status) + } +} + +//在请求前后者请求后自定义中间件处理 +func TestMiddleWare(t *testing.T) { + router := gin.Default() //Default会返回两个中间件 Logger(), Recovery().这接口调用之前和之后进行处理,所以我们也可以自定义中间件 + + router.Use(Logger()) + + router.GET("/test", func(c *gin.Context) { + example := c.MustGet("example").(string) + + // 打印:"12345" + log.Println(example) + }) + + // 监听并在 0.0.0.0:8080 上启动服务 + router.Run(":8080") +} diff --git a/example/gin/3-Http2Server_test.go b/example/gin/3-Http2Server_test.go new file mode 100644 index 0000000..ddeba6a --- /dev/null +++ b/example/gin/3-Http2Server_test.go @@ -0,0 +1,7 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 9:31 + * @Description: + */ + +package gin diff --git a/example/gin/30-DiyStructValid_test.go b/example/gin/30-DiyStructValid_test.go new file mode 100644 index 0000000..9c15e36 --- /dev/null +++ b/example/gin/30-DiyStructValid_test.go @@ -0,0 +1,64 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 15:12 + * @Description: + */ + +package gin + +import ( + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-playground/validator/v10" + "net/http" + "testing" + "time" +) + +// Booking 包含绑定和验证的数据。 +type Booking struct { + CheckIn time.Time `form:"check_in" binding:"required,diyValid" time_format:"2006-01-02"` + CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` +} + +//read note 自定义的校验类,需要实现 validator.Func方法(参数是validator.FieldLevel,这里把所有的参数都集合进去了,之前的版本是多个参数) +func bookDataValid(level validator.FieldLevel) bool { + if date, ok := level.Field().Interface().(time.Time); ok { + today := time.Now() + if today.Year() > date.Year() || today.YearDay() > date.YearDay() { + return false + } + } + + return true +} + +func TestDiyStructValid(t *testing.T) { + route := gin.Default() + + //read note 注册自定义的校验类,这边设置的key就是需要在结构体上绑定数据时候书写的tag标签,注意看上面Booking 的CheckIn标签(只有在结构体上书写了该标签,这个校验器才会去校验对应的字段) + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + _ = v.RegisterValidation("diyValid", bookDataValid) + } + + route.GET("/bookable", getBookable) + _ = route.Run(":8085") + + //请求示例 + _ = ` + $ 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 'diyValid' tag"} + ` +} + +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()}) + } +} diff --git a/example/gin/31-ReadCookie_test.go b/example/gin/31-ReadCookie_test.go new file mode 100644 index 0000000..44a679d --- /dev/null +++ b/example/gin/31-ReadCookie_test.go @@ -0,0 +1,32 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 15:27 + * @Description: + */ + +package gin + +import ( + "fmt" + "github.com/gin-gonic/gin" + "testing" +) + +func TestReadCookie(t *testing.T) { + + router := gin.Default() + + router.GET("/cookie", func(c *gin.Context) { + + //read note 这边需要web前端发送对应的cookie + cookie, err := c.Cookie("gin_cookie") + + //read note 设置对应值的cookie + if err != nil { + cookie = "NotSet" + c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) + } + fmt.Printf("Cookie value: %s \n", cookie) + }) + _ = router.Run() +} diff --git a/example/gin/32-RouterMatch_test.go b/example/gin/32-RouterMatch_test.go new file mode 100644 index 0000000..b637280 --- /dev/null +++ b/example/gin/32-RouterMatch_test.go @@ -0,0 +1,42 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 15:54 + * @Description: + */ + +package gin + +import ( + "github.com/gin-gonic/gin" + "net/http" + "testing" +) + +func TestRouterMatch(t *testing.T) { + router := gin.Default() + + OnlyMatch(router) //只能匹配到指定格式 + + MathMany(router) //*匹配多重字段 + + _ = router.Run(":8080") +} + +func MathMany(router *gin.Engine) { + // 此 handler 将匹配 /user/john/ 和 /user/john/send + // 如果没有其他路由匹配 /user/john,它将重定向到 /user/john/ + 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) + }) +} + +func OnlyMatch(router *gin.Engine) { + // 此 handler 将匹配 /user/john 但不会匹配 /user/ 或者 /user + router.GET("/user/:name", func(c *gin.Context) { + name := c.Param("name") + c.String(http.StatusOK, "Hello %s", name) + }) +} diff --git a/example/gin/33-RouteGroup_test.go b/example/gin/33-RouteGroup_test.go new file mode 100644 index 0000000..80f37b5 --- /dev/null +++ b/example/gin/33-RouteGroup_test.go @@ -0,0 +1,48 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 16:15 + * @Description: + */ + +package gin + +import ( + "fmt" + "github.com/gin-gonic/gin" + "testing" +) + +//路有组就是把相同前缀的路由路径分在一起。 +func TestRouteGroup(t *testing.T) { + router := gin.Default() + + // 简单的路由组: v1 + v1 := router.Group("/v1") + { + v1.POST("/login", func(context *gin.Context) { + fmt.Println(context.Params) + }) + v1.POST("/submit", func(context *gin.Context) { + fmt.Println(context.Params) + }) + v1.POST("/read", func(context *gin.Context) { + fmt.Println(context.Params) + }) + } + + // 简单的路由组: v2 + v2 := router.Group("/v2") + { + v2.POST("/login", func(context *gin.Context) { + fmt.Println(context.Params) + }) + v2.POST("/submit", func(context *gin.Context) { + fmt.Println(context.Params) + }) + v2.POST("/read", func(context *gin.Context) { + fmt.Println(context.Params) + }) + } + + _ = router.Run(":8080") +} diff --git a/example/gin/35-Redirect_test.go b/example/gin/35-Redirect_test.go new file mode 100644 index 0000000..be5b5a7 --- /dev/null +++ b/example/gin/35-Redirect_test.go @@ -0,0 +1,25 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 16:22 + * @Description: + */ + +package gin + +import ( + "github.com/gin-gonic/gin" + "testing" +) + +//重定向 +func TestRedirect(t *testing.T) { + r := gin.Default() + + r.GET("/test", func(c *gin.Context) { + c.Request.URL.Path = "/test2" + r.HandleContext(c) + }) + r.GET("/test2", func(c *gin.Context) { + c.JSON(200, gin.H{"hello": "world"}) + }) +} diff --git a/example/gin/4-Jsonp_test.go b/example/gin/4-Jsonp_test.go new file mode 100644 index 0000000..83d30bd --- /dev/null +++ b/example/gin/4-Jsonp_test.go @@ -0,0 +1,34 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 9:31 + * @Description: + */ + +package gin + +import ( + "fmt" + "github.com/gin-gonic/gin" + "net/http" + "testing" +) + +func TestJsonp(t *testing.T) { + r := gin.Default() + + r.GET("/JSONP?callback=x", func(c *gin.Context) { + data := map[string]interface{}{ + "foo": "bar", + } + + // callback 是 x + // 将输出:x({\"foo\":\"bar\"}) + c.JSONP(http.StatusOK, data) + }) + + // 监听并在 0.0.0.0:8080 上启动服务 + err := r.Run(":8080") + if err != nil { + fmt.Println(err.Error()) + } +} diff --git a/example/gin/7-PureJson_test.go b/example/gin/7-PureJson_test.go new file mode 100644 index 0000000..5c55057 --- /dev/null +++ b/example/gin/7-PureJson_test.go @@ -0,0 +1,7 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 9:39 + * @Description: + */ + +package gin diff --git a/example/gin/8-QueryPostForm_test.go b/example/gin/8-QueryPostForm_test.go new file mode 100644 index 0000000..a5de0e3 --- /dev/null +++ b/example/gin/8-QueryPostForm_test.go @@ -0,0 +1,34 @@ +/* + * @Author : huangzj + * @Time : 2020/12/8 9:39 + * @Description: + */ + +package gin + +import ( + "fmt" + "github.com/gin-gonic/gin" + "testing" +) + +//read note POST /post?id=1234&page=1 HTTP/1.1 +// Content-Type: application/x-www-form-urlencoded +// name=manu&message=this_is_great + +//read note Query是用来解析地址栏中的参数 +// PostForm是用来解析报文体中的参数 +func TestQueryPostForm(t *testing.T) { + router := gin.Default() + + router.POST("/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", id, page, name, message) + }) + router.Run(":8080") +} diff --git a/example/gin/readme.md b/example/gin/readme.md new file mode 100644 index 0000000..1fa24d5 --- /dev/null +++ b/example/gin/readme.md @@ -0,0 +1,15 @@ +Gin使用教程 + + https://learnku.com/docs/gin-gonic/2019 + +获取Gin框架命令 + + go get -u github.com/gin-gonic/gin + +Context官方Blog + + https://blog.golang.org/context + +深度解密Go Context + + https://learnku.com/articles/29877 \ No newline at end of file diff --git a/readme.md b/readme.md index d054024..85a8d51 100644 --- a/readme.md +++ b/readme.md @@ -13,6 +13,7 @@ json与struct之间转换处理工具使用示例 2020/7/13 : 新增 网络及端口测试、传值和传址的比较、切片测试、Map测试、通道测试、测试init调用、测试error、defer关键字、recover测试、测试携程的异常捕获、矩阵旋转 +2020/12/9 :新增Gin框架使用示例 # mod vendor模式加载包 通过go mod的方式加载的github上面的包会有报红的问题,但是包本身是可以运行的,这样就是会有一个问题,如果你想要点击去看方法的内容,没办法做到