Files
huangjin fe3ad20fe2 'init'
2026-06-29 17:34:59 +08:00

557 lines
15 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 工作流引擎核心逻辑
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
}