feat: 新增通知模块,暂时只有邮件和smsc发送

This commit is contained in:
TsMask
2025-07-15 15:02:08 +08:00
parent e67c0d6ba6
commit b6d4879cb1
3 changed files with 301 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
package notification
import (
"be.ems/src/framework/logger"
"github.com/gin-gonic/gin"
)
// 模块路由注册
func Setup(router *gin.Engine) {
logger.Infof("开始加载 ====> notification 模块路由")
}

View File

@@ -0,0 +1,126 @@
package service
import (
"bytes"
"crypto/tls"
"fmt"
ht "html/template"
"strings"
"time"
"github.com/wneessen/go-mail"
"be.ems/src/framework/config"
"be.ems/src/framework/logger"
"be.ems/src/framework/utils/date"
"be.ems/src/framework/utils/parse"
neDataModel "be.ems/src/modules/network_data/model"
)
// EmailAlarm 发告警邮件
func EmailAlarm(a neDataModel.Alarm, neIP string) error {
emailList := fmt.Sprint(config.Get("notification.email.list"))
if len(emailList) == 0 {
return fmt.Errorf("email list is empty")
}
// 模板
htmlBodyTemplate := `
<strong>Alarm information</strong>
<p style="text-indent: 2.5em;">Sequence: {{.AlarmSeq}}</p>
<p style="text-indent: 2.5em;">NE Name: {{.NeName}}</p>
<p style="text-indent: 4.5em;">NE IP: {{.NeIp}}</p>
<p style="text-indent: 5em;">Title: {{.AlarmTitle}}</p>
<p style="text-indent: 3.5em;">Severity: {{.OrigSeverity}}</p>
<p style="text-indent: 2.5em;">Event Time: {{.AlarmTime}}</p>
<p style="text-indent: 2em;">Alarm Status: {{.AlarmStatus}}</p>
<i>Automatic sent by OMC, please do not reply!</i>
`
htmlTpl, err := ht.New("htmltpl").Parse(htmlBodyTemplate)
if err != nil {
return fmt.Errorf("EmailAlarm Parse alarmId:%s fail %s", a.AlarmId, err.Error())
}
// 参数值
data := map[string]any{
"AlarmSeq": a.AlarmSeq,
"NeName": a.NeName,
"NeIp": neIP,
"AlarmTitle": a.AlarmTitle,
"OrigSeverity": a.OrigSeverity,
"AlarmTime": date.ParseDateToStr(a.EventTime, time.RFC3339),
"AlarmStatus": a.AlarmStatus,
}
buffer := bytes.NewBuffer(nil)
if err := htmlTpl.Execute(buffer, data); err != nil {
return fmt.Errorf("EmailAlarm Execute alarmId:%s fail %s", a.AlarmId, err.Error())
}
htmlStr := buffer.String()
// 发送邮件
err = EmailSendHTML(htmlStr, strings.Split(emailList, ","))
if err != nil {
return fmt.Errorf("EmailAlarm alarmId:%s fail %s", a.AlarmId, err.Error())
}
return err
}
// EmailSendHTML 发送HTML邮件
func EmailSendHTML(htmlStr string, toEmailArr []string) error {
// QQ 邮箱:
// SMTP 服务器地址smtp.qq.comSSL协议端口465/994 | 非SSL协议端口25
// 163 邮箱:
// SMTP 服务器地址smtp.163.com端口25
// host := "mail.agrandtech.com"
// port := 25
// userName := "smtpext@agrandtech.com"
// password := "1000smtp@omc!"
emailConf := config.Get("notification.email").(map[string]any)
enable := parse.Boolean(emailConf["enable"])
if !enable {
return fmt.Errorf("email notification not enable")
}
title := fmt.Sprint(emailConf["title"])
smtp := fmt.Sprint(emailConf["smtp"])
port := parse.Number(emailConf["port"])
user := fmt.Sprint(emailConf["user"])
password := fmt.Sprint(emailConf["password"])
message := mail.NewMsg()
// 发件人
if err := message.From(user); err != nil {
return fmt.Errorf("failed to set From address: %s", err)
}
// 收件人
hasTo := false
for _, v := range toEmailArr {
if err := message.AddTo(v); err != nil {
logger.Errorf("failed to set To address: %v %s", v, err)
continue
}
hasTo = true
}
if !hasTo {
return fmt.Errorf("failed to set To address not has")
}
// 邮件主题
message.Subject(title)
// 邮件HTML内容
message.SetBodyString(mail.TypeTextHTML, htmlStr)
// 连接到邮件SMTP服务器
client, err := mail.NewClient(smtp,
mail.WithSMTPAuth(mail.SMTPAuthAutoDiscover),
mail.WithUsername(user),
mail.WithPort(int(port)),
mail.WithPassword(password),
mail.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}),
)
if err != nil {
return fmt.Errorf("failed to create mail client: %s", err)
}
// 发送
if err := client.DialAndSend(message); err != nil {
return fmt.Errorf("failed to send mail: %s", err)
}
return nil
}

View File

@@ -0,0 +1,164 @@
package service
import (
"bytes"
"fmt"
"strings"
tt "text/template"
"time"
"github.com/linxGnu/gosmpp"
"github.com/linxGnu/gosmpp/data"
"github.com/linxGnu/gosmpp/pdu"
"be.ems/src/framework/config"
"be.ems/src/framework/logger"
"be.ems/src/framework/utils/date"
"be.ems/src/framework/utils/parse"
neDataModel "be.ems/src/modules/network_data/model"
)
var smscSession *gosmpp.Session
// connSM 连接smsc
func connSM() *gosmpp.Session {
if smscSession != nil {
return smscSession
}
smscAddr := fmt.Sprint(config.Get("notification.smsc.addr"))
systemType := fmt.Sprint(config.Get("notification.smsc.systemtype"))
systemID := fmt.Sprint(config.Get("notification.smsc.systemid"))
password := fmt.Sprint(config.Get("notification.smsc.password"))
// 建立连接
session, err := gosmpp.NewSession(
gosmpp.TXConnector(gosmpp.NonTLSDialer, gosmpp.Auth{
SMSC: smscAddr,
SystemID: systemID,
Password: password,
SystemType: systemType,
}),
gosmpp.Settings{
ReadTimeout: 2 * time.Second,
OnSubmitError: func(_ pdu.PDU, err error) {
logger.Errorf("failed to smpp submit error: %s", err.Error())
},
OnRebindingError: func(err error) {
logger.Errorf("failed to smpp rebinding error: %s", err.Error())
},
}, -1)
if err != nil {
logger.Errorf("failed to create smpp new session error: %s", err.Error())
return nil
}
// defer session.Close()
smscSession = session
return smscSession
}
// newSubmitSM 构建短信提交
func newSubmitSM(destSM string, message string) (*pdu.SubmitSM, error) {
dataCoding := parse.Number(config.Get("notification.smsc.coding"))
enc := data.FromDataCoding(byte(dataCoding))
srcSM := fmt.Sprint(config.Get("notification.smsc.servicenumber"))
// 源地址
srcAddr := pdu.NewAddress()
srcAddr.SetTon(5)
srcAddr.SetNpi(0)
err := srcAddr.SetAddress(srcSM)
if err != nil {
return nil, err
}
destAddr := pdu.NewAddress()
destAddr.SetTon(1)
destAddr.SetNpi(1)
err = destAddr.SetAddress(destSM)
if err != nil {
return nil, err
}
// build up submitSM
submitSM := pdu.NewSubmitSM().(*pdu.SubmitSM)
submitSM.SourceAddr = srcAddr
submitSM.DestAddr = destAddr
if err = submitSM.Message.SetMessageWithEncoding(message, enc); err != nil {
return nil, err
}
submitSM.ProtocolID = 0
submitSM.RegisteredDelivery = 1
submitSM.ReplaceIfPresentFlag = 0
submitSM.EsmClass = 0
return submitSM, nil
}
// SMSCAlarm 发告警短信
func SMSCAlarm(a neDataModel.Alarm, neIP string) error {
mobileList := fmt.Sprint(config.Get("notification.smsc.list"))
if len(mobileList) == 0 {
return fmt.Errorf("smsc list is empty")
}
// 模板
textBodyTemplate := `Alarm Notification
Sequence: {{.AlarmSeq}},
NE Name: {{.NeName}}
NE IP: {{.NeIp}}
Title: {{.AlarmTitle}}
Severity: {{.OrigSeverity}}
Event Time: {{.AlarmTime}}
Alarm Status: {{.AlarmStatus}}
Automatic sent by OMC, please do not reply!`
textTpl, err := tt.New("texttpl").Parse(textBodyTemplate)
if err != nil {
return fmt.Errorf("SMSCAlarm alarmId:%s fail %s", a.AlarmId, err.Error())
}
// 参数值
data := map[string]any{
"AlarmSeq": a.AlarmSeq,
"NeName": a.NeName,
"NeIp": neIP,
"AlarmTitle": a.AlarmTitle,
"OrigSeverity": a.OrigSeverity,
"AlarmTime": date.ParseDateToStr(a.EventTime, time.RFC3339),
"AlarmStatus": a.AlarmStatus,
}
buffer := bytes.NewBuffer(nil)
if err := textTpl.Execute(buffer, data); err != nil {
return fmt.Errorf("EmailAlarm Execute alarmId:%s fail %s", a.AlarmId, err.Error())
}
textStr := buffer.String()
// 发送短信
err = SMSCSendText(textStr, strings.Split(mobileList, ","))
if err != nil {
return fmt.Errorf("EmailAlarm alarmId:%s fail %s", a.AlarmId, err.Error())
}
return err
}
// SMSCSendText 发送文本短信
func SMSCSendText(textStr string, toMobileArr []string) error {
enable := parse.Boolean(config.Get("notification.smsc.enable"))
if !enable {
return fmt.Errorf("smsc notification not enable")
}
sm := connSM()
if sm == nil {
return fmt.Errorf("smpp new session create failed")
}
errArr := []string{}
for _, v := range toMobileArr {
submitSM, err := newSubmitSM(v, textStr)
if err != nil {
errArr = append(errArr, err.Error())
continue
}
if err = sm.Transceiver().Submit(submitSM); err != nil {
errArr = append(errArr, err.Error())
continue
}
}
if len(errArr) > 0 {
return fmt.Errorf("%s", strings.Join(errArr, ","))
}
return nil
}