+ LoadEngine
This commit is contained in:
parent
b530daebfa
commit
96d37847b5
13 changed files with 768 additions and 31 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -16,5 +16,4 @@
|
|||
.DS_Store
|
||||
.env*
|
||||
.tmp
|
||||
dist
|
||||
ui
|
||||
dist
|
||||
2
Makefile
2
Makefile
|
|
@ -111,7 +111,7 @@ bindata:
|
|||
mkdir -p .tmp/data
|
||||
cp -r ui .tmp/data/
|
||||
cp -r xiang .tmp/data/
|
||||
go-bindata -o data.go .tmp/data/...
|
||||
go-bindata -fs -o data.go -prefix ".tmp/data/" .tmp/data/...
|
||||
rm -rf .tmp/data
|
||||
|
||||
# 编译可执行文件
|
||||
|
|
|
|||
255
bindata.go
255
bindata.go
File diff suppressed because one or more lines are too long
15
config.go
15
config.go
|
|
@ -23,13 +23,14 @@ type Config struct {
|
|||
|
||||
// XiangConfig 象传应用引擎配置
|
||||
type XiangConfig struct {
|
||||
Mode string `json:"mode,omitempty" env:"XIANG_MODE" envDefault:"release"` // 象传引擎模式 debug/release/test
|
||||
Source string `json:"source,omitempty" env:"XIANG_SOURCE" envDefault:"fs://."` // 源码路径(用于调试时载入数据)
|
||||
Root string `json:"root,omitempty" env:"XIANG_ROOT" envDefault:"."` // 应用文件目录
|
||||
RootAPI string `json:"root_api,omitempty" env:"XIANG_ROOT_API"` // 应用API文件目录
|
||||
RootModel string `json:"root_model,omitempty" env:"XIANG_ROOT_MODEL"` // 应用模型文件目录
|
||||
RootFLow string `json:"root_flow,omitempty" env:"XIANG_ROOT_FLOW"` // 应用工作流文件目录
|
||||
RootPlugin string `json:"root_plugin,omitempty" env:"XIANG_ROOT_FLOW"` // 应用插件文件目录
|
||||
Mode string `json:"mode,omitempty" env:"XIANG_MODE" envDefault:"release"` // 象传引擎模式 debug/release/test
|
||||
Source string `json:"source,omitempty" env:"XIANG_SOURCE" envDefault:"fs://."` // 源码路径(用于调试时载入数据)
|
||||
Path string `json:"path,omitempty" env:"XIANG_PATH" envDefault:"bin://xiang"` // 引擎文件目录
|
||||
Root string `json:"root,omitempty" env:"XIANG_ROOT" envDefault:"fs://."` // 应用文件目录
|
||||
RootAPI string `json:"root_api,omitempty" env:"XIANG_ROOT_API"` // 应用API文件目录
|
||||
RootModel string `json:"root_model,omitempty" env:"XIANG_ROOT_MODEL"` // 应用模型文件目录
|
||||
RootFLow string `json:"root_flow,omitempty" env:"XIANG_ROOT_FLOW"` // 应用工作流文件目录
|
||||
RootPlugin string `json:"root_plugin,omitempty" env:"XIANG_ROOT_FLOW"` // 应用插件文件目录
|
||||
}
|
||||
|
||||
// ServiceConfig 服务配置
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ func TestNewConfig(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.Equal(t, cfg.Mode, os.Getenv("XIANG_MODE"))
|
||||
assert.Equal(t, cfg.Source, os.Getenv("XIANG_SOURCE"))
|
||||
assert.Equal(t, cfg.Root, os.Getenv("XIANG_ROOT"))
|
||||
assert.Equal(t, cfg.Path, os.Getenv("XIANG_PATH"))
|
||||
|
||||
assert.Equal(t, cfg.Service.Debug, vBool(os.Getenv("XIANG_SERVICE_DEBUG")))
|
||||
assert.Equal(t, strings.Join(cfg.Service.Allow, "|"), os.Getenv("XIANG_SERVICE_ALLOW"))
|
||||
|
|
|
|||
19
init_test.go
Normal file
19
init_test.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var cfg Config
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
cfg = NewConfig()
|
||||
|
||||
// Run test suites
|
||||
exitVal := m.Run()
|
||||
|
||||
// we can do clean up code here
|
||||
|
||||
os.Exit(exitVal)
|
||||
}
|
||||
120
load.go
120
load.go
|
|
@ -1,6 +1,126 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/yaoapp/gou"
|
||||
"github.com/yaoapp/kun/exception"
|
||||
)
|
||||
|
||||
// Script 脚本文件类型
|
||||
type Script struct {
|
||||
Name string
|
||||
Type string
|
||||
Content []byte
|
||||
}
|
||||
|
||||
// Load 根据配置加载 API, FLow, Model, Plugin
|
||||
func Load() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadEngine 载入引擎 API, Flow, Model 配置
|
||||
func LoadEngine(from string) {
|
||||
|
||||
var scripts []Script
|
||||
if strings.HasPrefix(from, "fs://") || !strings.Contains(from, "://") {
|
||||
root := strings.TrimPrefix(from, "fs://")
|
||||
scripts = getFilesFS(root, ".json")
|
||||
} else if strings.HasPrefix(from, "bin://") {
|
||||
root := strings.TrimPrefix(from, "bin://")
|
||||
scripts = getFilesBin(root, ".json")
|
||||
}
|
||||
|
||||
if scripts == nil {
|
||||
exception.New("读取文件失败", 500, from).Throw()
|
||||
}
|
||||
|
||||
if len(scripts) == 0 {
|
||||
exception.New("读取文件失败, 未找到任何可执行脚本", 500, from).Throw()
|
||||
}
|
||||
|
||||
// 加载 API, Flow, Models
|
||||
for _, script := range scripts {
|
||||
switch script.Type {
|
||||
case "models":
|
||||
gou.LoadModel(string(script.Content), script.Name)
|
||||
break
|
||||
case "flows":
|
||||
gou.LoadFlow(string(script.Content), script.Name)
|
||||
break
|
||||
case "api":
|
||||
gou.LoadAPI(string(script.Content), script.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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()
|
||||
}
|
||||
files = append(files, Script{
|
||||
Name: name,
|
||||
Type: typ,
|
||||
Content: content,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return files
|
||||
}
|
||||
|
||||
// getFilesBin 从 bindata 中读取文件列表
|
||||
func getFilesBin(root string, typ string) []Script {
|
||||
files := []Script{}
|
||||
binfiles := AssetNames()
|
||||
for _, path := range binfiles {
|
||||
if strings.HasSuffix(path, typ) {
|
||||
file := strings.TrimPrefix(path, root+"/")
|
||||
name, typ := getTypeName(file)
|
||||
content, err := 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
|
||||
}
|
||||
|
|
|
|||
18
load_test.go
18
load_test.go
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -10,3 +11,20 @@ func TestLoad(t *testing.T) {
|
|||
err := Load()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
// 从文件系统载入引擎文件
|
||||
func TestLoadEngineFS(t *testing.T) {
|
||||
root := "fs://" + path.Join(cfg.Source, "/xiang")
|
||||
assert.NotPanics(t, func() {
|
||||
LoadEngine(root)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// 从BinDataz载入引擎文件
|
||||
func TestLoadEngineBin(t *testing.T) {
|
||||
root := "bin://xiang"
|
||||
assert.NotPanics(t, func() {
|
||||
LoadEngine(root)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
1
ui/index.html
Normal file
1
ui/index.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
hello world
|
||||
19
xiang/apis/sys.http.json
Normal file
19
xiang/apis/sys.http.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "系统接口",
|
||||
"version": "1.0.0",
|
||||
"description": "系统接口API",
|
||||
"group": "sys",
|
||||
"guard": "bearer-jwt",
|
||||
"paths": [
|
||||
{
|
||||
"path": "/info/:id",
|
||||
"method": "GET",
|
||||
"process": "models.user.Find",
|
||||
"in": ["$param.id", ":params"],
|
||||
"out": {
|
||||
"status": 200,
|
||||
"type": "application/json"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
23
xiang/flows/table/get.flow.json
Normal file
23
xiang/flows/table/get.flow.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"label": "最新信息",
|
||||
"version": "1.0.0",
|
||||
"description": "最新信息",
|
||||
"nodes": [
|
||||
{
|
||||
"name": "user",
|
||||
"process": "models.user.get",
|
||||
"args": [
|
||||
{
|
||||
"select": ["id", "name", "mobile"],
|
||||
"limit": 20,
|
||||
"orders": [{ "column": "created_at", "option": "desc" }],
|
||||
"wheres": [
|
||||
{ "column": "status", "value": "enabled" },
|
||||
{ "column": "name", "value": "{{$in.0}}", "op": "like" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"outs": ["{{$out}}"]
|
||||
}
|
||||
]
|
||||
}
|
||||
320
xiang/models/user.json
Normal file
320
xiang/models/user.json
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
{
|
||||
"name": "用户",
|
||||
"table": {
|
||||
"name": "user",
|
||||
"comment": "用户表",
|
||||
"engine": "InnoDB"
|
||||
},
|
||||
"columns": [
|
||||
{ "label": "ID", "name": "id", "type": "ID" },
|
||||
{
|
||||
"label": "厂商",
|
||||
"name": "manu_id",
|
||||
"type": "bigInteger",
|
||||
"length": 50,
|
||||
"comment": "所属厂商",
|
||||
"nullable": true,
|
||||
"index": true,
|
||||
"validations": [
|
||||
{
|
||||
"method": "typeof",
|
||||
"args": ["integer"],
|
||||
"message": "{{input}}类型错误, {{label}}应为数字"
|
||||
},
|
||||
{
|
||||
"method": "min",
|
||||
"args": [0],
|
||||
"message": "{{label}}应大于0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "类型",
|
||||
"name": "type",
|
||||
"type": "enum",
|
||||
"option": ["admin", "staff", "user"],
|
||||
"comment": "账号类型 admin 管理员, staff 员工, user 用户",
|
||||
"default": "staff",
|
||||
"index": true,
|
||||
"validations": [
|
||||
{
|
||||
"method": "typeof",
|
||||
"args": ["string"],
|
||||
"message": "{{input}}类型错误, {{label}}应该为字符串"
|
||||
},
|
||||
{
|
||||
"method": "enum",
|
||||
"args": ["admin", "staff", "user"],
|
||||
"message": "{{input}}不在许可范围, {{label}}应该为 admin/staff/user"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "手机号",
|
||||
"name": "mobile",
|
||||
"type": "string",
|
||||
"length": 50,
|
||||
"comment": "手机号",
|
||||
"index": true,
|
||||
"crypt": "AES",
|
||||
"validations": [
|
||||
{
|
||||
"method": "typeof",
|
||||
"args": ["string"],
|
||||
"message": "{{input}}类型错误, {{label}}应该为字符串"
|
||||
},
|
||||
{
|
||||
"method": "pattern",
|
||||
"args": ["^1[3-9]\\d{9}$"],
|
||||
"message": "{{input}}格式错误"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "登录密码",
|
||||
"name": "password",
|
||||
"type": "string",
|
||||
"length": 256,
|
||||
"comment": "登录密码",
|
||||
"crypt": "PASSWORD",
|
||||
"index": true,
|
||||
"validations": [
|
||||
{
|
||||
"method": "typeof",
|
||||
"args": ["string"],
|
||||
"message": "{{input}}类型错误, {{label}}应该为字符串"
|
||||
},
|
||||
{
|
||||
"method": "minLength",
|
||||
"args": [6],
|
||||
"message": "{{label}}应该由6-18位,大小写字母、数字和符号构成"
|
||||
},
|
||||
{
|
||||
"method": "maxLength",
|
||||
"args": [18],
|
||||
"message": "{{label}}应该由6-18位,大小写字母、数字和符号构成"
|
||||
},
|
||||
{
|
||||
"method": "pattern",
|
||||
"args": ["[0-9]+"],
|
||||
"message": "{{label}}应该至少包含一个数字"
|
||||
},
|
||||
{
|
||||
"method": "pattern",
|
||||
"args": ["[A-Z]+"],
|
||||
"message": "{{label}}应该至少包含一个大写字母"
|
||||
},
|
||||
{
|
||||
"method": "pattern",
|
||||
"args": ["[a-z]+"],
|
||||
"message": "{{label}}应该至少包含一个小写字母"
|
||||
},
|
||||
{
|
||||
"method": "pattern",
|
||||
"args": ["[@#$&*]+"],
|
||||
"message": "{{label}}应该至少包含一个符号"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "姓名",
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"length": 80,
|
||||
"comment": "姓名",
|
||||
"index": true,
|
||||
"validations": [
|
||||
{
|
||||
"method": "typeof",
|
||||
"args": ["string"],
|
||||
"message": "{{input}}类型错误, {{label}}应该为字符串"
|
||||
},
|
||||
{
|
||||
"method": "minLength",
|
||||
"args": [2],
|
||||
"message": "{{label}}至少需要2个字"
|
||||
},
|
||||
{
|
||||
"method": "maxLength",
|
||||
"args": [40],
|
||||
"message": "{{label}}不能超过20个字"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "身份证号码",
|
||||
"name": "idcard",
|
||||
"type": "string",
|
||||
"length": 256,
|
||||
"comment": "身份证号码",
|
||||
"crypt": "AES",
|
||||
"nullable": true,
|
||||
"index": true,
|
||||
"validations": [
|
||||
{
|
||||
"method": "typeof",
|
||||
"args": ["string"],
|
||||
"message": "{{input}}类型错误, {{label}}应该为字符串"
|
||||
},
|
||||
{
|
||||
"method": "pattern",
|
||||
"args": ["^(\\d{18})|(\\d{14}X)$"],
|
||||
"message": "{{label}}格式错误"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "账户余额",
|
||||
"name": "balance",
|
||||
"type": "integer",
|
||||
"length": 20,
|
||||
"comment": "账户余额(冗余)",
|
||||
"default": 0,
|
||||
"index": true,
|
||||
"validations": [
|
||||
{
|
||||
"method": "typeof",
|
||||
"args": ["integer"],
|
||||
"message": "{{input}}类型错误, {{label}}应为数字"
|
||||
},
|
||||
{
|
||||
"method": "min",
|
||||
"args": [0],
|
||||
"message": "{{label}}应大于0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "API Key",
|
||||
"name": "key",
|
||||
"type": "string",
|
||||
"length": 256,
|
||||
"comment": "API Key",
|
||||
"nullable": true,
|
||||
"unique": true,
|
||||
"validations": [
|
||||
{
|
||||
"method": "typeof",
|
||||
"args": ["string"],
|
||||
"message": "{{input}}类型错误, {{label}}应该为字符串"
|
||||
},
|
||||
{
|
||||
"method": "pattern",
|
||||
"args": ["^[0-9A-Za-z@#$&*]{8}$"],
|
||||
"message": " {{label}}应该由8位,大小写字母、数字和符号构成"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "API 密钥",
|
||||
"name": "secret",
|
||||
"type": "string",
|
||||
"length": 256,
|
||||
"nullable": true,
|
||||
"crypt": "AES",
|
||||
"comment": "API 密钥",
|
||||
"index": true,
|
||||
"validations": [
|
||||
{
|
||||
"method": "typeof",
|
||||
"args": ["string"],
|
||||
"message": "{{input}}类型错误, {{label}}应该为字符串"
|
||||
},
|
||||
{
|
||||
"method": "pattern",
|
||||
"args": ["^[0-9A-Za-z@#$&*]{32}$"],
|
||||
"message": "{{label}}应该由32位,大小写字母、数字和符号构成"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "简历",
|
||||
"name": "resume",
|
||||
"type": "text",
|
||||
"comment": "简历",
|
||||
"nullable": true
|
||||
},
|
||||
{
|
||||
"label": "扩展信息",
|
||||
"name": "extra",
|
||||
"type": "json",
|
||||
"comment": "扩展信息",
|
||||
"nullable": true
|
||||
},
|
||||
{
|
||||
"label": "状态",
|
||||
"comment": "用户状态 enabled 有效, disabled 无效",
|
||||
"name": "status",
|
||||
"type": "enum",
|
||||
"default": "enabled",
|
||||
"option": ["enabled", "disabled"],
|
||||
"index": true,
|
||||
"validations": [
|
||||
{
|
||||
"method": "typeof",
|
||||
"args": ["string"],
|
||||
"message": "{{input}}类型错误, {{label}}应该为字符串"
|
||||
},
|
||||
{
|
||||
"method": "enum",
|
||||
"args": ["enabled", "disabled"],
|
||||
"message": "{{input}}不在许可范围, {{label}}应该为 enabled/disabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"relations": {},
|
||||
"values": [
|
||||
{
|
||||
"name": "管理员",
|
||||
"manu_id": 1,
|
||||
"type": "admin",
|
||||
"idcard": "230624198301170015",
|
||||
"mobile": "13900001111",
|
||||
"password": "cvSK@RY6",
|
||||
"key": "FB3fxCeQ",
|
||||
"secret": "XMTdNRVigbgUiAPdiJCfaWgWcz2PaQXw",
|
||||
"status": "enabled",
|
||||
"extra": { "sex": "男" }
|
||||
},
|
||||
{
|
||||
"name": "员工",
|
||||
"manu_id": 1,
|
||||
"type": "staff",
|
||||
"idcard": "23082619820207024X",
|
||||
"mobile": "13900002222",
|
||||
"password": "qV@uT1DI",
|
||||
"key": "JDh2ZiUt",
|
||||
"secret": "wBeYjL7FjbcvpAdBrxtDFfjydsoPKhRN",
|
||||
"status": "enabled",
|
||||
"extra": { "sex": "女" }
|
||||
},
|
||||
{
|
||||
"name": "用户",
|
||||
"manu_id": 2,
|
||||
"type": "user",
|
||||
"idcard": "23082619820207004X",
|
||||
"mobile": "13900003333",
|
||||
"password": "qV@uT1DI",
|
||||
"key": "XZ12MiPz",
|
||||
"secret": "wBeYjL7FjbcvpAdBrxtDFfjydsoPKhRN",
|
||||
"status": "enabled",
|
||||
"extra": { "sex": "女" }
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"comment": "厂商用户",
|
||||
"name": "manu_id_mobile_unique",
|
||||
"columns": ["manu_id", "mobile"],
|
||||
"type": "unique"
|
||||
},
|
||||
{
|
||||
"comment": "简历全文检索",
|
||||
"name": "resume_fulltext",
|
||||
"columns": ["resume"],
|
||||
"type": "fulltext"
|
||||
}
|
||||
],
|
||||
"option": { "timestamps": true, "soft_deletes": true }
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
func TestMainCMD(t *testing.T) {
|
||||
assert.NotPanics(t, func() {
|
||||
main()
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue