-
Notifications
You must be signed in to change notification settings - Fork 214
Feat/multi format audit report export #3238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
LordofAvernus
wants to merge
14
commits into
main
Choose a base branch
from
feat/multi-format-audit-report-export
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
0d80d29
feat(utils): add report export format enums, data models, and generat…
LordofAvernus 768df70
feat(utils): implement CSV report generator with unit tests
LordofAvernus 47500f7
feat(utils): implement HTML report generator with template and unit t…
LordofAvernus 19ac482
feat(utils): implement CE format dispatch for ExportAuditReport
LordofAvernus 6eb0a3c
feat(api): refactor DownloadTaskSQLReportFile to support multi-format…
LordofAvernus b490691
docs(swagger): add export_format query parameter to sql_report endpoint
LordofAvernus a01d8a0
fix(report): fix BUG-001,003,005,007 in multi-format export (CE part)
LordofAvernus 8f6d839
fix(utils): return error for unsupported export format instead of CSV…
LordofAvernus 2d973a0
refactor(auditreport): move report export to server and apply review …
LordofAvernus 0fc0407
refactor(report): optimize SQL list initialization in BuildAuditRepor…
LordofAvernus 52697f8
feat(auditreport): move report ExportFormat to server layer
LordofAvernus e76654f
fix(report): unify report timestamps and simplify level distribution
LordofAvernus 502ad1a
docs(api): add export_format enum for sql_report endpoint
LordofAvernus 2c3d81f
ci: update checkout, setup-go and golangci-lint-action
LordofAvernus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| package utils | ||
|
|
||
| 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 报告的表头列表 | ||
|
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, | ||
| } | ||
|
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 返回生成器支持的格式 | ||
|
LordofAvernus marked this conversation as resolved.
Outdated
|
||
| Format() ExportFormat | ||
| } | ||
|
|
||
| // ExportAuditReport 统一导出入口(CE/EE 通过 build tags 区分实现) | ||
|
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 中。 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| package utils | ||
|
|
||
| import ( | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestNormalizeExportFormatStr(t *testing.T) { | ||
|
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) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.