diff --git a/cmd/dashboard/main.go b/cmd/dashboard/main.go index 6db1540..0ae23de 100644 --- a/cmd/dashboard/main.go +++ b/cmd/dashboard/main.go @@ -40,7 +40,7 @@ var ( frontendDist embed.FS ) -func initSystem() error { +func initSystem(bus chan<- *model.Service) error { // 初始化管理员账户 var usersCount int64 if err := singleton.DB.Model(&model.User{}).Count(&usersCount).Error; err != nil { @@ -61,7 +61,9 @@ func initSystem() error { } // 启动 singleton 包下的所有服务 - singleton.LoadSingleton() + if err := singleton.LoadSingleton(bus); err != nil { + return err + } // 每天的3:30 对 监控记录 和 流量记录 进行清理 if _, err := singleton.CronShared.AddFunc("0 30 3 * * *", singleton.CleanServiceHistory); err != nil { @@ -107,12 +109,13 @@ func main() { os.Exit(0) } + serviceSentinelDispatchBus := make(chan *model.Service) // 用于传递服务监控任务信息的channel // 初始化 dao 包 if err := utils.FirstError(singleton.InitFrontendTemplates, func() error { return singleton.InitConfigFromPath(dashboardCliParam.ConfigFile) }, singleton.InitTimezoneAndCache, func() error { return singleton.InitDBFromPath(dashboardCliParam.DatabaseLocation) }, - initSystem); err != nil { + func() error { return initSystem(serviceSentinelDispatchBus) }); err != nil { log.Fatal(err) } @@ -122,15 +125,9 @@ func main() { } singleton.CleanServiceHistory() - serviceSentinelDispatchBus := make(chan *model.Service) // 用于传递服务监控任务信息的channel rpc.DispatchKeepalive() go rpc.DispatchTask(serviceSentinelDispatchBus) go singleton.AlertSentinelStart() - singleton.ServiceSentinelShared, err = singleton.NewServiceSentinel( - serviceSentinelDispatchBus, singleton.ServerShared, singleton.NotificationShared, singleton.CronShared) - if err != nil { - log.Fatal(err) - } grpcHandler := rpc.ServeRPC() httpHandler := controller.ServeWeb(frontendDist) diff --git a/model/common.go b/model/common.go index 182b21b..65f316f 100644 --- a/model/common.go +++ b/model/common.go @@ -2,6 +2,7 @@ package model import ( "cmp" + "iter" "slices" "strconv" "strings" @@ -71,35 +72,63 @@ func FindByUserID[S ~[]E, E CommonInterface](s S, uid uint64) []uint64 { } func SearchByIDCtx[S ~[]E, E CommonInterface](c *gin.Context, x S) S { - switch any(x).(type) { - case []*Server: - l := searchByIDCtxServer(c, any(x).([]*Server)) - return any(l).(S) - default: - var s S - for idStr := range strings.SplitSeq(c.Query("id"), ",") { - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil { - continue - } - - s = appendBinarySearch(s, x, id) - } - return utils.IfOr(len(s) > 0, s, x) - } + return SearchByID(strings.SplitSeq(c.Query("id"), ","), x) } -func searchByIDCtxServer(c *gin.Context, x []*Server) []*Server { - list1, list2 := SplitList(x) +func SearchByID[S ~[]E, E CommonInterface](seq iter.Seq[string], x S) S { + if hasPriorityList[E]() { + return searchByIDPri(seq, x) + } - var clist1, clist2 []*Server - for idStr := range strings.SplitSeq(c.Query("id"), ",") { + var s S + for idStr := range seq { id, err := strconv.ParseUint(idStr, 10, 64) if err != nil { continue } - clist1 = appendBinarySearch(clist1, list1, id) + s = appendBinarySearch(s, x, id) + } + return utils.IfOr(len(s) > 0, s, x) +} + +func hasPriorityList[T CommonInterface]() bool { + var class T + + switch any(class).(type) { + case *Server: + return true + default: + return false + } +} + +type splitter[S ~[]E, E CommonInterface] interface { + // SplitList should split a sorted list into two separate lists: + // The first list contains elements with a priority set (DisplayIndex != 0). + // The second list contains elements without a priority set (DisplayIndex == 0). + // The original slice is not modified. If no element without a priority is found, it returns nil. + // Should be safe to use with a nil pointer. + SplitList(x S) (S, S) +} + +func searchByIDPri[S ~[]E, E CommonInterface](seq iter.Seq[string], x S) S { + var class E + split, ok := any(class).(splitter[S, E]) + if !ok { + return nil + } + + plist, list2 := split.SplitList(x) + + var clist1, clist2 S + for idStr := range seq { + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + continue + } + + clist1 = appendSearch(clist1, plist, id) clist2 = appendBinarySearch(clist2, list2, id) } @@ -115,3 +144,13 @@ func appendBinarySearch[S ~[]E, E CommonInterface](x, y S, target uint64) S { } return x } + +func appendSearch[S ~[]E, E CommonInterface](x, y S, target uint64) S { + if i := slices.IndexFunc(y, func(e E) bool { + return e.GetID() == target + }); i != -1 { + x = append(x, y[i]) + } + + return x +} diff --git a/model/common_test.go b/model/common_test.go new file mode 100644 index 0000000..ed4d94c --- /dev/null +++ b/model/common_test.go @@ -0,0 +1,49 @@ +package model + +import ( + "reflect" + "slices" + "testing" +) + +func TestSearchByID(t *testing.T) { + t.Run("WithoutPriorityList", func(t *testing.T) { + list, exp := []*DDNSProfile{ + {Common: Common{ID: 1}}, + {Common: Common{ID: 2}}, + {Common: Common{ID: 3}}, + {Common: Common{ID: 4}}, + {Common: Common{ID: 5}}, + }, []*DDNSProfile{ + {Common: Common{ID: 4}}, + {Common: Common{ID: 1}}, + {Common: Common{ID: 3}}, + } + + searchList := slices.Values([]string{"4", "1", "3"}) + filtered := SearchByID(searchList, list) + if !reflect.DeepEqual(filtered, exp) { + t.Fatalf("expected %v, but got %v", exp, filtered) + } + }) + + t.Run("WithPriorityTest", func(t *testing.T) { + list, exp := []*Server{ + {Common: Common{ID: 5}, DisplayIndex: 2}, + {Common: Common{ID: 4}, DisplayIndex: 1}, + {Common: Common{ID: 1}}, + {Common: Common{ID: 2}}, + {Common: Common{ID: 3}}, + }, []*Server{ + {Common: Common{ID: 4}, DisplayIndex: 1}, + {Common: Common{ID: 5}, DisplayIndex: 2}, + {Common: Common{ID: 3}}, + } + + searchList := slices.Values([]string{"3", "4", "5"}) + filtered := SearchByID(searchList, list) + if !reflect.DeepEqual(filtered, exp) { + t.Fatalf("expected %v, but got %v", exp, filtered) + } + }) +} diff --git a/model/server.go b/model/server.go index 66b9b8a..165d7ac 100644 --- a/model/server.go +++ b/model/server.go @@ -73,11 +73,7 @@ func (s *Server) AfterFind(tx *gorm.DB) error { return nil } -// Split a sorted server list into two separate lists: -// The first list contains servers with a priority set (DisplayIndex != 0). -// The second list contains servers without a priority set (DisplayIndex == 0). -// The original slice is not modified. If no server without a priority is found, it returns nil. -func SplitList(x []*Server) ([]*Server, []*Server) { +func (s *Server) SplitList(x []*Server) ([]*Server, []*Server) { pri := func(s *Server) bool { return s.DisplayIndex == 0 } diff --git a/service/singleton/singleton.go b/service/singleton/singleton.go index 5f89ad1..743a1f5 100644 --- a/service/singleton/singleton.go +++ b/service/singleton/singleton.go @@ -51,7 +51,7 @@ func InitTimezoneAndCache() error { } // LoadSingleton 加载子服务并执行 -func LoadSingleton() { +func LoadSingleton(bus chan<- *model.Service) (err error) { initUser() // 加载用户ID绑定表 initI18n() // 加载本地化服务 NotificationShared = NewNotificationClass() // 加载通知服务 @@ -59,6 +59,8 @@ func LoadSingleton() { CronShared = NewCronClass() // 加载定时任务 NATShared = NewNATClass() DDNSShared = NewDDNSClass() + ServiceSentinelShared, err = NewServiceSentinel(bus, ServerShared, NotificationShared, CronShared) + return } // InitFrontendTemplates 从内置文件中加载FrontendTemplates