package api import ( "net/http" "strconv" "time" "github.com/gin-gonic/gin" "ws_usernode/internal/config" "ws_usernode/internal/service" ) // AuditHandler 审计接口(admin):查询 + 手动 CSV 导出(PLAN F6)。 type AuditHandler struct { svc *service.AuditService cfg *config.Config } // NewAuditHandler 创建审计 handler。 func NewAuditHandler(svc *service.AuditService, cfg *config.Config) *AuditHandler { return &AuditHandler{svc: svc, cfg: cfg} } // parseTime 解析可选的时间参数(RFC3339)。 func parseTime(v string) (*time.Time, error) { if v == "" { return nil, nil } t, err := time.Parse(time.RFC3339, v) if err != nil { return nil, err } return &t, nil } // List GET /audit —— 审计查询(操作者/动作/资源/时间范围/分页)。 func (h *AuditHandler) List(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) since, err := parseTime(c.Query("since")) if err != nil { fail(c, http.StatusBadRequest, "since 需为 RFC3339 时间") return } until, err := parseTime(c.Query("until")) if err != nil { fail(c, http.StatusBadRequest, "until 需为 RFC3339 时间") return } rows, total, err := h.svc.Query(c.Request.Context(), service.AuditFilter{ ActorName: c.Query("actor"), Action: c.Query("action"), ResourceType: c.Query("resource_type"), ResourceID: c.Query("resource_id"), Result: c.Query("result"), Since: since, Until: until, Page: page, PageSize: pageSize, }) if err != nil { fail(c, http.StatusInternalServerError, err.Error()) return } ok(c, gin.H{"total": total, "items": rows}) } // Export GET /audit/export —— 手动 CSV 导出(可选时间范围)。 func (h *AuditHandler) Export(c *gin.Context) { since, err := parseTime(c.Query("since")) if err != nil { fail(c, http.StatusBadRequest, "since 需为 RFC3339 时间") return } until, err := parseTime(c.Query("until")) if err != nil { fail(c, http.StatusBadRequest, "until 需为 RFC3339 时间") return } c.Header("Content-Type", "text/csv; charset=utf-8") c.Header("Content-Disposition", `attachment; filename="audit-`+time.Now().Format("2006-01-02")+`.csv"`) // 已开始写响应体后无法再返回 JSON 错误;查询失败在此吞掉(导出为管理操作, // 失败由审计归档兜底),正常路径写入 CSV。 _ = h.svc.ExportCSV(c.Request.Context(), c.Writer, since, until) } // MyAudit GET /me/audit —— 外部用户查看与自己相关的审计痕迹(PLAN §8 个人中心)。 func (h *AuditHandler) MyAudit(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) sess := sessionFrom(c) if sess == nil { fail(c, http.StatusUnauthorized, "未登录") return } rows, total, err := h.svc.QueryByUser(c.Request.Context(), sess.RefID, page, pageSize) if err != nil { fail(c, http.StatusInternalServerError, err.Error()) return } ok(c, gin.H{"total": total, "items": rows}) }