diff --git a/Makefile b/Makefile index 6a518b19..0ef40c42 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ GOFILES := $(shell find . -name "*.go") VERSION := $(shell grep 'const VERSION =' share/vars.go |awk '{print $$4}' |sed 's/\"//g') # ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) -TESTFOLDER := $(shell $(GO) list ./... | grep -E 'xiang$$|global$$|table$$|user$$|xfs$$' | grep -v examples) +TESTFOLDER := $(shell $(GO) list ./... | grep -E 'xiang$$|entry$$|table$$|user$$|xfs$$' | grep -v examples) TESTTAGS ?= "" # 运行单元测试 diff --git a/cmd/migrate.go b/cmd/migrate.go index 2173e8af..30b10d8a 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" "github.com/yaoapp/gou" "github.com/yaoapp/xiang/config" - "github.com/yaoapp/xiang/global" + "github.com/yaoapp/xiang/entry" ) var name string @@ -18,7 +18,7 @@ var migrateCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { Boot() // 加载数据模型 - global.Load(config.Conf) + entry.Load(config.Conf) if name != "" { mod, has := gou.Models[name] diff --git a/cmd/start.go b/cmd/start.go index fa080523..ed7ff2fa 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -9,7 +9,8 @@ import ( "github.com/spf13/cobra" "github.com/yaoapp/gou" "github.com/yaoapp/xiang/config" - "github.com/yaoapp/xiang/global" + "github.com/yaoapp/xiang/entry" + "github.com/yaoapp/xiang/service" "github.com/yaoapp/xiang/share" ) @@ -18,7 +19,7 @@ var startCmd = &cobra.Command{ Short: "启动象传应用引擎", Long: `启动象传应用引擎`, Run: func(cmd *cobra.Command, args []string) { - defer global.ServiceStop(func() { fmt.Println("服务已关闭") }) + defer service.Stop(func() { fmt.Println("服务已关闭") }) Boot() mode := config.Conf.Mode @@ -31,7 +32,7 @@ var startCmd = &cobra.Command{ fmt.Printf(color.GreenString("\n象传应用引擎 v%s %s", share.VERSION, mode)) // 加载数据模型 API 等 - global.Load(config.Conf) + entry.Load(config.Conf) // 打印应用目录信息 fmt.Printf(color.WhiteString("\n---------------------------------")) @@ -78,10 +79,10 @@ var startCmd = &cobra.Command{ // 调试模式 if config.Conf.Mode == "debug" { - global.WatchChanges() + service.WatchChanges() } - global.ServiceStart() + service.Start() }, } diff --git a/entry/READE.md b/entry/READE.md new file mode 100644 index 00000000..11985dfc --- /dev/null +++ b/entry/READE.md @@ -0,0 +1,3 @@ +# 程序入口 + +加载各种文件 diff --git a/global/init_test.go b/entry/init_test.go similarity index 95% rename from global/init_test.go rename to entry/init_test.go index c3760965..02dea779 100644 --- a/global/init_test.go +++ b/entry/init_test.go @@ -1,4 +1,4 @@ -package global +package entry import ( "os" diff --git a/global/load.go b/entry/load.go similarity index 56% rename from global/load.go rename to entry/load.go index 8aee3ce0..d8cece70 100644 --- a/global/load.go +++ b/entry/load.go @@ -1,11 +1,9 @@ -package global +package entry import ( "fmt" - "io/ioutil" "log" "os" - "path" "path/filepath" "strings" @@ -21,29 +19,6 @@ import ( "github.com/yaoapp/xun/capsule" ) -// Conf 配置文件 -var Conf config.Config - -// Script 脚本文件类型 -type Script struct { - Name string - Type string - Content []byte - File string -} - -// AppRoot 应用目录 -type AppRoot struct { - APIs string - Flows string - Models string - Plugins string - Tables string - Charts string - Screens string - Data string -} - // Load 根据配置加载 API, FLow, Model, Plugin func Load(cfg config.Config) { @@ -51,7 +26,7 @@ func Load(cfg config.Config) { DBConnect(cfg.Database) LoadAppInfo(cfg.Root) LoadEngine(cfg.Path) - LoadApp(AppRoot{ + LoadApp(share.AppRoot{ APIs: cfg.RootAPI, Flows: cfg.RootFLow, Models: cfg.RootModel, @@ -65,9 +40,6 @@ func Load(cfg config.Config) { // 加密密钥函数 gou.LoadCrypt(fmt.Sprintf(`{"key":"%s"}`, cfg.Database.AESKey), "AES") gou.LoadCrypt(`{}`, "PASSWORD") - - // 设定已加载配置 - Conf = cfg } // LoadAppInfo 读取应用信息 @@ -167,13 +139,13 @@ func AppInit(cfg config.Config) { // LoadEngine 加载引擎的 API, Flow, Model 配置 func LoadEngine(from string) { - var scripts []Script + var scripts []share.Script if strings.HasPrefix(from, "fs://") || !strings.Contains(from, "://") { root := strings.TrimPrefix(from, "fs://") - scripts = getFilesFS(root, ".json") + scripts = share.GetFilesFS(root, ".json") } else if strings.HasPrefix(from, "bin://") { root := strings.TrimPrefix(from, "bin://") - scripts = getFilesBin(root, ".json") + scripts = share.GetFilesBin(root, ".json") } if scripts == nil { @@ -210,7 +182,7 @@ func LoadEngine(from string) { } // LoadApp 加载应用的 API, Flow, Model 和 Plugin -func LoadApp(app AppRoot) { +func LoadApp(app share.AppRoot) { // api string, flow string, model string, plugin string // 创建应用目录 @@ -235,7 +207,7 @@ func LoadApp(app AppRoot) { // 加载API if strings.HasPrefix(app.APIs, "fs://") || !strings.Contains(app.APIs, "://") { root := strings.TrimPrefix(app.APIs, "fs://") - scripts := getAppFilesFS(root, ".json") + scripts := share.GetAppFilesFS(root, ".json") for _, script := range scripts { // 验证API 加载逻辑 gou.LoadAPI(string(script.Content), script.Name) @@ -245,7 +217,7 @@ func LoadApp(app AppRoot) { // 加载Flow if strings.HasPrefix(app.Flows, "fs://") || !strings.Contains(app.Flows, "://") { root := strings.TrimPrefix(app.Flows, "fs://") - scripts := getAppFilesFS(root, ".json") + scripts := share.GetAppFilesFS(root, ".json") for _, script := range scripts { gou.LoadFlow(string(script.Content), script.Name) } @@ -254,7 +226,7 @@ func LoadApp(app AppRoot) { // 加载Model if strings.HasPrefix(app.Models, "fs://") || !strings.Contains(app.Models, "://") { root := strings.TrimPrefix(app.Models, "fs://") - scripts := getAppFilesFS(root, ".json") + scripts := share.GetAppFilesFS(root, ".json") for _, script := range scripts { gou.LoadModel(string(script.Content), script.Name) } @@ -263,7 +235,7 @@ func LoadApp(app AppRoot) { // 加载Plugin if strings.HasPrefix(app.Plugins, "fs://") || !strings.Contains(app.Plugins, "://") { root := strings.TrimPrefix(app.Plugins, "fs://") - scripts := getAppPlugins(root, ".so") + scripts := share.GetAppPlugins(root, ".so") for _, script := range scripts { gou.LoadPlugin(script.File, script.Name) } @@ -272,7 +244,7 @@ func LoadApp(app AppRoot) { // 加载Table if strings.HasPrefix(app.Tables, "fs://") || !strings.Contains(app.Tables, "://") { root := strings.TrimPrefix(app.Tables, "fs://") - scripts := getAppFilesFS(root, ".json") + scripts := share.GetAppFilesFS(root, ".json") for _, script := range scripts { // 验证API 加载逻辑 table.Load(string(script.Content), script.Name) @@ -280,175 +252,3 @@ func LoadApp(app AppRoot) { } } - -// / getAppPluins 遍历应用目录,读取文件列表 -func getAppPlugins(root string, typ string) []Script { - files := []Script{} - root = path.Join(root, "/") - filepath.Walk(root, func(file string, info os.FileInfo, err error) error { - if err != nil { - exception.Err(err, 500).Throw() - return err - } - if strings.HasSuffix(file, typ) { - files = append(files, getAppPluginFile(root, file)) - } - return nil - }) - return files -} - -// getAppPluginFile 读取文件 -func getAppPluginFile(root string, file string) Script { - name := getAppPluginFileName(root, file) - return Script{ - Name: name, - Type: "plugin", - File: file, - } -} - -// getAppFile 读取文件 -func getAppPluginFileName(root string, file string) string { - filename := strings.TrimPrefix(file, root+"/") - namer := strings.Split(filename, ".") - nametypes := strings.Split(namer[0], "/") - name := strings.Join(nametypes, ".") - return name -} - -// getAppFilesFS 遍历应用目录,读取文件列表 -func getAppFilesFS(root string, typ string) []Script { - files := []Script{} - root = path.Join(root, "/") - filepath.Walk(root, func(filepath string, info os.FileInfo, err error) error { - if err != nil { - exception.Err(err, 500).Throw() - return err - } - if strings.HasSuffix(filepath, typ) { - files = append(files, getAppFile(root, filepath)) - } - - return nil - }) - return files -} - -// getAppFile 读取文件 -func getAppFile(root string, filepath string) Script { - name := getAppFileName(root, filepath) - file, err := os.Open(filepath) - if err != nil { - exception.Err(err, 500).Throw() - } - - defer file.Close() - content, err := ioutil.ReadAll(file) - if err != nil { - exception.Err(err, 500).Throw() - } - return Script{ - Name: name, - Type: "app", - Content: content, - } -} - -// getAppFile 读取文件 -func getAppFileName(root string, file string) string { - filename := strings.TrimPrefix(file, root+"/") - namer := strings.Split(filename, ".") - nametypes := strings.Split(namer[0], "/") - name := strings.Join(nametypes, ".") - return name -} - -// getAppFileBaseName 读取文件base -func getAppFileBaseName(root string, file string) string { - filename := strings.TrimPrefix(file, root+"/") - namer := strings.Split(filename, ".") - return filepath.Join(root, namer[0]) -} - -// getFilesFS 遍历目录,读取文件列表 -func getFilesFS(root string, typ string) []Script { - files := []Script{} - root = path.Join(root, "/") - filepath.Walk(root, func(path string, info os.FileInfo, err error) error { - if err != nil { - exception.Err(err, 500).Throw() - return err - } - if strings.HasSuffix(path, typ) { - files = append(files, getFile(root, path)) - } - return nil - }) - return files -} - -// getFile 读取文件 -func getFile(root string, path string) Script { - filename := strings.TrimPrefix(path, root+"/") - name, typ := getTypeName(filename) - file, err := os.Open(path) - if err != nil { - exception.Err(err, 500).Throw() - } - - defer file.Close() - content, err := ioutil.ReadAll(file) - if err != nil { - exception.Err(err, 500).Throw() - } - return Script{ - Name: name, - Type: typ, - Content: content, - } -} - -// getFileName 读取文件 -func getFileName(root string, file string) string { - filename := strings.TrimPrefix(file, root+"/") - name, _ := getTypeName(filename) - return name -} - -// getFileBaseName 读取文件base -func getFileBaseName(root string, file string) string { - filename := strings.TrimPrefix(file, root+"/") - namer := strings.Split(filename, ".") - return filepath.Join(root, namer[0]) -} - -// getFilesBin 从 bindata 中读取文件列表 -func getFilesBin(root string, typ string) []Script { - files := []Script{} - binfiles := data.AssetNames() - for _, path := range binfiles { - if strings.HasSuffix(path, typ) { - file := strings.TrimPrefix(path, root+"/") - name, typ := getTypeName(file) - content, err := data.Asset(path) - if err != nil { - exception.Err(err, 500).Throw() - } - files = append(files, Script{ - Name: name, - Type: typ, - Content: content, - }) - } - } - return files -} - -func getTypeName(path string) (name string, typ string) { - namer := strings.Split(path, ".") - nametypes := strings.Split(namer[0], "/") - name = strings.Join(nametypes[1:], ".") - typ = nametypes[0] - return name, typ -} diff --git a/global/load_test.go b/entry/load_test.go similarity index 95% rename from global/load_test.go rename to entry/load_test.go index 82dbfbb9..2f097ed2 100644 --- a/global/load_test.go +++ b/entry/load_test.go @@ -1,4 +1,4 @@ -package global +package entry import ( "path" @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/yaoapp/xiang/config" + "github.com/yaoapp/xiang/share" "github.com/yaoapp/xiang/table" ) @@ -39,7 +40,7 @@ func TestLoadEngineBin(t *testing.T) { func TestLoadAppFS(t *testing.T) { defer Load(config.Conf) assert.NotPanics(t, func() { - LoadApp(AppRoot{ + LoadApp(share.AppRoot{ APIs: config.Conf.RootAPI, Flows: config.Conf.RootFLow, Models: config.Conf.RootModel, diff --git a/global/process.go b/entry/process.go similarity index 74% rename from global/process.go rename to entry/process.go index 6f5cef57..e21f33c0 100644 --- a/global/process.go +++ b/entry/process.go @@ -1,18 +1,19 @@ -package global +package entry import ( "github.com/yaoapp/gou" + "github.com/yaoapp/xiang/config" "github.com/yaoapp/xiang/share" "github.com/yaoapp/xiang/xfs" ) func init() { // 注册处理器 - gou.RegisterProcessHandler("xiang.global.Ping", processPing) - gou.RegisterProcessHandler("xiang.global.FileContent", processFileContent) - gou.RegisterProcessHandler("xiang.global.AppFileContent", processAppFileContent) - gou.RegisterProcessHandler("xiang.global.Inspect", processInspect) - gou.RegisterProcessHandler("xiang.global.Favicon", processFavicon) + gou.RegisterProcessHandler("xiang.main.Ping", processPing) + gou.RegisterProcessHandler("xiang.main.FileContent", processFileContent) + gou.RegisterProcessHandler("xiang.main.AppFileContent", processAppFileContent) + gou.RegisterProcessHandler("xiang.main.Inspect", processInspect) + gou.RegisterProcessHandler("xiang.main.Favicon", processFavicon) } // processCreate 运行模型 MustCreate @@ -22,7 +23,7 @@ func processPing(process *gou.Process) interface{} { "server": "象传应用引擎", "version": share.VERSION, "domain": share.DOMAIN, - "allows": Conf.Service.Allow, + "allows": config.Conf.Service.Allow, } return res } @@ -53,7 +54,7 @@ func processFileContent(process *gou.Process) interface{} { // processAppFileContent 返回应用文件内容 func processAppFileContent(process *gou.Process) interface{} { process.ValidateArgNums(2) - fs := xfs.New(Conf.RootData) + fs := xfs.New(config.Conf.RootData) filename := process.ArgsString(0) encode := process.ArgsBool(1, true) content := fs.MustReadFile(filename) diff --git a/entry/process_test.go b/entry/process_test.go new file mode 100644 index 00000000..86cadfb1 --- /dev/null +++ b/entry/process_test.go @@ -0,0 +1,16 @@ +package entry + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou" + "github.com/yaoapp/xiang/share" +) + +func TestProcessPing(t *testing.T) { + process := gou.NewProcess("xiang.main.ping") + res, ok := processPing(process).(map[string]interface{}) + assert.True(t, ok) + assert.Equal(t, res["version"], share.VERSION) +} diff --git a/global/process_test.go b/entry/table_test.go similarity index 91% rename from global/process_test.go rename to entry/table_test.go index 7e4da023..b636dfbc 100644 --- a/global/process_test.go +++ b/entry/table_test.go @@ -1,4 +1,4 @@ -package global +package entry import ( "testing" @@ -7,19 +7,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/yaoapp/gou" "github.com/yaoapp/kun/any" - "github.com/yaoapp/xiang/share" "github.com/yaoapp/xiang/table" "github.com/yaoapp/xun/capsule" ) -func TestProcessPing(t *testing.T) { - process := gou.NewProcess("xiang.global.ping") - res, ok := processPing(process).(map[string]interface{}) - assert.True(t, ok) - assert.Equal(t, res["version"], share.VERSION) -} - -func TestProcessSearch(t *testing.T) { +func TestTableProcessSearch(t *testing.T) { args := []interface{}{ "service", @@ -47,7 +39,7 @@ func TestProcessSearch(t *testing.T) { assert.Equal(t, 2, res.Get("pagesize")) } -func TestProcessFind(t *testing.T) { +func TestTableProcessFind(t *testing.T) { args := []interface{}{ "service", 1, @@ -60,7 +52,7 @@ func TestProcessFind(t *testing.T) { assert.Equal(t, any.Of(res.Get("id")).CInt(), 1) } -func TestProcessSave(t *testing.T) { +func TestTableProcessSave(t *testing.T) { args := []interface{}{ "service", map[string]interface{}{ @@ -82,7 +74,7 @@ func TestProcessSave(t *testing.T) { capsule.Query().Table("service").Where("id", id).Delete() } -func TestProcessDelete(t *testing.T) { +func TestTableProcessDelete(t *testing.T) { args := []interface{}{ "service", map[string]interface{}{ @@ -111,7 +103,7 @@ func TestProcessDelete(t *testing.T) { capsule.Query().Table("service").Where("id", id).Delete() } -func TestProcessInsert(t *testing.T) { +func TestTableProcessInsert(t *testing.T) { args := []interface{}{ "service", []string{"name", "short_name", "kind_id", "manu_id", "price_options"}, @@ -128,7 +120,7 @@ func TestProcessInsert(t *testing.T) { capsule.Query().Table("service").Where("name", "like", "I腾讯云主机I%").Delete() } -func TestProcessDeleteWhere(t *testing.T) { +func TestTableProcessDeleteWhere(t *testing.T) { args := []interface{}{ "service", map[string]interface{}{ @@ -164,7 +156,7 @@ func TestProcessDeleteWhere(t *testing.T) { capsule.Query().Table("service").Where("id", id).Delete() } -func TestProcessDeleteIn(t *testing.T) { +func TestTableProcessDeleteIn(t *testing.T) { args := []interface{}{ "service", map[string]interface{}{ @@ -197,7 +189,7 @@ func TestProcessDeleteIn(t *testing.T) { capsule.Query().Table("service").Where("id", id).Delete() } -func TestProcessUpdateWhere(t *testing.T) { +func TestTableProcessUpdateWhere(t *testing.T) { args := []interface{}{ "service", map[string]interface{}{ @@ -236,7 +228,7 @@ func TestProcessUpdateWhere(t *testing.T) { capsule.Query().Table("service").Where("id", id).Delete() } -func TestProcessUpdateIn(t *testing.T) { +func TestTableProcessUpdateIn(t *testing.T) { args := []interface{}{ "service", map[string]interface{}{ @@ -272,7 +264,7 @@ func TestProcessUpdateIn(t *testing.T) { capsule.Query().Table("service").Where("id", id).Delete() } -func TestProcessSetting(t *testing.T) { +func TestTableProcessSetting(t *testing.T) { args := []interface{}{"service", ""} process := gou.NewProcess("xiang.table.Setting", args...) response := table.ProcessSetting(process) @@ -288,7 +280,7 @@ func TestProcessSetting(t *testing.T) { assert.True(t, res.Has("view")) assert.True(t, res.Has("insert")) } -func TestProcessSettingList(t *testing.T) { +func TestTableProcessSettingList(t *testing.T) { args := []interface{}{"service", "list"} process := gou.NewProcess("xiang.table.Setting", args...) response := table.ProcessSetting(process) @@ -299,7 +291,7 @@ func TestProcessSettingList(t *testing.T) { assert.True(t, res.Has("primary")) } -func TestProcessSettingListEdit(t *testing.T) { +func TestTableProcessSettingListEdit(t *testing.T) { args := []interface{}{"service", "list, edit"} process := gou.NewProcess("xiang.table.Setting", args...) response := table.ProcessSetting(process) diff --git a/global/user_test.go b/entry/user_test.go similarity index 97% rename from global/user_test.go rename to entry/user_test.go index 310fdefa..0036b136 100644 --- a/global/user_test.go +++ b/entry/user_test.go @@ -1,4 +1,4 @@ -package global +package entry import ( "testing" diff --git a/entry/watch_test.go b/entry/watch_test.go new file mode 100644 index 00000000..442446fa --- /dev/null +++ b/entry/watch_test.go @@ -0,0 +1,23 @@ +package entry + +import ( + "log" + "path" + "testing" + "time" + + "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, "/app/flows") + assert.NotPanics(t, func() { + go share.Watch(root, func(op string, file string) { + log.Println(op, file) + }) + time.Sleep(time.Second * 2) + defer share.StopWatch() + }) +} diff --git a/global/watch_test.go b/global/watch_test.go deleted file mode 100644 index 30f5792e..00000000 --- a/global/watch_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package global - -import ( - "log" - "path" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestWatch(t *testing.T) { - root := path.Join(Conf.Source, "/app/flows") - assert.NotPanics(t, func() { - go Watch(root, func(op string, file string) { - log.Println(op, file) - }) - time.Sleep(time.Second * 2) - defer StopWatch() - }) -} diff --git a/main_test.go b/main_test.go index f1f2413a..d3da2c32 100644 --- a/main_test.go +++ b/main_test.go @@ -15,7 +15,7 @@ import ( "github.com/yaoapp/kun/maps" "github.com/yaoapp/kun/utils" "github.com/yaoapp/xiang/config" - "github.com/yaoapp/xiang/global" + "github.com/yaoapp/xiang/service" ) func TestCommandVersion(t *testing.T) { @@ -42,7 +42,7 @@ func TestCommandStart(t *testing.T) { oldArgs := os.Args defer func() { os.Args = oldArgs - global.ServiceStop(func() {}) + service.Stop(func() {}) log.Println("服务已关闭") }() go func() { @@ -126,7 +126,7 @@ func TestCommandStop(t *testing.T) { assert.Equal(t, "管理员", res.Get("name")) // 测试关闭 - global.ServiceStop(func() { log.Println("服务已关闭") }) + service.Stop(func() { log.Println("服务已关闭") }) time.Sleep(time.Second * 2) _, err = request() assert.NotNil(t, err) diff --git a/global/guard.go b/service/guard.go similarity index 85% rename from global/guard.go rename to service/guard.go index 2edfb264..b9dad96d 100644 --- a/global/guard.go +++ b/service/guard.go @@ -1,4 +1,4 @@ -package global +package service import ( "fmt" @@ -6,6 +6,7 @@ import ( "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" + "github.com/yaoapp/xiang/config" "github.com/yaoapp/xiang/user" "github.com/yaoapp/xiang/xlog" ) @@ -25,11 +26,11 @@ func bearerJWT(c *gin.Context) { } tokenString = strings.TrimSpace(strings.TrimPrefix(tokenString, "Bearer ")) - if Conf.Mode == "debug" { - xlog.Printf("JWT: %s Secret: %s", tokenString, Conf.JWT.Secret) + if config.Conf.Mode == "debug" { + xlog.Printf("JWT: %s Secret: %s", tokenString, config.Conf.JWT.Secret) } token, err := jwt.ParseWithClaims(tokenString, &user.JwtClaims{}, func(token *jwt.Token) (interface{}, error) { - return []byte(Conf.JWT.Secret), nil + return []byte(config.Conf.JWT.Secret), nil }) if err != nil { diff --git a/global/middleware.go b/service/middleware.go similarity index 98% rename from global/middleware.go rename to service/middleware.go index 310be6b0..9d9053f7 100644 --- a/global/middleware.go +++ b/service/middleware.go @@ -1,4 +1,4 @@ -package global +package service import ( "fmt" diff --git a/global/service.go b/service/service.go similarity index 76% rename from global/service.go rename to service/service.go index eb92046f..f13a9777 100644 --- a/global/service.go +++ b/service/service.go @@ -1,4 +1,4 @@ -package global +package service import ( "log" @@ -6,20 +6,22 @@ import ( "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) var shutdownComplete = make(chan bool) -// ServiceStart 启动服务 -func ServiceStart() { +// Start 启动服务 +func Start() { gou.SetHTTPGuards(Guards) gou.ServeHTTP( gou.Server{ - Host: Conf.Service.Host, - Port: Conf.Service.Port, - Allows: Conf.Service.Allow, + Host: config.Conf.Service.Host, + Port: config.Conf.Service.Port, + Allows: config.Conf.Service.Allow, Root: "/api", }, &shutdown, func(s gou.Server) { @@ -28,8 +30,8 @@ func ServiceStart() { Middlewares...) } -// ServiceStop 关闭服务 -func ServiceStop(onComplete func()) { +// Stop 关闭服务 +func Stop(onComplete func()) { shutdown <- true <-shutdownComplete onComplete() @@ -37,15 +39,15 @@ func ServiceStop(onComplete func()) { // WatchChanges 监听配置文件变更 func WatchChanges() { - watchEngine(Conf.Path) - watchApp(AppRoot{ - APIs: Conf.RootAPI, - Flows: Conf.RootFLow, - Models: Conf.RootModel, - Plugins: Conf.RootPlugin, - Tables: Conf.RootTable, - Charts: Conf.RootChart, - Screens: Conf.RootScreen, + 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, }) } @@ -61,23 +63,23 @@ func watchEngine(from string) { } // 监听 flows (这里应该重构) - go Watch(filepath.Join(rootAbs, "flows"), func(op string, file string) { + go share.Watch(filepath.Join(rootAbs, "flows"), func(op string, file string) { if !strings.HasSuffix(file, ".json") { return } if strings.HasSuffix(file, ".js") { - basName := getFileBaseName(root, file) + basName := share.GetFileBaseName(root, file) file = basName + ".flow.json" } if op == "write" || op == "create" { - script := getFile(root, file) + 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." + getFileName(root, file) + name := "xiang." + share.GetFileName(root, file) if _, has := gou.Flows[name]; has { delete(gou.Flows, name) log.Printf("Flow %s 已经移除", name) @@ -86,17 +88,17 @@ func watchEngine(from string) { }) // 监听 models - go Watch(filepath.Join(rootAbs, "models"), func(op string, file string) { + go share.Watch(filepath.Join(rootAbs, "models"), func(op string, file string) { if !strings.HasSuffix(file, ".json") { return } if op == "write" || op == "create" { - script := getFile(root, file) + 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." + getFileName(root, file) + name := "xiang." + share.GetFileName(root, file) if _, has := gou.Models[name]; has { delete(gou.Models, name) log.Printf("Model %s 已经移除", name) @@ -105,13 +107,13 @@ func watchEngine(from string) { }) // 监听 apis - go Watch(filepath.Join(rootAbs, "apis"), func(op string, file string) { + go share.Watch(filepath.Join(rootAbs, "apis"), func(op string, file string) { if !strings.HasSuffix(file, ".json") { return } if op == "write" || op == "create" { - script := getFile(root, file) + script := share.GetFile(root, file) gou.LoadAPI(string(script.Content), "xiang."+script.Name) // Reload log.Printf("API %s 已重新加载完毕", "xiang."+script.Name) @@ -123,7 +125,7 @@ func watchEngine(from string) { } } else if op == "remove" || op == "rename" { - name := "xiang." + getFileName(root, file) + name := "xiang." + share.GetFileName(root, file) if _, has := gou.APIs[name]; has { delete(gou.APIs, name) log.Printf("API %s 已经移除", name) @@ -132,21 +134,21 @@ func watchEngine(from string) { // 重启服务器 if op == "write" || op == "create" || op == "remove" || op == "rename" { - ServiceStop(func() { + Stop(func() { log.Printf("服务器重启完毕") - go ServiceStart() + go Start() }) } }) // 监听 tables - go Watch(filepath.Join(rootAbs, "tables"), func(op string, file string) { + go share.Watch(filepath.Join(rootAbs, "tables"), func(op string, file string) { if !strings.HasSuffix(file, ".json") { return } if op == "write" || op == "create" { - script := getFile(root, file) + script := share.GetFile(root, file) table.Load(string(script.Content), "xiang."+script.Name) // Reload api, has := gou.APIs["xiang.table"] if has { @@ -156,7 +158,7 @@ func watchEngine(from string) { log.Printf("数据表格 %s 已重新加载完毕", "xiang."+script.Name) } else if op == "remove" || op == "rename" { - name := "xiang." + getFileName(root, file) + name := "xiang." + share.GetFileName(root, file) if _, has := table.Tables[name]; has { delete(table.Tables, name) log.Printf("数据表格 %s 已经移除", name) @@ -165,16 +167,16 @@ func watchEngine(from string) { // 重启服务器 if op == "write" || op == "create" || op == "remove" || op == "rename" { - ServiceStop(func() { + Stop(func() { log.Printf("服务器重启完毕") - go ServiceStart() + go Start() }) } }) } // watchApp 监听应用目录文件变更 -func watchApp(app AppRoot) { +func watchApp(app share.AppRoot) { watchAppAPI(app.APIs) watchAppFlow(app.Flows) watchAppModel(app.Models) @@ -193,13 +195,13 @@ func watchAppTable(rootTable string) { log.Panicf("路径错误 %s %s", root, err) } - go Watch(rootAbs, func(op string, file string) { + go share.Watch(rootAbs, func(op string, file string) { if !strings.HasSuffix(file, ".json") { return } if op == "write" || op == "create" { - script := getAppFile(root, file) + script := share.GetAppFile(root, file) table.Load(string(script.Content), script.Name) // Reload api, has := gou.APIs["xiang.table"] if has { @@ -209,7 +211,7 @@ func watchAppTable(rootTable string) { log.Printf("数据表格 %s 已重新加载完毕", script.Name) } else if op == "remove" || op == "rename" { - name := getAppFileName(root, file) + name := share.GetAppFileName(root, file) if _, has := gou.APIs[name]; has { delete(table.Tables, name) log.Printf("数据表格 %s 已经移除", name) @@ -218,9 +220,9 @@ func watchAppTable(rootTable string) { // 重启服务器 if op == "write" || op == "create" || op == "remove" || op == "rename" { - ServiceStop(func() { + Stop(func() { log.Printf("服务器重启完毕") - go ServiceStart() + go Start() }) } }) @@ -237,18 +239,18 @@ func watchAppAPI(api string) { log.Panicf("路径错误 %s %s", root, err) } - go Watch(rootAbs, func(op string, file string) { + go share.Watch(rootAbs, func(op string, file string) { if !strings.HasSuffix(file, ".json") { return } if op == "write" || op == "create" { - script := getAppFile(root, file) + 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 := getAppFileName(root, file) + name := share.GetAppFileName(root, file) if _, has := gou.APIs[name]; has { delete(gou.APIs, name) log.Printf("API %s 已经移除", name) @@ -257,9 +259,9 @@ func watchAppAPI(api string) { // 重启服务器 if op == "write" || op == "create" || op == "remove" || op == "rename" { - ServiceStop(func() { + Stop(func() { log.Printf("服务器重启完毕") - go ServiceStart() + go Start() }) } }) @@ -275,20 +277,20 @@ func watchAppFlow(flow string) { if err != nil { log.Panicf("路径错误 %s %s", root, err) } - go Watch(rootAbs, func(op string, file string) { + 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 := getAppFileBaseName(root, file) + basName := share.GetAppFileBaseName(root, file) file = basName + ".flow.json" } if op == "write" || op == "create" { - script := getAppFile(root, file) + 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 := getAppFileName(root, file) + name := share.GetAppFileName(root, file) if _, has := gou.Flows[name]; has { delete(gou.Flows, name) log.Printf("Flow %s 已经移除", name) @@ -308,16 +310,16 @@ func watchAppModel(model string) { if err != nil { log.Panicf("路径错误 %s %s", root, err) } - go Watch(rootAbs, func(op string, file string) { + go share.Watch(rootAbs, func(op string, file string) { if !strings.HasSuffix(file, ".json") { return } if op == "write" || op == "create" { - script := getAppFile(root, file) + 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 := getAppFileName(root, file) + name := share.GetAppFileName(root, file) if _, has := gou.Models[name]; has { delete(gou.Models, name) log.Printf("Model %s 已经移除", name) @@ -336,17 +338,17 @@ func watchAppPlugin(plugin string) { if err != nil { log.Panicf("路径错误 %s %s", root, err) } - go Watch(rootAbs, func(op string, file string) { + go share.Watch(rootAbs, func(op string, file string) { if !strings.HasSuffix(file, ".so") { return } if op == "write" || op == "create" { - script := getAppPluginFile(root, file) + 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 := getAppPluginFileName(root, file) + name := share.GetAppPluginFileName(root, file) if _, has := gou.Plugins[name]; has { delete(gou.Plugins, name) log.Printf("Plugin %s 已经移除", name) diff --git a/share/api.go b/share/api.go new file mode 100644 index 00000000..dfe5bcae --- /dev/null +++ b/share/api.go @@ -0,0 +1,133 @@ +package share + +import ( + "fmt" + "strings" + + "github.com/gin-gonic/gin" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/any" + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/kun/utils" +) + +// IsAllow 鉴权处理程序 +func (api API) IsAllow(v interface{}) bool { + c, ok := v.(*gin.Context) + if !ok { + return false + } + + guards := strings.Split(api.Guard, ",") + for _, guard := range guards { + guard = strings.TrimSpace(guard) + handler, has := gou.HTTPGuards[guard] + if has { + handler(c) + fmt.Println(api.Guard, c.IsAborted()) + return c.IsAborted() + } + } + return false +} + +// ValidateLoop 循环引用校验 +func (api API) ValidateLoop(name string) API { + if strings.ToLower(api.Process) == strings.ToLower(name) { + exception.New("循环引用 %s", 400, name).Throw() + } + return api +} + +// ProcessIs 检查处理器名称 +func (api API) ProcessIs(name string) bool { + return strings.ToLower(api.Process) == strings.ToLower(name) +} + +// DefaultQueryParams 读取参数 QueryParam +func (api API) DefaultQueryParams(i int, defaults ...gou.QueryParam) gou.QueryParam { + param := gou.QueryParam{} + if len(defaults) > 0 { + param = defaults[0] + } + + if api.Default[i] == nil || len(api.Default) <= i { + return param + } + + param, ok := api.Default[i].(gou.QueryParam) + if !ok { + param, ok = gou.AnyToQueryParam(api.Default[i]) + } + + return param +} + +// DefaultInt 读取参数 Int +func (api API) DefaultInt(i int, defaults ...int) int { + value := 0 + ok := false + if len(defaults) > 0 { + value = defaults[0] + } + + if api.Default[i] == nil || len(api.Default) <= i { + return value + } + + value, ok = api.Default[i].(int) + if !ok { + value = any.Of(api.Default[i]).CInt() + } + + return value +} + +// DefaultString 读取参数 String +func (api API) DefaultString(i int, defaults ...string) string { + value := "" + ok := false + if len(defaults) > 0 { + value = defaults[0] + } + + if api.Default[i] == nil || len(api.Default) <= i { + return value + } + + value, ok = api.Default[i].(string) + if !ok { + value = any.Of(api.Default[i]).CString() + } + return value +} + +// MergeDefaultQueryParam 合并默认查询参数 +func (api API) MergeDefaultQueryParam(param gou.QueryParam, i int) gou.QueryParam { + if len(api.Default) > i && api.Default[i] != nil { + defaults, ok := gou.AnyToQueryParam(api.Default[i]) + if !ok { + exception.New("参数默认值数据结构错误", 400).Ctx(api.Default[i]).Throw() + } + if defaults.Withs != nil { + param.Withs = defaults.Withs + } + + if defaults.Select != nil { + param.Select = defaults.Select + utils.Dump(param.Select) + } + + if defaults.Wheres != nil { + if param.Wheres == nil { + param.Wheres = []gou.QueryWhere{} + } + param.Wheres = append(param.Wheres, defaults.Wheres...) + } + + if defaults.Orders != nil { + param.Orders = append(param.Orders, defaults.Orders...) + } + } + return param +} diff --git a/share/app.go b/share/app.go index c8a0ff80..5218f10d 100644 --- a/share/app.go +++ b/share/app.go @@ -1,6 +1,16 @@ package share -import "github.com/yaoapp/kun/maps" +import ( + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/kun/maps" + "github.com/yaoapp/xiang/data" +) // App 应用信息 var App AppInfo @@ -34,6 +44,26 @@ type AppStorageOSS struct { SessionName string `json:"sessionName,omitempty"` } +// Script 脚本文件类型 +type Script struct { + Name string + Type string + Content []byte + File string +} + +// AppRoot 应用目录 +type AppRoot struct { + APIs string + Flows string + Models string + Plugins string + Tables string + Charts string + Screens string + Data string +} + // Public 输出公共信息 func (app AppInfo) Public() AppInfo { app.Storage.COS = nil @@ -41,3 +71,176 @@ func (app AppInfo) Public() AppInfo { app.Storage.S3 = nil return app } + +// GetAppPlugins 遍历应用目录,读取文件列表 +func GetAppPlugins(root string, typ string) []Script { + files := []Script{} + root = path.Join(root, "/") + filepath.Walk(root, func(file string, info os.FileInfo, err error) error { + if err != nil { + exception.Err(err, 500).Throw() + return err + } + if strings.HasSuffix(file, typ) { + files = append(files, GetAppPluginFile(root, file)) + } + return nil + }) + return files +} + +// GetAppPluginFile 读取文件 +func GetAppPluginFile(root string, file string) Script { + name := GetAppPluginFileName(root, file) + return Script{ + Name: name, + Type: "plugin", + File: file, + } +} + +// GetAppPluginFileName 读取文件 +func GetAppPluginFileName(root string, file string) string { + filename := strings.TrimPrefix(file, root+"/") + namer := strings.Split(filename, ".") + nametypes := strings.Split(namer[0], "/") + name := strings.Join(nametypes, ".") + return name +} + +// GetAppFilesFS 遍历应用目录,读取文件列表 +func GetAppFilesFS(root string, typ string) []Script { + files := []Script{} + root = path.Join(root, "/") + filepath.Walk(root, func(filepath string, info os.FileInfo, err error) error { + if err != nil { + exception.Err(err, 500).Throw() + return err + } + if strings.HasSuffix(filepath, typ) { + files = append(files, GetAppFile(root, filepath)) + } + + return nil + }) + return files +} + +// GetAppFile 读取文件 +func GetAppFile(root string, filepath string) Script { + name := GetAppFileName(root, filepath) + file, err := os.Open(filepath) + if err != nil { + exception.Err(err, 500).Throw() + } + + defer file.Close() + content, err := ioutil.ReadAll(file) + if err != nil { + exception.Err(err, 500).Throw() + } + return Script{ + Name: name, + Type: "app", + Content: content, + } +} + +// GetAppFileName 读取文件 +func GetAppFileName(root string, file string) string { + filename := strings.TrimPrefix(file, root+"/") + namer := strings.Split(filename, ".") + nametypes := strings.Split(namer[0], "/") + name := strings.Join(nametypes, ".") + return name +} + +// GetAppFileBaseName 读取文件base +func GetAppFileBaseName(root string, file string) string { + filename := strings.TrimPrefix(file, root+"/") + namer := strings.Split(filename, ".") + return filepath.Join(root, namer[0]) +} + +// GetFilesFS 遍历目录,读取文件列表 +func GetFilesFS(root string, typ string) []Script { + files := []Script{} + root = path.Join(root, "/") + filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + exception.Err(err, 500).Throw() + return err + } + if strings.HasSuffix(path, typ) { + files = append(files, GetFile(root, path)) + } + return nil + }) + return files +} + +// GetFile 读取文件 +func GetFile(root string, path string) Script { + filename := strings.TrimPrefix(path, root+"/") + name, typ := GetTypeName(filename) + file, err := os.Open(path) + if err != nil { + exception.Err(err, 500).Throw() + } + + defer file.Close() + content, err := ioutil.ReadAll(file) + if err != nil { + exception.Err(err, 500).Throw() + } + return Script{ + Name: name, + Type: typ, + Content: content, + } +} + +// GetFileName 读取文件 +func GetFileName(root string, file string) string { + filename := strings.TrimPrefix(file, root+"/") + name, _ := GetTypeName(filename) + return name +} + +// GetFileBaseName 读取文件base +func GetFileBaseName(root string, file string) string { + filename := strings.TrimPrefix(file, root+"/") + namer := strings.Split(filename, ".") + return filepath.Join(root, namer[0]) +} + +// GetFilesBin 从 bindata 中读取文件列表 +func GetFilesBin(root string, typ string) []Script { + files := []Script{} + binfiles := data.AssetNames() + for _, path := range binfiles { + if strings.HasSuffix(path, typ) { + file := strings.TrimPrefix(path, root+"/") + name, typ := GetTypeName(file) + content, err := data.Asset(path) + if err != nil { + exception.Err(err, 500).Throw() + } + files = append(files, Script{ + Name: name, + Type: typ, + Content: content, + }) + } + } + return files +} + +// GetTypeName 读取类型 +func GetTypeName(path string) (name string, typ string) { + namer := strings.Split(path, ".") + nametypes := strings.Split(namer[0], "/") + name = strings.Join(nametypes[1:], ".") + typ = nametypes[0] + return name, typ +} diff --git a/table/columns.go b/share/columns.go similarity index 92% rename from table/columns.go rename to share/columns.go index 88a3a4ff..aff7e54b 100644 --- a/table/columns.go +++ b/share/columns.go @@ -1,4 +1,4 @@ -package table +package share import ( "fmt" @@ -52,20 +52,8 @@ var elms = map[string]Column{ "year": {View: Render{Type: "label"}, Edit: Render{Type: "datetime"}}, } -// loadColumns 加载字段呈现方式 -func (table *Table) loadColumns() { - if table.Bind.Model == "" { - return - } - defaults := getDefaultColumns(table.Bind.Model) - for name, column := range table.Columns { - defaults[name] = column - } - table.Columns = defaults -} - -// getDefaultColumns 读取数据模型字段的呈现方式 -func getDefaultColumns(name string) map[string]Column { +// GetDefaultColumns 读取数据模型字段的呈现方式 +func GetDefaultColumns(name string) map[string]Column { mod := gou.Select(name) cmap := mod.Columns columns := map[string]Column{} diff --git a/table/filters.go b/share/filters.go similarity index 71% rename from table/filters.go rename to share/filters.go index abbc6a42..7d5e1604 100644 --- a/table/filters.go +++ b/share/filters.go @@ -1,4 +1,4 @@ -package table +package share import ( "fmt" @@ -6,20 +6,8 @@ import ( "github.com/yaoapp/gou" ) -// loadFilters 加载查询过滤器 -func (table *Table) loadFilters() { - if table.Bind.Model == "" { - return - } - defaults := getDefaultFilters(table.Bind.Model) - for name, filter := range table.Filters { - defaults[name] = filter - } - table.Filters = defaults -} - -// getDefaultFilters 读取数据模型索引字段的过滤器 -func getDefaultFilters(name string) map[string]Filter { +// GetDefaultFilters 读取数据模型索引字段的过滤器 +func GetDefaultFilters(name string) map[string]Filter { mod := gou.Select(name) cmap := mod.Columns diff --git a/share/types.go b/share/types.go new file mode 100644 index 00000000..d4990bd6 --- /dev/null +++ b/share/types.go @@ -0,0 +1,40 @@ +package share + +// API API 配置数据结构 +type API struct { + Name string `json:"-"` + Source string `json:"-"` + Process string `json:"process,omitempty"` + Guard string `json:"guard,omitempty"` + Default []interface{} `json:"default,omitempty"` +} + +// Column 字段呈现方式 +type Column struct { + Label string `json:"label"` + View Render `json:"view,omitempty"` + Edit Render `json:"edit,omitempty"` + Form Render `json:"form,omitempty"` +} + +// Filter 查询过滤器 +type Filter struct { + Label string `json:"label"` + Bind string `json:"bind,omitempty"` + Input Render `json:"input,omitempty"` +} + +// Page 页面 +type Page struct { + Primary string `json:"primary"` + Layout map[string]interface{} `json:"layout"` + Actions map[string]Render `json:"actions,omitempty"` + Option map[string]interface{} `json:"option,omitempty"` +} + +// Render 组件渲染方式 +type Render struct { + Type string `json:"type,omitempty"` + Props map[string]interface{} `json:"props,omitempty"` + Components map[string]interface{} `json:"components,omitempty"` +} diff --git a/share/vars.go b/share/vars.go index 42e01e9b..15974d67 100644 --- a/share/vars.go +++ b/share/vars.go @@ -5,7 +5,7 @@ import ( ) // VERSION 版本号 -const VERSION = "0.8.7" +const VERSION = "0.8.8" // DOMAIN 许可域 const DOMAIN = "*.iqka.com" diff --git a/global/watch.go b/share/watch.go similarity index 99% rename from global/watch.go rename to share/watch.go index 36d86609..42d4167f 100644 --- a/global/watch.go +++ b/share/watch.go @@ -1,4 +1,4 @@ -package global +package share import ( "io/fs" diff --git a/table/api.go b/table/api.go index 3c4a2b8a..4ae931d3 100644 --- a/table/api.go +++ b/table/api.go @@ -2,69 +2,13 @@ package table import ( "fmt" - "strings" - "github.com/gin-gonic/gin" "github.com/yaoapp/gou" - "github.com/yaoapp/kun/any" - "github.com/yaoapp/kun/exception" - "github.com/yaoapp/kun/utils" + "github.com/yaoapp/xiang/share" ) -// loadAPIs 加载数据管理 API -func (table *Table) loadAPIs() { - if table.Bind.Model == "" { - return - } - defaults := getDefaultAPIs(table.Bind) - defaults["setting"] = apiDefaultSetting(table) - - for name := range table.APIs { - if _, has := defaults[name]; !has { - delete(table.APIs, name) - continue - } - - api := defaults[name] - api.Name = name - if table.APIs[name].Process != "" { - api.Process = table.APIs[name].Process - } - - if table.APIs[name].Guard != "" { - api.Guard = table.APIs[name].Guard - } - - if table.APIs[name].Default != nil { - api.Default = table.APIs[name].Default - } - defaults[name] = api - } - - table.APIs = defaults -} - -// getDefaultAPIs 读取数据模型绑定的APIs -func getDefaultAPIs(bind Bind) map[string]API { - name := bind.Model - model := gou.Select(name) - apis := map[string]API{ - "search": apiSearchDefault(model, bind.Withs), - "find": apiFindDefault(model, bind.Withs), - "save": apiDefault(model, "save", "Save"), - "delete": apiDefault(model, "delete", "Delete"), - "insert": apiDefault(model, "insert", "Insert"), - "delete-in": apiDefault(model, "delete-in", "DeleteWhere"), - "delete-where": apiDefaultWhere(model, bind.Withs, "delete-where", "DeleteWhere"), - "update-in": apiDefault(model, "update-in", "UpdateWhere"), - "update-where": apiDefaultWhere(model, bind.Withs, "update-where", "UpdateWhere"), - } - - return apis -} - // apiSearchDefault search 接口默认值 -func apiSearchDefault(model *gou.Model, withs map[string]gou.With) API { +func apiSearchDefault(model *gou.Model, withs map[string]gou.With) share.API { query := gou.QueryParam{} if model.MetaData.Option.Timestamps { query.Orders = []gou.QueryOrder{ @@ -76,7 +20,7 @@ func apiSearchDefault(model *gou.Model, withs map[string]gou.With) API { query.Withs = withs } - return API{ + return share.API{ Name: "search", Guard: "bearer-jwt", Process: fmt.Sprintf("models.%s.Paginate", model.Name), @@ -85,14 +29,14 @@ func apiSearchDefault(model *gou.Model, withs map[string]gou.With) API { } // apiFindDefault find 接口默认值 -func apiFindDefault(model *gou.Model, withs map[string]gou.With) API { +func apiFindDefault(model *gou.Model, withs map[string]gou.With) share.API { query := gou.QueryParam{} if withs != nil { query.Withs = withs } - return API{ + return share.API{ Name: "find", Guard: "bearer-jwt", Process: fmt.Sprintf("models.%s.Find", model.Name), @@ -101,8 +45,8 @@ func apiFindDefault(model *gou.Model, withs map[string]gou.With) API { } // apiFindDefault 接口默认值 -func apiDefault(model *gou.Model, name string, process string) API { - return API{ +func apiDefault(model *gou.Model, name string, process string) share.API { + return share.API{ Name: name, Guard: "bearer-jwt", Process: fmt.Sprintf("models.%s.%s", model.Name, process), @@ -110,14 +54,14 @@ func apiDefault(model *gou.Model, name string, process string) API { } // apiFindDefault 接口默认值 -func apiDefaultWhere(model *gou.Model, withs map[string]gou.With, name string, process string) API { +func apiDefaultWhere(model *gou.Model, withs map[string]gou.With, name string, process string) share.API { query := gou.QueryParam{} if withs != nil { query.Withs = withs } - return API{ + return share.API{ Name: name, Guard: "bearer-jwt", Process: fmt.Sprintf("models.%s.%s", model.Name, process), @@ -126,131 +70,10 @@ func apiDefaultWhere(model *gou.Model, withs map[string]gou.With, name string, p } // apiDefaultSetting 数据表格配置默认值 -func apiDefaultSetting(table *Table) API { - return API{ +func apiDefaultSetting(table *Table) share.API { + return share.API{ Name: "setting", Guard: "bearer-jwt", Process: fmt.Sprintf("xiang.table.setting"), } } - -// IsAllow 鉴权处理程序 -func (api API) IsAllow(v interface{}) bool { - c, ok := v.(*gin.Context) - if !ok { - return false - } - - guards := strings.Split(api.Guard, ",") - for _, guard := range guards { - guard = strings.TrimSpace(guard) - handler, has := gou.HTTPGuards[guard] - if has { - handler(c) - fmt.Println(api.Guard, c.IsAborted()) - return c.IsAborted() - } - } - return false -} - -// ValidateLoop 循环引用校验 -func (api API) ValidateLoop(name string) API { - if strings.ToLower(api.Process) == strings.ToLower(name) { - exception.New("循环引用 %s", 400, name).Throw() - } - return api -} - -// ProcessIs 检查处理器名称 -func (api API) ProcessIs(name string) bool { - return strings.ToLower(api.Process) == strings.ToLower(name) -} - -// DefaultQueryParams 读取参数 QueryParam -func (api API) DefaultQueryParams(i int, defaults ...gou.QueryParam) gou.QueryParam { - param := gou.QueryParam{} - if len(defaults) > 0 { - param = defaults[0] - } - - if api.Default[i] == nil || len(api.Default) <= i { - return param - } - - param, ok := api.Default[i].(gou.QueryParam) - if !ok { - param, ok = gou.AnyToQueryParam(api.Default[i]) - } - - return param -} - -// DefaultInt 读取参数 Int -func (api API) DefaultInt(i int, defaults ...int) int { - value := 0 - ok := false - if len(defaults) > 0 { - value = defaults[0] - } - - if api.Default[i] == nil || len(api.Default) <= i { - return value - } - - value, ok = api.Default[i].(int) - if !ok { - value = any.Of(api.Default[i]).CInt() - } - - return value -} - -// DefaultString 读取参数 String -func (api API) DefaultString(i int, defaults ...string) string { - value := "" - ok := false - if len(defaults) > 0 { - value = defaults[0] - } - - if api.Default[i] == nil || len(api.Default) <= i { - return value - } - - value, ok = api.Default[i].(string) - if !ok { - value = any.Of(api.Default[i]).CString() - } - return value -} - -// MergeDefaultQueryParam 合并默认查询参数 -func (api API) MergeDefaultQueryParam(param gou.QueryParam, i int) gou.QueryParam { - if len(api.Default) > i && api.Default[i] != nil { - defaults, ok := gou.AnyToQueryParam(api.Default[i]) - if !ok { - exception.New("参数默认值数据结构错误", 400).Ctx(api.Default[i]).Throw() - } - if defaults.Withs != nil { - param.Withs = defaults.Withs - } - - if defaults.Select != nil { - param.Select = defaults.Select - utils.Dump(param.Select) - } - - if defaults.Wheres != nil { - if param.Wheres == nil { - param.Wheres = []gou.QueryWhere{} - } - param.Wheres = append(param.Wheres, defaults.Wheres...) - } - - if defaults.Orders != nil { - param.Orders = append(param.Orders, defaults.Orders...) - } - } - return param -} diff --git a/table/table.go b/table/table.go index 2d68b5a4..139aae3c 100644 --- a/table/table.go +++ b/table/table.go @@ -6,8 +6,10 @@ import ( "os" "strings" + "github.com/yaoapp/gou" "github.com/yaoapp/gou/helper" "github.com/yaoapp/kun/exception" + "github.com/yaoapp/xiang/share" ) // Tables 已载入模型 @@ -58,7 +60,83 @@ func Select(name string) *Table { } // Reload 更新数据表格配置 -func (tab *Table) Reload() *Table { - tab = Load(tab.Source, tab.Name) - return tab +func (table *Table) Reload() *Table { + *table = *Load(table.Source, table.Name) + return table +} + +// loadAPIs 加载数据管理 API +func (table *Table) loadAPIs() { + if table.Bind.Model == "" { + return + } + defaults := getDefaultAPIs(table.Bind) + defaults["setting"] = apiDefaultSetting(table) + + for name := range table.APIs { + if _, has := defaults[name]; !has { + delete(table.APIs, name) + continue + } + + api := defaults[name] + api.Name = name + if table.APIs[name].Process != "" { + api.Process = table.APIs[name].Process + } + + if table.APIs[name].Guard != "" { + api.Guard = table.APIs[name].Guard + } + + if table.APIs[name].Default != nil { + api.Default = table.APIs[name].Default + } + defaults[name] = api + } + + table.APIs = defaults +} + +// getDefaultAPIs 读取数据模型绑定的APIs +func getDefaultAPIs(bind Bind) map[string]share.API { + name := bind.Model + model := gou.Select(name) + apis := map[string]share.API{ + "search": apiSearchDefault(model, bind.Withs), + "find": apiFindDefault(model, bind.Withs), + "save": apiDefault(model, "save", "Save"), + "delete": apiDefault(model, "delete", "Delete"), + "insert": apiDefault(model, "insert", "Insert"), + "delete-in": apiDefault(model, "delete-in", "DeleteWhere"), + "delete-where": apiDefaultWhere(model, bind.Withs, "delete-where", "DeleteWhere"), + "update-in": apiDefault(model, "update-in", "UpdateWhere"), + "update-where": apiDefaultWhere(model, bind.Withs, "update-where", "UpdateWhere"), + } + + return apis +} + +// loadFilters 加载查询过滤器 +func (table *Table) loadFilters() { + if table.Bind.Model == "" { + return + } + defaults := share.GetDefaultFilters(table.Bind.Model) + for name, filter := range table.Filters { + defaults[name] = filter + } + table.Filters = defaults +} + +// loadColumns 加载字段呈现方式 +func (table *Table) loadColumns() { + if table.Bind.Model == "" { + return + } + defaults := share.GetDefaultColumns(table.Bind.Model) + for name, column := range table.Columns { + defaults[name] = column + } + table.Columns = defaults } diff --git a/table/types.go b/table/types.go index cdbb4370..9acc10bd 100644 --- a/table/types.go +++ b/table/types.go @@ -1,23 +1,26 @@ package table -import "github.com/yaoapp/gou" +import ( + "github.com/yaoapp/gou" + "github.com/yaoapp/xiang/share" +) // Table 数据表格配置结构 type Table struct { - Table string `json:"-"` - Source string `json:"-"` - Name string `json:"name"` - Version string `json:"version"` - Title string `json:"title,omitempty"` - Decription string `json:"decription,omitempty"` - Bind Bind `json:"bind,omitempty"` - APIs map[string]API `json:"apis,omitempty"` - Columns map[string]Column `json:"columns,omitempty"` - Filters map[string]Filter `json:"filters,omitempty"` - List Page `json:"list,omitempty"` - Edit Page `json:"edit,omitempty"` - View Page `json:"view,omitempty"` - Insert Page `json:"insert,omitempty"` + Table string `json:"-"` + Source string `json:"-"` + Name string `json:"name"` + Version string `json:"version"` + Title string `json:"title,omitempty"` + Decription string `json:"decription,omitempty"` + Bind Bind `json:"bind,omitempty"` + APIs map[string]share.API `json:"apis,omitempty"` + Columns map[string]share.Column `json:"columns,omitempty"` + Filters map[string]share.Filter `json:"filters,omitempty"` + List share.Page `json:"list,omitempty"` + Edit share.Page `json:"edit,omitempty"` + View share.Page `json:"view,omitempty"` + Insert share.Page `json:"insert,omitempty"` } // Bind 绑定数据模型 @@ -25,42 +28,3 @@ type Bind struct { Model string `json:"model"` Withs map[string]gou.With `json:"withs,omitempty"` } - -// API API 配置数据结构 -type API struct { - Name string `json:"-"` - Source string `json:"-"` - Process string `json:"process,omitempty"` - Guard string `json:"guard,omitempty"` - Default []interface{} `json:"default,omitempty"` -} - -// Column 字段呈现方式 -type Column struct { - Label string `json:"label"` - View Render `json:"view,omitempty"` - Edit Render `json:"edit,omitempty"` - Form Render `json:"form,omitempty"` -} - -// Filter 查询过滤器 -type Filter struct { - Label string `json:"label"` - Bind string `json:"bind,omitempty"` - Input Render `json:"input,omitempty"` -} - -// Page 页面 -type Page struct { - Primary string `json:"primary"` - Layout map[string]interface{} `json:"layout"` - Actions map[string]Render `json:"actions,omitempty"` - Option map[string]interface{} `json:"option,omitempty"` -} - -// Render 组件渲染方式 -type Render struct { - Type string `json:"type,omitempty"` - Props map[string]interface{} `json:"props,omitempty"` - Components map[string]interface{} `json:"components,omitempty"` -} diff --git a/xiang/apis/xiang.http.json b/xiang/apis/xiang.http.json index c0514586..e47e82c5 100644 --- a/xiang/apis/xiang.http.json +++ b/xiang/apis/xiang.http.json @@ -9,7 +9,7 @@ "path": "/ping", "method": "GET", "guard": "-", - "process": "xiang.global.Ping", + "process": "xiang.main.Ping", "in": [], "out": { "status": 200, @@ -20,7 +20,7 @@ "path": "/inspect", "method": "GET", "guard": "-", - "process": "xiang.global.inspect", + "process": "xiang.main.inspect", "in": [], "out": { "status": 200, @@ -31,7 +31,7 @@ "path": "/favicon.ico", "method": "GET", "guard": "-", - "process": "xiang.global.Favicon", + "process": "xiang.main.Favicon", "in": [], "out": { "status": 200,