refactor: 清理SQL审计全栈死代码 (删除孤立组件+handler+model+路由+mock+API定义, 共-1278行)

SQL审计菜单此前已从侧边栏移除,但死代码残留(违反step5清理清单):
- 删除 frontend/components/audit/SQLAuditManager.tsx (629行, 无任何外部引用)
- 删除 backend/handlers/sql_audit.go (435行) + backend/models/sql_audit.go (64行)
- 移除 routes.go 中13条SQL审计路由 (API不再暴露, 返回404)
- 移除 database.go AutoMigrate 中2个SQL审计表
- 移除 api.ts sqlAuditApi 对象 (37行)
- 移除 mocks SQL审计mock数据 (90行) + barrel引用
This commit is contained in:
attacker committed 2026-06-16 03:05:14 +08:00
1 parent a6e8b5ab4f
commit a26f186e9b
8 files changed
+1 -1278

No files matched your search

-435
View File
@@ -1,435 +0,0 @@
package handlers
import (
"fmt"
"net/http"
"regexp"
"strings"
"time"
"ezops/internal/models"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// SQLAuditHandler SQL审计处理器 // Logan.Li
type SQLAuditHandler struct {
db *gorm.DB
}
func NewSQLAuditHandler(db *gorm.DB) *SQLAuditHandler {
return &SQLAuditHandler{db: db}
}
// ListRecords 获取审计记录列表 // Logan.Li
func (h *SQLAuditHandler) ListRecords(c *gin.Context) {
// 分页
page := 1
pageSize := 50
if c.Query("page") != "" {
fmt.Sscanf(c.Query("page"), "%d", &page)
}
if c.Query("page_size") != "" {
fmt.Sscanf(c.Query("page_size"), "%d", &pageSize)
}
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 500 {
pageSize = 50
}
query := h.db.Model(&models.SQLAuditRecord{})
// 筛选
if status := c.Query("status"); status != "" {
query = query.Where("status = ?", status)
}
if risk := c.Query("risk_level"); risk != "" {
query = query.Where("risk_level = ?", risk)
}
if keyword := c.Query("keyword"); keyword != "" {
query = query.Where("sql_content LIKE ? OR database_name LIKE ? OR executor LIKE ?",
"%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
}
var total int64
query.Count(&total)
var records []models.SQLAuditRecord
query.Order("created_at DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&records)
c.JSON(http.StatusOK, models.Response{
Code: 200,
Message: "success",
Data: models.ListResponse{
List: records,
Total: total,
},
})
}
// GetRecord 获取审计记录详情 // Logan.Li
func (h *SQLAuditHandler) GetRecord(c *gin.Context) {
id := c.Param("id")
var record models.SQLAuditRecord
if err := h.db.First(&record, id).Error; err != nil {
c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "记录不存在"})
return
}
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "success", Data: record})
}
// AnalyzeSQL 分析SQL并创建审计记录 // Logan.Li
func (h *SQLAuditHandler) AnalyzeSQL(c *gin.Context) {
var req models.AnalyzeSQLRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "参数错误"})
return
}
if strings.TrimSpace(req.SQL) == "" {
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "SQL不能为空"})
return
}
sqlUpper := strings.ToUpper(strings.TrimSpace(req.SQL))
// 判断 SQL 类型
sqlType := detectSQLType(sqlUpper)
// 提取表名
tableName := extractTableName(sqlUpper)
// 风险分析
riskLevel, matchedRules := h.analyzeRisk(sqlUpper)
// 获取当前用户
username := c.GetString("username")
if username == "" {
username = "system"
}
rulesJSON := "[]"
if len(matchedRules) > 0 {
rulesJSON = fmt.Sprintf(`["%s"]`, strings.Join(matchedRules, `","`))
}
record := models.SQLAuditRecord{
SQLContent: req.SQL,
SQLType: sqlType,
DatabaseName: req.DatabaseName,
TargetTable: tableName,
RiskLevel: riskLevel,
RiskRules: rulesJSON,
Status: "pending",
Executor: username,
Application: req.Application,
SourceIP: c.ClientIP(),
}
if err := h.db.Create(&record).Error; err != nil {
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "创建审计记录失败"})
return
}
c.JSON(http.StatusOK, models.Response{
Code: 200,
Message: "分析完成",
Data: models.AnalyzeSQLResponse{
ID: int64(record.ID),
RiskLevel: riskLevel,
RiskRules: matchedRules,
SQLType: sqlType,
TableName: tableName,
},
})
}
// ApproveRecord 审批通过 // Logan.Li
func (h *SQLAuditHandler) ApproveRecord(c *gin.Context) {
id := c.Param("id")
username := c.GetString("username")
if username == "" {
username = "admin"
}
result := h.db.Model(&models.SQLAuditRecord{}).Where("id = ? AND status = ?", id, "pending").
Updates(map[string]interface{}{"status": "approved", "approver": username})
if result.Error != nil {
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "审批失败"})
return
}
if result.RowsAffected == 0 {
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "记录不存在或非待审批状态"})
return
}
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "审批通过"})
}
// RejectRecord 审批驳回 // Logan.Li
func (h *SQLAuditHandler) RejectRecord(c *gin.Context) {
id := c.Param("id")
username := c.GetString("username")
if username == "" {
username = "admin"
}
result := h.db.Model(&models.SQLAuditRecord{}).Where("id = ? AND status = ?", id, "pending").
Updates(map[string]interface{}{"status": "rejected", "approver": username})
if result.Error != nil {
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "驳回失败"})
return
}
if result.RowsAffected == 0 {
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "记录不存在或非待审批状态"})
return
}
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "已驳回"})
}
// ExecuteRecord 执行SQL(模拟) // Logan.Li
func (h *SQLAuditHandler) ExecuteRecord(c *gin.Context) {
id := c.Param("id")
var record models.SQLAuditRecord
if err := h.db.First(&record, id).Error; err != nil {
c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "记录不存在"})
return
}
if record.Status != "approved" {
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "只能执行已审批的SQL"})
return
}
now := time.Now()
// 模拟执行(真实环境需连接目标数据库执行)
h.db.Model(&record).Updates(map[string]interface{}{
"status": "executed",
"execute_time": now,
"affected_rows": 0,
"execution_time": 10,
})
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "SQL执行成功"})
}
// DeleteRecord 删除审计记录 // Logan.Li
func (h *SQLAuditHandler) DeleteRecord(c *gin.Context) {
id := c.Param("id")
if err := h.db.Delete(&models.SQLAuditRecord{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "删除失败"})
return
}
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "已删除"})
}
// ==================== 审计规则管理 ====================
// ListRules 获取审计规则列表 // Logan.Li
func (h *SQLAuditHandler) ListRules(c *gin.Context) {
var rules []models.SQLAuditRule
h.db.Order("created_at DESC").Find(&rules)
c.JSON(http.StatusOK, models.Response{
Code: 200,
Message: "success",
Data: models.ListResponse{
List: rules,
Total: int64(len(rules)),
},
})
}
// CreateRule 创建审计规则 // Logan.Li
func (h *SQLAuditHandler) CreateRule(c *gin.Context) {
var rule models.SQLAuditRule
if err := c.ShouldBindJSON(&rule); err != nil {
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "参数错误"})
return
}
if rule.Name == "" {
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "规则名称不能为空"})
return
}
if rule.Severity == "" {
rule.Severity = "medium"
}
rule.Enabled = true
if err := h.db.Create(&rule).Error; err != nil {
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "创建规则失败"})
return
}
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "规则已创建", Data: rule})
}
// UpdateRule 更新审计规则 // Logan.Li
func (h *SQLAuditHandler) UpdateRule(c *gin.Context) {
id := c.Param("id")
var rule models.SQLAuditRule
if err := h.db.First(&rule, id).Error; err != nil {
c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "规则不存在"})
return
}
var req models.SQLAuditRule
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "参数错误"})
return
}
h.db.Model(&rule).Updates(map[string]interface{}{
"name": req.Name,
"description": req.Description,
"pattern": req.Pattern,
"severity": req.Severity,
"category": req.Category,
"enabled": req.Enabled,
})
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "规则已更新", Data: rule})
}
// DeleteRule 删除审计规则 // Logan.Li
func (h *SQLAuditHandler) DeleteRule(c *gin.Context) {
id := c.Param("id")
if err := h.db.Delete(&models.SQLAuditRule{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "删除失败"})
return
}
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "规则已删除"})
}
// ToggleRule 启用/禁用规则 // Logan.Li
func (h *SQLAuditHandler) ToggleRule(c *gin.Context) {
id := c.Param("id")
var rule models.SQLAuditRule
if err := h.db.First(&rule, id).Error; err != nil {
c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "规则不存在"})
return
}
h.db.Model(&rule).Update("enabled", !rule.Enabled)
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "状态已更新"})
}
// ==================== 辅助函数 ====================
// detectSQLType 检测SQL类型 // Logan.Li
func detectSQLType(sqlUpper string) string {
switch {
case strings.HasPrefix(sqlUpper, "DROP"):
return "DDL-DROP"
case strings.HasPrefix(sqlUpper, "TRUNCATE"):
return "DDL-TRUNCATE"
case strings.HasPrefix(sqlUpper, "ALTER"):
return "DDL-ALTER"
case strings.HasPrefix(sqlUpper, "CREATE"):
return "DDL-CREATE"
case strings.HasPrefix(sqlUpper, "DELETE"):
return "DML-DELETE"
case strings.HasPrefix(sqlUpper, "UPDATE"):
return "DML-UPDATE"
case strings.HasPrefix(sqlUpper, "INSERT"):
return "DML-INSERT"
case strings.HasPrefix(sqlUpper, "SELECT"):
return "DQL"
case strings.HasPrefix(sqlUpper, "GRANT"), strings.HasPrefix(sqlUpper, "REVOKE"):
return "DCL"
default:
return "OTHER"
}
}
// extractTableName 提取表名 // Logan.Li
func extractTableName(sqlUpper string) string {
// 匹配 FROM/INTO/UPDATE/TABLE 后面的表名
patterns := []string{
`FROM\s+(\w+)`,
`INTO\s+(\w+)`,
`UPDATE\s+(\w+)`,
`TABLE\s+(\w+)`,
`JOIN\s+(\w+)`,
}
for _, p := range patterns {
re := regexp.MustCompile(p)
m := re.FindStringSubmatch(sqlUpper)
if len(m) > 1 {
return strings.ToLower(m[1])
}
}
return ""
}
// analyzeRisk 分析SQL风险等级 // Logan.Li
func (h *SQLAuditHandler) analyzeRisk(sqlUpper string) (string, []string) {
// 从数据库加载启用的规则
var rules []models.SQLAuditRule
h.db.Where("enabled = ?", true).Find(&rules)
// 如果没有数据库规则,使用内置规则
if len(rules) == 0 {
rules = builtinRules()
}
var matched []string
maxSeverity := "safe"
for _, rule := range rules {
if matchRule(sqlUpper, rule) {
matched = append(matched, rule.Name)
if severityRank(rule.Severity) > severityRank(maxSeverity) {
maxSeverity = rule.Severity
}
}
}
return maxSeverity, matched
}
// matchRule 检查SQL是否匹配规则 // Logan.Li
func matchRule(sqlUpper string, rule models.SQLAuditRule) bool {
// 按 category 快速匹配
if rule.Category != "" {
if strings.Contains(sqlUpper, strings.ToUpper(rule.Category)) {
return true
}
}
// 按 pattern 正则匹配
if rule.Pattern != "" {
if re, err := regexp.Compile("(?i)" + rule.Pattern); err == nil {
if re.MatchString(sqlUpper) {
return true
}
}
}
return false
}
// severityRank 风险等级排序 // Logan.Li
func severityRank(s string) int {
switch s {
case "high":
return 4
case "medium":
return 3
case "low":
return 2
default:
return 1
}
}
// builtinRules 内置审计规则 // Logan.Li
func builtinRules() []models.SQLAuditRule {
return []models.SQLAuditRule{
{Name: "DROP TABLE", Category: "DROP TABLE", Severity: "high", Description: "删除表操作,不可恢复"},
{Name: "TRUNCATE TABLE", Category: "TRUNCATE", Severity: "high", Description: "清空表数据"},
{Name: "DELETE 无 WHERE", Pattern: `DELETE\s+FROM\s+\w+\s*(?:;|$)`, Severity: "high", Description: "无条件的DELETE操作"},
{Name: "UPDATE 无 WHERE", Pattern: `UPDATE\s+\w+\s+SET.*(?:;|$)`, Severity: "high", Description: "无条件的UPDATE操作"},
{Name: "ALTER TABLE", Category: "ALTER", Severity: "medium", Description: "修改表结构"},
{Name: "DROP DATABASE", Category: "DROP DATABASE", Severity: "high", Description: "删除数据库"},
{Name: "GRANT/REVOKE", Category: "GRANT", Severity: "medium", Description: "权限变更操作"},
}
}
-18
View File
@@ -371,24 +371,6 @@ func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config) {
audit.POST("/logs/clear", auditHandler.ClearAuditLogs)
}
// SQL审计路由 // Logan.Li
sqlAuditHandler := handlers.NewSQLAuditHandler(db)
sqlAudit := authorized.Group("/sql-audit")
{
sqlAudit.GET("/records", sqlAuditHandler.ListRecords)
sqlAudit.GET("/records/:id", sqlAuditHandler.GetRecord)
sqlAudit.POST("/analyze", sqlAuditHandler.AnalyzeSQL)
sqlAudit.POST("/records/:id/approve", sqlAuditHandler.ApproveRecord)
sqlAudit.POST("/records/:id/reject", sqlAuditHandler.RejectRecord)
sqlAudit.POST("/records/:id/execute", sqlAuditHandler.ExecuteRecord)
sqlAudit.DELETE("/records/:id", sqlAuditHandler.DeleteRecord)
sqlAudit.GET("/rules", sqlAuditHandler.ListRules)
sqlAudit.POST("/rules", sqlAuditHandler.CreateRule)
sqlAudit.PUT("/rules/:id", sqlAuditHandler.UpdateRule)
sqlAudit.DELETE("/rules/:id", sqlAuditHandler.DeleteRule)
sqlAudit.POST("/rules/:id/toggle", sqlAuditHandler.ToggleRule)
}
// Agent 上报路由(无需 JWT)
agentHandler := handlers.NewAgentHandler(db)
taskHandler := handlers.NewAgentTaskHandler(db)