Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions sqle/utils/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"io/fs"
"os"
"strings"
"time"
)

Expand Down Expand Up @@ -80,6 +81,9 @@ type ExportFormat string
const (
CsvExportFormat ExportFormat = "csv"
ExcelExportFormat ExportFormat = "excel"
ExportFormatHTML ExportFormat = "html"
ExportFormatPDF ExportFormat = "pdf"
ExportFormatWORD ExportFormat = "word"
)

// ExportDataResult 导出数据的结果
Expand Down Expand Up @@ -168,6 +172,25 @@ func NormalizeExportFormat(format *ExportFormat) ExportFormat {
return *format
}

// NormalizeExportFormatStr 规范化导出格式参数(字符串版本),默认返回 CSV(向后兼容)
// 支持 html/pdf/word/docx/excel/xlsx/csv 等输入的规范化,空字符串和无效值默认返回 CSV。
func NormalizeExportFormatStr(format string) ExportFormat {
switch strings.ToLower(strings.TrimSpace(format)) {
case "html":
return ExportFormatHTML
case "pdf":
return ExportFormatPDF
case "word", "docx":
return ExportFormatWORD
case "excel", "xlsx":
return ExcelExportFormat
case "csv", "":
return CsvExportFormat
default:
return CsvExportFormat
}
}

// ExportData 根据导出格式导出数据
// header: 表头字符串数组
// rows: 数据行,二维字符串数组
Expand Down
144 changes: 144 additions & 0 deletions sqle/utils/report_generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package utils
Comment thread
LordofAvernus marked this conversation as resolved.
Outdated

import (
"fmt"
"time"
)

// AuditReportData 审核报告完整数据模型
type AuditReportData struct {
// 元信息
TaskID uint64 `json:"task_id"`
Title string `json:"title"` // 报告标题 (i18n)
InstanceName string `json:"instance_name"` // 数据源名称
Schema string `json:"schema"`
GeneratedAt time.Time `json:"generated_at"` // 报告生成时间
Lang string `json:"lang"` // 语言: zh-CN / en-US
LogoBase64 string `json:"logo_base64"` // Logo 图片 base64

// 审核概要
Summary AuditSummary `json:"summary"`

// 审核结果统计
Statistics AuditStatistics `json:"statistics"`

// SQL 列表
SQLList []AuditSQLItem `json:"sql_list"` // 全部 SQL
ProblemSQLs []AuditSQLItem `json:"problem_sqls"` // 问题 SQL(AuditLevel != normal)

// 国际化标签
Labels ReportLabels `json:"labels"`
}

// CSVHeaders 返回 CSV 报告的表头列表
Comment thread
LordofAvernus marked this conversation as resolved.
Outdated
func (d *AuditReportData) CSVHeaders() []string {
return []string{
d.Labels.Number,
d.Labels.SQL,
d.Labels.AuditStatus,
d.Labels.AuditResult,
d.Labels.ExecStatus,
d.Labels.ExecResult,
d.Labels.RollbackSQL,
d.Labels.Description,
}
}

// AuditSummary 审核概要
type AuditSummary struct {
AuditTime string `json:"audit_time"`
InstanceName string `json:"instance_name"`
Schema string `json:"schema"`
TotalSQL int `json:"total_sql"`
PassRate float64 `json:"pass_rate"`
Score int32 `json:"score"`
AuditLevel string `json:"audit_level"`
}

// AuditStatistics 审核结果统计
type AuditStatistics struct {
LevelDistribution []LevelCount `json:"level_distribution"` // 按等级分布
RuleHits []RuleHit `json:"rule_hits"` // 规则命中统计
}

// LevelCount 等级统计
type LevelCount struct {
Level string `json:"level"` // normal/notice/warn/error
Count int `json:"count"`
}

// RuleHit 规则命中统计
type RuleHit struct {
RuleName string `json:"rule_name"`
HitCount int `json:"hit_count"`
}

// AuditSQLItem 单条 SQL 审核结果
type AuditSQLItem struct {
Number uint `json:"number"`
SQL string `json:"sql"`
AuditLevel string `json:"audit_level"`
AuditStatus string `json:"audit_status"`
AuditResult string `json:"audit_result"` // 审核结果描述
ExecStatus string `json:"exec_status"`
ExecResult string `json:"exec_result"`
RollbackSQL string `json:"rollback_sql"`
Description string `json:"description"`
// HTML/PDF/WORD 报告扩展字段
RuleName string `json:"rule_name"` // 触发的规则名称
Suggestion string `json:"suggestion"` // 优化建议
}

// ToCSVRow 将审核 SQL 项转换为 CSV 行数据
func (item *AuditSQLItem) ToCSVRow() []string {
return []string{
fmt.Sprintf("%d", item.Number),
item.SQL,
item.AuditStatus,
item.AuditResult,
item.ExecStatus,
item.ExecResult,
item.RollbackSQL,
item.Description,
}
Comment thread
LordofAvernus marked this conversation as resolved.
Outdated
}

// ReportLabels 报告中的国际化标签
type ReportLabels struct {
AuditSummary string `json:"audit_summary"`
ResultStatistics string `json:"result_statistics"`
ProblemSQLList string `json:"problem_sql_list"`
RuleHitStatistics string `json:"rule_hit_statistics"`
AuditTime string `json:"audit_time"`
DataSource string `json:"data_source"`
Schema string `json:"schema"`
TotalSQL string `json:"total_sql"`
PassRate string `json:"pass_rate"`
Score string `json:"score"`
AuditLevel string `json:"audit_level"`
Number string `json:"number"`
SQL string `json:"sql"`
AuditStatus string `json:"audit_status"`
AuditResult string `json:"audit_result"`
ExecStatus string `json:"exec_status"`
ExecResult string `json:"exec_result"`
RollbackSQL string `json:"rollback_sql"`
RuleName string `json:"rule_name"`
Description string `json:"description"`
Suggestion string `json:"suggestion"`
Count string `json:"count"`
HitCount string `json:"hit_count"`
}

// ReportGenerator 报告生成器接口
type ReportGenerator interface {
// Generate 根据报告数据生成指定格式的文件
Generate(data *AuditReportData) (*ExportDataResult, error)
// Format 返回生成器支持的格式
Comment thread
LordofAvernus marked this conversation as resolved.
Outdated
Format() ExportFormat
}

// ExportAuditReport 统一导出入口(CE/EE 通过 build tags 区分实现)
Comment thread
LordofAvernus marked this conversation as resolved.
Outdated
// CE 版本支持 CSV 和 HTML 格式,EE 版本额外支持 PDF 和 WORD 格式。
// 函数签名:func ExportAuditReport(format ExportFormat, data *AuditReportData) (*ExportDataResult, error)
// 实现分别位于 report_generator_ce.go 和 report_generator_ee.go 中。
90 changes: 90 additions & 0 deletions sqle/utils/report_generator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package utils

import (
"testing"
)

func TestNormalizeExportFormatStr(t *testing.T) {
Comment thread
LordofAvernus marked this conversation as resolved.
Outdated
testCases := map[string]struct {
input string
expected ExportFormat
}{
"empty string defaults to csv": {
input: "",
expected: CsvExportFormat,
},
"csv returns csv": {
input: "csv",
expected: CsvExportFormat,
},
"CSV uppercase returns csv": {
input: "CSV",
expected: CsvExportFormat,
},
"excel returns excel": {
input: "excel",
expected: ExcelExportFormat,
},
"xlsx returns excel": {
input: "xlsx",
expected: ExcelExportFormat,
},
"html returns html": {
input: "html",
expected: ExportFormatHTML,
},
"HTML uppercase returns html": {
input: "HTML",
expected: ExportFormatHTML,
},
"pdf returns pdf": {
input: "pdf",
expected: ExportFormatPDF,
},
"PDF uppercase returns pdf": {
input: "PDF",
expected: ExportFormatPDF,
},
"word returns word": {
input: "word",
expected: ExportFormatWORD,
},
"WORD uppercase returns word": {
input: "WORD",
expected: ExportFormatWORD,
},
"docx returns word": {
input: "docx",
expected: ExportFormatWORD,
},
"DOCX uppercase returns word": {
input: "DOCX",
expected: ExportFormatWORD,
},
"invalid value defaults to csv": {
input: "invalid",
expected: CsvExportFormat,
},
"unknown format defaults to csv": {
input: "json",
expected: CsvExportFormat,
},
"whitespace-only defaults to csv": {
input: " ",
expected: CsvExportFormat,
},
"leading and trailing spaces are trimmed": {
input: " pdf ",
expected: ExportFormatPDF,
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
result := NormalizeExportFormatStr(tc.input)
if result != tc.expected {
t.Errorf("NormalizeExportFormatStr(%q) = %q, want %q", tc.input, result, tc.expected)
}
})
}
}