feat(M4): 生命周期 + 审计 — 到期锁定/回收 cron、审计查询/CSV 导出/每日归档、settings 动态策略
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
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"),
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user