[add]Workflow 处理器
This commit is contained in:
parent
66eaceca1c
commit
ae0cc0b60c
3 changed files with 359 additions and 6 deletions
119
workflow/process.go
Normal file
119
workflow/process.go
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
package workflow
|
||||||
|
|
||||||
|
import "github.com/yaoapp/gou"
|
||||||
|
|
||||||
|
// Process
|
||||||
|
// 读取工作流 xiang.workflow.Find(name, workflow_id)
|
||||||
|
// 读取工作流 xiang.workflow.Open(name, uid, data_id)
|
||||||
|
// 保存工作流 xiang.workflow.Save(name, uid, node_name, data_id, input, ...output)
|
||||||
|
// 进入下一个节点 xiang.workflow.Next(name, uid, workflow_id, output)
|
||||||
|
// 跳转到指定节点 xiang.workflow.Goto(name, uid, workflow_id, node_name, output)
|
||||||
|
// 更新工作流状态 xiang.workflow.Status(name, uid, workflow_id, status_name, output)
|
||||||
|
// 标记结束流程 xiang.workflow.Done(name, uid, workflow_id, output)
|
||||||
|
// 标记关闭流程 xiang.workflow.Close(name, uid, workflow_id, output)
|
||||||
|
// 标记重置流程 xiang.workflow.Reset(name, uid, workflow_id, output)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// 注册处理器
|
||||||
|
gou.RegisterProcessHandler("xiang.workflow.Find", ProcessFind)
|
||||||
|
gou.RegisterProcessHandler("xiang.workflow.Open", ProcessOpen)
|
||||||
|
gou.RegisterProcessHandler("xiang.workflow.Save", ProcessSave)
|
||||||
|
gou.RegisterProcessHandler("xiang.workflow.Next", ProcessNext)
|
||||||
|
gou.RegisterProcessHandler("xiang.workflow.Goto", ProcessGoto)
|
||||||
|
gou.RegisterProcessHandler("xiang.workflow.Status", ProcessStatus)
|
||||||
|
gou.RegisterProcessHandler("xiang.workflow.Done", ProcessDone)
|
||||||
|
gou.RegisterProcessHandler("xiang.workflow.Close", ProcessClose)
|
||||||
|
gou.RegisterProcessHandler("xiang.workflow.Reset", ProcessReset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessFind xiang.workflow.Find 读取工作流
|
||||||
|
// args: [工作流名称*, 工作流ID*]
|
||||||
|
// return: map[string]interface{} 工作流数据记录
|
||||||
|
func ProcessFind(process *gou.Process) interface{} {
|
||||||
|
process.ValidateArgNums(2)
|
||||||
|
wflow := Select(process.ArgsString(0))
|
||||||
|
return wflow.Find(process.ArgsInt(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessOpen xiang.workflow.Open 读取工作流
|
||||||
|
// args: [工作流名称*, 当前用户ID*, 关联数据ID*]
|
||||||
|
// return: map[string]interface{} 工作流数据记录
|
||||||
|
func ProcessOpen(process *gou.Process) interface{} {
|
||||||
|
process.ValidateArgNums(3)
|
||||||
|
wflow := Select(process.ArgsString(0))
|
||||||
|
return wflow.Open(process.ArgsInt(1), process.Args[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessSave xiang.workflow.Save 保存工作流节点信息
|
||||||
|
// args: [工作流名称*, 当前用户ID*, 节点名称*, 关联数据ID*, 输入数据*, 输出数据] (输入数据: {"data":{}, "form":{}} data 关联数据记录信息, form 工作流body表单信息, 输出数据: {"foo":"bar"} )
|
||||||
|
// return: map[string]interface{} 工作流数据记录
|
||||||
|
func ProcessSave(process *gou.Process) interface{} {
|
||||||
|
process.ValidateArgNums(5)
|
||||||
|
wflow := Select(process.ArgsString(0))
|
||||||
|
input := InputOf(process.ArgsMap(4))
|
||||||
|
output := map[string]interface{}{}
|
||||||
|
if process.NumOfArgsIs(6) {
|
||||||
|
output = process.ArgsMap(5)
|
||||||
|
}
|
||||||
|
return wflow.Save(process.ArgsInt(1), process.ArgsString(2), process.Args[3], input, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessNext xiang.workflow.Next 进入下一个节点
|
||||||
|
// args: [工作流名称*, 当前用户ID*, 工作流ID*, 输出数据*] (输出数据: {"foo":"bar"} )
|
||||||
|
// return: map[string]interface{} 工作流数据记录
|
||||||
|
func ProcessNext(process *gou.Process) interface{} {
|
||||||
|
process.ValidateArgNums(4)
|
||||||
|
wflow := Select(process.ArgsString(0))
|
||||||
|
output := process.ArgsMap(3)
|
||||||
|
return wflow.Next(process.ArgsInt(1), process.ArgsInt(2), output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessGoto xiang.workflow.Goto 跳转到指定节点
|
||||||
|
// args: [工作流名称*, 当前用户ID*, 工作流ID*, 节点名称*, 输出数据*] (输出数据: {"foo":"bar"} )
|
||||||
|
// return: map[string]interface{} 工作流数据记录
|
||||||
|
func ProcessGoto(process *gou.Process) interface{} {
|
||||||
|
process.ValidateArgNums(5)
|
||||||
|
wflow := Select(process.ArgsString(0))
|
||||||
|
output := process.ArgsMap(4)
|
||||||
|
return wflow.Goto(process.ArgsInt(1), process.ArgsInt(2), process.ArgsString(3), output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessStatus xiang.workflow.Status 更新工作流状态
|
||||||
|
// args: [工作流名称*, 当前用户ID*, 工作流ID*, 状态名称*, 输出数据*] (输出数据: {"foo":"bar"} )
|
||||||
|
// return: map[string]interface{} 工作流数据记录
|
||||||
|
func ProcessStatus(process *gou.Process) interface{} {
|
||||||
|
process.ValidateArgNums(5)
|
||||||
|
wflow := Select(process.ArgsString(0))
|
||||||
|
output := process.ArgsMap(4)
|
||||||
|
return wflow.Status(process.ArgsInt(1), process.ArgsInt(2), process.ArgsString(3), output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessDone xiang.workflow.Done 标记结束流程
|
||||||
|
// args: [工作流名称*, 当前用户ID*, 工作流ID*, 输出数据*] (输出数据: {"foo":"bar"} )
|
||||||
|
// return: map[string]interface{} 工作流数据记录
|
||||||
|
func ProcessDone(process *gou.Process) interface{} {
|
||||||
|
process.ValidateArgNums(4)
|
||||||
|
wflow := Select(process.ArgsString(0))
|
||||||
|
output := process.ArgsMap(3)
|
||||||
|
return wflow.Done(process.ArgsInt(1), process.ArgsInt(2), output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessClose xiang.workflow.Close 标记关闭流程
|
||||||
|
// args: [工作流名称*, 当前用户ID*, 工作流ID*, 输出数据*] (输出数据: {"foo":"bar"} )
|
||||||
|
// return: map[string]interface{} 工作流数据记录
|
||||||
|
func ProcessClose(process *gou.Process) interface{} {
|
||||||
|
process.ValidateArgNums(4)
|
||||||
|
wflow := Select(process.ArgsString(0))
|
||||||
|
output := process.ArgsMap(3)
|
||||||
|
return wflow.Close(process.ArgsInt(1), process.ArgsInt(2), output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessReset xiang.workflow.Reset 标记重置流程
|
||||||
|
// args: [工作流名称*, 当前用户ID*, 工作流ID*, 输出数据*] (输出数据: {"foo":"bar"} )
|
||||||
|
// return: map[string]interface{} 工作流数据记录
|
||||||
|
func ProcessReset(process *gou.Process) interface{} {
|
||||||
|
process.ValidateArgNums(4)
|
||||||
|
wflow := Select(process.ArgsString(0))
|
||||||
|
output := process.ArgsMap(3)
|
||||||
|
return wflow.Reset(process.ArgsInt(1), process.ArgsInt(2), output)
|
||||||
|
}
|
||||||
222
workflow/process_test.go
Normal file
222
workflow/process_test.go
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
package workflow
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/yaoapp/gou"
|
||||||
|
"github.com/yaoapp/kun/any"
|
||||||
|
"github.com/yaoapp/xiang/config"
|
||||||
|
"github.com/yaoapp/xiang/flow"
|
||||||
|
"github.com/yaoapp/xiang/model"
|
||||||
|
"github.com/yaoapp/xiang/query"
|
||||||
|
"github.com/yaoapp/xiang/share"
|
||||||
|
"github.com/yaoapp/xun/capsule"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
share.DBConnect(config.Conf.Database)
|
||||||
|
share.Load(config.Conf)
|
||||||
|
model.Load(config.Conf)
|
||||||
|
engineModels := path.Join(config.Conf.Source, "xiang", "models")
|
||||||
|
model.LoadFrom(engineModels, "xiang.")
|
||||||
|
query.Load(config.Conf)
|
||||||
|
flow.Load(config.Conf)
|
||||||
|
Load(config.Conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessFind(t *testing.T) {
|
||||||
|
assignFlow := Select("assign")
|
||||||
|
wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||||
|
Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||||
|
Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||||
|
})
|
||||||
|
|
||||||
|
args := []interface{}{"assign", wflow["id"]}
|
||||||
|
res := gou.NewProcess("xiang.workflow.Find", args...).Run()
|
||||||
|
|
||||||
|
data := any.Of(res).Map().MapStrAny.Dot()
|
||||||
|
assert.Equal(t, wflow["id"], data.Get("id"))
|
||||||
|
|
||||||
|
// 清理数据
|
||||||
|
capsule.Query().From("xiang_workflow").Truncate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessOpen(t *testing.T) {
|
||||||
|
assignFlow := Select("assign")
|
||||||
|
wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||||
|
Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||||
|
Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||||
|
})
|
||||||
|
|
||||||
|
args := []interface{}{"assign", 1, 1}
|
||||||
|
res := gou.NewProcess("xiang.workflow.Open", args...).Run()
|
||||||
|
|
||||||
|
data := any.Of(res).Map().MapStrAny.Dot()
|
||||||
|
assert.Equal(t, wflow["id"], data.Get("id"))
|
||||||
|
|
||||||
|
// 清理数据
|
||||||
|
capsule.Query().From("xiang_workflow").Truncate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessSave(t *testing.T) {
|
||||||
|
args := []interface{}{"assign",
|
||||||
|
1, "选择商务负责人", 1,
|
||||||
|
map[string]interface{}{
|
||||||
|
"data": map[string]interface{}{"id": 1, "name": "云主机"},
|
||||||
|
"form": map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||||
|
},
|
||||||
|
map[string]interface{}{"foo": "bar"},
|
||||||
|
}
|
||||||
|
res := gou.NewProcess("xiang.workflow.Save", args...).Run()
|
||||||
|
data := any.Of(res).Map().MapStrAny.Dot()
|
||||||
|
assert.Equal(t, int64(1), data.Get("id"))
|
||||||
|
|
||||||
|
// 清理数据
|
||||||
|
capsule.Query().From("xiang_workflow").Truncate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessNext(t *testing.T) {
|
||||||
|
assignFlow := Select("assign")
|
||||||
|
wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||||
|
Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||||
|
Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||||
|
})
|
||||||
|
|
||||||
|
args := []interface{}{"assign",
|
||||||
|
1, wflow["id"], map[string]interface{}{
|
||||||
|
"项目名称": "测试项目",
|
||||||
|
"商务负责人名称": "林明波",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := gou.NewProcess("xiang.workflow.Next", args...).Run()
|
||||||
|
data := any.Of(res).Map().MapStrAny.Dot()
|
||||||
|
assert.Equal(t, wflow["id"], data.Get("id"))
|
||||||
|
assert.Equal(t, "项目负责人审批", data.Get("node_name"))
|
||||||
|
|
||||||
|
// 清理数据
|
||||||
|
capsule.Query().From("xiang_workflow").Truncate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessGoto(t *testing.T) {
|
||||||
|
assignFlow := Select("assign")
|
||||||
|
wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||||
|
Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||||
|
Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||||
|
})
|
||||||
|
id := any.Of(wflow["id"]).CInt()
|
||||||
|
assignFlow.Next(1, id, map[string]interface{}{
|
||||||
|
"项目名称": "测试项目",
|
||||||
|
"商务负责人名称": "林明波",
|
||||||
|
})
|
||||||
|
|
||||||
|
args := []interface{}{"assign",
|
||||||
|
2, id, "选择商务负责人",
|
||||||
|
map[string]interface{}{"审批结果": "驳回"},
|
||||||
|
}
|
||||||
|
res := gou.NewProcess("xiang.workflow.Goto", args...).Run()
|
||||||
|
data := any.Of(res).Map().MapStrAny.Dot()
|
||||||
|
assert.Equal(t, wflow["id"], data.Get("id"))
|
||||||
|
assert.Equal(t, "选择商务负责人", data.Get("node_name"))
|
||||||
|
|
||||||
|
// 清理数据
|
||||||
|
capsule.Query().From("xiang_workflow").Truncate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessStatus(t *testing.T) {
|
||||||
|
assignFlow := Select("assign")
|
||||||
|
wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||||
|
Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||||
|
Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||||
|
})
|
||||||
|
id := any.Of(wflow["id"]).CInt()
|
||||||
|
assignFlow.Next(1, id, map[string]interface{}{
|
||||||
|
"项目名称": "测试项目",
|
||||||
|
"商务负责人名称": "林明波",
|
||||||
|
})
|
||||||
|
|
||||||
|
args := []interface{}{"assign",
|
||||||
|
2, id, "已完成", map[string]interface{}{"关闭原因": "测试完成"},
|
||||||
|
}
|
||||||
|
res := gou.NewProcess("xiang.workflow.Status", args...).Run()
|
||||||
|
data := any.Of(res).Map().MapStrAny.Dot()
|
||||||
|
assert.Equal(t, wflow["id"], data.Get("id"))
|
||||||
|
assert.Equal(t, "已完成", data.Get("status"))
|
||||||
|
|
||||||
|
// 清理数据
|
||||||
|
capsule.Query().From("xiang_workflow").Truncate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessDone(t *testing.T) {
|
||||||
|
assignFlow := Select("assign")
|
||||||
|
wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||||
|
Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||||
|
Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||||
|
})
|
||||||
|
id := any.Of(wflow["id"]).CInt()
|
||||||
|
assignFlow.Next(1, id, map[string]interface{}{
|
||||||
|
"项目名称": "测试项目",
|
||||||
|
"商务负责人名称": "林明波",
|
||||||
|
})
|
||||||
|
|
||||||
|
args := []interface{}{"assign",
|
||||||
|
2, id, map[string]interface{}{"关闭原因": "测试完成"},
|
||||||
|
}
|
||||||
|
res := gou.NewProcess("xiang.workflow.Done", args...).Run()
|
||||||
|
data := any.Of(res).Map().MapStrAny.Dot()
|
||||||
|
assert.Equal(t, wflow["id"], data.Get("id"))
|
||||||
|
assert.Equal(t, "已完成", data.Get("status"))
|
||||||
|
|
||||||
|
// 清理数据
|
||||||
|
capsule.Query().From("xiang_workflow").Truncate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessClose(t *testing.T) {
|
||||||
|
assignFlow := Select("assign")
|
||||||
|
wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||||
|
Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||||
|
Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||||
|
})
|
||||||
|
id := any.Of(wflow["id"]).CInt()
|
||||||
|
assignFlow.Next(1, id, map[string]interface{}{
|
||||||
|
"项目名称": "测试项目",
|
||||||
|
"商务负责人名称": "林明波",
|
||||||
|
})
|
||||||
|
|
||||||
|
args := []interface{}{"assign",
|
||||||
|
2, id, map[string]interface{}{"关闭原因": "测试关闭"},
|
||||||
|
}
|
||||||
|
res := gou.NewProcess("xiang.workflow.Close", args...).Run()
|
||||||
|
data := any.Of(res).Map().MapStrAny.Dot()
|
||||||
|
assert.Equal(t, wflow["id"], data.Get("id"))
|
||||||
|
assert.Equal(t, "已关闭", data.Get("status"))
|
||||||
|
|
||||||
|
// 清理数据
|
||||||
|
capsule.Query().From("xiang_workflow").Truncate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessReset(t *testing.T) {
|
||||||
|
assignFlow := Select("assign")
|
||||||
|
wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||||
|
Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||||
|
Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||||
|
})
|
||||||
|
id := any.Of(wflow["id"]).CInt()
|
||||||
|
assignFlow.Next(1, id, map[string]interface{}{
|
||||||
|
"项目名称": "测试项目",
|
||||||
|
"商务负责人名称": "林明波",
|
||||||
|
})
|
||||||
|
|
||||||
|
args := []interface{}{"assign",
|
||||||
|
2, id, map[string]interface{}{"驳回原因": "字段填写错误"},
|
||||||
|
}
|
||||||
|
res := gou.NewProcess("xiang.workflow.Reset", args...).Run()
|
||||||
|
data := any.Of(res).Map().MapStrAny.Dot()
|
||||||
|
assert.Equal(t, wflow["id"], data.Get("id"))
|
||||||
|
assert.Equal(t, "进行中", data.Get("status"))
|
||||||
|
assert.Equal(t, "选择商务负责人", data.Get("node_name"))
|
||||||
|
|
||||||
|
// 清理数据
|
||||||
|
capsule.Query().From("xiang_workflow").Truncate()
|
||||||
|
}
|
||||||
|
|
@ -17,19 +17,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Process
|
// Process
|
||||||
// 读取工作流 xiang.workflow.Open(uid, data_id)
|
|
||||||
// 读取工作流 xiang.workflow.Find(workflow_id)
|
// 读取工作流 xiang.workflow.Find(workflow_id)
|
||||||
|
// 读取工作流 xiang.workflow.Open(uid, data_id)
|
||||||
// 保存工作流 xiang.workflow.Save(uid, node_name, data_id, input, ...output)
|
// 保存工作流 xiang.workflow.Save(uid, node_name, data_id, input, ...output)
|
||||||
// 进入下一个节点 xiang.workflow.Next(uid, workflow_id, output)
|
// 进入下一个节点 xiang.workflow.Next(uid, workflow_id, output)
|
||||||
// 跳转到指定节点 xiang.workflow.Goto(uid, workflow_id, node_name, output)
|
// 跳转到指定节点 xiang.workflow.Goto(uid, workflow_id, node_name, output)
|
||||||
// 更新流程状态 xiang.workflow.Status(uid, workflow_id, status_name, output)
|
// 更新工作流状态 xiang.workflow.Status(uid, workflow_id, status_name, output)
|
||||||
// 结束流程 xiang.workflow.Done(uid, workflow_id, output)
|
// 标记结束流程 xiang.workflow.Done(uid, workflow_id, output)
|
||||||
// 关闭流程 xiang.workflow.Close(uid, workflow_id, output)
|
// 标记关闭流程 xiang.workflow.Close(uid, workflow_id, output)
|
||||||
// 重置流程 xiang.workflow.Reset(uid, workflow_id, output)
|
// 标记重置流程 xiang.workflow.Reset(uid, workflow_id, output)
|
||||||
|
|
||||||
// API:
|
// API:
|
||||||
// 读取工作流 GET /api/xiang/workflow/<工作流名称>/open
|
|
||||||
// 读取工作流 GET /api/xiang/workflow/<工作流名称>/find/:id
|
// 读取工作流 GET /api/xiang/workflow/<工作流名称>/find/:id
|
||||||
|
// 读取工作流 GET /api/xiang/workflow/<工作流名称>/open
|
||||||
// 读取工作流配置 GET /api/xiang/workflow/<工作流名称>/setting
|
// 读取工作流配置 GET /api/xiang/workflow/<工作流名称>/setting
|
||||||
// 调用自定义API POST /api/xiang/workflow/<工作流名称>/<自定义API路由>
|
// 调用自定义API POST /api/xiang/workflow/<工作流名称>/<自定义API路由>
|
||||||
|
|
||||||
|
|
@ -435,3 +435,15 @@ func (workflow *WorkFlow) IsLastNode(name string) bool {
|
||||||
func (workflow *WorkFlow) Len() int {
|
func (workflow *WorkFlow) Len() int {
|
||||||
return len(workflow.Nodes)
|
return len(workflow.Nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InputOf 映射表转换为Input
|
||||||
|
func InputOf(in map[string]interface{}) Input {
|
||||||
|
input := Input{Data: map[string]interface{}{}, Form: map[string]interface{}{}}
|
||||||
|
if data, ok := in["data"].(map[string]interface{}); ok {
|
||||||
|
input.Data = data
|
||||||
|
}
|
||||||
|
if form, ok := in["form"].(map[string]interface{}); ok {
|
||||||
|
input.Form = form
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue