2026-05-12 09:53:20 +08:00

96 lines
2.2 KiB
Go

package middleware
import (
"fmt"
"net/http"
"strings"
"time"
"hyapp-admin-server/internal/model"
"github.com/gin-gonic/gin"
)
const contextAuditLogged = "auditLogged"
type AuditStore interface {
CreateOperationLog(log model.OperationLog) error
}
func Audit(store AuditStore) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
if store == nil || c.GetBool(contextAuditLogged) || !shouldAudit(c.Request.Method) {
return
}
status := "success"
if c.Writer.Status() >= http.StatusBadRequest {
status = "failed"
}
path := c.FullPath()
if path == "" {
path = c.Request.URL.Path
}
_ = store.CreateOperationLog(model.OperationLog{
UserID: CurrentUserID(c),
RequestID: CurrentRequestID(c),
Username: CurrentUsername(c),
Action: strings.ToLower(c.Request.Method) + " " + path,
Resource: auditResource(path),
ResourceID: auditResourceID(c),
Method: c.Request.Method,
Path: c.Request.URL.Path,
IP: c.ClientIP(),
UserAgent: c.Request.UserAgent(),
Status: status,
HTTPStatus: c.Writer.Status(),
RiskLevel: auditRisk(c.Request.Method, path),
LatencyMS: time.Since(start).Milliseconds(),
Detail: fmt.Sprintf("http_status=%d", c.Writer.Status()),
})
}
}
func auditResourceID(c *gin.Context) string {
if id := c.Param("id"); id != "" {
return id
}
return ""
}
func auditRisk(method string, path string) string {
if method == http.MethodDelete || strings.Contains(path, "reset-password") || strings.Contains(path, "permissions") || strings.Contains(path, "data-scopes") {
return "high"
}
if shouldAudit(method) {
return "normal"
}
return "low"
}
func MarkAuditLogged(c *gin.Context) {
c.Set(contextAuditLogged, true)
}
func shouldAudit(method string) bool {
switch method {
case http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete:
return true
default:
return false
}
}
func auditResource(path string) string {
parts := strings.SplitSeq(strings.Trim(path, "/"), "/")
for part := range parts {
if part != "" && !strings.HasPrefix(part, ":") && part != "api" && part != "v1" {
return part
}
}
return "unknown"
}