+ page
This commit is contained in:
parent
2253a98903
commit
9d423245dd
19 changed files with 613 additions and 11 deletions
|
|
@ -19,8 +19,8 @@ func LoadFrom(dir string, prefix string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
share.Walk(dir, ".json", func(root, filename string) {
|
share.Walk(dir, ".json", func(root, filename string) {
|
||||||
name := share.SpecName(root, filename)
|
name := prefix + share.SpecName(root, filename)
|
||||||
content := share.ReadFile(filename)
|
content := share.ReadFile(filename)
|
||||||
gou.LoadAPI(string(content), prefix+name)
|
gou.LoadAPI(string(content), name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,13 +27,24 @@ func LoadFrom(dir string, prefix string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
share.Walk(dir, ".json", func(root, filename string) {
|
share.Walk(dir, ".json", func(root, filename string) {
|
||||||
name := share.SpecName(root, filename)
|
name := prefix + share.SpecName(root, filename)
|
||||||
content := share.ReadFile(filename)
|
content := share.ReadFile(filename)
|
||||||
_, err := LoadChart(content, name)
|
_, err := LoadChart(content, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exception.New("%s 图表格式错误", 400, name).Ctx(filename).Throw()
|
exception.New("%s 图表格式错误", 400, name).Ctx(filename).Throw()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Load Script
|
||||||
|
share.Walk(dir, ".js", func(root, filename string) {
|
||||||
|
name := prefix + share.SpecName(root, filename)
|
||||||
|
chart := Select(name)
|
||||||
|
if chart != nil {
|
||||||
|
script := share.ScriptName(filename)
|
||||||
|
content := share.ReadFile(filename)
|
||||||
|
chart.LoadScript(string(content), script)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadChart 载入数据表格
|
// LoadChart 载入数据表格
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/yaoapp/xiang/config"
|
"github.com/yaoapp/xiang/config"
|
||||||
"github.com/yaoapp/xiang/flow"
|
"github.com/yaoapp/xiang/flow"
|
||||||
"github.com/yaoapp/xiang/model"
|
"github.com/yaoapp/xiang/model"
|
||||||
|
"github.com/yaoapp/xiang/page"
|
||||||
"github.com/yaoapp/xiang/plugin"
|
"github.com/yaoapp/xiang/plugin"
|
||||||
"github.com/yaoapp/xiang/query"
|
"github.com/yaoapp/xiang/query"
|
||||||
"github.com/yaoapp/xiang/share"
|
"github.com/yaoapp/xiang/share"
|
||||||
|
|
@ -33,6 +34,7 @@ func Load(cfg config.Config) {
|
||||||
plugin.Load(cfg) // 加载业务插件 plugin
|
plugin.Load(cfg) // 加载业务插件 plugin
|
||||||
table.Load(cfg) // 加载数据表格 table
|
table.Load(cfg) // 加载数据表格 table
|
||||||
chart.Load(cfg) // 加载分析图表 chart
|
chart.Load(cfg) // 加载分析图表 chart
|
||||||
|
page.Load(cfg) // 加载页面 page
|
||||||
|
|
||||||
// 加密密钥函数
|
// 加密密钥函数
|
||||||
gou.LoadCrypt(fmt.Sprintf(`{"key":"%s"}`, cfg.Database.AESKey), "AES")
|
gou.LoadCrypt(fmt.Sprintf(`{"key":"%s"}`, cfg.Database.AESKey), "AES")
|
||||||
|
|
|
||||||
15
flow/flow.go
15
flow/flow.go
|
|
@ -19,8 +19,19 @@ func LoadFrom(dir string, prefix string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
share.Walk(dir, ".json", func(root, filename string) {
|
share.Walk(dir, ".json", func(root, filename string) {
|
||||||
name := share.SpecName(root, filename)
|
name := prefix + share.SpecName(root, filename)
|
||||||
content := share.ReadFile(filename)
|
content := share.ReadFile(filename)
|
||||||
gou.LoadFlow(string(content), prefix+name)
|
gou.LoadFlow(string(content), name)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Load Script
|
||||||
|
share.Walk(dir, ".js", func(root, filename string) {
|
||||||
|
name := prefix + share.SpecName(root, filename)
|
||||||
|
flow := gou.SelectFlow(name)
|
||||||
|
if flow != nil {
|
||||||
|
script := share.ScriptName(filename)
|
||||||
|
content := share.ReadFile(filename)
|
||||||
|
flow.LoadScript(string(content), script)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ func LoadFrom(dir string, prefix string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
share.Walk(dir, ".json", func(root, filename string) {
|
share.Walk(dir, ".json", func(root, filename string) {
|
||||||
name := share.SpecName(root, filename)
|
name := prefix + share.SpecName(root, filename)
|
||||||
content := share.ReadFile(filename)
|
content := share.ReadFile(filename)
|
||||||
gou.LoadModel(string(content), prefix+name)
|
gou.LoadModel(string(content), name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
page/REAME.md
Normal file
1
page/REAME.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
# 数据页面
|
||||||
60
page/api.go
Normal file
60
page/api.go
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
package page
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yaoapp/xiang/share"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetupAPIs 设定API数据
|
||||||
|
func (page *Page) SetupAPIs() {
|
||||||
|
|
||||||
|
defaults := map[string]share.API{
|
||||||
|
"data": apiDataDefault(),
|
||||||
|
"setting": apiSettingDefault(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开发者填写的规则
|
||||||
|
for name := range page.APIs {
|
||||||
|
if _, has := defaults[name]; !has {
|
||||||
|
delete(page.APIs, name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
api := defaults[name]
|
||||||
|
api.Name = name
|
||||||
|
if page.APIs[name].Process != "" {
|
||||||
|
api.Process = page.APIs[name].Process
|
||||||
|
}
|
||||||
|
|
||||||
|
if page.APIs[name].Guard != "" {
|
||||||
|
api.Guard = page.APIs[name].Guard
|
||||||
|
}
|
||||||
|
|
||||||
|
if page.APIs[name].Default != nil {
|
||||||
|
api.Default = page.APIs[name].Default
|
||||||
|
}
|
||||||
|
|
||||||
|
defaults[name] = api
|
||||||
|
}
|
||||||
|
|
||||||
|
page.APIs = defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
// apiSearchDefault data 接口默认值
|
||||||
|
func apiDataDefault() share.API {
|
||||||
|
param := map[string]interface{}{}
|
||||||
|
return share.API{
|
||||||
|
Name: "data",
|
||||||
|
Guard: "bearer-jwt",
|
||||||
|
Process: "xiang.page.data",
|
||||||
|
Default: []interface{}{param},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// apiSettingDefault setting 接口默认值
|
||||||
|
func apiSettingDefault() share.API {
|
||||||
|
return share.API{
|
||||||
|
Name: "setting",
|
||||||
|
Guard: "bearer-jwt",
|
||||||
|
Process: "xiang.page.setting",
|
||||||
|
}
|
||||||
|
}
|
||||||
86
page/page.go
Normal file
86
page/page.go
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
package page
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/yaoapp/gou"
|
||||||
|
"github.com/yaoapp/kun/exception"
|
||||||
|
"github.com/yaoapp/xiang/config"
|
||||||
|
"github.com/yaoapp/xiang/share"
|
||||||
|
"github.com/yaoapp/xiang/xlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pages 已载入页面
|
||||||
|
var Pages = map[string]*Page{}
|
||||||
|
|
||||||
|
// Load 加载页面
|
||||||
|
func Load(cfg config.Config) {
|
||||||
|
LoadFrom(cfg.RootPage, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFrom 从特定目录加载
|
||||||
|
func LoadFrom(dir string, prefix string) {
|
||||||
|
|
||||||
|
if share.DirNotExists(dir) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
share.Walk(dir, ".json", func(root, filename string) {
|
||||||
|
name := prefix + share.SpecName(root, filename)
|
||||||
|
content := share.ReadFile(filename)
|
||||||
|
_, err := LoadPage(content, name)
|
||||||
|
if err != nil {
|
||||||
|
exception.New("%s 页面格式错误", 400, name).Ctx(filename).Throw()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Load Script
|
||||||
|
share.Walk(dir, ".js", func(root, filename string) {
|
||||||
|
name := prefix + share.SpecName(root, filename)
|
||||||
|
page := Select(name)
|
||||||
|
if page != nil {
|
||||||
|
script := share.ScriptName(filename)
|
||||||
|
content := share.ReadFile(filename)
|
||||||
|
page.LoadScript(string(content), script)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadPage 载入页面
|
||||||
|
func LoadPage(source []byte, name string) (*Page, error) {
|
||||||
|
page := &Page{
|
||||||
|
Flow: gou.Flow{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := jsoniter.Unmarshal(source, page)
|
||||||
|
if err != nil {
|
||||||
|
xlog.Println(name)
|
||||||
|
xlog.Println(err.Error())
|
||||||
|
xlog.Println(string(source))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
page.Prepare()
|
||||||
|
page.SetupAPIs()
|
||||||
|
Pages[name] = page
|
||||||
|
return page, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select 读取已加载页面
|
||||||
|
func Select(name string) *Page {
|
||||||
|
page, has := Pages[name]
|
||||||
|
if !has {
|
||||||
|
exception.New(
|
||||||
|
fmt.Sprintf("Page:%s; 尚未加载", name),
|
||||||
|
400,
|
||||||
|
).Throw()
|
||||||
|
}
|
||||||
|
return page
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetData 运行 flow 返回数值
|
||||||
|
func (page Page) GetData(params map[string]interface{}) interface{} {
|
||||||
|
return page.Flow.Exec(params)
|
||||||
|
}
|
||||||
29
page/page_test.go
Normal file
29
page/page_test.go
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
package page
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/yaoapp/xiang/config"
|
||||||
|
"github.com/yaoapp/xiang/model"
|
||||||
|
"github.com/yaoapp/xiang/query"
|
||||||
|
"github.com/yaoapp/xiang/share"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoad(t *testing.T) {
|
||||||
|
share.DBConnect(config.Conf.Database)
|
||||||
|
model.Load(config.Conf)
|
||||||
|
query.Load(config.Conf)
|
||||||
|
|
||||||
|
Load(config.Conf)
|
||||||
|
LoadFrom("not a path", "404.")
|
||||||
|
check(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func check(t *testing.T) {
|
||||||
|
keys := []string{}
|
||||||
|
for key := range Pages {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 1, len(keys))
|
||||||
|
}
|
||||||
82
page/process.go
Normal file
82
page/process.go
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
package page
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yaoapp/gou"
|
||||||
|
"github.com/yaoapp/kun/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 注册处理器
|
||||||
|
func init() {
|
||||||
|
gou.RegisterProcessHandler("xiang.page.data", ProcessData)
|
||||||
|
gou.RegisterProcessHandler("xiang.page.setting", ProcessSetting)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessData xiang.page.data
|
||||||
|
// 查询数据分析页面中定义的数据
|
||||||
|
func ProcessData(process *gou.Process) interface{} {
|
||||||
|
|
||||||
|
process.ValidateArgNums(3)
|
||||||
|
name := process.ArgsString(0)
|
||||||
|
params := process.ArgsMap(1)
|
||||||
|
page := Select(name)
|
||||||
|
api := page.APIs["data"]
|
||||||
|
if process.NumOfArgsIs(4) && api.IsAllow(process.Args[3]) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(api.Default) > 0 {
|
||||||
|
if defaults, ok := api.Default[0].(map[string]interface{}); ok {
|
||||||
|
for key, value := range defaults {
|
||||||
|
if !params.Has(key) {
|
||||||
|
params.Set(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return page.GetData(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessSetting xiang.page.setting
|
||||||
|
// 查询数据分析页面中定义的数据
|
||||||
|
func ProcessSetting(process *gou.Process) interface{} {
|
||||||
|
|
||||||
|
process.ValidateArgNums(2)
|
||||||
|
name := process.ArgsString(0)
|
||||||
|
field := process.ArgsString(1)
|
||||||
|
page := Select(name)
|
||||||
|
api := page.APIs["setting"]
|
||||||
|
if process.NumOfArgsIs(2) && api.IsAllow(process.Args[1]) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := strings.Split(field, ",")
|
||||||
|
setting := maps.Map{
|
||||||
|
"name": page.Name,
|
||||||
|
"label": page.Label,
|
||||||
|
"version": page.Version,
|
||||||
|
"description": page.Description,
|
||||||
|
"filters": page.Filters,
|
||||||
|
"page": page.Page,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fields) == 1 && setting.Has(fields[0]) {
|
||||||
|
field := strings.TrimSpace(fields[0])
|
||||||
|
return setting.Get(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fields) > 1 {
|
||||||
|
res := maps.Map{}
|
||||||
|
for _, field := range fields {
|
||||||
|
field = strings.TrimSpace(field)
|
||||||
|
if setting.Has(field) {
|
||||||
|
res.Set(field, setting.Get(field))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
return setting
|
||||||
|
|
||||||
|
}
|
||||||
77
page/process_test.go
Normal file
77
page/process_test.go
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
package page
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/yaoapp/gou"
|
||||||
|
"github.com/yaoapp/kun/any"
|
||||||
|
"github.com/yaoapp/kun/utils"
|
||||||
|
"github.com/yaoapp/xiang/config"
|
||||||
|
_ "github.com/yaoapp/xiang/helper"
|
||||||
|
"github.com/yaoapp/xiang/model"
|
||||||
|
"github.com/yaoapp/xiang/query"
|
||||||
|
"github.com/yaoapp/xiang/share"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
share.DBConnect(config.Conf.Database)
|
||||||
|
model.Load(config.Conf)
|
||||||
|
query.Load(config.Conf)
|
||||||
|
Load(config.Conf)
|
||||||
|
}
|
||||||
|
func TestProcessSetting(t *testing.T) {
|
||||||
|
|
||||||
|
args := []interface{}{
|
||||||
|
"service.compare",
|
||||||
|
nil,
|
||||||
|
&gin.Context{},
|
||||||
|
}
|
||||||
|
process := gou.NewProcess("xiang.page.Setting", args...)
|
||||||
|
response := ProcessSetting(process)
|
||||||
|
assert.NotNil(t, response)
|
||||||
|
res := any.Of(response).Map()
|
||||||
|
assert.True(t, res.Has("name"))
|
||||||
|
assert.True(t, res.Has("label"))
|
||||||
|
assert.True(t, res.Has("description"))
|
||||||
|
assert.True(t, res.Has("page"))
|
||||||
|
assert.True(t, res.Has("version"))
|
||||||
|
|
||||||
|
args = []interface{}{
|
||||||
|
"service.compare",
|
||||||
|
"page,name",
|
||||||
|
&gin.Context{},
|
||||||
|
}
|
||||||
|
process = gou.NewProcess("xiang.page.Setting", args...)
|
||||||
|
response = ProcessSetting(process)
|
||||||
|
assert.NotNil(t, response)
|
||||||
|
|
||||||
|
res = any.Of(response).Map()
|
||||||
|
assert.True(t, res.Has("name"))
|
||||||
|
assert.True(t, res.Has("page"))
|
||||||
|
assert.False(t, res.Has("label"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessData(t *testing.T) {
|
||||||
|
|
||||||
|
params := url.Values{
|
||||||
|
"from": []string{"1981-01-01", "1990-01-01"},
|
||||||
|
}
|
||||||
|
params.Set("to", "2049-12-31")
|
||||||
|
|
||||||
|
args := []interface{}{
|
||||||
|
"service.compare",
|
||||||
|
params,
|
||||||
|
&gin.Context{},
|
||||||
|
}
|
||||||
|
process := gou.NewProcess("xiang.page.Data", args...)
|
||||||
|
response := ProcessData(process)
|
||||||
|
utils.Dump(response)
|
||||||
|
|
||||||
|
assert.NotNil(t, response)
|
||||||
|
|
||||||
|
res := any.Of(response).Map().Dot()
|
||||||
|
assert.Equal(t, "北京", res.Get("合并.0.城市"))
|
||||||
|
}
|
||||||
14
page/types.go
Normal file
14
page/types.go
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
package page
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yaoapp/gou"
|
||||||
|
"github.com/yaoapp/xiang/share"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Page 页面格式
|
||||||
|
type Page struct {
|
||||||
|
gou.Flow
|
||||||
|
APIs map[string]share.API `json:"apis,omitempty"`
|
||||||
|
Filters map[string]share.Filter `json:"filters,omitempty"`
|
||||||
|
Page share.Page `json:"page,omitempty"`
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/yaoapp/gou"
|
"github.com/yaoapp/gou"
|
||||||
"github.com/yaoapp/xiang/chart"
|
"github.com/yaoapp/xiang/chart"
|
||||||
"github.com/yaoapp/xiang/config"
|
"github.com/yaoapp/xiang/config"
|
||||||
|
"github.com/yaoapp/xiang/page"
|
||||||
"github.com/yaoapp/xiang/share"
|
"github.com/yaoapp/xiang/share"
|
||||||
"github.com/yaoapp/xiang/table"
|
"github.com/yaoapp/xiang/table"
|
||||||
)
|
)
|
||||||
|
|
@ -22,6 +23,7 @@ func Watch(cfg config.Config) {
|
||||||
WatchPlugin(cfg.RootPlugin)
|
WatchPlugin(cfg.RootPlugin)
|
||||||
WatchTable(cfg.RootTable, "")
|
WatchTable(cfg.RootTable, "")
|
||||||
WatchChart(cfg.RootChart, "")
|
WatchChart(cfg.RootChart, "")
|
||||||
|
WatchPage(cfg.RootPage, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// WatchEngine 监听监听引擎内建数据变更
|
// WatchEngine 监听监听引擎内建数据变更
|
||||||
|
|
@ -114,13 +116,23 @@ func WatchFlow(root string, prefix string) {
|
||||||
|
|
||||||
if strings.HasSuffix(filename, ".js") {
|
if strings.HasSuffix(filename, ".js") {
|
||||||
name := prefix + share.SpecName(root, filename)
|
name := prefix + share.SpecName(root, filename)
|
||||||
filename = name + ".flow.json"
|
name = strings.ReplaceAll(name, ".", "/")
|
||||||
|
filename = filepath.Join(root, name+".flow.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
if op == "write" || op == "create" {
|
if op == "write" || op == "create" {
|
||||||
name := prefix + share.SpecName(root, filename)
|
name := prefix + share.SpecName(root, filename)
|
||||||
content := share.ReadFile(filename)
|
content := share.ReadFile(filename)
|
||||||
gou.LoadFlow(string(content), name) // Reload
|
flow := gou.LoadFlow(string(content), name) // Reload
|
||||||
|
if flow != nil { // Reload Script
|
||||||
|
dir := filepath.Dir(filename)
|
||||||
|
share.Walk(dir, ".js", func(root, filename string) {
|
||||||
|
script := share.ScriptName(filename)
|
||||||
|
content := share.ReadFile(filename)
|
||||||
|
flow.LoadScript(string(content), script)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("Flow %s 已重新加载完毕", name)
|
log.Printf("Flow %s 已重新加载完毕", name)
|
||||||
|
|
||||||
} else if op == "remove" || op == "rename" {
|
} else if op == "remove" || op == "rename" {
|
||||||
|
|
@ -214,13 +226,22 @@ func WatchChart(root string, prefix string) {
|
||||||
|
|
||||||
if strings.HasSuffix(filename, ".js") {
|
if strings.HasSuffix(filename, ".js") {
|
||||||
name := prefix + share.SpecName(root, filename)
|
name := prefix + share.SpecName(root, filename)
|
||||||
filename = name + ".chart.json"
|
name = strings.ReplaceAll(name, ".", "/")
|
||||||
|
filename = filepath.Join(root, name+".chart.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
if op == "write" || op == "create" {
|
if op == "write" || op == "create" {
|
||||||
name := prefix + share.SpecName(root, filename)
|
name := prefix + share.SpecName(root, filename)
|
||||||
content := share.ReadFile(filename)
|
content := share.ReadFile(filename)
|
||||||
chart.LoadChart(content, name) // Relaod
|
chart, _ := chart.LoadChart(content, name) // Relaod
|
||||||
|
if chart != nil { // Reload Script
|
||||||
|
dir := filepath.Dir(filename)
|
||||||
|
share.Walk(dir, ".js", func(root, filename string) {
|
||||||
|
script := share.ScriptName(filename)
|
||||||
|
content := share.ReadFile(filename)
|
||||||
|
chart.LoadScript(string(content), script)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
api, has := gou.APIs["xiang.chart"]
|
api, has := gou.APIs["xiang.chart"]
|
||||||
if has {
|
if has {
|
||||||
|
|
@ -246,3 +267,57 @@ func WatchChart(root string, prefix string) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WatchPage 监听页面更新
|
||||||
|
func WatchPage(root string, prefix string) {
|
||||||
|
if share.DirNotExists(root) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
root = share.DirAbs(root)
|
||||||
|
go share.Watch(root, func(op string, filename string) {
|
||||||
|
if !strings.HasSuffix(filename, ".json") && !strings.HasSuffix(filename, ".js") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(filename, ".js") {
|
||||||
|
name := prefix + share.SpecName(root, filename)
|
||||||
|
name = strings.ReplaceAll(name, ".", "/")
|
||||||
|
filename = filepath.Join(root, name+".page.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
if op == "write" || op == "create" {
|
||||||
|
name := prefix + share.SpecName(root, filename)
|
||||||
|
content := share.ReadFile(filename)
|
||||||
|
page, _ := page.LoadPage(content, name) // Relaod
|
||||||
|
if page != nil { // Reload Script
|
||||||
|
dir := filepath.Dir(filename)
|
||||||
|
share.Walk(dir, ".js", func(root, filename string) {
|
||||||
|
script := share.ScriptName(filename)
|
||||||
|
content := share.ReadFile(filename)
|
||||||
|
page.LoadScript(string(content), script)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
api, has := gou.APIs["xiang.page"]
|
||||||
|
if has {
|
||||||
|
api.Reload() // 重载API
|
||||||
|
}
|
||||||
|
log.Printf("Page %s 已重新加载完毕", name)
|
||||||
|
|
||||||
|
} else if op == "remove" || op == "rename" {
|
||||||
|
name := prefix + share.SpecName(root, filename)
|
||||||
|
if _, has := page.Pages[name]; has {
|
||||||
|
delete(page.Pages, name)
|
||||||
|
log.Printf("Page %s 已经移除", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重启服务器
|
||||||
|
if op == "write" || op == "create" || op == "remove" || op == "rename" {
|
||||||
|
Stop(func() {
|
||||||
|
log.Printf("服务器重启完毕")
|
||||||
|
go Start()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,16 @@ func SpecName(root string, file string) string {
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ScriptName 解析数据处理脚本名称
|
||||||
|
func ScriptName(filename string) string {
|
||||||
|
filename = strings.TrimSuffix(filename, ".js")
|
||||||
|
namer := strings.Split(filename, ".") // ["foo/bar", "http", "json"]
|
||||||
|
if len(namer) < 2 {
|
||||||
|
return namer[0]
|
||||||
|
}
|
||||||
|
return namer[len(namer)-1]
|
||||||
|
}
|
||||||
|
|
||||||
// ReadFile 读取文件
|
// ReadFile 读取文件
|
||||||
func ReadFile(filename string) []byte {
|
func ReadFile(filename string) []byte {
|
||||||
file, err := os.Open(filename)
|
file, err := os.Open(filename)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
function main(args, out, res) {
|
function main(args, out, res) {
|
||||||
console.log(args, out, res);
|
console.log(args, out, res);
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
tests/pages/service/compare.data.js
Normal file
3
tests/pages/service/compare.data.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
function main(args, out, res) {
|
||||||
|
return { args: args };
|
||||||
|
}
|
||||||
111
tests/pages/service/compare.page.json
Normal file
111
tests/pages/service/compare.page.json
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
{
|
||||||
|
"label": "指标对比",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "指标对比用于测试",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"name": "行业",
|
||||||
|
"engine": "xiang",
|
||||||
|
"query": {
|
||||||
|
"debug": true,
|
||||||
|
"select": [
|
||||||
|
"city",
|
||||||
|
":COUNT(id) as 数量",
|
||||||
|
"industries[*](string 50) as industry"
|
||||||
|
],
|
||||||
|
"from": "$service",
|
||||||
|
"wheres": [
|
||||||
|
{ "field": "created_at", ">": "?:$from.0" },
|
||||||
|
{ "field": "created_at", "<": "?:$to" }
|
||||||
|
],
|
||||||
|
"orders": "数量 desc",
|
||||||
|
"limit": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "计费",
|
||||||
|
"engine": "xiang",
|
||||||
|
"query": {
|
||||||
|
"select": ["city", ":COUNT(id) as 数量", "price_options[*] as option"],
|
||||||
|
"from": "$service",
|
||||||
|
"wheres": [
|
||||||
|
{ "field": "created_at", ">": "?:$from.0" },
|
||||||
|
{ "field": "created_at", "<": "?:$to" }
|
||||||
|
],
|
||||||
|
"orders": "数量 desc",
|
||||||
|
"limit": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "脚本",
|
||||||
|
"script": "data",
|
||||||
|
"outs": ["{{$out}}", "1"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "合并结果",
|
||||||
|
"process": "xiang.helper.ArrayPluck",
|
||||||
|
"args": [
|
||||||
|
["城市", "行业", "计费"],
|
||||||
|
{
|
||||||
|
"行业": { "key": "city", "value": "数量", "items": "{{$res.行业}}" },
|
||||||
|
"计费": { "key": "city", "value": "数量", "items": "{{$res.计费}}" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": {
|
||||||
|
"合并": "{{$res.合并结果}}",
|
||||||
|
"计费": "{{$res.计费}}",
|
||||||
|
"行业": "{{$res.行业}}",
|
||||||
|
"参数": "{{$in}}",
|
||||||
|
"脚本": "{{$res.脚本}}",
|
||||||
|
"page": true
|
||||||
|
},
|
||||||
|
"apis": {
|
||||||
|
"data": {
|
||||||
|
"disable": false,
|
||||||
|
"guard": "-",
|
||||||
|
"default": [{ "from": "1980-01-02", "to": "2050-12-31" }]
|
||||||
|
},
|
||||||
|
"setting": {
|
||||||
|
"disable": false,
|
||||||
|
"guard": "-"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"开始时间": {
|
||||||
|
"label": "开始时间",
|
||||||
|
"bind": "from",
|
||||||
|
"input": {
|
||||||
|
"type": "date",
|
||||||
|
"props": {
|
||||||
|
"placeholder": "请选择开始时间"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"结束时间": {
|
||||||
|
"label": "结束时间",
|
||||||
|
"bind": "to",
|
||||||
|
"input": {
|
||||||
|
"type": "date",
|
||||||
|
"props": {
|
||||||
|
"placeholder": "请选择结束时间"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"page": {
|
||||||
|
"primary": "城市",
|
||||||
|
"layout": {
|
||||||
|
"filters": [
|
||||||
|
{ "name": "开始时间", "width": 6 },
|
||||||
|
{ "name": "结束时间", "width": 6 }
|
||||||
|
],
|
||||||
|
"charts": [
|
||||||
|
{ "type": "line", "props": {} },
|
||||||
|
{ "type": "bar", "props": {} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"actions": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
xiang/apis/page.http.json
Normal file
29
xiang/apis/page.http.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"name": "页面接口",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "页面接口API",
|
||||||
|
"group": "xiang/page",
|
||||||
|
"guard": "in-process",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "/:name/data",
|
||||||
|
"method": "GET",
|
||||||
|
"process": "xiang.page.Data",
|
||||||
|
"in": ["$param.name", ":query"],
|
||||||
|
"out": {
|
||||||
|
"status": 200,
|
||||||
|
"type": "application/json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/:name/setting",
|
||||||
|
"method": "GET",
|
||||||
|
"process": "xiang.page.Setting",
|
||||||
|
"in": ["$param.name", "$query.select"],
|
||||||
|
"out": {
|
||||||
|
"status": 200,
|
||||||
|
"type": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue