Compare commits

..

2 Commits

Author SHA1 Message Date
wuko233 2167db4918 [notifier] 切换依赖为gomail,支持自定义端口及SSL 2026-04-05 12:10:14 +08:00
wuko233 18e1672114 [notifier] 新增email告警方式 2026-04-05 11:29:50 +08:00
8 changed files with 197 additions and 4 deletions

1
.gitignore vendored
View File

@ -23,3 +23,4 @@ go.work
sysmonitord.code-workspace
data/
config.yaml

View File

@ -9,6 +9,7 @@ import (
"sysmonitord/internal/monitor/detector"
"sysmonitord/internal/monitor/timer"
"sysmonitord/internal/monitor/watcher"
"sysmonitord/internal/notifier"
"sysmonitord/internal/scanner/file"
"sysmonitord/internal/scanner/process"
"sysmonitord/internal/storage"
@ -107,6 +108,10 @@ var StartCmd = &cobra.Command{
procScheduler := timer.NewScheduler(time.Duration(cfg.Scanner.Process.Interval)*time.Second, procDetector)
procScheduler.Start()
// ====== 启动告警管理器 ======
alerter := notifier.NewAlerter(cfg.Notification)
alerter.Start()
logger.Log.Info("系统监控守护服务已启动,正在监控系统变化...")
quit := make(chan os.Signal, 1)
@ -126,6 +131,14 @@ var StartCmd = &cobra.Command{
fileDetector.HandleEvent(event.Path, event.Op.String())
}
// test
alerter.PushAlert(notifier.AlertEvent{
Type: "File",
Path: event.Path,
Reason: event.Op.String(),
Details: "To test",
})
case err := <-mon.Errors():
logger.Log.Error("文件监听错误", zap.Error(err))

2
go.mod
View File

@ -23,5 +23,7 @@ require (
go.uber.org/zap v1.27.1 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.28.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

4
go.sum
View File

@ -50,6 +50,10 @@ golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,10 +1,28 @@
package config
type Config struct {
Log LogConfig `yaml:"log"`
Audit AuditConfig `yaml:"audit"`
Scanner ScannerConfig `yaml:"scanner"`
Storage StorageConfig `yaml:"storage"`
Log LogConfig `yaml:"log"`
Audit AuditConfig `yaml:"audit"`
Scanner ScannerConfig `yaml:"scanner"`
Storage StorageConfig `yaml:"storage"`
Notification NotificationConfig `yaml:"notification"`
}
type NotificationConfig struct {
Email EmailConfig `yaml:"email"`
}
type EmailConfig struct {
Enabled bool `yaml:"enabled"`
Recipients []string `yaml:"recipients"`
SMTP SMTPConfig `yaml:"smtp"`
}
type SMTPConfig struct {
Server string `yaml:"server"`
Port int `yaml:"port"`
Username string `yaml:"username"`
Password string `yaml:"password"`
}
type LogConfig struct {

View File

@ -0,0 +1,98 @@
package notifier
import (
"fmt"
"sync"
"sysmonitord/internal/config"
"sysmonitord/internal/notifier/mail"
"sysmonitord/pkg/logger"
"time"
"go.uber.org/zap"
)
type AlertEvent struct {
Type string
Path string
Reason string
Details string
}
type Alerter struct {
cfg config.NotificationConfig
mailer *mail.Mailer
eventChan chan AlertEvent
buffer []AlertEvent
mu sync.Mutex
timer *time.Timer
interval time.Duration
}
func NewAlerter(cfg config.NotificationConfig) *Alerter {
return &Alerter{
cfg: cfg,
mailer: mail.NewMailer(cfg.Email),
eventChan: make(chan AlertEvent, 100),
buffer: make([]AlertEvent, 0),
interval: 30 * time.Second, // Todo: 可配置化
}
}
func (a *Alerter) Start() {
go a.loop()
}
func (a *Alerter) PushAlert(event AlertEvent) {
select {
case a.eventChan <- event:
logger.Log.Debug("[notifier] 推送告警事件", zap.String("path", event.Path), zap.String("reason", event.Reason))
default:
logger.Log.Warn("[notifier] 告警事件通道已满,丢弃告警", zap.String("path", event.Path), zap.String("reason", event.Reason))
}
}
func (a *Alerter) loop() {
a.timer = time.NewTimer(a.interval)
for {
select {
case event := <-a.eventChan:
a.mu.Lock()
a.buffer = append(a.buffer, event)
a.mu.Unlock()
logger.Log.Debug("[notifier] 收到告警,加入待发送序列", zap.String("path", event.Path))
case <-a.timer.C:
logger.Log.Debug("[notifier] 定时检查告警事件,准备发送", zap.Int("count", len(a.buffer)))
a.mu.Lock()
if len(a.buffer) > 0 {
a.sendAlert()
a.buffer = make([]AlertEvent, 0)
}
a.mu.Unlock()
a.timer.Reset(a.interval)
}
}
}
func (a *Alerter) sendAlert() {
if len(a.buffer) == 0 {
return
}
subject := fmt.Sprintf("【Sysmonitor】新增 %d 个告警", len(a.buffer))
body := "以下是最近的告警事件:\n\n"
for _, event := range a.buffer {
body += fmt.Sprintf("- [%s] %s: %s (%s)\n", event.Type, event.Path, event.Reason, event.Details)
}
body += "\n请及时关注系统安全状况。"
if err := a.mailer.Send(subject, body); err != nil {
logger.Log.Error("[notifier] 发送告警邮件失败", zap.Error(err))
} else {
logger.Log.Debug("[notifier] 告警邮件发送成功", zap.Int("count", len(a.buffer)))
}
}

View File

@ -0,0 +1,57 @@
package mail
import (
"fmt"
"sysmonitord/internal/config"
"sysmonitord/pkg/logger"
"go.uber.org/zap"
"gopkg.in/gomail.v2"
)
type Mailer struct {
cfg config.EmailConfig
}
func NewMailer(cfg config.EmailConfig) *Mailer {
return &Mailer{cfg: cfg}
}
func (m *Mailer) Send(subject, body string) error {
if !m.cfg.Enabled {
logger.Log.Debug("[notifier] 未启用邮件通知,跳过....")
return nil
}
if m.cfg.SMTP.Server == "" || m.cfg.SMTP.Port == 0 {
logger.Log.Error("[notifier] SMTP配置缺失",
zap.String("server", m.cfg.SMTP.Server),
zap.Int("port", m.cfg.SMTP.Port),
)
return fmt.Errorf("SMTP配置缺失")
}
msg := gomail.NewMessage()
msg.SetHeader("From", m.cfg.SMTP.Username)
msg.SetHeader("To", m.cfg.Recipients...)
msg.SetHeader("Subject", subject)
msg.SetBody("text/plain", body)
d := gomail.NewDialer(
m.cfg.SMTP.Server,
m.cfg.SMTP.Port,
m.cfg.SMTP.Username,
m.cfg.SMTP.Password,
)
if m.cfg.SMTP.Port == 465 {
d.SSL = true
}
if err := d.DialAndSend(msg); err != nil {
logger.Log.Error("[notifier] 邮件发送失败", zap.Error(err))
return err
}
logger.Log.Info("[notifier] 邮件发送成功", zap.Strings("recipients", m.cfg.Recipients))
return nil
}