From e1f168b2742f319550d72da9f684da35c5693fbd Mon Sep 17 00:00:00 2001 From: coward Date: Mon, 3 Jun 2024 17:09:45 +0800 Subject: [PATCH] =?UTF-8?q?:art:=E6=96=B0=E5=A2=9E=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- http/api/dashboard.go | 42 +++++++++++++++ http/param/base.go | 4 ++ main.go | 1 + middleware/system_log.go | 107 +++++++++++++++++++++++++++++++++++++++ model/entity/system.go | 23 +++++++++ model/vo/dashboard.go | 14 +++++ repository/system_log.go | 50 ++++++++++++++++++ route/captcha.go | 3 +- route/client.go | 2 +- route/dashboard.go | 17 +++++++ route/server.go | 2 +- route/setting.go | 2 +- route/user.go | 4 +- script/script.go | 1 + 14 files changed, 266 insertions(+), 6 deletions(-) create mode 100644 http/api/dashboard.go create mode 100644 middleware/system_log.go create mode 100644 model/vo/dashboard.go create mode 100644 repository/system_log.go create mode 100644 route/dashboard.go diff --git a/http/api/dashboard.go b/http/api/dashboard.go new file mode 100644 index 0000000..fcf48a6 --- /dev/null +++ b/http/api/dashboard.go @@ -0,0 +1,42 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "wireguard-dashboard/http/param" + "wireguard-dashboard/model/entity" + "wireguard-dashboard/repository" + "wireguard-dashboard/utils" +) + +type dashboard struct{} + +func Dashboard() dashboard { + return dashboard{} +} + +// List +// @description: 操作日志分页列表 +// @receiver d +// @param c +func (d dashboard) List(c *gin.Context) { + var p param.OnlyPage + if err := c.ShouldBind(&p); err != nil { + utils.GinResponse(c).FailedWithErr("参数错误", err) + return + } + + // 如果不是超级管理员只能看自己的 + userInfo, ok := c.Get("user") + if !ok { + utils.GinResponse(c).AuthorizationFailed() + return + } + + data, count, err := repository.SystemLog().List(p, userInfo.(*entity.User)) + if err != nil { + utils.GinResponse(c).FailedWithErr("获取失败", err) + return + } + + utils.GinResponse(c).OkWithPage(data, count, p.Current, p.Size) +} diff --git a/http/param/base.go b/http/param/base.go index 6d3c1c6..904f9a0 100644 --- a/http/param/base.go +++ b/http/param/base.go @@ -4,3 +4,7 @@ type page struct { Current int `json:"current" form:"current" binding:"required"` Size int `json:"size" form:"size" binding:"required"` } + +type OnlyPage struct { + page +} diff --git a/main.go b/main.go index 85d9f25..5aff427 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,7 @@ func main() { route.ServerApi, route.ClientApi, route.SettingApi, + route.DashboardApi, ) handler := route.InitRouter() diff --git a/middleware/system_log.go b/middleware/system_log.go new file mode 100644 index 0000000..b1628ab --- /dev/null +++ b/middleware/system_log.go @@ -0,0 +1,107 @@ +package middleware + +import ( + "bytes" + "gitee.ltd/lxh/logger/log" + "github.com/gin-gonic/gin" + jsoniter "github.com/json-iterator/go" + "io" + "regexp" + "strings" + "time" + "wireguard-dashboard/model/entity" + "wireguard-dashboard/repository" +) + +// bodyWriter +// @description: 重写ResponseBody +type bodyWriter struct { + gin.ResponseWriter + body *bytes.Buffer +} + +func SystemLogRequest() gin.HandlerFunc { + return func(c *gin.Context) { + var userId string + if userInfo, ok := c.Get("user"); ok { + userId = userInfo.(*entity.User).Id + } + + // 开始时间 + start := time.Now() + host := c.Request.Host // 请求域名 + path := c.Request.URL.Path // 接口地址 + query := c.Request.URL.RawQuery // 参数 + if strings.Contains(path, "/api/dashboard/list") { + c.Next() + return + } + + var bodyStr string + if !strings.Contains(path, "/api/login") { + body, err := c.GetRawData() // body参数 + if err == nil { + bodyStr = string(body) + c.Request.Body = io.NopCloser(bytes.NewBuffer(body)) + reg := regexp.MustCompile("\\s+") + bodyStr = reg.ReplaceAllString(bodyStr, "") + } + } + + method := c.Request.Method // 请求方式 + ip := c.ClientIP() // 取出IP + // 处理实际客户端IP + if c.Request.Header.Get("U-Real-Ip") != "" { + ip = c.Request.Header.Get("U-Real-Ip") // 这个是网关Nginx自定义的Header头 + } else if c.Request.Header.Get("U-Forwarded-For") != "" { + ip = c.Request.Header.Get("U-Forwarded-For") // 这个是网关Nginx自定义的Header头 + } + ua := c.Request.UserAgent() // UA + + // 重写客户端IP + c.Request.Header.Set("X-Forwarded-For", ip) + c.Request.Header.Set("X-Real-Ip", ip) + + // 拦截response + bw := &bodyWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer} + c.Writer = bw + + header := c.Request.Header + headerStr, _ := jsoniter.MarshalToString(header) + + // 执行下一步请求 + c.Next() + // 计算耗时 + cost := time.Since(start).Milliseconds() + + // 组装实体 + l := entity.SystemLog{ + UserId: userId, + ClientIP: ip, + Host: host, + Method: method, + Uri: path, + Header: headerStr, + Body: bodyStr, + Form: "", + Query: query, + UserAgent: ua, + Cost: cost, + StatusCode: c.Writer.Status(), + Response: "", + } + + // 如果不是返回200,把返回值保存一下 + if c.Writer.Status() != 200 { + resp := bw.body.String() + l.Response = resp + } + + go func() { + if er := repository.SystemLog().SaveLog(&l); er != nil { + log.Debugf("请求日志: %+v", l) + log.Errorf("保存请求日志失败: %v", er) + } + }() + } +} diff --git a/model/entity/system.go b/model/entity/system.go index b7d638f..46cbfaf 100644 --- a/model/entity/system.go +++ b/model/entity/system.go @@ -10,3 +10,26 @@ type Setting struct { func (Setting) TableName() string { return "t_setting" } + +// SystemLog +// @description: 系统日志 +type SystemLog struct { + Base + UserId string `json:"userId" gorm:"type:char(40);comment:'用户id'"` + ClientIP string `json:"clientIP" gorm:"type:varchar(60);not null;comment:'客户端IP'"` + Host string `json:"host" gorm:"type:varchar(255);not null;comment:'请求域名'"` + Method string `json:"method" gorm:"type:varchar(20);not null;comment:'请求方法[GET POST DELETE PUT PATCH]'"` + Uri string `json:"uri" gorm:"type:varchar(255);not null;comment:'请求path'"` + Header string `json:"header" gorm:"type:text;comment:'请求头'"` + Body string `json:"body" gorm:"type:text;comment:'请求体'"` + Form string `json:"form" gorm:"type:text;comment:'请求表单'"` + Query string `json:"query" gorm:"type:text;comment:'请求query'"` + UserAgent string `json:"userAgent" gorm:"type:text;comment:'ua信息'"` + Cost int64 `json:"cost" gorm:"type:int(10);comment:'请求耗时'"` + StatusCode int `json:"statusCode" gorm:"type:int(10);comment:'响应状态码'"` + Response string `json:"response" gorm:"type:text;comment:'返回数据'"` +} + +func (SystemLog) TableName() string { + return "t_system_log" +} diff --git a/model/vo/dashboard.go b/model/vo/dashboard.go new file mode 100644 index 0000000..11e51d7 --- /dev/null +++ b/model/vo/dashboard.go @@ -0,0 +1,14 @@ +package vo + +import "wireguard-dashboard/model/entity" + +type SystemLogItem struct { + Id string `json:"id"` + Username string `json:"username"` + ClientIP string `json:"clientIP"` + Method string `json:"method"` + Host string `json:"host"` + Uri string `json:"uri"` + StatusCode int `json:"statusCode"` + CreatedAt entity.JsonTime `json:"createdAt"` +} diff --git a/repository/system_log.go b/repository/system_log.go new file mode 100644 index 0000000..88fdad4 --- /dev/null +++ b/repository/system_log.go @@ -0,0 +1,50 @@ +package repository + +import ( + "gorm.io/gorm" + "wireguard-dashboard/client" + "wireguard-dashboard/constant" + "wireguard-dashboard/http/param" + "wireguard-dashboard/model/entity" + "wireguard-dashboard/model/vo" + "wireguard-dashboard/utils" +) + +type systemLog struct { + *gorm.DB +} + +func SystemLog() systemLog { + return systemLog{ + client.DB, + } +} + +// SaveLog +// @description: 保存记录 +// @receiver systemLog +// @param data +// @return err +func (s systemLog) SaveLog(data *entity.SystemLog) (err error) { + return s.Create(&data).Error +} + +// List +// @description: 分页列表 +// @receiver s +// @param p +// @return data +// @return total +// @return err +func (s systemLog) List(p param.OnlyPage, loginUser *entity.User) (data []vo.SystemLogItem, total int64, err error) { + sel := s.Scopes(utils.Page(p.Current, p.Size)).Table("t_system_log as tsl"). + Joins("LEFT JOIN t_user as tu ON tu.id = tsl.user_id"). + Select("tsl.id", "tu.name as username", "tsl.client_ip", "tsl.method", "tsl.status_code", "tsl.host", "tsl.uri", "tsl.created_at"). + Order("tsl.created_at DESC") + + if loginUser.IsAdmin == constant.NormalAdmin { + sel.Where("tsl.user_id = ?", loginUser.Id) + } + err = sel.Find(&data).Offset(-1).Limit(-1).Count(&total).Error + return +} diff --git a/route/captcha.go b/route/captcha.go index b0f9c3e..6ce6fa9 100644 --- a/route/captcha.go +++ b/route/captcha.go @@ -3,13 +3,14 @@ package route import ( "github.com/gin-gonic/gin" "wireguard-dashboard/http/api" + "wireguard-dashboard/middleware" ) // CaptchaApi // @description: 验证码 // @param r func CaptchaApi(r *gin.RouterGroup) { - captcha := r.Group("captcha") + captcha := r.Group("captcha", middleware.SystemLogRequest()) { captcha.GET("", api.Captcha().GenerateCaptcha) // 生成验证码 } diff --git a/route/client.go b/route/client.go index 3f55af9..f54a408 100644 --- a/route/client.go +++ b/route/client.go @@ -7,7 +7,7 @@ import ( ) func ClientApi(r *gin.RouterGroup) { - apiGroup := r.Group("client", middleware.Authorization()) + apiGroup := r.Group("client", middleware.Authorization(), middleware.SystemLogRequest()) { apiGroup.GET("list", api.Client().List) // 客户端列表 apiGroup.POST("save", middleware.Permission(), api.Client().Save) // 新增/编辑客户端 diff --git a/route/dashboard.go b/route/dashboard.go new file mode 100644 index 0000000..c2f87bb --- /dev/null +++ b/route/dashboard.go @@ -0,0 +1,17 @@ +package route + +import ( + "github.com/gin-gonic/gin" + "wireguard-dashboard/http/api" + "wireguard-dashboard/middleware" +) + +// DashboardApi +// @description: 控制台相关API +// @param r +func DashboardApi(r *gin.RouterGroup) { + apiGroup := r.Group("dashboard", middleware.Authorization(), middleware.SystemLogRequest()) + { + apiGroup.GET("list", api.Dashboard().List) // 操作日志 + } +} diff --git a/route/server.go b/route/server.go index ca68863..4d6f6fa 100644 --- a/route/server.go +++ b/route/server.go @@ -7,7 +7,7 @@ import ( ) func ServerApi(r *gin.RouterGroup) { - apiGroup := r.Group("server", middleware.Authorization()) + apiGroup := r.Group("server", middleware.Authorization(), middleware.Permission(), middleware.SystemLogRequest()) { apiGroup.GET("", api.Server().GetServer) // 获取服务端信息 apiGroup.POST("", middleware.Permission(), api.Server().SaveServer) // 新增/更新服务端信息 diff --git a/route/setting.go b/route/setting.go index 8a4f885..74eb65a 100644 --- a/route/setting.go +++ b/route/setting.go @@ -10,7 +10,7 @@ import ( // @description: 设置相关API // @param r func SettingApi(r *gin.RouterGroup) { - apiGroup := r.Group("setting", middleware.Authorization()) + apiGroup := r.Group("setting", middleware.Authorization(), middleware.SystemLogRequest()) { apiGroup.POST("save", middleware.Permission(), api.Setting().SetSetting) // 添加/更改设置 - 设置其他的配置 apiGroup.POST("server-global", middleware.Permission(), api.Setting().SetServerGlobal) // 设置服务端全局配置 diff --git a/route/user.go b/route/user.go index 7b615ce..eea5b82 100644 --- a/route/user.go +++ b/route/user.go @@ -8,13 +8,13 @@ import ( func UserApi(r *gin.RouterGroup) { // 登陆相关API - login := r.Group("/login") + login := r.Group("/login", middleware.SystemLogRequest()) { login.POST("", api.UserApi().Login) } // 用户登陆后相关的API - userApi := r.Group("user", middleware.Authorization()) + userApi := r.Group("user", middleware.Authorization(), middleware.SystemLogRequest()) { userApi.DELETE("logout", api.UserApi().Logout) // 用户退出登陆 userApi.GET("", api.UserApi().GetUser) // 获取登陆用户信息 diff --git a/script/script.go b/script/script.go index 5f85663..8768b5e 100644 --- a/script/script.go +++ b/script/script.go @@ -47,6 +47,7 @@ func (s Script) DBMigrate() error { new(entity.Server), new(entity.Client), new(entity.Setting), + new(entity.SystemLog), } return client.DB.AutoMigrate(ent...)