gin 基本使用 2
下载文件
在 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-data
和 application/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
是根据请求头 Method
和 ContentType
去匹配该用哪种形式去解析,一般这个方法用来解析 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
:字符串后缀dive
:dive
后面的验证是针对数组中每一个元素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")
}
中间件
- 中间件是按照顺序执行
- 如果某个中间件执行后,不想后面的中间件执行,使用
c.Abort()
c.Abort()
需要写在c.Next()
之前,否则后面的中间件还是会执行c.Next()
上面是请求中间件,下面是响应中间件- 请求顺序是
m1
、m2
- 响应顺序是
m2
、m1
- 请求顺序是
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_id
和 user_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