初始提交

This commit is contained in:
靳中伟 2025-03-17 14:58:05 +08:00
commit a950b043c8
157 changed files with 23858 additions and 0 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="pytf" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="pytf" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/tianfeng_task.iml" filepath="$PROJECT_DIR$/.idea/tianfeng_task.iml" />
</modules>
</component>
</project>

12
.idea/tianfeng_task.iml generated Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="pytf" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

431
README.md Normal file
View File

@ -0,0 +1,431 @@
# 天风任务模块
## 项目概述
天风任务模块是AMR自主移动机器人调度系统的核心组件之一用于管理和监控机器人任务的全生命周期。该模块提供了低代码配置工具使用户能够通过可视化界面设计和配置复杂的机器人任务流程无需编写大量代码。系统支持任务版本控制、流程编辑、测试执行和源代码生成等功能大幅提高了机器人任务配置的效率和灵活性。
## 项目架构
天风任务模块采用分层架构设计,遵循关注点分离原则,各层次职责明确,便于维护和扩展:
### 1. 架构层次
- **表现层API层**处理HTTP请求和响应提供RESTful API接口
- **业务逻辑层Service层**:实现核心业务逻辑,处理任务和工作流的创建、执行和管理
- **代理层Agents层**:提供智能代理服务,支持任务自动化和优化(已设计,尚未实现)
- **领域层Core层**:定义核心领域模型和业务规则,包含工作流引擎和组件系统
- **数据层Data层**:提供数据模型定义和数据访问功能,支持任务、流程、组件等数据的持久化
- **智能服务层**:提供向量化和向量存储服务,支持知识检索和智能决策(已设计,尚未实现)
- **基础设施层**:提供数据持久化、日志记录、配置管理等基础服务
### 2. 设计模式
- **工厂模式**:用于组件的创建和注册
- **策略模式**:用于实现不同类型组件的执行逻辑
- **观察者模式**:用于任务状态变更通知
- **命令模式**:用于任务执行和撤销操作
- **组合模式**:用于构建组件树和工作流结构
- **仓储模式**:用于数据访问和持久化
### 3. 系统架构图
```
+-------------------+
| 用户界面 |
+--------+----------+
|
v
+------------------+ +----------+---------+
| | | |
| 外部知识库 +------------->+ API层 |
| | | |
+------------------+ +----+------+--------+
| |
| |
+----------------------------+ |
| |
v v
+----------+---------+ +---------+----------+
| | | |
| Agent系统 |<------------>| 业务服务层 |
| (包含LLM功能) | | |
+----+---------------+ +--------+-----------+
| |
| |
| v
| +--------+-----------+
| | |
| | 工作流引擎 |
| | |
| +--------+-----------+
| |
| |
| v
| +--------+-----------+
| | |
+----------------------------->+ 组件系统 |
| | |
| +--------+-----------+
| |
v v
+----+---------------+ +--------+-----------+
| | | |
| 智能服务层 | | 外部系统/设备 |
| (向量化和向量存储) | | |
+----+---------------+ +--------------------+
|
|
v
+----+---+
| |
|向量数据库|
| |
+--------+
```
> **注意**架构图中的Agent系统、外部知识库、智能服务层和向量数据库部分已完成设计但尚未实现。这些组件将在后续版本中逐步开发和集成。
## 项目结构
```
tianfeng_task/
├── api/ # API接口定义
│ ├── task_api.py # 任务相关API
│ ├── workflow_api.py # 工作流相关API
│ └── component_api.py # 组件相关API
├── services/ # 业务服务层
│ ├── task_service.py # 任务服务
│ ├── workflow_service.py # 工作流服务
│ ├── component_service.py # 组件服务
│ └── editor_service.py # 编辑器服务
├── agents/ # 智能代理层(已设计,尚未实现)
│ ├── base_agent.py # 代理基类
│ ├── task_agent.py # 任务代理
│ ├── llm/ # 大语言模型集成
│ ├── prompts/ # 提示词模板
│ ├── tools/ # 代理工具集
│ └── teams/ # 代理团队协作
├── components/ # 组件实现
│ ├── base_components.py # 基础组件
│ ├── flow_components.py # 流程控制组件
│ ├── http_components.py # HTTP请求组件
│ ├── robot_components.py # 机器人调度组件
│ ├── script_components.py # 脚本组件
│ ├── site_components.py # 库位组件
│ ├── subtask_components.py # 子任务组件
│ ├── task_components.py # 任务组件
│ └── device_components.py # 设备组件
├── core/ # 核心功能模块
│ ├── component.py # 组件基类定义
│ ├── context.py # 执行上下文
│ ├── exceptions.py # 异常定义
│ └── workflow.py # 工作流引擎
├── data/ # 数据存储
│ ├── models/ # 数据模型定义
│ │ ├── base.py # 基础模型
│ │ ├── task.py # 任务相关模型
│ │ ├── task_flow.py # 任务流程模型
│ │ ├── task_variable.py # 任务变量模型
│ │ ├── task_edit_history.py # 编辑历史模型
│ │ ├── component.py # 组件模型
│ │ ├── robot.py # 机器人模型
│ │ ├── storage.py # 库位模型
│ │ ├── device.py # 设备模型
│ │ └── user_operation.py # 用户操作日志模型
│ ├── session.py # 数据库会话管理
│ └── repositories/ # 数据仓储实现
├── intelligent/ # 智能服务层(已设计,尚未实现)
│ ├── vectorization/ # 向量化服务
│ ├── retrieval/ # 知识检索服务
│ ├── embedding/ # 嵌入模型服务
│ └── vector_store/ # 向量存储服务
├── migrations/ # 数据库迁移
│ ├── versions/ # 迁移版本
│ ├── env.py # 迁移环境配置
│ ├── script.py.mako # 迁移脚本模板
│ └── alembic.ini # Alembic配置
├── scripts/ # 脚本工具
│ ├── generate_migration.py # 生成迁移脚本
│ └── run_migration.py # 执行迁移脚本
├── utils/ # 工具函数
│ ├── logger.py # 日志工具
│ ├── validators.py # 数据验证工具
│ └── helpers.py # 辅助函数
├── config/ # 配置文件
│ ├── database.py # 数据库配置
│ ├── component_registry.py # 组件注册配置
│ └── settings.py # 应用设置
├── logs/ # 日志文件
├── tests/ # 测试代码
│ ├── unit/ # 单元测试
│ ├── integration/ # 集成测试
│ └── fixtures/ # 测试数据
├── app.py # 应用入口
└── requirements.txt # 依赖包列表
```
## 核心功能
### 1. 任务管理
- **任务创建**:支持创建普通任务和定时任务
- **任务监控**:实时监控任务执行状态和进度
- **任务操作**:支持启动、暂停、取消、重试等操作
- **任务历史**:记录任务执行历史和结果
- **版本控制**:支持任务版本管理,可以回滚到历史版本
- **任务测试**:支持在测试环境中执行任务,验证任务配置
### 2. 任务编辑器
天风任务模块提供了强大的可视化编辑器,支持以下功能:
- **流程图设计**:通过拖拽方式设计任务流程图
- **属性配置**:配置组件和连接的属性
- **变量管理**:定义和管理任务执行过程中的变量
- **撤销/重做**:支持编辑操作的撤销和重做
- **源代码生成**:自动生成任务执行的源代码
- **版本备份**:支持任务配置的版本备份和恢复
### 3. 组件库
天风任务模块提供了丰富的组件库,用户可以通过拖拽组件和配置参数的方式设计复杂的任务流程:
- **子任务组件**:支持任务的模块化和复用
- **脚本组件**支持编写自定义JavaScript脚本
- **HTTP请求组件**:支持与外部系统进行通信
- **任务组件**:提供任务数据管理和状态控制功能
- **流程组件**:提供条件判断、循环、并行执行等流程控制功能
- **基础组件**提供数据验证、ID生成、时间处理等基础功能
- **库位组件**:提供库位管理和操作功能
- **机器人调度组件**:提供机器人选择、控制和状态监控功能
- **设备组件**:提供与外部设备通信的功能
### 4. 智能代理系统(已设计,尚未实现)
- **任务代理**:自动分析和优化任务流程
- **LLM集成**:集成大语言模型,提供智能决策支持
- **代理工具**:提供丰富的工具集,支持代理执行各种操作
- **团队协作**:支持多代理协作完成复杂任务
- **知识检索**:从外部知识库中检索相关信息,辅助决策
### 5. 智能服务层(已设计,尚未实现)
- **向量化服务**:将文本、图像等数据转换为向量表示
- **知识检索**:基于语义相似度进行知识检索
- **嵌入模型**:提供多种嵌入模型,支持不同类型数据的向量化
- **向量存储**:高效存储和检索向量数据
### 6. 工作流引擎
- **流程执行**:解析和执行任务流程图
- **上下文管理**:管理任务执行过程中的变量和状态
- **错误处理**:提供异常捕获和处理机制
- **并行执行**:支持多分支并行执行
- **动态加载**:支持动态加载和执行组件
### 7. 数据模型
系统提供了完善的数据模型,支持任务管理的各个方面:
- **任务模型**:存储任务基本信息和配置
- **任务版本**:管理任务的不同版本
- **任务记录**:记录任务的执行情况
- **任务流程节点**:存储流程图中的节点信息
- **任务流程连接**:存储流程图中的连接信息
- **任务变量定义**:管理任务中使用的变量
- **任务编辑历史**:记录编辑操作,支持撤销/重做
- **任务备份**:存储任务的备份数据
## 执行业务流程
### 系统启动流程
1. 应用启动app.py
2. 加载配置信息config模块
3. 初始化日志系统utils.logger
4. 初始化数据库连接data.session
5. 注册所有组件config.component_registry
6. 注册API路由api模块
7. 启动HTTP服务uvicorn
### 任务创建流程
1. 用户在天风任务主页面点击"定义新任务"按钮
2. 前端发送创建任务请求到API层/api/tasks
3. API层调用任务服务task_service.py创建任务记录
4. 系统创建任务并返回任务ID
5. 前端跳转到任务编辑页面
6. 用户通过拖拽组件设计任务流程
7. 前端发送保存流程请求到API层/api/workflows
8. API层调用工作流服务workflow_service.py保存流程配置
9. 系统将流程配置保存到数据库,创建新版本
### 任务编辑流程
1. 用户打开任务编辑页面
2. 系统加载任务的最新版本和流程图数据
3. 用户通过拖拽组件和连接设计流程图
4. 用户配置组件属性和变量
5. 系统记录每一步编辑操作,支持撤销/重做
6. 用户点击保存按钮,系统创建新版本
7. 用户可以点击测试按钮,在测试环境中执行任务
8. 用户可以点击生成源码按钮,系统生成任务执行代码
### 任务执行流程
1. 用户在任务列表中选择任务并点击"执行"按钮
2. 前端发送执行任务请求到API层/api/tasks/{id}/execute
3. API层调用任务服务task_service.py创建任务记录
4. 任务服务加载任务配置和工作流定义
5. 任务服务初始化执行上下文context.py
6. 任务服务调用工作流引擎workflow.py执行任务
7. 工作流引擎解析任务流程图并按顺序执行各组件
8. 组件执行结果存储在上下文中,供后续组件使用
9. 系统实时更新任务状态和进度
10. 任务执行完成后,系统记录执行结果和日志
### 智能任务执行流程(已设计,尚未实现)
1. 用户在任务列表中选择任务并点击"智能执行"按钮
2. 前端发送智能执行任务请求到API层
3. API层调用Agent系统进行任务分析和优化
4. Agent系统从外部知识库检索相关知识
5. Agent系统基于LLM和检索到的知识生成执行计划
6. Agent系统调用业务服务层执行优化后的任务
7. 业务服务层调用工作流引擎执行任务
8. 执行过程中Agent系统持续监控任务状态
9. 遇到异常情况时Agent系统自动调整执行策略
10. 任务执行完成后Agent系统生成执行报告和优化建议
## 交互方式
天风任务模块提供多种交互方式,满足不同场景的需求:
### 1. 用户界面交互
- **任务列表界面**
- 展示所有任务及其状态
- 提供任务筛选、搜索和排序功能
- 支持任务的创建、编辑、删除、执行等操作
- 显示任务执行状态和进度
- **任务编辑界面**
- 提供可视化的流程设计工具
- 左侧组件面板:展示可用组件
- 中央流程图编辑区:通过拖拽设计任务流程
- 右侧属性面板:配置组件参数
- 顶部工具栏:保存、运行、测试、撤销、重做等操作
- **任务监控界面**
- 实时显示任务执行状态和进度
- 展示任务执行日志和变量值
- 提供任务暂停、取消、重试等操作
### 2. API接口交互
系统提供RESTful API接口支持第三方系统集成
- **任务管理API**
- `GET /api/tasks`:获取任务列表
- `POST /api/tasks`:创建新任务
- `GET /api/tasks/{id}`:获取任务详情
- `PUT /api/tasks/{id}`:更新任务
- `DELETE /api/tasks/{id}`:删除任务
- `POST /api/tasks/{id}/execute`:执行任务
- `POST /api/tasks/{id}/cancel`:取消任务
- **工作流管理API**
- `GET /api/workflows/{id}`:获取工作流定义
- `POST /api/workflows`:保存工作流定义
- `GET /api/workflows/{id}/validate`:验证工作流定义
- **编辑器API**
- `POST /api/editor/save`:保存编辑内容
- `POST /api/editor/undo`:撤销操作
- `POST /api/editor/redo`:重做操作
- `POST /api/editor/backup`:创建备份
- `GET /api/editor/history`:获取编辑历史
- **智能服务API**(已设计,尚未实现):
- `POST /api/intelligent/analyze`:分析任务流程
- `POST /api/intelligent/optimize`:优化任务流程
- `POST /api/intelligent/search`:知识检索
- `POST /api/intelligent/vectorize`:向量化数据
## 技术栈
- **后端**FastAPI (Python)
- **数据库**SQLAlchemy ORM支持MySQL、PostgreSQL等
- **前端**React + Ant Design低代码编辑器
- **API文档**Swagger UI自动生成
- **工作流引擎**自研基于DAG的工作流引擎
- **组件系统**:可扩展的组件注册和执行系统
- **数据迁移**Alembic
- **智能代理**:基于大语言模型的智能代理系统(已设计,尚未实现)
- **向量数据库**:支持高效的向量存储和检索(已设计,尚未实现)
- **嵌入模型**:支持文本、图像等数据的向量化(已设计,尚未实现)
## 部署说明
### 环境要求
- Python 3.8+
- 数据库MySQL/PostgreSQL
- Node.js 14+(前端开发)
### 安装步骤
1. 克隆代码库
2. 安装依赖:`pip install -r requirements.txt`
3. 配置数据库连接:修改`config/database.py`
4. 初始化数据库:`python -m scripts.run_migration`
5. 启动应用:`python app.py`
## 使用示例
### 创建简单任务
1. 进入天风任务主页面
2. 点击"定义新任务"按钮
3. 输入任务名称"测试任务",选择"普通任务"类型
4. 在编辑页面拖入"选择执行机器人"组件
5. 配置机器人选择参数
6. 拖入"机器人通用动作"组件,配置目标站点
7. 连接组件,形成执行流程
8. 保存任务并执行
### 使用版本控制
1. 打开现有任务的编辑页面
2. 修改任务流程或组件配置
3. 点击"保存"按钮,系统自动创建新版本
4. 在版本历史中可以查看所有历史版本
5. 可以选择任意历史版本进行查看或恢复
### 使用撤销/重做功能
1. 在编辑页面进行操作(如添加节点、移动节点等)
2. 如需撤销操作,点击工具栏中的"撤销"按钮
3. 如需重做已撤销的操作,点击"重做"按钮
4. 系统会记录编辑会话中的所有操作历史
## 常见问题
1. **任务执行失败怎么办?**
- 查看任务执行日志,定位失败原因
- 在测试环境中验证任务配置
- 修改任务配置,解决问题后重试
2. **如何调试复杂任务?**
- 使用测试功能验证任务配置
- 查看执行日志和变量值
- 使用条件断点和单步执行
3. **如何管理大量任务?**
- 使用任务模板功能创建可复用的任务模板
- 使用子任务功能将复杂任务拆分为多个子任务
- 使用标签和分类功能组织任务
## 联系方式
如有问题或建议,请联系系统管理员或开发团队。

Binary file not shown.

0
agents/__init__.py Normal file
View File

3
agents/base_agent.py Normal file
View File

@ -0,0 +1,3 @@
"""
Agent基类
"""

0
agents/llm/__init__.py Normal file
View File

3
agents/llm/base_llm.py Normal file
View File

@ -0,0 +1,3 @@
"""
LLM基类
"""

3
agents/llm/openai_llm.py Normal file
View File

@ -0,0 +1,3 @@
"""
OpenAI LLM实现
"""

View File

View File

@ -0,0 +1,3 @@
"""
任务分析提示词
"""

3
agents/task_agent.py Normal file
View File

@ -0,0 +1,3 @@
"""
任务Agent
"""

0
agents/teams/__init__.py Normal file
View File

0
agents/tools/__init__.py Normal file
View File

View File

@ -0,0 +1,3 @@
"""
任务相关工具
"""

0
api/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

133
api/component_api.py Normal file
View File

@ -0,0 +1,133 @@
# api/component_api.py
from typing import Dict, Any, List, Optional
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from services.component_service import ComponentService
from config.component_config import (
ComponentDiscovery,
ComponentCategory
)
from config.component_detail_config import ComponentDetailConfig
from config.api_config import (
ApiResponseCode,
ApiResponseMessage
)
from api.models import ApiResponse, ComponentDiscoverInput
# 创建路由器
router = APIRouter(prefix="/component", tags=["组件管理"])
# 创建服务实例
component_service = ComponentService()
@router.get("/components", response_model=ApiResponse)
async def get_components():
"""获取所有组件类型及其详细信息"""
try:
# 获取所有组件类型
component_types = component_service.get_all_component_types()
# 获取组件类型中文名称映射
component_type_names = ComponentDetailConfig.get_component_type_names()
# 构建组件类型信息,包含英文标识和中文名称
component_type_info = []
for component_type in component_types:
# 获取组件类型的中文名称,如果没有则使用英文标识
type_name = component_type_names.get(component_type, component_type)
component_type_info.append({
"type": component_type, # 英文标识
"name": type_name, # 中文名称
})
# 按类别分组
component_categories = {}
for component_type in component_types:
# 根据组件类型推断类别
category = ComponentCategory.BASE # 默认类别
# 使用配置文件中的映射关系确定组件类别
mapping = ComponentCategory.get_mapping()
for prefix, cat in mapping.items():
if component_type.startswith(prefix):
category = cat
break
if category not in component_categories:
component_categories[category] = []
component_categories[category].append(component_type)
# 获取所有组件的详细配置
component_details = ComponentDetailConfig.get_all_components()
# 按组件类型分组
component_details_by_type = {}
for component in component_details:
component_type = component["type"]
if component_type not in component_details_by_type:
component_details_by_type[component_type] = []
component_details_by_type[component_type].append(component)
return {
"code": ApiResponseCode.SUCCESS,
"message": ApiResponseMessage.Component.FETCHED,
"data": {
"component_types": component_types,
"component_type_info": component_type_info, # 新增:包含中文名称的组件类型信息
"component_categories": component_categories,
"component_details": component_details,
"component_details_by_type": component_details_by_type
}
}
except Exception as e:
raise HTTPException(
status_code=ApiResponseCode.SERVER_ERROR,
detail=f"获取组件类型失败: {str(e)}"
)
@router.get("/components/{component_type}", response_model=ApiResponse)
async def get_components_by_type(component_type: str):
"""获取指定类型的组件详细信息"""
try:
# 获取指定类型的组件详细配置
components = ComponentDetailConfig.get_components_by_type(component_type)
if not components:
return {
"code": ApiResponseCode.NOT_FOUND,
"message": f"未找到类型为 {component_type} 的组件",
"data": {"components": []}
}
return {
"code": ApiResponseCode.SUCCESS,
"message": ApiResponseMessage.Component.FETCHED,
"data": {"components": components}
}
except Exception as e:
raise HTTPException(
status_code=ApiResponseCode.SERVER_ERROR,
detail=f"获取组件详细信息失败: {str(e)}"
)
@router.post("/components/discover", response_model=ApiResponse)
async def discover_components(discover_input: ComponentDiscoverInput):
"""自动发现并注册组件"""
try:
# 自动发现并注册组件
component_service.auto_discover_components(discover_input.package_name)
# 获取所有组件类型
component_types = component_service.get_all_component_types()
return {
"code": ApiResponseCode.SUCCESS,
"message": f"{ApiResponseMessage.Component.DISCOVERED},共发现 {len(component_types)} 个组件",
"data": {"component_types": component_types}
}
except Exception as e:
raise HTTPException(
status_code=ApiResponseCode.SERVER_ERROR,
detail=f"自动发现并注册组件失败: {str(e)}"
)

93
api/models.py Normal file
View File

@ -0,0 +1,93 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
API模型模块
包含API请求和响应的数据模型
"""
from typing import Dict, Any, List, Optional, Literal
from pydantic import BaseModel, Field, validator
from config.component_config import ComponentDiscovery
from config.task_config import TASK_TYPE_CONFIG, TaskType, TaskStatus
from enum import Enum
# 获取所有任务类型的key值
TASK_TYPE_KEYS = list(TASK_TYPE_CONFIG.keys())
# 排序字段枚举
class SortField(str, Enum):
CREATED_AT = "created_at"
UPDATED_AT = "updated_at"
NAME = "name"
# 排序方式枚举
class SortOrder(str, Enum):
ASC = "asc"
DESC = "desc"
# 通用响应模型
class ApiResponse(BaseModel):
code: int = Field(..., description="状态码")
message: str = Field(..., description="消息")
data: Optional[dict] = Field(None, description="数据")
# 任务相关模型
class TaskInput(BaseModel):
name: str = Field(..., description="任务名称")
task_type: TaskType = Field(
...,
description="任务类型,可选值为:" + ", ".join([t.value for t in TaskType]),
example=TaskType.NORMAL
)
class TaskBatchInput(BaseModel):
tasks: List[TaskInput]
class TaskIdList(BaseModel):
task_ids: List[str]
class TaskTypeInfo(BaseModel):
key: str
name: str
description: str
# 工作流相关模型
class WorkflowInput(BaseModel):
name: str
workflow_type: Optional[str] = "normal"
description: Optional[str] = ""
blocks: Optional[List[Dict[str, Any]]] = []
variables: Optional[Dict[str, Any]] = {}
schedule: Optional[Dict[str, Any]] = None
class WorkflowUpdateInput(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
blocks: Optional[List[Dict[str, Any]]] = None
variables: Optional[Dict[str, Any]] = None
schedule: Optional[Dict[str, Any]] = None
class WorkflowExecuteInput(BaseModel):
task_inputs: Optional[Dict[str, Any]] = None
class WorkflowImportInput(BaseModel):
workflow_json: str
# 组件相关模型
class ComponentDiscoverInput(BaseModel):
package_name: Optional[str] = ComponentDiscovery.DEFAULT_PACKAGE
# 任务编辑相关模型
class TaskUpdateInput(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
task_type: Optional[TaskType] = None
blocks: Optional[List[Dict[str, Any]]] = None
variables: Optional[Dict[str, Any]] = None
schedule: Optional[Dict[str, Any]] = None
class TaskEditInput(BaseModel):
task_id: str
blocks: List[Dict[str, Any]]
variables: Optional[Dict[str, Any]] = {}

431
api/task_api.py Normal file
View File

@ -0,0 +1,431 @@
# api/task_api.py
from typing import Dict, Any, List, Optional
from fastapi import APIRouter, HTTPException, Depends, Query
from pydantic import BaseModel, Field
from services.task_service import TaskService
from core.exceptions import TianfengTaskError
from config.task_config import (
get_all_task_types,
get_task_type_name,
get_task_type_description,
TaskTypeConfig,
TaskStatusConfig,
DefaultConfig,
TASK_TYPE_CONFIG,
TASK_TYPE_NORMAL,
TASK_TYPE_SCHEDULED,
DEFAULT_TASK_DESCRIPTION,
DEFAULT_TEMPLATE_DESCRIPTION,
TaskType,
TaskStatus
)
from api.models import ApiResponse, TaskInput, TaskBatchInput, TaskIdList, TaskTypeInfo, SortField, SortOrder, TaskUpdateInput, TaskEditInput
# 创建路由器
router = APIRouter(tags=["任务管理"])
# 创建服务实例
task_service = TaskService()
@router.get("/tasks", response_model=ApiResponse)
async def get_tasks(
status: Optional[TaskStatus] = Query(None, description="任务状态"),
task_type: Optional[TaskType] = Query(None, description="任务类型"),
name: Optional[str] = Query(None, description="任务名称(模糊查询)"),
is_scheduled: Optional[bool] = Query(None, description="是否为定时任务"),
created_start: Optional[int] = Query(None, description="创建时间起始(毫秒时间戳)"),
created_end: Optional[int] = Query(None, description="创建时间结束(毫秒时间戳)"),
sort_by: SortField = Query(default=SortField.CREATED_AT, description="排序字段"),
sort_order: SortOrder = Query(default=SortOrder.DESC, description="排序方式"),
page: int = Query(1, ge=1, description="页码"),
page_size: int = Query(10, ge=1, le=100, description="每页数量")
):
"""
获取任务列表
支持多种筛选条件排序和分页
"""
try:
# 获取任务列表
tasks, total = task_service.get_all_tasks(
status=status,
task_type=task_type,
name=name,
is_scheduled=is_scheduled,
created_start=created_start,
created_end=created_end,
sort_by=sort_by,
sort_order=sort_order,
page=page,
page_size=page_size
)
# 构建分页信息
pagination = {
"page": page,
"page_size": page_size,
"total": total,
"total_pages": (total + page_size - 1) // page_size
}
return {
"code": 200,
"message": "获取任务列表成功",
"data": {
"tasks": tasks,
"pagination": pagination
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取任务列表失败: {str(e)}")
@router.get("/task/types", response_model=ApiResponse)
async def get_task_types():
"""获取任务类型列表"""
try:
# 从配置文件中获取任务类型列表
task_types = get_all_task_types()
# 添加value字段枚举值
for task_type in task_types:
# 从TaskType枚举中获取value
try:
# 尝试通过名称获取枚举值
task_type["value"] = TaskType[task_type["key"]].value
except (KeyError, AttributeError):
# 如果枚举中没有对应的值则使用key的小写作为value
task_type["value"] = task_type["key"]
return {
"code": 200,
"message": "获取任务类型列表成功",
"data": task_types
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取任务类型列表失败: {str(e)}")
@router.post("/task/create", response_model=ApiResponse)
async def create_task(task_input: TaskInput):
"""创建任务"""
try:
# 获取任务类型值(处理枚举)
task_type = task_input.task_type
if hasattr(task_type, "value"):
task_type_value = task_type.value
else:
task_type_value = task_type
# 从配置文件中获取任务类型信息
task_type_info = TaskTypeConfig.DETAILS.get(task_type_value, {})
# 根据任务类型决定是否为定时任务
is_scheduled = task_type_value == TASK_TYPE_SCHEDULED
# 创建任务,设置固定参数
task = task_service.create_task(
name=task_input.name,
task_type=task_type_value, # 使用key作为task_type
description=DEFAULT_TASK_DESCRIPTION, # 使用配置文件中的默认备注
template_desc=DEFAULT_TEMPLATE_DESCRIPTION, # 使用配置文件中的默认模板描述
is_scheduled=is_scheduled # 根据任务类型决定
)
# 在返回结果中添加任务类型的中文名称
if "task_type_name" not in task and task_type_info:
task["task_type_name"] = task_type_info.get("name", task_type_value)
return {
"code": 200,
"message": "创建任务成功",
"data": task
}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"创建任务失败: {str(e)}")
@router.get("/task/{task_id}", response_model=ApiResponse)
async def get_task(task_id: str):
"""获取任务详情"""
try:
# 获取任务
task = task_service.get_task_by_id(task_id)
if not task:
return {
"code": 404,
"message": f"任务不存在: {task_id}",
"data": None
}
return {
"code": 200,
"message": "获取任务详情成功",
"data": task
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取任务详情失败: {str(e)}")
@router.delete("/task/{task_id}", response_model=ApiResponse)
async def delete_task(task_id: str):
"""删除任务"""
try:
# 删除任务
success = task_service.delete_task(task_id)
# 如果任务不存在,返回成功但提示任务不存在
if not success:
return {
"code": 200,
"message": f"任务不存在或已被删除: {task_id}",
"data": None
}
return {
"code": 200,
"message": "删除任务成功",
"data": None
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"删除任务失败: {str(e)}")
@router.post("/task/{task_id}/execute", response_model=ApiResponse)
async def execute_task(task_id: str):
"""执行任务"""
try:
# 执行任务
task = task_service.execute_task(task_id)
return {
"code": 200,
"message": "执行任务成功",
"data": task
}
except ValueError as e:
# 任务不存在
return {
"code": 404,
"message": str(e),
"data": None
}
except TianfengTaskError as e:
# 业务逻辑错误
return {
"code": 400,
"message": str(e),
"data": None
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"执行任务失败: {str(e)}")
@router.post("/task/{task_id}/cancel", response_model=ApiResponse)
async def cancel_task(task_id: str):
"""取消任务"""
try:
# 取消任务
task = task_service.cancel_task(task_id)
return {
"code": 200,
"message": "取消任务成功",
"data": task
}
except ValueError as e:
# 任务不存在或状态不允许取消
return {
"code": 400,
"message": str(e),
"data": None
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"取消任务失败: {str(e)}")
@router.delete("/task/batch", response_model=ApiResponse)
async def batch_delete_tasks(id_list: TaskIdList):
"""批量删除任务"""
try:
# 批量删除任务
deleted_count = 0
not_found_ids = []
for task_id in id_list.task_ids:
if task_service.delete_task(task_id):
deleted_count += 1
else:
not_found_ids.append(task_id)
# 构建消息
message = f"批量删除任务成功,共删除 {deleted_count} 个任务"
if not_found_ids:
message += f",有 {len(not_found_ids)} 个任务不存在或已被删除"
return {
"code": 200,
"message": message,
"data": {
"deleted_count": deleted_count,
"total_count": len(id_list.task_ids),
"not_found_ids": not_found_ids
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"批量删除任务失败: {str(e)}")
@router.put("/task/{task_id}", response_model=ApiResponse)
async def update_task(task_id: str, task_update: TaskUpdateInput):
"""更新任务基本信息"""
try:
# 检查任务是否存在
task = task_service.get_task_by_id(task_id)
if not task:
return {
"code": 404,
"message": f"任务 {task_id} 不存在",
"data": None
}
# 更新任务信息
updated_task = task_service.update_task(
task_id=task_id,
name=task_update.name,
description=task_update.description,
task_type=task_update.task_type,
blocks=task_update.blocks,
variables=task_update.variables,
schedule=task_update.schedule
)
return {
"code": 200,
"message": "更新任务成功",
"data": {"task": updated_task}
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"更新任务失败: {str(e)}")
@router.post("/task/edit", response_model=ApiResponse)
async def edit_task(task_edit: TaskEditInput):
"""编辑任务流程和变量"""
try:
# 检查任务是否存在
task = task_service.get_task_by_id(task_edit.task_id)
if not task:
return {
"code": 404,
"message": f"任务 {task_edit.task_id} 不存在",
"data": None
}
# 更新任务流程和变量
updated_task = task_service.update_task_workflow(
task_id=task_edit.task_id,
blocks=task_edit.blocks,
variables=task_edit.variables
)
return {
"code": 200,
"message": "编辑任务成功",
"data": {"task": updated_task}
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"编辑任务失败: {str(e)}")
@router.get("/task/{task_id}/edit", response_model=ApiResponse)
async def get_task_edit_info(task_id: str):
"""获取任务编辑信息"""
try:
# 获取任务信息
task = task_service.get_task_by_id(task_id)
if not task:
return {
"code": 404,
"message": f"任务 {task_id} 不存在",
"data": None
}
# 获取可用的子任务列表(排除当前任务自身)
available_subtasks = []
try:
all_tasks, _ = task_service.get_all_tasks(page=1, page_size=1000) # 获取所有任务
for t in all_tasks:
if t["task_id"] != task_id: # 排除当前任务
available_subtasks.append({
"task_id": t["task_id"],
"name": t["name"]
})
except Exception as e:
# 如果获取任务列表失败,记录错误但继续执行
print(f"获取可用子任务列表失败: {str(e)}")
# 获取组件详细信息
from config.component_detail_config import ComponentDetailConfig
component_details = ComponentDetailConfig.get_all_components()
# 获取组件类型中文名称映射
component_type_names = ComponentDetailConfig.get_component_type_names()
# 按组件类型分组并添加中文名称
component_types = {}
for component in component_details:
component_type = component["type"]
# 如果该类型还未添加到结果中,先创建类型信息
if component_type not in component_types:
# 获取组件类型的中文名称,如果没有则使用英文标识
type_name = component_type_names.get(component_type, component_type)
component_types[component_type] = {
"type": component_type, # 英文标识
"name": type_name, # 中文名称
"components": [] # 该类型下的组件列表
}
# 添加组件到对应类型下
component_types[component_type]["components"].append(component)
# 不再将子任务列表嵌入到组件类型对象中
if "subtask" in component_types:
component_types["subtask"]["available_subtasks"] = available_subtasks
return {
"code": 200,
"message": "获取任务编辑信息成功",
"data": {
"task": task,
"component_types": component_types,
"available_subtasks": available_subtasks # 作为单独的字段返回
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取任务编辑信息失败: {str(e)}")
@router.get("/tasks/available-subtasks", response_model=ApiResponse)
async def get_available_subtasks(current_task_id: Optional[str] = None):
"""获取可用的子任务列表"""
try:
# 获取所有任务
all_tasks, _ = task_service.get_all_tasks(page=1, page_size=1000)
# 过滤出可用的子任务如果提供了当前任务ID则排除当前任务
available_subtasks = []
for task in all_tasks:
if not current_task_id or task["task_id"] != current_task_id:
available_subtasks.append({
"task_id": task["task_id"],
"name": task["name"]
})
return {
"code": 200,
"message": "获取可用子任务列表成功",
"data": {
"subtasks": available_subtasks
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取可用子任务列表失败: {str(e)}")

184
api/workflow_api.py Normal file
View File

@ -0,0 +1,184 @@
# api/workflow_api.py
from typing import Dict, Any, List, Optional
from fastapi import APIRouter, HTTPException, Depends, Query
from pydantic import BaseModel, Field
from services.workflow_service import WorkflowService
from core.exceptions import TianfengTaskError
from core.workflow import WorkflowDefinition
from config.api_config import ApiResponseCode, ApiResponseMessage
from api.models import ApiResponse, WorkflowInput, WorkflowUpdateInput, WorkflowExecuteInput, WorkflowImportInput
# 创建路由器
router = APIRouter(prefix="/workflow", tags=["工作流管理"])
# 创建服务实例
workflow_service = WorkflowService()
@router.get("/workflows", response_model=ApiResponse)
async def get_workflows(
type: Optional[str] = Query(None, description="工作流类型")
):
"""获取工作流列表"""
try:
# 获取工作流列表
workflows = workflow_service.get_all_workflows(type)
# 转换为字典列表
workflow_dicts = [wf.to_dict() for wf in workflows]
return {
"code": ApiResponseCode.SUCCESS,
"message": "获取工作流列表成功",
"data": workflow_dicts
}
except Exception as e:
raise HTTPException(status_code=ApiResponseCode.SERVER_ERROR, detail=f"获取工作流列表失败: {str(e)}")
@router.get("/workflows/{workflow_id}", response_model=ApiResponse)
async def get_workflow(workflow_id: str):
"""获取工作流详情"""
try:
# 获取工作流
workflow = workflow_service.get_workflow_by_id(workflow_id)
if not workflow:
raise HTTPException(status_code=ApiResponseCode.NOT_FOUND, detail=f"找不到工作流: {workflow_id}")
return {
"code": ApiResponseCode.SUCCESS,
"message": "获取工作流详情成功",
"data": workflow.to_dict()
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=ApiResponseCode.SERVER_ERROR, detail=f"获取工作流详情失败: {str(e)}")
@router.post("/workflows", response_model=ApiResponse)
async def create_workflow(workflow_input: WorkflowInput):
"""创建工作流"""
try:
# 创建工作流
workflow = workflow_service.create_workflow(
name=workflow_input.name,
workflow_type=workflow_input.workflow_type,
description=workflow_input.description,
blocks=workflow_input.blocks,
variables=workflow_input.variables,
schedule=workflow_input.schedule
)
return {
"code": ApiResponseCode.SUCCESS,
"message": ApiResponseMessage.Workflow.CREATED,
"data": workflow.to_dict()
}
except Exception as e:
raise HTTPException(status_code=ApiResponseCode.SERVER_ERROR, detail=f"创建工作流失败: {str(e)}")
@router.put("/workflows/{workflow_id}", response_model=ApiResponse)
async def update_workflow(workflow_id: str, workflow_input: WorkflowUpdateInput):
"""更新工作流"""
try:
# 更新工作流
workflow = workflow_service.update_workflow(
workflow_id=workflow_id,
name=workflow_input.name,
description=workflow_input.description,
blocks=workflow_input.blocks,
variables=workflow_input.variables,
schedule=workflow_input.schedule
)
if not workflow:
raise HTTPException(status_code=ApiResponseCode.NOT_FOUND, detail=f"找不到工作流: {workflow_id}")
return {
"code": ApiResponseCode.SUCCESS,
"message": ApiResponseMessage.Workflow.UPDATED,
"data": workflow.to_dict()
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=ApiResponseCode.SERVER_ERROR, detail=f"更新工作流失败: {str(e)}")
@router.delete("/workflows/{workflow_id}", response_model=ApiResponse)
async def delete_workflow(workflow_id: str):
"""删除工作流"""
try:
# 删除工作流
success = workflow_service.delete_workflow(workflow_id)
if not success:
raise HTTPException(status_code=ApiResponseCode.NOT_FOUND, detail=f"找不到工作流: {workflow_id}")
return {
"code": ApiResponseCode.SUCCESS,
"message": ApiResponseMessage.Workflow.DELETED,
"data": None
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=ApiResponseCode.SERVER_ERROR, detail=f"删除工作流失败: {str(e)}")
@router.post("/workflows/{workflow_id}/execute", response_model=ApiResponse)
async def execute_workflow(workflow_id: str, execute_input: WorkflowExecuteInput):
"""执行工作流"""
try:
# 获取工作流
workflow = workflow_service.get_workflow_by_id(workflow_id)
if not workflow:
raise HTTPException(status_code=ApiResponseCode.NOT_FOUND, detail=f"找不到工作流: {workflow_id}")
# 执行工作流
result = workflow_service.execute_workflow(
workflow=workflow,
task_inputs=execute_input.task_inputs
)
return {
"code": ApiResponseCode.SUCCESS,
"message": ApiResponseMessage.Workflow.EXECUTED,
"data": result
}
except HTTPException:
raise
except TianfengTaskError as e:
raise HTTPException(status_code=ApiResponseCode.BAD_REQUEST, detail=str(e))
except Exception as e:
raise HTTPException(status_code=ApiResponseCode.SERVER_ERROR, detail=f"执行工作流失败: {str(e)}")
@router.post("/workflows/import", response_model=ApiResponse)
async def import_workflow(import_input: WorkflowImportInput):
"""导入工作流"""
try:
# 导入工作流
workflow = workflow_service.import_workflow(import_input.workflow_json)
return {
"code": ApiResponseCode.SUCCESS,
"message": ApiResponseMessage.Workflow.IMPORTED,
"data": workflow.to_dict()
}
except Exception as e:
raise HTTPException(status_code=ApiResponseCode.SERVER_ERROR, detail=f"导入工作流失败: {str(e)}")
@router.get("/workflows/{workflow_id}/export", response_model=ApiResponse)
async def export_workflow(workflow_id: str):
"""导出工作流"""
try:
# 导出工作流
workflow_json = workflow_service.export_workflow(workflow_id)
return {
"code": ApiResponseCode.SUCCESS,
"message": ApiResponseMessage.Workflow.EXPORTED,
"data": {"workflow_json": workflow_json}
}
except TianfengTaskError as e:
raise HTTPException(status_code=ApiResponseCode.BAD_REQUEST, detail=str(e))
except Exception as e:
raise HTTPException(status_code=ApiResponseCode.SERVER_ERROR, detail=f"导出工作流失败: {str(e)}")

186
app.py Normal file
View File

@ -0,0 +1,186 @@
# app.py
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
import logging
import time
from utils.logger import setup_logger
from config.component_registry import register_all_components
from config.settings import (
AppConfig, ServerConfig, ApiConfig, CorsConfig
)
from api.task_api import router as task_router
from api.workflow_api import router as workflow_router
from api.component_api import router as component_router
from core.exceptions import TianfengTaskError
from config.api_config import ApiResponseCode, ApiResponseMessage
from config.component_config import ComponentCategoryConfig
# 导入数据库相关模块
from config.database import DBConfig, CacheConfig, db_session
import data.models # 导入所有模型以确保它们被注册
from data.models.component import ComponentCategory, ComponentType, Component, ComponentCategoryEnum
# 导入Lifespan
from contextlib import asynccontextmanager
# 设置日志
logger = setup_logger()
# 定义Lifespan上下文管理器
@asynccontextmanager
async def lifespan(app: FastAPI):
"""
应用生命周期管理
在应用启动时执行初始化操作在应用关闭时执行清理操作
"""
# 应用启动时执行
logger.info("应用启动")
yield # 应用运行期间
# 应用关闭时执行
logger.info("应用关闭")
DBConfig.shutdown_session()
# 创建FastAPI应用使用Lifespan
app = FastAPI(
title=ApiConfig.TITLE,
description=ApiConfig.DESCRIPTION,
version=ApiConfig.VERSION,
lifespan=lifespan
)
# 添加CORS中间件
app.add_middleware(
CORSMiddleware,
allow_origins=CorsConfig.ALLOW_ORIGINS,
allow_credentials=CorsConfig.ALLOW_CREDENTIALS,
allow_methods=CorsConfig.ALLOW_METHODS,
allow_headers=CorsConfig.ALLOW_HEADERS,
)
# 初始化数据库
def init_database():
"""初始化数据库,创建所有表"""
try:
logger.info("开始初始化数据库...")
# 初始化数据库表
DBConfig.init_db()
logger.info("数据库表创建成功")
# 初始化基础数据
logger.info("开始初始化基础数据...")
init_base_data(db_session)
logger.info("基础数据初始化成功")
return True
except Exception as e:
logger.error(f"数据库初始化失败: {str(e)}")
raise
def init_base_data(db_session):
"""初始化基础数据"""
try:
# 导入需要的模型
# 检查是否已存在组件分类
existing_categories = db_session.query(ComponentCategory).filter(ComponentCategory.is_deleted == False).all()
if existing_categories:
logger.info("基础数据已存在,跳过初始化")
return
# 创建组件分类
categories = []
for category_enum in ComponentCategoryEnum:
category = ComponentCategory(
name=ComponentCategoryConfig.get_category_name(category_enum),
code=category_enum,
description=ComponentCategoryConfig.get_category_description(category_enum),
icon=f"icon-{category_enum.value}",
order=ComponentCategoryConfig.get_category_order(category_enum)
)
categories.append(category)
db_session.add(category)
db_session.commit()
logger.info(f"创建了 {len(categories)} 个组件分类")
# 这里可以添加更多基础数据的初始化,如组件类型、系统组件等
except Exception as e:
db_session.rollback()
logger.error(f"基础数据初始化失败: {str(e)}")
raise
# 初始化数据库
init_database()
# 注册所有组件
register_all_components()
# 注册API路由
app.include_router(task_router, prefix=ApiConfig.PREFIX)
app.include_router(workflow_router, prefix=ApiConfig.PREFIX)
app.include_router(component_router, prefix=ApiConfig.PREFIX)
# 请求中间件
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response
# 数据库会话中间件
@app.middleware("http")
async def db_session_middleware(request: Request, call_next):
try:
response = await call_next(request)
return response
finally:
db_session.remove()
# 全局异常处理
@app.exception_handler(TianfengTaskError)
async def tianfeng_task_error_handler(request: Request, exc: TianfengTaskError):
"""处理天风任务模块异常"""
return JSONResponse(
status_code=ApiResponseCode.BAD_REQUEST,
content={
"code": ApiResponseCode.BAD_REQUEST,
"message": str(exc),
"data": None
}
)
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
"""处理通用异常"""
logger.exception("未处理的异常")
return JSONResponse(
status_code=ApiResponseCode.SERVER_ERROR,
content={
"code": ApiResponseCode.SERVER_ERROR,
"message": f"{ApiResponseMessage.SERVER_ERROR}: {str(exc)}",
"data": None
}
)
# 健康检查接口
@app.get(f"{ApiConfig.PREFIX}/health")
async def health_check():
"""健康检查"""
return {
"code": ApiResponseCode.SUCCESS,
"message": "服务正常",
"data": {
"app_name": AppConfig.NAME,
"app_version": AppConfig.VERSION,
"status": "healthy"
}
}
# 主函数
if __name__ == "__main__":
import uvicorn
logger.info(f"启动天风任务模块服务,调试模式: {AppConfig.DEBUG}")
uvicorn.run("app:app", host=ServerConfig.HOST, port=ServerConfig.PORT, reload=ServerConfig.RELOAD)

0
components/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,222 @@
# components/base_components.py
"""
基础组件
"""
from typing import Dict, Any, Optional
import time
import hashlib
import json
import uuid
from core.component import Component, ComponentFactory
from core.exceptions import ComponentError
@ComponentFactory.register("check_task_instance_id_exists")
class CheckTaskInstanceIdExistsComponent(Component):
"""校验任务实例Id是否存在组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
task_instance_id = self.resolve_param("task_instance_id")
# 验证必要参数
self.validate_required_params(["task_instance_id"])
# 实际实现中这里应该查询数据库验证任务ID是否存在
# 这里简化为检查是否与当前任务ID相同
exists = task_instance_id == self.context.task_id
result = {"exists": exists}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("create_unique_id")
class CreateUniqueIdComponent(Component):
"""创建唯一ID组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
prefix = self.resolve_param("prefix", "")
# 生成唯一ID
unique_id = f"{prefix}{uuid.uuid4().hex}"
result = {"id": unique_id}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("current_timestamp")
class CurrentTimestampComponent(Component):
"""当前时间戳组件"""
def execute(self) -> Dict[str, Any]:
# 获取当前时间戳(毫秒级)
timestamp = int(time.time() * 1000)
result = {"timestamp": timestamp}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("current_time")
class CurrentTimeComponent(Component):
"""当前时间组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
format_str = self.resolve_param("format", "%Y-%m-%d %H:%M:%S")
# 获取格式化的当前时间
current_time = time.strftime(format_str, time.localtime())
result = {"time": current_time}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("execute_sql")
class ExecuteSqlComponent(Component):
"""执行SQL组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
sql = self.resolve_param("sql")
params = self.resolve_param("params", [])
# 验证必要参数
self.validate_required_params(["sql"])
# 实际实现中这里应该执行SQL语句
# 这里简化为返回模拟结果
result = {
"affected_rows": 1,
"success": True
}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("query_sql")
class QuerySqlComponent(Component):
"""查询SQL组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
sql = self.resolve_param("sql")
params = self.resolve_param("params", [])
# 验证必要参数
self.validate_required_params(["sql"])
# 实际实现中这里应该执行SQL查询
# 这里简化为返回模拟结果
result = {
"result": [{"id": 1, "name": "示例数据"}],
"count": 1
}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("string_md5_encrypt")
class StringMd5EncryptComponent(Component):
"""字符串MD5加密组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
text = self.resolve_param("text")
uppercase = self.resolve_param("uppercase", False)
salt = self.resolve_param("salt", "")
# 验证必要参数
self.validate_required_params(["text"])
# 执行MD5加密
md5_hash = hashlib.md5((text + salt).encode()).hexdigest()
if uppercase:
md5_hash = md5_hash.upper()
result = {"md5": md5_hash}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("string_to_json_array")
class StringToJsonArrayComponent(Component):
"""将字符串转换成JSON数组组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
text = self.resolve_param("text")
default = self.resolve_param("default", [])
# 验证必要参数
self.validate_required_params(["text"])
# 执行转换
try:
json_array = json.loads(text)
if not isinstance(json_array, list):
json_array = default
except Exception:
json_array = default
result = {"jsonArray": json_array}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("string_to_json_object")
class StringToJsonObjectComponent(Component):
"""将字符串转换成JSON对象组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
text = self.resolve_param("text")
default = self.resolve_param("default", {})
# 验证必要参数
self.validate_required_params(["text"])
# 执行转换
try:
json_object = json.loads(text)
if not isinstance(json_object, dict):
json_object = default
except Exception:
json_object = default
result = {"jsonObject": json_object}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("print")
class PrintComponent(Component):
"""打印组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
content = self.resolve_param("content")
# 验证必要参数
self.validate_required_params(["content"])
# 执行打印
print(content)
result = {"printed": True, "content": content}
# 存储结果
self.store_result(result)
return result

View File

@ -0,0 +1,89 @@
# components/device_components.py
"""
设备组件
"""
from typing import Dict, Any, Optional
import time
from core.component import Component, ComponentFactory
from core.exceptions import ComponentError
@ComponentFactory.register("wait_modbus_value")
class WaitModbusValueComponent(Component):
"""通用等待Modbus值(Name)组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
target_value = self.resolve_param("target_value")
name = self.resolve_param("name")
address = self.resolve_param("address")
description = self.resolve_param("description", "")
timeout = self.resolve_param("timeout", 30000) # 默认30秒超时
# 验证必要参数
self.validate_required_params(["target_value", "name"])
# 实际实现中这里应该轮询Modbus设备直到值匹配或超时
# 这里简化为返回模拟数据
start_time = time.time() * 1000
current_value = None
success = False
# 模拟等待过程
while (time.time() * 1000 - start_time) < timeout:
# 模拟读取当前值
current_value = target_value # 假设值已经匹配
if current_value == target_value:
success = True
break
# 等待一段时间再次检查
time.sleep(0.1)
if not success:
result = {
"success": False,
"timeout": True,
"name": name,
"address": address,
"targetValue": target_value,
"currentValue": current_value
}
else:
result = {
"success": True,
"name": name,
"address": address,
"value": current_value
}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("write_modbus_value")
class WriteModbusValueComponent(Component):
"""通用写入Modbus值(Name)组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
write_value = self.resolve_param("write_value")
name = self.resolve_param("name")
address = self.resolve_param("address")
description = self.resolve_param("description", "")
# 验证必要参数
self.validate_required_params(["write_value", "name"])
# 实际实现中这里应该调用Modbus设备API写入值
# 这里简化为返回模拟数据
result = {
"success": True,
"name": name,
"address": address,
"value": write_value
}
# 存储结果
self.store_result(result)
return result

View File

@ -0,0 +1,188 @@
# components/flow_components.py
"""
流程控制组件
"""
from typing import Dict, Any, List, Optional
from core.component import Component, ComponentFactory
from core.exceptions import ComponentError
@ComponentFactory.register("if")
class IfComponent(Component):
"""If条件组件"""
def execute(self) -> Dict[str, Any]:
# 注意:实际的条件逻辑在工作流执行器中实现
# 这里只是记录条件表达式
condition = self.resolve_param("condition")
# 验证必要参数
self.validate_required_params(["condition"])
result = {"condition": condition}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("if_else")
class IfElseComponent(Component):
"""If-Else条件组件"""
def execute(self) -> Dict[str, Any]:
# 注意:实际的条件逻辑在工作流执行器中实现
# 这里只是记录条件表达式
condition = self.resolve_param("condition")
# 验证必要参数
self.validate_required_params(["condition"])
result = {"condition": condition}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("if_else_if")
class IfElseIfComponent(Component):
"""If-Else-If条件组件"""
def execute(self) -> Dict[str, Any]:
# 注意:实际的条件逻辑在工作流执行器中实现
# 这里只是记录条件表达式
conditions = self.resolve_param("conditions", [])
# 验证必要参数
self.validate_required_params(["conditions"])
result = {"conditions": conditions}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("for_each")
class ForEachComponent(Component):
"""遍历数组组件"""
def execute(self) -> Dict[str, Any]:
# 注意:实际的循环逻辑在工作流执行器中实现
# 这里只是记录循环参数
items = self.resolve_param("items")
# 验证必要参数
self.validate_required_params(["items"])
result = {"items": items}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("while")
class WhileComponent(Component):
"""While循环组件"""
def execute(self) -> Dict[str, Any]:
# 注意:实际的循环逻辑在工作流执行器中实现
# 这里只是记录循环条件
condition = self.resolve_param("condition")
max_iterations = self.resolve_param("max_iterations", 100)
# 验证必要参数
self.validate_required_params(["condition"])
result = {
"condition": condition,
"max_iterations": max_iterations
}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("break")
class BreakComponent(Component):
"""Break组件中断当前循环"""
def execute(self) -> Dict[str, Any]:
# 注意:实际的中断逻辑在工作流执行器中实现
# 这里只是记录中断意图
result = {"break_requested": True}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("return")
class ReturnComponent(Component):
"""Return组件结束当前流程并返回结果"""
def execute(self) -> Dict[str, Any]:
# 获取参数
return_value = self.resolve_param("return_value")
# 注意:实际的返回逻辑在工作流执行器中实现
# 这里只是记录返回值
result = {"return_value": return_value, "return_requested": True}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("delay")
class DelayComponent(Component):
"""延迟组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
milliseconds = self.resolve_param("milliseconds", 1000)
# 验证必要参数
self.validate_required_params(["milliseconds"])
# 执行延迟
import time
time.sleep(milliseconds / 1000)
result = {"delayed_ms": milliseconds}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("parallel_execute")
class ParallelExecuteComponent(Component):
"""并行执行组件"""
def execute(self) -> Dict[str, Any]:
# 注意:实际的并行执行逻辑在工作流执行器中实现
# 这里只是记录并行执行意图
result = {"parallel_requested": True}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("serial_execute")
class SerialExecuteComponent(Component):
"""串行执行组件"""
def execute(self) -> Dict[str, Any]:
# 注意:实际的串行执行逻辑在工作流执行器中实现
# 这里只是记录串行执行意图
result = {"serial_requested": True}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("throw_exception")
class ThrowExceptionComponent(Component):
"""抛出异常组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
error_message = self.resolve_param("error_message", "自定义异常")
# 抛出异常
raise ComponentError(error_message)

View File

@ -0,0 +1,104 @@
# components/http_components.py
"""
HTTP请求组件
"""
import requests
from typing import Dict, Any, Optional
from core.component import Component, ComponentFactory
from core.exceptions import ComponentError
@ComponentFactory.register("http_get_request")
class HttpGetRequestComponent(Component):
"""HTTP GET请求组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
url = self.resolve_param("url")
headers = self.resolve_param("headers", {})
params = self.resolve_param("params", {})
timeout = self.resolve_param("timeout", 30)
# 验证必要参数
self.validate_required_params(["url"])
try:
# 执行HTTP请求
response = requests.get(url, headers=headers, params=params, timeout=timeout)
response.raise_for_status()
# 处理响应
try:
response_data = response.json()
except ValueError:
response_data = response.text
result = {
"response": response_data,
"status_code": response.status_code,
"headers": dict(response.headers)
}
# 存储结果
self.store_result(result)
return result
except requests.RequestException as e:
error_result = {
"error": str(e),
"status_code": getattr(e.response, "status_code", None) if hasattr(e, "response") else None
}
self.store_result(error_result)
raise ComponentError(f"HTTP GET请求失败: {str(e)}")
@ComponentFactory.register("http_post_request")
class HttpPostRequestComponent(Component):
"""HTTP POST请求组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
url = self.resolve_param("url")
data = self.resolve_param("data", {})
headers = self.resolve_param("headers", {})
content_type = self.resolve_param("content_type", "json")
timeout = self.resolve_param("timeout", 30)
# 验证必要参数
self.validate_required_params(["url"])
try:
# 设置内容类型
if content_type == "json" and "Content-Type" not in headers:
headers["Content-Type"] = "application/json"
# 执行HTTP请求
if content_type == "json":
response = requests.post(url, json=data, headers=headers, timeout=timeout)
else:
response = requests.post(url, data=data, headers=headers, timeout=timeout)
response.raise_for_status()
# 处理响应
try:
response_data = response.json()
except ValueError:
response_data = response.text
result = {
"response": response_data,
"status_code": response.status_code,
"headers": dict(response.headers)
}
# 存储结果
self.store_result(result)
return result
except requests.RequestException as e:
error_result = {
"error": str(e),
"status_code": getattr(e.response, "status_code", None) if hasattr(e, "response") else None
}
self.store_result(error_result)
raise ComponentError(f"HTTP POST请求失败: {str(e)}")

View File

@ -0,0 +1,176 @@
# components/robot_components.py
from typing import Dict, Any, List, Optional
from core.component import Component, ComponentFactory
from core.exceptions import ComponentError
@ComponentFactory.register("select_robot")
class SelectRobotComponent(Component):
"""选择执行机器人组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
priority = self.resolve_param("priority", "distance")
robot_id = self.resolve_param("robot_id")
tags = self.resolve_param("tags", [])
robot_group = self.resolve_param("robot_group")
# 如果直接指定了机器人ID则使用该机器人
if robot_id:
selected_robot = robot_id
else:
# 实际实现中这里应该调用机器人调度系统API选择合适的机器人
# 这里简化为返回模拟数据
selected_robot = "robot_001"
# 设置执行机器人
self.context.set_executing_robot(selected_robot)
result = {
"selectedAgvId": selected_robot,
"priority": priority
}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("get_robot_position")
class GetRobotPositionComponent(Component):
"""获取机器人位置组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
robot_id = self.resolve_param("robot_id")
# 验证必要参数
self.validate_required_params(["robot_id"])
# 实际实现中这里应该调用机器人系统API获取位置
# 这里简化为返回模拟数据
position = {
"x": 100.5,
"y": 200.3,
"angle": 45.0,
"station": "station_A"
}
result = {
"position": position,
"station": position["station"]
}
# 存储结果
self.store_result(result)
return result
# components/robot_components.py (继续)
@ComponentFactory.register("robot_action")
class RobotActionComponent(Component):
"""机器人通用动作组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
target_station = self.resolve_param("target_station")
robot_id = self.resolve_param("robot_id", self.context.executing_robot)
goods_id = self.resolve_param("goods_id")
speed = self.resolve_param("speed")
# 验证必要参数
self.validate_required_params(["target_station"])
if not robot_id:
raise ComponentError("未指定机器人ID请先选择执行机器人或直接指定robot_id参数")
# 实际实现中这里应该调用机器人控制API执行动作
# 这里简化为返回模拟数据
result = {
"robotId": robot_id,
"targetStation": target_station,
"goodsId": goods_id,
"status": "executing"
}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("change_robot_destination")
class ChangeRobotDestinationComponent(Component):
"""机器人更换目的地组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
target_station = self.resolve_param("target_station")
robot_id = self.resolve_param("robot_id", self.context.executing_robot)
goods_id = self.resolve_param("goods_id")
# 验证必要参数
self.validate_required_params(["target_station"])
if not robot_id:
raise ComponentError("未指定机器人ID请先选择执行机器人或直接指定robot_id参数")
# 实际实现中这里应该调用机器人控制API更改目的地
# 这里简化为返回模拟数据
result = {
"robotId": robot_id,
"newTargetStation": target_station,
"goodsId": goods_id,
"status": "destination_changed"
}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("get_robot_battery")
class GetRobotBatteryComponent(Component):
"""获取机器人电量组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
robot_id = self.resolve_param("robot_id", self.context.executing_robot)
# 验证必要参数
if not robot_id:
raise ComponentError("未指定机器人ID请先选择执行机器人或直接指定robot_id参数")
# 实际实现中这里应该调用机器人系统API获取电量
# 这里简化为返回模拟数据
battery_level = 85 # 电量百分比
result = {"batteryLevel": battery_level}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("get_robot_pgv_code")
class GetRobotPGVCodeComponent(Component):
"""获取机器人PGV码组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
robot_id = self.resolve_param("robot_id", self.context.executing_robot)
# 验证必要参数
if not robot_id:
raise ComponentError("未指定机器人ID请先选择执行机器人或直接指定robot_id参数")
# 实际实现中这里应该调用机器人系统API获取PGV码
# 这里简化为返回模拟数据
pgv_code = {
"code": "PGV12345",
"timestamp": int(time.time() * 1000),
"coordinates": {
"x": 123.45,
"y": 67.89
},
"status": "valid"
}
result = {"codeInfo": pgv_code}
# 存储结果
self.store_result(result)
return result

View File

@ -0,0 +1,74 @@
# components/script_components.py
"""
脚本执行组件
"""
from typing import Dict, Any, Optional
from core.component import Component, ComponentFactory
from core.exceptions import ComponentError
@ComponentFactory.register("run_script")
class RunScriptComponent(Component):
"""脚本执行组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
script_code = self.resolve_param("script_code")
params = self.resolve_param("params", {})
# 验证必要参数
self.validate_required_params(["script_code"])
# 创建本地变量字典
locals_dict = {
"params": params,
"context": self.context,
"blocks": self.context.blocks,
"variables": self.context.variables,
"task": {
"id": self.context.task_id,
"variables": self.context.variables
},
"taskInputs": self.context.task_inputs
}
try:
# 执行脚本代码
exec(script_code, {"__builtins__": __builtins__}, locals_dict)
# 获取结果
result = {"result": locals_dict.get("result", None)}
# 存储结果
self.store_result(result)
return result
except Exception as e:
error_result = {"error": str(e)}
self.store_result(error_result)
raise ComponentError(f"脚本执行失败: {str(e)}")
@ComponentFactory.register("set_task_variables")
class SetTaskVariablesComponent(Component):
"""设置任务变量组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
variables = self.params.copy()
# 移除特殊参数
if "id" in variables:
del variables["id"]
updated_vars = {}
# 设置任务变量
for name, value in variables.items():
resolved_value = self.resolve_param(name)
self.context.set_variable(name, resolved_value)
updated_vars[name] = resolved_value
result = {"updated_variables": list(updated_vars.keys())}
# 存储结果
self.store_result(result)
return result

View File

@ -0,0 +1,296 @@
# components/site_components.py
"""
库位组件
"""
from typing import Dict, Any, List, Optional
from core.component import Component, ComponentFactory
from core.exceptions import ComponentError
@ComponentFactory.register("batch_set_site")
class BatchSetSiteComponent(Component):
"""批量设置库位组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
site_ids = self.resolve_param("site_ids", [])
area_set = self.resolve_param("area_set")
occupied = self.resolve_param("occupied")
has_material = self.resolve_param("has_material")
is_material_locked = self.resolve_param("is_material_locked")
# 验证必要参数
self.validate_required_params(["site_ids"])
# 实际实现中这里应该调用库位管理系统API批量设置库位
# 这里简化为返回模拟结果
result = {
"affected_sites": site_ids,
"success": True
}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("get_dense_site")
class GetDenseSiteComponent(Component):
"""获取密集库位组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
area_set = self.resolve_param("area_set")
operation = self.resolve_param("operation") # 取/放
goods = self.resolve_param("goods")
auto_lock = self.resolve_param("auto_lock", False)
retry = self.resolve_param("retry", False)
# 验证必要参数
self.validate_required_params(["area_set", "operation"])
# 实际实现中这里应该调用库位管理系统API获取密集库位
# 这里简化为返回模拟结果
site_id = "site_001"
result = {"siteId": site_id}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("query_site")
class QuerySiteComponent(Component):
"""查询库位组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
site_id = self.resolve_param("site_id")
goods = self.resolve_param("goods")
has_goods = self.resolve_param("has_goods")
is_locked = self.resolve_param("is_locked")
is_inventory = self.resolve_param("is_inventory")
retry_interval = self.resolve_param("retry_interval", 1000)
# 实际实现中这里应该调用库位管理系统API查询库位
# 这里简化为返回模拟结果
site_info = {
"id": site_id or "site_001",
"hasGoods": has_goods if has_goods is not None else False,
"isLocked": is_locked if is_locked is not None else False,
"isInventory": is_inventory if is_inventory is not None else True,
"goods": goods
}
result = {"site": site_info}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("lock_site")
class LockSiteComponent(Component):
"""锁定库位组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
site_id = self.resolve_param("site_id")
reason = self.resolve_param("reason", "任务锁定")
# 验证必要参数
self.validate_required_params(["site_id"])
# 实际实现中这里应该调用库位管理系统API锁定库位
# 这里简化为返回模拟结果
result = {
"siteId": site_id,
"locked": True,
"reason": reason
}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("unlock_site")
class UnlockSiteComponent(Component):
"""解锁库位组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
site_id = self.resolve_param("site_id")
reason = self.resolve_param("reason", "任务解锁")
# 验证必要参数
self.validate_required_params(["site_id"])
# 实际实现中这里应该调用库位管理系统API解锁库位
# 这里简化为返回模拟结果
result = {
"siteId": site_id,
"locked": False,
"reason": reason
}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("get_locked_sites_by_task")
class GetLockedSitesByTaskComponent(Component):
"""根据任务实例ID获取所有加锁库位组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
task_instance_id = self.resolve_param("task_instance_id")
# 验证必要参数
self.validate_required_params(["task_instance_id"])
# 实际实现中这里应该调用库位管理系统API获取任务锁定的库位
# 这里简化为返回模拟结果
site_ids = ["site_001", "site_002"]
result = {"siteIds": site_ids}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("get_site_extension_property")
class GetSiteExtensionPropertyComponent(Component):
"""获取库位扩展属性值组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
site_id = self.resolve_param("site_id")
property_name = self.resolve_param("property_name")
# 验证必要参数
self.validate_required_params(["site_id", "property_name"])
# 实际实现中这里应该调用库位管理系统API获取库位扩展属性
# 这里简化为返回模拟结果
value = f"属性值_{property_name}"
result = {"value": value}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("set_site_extension_property")
class SetSiteExtensionPropertyComponent(Component):
"""设置库位扩展属性组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
site_id = self.resolve_param("site_id")
property_name = self.resolve_param("property_name")
property_value = self.resolve_param("property_value")
# 验证必要参数
self.validate_required_params(["site_id", "property_name", "property_value"])
# 实际实现中这里应该调用库位管理系统API设置库位扩展属性
# 这里简化为返回模拟结果
result = {
"siteId": site_id,
"propertyName": property_name,
"propertyValue": property_value,
"success": True
}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("set_site_goods")
class SetSiteGoodsComponent(Component):
"""设置库位货物组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
site_id = self.resolve_param("site_id")
goods = self.resolve_param("goods")
# 验证必要参数
self.validate_required_params(["site_id", "goods"])
# 实际实现中这里应该调用库位管理系统API设置库位货物
# 这里简化为返回模拟结果
result = {
"siteId": site_id,
"goods": goods,
"success": True
}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("set_site_empty")
class SetSiteEmptyComponent(Component):
"""设置库位为空组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
site_id = self.resolve_param("site_id")
# 验证必要参数
self.validate_required_params(["site_id"])
# 实际实现中这里应该调用库位管理系统API设置库位为空
# 这里简化为返回模拟结果
result = {
"siteId": site_id,
"success": True
}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("set_site_occupied")
class SetSiteOccupiedComponent(Component):
"""设置库位为占用组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
site_id = self.resolve_param("site_id")
# 验证必要参数
self.validate_required_params(["site_id"])
# 实际实现中这里应该调用库位管理系统API设置库位为占用
# 这里简化为返回模拟结果
result = {
"siteId": site_id,
"success": True
}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("set_site_tag")
class SetSiteTagComponent(Component):
"""设置库位标签组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
site_id = self.resolve_param("site_id")
tag = self.resolve_param("tag")
# 验证必要参数
self.validate_required_params(["site_id", "tag"])
# 实际实现中这里应该调用库位管理系统API设置库位标签
# 这里简化为返回模拟结果
result = {
"siteId": site_id,
"tag": tag,
"success": True
}
# 存储结果
self.store_result(result)
return result

View File

@ -0,0 +1,75 @@
"""
子任务组件
"""
# components/subtask_components.py
from typing import Dict, Any, Optional
from core.component import Component, ComponentFactory
from core.exceptions import ComponentError
from services.workflow_service import WorkflowService
@ComponentFactory.register("subtask")
class SubtaskComponent(Component):
"""子任务组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
subtask_id = self.resolve_param("subtask_id")
async_execute = self.resolve_param("async_execute", False)
input_params = {}
# 收集所有以input_开头的参数作为子任务输入
for key, value in self.params.items():
if key.startswith("input_"):
param_name = key[6:] # 去掉"input_"前缀
input_params[param_name] = self.resolve_param(key)
# 验证必要参数
self.validate_required_params(["subtask_id"])
try:
# 获取工作流服务
workflow_service = WorkflowService()
# 获取子任务定义
subtask_definition = workflow_service.get_workflow_by_id(subtask_id)
if not subtask_definition:
raise ComponentError(f"找不到子任务: {subtask_id}")
# 执行子任务
if async_execute:
# 异步执行子任务
# 实际实现中,这里应该启动一个新线程或任务来执行子任务
# 这里简化为返回模拟数据
result = {
"subtaskId": subtask_id,
"status": "started",
"async": True,
"inputParams": input_params
}
else:
# 同步执行子任务
subtask_result = workflow_service.execute_workflow(
subtask_definition,
input_params
)
result = {
"subtaskId": subtask_id,
"status": subtask_result.get("task_status", "completed"),
"async": False,
"result": subtask_result
}
# 存储结果
self.store_result(result)
return result
except Exception as e:
error_result = {
"subtaskId": subtask_id,
"status": "failed",
"error": str(e)
}
self.store_result(error_result)
raise ComponentError(f"子任务执行失败: {str(e)}")

View File

@ -0,0 +1,142 @@
# components/task_components.py
"""
任务组件
"""
from typing import Dict, Any, Optional
from core.component import Component, ComponentFactory
from core.exceptions import ComponentError
@ComponentFactory.register("cache_data")
class CacheDataComponent(Component):
"""缓存数据组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
key = self.resolve_param("key")
value = self.resolve_param("value")
# 验证必要参数
self.validate_required_params(["key"])
# 缓存数据
cache_key = f"cache_{key}"
self.context.set_variable(cache_key, value)
result = {"key": key}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("clear_cache_data")
class ClearCacheDataComponent(Component):
"""清除缓存数据组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
key = self.resolve_param("key")
# 验证必要参数
self.validate_required_params(["key"])
# 清除缓存数据
cache_key = f"cache_{key}"
if cache_key in self.context.variables:
del self.context.variables[cache_key]
result = {"success": True}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("get_cache_data")
class GetCacheDataComponent(Component):
"""获取缓存数据组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
key = self.resolve_param("key")
# 验证必要参数
self.validate_required_params(["key"])
# 获取缓存数据
cache_key = f"cache_{key}"
value = self.context.get_variable(cache_key)
result = {"value": value}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("set_task_status")
class SetTaskStatusComponent(Component):
"""设置任务状态组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
status = self.resolve_param("status")
# 验证必要参数
self.validate_required_params(["status"])
# 设置任务状态
self.context.set_task_status(status)
result = {"status": status}
# 存储结果
self.store_result(result)
return result
# components/task_components.py (继续)
@ComponentFactory.register("jump_to_block")
class JumpToBlockComponent(Component):
"""跳到某个块组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
target_block_id = self.resolve_param("target_block_id")
# 验证必要参数
self.validate_required_params(["target_block_id"])
# 注意:实际的跳转逻辑需要在工作流执行器中实现
# 这里只是记录跳转意图
result = {
"target_block_id": target_block_id,
"jump_requested": True
}
# 存储结果
self.store_result(result)
return result
@ComponentFactory.register("get_task_input_param")
class GetTaskInputParamComponent(Component):
"""获取任务的输入参数组件"""
def execute(self) -> Dict[str, Any]:
# 获取参数
task_instance_id = self.resolve_param("task_instance_id")
param_name = self.resolve_param("param_name")
# 验证必要参数
self.validate_required_params(["param_name"])
# 获取输入参数
# 如果指定了任务实例ID则需要从数据库或其他存储中获取
# 这里简化为直接从当前任务的输入参数中获取
if task_instance_id and task_instance_id != self.context.task_id:
# 实际实现中,这里应该查询数据库获取指定任务的输入参数
param_value = None
else:
param_value = self.context.task_inputs.get(param_name)
result = {"inputParamValue": param_value}
# 存储结果
self.store_result(result)
return result

0
config/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

62
config/api_config.py Normal file
View File

@ -0,0 +1,62 @@
"""
API响应配置文件
包含API响应格式状态码和消息模板等配置
"""
from typing import Dict, Any, Optional, Type
class ApiResponseModel:
"""API响应模型配置"""
CODE = int # 响应状态码
MESSAGE = str # 响应消息
DATA = None # 响应数据,可为任意类型
class ApiResponseCode:
"""API响应状态码"""
SUCCESS = 200 # 成功
BAD_REQUEST = 400 # 请求错误
UNAUTHORIZED = 401 # 未授权
FORBIDDEN = 403 # 禁止访问
NOT_FOUND = 404 # 资源不存在
SERVER_ERROR = 500 # 服务器错误
class ApiResponseMessage:
"""API响应消息模板"""
# 通用消息
SUCCESS = "操作成功"
FAILED = "操作失败"
SERVER_ERROR = "服务器内部错误"
# 任务相关消息
class Task:
"""任务相关消息"""
CREATED = "任务创建成功"
UPDATED = "任务更新成功"
DELETED = "任务删除成功"
EXECUTED = "任务执行成功"
CANCELED = "任务取消成功"
NOT_FOUND = "任务不存在"
# 工作流相关消息
class Workflow:
"""工作流相关消息"""
SAVED = "工作流保存成功"
VALIDATED = "工作流验证成功"
NOT_FOUND = "工作流不存在"
CREATED = "工作流创建成功"
UPDATED = "工作流更新成功"
DELETED = "工作流删除成功"
EXECUTED = "工作流执行成功"
IMPORTED = "工作流导入成功"
EXPORTED = "工作流导出成功"
# 组件相关消息
class Component:
"""组件相关消息"""
FETCHED = "获取组件类型成功"
DISCOVERED = "自动发现并注册组件成功"
class ApiPagination:
"""分页配置"""
DEFAULT_PAGE = 1 # 默认页码
DEFAULT_PAGE_SIZE = 10 # 默认每页记录数
MAX_PAGE_SIZE = 100 # 最大每页记录数

127
config/component_config.py Normal file
View File

@ -0,0 +1,127 @@
"""
组件配置文件
包含组件发现注册和分类的相关配置
"""
from typing import Dict, List, Any
from data.models.component import ComponentCategoryEnum
class ComponentDiscovery:
"""组件自动发现配置"""
DEFAULT_PACKAGE = "components" # 默认组件包名
AUTO_REGISTER = True # 是否在启动时自动注册所有组件
class ComponentCategory:
"""组件类别配置"""
# 组件类别定义
HTTP_REQUEST = "HTTP请求"
SCRIPT = "脚本"
FLOW = "流程"
ROBOT = "机器人调度"
SITE = "库位"
DEVICE = "设备"
SUBTASK = "子任务"
TASK = "任务"
BASE = "基础"
# 组件类别映射配置
# 用于将组件类型映射到对应的类别,便于前端展示
@classmethod
def get_mapping(cls) -> Dict[str, str]:
"""获取组件类型到类别的映射"""
return {
# HTTP请求组件
"http": cls.HTTP_REQUEST,
# 脚本组件
"script": cls.SCRIPT,
# 流程控制组件
"if": cls.FLOW,
"if_else": cls.FLOW,
"if_else_if": cls.FLOW,
"for_each": cls.FLOW,
"while": cls.FLOW,
"break": cls.FLOW,
"return": cls.FLOW,
"delay": cls.FLOW,
# 机器人调度组件
"robot": cls.ROBOT,
# 库位组件
"site": cls.SITE,
# 设备组件
"modbus": cls.DEVICE,
# 子任务组件
"subtask": cls.SUBTASK,
}
# 组件分类优先级
# 控制前端组件面板中各分类的显示顺序
@classmethod
def get_priority(cls) -> List[str]:
"""获取组件类别优先级列表"""
return [
cls.SUBTASK,
cls.SCRIPT,
cls.HTTP_REQUEST,
cls.TASK,
cls.FLOW,
cls.BASE,
cls.SITE,
cls.ROBOT,
cls.DEVICE
]
class ComponentCategoryConfig:
"""组件分类配置"""
@classmethod
def get_category_name(cls, category_enum):
"""获取分类名称"""
names = {
ComponentCategoryEnum.SUBTASK: "子任务",
ComponentCategoryEnum.SCRIPT: "脚本",
ComponentCategoryEnum.HTTP: "HTTP请求",
ComponentCategoryEnum.TASK: "任务",
ComponentCategoryEnum.FLOW: "流程",
ComponentCategoryEnum.BASIC: "基础",
ComponentCategoryEnum.STORAGE: "库位",
ComponentCategoryEnum.ROBOT: "机器人调度",
ComponentCategoryEnum.DEVICE: "设备"
}
return names.get(category_enum, category_enum.value)
@classmethod
def get_category_description(cls, category_enum):
"""获取分类描述"""
descriptions = {
ComponentCategoryEnum.SUBTASK: "可重用的子任务组件",
ComponentCategoryEnum.SCRIPT: "执行脚本代码的组件",
ComponentCategoryEnum.HTTP: "发送HTTP请求的组件",
ComponentCategoryEnum.TASK: "任务管理相关组件",
ComponentCategoryEnum.FLOW: "流程控制相关组件",
ComponentCategoryEnum.BASIC: "基础功能组件",
ComponentCategoryEnum.STORAGE: "库位管理相关组件",
ComponentCategoryEnum.ROBOT: "机器人调度相关组件",
ComponentCategoryEnum.DEVICE: "设备控制相关组件"
}
return descriptions.get(category_enum, f"{category_enum.value}类组件")
@classmethod
def get_category_order(cls, category_enum):
"""获取分类排序"""
orders = {
ComponentCategoryEnum.BASIC: 1,
ComponentCategoryEnum.FLOW: 2,
ComponentCategoryEnum.TASK: 3,
ComponentCategoryEnum.SUBTASK: 4,
ComponentCategoryEnum.SCRIPT: 5,
ComponentCategoryEnum.HTTP: 6,
ComponentCategoryEnum.ROBOT: 7,
ComponentCategoryEnum.STORAGE: 8,
ComponentCategoryEnum.DEVICE: 9
}
return orders.get(category_enum, 99)

View File

@ -0,0 +1,472 @@
"""
组件详细配置文件
包含各种组件类型及其子组件的配置信息
"""
from typing import Dict, List, Any, Optional
class ScriptComponentConfig:
"""脚本组件配置"""
# 脚本组件类型
RUN_SCRIPT = "run_script" # 运行脚本
SET_VARIABLES = "set_variables" # 设置task.variables
# 脚本组件详细配置
@classmethod
def get_components(cls) -> List[Dict[str, Any]]:
"""获取脚本组件列表"""
return [
{
"type": "script",
"sub_type": cls.RUN_SCRIPT,
"name": "运行脚本",
"description": "执行JavaScript代码并返回结果",
"icon": "code", # 图标名称,前端可用
"params": [
{
"name": "function_name",
"label": "函数名",
"type": "string",
"required": False,
"description": "定义脚本中的主函数名称",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": True
},
{
"type": "expression",
"label": "表达式",
"default": False
}
]
},
{
"name": "params",
"label": "函数参数",
"type": "array",
"required": False,
"description": "传递给脚本的参数",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": True
},
{
"type": "expression",
"label": "表达式",
"default": False
}
]
}
]
},
{
"type": "script",
"sub_type": cls.SET_VARIABLES,
"name": "设置task.variables",
"description": "设置和管理任务变量",
"icon": "variable",
"params": [
{
"name": "function_name",
"label": "函数名",
"type": "string",
"required": False,
"description": "定义脚本中的主函数名称",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": True
},
{
"type": "expression",
"label": "表达式",
"default": False
}
]
},
{
"name": "params",
"label": "函数参数",
"type": "array",
"required": False,
"description": "传递给脚本的参数",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": True
},
{
"type": "expression",
"label": "表达式",
"default": False
}
]
}
]
}
]
class HttpComponentConfig:
"""HTTP请求组件配置"""
# HTTP请求组件类型
HTTP_REQUEST = "http_request" # HTTP请求
# HTTP请求组件详细配置
@classmethod
def get_components(cls) -> List[Dict[str, Any]]:
"""获取HTTP请求组件列表"""
return [
{
"type": "http",
"sub_type": cls.HTTP_REQUEST,
"name": "HTTP请求",
"description": "发送HTTP请求并处理响应",
"icon": "http",
"params": [
{
"name": "method",
"label": "请求方法",
"type": "select",
"options": ["GET", "POST", "PUT", "DELETE", "PATCH"],
"required": True,
"description": "HTTP请求方法"
},
{
"name": "url",
"label": "请求URL",
"type": "string",
"required": True,
"description": "请求的目标URL"
},
{
"name": "headers",
"label": "请求头",
"type": "object",
"required": False,
"description": "HTTP请求头"
},
{
"name": "body",
"label": "请求体",
"type": "object",
"required": False,
"description": "HTTP请求体"
},
{
"name": "timeout",
"label": "超时时间",
"type": "number",
"required": False,
"description": "请求超时时间(毫秒)"
}
]
}
]
class FlowComponentConfig:
"""流程控制组件配置"""
# 流程控制组件类型
IF = "if" # 条件判断
IF_ELSE = "if_else" # 条件分支
FOR_EACH = "for_each" # 循环遍历
WHILE = "while" # 条件循环
# 流程控制组件详细配置
@classmethod
def get_components(cls) -> List[Dict[str, Any]]:
"""获取流程控制组件列表"""
return [
{
"type": "flow",
"sub_type": cls.IF,
"name": "条件判断",
"description": "根据条件执行不同的操作",
"icon": "branch",
"params": [
{
"name": "condition",
"label": "条件表达式",
"type": "expression",
"required": True,
"description": "条件判断表达式"
}
]
},
{
"type": "flow",
"sub_type": cls.IF_ELSE,
"name": "条件分支",
"description": "根据条件执行不同的分支",
"icon": "branch-multiple",
"params": [
{
"name": "condition",
"label": "条件表达式",
"type": "expression",
"required": True,
"description": "条件判断表达式"
}
]
},
{
"type": "flow",
"sub_type": cls.FOR_EACH,
"name": "循环遍历",
"description": "遍历数组或对象的每个元素",
"icon": "loop",
"params": [
{
"name": "collection",
"label": "集合表达式",
"type": "expression",
"required": True,
"description": "要遍历的数组或对象"
},
{
"name": "item_name",
"label": "元素变量名",
"type": "string",
"required": True,
"description": "当前元素的变量名"
},
{
"name": "index_name",
"label": "索引变量名",
"type": "string",
"required": False,
"description": "当前索引的变量名"
}
]
},
{
"type": "flow",
"sub_type": cls.WHILE,
"name": "条件循环",
"description": "当条件为真时重复执行",
"icon": "loop-circular",
"params": [
{
"name": "condition",
"label": "条件表达式",
"type": "expression",
"required": True,
"description": "循环条件表达式"
}
]
}
]
class SubtaskComponentConfig:
"""子任务组件配置"""
# 子任务组件类型
SUBTASK = "subtask" # 子任务
# 子任务组件详细配置
@classmethod
def get_components(cls) -> List[Dict[str, Any]]:
"""获取子任务组件列表"""
return [
{
"type": "subtask",
"sub_type": cls.SUBTASK,
"name": "子任务",
"description": "执行已定义的任务作为子任务",
"icon": "subtask",
"params": [
{
"name": "task_id",
"label": "选择子任务",
"type": "select",
"required": True,
"description": "选择要执行的子任务(从已创建的任务中选择)",
"data_source": "available_subtasks", # 指示前端从API返回的available_subtasks字段获取数据
"display_field": "name", # 显示任务名称
"value_field": "task_id" # 使用任务ID作为值
},
{
"name": "params",
"label": "任务参数",
"type": "object",
"required": False,
"description": "传递给子任务的参数"
},
{
"name": "wait_complete",
"label": "等待完成",
"type": "boolean",
"required": False,
"default": True,
"description": "是否等待子任务完成后再继续执行"
}
]
}
]
# 组件配置管理类
class ComponentDetailConfig:
"""组件详细配置管理"""
# 组件类型中文名称映射
@classmethod
def get_component_type_names(cls) -> Dict[str, str]:
"""获取组件类型的中文名称映射"""
return {
# 基础类型
"script": "脚本",
"http": "HTTP请求",
"flow": "流程",
"robot": "机器人调度",
"site": "库位",
"device": "设备",
"subtask": "子任务",
"task": "任务",
"basic": "基础",
# 脚本组件
"run_script": "运行脚本",
"set_task_variables": "设置任务变量",
"runscript": "运行脚本",
"settaskvariables": "设置任务变量",
# HTTP请求组件
"http_get_request": "GET请求",
"http_post_request": "POST请求",
"httpgetrequest": "GET请求",
"httppostrequest": "POST请求",
# 流程控制组件
"if": "条件判断",
"if_else": "条件分支",
"if_else_if": "多条件分支",
"for_each": "循环遍历",
"while": "条件循环",
"break": "跳出循环",
"return": "返回",
"delay": "延时",
"parallel_execute": "并行执行",
"serial_execute": "串行执行",
"throw_exception": "抛出异常",
"foreach": "循环遍历",
"ifelse": "条件分支",
"ifelseif": "多条件分支",
"parallelexecute": "并行执行",
"serialexecute": "串行执行",
"throwexception": "抛出异常",
# 机器人调度组件
"select_robot": "选择机器人",
"get_robot_position": "获取机器人位置",
"robot_action": "机器人动作",
"change_robot_destination": "更改机器人目的地",
"get_robot_battery": "获取机器人电量",
"get_robot_pgv_code": "获取机器人PGV码",
"changerobotdestination": "更改机器人目的地",
"getrobotbattery": "获取机器人电量",
"getrobotpgvcode": "获取机器人PGV码",
"getrobotposition": "获取机器人位置",
"robotaction": "机器人动作",
"selectrobot": "选择机器人",
# 库位组件
"batch_set_site": "批量设置库位",
"get_dense_site": "获取密集库位",
"query_site": "查询库位",
"lock_site": "锁定库位",
"unlock_site": "解锁库位",
"get_locked_sites_by_task": "获取任务锁定的库位",
"get_site_extension_property": "获取库位扩展属性",
"set_site_extension_property": "设置库位扩展属性",
"set_site_goods": "设置库位货物",
"set_site_empty": "设置库位为空",
"set_site_occupied": "设置库位为占用",
"set_site_tag": "设置库位标签",
"batchsetsite": "批量设置库位",
"getdensesite": "获取密集库位",
"getlockedsitesbytask": "获取任务锁定的库位",
"getsiteextensionproperty": "获取库位扩展属性",
"locksite": "锁定库位",
"querysite": "查询库位",
"setsiteempty": "设置库位为空",
"setsiteextensionproperty": "设置库位扩展属性",
"setsitegoods": "设置库位货物",
"setsiteoccupied": "设置库位为占用",
"setsitetag": "设置库位标签",
"unlocksite": "解锁库位",
# 任务组件
"cache_data": "缓存数据",
"clear_cache_data": "清除缓存数据",
"get_cache_data": "获取缓存数据",
"set_task_status": "设置任务状态",
"jump_to_block": "跳转到块",
"get_task_input_param": "获取任务输入参数",
"cachedata": "缓存数据",
"clearcachedata": "清除缓存数据",
"getcachedata": "获取缓存数据",
"gettaskinputparam": "获取任务输入参数",
"jumptoblock": "跳转到块",
"settaskstatus": "设置任务状态",
# 基础组件
"check_task_instance_id_exists": "检查任务实例ID是否存在",
"create_unique_id": "创建唯一ID",
"current_timestamp": "当前时间戳",
"current_time": "当前时间",
"execute_sql": "执行SQL",
"query_sql": "查询SQL",
"string_md5_encrypt": "字符串MD5加密",
"string_to_json_array": "字符串转JSON数组",
"string_to_json_object": "字符串转JSON对象",
"print": "打印",
"checktaskinstanceidexists": "检查任务实例ID是否存在",
"createuniqueid": "创建唯一ID",
"currenttime": "当前时间",
"currenttimestamp": "当前时间戳",
"executesql": "执行SQL",
"querysql": "查询SQL",
"stringmd5encrypt": "字符串MD5加密",
"stringtojsonarray": "字符串转JSON数组",
"stringtojsonobject": "字符串转JSON对象",
# 设备组件
"wait_modbus_value": "等待Modbus值",
"write_modbus_value": "写入Modbus值",
"waitmodbusvalue": "等待Modbus值",
"writemodbusvalue": "写入Modbus值"
}
@classmethod
def get_all_components(cls) -> List[Dict[str, Any]]:
"""获取所有组件详细配置"""
all_components = []
# 添加子任务组件(放在第一位)
all_components.extend(SubtaskComponentConfig.get_components())
# 添加脚本组件
all_components.extend(ScriptComponentConfig.get_components())
# 添加HTTP请求组件
all_components.extend(HttpComponentConfig.get_components())
# 添加流程控制组件
all_components.extend(FlowComponentConfig.get_components())
# 可以继续添加其他类型的组件...
return all_components
@classmethod
def get_components_by_type(cls, component_type: str) -> List[Dict[str, Any]]:
"""根据组件类型获取组件列表"""
all_components = cls.get_all_components()
return [comp for comp in all_components if comp["type"] == component_type]

View File

@ -0,0 +1,23 @@
"""
组件注册表
"""
# config/component_registry.py
from core.component import ComponentFactory
import importlib
from config.settings import COMPONENT_PACKAGES
from utils.logger import get_logger
# 获取日志记录器
logger = get_logger(__name__)
def register_all_components():
"""注册所有组件"""
for package_name in COMPONENT_PACKAGES:
try:
ComponentFactory.auto_discover(package_name)
logger.info(f"自动注册组件包: {package_name}")
except ImportError:
logger.error(f"导入组件包失败: {package_name}")
except Exception as e:
logger.error(f"注册组件包失败: {package_name}, 错误: {str(e)}")

347
config/database.py Normal file
View File

@ -0,0 +1,347 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
数据库连接配置模块
包含数据库连接参数和SQLAlchemy配置以及Redis缓存配置
"""
import os
import json
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session
class ConfigDict:
"""配置字典类,支持通过点号访问配置项"""
def __init__(self, **kwargs):
for key, value in kwargs.items():
if isinstance(value, dict):
setattr(self, key, ConfigDict(**value))
else:
setattr(self, key, value)
def get(self, key, default=None):
return getattr(self, key, default)
def to_dict(self):
result = {}
for key, value in self.__dict__.items():
if isinstance(value, ConfigDict):
result[key] = value.to_dict()
else:
result[key] = value
return result
# 数据库连接配置
DB_CONFIG = ConfigDict(
default=dict(
dialect='mysql',
driver='pymysql',
username='root',
password='root',
host='localhost',
port=3306,
database='tianfeng_task',
charset='utf8mb4'
),
test=dict(
dialect='sqlite',
database=':memory:'
)
)
# Redis缓存配置
REDIS_CONFIG = ConfigDict(
default=dict(
host='localhost',
port=6379,
db=0,
password=None,
prefix='tianfeng:',
socket_timeout=5,
socket_connect_timeout=5,
decode_responses=True
),
test=dict(
host='localhost',
port=6379,
db=1,
password=None,
prefix='tianfeng_test:',
decode_responses=True
)
)
# 当前环境,可通过环境变量设置
ENV = os.environ.get('TIANFENG_ENV', 'default')
# 根据环境获取数据库配置
db_conf = getattr(DB_CONFIG, ENV)
# 构建数据库连接URL
if db_conf.dialect == 'sqlite':
DATABASE_URL = f"{db_conf.dialect}:///{db_conf.database}"
else:
DATABASE_URL = (
f"{db_conf.dialect}+{db_conf.driver}://"
f"{db_conf.username}:{db_conf.password}@"
f"{db_conf.host}:{db_conf.port}/{db_conf.database}?"
f"charset={db_conf.charset}"
)
# 创建数据库引擎
engine = create_engine(
DATABASE_URL,
pool_size=20,
max_overflow=0,
pool_recycle=3600,
pool_pre_ping=True,
echo=False # 设置为True可以显示SQL语句用于调试
)
# 创建会话工厂
SessionFactory = sessionmaker(bind=engine)
# 创建线程安全的会话
db_session = scoped_session(SessionFactory)
# 创建基类
Base = declarative_base()
Base.query = db_session.query_property()
# 数据库配置类
class DBConfig:
"""数据库配置类,提供数据库相关的配置和方法"""
config = DB_CONFIG
env = ENV
url = DATABASE_URL
engine = engine
session = db_session
base = Base
@classmethod
def get_config(cls):
"""获取当前环境的数据库配置"""
return getattr(cls.config, cls.env)
@classmethod
def get_session(cls):
"""获取数据库会话"""
return cls.session
@classmethod
def init_db(cls):
"""
初始化数据库
创建所有表
"""
# 导入所有模型确保它们已注册到Base
import data.models
# 首先尝试创建数据库(如果不存在)
if cls.get_config().dialect != 'sqlite':
from sqlalchemy import text
# 创建一个不指定数据库的连接
db_conf = cls.get_config()
temp_url = (
f"{db_conf.dialect}+{db_conf.driver}://"
f"{db_conf.username}:{db_conf.password}@"
f"{db_conf.host}:{db_conf.port}/"
f"?charset={db_conf.charset}"
)
temp_engine = create_engine(temp_url)
with temp_engine.connect() as conn:
conn.execute(text(f"CREATE DATABASE IF NOT EXISTS {db_conf.database} CHARACTER SET {db_conf.charset} COLLATE {db_conf.charset}_unicode_ci;"))
conn.commit()
temp_engine.dispose()
# 创建所有表
cls.base.metadata.create_all(bind=cls.engine)
@classmethod
def shutdown_session(cls, exception=None):
"""
关闭会话
在应用程序关闭时调用
"""
cls.session.remove()
# 缓存配置类
class CacheConfig:
"""缓存配置类提供Redis缓存相关的配置和方法"""
config = REDIS_CONFIG
env = ENV
_redis_client = None
@classmethod
def get_config(cls):
"""获取当前环境的Redis配置"""
return getattr(cls.config, cls.env)
@classmethod
def get_redis_client(cls):
"""获取Redis客户端实例"""
if cls._redis_client is None:
try:
import redis
redis_conf = cls.get_config()
cls._redis_client = redis.Redis(
host=redis_conf.host,
port=redis_conf.port,
db=redis_conf.db,
password=redis_conf.password,
socket_timeout=getattr(redis_conf, 'socket_timeout', 5),
socket_connect_timeout=getattr(redis_conf, 'socket_connect_timeout', 5),
decode_responses=getattr(redis_conf, 'decode_responses', True)
)
except ImportError:
raise ImportError("Redis package is not installed. Please install it with 'pip install redis'")
except Exception as e:
print(f"Error connecting to Redis: {e}")
return None
return cls._redis_client
@classmethod
def get_key(cls, key):
"""获取带前缀的缓存键"""
prefix = getattr(cls.get_config(), 'prefix', 'tianfeng:')
return f"{prefix}{key}"
@classmethod
def set(cls, key, value, expire=None):
"""
设置缓存
Args:
key (str): 缓存键
value (any): 缓存值非字符串类型会被JSON序列化
expire (int, optional): 过期时间
Returns:
bool: 是否设置成功
"""
redis_client = cls.get_redis_client()
if not redis_client:
return False
if not isinstance(value, (str, int, float, bool)):
value = json.dumps(value)
full_key = cls.get_key(key)
if expire:
return redis_client.setex(full_key, expire, value)
else:
return redis_client.set(full_key, value)
@classmethod
def get(cls, key, default=None):
"""
获取缓存
Args:
key (str): 缓存键
default (any, optional): 默认值
Returns:
any: 缓存值或默认值
"""
redis_client = cls.get_redis_client()
if not redis_client:
return default
full_key = cls.get_key(key)
value = redis_client.get(full_key)
if value is None:
return default
# 尝试解析JSON
try:
if value.startswith('{') or value.startswith('['):
return json.loads(value)
except (json.JSONDecodeError, AttributeError):
pass
return value
@classmethod
def delete(cls, key):
"""
删除缓存
Args:
key (str): 缓存键
Returns:
bool: 是否删除成功
"""
redis_client = cls.get_redis_client()
if not redis_client:
return False
full_key = cls.get_key(key)
return redis_client.delete(full_key) > 0
@classmethod
def exists(cls, key):
"""
检查缓存是否存在
Args:
key (str): 缓存键
Returns:
bool: 是否存在
"""
redis_client = cls.get_redis_client()
if not redis_client:
return False
full_key = cls.get_key(key)
return redis_client.exists(full_key) > 0
@classmethod
def ttl(cls, key):
"""
获取缓存剩余过期时间
Args:
key (str): 缓存键
Returns:
int: 剩余秒数-1表示永不过期-2表示不存在
"""
redis_client = cls.get_redis_client()
if not redis_client:
return -2
full_key = cls.get_key(key)
return redis_client.ttl(full_key)
@classmethod
def clear_all(cls):
"""
清除当前环境下的所有缓存
Returns:
bool: 是否清除成功
"""
redis_client = cls.get_redis_client()
if not redis_client:
return False
prefix = getattr(cls.get_config(), 'prefix', 'tianfeng:')
keys = redis_client.keys(f"{prefix}*")
if keys:
return redis_client.delete(*keys) > 0
return True
# 兼容旧代码的函数
def init_db():
"""初始化数据库(兼容旧代码)"""
DBConfig.init_db()
def shutdown_session(exception=None):
"""关闭会话(兼容旧代码)"""
DBConfig.shutdown_session(exception)

108
config/settings.py Normal file
View File

@ -0,0 +1,108 @@
# config/settings.py
"""
应用全局配置文件
包含应用基本信息服务配置数据库配置日志配置等
"""
import os
from dataclasses import dataclass
from typing import List, Dict, Any
# 应用基本信息
class AppConfig:
"""应用基本配置"""
NAME = "天风任务模块" # 应用名称
VERSION = "1.0.0" # 应用版本号
DEBUG = os.environ.get("DEBUG", "True").lower() == "true" # 调试模式开关,可通过环境变量设置
# 服务配置
class ServerConfig:
"""服务器配置"""
HOST = os.environ.get("HOST", "0.0.0.0") # 服务监听地址
PORT = int(os.environ.get("PORT", 8000)) # 服务监听端口
RELOAD = AppConfig.DEBUG # 是否启用热重载默认与DEBUG模式一致
# API配置
class ApiConfig:
"""API配置"""
PREFIX = "/api/v1"
TITLE = "天风任务模块API" # API文档标题
DESCRIPTION = "AMR调度系统中天风任务模块的API接口" # API文档描述
VERSION = AppConfig.VERSION # API版本号
# CORS配置跨域资源共享
class CorsConfig:
"""CORS配置"""
ALLOW_ORIGINS = ["*"] # 允许的源,* 表示允许所有源
ALLOW_CREDENTIALS = True # 是否允许携带凭证
ALLOW_METHODS = ["*"] # 允许的HTTP方法
ALLOW_HEADERS = ["*"] # 允许的HTTP头
# 数据库配置
class DatabaseConfig:
"""数据库配置"""
HOST = os.environ.get("DB_HOST", "localhost") # 数据库主机
PORT = int(os.environ.get("DB_PORT", 3306)) # 数据库端口
USER = os.environ.get("DB_USER", "root") # 数据库用户名
PASSWORD = os.environ.get("DB_PASSWORD", "password") # 数据库密码
DATABASE = os.environ.get("DB_NAME", "tianfeng_task") # 数据库名
CHARSET = "utf8mb4" # 字符集
@classmethod
def as_dict(cls) -> Dict[str, Any]:
"""返回数据库配置字典"""
return {
"host": cls.HOST,
"port": cls.PORT,
"user": cls.USER,
"password": cls.PASSWORD,
"database": cls.DATABASE,
"charset": cls.CHARSET
}
# 日志配置
class LogConfig:
"""日志配置"""
LEVEL = os.environ.get("LOG_LEVEL", "INFO") # 日志级别
FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" # 日志格式
FILE = os.environ.get("LOG_FILE", "logs/tianfeng_task.log") # 日志文件路径
@classmethod
def as_dict(cls) -> Dict[str, Any]:
"""返回日志配置字典"""
return {
"level": cls.LEVEL,
"format": cls.FORMAT,
"file": cls.FILE
}
# 组件配置
class ComponentConfig:
"""组件配置"""
PACKAGES = ["components"] # 组件包路径
# 响应码配置
class ResponseCode:
"""响应码配置"""
SUCCESS = 200 # 成功
BAD_REQUEST = 400 # 请求错误
UNAUTHORIZED = 401 # 未授权
FORBIDDEN = 403 # 禁止访问
NOT_FOUND = 404 # 资源不存在
SERVER_ERROR = 500 # 服务器错误
# 任务状态配置
class TaskStatus:
"""任务状态配置"""
PENDING = "待执行" # 待执行
RUNNING = "执行中" # 执行中
COMPLETED = "已完成" # 已完成
FAILED = "执行失败" # 执行失败
CANCELED = "已取消" # 已取消
PAUSED = "暂停中" # 暂停中
WAITING = "等待中" # 等待中
# 导出日志配置字典,供其他模块使用
LOG_CONFIG = LogConfig.as_dict()
# 导出组件包列表,供组件注册使用
COMPONENT_PACKAGES = ComponentConfig.PACKAGES

290
config/task_config.py Normal file
View File

@ -0,0 +1,290 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
任务配置模块
包含任务类型状态等配置信息
"""
from enum import Enum
# 任务类型枚举
class TaskType(str, Enum):
NORMAL = "NORMAL"
SCHEDULED = "SCHEDULED"
# 任务状态枚举
class TaskStatus(str, Enum):
PENDING = "PENDING"
RUNNING = "RUNNING"
COMPLETED = "COMPLETED"
CANCELLED = "CANCELLED"
FAILED = "FAILED"
PAUSED = "PAUSED"
WAITING = "WAITING"
# 任务类型配置
class TaskTypeConfig:
"""任务类型配置"""
NORMAL = TaskType.NORMAL.value
SCHEDULED = TaskType.SCHEDULED.value
# 任务类型详细信息
DETAILS = {
NORMAL: {
"name": "普通任务",
"description": "立即执行的一次性任务"
},
SCHEDULED: {
"name": "定时任务",
"description": "按照预设时间执行的任务"
}
}
@classmethod
def get_name(cls, task_type_key):
"""
获取任务类型的显示名称
Args:
task_type_key (str): 任务类型的键名
Returns:
str: 任务类型的显示名称
"""
return cls.DETAILS.get(task_type_key, {}).get("name", task_type_key)
@classmethod
def get_description(cls, task_type_key):
"""
获取任务类型的描述
Args:
task_type_key (str): 任务类型的键名
Returns:
str: 任务类型的描述
"""
return cls.DETAILS.get(task_type_key, {}).get("description", "")
@classmethod
def get_all(cls):
"""
获取所有任务类型
Returns:
list: 任务类型列表每个元素包含keyname和description
"""
task_types = []
for key, config in cls.DETAILS.items():
task_types.append({
"key": key,
"name": config["name"],
"description": config["description"]
})
return task_types
# 任务状态配置
class TaskStatusConfig:
"""任务状态配置"""
PENDING = TaskStatus.PENDING.value
RUNNING = TaskStatus.RUNNING.value
COMPLETED = TaskStatus.COMPLETED.value
CANCELLED = TaskStatus.CANCELLED.value
FAILED = TaskStatus.FAILED.value
PAUSED = TaskStatus.PAUSED.value
WAITING = TaskStatus.WAITING.value
# 任务状态详细信息
DETAILS = {
PENDING: {
"name": "待执行",
"description": "任务已创建但尚未开始执行"
},
RUNNING: {
"name": "执行中",
"description": "任务正在执行"
},
COMPLETED: {
"name": "已完成",
"description": "任务已成功完成"
},
CANCELLED: {
"name": "已取消",
"description": "任务被用户取消"
},
FAILED: {
"name": "执行失败",
"description": "任务执行过程中出现错误"
},
PAUSED: {
"name": "暂停中",
"description": "任务被暂停"
},
WAITING: {
"name": "等待中",
"description": "任务正在等待某些条件满足"
}
}
@classmethod
def get_name(cls, task_status_key):
"""
获取任务状态的显示名称
Args:
task_status_key (str): 任务状态的键名
Returns:
str: 任务状态的显示名称
"""
return cls.DETAILS.get(task_status_key, {}).get("name", task_status_key)
@classmethod
def get_description(cls, task_status_key):
"""
获取任务状态的描述
Args:
task_status_key (str): 任务状态的键名
Returns:
str: 任务状态的描述
"""
return cls.DETAILS.get(task_status_key, {}).get("description", "")
@classmethod
def get_all(cls):
"""
获取所有任务状态
Returns:
list: 任务状态列表每个元素包含keyname和description
"""
task_statuses = []
for key, config in cls.DETAILS.items():
task_statuses.append({
"key": key,
"name": config["name"],
"description": config["description"]
})
return task_statuses
# 默认值配置
class DefaultConfig:
"""默认值配置"""
TASK_DESCRIPTION = "" # 默认任务描述(备注)
TEMPLATE_DESCRIPTION = "用户自有模板" # 默认模板描述
# 为了向后兼容,保留原有的函数和变量
TASK_TYPE_CONFIG = TaskTypeConfig.DETAILS
TASK_STATUS_CONFIG = TaskStatusConfig.DETAILS
# 任务类型常量
TASK_TYPE_NORMAL = TaskTypeConfig.NORMAL
TASK_TYPE_SCHEDULED = TaskTypeConfig.SCHEDULED
# 默认值配置
DEFAULT_TASK_DESCRIPTION = DefaultConfig.TASK_DESCRIPTION
DEFAULT_TEMPLATE_DESCRIPTION = DefaultConfig.TEMPLATE_DESCRIPTION
# 获取任务类型名称
def get_task_type_name(task_type_key):
"""
获取任务类型的显示名称
Args:
task_type_key (str): 任务类型的键名
Returns:
str: 任务类型的显示名称
"""
return TaskTypeConfig.get_name(task_type_key)
# 获取任务类型描述
def get_task_type_description(task_type_key):
"""
获取任务类型的描述
Args:
task_type_key (str): 任务类型的键名
Returns:
str: 任务类型的描述
"""
return TaskTypeConfig.get_description(task_type_key)
# 获取任务状态名称
def get_task_status_name(task_status_key):
"""
获取任务状态的显示名称
Args:
task_status_key (str): 任务状态的键名
Returns:
str: 任务状态的显示名称
"""
return TaskStatusConfig.get_name(task_status_key)
# 获取任务状态描述
def get_task_status_description(task_status_key):
"""
获取任务状态的描述
Args:
task_status_key (str): 任务状态的键名
Returns:
str: 任务状态的描述
"""
return TaskStatusConfig.get_description(task_status_key)
# 获取所有任务类型
def get_all_task_types():
"""
获取所有任务类型
Returns:
list: 任务类型列表每个元素包含keyname和description
"""
return TaskTypeConfig.get_all()
# 获取所有任务状态
def get_all_task_statuses():
"""
获取所有任务状态
Returns:
list: 任务状态列表每个元素包含keyname和description
"""
return TaskStatusConfig.get_all()
# 获取任务类型枚举
def get_task_type_enum(task_type_key):
"""
获取任务类型的枚举值
Args:
task_type_key (str): 任务类型的键名
Returns:
str: 任务类型的枚举值小写
"""
return task_type_key.lower()
# 获取任务状态枚举
def get_task_status_enum(task_status_key):
"""
获取任务状态的枚举值
Args:
task_status_key (str): 任务状态的键名
Returns:
str: 任务状态的枚举值小写
"""
return task_status_key.lower()

0
core/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

115
core/component.py Normal file
View File

@ -0,0 +1,115 @@
# core/component.py
"""
组件基类和工厂
"""
from abc import ABC, abstractmethod
from typing import Dict, Any, Type, Callable, List, Optional
import importlib
import inspect
import pkgutil
from .context import TaskContext
from .exceptions import ComponentError, ParameterError
from utils.logger import get_logger
# 获取日志记录器
logger = get_logger(__name__)
class Component(ABC):
"""组件基类"""
def __init__(self, block_id: str, params: Dict[str, Any]):
self.block_id = block_id
self.params = params
self.context = TaskContext.get_instance()
@abstractmethod
def execute(self) -> Dict[str, Any]:
"""执行组件逻辑"""
pass
def resolve_param(self, param_name: str, default=None) -> Any:
"""解析参数值,处理变量引用"""
if param_name not in self.params:
return default
value = self.params[param_name]
# 处理变量引用 ${xxx}
if isinstance(value, str) and value.startswith("${") and value.endswith("}"):
var_path = value[2:-1].split(".")
current = self.context.variables
for part in var_path:
if isinstance(current, dict) and part in current:
current = current[part]
else:
return default # 变量不存在,返回默认值
return current
# 处理块引用 blocks.bX.xxx
if isinstance(value, str) and value.startswith("blocks."):
parts = value.split(".")
if len(parts) >= 3 and parts[0] == "blocks" and parts[1] in self.context.blocks:
block_data = self.context.blocks[parts[1]]
if len(parts) == 3 and parts[2] in block_data:
return block_data[parts[2]]
return value
def store_result(self, result: Dict[str, Any]) -> None:
"""存储执行结果"""
self.context.set_block_result(self.block_id, result)
def validate_required_params(self, required_params: List[str]) -> None:
"""验证必要参数"""
for param in required_params:
if param not in self.params or self.params[param] is None:
raise ParameterError(f"缺少必要参数: {param}")
class ComponentFactory:
"""组件工厂,负责创建组件实例"""
_components: Dict[str, Type[Component]] = {}
@classmethod
def register(cls, component_type: str) -> Callable:
"""注册组件类的装饰器"""
def decorator(component_class: Type[Component]) -> Type[Component]:
cls._components[component_type] = component_class
logger.info(f"注册组件: {component_type} -> {component_class.__name__}")
return component_class
return decorator
@classmethod
def create(cls, block_id: str, component_type: str, params: Dict[str, Any]) -> Component:
"""创建组件实例"""
if component_type not in cls._components:
raise ComponentError(f"未知的组件类型: {component_type}")
component_class = cls._components[component_type]
return component_class(block_id, params)
@classmethod
def get_component_types(cls) -> List[str]:
"""获取所有已注册的组件类型"""
return list(cls._components.keys())
@classmethod
def auto_discover(cls, package_name: str) -> None:
"""自动发现并注册组件"""
logger.info(f"自动发现组件: {package_name}")
package = importlib.import_module(package_name)
for _, module_name, is_pkg in pkgutil.iter_modules(package.__path__, package.__name__ + '.'):
if not is_pkg:
try:
module = importlib.import_module(module_name)
for name, obj in inspect.getmembers(module):
if (inspect.isclass(obj) and issubclass(obj, Component) and
obj != Component and not inspect.isabstract(obj)):
# 从类名推断组件类型
component_type = obj.__name__.replace('Component', '').lower()
cls._components[component_type] = obj
logger.info(f"自动注册组件: {component_type} -> {obj.__name__}")
except Exception as e:
logger.error(f"加载组件模块失败: {module_name}, 错误: {str(e)}")

94
core/context.py Normal file
View File

@ -0,0 +1,94 @@
# core/context.py
"""
任务上下文
"""
from typing import Dict, Any, Optional
import threading
import time
class TaskContext:
"""
任务上下文存储任务变量和执行结果
使用线程本地存储确保线程安全
"""
_thread_local = threading.local()
@classmethod
def get_instance(cls):
"""获取当前线程的上下文实例"""
if not hasattr(cls._thread_local, 'instance'):
cls._thread_local.instance = TaskContext()
return cls._thread_local.instance
def __init__(self):
self.task_id: Optional[str] = None
self.workflow_id: Optional[str] = None
self.variables: Dict[str, Any] = {} # 任务变量
self.blocks: Dict[str, Any] = {} # 块执行结果
self.task_inputs: Dict[str, Any] = {} # 任务输入参数
self.task_status: str = "pending" # 任务状态
self.start_time: Optional[int] = None # 开始时间
self.end_time: Optional[int] = None # 结束时间
self.executing_robot: Optional[str] = None # 执行机器人
def reset(self):
"""重置上下文"""
self.__init__()
def initialize_task(self, task_id: str, workflow_id: str, inputs: Dict[str, Any] = None):
"""初始化任务"""
self.task_id = task_id
self.workflow_id = workflow_id
self.start_time = int(time.time() * 1000)
self.task_status = "executing"
if inputs:
self.task_inputs = inputs.copy()
self.variables["taskInputs"] = self.task_inputs
def set_variable(self, name: str, value: Any):
"""设置变量值"""
self.variables[name] = value
def get_variable(self, name: str, default=None):
"""获取变量值"""
return self.variables.get(name, default)
def set_block_result(self, block_id: str, result: Dict[str, Any]):
"""设置块执行结果"""
self.blocks[block_id] = result
def get_block_result(self, block_id: str, default=None):
"""获取块执行结果"""
return self.blocks.get(block_id, default)
def set_task_status(self, status: str):
"""设置任务状态"""
self.task_status = status
self.variables["taskStatus"] = status
def set_executing_robot(self, robot_id: str):
"""设置执行机器人"""
self.executing_robot = robot_id
self.variables["executingRobot"] = robot_id
def complete_task(self, status: str = "completed"):
"""完成任务"""
self.task_status = status
self.variables["taskStatus"] = status
self.end_time = int(time.time() * 1000)
def to_dict(self):
"""转换为字典"""
return {
"task_id": self.task_id,
"workflow_id": self.workflow_id,
"variables": self.variables,
"blocks": self.blocks,
"task_inputs": self.task_inputs,
"task_status": self.task_status,
"start_time": self.start_time,
"end_time": self.end_time,
"executing_robot": self.executing_robot
}

31
core/exceptions.py Normal file
View File

@ -0,0 +1,31 @@
# core/exceptions.py
"""
自定义异常
"""
class TianfengTaskError(Exception):
"""天风任务模块基础异常"""
pass
class ComponentError(TianfengTaskError):
"""组件执行异常"""
pass
class ParameterError(ComponentError):
"""参数错误异常"""
pass
class WorkflowError(TianfengTaskError):
"""工作流执行异常"""
pass
class DatabaseError(TianfengTaskError):
"""数据库异常"""
pass
class ValidationError(TianfengTaskError):
"""验证异常"""
pass
class ApiError(TianfengTaskError):
"""API异常"""
pass

View File

View File

@ -0,0 +1,3 @@
"""
向量化核心功能
"""

View File

@ -0,0 +1,3 @@
"""
向量存储核心功能
"""

287
core/workflow.py Normal file
View File

@ -0,0 +1,287 @@
# core/workflow.py
"""
工作流定义和执行
"""
from typing import Dict, Any, List, Optional
import time
import json
import uuid
from .context import TaskContext
from .component import ComponentFactory
from .exceptions import WorkflowError, ComponentError
from utils.logger import get_logger
# 获取日志记录器
logger = get_logger(__name__)
class WorkflowDefinition:
"""工作流定义"""
def __init__(self, workflow_id: Optional[str] = None, name: str = "", workflow_type: str = "normal"):
self.workflow_id = workflow_id or f"wf_{uuid.uuid4().hex}"
self.name = name
self.workflow_type = workflow_type # normal或scheduled
self.variables: Dict[str, Any] = {}
self.blocks: List[Dict[str, Any]] = []
self.schedule: Optional[Dict[str, Any]] = None # 定时任务的调度信息
self.version: str = "1.0"
self.description: str = ""
self.created_at: int = int(time.time() * 1000)
self.updated_at: int = int(time.time() * 1000)
def add_block(self, block: Dict[str, Any]) -> None:
"""添加块"""
self.blocks.append(block)
def set_variables(self, variables: Dict[str, Any]) -> None:
"""设置变量"""
self.variables = variables.copy()
def set_schedule(self, schedule: Dict[str, Any]) -> None:
"""设置调度信息"""
self.schedule = schedule
self.workflow_type = "scheduled"
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
return {
"workflow_id": self.workflow_id,
"name": self.name,
"workflow_type": self.workflow_type,
"variables": self.variables,
"blocks": self.blocks,
"schedule": self.schedule,
"version": self.version,
"description": self.description,
"created_at": self.created_at,
"updated_at": self.updated_at
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'WorkflowDefinition':
"""从字典创建工作流定义"""
workflow = cls(
workflow_id=data.get("workflow_id"),
name=data.get("name", ""),
workflow_type=data.get("workflow_type", "normal")
)
workflow.variables = data.get("variables", {})
workflow.blocks = data.get("blocks", [])
workflow.schedule = data.get("schedule")
workflow.version = data.get("version", "1.0")
workflow.description = data.get("description", "")
workflow.created_at = data.get("created_at", int(time.time() * 1000))
workflow.updated_at = data.get("updated_at", int(time.time() * 1000))
return workflow
def to_json(self) -> str:
"""转换为JSON字符串"""
return json.dumps(self.to_dict())
@classmethod
def from_json(cls, json_str: str) -> 'WorkflowDefinition':
"""从JSON字符串创建工作流定义"""
data = json.loads(json_str)
return cls.from_dict(data)
class WorkflowExecutor:
"""工作流执行器"""
def __init__(self):
self.component_factory = ComponentFactory
def execute(self, workflow: WorkflowDefinition, task_inputs: Dict[str, Any] = None) -> Dict[str, Any]:
"""执行工作流"""
# 获取上下文
context = TaskContext.get_instance()
# 重置上下文
context.reset()
# 生成任务ID
task_id = f"task_{uuid.uuid4().hex}"
# 初始化任务
context.initialize_task(task_id, workflow.workflow_id, task_inputs)
# 设置任务变量
context.variables.update(workflow.variables)
logger.info(f"开始执行工作流: {workflow.name} (ID: {workflow.workflow_id}), 任务ID: {task_id}")
try:
# 执行组件
self._execute_blocks(workflow.blocks)
# 完成任务
context.complete_task("completed")
logger.info(f"工作流执行完成: {workflow.name} (ID: {workflow.workflow_id}), 任务ID: {task_id}")
# 返回执行结果
return context.to_dict()
except Exception as e:
# 设置任务状态为执行失败
context.complete_task("failed")
error_msg = f"工作流执行失败: {str(e)}"
logger.error(f"{error_msg}, 工作流: {workflow.name} (ID: {workflow.workflow_id}), 任务ID: {task_id}")
# 返回错误信息
result = context.to_dict()
result["error"] = error_msg
return result
def _execute_blocks(self, blocks: List[Dict[str, Any]]) -> None:
"""执行一系列组件"""
context = TaskContext.get_instance()
for block in blocks:
block_id = block["id"]
block_type = block["type"]
params = block.get("params", {})
logger.debug(f"执行组件: {block_id} ({block_type})")
# 特殊处理流程控制组件
if block_type == "if_condition":
self._execute_if_condition(block_id, params)
elif block_type == "for_each":
self._execute_for_each(block_id, params)
elif block_type == "while_loop":
self._execute_while_loop(block_id, params)
else:
# 创建并执行普通组件
try:
component = self.component_factory.create(block_id, block_type, params)
result = component.execute()
logger.debug(f"组件 {block_id} ({block_type}) 执行成功")
except Exception as e:
error_msg = f"组件 {block_id} ({block_type}) 执行失败: {str(e)}"
logger.error(error_msg)
context.set_block_result(block_id, {"error": error_msg})
# 检查是否需要中断执行
if block.get("stop_on_error", True):
raise ComponentError(error_msg)
def _execute_if_condition(self, block_id: str, params: Dict[str, Any]) -> None:
"""执行条件组件"""
context = TaskContext.get_instance()
try:
# 计算条件表达式
condition_expr = params.get("condition", "False")
condition_value = eval(condition_expr, {"__builtins__": {}}, {
"context": context,
"blocks": context.blocks,
"variables": context.variables
})
# 存储条件结果
context.set_block_result(block_id, {"condition": condition_value})
# 根据条件执行相应分支
if condition_value and "true_blocks" in params:
self._execute_blocks(params["true_blocks"])
elif not condition_value and "false_blocks" in params:
self._execute_blocks(params["false_blocks"])
except Exception as e:
error_msg = f"条件组件 {block_id} 执行失败: {str(e)}"
logger.error(error_msg)
context.set_block_result(block_id, {"error": error_msg})
raise ComponentError(error_msg)
def _execute_for_each(self, block_id: str, params: Dict[str, Any]) -> None:
"""执行循环组件"""
context = TaskContext.get_instance()
try:
# 获取循环项
items_expr = params.get("items", "[]")
items = eval(items_expr, {"__builtins__": {}}, {
"context": context,
"blocks": context.blocks,
"variables": context.variables
})
results = []
# 遍历执行循环体
for index, item in enumerate(items):
# 设置循环变量
context.set_variable("loop_item", item)
context.set_variable("loop_index", index)
# 执行循环体
if "loop_blocks" in params:
self._execute_blocks(params["loop_blocks"])
# 收集结果
if "result_var" in params:
result_var = params["result_var"]
if result_var in context.variables:
results.append(context.get_variable(result_var))
# 存储循环结果
context.set_block_result(block_id, {
"count": len(items),
"results": results
})
except Exception as e:
error_msg = f"循环组件 {block_id} 执行失败: {str(e)}"
logger.error(error_msg)
context.set_block_result(block_id, {"error": error_msg})
raise ComponentError(error_msg)
def _execute_while_loop(self, block_id: str, params: Dict[str, Any]) -> None:
"""执行while循环组件"""
context = TaskContext.get_instance()
try:
max_iterations = params.get("max_iterations", 100)
iteration = 0
results = []
# 循环执行
while iteration < max_iterations:
# 计算条件表达式
condition_expr = params.get("condition", "False")
condition_value = eval(condition_expr, {"__builtins__": {}}, {
"context": context,
"blocks": context.blocks,
"variables": context.variables
})
if not condition_value:
break
# 设置循环变量
context.set_variable("loop_iteration", iteration)
# 执行循环体
if "loop_blocks" in params:
self._execute_blocks(params["loop_blocks"])
# 收集结果
if "result_var" in params:
result_var = params["result_var"]
if result_var in context.variables:
results.append(context.get_variable(result_var))
iteration += 1
# 存储循环结果
context.set_block_result(block_id, {
"iterations": iteration,
"results": results
})
except Exception as e:
error_msg = f"While循环组件 {block_id} 执行失败: {str(e)}"
logger.error(error_msg)
context.set_block_result(block_id, {"error": error_msg})
raise ComponentError(error_msg)

11
data/__init__.py Normal file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
数据模块
包含数据库模型和数据访问功能
"""
from data.session import initialize_database, get_session, session_scope
__all__ = ['initialize_database', 'get_session', 'session_scope']

Binary file not shown.

Binary file not shown.

View File

View File

View File

42
data/models/__init__.py Normal file
View File

@ -0,0 +1,42 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
数据模型模块
包含所有数据库模型的定义
"""
from data.models.base import BaseModel
from data.models.component import ComponentCategory, ComponentType, Component
from data.models.task import Task
from data.models.task_version import TaskVersion
from data.models.task_record import TaskRecord
from data.models.task_flow_block import TaskFlowBlock, BlockType
from data.models.component_parameter import (
ComponentParameterDefinition,
ComponentParameterValue,
ParameterType,
ParameterValueFormat
)
from data.models.task_variable import TaskVariableDefinition
from data.models.task_backup import TaskBackup
from data.models.task_edit_history import TaskEditHistory
__all__ = [
'BaseModel',
'ComponentCategory',
'ComponentType',
'Component',
'Task',
'TaskVersion',
'TaskRecord',
'TaskFlowBlock',
'BlockType',
'ComponentParameterDefinition',
'ComponentParameterValue',
'ParameterType',
'ParameterValueFormat',
'TaskVariableDefinition',
'TaskBackup',
'TaskEditHistory'
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

127
data/models/base.py Normal file
View File

@ -0,0 +1,127 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
基础模型模块
包含所有模型共用的字段和方法
"""
import datetime
from sqlalchemy import Column, Integer, DateTime, Boolean
from config.database import Base
class BaseModel(Base):
"""
基础模型类
包含所有模型共用的字段和方法
"""
__abstract__ = True # 声明为抽象类,不会创建表
id = Column(Integer, primary_key=True, autoincrement=True, comment='主键ID')
created_at = Column(DateTime, default=datetime.datetime.now, comment='创建时间')
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='更新时间')
is_deleted = Column(Boolean, default=False, comment='是否删除(软删除标记)')
def to_dict(self):
"""
将模型转换为字典
用于API响应
"""
result = {}
for column in self.__table__.columns:
value = getattr(self, column.name)
# 处理日期时间类型
if isinstance(value, datetime.datetime):
value = value.strftime('%Y-%m-%d %H:%M:%S')
result[column.name] = value
return result
def to_json(self, fields=None, exclude=None, timestamp_format='ms'):
"""
将模型转换为JSON友好的字典
Args:
fields (list, optional): 需要包含的字段列表为None则包含所有字段
exclude (list, optional): 需要排除的字段列表
timestamp_format (str, optional): 时间戳格式可选值'ms'毫秒时间戳, 'iso'ISO格式, 'str'字符串格式
Returns:
dict: 包含指定字段的字典
"""
result = {}
# 获取所有列名
columns = [column.name for column in self.__table__.columns]
# 如果指定了fields则只包含这些字段
if fields:
columns = [col for col in columns if col in fields]
# 排除指定的字段
if exclude:
columns = [col for col in columns if col not in exclude]
# 获取字段值
for column in columns:
value = getattr(self, column)
# 处理日期时间类型
if isinstance(value, datetime.datetime):
if timestamp_format == 'ms':
# 转换为毫秒时间戳
value = int(value.timestamp() * 1000)
elif timestamp_format == 'iso':
# 转换为ISO格式
value = value.isoformat()
else:
# 转换为字符串格式
value = value.strftime('%Y-%m-%d %H:%M:%S')
# 处理枚举类型
elif hasattr(value, 'name') and hasattr(value, 'value'):
# 如果是枚举类型,返回其值
value = value.value
result[column] = value
return result
@classmethod
def get_by_id(cls, id):
"""
根据ID获取记录
"""
return cls.query.filter(cls.id == id, cls.is_deleted == False).first()
@classmethod
def get_all(cls, page=1, per_page=20):
"""
获取所有记录分页
"""
return cls.query.filter(cls.is_deleted == False).paginate(page=page, per_page=per_page)
def save(self):
"""
保存记录
"""
from config.database import db_session
db_session.add(self)
db_session.commit()
return self
def delete(self):
"""
删除记录软删除
"""
self.is_deleted = True
self.save()
return self
def hard_delete(self):
"""
硬删除记录
"""
from config.database import db_session
db_session.delete(self)
db_session.commit()
return self

135
data/models/component.py Normal file
View File

@ -0,0 +1,135 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
组件模型模块
包含组件组件类型和组件分类相关的数据模型
"""
import enum
from sqlalchemy import Column, Integer, String, Text, Boolean, Enum, ForeignKey, JSON
from sqlalchemy.orm import relationship
from data.models.base import BaseModel
class ComponentCategoryEnum(enum.Enum):
"""
组件分类枚举
"""
SUBTASK = 'subtask' # 子任务
SCRIPT = 'script' # 脚本
HTTP = 'http' # HTTP请求
TASK = 'task' # 任务
FLOW = 'flow' # 流程
BASIC = 'basic' # 基础
STORAGE = 'storage' # 库位
ROBOT = 'robot' # 机器人调度
DEVICE = 'device' # 设备
class ComponentCategory(BaseModel):
"""
组件分类模型
表示组件的分类
"""
__tablename__ = 'component_categories'
name = Column(String(50), nullable=False, comment='分类名称')
code = Column(Enum(ComponentCategoryEnum), nullable=False, unique=True, comment='分类编码')
description = Column(String(500), nullable=True, comment='分类描述')
icon = Column(String(100), nullable=True, comment='分类图标')
order = Column(Integer, default=0, comment='排序顺序')
# 关联关系
types = relationship('ComponentType', back_populates='category', cascade='all, delete-orphan')
def __repr__(self):
return f"<ComponentCategory(id={self.id}, name='{self.name}', code='{self.code}')>"
@classmethod
def get_by_code(cls, code):
"""
根据编码获取分类
"""
return cls.query.filter(cls.code == code, cls.is_deleted == False).first()
class ComponentType(BaseModel):
"""
组件类型模型
表示组件的类型
"""
__tablename__ = 'component_types'
category_id = Column(Integer, ForeignKey('component_categories.id'), nullable=False, comment='分类ID')
name = Column(String(100), nullable=False, comment='类型名称')
code = Column(String(50), nullable=False, unique=True, comment='类型编码')
description = Column(String(500), nullable=True, comment='类型描述')
icon = Column(String(100), nullable=True, comment='类型图标')
order = Column(Integer, default=0, comment='排序顺序')
is_system = Column(Boolean, default=False, comment='是否为系统组件')
# 关联关系
category = relationship('ComponentCategory', back_populates='types')
components = relationship('Component', back_populates='type', cascade='all, delete-orphan')
parameter_definitions = relationship('ComponentParameterDefinition', back_populates='component_type', cascade='all, delete-orphan')
def __repr__(self):
return f"<ComponentType(id={self.id}, name='{self.name}', code='{self.code}')>"
@classmethod
def get_by_code(cls, code):
"""
根据编码获取类型
"""
return cls.query.filter(cls.code == code, cls.is_deleted == False).first()
@classmethod
def get_by_category(cls, category_id):
"""
根据分类获取类型列表
"""
return cls.query.filter(cls.category_id == category_id, cls.is_deleted == False).order_by(cls.order).all()
class Component(BaseModel):
"""
组件模型
表示一个具体的组件
"""
__tablename__ = 'components'
type_id = Column(Integer, ForeignKey('component_types.id'), nullable=False, comment='类型ID')
name = Column(String(100), nullable=False, comment='组件名称')
code = Column(String(50), nullable=False, unique=True, comment='组件编码')
description = Column(String(500), nullable=True, comment='组件描述')
icon = Column(String(100), nullable=True, comment='组件图标')
version = Column(String(20), nullable=False, default='1.0.0', comment='组件版本')
is_system = Column(Boolean, default=False, comment='是否为系统组件')
is_enabled = Column(Boolean, default=True, comment='是否启用')
config_schema = Column(JSON, nullable=True, comment='配置模式JSON Schema')
default_config = Column(JSON, nullable=True, comment='默认配置')
implementation = Column(Text, nullable=True, comment='组件实现代码')
# 关联关系
type = relationship('ComponentType', back_populates='components')
def __repr__(self):
return f"<Component(id={self.id}, name='{self.name}', code='{self.code}')>"
@classmethod
def get_by_code(cls, code):
"""
根据编码获取组件
"""
return cls.query.filter(cls.code == code, cls.is_deleted == False).first()
@classmethod
def get_by_type(cls, type_id):
"""
根据类型获取组件列表
"""
return cls.query.filter(cls.type_id == type_id, cls.is_enabled == True, cls.is_deleted == False).all()
@classmethod
def get_system_components(cls):
"""
获取所有系统组件
"""
return cls.query.filter(cls.is_system == True, cls.is_enabled == True, cls.is_deleted == False).all()

Some files were not shown because too many files have changed in this diff Show More