你用Go写过中间件吗?带你用Go实现【操作日志中间件】

4,438 阅读3分钟

需求说明

  1. 管理后台所有修改,添加,删除的操作都要记录
  2. 操作日志的统计不影响主程序的性能

需求分析

  1. 把相关代码封装成中间件,独立使用
  2. 合理使用goroutine,不影响主线程的性能

文档说明

  1. 基于golang语言开发
  2. 基于gin网络框架开发
  3. 基于MySQL5.8开发
  4. 把操作日志部分封装成中间件,在rourter文件中引用
  5. 非核心代码已省略,用3个竖着排列的点号.表示

数据库表结构设置

操作日志表

操作日志表

代码

中间件代码

代码分析

  1. 我们可以通过context直接获得请求方式和请求的url
  2. 无法直接获得返回信息,我们可以借助"ResponseWriter",运用拦截器的思想,把返回信息先截取到,再向下继续传递
  3. 像获得客户端ip这类方法封装到util中,方便灵活调用
  4. 我们无法将adminLogs()方法整体设置为goroutine,因为这样会将context的事件传递在新的协程中进行,无法正常传递。
  5. 所以我们再c.Next()事件传递之后,把json解析成结构体,以及保存操作日志到数据库的操作设置为使用goroutine协程操作
  6. 操作日志是没有比较记录查询操作的,所以我们把请求方式为GET的过滤掉
package middleware

//amdin操作日志
import (
    .
    .
    .
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"strings"
)

type bodyLogWriter struct {
	gin.ResponseWriter
	bodyBuf *bytes.Buffer
}

func (w bodyLogWriter) Write(b []byte) (int, error) {
	w.bodyBuf.Write(b)
	return w.ResponseWriter.Write(b)
}

var CommonLogInterceptor = commonLogInterceptor()

/*
1 使用goroutine和channel实现操作日志的入库保存,尽可能的不影响主程序
2 goroutine协程,提高并发量
3 channel通道
*/
func commonLogInterceptor() gin.HandlerFunc {
	return func(c *gin.Context) {
		adminLogs(c)
	}
}

//获得每次请求返回的code和message
func adminLogs(c *gin.Context) {
	if admin, _ := c.Get("admin"); admin != nil {
		method := c.Request.Method
		url := c.Request.URL.Path

		strBody := ""
		var blw bodyLogWriter
		blw = bodyLogWriter{bodyBuf: bytes.NewBufferString(""), ResponseWriter: c.Writer}
		c.Writer = blw
		c.Next()

		if method != "GET" {
			strBody = strings.Trim(blw.bodyBuf.String(), "\n")
			go func(strBody string) {
				var returnJson api.ReturnJson
				json.Unmarshal([]byte(strBody), &returnJson)
				message := fmt.Sprintf("%v", returnJson.Message)

				adminInfo := admin.(**service.RunningClaims)
				adminId := (*adminInfo).ID
				adminName := (*adminInfo).Account

				var log = model.AdminLog{
					AdminId:   adminId,
					AdminName: adminName,
					Method:    method,
					Url:       url,
					Ip:        util.RemoteIP(c.Request),
					Code:      returnJson.Code,
					Message:   message,
				}

				model.CreateLog(log)
			}(strBody)
		}
	}
}

model层代码

package model

type AdminLog struct {
	ID int `json:"id"`
	AdminId uint `json:"admin_id"`
	AdminName string `json:"admin_name"`
	Method string `json:"method"`
	Ip string `json:"ip"`
	Url string `json:"url"`
	Code int `json:"code"`
	Message string `json:"message"`
}

func CreateLog(log AdminLog)  {
	DB.Create(&log)
}

路由代码

package server

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

// NewRouter 路由配置
func NewRouter() *gin.Engine {
	r := gin.Default()

	// 其他中间件
	.
	.
	.
	// 路由
	v1 := r.Group("/api/v1")
	{
		v1.POST("login", api.Login)
		auth := v1.Group("")
		//登录校验中间件
		auth.Use(middleware.AuthRequired())
		//关键代码:权限角色校验
		auth.Use(middleware.AuthCheckMiddleware)
		//操作日志
		auth.Use(middleware.CommonLogInterceptor)
		{
            .
            .
            .
			// 获取所有学校
			{
				auth.GET("/school/", api.GetSchoolInfo)
			}
			.
			.
			.
		}

	}
	return r
}

总结

  1. 以上则是我的实现思路
  2. 还有另外一种思路,计划已消息队列的方式实现,发送通知进行日志的报错

相关文章

  1. GO部分打算做成一个系列,最终把封装好的代码开源出来
  2. 上一篇:Go gin框架封装中间件之1:用户角色权限管理中间件

一起学习,升级打怪

我们搞了一个对学Go真正有帮助的群,欢迎加入:

公众号:程序员升级打怪之旅

微信号:wangzhongyang1993