当前位置:   article > 正文

Go实战Gin+Vue+微服务打造秒杀商城第三课 gin中间件和日志_go+vue 开发实战

go+vue 开发实战

gin中间件

中间件介绍

一、什么是中间件

  • 开发者自定义的钩子(Hook)函数
  • 类似python中的装饰器

二、中间件的作用

  • 中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等
  • 需要对某一类函数进行通用的前置或者后置处理

三、使用场景

  1. 逻辑执行之前

    • token等认证
    • 权限校验
    • 限流
    • 数据过滤
    • 白名单
  2. 逻辑执行之后

    • 数据过滤,比如敏感词等
    • 统一的响应头等

中间件的使用

一、使用中间件

router := gin.New()

router.Use(gin.Logger())
router.Use(gin.Recovery())
  • 1
  • 2
  • 3
  • 4

注意:中间件的回调要先于用户定义的路径处理函数

二、中间价的使用位置说明

中间件的使用顺序绝对了什么时候执行中间件,比如有三个路由:

router := gin.Default()

router.Get("/login",xxx)

router.Get("/user_list",xxx)
router.Get("/news_list",xxx)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

加入user_list和news_list需要在登陆后才可以访问,login不要登录认证就可访问,

这时候我们需要一个token认证的中间件,那这个中间件Use的位置会有影响,如下:

router := gin.Default()

router.Get("/login",xxx)

router.User(MiddleWare())

router.Get("/user_list",xxx)
router.Get("/news_list",xxx)

Use不能放在login的前面,不然也会对login进行拦截认证
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

注意:Use不能放在login的前面,不然也会对login进行拦截认证

三、中间件执行顺序示例

func MiddlewareA() gin.HandlerFunc {
    return func(c *gin.Context) {
        fmt.Println("MiddlewareA before request")
        // before request
        c.Next()
        // after request
        fmt.Println("MiddlewareA after request")
    }
}

func MiddlewareB() gin.HandlerFunc {
    return func(c *gin.Context) {
        fmt.Println("MiddlewareB before request")
        // before request
        c.Next()
        // after request
        fmt.Println("MiddlewareB after request")
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

内置中间件

一、gin内置中间件

  • func BasicAuth(accounts Accounts) HandlerFunc
  • func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc realm:认证分组
  • func Bind(val interface{}) HandlerFunc //拦截请求参数并进行绑定
  • func ErrorLogger() HandlerFunc //错误日志处理
  • func ErrorLoggerT(typ ErrorType) HandlerFunc //自定义类型的错误日志处理
  • func Logger() HandlerFunc //日志记录
  • func LoggerWithConfig(conf LoggerConfig) HandlerFunc
  • func LoggerWithFormatter(f LogFormatter) HandlerFunc
  • func LoggerWithWriter(out io.Writer, notlogged …string) HandlerFunc
  • func Recovery() HandlerFunc
  • func RecoveryWithWriter(out io.Writer) HandlerFunc
  • func WrapF(f http.HandlerFunc) HandlerFunc //将http.HandlerFunc包装成中间件
  • func WrapH(h http.Handler) HandlerFunc //将http.Handler包装成中间件

自定义中间件

一、自定义中间件的两种方式

//自定义中间件第1种定义方式
func MiddleWare1(ctx *gin.Context)  {

    fmt.Println("这是自定义中间件1")
}

自定义中间件第2种定义方式
func MiddleWare2() gin.HandlerFunc  {
    return func(ctx *gin.Context) {

        fmt.Println("这是自定义i中间件2")
    }
}



router := gin.New()

router.Use(MiddleWare1)      // 需要加括号
router.Use(MiddleWare2())        // 不需要加括号,当成参数
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

中间件中的Next和Abort

一、Next

在我们定义的众多中间件,会形成一条中间件链,而通过 Next 函数来对后面的中间件进行执行
特点:

  • 1.当遇到c.Next()函数时 它取出所有的没被执行过的注册的函数都执⾏⼀遍,然后再回到本函数中,有点类似递归函数
  • 2.Next 函数是在请求前执行,而 Next 函数后是在请求后执行。
  • 3.可以用在token校验,把用户id存起来供给功能性函数使用

二、Abort

  1. ctx.Abort()方法的作用 终止调用整个链条
  2. 比如:token认证没有通过,不能直接使用return返回,而是使用Abort来终止

三、中间件执行顺序示例

func MiddleWare1(ctx *gin.Context)  {

    fmt.Println("这是自定义中间件1--开始")
    ctx.Next()
    fmt.Println("这是自定义中间件1--结束")
}

func MiddleWare2() gin.HandlerFunc {

    return func(ctx *gin.Context) {
        fmt.Println("这是自定义中间件2--开始")

        if 3 < 4{   // 满足条件
            ctx.Abort()
        }
        ctx.Next()
        fmt.Println("这是自定义中间件2--结束")
    }
}

func MiddleWare3(ctx *gin.Context)  {
    fmt.Println("这是自定义中间件3--开始")
    ctx.Next()
    fmt.Println("这是自定义中间件3--结束")
}


router := gin.Default()

router.Use(Middleware1,Middleware2(),Middleware3)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

四、利用Next计算请求时间

func Middle(ctx *gin.Context){
    t := time.Now()

    //可以设置一些公共参数
    c.Set("example", "12345")
    //等其他中间件先执行
    c.Next()
    //获取耗时
    latency := time.Since(t)
    fmt.Printf("cost time:%d us", latency/1000)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

路由组中间件的使用

一、使用

v1 := router.Group("/v1")
v1.Use(GroupRouterMiddle())

{
    v1.Get...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

全局中间件的使用

一、使用

绑定在根router上即可

router := gin.Default()

//使用自定义的全局中间件
router.Use(GlobalMiddle)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

二、中间件执行顺序

全局中间件 > 路由组中间件 > 路由中间件,如果是同一类别,那就取决于append的前后顺序了

局部中间件的使用

一、自定义中间件

 func Middle1Ware() gin.HandlerFunc{
    return func(c *gin.Context) {
        t := time.Now()
        fmt.Println("我是自定义中间件第2种定义方式---请求之前")
        //在gin上下文中定义一个变量
        c.Set("example", "CustomRouterMiddle2")
        //请求之前
        c.Next()
        fmt.Println("我是自定义中间件第2种定义方式---请求之后")
        //请求之后
        //计算整个请求过程耗时
        t2 := time.Since(t)
        fmt.Println(t2)
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

二、局部使用中间件

// 路由映射时可以传多个HandlerFunc
router := gin.Default()

router.GET("/hello",Middle1Ware(),Hello)
  • 1
  • 2
  • 3
  • 4

BasicAuth中间件

一、BasicAuth中间件的使用

// 局部使用中间价
chap05.GET("/basic",gin.BasicAuth(gin.Accounts{
        "zs":"123456",
        "ls":"123",
        "ww":"1234",
    }),BasicAuthTest)


// 私有数据
var map_data map[string]interface{} = map[string]interface{}{
    "zs":gin.H{"age":18,"addr":"zs-xx"},
    "ls":gin.H{"age":19,"addr":"ls-xx"},
    "ww":gin.H{"age":20,"addr":"ww-xx"},
}


// 获取私有数据。如果没有权限则获取不到
func BasicAuthTest(ctx *gin.Context)  {

    user_name := ctx.Query("user_name")

    data ,ok := map_data[user_name]

    if ok{
        ctx.JSON(http.StatusOK,gin.H{"user_name":user_name,"data":data})
    }else {
        ctx.JSON(http.StatusOK,gin.H{"user_name":user_name,"data":"没有权限"})
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

一文读懂HTTP Basic身份认证:https://juejin.im/entry/6844903586405564430

执行逻辑:

登录页面(没有中间件) – 会设置session – 其他路由回去session的key – 获取对应的数据

二、WrapF

gin.WrapF(IndexHandler)


func IndexHandler(w http.ResponseWriter, r *http.Request)  {
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

三、WrapH和WrapF的区别

需要自己去定义struct实现这个Handler接口

type TestStruct struct {}

func (test *TestStruct) TestH(w http.ResponseWriter, r *http.Request) {
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

gin日志

日志介绍

一、日志的作用

  1. 记录用户操作的审计日志,甚至有的时候就是监管部门的要求。
  2. 快速定位问题的根源
  3. 追踪程序执行的过程。
  4. 追踪数据的变化
  5. 数据统计和性能分析
  6. 采集运行环境数据

日志是程序的重要组成部分

二、日志模板

1.什么是日志模板?

一种统一的格式,一种规范

2.日志模板的作用?

  • 可读性
  • 数据分析,二次挖掘

日志的使用

基于gin的日志中间件

一、使用日志文件

// 1.创建日志文件
f, _ := os.Create("gin.log")

// 2.重新赋值DefaultWriter
gin.DefaultWriter = io.MultiWriter(f)

// 同时在控制台打印信息
gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

二、自定义日志格式

LoggerWithFormatter中间件指定日志格式

router := gin.New()
router.Use(gin.Recovery())

router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {

    // 你的自定义格式
    return fmt.Sprintf("%s\t|\t%s|\t%s|\t%s|\t%s|\t%d|\t%s|\t%s|\t%s \n",
        //客户端IP
        param.ClientIP,
        //时间格式
        param.TimeStamp.Format("2006-01-02 15:04:05"),
        //http请求方式 get post等
        param.Method,
        //客户端请求的路径
        param.Path,
        //http请求协议版本
        param.Request.Proto,
        //http请求状态码
        param.StatusCode,
        //耗时
        param.Latency,
        //http请求代理头
        param.Request.UserAgent(),
        //处理请求错误时设置错误消息
        param.ErrorMessage,
        )
    }))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

logrus第三方库的使用

一、安装

github.com/sirupsen/logrus
  • 1

二、使用logrus自定义日志中间件

func LoggerFile(c *gin.Context)  {
    file_dir := "logs/" + "gin_project.log"

    //写入文件
    src, err := os.OpenFile(file_dir, os.O_APPEND|os.O_WRONLY, os.ModeAppend)

    if err != nil {
        fmt.Println("err", err)
    }


    //实例化
    logger := logrus.New()

    //设置输出
    logger.Out = src

    //设置日志级别
    logger.SetLevel(logrus.DebugLevel)

    //设置日志格式,格式化时间
    logger.SetFormatter(&logrus.TextFormatter{TimestampFormat:"2006-01-02 15:04:05"})


    // 开始时间
    startTime := time.Now()

    // 处理请求
    c.Next()

    // 结束时间
    endTime := time.Now()

    // 执行时间
    latencyTime := endTime.Sub(startTime)

    // 请求方式
    reqMethod := c.Request.Method

    // 请求路由
    reqUri := c.Request.RequestURI

    // 状态码
    statusCode := c.Writer.Status()

    // 请求IP
    clientIP := c.ClientIP()

    // 日志格式
    logger.Infof("| %3d | %13v | %15s | %s | %s |",
        statusCode,
        latencyTime,
        clientIP,
        reqMethod,
        reqUri,
    )

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

三、使用自定义日志中间件

router.Use(LoggerFile)
  • 1

拆分:https://blog.csdn.net/u010918487/article/details/86146691

logrus 不支持输出文件名和行号

logrus使用配置文件

一、配置文件

log_conf.json

{
  "log_dir":"logs/gin_project.log",
  "log_level":"info"
}
  • 1
  • 2
  • 3
  • 4

二、加载配置

    type LogConfig struct {
        LogDir string `json:"log_dir"`  // 相对路径 + 文件名:e.g: logs/gin_project.log
        LogLevel string `json:"log_level"`// 日志級別

    }


    func LoadLogConf() *LogConfig {

        log_conf := LogConfig{}

        file,err := os.Open("conf/log_conf.json")

        if err != nil {
            panic(err)
        }

        defer file.Close()

        byte_data,err2 := ioutil.ReadAll(file)

        if err2 != nil {
            panic(err2)
        }

        err3 := json.Unmarshal(byte_data,&log_conf)

        if err3 != nil {
            panic(err3)
        }


        return &log_conf

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

三、初始化

package logs

import (
    "os"
    "github.com/sirupsen/logrus"
)

var Log = logrus.New() // 创建一个log示例

func init() {

    // 日志級別映射
    log_level_mapping := map[string]logrus.Level{
        "trace":logrus.TraceLevel,
        "debug":logrus.DebugLevel,
        "info":logrus.InfoLevel,
        "warn":logrus.WarnLevel,
        "error":logrus.ErrorLevel,
        "fata":logrus.FatalLevel,
        "panic":logrus.PanicLevel,

    }

    // 初始化配置
    log_conf := LoadLogConf()

    //设置输出
    dir, err := os.OpenFile(log_conf.LogDir, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
    if err != nil {
        fmt.Println("err", err)
    }
    Log.Out = dir

    // 设置日志级别
    Log.Level = log_level_mapping[log_conf.LogLevel]

    //设置日志格式,格式化时间
    Log.SetFormatter(&logrus.TextFormatter{TimestampFormat:"2006-01-02 15:04:05"})
}


TextFormatter:文本格式
JSONFormatter:json格式
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

四、使用

logs.Log.Warn("这是一个warnning级别的日志")
logs.Log.WithFields(logrus.Fields{
            "msg": "测试的错误",
        }).Warn("这是一个warnning级别的日志")
  • 1
  • 2
  • 3
  • 4
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/457413
推荐阅读
相关标签
  

闽ICP备14008679号