优化代码结构 v0.8.8

This commit is contained in:
Max 2021-10-18 21:38:39 +08:00
parent 7b87f3fee5
commit d03e1fc2e3
28 changed files with 653 additions and 617 deletions

View file

@ -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 ?= ""
# 运行单元测试

View file

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

View file

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

3
entry/READE.md Normal file
View file

@ -0,0 +1,3 @@
# 程序入口
加载各种文件

View file

@ -1,4 +1,4 @@
package global
package entry
import (
"os"

View file

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

View file

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

View file

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

16
entry/process_test.go Normal file
View file

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

View file

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

View file

@ -1,4 +1,4 @@
package global
package entry
import (
"testing"

23
entry/watch_test.go Normal file
View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
package global
package service
import (
"fmt"

View file

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

133
share/api.go Normal file
View file

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

View file

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

View file

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

View file

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

40
share/types.go Normal file
View file

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

View file

@ -5,7 +5,7 @@ import (
)
// VERSION 版本号
const VERSION = "0.8.7"
const VERSION = "0.8.8"
// DOMAIN 许可域
const DOMAIN = "*.iqka.com"

View file

@ -1,4 +1,4 @@
package global
package share
import (
"io/fs"

View file

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

View file

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

View file

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

View file

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