# goFlow **Repository Path**: iotplc/go-flow ## Basic Information - **Project Name**: goFlow - **Description**: No description available - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-11 - **Last Updated**: 2026-04-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # GoFlow GoFlow 是一个轻量级、可嵌入的 Go 工作流引擎,采用**纯 JSON 流程定义**(非 BPMN XML),开箱即用、零业务耦合。 ## 特性 - **13 种节点类型** — start(开始) / approval(审批) / cc(抄送) / exclusive(互斥网关) / inclusive(包容网关) / parallel(并行网关) / condition(条件) / branch(分支) / timer(计时等待) / notify(消息通知) / service(服务) / jump(跳转) / end(结束) - **3 种网关** — 互斥 (exclusive)、包容 (inclusive)、并行 (parallel),支持任意嵌套 - **9 种指派方式** — user(指定用户) / role(指定角色) / choice(发起人自选) / self(发起人本人) / leader(主管) / orgLeader(组织负责人) / formUser(表单用户) / formRole(表单角色) / deptAttendance(部门考勤负责人) - **多人审批** — sequential(依次审批)、joint(会签,支持百分比)、single(或签) - **审批超时** — 可配置超时时间和超时动作(自动通过/自动拒绝) - **自动审批规则** — 支持 filter 规则和 JavaScript 脚本两种条件模式 - **条件评估** — 基于 [expr](https://github.com/expr-lang/expr) 表达式引擎,内置 contains / startsWith / isEmpty / dateBetween 等函数 - **脚本执行** — 服务节点与脚本条件通过 SPI 接口适配任意 JS 引擎(goja、v8go 等) - **事务安全** — 所有写操作在单一事务内完成,支持外部事务注入(业务数据与引擎数据同一事务) - **零业务耦合** — 通过 SPI 接口注入身份、通知、调度、存储等外部依赖 - **Option 模式** — 构造引擎时按需注入所需服务,最少只需一个 `Store` - **REST API 适配器** — `contrib/gin` 提供开箱即用的 Gin HTTP 端点(流程定义 / 引擎操作 / 查询),集成方 4 行代码即可拥有完整工作流 REST API ## 快速开始 ### 安装 ```bash go get gitee.com/iotplc/go-flow go get https://github.com/zhuqiyun/go-flow ``` ### 最简示例 ```go package main import ( "log" "gitee.com/iotplc/go-flow/engine" gormstore "gitee.com/iotplc/go-flow/contrib/gorm" "gorm.io/driver/mysql" "gorm.io/gorm" ) func main() { // 1. 初始化数据库 db, err := gorm.Open(mysql.Open("dsn"), &gorm.Config{}) // dsn 格式参考 gorm 文档 if err != nil { log.Fatal(err) } // 2. 创建 Store 并自动建表 store := gormstore.NewStore(db) store.AutoMigrate() // 3. 创建引擎(仅 Store 为必选) eng := engine.NewFlowEngine(store) // 4. 启动流程 instance, err := eng.StartFlow( "flow-001", // 流程定义 ID(需提前写入 flow_definitions 表) 1, // 版本号 "order-123", // 业务主键(关联你的业务记录) "user-001", // 发起人 ID map[string]interface{}{ // 表单数据(用于条件判断、通知内容等) "amount": 15000, "reason": "设备采购", }, nil, // 自选审批人(assigneeType=choice 时传入) ) if err != nil { log.Fatal(err) } log.Printf("流程已启动: %s", instance.ID) // 5. 查询待办任务 tasks, _ := eng.GetTasksByAssignee("user-002", "1") // 查询 user-002 的待处理任务 if len(tasks) > 0 { // 6. 审批通过 err = eng.CompleteTask( tasks[0].ID, // 任务 ID "complete", // 动作:complete=通过, refuse=拒绝, back=回退 "同意采购", // 审批意见 "user-002", // 操作人 ID "", // 附件(JSON 字符串,可为空) "", // 更新的表单数据(JSON 字符串,可为空) ) if err != nil { log.Fatal(err) } } } ``` ### 完整初始化(注入所有服务) ```go eng := engine.NewFlowEngine( store, engine.WithIdentity(myIdentityService), // 身份解析(role/leader 等指派方式需要) engine.WithNotify(myNotifyService), // 消息通知(notify 节点需要) engine.WithScheduler(myScheduler), // 定时任务(timer 节点 + 审批超时需要) engine.WithScript(myScriptExecutor), // JS 脚本(service 节点 + 脚本条件需要) engine.WithLogger(myLogger), // 日志输出(默认不输出日志) ) ``` > **按需注入**:如果你的流程不用定时节点和审批超时,就不需要注入 Scheduler;如果不用 JS 脚本,就不需要 ScriptExecutor。只有 Store 是必须的。 ### 配合 REST API 适配器 如果你使用 Gin 框架,可以直接使用 `contrib/gin` 获得完整的工作流 REST API(详见 [REST API 适配器](#rest-api-适配器-contribgin) 章节): ```go import gincontrib "gitee.com/iotplc/go-flow/contrib/gin" wfHandler := gincontrib.NewWorkflowHandler(eng, store, db, myIdentityService) wfHandler.RegisterRoutes(router.Group("/api")) ``` ## 架构设计 ``` ┌──────────────────────────────────────────────────────┐ │ Your Application │ ├──────────────────────────────────────────────────────┤ │ engine.FlowEngine │ │ ┌────────┐ ┌─────────┐ ┌─────────┐ ┌────────────┐ │ │ │ Parser │ │Executor │ │Handlers │ │ Gateway │ │ │ └────────┘ └─────────┘ └─────────┘ └────────────┘ │ │ ┌─────────────────┐ ┌──────────────────────────┐ │ │ │ Condition │ │ engine/types.go │ │ │ └─────────────────┘ └──────────────────────────┘ │ ├──────────────────────────────────────────────────────┤ │ spi (interfaces) │ │ Store │ IdentityService │ NotifyService │ Scheduler │ │ ScriptExecutor │ Logger │ ├──────────────────────────────────────────────────────┤ │ contrib (optional) │ │ gorm/Store │ gocron/Scheduler │ goja/ScriptExecutor │ │ mail/Notifier │ zap/Logger │ gin/Handler │ └──────────────────────────────────────────────────────┘ ``` ## 项目结构 ``` goflow/ ├── spi/ # SPI 接口层(使用者需实现) │ ├── model.go # 存储模型 & 状态枚举 │ ├── store.go # Store 数据操作接口 │ ├── identity.go # IdentityService 身份解析接口 │ ├── notify.go # NotifyService 通知服务接口 │ ├── scheduler.go # Scheduler 定时调度接口 │ ├── script.go # ScriptExecutor 脚本执行接口 │ └── logger.go # Logger 日志接口 & NopLogger ├── engine/ # 核心引擎(无外部依赖*) │ ├── types.go # FlowNode、NodeType 等类型 │ ├── parser.go # JSON 流程定义解析器 │ ├── engine.go # FlowEngine 主体 + 全部 API │ ├── executor.go # 节点执行调度器 │ ├── handlers.go # 节点处理器 + 审批人解析 │ ├── condition.go # 条件评估器(expr) │ └── gateway.go # 网关处理器 └── contrib/ # 可选的 SPI 内置实现 ├── gin/ # Gin REST API 适配器 │ ├── handler.go # WorkflowHandler + 路由注册 │ ├── engine_api.go # 引擎操作 API(启动/审批/暂停/催办等) │ ├── query_api.go # 查询 API(实例/任务/日志/网关分支) │ ├── definition_api.go # 流程定义管理 API(CRUD/发布/启停) │ └── response.go # 响应工具(分页参数解析等) ├── gorm/store.go # GORM Store(MySQL/PostgreSQL/SQLite) ├── gocron/scheduler.go # gocron v2 定时调度器 ├── goja/script.go # goja JS 脚本执行器 ├── mail/notifier.go # go-mail 邮件通知 └── zap/logger.go # zap + lumberjack 日志 ``` > \* engine 包仅依赖 `github.com/expr-lang/expr` 和 `github.com/google/uuid` ## API 参考 ### 流程生命周期 #### `StartFlow` 启动流程 ```go func (e *FlowEngine) StartFlow( flowDefID string, // 流程定义 ID(flow_definitions 表的主键) flowVersion int32, // 流程版本号 businessKey string, // 业务主键(关联你的业务记录,如订单ID) initiator string, // 发起人用户 ID formData map[string]interface{}, // 表单数据(用于条件判断、自动审批、通知内容等) choiceAssignees []string, // 自选审批人(assigneeType=choice 时传入,其他时传 nil) ) (*spi.FlowInstance, error) ``` > formData 会存入流程上下文,后续的条件节点、自动审批规则、formUser/formRole 指派等都会从中读取字段。 #### `StopFlow` 终止流程 ```go func (e *FlowEngine) StopFlow( instanceID string, // 流程实例 ID operator string, // 操作人 ID reason string, // 终止原因 ) error ``` > 只能终止状态为“运行中”的流程。终止时会取消所有待处理任务并清理定时器。 #### `SuspendFlow` 暂停流程 ```go func (e *FlowEngine) SuspendFlow(instanceID string, operator string, reason string) error ``` > 暂停后:任务不可操作、定时器全部取消、超时不计时。可通过 ResumeFlow 恢复。 #### `ResumeFlow` 恢复流程 ```go func (e *FlowEngine) ResumeFlow(instanceID string, operator string) error ``` > 恢复时自动重算所有待处理任务的超时时间(暂停期间不消耗超时),并重新注册定时器。 #### `RevokeFlow` 撤回流程 ```go func (e *FlowEngine) RevokeFlow(instanceID string, initiator string, reason string) error ``` > 只有发起人可以撤回,且仅当还没有任务被处理过时才能撤回。流程定义需要 `enableWithdraw` = "1"。 --- ### 任务操作 #### `CompleteTask` 完成任务 ```go func (e *FlowEngine) CompleteTask( taskID string, // 任务 ID action string, // 动作:"complete"(通过)/ "refuse"(拒绝)/ "back"(回退) comment string, // 审批意见 operator string, // 操作人 ID attachments string, // 附件(JSON 格式,可为空字符串) formData string, // 更新的表单数据(JSON 格式,可为空字符串) ) error ``` **action 取值说明:** | action | 含义 | 后续行为 | |--------|------|----------| | `complete` | 审批通过 | 会签模式下等全部通过,或签模式下直接流转到下一节点 | | `refuse` | 拒绝 | 终止整个流程,实例状态变为“已终止” | | `back` | 回退 | 取消当前节点所有任务,跳回上一个审批节点重新创建任务 | #### `BatchCompleteTask` 批量完成任务 ```go func (e *FlowEngine) BatchCompleteTask( taskIDs []string, // 任务 ID 列表 action string, // 动作(同 CompleteTask) comment string, // 审批意见 operator string, // 操作人 ID attachments string, // 附件 ) []BatchTaskResult ``` > 返回每个任务的处理结果,部分失败不影响其他任务。 #### `DelegateTask` 委派任务 ```go func (e *FlowEngine) DelegateTask( taskID string, // 任务 ID operator string, // 当前处理人 delegateTo string, // 委派给谁 reason string, // 委派原因 ) error ``` > 委派后,被委派人审批完成后任务自动回到原处理人手中。需要流程定义 `enableDelegation` = "1" 且节点 `operations.delegate` = true。 #### `TransferTask` 转交任务 ```go func (e *FlowEngine) TransferTask( taskID string, // 任务 ID operator string, // 当前处理人 transferTo string, // 转交给谁 reason string, // 转交原因 ) error ``` > 转交后,原任务标记为“已转交”,为新处理人创建新任务。与委派的区别:转交后不回来。 #### `AddMultiTask` 加签 ```go func (e *FlowEngine) AddMultiTask( taskID string, // 任务 ID operator string, // 操作人 addUsers []string, // 要加签的用户 ID 列表 reason string, // 加签原因 ) error ``` > 为当前审批节点追加新的审批人,他们会立即收到任务。需要节点 `operations.addMulti` = true。 #### `MinusMultiTask` 减签 ```go func (e *FlowEngine) MinusMultiTask( taskID string, // 任务 ID operator string, // 操作人 removeUser string, // 要移除的用户 ID reason string, // 减签原因 ) error ``` > 移除指定用户的待处理任务。需要节点 `operations.minusMulti` = true。 #### `UrgeFlow` 催办 ```go func (e *FlowEngine) UrgeFlow(instanceID string, operator string) ([]map[string]interface{}, error) ``` > 向当前节点所有待处理任务的审批人发送催办通知,返回被催办人列表(含 `assignee`、`assignee_name`、`node_id`)。需要注入 NotifyService。 --- ### 查询 #### `GetInstanceByID` 查询流程实例 ```go func (e *FlowEngine) GetInstanceByID(instanceID string) (*spi.FlowInstance, error) ``` #### `GetTasksByAssignee` 查询用户任务列表 ```go func (e *FlowEngine) GetTasksByAssignee( assignee string, // 用户 ID status string, // 任务状态过滤("1"=待处理,空字符串=全部) ) ([]*spi.FlowTask, error) ``` > 典型用法:查询“我的待办”传 status="1",查询“我的已办”传 status="" 再在业务层过滤。 #### `GetInstanceLogs` 查询流程执行日志 ```go func (e *FlowEngine) GetInstanceLogs(instanceID string) ([]*spi.FlowNodeLog, error) ``` > 返回该流程实例的全部节点执行记录,按时间升序排列。 #### `GetTaskNodeOperations` 查询任务节点操作权限 ```go func (e *FlowEngine) GetTaskNodeOperations(taskID string) (*OperationPermissions, error) ``` > 前端用这个接口判断当前任务可以显示哪些按钮(通过/拒绝/回退/转交/委派/加签/减签)。 #### `GetNodePendingTasks` 查询节点待处理任务 ```go func (e *FlowEngine) GetNodePendingTasks(taskID string) ([]map[string]interface{}, error) ``` > 返回同一节点的加签待处理任务列表,每条记录包含 `task_id`、`assignee`、`assignee_name`、`create_time` 字段。 --- ### 回调(供 Scheduler 调用) 当你实现 `Scheduler` 接口时,定时任务到期后需要回调引擎的以下方法: #### `CompleteTimerTask` 定时节点到期回调 ```go func (e *FlowEngine) CompleteTimerTask(taskID string) error ``` > timer 节点的计时结束后,由 Scheduler 调用此方法完成任务并流转到下一节点。 #### `CompleteApprovalTimeout` 审批超时回调 ```go func (e *FlowEngine) CompleteApprovalTimeout(taskID string) error ``` > 审批任务超时后,由 Scheduler 调用。根据节点配置的 `timeoutAction` 自动执行通过或拒绝。 ## SPI 接口说明 SPI (Service Provider Interface) 是 GoFlow 的扩展机制。你通过实现这些接口,将你的业务系统接入引擎。 ### Store(必选) 数据持久化接口。项目自带 `contrib/gorm` 默认实现,支持 MySQL / PostgreSQL / SQLite 等所有 GORM 支持的数据库。 也可以自行实现,适配 MongoDB、Redis 或其他存储。完整接口定义见 [spi/store.go](spi/store.go),包含 30+ 个方法,主要分为: | 分类 | 方法 | 说明 | |------|------|------| | 流程定义 | `GetFlowDefinition` | 根据 ID+版本查询流程定义 | | 流程实例 | `CreateInstance` / `GetInstance` | 创建/查询实例 | | 流程实例 | `UpdateInstanceStatus` / `UpdateInstanceNodeID` / `UpdateInstanceContext` | 更新实例字段 | | 流程实例 | `UpdateInstanceSuspendedAt` | 更新暂停时间(暂停时设置、恢复时清空) | | 流程任务 | `CreateTask` / `GetTask` / `UpdateTask` | 创建/查询/更新任务 | | 流程任务 | `CountTasks` / `CountTasksByStatuses` / `CountProcessedTasks` | 统计任务 | | 流程任务 | `CancelPendingTasks` / `CancelAllPendingTasks` / `GetPendingTasks` | 取消/查询待处理任务 | | 流程任务 | `GetTasksByAssignee` / `CountBranchPendingTasks` | 用户任务/分支任务 | | 日志 | `CreateNodeLog` / `GetInstanceLogs` | 创建/查询执行日志 | | 网关 | `CreateGatewayBranch` | 创建网关分支记录 | | 流程设计 | `CreateFlowDesign` / `UpdateFlowDesign` / `GetFlowDesign` / `DeleteFlowDesign` | 流程设计模型 CRUD | | 分页查询 | `PageFlowDesigns` / `PageInstances` / `PageTasks` | 分页查询(通过 `PageQuery` 传参) | | 事务 | `Transaction(fn func(tx Store) error)` | 事务包装,fn 中的 tx 是事务内的 Store 副本 | **使用 contrib/gorm 的示例:** ```go import gormstore "gitee.com/iotplc/go-flow/contrib/gorm" store := gormstore.NewStore(db) // db 是 *gorm.DB 实例 store.AutoMigrate() // 自动建表(生产环境建议用 migration 工具) eng := engine.NewFlowEngine(store) ``` **外部事务注入:** 当业务需要保证**业务数据与流程数据在同一个数据库事务**中时(例如保存表单数据 + 启动流程),可以使用 `FromDB` + `WithStore`: ```go import gormstore "gitee.com/iotplc/go-flow/contrib/gorm" // eng 是已创建的 FlowEngine 实例 db.Transaction(func(tx *gorm.DB) error { // 1. 业务操作(使用同一个 tx) if err := tx.Create(&myBusinessData).Error; err != nil { return err } // 2. 用同一事务创建引擎副本 txStore := gormstore.FromDB(tx) txEngine := eng.WithStore(txStore) // 3. 引擎操作也在同一事务中 instance, err := txEngine.StartFlow("flow-001", 1, "biz-123", "user-001", formData, nil) if err != nil { return err // 回滚:业务数据 + 流程实例/任务全部回滚 } // 4. 继续业务操作 return tx.Model(&myBusinessData).Update("instance_id", instance.ID).Error }) ``` 相关 API: | 方法 | 说明 | |------|------| | `gormstore.FromDB(db *gorm.DB) *Store` | 从外部 `*gorm.DB`(可以是事务 tx)创建 Store | | `engine.WithStore(s spi.Store) *FlowEngine` | 创建使用指定 Store 的引擎副本 | | `store.DB() *gorm.DB` | 返回底层 `*gorm.DB`(业务层需要直接操作数据库时使用) | --- ### IdentityService(推荐) 身份解析接口,用于将流程节点的指派方式解析为具体用户 ID。 ```go type IdentityService interface { // 根据角色ID列表查询关联的启用用户ID列表 // 用于 assigneeType=role 和 formRole GetUsersByRoles(roleIDs []string) ([]string, error) // 获取用户的第 N 级上级主管 // level=1 表示直属主管,level=2 表示上上级 // 用于 assigneeType=leader 和 orgLeader GetLeader(userID string, level int) ([]string, error) // 获取用户所属部门的考勤负责人 // 用于 assigneeType=deptAttendance GetDeptAttendanceLeader(userID string) ([]string, error) // 获取系统管理员用户ID列表 // 用于 nobody=admin 时的备用审批人 GetAdminUsers() ([]string, error) // 根据用户ID获取用户名称(用于任务记录显示) GetUserName(userID string) (string, error) // 批量获取用户名称(减少 N+1 查询,contrib/gin 的姓名自动填充功能依赖此方法) // 返回 map[userID]userName,不存在的用户ID不出现在结果中 GetUserNames(userIDs []string) (map[string]string, error) } ``` **影响范围:** 不注入时,`role` / `leader` / `orgLeader` / `deptAttendance` / `formRole` / `nobody=admin` 等功能不可用。`GetUserNames` 不实现时 `contrib/gin` 的审批人姓名自动填充功能不生效。 **实现示例:** ```go type MyIdentityService struct { db *gorm.DB } func (s *MyIdentityService) GetUsersByRoles(roleIDs []string) ([]string, error) { var userIDs []string err := s.db.Table("sys_user_roles"). Where("role_id IN ?", roleIDs). Pluck("user_id", &userIDs).Error return userIDs, err } func (s *MyIdentityService) GetLeader(userID string, level int) ([]string, error) { // 根据你的组织架构实现,例如遍历部门树向上查找 // level=1 返回直属主管、level=2 返回上上级主管 // ... } func (s *MyIdentityService) GetUserNames(userIDs []string) (map[string]string, error) { type NameRow struct{ ID, Name string } var rows []NameRow err := s.db.Table("sys_user").Select("id, name").Where("id IN ?", userIDs).Find(&rows).Error m := make(map[string]string, len(rows)) for _, r := range rows { m[r.ID] = r.Name } return m, err } // 其他方法类似... ``` --- ### NotifyService(可选) 通知服务接口,用于 notify 节点发送消息和催办功能。 ```go type NotifyService interface { // 发送指定类型的通知 // notifyType: site(站内信) / email(邮件) / sms(短信) / wechat(微信) / dingtalk(钉钉) / feishu(飞书) SendByType(notifyType string, userID string, subject string, content string) error } ``` **本项目在 `contrib/mail` 包中提供了基于 [go-mail](https://github.com/wneessen/go-mail) 的邮件通知实现,开箱即用:** ```go import ( goflowMail "gitee.com/iotplc/go-flow/contrib/mail" "gitee.com/iotplc/go-flow/engine" ) notifier, _ := goflowMail.NewEmailNotifier( goflowMail.SMTPConfig{ Host: "smtp.example.com", Port: 465, Username: "sender@example.com", Password: "****", From: "sender@example.com", FromName: "流程通知", UseSSL: true, }, // UserEmailResolver: 根据用户ID查询邮箱,由使用者实现 func(userID string) (string, error) { // 例如从数据库查询: SELECT email FROM users WHERE id = ? return db.GetUserEmail(userID) }, ) eng := engine.NewFlowEngine(store, engine.WithNotify(notifier)) ``` - 仅处理 `notifyType="email"` 的通知,其他类型静默忽略 - 支持 SSL/TLS 和 STARTTLS - 邮件内容以 HTML 格式发送 **若需支持多种通知类型,可自行实现完整的 NotifyService:** ```go type MyNotifyService struct{} func (s *MyNotifyService) SendByType(notifyType string, userID string, subject string, content string) error { switch notifyType { case "site": return insertSiteMessage(userID, subject, content) // 写入站内信表 case "email": return sendEmail(userID, subject, content) // 调用邮件 SDK case "dingtalk": return sendDingtalk(userID, subject, content) // 调用钉钉 API default: return fmt.Errorf("不支持的通知类型: %s", notifyType) } } ``` --- ### Scheduler(可选) 定时任务调度接口,用于 **计时等待节点** 和 **审批超时** 功能。 ```go type Scheduler interface { // 注册定时任务,到达 executeAt 时间后回调引擎 RegisterTimer(taskID string, executeAt time.Time) error // 取消定时任务(暂停/终止/撤回时会调用) CancelTimer(taskID string) error } ``` **关键概念:** Scheduler 只负责"定时触发",到期后要**回调引擎**。引擎提供了统一回调入口 `OnTimerExpired(taskID)`,会自动根据任务节点类型分发: - timer 节点到期 → 内部调用 `CompleteTimerTask(taskID)` - 审批超时 → 内部调用 `CompleteApprovalTimeout(taskID)` **本项目在 `contrib/gocron` 包中提供了基于 [gocron v2](https://github.com/go-co-op/gocron) 的内置实现,开箱即用:** ```go import ( goflowCron "gitee.com/iotplc/go-flow/contrib/gocron" "gitee.com/iotplc/go-flow/engine" ) // 1. 创建调度器 scheduler, _ := goflowCron.NewScheduler() // 2. 创建引擎,注入调度器 eng := engine.NewFlowEngine(store, engine.WithScheduler(scheduler)) // 3. 将引擎的统一回调绑定到调度器 scheduler.SetCallback(eng.OnTimerExpired) // 程序退出时关闭 defer scheduler.Shutdown() ``` > 注意:如果是分布式环境,建议使用持久化的调度器(如 Asynq / Machinery),避免重启后丢失定时任务。 > 可参考 `contrib/gocron` 的实现自行开发。 --- ### ScriptExecutor(可选) JavaScript 脚本执行接口,用于 **服务节点** 和 **脚本条件评估**。 ```go type ScriptExecutor interface { // 执行脚本,返回执行结果 // vars 包含注入到脚本的变量(formData, initiator, instanceId 等) Execute(script string, vars map[string]interface{}) (interface{}, error) // 执行脚本并返回布尔结果(用于 conditionType=script / autoApprovalType=script) EvalBool(script string, vars map[string]interface{}) (bool, error) } ``` **vars 中可用的变量:** | 变量名 | 类型 | 说明 | |--------|------|------| | `formData` | object | 启动流程时传入的表单数据 | | `initiator` | string | 流程发起人 ID | | `instanceId` | string | 流程实例 ID | **本项目在 `contrib/goja` 包中提供了基于 [goja](https://github.com/dop251/goja)(纯 Go 实现的 ES5.1+ JS 引擎)的内置实现,开箱即用:** ```go import ( goflowJS "gitee.com/iotplc/go-flow/contrib/goja" "gitee.com/iotplc/go-flow/engine" ) executor := goflowJS.NewScriptExecutor() eng := engine.NewFlowEngine(store, engine.WithScript(executor)) ``` - 脚本中可使用 `return` 语句返回值(自动包装为 IIFE) - 纯表达式(如 `formData.amount > 1000`)也可直接使用 --- ### Logger(可选) 日志输出接口,默认使用 `NopLogger`(不输出任何日志)。 ```go type Logger interface { Infof(format string, args ...interface{}) Warnf(format string, args ...interface{}) Errorf(format string, args ...interface{}) } ``` **本项目在 `contrib/zap` 包中提供了基于 [zap](https://github.com/uber-go/zap) + [lumberjack](https://github.com/natefinch/lumberjack) 的内置实现,支持分级文件输出 + 自动轮转压缩:** ```go import ( goflowLog "gitee.com/iotplc/go-flow/contrib/zap" "gitee.com/iotplc/go-flow/engine" ) logger := goflowLog.NewLogger(true) // true=启用文件日志, false=不输出 eng := engine.NewFlowEngine(store, engine.WithLogger(logger)) defer logger.Sync() // 自定义配置 opts := goflowLog.DefaultOptions() opts.InfoLogPath = "./logs/flow_info.log" opts.ErrorLogPath = "./logs/flow_error.log" logger2 := goflowLog.NewLoggerWithOptions(true, opts) ``` **或适配 slog 等其他日志框架:** ```go type SlogAdapter struct{} func (SlogAdapter) Infof(format string, args ...interface{}) { slog.Info(fmt.Sprintf(format, args...)) } func (SlogAdapter) Warnf(format string, args ...interface{}) { slog.Warn(fmt.Sprintf(format, args...)) } func (SlogAdapter) Errorf(format string, args ...interface{}) { slog.Error(fmt.Sprintf(format, args...)) } eng := engine.NewFlowEngine(store, engine.WithLogger(SlogAdapter{})) ``` --- ## REST API 适配器 (contrib/gin) `contrib/gin` 提供开箱即用的 Gin HTTP 端点,覆盖流程定义管理、引擎操作、查询 三大类 API。集成方无需自己编写 HTTP handler,只需初始化并注册路由即可。 ### 快速集成 ```go import gincontrib "gitee.com/iotplc/go-flow/contrib/gin" // 1. 创建 WorkflowHandler wfHandler := gincontrib.NewWorkflowHandler(engine, store, db, identityService) // 2. (可选)设置业务钩子(在流程启动前后注入业务逻辑) wfHandler.SetBusinessHook(myBusinessHook) // 3. (可选)设置 gin.Context 中用户ID 的 key(默认 "userID") wfHandler.SetUserIDKey("userID") // 4. 注册路由 wfHandler.RegisterRoutes(authedRouterGroup) ``` > 以上 4 行代码会自动注册以下所有 REST 端点。 ### 流程定义管理 `/v1/flowDefinitions` | 方法 | 路径 | 说明 | |------|------|------| | GET | `/page` | 分页查询流程设计列表 | | GET | `/list` | 查询全部已启用流程设计 | | POST | `/createOrUpdate` | 创建或更新流程设计 | | GET | `/:id` | 获取单个流程设计 | | GET | `/:id/:version` | 获取指定版本的流程定义 | | DELETE | `/:id/:version` | 删除指定版本的流程定义 | | GET | `/versions/:id` | 获取流程的所有版本列表 | | GET | `/enabled/:id` | 获取流程当前启用版本 | | POST | `/publish` | 发布流程(版本+1 并部署到 flow_definitions) | | PUT | `/activate/:id` | 启用流程设计 | | PUT | `/suspend/:id` | 停用流程设计 | ### 引擎操作 `/v1/flowEngine` | 方法 | 路径 | 说明 | |------|------|------| | POST | `/start` | 启动流程(支持 BusinessHook 钩子) | | POST | `/task/complete` | 完成任务(通过/拒绝/回退) | | POST | `/task/batchComplete` | 批量完成任务 | | POST | `/task/delegate` | 委派任务 | | POST | `/task/transfer` | 转交任务 | | POST | `/task/addMulti` | 加签 | | POST | `/task/minusMulti` | 减签 | | POST | `/stop` | 终止流程 | | POST | `/suspend` | 暂停流程 | | POST | `/resume` | 恢复流程 | | POST | `/revoke` | 撤回流程 | | POST | `/urge` | 催办 | | GET | `/instance/:id` | 查询流程实例详情 | | GET | `/instance/:id/logs` | 查询流程执行日志 | | GET | `/task/:id/operations` | 查询任务操作权限 | | GET | `/task/:id/pendingUsers` | 查询节点待处理用户 | | GET | `/tasks/todo` | 查询待办任务 | | GET | `/tasks/done` | 查询已办任务 | | GET | `/dashboard` | 工作流看板统计 | ### 查询 API | 组 | 方法 | 路径 | 说明 | |------|------|------|------| | 实例 | GET | `/v1/flowInstances/page` | 分页查询流程实例 | | 实例 | GET | `/v1/flowInstances/businessKey/:key` | 按业务主键查询实例 | | 实例 | GET | `/v1/flowInstances/:id` | 查询单个实例 | | 任务 | GET | `/v1/flowNodeTasks/page` | 分页查询任务 | | 任务 | GET | `/v1/flowNodeTasks/list` | 查询任务列表 | | 任务 | GET | `/v1/flowNodeTasks/:id` | 查询单个任务 | | 任务 | GET | `/v1/flowNodeTasks/instance/:instanceId` | 按实例查询任务 | | 任务 | GET | `/v1/flowNodeTasks/assignee/:assignee` | 按处理人查询任务 | | 日志 | GET | `/v1/flowNodeLogs/page` | 分页查询执行日志 | | 日志 | GET | `/v1/flowNodeLogs/list` | 查询日志列表 | | 日志 | GET | `/v1/flowNodeLogs/:id` | 查询单条日志 | | 日志 | GET | `/v1/flowNodeLogs/instance/:instanceId` | 按实例查询日志 | | 网关分支 | GET | `/v1/flowGatewayBranches/page` | 分页查询网关分支 | | 网关分支 | GET | `/v1/flowGatewayBranches/list` | 查询网关分支列表 | | 网关分支 | GET | `/v1/flowGatewayBranches/:id` | 查询单条网关分支 | ### BusinessHook 业务钩子 集成方可通过实现 `BusinessHook` 接口,在流程启动前后注入业务逻辑(如创建表单数据、回填实例ID): ```go type BusinessHook interface { // OnBeforeStart 启动前回调,返回 businessKey 用于关联业务数据 OnBeforeStart(c *gin.Context, flowDesignID uint, formData map[string]interface{}) (businessKey string, err error) // OnAfterStart 启动后回调,用于回填 instance_id 等 OnAfterStart(c *gin.Context, instanceID, businessKey string) error } ``` > 不设置 BusinessHook 时,启动流程的请求体中必须直接提供 `businessKey` 字段。 ## 流程定义格式 GoFlow 使用 **JSON 链表树** 结构定义流程(非 BPMN XML)。每个节点通过 `next` 属性指向下一个节点,网关节点通过 `branches` 定义分支,整体结构类似: ``` start → approval → exclusive → end ├─ condition_1 → approval_2 → (next of exclusive) └─ condition_2 (default) → (next of exclusive) ``` ### 通用属性 所有节点共享以下基础属性: | 属性 | 类型 | 必填 | 说明 | |------|------|------|------| | `id` | string | ✔ | 节点唯一ID | | `pid` | string | | 父节点ID(分支内节点指向网关ID) | | `name` | string | ✔ | 节点名称(显示用) | | `type` | string | ✔ | 节点类型,见下表 | | `next` | FlowNode | | 下一个节点(链表指针) | | `formProperties` | FormProperty[] | | 表单字段权限配置 | | `executionListeners` | Listener[] | | 节点执行监听器 | ### 节点类型一览 | type | 名称 | 说明 | |------|------|------| | `start` | 开始节点 | 流程起点,自动流转到 next | | `approval` | 审批节点 | 创建审批任务,等待人工处理 | | `cc` | 抄送节点 | 创建抄送任务(仅通知),自动流转 | | `exclusive` | 互斥网关 | 按顺序评估条件,**只走第一个**命中的分支 | | `inclusive` | 包容网关 | 评估所有条件,**走所有**命中的分支(并行执行) | | `parallel` | 并行网关 | **所有分支**无条件并行执行 | | `condition` | 条件节点 | 互斥/包容网关的子节点,携带条件规则 | | `branch` | 并行分支 | 并行网关的子节点 | | `timer` | 计时等待 | 等待指定时间后自动流转 | | `notify` | 消息通知 | 发送通知后自动流转 | | `service` | 服务节点 | 执行 JS 脚本后自动流转 | | `jump` | 节点跳转 | 条件满足时跳转到指定节点 | | `end` | 结束节点 | 流程结束,标记实例为已完成 | --- ### 审批节点 `approval` 审批节点是最复杂的节点类型,支持多种指派方式、多人审批模式、超时处理、自动审批等。 #### 指派方式 `assigneeType` | assigneeType | 含义 | 配合属性 | 示例 | |-------------|------|---------|------| | `user` | 指定用户 | `users: ["u001","u002"]` | 固定人员审批 | | `role` | 指定角色 | `roles: ["r001"]` | 角色下所有用户(需 IdentityService) | | `choice` | 发起人自选 | `choice: true` | 发起时通过 choiceAssignees 传入 | | `self` | 发起人本人 | `self: true` | 发起人即审批人 | | `leader` | 主管审批 | `leader: 1` | 1=直属主管,2=上上级(需 IdentityService) | | `orgLeader` | 组织负责人 | `orgLeader: 1` | 同 leader,但按组织架构查找 | | `formUser` | 表单中的用户 | `formUser: "reviewerId"` | 从表单数据中动态读取用户ID | | `formRole` | 表单中的角色 | `formRole: "deptRoleId"` | 从表单数据中动态读取角色ID | | `deptAttendance` | 部门考勤负责人 | - | 需 IdentityService | #### 多人审批模式 `multi` | multi | 含义 | 配合属性 | |-------|------|----------| | `sequential` | 依次审批 | 按顺序逐个审批,前一个通过后下一个才收到任务 | | `joint` | 会签 | 所有人同时收到任务,达到 `multiPercent`% 通过即通过 | | `single` | 或签 | 所有人同时收到任务,任一人通过即通过 | #### 空审批人处理 `nobody` | nobody | 含义 | |--------|------| | `pass` | 自动通过,跳过该节点 | | `refuse` | 自动拒绝,终止流程 | | `admin` | 转交系统管理员审批(需 IdentityService) | | `assign` | 转交指定人员 `nobodyUsers` 审批 | #### 操作权限 `operations` ```json { "complete": true, // 允许通过 "refuse": true, // 允许拒绝 "back": true, // 允许回退到上一节点 "transfer": false, // 允许转交 "delegate": false, // 允许委派 "addMulti": false, // 允许加签 "minusMulti": false // 允许减签 } ``` #### 审批超时配置 | 属性 | 类型 | 说明 | |------|------|------| | `timeoutEnabled` | bool | 是否启用超时 | | `timeoutDuration` | int | 超时时长数值 | | `timeoutUnit` | string | 时间单位:`minute` / `hour` / `day` | | `timeoutAction` | string | 超时动作:`complete`(自动通过)/ `refuse`(自动拒绝) | > 暂停期间不消耗超时时长,恢复后自动重算剩余时间。 #### 自动审批规则 | 属性 | 类型 | 说明 | |------|------|------| | `autoApprovalEnabled` | bool | 是否启用自动审批 | | `autoApprovalAction` | string | `pass`(自动通过)/ `refuse`(自动拒绝) | | `autoApprovalType` | string | `filter`(规则匹配)/ `script`(JS 脚本) | | `autoApprovalConditions` | FilterRules | type=filter 时的条件规则 | | `autoApprovalScript` | string | type=script 时的 JS 脚本 | #### 完整审批节点示例 ```json { "id": "approval_001", "name": "主管审批", "type": "approval", "assigneeType": "leader", "leader": 1, "multi": "single", "nobody": "pass", "operations": { "complete": true, "refuse": true, "back": true, "transfer": true, "delegate": true, "addMulti": false, "minusMulti": false }, "timeoutEnabled": true, "timeoutDuration": 24, "timeoutUnit": "hour", "timeoutAction": "complete", "formProperties": [ { "id": "amount", "name": "金额", "hidden": false }, { "id": "reason", "name": "申请理由", "hidden": false } ], "next": { "..." : "..." } } ``` --- ### 抄送节点 `cc` 抄送节点和审批节点共用指派属性(`assigneeType` / `users` / `roles` 等),但仅创建只读通知任务,不阻塞流程。 ```json { "id": "cc_001", "name": "抄送财务部", "type": "cc", "assigneeType": "role", "roles": ["finance_role_id"], "next": { "..." : "..." } } ``` --- ### 互斥网关 `exclusive` 按 `branches` 顺序评估条件,只执行第一个命中的分支。网关的 `next` 是所有分支的汇聚点。 ```json { "id": "exclusive_001", "name": "金额判断", "type": "exclusive", "branches": [ { "id": "cond_001", "name": "金额>10000", "type": "condition", "pid": "exclusive_001", "conditions": { "operator": "and", "conditions": [ { "field": "amount", "operator": ">", "value": 10000 } ] }, "next": { "id": "approval_002", "name": "总监审批", "type": "approval", "assigneeType": "user", "users": ["director_001"], "multi": "single", "nobody": "pass" } }, { "id": "cond_002", "name": "默认分支", "type": "condition", "pid": "exclusive_001", "def": true } ], "next": { "id": "end_001", "name": "结束", "type": "end" } } ``` --- ### 包容网关 `inclusive` / 并行网关 `parallel` - **包容网关**:分支用 `condition` 节点,所有条件命中的分支并行执行,全部完成后汇聚到 `next` - **并行网关**:分支用 `branch` 节点,所有分支无条件并行执行,全部完成后汇聚到 `next` ```json { "id": "parallel_001", "name": "并行审批", "type": "parallel", "branches": [ { "id": "branch_001", "name": "财务审批", "type": "branch", "pid": "parallel_001", "next": { "id": "approval_a", "name": "财务经理", "type": "approval", "...":"..." } }, { "id": "branch_002", "name": "法务审批", "type": "branch", "pid": "parallel_001", "next": { "id": "approval_b", "name": "法务经理", "type": "approval", "...":"..." } } ], "next": { "id": "end_001", "name": "结束", "type": "end" } } ``` > 网关支持任意层级嵌套(分支内可再包含网关)。 --- ### 条件节点 `condition` 条件节点是互斥/包容网关的子节点,携带筛选规则。 | 属性 | 类型 | 说明 | |------|------|------| | `def` | bool | 是否为默认分支(其他分支都不命中时走这里) | | `conditionType` | string | `filter`(规则匹配)/ `script`(JS 脚本) | | `conditions` | FilterRules | conditionType=filter 时的规则 | | `script` | string | conditionType=script 时的 JS 脚本 | --- ### 条件规则 FilterRules FilterRules 是一个递归结构,支持嵌套分组: ```json { "operator": "and", "conditions": [ { "field": "amount", "operator": ">", "value": 10000 }, { "field": "type", "operator": "==", "value": "采购" } ], "groups": [ { "operator": "or", "conditions": [ { "field": "dept", "operator": "==", "value": "财务部" }, { "field": "dept", "operator": "==", "value": "行政部" } ] } ] } ``` **支持的运算符:** | 运算符 | 含义 | 示例 | |--------|------|------| | `==` | 等于 | `{"field":"status", "operator":"==", "value":"已提交"}` | | `!=` | 不等于 | `{"field":"type", "operator":"!=", "value":"草稿"}` | | `>` `>=` `<` `<=` | 数值比较 | `{"field":"amount", "operator":">", "value":5000}` | | `contains` | 包含 | `{"field":"tags", "operator":"contains", "value":"紧急"}` | | `notContains` | 不包含 | | | `startsWith` | 开头是 | | | `endsWith` | 结尾是 | | | `empty` | 为空 | `{"field":"remark", "operator":"empty", "value":null}` | | `notEmpty` | 不为空 | | | `in` | 在列表中 | `{"field":"level", "operator":"in", "value":["A","B"]}` | | `notIn` | 不在列表中 | | | `dateBetween` | 日期范围 | `{"field":"date", "operator":"dateBetween", "value":["2024-01-01","2024-12-31"]}` | > 条件中的 `field` 对应表单数据 (formData) 中的字段名。 --- ### 计时等待节点 `timer` | 属性 | 类型 | 说明 | |------|------|------| | `waitType` | string | `duration`(等待时长)/ `date`(指定日期) | | `unit` | string | waitType=duration 时:`minute` / `hour` / `day` | | `duration` | int | waitType=duration 时:等待数值 | | `timeDate` | string | waitType=date 时:目标时间(如 `2024-12-31 18:00:00`) | ```json { "id": "timer_001", "name": "等待2小时", "type": "timer", "waitType": "duration", "unit": "hour", "duration": 2 } ``` --- ### 消息通知节点 `notify` | 属性 | 类型 | 说明 | |------|------|------| | `types` | string[] | 通知渠道:`site` / `email` / `sms` / `wechat` / `dingtalk` / `feishu` | | `subject` | string | 通知标题 | | `content` | string | 通知内容 | 指派属性与审批节点相同(`assigneeType` / `users` / `roles` 等)。 ```json { "id": "notify_001", "name": "通知发起人", "type": "notify", "assigneeType": "self", "self": true, "types": ["site", "email"], "subject": "您的申请已通过", "content": "您提交的申请已通过全部审批。" } ``` --- ### 服务节点 `service` | 属性 | 类型 | 说明 | |------|------|------| | `implementationType` | string | 实现类型,目前仅支持 `script` | | `implementation` | string | JS 脚本内容(通过 ScriptExecutor SPI 执行) | ```json { "id": "service_001", "name": "更新库存", "type": "service", "implementationType": "script", "implementation": "var result = DBExec('UPDATE inventory SET qty = qty - ? WHERE id = ?', [formData.qty, formData.itemId]); return result;" } ``` --- ### 节点跳转 `jump` | 属性 | 类型 | 说明 | |------|------|------| | `jumpTarget` | string | 目标节点ID | | `jumpConditions` | FilterRules | 跳转条件(满足则跳转,不满足则走 next) | ```json { "id": "jump_001", "name": "金额超限跳回", "type": "jump", "jumpTarget": "approval_001", "jumpConditions": { "operator": "and", "conditions": [{ "field": "amount", "operator": ">", "value": 50000 }] }, "next": { "id": "end_001", "name": "结束", "type": "end" } } ``` --- ### 完整流程示例 以下是一个包含开始 → 主管审批 → 金额判断(互斥网关)→ 结束 的完整示例: ```json { "id": "start_001", "name": "开始", "type": "start", "formProperties": [ { "id": "amount", "name": "金额", "hidden": false }, { "id": "reason", "name": "申请理由", "hidden": false } ], "next": { "id": "approval_001", "name": "主管审批", "type": "approval", "assigneeType": "leader", "leader": 1, "multi": "single", "nobody": "pass", "operations": { "complete": true, "refuse": true, "back": false, "transfer": true, "delegate": false, "addMulti": false, "minusMulti": false }, "timeoutEnabled": true, "timeoutDuration": 48, "timeoutUnit": "hour", "timeoutAction": "complete", "next": { "id": "exclusive_001", "name": "金额判断", "type": "exclusive", "branches": [ { "id": "cond_001", "name": "金额>10000", "type": "condition", "pid": "exclusive_001", "conditionType": "filter", "conditions": { "operator": "and", "conditions": [ { "field": "amount", "operator": ">", "value": 10000 } ] }, "next": { "id": "approval_002", "name": "总监审批", "type": "approval", "assigneeType": "user", "users": ["director_001"], "multi": "single", "nobody": "admin", "operations": { "complete": true, "refuse": true, "back": true, "transfer": false, "delegate": false, "addMulti": false, "minusMulti": false } } }, { "id": "cond_002", "name": "默认分支", "type": "condition", "pid": "exclusive_001", "def": true } ], "next": { "id": "end_001", "name": "结束", "type": "end" } } } } ``` ## 状态说明 ### 流程实例状态 | 值 | 常量 | 含义 | |----|------|------| | `1` | `InstanceRunning` | 运行中 | | `2` | `InstanceCompleted` | 已完成 | | `3` | `InstanceTerminated` | 已终止 | | `4` | `InstanceSuspended` | 已暂停 | | `5` | `InstanceWithdrawn` | 已撤回 | ### 任务状态 | 值 | 常量 | 含义 | |----|------|------| | `1` | `TaskPending` | 待处理 | | `2` | `TaskCompleted` | 已完成 | | `3` | `TaskRefused` | 已拒绝 | | `4` | `TaskBacked` | 已回退 | | `5` | `TaskTransferred` | 已转交 | | `6` | `TaskDelegated` | 已委派 | | `7` | `TaskCancelled` | 已取消 | | `8` | `TaskMinusMulti` | 已减签 | ## 数据库表 使用 `contrib/gorm` 时调用 `store.AutoMigrate()` 会自动创建以下表。 ### flow_form_model 流程设计表 | 字段 | 类型 | 说明 | |------|------|------| | id | bigint PK | 自增主键 | | name | varchar | 流程名称 | | code | varchar | 流程编码 | | remark | varchar | 描述 | | flow_json | longtext | 流程设计 JSON | | form_json | longtext | 表单设计 JSON | | version | int | 当前版本号 | | status | int | 1=启用 0=停用 2=禁用 | | icon | varchar | 流程图标 | | group_id | bigint | 分组 ID | | form_model_id | bigint | 关联的独立表单模型 ID | | enable_withdraw | varchar(1) | 是否允许撤回 | | enable_proxy | varchar(1) | 是否允许代理 | | created_at | datetime | 创建时间 | | updated_at | datetime | 更新时间 | > 此表是流程设计器的主表,存储流程 JSON 和表单 JSON。发布时版本号+1 并自动部署到 `flow_definitions` 表。 ### flow_definitions 流程定义表 | 字段 | 类型 | 说明 | |------|------|------| | id | varchar(64) PK | 流程定义 ID | | flow_version | int | 版本号(同一 id 可以有多个版本) | | name | varchar | 流程名称 | | code | varchar | 流程编码 | | content | json | 流程定义 JSON(即上述的链表树结构) | | status | varchar(1) | 1=启用 2=禁用 | | enable_withdraw | varchar(1) | 1=允许撤回 2=不允许 | | enable_delegation | varchar | 是否允许委派 | > 流程定义可通过 `contrib/gin` 的发布接口自动写入,也可以自行通过流程设计器前端保存。引擎核心只读取不修改它。 ### flow_instances 流程实例表 | 字段 | 类型 | 说明 | |------|------|------| | id | varchar(64) PK | 实例 ID(引擎自动生成,如 `INST_a1b2c3d4`) | | flow_id | varchar | 关联的流程定义 ID | | flow_version | int | 关联的流程版本 | | business_key | varchar | 业务主键(关联你的业务记录) | | current_node_id | varchar | 当前执行到的节点 ID | | context | json | 流程上下文(包含 formData、initiator、choiceAssignees 等) | | status | varchar(1) | 1=运行中 2=已完成 3=已终止 4=已暂停 5=已撤回 | | initiator | varchar | 发起人用户 ID | | started_at | datetime | 启动时间 | | completed_at | datetime | 完成时间(完成/终止/撤回时设置) | | suspended_at | datetime | 暂停时间(暂停时设置,恢复时清空) | ### flow_node_tasks 流程任务表 | 字段 | 类型 | 说明 | |------|------|------| | id | varchar(64) PK | 任务 ID(引擎自动生成) | | instance_id | varchar | 关联的流程实例 ID | | node_id | varchar | 关联的节点 ID | | node_type | varchar | 节点类型(approval / cc / timer 等) | | assignee | varchar | 处理人用户 ID | | assignee_name | varchar | 处理人名称 | | status | varchar(1) | 1=待处理 2=已完成 3=已拒绝 4=已回退 5=已转交 6=已委派 7=已取消 8=已减签 | | action | varchar | 操作类型(complete / refuse / back / cancel / transfer / delegate / addMulti) | | comment | text | 审批意见 | | attachments | text | 附件(JSON) | | delegated_from | varchar | 委派来源任务 ID(委派场景) | | create_time | datetime | 任务创建时间 | | complete_time | datetime | 任务完成时间 | | expire_time | datetime | 超时时间(审批超时 / timer 节点) | ### flow_node_logs 节点执行日志表 | 字段 | 类型 | 说明 | |------|------|------| | id | bigint PK | 自增主键 | | instance_id | varchar | 关联的流程实例 ID | | node_id | varchar | 节点 ID | | node_type | varchar | 节点类型 | | node_name | varchar | 节点名称 | | executor | varchar | 执行人 | | exec_result | varchar(1) | 1=成功 2=失败 | | exec_msg | text | 执行信息(审批意见、系统日志等) | | start_time | datetime | 开始时间 | | end_time | datetime | 结束时间 | | duration_ms | int | 耗时(毫秒) | ### flow_gateway_branches 网关分支记录表 | 字段 | 类型 | 说明 | |------|------|------| | id | varchar(64) PK | 记录 ID | | instance_id | varchar | 关联的流程实例 ID | | gateway_node_id | varchar | 网关节点 ID | | condition_node_id | varchar | 条件/分支节点 ID | | condition_rules | json | 条件规则快照 | | condition_result | varchar(1) | 1=满足 2=不满足 | | is_default | varchar(1) | 1=是默认分支 2=否 | | executed_at | datetime | 执行时间 | ## 依赖 | 依赖 | 用途 | 范围 | |------|------|------| | [expr-lang/expr](https://github.com/expr-lang/expr) | 条件表达式评估 | engine(必选) | | [google/uuid](https://github.com/google/uuid) | 生成唯一 ID | engine(必选) | | [gin-gonic/gin](https://github.com/gin-gonic/gin) | HTTP Web 框架 | contrib/gin | | [gorm.io/gorm](https://gorm.io) | ORM 框架 | contrib/gorm, contrib/gin | | [go-co-op/gocron/v2](https://github.com/go-co-op/gocron) | 定时任务调度 | contrib/gocron | | [dop251/goja](https://github.com/dop251/goja) | ES5.1+ JS 引擎 | contrib/goja | | [wneessen/go-mail](https://github.com/wneessen/go-mail) | 邮件发送 | contrib/mail | | [uber-go/zap](https://github.com/uber-go/zap) | 结构化日志 | contrib/zap | | [natefinch/lumberjack](https://github.com/natefinch/lumberjack) | 日志轮转 | contrib/zap | ## License Apache License 2.0