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: "权限变更操作"}, } }