Files
BackupX/server/internal/http/report_handler.go
Wu Qing a0d1e66199 feat(reports): 企业合规报表(后端聚合 + CSV 导出 + 前端页面) (#82)
新增合规报表:ReportService 逐任务聚合备份合规证据(成功率/最近成功/SLA 判定/加密/受保护量)+ JSON/CSV API;前端新增 /reports 页面(汇总卡片+明细表+CSV 导出)。后端 go test、前端 tsc+vite、端到端路由验证均通过。
2026-05-27 08:14:56 +08:00

96 lines
2.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package http
import (
"encoding/csv"
"fmt"
"strconv"
"strings"
"time"
"backupx/server/internal/service"
"backupx/server/pkg/response"
"github.com/gin-gonic/gin"
)
type ReportHandler struct {
service *service.ReportService
}
func NewReportHandler(reportService *service.ReportService) *ReportHandler {
return &ReportHandler{service: reportService}
}
func reportDays(c *gin.Context) int {
days := 30
if v := strings.TrimSpace(c.Query("days")); v != "" {
if parsed, err := strconv.Atoi(v); err == nil && parsed > 0 {
days = parsed
}
}
return days
}
// Compliance 返回 JSON 合规报表(按任务的备份合规证据 + 汇总)。
func (h *ReportHandler) Compliance(c *gin.Context) {
payload, err := h.service.ComplianceReport(c.Request.Context(), reportDays(c))
if err != nil {
response.Error(c, err)
return
}
response.Success(c, payload)
}
// ComplianceCSV 把合规报表导出为 CSV供审计归档。带 UTF-8 BOM 以便 Excel 正确识别中文。
func (h *ReportHandler) ComplianceCSV(c *gin.Context) {
report, err := h.service.ComplianceReport(c.Request.Context(), reportDays(c))
if err != nil {
response.Error(c, err)
return
}
filename := fmt.Sprintf("backupx-compliance-%s.csv", report.GeneratedAt.Format("20060102-150405"))
c.Header("Content-Type", "text/csv; charset=utf-8")
c.Header("Content-Disposition", "attachment; filename="+filename)
_, _ = c.Writer.WriteString("\ufeff") // UTF-8 BOM
w := csv.NewWriter(c.Writer)
_ = w.Write([]string{
"任务ID", "任务名", "类型", "启用", "节点", "加密", "保留天数", "SLA(RPO小时)",
"周期内运行", "成功", "失败", "成功率", "最近状态", "最近运行(UTC)", "最近成功(UTC)", "保护字节数", "合规判定",
})
for _, row := range report.Tasks {
_ = w.Write([]string{
strconv.FormatUint(uint64(row.TaskID), 10),
row.TaskName,
row.Type,
boolCN(row.Enabled),
row.NodeName,
boolCN(row.Encrypted),
strconv.Itoa(row.RetentionDays),
strconv.Itoa(row.SLAHoursRPO),
strconv.Itoa(row.TotalRuns),
strconv.Itoa(row.Successes),
strconv.Itoa(row.Failures),
fmt.Sprintf("%.2f%%", row.SuccessRate*100),
row.LastStatus,
fmtTimePtr(row.LastRunAt),
fmtTimePtr(row.LastSuccessAt),
strconv.FormatInt(row.ProtectedBytes, 10),
row.Risk,
})
}
w.Flush()
}
func boolCN(b bool) string {
if b {
return "是"
}
return "否"
}
func fmtTimePtr(t *time.Time) string {
if t == nil {
return ""
}
return t.UTC().Format("2006-01-02 15:04:05")
}