gin启动服务的三种方式

func main() {
	engine := gin.Default()
	engine.GET("/", func(context *gin.Context) {
		context.JSON(200, gin.H{"msg": "OK2"})
	})

	// 启动http服务的3中方法
	// 方法1
	//engine.Run(":8000")
	
	// 方法2
	//http.ListenAndServe(":8000", engine)
	
	// 方法3
	server := &http.Server{
		Addr: ":8000",
		Handler: engine,
		ReadTimeout: 10 * time.Second,
		WriteTimeout: 10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	server.ListenAndServe()
}

路由

路由参数
api 参数通过Context的Param方法来获取

# url请求:http://127.0.0.1:8000/你好啊
	engine.GET("/:name", func(context *gin.Context) {
		// 获取位置参数
		fmt.Println(context.Param("name"))
		context.JSON(200, gin.H{"name": context.Param("name")})
	})

URL 参数通过 DefaultQuery 或 Query 方法获取
Query

# url请求:http://127.0.0.1:8000/?name=张三
	engine.GET("/", func(context *gin.Context) {
		fmt.Println(context.Query("name"))
		context.JSON(200, gin.H{"name": context.Query("name")})
	})

DefaultQuery

	// url: http://127.0.0.1:8000
	engine.GET("/", func(context *gin.Context) {
		fmt.Println(context.DefaultQuery("name", "默认值"))
		context.JSON(200, gin.H{"name": context.DefaultQuery("name", "默认值")})
	})

GetQueryArray

	// url: http://127.0.0.1:8000?hobby=篮球&hobby=乒乓球
	engine.GET("/", func(context *gin.Context) {
		if hobbies, ok := context.GetQueryArray("hobby"); ok {
			fmt.Println(hobbies)
			context.JSON(200, gin.H{"hobbies": hobbies})
		}
	})
表单参数通过PostForm方法获取
	// url: http://127.0.0.1:8000, 从form中传递参数
	engine.POST("/", func(context *gin.Context) {
		fmt.Println(context.PostForm("name"))
		context.JSON(http.StatusOK, gin.H{"name": context.PostForm("name")})
	})

GetPostFormArray

	// url: http://127.0.0.1:8000, form中传递多个相同的hobby参数
	engine.POST("/", func(context *gin.Context) {
		if hobbies, ok := context.GetPostFormArray("hobby"); ok {
			fmt.Println(hobbies)
			context.JSON(http.StatusOK, gin.H{"hobbies": hobbies})
		}
	})

路由群组

	// 路由群组
	userGroup := engine.Group("/user")
	// url: http://127.0.0.1:8000/user/login, 记得需要加上路由组的前缀才能访问
	userGroup.POST("/login", func(context *gin.Context) {
		context.String(200, "OK")
	})

控制器

数据解析绑定
模型绑定可以将请求体绑定给一个类型,目前支持绑定的类型有 JSON, XML 和标准表单数据 (foo=bar&boo=baz)。
要注意的是绑定时需要给字段设置绑定类型的标签。比如绑定 JSON 数据时,设置 json:"fieldname"。
使用绑定方法时,Gin 会根据请求头中 Content-Type 来自动判断需要解析的类型。如果你明确绑定的类型,你可以不用自动推断,而用 BindWith 方法。
你也可以指定某字段是必需的。如果一个字段被 binding:"required" 修饰而值却是空的,请求会失败并返回错误。
绑定json的例子:

type Login struct {
	User string `json:"user" form:"user" binding:"required"`
	Password string `json:"password" form:"password" binding:"required"`
}
func main() {
	engine := gin.Default()
	engine.HandleMethodNotAllowed = true  // 开启方法不允许校验

	// url: http://127.0.0.1:8000/login
	engine.POST("/login", func(context *gin.Context) {
		var login Login
		if err := context.BindJSON(&login); err != nil {
			log.Println(err)
			context.String(400, "错误请求")
		}else{
			fmt.Println(login)
			context.JSON(200, login)
		}
	})

	engine.Run(":8000")
}

绑定普通表单的例子:根据请求头中的Content-Type自动推断

type Login struct {
	User string `json:"user" form:"user" binding:"required"`
	Password string `json:"password" form:"password" binding:"required"`
}
func main() {
	engine := gin.Default()
	engine.HandleMethodNotAllowed = true  // 开启方法不允许校验

	// url: http://127.0.0.1:8000/login
	engine.POST("/login", func(context *gin.Context) {
		var login Login
		// 根据请求头中的Content-Type自动推断
		if err := context.Bind(&login); err == nil {
			if login.User == "mayanan" && login.Password == "123456" {
				context.JSON(http.StatusOK, gin.H{"status": "you are login int"})
			}else{
				context.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
			}
		}else {
			context.JSON(http.StatusBadRequest, gin.H{"err": err.Error(), "status": "failed"})
		}
	})

	engine.Run(":8000")
}

绑定多媒体表单的例子:

	// url: http://127.0.0.1:8000/login
	engine.POST("/login", func(context *gin.Context) {
		var login Login
		// 可以显示的声明来绑定多媒体表单
		if err := context.BindWith(&login, binding.Form); err == nil {
			if login.User == "mayanan" && login.Password == "123456" {
				context.JSON(200, gin.H{"status": "user login in"})
			}else{
				context.JSON(401, gin.H{"status": "unauthorized"})
			}
		}else{
			context.JSON(400, gin.H{"err": err.Error()})
		}
	})

请求

  • 请求头
  • 请求参数
  • cookies
  • 上传文件
	// url: http://127.0.0.1:8000/upload
	engine.POST("/upload", func(context *gin.Context) {
		fileHeader, err := context.FormFile("file")
		if err != nil {
			context.JSON(400, gin.H{"err": "参数错误"})
			return
		}
		file, _ := fileHeader.Open()
		defer file.Close()
		newFile, _ := os.Create(fileHeader.Filename)
		defer newFile.Close()
		_, _ = io.Copy(newFile, file)
		context.String(200, "Ok")
	})

响应

  • 响应头
  • 附加cookie
  • 字符串响应
c.String(http.StatusOK, "some string")
	// url: http://127.0.0.1:8000/moreJSON
	engine.GET("/moreJSON", func(context *gin.Context) {
		var msg struct{
			Name string `json:"user" xml:"user"`
			Msg string `xml:"msg"`
			Number int `xml:"number"`
			SS struct{
				User string
				Age int
			}
		}
		msg.Name = "张三"
		msg.Msg = "消息体"
		msg.Number = 888
		msg.SS.User = "李四"
		msg.SS.Age = 18
		//context.JSON(http.StatusOK, msg)
		context.YAML(http.StatusOK, msg)
	})
  1. 视图响应
    先要使用LoadHTMLTemplates加载模板文件
func main() {
	engine := gin.Default()
	engine.HandleMethodNotAllowed = true  // 开启方法不允许校验

	// 加载模板文件
	//engine.LoadHTMLGlob("html/*")
	engine.LoadHTMLFiles("html/index.html", "html/user.html")

	// url: http://127.0.0.1:8000
	engine.GET("/index.html", func(context *gin.Context) {
		context.HTML(http.StatusOK, "index.html", gin.H{"name": "张三"})
	})
	engine.GET("/user.html", func(context *gin.Context) {
		var User struct{
			User string `json:"user"`
			Age int `json:"age"`
		}
		User.User = "李四"
		User.Age = 18

		data, _ := json.Marshal(&User)
		m := make(map[string]any)
		json.Unmarshal(data, &m)

		context.HTML(http.StatusOK, "user.html", m)
	})

	engine.Run(":8000")
}
  1. 文件响应
func main() {
	engine := gin.Default()
	engine.HandleMethodNotAllowed = true  // 开启方法不允许校验

	// url: http://127.0.0.1:8000/index/user.html
	//engine.Static("/index", "./html")
	// url: http://127.0.0.1:8000/index/index.html
	//engine.StaticFS("/index", gin.Dir("./html", false))
	// url: http://127.0.0.1:8000/index
	engine.StaticFile("/index", "./html/index.html")

	engine.Run(":8000")
}
  1. 重定向
	engine.GET("/redirect/mayanan", func(context *gin.Context) {
		context.Redirect(http.StatusMovedPermanently, "http://mayanan.cn")
	})
  1. 同步异步
    goroutine机制可以方便的实现异步处理
func main() {
	engine := gin.Default()
	engine.HandleMethodNotAllowed = true  // 开启方法不允许校验

	engine.GET("/long_async", func(context *gin.Context) {
		// Copy 返回可以在请求范围之外安全使用的当前上下文的副本。当必须将上下文传递给 goroutine 时,必须使用它。
		// goroutine中只能使用上下文的副本
		// 1. 异步
		cp := context.Copy()
		go func() {
			time.Sleep(5 * time.Second)
			// 注意:goroutine中必须使用上下文副本
			log.Println("done! in path", cp.Request.URL.Path)
		}()
	})

	engine.GET("/long_sync", func(context *gin.Context) {
		// 同步:可以使用原始上下文,context
		time.Sleep(5 * time.Second)
		log.Println("done! int path", context.Request.URL.Path)
	})

	engine.Run(":8000")
}

中间件

  1. 分类使用方式
func main() {
	router := gin.New()
	router.HandleMethodNotAllowed = true  // 开启方法不允许校验

	// 1. 全局中间件
	router.Use(gin.Logger(), gin.Recovery())

	// 2. 单路由的中间件,可以加任意多个
	router.GET("/index", MyMiddleWare(), benchEndPoint(), func(context *gin.Context) {
		fmt.Println("/index 执行了")
		context.String(200, "GET OK")
	})

	// 3. 群组路由的中间件
	authorized := router.Group("/", MyMiddleWare())
	// 或者这样使用
	//authorized = router.Group("/")
	//authorized.Use(MyMiddleWare())
	{
		authorized.POST("/login", func(context *gin.Context) {
			fmt.Println("login 执行了")
			context.String(200, "POST OK")
		})
	}

	router.Run(":8000")
}
func MyMiddleWare() gin.HandlerFunc {
	return func(context *gin.Context) {
		fmt.Println("MyMiddleWare执行了")
	}
}
func benchEndPoint() gin.HandlerFunc {
	return func(context *gin.Context) {
		fmt.Println("benchEndPoint执行了")
	}
}
  1. 自定义中间件
func main() {
	router := gin.New()
	router.HandleMethodNotAllowed = true  // 开启方法不允许校验

	// 自定义中间件
	router.Use(MyLogger1())
	router.Use(MyLogger2())
	router.GET("/index", func(context *gin.Context) {
		log.Println("index执行了", context.Value("example1").(string), context.Value("example2").(string))
		context.String(200, "OK")
	})

	router.Run(":8000")
}
func MyLogger1() gin.HandlerFunc {
	return func(context *gin.Context) {
		start := time.Now()
		context.Set("example1", "123456")
		// 请求前
		log.Println("MyLogger1请求前执行了")

		context.Next()  // 处理请求

		// 请求后
		duration := time.Since(start)
		log.Println("MyLogger1请求后执行了:", duration)

		context.Writer.WriteHeader(500)
		status := context.Writer.Status()
		log.Println("MyLogger1, status: ", status)
	}
}
func MyLogger2() gin.HandlerFunc {
	return func(context *gin.Context) {
		start := time.Now()
		context.Set("example2", "987654")
		// 请求前
		log.Println("MyLogger2请求前执行了")

		context.Next()  // 处理请求

		// 请求后
		duration := time.Since(start)
		log.Println("MyLogger2请求后执行了:", duration)

		status := context.Writer.Status()
		log.Println("MyLogger2, status: ", status)
	}
}

输出结果:

2022/09/02 15:37:42 MyLogger1请求前执行了
2022/09/02 15:37:42 MyLogger2请求前执行了
2022/09/02 15:37:42 index执行了 123456 987654
2022/09/02 15:37:42 MyLogger2请求后执行了: 529.9µs
2022/09/02 15:37:42 MyLogger2, status:  200
2022/09/02 15:37:42 MyLogger1请求后执行了: 107.7419ms
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 200 with 500
2022/09/02 15:37:42 MyLogger1, status:  500
  1. 中间件参数
  • 内置中间件
    简单认证-BasicAuth
func main() {
	router := gin.New()
	router.HandleMethodNotAllowed = true  // 开启方法不允许校验

	// 使用gin.BasicAuth中间件,设置授权用户
	authorized := router.Group("/admin", gin.BasicAuth(gin.Accounts{
		"张三": "123456",
		"lisi": "456",
		"wangwu": "7890",
	}))
	authorized.GET("/index", func(context *gin.Context) {
		user := context.MustGet(gin.AuthUserKey).(string)
		if info, ok := secret[user]; ok {
			context.JSON(http.StatusOK, gin.H{"user": user, "info": info})
		}else{
			context.JSON(http.StatusOK, gin.H{"user": user, "info": "NO info"})
		}
	})

	router.Run(":8000")

	// 注意:要想认证通过,需要在请求头里增加:Authorization:b64
	//str := "张三" + ":" + "123456"
	//b64 := "Basic " + base64.StdEncoding.EncodeToString([]byte(str))
	//fmt.Println(b64)
}