From 26d3f8d8a540246f2e281197a3971cad8f17e364 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 14 Oct 2022 21:25:04 +0800 Subject: [PATCH] [add] Yao Stuido Server & CLI --- cmd/brain.go | 1 - cmd/root.go | 18 ++++++++- cmd/service.go | 1 + cmd/start.go | 31 +++++++++++++++ cmd/studio/run.go | 93 +++++++++++++++++++++++++++++++++++++++++++ cmd/studio/utils.go | 53 ++++++++++++++++++++++++ engine/load.go | 7 ++++ fs/fs.go | 6 +-- fs/fs_test.go | 13 +++++- studio/middleware.go | 16 +++++++- studio/router.go | 6 +-- studio/studio.go | 4 +- studio/studio_test.go | 6 ++- 13 files changed, 240 insertions(+), 15 deletions(-) delete mode 100644 cmd/brain.go create mode 100644 cmd/studio/run.go create mode 100644 cmd/studio/utils.go diff --git a/cmd/brain.go b/cmd/brain.go deleted file mode 100644 index 1d619dd0..00000000 --- a/cmd/brain.go +++ /dev/null @@ -1 +0,0 @@ -package cmd diff --git a/cmd/root.go b/cmd/root.go index 84d63d31..ca3009a6 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/cobra" "github.com/yaoapp/kun/exception" + "github.com/yaoapp/yao/cmd/studio" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/share" ) @@ -84,8 +85,23 @@ var rootCmd = &cobra.Command{ }, } +var studioCmd = &cobra.Command{ + Use: "studio", + Short: "Yao Studio CLI", + Long: `Yao Studio CLI`, + Args: cobra.MinimumNArgs(1), + CompletionOptions: cobra.CompletionOptions{ + DisableDefaultCmd: true, + }, + Run: func(cmd *cobra.Command, args []string) { + fmt.Fprintln(os.Stderr, L("One or more arguments are not correct"), args) + os.Exit(1) + }, +} + // 加载命令 func init() { + studioCmd.AddCommand(studio.RunCmd) rootCmd.AddCommand( versionCmd, migrateCmd, @@ -93,11 +109,11 @@ func init() { startCmd, runCmd, initCmd, - serviceCmd, dumpCmd, restoreCmd, socketCmd, websocketCmd, + studioCmd, ) // rootCmd.SetHelpCommand(helpCmd) rootCmd.PersistentFlags().StringVarP(&appPath, "app", "a", "", L("Application directory")) diff --git a/cmd/service.go b/cmd/service.go index 59e516fd..e0eed14e 100644 --- a/cmd/service.go +++ b/cmd/service.go @@ -14,6 +14,7 @@ import ( "github.com/yaoapp/yao/share" ) +// **** DEPRECATED **** var serviceCmd = &cobra.Command{ Use: "service", Short: L("Service manager"), diff --git a/cmd/start.go b/cmd/start.go index 98e5d30f..4e9c6a29 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -23,6 +23,7 @@ import ( "github.com/yaoapp/yao/fs" "github.com/yaoapp/yao/service" "github.com/yaoapp/yao/share" + "github.com/yaoapp/yao/studio" ) var startDebug = false @@ -90,11 +91,18 @@ var startCmd = &cobra.Command{ // development mode if mode == "development" { + + // Start Studio Server + go studio.Start(config.Conf) + defer studio.Stop() + printApis(false) printTasks(false) printSchedules(false) printConnectors(false) printStores(false) + printStudio(false, host) + } if mode == "development" && !startDisableWatching { @@ -185,6 +193,29 @@ func printStores(silent bool) { } } +func printStudio(silent bool, host string) { + + if silent { + log.Info("[Studio] http://%s:%d", host, config.Conf.Studio.Port) + if config.Conf.Studio.Auto { + log.Info("[Studio] Secret: %s", config.Conf.Studio.Secret) + } + return + } + + fmt.Println(color.WhiteString("\n---------------------------------")) + fmt.Println(color.WhiteString(L("Yao Studio Server"))) + fmt.Println(color.WhiteString("---------------------------------")) + fmt.Printf(color.CyanString("HOST : ")) + fmt.Printf(color.WhiteString(" %s\n", config.Conf.Host)) + fmt.Printf(color.CyanString("PORT : ")) + fmt.Printf(color.WhiteString(" %d\n", config.Conf.Studio.Port)) + if config.Conf.Studio.Auto { + fmt.Printf(color.CyanString("SECRET: ")) + fmt.Printf(color.WhiteString(" %s\n", config.Conf.Studio.Secret)) + } +} + func printSchedules(silent bool) { if len(gou.Schedules) == 0 { diff --git a/cmd/studio/run.go b/cmd/studio/run.go new file mode 100644 index 00000000..5794959c --- /dev/null +++ b/cmd/studio/run.go @@ -0,0 +1,93 @@ +package studio + +import ( + "fmt" + "strings" + + "github.com/fatih/color" + jsoniter "github.com/json-iterator/go" + "github.com/spf13/cobra" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/kun/utils" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/engine" + "github.com/yaoapp/yao/share" +) + +// RunCmd command +var RunCmd = &cobra.Command{ + Use: "run", + Short: L("Execute Yao Studio Script"), + Long: L("Execute Yao Studio Script"), + Run: func(cmd *cobra.Command, args []string) { + defer share.SessionStop() + defer gou.KillPlugins() + defer func() { + err := exception.Catch(recover()) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + } + }() + + Boot() + cfg := config.Conf + cfg.Session.IsCLI = true + engine.Load(cfg) + if len(args) < 1 { + fmt.Println(color.RedString(L("Not enough arguments"))) + fmt.Println(color.WhiteString(share.BUILDNAME + " help")) + return + } + + name := strings.Split(args[0], ".") + service := strings.Join(name[0:len(name)-1], ".") + method := name[len(name)-1] + + fmt.Println(color.GreenString(L("Studio Run: %s"), args[0])) + pargs := []interface{}{} + for i, arg := range args { + if i == 0 { + continue + } + + if strings.HasPrefix(arg, "::") { + arg := strings.TrimPrefix(arg, "::") + var v interface{} + err := jsoniter.Unmarshal([]byte(arg), &v) + if err != nil { + fmt.Println(color.RedString(L("Arguments: %s"), err.Error())) + return + } + pargs = append(pargs, v) + fmt.Println(color.WhiteString("args[%d]: %s", i-1, arg)) + } else if strings.HasPrefix(arg, "\\::") { + arg := "::" + strings.TrimPrefix(arg, "\\::") + pargs = append(pargs, arg) + fmt.Println(color.WhiteString("args[%d]: %s", i-1, arg)) + } else { + pargs = append(pargs, arg) + fmt.Println(color.WhiteString("args[%d]: %s", i-1, arg)) + } + } + + req := gou.Yao.New(service, method) + res, err := req.RootCall(pargs...) + if err != nil { + fmt.Println(color.RedString("--------------------------------------")) + fmt.Println(color.RedString(L("%s Error"), args[0])) + fmt.Println(color.RedString("--------------------------------------")) + utils.Dump(err) + fmt.Println(color.RedString("--------------------------------------")) + fmt.Println(color.GreenString(L("✨DONE✨"))) + return + } + + fmt.Println(color.WhiteString("--------------------------------------")) + fmt.Println(color.WhiteString(L("%s Response"), args[0])) + fmt.Println(color.WhiteString("--------------------------------------")) + utils.Dump(res) + fmt.Println(color.WhiteString("--------------------------------------")) + fmt.Println(color.GreenString(L("✨DONE✨"))) + }, +} diff --git a/cmd/studio/utils.go b/cmd/studio/utils.go new file mode 100644 index 00000000..fd101856 --- /dev/null +++ b/cmd/studio/utils.go @@ -0,0 +1,53 @@ +package studio + +import ( + "os" + "path/filepath" + + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/yao/config" +) + +var appPath string +var envFile string + +var langs = map[string]string{ + "Start Engine": "启动象传应用引擎", +} + +// L 多语言切换 +func L(words string) string { + + var lang = os.Getenv("YAO_LANG") + if lang == "" { + return words + } + + if trans, has := langs[words]; has { + return trans + } + return words +} + +// Boot 设定配置 +func Boot() { + root := config.Conf.Root + if appPath != "" { + r, err := filepath.Abs(appPath) + if err != nil { + exception.New("Root error %s", 500, err.Error()).Throw() + } + root = r + } + if envFile != "" { + config.Conf = config.LoadFrom(envFile) + } else { + config.Conf = config.LoadFrom(filepath.Join(root, ".env")) + } + + if config.Conf.Mode == "production" { + config.Production() + } else if config.Conf.Mode == "development" { + config.Development() + } +} diff --git a/engine/load.go b/engine/load.go index ab3c2355..06d705e1 100644 --- a/engine/load.go +++ b/engine/load.go @@ -27,6 +27,7 @@ import ( "github.com/yaoapp/yao/share" "github.com/yaoapp/yao/socket" "github.com/yaoapp/yao/store" + "github.com/yaoapp/yao/studio" "github.com/yaoapp/yao/table" "github.com/yaoapp/yao/task" "github.com/yaoapp/yao/websocket" @@ -64,6 +65,12 @@ func Load(cfg config.Config) (err error) { printErr(cfg.Mode, "FileSystem", err) } + // Load Studio + err = studio.Load(cfg) + if err != nil { + printErr(cfg.Mode, "Studio", err) + } + // 第二步: 建立数据库 & 会话连接 share.DBConnect(cfg.DB) // 创建数据库连接 // share.SessionConnect(cfg.Session) // 创建会话服务器链接 diff --git a/fs/fs.go b/fs/fs.go index 194c67da..d5b58c82 100644 --- a/fs/fs.go +++ b/fs/fs.go @@ -34,9 +34,9 @@ func Load(cfg config.Config) error { } fs.Register("system", system.New(dataRoot)) - fs.Register("dsl", dsl.New(root).DenyAbs(dslDenyList...)) // DSL - fs.Register("script", system.New(scriptRoot)) // Script - fs.Register("binary", system.New(root)) // Binary + // fs.Register("binary", system.New(root)) // Next + fs.RootRegister("dsl", dsl.New(root).DenyAbs(dslDenyList...)) // DSL + fs.RootRegister("script", system.New(scriptRoot)) // Script return nil } diff --git a/fs/fs_test.go b/fs/fs_test.go index cf352c8f..6e7f4c3f 100644 --- a/fs/fs_test.go +++ b/fs/fs_test.go @@ -33,7 +33,7 @@ func TestLoad(t *testing.T) { func TestDSL(t *testing.T) { Load(config.Conf) - dsl := fs.MustGet("dsl") + dsl := fs.MustRootGet("dsl") name := filepath.Join("models", "test.mod.json") _, err := fs.WriteFile(dsl, name, []byte(`{"foo": "bar", "hello":{ "int": 1, "float": 0.618}}`), 0644) assert.Nil(t, err) @@ -45,11 +45,16 @@ func TestDSL(t *testing.T) { if err != nil { t.Fatal(err) } + + _, err = fs.Get("dsl") + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "dsl does not registered") + } func TestScirpt(t *testing.T) { Load(config.Conf) - script := fs.MustGet("script") + script := fs.MustRootGet("script") name := "test.js" _, err := fs.WriteFile(script, name, []byte(`console.log("hello")`), 0644) assert.Nil(t, err) @@ -61,4 +66,8 @@ func TestScirpt(t *testing.T) { if err != nil { t.Fatal(err) } + + _, err = fs.Get("script") + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "script does not registered") } diff --git a/studio/middleware.go b/studio/middleware.go index 437d7a31..65ec3118 100644 --- a/studio/middleware.go +++ b/studio/middleware.go @@ -45,8 +45,18 @@ func hdRecovered(c *gin.Context, recovered interface{}) { c.AbortWithStatus(code) } -// cross domian -func hdCrossDomain(c *gin.Context) { +// CORS Cross-origin +func hdCORS(c *gin.Context) { + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") + c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT") + + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + c.Next() } // studio API Auth @@ -63,6 +73,7 @@ func hdAuth(c *gin.Context) { claims := helper.JwtValidate(tokenString, config.Conf.Studio.Secret) c.Set("__sid", claims.SID) + c.Next() return } else if strings.HasPrefix(tokenString, "Signature ") { // For Yao Studio @@ -71,6 +82,7 @@ func hdAuth(c *gin.Context) { ts := c.Request.Header.Get("Studio-Timestamp") query := c.Request.URL.Query() log.Trace("[Studio] %s, %s %s %v", signature, nonce, ts, query) + c.Next() return } diff --git a/studio/router.go b/studio/router.go index 8623f684..14ec80cc 100644 --- a/studio/router.go +++ b/studio/router.go @@ -18,7 +18,7 @@ var regExcp = regexp.MustCompile("^Exception\\|([0-9]+):(.+)$") // Serve start the api server func setRouter(router *gin.Engine) { - router.Use(gin.CustomRecovery(hdRecovered), hdAuth) + router.Use(gin.CustomRecovery(hdRecovered), hdCORS, hdAuth) // DSL ReadDir, ReadFile router.GET("/dsl/:method", func(c *gin.Context) { @@ -182,7 +182,7 @@ func setRouter(router *gin.Engine) { return } - service := fmt.Sprintf("__yao.studio.%s", c.Param("name")) + service := c.Param("name") payload, err := io.ReadAll(c.Request.Body) if err != nil { @@ -207,7 +207,7 @@ func setRouter(router *gin.Engine) { req.WithSid(fmt.Sprintf("%s", sid)) } - res, err := req.Call(fun.Args...) + res, err := req.RootCall(fun.Args...) if err != nil { // parse Exception code := 500 diff --git a/studio/studio.go b/studio/studio.go index b704e0ef..4e3ab054 100644 --- a/studio/studio.go +++ b/studio/studio.go @@ -134,8 +134,8 @@ func loadScriptFrom(dir string) error { messages := []string{} err := share.Walk(dir, ".js", func(root, filename string) { - name := fmt.Sprintf("__yao.studio.%s", share.SpecName(root, filename)) - err := gou.Yao.Load(filename, name) + name := share.SpecName(root, filename) + err := gou.Yao.RootLoad(filename, name) if err != nil { messages = append(messages, err.Error()) } diff --git a/studio/studio_test.go b/studio/studio_test.go index 2bb773c2..a3db0ff4 100644 --- a/studio/studio_test.go +++ b/studio/studio_test.go @@ -25,9 +25,13 @@ func TestLoad(t *testing.T) { } assert.NotNil(t, dfs) - res, err := gou.Yao.Engine.Call(map[string]interface{}{}, "__yao.studio.table", "Ping") + res, err := gou.Yao.Engine.RootCall(map[string]interface{}{}, "table", "Ping") assert.Nil(t, err) assert.Equal(t, "PONG", res) + + _, err = gou.Yao.Engine.Call(map[string]interface{}{}, "table", "Ping") + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "The table does not loaded") } func TestStartStop(t *testing.T) {