likes
comments
collection
share

gin 基本使用 2

作者站长头像
站长
· 阅读数 53

下载文件

go 中,相对路径是相对于目录的路径

// 访问 assets 路径,实际上是访问 static/assets 目录
router.StaticFs("/assets", http.Dir("static/assets"))
// 访问 icon 路径,实际上是访问 static/icon.png
router.StaticFile("/icon", "static/icon.png")

重定向

router.GET("/baidu", redict)
func redict(c *gin.Context) {
  c.Redirect(301, "https://www.baidu.com")
}

获取请求参数

query

?name=1&age=2 这种形式的参数叫做 query 参数

使用 Query 方法获取请求参数,如果参数不存在,返回空字符串,不能判断参数是否存在

如果要知道参数是否存在,可以使用 GetQuery 方法,返回参数值和是否存在

name := c.Query("name")  // 无法知道 name 有没有传递
name, ok := c.GetQuery("name") // 如果 ok 为 false,表示 name 没有传递
c.QueryArray("name") // 获取多个相同的参数
c.DefaultQuery("name", "default") // 如果 name 不存在,则使用默认值

path

/:id 这种形式的参数叫做 path 参数

id := c.Param("id")

formData

multipart/form-dataapplication/x-www-form-urlencoded 这两种表单数据的获取方式是一样的

c.PostForm("name")
c.PostFormArray("name")
c.DefaultPostForm("name", "default") // 如果 name 不存在,则使用默认值
c.MultipartForm() // 接收所有的表单 数据

获取原始参数

c.GetRawData() // 获取原始数据

头信息

获取请求体

c.GetHeader("User-Agent") // 获取请求头,只能获取到一个,不区分大小写
c.Request.Header.Get("User-Agent") // 获取请求头,只能获取到一个,不区分大小写
c.Request.Header["User-Agent"]  // 可以获取多个相同的请求头,是一个 map 切片:map[string][]string

设置响应头

c.Header("Content-Type", "application/json")

参数绑定

ShouldBindJSON

ShouldBindJSON 是解析 json 形式的数据,json tag 使用 json 标签

// 请求体: {"name": "1", "age": "2"}
type User struct {
  Name string `json:"name"`
  Age  string `json:"age"`
}
u := User{}
c.ShouldBindJSON(&u)

ShouldBindQuery

ShouldBindQuery 是解析 query 形式的数据,json tag 使用 form 标签

// 路由: /user?name=1&age=2
type User struct {
  Name string `form:"name"`
  Age  string `form:"age"`
}
u := User{}
c.ShouldBindQuery(&u)

ShouldBindUri

ShouldBindUri 是解析 path 形式的数据,json tag 使用 uri 标签

// 路由: /user/:name/:age
type User struct {
  Name string `uri:"name"`
  Age  string `uri:"age"`
}
u := User{}
c.ShouldBindUri(&u)

ShouldBind

ShouldBind 是根据请求头 MethodContentType 去匹配该用哪种形式去解析,一般这个方法用来解析 form 表单数据,因为没有 ShouldBindForm 方法,而其他都有各自的方法

// 请求体: {"name": "1", "age": "2"}
type User struct {
  Name string `form:"name"`
  Age  string `form:"age"`
}
u := User{}
c.ShouldBind(&u)

注意:不能解析 x-www-form-urlencoded 格式

验证器

  • oneof:枚举
  • contains:包含某个字符串
  • excludes:不包含某个字符串
  • startsWith:字符串前缀
  • endsWith:字符串后缀
  • divedive 后面的验证是针对数组中每一个元素
  • required:必填
  • omitempty:可以为空
  • min:最小值
  • max:最大值
  • eq:等于
  • ne:不等于
  • gt:大于
  • gte:大于等于
  • lt:小于
  • lte:小于等于
  • len:长度
  • eqfield:等于某个字段
  • nefield:不等于某个字段

文档地址

自定义验证器的错误信息

gin 提供了自定义验证器的方法,只需要实现 validator.Func 方法即可

func GetValidMsg(err error, obj interface{}) string {
  getObj := reflect.TypeOf(obj)
  // 断言是否是 validator.ValidationErrors 类型
  // age int 类型,校验失败,这里不会被断言成功
  errs, ok := err.(validator.ValidationErrors)
  if ok {
    for _, e := range errs {
      // 获取字段的 tag 信息
      if f, exist := getObj.Elem().FieldByName(e.Field()); exist {
        return f.Tag.Get("msg")
      }
    }
  }
  return ""
}

使用:

type User struct {
  Name string `json:"name" binding:"required" msg:"name 必传"`
  Age  int    `json:"age" binding:"required" msg:"age 必传"`
}

u := User{}
if err := c.ShouldBindJSON(&u); err != nil {
  msg := GetValidMsg(err, &u)
  c.JSON(400, gin.H{"msg": msg})
  return
}

这种校验器,只能校验一个 gin 上下文,比如 age 校验 int 类型失败 validator.ValidationErrors 类型,是不会断言成功的,所以就不会返回 msg 信息

自定义验证器

使用 v.RegisterValidation 方法注册验证器 sign

func signValid(fl validator.FieldLevel) bool {
  nameList := []string{"uccs", "astak"}
  for _, name := range nameList {
    // 如果 name 存在,返回 false
    if name == fl.Field().Interface().(string) {
      return false
    }
  }
  return true
}

type User struct {
  Name string `json:"name" binding:"required,sign" msg:"用户名校验失败"`
  Age  int    `json:"age" binding:"required" msg:"age 错误"`
}

if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
  // 注册验证器
  v.RegisterValidation("sign", signValid)
}

if err := c.ShouldBindJSON(&u); err != nil {
  msg := GetValidMsg(err, &u)
  c.JSON(400, gin.H{"msg": msg})
  return
}

文件上传

saveUploadedFile

// 上传单文件
func formFile(c *gin.Context) {
  file, _ := c.FormFile("file")
  fileName := fmt.Sprintf("./upload/%v", file.Filename)
  c.SaveUploadedFile(file, fileName)
}
// 上传多个文件
func formFiles(c *gin.Context) {
  form, _ := c.MultipartForm()
  files, _ := form.File["files"]
  for _, file := range files {
    fileName := fmt.Sprintf("./upload/%v", file.Filename)
    c.SaveUploadedFile(file, fileName)
  }
}

io.Copy

func formFile(c *gin.Context) {
  file, _ := c.FormFile("file")
  fileName := fmt.Sprintf("./upload/%v", file.Filename)
  readerFile, _ := file.Open()
  writerFile, _ := os.Create(fileName)
  defer writerFile.Close()
  defer readerFile.Close()
  io.Copy(writerFile, readerFile)
}

文件下载

func downloadFile(c *gin.Context) {
  // 设置下载文件的格式
  c.Header("Content-Type", "application/octet-stream")
  // 设置下载文件的名字
  c.Header("Content-Disposition", "attachment; filename=xxx.png")
  // 设置下载文件的路径
  c.File("./upload/orange-parrots-8608540.jpg")
}

中间件

  1. 中间件是按照顺序执行
  2. 如果某个中间件执行后,不想后面的中间件执行,使用 c.Abort()
  3. c.Abort() 需要写在 c.Next() 之前,否则后面的中间件还是会执行
  4. c.Next() 上面是请求中间件,下面是响应中间件
    1. 请求顺序是 m1m2
    2. 响应顺序是 m2m1
func m1(c *gin.Context){
  c.Abort()
}
func m2(c *gin.Context){
  // 请求中间件
  c.Next()
  // 响应中间件
}

router.GET("/", m1, func(c *gin.Context){}, m2)

全局中间件

全局中间件使用 router.Use 方法

router.Use(m1, m2)

中间件向后传递数据使用 Set 方法

c.Set("key", value)

如果要使用中间件中的数据,使用 Get 方法,然后用断言转换类型

_user := c.Get("key")
user, ok := _user.(User)

日志

将日志写入文件中

func main() {
  // 日志文件
  file, _ := os.Create("gin.log")
  gin.DefaultWriter = io.MultiWriter(file, os.Stdout)
  router := gin.Default()
  router.GET("/ping", func(c *gin.Context) {})
  router.Run(":8080")
}

设置路由的日志格式

  • httpMethod:请求方法
  • absolutionPath:请求路径
  • handlerName:处理函数名
  • nuHandlers:处理函数数量
gin.DebugPrintRouteFunc = func(httpMethod, absolutionPath, handlerName string, nuHandlers int) {
  log.Printf("[ uccs ] %s %s %s %d \n", httpMethod, absolutionPath, handlerName, nuHandlers)
}

修改 release 模式

gin.SetMode(gin.ReleaseMode)

自定义日志

自定义日志格式,需要使用 gin.New

router := gin.New()
router.Use(gin.LoggerWithFormatter(func(params gin.LogFormatterParams) string {
  return fmt.Sprintf(
    "[ uccs ] %s |%d|  %s  %s\n",
    params.TimeStamp.Format("2006-01-02 15:04:05"),
    params.StatusCode,
    params.Method,
    params.Path,
  )
}))

带颜色的日志

输出颜色的方法:

// 后景色
fmt.Printf("正常颜色 \033[40m 黑色\033[0m 正常颜色\n")
fmt.Printf("正常颜色 \033[41m 红色\033[0m 正常颜色\n")
fmt.Printf("正常颜色 \033[42m 绿色\033[0m 正常颜色\n")
fmt.Printf("正常颜色 \033[43m 黄色\033[0m 正常颜色\n")
fmt.Printf("正常颜色 \033[44m 蓝色\033[0m 正常颜色\n")
fmt.Printf("正常颜色 \033[45m 紫色\033[0m 正常颜色\n")
fmt.Printf("正常颜色 \033[46m 青色\033[0m 正常颜色\n")
fmt.Printf("正常颜色 \033[47m 灰色\033[0m 正常颜色\n")
// 前景色
fmt.Printf("正常颜色 \033[30m 黑色\033[0m 正常颜色\n")
fmt.Printf("正常颜色 \033[31m 红色\033[0m 正常颜色\n")
fmt.Printf("正常颜色 \033[32m 绿色\033[0m 正常颜色\n")
fmt.Printf("正常颜色 \033[33m 黄色\033[0m 正常颜色\n")
fmt.Printf("正常颜色 \033[34m 蓝色\033[0m 正常颜色\n")
fmt.Printf("正常颜色 \033[35m 紫色\033[0m 正常颜色\n")
fmt.Printf("正常颜色 \033[36m 青色\033[0m 正常颜色\n")
fmt.Printf("正常颜色 \033[37m 灰色\033[0m 正常颜色\n")

有颜色日志格式:

router.Use(gin.LoggerWithFormatter(func(params gin.LogFormatterParams) string {
  return fmt.Sprintf(
    "[ uccs ] %s |%d|  %s %s %s  %s\n",
    params.TimeStamp.Format("2006-01-02 15:04:05"),
    params.StatusCode,
    params.MethodColor(), params.Method, params.ResetColor(),
    params.Path,
  )
}))

第三方日志库 logrus

默认是 info 等级,可通过 logrus.GetLevel() 方法查看

设置日志等级 logrus.SetLevel(logrus.DebugLevel)

设置特定字段

在处理用户请求 http 时,所有的日志都会有 request_iduser_id,为了避免每次记录日志都要使用 logrus.WithFields(),我们可以在中间件中使用

logrus.WithFields(logrus.Fields{
  "request_id": request_id
})

格式化输出

logrus.SetFormatter(&logrus.JSONFormatter{})
logrus.SetFormatter(&logrus.TextFormatter{}) // 默认格式

颜色输出

  • ForceColors:强制颜色输出
  • DisableColors:禁用颜色输出
  • ForceQuote:强制引号
  • DisableQuote:禁用引号
  • DisableTimestamp:禁用时间戳
  • FullTimestamp:完整时间戳
  • TimestampFormat:时间戳格式
logrus.SetFormatter(&logrus.TextFormatter{
  ForceColors:     true,
  FullTimestamp:   true,
  TimestampFormat: "2006-01-02 15:04:05",
})

输出到文件

file, _ := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
logrus.SetOutput(file)

同时输出到文件和控制台

file, _ := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
writes := []io.Writer{
  file,
  os.Stdout,
}
logrus.SetOutput(io.MultiWriter(writes...))

自定义日志输出格式

var (
  ccRed    = 1
  ccYellow = 3
  ccBlue   = 4
  ccCyan   = 6
  ccGray   = 7
)

type MyFormatter struct {
  Prefix string
}

func (f MyFormatter) Format(entry *logrus.Entry) ([]byte, error) {
  var color int
  switch entry.Level {

  case logrus.ErrorLevel:
    color = ccRed
  case logrus.WarnLevel:
    color = ccYellow
  case logrus.InfoLevel:
    color = ccBlue
  case logrus.DebugLevel:
    color = ccCyan
  default:
    color = ccGray
  }
  // 设置 buffer 缓冲区
  var b *bytes.Buffer
  if entry.Buffer == nil {
    b = &bytes.Buffer{}
  } else {
    b = entry.Buffer
  }

  // 时间格式化
  formatTime := entry.Time.Format("2006-01-02 15:04:05")
  // 文件行号
  fileVal := fmt.Sprintf("%s:%d", path.Base(entry.Caller.File), entry.Caller.Line)
  fmt.Fprintf(b, "[%s] \033[3%dm[%s]\033[0m [%s] %s %s\n", f.Prefix, color, entry.Level, formatTime, fileVal, entry.Message)
  return b.Bytes(), nil
}

func main() {
  logrus.SetReportCaller(true)
  logrus.SetLevel(logrus.DebugLevel)
  logrus.SetFormatter(&MyFormatter{Prefix: "GIN"})
  logrus.Debug("你好")
  logrus.Info("你好")
  logrus.Warn("你好")
}

// [GIN] [debug] [2024-06-04 20:28:24] main.go:59 你好
// [GIN] [info] [2024-06-04 20:28:24] main.go:60 你好
// [GIN] [warning] [2024-06-04 20:28:24] main.go:61 你好

hook

Levels 是作用等级的函数

type MyHook struct{}

// 作用等级的函数
func (hook MyHook) Levels() []logrus.Level {
  // 只输出 error 的日志
  return []logrus.Level{logrus.ErrorLevel}
}

func (hook MyHook) Fire(entry *logrus.Entry) error {
  return nil
}

logrus.AddHook(&MyHook{})
转载自:https://juejin.cn/post/7377217883305099302
评论
请登录