package services import ( "bytes" "ems-backend/models" "encoding/json" "errors" "fmt" "io" "log" "net/http" "strings" "time" "github.com/google/uuid" ) // CheckAndAutoHandleAlarms 检查告警数量并执行自动处理 func CheckAndAutoHandleAlarms() { // 1. 获取配置 var autoAck, thresholdStr string models.DB.Model(&models.SysConfig{}).Where("config_key = ?", "ai_auto_ack").Select("config_value").Scan(&autoAck) models.DB.Model(&models.SysConfig{}).Where("config_key = ?", "ai_alert_threshold").Select("config_value").Scan(&thresholdStr) if autoAck != "true" { return } threshold := 200 // 默认值 if _, err := fmt.Sscanf(thresholdStr, "%d", &threshold); err != nil { // 解析失败使用默认值 } // 2. 检查当前活跃告警数量 var count int64 models.DB.Model(&models.AlarmLog{}).Where("status = ?", "ACTIVE").Count(&count) if count > int64(threshold) { log.Printf("Active alarms (%d) exceeded threshold (%d), triggering auto-confirm...", count, threshold) go processAutoConfirmAndReport() } } func processAutoConfirmAndReport() { // 1. 获取待确认的300条告警 var alarms []models.AlarmLog err := models.DB.Where("status = ?", "ACTIVE"). Order("start_time asc"). // 处理最早的告警 Limit(300). Find(&alarms).Error if err != nil || len(alarms) == 0 { return } // 2. 批量确认告警 (更新状态为 CONFIRMED) var alarmIDs []uuid.UUID for _, a := range alarms { alarmIDs = append(alarmIDs, a.ID) } tx := models.DB.Begin() // 注意:AlarmLog 的状态字段可能是 Status 或 status,根据 schema.go 是 Status if err := tx.Model(&models.AlarmLog{}). Where("id IN ?", alarmIDs). Update("status", "CONFIRMED").Error; err != nil { tx.Rollback() log.Printf("Failed to auto-confirm alarms: %v", err) return } tx.Commit() // 3. 生成 AI 分析报表 GenerateAIReportInternal(alarms, fmt.Sprintf("自动告警处理报告 (已确认 %d 条) - %s", len(alarms), time.Now().Format("2006-01-02 15:04:05")), "AUTO_ALARM_ANALYSIS") } // GenerateAIReportInternal 内部生成报表逻辑 func GenerateAIReportInternal(alarms []models.AlarmLog, title string, reportType string) (*models.AIAnalysisReport, error) { // 获取 AI 配置 var apiUrl, apiKey, model string models.DB.Model(&models.SysConfig{}).Where("config_key = ?", "ai_api_url").Select("config_value").Scan(&apiUrl) models.DB.Model(&models.SysConfig{}).Where("config_key = ?", "ai_api_key").Select("config_value").Scan(&apiKey) models.DB.Model(&models.SysConfig{}).Where("config_key = ?", "ai_model").Select("config_value").Scan(&model) if apiUrl == "" || apiKey == "" { return nil, errors.New("AI configuration missing") } if model == "" { model = "gpt-3.5-turbo" } // 创建报表记录 report := models.AIAnalysisReport{ ReportType: reportType, Title: title, Status: "GENERATING", AlertCount: len(alarms), } if err := models.DB.Create(&report).Error; err != nil { return nil, err } // 异步调用 AI go func(rID uuid.UUID, alarms []models.AlarmLog, url, key, model string) { // 构建 Prompt prompt := fmt.Sprintf("系统包含 %d 条告警信息。以下是这些告警的摘要(前20条):\n", len(alarms)) limit := 20 if len(alarms) < 20 { limit = len(alarms) } for i := 0; i < limit; i++ { a := alarms[i] prompt += fmt.Sprintf("- [%s] 类型: %s, 内容: %s\n", a.StartTime.Format("15:04:05"), a.Type, a.Content) } prompt += "\n请根据以上信息,生成一份简要的监控分析报表,包含:\n1. 告警爆发原因分析\n2. 系统风险评估\n3. 后续优化建议\n请使用 Markdown 格式。" // 调用 AI content, err := FetchAIResponse(url, key, model, prompt) status := "COMPLETED" if err != nil { content = "生成失败: " + err.Error() status = "FAILED" } models.DB.Model(&models.AIAnalysisReport{}).Where("id = ?", rID).Updates(map[string]interface{}{ "status": status, "content": content, }) }(report.ID, alarms, NormalizeChatUrl(apiUrl), apiKey, model) return &report, nil } // NormalizeChatUrl 规范化 Chat 接口地址 (Public for controller usage if needed) func NormalizeChatUrl(inputUrl string) string { inputUrl = strings.TrimSpace(inputUrl) if strings.HasSuffix(inputUrl, "/chat/completions") { return inputUrl } // 如果是 /models 结尾,替换 if strings.HasSuffix(inputUrl, "/models") { return strings.Replace(inputUrl, "/models", "/chat/completions", 1) } // 如果是 /v1 结尾 if strings.HasSuffix(inputUrl, "/v1") { return strings.TrimRight(inputUrl, "/") + "/chat/completions" } return strings.TrimRight(inputUrl, "/") + "/chat/completions" } // 全局 HTTP Client 复用,优化连接池 var aiHttpClient = &http.Client{ Timeout: 60 * time.Second, Transport: &http.Transport{ MaxIdleConns: 10, IdleConnTimeout: 30 * time.Second, DisableKeepAlives: false, }, } // FetchAIResponse 实际调用 (Public for controller usage) func FetchAIResponse(apiUrl, apiKey, model, prompt string) (string, error) { requestBody, _ := json.Marshal(map[string]interface{}{ "model": model, "messages": []map[string]string{ {"role": "user", "content": prompt}, }, "temperature": 0.7, }) req, err := http.NewRequest("POST", apiUrl, bytes.NewBuffer(requestBody)) if err != nil { return "", err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+apiKey) // 使用全局 Client 复用连接 resp, err := aiHttpClient.Do(req) if err != nil { return "", err } defer resp.Body.Close() bodyBytes, _ := io.ReadAll(resp.Body) if resp.StatusCode != 200 { return "", fmt.Errorf("API Error: %d - %s", resp.StatusCode, string(bodyBytes)) } var result struct { Choices []struct { Message struct { Content string `json:"content"` } `json:"message"` } `json:"choices"` Error struct { Message string `json:"message"` } `json:"error"` } if err := json.Unmarshal(bodyBytes, &result); err != nil { return "", fmt.Errorf("Parse Error: %v", err) } if len(result.Choices) > 0 { return result.Choices[0].Message.Content, nil } return "", errors.New("Empty response from AI") }