🆕新增操作日志

This commit is contained in:
coward 2024-08-08 17:26:50 +08:00
parent 1cb2a919d1
commit c1d696ac19
11 changed files with 239 additions and 4 deletions

40
http/api/dashboard.go Normal file
View File

@ -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)
}

108
http/middleware/request.go Normal file
View File

@ -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)
}
}()
}
}

View File

@ -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) // 删除客户端

17
http/router/dashboard.go Normal file
View File

@ -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) // 请求日志
}
}

View File

@ -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) // 登陆

View File

@ -47,5 +47,6 @@ func Rooters() {
UserApi,
ClientApi,
SettingApi,
DashboardApi,
)
}

View File

@ -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) // 删除配置

View File

@ -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) // 新增/编辑用户

14
http/vo/log.go Normal file
View File

@ -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"`
}

View File

@ -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"
}

49
service/log.go Normal file
View File

@ -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
}