Bootstrap

Golang Gin框架

Golang Gin框架

1. 返回json

func main() {
	engine := gin.Default()
    // http://localhost:8080/a
	engine.GET("/a", func(c *gin.Context) {
        //返回JSON map
		c.JSON(http.StatusOK, gin.H{"message": "Hello World", "status": "ok"})
	})
	type user struct {
		Name string `json:"name"` //定义返回前端的json字段
		Age  int
		Sex  string
	} 
    // http://localhost:8080/b
	engine.GET("/b", func(c *gin.Context) {
		user := user{
			Name: "John Doe",
			Age:  3600,
			Sex:  "man",
		}
        //返回JSON结构体
		c.JSON(http.StatusOK, user)
	})
	engine.Run()
}

2. 获取querystring参数

func main() {
	engine := gin.Default()
    // http://localhost:8080/a?name=xx&age=xxx
	engine.GET("/a", func(c *gin.Context) {
		name := c.Query("name")
        name := c.Query("age")
		c.JSON(http.StatusOK, gin.H{"name": name, "age": age})
	})
    // http://localhost:8080/b
	engine.GET("/b", func(c *gin.Context) {
		// DefaultQuery 未匹配到会赋默认值
		name := c.DefaultQuery("name", "张三")
		c.JSON(http.StatusOK, name)
	})
    // http://localhost:8080/c?name=李四
	engine.GET("/c", func(c *gin.Context) {
		// GetQuery 会返回一个bool
		name, ok := c.GetQuery("name")
		if !ok {
			name = "张三"
		}
		c.JSON(http.StatusOK, name)
	})
	engine.Run()
}

3. 获取form参数

func main() {
	engine := gin.Default()
	engine.POST("/a", func(c *gin.Context) {
		username := c.PostForm("username")
		password := c.PostForm("password")
		c.JSON(http.StatusOK, gin.H{"username": username, "password": password})
	})
	engine.POST("/b", func(c *gin.Context) {
		// DefaultQuery 赋默认值
		username := c.DefaultQuery("username", "root")
		password := c.DefaultQuery("password", "root")
		c.JSON(http.StatusOK, gin.H{"username": username, "password": password})
	})
	engine.POST("/c", func(c *gin.Context) {
		// DefaultQuery 赋默认值
		username, ok := c.GetPostForm("username")
		if !ok {
			username = "111"
		}
		password, ok := c.GetPostForm("password")
		if !ok {
			password = "xxx"
		}
		c.JSON(http.StatusOK, gin.H{"username": username, "password": password})
	})
	engine.Run()
}

4. 获取URI路径参数

func main() {
	engine := gin.Default()
	//http://localhost:8080/zhangsan/18
	engine.GET("/:name/:age", func(c *gin.Context) {
		name := c.Param("name")
		age := c.Param("age")
		c.JSON(http.StatusOK, gin.H{"name": name, "age": age})
	})
	engine.Run()
}

5. gin参数绑定

type UserInfo struct {
    Username string `form:"username" json:"username"`
    Password string `form:"password" json:"password"`
}
func main() {
	engine := gin.Default()
	//http://localhost:8080/zhangsan/18
	engine.GET("/a", func(c *gin.Context) {
		var u UserInfo          //声明一个UserInfo结构体变量
		err := c.ShouldBind(&u) // 要传地址
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err})
		}
		c.JSON(http.StatusOK, u)
	})
	engine.Run()
}

6. 文件上传

func main() {
	engine := gin.Default()
	// 处理multipart forms提交文件的大小限制是 32 MiB
	// 可以通过下列方式修改
	engine.MaxMultipartMemory = 8 << 20 // 8MiB

	//单文件上传
	engine.POST("/uploadOne", func(c *gin.Context) {
		file, err := c.FormFile("filename")
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
		//dst := fmt.Sprintf("./%s", file.Filename)
		dst := path.Join("./", file.Filename)
		err = c.SaveUploadedFile(file, dst)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
		c.JSON(http.StatusOK, gin.H{"status": "ok"})
	})

	//多文件上传
	engine.POST("/uploadMore", func(c *gin.Context) {
		form, err := c.MultipartForm()
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
		files := form.File["file"]
		for index, file := range files {
			log.Printf(file.Filename)
			dst := fmt.Sprintf("./%s_%d", file.Filename, index)
			err := c.SaveUploadedFile(file, dst)
			if err != nil {
				c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			}
		}
		c.JSON(http.StatusOK, gin.H{"status": "ok"})
	})
	engine.Run()
}

7. gin请求重定向与转发

func main() {
	engine := gin.Default()
	engine.GET("/a", func(c *gin.Context) {
		// 请求重定向
		c.Redirect(http.StatusMovedPermanently, "https://baidu.com")
		engine.HandleContext(c) //继续后续处理
	})

	// 请求转发
	engine.GET("/b", func(c *gin.Context) {
		c.Request.URL.Path = "/c"
	})
	engine.GET("/c", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"status": "success"})
	})
	engine.Run()
}

8. gin路由和路由组及嵌套路由组

func main() {
	engine := gin.Default()
	//定义路由组
	group := engine.Group("index")
	{
		group.GET("/a", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"method": "GET"})
		})
		group.POST("/b", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"method": "POST"})
		})
		group.PUT("/c", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"method": "PUT"})
		})
		group.DELETE("/d", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"method": "DELETE"})
		})
		// 嵌套路由组
		groupChild := engine.Group("child")
		{
			groupChild.GET("/e", func(c *gin.Context) {
				c.JSON(http.StatusOK, gin.H{"method": "GET"})
			})
			groupChild.POST("/f", func(c *gin.Context) {
				c.JSON(http.StatusOK, gin.H{"method": "POST"})
			})
		}
	}

	engine.Any("/any", func(c *gin.Context) {
		switch c.Request.Method {
		case http.MethodGet:
			c.JSON(http.StatusOK, gin.H{"method": "GET"})
		case http.MethodPost:
			c.JSON(http.StatusOK, gin.H{"method": "POST"})
			// ....
		}
	})

	// NoRoute 访问未定义的路由
	engine.NoRoute(func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"msg": "page not found"})
	})
	engine.Run()
}

9. gin中间件

Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

9.1 定义中间件

Gin中的中间件必须是一个gin.HandlerFunc类型。例如像下面的代码一样定义一个统计请求耗时的中间件

// StatCost 是一个统计耗时请求耗时的中间件
func StatCost() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		c.Set("name", "小王子") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
		// 调用该请求的剩余处理程序
		c.Next()
		// 不调用该请求的剩余处理程序
		// c.Abort()
		// 计算耗时
		cost := time.Since(start)
		log.Println(cost)
	}
}

9.2 注册中间件

在gin框架中,可以为每个路由添加任意数量的中间件。

9.2.1 为全局路由注册
func main() {
	// 新建一个没有任何默认中间件的路由
	r := gin.New()
	// 注册一个全局中间件
	r.Use(StatCost())
	
	r.GET("/test", func(c *gin.Context) {
		name := c.MustGet("name").(string) // 从上下文取值
		log.Println(name)
		c.JSON(http.StatusOK, gin.H{
			"message": "Hello world!",
		})
	})
	r.Run()
}
9.2.2 为某个路由单独注册
// 给/test2路由单独注册中间件(可注册多个)
r.GET("/test2", StatCost(), func(c *gin.Context) {
	name := c.MustGet("name").(string) // 从上下文取值
	log.Println(name)
	c.JSON(http.StatusOK, gin.H{
		"message": "Hello world!",
	})
})
9.2.3 为路由组注册中间件

写法一

shopGroup := r.Group("/shop", StatCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

写法二

shopGroup := r.Group("/shop")
shopGroup.Use(StatCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

9.3 中间件注意事项

9.3.1 gin默认中间件

gin.Default()默认使用了LoggerRecovery中间件,其中:

  • Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release
  • Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。

如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。

9.3.2 gin中间件中使用goroutine

当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。

10. gin运行多个服务

可以在多个端口启动服务,例如:

package main

import (
	"log"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"golang.org/x/sync/errgroup"
)

var (
	g errgroup.Group
)

func router01() http.Handler {
	e := gin.New()
	e.Use(gin.Recovery())
	e.GET("/", func(c *gin.Context) {
		c.JSON(
			http.StatusOK,
			gin.H{
				"code":  http.StatusOK,
				"error": "Welcome server 01",
			},
		)
	})
	return e
}

func router02() http.Handler {
	e := gin.New()
	e.Use(gin.Recovery())
	e.GET("/", func(c *gin.Context) {
		c.JSON(
			http.StatusOK,
			gin.H{
				"code":  http.StatusOK,
				"error": "Welcome server 02",
			},
		)
	})

	return e
}

func main() {
	server01 := &http.Server{
		Addr:         ":8080",
		Handler:      router01(),
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	server02 := &http.Server{
		Addr:         ":8081",
		Handler:      router02(),
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}
   // 借助errgroup.Group或者自行开启两个goroutine分别启动两个服务
	g.Go(func() error {
		return server01.ListenAndServe()
	})

	g.Go(func() error {
		return server02.ListenAndServe()
	})

	if err := g.Wait(); err != nil {
		log.Fatal(err)
	}
}
;