103 lines
3.0 KiB
Go
103 lines
3.0 KiB
Go
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)
|
|
}
|
|
|
|
// 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})
|
|
}
|