refactor: simplify server & service manipulation (#993)

* refactor: simplify server & service manipulation

* update

* fix

* update for nat, ddns & notification

* chore

* update cron

* update dependencies

* use of function iterators

* update default dns servers
This commit is contained in:
UUBulb
2025-02-21 23:08:12 +08:00
committed by GitHub
parent 21eefde995
commit 91bef2882a
32 changed files with 987 additions and 1083 deletions

View File

@@ -56,10 +56,7 @@ func (a *authHandler) Check(ctx context.Context) (uint64, error) {
return 0, status.Error(codes.Unauthenticated, "客户端 UUID 不合法")
}
singleton.ServerLock.RLock()
clientID, hasID := singleton.ServerUUIDToID[clientUUID]
singleton.ServerLock.RUnlock()
clientID, hasID := singleton.ServerShared.UUIDToID(clientUUID)
if !hasID {
s := model.Server{UUID: clientUUID, Name: petname.Generate(2, "-"), Common: model.Common{
UserID: userId,
@@ -67,14 +64,9 @@ func (a *authHandler) Check(ctx context.Context) (uint64, error) {
if err := singleton.DB.Create(&s).Error; err != nil {
return 0, status.Error(codes.Unauthenticated, err.Error())
}
model.InitServer(&s)
singleton.ServerLock.Lock()
singleton.ServerList[s.ID] = &s
singleton.ServerUUIDToID[clientUUID] = s.ID
singleton.ServerLock.Unlock()
singleton.ReSortServer()
singleton.ServerShared.Update(&s, clientUUID)
clientID = s.ID
}

View File

@@ -44,10 +44,8 @@ func (s *NezhaHandler) RequestTask(stream pb.NezhaService_RequestTaskServer) err
return err
}
singleton.ServerLock.Lock()
singleton.ServerList[clientID].TaskStream = stream
singleton.ServerLock.Unlock()
server, _ := singleton.ServerShared.Get(clientID)
server.TaskStream = stream
var result *pb.TaskResult
for {
result, err = stream.Recv()
@@ -58,22 +56,18 @@ func (s *NezhaHandler) RequestTask(stream pb.NezhaService_RequestTaskServer) err
switch result.GetType() {
case model.TaskTypeCommand:
// 处理上报的计划任务
singleton.CronLock.RLock()
cr := singleton.Crons[result.GetId()]
singleton.CronLock.RUnlock()
cr, _ := singleton.CronShared.Get(result.GetId())
if cr != nil {
// 保存当前服务器状态信息
var curServer model.Server
singleton.ServerLock.RLock()
copier.Copy(&curServer, singleton.ServerList[clientID])
singleton.ServerLock.RUnlock()
copier.Copy(&curServer, server)
if cr.PushSuccessful && result.GetSuccessful() {
singleton.SendNotification(cr.NotificationGroupID, fmt.Sprintf("[%s] %s, %s\n%s", singleton.Localizer.T("Scheduled Task Executed Successfully"),
cr.Name, singleton.ServerList[clientID].Name, result.GetData()), nil, &curServer)
singleton.NotificationShared.SendNotification(cr.NotificationGroupID, fmt.Sprintf("[%s] %s, %s\n%s", singleton.Localizer.T("Scheduled Task Executed Successfully"),
cr.Name, server.Name, result.GetData()), nil, &curServer)
}
if !result.GetSuccessful() {
singleton.SendNotification(cr.NotificationGroupID, fmt.Sprintf("[%s] %s, %s\n%s", singleton.Localizer.T("Scheduled Task Executed Failed"),
cr.Name, singleton.ServerList[clientID].Name, result.GetData()), nil, &curServer)
singleton.NotificationShared.SendNotification(cr.NotificationGroupID, fmt.Sprintf("[%s] %s, %s\n%s", singleton.Localizer.T("Scheduled Task Executed Failed"),
cr.Name, server.Name, result.GetData()), nil, &curServer)
}
singleton.DB.Model(cr).Updates(model.Cron{
LastExecutedAt: time.Now().Add(time.Second * -1 * time.Duration(result.GetDelay())),
@@ -81,16 +75,13 @@ func (s *NezhaHandler) RequestTask(stream pb.NezhaService_RequestTaskServer) err
})
}
case model.TaskTypeReportConfig:
singleton.ServerLock.RLock()
if len(singleton.ServerList[clientID].ConfigCache) < 1 {
if len(server.ConfigCache) < 1 {
if !result.GetSuccessful() {
singleton.ServerList[clientID].ConfigCache <- errors.New(result.Data)
singleton.ServerLock.RUnlock()
server.ConfigCache <- errors.New(result.Data)
continue
}
singleton.ServerList[clientID].ConfigCache <- result.Data
server.ConfigCache <- result.Data
}
singleton.ServerLock.RUnlock()
default:
if model.IsServiceSentinelNeeded(result.GetType()) {
singleton.ServiceSentinelShared.Dispatch(singleton.ReportData{
@@ -117,10 +108,7 @@ func (s *NezhaHandler) ReportSystemState(stream pb.NezhaService_ReportSystemStat
}
state := model.PB2State(state)
singleton.ServerLock.RLock()
server, ok := singleton.ServerList[clientID]
singleton.ServerLock.RUnlock()
server, ok := singleton.ServerShared.Get(clientID)
if !ok || server == nil {
return nil
}
@@ -145,10 +133,7 @@ func (s *NezhaHandler) onReportSystemInfo(c context.Context, r *pb.Host) error {
}
host := model.PB2Host(r)
singleton.ServerLock.RLock()
defer singleton.ServerLock.RUnlock()
server, ok := singleton.ServerList[clientID]
server, ok := singleton.ServerShared.Get(clientID)
if !ok || server == nil {
return fmt.Errorf("server not found")
}
@@ -234,9 +219,7 @@ func (s *NezhaHandler) ReportGeoIP(c context.Context, r *pb.GeoIP) (*pb.GeoIP, e
joinedIP := geoip.IP.Join()
singleton.ServerLock.RLock()
server, ok := singleton.ServerList[clientID]
singleton.ServerLock.RUnlock()
server, ok := singleton.ServerShared.Get(clientID)
if !ok || server == nil {
return nil, fmt.Errorf("server not found")
}
@@ -247,7 +230,7 @@ func (s *NezhaHandler) ReportGeoIP(c context.Context, r *pb.GeoIP) (*pb.GeoIP, e
ipv4 := geoip.IP.IPv4Addr
ipv6 := geoip.IP.IPv6Addr
providers, err := singleton.GetDDNSProvidersFromProfiles(server.DDNSProfiles, &ddns.IP{Ipv4Addr: ipv4, Ipv6Addr: ipv6})
providers, err := singleton.DDNSShared.GetDDNSProvidersFromProfiles(server.DDNSProfiles, &model.IP{IPv4Addr: ipv4, IPv6Addr: ipv6})
if err == nil {
for _, provider := range providers {
domains := server.OverrideDDNSDomains[provider.GetProfileID()]
@@ -268,7 +251,7 @@ func (s *NezhaHandler) ReportGeoIP(c context.Context, r *pb.GeoIP) (*pb.GeoIP, e
joinedIP != "" &&
server.GeoIP.IP != geoip.IP {
singleton.SendNotification(singleton.Conf.IPChangeNotificationGroupID,
singleton.NotificationShared.SendNotification(singleton.Conf.IPChangeNotificationGroupID,
fmt.Sprintf(
"[%s] %s, %s => %s",
singleton.Localizer.T("IP Changed"),

View File

@@ -132,15 +132,14 @@ func OnDeleteAlert(id []uint64) {
func checkStatus() {
AlertsLock.RLock()
defer AlertsLock.RUnlock()
ServerLock.RLock()
defer ServerLock.RUnlock()
m := ServerShared.GetList()
for _, alert := range Alerts {
// 跳过未启用
if !alert.Enabled() {
continue
}
for _, server := range ServerList {
for _, server := range m {
// 监测点
UserLock.RLock()
var role uint8
@@ -168,20 +167,20 @@ func checkStatus() {
alertsPrevState[alert.ID][server.ID] = _RuleCheckFail
message := fmt.Sprintf("[%s] %s(%s) %s", Localizer.T("Incident"),
server.Name, IPDesensitize(server.GeoIP.IP.Join()), alert.Name)
go SendTriggerTasks(alert.FailTriggerTasks, curServer.ID)
go SendNotification(alert.NotificationGroupID, message, NotificationMuteLabel.ServerIncident(server.ID, alert.ID), &curServer)
go CronShared.SendTriggerTasks(alert.FailTriggerTasks, curServer.ID)
go NotificationShared.SendNotification(alert.NotificationGroupID, message, NotificationMuteLabel.ServerIncident(server.ID, alert.ID), &curServer)
// 清除恢复通知的静音缓存
UnMuteNotification(alert.NotificationGroupID, NotificationMuteLabel.ServerIncidentResolved(server.ID, alert.ID))
NotificationShared.UnMuteNotification(alert.NotificationGroupID, NotificationMuteLabel.ServerIncidentResolved(server.ID, alert.ID))
}
} else {
// 本次通过检查但上一次的状态为失败,则发送恢复通知
if alertsPrevState[alert.ID][server.ID] == _RuleCheckFail {
message := fmt.Sprintf("[%s] %s(%s) %s", Localizer.T("Resolved"),
server.Name, IPDesensitize(server.GeoIP.IP.Join()), alert.Name)
go SendTriggerTasks(alert.RecoverTriggerTasks, curServer.ID)
go SendNotification(alert.NotificationGroupID, message, NotificationMuteLabel.ServerIncidentResolved(server.ID, alert.ID), &curServer)
go CronShared.SendTriggerTasks(alert.RecoverTriggerTasks, curServer.ID)
go NotificationShared.SendNotification(alert.NotificationGroupID, message, NotificationMuteLabel.ServerIncidentResolved(server.ID, alert.ID), &curServer)
// 清除失败通知的静音缓存
UnMuteNotification(alert.NotificationGroupID, NotificationMuteLabel.ServerIncident(server.ID, alert.ID))
NotificationShared.UnMuteNotification(alert.NotificationGroupID, NotificationMuteLabel.ServerIncident(server.ID, alert.ID))
}
alertsPrevState[alert.ID][server.ID] = _RuleCheckPass
}

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"slices"
"strings"
"sync"
"github.com/jinzhu/copier"
@@ -16,36 +15,32 @@ import (
pb "github.com/nezhahq/nezha/proto"
)
var (
Cron *cron.Cron
Crons map[uint64]*model.Cron // [CronID] -> *model.Cron
CronLock sync.RWMutex
CronList []*model.Cron
)
func InitCronTask() {
Cron = cron.New(cron.WithSeconds(), cron.WithLocation(Loc))
Crons = make(map[uint64]*model.Cron)
type CronClass struct {
class[uint64, *model.Cron]
*cron.Cron
}
// loadCronTasks 加载计划任务
func loadCronTasks() {
InitCronTask()
DB.Find(&CronList)
func NewCronClass() *CronClass {
cronx := cron.New(cron.WithSeconds(), cron.WithLocation(Loc))
list := make(map[uint64]*model.Cron)
var sortedList []*model.Cron
DB.Find(&sortedList)
var err error
var notificationGroupList []uint64
notificationMsgMap := make(map[uint64]*strings.Builder)
for _, cron := range CronList {
for _, cron := range sortedList {
// 触发任务类型无需注册
if cron.TaskType == model.CronTypeTriggerTask {
Crons[cron.ID] = cron
list[cron.ID] = cron
continue
}
// 注册计划任务
cron.CronJobID, err = Cron.AddFunc(cron.Scheduler, CronTrigger(cron))
cron.CronJobID, err = cronx.AddFunc(cron.Scheduler, CronTrigger(cron))
if err == nil {
Crons[cron.ID] = cron
list[cron.ID] = cron
} else {
// 当前通知组首次出现 将其加入通知组列表并初始化通知组消息缓存
if _, ok := notificationMsgMap[cron.NotificationGroupID]; !ok {
@@ -56,61 +51,74 @@ func loadCronTasks() {
notificationMsgMap[cron.NotificationGroupID].WriteString(fmt.Sprintf("%d,", cron.ID))
}
}
// 向注册错误的计划任务所在通知组发送通知
for _, gid := range notificationGroupList {
notificationMsgMap[gid].WriteString(Localizer.T("] These tasks will not execute properly. Fix them in the admin dashboard."))
SendNotification(gid, notificationMsgMap[gid].String(), nil)
NotificationShared.SendNotification(gid, notificationMsgMap[gid].String(), nil)
}
cronx.Start()
return &CronClass{
class: class[uint64, *model.Cron]{
list: list,
sortedList: sortedList,
},
Cron: cronx,
}
Cron.Start()
}
func OnRefreshOrAddCron(c *model.Cron) {
CronLock.Lock()
defer CronLock.Unlock()
crOld := Crons[c.ID]
func (c *CronClass) Update(cr *model.Cron) {
c.listMu.Lock()
crOld := c.list[cr.ID]
if crOld != nil && crOld.CronJobID != 0 {
Cron.Remove(crOld.CronJobID)
c.Cron.Remove(crOld.CronJobID)
}
delete(Crons, c.ID)
Crons[c.ID] = c
delete(c.list, cr.ID)
c.list[cr.ID] = cr
c.listMu.Unlock()
c.sortList()
}
func UpdateCronList() {
CronLock.RLock()
defer CronLock.RUnlock()
func (c *CronClass) Delete(idList []uint64) {
c.listMu.Lock()
for _, id := range idList {
cr := c.list[id]
if cr != nil && cr.CronJobID != 0 {
c.Cron.Remove(cr.CronJobID)
}
delete(c.list, id)
}
c.listMu.Unlock()
CronList = utils.MapValuesToSlice(Crons)
slices.SortFunc(CronList, func(a, b *model.Cron) int {
c.sortList()
}
func (c *CronClass) sortList() {
c.listMu.RLock()
defer c.listMu.RUnlock()
sortedList := utils.MapValuesToSlice(c.list)
slices.SortFunc(sortedList, func(a, b *model.Cron) int {
return cmp.Compare(a.ID, b.ID)
})
c.sortedListMu.Lock()
defer c.sortedListMu.Unlock()
c.sortedList = sortedList
}
func OnDeleteCron(id []uint64) {
CronLock.Lock()
defer CronLock.Unlock()
for _, i := range id {
cr := Crons[i]
if cr != nil && cr.CronJobID != 0 {
Cron.Remove(cr.CronJobID)
}
delete(Crons, i)
}
}
func ManualTrigger(c *model.Cron) {
CronTrigger(c)()
}
func SendTriggerTasks(taskIDs []uint64, triggerServer uint64) {
CronLock.RLock()
func (c *CronClass) SendTriggerTasks(taskIDs []uint64, triggerServer uint64) {
c.listMu.RLock()
var cronLists []*model.Cron
for _, taskID := range taskIDs {
if c, ok := Crons[taskID]; ok {
if c, ok := c.list[taskID]; ok {
cronLists = append(cronLists, c)
}
}
CronLock.RUnlock()
c.listMu.RUnlock()
// 依次调用CronTrigger发送任务
for _, c := range cronLists {
@@ -118,6 +126,10 @@ func SendTriggerTasks(taskIDs []uint64, triggerServer uint64) {
}
}
func ManualTrigger(cr *model.Cron) {
CronTrigger(cr)()
}
func CronTrigger(cr *model.Cron, triggerServer ...uint64) func() {
crIgnoreMap := make(map[uint64]bool)
for j := 0; j < len(cr.Servers); j++ {
@@ -128,9 +140,7 @@ func CronTrigger(cr *model.Cron, triggerServer ...uint64) func() {
if len(triggerServer) == 0 {
return
}
ServerLock.RLock()
defer ServerLock.RUnlock()
if s, ok := ServerList[triggerServer[0]]; ok {
if s, ok := ServerShared.Get(triggerServer[0]); ok {
if s.TaskStream != nil {
s.TaskStream.Send(&pb.Task{
Id: cr.ID,
@@ -141,15 +151,13 @@ func CronTrigger(cr *model.Cron, triggerServer ...uint64) func() {
// 保存当前服务器状态信息
curServer := model.Server{}
copier.Copy(&curServer, s)
SendNotification(cr.NotificationGroupID, Localizer.Tf("[Task failed] %s: server %s is offline and cannot execute the task", cr.Name, s.Name), nil, &curServer)
NotificationShared.SendNotification(cr.NotificationGroupID, Localizer.Tf("[Task failed] %s: server %s is offline and cannot execute the task", cr.Name, s.Name), nil, &curServer)
}
}
return
}
ServerLock.RLock()
defer ServerLock.RUnlock()
for _, s := range ServerList {
for _, s := range ServerShared.Range {
if cr.Cover == model.CronCoverAll && crIgnoreMap[s.ID] {
continue
}
@@ -166,7 +174,7 @@ func CronTrigger(cr *model.Cron, triggerServer ...uint64) func() {
// 保存当前服务器状态信息
curServer := model.Server{}
copier.Copy(&curServer, s)
SendNotification(cr.NotificationGroupID, Localizer.Tf("[Task failed] %s: server %s is offline and cannot execute the task", cr.Name, s.Name), nil, &curServer)
NotificationShared.SendNotification(cr.NotificationGroupID, Localizer.Tf("[Task failed] %s: server %s is offline and cannot execute the task", cr.Name, s.Name), nil, &curServer)
}
}
}

View File

@@ -4,7 +4,6 @@ import (
"cmp"
"fmt"
"slices"
"sync"
"github.com/libdns/cloudflare"
tencentcloud "github.com/nezhahq/libdns-tencentcloud"
@@ -16,67 +15,61 @@ import (
"github.com/nezhahq/nezha/pkg/utils"
)
var (
DDNSCache map[uint64]*model.DDNSProfile
DDNSCacheLock sync.RWMutex
DDNSList []*model.DDNSProfile
DDNSListLock sync.RWMutex
)
type DDNSClass struct {
class[uint64, *model.DDNSProfile]
}
func initDDNS() {
DB.Find(&DDNSList)
DDNSCache = make(map[uint64]*model.DDNSProfile)
for i := 0; i < len(DDNSList); i++ {
DDNSCache[DDNSList[i].ID] = DDNSList[i]
func NewDDNSClass() *DDNSClass {
var sortedList []*model.DDNSProfile
DB.Find(&sortedList)
list := make(map[uint64]*model.DDNSProfile, len(sortedList))
for _, profile := range sortedList {
list[profile.ID] = profile
}
dc := &DDNSClass{
class: class[uint64, *model.DDNSProfile]{
list: list,
sortedList: sortedList,
},
}
OnNameserverUpdate()
return dc
}
func OnDDNSUpdate(p *model.DDNSProfile) {
DDNSCacheLock.Lock()
defer DDNSCacheLock.Unlock()
DDNSCache[p.ID] = p
func (c *DDNSClass) Update(p *model.DDNSProfile) {
c.listMu.Lock()
c.list[p.ID] = p
c.listMu.Unlock()
c.sortList()
}
func OnDDNSDelete(id []uint64) {
DDNSCacheLock.Lock()
defer DDNSCacheLock.Unlock()
for _, i := range id {
delete(DDNSCache, i)
func (c *DDNSClass) Delete(idList []uint64) {
c.listMu.Lock()
for _, id := range idList {
delete(c.list, id)
}
c.listMu.Unlock()
c.sortList()
}
func UpdateDDNSList() {
DDNSCacheLock.RLock()
defer DDNSCacheLock.RUnlock()
DDNSListLock.Lock()
defer DDNSListLock.Unlock()
DDNSList = utils.MapValuesToSlice(DDNSCache)
slices.SortFunc(DDNSList, func(a, b *model.DDNSProfile) int {
return cmp.Compare(a.ID, b.ID)
})
}
func OnNameserverUpdate() {
ddns2.InitDNSServers(Conf.DNSServers)
}
func GetDDNSProvidersFromProfiles(profileId []uint64, ip *ddns2.IP) ([]*ddns2.Provider, error) {
func (c *DDNSClass) GetDDNSProvidersFromProfiles(profileId []uint64, ip *model.IP) ([]*ddns2.Provider, error) {
profiles := make([]*model.DDNSProfile, 0, len(profileId))
DDNSCacheLock.RLock()
c.listMu.RLock()
for _, id := range profileId {
if profile, ok := DDNSCache[id]; ok {
if profile, ok := c.list[id]; ok {
profiles = append(profiles, profile)
} else {
DDNSCacheLock.RUnlock()
c.listMu.RUnlock()
return nil, fmt.Errorf("无法找到DDNS配置 ID %d", id)
}
}
DDNSCacheLock.RUnlock()
c.listMu.RUnlock()
providers := make([]*ddns2.Provider, 0, len(profiles))
for _, profile := range profiles {
@@ -100,3 +93,21 @@ func GetDDNSProvidersFromProfiles(profileId []uint64, ip *ddns2.IP) ([]*ddns2.Pr
}
return providers, nil
}
func (c *DDNSClass) sortList() {
c.listMu.RLock()
defer c.listMu.RUnlock()
sortedList := utils.MapValuesToSlice(c.list)
slices.SortFunc(sortedList, func(a, b *model.DDNSProfile) int {
return cmp.Compare(a.ID, b.ID)
})
c.sortedListMu.Lock()
defer c.sortedListMu.Unlock()
c.sortedList = sortedList
}
func OnNameserverUpdate() {
ddns2.InitDNSServers(Conf.DNSServers)
}

View File

@@ -3,69 +3,89 @@ package singleton
import (
"cmp"
"slices"
"sync"
"github.com/nezhahq/nezha/model"
"github.com/nezhahq/nezha/pkg/utils"
)
var (
NATCache = make(map[string]*model.NAT)
NATCacheRwLock sync.RWMutex
type NATClass struct {
class[string, *model.NAT]
NATIDToDomain = make(map[uint64]string)
NATList []*model.NAT
NATListLock sync.RWMutex
)
idToDomain map[uint64]string
}
func initNAT() {
DB.Find(&NATList)
NATCache = make(map[string]*model.NAT)
for i := 0; i < len(NATList); i++ {
NATCache[NATList[i].Domain] = NATList[i]
NATIDToDomain[NATList[i].ID] = NATList[i].Domain
func NewNATClass() *NATClass {
var sortedList []*model.NAT
DB.Find(&sortedList)
list := make(map[string]*model.NAT, len(sortedList))
idToDomain := make(map[uint64]string, len(sortedList))
for _, profile := range list {
list[profile.Domain] = profile
idToDomain[profile.ID] = profile.Domain
}
return &NATClass{
class: class[string, *model.NAT]{
list: list,
sortedList: sortedList,
},
idToDomain: idToDomain,
}
}
func OnNATUpdate(n *model.NAT) {
NATCacheRwLock.Lock()
defer NATCacheRwLock.Unlock()
func (c *NATClass) Update(n *model.NAT) {
c.listMu.Lock()
if oldDomain, ok := NATIDToDomain[n.ID]; ok && oldDomain != n.Domain {
delete(NATCache, oldDomain)
if oldDomain, ok := c.idToDomain[n.ID]; ok && oldDomain != n.Domain {
delete(c.list, oldDomain)
}
NATCache[n.Domain] = n
NATIDToDomain[n.ID] = n.Domain
c.list[n.Domain] = n
c.idToDomain[n.ID] = n.Domain
c.listMu.Unlock()
c.sortList()
}
func OnNATDelete(id []uint64) {
NATCacheRwLock.Lock()
defer NATCacheRwLock.Unlock()
func (c *NATClass) Delete(idList []uint64) {
c.listMu.Lock()
for _, i := range id {
if domain, ok := NATIDToDomain[i]; ok {
delete(NATCache, domain)
delete(NATIDToDomain, i)
for _, id := range idList {
if domain, ok := c.idToDomain[id]; ok {
delete(c.list, domain)
delete(c.idToDomain, id)
}
}
c.listMu.Unlock()
c.sortList()
}
func UpdateNATList() {
NATCacheRwLock.RLock()
defer NATCacheRwLock.RUnlock()
func (c *NATClass) GetNATConfigByDomain(domain string) *model.NAT {
c.listMu.RLock()
defer c.listMu.RUnlock()
NATListLock.Lock()
defer NATListLock.Unlock()
return c.list[domain]
}
NATList = utils.MapValuesToSlice(NATCache)
slices.SortFunc(NATList, func(a, b *model.NAT) int {
func (c *NATClass) GetDomain(id uint64) string {
c.listMu.RLock()
defer c.listMu.RUnlock()
return c.idToDomain[id]
}
func (c *NATClass) sortList() {
c.listMu.RLock()
defer c.listMu.RUnlock()
sortedList := utils.MapValuesToSlice(c.list)
slices.SortFunc(sortedList, func(a, b *model.NAT) int {
return cmp.Compare(a.ID, b.ID)
})
}
func GetNATConfigByDomain(domain string) *model.NAT {
NATCacheRwLock.RLock()
defer NATCacheRwLock.RUnlock()
return NATCache[domain]
c.sortedListMu.Lock()
defer c.sortedListMu.Unlock()
c.sortedList = sortedList
}

View File

@@ -16,217 +16,193 @@ const (
firstNotificationDelay = time.Minute * 15
)
// 通知方式
var (
NotificationList map[uint64]map[uint64]*model.Notification // [NotificationGroupID][NotificationID] -> model.Notification
NotificationIDToGroups map[uint64]map[uint64]struct{} // [NotificationID] -> NotificationGroupID
type NotificationClass struct {
class[uint64, *model.Notification]
NotificationMap map[uint64]*model.Notification
NotificationListSorted []*model.Notification
NotificationGroup map[uint64]string // [NotificationGroupID] -> [NotificationGroupName]
groupToIDList map[uint64]map[uint64]*model.Notification
idToGroupList map[uint64]map[uint64]struct{}
NotificationsLock sync.RWMutex
NotificationSortedLock sync.RWMutex
NotificationGroupLock sync.RWMutex
)
// InitNotification 初始化 GroupID <-> ID <-> Notification 的映射
func initNotification() {
NotificationList = make(map[uint64]map[uint64]*model.Notification)
NotificationIDToGroups = make(map[uint64]map[uint64]struct{})
NotificationGroup = make(map[uint64]string)
groupList map[uint64]string
groupMu sync.RWMutex
}
// loadNotifications 从 DB 初始化通知方式相关参数
func loadNotifications() {
initNotification()
func NewNotificationClass() *NotificationClass {
var sortedList []*model.Notification
groupToIDList := make(map[uint64]map[uint64]*model.Notification)
idToGroupList := make(map[uint64]map[uint64]struct{})
groupNotifications := make(map[uint64][]uint64)
var ngn []model.NotificationGroupNotification
if err := DB.Find(&ngn).Error; err != nil {
panic(err)
}
DB.Find(&ngn)
for _, n := range ngn {
groupNotifications[n.NotificationGroupID] = append(groupNotifications[n.NotificationGroupID], n.NotificationID)
}
if err := DB.Find(&NotificationListSorted).Error; err != nil {
panic(err)
DB.Find(&sortedList)
list := make(map[uint64]*model.Notification, len(sortedList))
for _, n := range sortedList {
list[n.ID] = n
}
var groups []model.NotificationGroup
DB.Find(&groups)
groupList := make(map[uint64]string)
for _, grp := range groups {
NotificationGroup[grp.ID] = grp.Name
}
NotificationMap = make(map[uint64]*model.Notification, len(NotificationListSorted))
for i := range NotificationListSorted {
NotificationMap[NotificationListSorted[i].ID] = NotificationListSorted[i]
groupList[grp.ID] = grp.Name
}
for gid, nids := range groupNotifications {
NotificationList[gid] = make(map[uint64]*model.Notification)
groupToIDList[gid] = make(map[uint64]*model.Notification)
for _, nid := range nids {
if n, ok := NotificationMap[nid]; ok {
NotificationList[gid][n.ID] = n
if n, ok := list[nid]; ok {
groupToIDList[gid][n.ID] = n
if NotificationIDToGroups[n.ID] == nil {
NotificationIDToGroups[n.ID] = make(map[uint64]struct{})
if idToGroupList[n.ID] == nil {
idToGroupList[n.ID] = make(map[uint64]struct{})
}
NotificationIDToGroups[n.ID][gid] = struct{}{}
idToGroupList[n.ID][gid] = struct{}{}
}
}
}
nc := &NotificationClass{
class: class[uint64, *model.Notification]{
list: list,
sortedList: sortedList,
},
groupToIDList: groupToIDList,
idToGroupList: idToGroupList,
groupList: groupList,
}
return nc
}
func UpdateNotificationList() {
NotificationsLock.RLock()
defer NotificationsLock.RUnlock()
func (c *NotificationClass) Update(n *model.Notification) {
c.listMu.Lock()
NotificationSortedLock.Lock()
defer NotificationSortedLock.Unlock()
_, ok := c.list[n.ID]
c.list[n.ID] = n
NotificationListSorted = utils.MapValuesToSlice(NotificationMap)
slices.SortFunc(NotificationListSorted, func(a, b *model.Notification) int {
return cmp.Compare(a.ID, b.ID)
})
}
// OnRefreshOrAddNotificationGroup 刷新通知方式组相关参数
func OnRefreshOrAddNotificationGroup(ng *model.NotificationGroup, ngn []uint64) {
NotificationsLock.Lock()
defer NotificationsLock.Unlock()
NotificationGroupLock.Lock()
defer NotificationGroupLock.Unlock()
var isEdit bool
if _, ok := NotificationGroup[ng.ID]; ok {
isEdit = true
if ok {
if gids, ok := c.idToGroupList[n.ID]; ok {
for gid := range gids {
c.groupToIDList[gid][n.ID] = n
}
}
}
if !isEdit {
AddNotificationGroupToList(ng, ngn)
c.listMu.Unlock()
c.sortList()
}
func (c *NotificationClass) UpdateGroup(ng *model.NotificationGroup, ngn []uint64) {
c.groupMu.Lock()
defer c.groupMu.Unlock()
_, ok := c.groupList[ng.ID]
c.groupList[ng.ID] = ng.Name
c.listMu.Lock()
defer c.listMu.Unlock()
if !ok {
c.groupToIDList[ng.ID] = make(map[uint64]*model.Notification, len(ngn))
for _, n := range ngn {
if c.idToGroupList[n] == nil {
c.idToGroupList[n] = make(map[uint64]struct{})
}
c.idToGroupList[n][ng.ID] = struct{}{}
c.groupToIDList[ng.ID][n] = c.list[n]
}
} else {
UpdateNotificationGroupInList(ng, ngn)
}
}
// AddNotificationGroupToList 添加通知方式组到map中
func AddNotificationGroupToList(ng *model.NotificationGroup, ngn []uint64) {
NotificationGroup[ng.ID] = ng.Name
NotificationList[ng.ID] = make(map[uint64]*model.Notification, len(ngn))
for _, n := range ngn {
if NotificationIDToGroups[n] == nil {
NotificationIDToGroups[n] = make(map[uint64]struct{})
oldList := make(map[uint64]struct{})
for nid := range c.groupToIDList[ng.ID] {
oldList[nid] = struct{}{}
}
NotificationIDToGroups[n][ng.ID] = struct{}{}
NotificationList[ng.ID][n] = NotificationMap[n]
}
}
// UpdateNotificationGroupInList 在 map 中更新通知方式组
func UpdateNotificationGroupInList(ng *model.NotificationGroup, ngn []uint64) {
NotificationGroup[ng.ID] = ng.Name
oldList := make(map[uint64]struct{})
for nid := range NotificationList[ng.ID] {
oldList[nid] = struct{}{}
}
NotificationList[ng.ID] = make(map[uint64]*model.Notification)
for _, nid := range ngn {
NotificationList[ng.ID][nid] = NotificationMap[nid]
if NotificationIDToGroups[nid] == nil {
NotificationIDToGroups[nid] = make(map[uint64]struct{})
c.groupToIDList[ng.ID] = make(map[uint64]*model.Notification)
for _, nid := range ngn {
c.groupToIDList[ng.ID][nid] = c.list[nid]
if c.idToGroupList[nid] == nil {
c.idToGroupList[nid] = make(map[uint64]struct{})
}
c.idToGroupList[nid][ng.ID] = struct{}{}
}
NotificationIDToGroups[nid][ng.ID] = struct{}{}
}
for oldID := range oldList {
if _, ok := NotificationList[ng.ID][oldID]; !ok {
delete(NotificationIDToGroups[oldID], ng.ID)
if len(NotificationIDToGroups[oldID]) == 0 {
delete(NotificationIDToGroups, oldID)
for oldID := range oldList {
if _, ok := c.groupToIDList[ng.ID][oldID]; !ok {
delete(c.groupToIDList[oldID], ng.ID)
if len(c.idToGroupList[oldID]) == 0 {
delete(c.idToGroupList, oldID)
}
}
}
}
}
// UpdateNotificationGroupInList 删除通知方式组
func OnDeleteNotificationGroup(gids []uint64) {
NotificationsLock.Lock()
defer NotificationsLock.Unlock()
func (c *NotificationClass) Delete(idList []uint64) {
c.listMu.Lock()
for _, id := range idList {
delete(c.list, id)
// 如果绑定了通知组才删除
if gids, ok := c.idToGroupList[id]; ok {
for gid := range gids {
delete(c.groupToIDList[gid], id)
delete(c.idToGroupList, id)
}
}
}
c.listMu.Unlock()
c.sortList()
}
func (c *NotificationClass) DeleteGroup(gids []uint64) {
c.listMu.Lock()
defer c.listMu.Unlock()
c.groupMu.Lock()
defer c.groupMu.Unlock()
for _, gid := range gids {
delete(NotificationGroup, gid)
delete(NotificationList, gid)
delete(c.groupList, gid)
delete(c.groupToIDList, gid)
}
}
// OnRefreshOrAddNotification 刷新通知方式相关参数
func OnRefreshOrAddNotification(n *model.Notification) {
NotificationsLock.Lock()
defer NotificationsLock.Unlock()
func (c *NotificationClass) GetGroupName(gid uint64) string {
c.groupMu.RLock()
defer c.groupMu.RUnlock()
var isEdit bool
_, ok := NotificationMap[n.ID]
if ok {
isEdit = true
}
if !isEdit {
AddNotificationToList(n)
} else {
UpdateNotificationInList(n)
}
return c.groupList[gid]
}
// AddNotificationToList 添加通知方式到map中
func AddNotificationToList(n *model.Notification) {
NotificationMap[n.ID] = n
func (c *NotificationClass) sortList() {
c.listMu.RLock()
defer c.listMu.RUnlock()
sortedList := utils.MapValuesToSlice(c.list)
slices.SortFunc(sortedList, func(a, b *model.Notification) int {
return cmp.Compare(a.ID, b.ID)
})
c.sortedListMu.Lock()
defer c.sortedListMu.Unlock()
c.sortedList = sortedList
}
// UpdateNotificationInList 在 map 中更新通知方式
func UpdateNotificationInList(n *model.Notification) {
NotificationMap[n.ID] = n
// 如果已经与通知组有绑定关系,更新
if gids, ok := NotificationIDToGroups[n.ID]; ok {
for gid := range gids {
NotificationList[gid][n.ID] = n
}
}
}
// OnDeleteNotification 在map和表中删除通知方式
func OnDeleteNotification(id []uint64) {
NotificationsLock.Lock()
defer NotificationsLock.Unlock()
for _, i := range id {
delete(NotificationMap, i)
// 如果绑定了通知组才删除
if gids, ok := NotificationIDToGroups[i]; ok {
for gid := range gids {
delete(NotificationList[gid], i)
delete(NotificationIDToGroups, i)
}
}
}
}
func UnMuteNotification(notificationGroupID uint64, muteLabel *string) {
fullMuteLabel := *NotificationMuteLabel.AppendNotificationGroupName(muteLabel, notificationGroupID)
func (c *NotificationClass) UnMuteNotification(notificationGroupID uint64, muteLabel *string) {
fullMuteLabel := *NotificationMuteLabel.AppendNotificationGroupName(muteLabel, c.GetGroupName(notificationGroupID))
Cache.Delete(fullMuteLabel)
}
// SendNotification 向指定的通知方式组的所有通知方式发送通知
func SendNotification(notificationGroupID uint64, desc string, muteLabel *string, ext ...*model.Server) {
func (c *NotificationClass) SendNotification(notificationGroupID uint64, desc string, muteLabel *string, ext ...*model.Server) {
if muteLabel != nil {
// 将通知方式组名称加入静音标志
muteLabel := *NotificationMuteLabel.AppendNotificationGroupName(muteLabel, notificationGroupID)
muteLabel := *NotificationMuteLabel.AppendNotificationGroupName(muteLabel, c.GetGroupName(notificationGroupID))
// 通知防骚扰策略
var flag bool
if cacheN, has := Cache.Get(muteLabel); has {
@@ -259,12 +235,12 @@ func SendNotification(notificationGroupID uint64, desc string, muteLabel *string
}
}
// 向该通知方式组的所有通知方式发出通知
NotificationsLock.RLock()
defer NotificationsLock.RUnlock()
for _, n := range NotificationList[notificationGroupID] {
c.listMu.RLock()
defer c.listMu.RUnlock()
for _, n := range c.groupToIDList[notificationGroupID] {
log.Printf("NEZHA>> Try to notify %s", n.Name)
}
for _, n := range NotificationList[notificationGroupID] {
for _, n := range c.groupToIDList[notificationGroupID] {
ns := model.NotificationServerBundle{
Notification: n,
Server: nil,
@@ -300,10 +276,8 @@ func (_NotificationMuteLabel) ServerIncidentResolved(alertId uint64, serverId ui
return &label
}
func (_NotificationMuteLabel) AppendNotificationGroupName(label *string, notificationGroupID uint64) *string {
NotificationGroupLock.RLock()
defer NotificationGroupLock.RUnlock()
newLabel := fmt.Sprintf("%s:%s", *label, NotificationGroup[notificationGroupID])
func (_NotificationMuteLabel) AppendNotificationGroupName(label *string, notificationGroupName string) *string {
newLabel := fmt.Sprintf("%s:%s", *label, notificationGroupName)
return &newLabel
}

View File

@@ -10,7 +10,7 @@ import (
var (
OnlineUserMap = make(map[string]*model.OnlineUser)
OnlineUserMapLock = new(sync.Mutex)
OnlineUserMapLock sync.Mutex
)
func AddOnlineUser(connId string, user *model.OnlineUser) {

View File

@@ -3,71 +3,101 @@ package singleton
import (
"cmp"
"slices"
"sync"
"github.com/nezhahq/nezha/model"
"github.com/nezhahq/nezha/pkg/utils"
)
var (
ServerList map[uint64]*model.Server // [ServerID] -> model.Server
ServerUUIDToID map[string]uint64 // [ServerUUID] -> ServerID
ServerLock sync.RWMutex
type ServerClass struct {
class[uint64, *model.Server]
SortedServerList []*model.Server // 用于存储服务器列表的 slice按照服务器 ID 排序
SortedServerListForGuest []*model.Server
SortedServerLock sync.RWMutex
)
uuidToID map[string]uint64
func InitServer() {
ServerList = make(map[uint64]*model.Server)
ServerUUIDToID = make(map[string]uint64)
sortedListForGuest []*model.Server
}
// loadServers 加载服务器列表并根据ID排序
func loadServers() {
InitServer()
func NewServerClass() *ServerClass {
sc := &ServerClass{
class: class[uint64, *model.Server]{
list: make(map[uint64]*model.Server),
},
uuidToID: make(map[string]uint64),
}
var servers []model.Server
DB.Find(&servers)
for _, s := range servers {
innerS := s
model.InitServer(&innerS)
ServerList[innerS.ID] = &innerS
ServerUUIDToID[innerS.UUID] = innerS.ID
sc.list[innerS.ID] = &innerS
sc.uuidToID[innerS.UUID] = innerS.ID
}
ReSortServer()
sc.sortList()
return sc
}
// ReSortServer 根据服务器ID 对服务器列表进行排序ID越大越靠前
func ReSortServer() {
ServerLock.RLock()
defer ServerLock.RUnlock()
SortedServerLock.Lock()
defer SortedServerLock.Unlock()
func (c *ServerClass) Update(s *model.Server, uuid string) {
c.listMu.Lock()
SortedServerList = utils.MapValuesToSlice(ServerList)
c.list[s.ID] = s
if uuid != "" {
c.uuidToID[uuid] = s.ID
}
c.listMu.Unlock()
c.sortList()
}
func (c *ServerClass) Delete(idList []uint64) {
c.listMu.Lock()
for _, id := range idList {
serverUUID := c.list[id].UUID
delete(c.uuidToID, serverUUID)
delete(c.list, id)
}
c.listMu.Unlock()
c.sortList()
}
func (c *ServerClass) GetSortedListForGuest() []*model.Server {
c.sortedListMu.RLock()
defer c.sortedListMu.RUnlock()
return slices.Clone(c.sortedListForGuest)
}
func (c *ServerClass) UUIDToID(uuid string) (id uint64, ok bool) {
c.listMu.RLock()
defer c.listMu.RUnlock()
id, ok = c.uuidToID[uuid]
return
}
func (c *ServerClass) sortList() {
c.listMu.RLock()
defer c.listMu.RUnlock()
c.sortedListMu.Lock()
defer c.sortedListMu.Unlock()
c.sortedList = utils.MapValuesToSlice(c.list)
// 按照服务器 ID 排序的具体实现ID越大越靠前
slices.SortStableFunc(SortedServerList, func(a, b *model.Server) int {
slices.SortStableFunc(c.sortedList, func(a, b *model.Server) int {
if a.DisplayIndex == b.DisplayIndex {
return cmp.Compare(a.ID, b.ID)
}
return cmp.Compare(b.DisplayIndex, a.DisplayIndex)
})
SortedServerListForGuest = make([]*model.Server, 0, len(SortedServerList))
for _, s := range SortedServerList {
c.sortedListForGuest = make([]*model.Server, 0, len(c.sortedList))
for _, s := range c.sortedList {
if !s.HideForGuest {
SortedServerListForGuest = append(SortedServerListForGuest, s)
c.sortedListForGuest = append(c.sortedListForGuest, s)
}
}
}
func OnServerDelete(sid []uint64) {
ServerLock.Lock()
defer ServerLock.Unlock()
for _, id := range sid {
serverUUID := ServerList[id].UUID
delete(ServerUUIDToID, serverUUID)
delete(ServerList, id)
}
}

View File

@@ -3,24 +3,26 @@ package singleton
import (
"cmp"
"fmt"
"iter"
"log"
"maps"
"slices"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/jinzhu/copier"
"github.com/nezhahq/nezha/model"
"github.com/nezhahq/nezha/pkg/utils"
pb "github.com/nezhahq/nezha/proto"
"golang.org/x/exp/constraints"
)
const (
_CurrentStatusSize = 30 // 统计 15 分钟内的数据为当前状态
)
var ServiceSentinelShared *ServiceSentinel
type serviceResponseItem struct {
model.ServiceResponseItem
@@ -39,26 +41,68 @@ type _TodayStatsOfService struct {
Delay float32 // 今日平均延迟
}
/*
使用缓存 channel处理上报的 Service 请求结果,然后判断是否需要报警
需要记录上一次的状态信息
加锁顺序serviceResponseDataStoreLock > monthlyStatusLock > servicesLock
*/
type ServiceSentinel struct {
// 服务监控任务上报通道
serviceReportChannel chan ReportData // 服务状态汇报管道
// 服务监控任务调度通道
dispatchBus chan<- *model.Service
serviceResponseDataStoreLock sync.RWMutex
serviceStatusToday map[uint64]*_TodayStatsOfService // [service_id] -> _TodayStatsOfService
serviceCurrentStatusIndex map[uint64]*indexStore // [service_id] -> 该监控ID对应的 serviceCurrentStatusData 的最新索引下标
serviceCurrentStatusData map[uint64][]*pb.TaskResult // [service_id] -> []model.ServiceHistory
serviceResponseDataStoreCurrentUp map[uint64]uint64 // [service_id] -> 当前服务在线计数
serviceResponseDataStoreCurrentDown map[uint64]uint64 // [service_id] -> 当前服务离线计数
serviceResponseDataStoreCurrentAvgDelay map[uint64]float32 // [service_id] -> 当前服务离线计数
serviceResponsePing map[uint64]map[uint64]*pingStore // [service_id] -> ClientID -> delay
lastStatus map[uint64]uint8
tlsCertCache map[uint64]string
servicesLock sync.RWMutex
serviceListLock sync.RWMutex
services map[uint64]*model.Service
serviceList []*model.Service
// 30天数据缓存
monthlyStatusLock sync.Mutex
monthlyStatus map[uint64]*serviceResponseItem
// references
serverc *ServerClass
notificationc *NotificationClass
crc *CronClass
}
// NewServiceSentinel 创建服务监控器
func NewServiceSentinel(serviceSentinelDispatchBus chan<- model.Service) {
ServiceSentinelShared = &ServiceSentinel{
func NewServiceSentinel(serviceSentinelDispatchBus chan<- *model.Service, sc *ServerClass, nc *NotificationClass, crc *CronClass) (*ServiceSentinel, error) {
ss := &ServiceSentinel{
serviceReportChannel: make(chan ReportData, 200),
serviceStatusToday: make(map[uint64]*_TodayStatsOfService),
serviceCurrentStatusIndex: make(map[uint64]*indexStore),
serviceCurrentStatusData: make(map[uint64][]*pb.TaskResult),
lastStatus: make(map[uint64]int),
lastStatus: make(map[uint64]uint8),
serviceResponseDataStoreCurrentUp: make(map[uint64]uint64),
serviceResponseDataStoreCurrentDown: make(map[uint64]uint64),
serviceResponseDataStoreCurrentAvgDelay: make(map[uint64]float32),
serviceResponsePing: make(map[uint64]map[uint64]*pingStore),
Services: make(map[uint64]*model.Service),
services: make(map[uint64]*model.Service),
tlsCertCache: make(map[uint64]string),
// 30天数据缓存
monthlyStatus: make(map[uint64]*serviceResponseItem),
dispatchBus: serviceSentinelDispatchBus,
serverc: sc,
notificationc: nc,
crc: crc,
}
// 加载历史记录
ServiceSentinelShared.loadServiceHistory()
ss.loadServiceHistory()
year, month, day := time.Now().Date()
today := time.Date(year, month, day, 0, 0, 0, 0, Loc)
@@ -71,56 +115,25 @@ func NewServiceSentinel(serviceSentinelDispatchBus chan<- model.Service) {
for i := 0; i < len(mhs); i++ {
totalDelay[mhs[i].ServiceID] += mhs[i].AvgDelay
totalDelayCount[mhs[i].ServiceID]++
ServiceSentinelShared.serviceStatusToday[mhs[i].ServiceID].Up += int(mhs[i].Up)
ServiceSentinelShared.monthlyStatus[mhs[i].ServiceID].TotalUp += mhs[i].Up
ServiceSentinelShared.serviceStatusToday[mhs[i].ServiceID].Down += int(mhs[i].Down)
ServiceSentinelShared.monthlyStatus[mhs[i].ServiceID].TotalDown += mhs[i].Down
ss.serviceStatusToday[mhs[i].ServiceID].Up += int(mhs[i].Up)
ss.monthlyStatus[mhs[i].ServiceID].TotalUp += mhs[i].Up
ss.serviceStatusToday[mhs[i].ServiceID].Down += int(mhs[i].Down)
ss.monthlyStatus[mhs[i].ServiceID].TotalDown += mhs[i].Down
}
for id, delay := range totalDelay {
ServiceSentinelShared.serviceStatusToday[id].Delay = delay / float32(totalDelayCount[id])
ss.serviceStatusToday[id].Delay = delay / float32(totalDelayCount[id])
}
// 启动服务监控器
go ServiceSentinelShared.worker()
go ss.worker()
// 每日将游标往后推一天
_, err := Cron.AddFunc("0 0 0 * * *", ServiceSentinelShared.refreshMonthlyServiceStatus)
_, err := crc.AddFunc("0 0 0 * * *", ss.refreshMonthlyServiceStatus)
if err != nil {
panic(err)
return nil, err
}
}
/*
使用缓存 channel处理上报的 Service 请求结果,然后判断是否需要报警
需要记录上一次的状态信息
加锁顺序serviceResponseDataStoreLock > monthlyStatusLock > servicesLock
*/
type ServiceSentinel struct {
// 服务监控任务上报通道
serviceReportChannel chan ReportData // 服务状态汇报管道
// 服务监控任务调度通道
dispatchBus chan<- model.Service
serviceResponseDataStoreLock sync.RWMutex
serviceStatusToday map[uint64]*_TodayStatsOfService // [service_id] -> _TodayStatsOfService
serviceCurrentStatusIndex map[uint64]*indexStore // [service_id] -> 该监控ID对应的 serviceCurrentStatusData 的最新索引下标
serviceCurrentStatusData map[uint64][]*pb.TaskResult // [service_id] -> []model.ServiceHistory
serviceResponseDataStoreCurrentUp map[uint64]uint64 // [service_id] -> 当前服务在线计数
serviceResponseDataStoreCurrentDown map[uint64]uint64 // [service_id] -> 当前服务离线计数
serviceResponseDataStoreCurrentAvgDelay map[uint64]float32 // [service_id] -> 当前服务离线计数
serviceResponsePing map[uint64]map[uint64]*pingStore // [service_id] -> ClientID -> delay
lastStatus map[uint64]int
tlsCertCache map[uint64]string
ServicesLock sync.RWMutex
ServiceListLock sync.RWMutex
Services map[uint64]*model.Service
ServiceList []*model.Service
// 30天数据缓存
monthlyStatusLock sync.Mutex
monthlyStatus map[uint64]*serviceResponseItem
return ss, nil
}
type indexStore struct {
@@ -169,14 +182,14 @@ func (ss *ServiceSentinel) Dispatch(r ReportData) {
}
func (ss *ServiceSentinel) UpdateServiceList() {
ss.ServicesLock.RLock()
defer ss.ServicesLock.RUnlock()
ss.servicesLock.RLock()
defer ss.servicesLock.RUnlock()
ss.ServiceListLock.Lock()
defer ss.ServiceListLock.Unlock()
ss.serviceListLock.Lock()
defer ss.serviceListLock.Unlock()
ss.ServiceList = utils.MapValuesToSlice(ss.Services)
slices.SortFunc(ss.ServiceList, func(a, b *model.Service) int {
ss.serviceList = utils.MapValuesToSlice(ss.services)
slices.SortFunc(ss.serviceList, func(a, b *model.Service) int {
return cmp.Compare(a.ID, b.ID)
})
}
@@ -190,25 +203,25 @@ func (ss *ServiceSentinel) loadServiceHistory() {
}
for i := 0; i < len(services); i++ {
task := *services[i]
task := services[i]
// 通过cron定时将服务监控任务传递给任务调度管道
services[i].CronJobID, err = Cron.AddFunc(task.CronSpec(), func() {
services[i].CronJobID, err = ss.crc.AddFunc(task.CronSpec(), func() {
ss.dispatchBus <- task
})
if err != nil {
panic(err)
}
ss.Services[services[i].ID] = services[i]
ss.services[services[i].ID] = services[i]
ss.serviceCurrentStatusData[services[i].ID] = make([]*pb.TaskResult, _CurrentStatusSize)
ss.serviceStatusToday[services[i].ID] = &_TodayStatsOfService{}
}
ss.ServiceList = services
ss.serviceList = services
year, month, day := time.Now().Date()
today := time.Date(year, month, day, 0, 0, 0, 0, Loc)
for i := 0; i < len(services); i++ {
ServiceSentinelShared.monthlyStatus[services[i].ID] = &serviceResponseItem{
ss.monthlyStatus[services[i].ID] = &serviceResponseItem{
service: services[i],
ServiceResponseItem: model.ServiceResponseItem{
Delay: &[30]float32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
@@ -227,38 +240,38 @@ func (ss *ServiceSentinel) loadServiceHistory() {
if dayIndex < 0 {
continue
}
ServiceSentinelShared.monthlyStatus[mhs[i].ServiceID].Delay[dayIndex] = (ServiceSentinelShared.monthlyStatus[mhs[i].ServiceID].Delay[dayIndex]*float32(delayCount[dayIndex]) + mhs[i].AvgDelay) / float32(delayCount[dayIndex]+1)
ss.monthlyStatus[mhs[i].ServiceID].Delay[dayIndex] = (ss.monthlyStatus[mhs[i].ServiceID].Delay[dayIndex]*float32(delayCount[dayIndex]) + mhs[i].AvgDelay) / float32(delayCount[dayIndex]+1)
delayCount[dayIndex]++
ServiceSentinelShared.monthlyStatus[mhs[i].ServiceID].Up[dayIndex] += int(mhs[i].Up)
ServiceSentinelShared.monthlyStatus[mhs[i].ServiceID].TotalUp += mhs[i].Up
ServiceSentinelShared.monthlyStatus[mhs[i].ServiceID].Down[dayIndex] += int(mhs[i].Down)
ServiceSentinelShared.monthlyStatus[mhs[i].ServiceID].TotalDown += mhs[i].Down
ss.monthlyStatus[mhs[i].ServiceID].Up[dayIndex] += int(mhs[i].Up)
ss.monthlyStatus[mhs[i].ServiceID].TotalUp += mhs[i].Up
ss.monthlyStatus[mhs[i].ServiceID].Down[dayIndex] += int(mhs[i].Down)
ss.monthlyStatus[mhs[i].ServiceID].TotalDown += mhs[i].Down
}
}
func (ss *ServiceSentinel) OnServiceUpdate(m model.Service) error {
func (ss *ServiceSentinel) Update(m *model.Service) error {
ss.serviceResponseDataStoreLock.Lock()
defer ss.serviceResponseDataStoreLock.Unlock()
ss.monthlyStatusLock.Lock()
defer ss.monthlyStatusLock.Unlock()
ss.ServicesLock.Lock()
defer ss.ServicesLock.Unlock()
ss.servicesLock.Lock()
defer ss.servicesLock.Unlock()
var err error
// 写入新任务
m.CronJobID, err = Cron.AddFunc(m.CronSpec(), func() {
m.CronJobID, err = ss.crc.AddFunc(m.CronSpec(), func() {
ss.dispatchBus <- m
})
if err != nil {
return err
}
if ss.Services[m.ID] != nil {
if ss.services[m.ID] != nil {
// 停掉旧任务
Cron.Remove(ss.Services[m.ID].CronJobID)
ss.crc.Remove(ss.services[m.ID].CronJobID)
} else {
// 新任务初始化数据
ss.monthlyStatus[m.ID] = &serviceResponseItem{
service: &m,
service: m,
ServiceResponseItem: model.ServiceResponseItem{
Delay: &[30]float32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Up: &[30]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
@@ -269,17 +282,17 @@ func (ss *ServiceSentinel) OnServiceUpdate(m model.Service) error {
ss.serviceStatusToday[m.ID] = &_TodayStatsOfService{}
}
// 更新这个任务
ss.Services[m.ID] = &m
ss.services[m.ID] = m
return nil
}
func (ss *ServiceSentinel) OnServiceDelete(ids []uint64) {
func (ss *ServiceSentinel) Delete(ids []uint64) {
ss.serviceResponseDataStoreLock.Lock()
defer ss.serviceResponseDataStoreLock.Unlock()
ss.monthlyStatusLock.Lock()
defer ss.monthlyStatusLock.Unlock()
ss.ServicesLock.Lock()
defer ss.ServicesLock.Unlock()
ss.servicesLock.Lock()
defer ss.servicesLock.Unlock()
for _, id := range ids {
delete(ss.serviceCurrentStatusIndex, id)
@@ -292,24 +305,24 @@ func (ss *ServiceSentinel) OnServiceDelete(ids []uint64) {
delete(ss.serviceStatusToday, id)
// 停掉定时任务
Cron.Remove(ss.Services[id].CronJobID)
delete(ss.Services, id)
ss.crc.Remove(ss.services[id].CronJobID)
delete(ss.services, id)
delete(ss.monthlyStatus, id)
}
}
func (ss *ServiceSentinel) LoadStats() map[uint64]*serviceResponseItem {
ss.ServicesLock.RLock()
defer ss.ServicesLock.RUnlock()
ss.servicesLock.RLock()
defer ss.servicesLock.RUnlock()
ss.serviceResponseDataStoreLock.RLock()
defer ss.serviceResponseDataStoreLock.RUnlock()
ss.monthlyStatusLock.Lock()
defer ss.monthlyStatusLock.Unlock()
// 刷新最新一天的数据
for k := range ss.Services {
ss.monthlyStatus[k].service = ss.Services[k]
for k := range ss.services {
ss.monthlyStatus[k].service = ss.services[k]
v := ss.serviceStatusToday[k]
// 30 天在线率,
@@ -354,14 +367,52 @@ func (ss *ServiceSentinel) CopyStats() map[uint64]model.ServiceResponseItem {
return sri
}
func (ss *ServiceSentinel) Get(id uint64) (s *model.Service, ok bool) {
ss.servicesLock.RLock()
defer ss.servicesLock.RUnlock()
s, ok = ss.services[id]
return
}
func (ss *ServiceSentinel) GetList() map[uint64]*model.Service {
ss.servicesLock.RLock()
defer ss.servicesLock.RUnlock()
return maps.Clone(ss.services)
}
func (ss *ServiceSentinel) GetSortedList() []*model.Service {
ss.serviceListLock.RLock()
defer ss.serviceListLock.RUnlock()
return slices.Clone(ss.serviceList)
}
func (ss *ServiceSentinel) CheckPermission(c *gin.Context, idList iter.Seq[uint64]) bool {
ss.servicesLock.RLock()
defer ss.servicesLock.RUnlock()
for id := range idList {
if s, ok := ss.services[id]; ok {
if !s.HasPermission(c) {
return false
}
}
}
return true
}
// worker 服务监控的实际工作流程
func (ss *ServiceSentinel) worker() {
// 从服务状态汇报管道获取汇报的服务数据
for r := range ss.serviceReportChannel {
if ss.Services[r.Data.GetId()] == nil || ss.Services[r.Data.GetId()].ID == 0 {
css, _ := ss.Get(r.Data.GetId())
if css == nil || css.ID == 0 {
log.Printf("NEZHA>> Incorrect service monitor report %+v", r)
continue
}
css = nil
mh := r.Data
if mh.Type == model.TaskTypeTCPPing || mh.Type == model.TaskTypeICMPPing {
serviceTcpMap, ok := ss.serviceResponsePing[mh.GetId()]
@@ -454,79 +505,20 @@ func (ss *ServiceSentinel) worker() {
}
}
cs, _ := ss.Get(mh.GetId())
m := ss.serverc.GetList()
// 延迟报警
if mh.Delay > 0 {
ss.ServicesLock.RLock()
if ss.Services[mh.GetId()].LatencyNotify {
notificationGroupID := ss.Services[mh.GetId()].NotificationGroupID
minMuteLabel := NotificationMuteLabel.ServiceLatencyMin(mh.GetId())
maxMuteLabel := NotificationMuteLabel.ServiceLatencyMax(mh.GetId())
if mh.Delay > ss.Services[mh.GetId()].MaxLatency {
// 延迟超过最大值
ServerLock.RLock()
reporterServer := ServerList[r.Reporter]
msg := Localizer.Tf("[Latency] %s %2f > %2f, Reporter: %s", ss.Services[mh.GetId()].Name, mh.Delay, ss.Services[mh.GetId()].MaxLatency, reporterServer.Name)
go SendNotification(notificationGroupID, msg, minMuteLabel)
ServerLock.RUnlock()
} else if mh.Delay < ss.Services[mh.GetId()].MinLatency {
// 延迟低于最小值
ServerLock.RLock()
reporterServer := ServerList[r.Reporter]
msg := Localizer.Tf("[Latency] %s %2f < %2f, Reporter: %s", ss.Services[mh.GetId()].Name, mh.Delay, ss.Services[mh.GetId()].MinLatency, reporterServer.Name)
go SendNotification(notificationGroupID, msg, maxMuteLabel)
ServerLock.RUnlock()
} else {
// 正常延迟, 清除静音缓存
UnMuteNotification(notificationGroupID, minMuteLabel)
UnMuteNotification(notificationGroupID, maxMuteLabel)
}
}
ss.ServicesLock.RUnlock()
delayCheck(&r, ss.notificationc, m, cs, mh)
}
// 状态变更报警+触发任务执行
if stateCode == StatusDown || stateCode != ss.lastStatus[mh.GetId()] {
ss.ServicesLock.Lock()
lastStatus := ss.lastStatus[mh.GetId()]
// 存储新的状态值
ss.lastStatus[mh.GetId()] = stateCode
// 判断是否需要发送通知
isNeedSendNotification := ss.Services[mh.GetId()].Notify && (lastStatus != 0 || stateCode == StatusDown)
if isNeedSendNotification {
ServerLock.RLock()
reporterServer := ServerList[r.Reporter]
notificationGroupID := ss.Services[mh.GetId()].NotificationGroupID
notificationMsg := Localizer.Tf("[%s] %s Reporter: %s, Error: %s", StatusCodeToString(stateCode), ss.Services[mh.GetId()].Name, reporterServer.Name, mh.Data)
muteLabel := NotificationMuteLabel.ServiceStateChanged(mh.GetId())
// 状态变更时,清除静音缓存
if stateCode != lastStatus {
UnMuteNotification(notificationGroupID, muteLabel)
}
go SendNotification(notificationGroupID, notificationMsg, muteLabel)
ServerLock.RUnlock()
}
// 判断是否需要触发任务
isNeedTriggerTask := ss.Services[mh.GetId()].EnableTriggerTask && lastStatus != 0
if isNeedTriggerTask {
ServerLock.RLock()
reporterServer := ServerList[r.Reporter]
ServerLock.RUnlock()
if stateCode == StatusGood && lastStatus != stateCode {
// 当前状态正常 前序状态非正常时 触发恢复任务
go SendTriggerTasks(ss.Services[mh.GetId()].RecoverTriggerTasks, reporterServer.ID)
} else if lastStatus == StatusGood && lastStatus != stateCode {
// 前序状态正常 当前状态非正常时 触发失败任务
go SendTriggerTasks(ss.Services[mh.GetId()].FailTriggerTasks, reporterServer.ID)
}
}
ss.ServicesLock.Unlock()
notifyCheck(&r, ss.notificationc, ss.crc, m, cs, mh, lastStatus, stateCode)
}
ss.serviceResponseDataStoreLock.Unlock()
@@ -538,22 +530,18 @@ func (ss *ServiceSentinel) worker() {
!strings.HasSuffix(mh.Data, "EOF") &&
!strings.HasSuffix(mh.Data, "timed out") {
errMsg = mh.Data
ss.ServicesLock.RLock()
if ss.Services[mh.GetId()].Notify {
if cs.Notify {
muteLabel := NotificationMuteLabel.ServiceTLS(mh.GetId(), "network")
go SendNotification(ss.Services[mh.GetId()].NotificationGroupID, Localizer.Tf("[TLS] Fetch cert info failed, Reporter: %s, Error: %s", ss.Services[mh.GetId()].Name, errMsg), muteLabel)
go ss.notificationc.SendNotification(cs.NotificationGroupID, Localizer.Tf("[TLS] Fetch cert info failed, Reporter: %s, Error: %s", cs.Name, errMsg), muteLabel)
}
ss.ServicesLock.RUnlock()
}
} else {
// 清除网络错误静音缓存
UnMuteNotification(ss.Services[mh.GetId()].NotificationGroupID, NotificationMuteLabel.ServiceTLS(mh.GetId(), "network"))
ss.notificationc.UnMuteNotification(cs.NotificationGroupID, NotificationMuteLabel.ServiceTLS(mh.GetId(), "network"))
var newCert = strings.Split(mh.Data, "|")
if len(newCert) > 1 {
ss.ServicesLock.Lock()
enableNotify := ss.Services[mh.GetId()].Notify
enableNotify := cs.Notify
// 首次获取证书信息时,缓存证书信息
if ss.tlsCertCache[mh.GetId()] == "" {
@@ -571,9 +559,8 @@ func (ss *ServiceSentinel) worker() {
ss.tlsCertCache[mh.GetId()] = mh.Data
}
notificationGroupID := ss.Services[mh.GetId()].NotificationGroupID
serviceName := ss.Services[mh.GetId()].Name
ss.ServicesLock.Unlock()
notificationGroupID := cs.NotificationGroupID
serviceName := cs.Name
// 需要发送提醒
if enableNotify {
@@ -588,7 +575,7 @@ func (ss *ServiceSentinel) worker() {
// 静音规则: 服务id+证书过期时间
// 用于避免多个监测点对相同证书同时报警
muteLabel := NotificationMuteLabel.ServiceTLS(mh.GetId(), fmt.Sprintf("expire_%s", expiresTimeStr))
go SendNotification(notificationGroupID, fmt.Sprintf("[TLS] %s %s", serviceName, errMsg), muteLabel)
go ss.notificationc.SendNotification(notificationGroupID, fmt.Sprintf("[TLS] %s %s", serviceName, errMsg), muteLabel)
}
// 证书变更提醒
@@ -598,7 +585,7 @@ func (ss *ServiceSentinel) worker() {
oldCert[0], expiresOld.Format("2006-01-02 15:04:05"), newCert[0], expiresNew.Format("2006-01-02 15:04:05"))
// 证书变更后会自动更新缓存,所以不需要静音
go SendNotification(notificationGroupID, fmt.Sprintf("[TLS] %s %s", serviceName, errMsg), nil)
go ss.notificationc.SendNotification(notificationGroupID, fmt.Sprintf("[TLS] %s %s", serviceName, errMsg), nil)
}
}
}
@@ -606,6 +593,63 @@ func (ss *ServiceSentinel) worker() {
}
}
func delayCheck(r *ReportData, nc *NotificationClass, m map[uint64]*model.Server, ss *model.Service, mh *pb.TaskResult) {
if !ss.LatencyNotify {
return
}
notificationGroupID := ss.NotificationGroupID
minMuteLabel := NotificationMuteLabel.ServiceLatencyMin(mh.GetId())
maxMuteLabel := NotificationMuteLabel.ServiceLatencyMax(mh.GetId())
if mh.Delay > ss.MaxLatency {
// 延迟超过最大值
reporterServer := m[r.Reporter]
msg := Localizer.Tf("[Latency] %s %2f > %2f, Reporter: %s", ss.Name, mh.Delay, ss.MaxLatency, reporterServer.Name)
go nc.SendNotification(notificationGroupID, msg, minMuteLabel)
} else if mh.Delay < ss.MinLatency {
// 延迟低于最小值
reporterServer := m[r.Reporter]
msg := Localizer.Tf("[Latency] %s %2f < %2f, Reporter: %s", ss.Name, mh.Delay, ss.MinLatency, reporterServer.Name)
go nc.SendNotification(notificationGroupID, msg, maxMuteLabel)
} else {
// 正常延迟, 清除静音缓存
nc.UnMuteNotification(notificationGroupID, minMuteLabel)
nc.UnMuteNotification(notificationGroupID, maxMuteLabel)
}
}
func notifyCheck(r *ReportData, nc *NotificationClass, crc *CronClass, m map[uint64]*model.Server,
ss *model.Service, mh *pb.TaskResult, lastStatus, stateCode uint8) {
// 判断是否需要发送通知
isNeedSendNotification := ss.Notify && (lastStatus != 0 || stateCode == StatusDown)
if isNeedSendNotification {
reporterServer := m[r.Reporter]
notificationGroupID := ss.NotificationGroupID
notificationMsg := Localizer.Tf("[%s] %s Reporter: %s, Error: %s", StatusCodeToString(stateCode), ss.Name, reporterServer.Name, mh.Data)
muteLabel := NotificationMuteLabel.ServiceStateChanged(mh.GetId())
// 状态变更时,清除静音缓存
if stateCode != lastStatus {
nc.UnMuteNotification(notificationGroupID, muteLabel)
}
go nc.SendNotification(notificationGroupID, notificationMsg, muteLabel)
}
// 判断是否需要触发任务
isNeedTriggerTask := ss.EnableTriggerTask && lastStatus != 0
if isNeedTriggerTask {
reporterServer := m[r.Reporter]
if stateCode == StatusGood && lastStatus != stateCode {
// 当前状态正常 前序状态非正常时 触发恢复任务
go crc.SendTriggerTasks(ss.RecoverTriggerTasks, reporterServer.ID)
} else if lastStatus == StatusGood && lastStatus != stateCode {
// 前序状态正常 当前状态非正常时 触发失败任务
go crc.SendTriggerTasks(ss.FailTriggerTasks, reporterServer.ID)
}
}
}
const (
_ = iota
StatusNoData
@@ -614,7 +658,7 @@ const (
StatusDown
)
func GetStatusCode[T float32 | uint64](percent T) int {
func GetStatusCode[T constraints.Float | constraints.Integer](percent T) uint8 {
if percent == 0 {
return StatusNoData
}
@@ -627,7 +671,7 @@ func GetStatusCode[T float32 | uint64](percent T) int {
return StatusDown
}
func StatusCodeToString(statusCode int) string {
func StatusCodeToString(statusCode uint8) string {
switch statusCode {
case StatusNoData:
return Localizer.T("No Data")

View File

@@ -2,9 +2,14 @@ package singleton
import (
_ "embed"
"iter"
"log"
"maps"
"slices"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/patrickmn/go-cache"
"gopkg.in/yaml.v3"
"gorm.io/driver/sqlite"
@@ -23,6 +28,13 @@ var (
Loc *time.Location
FrontendTemplates []model.FrontendTemplate
DashboardBootTime = uint64(time.Now().Unix())
ServerShared *ServerClass
ServiceSentinelShared *ServiceSentinel
DDNSShared *DDNSClass
NotificationShared *NotificationClass
NATShared *NATClass
CronShared *CronClass
)
//go:embed frontend-templates.yaml
@@ -40,13 +52,13 @@ func InitTimezoneAndCache() {
// LoadSingleton 加载子服务并执行
func LoadSingleton() {
initUser() // 加载用户ID绑定表
initI18n() // 加载本地化服务
loadNotifications() // 加载通知服务
loadServers() // 加载服务器列表
loadCronTasks() // 加载定时任务
initNAT()
initDDNS()
initUser() // 加载用户ID绑定表
initI18n() // 加载本地化服务
NotificationShared = NewNotificationClass() // 加载通知服务
ServerShared = NewServerClass() // 加载服务器列表
CronShared = NewCronClass() // 加载定时任务
NATShared = NewNATClass()
DDNSShared = NewDDNSClass()
}
// InitFrontendTemplates 从内置文件中加载FrontendTemplates
@@ -90,12 +102,13 @@ func InitDBFromPath(path string) {
// RecordTransferHourlyUsage 对流量记录进行打点
func RecordTransferHourlyUsage() {
ServerLock.Lock()
defer ServerLock.Unlock()
ServerShared.listMu.RLock()
defer ServerShared.listMu.RUnlock()
now := time.Now()
nowTrimSeconds := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location())
var txs []model.Transfer
for id, server := range ServerList {
for id, server := range ServerShared.list {
tx := model.Transfer{
ServerID: id,
In: utils.Uint64SubInt64(server.State.NetInTransfer, server.PrevTransferInSnapshot),
@@ -171,3 +184,58 @@ func IPDesensitize(ip string) string {
}
return utils.IPDesensitize(ip)
}
type class[K comparable, V model.CommonInterface] struct {
list map[K]V
listMu sync.RWMutex
sortedList []V
sortedListMu sync.RWMutex
}
func (c *class[K, V]) Get(id K) (s V, ok bool) {
c.listMu.RLock()
defer c.listMu.RUnlock()
s, ok = c.list[id]
return
}
func (c *class[K, V]) GetList() map[K]V {
c.listMu.RLock()
defer c.listMu.RUnlock()
return maps.Clone(c.list)
}
func (c *class[K, V]) GetSortedList() []V {
c.sortedListMu.RLock()
defer c.sortedListMu.RUnlock()
return slices.Clone(c.sortedList)
}
func (c *class[K, V]) Range(fn func(k K, v V) bool) {
c.listMu.RLock()
defer c.listMu.RUnlock()
for k, v := range c.list {
if !fn(k, v) {
break
}
}
}
func (c *class[K, V]) CheckPermission(ctx *gin.Context, idList iter.Seq[K]) bool {
c.listMu.RLock()
defer c.listMu.RUnlock()
for id := range idList {
if s, ok := c.list[id]; ok {
if !s.HasPermission(ctx) {
return false
}
}
}
return true
}

View File

@@ -65,12 +65,11 @@ func OnUserDelete(id []uint64, errorFunc func(string, ...interface{}) error) err
crons, servers []uint64
)
slist := ServerShared.GetSortedList()
clist := CronShared.GetSortedList()
for _, uid := range id {
err := DB.Transaction(func(tx *gorm.DB) error {
CronLock.RLock()
crons = model.FindByUserID(CronList, uid)
CronLock.RUnlock()
crons = model.FindByUserID(clist, uid)
cron = len(crons) > 0
if cron {
if err := tx.Unscoped().Delete(&model.Cron{}, "id in (?)", crons).Error; err != nil {
@@ -78,10 +77,7 @@ func OnUserDelete(id []uint64, errorFunc func(string, ...interface{}) error) err
}
}
SortedServerLock.RLock()
servers = model.FindByUserID(SortedServerList, uid)
SortedServerLock.RUnlock()
servers = model.FindByUserID(slist, uid)
server = len(servers) > 0
if server {
if err := tx.Unscoped().Delete(&model.Server{}, "id in (?)", servers).Error; err != nil {
@@ -107,7 +103,7 @@ func OnUserDelete(id []uint64, errorFunc func(string, ...interface{}) error) err
}
if cron {
OnDeleteCron(crons)
CronShared.Delete(crons)
}
if server {
@@ -122,21 +118,12 @@ func OnUserDelete(id []uint64, errorFunc func(string, ...interface{}) error) err
}
}
AlertsLock.Unlock()
OnServerDelete(servers)
ServerShared.Delete(servers)
}
secret := UserInfoMap[uid].AgentSecret
delete(AgentSecretToUserId, secret)
delete(UserInfoMap, uid)
}
if cron {
UpdateCronList()
}
if server {
ReSortServer()
}
return nil
}