首页 > 其他分享 >gin

gin

时间:2023-09-08 14:44:21浏览次数:32  
标签:engine context func gin main log

目录

gin

1. 快速开始

1.1 简介

1.介绍
Gin 是一个用 Go (Golang) 编写的 Web 框架。 它具有类似 martini 的 API,性能要好得多,多亏了 httprouter,速度提高了 40 倍。 如果您需要性能和良好的生产力,您一定会喜欢 Gin。

2.特性
2.1 快速
基于 Radix 树的路由,小内存占用。没有反射。可预测的 API 性能。

2.2 支持中间件
传入的 HTTP 请求可以由一系列中间件和最终操作来处理。 例如:Logger,Authorization,GZIP,最终操作 DB。

2.3 Crash 处理
Gin 可以 catch 一个发生在 HTTP 请求中的 panic 并 recover 它。这样,你的服务器将始终可用。例如,你可以向 Sentry 报告这个 panic!

2.4 JSON 验证
Gin 可以解析并验证请求的 JSON,例如检查所需值的存在。

2.5 路由组
更好地组织路由。是否需要授权,不同的 API 版本…… 此外,这些组可以无限制地嵌套而不会降低性能。

2.6 错误管理
Gin 提供了一种方便的方法来收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件,数据库并通过网络发送。

2.7 内置渲染
Gin 为 JSON,XML 和 HTML 渲染提供了易于使用的 API。

参考:https://gin-gonic.com/zh-cn/docs/examples/

1.2 依赖

go get -u github.com/gin-gonic/gin

1.3 实例

mkdir web
cd web 
go work init
go work use .
go mod init web
go get -u github.com/gin-gonic/gin
package main

import "github.com/gin-gonic/gin"

func main() {
	// 1. 创建gin引擎
	engine := gin.Default()
	// 2. 路由配置且返回json
	engine.GET("/", func(context *gin.Context) {
		context.JSON(200, gin.H{
			"data": "hello gin",
		})
	})
	// 3. 绑定端口启动
	engine.Run(":8080")
}

image


2. 路由

2.1 简单路由

package main

import "github.com/gin-gonic/gin"

func main() {
	engine := gin.Default()
	engine.GET("/", func(context *gin.Context) {
		context.JSON(200, gin.H{
			"methods": "GET",
		})
	})
	engine.POST("/", func(context *gin.Context) {
		context.JSON(200, gin.H{
			"methods": "POST",
		})
	})
	engine.PUT("/", func(context *gin.Context) {
		context.JSON(200, gin.H{
			"methods": "PUT",
		})
	})
	engine.PATCH("/", func(context *gin.Context) {
		context.JSON(200, gin.H{
			"methods": "PATCH",
		})
	})
	engine.DELETE("/", func(context *gin.Context) {
		context.JSON(200, gin.H{
			"methods": "DELETE",
		})
	})
	engine.HEAD("/", func(context *gin.Context) {
		context.JSON(200, gin.H{
			"methods": "HEAD",
		})
	})

	engine.Run(":8080")
}

2.2 路由组

package main

import "github.com/gin-gonic/gin"


func main() {
	engine := gin.Default()
	// 路由组(返回*RouterGroup结构体)
	reqGroup := engine.Group("/req")
	{
		reqGroup.GET("query1", func(context *gin.Context) {
			context.JSON(200, gin.H{
				"data": "query1",
			})
		})
		reqGroup.GET("query2", func(context *gin.Context) {
			context.JSON(200, gin.H{
				"data": "query2",
			})
		})
	}

	engine.Run(":8080")
}



image


3. 请求参数

3.1 查询字符串

package main

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


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

	reqGroup := engine.Group("/req")
	{
		reqGroup.GET("query", func(context *gin.Context) {

			//// 2.1 常规获取查询字符串?id=1&address=address1&address=address2?addressMap[home]=home1&addressMap[home2]=home2
			//query := context.Query("id")                         // ?id=1
			//defaultQuery := context.DefaultQuery("name", "lisi") // ?name=lisi 没有则用默认值(传了name但是为空字符串是不会执行这个方法的)
			//array := context.QueryArray("address")               // ?address=1&address=2  没有获取默认值方式
			//queryMap := context.QueryMap("addressMap")           //?addressMap[home]=xxx  没有获取默认值方式
			//
			//context.JSON(200, gin.H{
			//	"Id":         query,
			//	"Name":       defaultQuery,
			//	"Address":    array,
			//	"AddressMap": queryMap,
			//})

			/**
			如果确定类型可以直接使用特定绑定器绑定结构体
			context.ShouldBindJSON(&obj)
			context.ShouldBindXML(&obj)
			都是根据Content-Type判断应该使用哪个绑定器
			*/
			//
			// 2.2 绑定结构体 ?id=1&address=address1&address=address2?addressMap[home]=home1&addressMap[home2]=home2
			type Req struct {
				Id         int      `form:"id"`
				Name       string   `form:"name,default=lisi"`
				Address    []string `form:"address"`
				AddressMap map[string]string
			}
			var req Req // 可以通过绑定特定结构体 返回参数  结构体对应的字段添加标签 声明来源  id `form:"id"`
			err := context.ShouldBindQuery(&req) // 只绑定特定url查询字符串参数 忽略post数据
			//err := context.ShouldBind(&req) // 这种方式入参有必选 没有输入也不报错-200
			//err := context.BindQuery(&req) // 这种方式如果入参有必选 没有输入必选则报错-400
			if err != nil {
				log.Println(err)
			}
			req.AddressMap = context.QueryMap("addressMap")
			context.JSON(200, req)
		})
	}

	engine.Run(":8080")

}

image


3.2 路径参数

package main

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

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

	reqGroup := engine.Group("/req")
	{
		// 路径参数1
		reqGroup.GET("query/:id/:name", func(context *gin.Context) {
			id := context.Param("id")
			name := context.Param("name")
			context.JSON(200, gin.H{
				"id":   id,
				"name": name,
			})
		})
		// 路径参数2
		reqGroup.GET("query2/*id", func(context *gin.Context) {
			id := context.Param("id")
			context.JSON(200, gin.H{
				"id": id,
			})
		})
		// 路径参数3
		reqGroup.GET("query3/:id/:name", func(context *gin.Context) {

			// 绑定结构体
			type Req struct {
				Id         int               `uri:"id" json:"id"`                  // uri:""对路径参数取值
				Name       string            `uri:"name,default=lisi" json:"name"` // 针对输出的json 可以对字段进行重命名json:"x"
				Address    []string          `uri:"address" json:"-"`
				AddressMap map[string]string `json:"-"`
			}
			var req Req
			context.ShouldBindUri(&req) //  结构体声明标签 name `uri:"name"`  只有这种方式能获取到路径参数数据
			//context.BindUri(&req)
			context.JSON(200, req)
		})
	}

	engine.Run(":8080")
}

image


3.3 表单参数

package main

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

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

	reqGroup := engine.Group("/req")
	{
		reqGroup.POST("save/form", func(context *gin.Context) {

			//// 方式1
			//id := context.PostForm("id")
			//name := context.DefaultPostForm("name", "xyz")
			//addressArr := context.PostFormArray("address")
			//addressMap := context.PostFormMap("addressMap")
			//context.JSON(200, gin.H{
			//	"id":         id,
			//	"name":       name,
			//	"addressArr": addressArr,
			//	"addressMap": addressMap,
			//})

			// 方式2 绑定特定结构体
			type Req struct {
				Id         int               `form:"id" json:"id"`                  // form:""对表单参数取值
				Name       string            `form:"name,default=lisi" json:"name"` // 针对输出的json 可以对字段进行重命名json:"x"
				Address    []string          `form:"address" json:"addressArr"`
				AddressMap map[string]string `form:"addressMap" json:"addressMap"`
			}
			var req Req // 结构体声明 id `form:"id"`
			/**
			如果需要绑定不同结构体 需要使用
			context.ShouldBindBodyWith(&objA, binding.JSON);
			context.ShouldBindBodyWith(&objB, binding.JSON);
			*/
			err := context.ShouldBind(&req) // 依赖于请求头content-type解析成对应数据类型
			//err := context.Bind(&req)  // 依赖于请求头content-type解析成对应数据类型
			if err != nil {
				log.Println(err)
			}
			addressMap := context.PostFormMap("addressMap")
			req.AddressMap = addressMap
			context.JSON(200, req)
		})
	}

	engine.Run(":8080")
}

image


3.4 JSON参数

package main

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

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

	reqGroup := engine.Group("/req")
	{
		reqGroup.POST("save/json", func(context *gin.Context) {
			type Req struct {
				Id         int               `json:"id"`                // form:""对表单参数取值
				Name       string            `json:"name,default=lisi"` // 针对输出的json 可以对字段进行重命名json:"x"
				Address    []string          `json:"address"`
				AddressMap map[string]string `json:"addressMap"`
			}
			var req Req // 结构体声明 id `json:"id"`
			//err := context.ShouldBind(&req)
			//err := context.ShouldBindJSON(&req)  // 2
			err := context.ShouldBindWith(&req, binding.JSON) // 3; 2<=>3
			if err != nil {
				log.Println(err)
			}
			context.JSON(200, req)
		})
	}

	engine.Run(":8080")
}

image


3.5 文件上传

package main

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

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

	reqGroup := engine.Group("/req")
	{
		// 单文件
		reqGroup.POST("save/file", func(context *gin.Context) {
			// 获取文件对象
			file, err := context.FormFile("file") // 获取不到这个file参数会报错 且默认只取第一个
			if err != nil {
				log.Println(err)
			}
			// 保存图片
			err = context.SaveUploadedFile(file, file.Filename)
			if err != nil {
				log.Println("saveFileError=", err)
			}
			context.JSON(200, file)
		})
		// 多文件
		//engine.MaxMultipartMemory = 8 << 20 // 8mib 设置较低的内存限制
		reqGroup.POST("save/multiFile", func(context *gin.Context) {
			multiForm, err := context.MultipartForm()
			if err != nil {
				log.Println(err)
			}
			//files := multiForm.File["file"]  // 特定名称的上传文件方式
			files := multiForm.File // 切片数据(所有上传的文件)
			//values := multiForm.Value // 切片数据
			// 保存图片数据
			// _=入参f f1;fileArray=数据引用
			for _, fileArray := range files {
				for _, v := range fileArray {
					context.SaveUploadedFile(v, v.Filename) // 保存图片
				}
			}
			context.JSON(200, files)
		})
	}

	engine.Run(":8080")
}

image

image



4.响应参数

4.1 字符串

package main

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

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

	resGroup := engine.Group("/res")
	{
		resGroup.GET("string/:res", func(context *gin.Context) {
			context.String(http.StatusOK, context.Param("res"))
		})
	}

	engine.Run(":8080")
}

image


4.2 JSON

package main

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

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

	resGroup := engine.Group("/res")
	{
		resGroup.GET("json", func(context *gin.Context) {
			context.JSON(200, gin.H{
				"code": "0",
				"msg":  "",
			})
		})
	}

	engine.Run(":8080")
}

image


4.3 XML

package main

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

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

	resGroup := engine.Group("/res")
	{
			resGroup.GET("xml", func(context *gin.Context) {
			// 原生返回xml
			//context.XML(200, gin.H{
			//	"id":   1,
			//	"name": "xyz",
			//})
			///**
			//out:
			//<map>
			//    <id>1</id>
			//    <name>xyz</name>
			//</map>
			//*/

			// 自定义返回xml
			type myXml struct {
				XMLName xml.Name `xml:"xml"` // 指定最外层的标签
				Id      int      `xml:"id"`
				Name    string   `xml:"name"`
			}
			context.XML(200, myXml{
				Id:   3,
				Name: "xyz",
			})
		})
	}

	engine.Run(":8080")
}

image


4.4 YML

package main

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

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

	resGroup := engine.Group("/res")
	{
		resGroup.GET("yml", func(context *gin.Context) {
			context.YAML(200, gin.H{
				"id":   1,
				"name": "xyz",
			})
		})
	}

	engine.Run(":8080")
}

image


4.5 Header

package main

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

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

	resGroup := engine.Group("/res")
	{
		resGroup.GET("header", func(context *gin.Context) {
			context.Header("myName", "xyz") // 响应头设置
		})
	}

	engine.Run(":8080")
}

image


4.6 重定向

package main

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

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

	resGroup := engine.Group("/res")
	{
	resGroup.GET("redirect", func(context *gin.Context) {
			//context.Redirect(301, "http://localhost:8080/res/myRedirect")
			context.Redirect(http.StatusMovedPermanently, "http://localhost:8080/res/myRedirect")
		})
	resGroup.GET("myRedirect", func(context *gin.Context) {
			context.JSON(200, gin.H{
				"data": "/res/redirect->/res/myRedirect",
			})
		})
	}

	engine.Run(":8080")
}

image


4.7 文件

package main

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

func main() {
	engine := gin.Default()
	//engine := gin.New()
	//engine.Use(gin.Logger()).Use(gin.Recovery())
	// 静态资源加载
	// http://ip:port/img/a.jpg 可访问
	engine.Static("/img", "src/static/img") // 前一个参数为路由前缀、后一个参数为实际资源目录
	resGroup := engine.Group("/res")
	{
		resGroup.GET("file", func(context *gin.Context) {
			//context.File("G:\\workDoc\\png\\golang.jpg") // 正常预览
			context.FileAttachment("G:\\workDoc\\png\\golang.jpg", "my.png") // 起别名下载
		})
        
        resGroup.GET("someDataFromReader", func(context *gin.Context) {
			// 模拟发送请求
			resp, err := http.Get("http://localhost:8080/res/file")
			if err != nil || resp.StatusCode != http.StatusOK {
				context.Status(http.StatusServiceUnavailable)
				return
			}
			// 构建响应信息
			reader := resp.Body
			contentLength := resp.ContentLength
			contentType := resp.Header.Get("Content-Type")

			extraHeaders := map[string]string{
				"Content-Disposition": `attachment;filename="a.png"`,
			}
			context.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders) // 从数据流中读取并输出文件下载
		})
	}

	engine.Run(":8080")
}

image

image


4.8 模板渲染

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{.name }}</title>
</head>
<body>
    <p>原来的值:{{.name}}</p>
    <p>Lower的值:{{.name | upper }}</p>
</body>
</html>
package main

import (
	"encoding/xml"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"html/template"
	"log"
	"net/http"
	"strings"
)

func main() {
	engine := gin.Default()
	// 自定义模板函数(需要放置到静态模板加载前-重要)
	engine.SetFuncMap(template.FuncMap{
		"upper": func(myStr string) template.HTML {
			return template.HTML(strings.ToUpper(myStr))
		},
	})
	// 静态模板加载
	// 1. 特定文件
	//engine.LoadHTMLFiles("src/templates/a.html")
	// 2. 特定目录下
	engine.LoadHTMLGlob("src/templates/*")
    
    // 静态资源加载
	// curl http://ip:port/img/a.jpg 可访问
	engine.Static("/img", "src/static/img") // 前一个参数为路由前缀、后一个参数为实际资源目录

	resGroup := engine.Group("/res")
	{
		resGroup.GET("html", func(context *gin.Context) {
			context.HTML(200, "a.html", gin.H{
				"name": "golang",
			})
		})
	}

	engine.Run(":8080")
}

image


4.9 ProtoBuf

package main

import (
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/testdata/protoexample"

)

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

	resGroup := engine.Group("/res")
	{
		resGroup.GET("protobuf", func(context *gin.Context) {
			int64Slice := []int64{int64(1), int64(2)} // int64切片
			label := "test"
			// protobuf具体定义写在testdata/proexample文件中
			data := &protoexample.Test{
				Label: &label,
				Reps:  int64Slice,
			}
			// 响应数据变为为二进制数据
			// 将输出被protoexample.Test protobuf序列化的数据
			context.ProtoBuf(200, data)
		})
	}

	engine.Run(":8080")
}


5. 中间件

package main

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

// 自定义中间件(拦截请求到响应生命周期的特殊函数)
func myMW1() gin.HandlerFunc {
	return func(context *gin.Context) {
		log.Println("m1-中间件开始...")
		context.Set("m1", "m1")
		context.Next() // 后续业务处理
		log.Println("m1-中间件结束...")
	}
}

func myMW2() gin.HandlerFunc {
	return func(context *gin.Context) {
		log.Println("m2-中间件开始...")
		context.Set("m2", "m2")
		context.Next() // 后续请求业务处理
        //context.Abort() // 直接终止后续请求执行 请求无返回值
		log.Println("m2-中间件结束...")
	}
}

func main() {
	// 等价于 gin.New().Use(gin.Recovery()).Use(gin.Logger())
	engine := gin.Default()
	// 全局配置中间件
	engine.Use(myMW1())
	engine.GET("/mw1", func(context *gin.Context) {
		log.Println("mw1-请求处理开始...")
		val, _ := context.Get("m1") // 获取值
		log.Println("mw1-获取值=", val)
		context.JSON(200, gin.H{
			"m1": val,
		})
		log.Println("mw1-请求处理结束")
	})
	// 局部配置中间件
    // rGroup := engine.Group("/", myMw2()) 分组路由设置生效
	// rGroup.Use(myMw2()) 和上面一样效果
	engine.GET("/mw2", myMW2(), func(context *gin.Context) {
		log.Println("mw2-请求处理开始...")
		m1, _ := context.Get("m1")
		m2, _ := context.Get("m2")
		log.Println("mw2-获取值=", m1, m2)
		context.JSON(200, gin.H{
			"m1": m1,
			"m2": m2,
		})
		log.Println("mw2-请求处理结束...")
	})
	engine.Run()
}

image


6. 会话

package main

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

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

	statusGroup := engine.Group("/status")
	{
		// 设置cookie
		statusGroup.GET("/cookie/set", func(context *gin.Context) {
			context.SetCookie("myCookie", "xyz", 3600, "/", "localhost", true, true)
			context.JSON(200, "ok")
		})
		// 获取cookie
		statusGroup.GET("/cookie/get", func(context *gin.Context) {
			cookieVal, err := context.Cookie("myCookie")
			if err != nil {
				log.Println("getCookieError:", err)
			}
			context.JSON(200, gin.H{
				"myCookie": cookieVal,
			})
		})
		// 删除cookie(设置有效期为-1即可)
		statusGroup.GET("/cookie/delete", func(context *gin.Context) {
			context.SetCookie("myCookie", "xyz", -1, "/", "localhost", true, true)
			cookie, err := context.Cookie("myCookie")
			if err != nil {
				context.JSON(200, err.Error())
			} else {
				context.JSON(200, gin.H{
					"myCookie": cookie,
				})
			}
		})
	}
	engine.Run()
}

image


6.2 session

6.2.1 单个

go get github.com/gin-contrib/sessions
package main

import (
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
)

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

	store := cookie.NewStore([]byte("mySecretKey"))   // session需要用到的密钥
	engine.Use(sessions.Sessions("mySession", store)) // 单个session使用

	statusGroup := engine.Group("/status")
	{

		// session
		// 设置session(前置需要全局配置密钥) 需要调用save保存
		statusGroup.GET("/session/set", func(context *gin.Context) {
			session := sessions.Default(context)
			session.Set("password", "123456")
			session.Save()
			context.JSON(200, gin.H{
				"set": "password",
			})
		})
		// 获取session
		statusGroup.GET("/session/get", func(context *gin.Context) {
			session := sessions.Default(context)
			secret := session.Get("password")
			context.JSON(200, gin.H{
				"get": secret,
			})
		})
	}
	engine.Run()
}

image


6.2.2 多个

package main

import (
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
)

func main() {
	engine := gin.Default()
    
	store := cookie.NewStore([]byte("mySecretKey")) // session需要用到的密钥
	sessionNames := []string{"a", "b"}
	engine.Use(sessions.SessionsMany(sessionNames, store)) // 多个session使用

	statusGroup := engine.Group("/status")
	{
	
		// 设置多个session
		statusGroup.GET("/session/multiSet", func(context *gin.Context) {
			sessionA := sessions.DefaultMany(context, "a")
			sessionB := sessions.DefaultMany(context, "b")

			sessionA.Set("name", "golang")
			sessionA.Save()
			sessionB.Set("time", "2013")
			sessionB.Save()

			// session删除
			sessionA.Delete("time")
			context.JSON(200, gin.H{
				"name": sessionA.Get("name"),
				"time": sessionB.Get("time"),
			})
		})
	}
    
	engine.Run()
}

image


6.2.3 redis存储

go get github.com/gin-contrib/sessions/redis
package main

import (
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/redis"
	"github.com/gin-gonic/gin"
)

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

	// redis持久化session使用
	myRedisStore, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret"))
    // 注册中间件
	engine.Use(sessions.Sessions("redisSession", myRedisStore))

	statusGroup := engine.Group("/status")
	{
		// session会话信息保存到redis
		statusGroup.GET("/session/redis", func(context *gin.Context) {
			session := sessions.Default(context)
			var count int
			v := session.Get("count")
			if v == nil {
				count = 0
			} else {
				count = v.(int)
				count++
			}
			session.Set("count", count)
			session.Save()
			context.JSON(200, gin.H{
				"count": count,
			})
		})
	}
    
	engine.Run()
}

image


6. 扩展

6.1 异步任务

func main() {
	engine := gin.Default()
	engine.GET("/async", func(context *gin.Context) {
		copyC := context.Copy() // 在中间件或者handler中启动新的goroutine 需要使用只读副本*gin.Context上下文
		go func() {
			time.Sleep(time.Second * 3) // 耗时操作
			log.Println("asyncTask is done..." + copyC.Request.URL.Path)
		}() // 模拟异步任务
	})
	engine.GET("/sync", func(context *gin.Context) {
		time.Sleep(time.Second * 3)
		log.Println("syncTask is done..." + context.Request.URL.Path) // 同步任务无需拷贝上下文
	})  

	engine.Run()
}

image


6.2 自定义日志

func main() {
	// 日志配置
	gin.DisableConsoleColor() // 禁用控制台颜色 将日志写入文件不需要控制台颜色
	//gin.ForceConsoleColor() // 强制日志颜色化

	// 日志写入到文件
	logFile, _ := os.Create("ginWeb.log")
	//gin.DefaultWriter = io.MultiWriter(logFile)            // 单独输出到日志
    //gin.DefaultWriter = os.Stdout                          // 单独输出到控制台(默认)
	gin.DefaultWriter = io.MultiWriter(logFile, os.Stdout) // 日志输出到文件和控制台
    
    // 自定义日志输出格式
	//gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
	//	log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
	//}  // 使用gin.New() 不使用默认自带的中间件gin.Logger()

	engine := gin.Default()
	//engine.Use(gin.Recovery())
	//engine.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
	//	// 自定义格式
	//	return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
	//		param.ClientIP,                       // ::1
	//		param.TimeStamp.Format(time.RFC1123), // [Wed, 06 Sep 2023 20:45:35 CST]
	//		param.Method,                         // GET
	//		param.Path,                           // /log
	//		param.Request.Proto,                  // HTTP/1.1
	//		param.StatusCode,                     // 200
	//		param.Latency,                        // 0s
	//		param.Request.UserAgent(),            // PostmanRuntime/7.32.3
	//		param.ErrorMessage,                   // ""
	//	)
	//})) // 自定义日志输出格式 使用gin.New() 不使用默认自带的中间件gin.Logger()
	///**
	//out:
	//::1 - [Wed, 06 Sep 2023 20:49:47 CST] "GET /log HTTP/1.1 200 0s "PostmanRuntime/7.32.3" "
	//*/

	engine.GET("/log", func(context *gin.Context) {
		context.String(200, "ok")
	})

	engine.Run()
}

6.3 热重载

6.3.1 air

简介
监控文件代码变更后自动编译执行
特色:
	彩色日志输出
	自定义构建或其他命令
	支持忽略子目录
	air启动后可监听新目录
	更好构建过程
使用:
	1. 默认.air.toml
	1.1 切换目录到项目根目录
		cd /path/to/your_project
	1.2 启动air(使用配置文件-可自编辑或默认 这里使用默认)
		air -c .air.toml
		
	2. 自编辑.air.toml
	2.1 切换目录到项目根目录
		cd /path/to/your_project
	2.2 在当前目录生成air.toml
		ari init 
	2.3 按需自编辑
	2.4 启动air(可选参数 -c 特定目录下的air.toml)
		air
ps: 
参考文档:https://github.com/cosmtrek/air
依赖
go install github.com/cosmtrek/air@latest
配置文件
# Config file for [Air](https://github.com/cosmtrek/air) in TOML format

# Working directory
# . or absolute path, please note that the directories following must be under root.
root = "."
tmp_dir = "tmp"

[build]
# Just plain old shell command. You could use `make` as well.
cmd = "go build -o ./tmp/main ."
# Binary file yields from `cmd`.
bin = "tmp/main"
# Customize binary, can setup environment variables when run your app.
full_bin = "APP_ENV=dev APP_USER=air ./tmp/main"
# Watch these filename extensions.
include_ext = ["go", "tpl", "tmpl", "html"]
# Ignore these filename extensions or directories.
exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"]
# Watch these directories if you specified.
include_dir = []
# Watch these files.
include_file = []
# Exclude files.
exclude_file = []
# Exclude specific regular expressions.
exclude_regex = ["_test\\.go"]
# Exclude unchanged files.
exclude_unchanged = true
# Follow symlink for directories
follow_symlink = true
# This log file places in your tmp_dir.
log = "air.log"
# Poll files for changes instead of using fsnotify.
poll = false
# Poll interval (defaults to the minimum interval of 500ms).
poll_interval = 500 # ms
# It's not necessary to trigger build each time file changes if it's too frequent.
delay = 0 # ms
# Stop running old binary when build errors occur.
stop_on_error = true
# Send Interrupt signal before killing process (windows does not support this feature)
send_interrupt = false
# Delay after sending Interrupt signal
kill_delay = 500 # ms
# Rerun binary or not
rerun = false
# Delay after each executions
rerun_delay = 500
# Add additional arguments when running binary (bin/full_bin). Will run './tmp/main hello world'.
args_bin = ["hello", "world"]

[log]
# Show log time
time = false
# Only show main log (silences watcher, build, runner)
main_only = false

[color]
# Customize each part's color. If no color found, use the raw app log.
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"

[misc]
# Delete tmp directory on exit
clean_on_exit = true

[screen]
clear_on_rebuild = true
keep_scroll = true
实例
cd web/src

air 
或者
air -d # 会有更加详细日志输出

# ps: golang idea 打开终端操作Terminal
// 热加载air
func main() {
	// 参考文档: https://github.com/cosmtrek/air
	engine := gin.Default()
	log.Println("变动的值=", 33)
	engine.Run()
}

image


6.3.2 fresh

简介
监控文件代码变更后自动编译执行
使用:
	1. 切换到项目目录
		cd /path/to/myapp
	2.1 执行命令启动fresh
		fresh
	或者
	2.2 指定配置文件启动fresh
		fresh -c runner.conf

PS:
参考文档:https://github.com/gravityblast/fresh
依赖
go install github.com/pilu/fresh@latest
配置文件
root:              .
tmp_path:          ./tmp
build_name:        runner-build
build_log:         runner-build-errors.log
valid_ext:         .go, .tpl, .tmpl, .html
no_rebuild_ext:    .tpl, .tmpl, .html
ignored:           assets, tmp
build_delay:       600
colors:            1
log_color_main:    cyan
log_color_build:   yellow
log_color_runner:  green
log_color_watcher: magenta
log_color_app:
实例
cd web/src
fresh
// 热加载fresh
func main() {
	// 参考文档: https://github.com/gravityblast/fresh
	engine := gin.Default()
	log.Println("变动的值=", 33)
	engine.Run()
}

image


6.5 自定义HTTP配置

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()
}

6.6 参数校验

6.6.1 gin参数校验

type student struct {
	Name string `json:"name" binding:"required,startswith=a"`
	Age  int    `json:"age" binding:"required,gt=0,lte=130"`
	Sex  string `json:"sex" binding:"required,oneof=male female"`
}

func main() {

	engine := gin.Default()

	rg := engine.Group("/check")
	{
        // binding
		rg.POST("source", func(context *gin.Context) {
			var stu student
			if err := context.ShouldBind(&stu); err == nil {
				context.JSON(200, stu)
			} else {
				context.JSON(400, gin.H{"error": err.Error()})
			}
		})
	}

	engine.Run()
}

image

image


6.6.2 自定义参数校验器

go get github.com/go-playground/validator/v10
type user struct {
	Name  string `json:"name" validate:"required,startswith=a"`
	Age   uint8  `json:"age" validate:"required,gt=0,lte=130"`
	Sex   string `json:"sex" validate:"required,fieldValidation"`
	Email string `json:"email" validate:"required,email"`
}

type user2 struct {
	Name string `json:"name"`
}

var (
	validate *validator.Validate
)


func main() {

	engine := gin.Default()

	validate = validator.New() // 创建验证器

	rg := engine.Group("/check")
	{
		// 自定义特定字段-参数校验方法fieldValidation
		rg.POST("customFunc", func(context *gin.Context) {

			var u user
			// 注册并指定自定义参数校验方法-特定字段
			if err := validate.RegisterValidation("fieldValidation", func(fl validator.FieldLevel) bool {
				s := fl.Field().Interface().(string)
				return strings.Contains(s, "male")
			}); err != nil {
				log.Println("RegisterValidationError=" + err.Error())
				return
			}
			err := context.ShouldBind(&u)
			// 参数校验
			if err = validate.Struct(u); err == nil {
				context.JSON(200, u)
			} else {
				context.JSON(400, gin.H{"error": err.Error()})
			}
		})

		// 自定义结构体-参数校验方法
		rg.POST("customStruct", func(context *gin.Context) {
			var u user2

			//// 注册自定义结构体-参数校验方法-1
			//validate.RegisterStructValidation(func(sl validator.StructLevel) {
			//	mu := sl.Current().Interface().(user2)
			//	if len(mu.Name) == 0 || !strings.Contains(mu.Name, "a") {
			//		// 可多个参数校验
			//		sl.ReportError(mu.Name, "name", "Name", "nameTg", "name")
			//	}
			//}, user2{})

			// 注册自定义结构体-参数校验方法-2(定义结构体字段规则)
			rules := map[string]string{
                // 可多个参数校验
				"Name": "required,min=2,max=6,contains=a",
			}
			validate.RegisterStructValidationMapRules(rules, user2{})

			err := context.ShouldBind(&u)

			// 校验参数
			if err = validate.Struct(u); err != nil {
				context.JSON(400, gin.H{
					"error": err.Error(),
				})
			} else {
				context.JSON(200, u)
			}

		})
	}

	engine.Run()
}

image

image


6.6.3 中文错误提示

go get github.com/go-playground/validator/v10
type user2 struct {
	Name string `json:"name"`
}

var (
	validate *validator.Validate     // 校验器
	uni      *ut.UniversalTranslator // 翻译器
)

func main() {

	engine := gin.Default()

	validate = validator.New() // 创建验证器

	mEn := en.New()
	mZn := zh.New()
	uni = ut.New(mEn, mZn, mEn)
	translator, _ := uni.GetTranslator("zh")
	zh2.RegisterDefaultTranslations(validate, translator)
	rg := engine.Group("/check")
	{
		// 自定义结构体-参数校验方法
		rg.POST("customStruct", func(context *gin.Context) {
			var u user2

			//// 注册自定义结构体-参数校验方法-1
			//validate.RegisterStructValidation(func(sl validator.StructLevel) {
			//	mu := sl.Current().Interface().(user2)
			//	if len(mu.Name) == 0 || !strings.Contains(mu.Name, "a") {
			//		// 可多个参数校验
			//		sl.ReportError(mu.Name, "name", "Name", "nameTg", "name")
			//	}
			//}, user2{})

			// 注册自定义结构体-参数校验方法-2(定义结构体字段规则)
			rules := map[string]string{
				"Name": "required,min=2,max=6,contains=a",
			}
			validate.RegisterStructValidationMapRules(rules, user2{})

			err := context.ShouldBind(&u)

			// 校验参数
			if err = validate.Struct(u); err != nil {
				context.JSON(400, gin.H{
					"enError": err.Error(),                                            // 原始错误输出
					"zhError": err.(validator.ValidationErrors).Translate(translator), // 中文错误输出
				})
			} else {
				context.JSON(200, u)
			}

		})
	}

	engine.Run()
}

image


标签:engine,context,func,gin,main,log
From: https://www.cnblogs.com/fsh19991001/p/17687549.html

相关文章

  • [Maven] maven插件系列之maven-shade-plugin
    [Maven]maven插件系列之maven-shade-plugin1插件简述/PluginOverview1.1定义与目的/Definition&GoalsOfficialDefinitionApacheMaven:maven-shade-pluginThispluginprovidesthecapabilitytopackagetheartifactinanuber-jar,includingitsdependenc......
  • docker 安装nginx,并配置域名ssl证书(超详细)
    1、直接安装最新的nginxdockerpullnginx2、由于后期需要方便配置与管理nginx,需要把nginx容器内的文件夹进行挂载到宿主机中,所以此处需要进行到自己心仪的盘中创建文件夹(本次说明在/home)mkdirnginx&&cd$_&&mkdir-p{ssl,config,logs}ssl放域名对应证书config放nginx配置文......
  • 利用时间戳切割Nginx日志
    worker_processes2;events{worker_connections1024;}http{includemime.types;default_typeapplication/octet-stream;log_formataka_logs'{"@timestamp":"$time_iso8601",''&q......
  • nginx + php procedures
    https://mkyong.com/nginx/nginx-php-on-windows/https://www.youtube.com/watch?v=loSNnt9ZzWI&ab_channel=javafrmhowtostopnginx?nginx-sstophowtostartnginx?startnginx......
  • 启动nginx
    Docrootis:/usr/local/var/wwwThedefaultporthasbeensetin/usr/local/etc/nginx/nginx.confto8080sothatnginxcanrunwithoutsudo.nginxwillloadallfilesin/usr/local/etc/nginx/servers/.Tohavelaunchdstartnginxnowandrestartatlogin:b......
  • 自动化安装Nginx脚本:简化您的服务器配置
    在如今的网络世界中,Nginx作为一款高性能的Web服务器和反向代理服务器,扮演着至关重要的角色。然而,手动安装和配置Nginx可能会耗费大量时间和精力,特别是对于那些对Linux系统不太熟悉的人来说。幸运的是,我们为您带来了一个自动化的解决方案,能够简化整个Nginx安装和配置过程。我们为您......
  • Windows上要启动和停止关闭Nginx
    要启动和停止关闭Nginx,在Windows上,你可以使用以下命令:启动Nginx:```nginx```停止关闭Nginx:```nginx-sstop```如果你使用`startnginx`命令在后台启动了Nginx,则可以使用以下命令将其停止关闭:```nginx-squit```使用`stop`参数将会优雅地关闭Nginx,而使用`quit`参数将会......
  • Nginx_(背锅)解析漏洞复现
    目录Nginx解析漏洞复现1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现1、基础环境2、漏洞扫描3、漏洞验证1.5、深度利用GetShellNginx解析漏洞复现说明内容漏洞编号漏洞名称漏洞评级影响范围漏洞描述Nginx解析漏洞该解析漏洞是PHPCGI的......
  • Nginx_0.7.65_空字节漏洞
    目录Nginx_0.7.65_空字节漏洞1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现1、基础环境2、漏洞扫描3、漏洞验证Nginx_0.7.65_空字节漏洞1.1、漏洞描述1.2、漏洞等级1.3、影响版本0.7.651.4、漏洞复现1、基础环境链接:https://pan.baidu.com/s/1GBEb6ig6ogiQfXq-y......
  • AtCoder Beginner Contest 318 - D(状压 dp)
    目录D-GeneralWeightedMaxMatchingD-GeneralWeightedMaxMatching题意给定无向图,边有边权。让你选择一组边,满足任意两边不相交且总边权和最大。顶点数$\le16$思路状压DP求解该问题状态:利用n位二进制表示每个顶点是否已经被选择,0表示该顶点未选,1表示当前......