Files
team-api/internal/logic/monitor/alert_notify.go
T

203 lines
6.1 KiB
Go

package monitor
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"github.com/qianfree/team-api/internal/dao"
"github.com/qianfree/team-api/internal/logic/common"
do "github.com/qianfree/team-api/internal/model/do"
)
// dispatchAlertNotifications sends notifications for a triggered alert.
func dispatchAlertNotifications(ctx context.Context, rule map[string]any, eventID int64, triggerValue, threshold float64) {
methods := gconv.Strings(rule["notification_methods"])
ruleName := gconv.String(rule["name"])
metricType := gconv.String(rule["metric_type"])
level := gconv.String(rule["level"])
webhookURL := gconv.String(rule["webhook_url"])
notifyUserIDs := gconv.Int64s(rule["notify_user_ids"])
message := fmt.Sprintf("告警规则「%s」触发:指标 %s 当前值 %.2f 超过阈值 %.2f", ruleName, metricType, triggerValue, threshold)
subject := fmt.Sprintf("[%s] %s", levelLabel(level), ruleName)
var notifiedMethods []string
for _, method := range methods {
switch method {
case "email":
if len(notifyUserIDs) > 0 {
sendAlertEmailToAdmins(ctx, notifyUserIDs, subject, message)
notifiedMethods = append(notifiedMethods, "email")
}
case "in_app":
if len(notifyUserIDs) > 0 {
sendAlertInAppToAdmins(ctx, notifyUserIDs, subject, message)
notifiedMethods = append(notifiedMethods, "in_app")
}
case "webhook":
if webhookURL != "" {
sendAlertWebhook(ctx, webhookURL, rule, eventID, triggerValue, threshold)
notifiedMethods = append(notifiedMethods, "webhook")
}
}
}
// Update event with notified methods
if len(notifiedMethods) > 0 && eventID > 0 {
dao.OpsAlertEvents.Ctx(ctx).
Where("id", eventID).
Data(do.OpsAlertEvents{
NotifiedMethods: notifiedMethods,
}).
Update()
}
}
// sendAlertEmailToAdmins sends alert email to specified admin users.
func sendAlertEmailToAdmins(ctx context.Context, adminIDs []int64, subject, body string) {
// Create email sender from config
sender := common.NewEmailSender(&common.EmailConfig{
Host: g.Cfg().MustGet(ctx, "email.smtp.host").String(),
Port: g.Cfg().MustGet(ctx, "email.smtp.port").Int(),
Username: g.Cfg().MustGet(ctx, "email.smtp.username").String(),
Password: g.Cfg().MustGet(ctx, "email.smtp.password").String(),
From: g.Cfg().MustGet(ctx, "email.smtp.from").String(),
UseTLS: g.Cfg().MustGet(ctx, "email.smtp.port").Int() == 587 || g.Cfg().MustGet(ctx, "email.smtp.port").Int() == 465,
})
for _, adminID := range adminIDs {
type adminUser struct {
Email string `json:"email"`
}
var user adminUser
err := dao.SysAdminUsers.Ctx(ctx).
Where("id", adminID).
Where("status", "active").
Fields("email").
Scan(&user)
if err != nil || user.Email == "" {
continue
}
htmlBody := fmt.Sprintf(`
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2 style="color: #e74c3c;">%s</h2>
<div style="padding: 16px; background: #f8f9fa; border-radius: 8px;">
<p>%s</p>
<p style="color: #666;">时间:%s</p>
</div>
</div>
`, subject, body, time.Now().Format("2006-01-02 15:04:05"))
msg := &common.EmailMessage{
To: user.Email,
Subject: subject,
BodyHTML: htmlBody,
}
if err := sender.Send(ctx, msg); err != nil {
g.Log().Warningf(ctx, "send alert email to %s: %v", user.Email, err)
}
}
}
// sendAlertInAppToAdmins creates in-app messages for specified admin users.
func sendAlertInAppToAdmins(ctx context.Context, adminIDs []int64, title, content string) {
for _, adminID := range adminIDs {
_, err := dao.NtfMessages.Ctx(ctx).Insert(do.NtfMessages{
TenantId: 0, // System-level message,
UserId: adminID,
Type: "alert",
Title: title,
Content: content,
IsRead: 0,
})
if err != nil {
g.Log().Warningf(ctx, "create in-app alert for admin %d: %v", adminID, err)
}
}
}
// sendAlertWebhook sends an alert notification to a webhook URL.
func sendAlertWebhook(ctx context.Context, webhookURL string, rule map[string]any, eventID int64, triggerValue, threshold float64) {
payload := map[string]any{
"event_id": eventID,
"rule_id": rule["id"],
"rule_name": rule["name"],
"metric_type": rule["metric_type"],
"level": rule["level"],
"trigger_value": triggerValue,
"threshold": threshold,
"message": fmt.Sprintf("告警规则「%s」触发", gconv.String(rule["name"])),
"timestamp": time.Now().Format(time.RFC3339),
}
data, _ := json.Marshal(payload)
client := g.Client().SetTimeout(10 * time.Second)
resp, err := client.DoRequest(ctx, "POST", webhookURL, data)
if err != nil {
g.Log().Warningf(ctx, "send alert webhook to %s: %v", webhookURL, err)
return
}
defer resp.Close()
if resp.StatusCode >= 300 {
g.Log().Warningf(ctx, "alert webhook returned status %d from %s", resp.StatusCode, webhookURL)
}
}
// sendTestNotification sends a test notification using the rule's settings.
func sendTestNotification(ctx context.Context, rule map[string]any, event map[string]any) error {
methods := gconv.Strings(rule["notification_methods"])
webhookURL := gconv.String(rule["webhook_url"])
notifyUserIDs := gconv.Int64s(rule["notify_user_ids"])
subject := fmt.Sprintf("[测试] %s", gconv.String(event["trigger_message"]))
body := gconv.String(event["trigger_message"])
sent := false
for _, method := range methods {
switch method {
case "email":
if len(notifyUserIDs) > 0 {
sendAlertEmailToAdmins(ctx, notifyUserIDs, subject, body)
sent = true
}
case "in_app":
if len(notifyUserIDs) > 0 {
sendAlertInAppToAdmins(ctx, notifyUserIDs, subject, body)
sent = true
}
case "webhook":
if webhookURL != "" {
sendAlertWebhook(ctx, webhookURL, rule, 0, gconv.Float64(event["trigger_value"]), gconv.Float64(event["threshold_value"]))
sent = true
}
}
}
if !sent {
return common.NewBadRequestError("未配置任何有效的通知方式")
}
return nil
}
func levelLabel(level string) string {
switch level {
case "critical":
return "严重"
case "warning":
return "警告"
case "info":
return "信息"
default:
return level
}
}