package handler import ( "errors" "io" "log/slog" "strconv" "time" "kra/internal/biz" "kra/internal/server/httpx" "kra/internal/service" "kra/internal/service/dto" "github.com/gin-gonic/gin" ) type Audit struct { service *service.AuditService recorder *service.AuditRecorder logs *service.LogViewerService logger *slog.Logger } func NewAudit(service *service.AuditService, recorder *service.AuditRecorder, logs *service.LogViewerService, logger *slog.Logger) *Audit { return &Audit{service: service, recorder: recorder, logs: logs, logger: logger} } func page(c *gin.Context) (int, int) { value, _ := strconv.Atoi(c.Query("page")) size, _ := strconv.Atoi(c.Query("pageSize")) return value, size } func queryUintValue(c *gin.Context, key string) (uint, bool, error) { raw, ok := c.GetQuery(key) if !ok || raw == "" { return 0, false, nil } value, err := strconv.ParseUint(raw, 10, 0) return uint(value), true, err } func IDsFromQuery(c *gin.Context) []uint { values := c.QueryArray("IDs[]") if len(values) == 0 { values = c.QueryArray("ids[]") } ids := make([]uint, 0, len(values)) for _, value := range values { id, _ := strconv.ParseUint(value, 10, 64) if id > 0 { ids = append(ids, uint(id)) } } return ids } // auditID accepts both DELETE encodings supported by the administration API: // the identifier may be carried in the query string or in a JSON body. func auditID(c *gin.Context) (uint, error) { if raw := c.Query("ID"); raw != "" { value, err := strconv.ParseUint(raw, 10, 64) return uint(value), err } var request dto.IDRequest if err := c.ShouldBindJSON(&request); err != nil && !errors.Is(err, io.EOF) { return 0, err } return request.ID, nil } // auditIDs mirrors auditID for bulk deletes and supports both query-array and // JSON-body encodings used by compatible administration pages. func auditIDs(c *gin.Context) ([]uint, error) { if ids := IDsFromQuery(c); len(ids) > 0 { return ids, nil } var request dto.IDsRequest if err := c.ShouldBindJSON(&request); err != nil && !errors.Is(err, io.EOF) { return nil, err } return request.IDs, nil } func (h *Audit) Operations(c *gin.Context) { var req dto.OperationRecordSearchRequest if err := c.ShouldBindQuery(&req); err != nil { httpx.Fail(c, err.Error()) return } items, total, err := h.service.OperationsFilter(c.Request.Context(), req.Page, req.PageSize, req.Path, req.Method, req.Status) if err != nil { httpx.Fail(c, "获取失败") return } httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") } func (h *Audit) Operation(c *gin.Context) { var req dto.AuditIDQuery if err := c.ShouldBindQuery(&req); err != nil { httpx.Fail(c, err.Error()) return } if req.ID == 0 { httpx.Fail(c, "ID值不能为空") return } item, err := h.service.Operation(c.Request.Context(), req.ID) if err != nil { httpx.Fail(c, "查询失败") return } httpx.Write(c, httpx.CodeSuccess, gin.H{"reSysOperationRecord": item}, "查询成功") } func (h *Audit) DeleteOperation(c *gin.Context) { id, err := auditID(c) if err != nil { httpx.Fail(c, err.Error()) return } if id == 0 { httpx.Fail(c, "删除失败") return } if err := h.service.DeleteOperations(c.Request.Context(), []uint{id}); err != nil { httpx.Fail(c, "删除失败") return } httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *Audit) DeleteOperations(c *gin.Context) { ids, err := auditIDs(c) if err != nil { httpx.Fail(c, err.Error()) return } if err := h.service.DeleteOperations(c.Request.Context(), ids); err != nil { httpx.Fail(c, "批量删除失败") return } httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") } func (h *Audit) Logins(c *gin.Context) { var req dto.LoginLogSearchRequest if err := c.ShouldBindQuery(&req); err != nil { httpx.Fail(c, err.Error()) return } items, total, err := h.service.LoginsFilter(c.Request.Context(), req.Page, req.PageSize, req.Username, req.Status) if err != nil { httpx.Fail(c, "获取失败") return } httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") } func (h *Audit) Login(c *gin.Context) { var req dto.AuditIDQuery if err := c.ShouldBindQuery(&req); err != nil { httpx.Fail(c, err.Error()) return } item, err := h.service.Login(c.Request.Context(), req.ID) if err != nil { httpx.Fail(c, "查询失败") return } httpx.Write(c, httpx.CodeSuccess, item, "查询成功") } func (h *Audit) DeleteLogin(c *gin.Context) { id, err := auditID(c) if err != nil { httpx.Fail(c, err.Error()) return } if id == 0 { httpx.Fail(c, "删除失败") return } if err := h.service.DeleteLogins(c.Request.Context(), []uint{id}); err != nil { httpx.Fail(c, "删除失败") return } httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *Audit) DeleteLogins(c *gin.Context) { ids, err := auditIDs(c) if err != nil { httpx.Fail(c, err.Error()) return } if err := h.service.DeleteLogins(c.Request.Context(), ids); err != nil { httpx.Fail(c, "批量删除失败") return } httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") } func (h *Audit) DataAccess(c *gin.Context) { var req dto.DataAccessListRequest if err := c.ShouldBindJSON(&req); err != nil { httpx.Fail(c, err.Error()) return } items, total, err := h.service.DataAccessRequest(c.Request.Context(), &req) if err != nil { httpx.Fail(c, "获取失败:"+err.Error()) return } httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") } func (h *Audit) DeleteDataAccess(c *gin.Context) { var req dto.IDsRequest if err := c.ShouldBindJSON(&req); err != nil { httpx.Fail(c, err.Error()) return } if err := h.service.DeleteDataAccess(c.Request.Context(), req.IDs); err != nil { httpx.Fail(c, "删除失败:"+err.Error()) return } httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *Audit) LogDates(c *gin.Context) { month := c.Query("month") // Month uses `binding:"required"`; an omitted or empty query is a // generic parameter error, not the service's format-specific error. if month == "" { httpx.Fail(c, "参数错误") return } data, err := h.logs.LogDates(c.Request.Context(), month) if err != nil { failLogViewer(c, err, h.logger) return } httpx.Write(c, httpx.CodeSuccess, data, "获取成功") } func (h *Audit) LogFiles(c *gin.Context) { date := c.Query("date") if date == "" { httpx.Fail(c, "参数错误") return } data, err := h.logs.LogFiles(c.Request.Context(), date) if err != nil { failLogViewer(c, err, h.logger) return } httpx.Write(c, httpx.CodeSuccess, data, "获取成功") } func (h *Audit) LogContent(c *gin.Context) { date, path := c.Query("date"), c.Query("path") if date == "" || path == "" { httpx.Fail(c, "参数错误") return } var cursor *int64 if raw := c.Query("cursor"); raw != "" { value, err := strconv.ParseInt(raw, 10, 64) if err != nil { httpx.Fail(c, "参数错误") return } cursor = &value } // Preserve negative cursors for the usecase to classify with the compatible // "日志文件路径不合法" error rather than rejecting them as binding errors. data, err := h.logs.LogContent(c.Request.Context(), date, path, cursor) if err != nil { failLogViewer(c, err, h.logger) return } httpx.Write(c, httpx.CodeSuccess, data, "获取成功") } func failLogViewer(c *gin.Context, err error, logger *slog.Logger) { if logger != nil { logger.ErrorContext(c.Request.Context(), "日志查看失败", "mod", "log-viewer", "error", err) } message := "读取日志失败" switch { case errors.Is(err, biz.ErrInvalidLogMonth): message = "日志月份格式不正确" case errors.Is(err, biz.ErrInvalidLogDate): message = "日志日期格式不正确" case errors.Is(err, biz.ErrInvalidLogPath): message = "日志文件路径不合法" case errors.Is(err, biz.ErrLogFileNotFound): message = "日志文件不存在" case errors.Is(err, biz.ErrLogFileUnreadable): message = "日志文件不可读取" case errors.Is(err, biz.ErrLogRootUnavailable): message = "日志目录不可读取" } httpx.Fail(c, message) } func (h *Audit) DeleteError(c *gin.Context) { id, err := auditID(c) if err != nil { httpx.Fail(c, err.Error()) return } if err := h.service.DeleteErrors(c.Request.Context(), []uint{id}); err != nil { httpx.Fail(c, "删除失败:"+err.Error()) return } httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") } func (h *Audit) DeleteErrors(c *gin.Context) { ids, err := auditIDs(c) if err != nil { httpx.Fail(c, err.Error()) return } if err := h.service.DeleteErrors(c.Request.Context(), ids); err != nil { httpx.Fail(c, "批量删除失败:"+err.Error()) return } httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") } func (h *Audit) UpdateError(c *gin.Context) { var req dto.ErrorRecordRequest if err := c.ShouldBindJSON(&req); err != nil { httpx.Fail(c, err.Error()) return } if err := h.service.UpdateErrorRequest(c.Request.Context(), &req); err != nil { httpx.Fail(c, "更新失败:"+err.Error()) return } httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") } func (h *Audit) Error(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) item, err := h.service.Error(c.Request.Context(), uint(id)) if err != nil { httpx.Fail(c, "查询失败:"+err.Error()) return } httpx.OKWithData(c, item) } func (h *Audit) Errors(c *gin.Context) { p, size := page(c) createdAtRange := make([]time.Time, 0, 2) for _, raw := range c.QueryArray("createdAtRange[]") { value, parseErr := time.Parse(time.RFC3339, raw) if parseErr != nil { httpx.Fail(c, parseErr.Error()) return } createdAtRange = append(createdAtRange, value) } items, total, err := h.service.ErrorsFilter(c.Request.Context(), p, size, c.Query("form"), c.Query("info"), createdAtRange) if err != nil { httpx.Fail(c, "获取失败:"+err.Error()) return } httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功") } func (h *Audit) CreateError(c *gin.Context) { var req dto.ErrorRecordRequest if err := c.ShouldBindJSON(&req); err != nil { httpx.Fail(c, err.Error()) return } if err := h.recorder.CreateErrorRequest(c.Request.Context(), &req); err != nil { httpx.Fail(c, "创建失败:"+err.Error()) return } httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功") }