diff --git a/http/api/dashboard.go b/http/api/dashboard.go new file mode 100644 index 0000000..d8e8a9f --- /dev/null +++ b/http/api/dashboard.go @@ -0,0 +1,40 @@ +package api + +import ( + "fmt" + "github.com/gin-gonic/gin" + "wireguard-ui/http/param" + "wireguard-ui/http/response" + "wireguard-ui/http/vo" + "wireguard-ui/service" +) + +type DashboardApi struct{} + +func Dashboard() DashboardApi { + return DashboardApi{} +} + +// List +// @description: 操作日志 +// @receiver DashboardApi +// @param c +func (DashboardApi) List(c *gin.Context) { + var p param.Page + if err := c.ShouldBind(&p); err != nil { + response.R(c).Validator(err) + return + } + + var loginUser *vo.User + if loginUser = GetCurrentLoginUser(c); c.IsAborted() { + return + } + + data, total, err := service.Log().List(p, loginUser) + if err != nil { + response.R(c).FailedWithError(fmt.Errorf("获取操作日志失败: %v", err.Error())) + return + } + response.R(c).Paginate(data, total, p.Current, p.Size) +} diff --git a/http/middleware/request.go b/http/middleware/request.go new file mode 100644 index 0000000..c90ab77 --- /dev/null +++ b/http/middleware/request.go @@ -0,0 +1,108 @@ +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-ui/http/vo" + "wireguard-ui/model" + "wireguard-ui/service" +) + +// bodyWriter +// @description: 重写ResponseBody +type bodyWriter struct { + gin.ResponseWriter + body *bytes.Buffer +} + +func RequestLog() gin.HandlerFunc { + return func(c *gin.Context) { + var userId string + if userInfo, ok := c.Get("user"); ok { + userId = userInfo.(*vo.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/request/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("X-Real-IP") != "" { + ip = c.Request.Header.Get("X-Real-IP") // 这个是网关Nginx自定义的Header头 + } else if c.Request.Header.Get("X-Forwarded-For") != "" { + ip = c.Request.Header.Get("X-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 := model.RequestLog{ + 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 := service.Log().CreateLog(l); er != nil { + log.Debugf("请求日志: %+v", l) + log.Errorf("保存请求日志失败: %v", er) + } + }() + } +} diff --git a/http/router/client.go b/http/router/client.go index 9a278aa..2f51dc1 100644 --- a/http/router/client.go +++ b/http/router/client.go @@ -10,7 +10,7 @@ import ( // @description: 登陆相关API // @param r func ClientApi(r *gin.RouterGroup) { - client := r.Group("client", middleware.Authorization()) + client := r.Group("client", middleware.Authorization(), middleware.RequestLog()) { client.POST("", api.Client().Save) // 新增/编辑客户端 client.DELETE("/:id", api.Client().Delete) // 删除客户端 diff --git a/http/router/dashboard.go b/http/router/dashboard.go new file mode 100644 index 0000000..f2229a6 --- /dev/null +++ b/http/router/dashboard.go @@ -0,0 +1,17 @@ +package router + +import ( + "github.com/gin-gonic/gin" + "wireguard-ui/http/api" + "wireguard-ui/http/middleware" +) + +// DashboardApi +// @description: 控制台相关接口 +// @param r +func DashboardApi(r *gin.RouterGroup) { + dashboard := r.Group("dashboard", middleware.Authorization(), middleware.RequestLog()) + { + dashboard.GET("/request/list", api.Dashboard().List) // 请求日志 + } +} diff --git a/http/router/login.go b/http/router/login.go index 70f7c1b..12a4989 100644 --- a/http/router/login.go +++ b/http/router/login.go @@ -3,13 +3,14 @@ package router import ( "github.com/gin-gonic/gin" "wireguard-ui/http/api" + "wireguard-ui/http/middleware" ) // LoginApi // @description: 登陆相关API // @param r func LoginApi(r *gin.RouterGroup) { - login := r.Group("/login") + login := r.Group("/login", middleware.RequestLog()) { login.GET("/captcha", api.Login().Captcha) // 获取登陆验证码 login.POST("", api.Login().Login) // 登陆 diff --git a/http/router/root.go b/http/router/root.go index ca20716..0d2ece1 100644 --- a/http/router/root.go +++ b/http/router/root.go @@ -47,5 +47,6 @@ func Rooters() { UserApi, ClientApi, SettingApi, + DashboardApi, ) } diff --git a/http/router/setting.go b/http/router/setting.go index 723b0f5..1e976da 100644 --- a/http/router/setting.go +++ b/http/router/setting.go @@ -3,13 +3,14 @@ package router import ( "github.com/gin-gonic/gin" "wireguard-ui/http/api" + "wireguard-ui/http/middleware" ) // SettingApi // @description: 设置相关API // @param r func SettingApi(r *gin.RouterGroup) { - setting := r.Group("setting") + setting := r.Group("setting", middleware.Authorization(), middleware.RequestLog()) { setting.POST("", api.Setting().Set) // 新增/编辑设置 setting.DELETE("/:code", api.Setting().Delete) // 删除配置 diff --git a/http/router/user.go b/http/router/user.go index 108dd08..5a5687b 100644 --- a/http/router/user.go +++ b/http/router/user.go @@ -10,7 +10,7 @@ import ( // @description: 用户相关API // @param r func UserApi(r *gin.RouterGroup) { - userApi := r.Group("user", middleware.Authorization()) + userApi := r.Group("user", middleware.Authorization(), middleware.RequestLog()) { userApi.GET("/info", api.User().GetLoginUser) // 获取当前登陆用户信息 userApi.POST("", api.User().SaveUser) // 新增/编辑用户 diff --git a/http/vo/log.go b/http/vo/log.go new file mode 100644 index 0000000..44e23f0 --- /dev/null +++ b/http/vo/log.go @@ -0,0 +1,14 @@ +package vo + +import "wireguard-ui/model" + +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 model.JsonTime `json:"createdAt"` +} diff --git a/model/other.go b/model/other.go index cae5fb2..b5a9aac 100644 --- a/model/other.go +++ b/model/other.go @@ -16,3 +16,7 @@ type RequestLog struct { StatusCode int `json:"statusCode" gorm:"type:int(10);comment:'响应状态码'"` Response string `json:"response" gorm:"type:text;comment:'返回数据'"` } + +func (RequestLog) TableName() string { + return "t_request_log" +} diff --git a/service/log.go b/service/log.go new file mode 100644 index 0000000..fd99811 --- /dev/null +++ b/service/log.go @@ -0,0 +1,49 @@ +package service + +import ( + "gorm.io/gorm" + gdb "wireguard-ui/global/client" + "wireguard-ui/global/constant" + "wireguard-ui/http/param" + "wireguard-ui/http/vo" + "wireguard-ui/model" +) + +type log struct { + *gorm.DB +} + +func Log() log { + return log{ + gdb.DB, + } +} + +// CreateLog +// @description: 创建日志 +// @receiver l +// @param log +// @return error +func (l log) CreateLog(log model.RequestLog) error { + return l.Create(&log).Error +} + +// List +// @description: 分页列表 +// @receiver s +// @param p +// @return data +// @return total +// @return err +func (l log) List(p param.Page, loginUser *vo.User) (data []vo.SystemLogItem, total int64, err error) { + sel := l.Scopes(Paginate(p.Current, p.Size)).Table("t_request_log as tsl"). + Joins("LEFT JOIN t_user as tu ON tu.id = tsl.user_id"). + Select("tsl.id", "tu.nickname 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 +}