任务执行1-19
This commit is contained in:
219
internal/logic/audit/audit.go
Normal file
219
internal/logic/audit/audit.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package audit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.apinb.com/ops/logs/internal/impl"
|
||||
"git.apinb.com/ops/logs/internal/models"
|
||||
)
|
||||
|
||||
const (
|
||||
RiskNormal = "normal"
|
||||
RiskDangerous = "dangerous"
|
||||
|
||||
ApprovalPending = "pending"
|
||||
ApprovalApproved = "approved"
|
||||
ApprovalRejected = "rejected"
|
||||
)
|
||||
|
||||
type Record struct {
|
||||
TraceID string `json:"trace_id,omitempty"`
|
||||
SourceService string `json:"source_service,omitempty"`
|
||||
ActorID string `json:"actor_id,omitempty"`
|
||||
ActorName string `json:"actor_name,omitempty"`
|
||||
Action string `json:"action,omitempty"`
|
||||
ObjectType string `json:"object_type,omitempty"`
|
||||
ObjectID string `json:"object_id,omitempty"`
|
||||
OperationRisk string `json:"operation_risk,omitempty"`
|
||||
ApprovalID string `json:"approval_id,omitempty"`
|
||||
RequestMethod string `json:"request_method,omitempty"`
|
||||
RequestPath string `json:"request_path,omitempty"`
|
||||
ClientIP string `json:"client_ip,omitempty"`
|
||||
BeforeJSON string `json:"before_json,omitempty"`
|
||||
AfterJSON string `json:"after_json,omitempty"`
|
||||
Result string `json:"result,omitempty"`
|
||||
ErrorMessage string `json:"error_message,omitempty"`
|
||||
}
|
||||
|
||||
type ApprovalRequest struct {
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
SourceService string `json:"source_service,omitempty"`
|
||||
Action string `json:"action,omitempty"`
|
||||
ObjectType string `json:"object_type,omitempty"`
|
||||
ObjectID string `json:"object_id,omitempty"`
|
||||
RequesterID string `json:"requester_id,omitempty"`
|
||||
RequesterName string `json:"requester_name,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
BeforeJSON string `json:"before_json,omitempty"`
|
||||
AfterJSON string `json:"after_json,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
ReviewerID string `json:"reviewer_id,omitempty"`
|
||||
ReviewerName string `json:"reviewer_name,omitempty"`
|
||||
ReviewComment string `json:"review_comment,omitempty"`
|
||||
ReviewedAt *time.Time `json:"reviewed_at,omitempty"`
|
||||
}
|
||||
|
||||
func NormalizeRecord(record Record) Record {
|
||||
record.TraceID = strings.TrimSpace(record.TraceID)
|
||||
record.SourceService = strings.TrimSpace(record.SourceService)
|
||||
record.ActorID = strings.TrimSpace(record.ActorID)
|
||||
record.ActorName = strings.TrimSpace(record.ActorName)
|
||||
record.Action = strings.TrimSpace(record.Action)
|
||||
record.ObjectType = strings.TrimSpace(record.ObjectType)
|
||||
record.ObjectID = strings.TrimSpace(record.ObjectID)
|
||||
record.OperationRisk = strings.TrimSpace(strings.ToLower(record.OperationRisk))
|
||||
record.ApprovalID = strings.TrimSpace(record.ApprovalID)
|
||||
record.RequestMethod = strings.TrimSpace(strings.ToUpper(record.RequestMethod))
|
||||
record.RequestPath = strings.TrimSpace(record.RequestPath)
|
||||
record.ClientIP = strings.TrimSpace(record.ClientIP)
|
||||
record.Result = strings.TrimSpace(record.Result)
|
||||
if record.Result == "" {
|
||||
record.Result = "success"
|
||||
}
|
||||
if record.OperationRisk == "" {
|
||||
if IsDangerousOperation(record.Action, record.ObjectType) {
|
||||
record.OperationRisk = RiskDangerous
|
||||
} else {
|
||||
record.OperationRisk = RiskNormal
|
||||
}
|
||||
}
|
||||
return record
|
||||
}
|
||||
|
||||
func ValidateRecord(record Record) error {
|
||||
record = NormalizeRecord(record)
|
||||
if record.SourceService == "" {
|
||||
return errors.New("source_service is required")
|
||||
}
|
||||
if record.ActorID == "" {
|
||||
return errors.New("actor_id is required")
|
||||
}
|
||||
if record.Action == "" {
|
||||
return errors.New("action is required")
|
||||
}
|
||||
if record.ObjectType == "" {
|
||||
return errors.New("object_type is required")
|
||||
}
|
||||
if record.ObjectID == "" {
|
||||
return errors.New("object_id is required")
|
||||
}
|
||||
if record.OperationRisk != RiskNormal && record.OperationRisk != RiskDangerous {
|
||||
return errors.New("operation_risk must be normal or dangerous")
|
||||
}
|
||||
if record.OperationRisk == RiskDangerous && record.ApprovalID == "" {
|
||||
return errors.New("approval_id is required for dangerous operation")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsDangerousOperation(action, objectType string) bool {
|
||||
key := strings.ToLower(strings.TrimSpace(action) + " " + strings.TrimSpace(objectType))
|
||||
dangerWords := []string{
|
||||
"notification_policy",
|
||||
"notification policy",
|
||||
"silence_policy",
|
||||
"suppression",
|
||||
"escalation_policy",
|
||||
"automation_script",
|
||||
"script.execute",
|
||||
"script.rollback",
|
||||
}
|
||||
for _, word := range dangerWords {
|
||||
if strings.Contains(key, word) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func SaveRecord(record Record) (models.AuditLog, error) {
|
||||
record = NormalizeRecord(record)
|
||||
if err := ValidateRecord(record); err != nil {
|
||||
return models.AuditLog{}, err
|
||||
}
|
||||
row := models.AuditLog{
|
||||
TraceID: record.TraceID,
|
||||
SourceService: record.SourceService,
|
||||
ActorID: record.ActorID,
|
||||
ActorName: record.ActorName,
|
||||
Action: record.Action,
|
||||
ObjectType: record.ObjectType,
|
||||
ObjectID: record.ObjectID,
|
||||
OperationRisk: record.OperationRisk,
|
||||
ApprovalID: record.ApprovalID,
|
||||
RequestMethod: record.RequestMethod,
|
||||
RequestPath: record.RequestPath,
|
||||
ClientIP: record.ClientIP,
|
||||
BeforeJSON: record.BeforeJSON,
|
||||
AfterJSON: record.AfterJSON,
|
||||
Result: record.Result,
|
||||
ErrorMessage: record.ErrorMessage,
|
||||
}
|
||||
if err := impl.DBService.Create(&row).Error; err != nil {
|
||||
return models.AuditLog{}, err
|
||||
}
|
||||
return row, nil
|
||||
}
|
||||
|
||||
func NormalizeApproval(req ApprovalRequest) ApprovalRequest {
|
||||
req.RequestID = strings.TrimSpace(req.RequestID)
|
||||
req.SourceService = strings.TrimSpace(req.SourceService)
|
||||
req.Action = strings.TrimSpace(req.Action)
|
||||
req.ObjectType = strings.TrimSpace(req.ObjectType)
|
||||
req.ObjectID = strings.TrimSpace(req.ObjectID)
|
||||
req.RequesterID = strings.TrimSpace(req.RequesterID)
|
||||
req.RequesterName = strings.TrimSpace(req.RequesterName)
|
||||
req.Status = strings.TrimSpace(strings.ToLower(req.Status))
|
||||
req.ReviewerID = strings.TrimSpace(req.ReviewerID)
|
||||
req.ReviewerName = strings.TrimSpace(req.ReviewerName)
|
||||
if req.Status == "" {
|
||||
req.Status = ApprovalPending
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
func ValidateApprovalRequest(req ApprovalRequest) error {
|
||||
req = NormalizeApproval(req)
|
||||
if req.SourceService == "" {
|
||||
return errors.New("source_service is required")
|
||||
}
|
||||
if req.Action == "" {
|
||||
return errors.New("action is required")
|
||||
}
|
||||
if req.ObjectType == "" {
|
||||
return errors.New("object_type is required")
|
||||
}
|
||||
if req.ObjectID == "" {
|
||||
return errors.New("object_id is required")
|
||||
}
|
||||
if req.RequesterID == "" {
|
||||
return errors.New("requester_id is required")
|
||||
}
|
||||
if !IsDangerousOperation(req.Action, req.ObjectType) {
|
||||
return errors.New("operation is not classified as dangerous")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Transition(req ApprovalRequest, nextStatus, reviewerID, comment string) (ApprovalRequest, error) {
|
||||
req = NormalizeApproval(req)
|
||||
nextStatus = strings.TrimSpace(strings.ToLower(nextStatus))
|
||||
reviewerID = strings.TrimSpace(reviewerID)
|
||||
if req.Status != ApprovalPending {
|
||||
return ApprovalRequest{}, errors.New("only pending approval can be reviewed")
|
||||
}
|
||||
if nextStatus != ApprovalApproved && nextStatus != ApprovalRejected {
|
||||
return ApprovalRequest{}, errors.New("next status must be approved or rejected")
|
||||
}
|
||||
if reviewerID == "" {
|
||||
return ApprovalRequest{}, errors.New("reviewer_id is required")
|
||||
}
|
||||
now := time.Now()
|
||||
req.Status = nextStatus
|
||||
req.ReviewerID = reviewerID
|
||||
req.ReviewComment = strings.TrimSpace(comment)
|
||||
req.ReviewedAt = &now
|
||||
return req, nil
|
||||
}
|
||||
61
internal/logic/audit/audit_test.go
Normal file
61
internal/logic/audit/audit_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package audit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateRecordRequiresDangerousOperationsToCarryReviewID(t *testing.T) {
|
||||
record := Record{
|
||||
SourceService: "alert",
|
||||
ActorID: "u-1",
|
||||
Action: "policy.update",
|
||||
ObjectType: "notification_policy",
|
||||
ObjectID: "np-1",
|
||||
OperationRisk: RiskDangerous,
|
||||
}
|
||||
|
||||
if err := ValidateRecord(record); err == nil {
|
||||
t.Fatal("expected dangerous operation without approval id to fail")
|
||||
}
|
||||
|
||||
record.ApprovalID = "apr-1"
|
||||
if err := ValidateRecord(record); err != nil {
|
||||
t.Fatalf("expected valid dangerous audit record, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRecordClassifiesDangerousActions(t *testing.T) {
|
||||
record := NormalizeRecord(Record{
|
||||
SourceService: " alert ",
|
||||
Action: "notification_policy.update",
|
||||
ObjectType: " notification_policy ",
|
||||
ObjectID: " np-1 ",
|
||||
ActorID: " u-1 ",
|
||||
})
|
||||
|
||||
if record.SourceService != "alert" || record.ObjectType != "notification_policy" || record.ObjectID != "np-1" {
|
||||
t.Fatalf("record was not normalized: %#v", record)
|
||||
}
|
||||
if record.OperationRisk != RiskDangerous {
|
||||
t.Fatalf("notification policy changes must be dangerous, got %q", record.OperationRisk)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApprovalTransitionAllowsApproveOnlyFromPending(t *testing.T) {
|
||||
req := ApprovalRequest{Status: ApprovalPending}
|
||||
|
||||
approved, err := Transition(req, ApprovalApproved, "reviewer-1", "ok")
|
||||
if err != nil {
|
||||
t.Fatalf("expected pending approval to approve: %v", err)
|
||||
}
|
||||
if approved.Status != ApprovalApproved {
|
||||
t.Fatalf("unexpected status: %s", approved.Status)
|
||||
}
|
||||
if approved.ReviewerID != "reviewer-1" || approved.ReviewComment != "ok" {
|
||||
t.Fatalf("review metadata not stored: %#v", approved)
|
||||
}
|
||||
|
||||
if _, err := Transition(approved, ApprovalRejected, "reviewer-2", "late"); err == nil {
|
||||
t.Fatal("expected approved request to reject further transition")
|
||||
}
|
||||
}
|
||||
207
internal/logic/audit/controller.go
Normal file
207
internal/logic/audit/controller.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package audit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.apinb.com/bsm-sdk/core/infra"
|
||||
"git.apinb.com/ops/logs/internal/impl"
|
||||
"git.apinb.com/ops/logs/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func ListAuditLogs(ctx *gin.Context) {
|
||||
page, size := pageAndSize(ctx.DefaultQuery("page", "1"), ctx.DefaultQuery("page_size", "50"))
|
||||
q := impl.DBService.Model(&models.AuditLog{})
|
||||
if v := strings.TrimSpace(ctx.Query("source_service")); v != "" {
|
||||
q = q.Where("source_service = ?", v)
|
||||
}
|
||||
if v := strings.TrimSpace(ctx.Query("actor_id")); v != "" {
|
||||
q = q.Where("actor_id = ?", v)
|
||||
}
|
||||
if v := strings.TrimSpace(ctx.Query("action")); v != "" {
|
||||
q = q.Where("action = ?", v)
|
||||
}
|
||||
if v := strings.TrimSpace(ctx.Query("object_type")); v != "" {
|
||||
q = q.Where("object_type = ?", v)
|
||||
}
|
||||
if v := strings.TrimSpace(ctx.Query("object_id")); v != "" {
|
||||
q = q.Where("object_id = ?", v)
|
||||
}
|
||||
if v := strings.TrimSpace(ctx.Query("operation_risk")); v != "" {
|
||||
q = q.Where("operation_risk = ?", v)
|
||||
}
|
||||
if v := strings.TrimSpace(ctx.Query("result")); v != "" {
|
||||
q = q.Where("result = ?", v)
|
||||
}
|
||||
var total int64
|
||||
_ = q.Count(&total).Error
|
||||
var rows []models.AuditLog
|
||||
if err := q.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&rows).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"total": total, "page": page, "page_size": size, "items": rows})
|
||||
}
|
||||
|
||||
func CreateAuditLog(ctx *gin.Context) {
|
||||
var req Record
|
||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
req.ClientIP = firstNonEmpty(req.ClientIP, ctx.ClientIP())
|
||||
req.RequestMethod = firstNonEmpty(req.RequestMethod, ctx.Request.Method)
|
||||
req.RequestPath = firstNonEmpty(req.RequestPath, ctx.FullPath())
|
||||
row, err := SaveRecord(req)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, row)
|
||||
}
|
||||
|
||||
func ListApprovals(ctx *gin.Context) {
|
||||
page, size := pageAndSize(ctx.DefaultQuery("page", "1"), ctx.DefaultQuery("page_size", "50"))
|
||||
q := impl.DBService.Model(&models.DangerousOperationApproval{})
|
||||
if v := strings.TrimSpace(ctx.Query("source_service")); v != "" {
|
||||
q = q.Where("source_service = ?", v)
|
||||
}
|
||||
if v := strings.TrimSpace(ctx.Query("status")); v != "" {
|
||||
q = q.Where("status = ?", v)
|
||||
}
|
||||
if v := strings.TrimSpace(ctx.Query("requester_id")); v != "" {
|
||||
q = q.Where("requester_id = ?", v)
|
||||
}
|
||||
if v := strings.TrimSpace(ctx.Query("object_type")); v != "" {
|
||||
q = q.Where("object_type = ?", v)
|
||||
}
|
||||
var total int64
|
||||
_ = q.Count(&total).Error
|
||||
var rows []models.DangerousOperationApproval
|
||||
if err := q.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&rows).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"total": total, "page": page, "page_size": size, "items": rows})
|
||||
}
|
||||
|
||||
func CreateApproval(ctx *gin.Context) {
|
||||
var req ApprovalRequest
|
||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
req = NormalizeApproval(req)
|
||||
if req.RequestID == "" {
|
||||
req.RequestID = fmt.Sprintf("apr-%d", time.Now().UnixNano())
|
||||
}
|
||||
if err := ValidateApprovalRequest(req); err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
row := models.DangerousOperationApproval{
|
||||
RequestID: req.RequestID,
|
||||
SourceService: req.SourceService,
|
||||
Action: req.Action,
|
||||
ObjectType: req.ObjectType,
|
||||
ObjectID: req.ObjectID,
|
||||
RequesterID: req.RequesterID,
|
||||
RequesterName: req.RequesterName,
|
||||
Reason: req.Reason,
|
||||
BeforeJSON: req.BeforeJSON,
|
||||
AfterJSON: req.AfterJSON,
|
||||
Status: ApprovalPending,
|
||||
}
|
||||
if err := impl.DBService.Create(&row).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, row)
|
||||
}
|
||||
|
||||
func ApproveApproval(ctx *gin.Context) {
|
||||
reviewApproval(ctx, ApprovalApproved)
|
||||
}
|
||||
|
||||
func RejectApproval(ctx *gin.Context) {
|
||||
reviewApproval(ctx, ApprovalRejected)
|
||||
}
|
||||
|
||||
func reviewApproval(ctx *gin.Context, next string) {
|
||||
id, err := parseUintParam(ctx, "id")
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
var body struct {
|
||||
ReviewerID string `json:"reviewer_id"`
|
||||
ReviewerName string `json:"reviewer_name"`
|
||||
ReviewComment string `json:"review_comment"`
|
||||
}
|
||||
if err := ctx.ShouldBindJSON(&body); err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
var row models.DangerousOperationApproval
|
||||
if err := impl.DBService.First(&row, id).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
req := ApprovalRequest{
|
||||
RequestID: row.RequestID,
|
||||
SourceService: row.SourceService,
|
||||
Action: row.Action,
|
||||
ObjectType: row.ObjectType,
|
||||
ObjectID: row.ObjectID,
|
||||
RequesterID: row.RequesterID,
|
||||
Status: row.Status,
|
||||
}
|
||||
nextReq, err := Transition(req, next, body.ReviewerID, body.ReviewComment)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
row.Status = nextReq.Status
|
||||
row.ReviewerID = nextReq.ReviewerID
|
||||
row.ReviewerName = strings.TrimSpace(body.ReviewerName)
|
||||
row.ReviewComment = nextReq.ReviewComment
|
||||
row.ReviewedAt = nextReq.ReviewedAt
|
||||
if err := impl.DBService.Save(&row).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, row)
|
||||
}
|
||||
|
||||
func parseUintParam(ctx *gin.Context, name string) (uint, error) {
|
||||
v, err := strconv.ParseUint(ctx.Param(name), 10, 32)
|
||||
if err != nil || v == 0 {
|
||||
return 0, errors.New("invalid id")
|
||||
}
|
||||
return uint(v), nil
|
||||
}
|
||||
|
||||
func pageAndSize(pageText, sizeText string) (int, int) {
|
||||
page, _ := strconv.Atoi(pageText)
|
||||
size, _ := strconv.Atoi(sizeText)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 || size > 500 {
|
||||
size = 50
|
||||
}
|
||||
return page, size
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
66
internal/logic/audit/middleware.go
Normal file
66
internal/logic/audit/middleware.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package audit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ActorResolver func(*gin.Context) (id string, name string)
|
||||
|
||||
func Middleware(sourceService string, resolveActor ActorResolver) gin.HandlerFunc {
|
||||
sourceService = strings.TrimSpace(sourceService)
|
||||
return func(ctx *gin.Context) {
|
||||
if ctx.Request.Method == "GET" || ctx.Request.Method == "HEAD" || ctx.Request.Method == "OPTIONS" {
|
||||
ctx.Next()
|
||||
return
|
||||
}
|
||||
|
||||
var body []byte
|
||||
if ctx.Request.Body != nil {
|
||||
body, _ = io.ReadAll(ctx.Request.Body)
|
||||
ctx.Request.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
ctx.Next()
|
||||
|
||||
actorID, actorName := "", ""
|
||||
if resolveActor != nil {
|
||||
actorID, actorName = resolveActor(ctx)
|
||||
}
|
||||
if actorID == "" {
|
||||
actorID = firstNonEmpty(ctx.GetHeader("X-User-Id"), ctx.GetHeader("X-Actor-Id"), "unknown")
|
||||
}
|
||||
if actorName == "" {
|
||||
actorName = firstNonEmpty(ctx.GetHeader("X-User-Name"), ctx.GetHeader("X-Actor-Name"))
|
||||
}
|
||||
result := "success"
|
||||
if len(ctx.Errors) > 0 || ctx.Writer.Status() >= 400 {
|
||||
result = "failed"
|
||||
}
|
||||
_, _ = SaveRecord(Record{
|
||||
TraceID: firstNonEmpty(ctx.GetHeader("X-Trace-Id"), ctx.GetHeader("Request-Id")),
|
||||
SourceService: sourceService,
|
||||
ActorID: actorID,
|
||||
ActorName: actorName,
|
||||
Action: ctx.Request.Method + " " + ctx.FullPath(),
|
||||
ObjectType: routeObjectType(ctx.FullPath()),
|
||||
ObjectID: firstNonEmpty(ctx.Param("id"), ctx.Query("id"), ctx.FullPath()),
|
||||
RequestMethod: ctx.Request.Method,
|
||||
RequestPath: ctx.FullPath(),
|
||||
ClientIP: ctx.ClientIP(),
|
||||
AfterJSON: string(body),
|
||||
Result: result,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func routeObjectType(path string) string {
|
||||
path = strings.Trim(path, "/")
|
||||
if path == "" {
|
||||
return "unknown"
|
||||
}
|
||||
parts := strings.Split(path, "/")
|
||||
return parts[len(parts)-1]
|
||||
}
|
||||
Reference in New Issue
Block a user