目录
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")
}
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")
}
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")
}
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")
}
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")
}
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")
}
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")
}
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")
}
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")
}
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")
}
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")
}
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")
}
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")
}
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")
}
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")
}
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()
}
6. 会话
6.1 cookie
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()
}
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()
}
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()
}
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()
}
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()
}
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()
}
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()
}
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()
}
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()
}
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()
}
标签:engine,context,func,gin,main,log From: https://www.cnblogs.com/fsh19991001/p/17687549.html