package shared import ( "hyapp-admin-server/internal/middleware" "hyapp-admin-server/internal/model" "github.com/gin-gonic/gin" ) type OperationEvent struct { RequestID string UserID uint Username string Action string Resource string ResourceID string Method string Path string IP string UserAgent string Status string HTTPStatus int RiskLevel string LatencyMS int64 Detail string } type OperationLogger interface { CreateOperationLog(log model.OperationLog) error LogOperation(event OperationEvent) error } func OperationLog(c *gin.Context, logger OperationLogger, action string, resource string, status string, detail string) { OperationLogWithResourceID(c, logger, action, resource, c.Param("id"), status, detail) } // OperationLogWithResourceID is the explicit audit entry point for routes whose target id // does not use the shared ":id" path parameter name. func OperationLogWithResourceID(c *gin.Context, logger OperationLogger, action string, resource string, resourceID string, status string, detail string) { if logger == nil { return } // MarkAuditLogged keeps middleware from creating a second generic row for this write. middleware.MarkAuditLogged(c) _ = logger.LogOperation(operationEvent(c, action, resource, resourceID, status, detail)) } // OperationLogWithResourceIDStrict 是 fail-closed 版本:审计写失败时返回错误、且不抑制 // middleware 的兜底审计行。用于“操作本身在别处不留任何痕迹”的高危动作(如重签用户 token)—— // 这类动作宁可失败,也不能在没有任何审计记录的情况下成功。 func OperationLogWithResourceIDStrict(c *gin.Context, logger OperationLogger, action string, resource string, resourceID string, status string, detail string) error { if logger == nil { return nil } if err := logger.LogOperation(operationEvent(c, action, resource, resourceID, status, detail)); err != nil { return err } middleware.MarkAuditLogged(c) return nil } func operationEvent(c *gin.Context, action string, resource string, resourceID string, status string, detail string) OperationEvent { return OperationEvent{ RequestID: middleware.CurrentRequestID(c), UserID: middleware.CurrentUserID(c), Username: middleware.CurrentUsername(c), Action: action, Resource: resource, ResourceID: resourceID, Method: c.Request.Method, Path: c.Request.URL.Path, IP: c.ClientIP(), UserAgent: c.Request.UserAgent(), Status: status, HTTPStatus: c.Writer.Status(), Detail: detail, } }