126 lines
2.7 KiB
Go
126 lines
2.7 KiB
Go
|
|
package middleware
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"io"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/gin-gonic/gin"
|
||
|
|
|
||
|
|
"github.com/user-management-system/internal/domain"
|
||
|
|
"github.com/user-management-system/internal/repository"
|
||
|
|
)
|
||
|
|
|
||
|
|
type OperationLogMiddleware struct {
|
||
|
|
repo *repository.OperationLogRepository
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewOperationLogMiddleware(repo *repository.OperationLogRepository) *OperationLogMiddleware {
|
||
|
|
return &OperationLogMiddleware{repo: repo}
|
||
|
|
}
|
||
|
|
|
||
|
|
type bodyWriter struct {
|
||
|
|
gin.ResponseWriter
|
||
|
|
statusCode int
|
||
|
|
}
|
||
|
|
|
||
|
|
func newBodyWriter(w gin.ResponseWriter) *bodyWriter {
|
||
|
|
return &bodyWriter{ResponseWriter: w, statusCode: 200}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (bw *bodyWriter) WriteHeader(code int) {
|
||
|
|
bw.statusCode = code
|
||
|
|
bw.ResponseWriter.WriteHeader(code)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (bw *bodyWriter) WriteHeaderNow() {
|
||
|
|
bw.ResponseWriter.WriteHeaderNow()
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *OperationLogMiddleware) Record() gin.HandlerFunc {
|
||
|
|
return func(c *gin.Context) {
|
||
|
|
method := c.Request.Method
|
||
|
|
if method == "GET" || method == "HEAD" || method == "OPTIONS" {
|
||
|
|
c.Next()
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
var reqParams string
|
||
|
|
if c.Request.Body != nil {
|
||
|
|
bodyBytes, err := io.ReadAll(io.LimitReader(c.Request.Body, 4096))
|
||
|
|
if err == nil {
|
||
|
|
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||
|
|
reqParams = sanitizeParams(bodyBytes)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bw := newBodyWriter(c.Writer)
|
||
|
|
c.Writer = bw
|
||
|
|
|
||
|
|
c.Next()
|
||
|
|
|
||
|
|
var userIDPtr *int64
|
||
|
|
if uid, exists := c.Get("user_id"); exists {
|
||
|
|
if id, ok := uid.(int64); ok {
|
||
|
|
userID := id
|
||
|
|
userIDPtr = &userID
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
logEntry := &domain.OperationLog{
|
||
|
|
UserID: userIDPtr,
|
||
|
|
OperationType: methodToType(method),
|
||
|
|
OperationName: c.FullPath(),
|
||
|
|
RequestMethod: method,
|
||
|
|
RequestPath: c.Request.URL.Path,
|
||
|
|
RequestParams: reqParams,
|
||
|
|
ResponseStatus: bw.statusCode,
|
||
|
|
IP: c.ClientIP(),
|
||
|
|
UserAgent: c.Request.UserAgent(),
|
||
|
|
}
|
||
|
|
|
||
|
|
go func(entry *domain.OperationLog) {
|
||
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||
|
|
defer cancel()
|
||
|
|
_ = m.repo.Create(ctx, entry)
|
||
|
|
}(logEntry)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func methodToType(method string) string {
|
||
|
|
switch method {
|
||
|
|
case "POST":
|
||
|
|
return "CREATE"
|
||
|
|
case "PUT", "PATCH":
|
||
|
|
return "UPDATE"
|
||
|
|
case "DELETE":
|
||
|
|
return "DELETE"
|
||
|
|
default:
|
||
|
|
return "OTHER"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func sanitizeParams(data []byte) string {
|
||
|
|
var payload map[string]interface{}
|
||
|
|
if err := json.Unmarshal(data, &payload); err != nil {
|
||
|
|
if len(data) > 500 {
|
||
|
|
return string(data[:500]) + "..."
|
||
|
|
}
|
||
|
|
return string(data)
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, field := range []string{"password", "old_password", "new_password", "confirm_password", "secret", "token"} {
|
||
|
|
if _, ok := payload[field]; ok {
|
||
|
|
payload[field] = "***"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
result, err := json.Marshal(payload)
|
||
|
|
if err != nil {
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
return string(result)
|
||
|
|
}
|