From 490a1c999fca186fa150ca561174e7a17297dc95 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 20 Oct 2021 15:26:19 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=83=AD=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chart/chart.go | 11 +- cmd/start.go | 2 +- plugin/plugin.go | 4 +- plugin/plugin_test.go | 2 +- service/service.go | 326 -------------------------------- service/watch.go | 248 ++++++++++++++++++++++++ share/utils.go | 12 ++ {engine => share}/watch_test.go | 7 +- 8 files changed, 272 insertions(+), 340 deletions(-) create mode 100644 service/watch.go rename {engine => share}/watch_test.go (70%) diff --git a/chart/chart.go b/chart/chart.go index af0c15d9..daf05124 100644 --- a/chart/chart.go +++ b/chart/chart.go @@ -29,22 +29,21 @@ func LoadFrom(dir string, prefix string) { share.Walk(dir, ".json", func(root, filename string) { name := share.SpecName(root, filename) content := share.ReadFile(filename) - chart, err := LoadChart(content, name) + _, err := LoadChart(content, name) if err != nil { exception.New("%s 图表格式错误", 400, name).Ctx(filename).Throw() } - Charts[name] = chart }) } // LoadChart 载入数据表格 func LoadChart(source []byte, name string) (*Chart, error) { - chart := Chart{ + chart := &Chart{ Flow: gou.Flow{ Name: name, }, } - err := jsoniter.Unmarshal(source, &chart) + err := jsoniter.Unmarshal(source, chart) if err != nil { xlog.Println(name) xlog.Println(err.Error()) @@ -53,8 +52,8 @@ func LoadChart(source []byte, name string) (*Chart, error) { } chart.Prepare() chart.SetupAPIs() - - return &chart, nil + Charts[name] = chart + return chart, nil } // Select 读取已加载图表 diff --git a/cmd/start.go b/cmd/start.go index f99a574d..02a3a5c0 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -79,7 +79,7 @@ var startCmd = &cobra.Command{ // 调试模式 if config.Conf.Mode == "debug" { - service.WatchChanges() + service.Watch(config.Conf) } service.Start() diff --git a/plugin/plugin.go b/plugin/plugin.go index 9a5ae67b..7a5866e0 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -8,11 +8,11 @@ import ( // Load 加载业务插件 func Load(cfg config.Config) { - LoadFrom(cfg.RootPlugin, "") + LoadFrom(cfg.RootPlugin) } // LoadFrom 从特定目录加载 -func LoadFrom(dir string, prefix string) { +func LoadFrom(dir string) { if share.DirNotExists(dir) { return diff --git a/plugin/plugin_test.go b/plugin/plugin_test.go index 61cac039..559464e4 100644 --- a/plugin/plugin_test.go +++ b/plugin/plugin_test.go @@ -12,7 +12,7 @@ import ( func TestLoad(t *testing.T) { gou.Plugins = make(map[string]*gou.Plugin) Load(config.Conf) - LoadFrom("not a path", "404.") + LoadFrom("404") check(t) } diff --git a/service/service.go b/service/service.go index 973a1a7b..95f07cce 100644 --- a/service/service.go +++ b/service/service.go @@ -1,14 +1,8 @@ package service import ( - "log" - "path/filepath" - "strings" - "github.com/yaoapp/gou" "github.com/yaoapp/xiang/config" - "github.com/yaoapp/xiang/share" - "github.com/yaoapp/xiang/table" ) var shutdown = make(chan bool) @@ -36,323 +30,3 @@ func Stop(onComplete func()) { <-shutdownComplete onComplete() } - -// WatchChanges 监听配置文件变更 -func WatchChanges() { - watchEngine(config.Conf.Path) - watchApp(share.AppRoot{ - APIs: config.Conf.RootAPI, - Flows: config.Conf.RootFLow, - Models: config.Conf.RootModel, - Plugins: config.Conf.RootPlugin, - Tables: config.Conf.RootTable, - Charts: config.Conf.RootChart, - Screens: config.Conf.RootScreen, - }) -} - -// watchEngine 监听引擎目录文件变更 -func watchEngine(from string) { - if !strings.HasPrefix(from, "fs://") && strings.Contains(from, "://") { - return - } - root := strings.TrimPrefix(from, "fs://") - rootAbs, err := filepath.Abs(root) - if err != nil { - log.Panicf("路径错误 %s %s", root, err) - } - - // 监听 flows (这里应该重构) - go share.Watch(filepath.Join(rootAbs, "flows"), func(op string, file string) { - - if !strings.HasSuffix(file, ".json") { - return - } - - if strings.HasSuffix(file, ".js") { - basName := share.GetFileBaseName(root, file) - file = basName + ".flow.json" - } - - if op == "write" || op == "create" { - script := share.GetFile(root, file) - gou.LoadFlow(string(script.Content), "xiang."+script.Name) // Reload - log.Printf("Flow %s 已重新加载完毕", "xiang."+script.Name) - } else if op == "remove" || op == "rename" { - name := "xiang." + share.GetFileName(root, file) - if _, has := gou.Flows[name]; has { - delete(gou.Flows, name) - log.Printf("Flow %s 已经移除", name) - } - } - }) - - // 监听 models - go share.Watch(filepath.Join(rootAbs, "models"), func(op string, file string) { - - if !strings.HasSuffix(file, ".json") { - return - } - if op == "write" || op == "create" { - script := share.GetFile(root, file) - gou.LoadModel(string(script.Content), "xiang."+script.Name) // Reload - log.Printf("Model %s 已重新加载完毕", "xiang."+script.Name) - } else if op == "remove" || op == "rename" { - name := "xiang." + share.GetFileName(root, file) - if _, has := gou.Models[name]; has { - delete(gou.Models, name) - log.Printf("Model %s 已经移除", name) - } - } - }) - - // 监听 apis - go share.Watch(filepath.Join(rootAbs, "apis"), func(op string, file string) { - - if !strings.HasSuffix(file, ".json") { - return - } - if op == "write" || op == "create" { - script := share.GetFile(root, file) - gou.LoadAPI(string(script.Content), "xiang."+script.Name) // Reload - log.Printf("API %s 已重新加载完毕", "xiang."+script.Name) - - // 打印最新API信息 - api := gou.APIs["xiang."+script.Name] - log.Printf("%s(%d)", api.Name, len(api.HTTP.Paths)) - for _, p := range api.HTTP.Paths { - log.Println(p.Method, filepath.Join("/api", api.HTTP.Group, p.Path), "\tprocess:", p.Process) - } - - } else if op == "remove" || op == "rename" { - name := "xiang." + share.GetFileName(root, file) - if _, has := gou.APIs[name]; has { - delete(gou.APIs, name) - log.Printf("API %s 已经移除", name) - } - } - - // 重启服务器 - if op == "write" || op == "create" || op == "remove" || op == "rename" { - Stop(func() { - log.Printf("服务器重启完毕") - go Start() - }) - } - }) - - // 监听 tables - go share.Watch(filepath.Join(rootAbs, "tables"), func(op string, file string) { - - if !strings.HasSuffix(file, ".json") { - return - } - if op == "write" || op == "create" { - script := share.GetFile(root, file) - table.LoadTable(string(script.Content), "xiang."+script.Name) // Reload - api, has := gou.APIs["xiang.table"] - if has { - api.Reload() - } - - log.Printf("数据表格 %s 已重新加载完毕", "xiang."+script.Name) - - } else if op == "remove" || op == "rename" { - name := "xiang." + share.GetFileName(root, file) - if _, has := table.Tables[name]; has { - delete(table.Tables, name) - log.Printf("数据表格 %s 已经移除", name) - } - } - - // 重启服务器 - if op == "write" || op == "create" || op == "remove" || op == "rename" { - Stop(func() { - log.Printf("服务器重启完毕") - go Start() - }) - } - }) -} - -// watchApp 监听应用目录文件变更 -func watchApp(app share.AppRoot) { - watchAppAPI(app.APIs) - watchAppFlow(app.Flows) - watchAppModel(app.Models) - watchAppPlugin(app.Plugins) - watchAppTable(app.Tables) -} - -// watchAppTable 监听数据表格变更 -func watchAppTable(rootTable string) { - if !strings.HasPrefix(rootTable, "fs://") && strings.Contains(rootTable, "://") { - return - } - root := strings.TrimPrefix(rootTable, "fs://") - rootAbs, err := filepath.Abs(root) - if err != nil { - log.Panicf("路径错误 %s %s", root, err) - } - - go share.Watch(rootAbs, func(op string, file string) { - if !strings.HasSuffix(file, ".json") { - return - } - - if op == "write" || op == "create" { - script := share.GetAppFile(root, file) - table.LoadTable(string(script.Content), script.Name) // Reload - api, has := gou.APIs["xiang.table"] - if has { - api.Reload() - } - - log.Printf("数据表格 %s 已重新加载完毕", script.Name) - - } else if op == "remove" || op == "rename" { - name := share.GetAppFileName(root, file) - if _, has := gou.APIs[name]; has { - delete(table.Tables, name) - log.Printf("数据表格 %s 已经移除", name) - } - } - - // 重启服务器 - if op == "write" || op == "create" || op == "remove" || op == "rename" { - Stop(func() { - log.Printf("服务器重启完毕") - go Start() - }) - } - }) -} - -// watchAppAPI 监听API变更 -func watchAppAPI(api string) { - if !strings.HasPrefix(api, "fs://") && strings.Contains(api, "://") { - return - } - root := strings.TrimPrefix(api, "fs://") - rootAbs, err := filepath.Abs(root) - if err != nil { - log.Panicf("路径错误 %s %s", root, err) - } - - go share.Watch(rootAbs, func(op string, file string) { - if !strings.HasSuffix(file, ".json") { - return - } - - if op == "write" || op == "create" { - script := share.GetAppFile(root, file) - gou.LoadAPI(string(script.Content), script.Name) // Reload - log.Printf("API %s 已重新加载完毕", script.Name) - - } else if op == "remove" || op == "rename" { - name := share.GetAppFileName(root, file) - if _, has := gou.APIs[name]; has { - delete(gou.APIs, name) - log.Printf("API %s 已经移除", name) - } - } - - // 重启服务器 - if op == "write" || op == "create" || op == "remove" || op == "rename" { - Stop(func() { - log.Printf("服务器重启完毕") - go Start() - }) - } - }) -} - -// watchAppFlow 监听Flow变更 -func watchAppFlow(flow string) { - if !strings.HasPrefix(flow, "fs://") && strings.Contains(flow, "://") { - return - } - root := strings.TrimPrefix(flow, "fs://") - rootAbs, err := filepath.Abs(root) - if err != nil { - log.Panicf("路径错误 %s %s", root, err) - } - go share.Watch(rootAbs, func(op string, file string) { - if !strings.HasSuffix(file, ".json") && !strings.HasSuffix(file, ".js") { - return - } - if strings.HasSuffix(file, ".js") { - basName := share.GetAppFileBaseName(root, file) - file = basName + ".flow.json" - } - if op == "write" || op == "create" { - script := share.GetAppFile(root, file) - gou.LoadFlow(string(script.Content), script.Name) // Reload - log.Printf("Flow %s 已重新加载完毕", script.Name) - } else if op == "remove" || op == "rename" { - name := share.GetAppFileName(root, file) - if _, has := gou.Flows[name]; has { - delete(gou.Flows, name) - log.Printf("Flow %s 已经移除", name) - } - } - }) -} - -// watchAppModel 监听Model变更 -func watchAppModel(model string) { - if !strings.HasPrefix(model, "fs://") && strings.Contains(model, "://") { - return - } - - root := strings.TrimPrefix(model, "fs://") - rootAbs, err := filepath.Abs(root) - if err != nil { - log.Panicf("路径错误 %s %s", root, err) - } - go share.Watch(rootAbs, func(op string, file string) { - if !strings.HasSuffix(file, ".json") { - return - } - if op == "write" || op == "create" { - script := share.GetAppFile(root, file) - gou.LoadModel(string(script.Content), script.Name) // Reload - log.Printf("Model %s 已重新加载完毕", script.Name) - } else if op == "remove" || op == "rename" { - name := share.GetAppFileName(root, file) - if _, has := gou.Models[name]; has { - delete(gou.Models, name) - log.Printf("Model %s 已经移除", name) - } - } - }) -} - -// watchAppPlugin 监听Plugin变更 -func watchAppPlugin(plugin string) { - if !strings.HasPrefix(plugin, "fs://") && strings.Contains(plugin, "://") { - return - } - root := strings.TrimPrefix(plugin, "fs://") - rootAbs, err := filepath.Abs(root) - if err != nil { - log.Panicf("路径错误 %s %s", root, err) - } - go share.Watch(rootAbs, func(op string, file string) { - if !strings.HasSuffix(file, ".so") { - return - } - - if op == "write" || op == "create" { - script := share.GetAppPluginFile(root, file) - gou.LoadPlugin(script.File, script.Name) // Reload - log.Printf("Plugin %s 已重新加载完毕", script.Name) - } else if op == "remove" || op == "rename" { - name := share.GetAppPluginFileName(root, file) - if _, has := gou.Plugins[name]; has { - delete(gou.Plugins, name) - log.Printf("Plugin %s 已经移除", name) - } - } - }) -} diff --git a/service/watch.go b/service/watch.go new file mode 100644 index 00000000..5c0a8c55 --- /dev/null +++ b/service/watch.go @@ -0,0 +1,248 @@ +package service + +import ( + "fmt" + "log" + "path/filepath" + "strings" + + "github.com/yaoapp/gou" + "github.com/yaoapp/xiang/chart" + "github.com/yaoapp/xiang/config" + "github.com/yaoapp/xiang/share" + "github.com/yaoapp/xiang/table" +) + +// Watch 监听应用目录文件变更 +func Watch(cfg config.Config) { + WatchEngine(cfg.Path) + WatchModel(cfg.RootModel, "") + WatchAPI(cfg.RootAPI, "") + WatchFlow(cfg.RootFLow, "") + WatchPlugin(cfg.RootPlugin) + WatchTable(cfg.RootTable, "") + WatchChart(cfg.RootChart, "") +} + +// WatchEngine 监听监听引擎内建数据变更 +func WatchEngine(root string) { + if !strings.HasPrefix(root, "fs://") && strings.Contains(root, "://") { + return + } + root = strings.TrimPrefix(root, "fs://") + root = share.DirAbs(root) + + WatchModel(filepath.Join(root, "models"), "xiang.") + WatchAPI(filepath.Join(root, "apis"), "xiang.") + WatchFlow(filepath.Join(root, "flows"), "xiang.") + WatchTable(filepath.Join(root, "tables"), "xiang.") +} + +// WatchModel 监听业务接口更新 +func WatchModel(root string, prefix string) { + if share.DirNotExists(root) { + return + } + root = share.DirAbs(root) + go share.Watch(root, func(op string, filename string) { + + if !strings.HasSuffix(filename, ".json") { + return + } + if op == "write" || op == "create" { + name := prefix + share.SpecName(root, filename) + content := share.ReadFile(filename) + gou.LoadModel(string(content), name) // Reload + log.Printf("Model %s 已重新加载完毕", name) + + } else if op == "remove" || op == "rename" { + name := prefix + share.SpecName(root, filename) + if _, has := gou.Models[name]; has { + delete(gou.Models, name) + log.Printf("Model %s 已经移除", name) + } + } + }) +} + +// WatchAPI 监听业务接口更新 +func WatchAPI(root string, prefix string) { + if share.DirNotExists(root) { + return + } + root = share.DirAbs(root) + go share.Watch(root, func(op string, filename string) { + + if !strings.HasSuffix(filename, ".json") { + return + } + + if op == "write" || op == "create" { + name := prefix + share.SpecName(root, filename) + content := share.ReadFile(filename) + gou.LoadAPI(string(content), name) // Reload + log.Printf("API %s 已重新加载完毕", name) + + } else if op == "remove" || op == "rename" { + name := prefix + share.SpecName(root, filename) + if _, has := gou.APIs[name]; has { + delete(gou.APIs, name) + log.Printf("API %s 已经移除", name) + } + } + + // 重启服务器 + if op == "write" || op == "create" || op == "remove" || op == "rename" { + Stop(func() { + log.Printf("服务器重启完毕") + go Start() + }) + } + }) +} + +// WatchFlow 监听业务逻辑变更 +func WatchFlow(root string, prefix string) { + if share.DirNotExists(root) { + return + } + root = share.DirAbs(root) + go share.Watch(root, func(op string, filename string) { + if !strings.HasSuffix(filename, ".json") && !strings.HasSuffix(filename, ".js") { + return + } + + if strings.HasSuffix(filename, ".js") { + name := prefix + share.SpecName(root, filename) + filename = name + ".flow.json" + } + + if op == "write" || op == "create" { + name := prefix + share.SpecName(root, filename) + content := share.ReadFile(filename) + gou.LoadFlow(string(content), name) // Reload + log.Printf("Flow %s 已重新加载完毕", name) + + } else if op == "remove" || op == "rename" { + name := prefix + share.SpecName(root, filename) + if _, has := gou.Flows[name]; has { + delete(gou.Flows, name) + log.Printf("Flow %s 已经移除", name) + } + } + }) +} + +// WatchPlugin 监听业务插件变更 +func WatchPlugin(root string) { + if share.DirNotExists(root) { + return + } + root = share.DirAbs(root) + go share.Watch(root, func(op string, filename string) { + + if !strings.HasSuffix(filename, ".so") { + return + } + + if op == "write" || op == "create" { + name := share.SpecName(root, filename) + gou.LoadPlugin(filename, name) // Reload + log.Printf("Plugin %s 已重新加载完毕", name) + + } else if op == "remove" || op == "rename" { + name := share.SpecName(root, filename) + if _, has := gou.Plugins[name]; has { + delete(gou.Plugins, name) + log.Printf("Plugin %s 已经移除", name) + } + } + }) +} + +// WatchTable 监听数据表格更新 +func WatchTable(root string, prefix string) { + if share.DirNotExists(root) { + return + } + root = share.DirAbs(root) + go share.Watch(root, func(op string, filename string) { + + if !strings.HasSuffix(filename, ".json") { + return + } + + if op == "write" || op == "create" { + name := prefix + share.SpecName(root, filename) + content := share.ReadFile(filename) + table.LoadTable(string(content), name) // Reload Table + + api, has := gou.APIs["xiang.table"] + if has { + api.Reload() // 重载API + } + log.Printf("数据表格 %s 已重新加载完毕", name) + + } else if op == "remove" || op == "rename" { + name := prefix + share.SpecName(root, filename) + if _, has := table.Tables[name]; has { + delete(table.Tables, name) + log.Printf("数据表格 %s 已经移除", name) + } + } + + // 重启服务器 + if op == "write" || op == "create" || op == "remove" || op == "rename" { + Stop(func() { + log.Printf("服务器重启完毕") + go Start() + }) + } + }) +} + +// WatchChart 监听分析图表更新 +func WatchChart(root string, prefix string) { + if share.DirNotExists(root) { + return + } + root = share.DirAbs(root) + go share.Watch(root, func(op string, filename string) { + if !strings.HasSuffix(filename, ".json") && !strings.HasSuffix(filename, ".js") { + return + } + + if strings.HasSuffix(filename, ".js") { + name := prefix + share.SpecName(root, filename) + filename = name + ".chart.json" + } + + if op == "write" || op == "create" { + name := prefix + share.SpecName(root, filename) + content := share.ReadFile(filename) + chart.LoadChart(content, name) // Relaod + + api, has := gou.APIs["xiang.chart"] + if has { + fmt.Println("Reload API:", "--") + api.Reload() // 重载API + } + log.Printf("Chart %s 已重新加载完毕", name) + + } else if op == "remove" || op == "rename" { + name := prefix + share.SpecName(root, filename) + if _, has := chart.Charts[name]; has { + delete(chart.Charts, name) + log.Printf("Chart %s 已经移除", name) + } + } + + // 重启服务器 + if op == "write" || op == "create" || op == "remove" || op == "rename" { + Stop(func() { + log.Printf("服务器重启完毕") + go Start() + }) + } + }) +} diff --git a/share/utils.go b/share/utils.go index 9ba1a304..9252024d 100644 --- a/share/utils.go +++ b/share/utils.go @@ -2,6 +2,7 @@ package share import ( "io/ioutil" + "log" "os" "path" "path/filepath" @@ -61,6 +62,17 @@ func DirNotExists(dir string) bool { return false } +// DirAbs 文件绝对路径 +func DirAbs(dir string) string { + dir = strings.TrimPrefix(dir, "fs://") + dir = strings.TrimPrefix(dir, "file://") + dirAbs, err := filepath.Abs(dir) + if err != nil { + log.Panicf("获取绝对路径错误 %s %s", dir, err) + } + return dirAbs +} + // ************************************************ // 警告: 以下函数将被弃用 // ************************************************ diff --git a/engine/watch_test.go b/share/watch_test.go similarity index 70% rename from engine/watch_test.go rename to share/watch_test.go index ed47a4d8..3fe472b3 100644 --- a/engine/watch_test.go +++ b/share/watch_test.go @@ -1,4 +1,4 @@ -package engine +package share import ( "log" @@ -8,16 +8,15 @@ import ( "github.com/stretchr/testify/assert" "github.com/yaoapp/xiang/config" - "github.com/yaoapp/xiang/share" ) func TestWatch(t *testing.T) { root := path.Join(config.Conf.Source, "/tests/flows") assert.NotPanics(t, func() { - go share.Watch(root, func(op string, file string) { + go Watch(root, func(op string, file string) { log.Println(op, file) }) time.Sleep(time.Second * 2) - defer share.StopWatch() + defer StopWatch() }) }