This commit is contained in:
huangjin
2026-06-29 17:34:59 +08:00
commit fe3ad20fe2
271 changed files with 51767 additions and 0 deletions

View 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
}