文章
问答
冒泡
轻量级的Golang Web框架之Gin

概述

Gin是一个轻量级的Golang Web框架

特性

给我的感觉,Gin就是轻便,不笨重,因为是基于Golang语言编写的,占用的资源以及启动速度上也会很快,Gin本身其实很简单,所以可以很方便的支持第三方中间件的使用,另外Gin支持针对JSON字段的验证,组织路由方面引入了路由组的概念,可以方便的进行授权集成

要求

  • Go 1.13 or above

示例

启动一个Gin Web服务也很简单。

创建项目

这步不用说了吧

下载依赖

go get github.com/gin-gonic/gin

创建主函数

根目录创建一个main.go文件:

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"os"
)

func main() {
	r := gin.Default()
	r.GET("/ping", func(context *gin.Context) {
		context.JSON(200, gin.H{
			"message": "pong",
		})
	})

	err := r.Run(":8080")
	if err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}

	os.Exit(0)
}

官方的例子。

启动服务

启动服务之后,浏览器访问地址:http://localhost:8080/ping,即可看到响应。

常用配置

日志输出到文件

package main

import (
	"github.com/gin-gonic/gin"
	"io"
	"log"
	"os"
)

func main() {
	// 禁用在控制台打印颜色,因为日志文件中不需要颜色打印
	gin.DisableConsoleColor()

	//将日志记录到文件
	file, _ := os.Create("gin.log")
	//将日志只写到文件中
	//gin.DefaultWriter = io.MultiWriter(file)
	//将日志同时写到文件和控制台
	gin.DefaultWriter = io.MultiWriter(file, os.Stdout)

	r := gin.Default()
	r.GET("/ping", func(context *gin.Context) {
		context.JSON(200, gin.H{
			"message": "pong",
		})
	})

	err := r.Run(":8080")
	if err != nil {
		log.Println("exist error: ", err)
		os.Exit(-1)
	}

	os.Exit(0)
}

自定义日志格式

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"os"
	"time"
)

func main() {
	r := gin.Default()

	// LoggerWithFormatter中间件会将日志写入到gin.DefaultWriter
	// 默认情况下:gin.DefaultWriter = os.Stdout
	r.Use(gin.LoggerWithFormatter(func(params gin.LogFormatterParams) string {
		return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
			params.ClientIP,
			params.TimeStamp.Format(time.RFC1123),
			params.Method,
			params.Path,
			params.Request.Proto,
			params.StatusCode,
			params.Latency,
			params.Request.UserAgent(),
			params.ErrorMessage)
	}))

	r.GET("/ping", func(context *gin.Context) {
		context.JSON(200, gin.H{
			"message": "pong",
		})
	})

	err := r.Run(":8080")
	if err != nil {
		log.Println("exist error: ", err)
		os.Exit(-1)
	}

	os.Exit(0)
}

自定义中间件

自定义中间件:

package handler

import (
	"github.com/gin-gonic/gin"
	"log"
	"time"
)

func CustomLogger() gin.HandlerFunc {
	return func(c *gin.Context) {
		t := time.Now()

		//设置变量
		c.Set("example", "123456")

		//before request

		c.Next()

		// after request
		latency := time.Since(t)
		log.Print(latency)

		//获取我们发送的请求状态
		status := c.Writer.Status()
		log.Println(status)
	}
}

使用:

package main

import (
	"github.com/gin-gonic/gin"
	"go-gin-demo/handler"
	"log"
	"os"
)

func main() {
	r := gin.Default()
	r.Use(handler.CustomLogger())
	r.GET("/test", func(context *gin.Context) {
		example := context.MustGet("example").(string)
		log.Printf("example: %s\n", example)
	})

	err := r.Run(":8080")
	if err != nil {
		log.Println("exist error: ", err)
		os.Exit(-1)
	}

	os.Exit(0)
}

文件上传

有区分多个文件上传、单个文件上传

package handler

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
)

func UploadMultipleFiles(c *gin.Context) {
	form, _ := c.MultipartForm()
	files := form.File["files"]

	for _, file := range files {
		log.Printf("filename: %s\n", file.Filename)
	}

	c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
}

func UploadSingleFile(c *gin.Context) {
	file, _ := c.FormFile("file")

	log.Printf("filename: %s\n", file.Filename)

	c.String(http.StatusOK, fmt.Sprintf("'%s' files uploaded!", file.Filename))
}

使用:

package main

import (
	"github.com/gin-gonic/gin"
	"go-gin-demo/handler"
	"log"
	"os"
)

func main() {
	r := gin.Default()
	r.POST("/upload/multiple-file", handler.UploadMultipleFiles)
	r.POST("/upload/single-file", handler.UploadSingleFile)

	err := r.Run(":8080")
	if err != nil {
		log.Println("exist error: ", err)
		os.Exit(-1)
	}

	os.Exit(0)
}

请求数据绑定到结构体

处理方法:

package handler

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

type StructA struct {
	FieldA string `form:"field_a"`
}

type StructB struct {
	NestedStruct StructA
	FieldB       string `form:"field_b"`
}

type StructC struct {
	NestedStructPointer *StructA
	FieldC              string `form:"field_c"`
}

type StructD struct {
	NestedAnonyStruct struct {
		FieldX string `form:"field_x"`
	}
	FieldD string `form:"field_d"`
}

func GetDataB(c *gin.Context) {
	var b StructB
	err := c.Bind(&b)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "解析失败",
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"a": b.NestedStruct,
		"b": b.FieldB,
	})
}

func GetDataC(c *gin.Context) {
	var b StructC
	err := c.Bind(&b)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "解析失败",
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"a": b.NestedStructPointer,
		"c": b.FieldC,
	})
}

func GetDataD(c *gin.Context) {
	var b StructD
	err := c.Bind(&b)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": "解析失败",
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"x": b.NestedAnonyStruct,
		"d": b.FieldD,
	})
}

使用:

package main

import (
	"github.com/gin-gonic/gin"
	"go-gin-demo/handler"
	"log"
	"os"
)

func main() {
	r := gin.Default()

	r.GET("/getb", handler.GetDataB)
	r.GET("/getc", handler.GetDataC)
	r.GET("/getd", handler.GetDataD)

	err := r.Run(":8080")
	if err != nil {
		log.Println("exist error: ", err)
		os.Exit(-1)
	}

	os.Exit(0)
}

验证:

http://localhost:8080/getb?field_a=hello&field_b=world

http://localhost:8080/getc?field_a=hello&field_c=world

http://localhost:8080/getd?field_x=hello&field_d=world

绑定URI中的参数

处理方法:

package handler

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

type Person struct {
	ID   string `uri:"id" binding:"required,uuid"`
	Name string `uri:"name" binding:"required"`
}

func BindUriHandle(c *gin.Context) {
	var person Person
	err := c.ShouldBindUri(&person)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": err,
		})
	}
	c.JSON(http.StatusOK, gin.H{
		"name": person.Name,
		"id":   person.ID,
	})
}

使用:

package main

import (
	"github.com/gin-gonic/gin"
	"go-gin-demo/handler"
	"log"
	"os"
)

func main() {
	r := gin.Default()
	r.GET("/:name/:id", handler.BindUriHandle)

	err := r.Run(":8080")
	if err != nil {
		log.Println("exist error: ", err)
		os.Exit(-1)
	}

	os.Exit(0)
}

验证:

http://localhost:8080/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3

http://localhost:8080/thinkerou/not-uuid

自定义HTTP配置

直接使用http.ListenAndServe()

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

或者

func main() {
	router := gin.Default()

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

参数验证器

处理函数:

package handler

import (
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10"
	"log"
	"net/http"
)

type User struct {
	FirstName string `form:"firstName" binding:"required"`
	LastName  string `form:"lastName" binding:"required"`
	Age       uint8  `form:"age" binding:"gte=0,lte=130"`
	Email     string `form:"email" binding:"required,email,email_validation"`
}

func EmailValidation(fl validator.FieldLevel) bool {
	email := fl.Field().Interface().(string)
	return len(email) < 15
}

func UserStructLevelValidation(sl validator.StructLevel) {
	user := sl.Current().Interface().(User)
	if len(user.FirstName) >= 5 {
		sl.ReportError(user.FirstName, "firstName", "FirstName", "firstName gt 5", "")
	}

	if len(user.LastName) >= 5 {
		sl.ReportError(user.LastName, "lastName", "LastName", "lastName gt 5", "")
	}
}

func BindUserValidate(validate *validator.Validate) {
	validate.RegisterStructValidation(UserStructLevelValidation, User{})
	err := validate.RegisterValidation("email_validation", EmailValidation)
	if err != nil {
		log.Println("register age validator fail: ", err)
	}
}

func GetUser(c *gin.Context) {
	var b User
	if err := c.ShouldBindWith(&b, binding.Query); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
	} else {
		c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
	}
}

使用:

package main

import (
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10"
	"go-gin-demo/handler"
	"log"
	"os"
)

func main() {
	r := gin.Default()
    
	validate := binding.Validator.Engine().(*validator.Validate)
	handler.BindUserValidate(validate)
    
	r.GET("/user", handler.GetUser)

	err := r.Run(":8080")
	if err != nil {
		log.Println("exist error: ", err)
		os.Exit(-1)
	}

	os.Exit(0)
}

验证:

http://localhost:8080/user?firstName=1&lastName=2&age=3&email=wy@123.com

http://localhost:8080/user?firstName=&lastName=2&age=3&email=wy@123.com

http://localhost:8080/user?firstName=111111&lastName=2&age=3&email=wy@123.com

http://localhost:8080/user?firstName=1&lastName=2&age=3&email=wy1111111111111@123.com

路由分组

func main() {
	router := gin.Default()

	// Simple group: v1
	v1 := router.Group("/v1")
	{
		v1.POST("/login", loginEndpoint)
		v1.POST("/submit", submitEndpoint)
		v1.POST("/read", readEndpoint)
	}

	// Simple group: v2
	v2 := router.Group("/v2")
	{
		v2.POST("/login", loginEndpoint)
		v2.POST("/submit", submitEndpoint)
		v2.POST("/read", readEndpoint)
	}

	router.Run(":8080")
}
golang

关于作者

justin
123456
获得点赞
文章被阅读