feat(Go-StudyExample): 2020/12/9 :新增Gin框架使用示例

This commit is contained in:
Huangzj
2020-12-09 09:45:46 +08:00
parent 9ad10e8cd9
commit 1361db0274
22 changed files with 881 additions and 0 deletions
+34
View File
@@ -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": "<br>",
}
// 输出 : {"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())
}
}
+86
View File
@@ -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})
})
}
+40
View File
@@ -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")
}
+42
View File
@@ -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")
}
+52
View File
@@ -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")
}
+7
View File
@@ -0,0 +1,7 @@
/*
* @Author : huangzj
* @Time : 2020/12/8 9:27
* @Description
*/
package gin
+60
View File
@@ -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)
}
})
}
+133
View File
@@ -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=barboo=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 (
// <?xml version="1.0" encoding="UTF-8"?>
// <root>
// <user>user</user>
// <password>123</password>
// </root>)
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"})
})
}
+38
View File
@@ -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
`
}
+29
View File
@@ -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()
}
+51
View File
@@ -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")
}
+7
View File
@@ -0,0 +1,7 @@
/*
* @Author : huangzj
* @Time : 2020/12/8 9:31
* @Description
*/
package gin
+64
View File
@@ -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()})
}
}
+32
View File
@@ -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()
}
+42
View File
@@ -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)
})
}
+48
View File
@@ -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")
}
+25
View File
@@ -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"})
})
}
+34
View File
@@ -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())
}
}
+7
View File
@@ -0,0 +1,7 @@
/*
* @Author : huangzj
* @Time : 2020/12/8 9:39
* @Description
*/
package gin
+34
View File
@@ -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")
}
+15
View File
@@ -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