golang语言的办公工作流的包介绍——系列一
golang办公工作流workflow利用js-ojus/flow做测试——系列二
golang办公流程引擎初体验js-ojus/flow——系列三
golang办公流程引擎初体验js-ojus/flow——系列四
流程大致是这样的:
1.管理员定义好流程类型doctype,这个下面再分流程类型workflow1,workflow2,workflow下再具体分为节点node1,node2,
再定义通用的状态state,通用的动作action,以及根据workflow1下的节点来定义流程走向transition,
再定义contex里的用户-组-角色-权限
2.对于一个要走流程的文件document_001数据表的某个文件,先建立一个document,再给它定义多个事件event,根据workflow1下的节点多少来定义,节点数-1个事件,也就是transition的个数。这个事件就是定义将document_001里的某个文件从状态1 state1改为state2的动作action。
3.开始走流程了,用事件event来修改document_001数据表里某个文件的状态,这个状态从state1变为state2,符合node1里的state1到node2里的state2,也符合transition里的state1——action1——state2
package controllers
import (
"database/sql"
"github.com/astaxie/beego"
// "strings"
// "testing"
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/js-ojus/flow"
// "github.com/3xxx/meritms/models"
// _ "github.com/mattn/go-sqlite3"
// "github.com/astaxie/beego/httplib"
// "github.com/astaxie/beego/logs"
"log"
)
type MainController struct {
beego.Controller
}
func (c *MainController) Get() {
c.Data["Website"] = "beego.me"
c.Data["Email"] = "[email protected]"
c.TplName = "index.tpl"
}
// var db *sql.DB
// func init() {
// driver, connStr := "mysql", "root:root@/flow"
// tdb := fatal1(sql.Open(driver, connStr)).(*sql.DB)
// // flow.RegisterDB(tdb)
// if tdb == nil {
// log.Fatal("given database handle is `nil`")
// }
// db = tdb
// }
// @Title show wf list
// @Description show workflow page
// @Success 200 {object} models.GetProductsPage
// @Failure 400 Invalid page supplied
// @Failure 404 articls not found
// @router /workflow [get]
//页面
func (c *MainController) WorkFlow() {
c.TplName = "merit/workflow.tpl"
}
// @Title get wf list
// @Description get workflowlist by page
// @Success 200 {object} models.GetProductsPage
// @Failure 400 Invalid page supplied
// @Failure 404 data not found
// @router /flowtype [get]
//管理员定义流程类型doctype、流程状态state、流程节点node、
//流程动作action、流程流向transition、流程事件event
func (c *MainController) FlowType() {
// func init() {
// orm.RegisterDriver("mysql", orm.DRMySQL)//注册驱动
// orm.RegisterModel(new(Model))//注册 model
// orm.RegisterDataBase("default", "mysql", "test:123456@/test?charset=utf8",30,30)//注册默认数据库
//orm.RegisterDataBase("default", "mysql", "test:@/test?charset=utf8")//密码为空格式
// }
driver, connStr := "mysql", "travis@/flow?charset=utf8&parseTime=true"
tdb := fatal1(sql.Open(driver, connStr)).(*sql.DB)
if tdb == nil {
log.Fatal("given database handle is `nil`")
}
db := tdb
tx, _ := db.Begin()
db.Close()
//定义流程类型
dtID1, err := flow.DocTypes.New(tx, "图纸设计流程")
if err != nil {
fmt.Println(err)
}
dtID2, err := flow.DocTypes.New(tx, "合同评审流程")
if err != nil {
fmt.Println(err)
}
beego.Info(dtID2)
dtID3, err := flow.DocTypes.New(tx, "变更立项流程")
if err != nil {
fmt.Println(err)
}
beego.Info(dtID3)
//定义流程状态
dsID1, err := flow.DocStates.New(tx, "设计中...")
if err != nil {
fmt.Println(err)
}
dsID2, err := flow.DocStates.New(tx, "校核中...")
if err != nil {
fmt.Println(err)
}
dsID3, err := flow.DocStates.New(tx, "审查中...")
if err != nil {
fmt.Println(err)
}
flow.DocStates.New(tx, "批准中...")
flow.DocStates.New(tx, "申报中...")
flow.DocStates.New(tx, "评估中...")
flow.DocStates.New(tx, "审批中...")
//定义流程动作类型
daID1, err := flow.DocActions.New(tx, "设计完成后提交", false) //改变状态设计中...为校核中...
if err != nil {
fmt.Println(err)
}
daID2, err := flow.DocActions.New(tx, "校核完成后提交", false)
if err != nil {
fmt.Println(err)
}
daID3, err := flow.DocActions.New(tx, "审查完成后提交", false)
if err != nil {
fmt.Println(err)
}
daID4, err := flow.DocActions.New(tx, "核定完成后提交", true)
if err != nil {
fmt.Println(err)
}
daID5, err := flow.DocActions.New(tx, "编制完成后提交", true)
if err != nil {
fmt.Println(err)
}
daID6, err := flow.DocActions.New(tx, "审批完成后提交", false)
if err != nil {
fmt.Println(err)
}
daID7, err := flow.DocActions.New(tx, "立项完成后提交", false)
if err != nil {
fmt.Println(err)
}
//添加流程规则1:oldstate1 action1 newstate2
err = flow.DocTypes.AddTransition(tx, dtID1, dsID1, daID1, dsID2)
if err != nil {
beego.Error(err)
}
//添加流程规则2:oldstate2 action2 newstate3
err = flow.DocTypes.AddTransition(tx, dtID1, dsID2, daID2, dsID3)
if err != nil {
beego.Error(err)
}
//定义流程类型doctype下的流程类型workflow
workflowID1, err := flow.Workflows.New(tx, "图纸设计-三级校审流程", dtID1, dsID1) //初始状态是“设计中...”——校核——审查——完成
if err != nil {
fmt.Println(err)
}
beego.Info(workflowID1)
workflowID2, err := flow.Workflows.New(tx, "图纸设计-二级校审流程", dtID1, dsID1) //初始状态是“设计中...”-“校核”——完成
if err != nil {
fmt.Println(err)
}
beego.Info(workflowID2)
//定义合同评审下的流程类型:部门合同流程,总院合同流程
//略
//定义用户、组、角色、权限集合
accessContextID1, err := flow.AccessContexts.New(tx, "Context")
if err != nil {
beego.Error(err)
}
//定义流程类型workflow下的具体每个节点node,用户对文件执行某个动作(event里的action)后,会沿着这些节点走
// AddNode maps the given document state to the specified node. This
// map is consulted by the workflow when performing a state transition
// of the system.nodeID1
_, err = flow.Workflows.AddNode(tx, dtID1, dsID1, accessContextID1, workflowID1, "图纸设计-三级校审流程-设计", flow.NodeTypeBegin)
if err != nil {
fmt.Println(err)
}
_, err = flow.Workflows.AddNode(tx, dtID1, dsID2, accessContextID1, workflowID1, "图纸设计-三级校审流程-校核", flow.NodeTypeLinear)
if err != nil {
fmt.Println(err)
}
_, err = flow.Workflows.AddNode(tx, dtID1, dsID3, accessContextID1, workflowID1, "图纸设计-三级校审流程-审查", flow.NodeTypeEnd)
if err != nil {
fmt.Println(err)
}
//定义用户-组-角色-权限关系
res, err := tx.Exec(`INSERT INTO users_master(first_name, last_name, email, active)
VALUES('秦', '晓川-1', '[email protected]', 1)`)
if err != nil {
log.Fatalf("%v\n", err)
}
uid, _ := res.LastInsertId()
uID1 := flow.UserID(uid)
_, err = flow.Groups.NewSingleton(tx, uID1)
res, err = tx.Exec(`INSERT INTO users_master(first_name, last_name, email, active)
VALUES('秦', '晓川-2', '[email protected]', 1)`)
if err != nil {
log.Fatalf("%v\n", err)
}
uid, _ = res.LastInsertId()
uID2 := flow.UserID(uid)
_, err = flow.Groups.NewSingleton(tx, uID2)
res, err = tx.Exec(`INSERT INTO users_master(first_name, last_name, email, active)
VALUES('秦', '晓川-3', '[email protected]', 1)`)
if err != nil {
log.Fatalf("%v\n", err)
}
uid, _ = res.LastInsertId()
uID3 := flow.UserID(uid)
_, err = flow.Groups.NewSingleton(tx, uID3)
res, err = tx.Exec(`INSERT INTO users_master(first_name, last_name, email, active)
VALUES('秦', '晓川-4', '[email protected]', 1)`)
if err != nil {
log.Fatalf("%v\n", err)
}
uid, _ = res.LastInsertId()
uID4 := flow.UserID(uid)
_, err = flow.Groups.NewSingleton(tx, uID4)
gID1 := fatal1(flow.Groups.New(tx, "设计人员组", "G")).(flow.GroupID)
gID2 := fatal1(flow.Groups.New(tx, "校核人员组", "G")).(flow.GroupID)
fatal0(flow.Groups.AddUser(tx, gID1, uID1))
fatal0(flow.Groups.AddUser(tx, gID1, uID2))
fatal0(flow.Groups.AddUser(tx, gID1, uID3))
fatal0(flow.Groups.AddUser(tx, gID2, uID2))
fatal0(flow.Groups.AddUser(tx, gID2, uID3))
fatal0(flow.Groups.AddUser(tx, gID2, uID4))
roleID1 := fatal1(flow.Roles.New(tx, "设计人员角色")).(flow.RoleID)
roleID2 := fatal1(flow.Roles.New(tx, "校核人员角色")).(flow.RoleID)
//给角色role赋予action权限
fatal0(flow.Roles.AddPermissions(tx, roleID1, dtID1, []flow.DocActionID{daID1, daID2, daID3, daID4}))
fatal0(flow.Roles.AddPermissions(tx, roleID2, dtID1, []flow.DocActionID{daID1, daID2, daID3, daID4, daID5, daID6, daID7}))
//给用户组group赋予角色role
err = flow.AccessContexts.AddGroupRole(tx, accessContextID1, gID1, roleID1)
if err != nil {
beego.Error(err)
}
//将group和role加到accesscontext里——暂时不理解
err = flow.AccessContexts.AddGroupRole(tx, accessContextID1, gID2, roleID2)
if err != nil {
beego.Error(err) //UNIQUE constraint failed: wf_ac_group_roles.ac_id已修补
}
tx.Commit() //这个必须要!!!!!!
c.Data["json"] = "ok"
c.ServeJSON()
}
// @Title post wf state
// @Description post workflow state
// @Success 200 {object} models.GetProductsPage
// @Failure 400 Invalid page supplied
// @Failure 404 data not found
// @router /flowdocevent [get]
//对具体文件进行流程初始化,对具体文件进行定义动作事件
func (c *MainController) FlowDocEvent() {
//连接数据库
driver, connStr := "mysql", "travis@/flow?charset=utf8&parseTime=true"
tdb := fatal1(sql.Open(driver, connStr)).(*sql.DB)
if tdb == nil {
log.Fatal("given database handle is `nil`")
}
db := tdb
tx, err := db.Begin()
if err != nil {
beego.Error(err)
}
//查询预先定义的doctype流程类型
dtID1, err := flow.DocTypes.GetByName("图纸设计")
if err != nil {
beego.Error(err)
}
beego.Info(dtID1)
//查询预先定义的docstate状态1
dsID1, err := flow.DocStates.GetByName("设计中...")
if err != nil {
fmt.Println(err)
}
beego.Info(dsID1)
//查询预先定义的docstate状态2
dsID2, err := flow.DocStates.GetByName("校核中...")
if err != nil {
fmt.Println(err)
}
beego.Info(dsID2)
//查询预先定义的docstate状态3
dsID3, err := flow.DocStates.GetByName("审查中...")
if err != nil {
fmt.Println(err)
}
beego.Info(dsID3)
//查询预先定义的action动作1
daID1, err := flow.DocActions.GetByName("提交设计")
if err != nil {
fmt.Println(err)
}
beego.Info(daID1)
//查询预先定义的action动作2
daID2, err := flow.DocActions.GetByName("校核") //应该叫"提交校核"
if err != nil {
fmt.Println(err)
}
beego.Info(daID2)
//查询预先定义的action动作3
daID3, err := flow.DocActions.GetByName("审查") //应该叫"提交审查"
if err != nil {
fmt.Println(err)
}
beego.Info(daID3)
//查询预先定义的流程类型workflow,这个相当于doctype下面再分很多种流程
//比如doctype为图纸设计流程,下面可以分为二级校审流程,三级校审流程,四级校审流程
myWorkflow, err := flow.Workflows.GetByName("图纸设计-三级校审流程")
if err != nil {
beego.Error(err)
}
beego.Info(myWorkflow)
//查询context——这个应该是管理用户-组-权限的
accessContextID1, err := flow.AccessContexts.List("Context", 0, 0)
if err != nil {
beego.Error(err)
}
beego.Info(accessContextID1[0].ID)
beego.Info(flow.GroupID(1))
//开始为具体一个文件设立流程-此处是新建一个文件。对于旧文件应该怎么操作来着?
docNewInput := flow.DocumentsNewInput{
DocTypeID: dtID1.ID, //属于图纸设计类型的流程
AccessContextID: accessContextID1[0].ID, //所有用户权限符合这个contex的要求
GroupID: 11, //groupId,初始状态下的用户组,必须是个人用户组(一个用户也可以成为一个独特的组,因为用户无法赋予角色,所以必须将用户放到组里)
Title: "厂房布置图", //这个文件的名称
Data: "设计、制图: 秦晓川1, 校核: 秦晓川2", //文件的描述
}
// flow.Documents.New(tx, &docNewInput)
DocumentID1, err := flow.Documents.New(tx, &docNewInput)
if err != nil {
beego.Error(err)
}
// tx.Commit() //new后面一定要跟commit
beego.Info(DocumentID1)
beego.Info(daID2)
beego.Info(flow.GroupID(12))
//针对具体一个文件定义动作事件,从"校核中……"状态通过动作"校核"将它修改为"审查中……"
docEventInput := flow.DocEventsNewInput{
DocTypeID: dtID1.ID, //flow.DocTypeID(1),
DocumentID: DocumentID1,
DocStateID: dsID1.ID, //document state must be this state,文档的现状状态
DocActionID: daID2.ID, //flow.DocActionID(2),
GroupID: 12, //必须是个人用户组
Text: "校核",
}
docEventID1, err := flow.DocEvents.New(tx, &docEventInput)
if err != nil {
beego.Error(err)
}
tx.Commit() //一个函数里只能有一个commit,所以,这个是提前定义好的!!!!
beego.Info(docEventID1)
c.Data["json"] = "OK"
c.ServeJSON()
}
// @Title post wf state
// @Description post workflow state
// @Success 200 {object} models.GetProductsPage
// @Failure 400 Invalid page supplied
// @Failure 404 data not found
// @router /flownext [get]
//对具体文件修改状态
func (c *MainController) FlowNext() {
//连接数据库
driver, connStr := "mysql", "travis@/flow?charset=utf8&parseTime=true"
tdb := fatal1(sql.Open(driver, connStr)).(*sql.DB)
if tdb == nil {
log.Fatal("given database handle is `nil`")
}
db := tdb
tx, err := db.Begin()
if err != nil {
beego.Error(err)
}
myDocEvent, err := flow.DocEvents.Get(16)
if err != nil {
beego.Error(err)
}
beego.Info(myDocEvent)
//给出接受的组groupids
groupIds := []flow.GroupID{flow.GroupID(13)}
beego.Info(groupIds)
//查询workflow
myWorkflow, err := flow.Workflows.GetByName("图纸设计-三级校审流程")
if err != nil {
beego.Error(err)
}
beego.Info(myWorkflow)
newDocStateId, err := myWorkflow.ApplyEvent(tx, myDocEvent, groupIds)
if err != nil {
beego.Error(err)
}
tx.Commit() //一个函数里只能有一个commit!!!!
fmt.Println("newDocStateId=", newDocStateId, err)
c.Data["json"] = "OK"
c.ServeJSON()
}
// beego.Info(wflist)
// wflist, err = flow.DocStates.List(0, 0)
// if err != nil {
// beego.Error(err)
// }
// // beego.Info(wflist1)
// wflist, err = DocActions.List(0, 0)
// if err != nil {
// beego.Error(err)
// }
// wflist1, err = flow.Workflows.List(0, 0)
// if err != nil {
// beego.Error(err)
// }
// fatal1 expects a value and an error value as its arguments.
func fatal1(val1 interface{}, err error) interface{} {
if err != nil {
fmt.Println("%v", err)
}
return val1
}
// error0 expects only an error value as its argument.
func error0(err error) error {
if err != nil {
fmt.Println("%v", err)
}
return err
}
// error1 expects a value and an error value as its arguments.
func error1(val1 interface{}, err error) interface{} {
if err != nil {
fmt.Println("%v", err)
return nil
}
return val1
}
// fatal0 expects only an error value as its argument.
func fatal0(err error) {
if err != nil {
fmt.Println("%v", err)
}
}
安装mysql
建立flow数据库的用户:
CREATE USER 'travis'@'localhost' IDENTIFIED BY '';
在Navicat里新建一个flow数据库,选择utf8mb4——它源码里有个地方交代了。
开始数据库里的表下面是没有内容的,与下图不同。
然后给用户travis赋权。
必须将setup_db.sh文件拷贝到sql文件夹上一级,否则会出现下列错误:
将sql文件里的setup_db.sh和setup_blob_dirs.sh拷贝到上一级目录里(flow文件夹里),然后cmd窗口进入这个文件夹,运行
setup_db.sh -t
setup_blob_dirs.sh
go test
Flow里的sql语句是mysql的,和sqlite差别很大。从数据表初始化的sh文件开始就不同,一直到flow的go文件里的数据表插入数据,查询数据,jion连接表格,unique多列集合的约束,枚举数据类型,生成view数据表视图等等,太多的不同了。
在sqlite里,新建表用sh文件,也可以用改造好的beego自动建表,
package models
import (
"fmt"
"github.com/astaxie/beego/orm"
"time"
)
// CREATE TABLE users_master (
// id INT NOT NULL AUTO_INCREMENT,
// first_name VARCHAR(30) NOT NULL,
// last_name VARCHAR(30) NOT NULL,
// email VARCHAR(100) NOT NULL,
// active TINYINT(1) NOT NULL,
// PRIMARY KEY (id),
// UNIQUE (email)
// );
type users_master struct {
Id int64
First_name string `orm:"size(30)"`
Last_name string `orm:"size(30)"`
Email string `orm:"unique;size(100)"`
Active bool
}
// CREATE TABLE wf_ac_group_hierarchy (
// id INT NOT NULL AUTO_INCREMENT,
// ac_id INT NOT NULL,
// group_id INT NOT NULL,
// reports_to INT NOT NULL,
// PRIMARY KEY (id),
// FOREIGN KEY (ac_id) REFERENCES wf_access_contexts(id),
// FOREIGN KEY (group_id) REFERENCES wf_groups_master(id),
// FOREIGN KEY (reports_to) REFERENCES wf_groups_master(id),
// UNIQUE (ac_id, group_id)
// );
type wf_ac_group_hierarchy struct {
Id int64
Ac *wf_access_contexts `orm:"rel(fk);unique"`
Group *wf_groups_master `orm:"rel(fk);unique"`
Reports_to *wf_groups_master `orm:"rel(fk);column(reports_to)"`
}
// CREATE TABLE wf_ac_group_roles (
// id INT NOT NULL AUTO_INCREMENT,
// ac_id INT NOT NULL,
// group_id INT NOT NULL,
// role_id INT NOT NULL,
// PRIMARY KEY (id),
// FOREIGN KEY (ac_id) REFERENCES wf_access_contexts(id),
// FOREIGN KEY (group_id) REFERENCES wf_groups_master(id),
// FOREIGN KEY (role_id) REFERENCES wf_roles_master(id)
// );
type wf_ac_group_roles struct {
Id int64
Ac *wf_access_contexts `orm:"rel(fk)"`
Group *wf_groups_master `orm:"rel(fk)"`
Role *wf_roles_master `orm:"rel(fk)"`
}
……
……
// CREATE TABLE wf_workflows (
// id INT NOT NULL AUTO_INCREMENT,
// name VARCHAR(100) NOT NULL,
// doctype_id INT NOT NULL,
// docstate_id INT NOT NULL,
// active TINYINT(1) NOT NULL,
// PRIMARY KEY (id),
// FOREIGN KEY (doctype_id) REFERENCES wf_doctypes_master(id),
// FOREIGN KEY (docstate_id) REFERENCES wf_docstates_master(id),
// UNIQUE (name),
// UNIQUE (doctype_id)
// );
type wf_workflows struct {
Id int64
Name string `orm:"size(100);unique"`
Doctype *wf_doctypes_master `orm:"rel(fk);unique"`
Docstate *wf_docstates_master `orm:"rel(fk)"`
Active bool
}
func init() {
orm.RegisterModel(new(users_master), new(wf_ac_group_hierarchy), new(wf_ac_group_roles))
orm.RegisterModel(new(wf_access_contexts), new(wf_docactions_master), new(wf_docevent_application))
orm.RegisterModel(new(wf_docevents), new(wf_docstate_transitions), new(wf_docstates_master))
orm.RegisterModel(new(wf_doctypes_master), new(wf_document_blobs), new(wf_document_tags))
orm.RegisterModel(new(wf_group_users), new(wf_groups_master), new(wf_mailboxes))
orm.RegisterModel(new(wf_messages), new(wf_role_docactions), new(wf_roles_master))
orm.RegisterModel(new(wf_workflow_nodes), new(wf_workflows))
}
func InitFlow() {
sql := fmt.Sprintf("CREATE VIEW wf_ac_perms_v AS " +
"SELECT wf_ac_group_roles.ac_id, wf_ac_group_roles.group_id, wf_group_users.user_id, wf_ac_group_roles.role_id, wf_role_docactions.doctype_id, wf_role_docactions.docaction_id " +
"FROM wf_ac_group_roles " +
"JOIN wf_group_users ON wf_ac_group_roles.group_id = wf_group_users.group_id " +
"JOIN wf_role_docactions ON wf_ac_group_roles.role_id = wf_role_docactions.role_id;")
sql2 := fmt.Sprintf("INSERT INTO wf_docstates_master(name) VALUES('__RESERVED_CHILD_STATE__');")
sql3 := fmt.Sprintf("INSERT INTO wf_roles_master(name) VALUES('SUPER_ADMIN');")
sql4 := fmt.Sprintf("INSERT INTO wf_roles_master(name) VALUES('ADMIN');")
sql5 := fmt.Sprintf("CREATE VIEW wf_users_master AS SELECT id, first_name, last_name, email, active FROM users_master;")
o := orm.NewOrm()
res, err := o.Raw(sql).Exec()
if err == nil {
num, _ := res.RowsAffected()
fmt.Println("mysql row affected nums: ", num)
} else {
o.Rollback() // beego.Info("插入t_studentInfo表出错,事务回滚")
}
res, err = o.Raw(sql2).Exec()
if err == nil {
num, _ := res.RowsAffected()
fmt.Println("mysql row affected nums: ", num)
} else {
o.Rollback() // beego.Info("插入t_studentInfo表出错,事务回滚")
}
res, err = o.Raw(sql3).Exec()
if err == nil {
num, _ := res.RowsAffected()
fmt.Println("mysql row affected nums: ", num)
} else {
o.Rollback() // beego.Info("插入t_studentInfo表出错,事务回滚")
}
res, err = o.Raw(sql4).Exec()
if err == nil {
num, _ := res.RowsAffected()
fmt.Println("mysql row affected nums: ", num)
} else {
o.Rollback() // beego.Info("插入t_studentInfo表出错,事务回滚")
}
res, err = o.Raw(sql5).Exec()
if err == nil {
num, _ := res.RowsAffected()
fmt.Println("mysql row affected nums: ", num)
} else {
o.Rollback() // beego.Info("插入t_studentInfo表出错,事务回滚")
}
}
解决golang:unsupported Scan, storing driver.Value type []uint8 into type *time.Time
https://blog.csdn.net/han0373/article/details/81698713
在open连接后拼接参数:parseTime=true 即可