mirror of
https://github.com/nezhahq/nezha.git
synced 2025-01-22 20:58:14 -05:00
WIP: 通知方式分组 支持将不同的报警|监控|计划任务的通知 发送到指定的通知分组
This commit is contained in:
parent
8ab62858f9
commit
9c1d495eb0
@ -259,13 +259,14 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type cronForm struct {
|
type cronForm struct {
|
||||||
ID uint64
|
ID uint64
|
||||||
Name string
|
Name string
|
||||||
Scheduler string
|
Scheduler string
|
||||||
Command string
|
Command string
|
||||||
ServersRaw string
|
ServersRaw string
|
||||||
Cover uint8
|
Cover uint8
|
||||||
PushSuccessful string
|
PushSuccessful string
|
||||||
|
NotificationTag string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ma *memberAPI) addOrEditCron(c *gin.Context) {
|
func (ma *memberAPI) addOrEditCron(c *gin.Context) {
|
||||||
@ -278,6 +279,7 @@ func (ma *memberAPI) addOrEditCron(c *gin.Context) {
|
|||||||
cr.Command = cf.Command
|
cr.Command = cf.Command
|
||||||
cr.ServersRaw = cf.ServersRaw
|
cr.ServersRaw = cf.ServersRaw
|
||||||
cr.PushSuccessful = cf.PushSuccessful == "on"
|
cr.PushSuccessful = cf.PushSuccessful == "on"
|
||||||
|
cr.NotificationTag = cf.NotificationTag
|
||||||
cr.ID = cf.ID
|
cr.ID = cf.ID
|
||||||
cr.Cover = cf.Cover
|
cr.Cover = cf.Cover
|
||||||
err = utils.Json.Unmarshal([]byte(cf.ServersRaw), &cr.Servers)
|
err = utils.Json.Unmarshal([]byte(cf.ServersRaw), &cr.Servers)
|
||||||
@ -376,6 +378,7 @@ func (ma *memberAPI) forceUpdate(c *gin.Context) {
|
|||||||
type notificationForm struct {
|
type notificationForm struct {
|
||||||
ID uint64
|
ID uint64
|
||||||
Name string
|
Name string
|
||||||
|
Tag string // 分组名
|
||||||
URL string
|
URL string
|
||||||
RequestMethod int
|
RequestMethod int
|
||||||
RequestType int
|
RequestType int
|
||||||
@ -390,6 +393,7 @@ func (ma *memberAPI) addOrEditNotification(c *gin.Context) {
|
|||||||
err := c.ShouldBindJSON(&nf)
|
err := c.ShouldBindJSON(&nf)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
n.Name = nf.Name
|
n.Name = nf.Name
|
||||||
|
n.Tag = nf.Tag
|
||||||
n.RequestMethod = nf.RequestMethod
|
n.RequestMethod = nf.RequestMethod
|
||||||
n.RequestType = nf.RequestType
|
n.RequestType = nf.RequestType
|
||||||
n.RequestHeader = nf.RequestHeader
|
n.RequestHeader = nf.RequestHeader
|
||||||
@ -401,6 +405,10 @@ func (ma *memberAPI) addOrEditNotification(c *gin.Context) {
|
|||||||
err = n.Send("这是测试消息")
|
err = n.Send("这是测试消息")
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
// 保证Tag不为空
|
||||||
|
if n.Tag == "" {
|
||||||
|
n.Tag = "default"
|
||||||
|
}
|
||||||
if n.ID == 0 {
|
if n.ID == 0 {
|
||||||
err = singleton.DB.Create(&n).Error
|
err = singleton.DB.Create(&n).Error
|
||||||
} else {
|
} else {
|
||||||
@ -414,7 +422,7 @@ func (ma *memberAPI) addOrEditNotification(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
singleton.OnRefreshOrAddNotification(n)
|
singleton.OnRefreshOrAddNotification(&n)
|
||||||
c.JSON(http.StatusOK, model.Response{
|
c.JSON(http.StatusOK, model.Response{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
})
|
})
|
||||||
|
@ -72,6 +72,7 @@ type Config struct {
|
|||||||
TLS bool
|
TLS bool
|
||||||
|
|
||||||
EnableIPChangeNotification bool
|
EnableIPChangeNotification bool
|
||||||
|
IPChangeNotificationTag string
|
||||||
EnablePlainIPInNotification bool
|
EnablePlainIPInNotification bool
|
||||||
|
|
||||||
// IP变更提醒
|
// IP变更提醒
|
||||||
@ -102,6 +103,9 @@ func (c *Config) Read(path string) error {
|
|||||||
if c.GRPCPort == 0 {
|
if c.GRPCPort == 0 {
|
||||||
c.GRPCPort = 5555
|
c.GRPCPort = 5555
|
||||||
}
|
}
|
||||||
|
if c.EnableIPChangeNotification && c.IPChangeNotificationTag == "" {
|
||||||
|
c.IPChangeNotificationTag = "default"
|
||||||
|
}
|
||||||
|
|
||||||
c.updateIgnoredIPNotificationID()
|
c.updateIgnoredIPNotificationID()
|
||||||
return nil
|
return nil
|
||||||
|
@ -15,14 +15,15 @@ const (
|
|||||||
|
|
||||||
type Cron struct {
|
type Cron struct {
|
||||||
Common
|
Common
|
||||||
Name string
|
Name string
|
||||||
Scheduler string //分钟 小时 天 月 星期
|
Scheduler string //分钟 小时 天 月 星期
|
||||||
Command string
|
Command string
|
||||||
Servers []uint64 `gorm:"-"`
|
Servers []uint64 `gorm:"-"`
|
||||||
PushSuccessful bool // 推送成功的通知
|
PushSuccessful bool // 推送成功的通知
|
||||||
LastExecutedAt time.Time // 最后一次执行时间
|
NotificationTag string // 指定通知方式的分组
|
||||||
LastResult bool // 最后一次执行结果
|
LastExecutedAt time.Time // 最后一次执行时间
|
||||||
Cover uint8 // 计划任务覆盖范围 (0:仅覆盖特定服务器 1:仅忽略特定服务器)
|
LastResult bool // 最后一次执行结果
|
||||||
|
Cover uint8 // 计划任务覆盖范围 (0:仅覆盖特定服务器 1:仅忽略特定服务器)
|
||||||
|
|
||||||
CronJobID cron.EntryID `gorm:"-"`
|
CronJobID cron.EntryID `gorm:"-"`
|
||||||
ServersRaw string
|
ServersRaw string
|
||||||
|
@ -28,6 +28,7 @@ const (
|
|||||||
type Notification struct {
|
type Notification struct {
|
||||||
Common
|
Common
|
||||||
Name string
|
Name string
|
||||||
|
Tag string // 分组名
|
||||||
URL string
|
URL string
|
||||||
RequestMethod int
|
RequestMethod int
|
||||||
RequestType int
|
RequestType int
|
||||||
|
4
resource/template/component/cron.html
vendored
4
resource/template/component/cron.html
vendored
@ -38,6 +38,10 @@
|
|||||||
<label>推送成功的消息</label>
|
<label>推送成功的消息</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>通知方式组</label>
|
||||||
|
<input type="text" name="NotificationTag" placeholder="default">
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div class="ui warning message">
|
<div class="ui warning message">
|
||||||
<p>
|
<p>
|
||||||
|
@ -8,6 +8,10 @@
|
|||||||
<label>名称</label>
|
<label>名称</label>
|
||||||
<input type="text" name="Name">
|
<input type="text" name="Name">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>分组</label>
|
||||||
|
<input type="text" name="Tag">
|
||||||
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>URL</label>
|
<label>URL</label>
|
||||||
<input type="text" name="URL">
|
<input type="text" name="URL">
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
<th>计划</th>
|
<th>计划</th>
|
||||||
<th>命令</th>
|
<th>命令</th>
|
||||||
<th>成功推送</th>
|
<th>成功推送</th>
|
||||||
|
<th>通知方式组</th>
|
||||||
<th>覆盖范围</th>
|
<th>覆盖范围</th>
|
||||||
<th>特定服务器</th>
|
<th>特定服务器</th>
|
||||||
<th>最后执行</th>
|
<th>最后执行</th>
|
||||||
@ -33,6 +34,7 @@
|
|||||||
<td>{{$cron.Scheduler}}</td>
|
<td>{{$cron.Scheduler}}</td>
|
||||||
<td>{{$cron.Command}}</td>
|
<td>{{$cron.Command}}</td>
|
||||||
<td>{{$cron.PushSuccessful}}</td>
|
<td>{{$cron.PushSuccessful}}</td>
|
||||||
|
<td>{$cron.NotificationTag}</td>
|
||||||
<td>{{if eq $cron.Cover 0}}忽略所有{{else}}覆盖所有{{end}}</td>
|
<td>{{if eq $cron.Cover 0}}忽略所有{{else}}覆盖所有{{end}}</td>
|
||||||
<td>{{$cron.ServersRaw}}</td>
|
<td>{{$cron.ServersRaw}}</td>
|
||||||
<td>{{$cron.LastExecutedAt|tf}}</td>
|
<td>{{$cron.LastExecutedAt|tf}}</td>
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
<th>名称</th>
|
<th>名称</th>
|
||||||
|
<th>分组</th>
|
||||||
<th>URL</th>
|
<th>URL</th>
|
||||||
<th>验证SSL</th>
|
<th>验证SSL</th>
|
||||||
<th>管理</th>
|
<th>管理</th>
|
||||||
@ -25,6 +26,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{$notification.ID}}</td>
|
<td>{{$notification.ID}}</td>
|
||||||
<td>{{$notification.Name}}</td>
|
<td>{{$notification.Name}}</td>
|
||||||
|
<td>{{$notification.Tag}}</td>
|
||||||
<td>{{$notification.URL}}</td>
|
<td>{{$notification.URL}}</td>
|
||||||
<td>{{$notification.VerifySSL}}</td>
|
<td>{{$notification.VerifySSL}}</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -29,10 +29,10 @@ func (s *NezhaHandler) ReportTask(c context.Context, r *pb.TaskResult) (*pb.Rece
|
|||||||
singleton.ServerLock.RLock()
|
singleton.ServerLock.RLock()
|
||||||
defer singleton.ServerLock.RUnlock()
|
defer singleton.ServerLock.RUnlock()
|
||||||
if cr.PushSuccessful && r.GetSuccessful() {
|
if cr.PushSuccessful && r.GetSuccessful() {
|
||||||
singleton.SendNotification(fmt.Sprintf("[任务成功] %s ,服务器:%s,日志:\n%s", cr.Name, singleton.ServerList[clientID].Name, r.GetData()), false)
|
singleton.SendNotification(cr.NotificationTag, fmt.Sprintf("[任务成功] %s ,服务器:%s,日志:\n%s", cr.Name, singleton.ServerList[clientID].Name, r.GetData()), false)
|
||||||
}
|
}
|
||||||
if !r.GetSuccessful() {
|
if !r.GetSuccessful() {
|
||||||
singleton.SendNotification(fmt.Sprintf("[任务失败] %s ,服务器:%s,日志:\n%s", cr.Name, singleton.ServerList[clientID].Name, r.GetData()), false)
|
singleton.SendNotification(cr.NotificationTag, fmt.Sprintf("[任务失败] %s ,服务器:%s,日志:\n%s", cr.Name, singleton.ServerList[clientID].Name, r.GetData()), false)
|
||||||
}
|
}
|
||||||
singleton.DB.Model(cr).Updates(model.Cron{
|
singleton.DB.Model(cr).Updates(model.Cron{
|
||||||
LastExecutedAt: time.Now().Add(time.Second * -1 * time.Duration(r.GetDelay())),
|
LastExecutedAt: time.Now().Add(time.Second * -1 * time.Duration(r.GetDelay())),
|
||||||
@ -103,7 +103,7 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
|
|||||||
singleton.ServerList[clientID].Host.IP != "" &&
|
singleton.ServerList[clientID].Host.IP != "" &&
|
||||||
host.IP != "" &&
|
host.IP != "" &&
|
||||||
singleton.ServerList[clientID].Host.IP != host.IP {
|
singleton.ServerList[clientID].Host.IP != host.IP {
|
||||||
singleton.SendNotification(fmt.Sprintf(
|
singleton.SendNotification(singleton.Conf.IPChangeNotificationTag, fmt.Sprintf(
|
||||||
"[IP变更] %s ,旧IP:%s,新IP:%s。",
|
"[IP变更] %s ,旧IP:%s,新IP:%s。",
|
||||||
singleton.ServerList[clientID].Name, singleton.IPDesensitize(singleton.ServerList[clientID].Host.IP), singleton.IPDesensitize(host.IP)), true)
|
singleton.ServerList[clientID].Name, singleton.IPDesensitize(singleton.ServerList[clientID].Host.IP), singleton.IPDesensitize(host.IP)), true)
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
Cron *cron.Cron
|
Cron *cron.Cron
|
||||||
Crons map[uint64]*model.Cron
|
Crons map[uint64]*model.Cron // [CrondID] -> *model.Cron
|
||||||
CronLock sync.RWMutex
|
CronLock sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,9 +27,12 @@ func LoadCronTasks() {
|
|||||||
DB.Find(&crons)
|
DB.Find(&crons)
|
||||||
var err error
|
var err error
|
||||||
errMsg := new(bytes.Buffer)
|
errMsg := new(bytes.Buffer)
|
||||||
for i := 0; i < len(crons); i++ {
|
var notificationTagList []string
|
||||||
cr := crons[i]
|
for _, cr := range crons {
|
||||||
|
// 旧版本计划任务可能不存在通知组 为其添加默认通知组
|
||||||
|
if cr.NotificationTag == "" {
|
||||||
|
AddDefaultCronNotificationTag(&cr)
|
||||||
|
}
|
||||||
// 注册计划任务
|
// 注册计划任务
|
||||||
cr.CronJobID, err = Cron.AddFunc(cr.Scheduler, CronTrigger(cr))
|
cr.CronJobID, err = Cron.AddFunc(cr.Scheduler, CronTrigger(cr))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -39,15 +42,30 @@ func LoadCronTasks() {
|
|||||||
errMsg.WriteString("调度失败的计划任务:[")
|
errMsg.WriteString("调度失败的计划任务:[")
|
||||||
}
|
}
|
||||||
errMsg.WriteString(fmt.Sprintf("%d,", cr.ID))
|
errMsg.WriteString(fmt.Sprintf("%d,", cr.ID))
|
||||||
|
notificationTagList = append(notificationTagList, cr.NotificationTag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if errMsg.Len() > 0 {
|
if errMsg.Len() > 0 {
|
||||||
msg := errMsg.String()
|
msg := errMsg.String() + "] 这些任务将无法正常执行,请进入后点重新修改保存。"
|
||||||
SendNotification(msg[:len(msg)-1]+"] 这些任务将无法正常执行,请进入后点重新修改保存。", false)
|
for _, tag := range notificationTagList {
|
||||||
|
// 向调度错误的计划任务所包含的所有通知组发送通知
|
||||||
|
SendNotification(tag, msg, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Cron.Start()
|
Cron.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddDefaultCronNotificationTag 添加默认的计划任务通知组
|
||||||
|
func AddDefaultCronNotificationTag(c *model.Cron) {
|
||||||
|
CronLock.Lock()
|
||||||
|
defer CronLock.Unlock()
|
||||||
|
|
||||||
|
if c.NotificationTag == "" {
|
||||||
|
c.NotificationTag = "default"
|
||||||
|
}
|
||||||
|
DB.Save(c)
|
||||||
|
}
|
||||||
|
|
||||||
func ManualTrigger(c model.Cron) {
|
func ManualTrigger(c model.Cron) {
|
||||||
CronTrigger(c)()
|
CronTrigger(c)()
|
||||||
}
|
}
|
||||||
@ -74,7 +92,7 @@ func CronTrigger(cr model.Cron) func() {
|
|||||||
Type: model.TaskTypeCommand,
|
Type: model.TaskTypeCommand,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
SendNotification(fmt.Sprintf("[任务失败] %s,服务器 %s 离线,无法执行。", cr.Name, s.Name), false)
|
SendNotification(cr.NotificationTag, fmt.Sprintf("[任务失败] %s,服务器 %s 离线,无法执行。", cr.Name, s.Name), false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,46 +13,95 @@ import (
|
|||||||
const firstNotificationDelay = time.Minute * 15
|
const firstNotificationDelay = time.Minute * 15
|
||||||
|
|
||||||
// 通知方式
|
// 通知方式
|
||||||
var notifications []model.Notification
|
var (
|
||||||
var notificationsLock sync.RWMutex
|
NotificationList map[string]map[uint64]*model.Notification // [NotificationMethodTag][NotificationID] -> model.Notification
|
||||||
|
NotificationIDToTag map[uint64]string // [NotificationID] -> NotificationTag
|
||||||
|
notificationsLock sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
// LoadNotifications 从 DB 加载通知方式到 singleton.notifications 变量
|
// InitNotification 初始化 Tag <-> ID <-> Notification 的映射
|
||||||
|
func InitNotification() {
|
||||||
|
NotificationList = make(map[string]map[uint64]*model.Notification)
|
||||||
|
NotificationIDToTag = make(map[uint64]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadNotifications 从 DB 初始化通知方式相关参数
|
||||||
func LoadNotifications() {
|
func LoadNotifications() {
|
||||||
|
InitNotification()
|
||||||
notificationsLock.Lock()
|
notificationsLock.Lock()
|
||||||
|
defer notificationsLock.Unlock()
|
||||||
|
|
||||||
|
var notifications []model.Notification
|
||||||
if err := DB.Find(¬ifications).Error; err != nil {
|
if err := DB.Find(¬ifications).Error; err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
notificationsLock.Unlock()
|
for _, n := range notifications {
|
||||||
|
// 旧版本的Tag可能不存在 自动设置为默认值
|
||||||
|
if n.Tag == "" {
|
||||||
|
SetDefaultNotificationTagInDB(&n)
|
||||||
|
}
|
||||||
|
AddNotificationToList(&n)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func OnRefreshOrAddNotification(n model.Notification) {
|
// SetDefaultNotificationTagInDB 设置默认通知方式的 Tag
|
||||||
|
func SetDefaultNotificationTagInDB(n *model.Notification) {
|
||||||
|
n.Tag = "default"
|
||||||
|
if err := DB.Save(n).Error; err != nil {
|
||||||
|
log.Println("[ERROR]", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnRefreshOrAddNotification 刷新通知方式相关参数
|
||||||
|
func OnRefreshOrAddNotification(n *model.Notification) {
|
||||||
notificationsLock.Lock()
|
notificationsLock.Lock()
|
||||||
defer notificationsLock.Unlock()
|
defer notificationsLock.Unlock()
|
||||||
|
|
||||||
var isEdit bool
|
var isEdit bool
|
||||||
for i := 0; i < len(notifications); i++ {
|
if _, ok := NotificationList[n.Tag][n.ID]; ok {
|
||||||
if notifications[i].ID == n.ID {
|
isEdit = true
|
||||||
notifications[i] = n
|
|
||||||
isEdit = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !isEdit {
|
if !isEdit {
|
||||||
notifications = append(notifications, n)
|
AddNotificationToList(n)
|
||||||
|
} else {
|
||||||
|
UpdateNotificationInList(n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddNotificationToList 添加通知方式到map中
|
||||||
|
func AddNotificationToList(n *model.Notification) {
|
||||||
|
notificationsLock.Lock()
|
||||||
|
defer notificationsLock.Unlock()
|
||||||
|
|
||||||
|
// 当前 Tag 不存在,创建对应该 Tag 的 子 map 后再添加
|
||||||
|
if _, ok := NotificationList[n.Tag]; !ok {
|
||||||
|
NotificationList[n.Tag] = make(map[uint64]*model.Notification)
|
||||||
|
}
|
||||||
|
NotificationList[n.Tag][n.ID] = n
|
||||||
|
NotificationIDToTag[n.ID] = n.Tag
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNotificationInList 在 map 中更新通知方式
|
||||||
|
func UpdateNotificationInList(n *model.Notification) {
|
||||||
|
notificationsLock.Lock()
|
||||||
|
defer notificationsLock.Unlock()
|
||||||
|
|
||||||
|
NotificationList[n.Tag][n.ID] = n
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnDeleteNotification 在map中删除通知方式
|
||||||
func OnDeleteNotification(id uint64) {
|
func OnDeleteNotification(id uint64) {
|
||||||
notificationsLock.Lock()
|
notificationsLock.Lock()
|
||||||
defer notificationsLock.Unlock()
|
defer notificationsLock.Unlock()
|
||||||
for i := 0; i < len(notifications); i++ {
|
|
||||||
if notifications[i].ID == id {
|
delete(NotificationList[NotificationIDToTag[id]], id)
|
||||||
notifications = append(notifications[:i], notifications[i+1:]...)
|
delete(NotificationIDToTag, id)
|
||||||
i--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendNotification(desc string, muteable bool) {
|
// SendNotification 向指定的通知方式组的所有通知方式发送通知
|
||||||
if muteable {
|
func SendNotification(notificationTag string, desc string, mutable bool) {
|
||||||
|
if mutable {
|
||||||
// 通知防骚扰策略
|
// 通知防骚扰策略
|
||||||
nID := hex.EncodeToString(md5.New().Sum([]byte(desc))) // #nosec
|
nID := hex.EncodeToString(md5.New().Sum([]byte(desc))) // #nosec
|
||||||
var flag bool
|
var flag bool
|
||||||
@ -80,16 +129,17 @@ func SendNotification(desc string, muteable bool) {
|
|||||||
|
|
||||||
if !flag {
|
if !flag {
|
||||||
if Conf.Debug {
|
if Conf.Debug {
|
||||||
log.Println("NEZHA>> 静音的重复通知:", desc, muteable)
|
log.Println("NEZHA>> 静音的重复通知:", desc, mutable)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 发出通知
|
// 向该通知方式组的所有通知方式发出通知
|
||||||
notificationsLock.RLock()
|
notificationsLock.RLock()
|
||||||
defer notificationsLock.RUnlock()
|
defer notificationsLock.RUnlock()
|
||||||
for i := 0; i < len(notifications); i++ {
|
|
||||||
if err := notifications[i].Send(desc); err != nil {
|
for _, n := range NotificationList[notificationTag] {
|
||||||
|
if err := n.Send(desc); err != nil {
|
||||||
log.Println("NEZHA>> 发送通知失败:", err)
|
log.Println("NEZHA>> 发送通知失败:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user