[add] Yao Stuido Server & CLI

This commit is contained in:
Max 2022-10-14 21:25:04 +08:00
parent cd28cca8d6
commit 26d3f8d8a5
13 changed files with 240 additions and 15 deletions

View file

@ -1 +0,0 @@
package cmd

View file

@ -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"))

View file

@ -14,6 +14,7 @@ import (
"github.com/yaoapp/yao/share"
)
// **** DEPRECATED ****
var serviceCmd = &cobra.Command{
Use: "service",
Short: L("Service manager"),

View file

@ -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 {

93
cmd/studio/run.go Normal file
View file

@ -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✨")))
},
}

53
cmd/studio/utils.go Normal file
View file

@ -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()
}
}

View file

@ -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) // 创建会话服务器链接

View file

@ -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
}

View file

@ -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")
}

View file

@ -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
}

View file

@ -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

View file

@ -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())
}

View file

@ -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) {