diff --git a/.claude/commands/add-rule.md b/.claude/commands/add-rule.md new file mode 100644 index 00000000..5a99b4bb --- /dev/null +++ b/.claude/commands/add-rule.md @@ -0,0 +1,8 @@ +--- +name: add cluade code rule +description: 添加到本地CLAUDE.md规则 +--- + +# add cluade code rule + +将该内容 $0 添加到本项目的CLAUDE.md规则中 \ No newline at end of file diff --git a/.claude/commands/git-push.md b/.claude/commands/git-push.md index 40f55f37..9f979395 100644 --- a/.claude/commands/git-push.md +++ b/.claude/commands/git-push.md @@ -1,3 +1,35 @@ +--- +name: git push command +description: git 代码自动化分析并提交 +--- + # git push command -根据当前调整内容,并创建git的提交指令,并添加对应的提交信息,然后执行git push命令将本地的提交推送到远程仓库。 \ No newline at end of file +根据当前代码调整内容,完成以下步骤: + +## 执行步骤 + +1. **检查变更状态**:运行 `git status` 查看所有未跟踪和已修改的文件 +2. **查看变更详情**:运行 `git diff` 查看已暂存和未暂存的变更内容 +3. **查看最近提交**:运行 `git log` 查看最近的提交信息风格 +4. **分析变更内容**:总结变更的性质(新功能/修复/重构/测试/文档等) +5. **添加文件**:使用 `git add` 添加相关文件(避免使用 -A 或 . 一次性添加所有文件,注意排除敏感文件如 .env、credentials 等) +6. **创建提交**:创建新提交,提交信息遵循项目风格,格式为: + - 标题:简短描述(不超过 50 字符) + - 正文:详细说明(如果需要) + - 尾部:添加 Co-Authored-By: Claude Opus 4.6 +7. **验证提交**:运行 `git status` 确认提交成功 +8. **推送到远程**:运行 `git push` 将本地提交推送到远程仓库 + +## 注意事项 + +- 永远不要使用 `git add -A` 或 `git add .`,应指定具体文件名 +- 永远不要使用破坏性命令如 `git push --force`、`git reset --hard`、`git checkout .`、`git restore .`、`git clean -f` +- 永远不要跳过 hooks(--no-verify)或跳过签名(--no-gpg-sign) +- 如果 pre-commit hook 失败,修复问题后创建新的提交(不要 amend) +- 永远不要直接推送到 main/master 分支 +- 如果需要推送,创建新分支后推送 + +## 输出要求 + +完成后返回 PR URL(如果有创建 PR) \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7e0a76db..e6d79c3d 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,7 @@ build/ *.db ### macOS ### -.DS_Store \ No newline at end of file +.DS_Store + +### Worktrees ### +.worktrees/ \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 8376c099..bfc2dc56 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,300 +1,179 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +本文件为 Claude Code (claude.ai/code) 在本项目中工作时提供指导。 -# currentDate -Today's date is 2025-02-26. +## 项目概述 -## Project Overview +Flow Engine 是一个企业级工作流引擎,基于 Java 17 和 Spring Boot 3.5.9 构建。提供可视化流程设计、动态表单配置、多节点类型流转以及脚本扩展功能。项目采用前后端分离架构,支持 PC 端和移动端客户端。 -Flow Engine is an enterprise workflow engine built with Java 17 and Spring Boot 3.5.9, with a React/TypeScript frontend. It supports workflow design, form configuration, node management, and script execution (Groovy). +## 常用命令 -## Common Commands - -### Backend (Maven) +### 后端 (Java/Maven) ```bash -# Build entire project +# 构建整个项目 ./mvnw clean install -# Run all tests +# 运行所有测试 ./mvnw test -# Run tests for specific module +# 运行特定模块的测试 ./mvnw test -pl flow-engine-framework -# Run single test class -./mvnw test -Dtest=RandomUtilsTest - -# Run single test method -./mvnw test -Dtest=RandomUtilsTest#generateCode +# 运行特定的测试类 +./mvnw test -Dtest=ScriptRuntimeContextTest -# Skip tests during build -./mvnw clean install -DskipTests +# 运行示例应用 +cd flow-engine-example && mvn spring-boot:run ``` -### Frontend (pnpm) +### 前端 (React/pnpm) ```bash -cd frontend -pnpm install +# 安装依赖(使用 pnpm,而非 npm) +cd frontend && pnpm install -# Build all packages +# 构建所有包 pnpm run build -# Build individual packages (order matters: types → core → design → apps) -pnpm run build:flow-types # Build types first (no dependencies) -pnpm run build:flow-core # Build core API library -pnpm run build:flow-design # Build flow-design component library -pnpm run build:app-pc # Build PC application +# 构建特定应用 +pnpm run build:app-pc -# Watch mode for development -pnpm run watch:flow-design # Rebuild flow-design on changes +# 构建特定包 +pnpm run build:flow-core # 构建核心 API 库 +pnpm run build:flow-types # 构建类型定义库 +pnpm run build:flow-pc # 构建 PC 端组件库 -# Run PC app in dev mode (proxies to localhost:8090) -pnpm run dev:app-pc +# 开发模式 +pnpm run dev:app-pc # PC 端应用 +pnpm run dev:app-mobile # 移动端应用 ``` -## Architecture - -### Backend Modules - -- **flow-engine-framework** - Core framework with workflow engine, node types, form system, script execution -- **flow-engine-starter** - Spring Boot starter for web applications -- **flow-engine-starter-infra** - Infrastructure persistence layer with JPA entities, convertors, and repository implementations - - `entity/` - JPA entities (WorkflowEntity, FlowRecordEntity, DelayTaskEntity, etc.) - - `convert/` - Entity-Domain convertors - - `jpa/` - Spring Data JPA repositories - - `repository/impl/` - Repository interface implementations -- **flow-engine-example** - Example application - -### Frontend Structure - -- **apps/app-pc** - PC client application -- **apps/app-mobile** - Mobile client application (in development) -- **packages/flow-design** - Flow designer component library using @flowgram.ai fixed-layout-editor -- **packages/flow-core** - Core API library with HTTP client and service interfaces -- **packages/flow-types** - TypeScript type definitions for the entire frontend - - `pages/design-panel/types.ts` - TypeScript interfaces for Workflow, FlowNode, FlowForm - - `FlowNode.blocks?: FlowNode[]` - Child nodes for hierarchical structure - - `FlowNode.strategies` - Node strategies configuration - - `FlowNode.actions` - Node actions configuration - -Note: `app-mobile` is currently in development. `flow-pc` and `flow-mobile` packages are planned but not yet implemented. - -### Data Structure: Blocks vs Edges - -**Critical**: This project uses a hierarchical node structure via `blocks`, NOT edge-based connections. - -**Backend (Java)**: -- `IFlowNode.blocks()` returns `List` - child nodes -- Block nodes: `ConditionNode`, `ParallelNode`, `InclusiveNode` contain branch nodes -- Branch nodes: `ConditionBranchNode`, `ParallelBranchNode`, `InclusiveBranchNode` are children -- `FlowNodeEdgeManager` traverses blocks recursively to find next nodes - -**Frontend (TypeScript)**: -- `FlowNode.blocks?: FlowNode[]` - optional child nodes array -- Legacy `FlowEdge` interface exists but is deprecated -- @flowgram.ai fixed-layout-editor handles the visual representation - -### Core Layered Architecture - -The workflow engine is organized into 8 layers: - -1. **Workflow Layer** (`com.codingapi.flow.workflow`) - - `Workflow` - Top-level container with nodes, form definition - - `WorkflowBuilder` - Builder pattern for workflow construction - -2. **Node Layer** (`com.codingapi.flow.node`) - - `IFlowNode` - Interface defining node lifecycle methods, includes `blocks()` method for child nodes - - `BaseFlowNode` - Abstract base for all nodes, manages actions, strategies, and blocks - - `BaseAuditNode` - Abstract base for audit nodes (ApprovalNode, HandleNode) - - 15 node types: StartNode, EndNode, ApprovalNode, HandleNode, NotifyNode, ConditionBranchNode, ParallelBranchNode, RouterNode, InclusiveBranchNode, SubProcessNode, DelayNode, TriggerNode, ConditionNode, ParallelNode, InclusiveNode - - **Block nodes** (containers with child blocks): ConditionNode, ParallelNode, InclusiveNode - - **Branch nodes** (child nodes in blocks): ConditionBranchNode, ParallelBranchNode, InclusiveBranchNode - -3. **Action Layer** (`com.codingapi.flow.action`, `com.codingapi.flow.action.actions`) - - `IFlowAction` - Interface for node actions with `copy()` method - - `BaseAction` - Abstract base with `triggerNode()` for recursive traversal - - 8 action types (enum): PassAction, RejectAction, SaveAction, ReturnAction, TransferAction, AddAuditAction, DelegateAction, CustomAction - -4. **Record Layer** (`com.codingapi.flow.record`) - - `FlowRecord` - Execution record with states (TODO/DONE, RUNNING/FINISH/ERROR/DELETE) - - `FlowTodoRecord` - Todo record for pending tasks - - `FlowTodoMerge` - Todo merge record for aggregation - - Tracks processId, nodeId, currentOperatorId, formData, parallel branch info - -5. **Session Layer** (`com.codingapi.flow.session`) - - `FlowSession` - Execution context (currentOperator, workflow, currentNode, currentAction, currentRecord, formData, advice) - - `FlowAdvice` - Approval parameters (advice, signKey, backNode, transferOperators) - -6. **Manager Layer** (`com.codingapi.flow.manager`) - - `ActionManager` - Manages node actions, provides `getAction(Class)`, `verifySession()` - - `OperatorManager` - Manages node operators - - `NodeStrategyManager` - Manages node strategies, provides `loadOperators()`, `generateTitle()`, `verifySession()`, `getTimeoutTime()`, `isDone()` - - `WorkflowStrategyManager` - Manages workflow strategies - - `FlowNodeManager` - Node management functionality - - `FlowNodeState` - Classifies nodes as block nodes or branch nodes for traversal - - `FlowNodeEdgeManager` - Traverses hierarchical node structure via blocks to find next nodes +## 架构 -7. **Strategy Layer** (`com.codingapi.flow.strategy`) - - `INodeStrategy` - Interface with `copy()`, `getId()`, `strategyType()` - - **Node strategies** (16 types): MultiOperatorAuditStrategy, TimeoutStrategy, SameOperatorAuditStrategy, RecordMergeStrategy, ResubmitStrategy, AdviceStrategy, OperatorLoadStrategy, ErrorTriggerStrategy, NodeTitleStrategy, FormFieldPermissionStrategy, DelayStrategy, TriggerStrategy, RouterStrategy, SubProcessStrategy, RevokeStrategy - - **Workflow strategies** (2 types): InterfereStrategy, UrgeStrategy +### 后端分层架构(8 层架构) -8. **Script Layer** (`com.codingapi.flow.script.runtime`) - - `ScriptRuntimeContext` - Groovy script execution with thread-safe design and auto-cleanup - - `TitleGroovyRequest` - Context object for title expression scripts, providing access to operator info, workflow info, form data, and work code - - Script types: OperatorMatchScript, OperatorLoadScript, NodeTitleScript, ConditionScript, RouterNodeScript, SubProcessScript, TriggerScript, ErrorTriggerScript, RejectActionScript, CustomScript +1. **工作流层** - 流程定义 +2. **节点层** - 15 种节点类型(StartNode、EndNode、ApprovalNode、HandleNode、NotifyNode、RouterNode、SubProcessNode、DelayNode、TriggerNode、ConditionNode、ParallelNode、InclusiveNode 及其分支变体) +3. **动作层** - 8 种动作类型(Pass、Reject、Save、AddAudit、Delegate、Return、Transfer、Custom) +4. **记录层** - 流程实例记录 +5. **会话层** - 会话管理 +6. **管理器层** - 业务管理器(NodeStrategyManager、OperatorManager、ActionManager 等) +7. **策略层** - 策略驱动的配置 +8. **脚本层** - Groovy 脚本运行时 -### Supporting Architectures +### 设计模式 -- **Common Interfaces** (`com.codingapi.flow.common`) - - `ICopyAbility` - Interface for copy capability (used by strategies and actions) - - `IMapConvertor` - Interface for Map conversion (used by strategies and actions) -- **Repository Pattern** (`com.codingapi.flow.repository`) - Abstraction for data persistence, isolates framework from implementation. Includes: WorkflowRepository, FlowRecordRepository, WorkflowBackupRepository, ParallelBranchRepository, DelayTaskRepository, UrgeIntervalRepository, FlowTodoRecordRepository, FlowTodoMergeRepository. Implementations are in `flow-engine-starter-infra` under `com.codingapi.flow.infra.repository.impl`. Access via `RepositoryHolderContext` singleton. Pattern: Interface in framework, implementation in infra using JPA entities and convertors. -- **Gateway Pattern** (`com.codingapi.flow.gateway`) - Anti-corruption layer for external system integration (operators, users). Access via `GatewayContext` singleton. -- **Domain Objects** (`com.codingapi.flow.domain`) - DelayTask, DelayTaskManager, UrgeInterval -- **Event System** (`com.codingapi.flow.event`) - 5 event types: FlowRecordStartEvent, FlowRecordTodoEvent, FlowRecordDoneEvent, FlowRecordFinishEvent, FlowRecordUrgeEvent -- **Backup System** (`com.codingapi.flow.backup`) - WorkflowBackup for workflow versioning +- **建造者模式** - WorkflowBuilder、NodeBuilder、FormMetaBuilder +- **工厂模式** - FlowActionFactory +- **策略模式** - NodeStrategy、WorkflowStrategy +- **模板方法模式** - BaseAction、BaseNodeBuilder +- **责任链模式** - 动作执行链 +- **组合模式** - 带 blocks 的节点层级结构 -### Node Lifecycle (Critical for Understanding Flow) +### 模块结构 -When a node is executed, methods are called in this order: -1. `verifySession(session)` - Delegates to ActionManager and StrategyManager -2. `continueTrigger(session)` - Returns true to continue, false to generate records -3. `generateCurrentRecords(session)` - Generate FlowRecords (uses StrategyManager.loadOperators() for audit nodes) -4. `fillNewRecord(session, record)` - Fill record data (uses StrategyManager for audit nodes) -5. `isDone(session)` - Check if complete (uses StrategyManager for multi-person approval) +#### 后端模块 -### Flow Execution Lifecycle +| 模块 | 描述 | +|--------|-------------| +| `flow-engine-framework` | 核心工作流引擎框架 | +| `flow-engine-starter` | Spring Boot 启动器 | +| `flow-engine-starter-api` | API 层 | +| `flow-engine-starter-infra` | 基础设施层 | +| `flow-engine-starter-query` | 查询层 | +| `flow-engine-example` | 示例应用 | -**FlowCreateService** (starting a workflow): -1. Validate request → Get workflow → Verify workflow → Create backup → Validate creator → Build form data → Create start session → Verify session → Generate records → Save records → Push events +#### 前端模块 -**FlowActionService** (executing an action): -1. Validate request → Validate operator → Get record → Validate state → Load workflow → Get current node → Get action → Build form data → Create session → Verify session → Execute action +| 模块 | 描述 | 依赖 | +|--------|-------------|------| +| `flow-core` | 核心框架库(HTTP、Hooks、Presenter 等),不包含 UI 组件 | 无 | +| `flow-types` | TypeScript 类型定义(流程实例、表单、审批等业务类型) | flow-core | +| `flow-pc-ui` | PC 端基础 UI 组件库(按钮、输入框等原子组件) | 无 | +| `flow-pc-form` | PC 端表单相关组件(表单设计器、表单渲染等) | flow-core, flow-types | +| `flow-pc-design` | PC 端流程设计器组件(节点配置、属性面板等) | flow-core, flow-types, flow-pc-ui | +| `flow-pc-approval` | PC 端审批页面(待办/已办/审批处理等) | flow-pc-design, flow-pc-ui | -**FlowDetailService** (querying workflow details): -1. Fetch workflow details by processId → Include workflow definition, current records, form data +**前端模块依赖关系**: -**PassAction.run()** (typical action): -1. Check if node done (StrategyManager) → Update current record → Generate subsequent records → Trigger next nodes → Save records → Push events - -### Parallel Branch Execution - -When encountering `ParallelBranchNode`: -1. `filterBranches()` finds the convergence end node -2. Record parallel info (parallelBranchNodeId, parallelBranchTotal, parallelId) in FlowRecord -3. Execute all branches simultaneously -4. At convergence, `isWaitParallelRecord()` checks if all branches arrived -5. Once all arrive, clear parallel info and continue - -### Delay and Trigger Nodes - -**DelayNode**: Uses `DelayStrategy` with time units (SECOND/MINUTE/HOUR/DAY). Creates `DelayTask` which is managed by `DelayTaskManager` using `java.util.Timer` for scheduled execution. On trigger, `FlowDelayTriggerService` resumes the flow. - -**TriggerNode**: Uses `TriggerStrategy` with `TriggerScript`. Executes trigger script via `ScriptRuntimeContext` when node is reached. - -### Workflow-Level Features - -**Urge (催办)**: `UrgeStrategy` with configurable interval (default 60 seconds). Uses `UrgeInterval` domain object to track timing. Triggers `FlowRecordUrgeEvent` when interval expires. Used for automatic reminders of pending tasks. - -**Interfere (干预)**: `InterfereStrategy` controls whether admin intervention is allowed in the workflow. Enable/disable at workflow level. - -**Revoke (撤回)**: `RevokeStrategy` at node level supports two types: -- `REVOKE_NEXT`: Revoke subordinates, delete subsequent pending records, return to current node -- `REVOKE_CURRENT`: Revoke to current node, restore current node to pending state - -Key difference from Return (退回): Return is executed by current approver to go back to previous completed nodes; Revoke is executed by approved personnel to revoke subordinates or restore current node. - -### Key Design Patterns - -- **Builder**: `WorkflowBuilder`, `BaseNodeBuilder` with singleton `builder()` method -- **Factory**: `NodeFactory.getInstance()` (reflection + static `formMap()`), `NodeStrategyFactory`, `FlowActionFactory` -- **Strategy**: `INodeStrategy` with `StrategyManager` - strategy-driven configuration -- **Template Method**: `BaseFlowNode` (node lifecycle), `BaseAction` (action execution), `BaseStrategy` (strategy template) -- **Singleton**: `NodeFactory`, `ScriptRuntimeContext`, `RepositoryHolderContext`, `GatewayContext` (static final instance) -- **Chain of Responsibility**: `triggerNode()` recursive traversal, `StrategyManager` strategy iteration -- **Composite**: Nodes contain multiple strategies and actions -- **Copy Pattern**: `INodeStrategy.copy()`, `IFlowAction.copy()`, `BaseFlowNode.setActions()`, `BaseFlowNode.setStrategies()` - -### Strategy-Driven Node Configuration (Critical Architecture) +``` +flow-core (无UI) + ↑ +flow-types (类型定义) + ↑ ↑ + │ └── flow-pc-form + │ ↑ + └───────→ flow-pc-design ──→ flow-pc-approval + ↑ + flow-pc-ui (基础UI) +``` -All node configurations are implemented through strategies: -- **Operator loading**: `OperatorLoadStrategy` with Groovy script -- **Node title**: `NodeTitleStrategy` with Groovy script -- **Timeout**: `TimeoutStrategy` with timeout value and type -- **Permissions**: `FormFieldPermissionStrategy` with field permissions -- **Error handling**: `ErrorTriggerStrategy` with Groovy script -- **Multi-person approval**: `MultiOperatorAuditStrategy` with type (SEQUENCE/MERGE/ANY/RANDOM_ONE) +**模块划分原则**: -`StrategyManager` provides unified access: `loadOperators()`, `generateTitle()`, `getTimeoutTime()`, `verifySession()` +- **flow-core**:全局框架依赖,只包含与 UI 无关的基础能力(HTTP、状态管理、工具函数等) +- **flow-types**:全局类型定义,包含流程审批相关的业务类型(手机端和 PC 端共用) +- **flow-pc-ui**:PC 端基础 UI 组件库,提供原子化组件 +- **flow-pc-form**:表单相关功能,依赖 flow-core + flow-types +- **flow-pc-design**:流程设计器功能,包含节点配置、属性面板、脚本配置等(本次优化的主要模块) +- **flow-pc-approval**:审批页面功能,依赖 flow-pc-design -### Multi-Person Approval Modes +### 技术栈 -`MultiOperatorAuditStrategy.Type` enum: -- **SEQUENCE**: Sequential, hides subsequent records -- **MERGE**: Concurrent, requires percentage completion -- **ANY**: Any one person, completes on first approval -- **RANDOM_ONE**: Randomly selects one person +- **后端**:Java 17、Spring Boot 3.5.9、Groovy +- **前端**:React 18、TypeScript、pnpm、Rsbuild、Rslib、Antd -Implemented in `BaseAuditNode.isDone()`. +## 关键包 -### ScriptRuntimeContext Auto-Cleanup +- `com.codingapi.flow.node` - 节点实现(15 种类型) +- `com.codingapi.flow.action` - 动作实现(8 种类型) +- `com.codingapi.flow.strategy` - 策略接口和实现 +- `com.codingapi.flow.repository` - 数据访问层 +- `com.codingapi.flow.service` - 业务服务层 +- `com.codingapi.flow.script` - Groovy 脚本运行时 -Thread-safe Groovy script execution with dual auto-cleanup: -- **Threshold-based**: Triggers when `SCRIPT_LOCKS.size() > maxLockCacheSize` (default 1000) -- **Scheduled**: Periodic cleanup every `CLEANUP_INTERVAL_SECONDS` (default 300s) -- **Configuration**: JVM property `-Dflow.script.cleanup.interval`, or `setMaxLockCacheSize(int)` -- **Lifecycle**: Auto-starts on singleton init, registers shutdown hook, supports runtime enable/disable +## 开发规范 -Thread safety: Each execution creates independent GroovyClassLoader/GroovyShell. Fine-grained synchronization using script hashCode as lock key - same script serializes, different scripts run concurrently. +- **与用户沟通及编写文档时,所有内容必须使用中文表述** +- 前端包管理使用 pnpm(根据用户配置) +- 前端文件命名规范:使用小写字母 + 下划线组合(如 `script_editor.tsx`、`variable_picker.tsx`) +- **前端导入规范**:使用 `@/` 路径别名导入项目内部模块,避免使用相对路径导入 +- 设计涉及流程或 UML 图形的解决方案时,使用 Mermaid Markdown 语法 +- 在编写计划的时候要遵循 TDD 的开发规范,务必要在方案中进行对实现代码逻辑的单元测试设计。 +- 在设计计划方案或执行方案过程中,对于代码的设计规划与调整修改要遵循本项目的代码风格和架构设计规则 +- 设计的计划要保存到本地的 `docs/` 目录下,每一个计划以时间+标题的方式命名创建文件夹,例如 `2026-02-26-xxxx`,文件夹下内容分为 `plan.md` 以及其他设计文件(如设计文件 xxx.pen 或其他设计内容信息) -### Framework Exception Hierarchy +```typescript +// ✅ 正确:使用 @/ 路径别名 +import { GroovySyntaxConverter } from '@/components/design-editor/script/service/groovy-syntax-converter'; +import { ScriptType } from '@/components/design-editor/typings/groovy-script'; -All framework exceptions extend `FlowException` (RuntimeException). Exception codes follow the format `category.subcategory.errorType`. +// ❌ 错误:避免使用相对路径 +import { GroovySyntaxConverter } from '../../../src/components/...'; +``` -**Exception types**: -- `FlowValidationException` - Parameter validation (e.g., `validation.field.required`) -- `FlowNotFoundException` - Resource not found (e.g., `notFound.workflow.definition`) -- `FlowStateException` - Invalid state (e.g., `state.record.alreadyDone`) -- `FlowPermissionException` - Permission issues (e.g., `permission.field.readOnly`) -- `FlowConfigException` - Configuration errors (e.g., `config.node.strategies.required`) -- `FlowExecutionException` - Execution errors (e.g., `execution.script.error`) +### 面向对象开发规范 -**Critical exception rules**: -- MUST use static factory methods: `FlowValidationException.required("fieldName")` -- NEVER use `new FlowXXXException()` directly -- NEVER use `ERROR_CODE_PREFIX` constants -- All exception messages MUST be in English +除前端 **.tsx 组件** 以外的所有代码(Java 后端、TypeScript 非组件文件)均采用面向对象方式开发: -## Code Conventions +- **Service 层**:使用 class 定义,通过依赖注入或单例模式使用 +- **工具类**:使用 class 定义,提供实例方法或静态方法 +- **适配器/转换器**:使用 class 实现,支持扩展 -- Base package: `com.codingapi.flow` -- Interfaces prefixed with `I`: `IFlowNode`, `IFlowOperator` -- Builders use singleton pattern with static `builder()` method -- All comments and documentation in Chinese -- Lombok annotations for boilerplate (`@Data`, `@Getter`, `@Setter`) -- JUnit 5 with static assertions: `import static org.junit.jupiter.api.Assertions.*;` +```typescript +// ✅ 正确:使用 class 定义 Service +export class GroovySyntaxConverter { + private adapters: Map = new Map(); -## Extension Points + public registerAdapter(adapter: ScriptAdapter): void { + this.adapters.set(adapter.scriptType, adapter); + } -- **Custom Nodes**: Extend `BaseFlowNode` or `BaseAuditNode`, implement `IFlowNode` -- **Custom Actions**: Extend `BaseAction`, implement `IFlowAction` -- **Custom Strategies**: Extend `BaseStrategy`, implement `INodeStrategy` for node strategies or `IWorkflowStrategy` for workflow strategies -- **Custom Scripts**: Use `ScriptRuntimeContext` for Groovy execution -- **Event Listeners**: Listen to `FlowRecordStartEvent`, `FlowRecordTodoEvent`, `FlowRecordDoneEvent`, `FlowRecordFinishEvent`, `FlowRecordUrgeEvent` -- **Repository Implementations**: Implement repository interfaces in infra layer for persistence -- **Gateway Implementations**: Implement gateway interfaces for system integration + public toScript(...): string { ... } +} -## Documentation References +// ❌ 错误:避免直接使用函数导出 +export function toScript(...): string { ... } +export const toScript = (...): string => { ... }; +``` -- **Design.md** (root) - Comprehensive architecture documentation with class design, lifecycle diagrams, design patterns, and key implementation details -- **PRD.md** (root) - Product requirements document -- **designs/** (directory) - Design documents for specific features - - `title-expression-ui/README.md` - Title expression interactive UI design -- **frontend/apps/app-pc/AGENTS.md** - Frontend app development guidelines -- **frontend/CLAUDE.md** - Frontend-specific architecture and conventions -- **Rsbuild**: https://rsbuild.rs/llms.txt -- **Rspack**: https://rspack.rs/llms.txt diff --git a/Design.md b/Design.md deleted file mode 100644 index a0f279f4..00000000 --- a/Design.md +++ /dev/null @@ -1,988 +0,0 @@ -# 关键设计介绍 - -## 一、核心类设计 - -### 1. 流程定义层 (Workflow Layer) - -#### Workflow -- **位置**: `com.codingapi.flow.workflow.Workflow` -- **职责**: 流程定义的顶层容器,包含流程的完整定义信息 -- **核心属性**: - - `id`: 流程唯一标识 - - `code`: 流程编号 - - `title`: 流程名称 - - `form`: 流程表单定义 (`FormMeta`) - - `nodes`: 节点列表 (`List`) - - `operatorCreateScript`: 创建者匹配脚本 - - `strategies`: 流程策略列表 (`List`) - - `createdOperator`: 创建者ID - - `createdTime`: 创建时间 - - `updatedTime`: 更新时间 - - `schema`: 流程版本标识 -- **核心方法**: - - `verify()`: 验证流程定义的合法性 - - `nextNodes(IFlowNode)`: 获取指定节点的后续节点(通过FlowNodeEdgeManager遍历blocks) - - `getStartNode()`: 获取开始节点 - - `getEndNode()`: 获取结束节点 - - `toJson()`: 序列化为JSON - - `formJson(String)`: 从JSON反序列化(静态方法) -- **数据结构说明**: - - 节点关系通过`blocks`属性实现层次化结构,不再使用单独的`edges`边关系 - - 流程策略(InterfereStrategy、UrgeStrategy)在流程级别配置 - -#### WorkflowBuilder -- **位置**: `com.codingapi.flow.workflow.WorkflowBuilder` -- **职责**: 使用Builder模式构建Workflow对象 -- **设计模式**: Builder模式 -- **用法**: `WorkflowBuilder.builder().title("请假流程").node(startNode).build()` - ---- - -### 2. 节点层 (Node Layer) - -#### IFlowNode -- **位置**: `com.codingapi.flow.node.IFlowNode` -- **职责**: 所有流程节点的顶层接口,定义节点生命周期方法 -- **核心方法**(按执行顺序): - 1. `verifySession(FlowSession)`: 验证会话参数(委托给ActionManager和StrategyManager) - 2. `continueTrigger(FlowSession)`: 判断是否继续执行后续节点 - 3. `generateCurrentRecords(FlowSession)`: 生成当前节点的流程记录 - 4. `isDone(FlowSession)`: 判断节点是否完成 - 5. `fillNewRecord(FlowSession, FlowRecord)`: 填充新记录数据 - 6. `blocks()`: 获取当前节点的子节点列表(块节点有子节点,其他节点返回null或空) - 7. `filterBranches()`: 过滤条件分支(块节点用于筛选执行的分支) - 8. `actionManager()`: 获取动作管理器 - 9. `nodeStrategyManager()`: 获取策略管理器 - -#### BaseFlowNode -- **位置**: `com.codingapi.flow.node.BaseFlowNode` -- **职责**: 所有节点的抽象基类,实现IFlowNode的默认行为 -- **核心属性**: `id`, `name`, `order`, `actions`, `strategies`, `blocks`(子节点列表) -- **核心方法**: - - `isWaitParallelRecord()`: 判断是否等待并行节点汇聚 - - `verifySession()`: 委托给ActionManager和StrategyManager验证 - - `setActions()`: 支持动作的复制更新(CustomAction除外) - - `setStrategies()`: 支持策略的复制更新 - - `blocks()`: 返回子节点列表 - - `toMap()`: 序列化为Map(包含blocks字段) - - `fromMap()`: 从Map反序列化(从blocks字段加载子节点) - -#### BaseAuditNode -- **位置**: `com.codingapi.flow.node.BaseAuditNode` -- **职责**: 审批类节点的抽象基类(ApprovalNode、HandleNode) -- **核心属性**: - - `view`: 渲染视图 - - 继承自BaseFlowNode的actions和strategies -- **核心方法**: - - `continueTrigger()`: 返回false(审批节点需要生成记录) - - `fillNewRecord()`: 通过StrategyManager填充记录 - - `isDone()`: 通过StrategyManager判断多人审批完成状态 - - `generateCurrentRecords()`: 通过StrategyManager加载操作者并生成记录 - -#### 块节点系统 (Block Nodes System) - -**数据结构变更说明**: -- 旧设计: 使用独立的`FlowEdge`对象和`edges`列表表示节点连接关系 -- 新设计: 使用层次化的`blocks`属性,节点包含子节点形成树形结构 - -**FlowNodeState** - 节点状态分类 -- **位置**: `com.codingapi.flow.manager.FlowNodeState` -- **职责**: 对节点进行分类,判断节点类型 -- **节点分类**: - - **块节点 (Block Nodes)**: `CONDITION`, `INCLUSIVE`, `PARALLEL` - 包含子节点的容器节点 - - **分支节点 (Branch Nodes)**: `CONDITION_BRANCH`, `INCLUSIVE_BRANCH`, `PARALLEL_BRANCH` - 作为块节点的子节点 -- **核心方法**: - - `isBlockNode()`: 判断是否为块节点(有blocks属性) - - `isBranchNode()`: 判断是否为分支节点 - - `getBlocks()`: 获取节点的子节点列表 - - `getFirstBlocks()`: 获取第一个子节点(用于分支节点) - -**FlowNodeEdgeManager** - 节点关系管理器 -- **位置**: `com.codingapi.flow.manager.FlowNodeEdgeManager` -- **职责**: 管理节点之间的连接关系,通过遍历blocks实现 -- **核心方法**: - - `getNextNodes(IFlowNode)`: 获取指定节点的后续节点 - - `loadNextNodes()`: 递归加载下一节点(遍历blocks) -- **遍历逻辑**: - 1. 如果当前节点是块节点,返回其blocks - 2. 如果当前节点是分支节点,返回其第一个block - 3. 否则返回节点列表中的下一个节点 - 4. 递归处理嵌套的blocks - -**NodeMapBuilder** - 节点映射构建器 -- **位置**: `com.codingapi.flow.builder.NodeMapBuilder` -- **职责**: 从Map数据构建节点对象 -- **核心方法**: - - `loadNodes(Map)`: 从Map的"blocks"键加载子节点列表 - - `loadActions(Map)`: 加载节点动作 - - `loadNodeStrategies(Map)`: 加载节点策略 - -**节点关系示例**: -``` -Workflow -├── StartNode -├── ApprovalNode -├── ConditionNode (块节点) -│ ├── blocks: -│ │ ├── ConditionBranchNode (order=1) -│ │ │ └── blocks: [NotifyNode, EndNode] -│ │ └── ConditionBranchNode (order=2) -│ │ └── blocks: [HandleNode, EndNode] -└── EndNode -``` - -#### 节点类型一览 (15种) - -**基础节点 (9种)**: - -| 节点类型 | 类名 | NODE_TYPE | 说明 | -|---------|------|-----------|------| -| 开始节点 | StartNode | `START` | 流程起点 | -| 结束节点 | EndNode | `END` | 流程终点 | -| 审批节点 | ApprovalNode | `APPROVAL` | 需要审批的任务节点 | -| 办理节点 | HandleNode | `HANDLE` | 需要办理的任务节点 | -| 路由分支 | RouterNode | `ROUTER` | 普通路由节点 | -| 通知节点 | NotifyNode | `NOTIFY` | 发送通知 | -| 延迟节点 | DelayNode | `DELAY` | 延迟执行 | -| 触发节点 | TriggerNode | `TRIGGER` | 事件触发 | -| 子流程节点 | SubProcessNode | `SUB_PROCESS` | 嵌套子流程 | - -**块节点/容器节点 (3种)** - 包含子节点(blocks): - -| 节点类型 | 类名 | NODE_TYPE | 说明 | -|---------|------|-----------|------| -| 条件控制 | ConditionNode | `CONDITION` | 条件分支容器,包含多个ConditionBranchNode | -| 并行控制 | ParallelNode | `PARALLEL` | 并行分支容器,包含多个ParallelBranchNode | -| 包容控制 | InclusiveNode | `INCLUSIVE` | 包容分支容器,包含多个InclusiveBranchNode | - -**分支节点 (3种)** - 作为块节点的子节点: - -| 节点类型 | 类名 | NODE_TYPE | 说明 | -|---------|------|-----------|------| -| 条件分支 | ConditionBranchNode | `CONDITION_BRANCH` | 条件控制下的分支,包含条件脚本 | -| 并行分支 | ParallelBranchNode | `PARALLEL_BRANCH` | 并行控制下的分支 | -| 包容分支 | InclusiveBranchNode | `INCLUSIVE_BRANCH` | 包容控制下的分支 | - ---- - -### 3. 动作层 (Action Layer) - -#### IFlowAction -- **位置**: `com.codingapi.flow.action.IFlowAction` -- **职责**: 节点上可执行的动作接口 -- **核心方法**: - - `type()`: 动作类型 - - `run(FlowSession)`: 执行动作的主入口 - - `generateRecords()`: 生成流程记录 - - `copy(IFlowAction)`: 复制动作属性 - -#### BaseAction -- **位置**: `com.codingapi.flow.action.BaseAction` -- **职责**: 动作的抽象基类 -- **核心方法**: - - `triggerNode()`: 递归触发后续节点 - - `equals()`: 基于id判断相等 - -#### 动作类型 (ActionType枚举) - -| 动作类 | ActionType | 说明 | -|-------|------------|------| -| SaveAction | `SAVE` | 保存 | -| PassAction | `PASS` | 通过,流程继续往下流转 | -| RejectAction | `REJECT` | 拒绝,根据配置退回上级/指定节点或终止流程 | -| AddAuditAction | `ADD_AUDIT` | 加签,指定其他人一块审批(会签模式) | -| DelegateAction | `DELEGATE` | 委派,委派给其他人员审批,完成后流程返回自己 | -| ReturnAction | `RETURN` | 退回,需要设置退回的节点 | -| TransferAction | `TRANSFER` | 转办,将流程转移给指定用户审批 | -| CustomAction | `CUSTOM` | 自定义,需要配置脚本 | - ---- - -### 4. 流程记录层 (Record Layer) - -#### FlowRecord -- **位置**: `com.codingapi.flow.record.FlowRecord` -- **职责**: 流程执行过程中的每一条记录 -- **状态定义**: - - `SATE_RECORD_TODO = 0`: 待办 - - `SATE_RECORD_DONE = 1`: 已办 - - `SATE_FLOW_RUNNING = 0`: 运行中 - - `SATE_FLOW_DONE = 1`: 已完成 - - `SATE_FLOW_FINISH = 2`: 终止 - - `SATE_FLOW_ERROR = 3`: 异常 - - `SATE_FLOW_DELETE = 4`: 删除 -- **核心属性**: - - `processId`: 流程实例ID(每次启动生成) - - `nodeId`: 当前节点ID - - `currentOperatorId`: 当前审批人ID - - `formData`: 表单数据 - - `parallelId`: 并行分支ID - - `parallelBranchNodeId`: 并行分支节点ID - - `parallelBranchTotal`: 并行分支总数 - ---- - -### 5. 会话层 (Session Layer) - -#### FlowSession -- **位置**: `com.codingapi.flow.session.FlowSession` -- **职责**: 流程执行的上下文会话对象 -- **核心属性**: - - `currentOperator`: 当前操作者 - - `workflow`: 流程定义 - - `currentNode`: 当前节点 - - `currentAction`: 当前动作 - - `currentRecord`: 当前记录 - - `formData`: 表单数据 - - `advice`: 审批意见 -- **核心方法**: - - `matchNextNodes()`: 匹配后续节点 - - `updateSession()`: 更新会话 - -#### FlowAdvice -- **位置**: `com.codingapi.flow.session.FlowAdvice` -- **职责**: 封装审批操作的相关参数 -- **核心属性**: - - `advice`: 审批意见 - - `signKey`: 签名 - - `action`: 动作类型 - - `backNode`: 退回节点(类型为 `IFlowNode`,节点对象引用) - - `transferOperators`: 转办人员列表 - ---- - -### 6. 管理器层 (Manager Layer) - -#### ActionManager -- **位置**: `com.codingapi.flow.node.manager.ActionManager` -- **职责**: 管理节点的动作列表 -- **核心方法**: - - `getActionById(String)`: 根据id获取动作 - - `getAction(Class)`: 根据类型获取动作 - - `verifySession()`: 验证会话参数 - - `verify()`: 验证动作配置 - -#### OperatorManager -- **位置**: `com.codingapi.flow.node.manager.OperatorManager` -- **职责**: 管理节点的操作者列表 -- **核心方法**: - - `match(IFlowOperator)`: 判断操作者是否匹配 - -#### StrategyManager -- **位置**: `com.codingapi.flow.node.manager.StrategyManager` -- **职责**: 管理节点策略(多人审批、超时、退回等) -- **核心方法**: - - `getTimeoutTime()`: 获取超时时间 - - `isMergeable()`: 是否可合并 - - `isEnableAdvice()`: 审批意见是否必须填写 - - `isEnableSignable()`: 是否可签名 - - `isResume()`: 是否恢复到退回节点 - - `getMultiOperatorAuditStrategyType()`: 获取多人审批类型 - - `getMultiOperatorAuditMergePercent()`: 获取并签比例 - - `generateTitle()`: 生成节点标题 - - `loadOperators()`: 加载操作者 - - `verifySession()`: 验证会话参数 - - `getStrategy(Class)`: 获取指定策略 - ---- - -### 7. 策略层 (Strategy Layer) - -#### INodeStrategy -- **位置**: `com.codingapi.flow.strategy.node.INodeStrategy` -- **职责**: 节点策略的顶层接口 -- **核心方法**: - - `toMap()`: 转换为Map - - `getId()`: 获取策略ID - - `strategyType()`: 获取策略类型 - - `copy(INodeStrategy)`: 复制策略属性 - -#### BaseStrategy -- **位置**: `com.codingapi.flow.strategy.node.BaseStrategy` -- **职责**: 策略的抽象基类 -- **核心方法**: - - `equals()`: 基于id判断相等 - - `fromMap()`: 从Map构建策略对象 - -#### 策略类型 - -**节点策略 (16种)**: - -| 策略类 | 说明 | -|-------|------| -| MultiOperatorAuditStrategy | 多人审批策略(SEQUENCE顺序/MERGE并签/ANY或签/RANDOM_ONE随机) | -| TimeoutStrategy | 超时策略 | -| SameOperatorAuditStrategy | 同一操作者审批策略 | -| RecordMergeStrategy | 记录合并策略 | -| ResubmitStrategy | 重新提交策略(是否恢复到退回节点) | -| AdviceStrategy | 审批意见策略(是否必须填写、是否可签名) | -| OperatorLoadStrategy | 操作者加载策略 | -| ErrorTriggerStrategy | 异常触发策略 | -| NodeTitleStrategy | 节点标题策略 | -| FormFieldPermissionStrategy | 表单字段权限策略 | -| DelayStrategy | 延迟策略 | -| TriggerStrategy | 触发策略 | -| RouterStrategy | 路由策略 | -| SubProcessStrategy | 子流程策略 | -| RevokeStrategy | 撤回策略(REVOKE_NEXT撤回下级/REVOKE_CURRENT撤回到当前节点) | - -**工作流策略 (2种)**: - -| 策略类 | 说明 | -|-------|------| -| InterfereStrategy | 干预策略,用于控制流程是否允许管理员干预 | -| UrgeStrategy | 催办策略,支持配置催办间隔时间(默认60秒),通过UrgeInterval判断是否需要催办 | - ---- - -### 8. 脚本层 (Script Layer) - -#### ScriptRuntimeContext -- **位置**: `com.codingapi.flow.script.runtime.ScriptRuntimeContext` -- **职责**: Groovy脚本运行时环境,支持线程安全的脚本执行和自动资源清理 -- **设计特点**: - - **线程安全**: 使用脚本哈希值进行细粒度同步,不同脚本可并发执行 - - **自动清理**: 双重清理机制(阈值触发 + 定时清理) - - **资源管理**: 单例自动管理定时任务生命周期,注册JVM关闭钩子 -- **核心属性**: - - `SCRIPT_LOCKS`: 脚本锁映射表(ConcurrentHashMap),键为脚本哈希值 - - `EXECUTION_COUNTER`: 脚本执行计数器(AtomicInteger) - - `maxLockCacheSize`: 最大锁缓存阈值(默认1000) - - `CLEANUP_INTERVAL_SECONDS`: 定时清理间隔(默认300秒,可通过系统属性`flow.script.cleanup.interval`覆盖) -- **核心方法**: - - `run(String script, Class returnType, Object... args)`: 执行脚本 - - `execute(String method, String script, Class returnType, Object... args)`: 执行指定方法 - - `clearLockCache()`: 手动清理锁缓存 - - `setMaxLockCacheSize(int)`: 设置最大锁缓存阈值 - - `getLockCacheSize()`: 获取当前锁缓存大小 - - `getExecutionCount()`: 获取脚本执行总次数 - - `enableAutoCleanup()`: 启用自动清理 - - `disableAutoCleanup()`: 禁用自动清理 - - `getCleanupIntervalSeconds()`: 获取清理间隔 - -#### 自动清理机制 - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ ScriptRuntimeContext │ -│ 自动清理双重机制 │ -└─────────────────────────────────────────────────────────────────┘ - │ - ┌───────────────┴───────────────┐ - ▼ ▼ -┌─────────────────────────┐ ┌─────────────────────────┐ -│ 阈值触发清理 │ │ 定时清理任务 │ -│ (Threshold-based) │ │ (Scheduled) │ -└─────────────────────────┘ └─────────────────────────┘ - │ │ - ▼ ▼ -┌─────────────────────────┐ ┌─────────────────────────┐ -│ 触发条件: │ │ 触发条件: │ -│ SCRIPT_LOCKS.size() > │ │ 每隔 CLEANUP_INTERVAL_ │ -│ maxLockCacheSize │ │ SECONDS 秒执行一次 │ -└─────────────────────────┘ └─────────────────────────┘ - │ │ - ▼ ▼ -┌─────────────────────────┐ ┌─────────────────────────┐ -│ 清理动作: │ │ 清理动作: │ -│ SCRIPT_LOCKS.clear() │ │ SCRIPT_LOCKS.clear() │ -│ EXECUTION_COUNTER.set(0)│ │ EXECUTION_COUNTER.set(0)│ -└─────────────────────────┘ └─────────────────────────┘ -``` - -**配置方式**: -- **阈值配置**: `ScriptRuntimeContext.setMaxLockCacheSize(500)` -- **定时配置**: JVM启动参数 `-Dflow.script.cleanup.interval=300` -- **动态控制**: `enableAutoCleanup()` / `disableAutoCleanup()` - -**生命周期管理**: -1. 单例初始化时自动启动定时清理任务(守护线程) -2. 注册JVM关闭钩子确保资源释放 -3. 支持运行时动态启用/禁用自动清理 - -#### 脚本类型 - -| 脚本类 | 说明 | -|-------|------| -| OperatorMatchScript | 操作者匹配脚本 | -| OperatorLoadScript | 操作者加载脚本 | -| NodeTitleScript | 节点标题脚本 | -| ConditionScript | 条件判断脚本 | -| RouterNodeScript | 路由节点脚本 | -| SubProcessScript | 子流程脚本 | -| TriggerScript | 触发器脚本 | -| ErrorTriggerScript | 异常触发脚本 | -| RejectActionScript | 拒绝动作脚本 | -| CustomScript | 自定义动作脚本 | - ---- - -## 二、流程生命周期 - -### 1. 流程创建阶段 (FlowCreateService) - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ FlowCreateService │ -└─────────────────────────────────────────────────────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 1. 验证请求参数 │ - │ request.verify() │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 2. 获取流程定义 │ - │ workflowRepository.get() │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 3. 验证流程定义 │ - │ workflow.verify() │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 4. 创建/获取备份 │ - │ WorkflowBackup │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 5. 验证创建者权限 │ - │ matchCreatedOperator() │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 6. 构建表单数据 │ - │ new FormData() │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 7. 创建开始会话 │ - │ FlowSession.startSession() │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 8. 验证会话参数 │ - │ verifySession() │ - │ (委托给ActionManager和StrategyManager) │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 9. 生成流程记录 │ - │ generateCurrentRecords() │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 10. 保存记录 │ - │ flowRecordRepository.saveAll() │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 11. 推送事件 │ - │ FlowRecordStartEvent │ - │ FlowRecordTodoEvent │ - └─────────────────┘ -``` - ---- - -### 2. 流程执行阶段 (FlowActionService) - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ FlowActionService │ -└─────────────────────────────────────────────────────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 1. 验证请求参数 │ - │ request.verify() │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 2. 验证操作者 │ - │ flowOperatorGateway.get() │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 3. 获取流程记录 │ - │ flowRecordRepository.get() │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 4. 验证记录状态 │ - │ isTodo() │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 5. 加载流程定义 │ - │ workflowBackup.toWorkflow() │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 6. 获取当前节点 │ - │ workflow.getFlowNode() │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 7. 获取动作对象 │ - │ actionManager.getActionById() │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 8. 构建表单数据 │ - │ new FormData() │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 9. 创建执行会话 │ - │ new FlowSession() │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 10. 验证会话参数 │ - │ verifySession() │ - │ (委托给ActionManager和StrategyManager) │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ 11. 执行动作 │ - │ flowAction.run(session) │ - └─────────────────┘ -``` - ---- - -### 3. 节点生命周期 (Node Lifecycle) - -``` - ┌─────────────────────────────────────────┐ - │ 节点生命周期 │ - └─────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 1. verifySession(session) │ - │ 验证会话参数是否满足节点要求 │ - │ 委托给ActionManager和StrategyManager │ - └───────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 2. continueTrigger(session) │ - │ 判断是否继续执行后续节点 │ - │ true: 继续执行下一节点 │ - │ false: 执行步骤3 │ - └───────────────────────────────────────────┘ - │ │ - true │ │ false - ▼ ▼ - ┌─────────────────────┐ ┌─────────────────────────┐ - │ 递归执行下一节点 │ │ 3. generateCurrentRecords() │ - │ │ │ 生成当前节点的流程记录 │ - └─────────────────────┘ └─────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 4. fillNewRecord(session, record) │ - │ 填充新记录的数据 │ - │ 通过StrategyManager填充 │ - └───────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 5. isDone(session) │ - │ 判断节点是否完成 │ - │ 通过StrategyManager判断多人审批进度 │ - └───────────────────────────────────────────┘ - │ │ - done │ │ not done - ▼ ▼ - ┌─────────────────────┐ ┌─────────────────────┐ - │ 继续执行下一节点 │ │ 节点未完成,等待 │ - │ │ │ 下一次操作 │ - └─────────────────────┘ └─────────────────────┘ -``` - ---- - -### 4. 节点执行流程 (以PassAction为例) - -``` - ┌─────────────────────────────────────────┐ - │ PassAction.run() │ - └─────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 1. 判断节点是否完成 │ - │ currentNode.isDone(session) │ - │ 通过StrategyManager判断 │ - └───────────────────────────────────────────┘ - │ │ - done │ │ not done - ▼ ▼ - ┌─────────────────────┐ ┌─────────────────────┐ - │ 更新当前记录为已办 │ │ 更新当前记录, │ - │ record.update(...) │ │ 保持待办状态 │ - └─────────────────────┘ └─────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 2. 生成后续记录 │ - │ generateRecords(session) │ - │ 判断是否为退回重新提交 │ - └───────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 3. 触发后续节点 │ - │ triggerNode(session, callback) │ - └───────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 4. 获取下一节点列表 │ - │ session.matchNextNodes() │ - └───────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 5. 遍历下一节点 │ - │ for (IFlowNode node : nextNodes) │ - └───────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 6. 更新会话到下一节点 │ - │ session.updateSession(node) │ - └───────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 7. continueTrigger() │ - │ 判断是否继续执行 │ - └───────────────────────────────────────────┘ - │ │ - true │ │ false - ▼ ▼ - ┌─────────────────────┐ ┌─────────────────────┐ - │ 递归执行下一节点 │ │ 生成当前节点记录 │ - │ triggerNode() │ │ callback() │ - └─────────────────────┘ └─────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 8. 保存所有记录 │ - │ RepositoryContext.saveRecords() │ - └───────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 9. 推送事件 │ - │ FlowRecordDoneEvent │ - │ FlowRecordTodoEvent │ - └───────────────────────────────────────────┘ -``` - ---- - -### 5. 并行分支执行流程 - -``` - ┌─────────────────────────────────────────┐ - │ 遇到ParallelBranchNode │ - └─────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 1. filterBranches() │ - │ 分析并行分支的结束汇聚节点 │ - └───────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 2. 记录并行信息 │ - │ flowRecord.parallelBranchNode() │ - │ - parallelBranchNodeId: 汇聚节点ID │ - │ - parallelBranchTotal: 分支总数 │ - │ - parallelId: 并行实例ID │ - └───────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 3. 同时执行所有并行分支 │ - │ 为每个分支生成流程记录 │ - └───────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 4. 分支执行中... │ - │ 每个分支独立执行 │ - └───────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 5. 到达汇聚节点 │ - │ isWaitParallelRecord() │ - └───────────────────────────────────────────┘ - │ │ - 等待 │ │ 全部到达 - ▼ ▼ - ┌─────────────────────┐ ┌─────────────────────┐ - │ 增加触发计数 │ │ 清空并行信息 │ - │ 等待其他分支完成 │ │ 继续执行后续流程 │ - └─────────────────────┘ └─────────────────────┘ -``` - ---- - -### 6. 催办流程 (Urge Flow) - -``` - ┌─────────────────────────────────────────┐ - │ 催办检查流程 │ - └─────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 1. 获取催办策略 │ - │ UrgeStrategy │ - └───────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 2. 检查是否启用催办 │ - │ urgeStrategy.enable │ - └───────────────────────────────────────────┘ - │ │ - 启用 │ │ 未启用 - ▼ ▼ - ┌─────────────────────┐ ┌─────────────────────┐ - │ 检查催办间隔 │ │ 不执行催办 │ - └─────────────────────┘ └─────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 3. 检查是否需要催办 │ - │ urgeStrategy.hasUrge(urgeInterval) │ - └───────────────────────────────────────────┘ - │ │ - 需要催办 │ │ 不需要 - ▼ ▼ - ┌─────────────────────┐ ┌─────────────────────┐ - │ 推送催办事件 │ │ 等待下次检查 │ - │ FlowRecordUrgeEvent │ │ │ - └─────────────────────┘ └─────────────────────┘ -``` - -**UrgeInterval**: 记录催办时间间隔 -- `createTime`: 创建时间 -- `processId`: 流程实例ID -- `nodeId`: 节点ID - -**催办策略配置**: -- `enable`: 是否启用催办 -- `interval`: 催办间隔秒数(默认60秒) - ---- - -### 7. 撤回流程 (Revoke Flow) - -``` - ┌─────────────────────────────────────────┐ - │ 撤回操作流程 │ - └─────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 1. 获取撤回策略 │ - │ RevokeStrategy │ - └───────────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────────────────────────┐ - │ 2. 检查撤回类型 │ - │ revokeStrategy.type │ - └───────────────────────────────────────────┘ - │ │ - REVOKE_NEXT │ │ REVOKE_CURRENT - ▼ ▼ - ┌─────────────────────────┐ ┌─────────────────────────┐ - │ 撤回下级审批 │ │ 撤回到当前节点 │ - │ 删除后续待办记录 │ │ 恢复当前节点为待办 │ - │ 流程退回到当前节点 │ │ 清除已完成状态 │ - └─────────────────────────┘ └─────────────────────────┘ -``` - -**撤回策略类型**: -- `REVOKE_NEXT`: 撤回下级审批,删除后续待办记录 -- `REVOKE_CURRENT`: 撤回到当前节点,恢复当前节点为待办状态 - ---- - -## 三、核心设计模式 - -### 1. 建造者模式 (Builder Pattern) -- `WorkflowBuilder`: 构建流程定义 -- `BaseNodeBuilder`: 构建节点对象 -- `ActionBuilder`: 构建动作对象 -- `NodeStrategyBuilder`: 构建策略对象 - -### 2. 工厂模式 (Factory Pattern) -- `NodeFactory`: 创建不同类型的节点(通过反射调用静态formMap方法) -- `NodeStrategyFactory`: 创建不同类型的策略 -- `FlowActionFactory`: 创建不同类型的动作 - -### 3. 策略模式 (Strategy Pattern) -- `INodeStrategy`: 节点策略接口 -- `StrategyManager`: 管理多种策略,通过策略类型执行不同逻辑 -- `MultiOperatorAuditStrategy`: 多人审批策略(SEQUENCE/MERGE/ANY/RANDOM_ONE) -- `TimeoutStrategy`: 超时策略 -- `AdviceStrategy`: 审批意见策略 - -### 4. 模板方法模式 (Template Method Pattern) -- `BaseFlowNode`: 定义节点生命周期模板,子类实现特定行为 -- `BaseAction`: 定义动作执行模板,子类实现具体动作逻辑 -- `BaseStrategy`: 定义策略模板,子类实现具体策略逻辑 - -### 5. 单例模式 (Singleton Pattern) -- `NodeFactory.getInstance()`: 节点工厂单例 -- `ScriptRuntimeContext.getInstance()`: 脚本运行时单例 -- `RepositoryContext.getInstance()`: 仓储上下文单例 -- `GatewayContext.getInstance()`: 网关上下文单例 - -### 6. 责任链模式 (Chain of Responsibility Pattern) -- `triggerNode()`: 递归触发后续节点,形成责任链 -- `StrategyManager`: 遍历策略列表查找匹配策略 - -### 7. 组合模式 (Composite Pattern) -- 节点与策略的组合:每个节点包含多个策略 -- 节点与动作的组合:每个节点包含多个动作 - -### 8. 复制模式 (Copy Pattern) -- `INodeStrategy.copy()`: 策略属性复制 -- `IFlowAction.copy()`: 动作属性复制 -- `BaseFlowNode.setActions()`: 动作复制更新 -- `BaseFlowNode.setStrategies()`: 策略复制更新 - ---- - -## 四、扩展点 - -### 1. 自定义节点 -继承 `BaseFlowNode` 或 `BaseAuditNode`,实现 `IFlowNode` 接口 - -### 2. 自定义动作 -继承 `BaseAction`,实现 `IFlowAction` 接口 - -### 3. 自定义策略 -继承 `BaseStrategy`,实现 `INodeStrategy` 接口 - -### 4. 自定义脚本 -使用 `ScriptRuntimeContext` 执行 Groovy 脚本 - -### 5. 事件扩展 -监听 `FlowRecordStartEvent`, `FlowRecordTodoEvent`, `FlowRecordDoneEvent`, `FlowRecordFinishEvent`, `FlowRecordUrgeEvent` - ---- - -## 五、关键实现细节 - -### 1. 策略驱动的节点配置 -- 所有节点配置(操作者、标题、超时、权限等)都通过策略实现 -- `StrategyManager`统一管理所有策略的访问和执行 -- 策略支持复制更新,便于动态修改节点配置 - -### 2. 多人审批实现 -- 通过`MultiOperatorAuditStrategy`的type属性控制审批模式 -- SEQUENCE: 顺序审批,隐藏后续记录 -- MERGE: 并签,根据完成比例判断 -- ANY: 或签,任意一人完成即通过 -- RANDOM_ONE: 随机一人审批 - -### 3. 并行分支同步机制 -- 通过`parallelId`标识同一并行流程 -- 通过`parallelBranchNodeId`标记汇聚节点 -- 通过`parallelBranchTotal`记录分支总数 -- `RepositoryContext`维护并行触发计数 -- 汇聚节点检查所有分支是否到达后继续执行 - -### 4. 动作和策略的复制更新机制 -- 节点的`setActions()`和`setStrategies()`方法支持增量更新 -- 根据类型匹配现有对象,调用`copy()`方法复制属性 -- `CustomAction`特殊处理,支持动态添加 - -### 5. 脚本运行时线程安全设计 -- **问题**: GroovyShell和GroovyClassLoader都不是线程安全的 -- **方案**: 每次执行创建独立的GroovyClassLoader和GroovyShell实例 -- **细粒度同步**: 使用脚本哈希值作为锁键,相同脚本串行执行,不同脚本并发执行 -- **自动清理**: 阈值触发 + 定时清理双重机制,避免内存泄漏 - -### 6. 框架异常体系 - -**异常编码规则**: -所有框架异常使用字符串形式的错误码,格式为 `category.subcategory.errorType` - -**异常类型**: -- `FlowException`: 所有框架异常的基类(RuntimeException) -- `FlowValidationException`: 参数验证异常(`validation.field.required`、`validation.field.notEmpty`) -- `FlowNotFoundException`: 资源未找到异常(`notFound.workflow.definition`、`notFound.record.id`) -- `FlowStateException`: 状态异常(`state.record.alreadyDone`、`state.operator.notMatch`) -- `FlowPermissionException`: 权限异常(`permission.field.readOnly`、`permission.access.denied`) -- `FlowConfigException`: 配置异常(`config.node.strategies.required`、`config.edge.error`) -- `FlowExecutionException`: 执行异常(`execution.script.error`、`execution.node.error`) - -**异常使用规范**: -- 必须使用异常类的静态工厂方法创建异常,不直接使用 `new` 构造 -- 不使用 `ERROR_CODE_PREFIX` 等常量前缀 -- 所有异常信息使用英文 - -### 7. 流程催办机制 - -- **UrgeStrategy**: 工作流级别的催办策略 -- **催办间隔**: 默认60秒,可通过 `UrgeStrategy.interval` 配置 -- **UrgeInterval**: 记录催办时间间隔的领域对象 - - `createTime`: 创建时间 - - `processId`: 流程实例ID - - `nodeId`: 节点ID -- **催办判断**: `UrgeStrategy.hasUrge(urgeInterval)` 比较当前时间与创建时间的差值是否超过配置间隔 -- **催办事件**: 触发 `FlowRecordUrgeEvent` 通知相关系统 - -### 8. 流程干预机制 - -- **InterfereStrategy**: 工作流级别的干预策略 -- **干预控制**: 通过 `enable` 属性控制是否允许管理员干预流程 -- **应用场景**: 管理员强制干预、异常流程修正、跳节点处理 - -### 9. 节点撤回机制 - -- **RevokeStrategy**: 节点级别的撤回策略 -- **撤回类型**: - - `REVOKE_NEXT`: 撤回下级审批,删除后续待办记录,流程退回到当前节点 - - `REVOKE_CURRENT`: 撤回到当前节点,恢复当前节点为待办状态 -- **撤回权限**: 通过 `enable` 属性控制节点是否允许撤回 -- **与退回的区别**: - - 退回 (ReturnAction): 由当前审批人执行,退回到之前已完成的节点 - - 撤回 (RevokeStrategy): 由已审批的人员执行,撤回下级或恢复当前节点 diff --git a/PRD.md b/PRD.md deleted file mode 100644 index 840be715..00000000 --- a/PRD.md +++ /dev/null @@ -1,240 +0,0 @@ -# Flow Engine 产品需求文档 - -## 一、产品概述 - -Flow Engine 是一个企业级流程引擎,支持可视化流程设计、动态表单配置、多节点类型流转、脚本扩展等功能。采用前后端分离架构,后端基于 Java 17 + Spring Boot 3.5.9,前端基于 React + TypeScript。 - -## 二、核心功能模块 - -### 2.1 流程设计 - -流程设计是核心功能,提供可视化流程配置能力,分为四个部分:基础信息、表单设计、流程设计、高级设置。 - -#### 2.1.1 基础信息 - -流程定义的基础配置项: -- **流程分类**: 流程所属分类 -- **流程名称**: 流程显示名称 -- **流程编码**: 流程唯一标识 -- **流程描述**: 流程说明 -- **表单名称**: 关联的表单名称 -- **发起人设置**: 流程发起人配置 - - 指定部门 - - 指定角色 - - 任何人 -- **发起人匹配脚本**: Groovy 脚本动态匹配发起人 - -#### 2.1.2 表单设计 - -定义流程中流转的表单数据结构,支持主表和子表设计。 - -**主表字段配置**: -- 字段名称 -- 字段编码 -- 字段类型(string、int、date、boolean 等) -- 字段长度 -- 是否必填 -- 字段描述 -- 渲染方式(输入框、下拉框、日期选择器等) - -**子表配置**: -- 支持添加多个子表 -- 子表字段配置同主表 -- 支持子表数据在流程中流转 - -**字段权限控制**: -- READ - 只读 -- WRITE - 可编辑 -- NONE - 隐藏 - -#### 2.1.3 流程设计 - -基于画布的可视化流程设计,默认初始化开始节点、审批节点、结束节点。 - -**支持 15 种节点类型**: - -**基础节点 (9种)**: - -| 节点类型 | 说明 | 配置项 | -|---------|------|--------| -| 开始节点 | 流程起点 | 发起人设置 | -| 结束节点 | 流程终点 | - | -| 审批节点 | 需要审批的任务节点 | 审批人配置、多人审批策略、超时处理、审批意见、签名设置 | -| 办理节点 | 需要办理的任务节点 | 同审批节点 | -| 通知节点 | 发送通知,无需审批 | 通知人配置、通知内容 | -| 路由分支 | 普通路由节点 | 分支节点配置 | -| 子流程节点 | 嵌套子流程 | 子流程配置、子流程发起人 | -| 延迟节点 | 延迟执行 | 延迟时间、延迟单位 | -| 触发节点 | 事件触发 | 触发条件脚本、触发事件 | - -**块节点/容器节点 (3种)** - 包含子节点(blocks): - -| 节点类型 | 说明 | 配置项 | -|---------|------|--------| -| 条件控制 | 条件分支容器,包含多个条件分支 | 分支节点配置 | -| 并行控制 | 并行分支容器,包含多个并行分支 | 分支节点配置、汇聚节点 | -| 包容控制 | 包容分支容器,包含多个包容分支 | 分支节点配置 | - -**分支节点 (3种)** - 作为块节点的子节点: - -| 节点类型 | 说明 | 配置项 | -|---------|------|--------| -| 条件分支 | 条件控制下的分支 | 条件表达式脚本 | -| 并行分支 | 并行控制下的分支 | - | -| 包容分支 | 包容控制下的分支 | 条件表达式脚本 | - -**节点配置项**: -1. **基础信息**: 节点名称、节点类型 -2. **审批人配置**: - - 指定人员 - - 指定角色 - - 指定部门 - - 脚本动态加载审批人 -3. **多人审批策略**: - - SEQUENCE - 顺序审批 - - MERGE - 会签(可设置比例) - - ANY - 或签(任意一人通过) - - RANDOM_ONE - 随机一人审批 -4. **超时处理**: - - 超时时间设置 - - 处理方式:自动提醒、自动同意、自动拒绝 -5. **审批意见**: 是否必填 -6. **签名设置**: 是否需要签名 -7. **退回设置**: 退回后恢复到退回节点还是逐级提交 -8. **节点标题**: 支持脚本动态生成 -9. **异常触发**: 支持脚本配置异常处理逻辑 -10. **表单字段权限**: 字段级别的读写权限控制 - -**节点连接**: -- 支持节点间连线配置流转方向 -- 自动检测非法连接(如闭环、孤岛节点) - -#### 2.1.4 高级设置 - -全局流程配置: -- **流程撤销**: 是否允许撤销 -- **流程干预**: 是否允许干预 -- **流程催办**: 是否启用催办功能,支持配置催办间隔时间 -- **相同审批人跳过**: 自动跳过相同的审批人 -- **发起人作废流程**: 允许发起人作废流程 -- **通知配置**: 流程事件通知 - -### 2.2 流程操作 - -流程执行过程中支持的操作: - -| 操作 | 说明 | 状态变化 | -|-----|------|---------| -| 通过 | 审批通过,流程继续流转 | 待办 → 已办 | -| 拒绝 | 拒绝当前流程,可配置退回上级/指定节点/终止流程 | 待办 → 已办(流程可能终止) | -| 保存 | 保存当前表单数据,不推进流程 | 待办 → 待办 | -| 加签 | 增加其他审批人,以会签模式处理 | 增加待办 | -| 委派 | 委派给其他人审批,完成后返回 | 委派他人 | -| 退回 | 退回到指定节点重新审批 | 回退到指定节点 | -| 转办 | 将流程转移给指定用户 | 转移他人 | -| 撤回 | 撤回下级审批或恢复当前节点状态 | 根据撤回类型 | -| 自定义 | 通过脚本自定义操作 | 根据脚本逻辑 | - -### 2.3 委托代理 - -支持审批人委托功能,当审批人不在岗时,可将审批权限委托给他人。 - -**委托配置**: -- 委托人 -- 被委托人 -- 委托有效期 -- 委托范围(全部流程/指定流程) - -### 2.4 流程监控 - -- 流程实例查询 -- 流程记录查询 -- 待办/已办查询 -- 流程进度跟踪 - -### 2.5 流程催办 - -支持流程催办功能,当待办任务长时间未处理时,系统自动催办: - -**催办配置**: -- 催办间隔时间(默认60秒) -- 是否启用催办 - -**催办机制**: -- 系统定期检查待办任务的创建时间 -- 当超过配置的催办间隔时,触发催办事件 -- 支持通过事件集成消息通知(邮件、短信、企业微信等) - -### 2.6 流程呈现 - -支持 PC 端和移动端两种展示方式: -- **PC 端**: 完整的流程操作界面 -- **移动端**: 简化的移动操作界面 - -## 三、技术特性 - -### 3.1 扩展能力 - -- **自定义脚本**: 支持 Groovy 脚本扩展 - - 发起人匹配脚本 (OperatorMatchScript) - - 审批人加载脚本 (OperatorLoadScript) - - 节点标题脚本 (NodeTitleScript) - - 条件判断脚本 (ConditionScript) - - 路由节点脚本 (RouterNodeScript) - - 子流程脚本 (SubProcessScript) - - 触发器脚本 (TriggerScript) - - 异常触发脚本 (ErrorTriggerScript) - - 拒绝动作脚本 (RejectActionScript) - - 自定义动作脚本 (CustomScript) - -- **自定义节点**: 支持扩展新的节点类型 - -- **自定义操作**: 支持扩展新的操作类型 - -- **自定义策略**: 支持扩展新的节点策略 - -### 3.2 性能优化 - -- **脚本执行优化**: - - 线程安全的脚本运行时 - - 自动资源清理(阈值触发 + 定时清理) - - 细粒度同步锁 - -- **缓存机制**: 流程定义缓存 - -### 3.3 异常处理 - -完善的异常体系: -- FlowValidationException - 参数验证异常 -- FlowNotFoundException - 资源未找到异常 -- FlowStateException - 状态异常 -- FlowPermissionException - 权限异常 -- FlowConfigException - 配置异常 -- FlowExecutionException - 执行异常 - -## 四、非功能需求 - -### 4.1 安全性 - -- 操作人权限验证 -- 表单字段权限控制 -- 流程状态校验 - -### 4.2 可靠性 - -- 流程数据持久化 -- 并发控制 -- 事务支持 - -### 4.3 可维护性 - -- 策略驱动的节点配置 -- 清晰的异常信息 -- 完善的日志记录 - -## 五、待实现功能 - -- [ ] 流程版本管理 -- [ ] 流程模拟执行 -- [ ] 流程数据分析 -- [ ] 流程监控大屏 diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 4bd5fdf1..00000000 --- a/TODO.md +++ /dev/null @@ -1,325 +0,0 @@ -# Flow Engine 开发进度 - -## 已完成功能 ✅ - -### 节点功能 - -| 功能 | 状态 | 说明 | -|-----|------|------| -| 15 种节点类型 | ✅ | StartNode, EndNode, ApprovalNode, HandleNode, NotifyNode, RouterNode, SubProcessNode, DelayNode, TriggerNode (基础9种); ConditionNode, ParallelNode, InclusiveNode (块节点3种); ConditionBranchNode, ParallelBranchNode, InclusiveBranchNode (分支节点3种) | -| 节点层次化结构 | ✅ | 通过blocks属性实现节点间的层次关系,不再使用独立的边关系 | -| 节点生命周期管理 | ✅ | verifySession → continueTrigger → generateCurrentRecords → fillNewRecord → isDone | -| 节点配置面板 | ✅ | 基础信息、审批人配置、策略配置 | - -### 多人审批策略 - -| 功能 | 状态 | 说明 | -|-----|------|------| -| 顺序审批 (SEQUENCE) | ✅ | 按顺序依次审批,隐藏后续记录 | -| 会签 (MERGE) | ✅ | 可设置完成比例 | -| 或签 (ANY) | ✅ | 任意一人完成即可 | -| 随机审批 (RANDOM_ONE) | ✅ | 随机选择一人审批 | - -### 审批控制 - -| 功能 | 状态 | 说明 | -|-----|------|------| -| 相同审批人自动跳过 | ✅ | SameOperatorAuditStrategy | -| 审批意见必填控制 | ✅ | AdviceStrategy.adviceNullable | -| 签名控制 | ✅ | AdviceStrategy.signable | -| 超时处理 | ✅ | TimeoutStrategy (自动提醒、自动同意、自动拒绝) | -| 退回模式控制 | ✅ | ResubmitStrategy (恢复到退回节点/逐级提交) | - -### 流程操作 - -| 操作 | 状态 | 说明 | -|-----|------|------| -| 通过 (PASS) | ✅ | PassAction | -| 拒绝 (REJECT) | ✅ | RejectAction,支持退回上级/指定节点/终止流程 | -| 保存 (SAVE) | ✅ | SaveAction | -| 加签 (ADD_AUDIT) | ✅ | AddAuditAction | -| 委派 (DELEGATE) | ✅ | DelegateAction | -| 退回 (RETURN) | ✅ | ReturnAction | -| 转办 (TRANSFER) | ✅ | TransferAction | -| 自定义 (CUSTOM) | ✅ | CustomAction,支持脚本配置 | - -### 流程全局配置 - -| 功能 | 状态 | 说明 | -|-----|------|------| -| 流程撤销 | ✅ | Workflow.isRevoke | -| 流程干预 | ✅ | Workflow.isInterfere | -| 流程催办 | ✅ | UrgeStrategy,支持催办间隔配置(默认60秒) | -| 节点撤回 | ✅ | RevokeStrategy,支持撤回下级/撤回到当前节点 | -| 发起人匹配 | ✅ | OperatorMatchScript | - -### 委托功能 - -| 功能 | 状态 | 说明 | -|-----|------|------| -| 委托代理 | ✅ | 支持审批权限委托 | - -### 并行分支 - -| 功能 | 状态 | 说明 | -|-----|------|------| -| 并行分支执行 | ✅ | ParallelBranchNode | -| 汇聚节点检测 | ✅ | ParallelNodeRelationHelper | -| 并行分支同步 | ✅ | parallelId, parallelBranchNodeId, parallelBranchTotal | - -### 策略体系 - -| 策略 | 状态 | 说明 | -|-----|------|------| -| MultiOperatorAuditStrategy | ✅ | 多人审批策略 | -| TimeoutStrategy | ✅ | 超时策略 | -| SameOperatorAuditStrategy | ✅ | 同一操作者审批策略 | -| RecordMergeStrategy | ✅ | 记录合并策略 | -| ResubmitStrategy | ✅ | 重新提交策略 | -| AdviceStrategy | ✅ | 审批意见策略 | -| OperatorLoadStrategy | ✅ | 操作者加载策略 | -| ErrorTriggerStrategy | ✅ | 异常触发策略 | -| NodeTitleStrategy | ✅ | 节点标题策略 | -| FormFieldPermissionStrategy | ✅ | 表单字段权限策略 | -| DelayStrategy | ✅ | 延迟策略(支持秒/分钟/小时/天) | -| TriggerStrategy | ✅ | 触发策略 | -| RouterStrategy | ✅ | 路由策略 | -| SubProcessStrategy | ✅ | 子流程策略 | -| RevokeStrategy | ✅ | 撤回策略(撤回下级/撤回到当前节点) | -| InterfereStrategy | ✅ | 干预策略(工作流级别) | -| UrgeStrategy | ✅ | 催办策略(工作流级别,支持催办间隔配置) | - -### 脚本系统 - -| 功能 | 状态 | 说明 | -|-----|------|------| -| Groovy 脚本执行 | ✅ | ScriptRuntimeContext | -| 发起人匹配脚本 | ✅ | OperatorMatchScript | -| 审批人加载脚本 | ✅ | OperatorLoadScript | -| 节点标题脚本 | ✅ | NodeTitleScript | -| 条件判断脚本 | ✅ | ConditionScript | -| 路由节点脚本 | ✅ | RouterNodeScript | -| 子流程脚本 | ✅ | SubProcessScript | -| 触发器脚本 | ✅ | TriggerScript | -| 异常触发脚本 | ✅ | ErrorTriggerScript | -| 拒绝动作脚本 | ✅ | RejectActionScript | -| 自定义动作脚本 | ✅ | CustomScript | -| 线程安全 | ✅ | 细粒度同步锁,支持并发执行 | -| 自动资源清理 | ✅ | 阈值触发 + 定时清理 | - -### 延迟与触发节点 - -| 功能 | 状态 | 说明 | -|-----|------|------| -| 延迟节点 | ✅ | DelayNode,支持延迟时间单位配置(秒/分钟/小时/天) | -| 延迟任务管理 | ✅ | DelayTaskManager,基于Timer的延迟触发 | -| 触发节点 | ✅ | TriggerNode,支持触发脚本配置 | -| 触发任务执行 | ✅ | FlowDelayTriggerService | - -### 异常体系 - -| 异常 | 状态 | 说明 | -|-----|------|------| -| FlowException | ✅ | 基类 (RuntimeException) | -| FlowValidationException | ✅ | 参数验证异常 | -| FlowNotFoundException | ✅ | 资源未找到异常 | -| FlowStateException | ✅ | 状态异常 | -| FlowPermissionException | ✅ | 权限异常 | -| FlowConfigException | ✅ | 配置异常 | -| FlowExecutionException | ✅ | 执行异常 | -| 字符串错误码格式 | ✅ | `category.subcategory.errorType` 格式 | -| 静态工厂方法 | ✅ | 所有异常通过静态工厂方法创建 | - -### 表单系统 - -| 功能 | 状态 | 说明 | -|-----|------|------| -| 主表字段配置 | ✅ | FormMeta | -| 子表支持 | ✅ | SubFormMeta | -| 字段权限控制 | ✅ | READ/WRITE/NONE | -| 表单数据验证 | ✅ | FormData | - -### 事件系统 - -| 事件 | 状态 | 说明 | -|-----|------|------| -| FlowRecordStartEvent | ✅ | 流程开始事件 | -| FlowRecordTodoEvent | ✅ | 待办事件 | -| FlowRecordDoneEvent | ✅ | 已办事件 | -| FlowRecordFinishEvent | ✅ | 流程完成事件 | -| FlowRecordUrgeEvent | ✅ | 催办事件 | - -### 设计模式应用 - -| 模式 | 状态 | 说明 | -|-----|------|------| -| Builder Pattern | ✅ | WorkflowBuilder, BaseNodeBuilder | -| Factory Pattern | ✅ | NodeFactory, NodeStrategyFactory, FlowActionFactory | -| Strategy Pattern | ✅ | INodeStrategy + StrategyManager | -| Template Method | ✅ | BaseFlowNode, BaseAction, BaseStrategy | -| Singleton Pattern | ✅ | ScriptRuntimeContext, RepositoryContext, GatewayContext | -| Chain of Responsibility | ✅ | triggerNode() 递归触发 | -| Composite Pattern | ✅ | 节点包含多个策略和动作 | -| Copy Pattern | ✅ | 策略和动作的复制更新 | - ---- - -## 待开发功能 🚧 - -### 节点功能增强 - -- [ ] **条件分支节点增强** - - [ ] 支持多条件表达式组合 - - [ ] 条件优先级配置 - - [ ] 默认分支配置 - -- [ ] **子流程节点完善** - - [ ] 子流程参数传递机制 - - [ ] 子流程结果返回 - - [ ] 子流程异步调用 - - [ ] 子流程实例管理 - -- [ ] **包容分支节点完善** - - [ ] 包容分支汇聚逻辑 - - [ ] 包容分支条件配置 - -### 流程功能 - -- [ ] **流程版本管理** - - [ ] 流程版本历史记录 - - [ ] 版本对比功能 - - [ ] 版本回滚功能 - -- [ ] **流程模拟** - - [ ] 流程预执行 - - [ ] 流程路径分析 - - [ ] 潜在问题检测 - -- [ ] **流程撤回** - - [ ] 撤回条件判断 - - [ ] 撤回权限控制 - - [ ] 撤回记录追溯 - -- [ ] **流程干预** - - [ ] 管理员强制干预 - - [ ] 异常流程修正 - - [ ] 跳节点处理 - -### 监控与分析 - -- [ ] **流程监控大屏** - - [ ] 流程实例统计 - - [ ] 节点效率分析 - - [ ] 异常流程预警 - -- [ ] **流程数据分析** - - [ ] 流程周期统计 - - [ ] 审批效率分析 - - [ ] 流程瓶颈识别 - - [ ] 工作负载分析 - -### 性能优化 - -- [ ] **缓存机制** - - [ ] 流程定义缓存 - - [ ] 审批人缓存 - - [ ] 脚本编译缓存 - -- [ ] **异步处理** - - [ ] 异步事件通知 - - [ ] 异步脚本执行 - - [ ] 异步日志记录 - -- [ ] **数据库优化** - - [ ] 索引优化 - - [ ] 分表分页查询 - - [ ] 历史数据归档 - -### 前端功能 - -- [ ] **流程设计器** - - [ ] 节点拖拽设计 - - [ ] 连线绘制 - - [ ] 节点配置面板 - - [ ] 实时预览 - -- [ ] **流程展示** - - [ ] PC 端流程处理界面 - - [ ] 移动端流程处理界面 - - [ ] 流程进度可视化 - - [ ] 流程图展示 - -- [ ] **流程监控** - - [ ] 流程实例查询 - - [ ] 流程记录查询 - - [ ] 待办/已办列表 - - [ ] 流程统计分析 - -### 扩展功能 - -- [ ] **国际化支持** - - [ ] 多语言支持 - - [ ] 时区处理 - -- [ ] **消息通知** - - [ ] 邮件通知 - - [ ] 短信通知 - - [ ] 站内消息 - - [ ] 企业微信/钉钉集成 - -- [ ] **电子签名** - - [ ] 手写签名 - - [ ] 数字证书签名 - - [ ] 签名验证 - -- [ ] **附件管理** - - [ ] 附件上传 - - [ ] 附件预览 - - [ ] 附件权限控制 - ---- - -## 技术债务 🔧 - -- [ ] 补充单元测试覆盖率(目标 80%+) -- [ ] 完善 API 文档(Swagger/OpenAPI) -- [ ] 代码规范检查工具集成(Checkstyle/SonarQube) -- [ ] 性能基准测试 -- [ ] 安全漏洞扫描 -- [ ] 代码复杂度优化 -- [ ] 异常处理统一化 - ---- - -## 版本规划 - -### v0.2.0 (当前开发版本) - -- ✅ 15 种节点类型实现(新增ConditionNode、ParallelNode、InclusiveNode块节点) -- ✅ 节点层次化结构(blocks属性替代独立的边关系) -- ✅ FlowNodeState节点分类(块节点vs分支节点) -- ✅ FlowNodeEdgeManager关系管理器(遍历blocks实现) -- ✅ 延迟节点完整实现 -- ✅ 触发节点完整实现 -- ✅ 路由节点完整实现 -- ✅ 异常体系标准化(字符串错误码格式) -- ✅ 脚本系统完善(10种脚本类型) -- ✅ 流程催办功能(UrgeStrategy + UrgeInterval + FlowRecordUrgeEvent) -- ✅ 流程干预功能(InterfereStrategy) -- ✅ 节点撤回功能(RevokeStrategy) -- 🚧 前端流程设计器基础功能(@flowgram.ai集成) - -### v0.3.0 (计划中) - -- [ ] 流程版本管理 -- [ ] 流程模拟功能 -- [ ] 消息通知集成 -- [ ] 前端流程处理界面 - -### v1.0.0 (未来) - -- [ ] 流程监控大屏 -- [ ] 流程数据分析 -- [ ] 性能优化(缓存、异步) -- [ ] 完整的前后端实现 -- [ ] 企业级部署支持 diff --git a/designs/approval.pen b/designs/approval.pen deleted file mode 100644 index c199d832..00000000 --- a/designs/approval.pen +++ /dev/null @@ -1,973 +0,0 @@ -{ - "version": "2.8", - "children": [ - { - "type": "frame", - "id": "H0Aq7", - "x": 0, - "y": 0, - "name": "Design System", - "width": 400, - "layout": "vertical", - "gap": 40, - "padding": 28, - "children": [ - { - "type": "frame", - "id": "r5fJ4", - "name": "Button/Primary", - "reusable": true, - "height": 40, - "fill": "$--color-accent", - "stroke": { - "align": "center", - "thickness": 1, - "fill": "$--color-accent" - }, - "gap": 8, - "padding": [ - 10, - 20 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "geiia", - "name": "ButtonText", - "fill": "#FFFFFF", - "content": "通过", - "textAlign": "center", - "textAlignVertical": "middle", - "fontFamily": "Space Grotesk", - "fontSize": 13, - "fontWeight": "500" - } - ] - }, - { - "id": "fyr3p", - "type": "ref", - "ref": "r5fJ4", - "name": "Button/Secondary", - "y": 108, - "fill": "#FFFFFF", - "stroke": { - "align": "center", - "thickness": 1, - "fill": "$--color-accent" - } - }, - { - "id": "d3jNf", - "type": "ref", - "ref": "r5fJ4", - "name": "Button/Outline", - "y": 188, - "fill": "#FFFFFF", - "stroke": { - "align": "center", - "thickness": 1, - "fill": "$--color-border" - } - }, - { - "id": "2rwqv", - "type": "ref", - "ref": "r5fJ4", - "name": "Button/Ghost", - "y": 268, - "fill": "none", - "stroke": { - "thickness": 0, - "fill": "transparent" - }, - "padding": [ - 10, - 12 - ] - }, - { - "type": "frame", - "id": "Y0xmz", - "name": "StatusIcon/Completed", - "reusable": true, - "width": 24, - "height": 24, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "yOY6V", - "name": "Icon", - "fill": "$--color-success", - "content": "✓", - "textAlign": "center", - "textAlignVertical": "middle", - "fontFamily": "Inter", - "fontSize": 16 - } - ] - }, - { - "type": "frame", - "id": "v7PGP", - "name": "StatusIcon/Current", - "reusable": true, - "width": 24, - "height": 24, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "7jOsJ", - "name": "Dot", - "fill": "$--color-accent", - "width": 16, - "height": 16 - } - ] - }, - { - "type": "frame", - "id": "MWEbp", - "name": "StatusIcon/Pending", - "reusable": true, - "width": 24, - "height": 24, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "jNZdZ", - "name": "Circle", - "fill": "none", - "width": 16, - "height": 16, - "stroke": { - "align": "center", - "thickness": 2, - "fill": "$--color-text-muted" - } - } - ] - }, - { - "type": "frame", - "id": "Q7nd7", - "name": "FormField/ReadOnly", - "reusable": true, - "width": "fill_container", - "layout": "vertical", - "gap": 8, - "children": [ - { - "type": "text", - "id": "L7fix", - "name": "Label", - "fill": "$--color-text-secondary", - "content": "字段名称", - "fontFamily": "Inter", - "fontSize": 13 - }, - { - "type": "frame", - "id": "GoHrJ", - "name": "ValueContainer", - "width": "fill_container", - "height": 40, - "fill": "#FFFFFF", - "stroke": { - "align": "center", - "thickness": 1, - "fill": "$--color-border" - }, - "padding": [ - 10, - 16 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "HP9Uk", - "name": "Value", - "fill": "$--color-text-primary", - "content": "字段值", - "fontFamily": "Inter", - "fontSize": 14 - } - ] - } - ] - }, - { - "type": "frame", - "id": "jALdQ", - "name": "ApprovalNodeItem", - "reusable": true, - "width": "fill_container", - "gap": 12, - "children": [ - { - "type": "frame", - "id": "UGfOf", - "name": "IconContainer", - "width": 24, - "height": "fit_content(0)", - "layout": "vertical", - "alignItems": "center" - }, - { - "type": "frame", - "id": "OT9MM", - "name": "Content", - "width": "fill_container", - "layout": "vertical", - "gap": 12, - "children": [ - { - "type": "frame", - "id": "W0CwU", - "name": "Header", - "width": "fill_container", - "gap": 12, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "152m4", - "name": "NodeName", - "fill": "$--color-text-primary", - "content": "节点名称", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "500" - }, - { - "type": "frame", - "id": "UKK6F", - "name": "StatusBadge", - "reusable": true, - "width": 60, - "height": 24, - "fill": "$--color-success", - "padding": [ - 4, - 10 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "Z8Mks", - "name": "StatusText", - "fill": "#FFFFFF", - "content": "通过", - "textAlign": "center", - "textAlignVertical": "middle", - "fontFamily": "Inter", - "fontSize": 12, - "fontWeight": "500" - } - ] - } - ] - }, - { - "type": "frame", - "id": "BVFyN", - "name": "Info", - "width": "fill_container", - "layout": "vertical", - "gap": 6, - "children": [ - { - "type": "text", - "id": "QQCRM", - "name": "Approver", - "fill": "$--color-text-secondary", - "content": "审批人: 张三", - "fontFamily": "Inter", - "fontSize": 13 - }, - { - "type": "text", - "id": "YbMfY", - "name": "Time", - "fill": "$--color-text-muted", - "content": "2024-02-25 10:30", - "fontFamily": "Inter", - "fontSize": 12 - } - ] - }, - { - "type": "frame", - "id": "ZLJZr", - "name": "CommentArea", - "width": "fill_container", - "layout": "vertical", - "gap": 6, - "children": [ - { - "type": "text", - "id": "4dp39", - "name": "CommentLabel", - "fill": "$--color-text-secondary", - "content": "审批意见", - "fontFamily": "Inter", - "fontSize": 13 - }, - { - "type": "frame", - "id": "hJVqy", - "name": "CommentText", - "width": "fill_container", - "fill": "$--color-surface-tint", - "padding": 12, - "children": [ - { - "type": "text", - "id": "jOsZn", - "name": "Content", - "fill": "$--color-text-primary", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "同意采购,请财务部门审核。", - "fontFamily": "Inter", - "fontSize": 13 - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "MTclw", - "name": "CollapseButton", - "reusable": true, - "width": 32, - "height": 32, - "fill": "none", - "stroke": { - "align": "center", - "thickness": 1, - "fill": "$--color-border" - }, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "V6fOi", - "name": "Icon", - "fill": "$--color-text-secondary", - "content": "◀", - "textAlign": "center", - "textAlignVertical": "middle", - "fontFamily": "Inter", - "fontSize": 14 - } - ] - }, - { - "type": "frame", - "id": "vo8wW", - "name": "ExpandButton", - "reusable": true, - "width": 32, - "height": 32, - "fill": "#FFFFFF", - "stroke": { - "align": "center", - "thickness": 1, - "fill": "$--color-border" - }, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "5QBgk", - "name": "Icon", - "fill": "$--color-text-secondary", - "content": "▶", - "textAlign": "center", - "textAlignVertical": "middle", - "fontFamily": "Inter", - "fontSize": 14 - } - ] - }, - { - "type": "frame", - "id": "taMBt", - "name": "CollapsedStrip", - "reusable": true, - "width": 48, - "fill": "#FFFFFF", - "stroke": { - "align": "center", - "thickness": 1, - "fill": "$--color-border" - }, - "layout": "vertical", - "gap": 16, - "padding": [ - 12, - 8 - ], - "children": [ - { - "id": "5IDWO", - "type": "ref", - "ref": "vo8wW", - "name": "StripBtn" - }, - { - "type": "text", - "id": "eTqHS", - "name": "StripLabel", - "fill": "$--color-text-primary", - "content": "审批\n历史", - "lineHeight": 1.4, - "textAlign": "center", - "fontFamily": "Space Grotesk", - "fontSize": 14, - "fontWeight": "600" - } - ] - } - ] - }, - { - "type": "frame", - "id": "Eiyti", - "x": 500, - "y": 0, - "name": "Approval Screen", - "width": 1440, - "fill": "#ffffffff", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "lb1ge", - "name": "Header", - "width": "fill_container", - "height": 64, - "stroke": { - "thickness": 1, - "fill": "$--color-border" - }, - "padding": [ - 20, - 32 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "HO6K1", - "name": "HeaderLeft", - "height": "fill_container", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "jSWs6", - "name": "Title", - "fill": "$--color-text-primary", - "content": "审批详情", - "textAlignVertical": "middle", - "fontFamily": "Space Grotesk", - "fontSize": 18, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "hAhBA", - "name": "HeaderRight", - "height": "fill_container", - "gap": 12, - "alignItems": "center", - "children": [ - { - "id": "wMIrq", - "type": "ref", - "ref": "r5fJ4", - "name": "PassButton" - }, - { - "id": "FtQF8", - "type": "ref", - "ref": "r5fJ4", - "name": "RejectButton", - "fill": "#FFFFFF", - "stroke": { - "align": "center", - "thickness": 1, - "fill": "$--color-accent" - }, - "descendants": { - "geiia": { - "fill": "$--color-accent", - "content": "驳回" - } - } - }, - { - "id": "pKiQj", - "type": "ref", - "ref": "r5fJ4", - "name": "RevokeButton", - "fill": "#FFFFFF", - "stroke": { - "align": "center", - "thickness": 1, - "fill": "$--color-border" - }, - "descendants": { - "geiia": { - "fill": "$--color-text-primary", - "content": "撤回" - } - } - }, - { - "id": "tQJQj", - "type": "ref", - "ref": "r5fJ4", - "name": "CloseButton", - "fill": "none", - "stroke": { - "thickness": 0, - "fill": "transparent" - }, - "padding": [ - 10, - 12 - ], - "descendants": { - "geiia": { - "fill": "$--color-text-secondary", - "content": "关闭" - } - } - } - ] - } - ] - }, - { - "type": "frame", - "id": "300ij", - "name": "Body", - "width": "fill_container", - "gap": 24, - "padding": [ - 40, - 48, - 48, - 48 - ], - "children": [ - { - "type": "frame", - "id": "SJri1", - "name": "LeftContent", - "width": 880, - "layout": "vertical", - "gap": 24, - "children": [ - { - "type": "frame", - "id": "GCd39", - "name": "FormCard", - "width": "fill_container", - "fill": "#FFFFFF", - "stroke": { - "align": "center", - "thickness": 1, - "fill": "$--color-border" - }, - "layout": "vertical", - "padding": 28, - "children": [ - { - "type": "text", - "id": "rK77e", - "name": "Title", - "fill": "$--color-text-primary", - "content": "流程表单", - "fontFamily": "Space Grotesk", - "fontSize": 18, - "fontWeight": "600" - }, - { - "type": "frame", - "id": "7iOiP", - "name": "FormFields", - "width": "fill_container", - "layout": "vertical", - "gap": 16, - "children": [ - { - "id": "qluNt", - "type": "ref", - "ref": "Q7nd7", - "name": "Field1", - "width": "fill_container", - "descendants": { - "L7fix": { - "content": "申请标题" - }, - "HP9Uk": { - "content": "采购办公用品申请" - } - } - }, - { - "id": "88OuI", - "type": "ref", - "ref": "Q7nd7", - "name": "Field2", - "width": "fill_container", - "descendants": { - "L7fix": { - "content": "申请部门" - }, - "HP9Uk": { - "content": "技术部" - } - } - }, - { - "id": "JJzOr", - "type": "ref", - "ref": "Q7nd7", - "name": "Field3", - "width": "fill_container", - "descendants": { - "L7fix": { - "content": "申请金额" - }, - "HP9Uk": { - "content": "¥15,000.00" - } - } - }, - { - "id": "aOrE2", - "type": "ref", - "ref": "Q7nd7", - "name": "Field4", - "width": "fill_container", - "descendants": { - "L7fix": { - "content": "申请日期" - }, - "HP9Uk": { - "content": "2024-02-24" - } - } - }, - { - "id": "VfIko", - "type": "ref", - "ref": "Q7nd7", - "name": "Field5", - "width": "fill_container", - "descendants": { - "L7fix": { - "content": "申请事由" - }, - "HP9Uk": { - "content": "部门日常办公需要,采购电脑配件及文具用品" - } - } - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "AxmH9", - "name": "RightContent", - "width": 420, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "jh1VJ", - "name": "HistoryCard", - "width": "fill_container", - "fill": "#FFFFFF", - "stroke": { - "align": "center", - "thickness": 1, - "fill": "$--color-border" - }, - "layout": "vertical", - "gap": 20, - "padding": 24, - "children": [ - { - "type": "frame", - "id": "BVaLL", - "name": "TitleBar", - "width": "fill_container", - "gap": 12, - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "P5RbQ", - "name": "Title", - "fill": "$--color-text-primary", - "content": "审批节点历史记录", - "fontFamily": "Space Grotesk", - "fontSize": 18, - "fontWeight": "600" - }, - { - "id": "0bCKW", - "type": "ref", - "ref": "MTclw", - "name": "CollapseBtn" - } - ] - }, - { - "type": "frame", - "id": "yrBm2", - "name": "HistoryList", - "width": "fill_container", - "layout": "vertical", - "gap": 16, - "children": [ - { - "id": "bCwzp", - "type": "ref", - "ref": "jALdQ", - "name": "HistoryItem1", - "width": "fill_container", - "descendants": { - "UGfOf": { - "children": [ - { - "id": "iMe5r", - "type": "ref", - "ref": "Y0xmz", - "name": "StatusIcon" - } - ] - }, - "152m4": { - "content": "部门审批" - }, - "QQCRM": { - "content": "审批人: 王经理" - }, - "YbMfY": { - "content": "2024-02-24 14:30" - } - } - }, - { - "id": "IzCjx", - "type": "ref", - "ref": "jALdQ", - "name": "HistoryItem2", - "width": "fill_container", - "descendants": { - "UGfOf": { - "children": [ - { - "id": "JRtZQ", - "type": "ref", - "ref": "v7PGP", - "name": "StatusIcon" - } - ] - }, - "152m4": { - "content": "财务审批" - }, - "UKK6F": { - "fill": "$--color-accent" - }, - "Z8Mks": { - "content": "待审批" - }, - "QQCRM": { - "content": "审批人: 李总监" - }, - "YbMfY": { - "content": "等待处理" - } - } - }, - { - "id": "b3lJU", - "type": "ref", - "ref": "jALdQ", - "name": "HistoryItem3", - "width": "fill_container", - "descendants": { - "UGfOf": { - "children": [ - { - "id": "YPYal", - "type": "ref", - "ref": "MWEbp", - "name": "StatusIcon" - } - ] - }, - "152m4": { - "content": "总经理审批" - }, - "UKK6F": { - "fill": "$--color-text-secondary" - }, - "Z8Mks": { - "content": "待处理" - }, - "QQCRM": { - "content": "审批人: 张总" - }, - "YbMfY": { - "content": "待审批人处理" - } - } - } - ] - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "hX1PR", - "x": 2000, - "y": 0, - "name": "Demo Area", - "width": 500, - "gap": 40, - "padding": 48, - "children": [ - { - "type": "text", - "id": "FWWsO", - "name": "Label", - "fill": "$--color-text-muted", - "content": "展开状态", - "fontFamily": "Space Grotesk", - "fontSize": 14, - "fontWeight": "600" - }, - { - "id": "L9mDo", - "type": "ref", - "ref": "taMBt", - "name": "CollapsedDemo" - }, - { - "type": "text", - "id": "C8Mwq", - "name": "Label", - "fill": "$--color-text-muted", - "content": "收缩状态", - "fontFamily": "Space Grotesk", - "fontSize": 14, - "fontWeight": "600" - } - ] - } - ], - "variables": { - "--color-accent": { - "type": "color", - "value": "#E42313" - }, - "--color-bg": { - "type": "color", - "value": "#FFFFFF" - }, - "--color-border": { - "type": "color", - "value": "#E8E8E8" - }, - "--color-success": { - "type": "color", - "value": "#22C55E" - }, - "--color-surface-tint": { - "type": "color", - "value": "#FAFAFA" - }, - "--color-text-muted": { - "type": "color", - "value": "#B0B0B0" - }, - "--color-text-primary": { - "type": "color", - "value": "#0D0D0D" - }, - "--color-text-secondary": { - "type": "color", - "value": "#7A7A7A" - }, - "--font-body": { - "type": "string", - "value": "Inter" - }, - "--font-heading": { - "type": "string", - "value": "Space Grotesk" - }, - "--spacing-card-padding": { - "type": "number", - "value": 28 - }, - "--spacing-content-padding-h": { - "type": "number", - "value": 48 - }, - "--spacing-content-padding-v": { - "type": "number", - "value": 40 - }, - "--spacing-header-height": { - "type": "number", - "value": 64 - }, - "--spacing-section-gap": { - "type": "number", - "value": 24 - } - } -} \ No newline at end of file diff --git a/designs/title-expression-ui/README.md b/designs/title-expression-ui/README.md deleted file mode 100644 index 91da17f5..00000000 --- a/designs/title-expression-ui/README.md +++ /dev/null @@ -1,392 +0,0 @@ -# 标题表达式交互式UI设计 - -## 1. 需求概述 - -将节点标题表达式的配置从"直接编写Groovy脚本"改为"交互式可视化配置",同时保留高级用户直接编写脚本的能力。 - -### 核心功能 -- 文字内容用户直接输入 -- 变量(request属性)通过选择方式插入 -- 复杂场景可开启"高级配置"直接编辑Groovy脚本 -- 支持重置配置回到初始状态 - -### 设计原则 -- **所有标题本质都是Groovy脚本**,只是提供不同的配置方式 -- **普通模式**:通过UI交互选择变量插入,显示中文格式 -- **高级模式**:直接编写Groovy代码 -- 高级模式下无法解析回可视化,预览区域显示提示 -- 通过固定注释区分标题配置 - ---- - -## 2. request对象设计 - -### 2.1 Java对象结构 - -统一提供 `TitleGroovyRequest` 对象给所有Groovy脚本使用: - -```java -public class TitleGroovyRequest { - // 操作人信息 - private String operatorName; // 当前操作人姓名 - private Integer operatorId; // 当前操作人ID - private Boolean isFlowManager; // 是否流程管理员 - - // 流程信息 - private String workflowTitle; // 流程标题 - private String workflowCode; // 流程编码 - - // 节点信息 - private String nodeName; // 当前节点名称 - private String nodeType; // 当前节点类型 - - // 创建人信息 - private String creatorName; // 流程创建人姓名 - - // 表单数据 - private Map formData; // 表单字段值 - - // 流程编号 - private String workCode; // 流程编号 - - // Getters - public String getOperatorName(); - public Integer getOperatorId(); - public Boolean getIsFlowManager(); - public String getWorkflowTitle(); - public String getWorkflowCode(); - public String getNodeName(); - public String getNodeType(); - public String getCreatorName(); - public Object getFormData(String key); - public String getWorkCode(); -} -``` - ---- - -## 3. 变量映射类设计(核心) - -### 3.1 映射类结构 - -创建 `GroovyVariableMapping` 映射类,统一处理变量显示和转换(作为公共对象使用): - -```java -public class GroovyVariableMapping { - private String label; // 中文显示名称:如"当前操作人" - private String value; // 变量展示名:如"request.operatorName" - private String expression; // 用户界面表达式:如"request.getOperatorName()" - private String tag; // 分组,例如表单、流程、操作人等 - private int order; // 展示的数据顺序 -} -``` - -### 3.2 预定义变量映射 - -| label(中文显示) | value(变量名) | expression(Groovy语法) | tag(分组) | -|-----------------|---------------|----------------------|----------| -| 当前操作人 | request.operatorName | request.getOperatorName() | 操作人相关 | -| 当前操作人ID | request.operatorId | request.getOperatorId() | 操作人相关 | -| 是否管理员 | request.isFlowManager | request.getIsFlowManager() | 操作人相关 | -| 流程创建人 | request.creatorName | request.getCreatorName() | 操作人相关 | -| 流程标题 | request.workflowTitle | request.getWorkflowTitle() | 流程相关 | -| 流程编码 | request.workflowCode | request.getWorkflowCode() | 流程相关 | -| 当前节点 | request.nodeName | request.getNodeName() | 流程相关 | -| 节点类型 | request.nodeType | request.getNodeType() | 流程相关 | -| 流程编号 | request.workCode | request.getWorkCode() | 流程编号 | - -### 3.3 表单字段映射 - -表单字段需要动态从 `FlowFromMeta.fields` 获取: - -```java -// 从FlowFromMeta动态构建 -for (FlowFormFieldMeta field : flowFromMeta.getFields()) { - GroovyVariableMapping mapping = new GroovyVariableMapping(); - mapping.setLabel(field.getName()); // 如"请假天数" - mapping.setValue("request.formData(\"" + field.getCode() + "\")"); // 如request.formData("days") - mapping.setExpression("request.getFormData(\"" + field.getCode() + "\")"); // 如request.getFormData("days") - mapping.setTag("表单字段(当前表单)"); - mapping.setOrder(100 + index); -} -``` - ---- - -## 4. 语法规范 - -### 4.1 用户界面显示语法 - -在界面显示和编辑时,使用 `${中文标签}` 格式: - -``` -${当前操作人} -${请假天数} -${流程标题} -``` - -**完整示例:** -``` -你好,${当前操作人},有一笔${请假天数}元的审批 -``` - -### 4.2 实际Groovy脚本语法 - -```groovy -return "你好," + request.getOperatorName() + ",有一笔 " + request.getFormData("days") + " 元的审批" -``` - -### 4.3 语法转换规则 - -通过 GroovyVariableMapping 映射类进行转换: - -| label(界面显示) | value(变量名) | expression(Groovy语法) | -|-----------------|---------------|----------------------| -| 当前操作人 | request.operatorName | request.getOperatorName() | -| 请假天数 | request.formData("days") | request.getFormData("days") | -| 流程标题 | request.workflowTitle | request.getWorkflowTitle() | - -### 4.4 通过注释区分标题配置 - -在Groovy脚本中添加固定注释来标识这是标题配置: - -```groovy -// @TITLE -return "审批:" + request.getOperatorName() -``` - -系统通过检测 `// @TITLE` 注释来识别标题表达式配置。 - ---- - -## 5. 界面设计 - -### 5.1 界面清单 - -| 界面 | 用途 | 位置 | -|------|------|------| -| 节点属性面板 | 展示标题内容 + 编辑按钮 | 节点属性面板中 | -| 标题配置面板 | 点击编辑后的配置界面 | 弹框 | -| 变量选择器 | 插入变量(使用映射类数据) | 弹框 | -| 高级配置 | Groovy代码编辑 | 弹框 | - -### 5.2 节点属性面板 - -``` -┌─────────────────────────────────────┐ -│ 节点标题 │ -├─────────────────────────────────────┤ -│ ${当前操作人}的审批 [编辑] │ -└─────────────────────────────────────┘ -``` - -- 展示当前配置的标题内容(使用 `${label}` 格式) -- 点击"编辑"按钮进入标题配置面板 - -### 5.3 标题配置面板 - -#### 普通模式 -``` -┌─────────────────────────────────────────────────────────┐ -│ 标题配置 │ -├─────────────────────────────────────────────────────────┤ -│ 预览 │ -│ 你好,${当前操作人},有一笔${请假天数}元的审批 -│ ───────────────────────────────────────────────────── │ -│ [插入变量] [高级配置] │ -│ ───────────────────────────────────────────────────── │ -│ 内容 │ -│ ┌─────────────────────────────────────────────────────┐│ -│ │ 你好,${当前操作人},有一笔${请假天数}元的审批 ││ -│ └─────────────────────────────────────────────────────┘│ -│ 点击上方按钮插入变量,或直接输入文字内容 │ -│ ───────────────────────────────────────────────────── │ -│ [取消] [确定] │ -└─────────────────────────────────────────────────────────┘ -``` - -#### 高级模式 -``` -┌─────────────────────────────────────────────────────────┐ -│ 标题配置 │ -├─────────────────────────────────────────────────────────┤ -│ 预览 │ -│ ⚠ 用户自定义配置,无法预览 │ -│ ───────────────────────────────────────────────────── │ -│ [重置] [高级配置] │ -│ ───────────────────────────────────────────────────── │ -│ 内容 │ -│ ┌─────────────────────────────────────────────────────┐│ -│ │ // @TITLE ││ -│ │ return "审批:" + request.getOperatorName() ││ -│ └─────────────────────────────────────────────────────┘│ -│ ───────────────────────────────────────────────────── │ -│ [取消] [确定] │ -└─────────────────────────────────────────────────────────┘ -``` - -**关键设计说明:** -- 高级模式下预览区域显示"⚠ 用户自定义配置,无法预览"提示 -- 高级模式下"高级配置"按钮变为"重置"按钮 -- 点击"重置"可清除高级配置,回到普通模式 - -### 5.4 变量选择器(弹框) - -``` -┌─────────────────────────────┐ -│ 选择变量 │ -├─────────────────────────────┤ -│ ┌─────────────────────────┐│ -│ │ 🔍 搜索变量... ││ -│ └─────────────────────────┘│ -│ ▶ 操作人相关 │ -│ ● 当前操作人姓名 request.operatorName -│ ● 当前操作人ID request.operatorId -│ ● 是否管理员 request.isFlowManager -│ ● 流程创建人 request.creatorName -│ -│ ▶ 流程相关 │ -│ ● 流程标题 request.workflowTitle -│ ● 流程编码 request.workflowCode -│ ● 当前节点 request.nodeName -│ ● 节点类型 request.nodeType -│ -│ ▶ 表单字段(当前表单) │ -│ ● 请假天数 request.formData("days") -│ ● 请假原因 request.formData("reason") -│ ● 审批金额 request.formData("amount") -│ ● 更多字段... 动态加载当前表单字段 -│ -│ ▶ 流程编号 │ -│ ● 流程编号 request.workCode -└─────────────────────────────┘ -``` - -**显示规则:** -- 左侧:label(中文显示名称),如"当前操作人姓名" -- 右侧:value(变量名),如"request.operatorName" - ---- - -## 6. 实现方案 - -### 6.1 涉及文件 - -**后端(Java):** -- 创建 `TitleGroovyRequest` 对象封装所需数据 -- 创建 `TitleVariableMapping` 映射类(用于变量选择和语法转换) - -**前端(TypeScript):** -- 修改 `node-title.tsx` - 节点标题组件 -- 新增 `VariablePicker.tsx` - 变量选择器组件 - -### 6.2 核心逻辑 - -#### 6.2.1 变量映射服务 - -```typescript -// 前端:GroovyVariableMapping 公共对象 -interface GroovyVariableMapping { - label: string; // 中文显示名称:如"当前操作人" - value: string; // 变量展示名:如"request.operatorName" - expression: string; // 用户界面表达式:如"request.getOperatorName()" - tag: string; // 分组:如"操作人相关" - order: number; // 排序 -} - -// 预定义变量映射 -const VARIABLE_MAPPINGS: GroovyVariableMapping[] = [ - { label: '当前操作人', value: 'request.operatorName', expression: 'request.getOperatorName()', tag: '操作人相关', order: 1 }, - { label: '当前操作人ID', value: 'request.operatorId', expression: 'request.getOperatorId()', tag: '操作人相关', order: 2 }, - // ...其他预定义变量 -]; - -// 获取表单字段映射(动态) -function getFormFieldMappings(fields: FlowFormFieldMeta[]): GroovyVariableMapping[] { - return fields.map((field, index) => ({ - label: field.name, - value: 'request.formData("' + field.code + '")', - expression: 'request.getFormData("' + field.code + '")', - tag: '表单字段(当前表单)', - order: 100 + index - })); -} -``` - -#### 6.2.2 界面显示 → Groovy语法转换 - -```typescript -function toGroovySyntax(content: string, mappings: GroovyVariableMapping[]): string { - let result = content; - for (const mapping of mappings) { - // 将界面显示的label替换为Groovy expression - result = result.split(mapping.label).join('${' + mapping.expression + '}'); - } - // 添加 @TITLE 注释,并转换为Groovy字符串拼接 - return '// @TITLE\nreturn "' + result + '"'; -} -``` - -#### 6.2.3 Groovy语法 → 界面显示转换 - -```typescript -function toLabelExpression(groovyCode: string, mappings: GroovyVariableMapping[]): string { - let result = groovyCode; - // 移除 return " 和 " - result = result.replace(/^.*return\s+"([^"]*)".*$/, '$1'); - // 替换 + 拼接为直接量 - result = result.replace(/"\s*\+\s*"/g, ''); - // 将 ${expression} 替换为 label - for (const mapping of mappings) { - result = result.replaceAll('${' + mapping.expression + '}', mapping.label); - } - return result; -} -``` - -#### 6.2.4 变量选择器数据源 - -```typescript -// 合并预定义变量 + 动态表单字段 -function getVariablePickerData(flowFromMeta?: FlowFromMeta): GroovyVariableMapping[] { - const variables = [...VARIABLE_MAPPINGS]; - - // 添加表单字段(如果有) - if (flowFromMeta?.fields) { - variables.push(...getFormFieldMappings(flowFromMeta.fields)); - } - - // 按tag和order排序 - return variables.sort((a, b) => { - if (a.tag !== b.tag) return a.tag.localeCompare(b.tag); - return a.order - b.order; - }); -} -``` - -### 6.3 数据流 - -``` -┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────┐ -│ 用户界面输入 │────▶│ GroovyVariableMapping │────▶│ Groovy脚本 │ -│ 当前操作人 │ │ 映射类 │ │ request.getXxx()│ -└─────────────────┘ └──────────────────────┘ └─────────────────┘ - │ - ▼ - ┌──────────────────┐ - │ 变量选择器 │ - │ 数据源 │ - └──────────────────┘ - │ - ▼ - ┌──────────────────┐ - │ FlowFromMeta │ - │ 动态获取字段 │ - └──────────────────┘ -``` - ---- - -## 7. 设计文件位置 - -`designs/title-expression-ui/` diff --git a/designs/title-expression-ui/design.pen b/designs/title-expression-ui/design.pen deleted file mode 100644 index 496923c8..00000000 --- a/designs/title-expression-ui/design.pen +++ /dev/null @@ -1,1351 +0,0 @@ -{ - "version": "2.8", - "children": [ - { - "type": "frame", - "id": "bi8Au", - "x": -245, - "y": 93, - "name": "Frame", - "clip": true, - "width": 2420, - "height": 1254, - "fill": "#FFFFFF", - "layout": "none", - "children": [ - { - "type": "frame", - "id": "4e9Az", - "x": 1688, - "y": 377, - "name": "Advanced Editor Modal", - "width": 600, - "height": 500, - "fill": "#FFFFFF", - "cornerRadius": 8, - "stroke": { - "align": "center", - "thickness": 1, - "fill": "#E5E7EB" - }, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "keZeT", - "name": "advHeader", - "width": "fill_container", - "height": 56, - "stroke": { - "align": "inside", - "thickness": { - "bottom": 1 - }, - "fill": "#E5E7EB" - }, - "padding": [ - 16, - 20 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "SSmOk", - "name": "advTitle", - "fill": "#111827", - "content": "高级配置", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "600" - }, - { - "type": "icon_font", - "id": "YTvmV", - "name": "advCloseBtn", - "width": 20, - "height": 20, - "iconFontName": "x", - "iconFontFamily": "lucide", - "fill": "#6B7280" - } - ] - }, - { - "type": "frame", - "id": "cxUe1", - "name": "advBody", - "width": "fill_container", - "height": 380, - "layout": "vertical", - "gap": 16, - "padding": 20, - "children": [ - { - "type": "frame", - "id": "md5Ho", - "name": "advEditor", - "width": "fill_container", - "height": 280, - "fill": "#1E1E1E", - "cornerRadius": 8, - "padding": 16, - "children": [ - { - "type": "text", - "id": "xtQ83", - "name": "codeText", - "fill": "#D4D4D4", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "def run(request){\n def name = request.currentOperator.name\n def amount = request.getFormData(\"amount\")\n return \"你好,\" + name + \",有一笔\" + amount + \"元的审批\"\n}", - "fontFamily": "monospace", - "fontSize": 13, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "1dK12", - "name": "advFooter", - "width": "fill_container", - "height": 64, - "stroke": { - "align": "inside", - "thickness": { - "top": 1 - }, - "fill": "#E5E7EB" - }, - "gap": 12, - "padding": [ - 0, - 20 - ], - "justifyContent": "end", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "eH4a6", - "name": "advCancel", - "height": 36, - "cornerRadius": 6, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "#E5E7EB" - }, - "gap": 8, - "padding": [ - 8, - 16 - ], - "children": [ - { - "type": "text", - "id": "q8rsP", - "name": "advCancelText", - "fill": "#6B7280", - "content": "取消", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "xMycf", - "name": "advConfirm", - "height": 36, - "fill": "#3B82F6", - "cornerRadius": 6, - "gap": 8, - "padding": [ - 8, - 16 - ], - "children": [ - { - "type": "text", - "id": "09ma7", - "name": "advConfirmText", - "fill": "#FFFFFF", - "content": "确定", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "nsexy", - "x": 61, - "y": 94, - "name": "Default Title Panel", - "width": 744, - "height": 175, - "fill": "#FFFFFF", - "layout": "vertical", - "children": [ - { - "type": "text", - "id": "2846g", - "fill": "#111827", - "content": "节点标题", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "500" - }, - { - "type": "frame", - "id": "aR8E8", - "width": "fill_container", - "height": 60, - "gap": 12, - "padding": [ - 12, - 16 - ], - "children": [ - { - "type": "frame", - "id": "3RKcd", - "width": "fill_container", - "height": 36, - "fill": "#F9FAFB", - "cornerRadius": 6, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "#E5E7EB" - }, - "padding": [ - 8, - 12 - ], - "children": [ - { - "type": "text", - "id": "PP3Ff", - "fill": "#111827", - "content": "你好,${当前操作人},有一笔${请假天数}元的审批", - "fontFamily": "monospace", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "Kl36J", - "height": 32, - "fill": "#3B82F6", - "cornerRadius": 6, - "gap": 6, - "padding": [ - 6, - 12 - ], - "children": [ - { - "type": "text", - "id": "DEiOp", - "fill": "#FFFFFF", - "content": "配置标题", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "0Cpve", - "x": 61, - "y": 397, - "name": "Title Config Panel (Node Properties)", - "width": 892, - "height": 386, - "fill": "#FFFFFF", - "layout": "vertical", - "children": [ - { - "type": "text", - "id": "Dh8K9", - "fill": "#111827", - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "#000000ff" - }, - "content": "标题配置", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "600" - }, - { - "type": "text", - "id": "ATVM5", - "fill": "#6B7280", - "content": "预览", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "Vy1a4", - "width": "fill_container", - "height": 60, - "gap": 12, - "padding": [ - 12, - 16 - ], - "children": [ - { - "type": "frame", - "id": "wkUP1", - "width": "fill_container", - "height": 36, - "fill": "#F9FAFB", - "cornerRadius": 6, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "#E5E7EB" - }, - "padding": [ - 8, - 12 - ], - "children": [ - { - "type": "text", - "id": "zHvPn", - "fill": "#111827", - "content": "你好,${当前操作人},有一笔${请假天数}元的审批", - "fontFamily": "monospace", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "FBEgK", - "height": 32, - "fill": "#3B82F6", - "cornerRadius": 6, - "gap": 6, - "padding": [ - 6, - 12 - ], - "children": [ - { - "type": "text", - "id": "kVny3", - "fill": "#FFFFFF", - "content": "高级配置", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "GKBsg", - "width": "fill_container", - "height": 40, - "gap": 12, - "padding": [ - 16, - 16, - 8, - 16 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "CYwO9", - "height": 32, - "cornerRadius": 6, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "#3B82F6" - }, - "gap": 6, - "padding": [ - 6, - 12 - ], - "children": [ - { - "type": "text", - "id": "frDoo", - "fill": "#3B82F6", - "content": "插入变量", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "TvRbG", - "height": 32, - "cornerRadius": 6, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "#6B7280" - }, - "gap": 6, - "padding": [ - 6, - 12 - ], - "children": [ - { - "type": "text", - "id": "RkyXK", - "fill": "#6B7280", - "content": "重置", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "SgAXg", - "width": "fill_container", - "layout": "vertical", - "gap": 8, - "padding": [ - 0, - 16 - ], - "children": [ - { - "type": "frame", - "id": "D98AZ", - "width": "fill_container", - "height": 80, - "fill": "#FFFFFF", - "cornerRadius": 8, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "#E5E7EB" - }, - "padding": 12, - "children": [ - { - "type": "text", - "id": "TW6Pa", - "fill": "#111827", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "你好,${当前操作人},有一笔${请假天数}元的审批", - "fontFamily": "monospace", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "text", - "id": "9NN7Y", - "fill": "#9CA3AF", - "content": "点击上方按钮插入变量,或直接输入文字内容", - "fontFamily": "Inter", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "dJhS4", - "width": "fill_container", - "height": 56, - "gap": 12, - "padding": [ - 0, - 16 - ], - "justifyContent": "end", - "children": [ - { - "type": "frame", - "id": "UuhoR", - "height": 36, - "cornerRadius": 6, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "#E5E7EB" - }, - "gap": 8, - "padding": [ - 8, - 16 - ], - "children": [ - { - "type": "text", - "id": "t5sND", - "fill": "#6B7280", - "content": "取消", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "iIO1c", - "height": 36, - "fill": "#3B82F6", - "cornerRadius": 6, - "gap": 8, - "padding": [ - 8, - 16 - ], - "children": [ - { - "type": "text", - "id": "FnyQk", - "fill": "#FFFFFF", - "content": "确定", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "Tg0e4", - "x": 848, - "y": 470, - "name": "Variable Picker Modal", - "width": 439, - "height": 731, - "fill": "#FFFFFF", - "cornerRadius": 8, - "stroke": { - "align": "center", - "thickness": 1, - "fill": "#E5E7EB" - }, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "ho2wW", - "name": "modalHeader", - "width": "fill_container", - "height": 56, - "stroke": { - "align": "inside", - "thickness": { - "bottom": 1 - }, - "fill": "#E5E7EB" - }, - "padding": [ - 16, - 20 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "iQ1Dw", - "name": "modalTitle", - "fill": "#111827", - "content": "选择变量", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "600" - }, - { - "type": "icon_font", - "id": "KY8pm", - "name": "closeBtn", - "width": 20, - "height": 20, - "iconFontName": "x", - "iconFontFamily": "lucide", - "fill": "#6B7280" - } - ] - }, - { - "type": "frame", - "id": "HSlwr", - "name": "modalBody", - "width": "fill_container", - "height": 340, - "layout": "vertical", - "gap": 16, - "padding": 16, - "children": [ - { - "type": "frame", - "id": "ntCKP", - "name": "searchBox", - "width": "fill_container", - "height": 36, - "fill": "#F9FAFB", - "cornerRadius": 6, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "#E5E7EB" - }, - "padding": [ - 8, - 12 - ], - "children": [ - { - "type": "icon_font", - "id": "flH2t", - "name": "searchIcon", - "width": 16, - "height": 16, - "iconFontName": "search", - "iconFontFamily": "lucide", - "fill": "#9CA3AF" - }, - { - "type": "text", - "id": "uOZ2B", - "name": "searchText", - "fill": "#9CA3AF", - "content": "搜索变量...", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "i5fqr", - "name": "varList", - "width": "fill_container", - "height": 380, - "layout": "vertical", - "gap": 8, - "children": [ - { - "type": "frame", - "id": "IMuyC", - "name": "varGroup1", - "width": "fill_container", - "layout": "vertical", - "gap": 4, - "children": [ - { - "type": "text", - "id": "4oF9Q", - "name": "groupTitle1", - "fill": "#6B7280", - "content": "操作人相关", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "500" - }, - { - "type": "frame", - "id": "DB9Vm", - "name": "varItem1", - "width": "fill_container", - "height": 32, - "cornerRadius": 4, - "gap": 8, - "padding": [ - 8, - 12 - ], - "children": [ - { - "type": "icon_font", - "id": "z1Txh", - "name": "varItemIcon", - "width": 8, - "height": 8, - "iconFontName": "circle", - "iconFontFamily": "lucide", - "fill": "#3B82F6" - }, - { - "type": "text", - "id": "XDMv8", - "name": "varItemPath", - "fill": "#111827", - "content": "当前操作人姓名", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "HbUzR", - "name": "varItemDesc", - "fill": "#9CA3AF", - "content": "request.operatorName", - "fontFamily": "monospace", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "IzvSq", - "name": "varItem2", - "width": "fill_container", - "height": 32, - "cornerRadius": 4, - "gap": 8, - "padding": [ - 8, - 12 - ], - "children": [ - { - "type": "icon_font", - "id": "dqcqQ", - "name": "varItemIcon", - "width": 8, - "height": 8, - "iconFontName": "circle", - "iconFontFamily": "lucide", - "fill": "#3B82F6" - }, - { - "type": "text", - "id": "nIMte", - "name": "varItemPath", - "fill": "#111827", - "content": "当前操作人ID", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "RXuEH", - "name": "varItemDesc", - "fill": "#9CA3AF", - "content": "request.operatorId", - "fontFamily": "monospace", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "6DZwN", - "name": "varItem3", - "width": "fill_container", - "height": 32, - "cornerRadius": 4, - "gap": 8, - "padding": [ - 8, - 12 - ], - "children": [ - { - "type": "icon_font", - "id": "iZ8Q3", - "name": "varItemIcon", - "width": 8, - "height": 8, - "iconFontName": "circle", - "iconFontFamily": "lucide", - "fill": "#3B82F6" - }, - { - "type": "text", - "id": "X3Crr", - "name": "varItemPath", - "fill": "#111827", - "content": "是否流程管理员", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "NAIxS", - "name": "varItemDesc", - "fill": "#9CA3AF", - "content": "request.isFlowManager", - "fontFamily": "monospace", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "CkDfu", - "name": "varItem4", - "width": "fill_container", - "height": 32, - "cornerRadius": 4, - "gap": 8, - "padding": [ - 8, - 12 - ], - "children": [ - { - "type": "icon_font", - "id": "Lf8Gn", - "name": "varItemIcon", - "width": 8, - "height": 8, - "iconFontName": "circle", - "iconFontFamily": "lucide", - "fill": "#3B82F6" - }, - { - "type": "text", - "id": "RljJ5", - "name": "varItemPath", - "fill": "#111827", - "content": "流程创建人", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "2EMez", - "name": "varItemDesc", - "fill": "#9CA3AF", - "content": "request.creatorName", - "fontFamily": "monospace", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "DNFNj", - "name": "varGroup2", - "width": "fill_container", - "layout": "vertical", - "gap": 4, - "children": [ - { - "type": "text", - "id": "0bOCO", - "name": "groupTitle2", - "fill": "#6B7280", - "content": "流程相关", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "500" - }, - { - "type": "frame", - "id": "846LK", - "name": "varItem5", - "width": "fill_container", - "height": 32, - "cornerRadius": 4, - "gap": 8, - "padding": [ - 8, - 12 - ], - "children": [ - { - "type": "icon_font", - "id": "3rASn", - "name": "varItemIcon", - "width": 8, - "height": 8, - "iconFontName": "circle", - "iconFontFamily": "lucide", - "fill": "#10B981" - }, - { - "type": "text", - "id": "zzQP2", - "name": "varItemPath", - "fill": "#111827", - "content": "流程标题", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "f368m", - "name": "varItemDesc", - "fill": "#9CA3AF", - "content": "request.workflowTitle", - "fontFamily": "monospace", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "3Yxfn", - "name": "varItem6", - "width": "fill_container", - "height": 32, - "cornerRadius": 4, - "gap": 8, - "padding": [ - 8, - 12 - ], - "children": [ - { - "type": "icon_font", - "id": "KFVIr", - "name": "varItemIcon", - "width": 8, - "height": 8, - "iconFontName": "circle", - "iconFontFamily": "lucide", - "fill": "#10B981" - }, - { - "type": "text", - "id": "f8PSV", - "name": "varItemPath", - "fill": "#111827", - "content": "流程编码", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "lkUwz", - "name": "varItemDesc", - "fill": "#9CA3AF", - "content": "request.workflowCode", - "fontFamily": "monospace", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "CYsSk", - "name": "varItem7", - "width": "fill_container", - "height": 32, - "cornerRadius": 4, - "gap": 8, - "padding": [ - 8, - 12 - ], - "children": [ - { - "type": "icon_font", - "id": "0dYxC", - "name": "varItemIcon", - "width": 8, - "height": 8, - "iconFontName": "circle", - "iconFontFamily": "lucide", - "fill": "#10B981" - }, - { - "type": "text", - "id": "TrMtj", - "name": "varItemPath", - "fill": "#111827", - "content": "当前节点名称", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "C45PK", - "name": "varItemDesc", - "fill": "#9CA3AF", - "content": "request.nodeName", - "fontFamily": "monospace", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "6NbSZ", - "name": "varItem8", - "width": "fill_container", - "height": 32, - "cornerRadius": 4, - "gap": 8, - "padding": [ - 8, - 12 - ], - "children": [ - { - "type": "icon_font", - "id": "sDQg6", - "name": "varItemIcon", - "width": 8, - "height": 8, - "iconFontName": "circle", - "iconFontFamily": "lucide", - "fill": "#10B981" - }, - { - "type": "text", - "id": "0N908", - "name": "varItemPath", - "fill": "#111827", - "content": "当前节点类型", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "BLhPL", - "name": "varItemDesc", - "fill": "#9CA3AF", - "content": "request.nodeType", - "fontFamily": "monospace", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "7DtD7", - "name": "varGroup3", - "width": "fill_container", - "layout": "vertical", - "gap": 4, - "children": [ - { - "type": "text", - "id": "LikgK", - "name": "groupTitle3", - "fill": "#6B7280", - "content": "表单字段(当前表单)", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "500" - }, - { - "type": "frame", - "id": "rMf7N", - "name": "varItem9", - "width": "fill_container", - "height": 32, - "cornerRadius": 4, - "gap": 8, - "padding": [ - 8, - 12 - ], - "children": [ - { - "type": "icon_font", - "id": "wfeE3", - "name": "varItemIcon", - "width": 8, - "height": 8, - "iconFontName": "circle", - "iconFontFamily": "lucide", - "fill": "#F59E0B" - }, - { - "type": "text", - "id": "cVHSI", - "name": "varItemPath", - "fill": "#111827", - "content": "请假天数", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "4xjYq", - "name": "varItemDesc", - "fill": "#9CA3AF", - "content": "request.formData days", - "fontFamily": "monospace", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "mZ9kd", - "name": "varItem10", - "width": "fill_container", - "height": 32, - "cornerRadius": 4, - "gap": 8, - "padding": [ - 8, - 12 - ], - "children": [ - { - "type": "icon_font", - "id": "mUlBi", - "name": "varItemIcon", - "width": 8, - "height": 8, - "iconFontName": "circle", - "iconFontFamily": "lucide", - "fill": "#F59E0B" - }, - { - "type": "text", - "id": "EktRl", - "name": "varItemPath", - "fill": "#111827", - "content": "请假原因", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "X6RET", - "name": "varItemDesc", - "fill": "#9CA3AF", - "content": "request.formData reason", - "fontFamily": "monospace", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "0aeR2", - "name": "varItem11", - "width": "fill_container", - "height": 32, - "cornerRadius": 4, - "gap": 8, - "padding": [ - 8, - 12 - ], - "children": [ - { - "type": "icon_font", - "id": "ICylh", - "name": "varItemIcon", - "width": 8, - "height": 8, - "iconFontName": "circle", - "iconFontFamily": "lucide", - "fill": "#F59E0B" - }, - { - "type": "text", - "id": "59ISy", - "name": "varItemPath", - "fill": "#111827", - "content": "审批金额", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "Kl67r", - "name": "varItemDesc", - "fill": "#9CA3AF", - "content": "request.formData amount", - "fontFamily": "monospace", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "gt6ed", - "name": "varItem12", - "width": "fill_container", - "height": 32, - "cornerRadius": 4, - "gap": 8, - "padding": [ - 8, - 12 - ], - "children": [ - { - "type": "icon_font", - "id": "luowe", - "name": "varItemIcon", - "width": 8, - "height": 8, - "iconFontName": "loader", - "iconFontFamily": "lucide", - "fill": "#6B7280" - }, - { - "type": "text", - "id": "8UmA3", - "name": "varItemPath", - "fill": "#9CA3AF", - "content": "更多字段...", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "o3PkS", - "name": "varItemDesc", - "fill": "#9CA3AF", - "content": "动态加载当前表单字段", - "fontFamily": "Inter", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "naQjQ", - "name": "varGroup4", - "width": "fill_container", - "layout": "vertical", - "gap": 4, - "children": [ - { - "type": "text", - "id": "RMo1f", - "name": "groupTitle4", - "fill": "#6B7280", - "content": "流程编号", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "500" - }, - { - "type": "frame", - "id": "VsWbN", - "name": "varItem13", - "width": "fill_container", - "height": 32, - "cornerRadius": 4, - "gap": 8, - "padding": [ - 8, - 12 - ], - "children": [ - { - "type": "icon_font", - "id": "P7hd3", - "name": "varItemIcon", - "width": 8, - "height": 8, - "iconFontName": "circle", - "iconFontFamily": "lucide", - "fill": "#8B5CF6" - }, - { - "type": "text", - "id": "Vn2vU", - "name": "varItemPath", - "fill": "#111827", - "content": "流程编号", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "WP7vq", - "name": "varItemDesc", - "fill": "#9CA3AF", - "content": "request.workCode", - "fontFamily": "monospace", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/docs/plans/2025-02-26-title-expression-ui.md b/docs/2025-02-26-title-expression-ui/plan.md similarity index 82% rename from docs/plans/2025-02-26-title-expression-ui.md rename to docs/2025-02-26-title-expression-ui/plan.md index a2a991e7..23f88ed1 100644 --- a/docs/plans/2025-02-26-title-expression-ui.md +++ b/docs/2025-02-26-title-expression-ui/plan.md @@ -1,30 +1,30 @@ -# Title Expression UI Implementation Plan +# 标题表达式 UI 实现计划 -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. +> **给 Claude 的提示:** 必须使用 superpowers:executing-plans 技能来逐任务实现此计划。 -**Goal:** Build an interactive UI for node title expression configuration, replacing direct Groovy script editing with a user-friendly visual interface. +**目标:** 为节点标题表达式配置构建交互式 UI,用用户友好的可视化界面替代直接的 Groovy 脚本编辑。 -**Architecture:** -- Frontend maintains variable mappings (predefined + dynamic form fields) -- Backend provides `TitleGroovyRequest` for script execution -- Syntax conversion between display format (`${label}`) and Groovy code -- Two modes: Normal (visual) and Advanced (raw Groovy) +**架构:** +- 前端维护变量映射(预定义 + 动态表单字段) +- 后端提供 `TitleGroovyRequest` 用于脚本执行 +- 语法转换:显示格式(`${label}`)与 Groovy 代码之间转换 +- 两种模式:普通模式(可视化)和高级模式(原始 Groovy) -**Tech Stack:** -- Backend: Java 17, Spring Boot 3.5.9, Groovy ScriptRuntimeContext -- Frontend: React, TypeScript, Ant Design 6.x, @flowgram.ai/fixed-layout-editor +**技术栈:** +- 后端:Java 17、Spring Boot 3.5.9、Groovy ScriptRuntimeContext +- 前端:React、TypeScript、Ant Design 6.x、@flowgram.ai/fixed-layout-editor --- -## Phase 1: Backend Foundation +## 阶段一:后端基础设施 -### Task 1: Create TitleGroovyRequest +### 任务 1:创建 TitleGroovyRequest -**Files:** -- Create: `flow-engine-framework/src/main/java/com/codingapi/flow/script/runtime/TitleGroovyRequest.java` -- Test: `flow-engine-framework/src/test/java/com/codingapi/flow/script/runtime/TitleGroovyRequestTest.java` +**文件:** +- 创建:`flow-engine-framework/src/main/java/com/codingapi/flow/script/runtime/TitleGroovyRequest.java` +- 测试:`flow-engine-framework/src/test/java/com/codingapi/flow/script/runtime/TitleGroovyRequestTest.java` -**Step 1: Write the failing test** +**步骤 1:编写失败的测试** ```java package com.codingapi.flow.script.runtime; @@ -52,12 +52,12 @@ class TitleGroovyRequestTest { } ``` -**Step 2: Run test to verify it fails** +**步骤 2:运行测试验证失败** -Run: `./mvnw test -Dtest=TitleGroovyRequestTest -pl flow-engine-framework` -Expected: FAIL with "TitleGroovyRequest not found" +运行:`./mvnw test -Dtest=TitleGroovyRequestTest -pl flow-engine-framework` +预期:FAIL with "TitleGroovyRequest not found" -**Step 3: Write minimal implementation** +**步骤 3:编写最小化实现** ```java package com.codingapi.flow.script.runtime; @@ -139,12 +139,12 @@ public class TitleGroovyRequest { } ``` -**Step 4: Run test to verify it passes** +**步骤 4:运行测试验证通过** -Run: `./mvnw test -Dtest=TitleGroovyRequestTest -pl flow-engine-framework` -Expected: PASS +运行:`./mvnw test -Dtest=TitleGroovyRequestTest -pl flow-engine-framework` +预期:PASS -**Step 5: Commit** +**步骤 5:提交** ```bash git add flow-engine-framework/src/main/java/com/codingapi/flow/script/runtime/TitleGroovyRequest.java @@ -154,12 +154,12 @@ git commit -m "feat: add TitleGroovyRequest for node title expression" --- -### Task 2: Create GroovyVariableMapping DTO +### 任务 2:创建 GroovyVariableMapping DTO -**Files:** -- Create: `flow-engine-starter/src/main/java/com/codingapi/flow/web/dto/GroovyVariableMapping.java` +**文件:** +- 创建:`flow-engine-starter/src/main/java/com/codingapi/flow/web/dto/GroovyVariableMapping.java` -**Step 1: Create the DTO class** +**步骤 1:创建 DTO 类** ```java package com.codingapi.flow.web.dto; @@ -206,7 +206,7 @@ public class GroovyVariableMapping { } ``` -**Step 2: Commit** +**步骤 2:提交** ```bash git add flow-engine-starter/src/main/java/com/codingapi/flow/web/dto/GroovyVariableMapping.java @@ -215,13 +215,13 @@ git commit -m "feat: add GroovyVariableMapping DTO" --- -### Task 3: Update NodeTitleScript to use TitleGroovyRequest +### 任务 3:更新 NodeTitleScript 使用 TitleGroovyRequest -**Files:** -- Modify: `flow-engine-framework/src/main/java/com/codingapi/flow/script/node/NodeTitleScript.java` -- Test: `flow-engine-framework/src/test/java/com/codingapi/flow/script/node/NodeTitleScriptTest.java` +**文件:** +- 修改:`flow-engine-framework/src/main/java/com/codingapi/flow/script/node/NodeTitleScript.java` +- 测试:`flow-engine-framework/src/test/java/com/codingapi/flow/script/node/NodeTitleScriptTest.java` -**Step 1: Write the failing test** +**步骤 1:编写失败的测试** ```java package com.codingapi.flow.script.node; @@ -256,16 +256,16 @@ class NodeTitleScriptTest { } ``` -**Step 2: Run test to verify it fails** +**步骤 2:运行测试验证失败** -Run: `./mvnw test -Dtest=NodeTitleScriptTest -pl flow-engine-framework` -Expected: Current tests may pass, new test needs session setup +运行:`./mvnw test -Dtest=NodeTitleScriptTest -pl flow-engine-framework` +预期:当前测试可能通过,新测试需要设置 session -**Step 3: Update FlowSession to include title request data** +**步骤 3:更新 FlowSession 包含标题请求数据** -Review: `flow-engine-framework/src/main/java/com/codingapi/flow/session/FlowSession.java` +查看:`flow-engine-framework/src/main/java/com/codingapi/flow/session/FlowSession.java` -Add method to FlowSession for creating TitleGroovyRequest: +添加创建 TitleGroovyRequest 的方法: ```java /** @@ -305,7 +305,7 @@ public TitleGroovyRequest createTitleRequest() { } ``` -**Step 4: Update NodeTitleScript to use TitleGroovyRequest** +**步骤 4:更新 NodeTitleScript 使用 TitleGroovyRequest** ```java public String execute(FlowSession session) { @@ -314,12 +314,12 @@ public String execute(FlowSession session) { } ``` -**Step 5: Run tests to verify they pass** +**步骤 5:运行测试验证通过** -Run: `./mvnw test -Dtest=NodeTitleScriptTest -pl flow-engine-framework` -Expected: PASS +运行:`./mvnw test -Dtest=NodeTitleScriptTest -pl flow-engine-framework` +预期:PASS -**Step 6: Commit** +**步骤 6:提交** ```bash git add flow-engine-framework/src/main/java/com/codingapi/flow/session/FlowSession.java @@ -330,14 +330,14 @@ git commit -m "feat: update NodeTitleScript to use TitleGroovyRequest" --- -## Phase 2: Frontend Foundation +## 阶段二:前端基础设施 -### Task 4: Create TypeScript types for variable mapping +### 任务 4:创建变量映射的 TypeScript 类型定义 -**Files:** -- Create: `frontend/packages/flow-types/src/pages/design-panel/groovy-variable.ts` +**文件:** +- 创建:`frontend/packages/flow-types/src/pages/design-panel/groovy-variable.ts` -**Step 1: Create the type definitions** +**步骤 1:创建类型定义** ```typescript /** @@ -373,19 +373,19 @@ export const enum VariableTag { export type TitleExpressionMode = 'normal' | 'advanced'; ``` -**Step 2: Export from flow-types index** +**步骤 2:从 flow-types 导出** -Modify: `frontend/packages/flow-types/src/pages/design-panel/index.ts` +修改:`frontend/packages/flow-types/src/pages/design-panel/index.ts` ```typescript export * from './groovy-variable'; ``` -**Step 3: Build flow-types** +**步骤 3:构建 flow-types** -Run: `pnpm run build:flow-types` +运行:`pnpm run build:flow-types` -**Step 4: Commit** +**步骤 4:提交** ```bash git add frontend/packages/flow-types/src/pages/design-panel/groovy-variable.ts @@ -395,12 +395,12 @@ git commit -m "feat(flow-types): add GroovyVariableMapping types" --- -### Task 5: Create predefined variable mappings service +### 任务 5:创建预定义变量映射服务 -**Files:** -- Create: `frontend/packages/flow-design/src/services/groovy-variable-service.ts` +**文件:** +- 创建:`frontend/packages/flow-design/src/services/groovy-variable-service.ts` -**Step 1: Create the variable service** +**步骤 1:创建变量服务** ```typescript import { GroovyVariableMapping, VariableTag } from '@flow-engine/flow-types'; @@ -547,15 +547,15 @@ export class GroovyVariableService { } ``` -**Step 2: Export from services index** +**步骤 2:从 services 索引导出** -Modify: `frontend/packages/flow-design/src/services/index.ts` +修改:`frontend/packages/flow-design/src/services/index.ts` ```typescript export * from './groovy-variable-service'; ``` -**Step 3: Commit** +**步骤 3:提交** ```bash git add frontend/packages/flow-design/src/services/groovy-variable-service.ts @@ -565,12 +565,12 @@ git commit -m "feat(flow-design): add GroovyVariableService for variable mapping --- -### Task 6: Create syntax conversion utilities +### 任务 6:创建语法转换工具 -**Files:** -- Create: `frontend/packages/flow-design/src/utils/title-syntax-converter.ts` +**文件:** +- 创建:`frontend/packages/flow-design/src/utils/title-syntax-converter.ts` -**Step 1: Create the converter utility** +**步骤 1:创建转换器工具** ```typescript import { GroovyVariableMapping } from '@flow-engine/flow-types'; @@ -722,15 +722,15 @@ export class TitleSyntaxConverter { } ``` -**Step 2: Export from utils index** +**步骤 2:从 utils 索引导出** -Modify: `frontend/packages/flow-design/src/utils/index.ts` +修改:`frontend/packages/flow-design/src/utils/index.ts` ```typescript export * from './title-syntax-converter'; ``` -**Step 3: Commit** +**步骤 3:提交** ```bash git add frontend/packages/flow-design/src/utils/title-syntax-converter.ts @@ -740,15 +740,15 @@ git commit -m "feat(flow-design): add TitleSyntaxConverter for expression conver --- -## Phase 3: UI Components +## 阶段三:UI 组件 -### Task 7: Create VariablePicker component +### 任务 7:创建 VariablePicker 组件 -**Files:** -- Create: `frontend/packages/flow-design/src/components/design-editor/node-components/strategy/VariablePicker.tsx` -- Create: `frontend/packages/flow-design/src/components/design-editor/node-components/strategy/VariablePicker.module.less` +**文件:** +- 创建:`frontend/packages/flow-design/src/components/design-editor/node-components/strategy/VariablePicker.tsx` +- 创建:`frontend/packages/flow-design/src/components/design-editor/node-components/strategy/VariablePicker.module.less` -**Step 1: Write tests for VariablePicker** +**步骤 1:编写 VariablePicker 测试** ```typescript // VariablePicker.test.tsx @@ -778,7 +778,7 @@ describe('VariablePicker', () => { }); ``` -**Step 2: Create VariablePicker component** +**步骤 2:创建 VariablePicker 组件** ```typescript // VariablePicker.tsx @@ -883,7 +883,7 @@ export const VariablePicker: React.FC = ({ }; ``` -**Step 3: Create styles** +**步骤 3:创建样式** ```less // VariablePicker.module.less @@ -951,14 +951,14 @@ export const VariablePicker: React.FC = ({ } ``` -**Step 3: Run tests** +**步骤 4:运行测试** ```bash cd frontend/packages/flow-design pnpm run test VariablePicker.test.tsx ``` -**Step 4: Commit** +**步骤 5:提交** ```bash git add frontend/packages/flow-design/src/components/design-editor/node-components/strategy/VariablePicker.tsx @@ -969,13 +969,13 @@ git commit -m "feat(flow-design): add VariablePicker component" --- -### Task 8: Update NodeTitleStrategy component +### 任务 8:更新 NodeTitleStrategy 组件 -**Files:** -- Modify: `frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/strategy/node-title.tsx` -- Create: `frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/strategy/TitleConfigModal.tsx` +**文件:** +- 修改:`frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/strategy/node-title.tsx` +- 创建:`frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/strategy/TitleConfigModal.tsx` -**Step 1: Create TitleConfigModal component** +**步骤 1:创建 TitleConfigModal 组件** ```typescript // TitleConfigModal.tsx @@ -1167,7 +1167,7 @@ export const TitleConfigModal: React.FC = ({