Browse Source

工作流

xuwenhao 7 months ago
parent
commit
69e2c65c75

+ 227 - 0
server/dao/ACT_HI_PROCINST.go

@@ -0,0 +1,227 @@
+package dao
+
+import (
+	"gorm.io/gorm"
+	"server/dao/flow"
+	"server/global"
+	"strings"
+	"sync"
+)
+
+// ProcInst 流程实例
+type ProcInst struct {
+	global.GVA_MODEL
+	// 流程定义ID
+	ProcDefID int `json:"procDefId"`
+	// 流程定义名
+	ProcDefName string `json:"procDefName"`
+	// title 标题
+	Title string `json:"title"`
+	// 用户部门
+	Department string `json:"department"`
+	Company    string `json:"company"`
+	// 当前节点
+	NodeID string `json:"nodeID"`
+	// 审批人
+	Candidate string `json:"candidate"`
+	// 当前任务
+	TaskID        int    `json:"taskID"`
+	StartTime     string `json:"startTime"`
+	EndTime       string `json:"endTime"`
+	Duration      int64  `json:"duration"`
+	StartUserID   string `json:"startUserId"`
+	StartUserName string `json:"startUserName"`
+	IsFinished    bool   `gorm:"default:false" json:"isFinished"`
+}
+
+// GroupsNotNull 候选组
+func GroupsNotNull(groups []string, sql string) func(db *gorm.DB) *gorm.DB {
+	if len(groups) > 0 {
+		return func(db *gorm.DB) *gorm.DB {
+			return db.Or("candidate in (?) and "+sql, groups)
+		}
+	}
+	return func(db *gorm.DB) *gorm.DB {
+		return db
+	}
+}
+
+// DepartmentsNotNull 分管部门
+func DepartmentsNotNull(departments []string, sql string) func(db *gorm.DB) *gorm.DB {
+	if len(departments) > 0 {
+		return func(db *gorm.DB) *gorm.DB {
+			return db.Or("department in (?) and candidate=? and "+sql, departments, IdentityTypes[flow.MANAGER])
+		}
+	}
+	return func(db *gorm.DB) *gorm.DB {
+		return db
+	}
+}
+
+// StartByMyself 我发起的流程
+func StartByMyself(userID, company string, pageIndex, pageSize int) ([]*ProcInst, int64, error) {
+	maps := map[string]interface{}{
+		"start_user_id": userID,
+		"company":       company,
+	}
+	return findProcInsts(maps, pageIndex, pageSize)
+}
+
+// FindProcInstByID FindProcInstByID
+func FindProcInstByID(id int) (*ProcInst, error) {
+	var data = ProcInst{}
+	err := global.GVA_DB.Where("id=?", id).Find(&data).Error
+	if err != nil {
+		return nil, err
+	}
+	return &data, nil
+}
+
+// FindProcNotify 查询抄送我的流程
+func FindProcNotify(userID, company string, groups []string, pageIndex, pageSize int) ([]*ProcInst, int64, error) {
+	var datas []*ProcInst
+	var count int64
+	var sql string
+	if len(groups) != 0 {
+		var s []string
+		for _, val := range groups {
+			s = append(s, "\""+val+"\"")
+		}
+		sql = "select proc_inst_id from identitylink i where i.type='notifier' and i.company='" + company + "' and (i.user_id='" + userID + "' or i.group in (" + strings.Join(s, ",") + "))"
+	} else {
+		sql = "select proc_inst_id from identitylink i where i.type='notifier' and i.company='" + company + "' and i.user_id='" + userID + "'"
+	}
+	err := global.GVA_DB.Where("id in (" + sql + ")").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Order("start_time desc").Find(&datas).Error
+	if err != nil {
+		return datas, count, err
+	}
+	err = global.GVA_DB.Model(&ProcInst{}).Where("id in (" + sql + ")").Count(&count).Error
+	if err != nil {
+		return nil, count, err
+	}
+	return datas, count, err
+}
+func findProcInsts(maps map[string]interface{}, pageIndex, pageSize int) ([]*ProcInst, int64, error) {
+	var datas []*ProcInst
+	var count int64
+	selectDatas := func(in chan<- error, wg *sync.WaitGroup) {
+		go func() {
+			err := global.GVA_DB.Where(maps).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Order("start_time desc").Find(&datas).Error
+			in <- err
+			wg.Done()
+		}()
+	}
+	selectCount := func(in chan<- error, wg *sync.WaitGroup) {
+		err := global.GVA_DB.Model(&ProcInst{}).Where(maps).Count(&count).Error
+		in <- err
+		wg.Done()
+	}
+	var err1 error
+	var wg sync.WaitGroup
+	numberOfRoutine := 2
+	wg.Add(numberOfRoutine)
+	errStream := make(chan error, numberOfRoutine)
+	// defer fmt.Println("close channel")
+	selectDatas(errStream, &wg)
+	selectCount(errStream, &wg)
+	wg.Wait()
+	defer close(errStream) // 关闭通道
+	for i := 0; i < numberOfRoutine; i++ {
+		// log.Printf("send: %v", <-errStream)
+		if err := <-errStream; err != nil {
+			err1 = err
+		}
+	}
+	// fmt.Println("结束")
+	return datas, count, err1
+}
+
+// FindProcInsts FindProcInsts
+// 分页查询
+func FindProcInsts(userID, procName, company string, groups, departments []string, pageIndex, pageSize int) ([]*ProcInst, int64, error) {
+	var datas []*ProcInst
+	var count int64
+	var sql = " company='" + company + "' and is_finished=0 "
+	if len(procName) > 0 {
+		sql += "and proc_def_name='" + procName + "'"
+	}
+	// fmt.Println(sql)
+	selectDatas := func(in chan<- error, wg *sync.WaitGroup) {
+		go func() {
+			err := global.GVA_DB.Scopes(GroupsNotNull(groups, sql), DepartmentsNotNull(departments, sql)).
+				Or("candidate=? and "+sql, userID).
+				Offset((pageIndex - 1) * pageSize).Limit(pageSize).
+				Order("start_time desc").
+				Find(&datas).Error
+			in <- err
+			wg.Done()
+		}()
+	}
+	selectCount := func(in chan<- error, wg *sync.WaitGroup) {
+		go func() {
+			err := global.GVA_DB.Scopes(GroupsNotNull(groups, sql), DepartmentsNotNull(departments, sql)).Model(&ProcInst{}).Or("candidate=? and "+sql, userID).Count(&count).Error
+			in <- err
+			wg.Done()
+		}()
+	}
+	var err1 error
+	var wg sync.WaitGroup
+	numberOfRoutine := 2
+	wg.Add(numberOfRoutine)
+	errStream := make(chan error, numberOfRoutine)
+	// defer fmt.Println("close channel")
+	selectDatas(errStream, &wg)
+	selectCount(errStream, &wg)
+	wg.Wait()
+	defer close(errStream) // 关闭通道
+
+	for i := 0; i < numberOfRoutine; i++ {
+		// log.Printf("send: %v", <-errStream)
+		if err := <-errStream; err != nil {
+			err1 = err
+		}
+	}
+	// fmt.Println("结束")
+	return datas, count, err1
+}
+
+// Save save
+func (p *ProcInst) Save() (int, error) {
+	err := global.GVA_DB.Create(p).Error
+	if err != nil {
+		return 0, err
+	}
+	return int(p.ID), nil
+}
+
+// SaveTx SaveTx
+func (p *ProcInst) SaveTx(tx *gorm.DB) (int, error) {
+	if err := tx.Create(p).Error; err != nil {
+		tx.Rollback()
+		return 0, err
+	}
+	return int(p.ID), nil
+}
+
+// DelProcInstByID DelProcInstByID
+func DelProcInstByID(id int) error {
+	return global.GVA_DB.Where("id=?", id).Delete(&ProcInst{}).Error
+}
+
+// DelProcInstByIDTx DelProcInstByIDTx
+// 事务
+func DelProcInstByIDTx(id int, tx *gorm.DB) error {
+	return tx.Where("id=?", id).Delete(&ProcInst{}).Error
+}
+
+// UpdateTx UpdateTx
+func (p *ProcInst) UpdateTx(tx *gorm.DB) error {
+	return tx.Model(&ProcInst{}).Updates(p).Error
+}
+
+// FindFinishedProc FindFinishedProc
+func FindFinishedProc() ([]*ProcInst, error) {
+	var datas []*ProcInst
+	err := global.GVA_DB.Where("is_finished=1").Find(&datas).Error
+	return datas, err
+}

+ 93 - 0
server/dao/ACT_RE_PROCDEF.go

@@ -0,0 +1,93 @@
+package dao
+
+import (
+	"gorm.io/gorm"
+	"server/global"
+)
+
+// Procdef 流程定义表
+type Procdef struct {
+	global.GVA_MODEL
+	Name    string `json:"name,omitempty"`
+	Version int    `json:"version,omitempty"`
+	// 流程定义json字符串
+	Resource string `gorm:"size:10000" json:"resource,omitempty"`
+	// 用户id
+	Userid   string `json:"userid,omitempty"`
+	Username string `json:"username,omitempty"`
+	// 用户所在公司
+	Company    string `json:"company,omitempty"`
+	DeployTime string `json:"deployTime,omitempty"`
+}
+
+// Save save and return id
+// 保存并返回ID
+func (p *Procdef) Save() (ID int, err error) {
+	err = global.GVA_DB.Create(p).Error
+	if err != nil {
+		return 0, err
+	}
+	return int(p.ID), nil
+}
+
+// SaveTx SaveTx
+func (p *Procdef) SaveTx(tx *gorm.DB) error {
+	err := tx.Create(p).Error
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// GetProcdefLatestByNameAndCompany :get latest procdef by name and company
+// 根据名字和公司查询最新的流程定义
+func GetProcdefLatestByNameAndCompany(name, company string) (*Procdef, error) {
+	var p []*Procdef
+	err := global.GVA_DB.Where("name=? and company=?", name, company).Order("version desc").Find(&p).Error
+	if err != nil || len(p) == 0 {
+		return nil, err
+	}
+	return p[0], err
+}
+
+// GetProcdefByID 根据流程定义
+func GetProcdefByID(id int) (*Procdef, error) {
+	var p = &Procdef{}
+	err := global.GVA_DB.Where("id=?", id).Find(p).Error
+	return p, err
+}
+
+// DelProcdefByID del by id
+// 根据id删除
+func DelProcdefByID(id int) error {
+	err := global.GVA_DB.Where("id = ?", id).Delete(&Procdef{}).Error
+	return err
+}
+
+// DelProcdefByIDTx DelProcdefByIDTx
+func DelProcdefByIDTx(id int, tx *gorm.DB) error {
+	return tx.Where("id = ?", id).Delete(&Procdef{}).Error
+}
+
+// FindProcdefsWithCountAndPaged return result with total count and error
+// 返回查询结果和总条数
+func FindProcdefsWithCountAndPaged(pageIndex, pageSize int, maps map[string]interface{}) (datas []*Procdef, count int64, err error) {
+	err = global.GVA_DB.Select("id,name,version,userid,deploy_time").Where(maps).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&datas).Error
+	if err != nil && err != gorm.ErrRecordNotFound {
+		return nil, 0, err
+	}
+	err = global.GVA_DB.Model(&Procdef{}).Where(maps).Count(&count).Error
+	if err != nil {
+		return nil, 0, err
+	}
+	return datas, count, nil
+}
+
+// MoveProcdefToHistoryByIDTx 将流程定义移至历史纪录表
+func MoveProcdefToHistoryByIDTx(ID int, tx *gorm.DB) error {
+	err := tx.Exec("insert into procdef_history select * from procdef where id=?", ID).Error
+	if err != nil {
+		return err
+	}
+	return DelProcdefByIDTx(ID, tx)
+}

+ 87 - 0
server/dao/ACT_RU_EXECUTION.go

@@ -0,0 +1,87 @@
+package dao
+
+import (
+	"gorm.io/gorm"
+	"server/global"
+	"time"
+
+	"github.com/mumushuiding/util"
+)
+
+// Execution 流程实例(执行流)表
+// ProcInstID 流程实例ID
+// BusinessKey 启动业务时指定的业务主键
+// ProcDefID 流程定义数据的ID
+type Execution struct {
+	global.GVA_MODEL
+	Rev         int    `json:"rev"`
+	ProcInstID  int    `json:"procInstID"`
+	ProcDefID   int    `json:"procDefID"`
+	ProcDefName string `json:"procDefName"`
+	// NodeInfos 执行流经过的所有节点
+	NodeInfos string `gorm:"size:4000" json:"nodeInfos"`
+	IsActive  int8   `json:"isActive"`
+	StartTime string `json:"startTime"`
+}
+
+// Save save
+func (p *Execution) Save() (ID int, err error) {
+	err = global.GVA_DB.Create(p).Error
+	if err != nil {
+		return 0, err
+	}
+	return int(p.ID), nil
+}
+
+// SaveTx SaveTx
+// 接收外部事务
+func (p *Execution) SaveTx(tx *gorm.DB) (ID int, err error) {
+	p.StartTime = util.FormatDate(time.Now(), util.YYYY_MM_DD_HH_MM_SS)
+	if err := tx.Create(p).Error; err != nil {
+		return 0, err
+	}
+	return int(p.ID), nil
+}
+
+// GetExecByProcInst GetExecByProcInst
+// 根据流程实例id查询执行流
+func GetExecByProcInst(procInstID int) (*Execution, error) {
+	var p = &Execution{}
+	err := global.GVA_DB.Where("proc_inst_id=?", procInstID).Find(p).Error
+	// log.Printf("procdef:%v,err:%v", p, err)
+	if err == gorm.ErrRecordNotFound {
+		return nil, nil
+	}
+	if err != nil || p == nil {
+		return nil, err
+	}
+	return p, nil
+}
+
+// GetExecNodeInfosByProcInstID GetExecNodeInfosByProcInstID
+// 根据流程实例procInstID查询执行流经过的所有节点信息
+func GetExecNodeInfosByProcInstID(procInstID int) (string, error) {
+	var e = &Execution{}
+	err := global.GVA_DB.Select("node_infos").Where("proc_inst_id=?", procInstID).Find(e).Error
+	// fmt.Println(e)
+	if err != nil {
+		return "", err
+	}
+	return e.NodeInfos, nil
+}
+
+// ExistsExecByProcInst ExistsExecByProcInst
+// 指定流程实例的执行流是否已经存在
+func ExistsExecByProcInst(procInst int) (bool, error) {
+	e, err := GetExecByProcInst(procInst)
+	// var p = &Execution{}
+	// err := db.Where("proc_inst_id=?", procInst).Find(p).RecordNotFound
+	// log.Printf("errnotfound:%v", err)
+	if e != nil {
+		return true, nil
+	}
+	if err != nil {
+		return false, err
+	}
+	return false, nil
+}

+ 92 - 0
server/dao/ACT_RU_IDENTITYLINK.go

@@ -0,0 +1,92 @@
+package dao
+
+import (
+	"gorm.io/gorm"
+	"server/global"
+)
+
+// Identitylink 用户组同任务的关系
+type Identitylink struct {
+	global.GVA_MODEL
+	Group      string `json:"group,omitempty"`
+	Type       string `json:"type,omitempty"`
+	UserID     string `json:"userid,omitempty"`
+	UserName   string `json:"username,omitempty"`
+	TaskID     int    `json:"taskID,omitempty"`
+	Step       int    `json:"step"`
+	ProcInstID int    `json:"procInstID,omitempty"`
+	Company    string `json:"company,omitempty"`
+	Comment    string `json:"comment,omitempty"`
+}
+
+// IdentityType 类型
+type IdentityType int
+
+const (
+	// CANDIDATE 候选
+	CANDIDATE IdentityType = iota
+	// PARTICIPANT 参与人
+	PARTICIPANT
+	// MANAGER 上级领导
+	MANAGER
+	// NOTIFIER 抄送人
+	NOTIFIER
+)
+
+// IdentityTypes IdentityTypes
+var IdentityTypes = [...]string{CANDIDATE: "candidate", PARTICIPANT: "participant", MANAGER: "主管", NOTIFIER: "notifier"}
+
+// SaveTx SaveTx
+func (i *Identitylink) SaveTx(tx *gorm.DB) error {
+	// if len(i.Company) == 0 {
+	// 	return errors.New("Identitylink表的company字段不能为空!!")
+	// }
+	err := tx.Create(i).Error
+	return err
+}
+
+// DelCandidateByProcInstID DelCandidateByProcInstID
+// 删除历史候选人
+func DelCandidateByProcInstID(procInstID int, tx *gorm.DB) error {
+	return tx.Where("proc_inst_id=? and type=?", procInstID, IdentityTypes[CANDIDATE]).Delete(&Identitylink{}).Error
+}
+
+// ExistsNotifierByProcInstIDAndGroup 抄送人是否已经存在
+func ExistsNotifierByProcInstIDAndGroup(procInstID int, group string) (bool, error) {
+	var count int64
+	err := global.GVA_DB.Model(&Identitylink{}).Where("identitylink.proc_inst_id=? and identitylink.group=? and identitylink.type=?", procInstID, group, IdentityTypes[NOTIFIER]).Count(&count).Error
+	if err != nil {
+		if err == gorm.ErrRecordNotFound {
+			return false, nil
+		}
+		return false, err
+	}
+	if count > 0 {
+		return true, nil
+	}
+	return false, nil
+}
+
+// IfParticipantByTaskID IfParticipantByTaskID
+// 针对指定任务判断用户是否已经审批过了
+func IfParticipantByTaskID(userID, company string, taskID int) (bool, error) {
+	var count int64
+	err := global.GVA_DB.Model(&Identitylink{}).Where("user_id=? and company=? and task_id=?", userID, company, taskID).Count(&count).Error
+	if err != nil {
+		if err == gorm.ErrRecordNotFound {
+			return false, nil
+		}
+		return false, err
+	}
+	if count > 0 {
+		return true, nil
+	}
+	return false, nil
+}
+
+// FindParticipantByProcInstID 查询参与审批的人
+func FindParticipantByProcInstID(procInstID int) ([]*Identitylink, error) {
+	var datas []*Identitylink
+	err := global.GVA_DB.Select("id,user_id,user_name,step,comment").Where("proc_inst_id=? and type=?", procInstID, IdentityTypes[PARTICIPANT]).Order("id asc").Find(&datas).Error
+	return datas, err
+}

+ 85 - 0
server/dao/ACT_RU_TASK.go

@@ -0,0 +1,85 @@
+package dao
+
+import (
+	"github.com/jinzhu/gorm"
+	"server/global"
+)
+
+// import _ "github.com/jinzhu/gorm"
+
+// Task 流程任务表
+// ExecutionID 执行流ID
+// Name 任务名称,在流程文件中定义
+// TaskDefKey 任务定义的ID值
+// Assignee 被指派执行该任务的人
+// Owner 任务拥有人
+type Task struct {
+	global.GVA_MODEL
+	// Company 任务创建人对应的公司
+	// Company string `json:"company"`
+	// ExecutionID     string `json:"executionID"`
+	// 当前执行流所在的节点
+	NodeID string `json:"nodeId"`
+	Step   int    `json:"step"`
+	// 流程实例id
+	ProcInstID int    `json:"procInstID"`
+	Assignee   string `json:"assignee"`
+	CreateTime string `json:"createTime"`
+	ClaimTime  string `json:"claimTime"`
+	// 还未审批的用户数,等于0代表会签已经全部审批结束,默认值为1
+	MemberCount   int8 `json:"memberCount" gorm:"default:1"`
+	UnCompleteNum int8 `json:"unCompleteNum" gorm:"default:1"`
+	//审批通过数
+	AgreeNum int8 `json:"agreeNum"`
+	// and 为会签,or为或签,默认为or
+	ActType    string `json:"actType" gorm:"default:'or'"`
+	IsFinished bool   `gorm:"default:false" json:"isFinished"`
+}
+
+// NewTask 新建任务
+func (t *Task) NewTask() (int, error) {
+	err := global.GVA_DB.Create(t).Error
+	if err != nil {
+		return 0, err
+	}
+	return int(t.ID), nil
+}
+
+// UpdateTx UpdateTx
+func (t *Task) UpdateTx(tx *gorm.DB) error {
+	err := tx.Model(&Task{}).Updates(t).Error
+	return err
+}
+
+// GetTaskByID GetTaskById
+func GetTaskByID(id int) (*Task, error) {
+	var t = &Task{}
+	err := global.GVA_DB.Where("id=?", id).Find(t).Error
+	return t, err
+}
+
+// GetTaskLastByProInstID GetTaskLastByProInstID
+// 根据流程实例id获取上一个任务
+func GetTaskLastByProInstID(procInstID int) (*Task, error) {
+	var t = &Task{}
+	err := global.GVA_DB.Where("proc_inst_id=? and is_finished=1", procInstID).Order("claim_time desc").First(t).Error
+	return t, err
+}
+
+// NewTaskTx begin tx
+// 开启事务
+func (t *Task) NewTaskTx(tx *gorm.DB) (int, error) {
+	// str, _ := util.ToJSONStr(t)
+	// fmt.Printf("newTask:%s", str)
+	err := tx.Create(t).Error
+	if err != nil {
+		return 0, err
+	}
+	return int(t.ID), nil
+}
+
+// DeleteTask 删除任务
+func DeleteTask(id int) error {
+	err := global.GVA_DB.Where("id=?", id).Delete(&Task{}).Error
+	return err
+}

+ 16 - 0
server/dao/ExecutionHistory.go

@@ -0,0 +1,16 @@
+package dao
+
+import (
+	"gorm.io/gorm"
+)
+
+// ExecutionHistory ExecutionHistory
+// 执行流历史纪录
+type ExecutionHistory struct {
+	Execution
+}
+
+// CopyExecutionToHistoryByProcInstIDTx CopyExecutionToHistoryByProcInstIDTx
+func CopyExecutionToHistoryByProcInstIDTx(procInstID int, tx *gorm.DB) error {
+	return tx.Exec("insert into execution_history select * from execution where proc_inst_id=?", procInstID).Error
+}

+ 23 - 0
server/dao/IdentitylinkHistory.go

@@ -0,0 +1,23 @@
+package dao
+
+import (
+	"gorm.io/gorm"
+	"server/global"
+)
+
+// IdentitylinkHistory IdentitylinkHistory
+type IdentitylinkHistory struct {
+	Identitylink
+}
+
+// CopyIdentitylinkToHistoryByProcInstID CopyIdentitylinkToHistoryByProcInstID
+func CopyIdentitylinkToHistoryByProcInstID(procInstID int, tx *gorm.DB) error {
+	return tx.Exec("insert into identitylink_history select * from identitylink where proc_inst_id=?", procInstID).Error
+}
+
+// FindParticipantHistoryByProcInstID FindParticipantHistoryByProcInstID
+func FindParticipantHistoryByProcInstID(procInstID int) ([]*IdentitylinkHistory, error) {
+	var datas []*IdentitylinkHistory
+	err := global.GVA_DB.Select("id,user_id,step,comment").Where("proc_inst_id=? and type=?", procInstID, IdentityTypes[PARTICIPANT]).Order("id asc").Find(&datas).Error
+	return datas, err
+}

+ 137 - 0
server/dao/ProcInstHistory.go

@@ -0,0 +1,137 @@
+package dao
+
+import (
+	"server/global"
+	"strings"
+	"sync"
+
+	"github.com/jinzhu/gorm"
+)
+
+// ProcInstHistory ProcInstHistory
+type ProcInstHistory struct {
+	ProcInst
+}
+
+// StartHistoryByMyself 查询我发起的流程
+func StartHistoryByMyself(userID, company string, pageIndex, pageSize int) ([]*ProcInstHistory, int64, error) {
+	maps := map[string]interface{}{
+		"start_user_id": userID,
+		"company":       company,
+	}
+	return findProcInstsHistory(maps, pageIndex, pageSize)
+}
+func findProcInstsHistory(maps map[string]interface{}, pageIndex, pageSize int) ([]*ProcInstHistory, int64, error) {
+	var datas []*ProcInstHistory
+	var count int64
+	selectDatas := func(in chan<- error, wg *sync.WaitGroup) {
+		go func() {
+			err := global.GVA_DB.Where(maps).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Order("start_time desc").Find(&datas).Error
+			in <- err
+			wg.Done()
+		}()
+	}
+	selectCount := func(in chan<- error, wg *sync.WaitGroup) {
+		err := global.GVA_DB.Model(&ProcInstHistory{}).Where(maps).Count(&count).Error
+		in <- err
+		wg.Done()
+	}
+	var err1 error
+	var wg sync.WaitGroup
+	numberOfRoutine := 2
+	wg.Add(numberOfRoutine)
+	errStream := make(chan error, numberOfRoutine)
+	// defer fmt.Println("close channel")
+	selectDatas(errStream, &wg)
+	selectCount(errStream, &wg)
+	wg.Wait()
+	defer close(errStream) // 关闭通道
+	for i := 0; i < numberOfRoutine; i++ {
+		// log.Printf("send: %v", <-errStream)
+		if err := <-errStream; err != nil {
+			err1 = err
+		}
+	}
+	// fmt.Println("结束")
+	return datas, count, err1
+}
+
+// FindProcHistory 查询历史纪录
+func FindProcHistory(userID, company string, pageIndex, pageSize int) ([]*ProcInstHistory, int64, error) {
+	var datas []*ProcInstHistory
+	var count int64
+	var err1 error
+	var wg sync.WaitGroup
+	numberOfRoutine := 2
+	errStream := make(chan error, numberOfRoutine)
+	selectDatas := func(wg *sync.WaitGroup) {
+		go func() {
+			err := global.GVA_DB.Where("id in (select distinct proc_inst_id from identitylink_history where company=? and user_id=?)", company, userID).
+				Offset((pageIndex - 1) * pageSize).Limit(pageSize).
+				Order("start_time desc").Find(&datas).Error
+			errStream <- err
+			wg.Done()
+		}()
+	}
+	selectCount := func(wg *sync.WaitGroup) {
+		go func() {
+			err := global.GVA_DB.Model(&ProcInstHistory{}).
+				Where("id in (select distinct proc_inst_id from identitylink_history where company=? and user_id=?)", company, userID).
+				Count(&count).Error
+			errStream <- err
+			wg.Done()
+		}()
+	}
+	wg.Add(numberOfRoutine)
+	selectDatas(&wg)
+	selectCount(&wg)
+	wg.Wait()
+	close(errStream)
+
+	for i := 0; i < numberOfRoutine; i++ {
+		if err := <-errStream; err != nil {
+			err1 = err
+		}
+	}
+	return datas, count, err1
+}
+
+// SaveProcInstHistory SaveProcInstHistory
+func SaveProcInstHistory(p *ProcInst) error {
+	return global.GVA_DB.Table("proc_inst_history").Create(p).Error
+}
+
+// DelProcInstHistoryByID DelProcInstHistoryByID
+func DelProcInstHistoryByID(id int) error {
+	return global.GVA_DB.Where("id=?", id).Delete(&ProcInstHistory{}).Error
+}
+
+// SaveProcInstHistoryTx SaveProcInstHistoryTx
+func SaveProcInstHistoryTx(p *ProcInst, tx *gorm.DB) error {
+	return tx.Table("proc_inst_history").Create(p).Error
+}
+
+// FindProcHistoryNotify 查询抄送我的历史纪录
+func FindProcHistoryNotify(userID, company string, groups []string, pageIndex, pageSize int) ([]*ProcInstHistory, int64, error) {
+	var datas []*ProcInstHistory
+	var count int64
+	var sql string
+	if len(groups) != 0 {
+		var s []string
+		for _, val := range groups {
+			s = append(s, "\""+val+"\"")
+		}
+		sql = "select proc_inst_id from identitylink_history i where i.type='notifier' and i.company='" + company + "' and (i.user_id='" + userID + "' or i.group in (" + strings.Join(s, ",") + "))"
+	} else {
+		sql = "select proc_inst_id from identitylink_history i where i.type='notifier' and i.company='" + company + "' and i.user_id='" + userID + "'"
+	}
+	err := global.GVA_DB.Where("id in (" + sql + ")").Offset((pageIndex - 1) * pageSize).Limit(pageSize).Order("start_time desc").Find(&datas).Error
+	if err != nil {
+		return datas, count, err
+	}
+	err = global.GVA_DB.Model(&ProcInstHistory{}).Where("id in (" + sql + ")").Count(&count).Error
+	if err != nil {
+		return nil, count, err
+	}
+	return datas, count, err
+}

+ 17 - 0
server/dao/ProcdefHistory.go

@@ -0,0 +1,17 @@
+package dao
+
+import "server/global"
+
+// ProcdefHistory 历史流程定义
+type ProcdefHistory struct {
+	Procdef
+}
+
+// Save Save
+func (p *ProcdefHistory) Save() (ID int, err error) {
+	err = global.GVA_DB.Create(p).Error
+	if err != nil {
+		return 0, err
+	}
+	return int(p.ID), nil
+}

+ 15 - 0
server/dao/TaskHistory.go

@@ -0,0 +1,15 @@
+package dao
+
+import (
+	"gorm.io/gorm"
+)
+
+// TaskHistory TaskHistory
+type TaskHistory struct {
+	Task
+}
+
+// CopyTaskToHistoryByProInstID 根据procInstID查询结果,并将结果复制到task_history表
+func CopyTaskToHistoryByProInstID(procInstID int, tx *gorm.DB) error {
+	return tx.Exec("insert into task_history select * from task where proc_inst_id=?", procInstID).Error
+}

+ 470 - 0
server/dao/flow/node.go

@@ -0,0 +1,470 @@
+package flow
+
+import (
+	"container/list"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"log"
+	"os"
+	"strconv"
+	"time"
+
+	"github.com/mumushuiding/util"
+)
+
+// Node represents a specific logical unit of processing and routing
+// in a workflow.
+// 流程中的一个节点
+type Node struct {
+	Name           string          `json:"name,omitempty"`
+	Type           string          `json:"type,omitempty"`
+	NodeID         string          `json:"nodeId,omitempty"`
+	PrevID         string          `json:"prevId,omitempty"`
+	ChildNode      *Node           `json:"childNode,omitempty"`
+	ConditionNodes []*Node         `json:"conditionNodes,omitempty"`
+	Properties     *NodeProperties `json:"properties,omitempty"`
+}
+
+// ActionConditionType 条件类型
+type ActionConditionType int
+
+const (
+	// RANGE 条件类型: 范围
+	RANGE ActionConditionType = iota
+	// VALUE 条件类型: 值
+	VALUE
+)
+
+// ActionConditionTypes 所有条件类型
+var ActionConditionTypes = [...]string{RANGE: "dingtalk_actioner_range_condition", VALUE: "dingtalk_actioner_value_condition"}
+
+// NodeType 节点类型
+type NodeType int
+
+const (
+	// START 类型start
+	START NodeType = iota
+	ROUTE
+	CONDITION
+	APPROVER
+	NOTIFIER
+)
+
+// ActionRuleType 审批人类型
+type ActionRuleType int
+
+const (
+	MANAGER ActionRuleType = iota
+	LABEL
+)
+
+// NodeTypes 节点类型
+var NodeTypes = [...]string{START: "start", ROUTE: "route", CONDITION: "condition", APPROVER: "approver", NOTIFIER: "notifier"}
+var actionRuleTypes = [...]string{MANAGER: "target_management", LABEL: "target_label"}
+
+type NodeInfoType int
+
+const (
+	STARTER NodeInfoType = iota
+)
+
+var NodeInfoTypes = [...]string{STARTER: "starter"}
+
+type ActionerRule struct {
+	Type       string `json:"type,omitempty"`
+	LabelNames string `json:"labelNames,omitempty"`
+	Labels     int    `json:"labels,omitempty"`
+	IsEmpty    bool   `json:"isEmpty,omitempty"`
+	// 表示需要通过的人数 如果是会签
+	MemberCount int8 `json:"memberCount,omitempty"`
+	// and 表示会签 or表示或签,默认为或签
+	ActType string `json:"actType,omitempty"`
+	Level   int8   `json:"level,omitempty"`
+	AutoUp  bool   `json:"autoUp,omitempty"`
+}
+type NodeProperties struct {
+	// ONE_BY_ONE 代表依次审批
+	ActivateType       string             `json:"activateType,omitempty"`
+	AgreeAll           bool               `json:"agreeAll,omitempty"`
+	Conditions         [][]*NodeCondition `json:"conditions,omitempty"`
+	ActionerRules      []*ActionerRule    `json:"actionerRules,omitempty"`
+	NoneActionerAction string             `json:"noneActionerAction,omitempty"`
+}
+type NodeCondition struct {
+	Type       string `json:"type,omitempty"`
+	ParamKey   string `json:"paramKey,omitempty"`
+	ParamLabel string `json:"paramLabel,omitempty"`
+	IsEmpty    bool   `json:"isEmpty,omitempty"`
+	// 类型为range
+	LowerBound      string `json:"lowerBound,omitempty"`
+	LowerBoundEqual string `json:"lowerBoundEqual,omitempty"`
+	UpperBoundEqual string `json:"upperBoundEqual,omitempty"`
+	UpperBound      string `json:"upperBound,omitempty"`
+	BoundEqual      string `json:"boundEqual,omitempty"`
+	Unit            string `json:"unit,omitempty"`
+	// 类型为 value
+	ParamValues []string    `json:"paramValues,omitempty"`
+	OriValue    []string    `json:"oriValue,omitempty"`
+	Conds       []*NodeCond `json:"conds,omitempty"`
+}
+type NodeCond struct {
+	Type  string    `json:"type,omitempty"`
+	Value string    `json:"value,omitempty"`
+	Attrs *NodeUser `json:"attrs,omitempty"`
+}
+type NodeUser struct {
+	Name   string `json:"name,omitempty"`
+	Avatar string `json:"avatar,omitempty"`
+}
+
+// NodeInfo 节点信息
+type NodeInfo struct {
+	NodeID      string `json:"nodeId"`
+	Type        string `json:"type"`
+	Aprover     string `json:"approver"`
+	AproverType string `json:"aproverType"`
+	MemberCount int8   `json:"memberCount"`
+	Level       int8   `json:"level"`
+	ActType     string `json:"actType"`
+}
+
+// GetProcessConfigFromJSONFile test
+func (n *Node) GetProcessConfigFromJSONFile() {
+	file, err := os.Open("D:/Workspaces/go/src/github.com/go-workflow/go-workflow/processConfig2.json")
+	if err != nil {
+		log.Printf("cannot open file processConfig.json:%v", err)
+		panic(err)
+	}
+	decoder := json.NewDecoder(file)
+	err = decoder.Decode(n)
+	if err != nil {
+		log.Printf("decode processConfig.json failed:%v", err)
+	}
+}
+func (n *Node) add2ExecutionList(list *list.List) {
+	switch n.Type {
+	case NodeTypes[APPROVER], NodeTypes[NOTIFIER]:
+		var aprover string
+		if n.Properties.ActionerRules[0].Type == actionRuleTypes[MANAGER] {
+			aprover = "主管"
+		} else {
+			aprover = n.Properties.ActionerRules[0].LabelNames
+		}
+		list.PushBack(NodeInfo{
+			NodeID:      n.NodeID,
+			Type:        n.Properties.ActionerRules[0].Type,
+			Aprover:     aprover,
+			AproverType: n.Type,
+			MemberCount: n.Properties.ActionerRules[0].MemberCount,
+			ActType:     n.Properties.ActionerRules[0].ActType,
+		})
+		break
+	default:
+	}
+}
+
+// IfProcessConifgIsValid 检查流程配置是否有效
+func IfProcessConifgIsValid(node *Node) error {
+	// 节点名称是否有效
+	if len(node.NodeID) == 0 {
+		return errors.New("节点的【nodeId】不能为空!!")
+	}
+	// 检查类型是否有效
+	if len(node.Type) == 0 {
+		return errors.New("节点【" + node.NodeID + "】的类型【type】不能为空")
+	}
+	var flag = false
+	for _, val := range NodeTypes {
+		if val == node.Type {
+			flag = true
+			break
+		}
+	}
+	if !flag {
+		str, _ := util.ToJSONStr(NodeTypes)
+		return errors.New("节点【" + node.NodeID + "】的类型为【" + node.Type + "】,为无效类型,有效类型为" + str)
+	}
+	// 当前节点是否设置有审批人
+	if node.Type == NodeTypes[APPROVER] || node.Type == NodeTypes[NOTIFIER] {
+		if node.Properties == nil || node.Properties.ActionerRules == nil {
+			return errors.New("节点【" + node.NodeID + "】的Properties属性不能为空,如:`\"properties\": {\"actionerRules\": [{\"type\": \"target_label\",\"labelNames\": \"人事\",\"memberCount\": 1,\"actType\": \"and\"}],}`")
+		}
+	}
+	// 条件节点是否存在
+	if node.ConditionNodes != nil { // 存在条件节点
+		if len(node.ConditionNodes) == 1 {
+			return errors.New("节点【" + node.NodeID + "】条件节点下的节点数必须大于1")
+		}
+		// 根据条件变量选择节点索引
+		err := CheckConditionNode(node.ConditionNodes)
+		if err != nil {
+			return err
+		}
+	}
+
+	// 子节点是否存在
+	if node.ChildNode != nil {
+		return IfProcessConifgIsValid(node.ChildNode)
+	}
+	return nil
+}
+
+// CheckConditionNode 检查条件节点
+func CheckConditionNode(nodes []*Node) error {
+	for _, node := range nodes {
+		if node.Properties == nil {
+			return errors.New("节点【" + node.NodeID + "】的Properties对象为空值!!")
+		}
+		if len(node.Properties.Conditions) == 0 {
+			return errors.New("节点【" + node.NodeID + "】的Conditions对象为空值!!")
+		}
+		err := IfProcessConifgIsValid(node)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// ParseProcessConfig 解析流程定义json数据
+func ParseProcessConfig(node *Node, variable *map[string]string) (*list.List, error) {
+	// defer fmt.Println("----------解析结束--------")
+	list := list.New()
+	err := parseProcessConfig(node, variable, list)
+	return list, err
+}
+func parseProcessConfig(node *Node, variable *map[string]string, list *list.List) (err error) {
+	// fmt.Printf("nodeId=%s\n", node.NodeID)
+	node.add2ExecutionList(list)
+	// 存在条件节点
+	if node.ConditionNodes != nil {
+		// 如果条件节点只有一个或者条件只有一个,直接返回第一个
+		if variable == nil || len(node.ConditionNodes) == 1 {
+			err = parseProcessConfig(node.ConditionNodes[0].ChildNode, variable, list)
+			if err != nil {
+				return err
+			}
+		} else {
+			// 根据条件变量选择节点索引
+			condNode, err := GetConditionNode(node.ConditionNodes, variable)
+			if err != nil {
+				return err
+			}
+			if condNode == nil {
+				str, _ := util.ToJSONStr(variable)
+				return errors.New("节点【" + node.NodeID + "】找不到符合条件的子节点,检查变量【var】值是否匹配," + str)
+				// panic(err)
+			}
+			err = parseProcessConfig(condNode, variable, list)
+			if err != nil {
+				return err
+			}
+
+		}
+	}
+	// 存在子节点
+	if node.ChildNode != nil {
+		err = parseProcessConfig(node.ChildNode, variable, list)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// GetConditionNode 获取条件节点
+func GetConditionNode(nodes []*Node, maps *map[string]string) (result *Node, err error) {
+	map2 := *maps
+	for _, node := range nodes {
+		var flag int
+		for _, v := range node.Properties.Conditions[0] {
+			paramValue := map2[v.ParamKey]
+			if len(paramValue) == 0 {
+				return nil, errors.New("流程启动变量【var】的key【" + v.ParamKey + "】的值不能为空")
+			}
+			yes, err := checkConditions(v, paramValue)
+			if err != nil {
+				return nil, err
+			}
+			if yes {
+				flag++
+			}
+		}
+		// fmt.Printf("flag=%d\n", flag)
+		// 满足所有条件
+		if flag == len(node.Properties.Conditions[0]) {
+			result = node
+		}
+	}
+	return result, nil
+}
+func getConditionNode(nodes []*Node, maps *map[string]string) (result *Node, err error) {
+	map2 := *maps
+	// 获取所有conditionNodes
+	getNodesChan := func() <-chan *Node {
+		nodesChan := make(chan *Node, len(nodes))
+		go func() {
+			// defer fmt.Println("关闭nodeChan通道")
+			defer close(nodesChan)
+			for _, v := range nodes {
+				nodesChan <- v
+			}
+		}()
+		return nodesChan
+	}
+
+	//获取所有conditions
+	getConditionNode := func(nodesChan <-chan *Node, done <-chan interface{}) <-chan *Node {
+		resultStream := make(chan *Node, 2)
+		go func() {
+			// defer fmt.Println("关闭resultStream通道")
+			defer close(resultStream)
+			for {
+				select {
+				case <-done:
+					return
+				case <-time.After(10 * time.Millisecond):
+					fmt.Println("Time out.")
+				case node, ok := <-nodesChan:
+					if ok {
+						// for _, v := range node.Properties.Conditions[0] {
+						// 	conStream <- v
+						// 	fmt.Printf("接收 condition:%s\n", v.Type)
+						// }
+						var flag int
+						for _, v := range node.Properties.Conditions[0] {
+							// fmt.Println(v.ParamKey)
+							// fmt.Println(map2[v.ParamKey])
+							paramValue := map2[v.ParamKey]
+							if len(paramValue) == 0 {
+								log.Printf("key:%s的值为空\n", v.ParamKey)
+								// nodeAndErr.Err = errors.New("key:" + v.ParamKey + "的值为空")
+								break
+							}
+							yes, err := checkConditions(v, paramValue)
+							if err != nil {
+								// nodeAndErr.Err = err
+								break
+							}
+							if yes {
+								flag++
+							}
+						}
+						// fmt.Printf("flag=%d\n", flag)
+						// 满足所有条件
+						if flag == len(node.Properties.Conditions[0]) {
+							// fmt.Printf("flag=%d\n,send node:%s\n", flag, node.NodeID)
+							resultStream <- node
+						} else {
+							// fmt.Println("条件不完全满足")
+						}
+					}
+				}
+			}
+		}()
+		return resultStream
+	}
+	done := make(chan interface{})
+	// defer fmt.Println("结束所有goroutine")
+	defer close(done)
+	nodeStream := getNodesChan()
+	// for i := len(nodes); i > 0; i-- {
+	// 	getConditionNode(resultStream, nodeStream, done)
+	// }
+	resultStream := getConditionNode(nodeStream, done)
+	// for node := range resultStream {
+	// 	return node, nil
+	// }
+	for {
+		select {
+		case <-time.After(1 * time.Second):
+			fmt.Println("Time out")
+			return
+		case node := <-resultStream:
+			// result = node
+			return node, nil
+		}
+	}
+	// setResult(resultStream, done)
+	// time.Sleep(1 * time.Second)
+	// log.Println("----------寻找节点结束--------")
+	// return result, err
+}
+func checkConditions(cond *NodeCondition, value string) (bool, error) {
+	// 判断类型
+	switch cond.Type {
+	case ActionConditionTypes[RANGE]:
+		val, err := strconv.Atoi(value)
+		if err != nil {
+			return false, err
+		}
+		if len(cond.LowerBound) == 0 && len(cond.UpperBound) == 0 && len(cond.LowerBoundEqual) == 0 && len(cond.UpperBoundEqual) == 0 && len(cond.BoundEqual) == 0 {
+			return false, errors.New("条件【" + cond.Type + "】的上限或者下限值不能全为空")
+		}
+		// 判断下限,lowerBound
+		if len(cond.LowerBound) > 0 {
+			low, err := strconv.Atoi(cond.LowerBound)
+			if err != nil {
+				return false, err
+			}
+			if val <= low {
+				// fmt.Printf("val:%d小于lowerBound:%d\n", val, low)
+				return false, nil
+			}
+		}
+		if len(cond.LowerBoundEqual) > 0 {
+			le, err := strconv.Atoi(cond.LowerBoundEqual)
+			if err != nil {
+				return false, err
+			}
+			if val < le {
+				// fmt.Printf("val:%d小于lowerBound:%d\n", val, low)
+				return false, nil
+			}
+		}
+		// 判断上限,upperBound包含等于
+		if len(cond.UpperBound) > 0 {
+			upper, err := strconv.Atoi(cond.UpperBound)
+			if err != nil {
+				return false, err
+			}
+			if val >= upper {
+				return false, nil
+			}
+		}
+		if len(cond.UpperBoundEqual) > 0 {
+			ge, err := strconv.Atoi(cond.UpperBoundEqual)
+			if err != nil {
+				return false, err
+			}
+			if val > ge {
+				return false, nil
+			}
+		}
+		if len(cond.BoundEqual) > 0 {
+			equal, err := strconv.Atoi(cond.BoundEqual)
+			if err != nil {
+				return false, err
+			}
+			if val != equal {
+				return false, nil
+			}
+		}
+		return true, nil
+	case ActionConditionTypes[VALUE]:
+		if len(cond.ParamValues) == 0 {
+			return false, errors.New("条件节点【" + cond.Type + "】的 【paramValues】数组不能为空,值如:'paramValues:['调休','年假']")
+		}
+		for _, val := range cond.ParamValues {
+			if value == val {
+				return true, nil
+			}
+		}
+		// log.Printf("key:" + cond.ParamKey + "找不到对应的值")
+		return false, nil
+	default:
+		str, _ := util.ToJSONStr(ActionConditionTypes)
+		return false, errors.New("未知的NodeCondition类型【" + cond.Type + "】,正确类型应为以下中的一个:" + str)
+	}
+}

+ 3 - 0
server/go.mod

@@ -17,8 +17,10 @@ require (
 	github.com/gofrs/uuid/v5 v5.0.0
 	github.com/golang-jwt/jwt/v4 v4.5.0
 	github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.8+incompatible
+	github.com/jinzhu/gorm v1.9.16
 	github.com/jordan-wright/email v0.0.0-20200824153738-3f5bafa1cd84
 	github.com/mojocn/base64Captcha v1.3.6
+	github.com/mumushuiding/util v0.0.0-20220111075258-3f1897a1475a
 	github.com/pkg/errors v0.9.1
 	github.com/qiniu/api.v7/v7 v7.4.1
 	github.com/qiniu/qmgo v1.1.8
@@ -108,6 +110,7 @@ require (
 	github.com/spf13/jwalterweatherman v1.1.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/subosito/gotenv v1.4.2 // indirect
+	github.com/tealeg/xlsx v1.0.5 // indirect
 	github.com/tklauser/go-sysconf v0.3.11 // indirect
 	github.com/tklauser/numcpus v0.6.0 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect

+ 24 - 0
server/go.sum

@@ -44,9 +44,11 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
 github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
+github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
 github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
 github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible h1:KpbJFXwhVeuxNtBJ74MCGbIoaBok2uZvkD7QXp2+Wis=
 github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
+github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
 github.com/aws/aws-sdk-go v1.44.307 h1:2R0/EPgpZcFSUwZhYImq/srjaOrOfLv5MNRzrFyAM38=
 github.com/aws/aws-sdk-go v1.44.307/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
 github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
@@ -83,6 +85,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
+github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
@@ -95,6 +99,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
 github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
+github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
 github.com/flipped-aurora/gin-vue-admin/server v0.0.0-20240703013134-eebd8771bdd1 h1:tc9cWlmEfoKaBBlYzMiFyrZFV5UyEI6Lfbv/vpNJ5q8=
 github.com/flipped-aurora/gin-vue-admin/server v0.0.0-20240703013134-eebd8771bdd1/go.mod h1:DuaB1rq2m1vKjcOCBpuVjqppGOXSCbmPndxSRNSgqPQ=
 github.com/flipped-aurora/ws v1.0.2 h1:oEUz7sgrbPENvgli7Q4QpC0NIEbJucgR4yjcDMg/AjY=
@@ -144,6 +150,7 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO
 github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
 github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
 github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
 github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
@@ -161,6 +168,7 @@ github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV
 github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
 github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
 github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@@ -266,8 +274,11 @@ github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nD
 github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
 github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
 github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
+github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
+github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
 github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
 github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
 github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
@@ -304,6 +315,7 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
 github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
 github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
+github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
@@ -315,6 +327,9 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
 github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
+github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
+github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 github.com/microsoft/go-mssqldb v1.1.0 h1:jsV+tpvcPTbNNKW0o3kiCD69kOHICsfjZ2VcVu2lKYc=
 github.com/microsoft/go-mssqldb v1.1.0/go.mod h1:LzkFdl4z2Ck+Hi+ycGOTbL56VEfgoyA2DvYejrNGbRk=
 github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@@ -334,6 +349,8 @@ github.com/montanaflynn/stats v0.7.0 h1:r3y12KyNxj/Sb/iOE46ws+3mS1+MZca1wlHQFPsY
 github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
 github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ=
 github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
+github.com/mumushuiding/util v0.0.0-20220111075258-3f1897a1475a h1:oS78m7tdA6kKhGjliRClwR2PVSIQF6ZYG7O/uhFfvQg=
+github.com/mumushuiding/util v0.0.0-20220111075258-3f1897a1475a/go.mod h1:CtPHswBqqQBesxYl9KF7AoSxYAwoNtyE+5zzVQUJSVM=
 github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
 github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
@@ -407,6 +424,8 @@ github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+z
 github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
 github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04=
 github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E=
+github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
+github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
 github.com/tencentyun/cos-go-sdk-v5 v0.7.42 h1:Up1704BJjI5orycXKjpVpvuOInt9GC5pqY4knyE9Uds=
@@ -469,9 +488,11 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUu
 golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
 golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
@@ -522,6 +543,7 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
 golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -649,6 +671,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
@@ -813,6 +836,7 @@ google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGm
 google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=

+ 3 - 0
server/initialize/gorm.go

@@ -62,6 +62,9 @@ func RegisterTables() {
 		dao.FeeDetails{},
 		dao.Constructor{},
 		dao.Expenses{},
+
+		//------------workflow------------
+
 	)
 	if err != nil {
 		global.GVA_LOG.Error("register table failed", zap.Error(err))

+ 19 - 0
server/service/workflow/IdentitylinkHistoryService.go

@@ -0,0 +1,19 @@
+package service
+
+import (
+	"github.com/go-workflow/go-workflow/workflow-engine/model"
+	"github.com/mumushuiding/util"
+)
+
+// FindParticipantHistoryByProcInstID 历史纪录查询
+func FindParticipantHistoryByProcInstID(procInstID int) (string, error) {
+	datas, err := model.FindParticipantHistoryByProcInstID(procInstID)
+	if err != nil {
+		return "", err
+	}
+	str, err := util.ToJSONStr(datas)
+	if err != nil {
+		return "", err
+	}
+	return str, nil
+}

+ 43 - 0
server/service/workflow/cronJobService.go

@@ -0,0 +1,43 @@
+package service
+
+import (
+	"github.com/robfig/cron/v3"
+	"log"
+)
+
+// CronJobs CronJobs
+// 所有定时任务,在启动时会执行
+func CronJobs() {
+	go func() {
+		c := cron.New()
+		// 每隔 20 秒执行
+		spec := "*/20 * * * * ?"
+		//每隔20秒将已经结束的流程数据迁移至历史数据表
+		c.AddFunc(spec, func() {
+			MoveFinishedProcInstToHistory()
+		})
+		// c.AddFunc("*/5 * * * * ?", func() {
+		// 	log.Println("cron running")
+		// })
+		// 启动
+		c.Start()
+		log.Println("----------启动定时任务------------")
+		defer c.Stop()
+		select {}
+	}()
+	// c := cron.New()
+	// // 每天0点执行
+	// spec := "0 0 0 * * ?"
+	// //每天0点时将已经结束的流程数据迁移至历史数据表
+	// c.AddFunc(spec, func() {
+	// 	MoveFinishedProcInstToHistory()
+	// })
+	// c.AddFunc("*/5 * * * * ?", func() {
+	// 	log.Println("cron running")
+	// })
+	// // 启动
+	// c.Start()
+	// log.Println("----------启动定时任务------------")
+	// defer c.Stop()
+	// select {}
+}

+ 90 - 0
server/service/workflow/executionService.go

@@ -0,0 +1,90 @@
+package service
+
+import (
+	"errors"
+	"fmt"
+	"sync"
+
+	"github.com/jinzhu/gorm"
+
+	"github.com/mumushuiding/util"
+
+	"github.com/go-workflow/go-workflow/workflow-engine/flow"
+	"github.com/go-workflow/go-workflow/workflow-engine/model"
+)
+
+var execLock sync.Mutex
+
+// SaveExecution SaveExecution
+func SaveExecution(e *model.Execution) (ID int, err error) {
+	execLock.Lock()
+	defer execLock.Unlock()
+	// check if exists by procInst
+	yes, err := model.ExistsExecByProcInst(e.ProcInstID)
+	if err != nil {
+		return 0, err
+	}
+	if yes {
+		return 0, errors.New("流程实例【" + fmt.Sprintf("%d", e.ProcInstID) + "】已经存在执行流")
+	}
+	// save
+	return e.Save()
+}
+
+// SaveExecTx SaveExecTx
+func SaveExecTx(e *model.Execution, tx *gorm.DB) (ID int, err error) {
+	execLock.Lock()
+	defer execLock.Unlock()
+	// check if exists by procInst
+	yes, err := model.ExistsExecByProcInst(e.ProcInstID)
+	if err != nil {
+		return 0, err
+	}
+	if yes {
+		return 0, errors.New("流程实例【" + fmt.Sprintf("%d", e.ProcInstID) + "】已经存在执行流")
+	}
+	// save
+	return e.SaveTx(tx)
+}
+
+// GetExecByProcInst 根据流程实例查询执行流
+func GetExecByProcInst(procInst int) (*model.Execution, error) {
+	return model.GetExecByProcInst(procInst)
+}
+
+// GenerateExec GenerateExec
+// 根据流程定义node生成执行流
+func GenerateExec(e *model.Execution, node *flow.Node, userID string, variable *map[string]string, tx *gorm.DB) (int, error) {
+	list, err := flow.ParseProcessConfig(node, variable)
+	if err != nil {
+		return 0, err
+	}
+	list.PushBack(flow.NodeInfo{
+		NodeID: "结束",
+	})
+	list.PushFront(flow.NodeInfo{
+		NodeID:  "开始",
+		Type:    flow.NodeInfoTypes[flow.STARTER],
+		Aprover: userID,
+	})
+	arr := util.List2Array(list)
+	str, err := util.ToJSONStr(arr)
+	if err != nil {
+		return 0, err
+	}
+	e.NodeInfos = str
+	ID, err := SaveExecTx(e, tx)
+	return ID, err
+}
+
+// GetExecNodeInfosByProcInstID GetExecNodeInfosByProcInstID
+// 获取执行流经过的节点信息
+func GetExecNodeInfosByProcInstID(procInstID int) ([]*flow.NodeInfo, error) {
+	nodeinfoStr, err := model.GetExecNodeInfosByProcInstID(procInstID)
+	if err != nil {
+		return nil, err
+	}
+	var nodeInfos []*flow.NodeInfo
+	err = util.Str2Struct(nodeinfoStr, &nodeInfos)
+	return nodeInfos, err
+}

+ 139 - 0
server/service/workflow/identitylinkService.go

@@ -0,0 +1,139 @@
+package service
+
+import (
+	"github.com/go-workflow/go-workflow/workflow-engine/model"
+	"github.com/jinzhu/gorm"
+	"github.com/mumushuiding/util"
+)
+
+// SaveIdentitylinkTx SaveIdentitylinkTx
+func SaveIdentitylinkTx(i *model.Identitylink, tx *gorm.DB) error {
+	return i.SaveTx(tx)
+}
+
+// AddNotifierTx 添加抄送人候选用户组
+func AddNotifierTx(group, company string, step, procInstID int, tx *gorm.DB) error {
+	yes, err := ExistsNotifierByProcInstIDAndGroup(procInstID, group)
+	if err != nil {
+		return err
+	}
+	if yes {
+		return nil
+	}
+	i := &model.Identitylink{
+		Group:      group,
+		Type:       model.IdentityTypes[model.NOTIFIER],
+		Step:       step,
+		ProcInstID: procInstID,
+		Company:    company,
+	}
+	return SaveIdentitylinkTx(i, tx)
+}
+
+// AddCandidateGroupTx AddCandidateGroupTx
+// 添加候选用户组
+func AddCandidateGroupTx(group, company string, step, taskID, procInstID int, tx *gorm.DB) error {
+	err := DelCandidateByProcInstID(procInstID, tx)
+	if err != nil {
+		return err
+	}
+	i := &model.Identitylink{
+		Group:      group,
+		Type:       model.IdentityTypes[model.CANDIDATE],
+		TaskID:     taskID,
+		Step:       step,
+		ProcInstID: procInstID,
+		Company:    company,
+	}
+	return SaveIdentitylinkTx(i, tx)
+}
+
+// AddCandidateUserTx AddCandidateUserTx
+// 添加候选用户
+func AddCandidateUserTx(userID, company string, step, taskID, procInstID int, tx *gorm.DB) error {
+	err := DelCandidateByProcInstID(procInstID, tx)
+	if err != nil {
+		return err
+	}
+	i := &model.Identitylink{
+		UserID:     userID,
+		Type:       model.IdentityTypes[model.CANDIDATE],
+		TaskID:     taskID,
+		Step:       step,
+		ProcInstID: procInstID,
+		Company:    company,
+	}
+	return SaveIdentitylinkTx(i, tx)
+	// var wg sync.WaitGroup
+	// var err1, err2 error
+	// numberOfRoutine := 2
+	// wg.Add(numberOfRoutine)
+	// go func() {
+	// 	defer wg.Done()
+	// 	err1 = DelCandidateByProcInstID(procInstID, tx)
+	// }()
+	// go func() {
+	// 	defer wg.Done()
+	// 	i := &model.Identitylink{
+	// 		UserID:     userID,
+	// 		Type:       model.IdentityTypes[model.CANDIDATE],
+	// 		TaskID:     taskID,
+	// 		Step:       step,
+	// 		ProcInstID: procInstID,
+	// 		Company:    company,
+	// 	}
+	// 	err2 = SaveIdentitylinkTx(i, tx)
+	// }()
+	// wg.Wait()
+	// fmt.Println("保存identyilink结束")
+	// if err1 != nil {
+	// 	return err1
+	// }
+	// return err2
+}
+
+//AddParticipantTx AddParticipantTx
+// 添加任务参与人
+func AddParticipantTx(userID, username, company, comment string, taskID, procInstID, step int, tx *gorm.DB) error {
+	i := &model.Identitylink{
+		Type:       model.IdentityTypes[model.PARTICIPANT],
+		UserID:     userID,
+		UserName:   username,
+		ProcInstID: procInstID,
+		Step:       step,
+		Company:    company,
+		TaskID:     taskID,
+		Comment:    comment,
+	}
+	return SaveIdentitylinkTx(i, tx)
+}
+
+// IfParticipantByTaskID IfParticipantByTaskID
+// 针对指定任务判断用户是否已经审批过了
+func IfParticipantByTaskID(userID, company string, taskID int) (bool, error) {
+	return model.IfParticipantByTaskID(userID, company, taskID)
+}
+
+// DelCandidateByProcInstID DelCandidateByProcInstID
+// 删除历史候选人
+func DelCandidateByProcInstID(procInstID int, tx *gorm.DB) error {
+	return model.DelCandidateByProcInstID(procInstID, tx)
+}
+
+// ExistsNotifierByProcInstIDAndGroup 抄送人是否已经存在
+func ExistsNotifierByProcInstIDAndGroup(procInstID int, group string) (bool, error) {
+	return model.ExistsNotifierByProcInstIDAndGroup(procInstID, group)
+}
+
+// FindParticipantByProcInstID 查询参与审批的人
+func FindParticipantByProcInstID(procInstID int) (string, error) {
+	datas, err := model.FindParticipantByProcInstID(procInstID)
+	if err != nil {
+		return "", err
+	}
+	str, err := util.ToJSONStr(datas)
+	if err != nil {
+		return "", err
+	}
+	return str, nil
+}

+ 67 - 0
server/service/workflow/procInstHistoryService.go

@@ -0,0 +1,67 @@
+package service
+
+import (
+	"errors"
+
+	"github.com/go-workflow/go-workflow/workflow-engine/model"
+	"github.com/mumushuiding/util"
+)
+
+// FindProcHistory 查询我的审批
+func FindProcHistory(receiver *ProcessPageReceiver) (string, error) {
+	datas, count, err := findAllProcHistory(receiver)
+	if err != nil {
+		return "", err
+	}
+	return util.ToPageJSON(datas, count, receiver.PageIndex, receiver.PageSize)
+}
+
+// FindProcHistoryByToken 查询我的审批纪录
+func FindProcHistoryByToken(token string, receiver *ProcessPageReceiver) (string, error) {
+	userinfo, err := GetUserinfoFromRedis(token)
+	if err != nil {
+		return "", err
+	}
+	if len(userinfo.Company) == 0 {
+		return "", errors.New("保存在redis中的【用户信息 userinfo】字段 company 不能为空")
+	}
+	if len(userinfo.ID) == 0 {
+		return "", errors.New("保存在redis中的【用户信息 userinfo】字段 ID 不能为空")
+	}
+	receiver.Company = userinfo.Company
+	receiver.UserID = userinfo.ID
+	// receiver.Username = userinfo.Username
+	return FindProcHistory(receiver)
+}
+func findAllProcHistory(receiver *ProcessPageReceiver) ([]*model.ProcInstHistory, int, error) {
+	var page = util.Page{}
+	page.PageRequest(receiver.PageIndex, receiver.PageSize)
+	return model.FindProcHistory(receiver.UserID, receiver.Company, receiver.PageIndex, receiver.PageSize)
+}
+
+// DelProcInstHistoryByID DelProcInstHistoryByID
+func DelProcInstHistoryByID(id int) error {
+	return model.DelProcInstHistoryByID(id)
+}
+
+// StartHistoryByMyself 查询我发起的流程
+func StartHistoryByMyself(receiver *ProcessPageReceiver) (string, error) {
+	var page = util.Page{}
+	page.PageRequest(receiver.PageIndex, receiver.PageSize)
+	datas, count, err := model.StartHistoryByMyself(receiver.UserID, receiver.Company, receiver.PageIndex, receiver.PageSize)
+	if err != nil {
+		return "", err
+	}
+	return util.ToPageJSON(datas, count, receiver.PageIndex, receiver.PageSize)
+}
+
+// FindProcHistoryNotify 查询抄送我的流程
+func FindProcHistoryNotify(receiver *ProcessPageReceiver) (string, error) {
+	var page = util.Page{}
+	page.PageRequest(receiver.PageIndex, receiver.PageSize)
+	datas, count, err := model.FindProcHistoryNotify(receiver.UserID, receiver.Company, receiver.Groups, receiver.PageIndex, receiver.PageSize)
+	if err != nil {
+		return "", err
+	}
+	return util.ToPageJSON(datas, count, receiver.PageIndex, receiver.PageSize)
+}

+ 337 - 0
server/service/workflow/procInstService.go

@@ -0,0 +1,337 @@
+package service
+
+import (
+	"errors"
+	"sync"
+	"time"
+
+	"github.com/jinzhu/gorm"
+
+	"github.com/go-workflow/go-workflow/workflow-engine/flow"
+	"github.com/go-workflow/go-workflow/workflow-engine/model"
+	"github.com/mumushuiding/util"
+)
+
+// ProcessReceiver 接收页面传递参数
+type ProcessReceiver struct {
+	UserID     string             `json:"userId"`
+	ProcInstID string             `json:"procInstID"`
+	Username   string             `json:"username"`
+	Company    string             `json:"company"`
+	ProcName   string             `json:"procName"`
+	Title      string             `json:"title"`
+	Department string             `json:"department"`
+	Var        *map[string]string `json:"var"`
+}
+
+// ProcessPageReceiver 分页参数
+type ProcessPageReceiver struct {
+	util.Page
+	// 我分管的部门
+	Departments []string `json:"departments"`
+	// 我所属于的用户组或者角色
+	Groups     []string `josn:"groups"`
+	UserID     string   `json:"userID"`
+	Username   string   `json:"username"`
+	Company    string   `json:"company"`
+	ProcName   string   `json:"procName"`
+	ProcInstID string   `json:"procInstID"`
+}
+
+var copyLock sync.Mutex
+
+// GetDefaultProcessPageReceiver GetDefaultProcessPageReceiver
+func GetDefaultProcessPageReceiver() *ProcessPageReceiver {
+	var p = ProcessPageReceiver{}
+	p.PageIndex = 1
+	p.PageSize = 10
+	return &p
+}
+func findAll(pr *ProcessPageReceiver) ([]*model.ProcInst, int, error) {
+	var page = util.Page{}
+	page.PageRequest(pr.PageIndex, pr.PageSize)
+	return model.FindProcInsts(pr.UserID, pr.ProcName, pr.Company, pr.Groups, pr.Departments, pr.PageIndex, pr.PageSize)
+}
+
+// FindProcInstByID FindProcInstByID
+func FindProcInstByID(id int) (string, error) {
+	data, err := model.FindProcInstByID(id)
+	if err != nil {
+		return "", err
+	}
+	return util.ToJSONStr(data)
+}
+
+// FindAllPageAsJSON FindAllPageAsJSON
+func FindAllPageAsJSON(pr *ProcessPageReceiver) (string, error) {
+	datas, count, err := findAll(pr)
+	if err != nil {
+		return "", err
+	}
+	return util.ToPageJSON(datas, count, pr.PageIndex, pr.PageSize)
+}
+
+// FindMyProcInstByToken FindMyProcInstByToken
+// 根据token获取流程信息
+func FindMyProcInstByToken(token string, receiver *ProcessPageReceiver) (string, error) {
+	// 根据 token 获取用户信息
+	userinfo, err := GetUserinfoFromRedis(token)
+	if err != nil {
+		return "", err
+	}
+	if len(userinfo.Company) == 0 {
+		return "", errors.New("保存在redis中的【用户信息 userinfo】字段 company 不能为空")
+	}
+	if len(userinfo.ID) == 0 {
+		return "", errors.New("保存在redis中的【用户信息 userinfo】字段 ID 不能为空")
+	}
+	receiver.Company = userinfo.Company
+	receiver.Departments = userinfo.Departments
+	receiver.Groups = userinfo.Roles
+	receiver.UserID = userinfo.ID
+	// str, _ = util.ToJSONStr(receiver)
+	// fmt.Printf("receiver:%s\n", str)
+	return FindAllPageAsJSON(receiver)
+}
+
+// StartProcessInstanceByToken 启动流程
+func StartProcessInstanceByToken(token string, p *ProcessReceiver) (int, error) {
+	// 根据 token 获取用户信息
+	userinfo, err := GetUserinfoFromRedis(token)
+	if err != nil {
+		return 0, err
+	}
+	if len(userinfo.Company) == 0 {
+		return 0, errors.New("保存在redis中的【用户信息 userinfo】字段 company 不能为空")
+	}
+	if len(userinfo.Username) == 0 {
+		return 0, errors.New("保存在redis中的【用户信息 userinfo】字段 username 不能为空")
+	}
+	if len(userinfo.ID) == 0 {
+		return 0, errors.New("保存在redis中的【用户信息 userinfo】字段 ID 不能为空")
+	}
+	if len(userinfo.Department) == 0 {
+		return 0, errors.New("保存在redis中的【用户信息 userinfo】字段 department 不能为空")
+	}
+	p.Company = userinfo.Company
+	p.Department = userinfo.Department
+	p.UserID = userinfo.ID
+	p.Username = userinfo.Username
+	return p.StartProcessInstanceByID(p.Var)
+}
+
+// StartProcessInstanceByID 启动流程
+func (p *ProcessReceiver) StartProcessInstanceByID(variable *map[string]string) (int, error) {
+	// times := time.Now()
+	// runtime.GOMAXPROCS(2)
+	// 获取流程定义
+	node, prodefID, procdefName, err := GetResourceByNameAndCompany(p.ProcName, p.Company)
+	if err != nil {
+		return 0, err
+	}
+	// fmt.Printf("获取流程定义耗时:%v", time.Since(times))
+	//--------以下需要添加事务-----------------
+	step := 0 // 0 为开始节点
+	tx := model.GetTx()
+	// 新建流程实例
+	var procInst = model.ProcInst{
+		ProcDefID:     prodefID,
+		ProcDefName:   procdefName,
+		Title:         p.Title,
+		Department:    p.Department,
+		StartTime:     util.FormatDate(time.Now(), util.YYYY_MM_DD_HH_MM_SS),
+		StartUserID:   p.UserID,
+		StartUserName: p.Username,
+		Company:       p.Company,
+	} //开启事务
+	// times = time.Now()
+	procInstID, err := CreateProcInstTx(&procInst, tx) // 事务
+	// fmt.Printf("启动流程实例耗时:%v", time.Since(times))
+	exec := &model.Execution{
+		ProcDefID:  prodefID,
+		ProcInstID: procInstID,
+	}
+	task := &model.Task{
+		NodeID:        "开始",
+		ProcInstID:    procInstID,
+		Assignee:      p.UserID,
+		IsFinished:    true,
+		ClaimTime:     util.FormatDate(time.Now(), util.YYYY_MM_DD_HH_MM_SS),
+		Step:          step,
+		MemberCount:   1,
+		UnCompleteNum: 0,
+		ActType:       "or",
+		AgreeNum:      1,
+	}
+	// 生成执行流,一串运行节点
+	_, err = GenerateExec(exec, node, p.UserID, variable, tx) //事务
+	if err != nil {
+		tx.Rollback()
+		return 0, err
+	}
+	// 获取执行流信息
+	var nodeinfos []*flow.NodeInfo
+	err = util.Str2Struct(exec.NodeInfos, &nodeinfos)
+	if err != nil {
+		tx.Rollback()
+		return 0, err
+	}
+	// fmt.Printf("生成执行流耗时:%v", time.Since(times))
+	// -----------------生成新任务-------------
+	// times = time.Now()
+	if nodeinfos[0].ActType == "and" {
+		task.UnCompleteNum = nodeinfos[0].MemberCount
+		task.MemberCount = nodeinfos[0].MemberCount
+	}
+	_, err = NewTaskTx(task, tx)
+	if err != nil {
+		tx.Rollback()
+		return 0, err
+	}
+	// fmt.Printf("生成新任务耗时:%v", time.Since(times))
+	//--------------------流转------------------
+	// times = time.Now()
+	// 流程移动到下一环节
+	err = MoveStage(nodeinfos, p.UserID, p.Username, p.Company, "启动流程", "", task.ID, procInstID, step, true, tx)
+	if err != nil {
+		tx.Rollback()
+		return 0, err
+	}
+	// fmt.Printf("流转到下一流程耗时:%v", time.Since(times))
+	// fmt.Println("--------------提交事务----------")
+	tx.Commit() //结束事务
+	return procInstID, err
+}
+
+// CreateProcInstByID 新建流程实例
+// func CreateProcInstByID(processDefinitionID int, startUserID string) (int, error) {
+// 	var procInst = model.ProcInst{
+// 		ProcDefID:   processDefinitionID,
+// 		StartTime:   util.FormatDate(time.Now(), util.YYYY_MM_DD_HH_MM_SS),
+// 		StartUserID: startUserID,
+// 	}
+// 	return procInst.Save()
+// }
+
+// CreateProcInstTx CreateProcInstTx
+// 开户事务
+func CreateProcInstTx(procInst *model.ProcInst, tx *gorm.DB) (int, error) {
+
+	return procInst.SaveTx(tx)
+}
+
+// SetProcInstFinish SetProcInstFinish
+// 设置流程结束
+func SetProcInstFinish(procInstID int, endTime string, tx *gorm.DB) error {
+	var p = &model.ProcInst{}
+	p.ID = procInstID
+	p.EndTime = endTime
+	p.IsFinished = true
+	return p.UpdateTx(tx)
+}
+
+// StartByMyself 我发起的流程
+func StartByMyself(receiver *ProcessPageReceiver) (string, error) {
+	var page = util.Page{}
+	page.PageRequest(receiver.PageIndex, receiver.PageSize)
+	datas, count, err := model.StartByMyself(receiver.UserID, receiver.Company, receiver.PageIndex, receiver.PageSize)
+	if err != nil {
+		return "", err
+	}
+	return util.ToPageJSON(datas, count, receiver.PageIndex, receiver.PageSize)
+}
+
+// FindProcNotify 查询抄送我的
+func FindProcNotify(receiver *ProcessPageReceiver) (string, error) {
+	var page = util.Page{}
+	page.PageRequest(receiver.PageIndex, receiver.PageSize)
+	datas, count, err := model.FindProcNotify(receiver.UserID, receiver.Company, receiver.Groups, receiver.PageIndex, receiver.PageSize)
+	if err != nil {
+		return "", err
+	}
+	return util.ToPageJSON(datas, count, receiver.PageIndex, receiver.PageSize)
+}
+
+// UpdateProcInst UpdateProcInst
+// 更新流程实例
+func UpdateProcInst(procInst *model.ProcInst, tx *gorm.DB) error {
+	return procInst.UpdateTx(tx)
+}
+
+// MoveFinishedProcInstToHistory MoveFinishedProcInstToHistory
+func MoveFinishedProcInstToHistory() error {
+	// 要注意并发,可能会运行多个app实例
+	// 加锁
+	copyLock.Lock()
+	defer copyLock.Unlock()
+	// 从pro_inst表查询已经结束的流程
+	proinsts, err := model.FindFinishedProc()
+	if err != nil {
+		return err
+	}
+	if len(proinsts) == 0 {
+		return nil
+	}
+	for _, v := range proinsts {
+		// 复制 proc_inst
+		duration, err := util.TimeStrSub(v.EndTime, v.StartTime, util.YYYY_MM_DD_HH_MM_SS)
+		if err != nil {
+			return err
+		}
+		v.Duration = duration
+		err = copyProcToHistory(v)
+		if err != nil {
+			return err
+		}
+		tx := model.GetTx()
+		// 流程实例的task移至历史纪录
+		err = copyTaskToHistoryByProInstID(v.ID, tx)
+		if err != nil {
+			tx.Rollback()
+			DelProcInstHistoryByID(v.ID)
+			return err
+		}
+		// execution移至历史纪录
+		err = copyExecutionToHistoryByProcInstID(v.ID, tx)
+		if err != nil {
+			tx.Rollback()
+			DelProcInstHistoryByID(v.ID)
+			return err
+		}
+		// identitylink移至历史纪录
+		err = copyIdentitylinkToHistoryByProcInstID(v.ID, tx)
+		if err != nil {
+			tx.Rollback()
+			DelProcInstHistoryByID(v.ID)
+			return err
+		}
+		// 删除流程实例
+		err = DelProcInstByIDTx(v.ID, tx)
+		if err != nil {
+			tx.Rollback()
+			DelProcInstHistoryByID(v.ID)
+			return err
+		}
+		tx.Commit()
+	}
+
+	return nil
+}
+
+// DelProcInstByIDTx DelProcInstByIDTx
+func DelProcInstByIDTx(procInstID int, tx *gorm.DB) error {
+	return model.DelProcInstByIDTx(procInstID, tx)
+}
+func copyIdentitylinkToHistoryByProcInstID(procInstID int, tx *gorm.DB) error {
+	return model.CopyIdentitylinkToHistoryByProcInstID(procInstID, tx)
+}
+func copyExecutionToHistoryByProcInstID(procInstID int, tx *gorm.DB) error {
+	return model.CopyExecutionToHistoryByProcInstIDTx(procInstID, tx)
+}
+func copyProcToHistory(procInst *model.ProcInst) error {
+	return model.SaveProcInstHistory(procInst)
+
+}
+func copyTaskToHistoryByProInstID(procInstID int, tx *gorm.DB) error {
+	return model.CopyTaskToHistoryByProInstID(procInstID, tx)
+}

+ 193 - 0
server/service/workflow/procdefService.go

@@ -0,0 +1,193 @@
+package service
+
+import (
+	"errors"
+	"sync"
+	"time"
+
+	"github.com/go-workflow/go-workflow/workflow-engine/flow"
+
+	"github.com/mumushuiding/util"
+
+	"github.com/go-workflow/go-workflow/workflow-engine/model"
+)
+
+var saveLock sync.Mutex
+
+// Procdef 流程定义表
+type Procdef struct {
+	Name string `json:"name"`
+	// 流程定义json字符串
+	Resource *flow.Node `json:"resource"`
+	// 用户id
+	Userid   string `json:"userid"`
+	Username string `json:"username"`
+	// 用户所在公司
+	Company   string `json:"company"`
+	PageSize  int    `json:"pageSize"`
+	PageIndex int    `json:"pageIndex"`
+}
+
+// GetProcdefByID 根据流程定义id获取流程定义
+func GetProcdefByID(id int) (*model.Procdef, error) {
+	return model.GetProcdefByID(id)
+}
+
+// GetProcdefLatestByNameAndCompany GetProcdefLatestByNameAndCompany
+// 根据流程定义名字和公司查询流程定义
+func GetProcdefLatestByNameAndCompany(name, company string) (*model.Procdef, error) {
+	return model.GetProcdefLatestByNameAndCompany(name, company)
+}
+
+// GetResourceByNameAndCompany GetResourceByNameAndCompany
+// 获取流程定义配置信息
+func GetResourceByNameAndCompany(name, company string) (*flow.Node, int, string, error) {
+	prodef, err := GetProcdefLatestByNameAndCompany(name, company)
+	if err != nil {
+		return nil, 0, "", err
+	}
+	if prodef == nil {
+		return nil, 0, "", errors.New("流程【" + name + "】不存在")
+	}
+	node := &flow.Node{}
+	err = util.Str2Struct(prodef.Resource, node)
+	return node, prodef.ID, prodef.Name, err
+}
+
+// GetResourceByID GetResourceByID
+// 根据id查询流程定义
+func GetResourceByID(id int) (*flow.Node, int, error) {
+	prodef, err := GetProcdefByID(id)
+	if err != nil {
+		return nil, 0, err
+	}
+	node := &flow.Node{}
+	err = util.Str2Struct(prodef.Resource, node)
+	return node, prodef.ID, err
+}
+
+// SaveProcdefByToken SaveProcdefByToken
+func (p *Procdef) SaveProcdefByToken(token string) (int, error) {
+	// 根据 token 获取用户信息
+	userinfo, err := GetUserinfoFromRedis(token)
+	if err != nil {
+		return 0, err
+	}
+	if len(userinfo.Company) == 0 {
+		return 0, errors.New("保存在redis中的【用户信息 userinfo】字段 company 不能为空")
+	}
+	if len(userinfo.Username) == 0 {
+		return 0, errors.New("保存在redis中的【用户信息 userinfo】字段 username 不能为空")
+	}
+	if len(userinfo.ID) == 0 {
+		return 0, errors.New("保存在redis中的【用户信息 userinfo】字段 ID 不能为空")
+	}
+	p.Company = userinfo.Company
+	p.Userid = userinfo.ID
+	p.Username = userinfo.Username
+	return p.SaveProcdef()
+}
+
+// SaveProcdef 保存
+func (p *Procdef) SaveProcdef() (id int, err error) {
+	// 流程定义有效性检验
+	err = IsProdefValid(p.Resource)
+	if err != nil {
+		return 0, err
+	}
+	resource, err := util.ToJSONStr(p.Resource)
+	if err != nil {
+		return 0, err
+	}
+	// fmt.Println(resource)
+	var procdef = model.Procdef{
+		Name:     p.Name,
+		Userid:   p.Userid,
+		Username: p.Username,
+		Company:  p.Company,
+		Resource: resource,
+	}
+	return SaveProcdef(&procdef)
+}
+
+// SaveProcdef 保存
+func SaveProcdef(p *model.Procdef) (id int, err error) {
+	// 参数是否为空判定
+	saveLock.Lock()
+	defer saveLock.Unlock()
+	old, err := GetProcdefLatestByNameAndCompany(p.Name, p.Company)
+	if err != nil {
+		return 0, err
+	}
+	p.DeployTime = util.FormatDate(time.Now(), util.YYYY_MM_DD_HH_MM_SS)
+	if old == nil {
+		p.Version = 1
+		return p.Save()
+	}
+	tx := model.GetTx()
+	// 保存新版本
+	p.Version = old.Version + 1
+	err = p.SaveTx(tx)
+	if err != nil {
+		tx.Rollback()
+		return 0, err
+	}
+	// 转移旧版本
+	err = model.MoveProcdefToHistoryByIDTx(old.ID, tx)
+	if err != nil {
+		tx.Rollback()
+		return 0, err
+	}
+	tx.Commit()
+	return p.ID, nil
+}
+
+// ExistsProcdefByNameAndCompany if exists
+// 查询流程定义是否存在
+func ExistsProcdefByNameAndCompany(name, company string) (yes bool, version int, err error) {
+	p, err := GetProcdefLatestByNameAndCompany(name, company)
+	if p == nil {
+		return false, 1, err
+	}
+	version = p.Version + 1
+	return true, version, err
+}
+
+// FindAllPageAsJSON find by page and  transform result to string
+// 分页查询并将结果转换成 json 字符串
+func (p *Procdef) FindAllPageAsJSON() (string, error) {
+	datas, count, err := p.FindAll()
+	if err != nil {
+		return "", err
+	}
+	return util.ToPageJSON(datas, count, p.PageIndex, p.PageSize)
+}
+
+// FindAll FindAll
+func (p *Procdef) FindAll() ([]*model.Procdef, int, error) {
+	var page = util.Page{}
+	page.PageRequest(p.PageIndex, p.PageSize)
+	maps := p.getMaps()
+	return model.FindProcdefsWithCountAndPaged(page.PageIndex, page.PageSize, maps)
+}
+func (p *Procdef) getMaps() map[string]interface{} {
+	maps := make(map[string]interface{})
+	if len(p.Name) > 0 {
+		maps["name"] = p.Name
+	}
+	if len(p.Company) > 0 {
+		maps["company"] = p.Company
+	}
+	return maps
+}
+
+// DelProcdefByID del by id
+func DelProcdefByID(id int) error {
+	return model.DelProcdefByID(id)
+}
+
+// IsProdefValid 流程定义格式是否有效
+func IsProdefValid(node *flow.Node) error {
+
+	return flow.IfProcessConifgIsValid(node)
+}

+ 40 - 0
server/service/workflow/redisService.go

@@ -0,0 +1,40 @@
+package service
+
+import (
+	"github.com/mumushuiding/util"
+
+	"github.com/go-workflow/go-workflow/workflow-engine/model"
+)
+
+// UserInfo 用户信息
+type UserInfo struct {
+	Company string `json:"company"`
+	// 用户所属部门
+	Department string `json:"department"`
+	Username   string `json:"username"`
+	ID         string `json:"ID"`
+	// 用户的角色
+	Roles []string `json:"roles"`
+	// 用户负责的部门
+	Departments []string `json:"departments"`
+}
+
+// GetUserinfoFromRedis GetUserinfoFromRedis
+func GetUserinfoFromRedis(token string) (*UserInfo, error) {
+	result, err := GetValFromRedis(token)
+	if err != nil {
+		return nil, err
+	}
+	// fmt.Println(result)
+	var userinfo = &UserInfo{}
+	err = util.Str2Struct(result, userinfo)
+	if err != nil {
+		return nil, err
+	}
+	return userinfo, nil
+}
+
+// GetValFromRedis 从redis获取值
+func GetValFromRedis(key string) (string, error) {
+	return model.RedisGetVal(key)
+}

+ 437 - 0
server/service/workflow/taskService.go

@@ -0,0 +1,437 @@
+package service
+
+import (
+	"errors"
+	"fmt"
+	"math"
+	"server/dao"
+	"server/dao/flow"
+	"strconv"
+	"sync"
+	"time"
+
+	"github.com/jinzhu/gorm"
+
+	"github.com/mumushuiding/util"
+)
+
+// TaskReceiver 任务
+type TaskReceiver struct {
+	TaskID     int    `json:"taskID"`
+	UserID     string `json:"userID,omitempty"`
+	UserName   string `json:"username,omitempty"`
+	Pass       string `json:"pass,omitempty"`
+	Company    string `json:"company,omitempty"`
+	ProcInstID int    `json:"procInstID,omitempty"`
+	Comment    string `json:"comment,omitempty"`
+	Candidate  string `json:"candidate,omitempty"`
+}
+
+var completeLock sync.Mutex
+
+// NewTask 新任务
+func NewTask(t *dao.Task) (int, error) {
+	if len(t.NodeID) == 0 {
+		return 0, errors.New("request param nodeID can not be null / 任务当前所在节点nodeId不能为空!")
+	}
+	t.CreateTime = util.FormatDate(time.Now(), util.YYYY_MM_DD_HH_MM_SS)
+	return t.NewTask()
+}
+
+// NewTaskTx NewTaskTx
+// 开启事务
+func NewTaskTx(t *dao.Task, tx *gorm.DB) (int, error) {
+	if len(t.NodeID) == 0 {
+		return 0, errors.New("request param nodeID can not be null / 任务当前所在节点nodeId不能为空!")
+	}
+	t.CreateTime = util.FormatDate(time.Now(), util.YYYY_MM_DD_HH_MM_SS)
+	return t.NewTaskTx(tx)
+}
+
+// DeleteTask 删除任务
+func DeleteTask(id int) error {
+	return dao.DeleteTask(id)
+}
+
+// GetTaskByID GetTaskById
+func GetTaskByID(id int) (task *dao.Task, err error) {
+	return dao.GetTaskByID(id)
+}
+
+// GetTaskLastByProInstID GetTaskLastByProInstID
+func GetTaskLastByProInstID(procInstID int) (*dao.Task, error) {
+	return dao.GetTaskLastByProInstID(procInstID)
+}
+
+// CompleteByToken 通过token 审批任务
+func CompleteByToken(token string, receiver *TaskReceiver) error {
+	userinfo, err := GetUserinfoFromRedis(token)
+	if err != nil {
+		return err
+	}
+	pass, err := strconv.ParseBool(receiver.Pass)
+	if err != nil {
+		return err
+	}
+	err = Complete(receiver.TaskID, userinfo.ID, userinfo.Username, userinfo.Company, receiver.Comment, receiver.Candidate, pass)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// Complete Complete
+// 审批
+func Complete(taskID int, userID, username, company, comment, candidate string, pass bool) error {
+	tx := dao.GetTx()
+	err := CompleteTaskTx(taskID, userID, username, company, comment, candidate, pass, tx)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	tx.Commit()
+	return nil
+}
+
+// UpdateTaskWhenComplete 完成后更新任务
+func UpdateTaskWhenComplete(taskID int, userID string, pass bool, tx *gorm.DB) (*model.Task, error) {
+	// 获取task
+	completeLock.Lock()         // 关锁
+	defer completeLock.Unlock() //解锁
+	// 查询任务
+	task, err := GetTaskByID(taskID)
+	if err != nil {
+		return nil, err
+	}
+	if task == nil {
+		return nil, errors.New("任务【" + fmt.Sprintf("%d", task.ID) + "】不存在")
+	}
+	// 判断是否已经结束
+	if task.IsFinished == true {
+		if task.NodeID == "结束" {
+			return nil, errors.New("流程已经结束")
+		}
+		return nil, errors.New("任务【" + fmt.Sprintf("%d", taskID) + "】已经被审批过了!!")
+	}
+	// 设置处理人和处理时间
+	task.Assignee = userID
+	task.ClaimTime = util.FormatDate(time.Now(), util.YYYY_MM_DD_HH_MM_SS)
+	// ----------------会签 (默认全部通过才结束),只要存在一个不通过,就结束,然后流转到上一步
+	//同意
+	if pass {
+		task.AgreeNum++
+	} else {
+		task.IsFinished = true
+	}
+	// 未审批人数减一
+	task.UnCompleteNum--
+	// 判断是否结束
+	if task.UnCompleteNum == 0 {
+		task.IsFinished = true
+	}
+	err = task.UpdateTx(tx)
+	// str, _ := util.ToJSONStr(task)
+	// log.Println(str)
+	if err != nil {
+		return nil, err
+	}
+	return task, nil
+}
+
+// CompleteTaskTx CompleteTaskTx
+// 执行任务
+func CompleteTaskTx(taskID int, userID, username, company, comment, candidate string, pass bool, tx *gorm.DB) error {
+
+	//更新任务
+	task, err := UpdateTaskWhenComplete(taskID, userID, pass, tx)
+	if err != nil {
+		return err
+	}
+	// 如果是会签
+	if task.ActType == "and" {
+		// fmt.Println("------------------是会签,判断用户是否已经审批过了,避免重复审批-------")
+		// 判断用户是否已经审批过了(存在会签的情况)
+		yes, err := IfParticipantByTaskID(userID, company, taskID)
+		if err != nil {
+			tx.Rollback()
+			return err
+		}
+		if yes {
+			tx.Rollback()
+			return errors.New("您已经审批过了,请等待他人审批!)")
+		}
+	}
+
+	// 查看任务的未审批人数是否为0,不为0就不流转
+	if task.UnCompleteNum > 0 && pass == true { // 默认是全部通过
+		// 添加参与人
+		err := AddParticipantTx(userID, username, company, comment, task.ID, task.ProcInstID, task.Step, tx)
+		if err != nil {
+			return err
+		}
+		return nil
+	}
+	// 流转到下一流程
+	// nodeInfos, err := GetExecNodeInfosByProcInstID(task.ProcInstID)
+	// if err != nil {
+	// 	return err
+	// }
+	err = MoveStageByProcInstID(userID, username, company, comment, candidate, task.ID, task.ProcInstID, task.Step, pass, tx)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// WithDrawTaskByToken 撤回任务
+func WithDrawTaskByToken(token string, receiver *TaskReceiver) error {
+	userinfo, err := GetUserinfoFromRedis(token)
+	if err != nil {
+		return err
+	}
+	if len(userinfo.ID) == 0 {
+		return errors.New("保存在redis中的【用户信息 userinfo】字段 ID 不能为空!!")
+	}
+	if len(userinfo.Username) == 0 {
+		return errors.New("保存在redis中的【用户信息 userinfo】字段 username 不能为空!!")
+	}
+	if len(userinfo.Company) == 0 {
+		return errors.New("保存在redis中的【用户信息 userinfo】字段 company 不能为空")
+	}
+	return WithDrawTask(receiver.TaskID, receiver.ProcInstID, userinfo.ID, userinfo.Username, userinfo.Company, receiver.Comment)
+}
+
+// WithDrawTask 撤回任务
+func WithDrawTask(taskID, procInstID int, userID, username, company, comment string) error {
+	var err1, err2 error
+	var currentTask, lastTask *model.Task
+	var timesx time.Time
+	var wg sync.WaitGroup
+	timesx = time.Now()
+	wg.Add(2)
+	go func() {
+		currentTask, err1 = GetTaskByID(taskID)
+		wg.Done()
+	}()
+	go func() {
+		lastTask, err2 = GetTaskLastByProInstID(procInstID)
+		wg.Done()
+	}()
+	wg.Wait()
+	if err1 != nil {
+		if err1 == gorm.ErrRecordNotFound {
+			return errors.New("任务不存在")
+		}
+		return err1
+	}
+	if err2 != nil {
+		if err2 == gorm.ErrRecordNotFound {
+			return errors.New("找不到流程实例id为【" + fmt.Sprintf("%d", procInstID) + "】的任务,无权撤回")
+		}
+		return err2
+	}
+	// str1,_:=util.ToJSONStr(currentTask)
+	// str2,_:=util.ToJSONStr(lastTask)
+	// fmt.Println(str1)
+	// fmt.Println(str2)
+	if currentTask.Step == 0 {
+		return errors.New("开始位置无法撤回")
+	}
+	if lastTask.Assignee != userID {
+		return errors.New("只能撤回本人审批过的任务!!")
+	}
+	if currentTask.IsFinished {
+		return errors.New("已经审批结束,无法撤回!")
+	}
+	if currentTask.UnCompleteNum != currentTask.MemberCount {
+		return errors.New("已经有人审批过了,无法撤回!")
+	}
+	sub := currentTask.Step - lastTask.Step
+	if math.Abs(float64(sub)) != 1 {
+		return errors.New("只能撤回相邻的任务!!")
+	}
+	var pass = false
+	if sub < 0 {
+		pass = true
+	}
+	fmt.Printf("判断是否可以撤回,耗时:%v\n", time.Since(timesx))
+	timesx = time.Now()
+	tx := model.GetTx()
+	// 更新当前的任务
+	currentTask.IsFinished = true
+	err := currentTask.UpdateTx(tx)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	// 撤回
+	err = MoveStageByProcInstID(userID, username, company, comment, "", currentTask.ID, procInstID, currentTask.Step, pass, tx)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	tx.Commit()
+	fmt.Printf("撤回流程耗时:%v\n", time.Since(timesx))
+	return nil
+}
+
+// MoveStageByProcInstID MoveStageByProcInstID
+func MoveStageByProcInstID(userID, username, company, comment, candidate string, taskID, procInstID, step int, pass bool, tx *gorm.DB) (err error) {
+	nodeInfos, err := GetExecNodeInfosByProcInstID(procInstID)
+	if err != nil {
+		return err
+	}
+	return MoveStage(nodeInfos, userID, username, company, comment, candidate, taskID, procInstID, step, pass, tx)
+}
+
+// MoveStage MoveStage
+// 流程流转
+func MoveStage(nodeInfos []*flow.NodeInfo, userID, username, company, comment, candidate string, taskID, procInstID, step int, pass bool, tx *gorm.DB) (err error) {
+	// 添加上一步的参与人
+	err = AddParticipantTx(userID, username, company, comment, taskID, procInstID, step, tx)
+	if err != nil {
+		return err
+	}
+	if pass {
+		step++
+		if step-1 > len(nodeInfos) {
+			return errors.New("已经结束无法流转到下一个节点")
+		}
+	} else {
+		step--
+		if step < 0 {
+			return errors.New("处于开始位置,无法回退到上一个节点")
+		}
+	}
+	// 指定下一步执行人
+	if len(candidate) > 0 {
+		nodeInfos[step].Aprover = candidate
+	}
+	// 判断下一流程: 如果是审批人是:抄送人
+	// fmt.Printf("下一审批人类型:%s\n", nodeInfos[step].AproverType)
+	// fmt.Println(nodeInfos[step].AproverType == flow.NodeTypes[flow.NOTIFIER])
+	if nodeInfos[step].AproverType == flow.NodeTypes[flow.NOTIFIER] {
+		// 生成新的任务
+		var task = model.Task{
+			NodeID:     flow.NodeTypes[flow.NOTIFIER],
+			Step:       step,
+			ProcInstID: procInstID,
+			IsFinished: true,
+		}
+		task.IsFinished = true
+		_, err := task.NewTaskTx(tx)
+		if err != nil {
+			return err
+		}
+		// 添加抄送人
+		err = AddNotifierTx(nodeInfos[step].Aprover, company, step, procInstID, tx)
+		if err != nil {
+			return err
+		}
+		return MoveStage(nodeInfos, userID, username, company, comment, candidate, taskID, procInstID, step, pass, tx)
+	}
+	if pass {
+		return MoveToNextStage(nodeInfos, userID, company, taskID, procInstID, step, tx)
+	}
+	return MoveToPrevStage(nodeInfos, userID, company, taskID, procInstID, step, tx)
+}
+
+// MoveToNextStage MoveToNextStage
+// 通过
+func MoveToNextStage(nodeInfos []*flow.NodeInfo, userID, company string, currentTaskID, procInstID, step int, tx *gorm.DB) error {
+	var currentTime = util.FormatDate(time.Now(), util.YYYY_MM_DD_HH_MM_SS)
+	var task = getNewTask(nodeInfos, step, procInstID, currentTime) //新任务
+	var procInst = &model.ProcInst{                                 // 流程实例要更新的字段
+		NodeID:    nodeInfos[step].NodeID,
+		Candidate: nodeInfos[step].Aprover,
+	}
+	procInst.ID = procInstID
+	if (step + 1) != len(nodeInfos) { // 下一步不是【结束】
+		// 生成新的任务
+		taksID, err := task.NewTaskTx(tx)
+		if err != nil {
+			return err
+		}
+		// 添加candidate group
+		err = AddCandidateGroupTx(nodeInfos[step].Aprover, company, step, taksID, procInstID, tx)
+		if err != nil {
+			return err
+		}
+		// 更新流程实例
+		procInst.TaskID = taksID
+		err = UpdateProcInst(procInst, tx)
+		if err != nil {
+			return err
+		}
+	} else { // 最后一步直接结束
+		// 生成新的任务
+		task.IsFinished = true
+		task.ClaimTime = currentTime
+		taksID, err := task.NewTaskTx(tx)
+		if err != nil {
+			return err
+		}
+		// 删除候选用户组
+		err = DelCandidateByProcInstID(procInstID, tx)
+		if err != nil {
+			return err
+		}
+		// 更新流程实例
+		procInst.TaskID = taksID
+		procInst.EndTime = currentTime
+		procInst.IsFinished = true
+		procInst.Candidate = "审批结束"
+		err = UpdateProcInst(procInst, tx)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// MoveToPrevStage MoveToPrevStage
+// 驳回
+func MoveToPrevStage(nodeInfos []*flow.NodeInfo, userID, company string, currentTaskID, procInstID, step int, tx *gorm.DB) error {
+	// 生成新的任务
+	var task = getNewTask(nodeInfos, step, procInstID, util.FormatDate(time.Now(), util.YYYY_MM_DD_HH_MM_SS)) //新任务
+	taksID, err := task.NewTaskTx(tx)
+	if err != nil {
+		return err
+	}
+	var procInst = &model.ProcInst{ // 流程实例要更新的字段
+		NodeID:    nodeInfos[step].NodeID,
+		Candidate: nodeInfos[step].Aprover,
+		TaskID:    taksID,
+	}
+	procInst.ID = procInstID
+	err = UpdateProcInst(procInst, tx)
+	if err != nil {
+		return err
+	}
+	if step == 0 { // 流程回到起始位置,注意起始位置为0,
+		err = AddCandidateUserTx(nodeInfos[step].Aprover, company, step, taksID, procInstID, tx)
+		if err != nil {
+			return err
+		}
+		return nil
+	}
+	// 添加candidate group
+	err = AddCandidateGroupTx(nodeInfos[step].Aprover, company, step, taksID, procInstID, tx)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+func getNewTask(nodeInfos []*flow.NodeInfo, step, procInstID int, currentTime string) *model.Task {
+	var task = &model.Task{ // 新任务
+		NodeID:        nodeInfos[step].NodeID,
+		Step:          step,
+		CreateTime:    currentTime,
+		ProcInstID:    procInstID,
+		MemberCount:   nodeInfos[step].MemberCount,
+		UnCompleteNum: nodeInfos[step].MemberCount,
+		ActType:       nodeInfos[step].ActType,
+	}
+	return task
+}