概述
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")
}