'init'
This commit is contained in:
556
sc-lktx-backend/internal/modules/workflow/engine.go
Normal file
556
sc-lktx-backend/internal/modules/workflow/engine.go
Normal file
@@ -0,0 +1,556 @@
|
||||
// 工作流引擎核心逻辑
|
||||
package workflow
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"com.sclktx/m/v2/internal/common/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Engine 工作流引擎
|
||||
type Engine struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewEngine 创建工作流引擎
|
||||
func NewEngine(db *gorm.DB) *Engine {
|
||||
return &Engine{db: db}
|
||||
}
|
||||
|
||||
// Node 节点配置(从 nodes_json 解析)
|
||||
type Node struct {
|
||||
NodeID string `json:"node_id"`
|
||||
NodeName string `json:"node_name"`
|
||||
NodeType int `json:"node_type"` // 0-开始 1-审批 2-网关 3-结束
|
||||
PrevNodeIDs []string `json:"prev_node_ids"`
|
||||
UserIDs []string `json:"user_ids"`
|
||||
Roles []string `json:"roles"`
|
||||
GwConfig *GatewayConfig `json:"gw_config"`
|
||||
IsCosigned int `json:"is_cosigned"` // 0-否 1-会签
|
||||
NodeStartEvents []string `json:"node_start_events"`
|
||||
NodeEndEvents []string `json:"node_end_events"`
|
||||
TaskFinishEvents []string `json:"task_finish_events"`
|
||||
}
|
||||
|
||||
// GatewayConfig 网关配置
|
||||
type GatewayConfig struct {
|
||||
Conditions []Condition `json:"conditions"`
|
||||
InevitableNodes []string `json:"inevitable_nodes"`
|
||||
WaitForAllPrevNode int `json:"wait_for_all_prev_node"` // 0-否 1-是
|
||||
}
|
||||
|
||||
// Condition 条件分支
|
||||
type Condition struct {
|
||||
Expression string `json:"expression"`
|
||||
NodeID string `json:"node_id"`
|
||||
}
|
||||
|
||||
// parseNodes 解析流程定义的节点配置
|
||||
func (e *Engine) parseNodes(def *model.ProcessDefinition) ([]Node, error) {
|
||||
if def.NodesJSON == "" {
|
||||
return nil, errors.New("流程节点配置为空")
|
||||
}
|
||||
var nodes []Node
|
||||
if err := json.Unmarshal([]byte(def.NodesJSON), &nodes); err != nil {
|
||||
return nil, fmt.Errorf("解析节点配置失败: %w", err)
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// findStartNode 查找开始节点
|
||||
func findStartNode(nodes []Node) *Node {
|
||||
for i := range nodes {
|
||||
if nodes[i].NodeType == 0 {
|
||||
return &nodes[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// findNextNodes 查找下一个节点
|
||||
func findNextNodes(nodes []Node, currentNodeID string) []Node {
|
||||
var next []Node
|
||||
for _, n := range nodes {
|
||||
for _, prevID := range n.PrevNodeIDs {
|
||||
if prevID == currentNodeID {
|
||||
next = append(next, n)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
// findNodeByID 根据节点 ID 查找节点
|
||||
func findNodeByID(nodes []Node, nodeID string) *Node {
|
||||
for i := range nodes {
|
||||
if nodes[i].NodeID == nodeID {
|
||||
return &nodes[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseVariables 解析流程实例中的变量 JSON
|
||||
func (e *Engine) parseVariables(variablesJSON string) map[string]interface{} {
|
||||
var vars map[string]interface{}
|
||||
if variablesJSON != "" {
|
||||
json.Unmarshal([]byte(variablesJSON), &vars)
|
||||
}
|
||||
if vars == nil {
|
||||
vars = make(map[string]interface{})
|
||||
}
|
||||
return vars
|
||||
}
|
||||
|
||||
|
||||
// StartInstanceByCode 启动流程实例(按流程编码)
|
||||
func (e *Engine) StartInstanceByCode(processCode string, businessType string, businessID uint, starterID uint, variables map[string]interface{}) (*model.ProcessInstance, error) {
|
||||
var def model.ProcessDefinition
|
||||
if err := e.db.Where("process_code = ?", processCode).First(&def).Error; err != nil {
|
||||
return nil, fmt.Errorf("流程定义不存在: %s", processCode)
|
||||
}
|
||||
return e.StartInstance(def.ID, businessType, businessID, starterID, variables)
|
||||
}
|
||||
func (e *Engine) StartInstance(defID uint, businessType string, businessID uint, starterID uint, variables map[string]interface{}) (*model.ProcessInstance, error) {
|
||||
// 查询流程定义
|
||||
var def model.ProcessDefinition
|
||||
if err := e.db.First(&def, defID).Error; err != nil {
|
||||
return nil, errors.New("流程定义不存在")
|
||||
}
|
||||
if !def.IsActive {
|
||||
return nil, errors.New("流程定义已禁用")
|
||||
}
|
||||
|
||||
// 解析节点配置
|
||||
nodes, err := e.parseNodes(&def)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 查找开始节点
|
||||
startNode := findStartNode(nodes)
|
||||
if startNode == nil {
|
||||
return nil, errors.New("流程定义缺少开始节点")
|
||||
}
|
||||
|
||||
// 序列化变量
|
||||
variablesJSON := "{}"
|
||||
if variables != nil {
|
||||
b, _ := json.Marshal(variables)
|
||||
variablesJSON = string(b)
|
||||
}
|
||||
|
||||
// 创建流程实例
|
||||
instance := &model.ProcessInstance{
|
||||
ProcessDefID: def.ID,
|
||||
BusinessType: businessType,
|
||||
BusinessID: businessID,
|
||||
Status: 0, // 运行中
|
||||
StarterID: starterID,
|
||||
CurrentNodeID: startNode.NodeID,
|
||||
Variables: variablesJSON,
|
||||
}
|
||||
|
||||
if err := e.db.Create(instance).Error; err != nil {
|
||||
return nil, fmt.Errorf("创建流程实例失败: %w", err)
|
||||
}
|
||||
|
||||
// 流转到下一个节点
|
||||
if err := e.moveToNextNodes(instance, nodes, startNode.NodeID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
// moveToNextNodes 流转到下一个节点,创建任务(使用默认 DB)
|
||||
func (e *Engine) moveToNextNodes(instance *model.ProcessInstance, nodes []Node, currentNodeID string) error {
|
||||
return e.moveToNextNodesTx(e.db, instance, nodes, currentNodeID)
|
||||
}
|
||||
|
||||
// moveToNextNodesTx 流转到下一个节点(支持外部事务)
|
||||
func (e *Engine) moveToNextNodesTx(tx *gorm.DB, instance *model.ProcessInstance, nodes []Node, currentNodeID string) error {
|
||||
nextNodes := findNextNodes(nodes, currentNodeID)
|
||||
|
||||
// 如果没有下一个节点(到达结束节点),标记流程完成
|
||||
if len(nextNodes) == 0 {
|
||||
now := time.Now()
|
||||
if err := tx.Model(instance).Updates(map[string]interface{}{
|
||||
"status": 1, // 已完成
|
||||
"finished_at": now,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return e.updateAppointmentStatus(tx, instance, 1)
|
||||
}
|
||||
|
||||
// 为每个下一个节点创建任务
|
||||
for _, node := range nextNodes {
|
||||
switch node.NodeType {
|
||||
case 1: // 审批节点
|
||||
if err := e.createApprovalTasksTx(tx, instance, nodes, &node); err != nil {
|
||||
return err
|
||||
}
|
||||
case 3: // 结束节点
|
||||
now := time.Now()
|
||||
if err := tx.Model(instance).Updates(map[string]interface{}{
|
||||
"status": 1,
|
||||
"finished_at": now,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return e.updateAppointmentStatus(tx, instance, 1)
|
||||
case 2: // 网关节点 - 自动判断条件
|
||||
if err := e.handleGatewayNodeTx(tx, instance, nodes, &node); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新当前节点
|
||||
if len(nextNodes) > 0 {
|
||||
tx.Model(instance).Update("current_node_id", nextNodes[0].NodeID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleGatewayNodeTx 处理网关节点(支持外部事务)
|
||||
func (e *Engine) handleGatewayNodeTx(tx *gorm.DB, instance *model.ProcessInstance, nodes []Node, gwNode *Node) error {
|
||||
if gwNode.GwConfig == nil {
|
||||
return e.moveToNextNodesTx(tx, instance, nodes, gwNode.NodeID)
|
||||
}
|
||||
|
||||
// 解析流程变量
|
||||
var variables map[string]interface{}
|
||||
if instance.Variables != "" {
|
||||
json.Unmarshal([]byte(instance.Variables), &variables)
|
||||
}
|
||||
if variables == nil {
|
||||
variables = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// 评估条件
|
||||
for _, cond := range gwNode.GwConfig.Conditions {
|
||||
if evaluateCondition(cond.Expression, variables) {
|
||||
return e.moveToNextNodesTx(tx, instance, nodes, cond.NodeID)
|
||||
}
|
||||
}
|
||||
|
||||
// 没有条件满足,走必经节点
|
||||
if len(gwNode.GwConfig.InevitableNodes) > 0 {
|
||||
return e.moveToNextNodesTx(tx, instance, nodes, gwNode.GwConfig.InevitableNodes[0])
|
||||
}
|
||||
|
||||
return e.moveToNextNodesTx(tx, instance, nodes, gwNode.NodeID)
|
||||
}
|
||||
|
||||
// createApprovalTasks 为审批节点创建任务(使用默认 DB)
|
||||
func (e *Engine) createApprovalTasks(instance *model.ProcessInstance, nodes []Node, node *Node) error {
|
||||
return e.createApprovalTasksTx(e.db, instance, nodes, node)
|
||||
}
|
||||
|
||||
// createApprovalTasksTx 为审批节点创建任务(支持外部事务)
|
||||
func (e *Engine) createApprovalTasksTx(tx *gorm.DB, instance *model.ProcessInstance, nodes []Node, node *Node) error {
|
||||
// 解析审批人:优先使用 user_ids,其次使用 roles
|
||||
var assigneeIDs []uint
|
||||
variables := e.parseVariables(instance.Variables)
|
||||
|
||||
if len(node.UserIDs) > 0 {
|
||||
for _, uidStr := range node.UserIDs {
|
||||
if strings.HasPrefix(uidStr, "$") {
|
||||
// $变量名 → 从流程变量中取值
|
||||
varName := uidStr[1:]
|
||||
if val, ok := variables[varName]; ok {
|
||||
switch v := val.(type) {
|
||||
case float64:
|
||||
assigneeIDs = append(assigneeIDs, uint(v))
|
||||
case uint:
|
||||
assigneeIDs = append(assigneeIDs, v)
|
||||
case int:
|
||||
assigneeIDs = append(assigneeIDs, uint(v))
|
||||
case string:
|
||||
// 先尝试解析为单个 ID
|
||||
if id, err := strconv.ParseUint(v, 10, 64); err == nil {
|
||||
assigneeIDs = append(assigneeIDs, uint(id))
|
||||
} else {
|
||||
// 再尝试解析为 JSON 数组(如 "[1,2,3]")
|
||||
var ids []uint
|
||||
if err := json.Unmarshal([]byte(v), &ids); err == nil {
|
||||
assigneeIDs = append(assigneeIDs, ids...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 静态数字 ID
|
||||
var uid uint
|
||||
if _, err := fmt.Sscanf(uidStr, "%d", &uid); err == nil {
|
||||
assigneeIDs = append(assigneeIDs, uid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(assigneeIDs) == 0 && len(node.Roles) > 0 {
|
||||
// 根据角色编码查找用户
|
||||
var users []model.User
|
||||
tx.Where("role IN ?", node.Roles).Find(&users)
|
||||
for _, u := range users {
|
||||
assigneeIDs = append(assigneeIDs, u.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(assigneeIDs) == 0 {
|
||||
// 没有找到审批人 — 跳过此节点,自动流转到下一节点(适用于未配置分管领导等情况)
|
||||
return e.moveToNextNodesTx(tx, instance, nodes, node.NodeID)
|
||||
}
|
||||
|
||||
// 为每个审批人创建任务
|
||||
for _, assigneeID := range assigneeIDs {
|
||||
task := &model.Task{
|
||||
InstanceID: instance.ID,
|
||||
NodeID: node.NodeID,
|
||||
NodeName: node.NodeName,
|
||||
Status: 0, // 待处理
|
||||
AssigneeID: &assigneeID,
|
||||
}
|
||||
if err := tx.Create(task).Error; err != nil {
|
||||
return fmt.Errorf("创建任务失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleGatewayNode 处理网关节点
|
||||
func (e *Engine) handleGatewayNode(instance *model.ProcessInstance, nodes []Node, gwNode *Node) error {
|
||||
if gwNode.GwConfig == nil {
|
||||
return e.moveToNextNodes(instance, nodes, gwNode.NodeID)
|
||||
}
|
||||
|
||||
// 解析流程变量
|
||||
var variables map[string]interface{}
|
||||
if instance.Variables != "" {
|
||||
json.Unmarshal([]byte(instance.Variables), &variables)
|
||||
}
|
||||
if variables == nil {
|
||||
variables = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// 评估条件
|
||||
for _, cond := range gwNode.GwConfig.Conditions {
|
||||
if evaluateCondition(cond.Expression, variables) {
|
||||
return e.moveToNextNodes(instance, nodes, cond.NodeID)
|
||||
}
|
||||
}
|
||||
|
||||
// 没有条件满足,走必经节点
|
||||
if len(gwNode.GwConfig.InevitableNodes) > 0 {
|
||||
return e.moveToNextNodes(instance, nodes, gwNode.GwConfig.InevitableNodes[0])
|
||||
}
|
||||
|
||||
return e.moveToNextNodes(instance, nodes, gwNode.NodeID)
|
||||
}
|
||||
|
||||
// evaluateCondition 简单条件评估(支持 $days>=3 这样的表达式)
|
||||
func evaluateCondition(expression string, variables map[string]interface{}) bool {
|
||||
// 简单实现:解析 "$key op value" 格式
|
||||
var key string
|
||||
var op string
|
||||
var expected float64
|
||||
n, _ := fmt.Sscanf(expression, "$%s %s %f", &key, &op, &expected)
|
||||
if n != 3 {
|
||||
return false
|
||||
}
|
||||
|
||||
val, ok := variables[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
var numVal float64
|
||||
switch v := val.(type) {
|
||||
case float64:
|
||||
numVal = v
|
||||
case int:
|
||||
numVal = float64(v)
|
||||
case json.Number:
|
||||
numVal, _ = v.Float64()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
switch op {
|
||||
case ">=":
|
||||
return numVal >= expected
|
||||
case ">":
|
||||
return numVal > expected
|
||||
case "<=":
|
||||
return numVal <= expected
|
||||
case "<":
|
||||
return numVal < expected
|
||||
case "==":
|
||||
return numVal == expected
|
||||
case "!=":
|
||||
return numVal != expected
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ApproveTask 审批通过任务
|
||||
func (e *Engine) ApproveTask(taskID uint, userID uint, comment string) error {
|
||||
var task model.Task
|
||||
if err := e.db.First(&task, taskID).Error; err != nil {
|
||||
return errors.New("任务不存在")
|
||||
}
|
||||
if task.Status != 0 {
|
||||
return errors.New("任务已处理")
|
||||
}
|
||||
// 验证审批人身份
|
||||
if task.AssigneeID == nil || *task.AssigneeID != userID {
|
||||
return errors.New("您不是该任务的审批人")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
// 开启事务,保证一致性
|
||||
tx := e.db.Begin()
|
||||
|
||||
if err := tx.Model(&task).Updates(map[string]interface{}{
|
||||
"status": 1, // 已完成
|
||||
"comment": comment,
|
||||
"handled_at": now,
|
||||
}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("更新任务失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取实例和流程定义
|
||||
var instance model.ProcessInstance
|
||||
if err := tx.First(&instance, task.InstanceID).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
var def model.ProcessDefinition
|
||||
if err := tx.First(&def, instance.ProcessDefID).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
nodes, err := e.parseNodes(&def)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
// 查找当前节点的定义
|
||||
currentNode := findNodeByID(nodes, task.NodeID)
|
||||
needAllApprovers := currentNode != nil && currentNode.IsCosigned == 1
|
||||
|
||||
if needAllApprovers {
|
||||
// 会签模式(is_cosigned=1):需要所有任务都完成
|
||||
var pendingCount int64
|
||||
tx.Model(&model.Task{}).
|
||||
Where("instance_id = ? AND node_id = ? AND status = 0", task.InstanceID, task.NodeID).
|
||||
Count(&pendingCount)
|
||||
if pendingCount > 0 {
|
||||
tx.Commit()
|
||||
return nil // 等待其他审批人
|
||||
}
|
||||
}
|
||||
// 非会签模式(is_cosigned=0 或不设置):一人通过即流转下一节点
|
||||
// 取消本节点其他待处理任务,防止并发重复创建下一节点
|
||||
if !needAllApprovers {
|
||||
tx.Model(&model.Task{}).
|
||||
Where("instance_id = ? AND node_id = ? AND status = 0 AND id != ?", task.InstanceID, task.NodeID, task.ID).
|
||||
Update("status", 3) // 已取消
|
||||
}
|
||||
|
||||
// 流转到下一个节点(在同一事务中)
|
||||
if err := e.moveToNextNodesTx(tx, &instance, nodes, task.NodeID); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit().Error
|
||||
}
|
||||
|
||||
// RejectTask 审批拒绝任务
|
||||
func (e *Engine) RejectTask(taskID uint, userID uint, comment string) error {
|
||||
var task model.Task
|
||||
if err := e.db.First(&task, taskID).Error; err != nil {
|
||||
return errors.New("任务不存在")
|
||||
}
|
||||
if task.Status != 0 {
|
||||
return errors.New("任务已处理")
|
||||
}
|
||||
// 验证审批人身份
|
||||
if task.AssigneeID == nil || *task.AssigneeID != userID {
|
||||
return errors.New("您不是该任务的审批人")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
tx := e.db.Begin()
|
||||
|
||||
if err := tx.Model(&task).Updates(map[string]interface{}{
|
||||
"status": 2, // 已拒绝
|
||||
"comment": comment,
|
||||
"handled_at": now,
|
||||
}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("更新任务失败: %w", err)
|
||||
}
|
||||
|
||||
// 终止流程实例
|
||||
var instance model.ProcessInstance
|
||||
if err := tx.First(&instance, task.InstanceID).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&instance).Updates(map[string]interface{}{
|
||||
"status": 2, // 已终止
|
||||
"finished_at": now,
|
||||
}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新业务状态
|
||||
if err := e.updateAppointmentStatus(tx, &instance, 2); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit().Error
|
||||
}
|
||||
|
||||
// checkNodeCompletion 检查节点所有任务是否完成
|
||||
func (e *Engine) checkNodeCompletion(instanceID uint, nodeID string) (*Node, bool, error) {
|
||||
// 检查是否还有待处理的任务
|
||||
var pendingCount int64
|
||||
e.db.Model(&model.Task{}).
|
||||
Where("instance_id = ? AND node_id = ? AND status = 0", instanceID, nodeID).
|
||||
Count(&pendingCount)
|
||||
|
||||
return nil, pendingCount == 0, nil
|
||||
}
|
||||
|
||||
// updateAppointmentStatus 工作流完成后更新业务表状态
|
||||
func (e *Engine) updateAppointmentStatus(tx *gorm.DB, instance *model.ProcessInstance, apptStatus int) error {
|
||||
if instance.BusinessType != "appointment" {
|
||||
return nil // 只处理预约业务
|
||||
}
|
||||
return tx.Model(&model.Appointment{}).Where("id = ?", instance.BusinessID).Updates(map[string]interface{}{
|
||||
"status": apptStatus,
|
||||
}).Error
|
||||
}
|
||||
Reference in New Issue
Block a user