This commit is contained in:
靳中伟 2025-03-18 18:34:03 +08:00
parent 9032a75118
commit a02f24a47c
46 changed files with 21849 additions and 807 deletions

1
.idea/vcs.xml generated
View File

@ -2,5 +2,6 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/tianfeng_task_modules" vcs="Git" />
</component>
</project>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -11,86 +11,3 @@ from api.models import ApiResponse
# 创建路由器
router = APIRouter(prefix="/common-params", tags=["常用参数"])
# 创建服务实例
common_params_service = CommonParamsService()
@router.get("/types", response_model=ApiResponse)
async def get_param_types():
"""获取所有常用参数类型"""
try:
param_types = CommonParamsConfig.get_param_types()
return {
"code": ApiResponseCode.SUCCESS,
"message": "获取常用参数类型成功",
"data": {
"param_types": param_types
}
}
except Exception as e:
raise HTTPException(
status_code=ApiResponseCode.SERVER_ERROR,
detail=f"获取常用参数类型失败: {str(e)}"
)
@router.get("/data/{param_type}", response_model=ApiResponse)
async def get_param_data(param_type: str):
"""获取指定类型的常用参数数据"""
try:
# 检查参数类型是否有效
param_type_config = CommonParamsConfig.get_param_type_by_type(param_type)
if not param_type_config:
return {
"code": ApiResponseCode.NOT_FOUND,
"message": f"未找到参数类型: {param_type}",
"data": {"param_data": []}
}
# 获取参数数据
param_data = common_params_service.get_param_data(param_type)
return {
"code": ApiResponseCode.SUCCESS,
"message": f"获取{param_type_config['name']}数据成功",
"data": {
"param_type": param_type_config,
"param_data": param_data
}
}
except Exception as e:
raise HTTPException(
status_code=ApiResponseCode.SERVER_ERROR,
detail=f"获取常用参数数据失败: {str(e)}"
)
@router.get("/all", response_model=ApiResponse)
async def get_all_param_data():
"""获取所有常用参数数据"""
try:
# 获取所有参数类型
param_types = CommonParamsConfig.get_param_types()
# 获取所有参数数据
all_param_data = {}
for param_type_config in param_types:
param_type = param_type_config["type"]
param_data = common_params_service.get_param_data(param_type)
all_param_data[param_type] = {
"param_type": param_type_config,
"param_data": param_data
}
return {
"code": ApiResponseCode.SUCCESS,
"message": "获取所有常用参数数据成功",
"data": {
"param_types": param_types,
"all_param_data": all_param_data
}
}
except Exception as e:
raise HTTPException(
status_code=ApiResponseCode.SERVER_ERROR,
detail=f"获取所有常用参数数据失败: {str(e)}"
)

View File

@ -19,4 +19,15 @@ from api.models.workflow import (
)
# 导出组件相关模型
from api.models.component import ComponentDiscoverInput
from api.models.component import ComponentDiscoverInput
# 导出任务实例相关模型
from api.models.task_instance import (
TaskInstanceCreateInput, TaskInstanceUpdateInput,
TaskInputParamItem, TaskInputParamsInput, TaskInstanceStatus
)
# 导出任务参数相关模型
from api.models.task_param import (
TaskInputParamModel, TaskInputParamsUpdateRequest
)

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,53 @@
"""
任务实例相关API模型
包含任务实例的请求和响应的数据模型
"""
from typing import Dict, Any, List, Optional
from pydantic import BaseModel, Field
from enum import Enum
# 请求模型
class TaskInstanceCreateInput(BaseModel):
"""任务实例创建输入模型"""
task_id: str = Field(..., description="任务ID")
name: Optional[str] = Field(None, description="任务名称")
variables: Optional[Dict[str, Any]] = Field(None, description="任务变量")
priority: int = Field(1, description="任务优先级")
input_params: Optional[Dict[str, Any]] = Field(None, description="任务输入参数")
block_outputs: Optional[Dict[str, Any]] = Field(None, description="块输出参数")
context_params: Optional[Dict[str, Any]] = Field(None, description="上下文参数")
class TaskInstanceUpdateInput(BaseModel):
"""任务实例更新输入模型"""
variables: Optional[Dict[str, Any]] = Field(None, description="任务变量")
priority: Optional[int] = Field(None, description="任务优先级")
input_params: Optional[Dict[str, Any]] = Field(None, description="任务输入参数")
block_outputs: Optional[Dict[str, Any]] = Field(None, description="块输出参数")
context_params: Optional[Dict[str, Any]] = Field(None, description="上下文参数")
# 任务输入参数模型
class TaskInputParamItem(BaseModel):
"""任务输入参数项目"""
key: str = Field(..., description="参数名")
label: str = Field(..., description="参数标签")
type: str = Field(..., description="参数类型")
required: bool = Field(False, description="是否必填")
default_value: Optional[Any] = Field(None, description="默认值")
description: Optional[str] = Field(None, description="参数说明")
options: Optional[Dict[str, Any]] = Field(None, description="选项配置,用于下拉选择、单选、多选类型")
validation_rules: Optional[Dict[str, Any]] = Field(None, description="验证规则")
class TaskInputParamsInput(BaseModel):
"""任务输入参数添加/更新模型"""
params: List[TaskInputParamItem] = Field(..., description="输入参数列表")
# 任务实例状态枚举
class TaskInstanceStatus(str, Enum):
"""任务实例状态枚举"""
DRAFT = "draft" # 草稿
PUBLISHED = "published" # 已发布
RUNNING = "running" # 运行中
COMPLETED = "completed" # 已完成
FAILED = "failed" # 失败
CANCELED = "canceled" # 已取消

25
api/models/task_param.py Normal file
View File

@ -0,0 +1,25 @@
"""
任务参数相关API模型
包含任务参数的请求和响应的数据模型
"""
from typing import Dict, Any, List, Optional
from pydantic import BaseModel, Field
from enum import Enum
from config.task_config import TaskInputParamType
# 任务输入参数模型
class TaskInputParamModel(BaseModel):
"""任务输入参数模型"""
param_id: Optional[str] = Field(None, description="参数ID新增参数时不需要传")
param_name: str = Field(..., description="变量名")
label: str = Field(..., description="标签")
param_type: TaskInputParamType = Field(..., description="类型")
required: bool = Field(False, description="是否必填")
default_value: Optional[Any] = Field(None, description="默认值")
description: Optional[str] = Field(None, description="说明")
class TaskInputParamsUpdateRequest(BaseModel):
"""任务输入参数更新请求"""
task_id: str
params: List[TaskInputParamModel]

View File

@ -26,6 +26,7 @@ from api.models import (
ApiResponse, TaskInput, TaskBatchInput, TaskIdList,
TaskTypeInfo, SortField, SortOrder, TaskUpdateInput, TaskEditInput
)
# from data import get_session
# 创建路由器
router = APIRouter(tags=["任务管理"])
@ -152,29 +153,6 @@ async def create_task(task_input: TaskInput):
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):
"""删除任务"""
@ -199,56 +177,56 @@ async def delete_task(task_id: str):
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)
# @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)}")
# 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)
# @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)}")
# 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)}")
@ -316,72 +294,6 @@ async def update_task(task_id: str, task_update: TaskUpdateInput):
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
)
# 获取编辑中的任务实例
editing_instance = task_instance_service.get_editing_instance_by_task_id(task_edit.task_id)
# 准备更新的参数
update_params = {
"variables": task_edit.variables,
"priority": task_edit.priority if hasattr(task_edit, "priority") else None
}
# 如果有自定义输入参数,添加到更新参数中
if hasattr(task_edit, "input_params") and task_edit.input_params is not None:
update_params["input_params"] = task_edit.input_params
# 如果有块输出参数,添加到更新参数中
if hasattr(task_edit, "block_outputs") and task_edit.block_outputs is not None:
update_params["block_outputs"] = task_edit.block_outputs
# 如果有上下文参数,添加到更新参数中
if hasattr(task_edit, "context_params") and task_edit.context_params is not None:
update_params["context_params"] = task_edit.context_params
if editing_instance:
# 更新编辑中的任务实例
instance = task_instance_service.update_instance(
instance_id=editing_instance["instance_id"],
**update_params
)
else:
# 创建新的任务实例
instance = task_instance_service.create_instance(
task_id=task_edit.task_id,
name=task.get("name"),
**update_params
)
return {
"code": 200,
"message": "编辑任务成功",
"data": {
"task": updated_task,
"instance": instance
}
}
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):
"""获取任务编辑信息"""
@ -398,22 +310,32 @@ async def get_task_edit_info(task_id: str):
# 获取或创建编辑中的任务实例
instance = task_instance_service.get_or_create_editing_instance(task_id)
# 获取任务输入参数
from services.task_param_service import TaskParamService
task_param_service = TaskParamService()
task_input_params, _ = task_param_service.get_task_input_params(task_id, instance["instance_id"])
# 将任务输入参数添加到实例数据中
instance["task_input_params"] = task_input_params
# 获取可用的子任务列表(排除当前任务自身)
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({
subtask_info = {
"task_id": t["task_id"],
"name": t["name"]
})
}
available_subtasks.append(subtask_info)
except Exception as e:
# 如果获取任务列表失败,记录错误但继续执行
print(f"获取可用子任务列表失败: {str(e)}")
# 获取组件详细信息
from config.component_config import ComponentDetailConfig
from config.component_config import ComponentDetailConfig, SubtaskComponentConfig
component_details = ComponentDetailConfig.get_all_components()
# 获取组件类型中文名称映射
@ -437,6 +359,14 @@ async def get_task_edit_info(task_id: str):
# 添加组件到对应类型下
component_types[component_type]["components"].append(component)
# 特殊处理subtask类型将每个可用子任务构建为独立组件
if "subtask" in component_types and available_subtasks:
# 使用配置中的方法生成子任务组件
subtask_components = SubtaskComponentConfig.generate_subtask_components(available_subtasks)
# 替换原有的组件列表
component_types["subtask"]["components"] = subtask_components
# 获取常用参数数据
from services.common_params_service import CommonParamsService
from config.component_config import CommonParamsConfig
@ -454,23 +384,6 @@ async def get_task_edit_info(task_id: str):
"data": param_data
}
# 从配置文件中获取系统默认参数
task_input_params = TaskInputParamConfig.build_system_params_with_values(task_id, instance)
# 如果实例中有自定义的输入参数,添加到列表中
if instance.get("input_params"):
for key, value in instance["input_params"].items():
# 检查是否已存在同名参数
if not any(param["key"] == key for param in task_input_params):
task_input_params.append({
"key": key,
"name": key, # 可以根据需要设置更友好的名称
"value": value,
"type": "string", # 可以根据值的类型动态设置
"is_system": False,
"is_readonly": False
})
# 获取块输出参数和上下文参数
block_output_params = instance.get("block_outputs", {})
context_params = instance.get("context_params", {})
@ -482,9 +395,8 @@ async def get_task_edit_info(task_id: str):
"task": task,
"instance": instance,
"component_types": component_types,
"available_subtasks": available_subtasks,
"common_params": common_params,
"task_input_params": task_input_params,
"task_input_params": instance["task_input_params"], # 使用实例中的任务输入参数
"block_output_params": block_output_params,
"context_params": context_params
}
@ -518,42 +430,42 @@ async def get_available_subtasks(current_task_id: Optional[str] = None):
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取可用子任务列表失败: {str(e)}")
@router.post("/task/{task_id}/publish", response_model=ApiResponse)
async def publish_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
}
# @router.post("/task/{task_id}/publish", response_model=ApiResponse)
# async def publish_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
# }
# 获取编辑中的任务实例
editing_instance = task_instance_service.get_editing_instance_by_task_id(task_id)
if not editing_instance:
return {
"code": 404,
"message": f"任务 {task_id} 没有编辑中的实例",
"data": None
}
# # 获取编辑中的任务实例
# editing_instance = task_instance_service.get_editing_instance_by_task_id(task_id)
# if not editing_instance:
# return {
# "code": 404,
# "message": f"任务 {task_id} 没有编辑中的实例",
# "data": None
# }
# 发布任务实例
published_instance = task_instance_service.publish_instance(editing_instance["instance_id"])
# # 发布任务实例
# published_instance = task_instance_service.publish_instance(editing_instance["instance_id"])
# 构建响应数据
response_data = {
"task": task,
"instance": published_instance
}
# # 构建响应数据
# response_data = {
# "task": task,
# "instance": published_instance
# }
return {
"code": 200,
"message": "发布任务成功",
"data": response_data
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"发布任务失败: {str(e)}")
# return {
# "code": 200,
# "message": "发布任务成功",
# "data": response_data
# }
# except Exception as e:
# raise HTTPException(status_code=500, detail=f"发布任务失败: {str(e)}")

View File

@ -5,8 +5,11 @@
from typing import Dict, Any, List, Optional
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel, Field
from services.task_instance_service import TaskInstanceService, TaskInstanceStatus
from api.models import ApiResponse
from services.task_instance_service import TaskInstanceService
from api.models import (
ApiResponse, TaskInstanceCreateInput, TaskInstanceUpdateInput,
TaskInputParamItem, TaskInputParamsInput, TaskInstanceStatus
)
# 创建路由器
router = APIRouter(prefix="/task-instances", tags=["任务实例"])
@ -14,25 +17,6 @@ router = APIRouter(prefix="/task-instances", tags=["任务实例"])
# 创建服务实例
task_instance_service = TaskInstanceService()
# 请求模型
class TaskInstanceCreateInput(BaseModel):
"""任务实例创建输入模型"""
task_id: str = Field(..., description="任务ID")
name: Optional[str] = Field(None, description="任务名称")
variables: Optional[Dict[str, Any]] = Field(None, description="任务变量")
priority: int = Field(1, description="任务优先级")
input_params: Optional[Dict[str, Any]] = Field(None, description="任务输入参数")
block_outputs: Optional[Dict[str, Any]] = Field(None, description="块输出参数")
context_params: Optional[Dict[str, Any]] = Field(None, description="上下文参数")
class TaskInstanceUpdateInput(BaseModel):
"""任务实例更新输入模型"""
variables: Optional[Dict[str, Any]] = Field(None, description="任务变量")
priority: Optional[int] = Field(None, description="任务优先级")
input_params: Optional[Dict[str, Any]] = Field(None, description="任务输入参数")
block_outputs: Optional[Dict[str, Any]] = Field(None, description="块输出参数")
context_params: Optional[Dict[str, Any]] = Field(None, description="上下文参数")
# API接口
@router.post("/create", response_model=ApiResponse)
async def create_task_instance(instance_input: TaskInstanceCreateInput):
@ -63,156 +47,4 @@ async def create_task_instance(instance_input: TaskInstanceCreateInput):
except Exception as e:
raise HTTPException(status_code=500, detail=f"创建任务实例失败: {str(e)}")
@router.get("/{instance_id}", response_model=ApiResponse)
async def get_task_instance(instance_id: str):
"""获取任务实例详情"""
try:
# 获取任务实例
instance = task_instance_service.get_instance_by_id(instance_id)
if not instance:
return {
"code": 404,
"message": f"任务实例不存在: {instance_id}",
"data": None
}
return {
"code": 200,
"message": "获取任务实例成功",
"data": instance
}
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_instances(task_id: str):
"""获取任务的所有实例"""
try:
# 获取任务实例
instances = task_instance_service.get_instances_by_task_id(task_id)
return {
"code": 200,
"message": "获取任务实例列表成功",
"data": {
"instances": instances,
"total": len(instances)
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取任务实例列表失败: {str(e)}")
@router.get("/task/{task_id}/latest", response_model=ApiResponse)
async def get_latest_task_instance(task_id: str):
"""获取任务的最新实例"""
try:
# 获取最新任务实例
instance = task_instance_service.get_latest_instance_by_task_id(task_id)
if not instance:
return {
"code": 404,
"message": f"任务 {task_id} 没有实例",
"data": None
}
return {
"code": 200,
"message": "获取最新任务实例成功",
"data": instance
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取最新任务实例失败: {str(e)}")
@router.put("/{instance_id}", response_model=ApiResponse)
async def update_task_instance(instance_id: str, instance_input: TaskInstanceUpdateInput):
"""更新任务实例"""
try:
# 更新任务实例
instance = task_instance_service.update_instance(
instance_id=instance_id,
variables=instance_input.variables,
priority=instance_input.priority,
input_params=instance_input.input_params,
block_outputs=instance_input.block_outputs,
context_params=instance_input.context_params
)
if not instance:
return {
"code": 404,
"message": f"任务实例不存在: {instance_id}",
"data": None
}
return {
"code": 200,
"message": "更新任务实例成功",
"data": instance
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"更新任务实例失败: {str(e)}")
@router.delete("/{instance_id}", response_model=ApiResponse)
async def delete_task_instance(instance_id: str):
"""删除任务实例"""
try:
# 删除任务实例
success = task_instance_service.delete_instance(instance_id)
if not success:
return {
"code": 404,
"message": f"任务实例不存在: {instance_id}",
"data": None
}
return {
"code": 200,
"message": "删除任务实例成功",
"data": None
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"删除任务实例失败: {str(e)}")
@router.post("/{instance_id}/publish", response_model=ApiResponse)
async def publish_task_instance(instance_id: str):
"""发布任务实例"""
try:
# 发布任务实例
instance = task_instance_service.publish_instance(instance_id)
if not instance:
return {
"code": 404,
"message": f"任务实例不存在: {instance_id}",
"data": None
}
return {
"code": 200,
"message": "发布任务实例成功",
"data": instance
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"发布任务实例失败: {str(e)}")
@router.get("/task/{task_id}/editing", response_model=ApiResponse)
async def get_or_create_editing_instance(task_id: str):
"""获取或创建编辑中的任务实例"""
try:
# 获取或创建编辑中的任务实例
instance = task_instance_service.get_or_create_editing_instance(task_id)
return {
"code": 200,
"message": "获取编辑中的任务实例成功",
"data": instance
}
except ValueError as e:
return {
"code": 404,
"message": str(e),
"data": None
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取编辑中的任务实例失败: {str(e)}")

170
api/task_param_api.py Normal file
View File

@ -0,0 +1,170 @@
"""
任务参数API
包含任务输入参数相关的API接口
"""
from typing import Dict, Any, List, Optional
from fastapi import APIRouter, HTTPException, Depends, Query, Path, Request
from pydantic import BaseModel, Field
from config.task_config import (
TaskInputParamConfig,
TaskInputParamType,
SystemParamKey
)
from services.task_param_service import TaskParamService
from core.exceptions import TianfengTaskError
from api.models import (
ApiResponse, TaskInputParamModel, TaskInputParamsUpdateRequest
)
import logging
import json
# 创建日志记录器
logger = logging.getLogger(__name__)
# 创建路由器
router = APIRouter(tags=["任务参数管理"])
# 创建服务实例
task_param_service = TaskParamService()
@router.get("/task/input-params/fields", response_model=ApiResponse)
async def get_task_param_types():
"""获取任务输入参数表单字段定义"""
try:
# 从配置中获取表单字段定义
form_fields = TaskInputParamConfig.get_input_params_form_fields()
return {
"code": 200,
"message": "获取任务输入参数表单字段定义成功",
"data": {
"form_fields": form_fields
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取任务输入参数表单字段定义失败: {str(e)}")
@router.get("/task/{task_id}/input-params", response_model=ApiResponse)
async def get_task_input_params(
task_id: str = Path(..., description="任务ID"),
instance_id: str = Query(None, description="任务实例ID不传则使用最新实例")
):
"""
获取任务输入参数配置
返回任务输入参数的配置信息包括系统默认参数和用户自定义参数
"""
try:
# 使用任务参数服务获取参数
params, found_instance_id = task_param_service.get_task_input_params(task_id, instance_id)
return {
"code": 200,
"message": "获取任务输入参数成功",
"data": {
"task_id": task_id,
"instance_id": found_instance_id,
"params": params
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取任务输入参数失败: {str(e)}")
def safe_process_default_value(param_dict: Dict[str, Any]) -> None:
"""
安全处理参数的default_value值确保其是JSON可序列化的
Args:
param_dict: 参数字典将被原地修改
"""
# 如果没有default_value键不处理
if "default_value" not in param_dict:
return
default_value = param_dict["default_value"]
param_type = param_dict.get("param_type", "")
# None值保持不变
if default_value is None:
return
# 只对json和array类型做特殊处理
if param_type not in ["json", "array"]:
return
# 如果已经是字符串尝试解析为JSON对象
if isinstance(default_value, str):
try:
param_dict["default_value"] = json.loads(default_value)
except json.JSONDecodeError:
# 解析失败保持原字符串
pass
@router.post("/task/{task_id}/input-params", response_model=ApiResponse)
async def update_task_input_params(
task_id: str = Path(..., description="任务ID"),
request: List[TaskInputParamModel] = None,
instance_id: str = Query(None, description="任务实例ID不传则使用最新实例或创建新实例")
):
"""
更新任务输入参数配置
批量保存用户自定义的任务输入参数配置只有点击"完成"按钮后才会生效
请求示例:
```json
[
{
"param_name": "robotId",
"label": "机器人ID",
"param_type": "string",
"required": true,
"default_value": "",
"description": "执行任务的机器人ID"
},
{
"param_name": "timeout",
"label": "超时时间",
"param_type": "integer",
"required": false,
"default_value": 3600,
"description": "任务执行的超时时间(秒)"
}
]
```
"""
try:
# 将Pydantic模型转换为字典列表并安全处理default_value
params = []
for param in request:
param_dict = param.dict(exclude_none=True)
safe_process_default_value(param_dict)
params.append(param_dict)
# 使用任务参数服务更新参数
updated_count, found_instance_id, has_changes = task_param_service.update_task_input_params(task_id, params, instance_id)
# 根据是否有变动返回不同的消息
message = "更新任务输入参数成功" if has_changes else "任务输入参数无变动"
return {
"code": 200,
"message": message,
"data": {
"task_id": task_id,
"instance_id": found_instance_id,
"updated_params_count": updated_count,
"has_changes": has_changes
}
}
except ValueError as e:
return {
"code": 400,
"message": str(e),
"data": None
}
except Exception as e:
import traceback
error_detail = f"更新任务输入参数失败: {str(e)}\n{traceback.format_exc()}"
raise HTTPException(status_code=500, detail=error_detail)

View File

@ -16,172 +16,3 @@ 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)}")

520
api_doc/API接口文档.md Normal file
View File

@ -0,0 +1,520 @@
# 天风任务系统 API 接口文档
## 目录
- [组件管理 API](#组件管理-api)
- [获取所有组件类型及详细信息](#获取所有组件类型及详细信息)
- [获取指定类型的组件详细信息](#获取指定类型的组件详细信息)
- [自动发现并注册组件](#自动发现并注册组件)
- [任务管理 API](#任务管理-api)
- [获取任务列表](#获取任务列表)
- [获取任务类型列表](#获取任务类型列表)
- [创建任务](#创建任务)
- [删除任务](#删除任务)
- [批量删除任务](#批量删除任务)
- [更新任务基本信息](#更新任务基本信息)
- [获取任务编辑信息](#获取任务编辑信息)(还没完成)
- [获取可用的子任务列表](#获取可用的子任务列表)
- [任务参数管理 API](#任务参数管理-api)
- [获取任务输入参数表单字段定义](#获取任务输入参数表单字段定义)
- [获取任务输入参数配置](#获取任务输入参数配置)
- [更新任务输入参数配置](#更新任务输入参数配置)
## 组件管理 API
### 获取所有组件类型及详细信息
获取系统中所有组件类型及其详细信息。
**接口地址**`/component/components`
**请求方式**`GET`
**请求参数**:无
**响应数据**
```json
{
"code": 200,
"message": "获取组件类型成功",
"data": {
"component_types": ["string"],
"component_type_info": [
{
"type": "string",
"name": "string"
}
],
"component_categories": {
"category": ["string"]
},
"component_details": ["object"],
"component_details_by_type": {
"type": ["object"]
}
}
}
```
**说明**
- `component_types`: 所有组件类型的列表
- `component_type_info`: 包含中文名称的组件类型信息
- `component_categories`: 按类别分组的组件类型
- `component_details`: 所有组件的详细配置
- `component_details_by_type`: 按组件类型分组的详细配置
### 获取指定类型的组件详细信息
获取指定类型的组件详细信息。
**接口地址**`/component/components/{component_type}`
**请求方式**`GET`
**请求参数**
| 参数名 | 类型 | 必须 | 说明 |
|-------|------|------|------|
| component_type | string | 是 | 组件类型 |
**响应数据**
```json
{
"code": 200,
"message": "获取组件详细信息成功",
"data": {
"components": ["object"]
}
}
```
**说明**
- `components`: 指定类型的组件详细配置列表
### 自动发现并注册组件
自动发现并注册组件。
**接口地址**`/component/components/discover`
**请求方式**`POST`
**请求参数**
```json
{
"package_name": "string"
}
```
**响应数据**
```json
{
"code": 200,
"message": "自动发现组件成功,共发现 x 个组件",
"data": {
"component_types": ["string"]
}
}
```
**说明**
- `component_types`: 发现的组件类型列表
## 任务管理 API
### 获取任务列表
获取任务列表,支持多种筛选条件、排序和分页。
**接口地址**`/tasks`
**请求方式**`GET`
**请求参数**
| 参数名 | 类型 | 必须 | 说明 |
|-------|------|------|------|
| status | string | 否 | 任务状态 |
| task_type | string | 否 | 任务类型 |
| name | string | 否 | 任务名称(模糊查询)|
| is_scheduled | boolean | 否 | 是否为定时任务 |
| created_start | integer | 否 | 创建时间起始(毫秒时间戳)|
| created_end | integer | 否 | 创建时间结束(毫秒时间戳)|
| sort_by | string | 否 | 排序字段,默认为 CREATED_AT |
| sort_order | string | 否 | 排序方式,默认为 DESC |
| page | integer | 否 | 页码,默认为 1 |
| page_size | integer | 否 | 每页数量,默认为 10 |
**响应数据**
```json
{
"code": 200,
"message": "获取任务列表成功",
"data": {
"tasks": ["object"],
"pagination": {
"page": 1,
"page_size": 10,
"total": 100,
"total_pages": 10
}
}
}
```
**说明**
- `tasks`: 任务列表
- `pagination`: 分页信息
### 获取任务类型列表
获取系统中所有任务类型列表。
**接口地址**`/task/types`
**请求方式**`GET`
**请求参数**:无
**响应数据**
```json
{
"code": 200,
"message": "获取任务类型列表成功",
"data": [
{
"key": "string",
"name": "string",
"description": "string",
"value": "string"
}
]
}
```
**说明**
- `key`: 任务类型的键
- `name`: 任务类型的名称
- `description`: 任务类型的描述
- `value`: 任务类型的枚举值
### 创建任务
创建一个新任务。
**接口地址**`/task/create`
**请求方式**`POST`
**请求参数**
```json
{
"name": "string",
"task_type": "string"
}
```
**响应数据**
```json
{
"code": 200,
"message": "创建任务成功",
"data": {
"task_id": "string",
"name": "string",
"task_type": "string",
"task_type_name": "string",
"created_at": "timestamp",
"updated_at": "timestamp"
}
}
```
**说明**
- `task_id`: 任务ID
- `name`: 任务名称
- `task_type`: 任务类型
- `task_type_name`: 任务类型中文名称
### 删除任务
删除指定ID的任务。
**接口地址**`/task/{task_id}`
**请求方式**`DELETE`
**请求参数**
| 参数名 | 类型 | 必须 | 说明 |
|-------|------|------|------|
| task_id | string | 是 | 任务ID |
**响应数据**
```json
{
"code": 200,
"message": "删除任务成功",
"data": null
}
```
### 批量删除任务
批量删除多个任务。
**接口地址**`/task/batch`
**请求方式**`DELETE`
**请求参数**
```json
{
"task_ids": ["string"]
}
```
**响应数据**
```json
{
"code": 200,
"message": "批量删除任务成功,共删除 x 个任务",
"data": {
"deleted_count": 0,
"total_count": 0,
"not_found_ids": ["string"]
}
}
```
**说明**
- `deleted_count`: 成功删除的任务数量
- `total_count`: 总共请求删除的任务数量
- `not_found_ids`: 未找到的任务ID列表
### 更新任务基本信息
更新任务的基本信息。
**接口地址**`/task/{task_id}`
**请求方式**`PUT`
**请求参数**
| 参数名 | 类型 | 必须 | 说明 |
|-------|------|------|------|
| task_id | string | 是 | 任务ID |
```json
{
"name": "string",
"description": "string",
"task_type": "string",
"blocks": ["object"],
"variables": ["object"],
"schedule": "object"
}
```
**响应数据**
```json
{
"code": 200,
"message": "更新任务成功",
"data": {
"task": "object"
}
}
```
### 获取任务编辑信息
获取任务的编辑信息,包括任务实例、组件、参数等。
**接口地址**`/task/{task_id}/edit`
**请求方式**`GET`
**请求参数**
| 参数名 | 类型 | 必须 | 说明 |
|-------|------|------|------|
| task_id | string | 是 | 任务ID |
**响应数据**
```json
{
"code": 200,
"message": "获取任务编辑信息成功",
"data": {
"task": "object",
"instance": "object",
"component_types": "object",
"common_params": "object",
"task_input_params": ["object"],
"block_output_params": "object",
"context_params": "object"
}
}
```
**说明**
- `task`: 任务基本信息
- `instance`: 任务实例信息
- `component_types`: 组件类型信息
- `common_params`: 常用参数数据
- `task_input_params`: 任务输入参数
- `block_output_params`: 块输出参数
- `context_params`: 上下文参数
### 获取可用的子任务列表
获取可用的子任务列表,用于任务嵌套。
**接口地址**`/tasks/available-subtasks`
**请求方式**`GET`
**请求参数**
| 参数名 | 类型 | 必须 | 说明 |
|-------|------|------|------|
| current_task_id | string | 否 | 当前任务ID用于排除自身 |
**响应数据**
```json
{
"code": 200,
"message": "获取可用子任务列表成功",
"data": {
"subtasks": [
{
"task_id": "string",
"name": "string"
}
]
}
}
```
## 任务参数管理 API
### 获取任务输入参数表单字段定义
获取任务输入参数表单字段定义,用于前端表单生成。
**接口地址**`/task/input-params/fields`
**请求方式**`GET`
**请求参数**:无
**响应数据**
```json
{
"code": 200,
"message": "获取任务输入参数表单字段定义成功",
"data": {
"form_fields": ["object"]
}
}
```
**说明**
- `form_fields`: 表单字段定义列表
### 获取任务输入参数配置
获取任务输入参数配置,包括系统默认参数和用户自定义参数。
**接口地址**`/task/{task_id}/input-params`
**请求方式**`GET`
**请求参数**
| 参数名 | 类型 | 必须 | 说明 |
|-------|------|------|------|
| task_id | string | 是 | 任务ID |
| instance_id | string | 否 | 任务实例ID不传则使用最新实例 |
**响应数据**
```json
{
"code": 200,
"message": "获取任务输入参数成功",
"data": {
"task_id": "string",
"instance_id": "string",
"params": ["object"]
}
}
```
**说明**
- `params`: 任务输入参数列表
### 更新任务输入参数配置
更新任务输入参数配置,批量保存用户自定义的任务输入参数配置。
**接口地址**`/task/{task_id}/input-params`
**请求方式**`POST`
**请求参数**
| 参数名 | 类型 | 必须 | 说明 |
|-------|------|------|------|
| task_id | string | 是 | 任务ID |
| instance_id | string | 否 | 任务实例ID不传则使用最新实例或创建新实例 |
请求体:
```json
[
{
"param_name": "string",
"label": "string",
"param_type": "string",
"required": true,
"default_value": "any",
"description": "string"
}
]
```
**响应数据**
```json
{
"code": 200,
"message": "更新任务输入参数成功",
"data": {
"task_id": "string",
"instance_id": "string",
"updated_params_count": 0,
"has_changes": true
}
}
```
**说明**
- `updated_params_count`: 更新的参数数量
- `has_changes`: 是否有参数变动

View File

@ -0,0 +1,119 @@
# 天风任务系统枚举值说明文档
本文档详细说明了系统中使用的各种枚举值,用于开发和接口调用参考。
## 目录
- [参数类型枚举](#参数类型枚举)
- [参数值格式枚举](#参数值格式枚举)
- [任务类型枚举](#任务类型枚举)
- [任务状态枚举](#任务状态枚举)
- [任务实例状态枚举](#任务实例状态枚举)
## 参数类型枚举
组件参数定义表使用的参数类型枚举(ParameterType)
| 枚举值 | 说明 | 描述 |
| --- | --- | --- |
| STRING | 'string' | 字符串类型参数 |
| NUMBER | 'number' | 数字类型参数 |
| BOOLEAN | 'boolean' | 布尔值类型参数 |
| ARRAY | 'array' | 数组类型参数 |
| OBJECT | 'object' | 对象类型参数 |
| DATE | 'date' | 日期类型参数 |
| TIME | 'time' | 时间类型参数 |
| DATETIME | 'datetime' | 日期时间类型参数 |
| FILE | 'file' | 文件类型参数 |
| SELECT | 'select' | 单选选择器类型参数 |
| MULTI_SELECT | 'multi_select' | 多选选择器类型参数 |
| CUSTOM | 'custom' | 自定义类型参数 |
## 参数值格式枚举
组件参数值表使用的值格式枚举(ParameterValueFormat)
| 枚举值 | 说明 | 描述 |
| --- | --- | --- |
| SIMPLE | 'simple' | 简单值格式,直接使用值 |
| JSON | 'json' | JSON格式值需要经过JSON解析 |
| EXPRESSION | 'expression' | 表达式格式,值需要经过表达式引擎解析 |
## 任务类型枚举
任务表使用的任务类型枚举(TaskType)
| 枚举值 | 说明 | 描述 |
| --- | --- | --- |
| NORMAL | 'normal' | 普通任务 |
| SCHEDULED | 'scheduled' | 定时任务,需要按照计划自动执行 |
## 任务状态枚举
任务表使用的任务状态枚举(TaskStatus)
| 枚举值 | 说明 | 描述 |
| --- | --- | --- |
| PENDING | 'pending' | 待执行状态,任务已创建但尚未开始执行 |
| RUNNING | 'running' | 执行中状态,任务正在执行 |
| COMPLETED | 'completed' | 已完成状态,任务已成功执行完成 |
| CANCELLED | 'cancelled' | 已取消状态,任务被手动取消 |
| FAILED | 'failed' | 执行失败状态,任务执行过程中发生错误 |
| PAUSED | 'paused' | 暂停中状态,任务执行被暂停 |
| WAITING | 'waiting' | 等待中状态,任务等待某些条件满足后继续执行 |
## 任务实例状态枚举
任务实例表使用的状态枚举(TaskInstanceStatus)
| 枚举值 | 说明 | 描述 |
| --- | --- | --- |
| EDITING | 'editing' | 编辑中状态,任务实例正在被编辑 |
| PUBLISHED | 'published' | 已发布状态,任务实例已发布,可以被执行 |
## 使用示例
### 参数类型的使用示例
```python
# 在创建组件参数定义时指定参数类型
parameter_def = ComponentParameterDefinition.create_parameter_definition(
component_type_id=1,
code="input_text",
name="输入文本",
parameter_type=ParameterType.STRING,
description="请输入文本内容",
is_required=True
)
```
### 任务类型的使用示例
```python
# 创建一个定时任务
task = Task.create_task(
name="每日数据同步",
task_type=TaskType.SCHEDULED,
description="每天凌晨2点执行数据同步",
is_scheduled=True,
schedule_expression="0 2 * * *"
)
```
### 任务状态的使用示例
```python
# 检查任务是否正在执行
if task.status == TaskStatus.RUNNING:
print("任务正在执行中,请稍后...")
```
### 任务实例状态的使用示例
```python
# 获取所有编辑中的任务实例
editing_instances = TaskInstance.query.filter(
TaskInstance.status == TaskInstanceStatus.EDITING,
TaskInstance.is_deleted == False
).all()
```

View File

@ -0,0 +1,65 @@
# 天风任务系统数据模型关系图
## 核心表关系
```
┌───────────────────┐ ┌───────────────────┐
│ Task │1 N │ TaskInstance │
│ ├──────────┤ │
│ 任务基本信息 │ │ 任务实例(编辑状态) │
└───────────────────┘ └─────────┬─────────┘
│1
│N
┌─────────▼─────────┐
│ TaskInputParam │
│ │
│ 任务输入参数 │
└───────────────────┘
```
## 组件参数关系
```
┌───────────────────┐ ┌───────────────────┐
│ ComponentType │1 N │ ComponentParameter│
│ ├──────────┤ Definition │
│ 组件类型 │ │ │
└───────────────────┘ │ 组件参数定义 │
│ └───────────────────┘
│1
│N
┌────────▼──────────┐
│ ParameterTemplate│
│ │
│ 参数模板 │
└───────────────────┘
```
## 详细表关系
天风任务系统的核心数据模型关系如下:
1. **任务管理**:
- Task(任务) → TaskInstance(任务实例) → TaskInputParam(任务输入参数)
- 一个任务可以有多个任务实例(不同编辑状态)
- 一个任务实例有多个输入参数
2. **组件管理**:
- ComponentType(组件类型) → ComponentParameterDefinition(组件参数定义)
- ComponentType(组件类型) → ParameterTemplate(参数模板)
3. **任务执行**:
- Task(任务) → TaskRecord(任务执行记录) → TaskRecordDetail(任务执行详情)
## 说明
1. **任务(Task)**是系统的核心实体,它定义了一个完整的工作流。
2. **任务实例(TaskInstance)**记录了任务在每次编辑过程中的状态,支持草稿和发布版本管理。
3. **任务输入参数(TaskInputParam)**定义了执行任务时需要的参数。
4. **组件(Component)**是任务的构建块,通过组件参数配置其行为。

View File

@ -0,0 +1,192 @@
# 天风任务系统数据表结构文档
## 目录
- [组件参数相关表](#组件参数相关表)
- [组件参数定义表 (component_parameter_definitions)](#组件参数定义表-component_parameter_definitions)
- [组件参数值表 (component_parameter_values)](#组件参数值表-component_parameter_values)
- [参数模板表 (parameter_templates)](#参数模板表-parameter_templates)
- [任务相关表](#任务相关表)
- [任务表 (tasks)](#任务表-tasks)
- [任务实例表 (task_instances)](#任务实例表-task_instances)
- [任务输入参数表 (task_input_params)](#任务输入参数表-task_input_params)
## 组件参数相关表
### 组件参数定义表 (component_parameter_definitions)
该表用于定义组件类型的输入参数。
| 字段名 | 类型 | 是否必填 | 描述 |
| --- | --- | --- | --- |
| id | Integer | 是 | 主键 |
| component_type_id | Integer | 是 | 组件类型ID外键关联组件类型表 |
| code | String(100) | 是 | 参数代码 |
| name | String(100) | 是 | 参数名称 |
| description | String(500) | 否 | 参数描述 |
| parameter_type | Enum(ParameterType) | 是 | 参数类型,枚举值 |
| is_required | Boolean | 否 | 是否必填默认False |
| default_value | JSON | 否 | 默认值 |
| validation_rules | JSON | 否 | 验证规则 |
| options | JSON | 否 | 选项(用于选择器类型) |
| ui_config | JSON | 否 | UI配置 |
| order | Integer | 否 | 排序默认0 |
| created_at | DateTime | 否 | 创建时间 |
| updated_at | DateTime | 否 | 更新时间 |
| is_deleted | Boolean | 否 | 是否删除默认False |
**参数类型枚举(ParameterType)**:
- STRING = 'string'(字符串)
- NUMBER = 'number'(数字)
- BOOLEAN = 'boolean'(布尔值)
- ARRAY = 'array'(数组)
- OBJECT = 'object'(对象)
- DATE = 'date'(日期)
- TIME = 'time'(时间)
- DATETIME = 'datetime'(日期时间)
- FILE = 'file'(文件)
- SELECT = 'select'(选择器)
- MULTI_SELECT = 'multi_select'(多选选择器)
- CUSTOM = 'custom'(自定义类型)
### 组件参数值表 (component_parameter_values)
该表用于存储组件实例的参数值。
| 字段名 | 类型 | 是否必填 | 描述 |
| --- | --- | --- | --- |
| id | Integer | 是 | 主键 |
| component_instance_id | Integer | 是 | 组件实例ID |
| parameter_code | String(100) | 是 | 参数代码 |
| value | JSON | 否 | 参数值 |
| value_format | Enum(ParameterValueFormat) | 是 | 值格式默认SIMPLE |
| is_expression | Boolean | 否 | 是否为表达式默认False |
| expression | String(500) | 否 | 表达式内容 |
| created_at | DateTime | 否 | 创建时间 |
| updated_at | DateTime | 否 | 更新时间 |
| is_deleted | Boolean | 否 | 是否删除默认False |
**参数值格式枚举(ParameterValueFormat)**:
- SIMPLE = 'simple'(简单值)
- JSON = 'json'JSON格式
- EXPRESSION = 'expression'(表达式)
### 参数模板表 (parameter_templates)
该表用于存储常用的参数配置模板。
| 字段名 | 类型 | 是否必填 | 描述 |
| --- | --- | --- | --- |
| id | Integer | 是 | 主键 |
| name | String(100) | 是 | 模板名称 |
| component_type_id | Integer | 是 | 组件类型ID外键关联组件类型表 |
| description | String(500) | 否 | 模板描述 |
| parameter_values | JSON | 是 | 参数值配置 |
| is_system | Boolean | 否 | 是否为系统模板默认False |
| created_by | String(100) | 否 | 创建用户ID |
| created_at | DateTime | 否 | 创建时间 |
| updated_at | DateTime | 否 | 更新时间 |
| is_deleted | Boolean | 否 | 是否删除默认False |
## 任务相关表
### 任务表 (tasks)
该表表示一个任务的基本信息。
| 字段名 | 类型 | 是否必填 | 描述 |
| --- | --- | --- | --- |
| id | Integer | 是 | 主键,自增 |
| task_id | String(36) | 是 | 任务UUID用于外部引用唯一索引 |
| name | String(100) | 是 | 任务名称 |
| task_type | String(100) | 是 | 任务类型 |
| description | String(500) | 否 | 任务描述 |
| is_template | Boolean | 否 | 是否为模板默认False |
| template_description | String(500) | 否 | 模板描述 |
| current_version_id | Integer | 否 | 当前版本ID |
| is_enabled | Boolean | 否 | 是否启用默认True |
| created_by | String(100) | 否 | 创建用户ID |
| updated_by | String(100) | 否 | 最后更新用户ID |
| is_scheduled | Boolean | 否 | 是否为定时任务默认False |
| schedule_expression | String(100) | 否 | 定时表达式Cron格式 |
| next_run_time | DateTime | 否 | 下次执行时间 |
| created_at | DateTime | 否 | 创建时间 |
| updated_at | DateTime | 否 | 更新时间 |
| is_deleted | Boolean | 否 | 是否删除默认False |
**任务类型枚举(TaskType)**:
- NORMAL普通任务
- SCHEDULED定时任务
**任务状态枚举(TaskStatus)**:
- PENDING待执行
- RUNNING执行中
- COMPLETED已完成
- CANCELLED已取消
- FAILED执行失败
- PAUSED暂停中
- WAITING等待中
### 任务实例表 (task_instances)
该表用于存储任务管理的数据,记录每次编辑任务的实例。
| 字段名 | 类型 | 是否必填 | 描述 |
| --- | --- | --- | --- |
| id | Integer | 是 | 主键,自增 |
| instance_id | String(36) | 是 | 实例唯一ID用于外部引用唯一索引 |
| task_id | String(50) | 是 | 关联的任务ID外键关联tasks表 |
| name | String(100) | 是 | 任务名称(复制自任务表) |
| variables | JSON | 否 | 任务变量 |
| priority | Integer | 否 | 任务优先级默认1 |
| block_outputs | JSON | 否 | 块输出参数 |
| context_params | JSON | 否 | 上下文参数 |
| status | Enum(TaskInstanceStatus) | 否 | 任务实例状态默认EDITING |
| created_at | DateTime | 否 | 创建时间 |
| updated_at | DateTime | 否 | 更新时间 |
| is_deleted | Boolean | 否 | 是否删除默认False |
**任务实例状态枚举(TaskInstanceStatus)**:
- EDITING编辑中
- PUBLISHED已发布
### 任务输入参数表 (task_input_params)
该表用于存储任务输入参数的数据。
| 字段名 | 类型 | 是否必填 | 描述 |
| --- | --- | --- | --- |
| id | Integer | 是 | 主键ID自增 |
| param_id | String(36) | 是 | 参数唯一ID用于外部引用唯一索引 |
| instance_id | String(36) | 是 | 关联的任务实例ID索引 |
| task_id | String(50) | 是 | 任务ID冗余存储便于查询索引 |
| param_name | String(50) | 否 | 参数名称,变量名 |
| label | String(100) | 否 | 参数标签,显示名称 |
| param_type | String(20) | 是 | 参数类型 |
| required | Boolean | 否 | 是否必填默认False |
| default_value | Text | 否 | 默认值JSON序列化的字符串 |
| description | Text | 否 | 参数说明 |
| is_system | Boolean | 否 | 是否系统参数默认False |
| is_readonly | Boolean | 否 | 是否只读参数默认False |
| sort_order | Integer | 否 | 排序顺序默认0 |
| created_at | DateTime | 否 | 创建时间 |
| updated_at | DateTime | 否 | 更新时间 |
| is_deleted | Boolean | 否 | 是否删除默认False |
## 表关系说明
1. **组件参数关系**:
- `ComponentParameterDefinition` 定义了组件类型的参数
- `ComponentParameterValue` 存储了组件实例的具体参数值
- `ParameterTemplate` 提供了组件参数的模板配置
2. **任务关系**:
- `Task` 是任务的主表,包含基本信息
- `TaskInstance` 是任务的实例表,记录每次编辑的状态
- `TaskInputParam` 存储任务的输入参数
3. **关联关系**:
- 任务(Task) 1:N 任务实例(TaskInstance)
- 任务实例(TaskInstance) 1:N 任务输入参数(TaskInputParam)
- 组件类型 1:N 组件参数定义(ComponentParameterDefinition)
- 组件类型 1:N 参数模板(ParameterTemplate)

56
app.py
View File

@ -4,6 +4,7 @@ from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
import logging
import time
import traceback
from utils.logger import setup_logger
from config.component_config import register_all_components
from config.settings import (
@ -12,10 +13,11 @@ from config.settings import (
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 api.common_params_api import router as common_params_router
from api.task_param_api import router as task_params_router
from core.exceptions import TianfengTaskError
from config.api_config import ApiResponseCode, ApiResponseMessage
from config.component_config import ComponentCategoryConfig
from api.task_instance_api import router as task_instance_router
# 导入数据库相关模块
from config.database import DBConfig, CacheConfig, db_session
import data.models # 导入所有模型以确保它们被注册
@ -64,33 +66,53 @@ def init_database():
"""初始化数据库,创建所有表"""
try:
logger.info("开始初始化数据库...")
# 初始化数据库表
DBConfig.init_db()
logger.info("数据库表创建成功")
logger.info("开始创建数据库表...")
try:
DBConfig.init_db()
logger.info("数据库表创建成功")
except Exception as table_err:
logger.error(f"数据库表创建失败: {str(table_err)}")
# 打印详细错误信息和堆栈跟踪
logger.error(traceback.format_exc())
raise
# 初始化基础数据
logger.info("开始初始化基础数据...")
init_base_data(db_session)
logger.info("基础数据初始化成功")
try:
init_base_data(db_session)
logger.info("基础数据初始化成功")
except Exception as data_err:
logger.error(f"基础数据初始化失败: {str(data_err)}")
# 打印详细错误信息和堆栈跟踪
logger.error(traceback.format_exc())
raise
return True
except Exception as e:
logger.error(f"数据库初始化失败: {str(e)}")
raise
logger.error(traceback.format_exc())
# 继续执行程序,但记录错误
return False
def init_base_data(db_session):
"""初始化基础数据"""
try:
# 导入需要的模型
# 导入需要的模型
logger.info("检查基础数据...")
# 检查是否已存在组件分类
existing_categories = db_session.query(ComponentCategory).filter(ComponentCategory.is_deleted == False).all()
if existing_categories:
logger.info("基础数据已存在,跳过初始化")
logger.info(f"已存在 {len(existing_categories)} 个组件分类,跳过初始化")
return
logger.info("开始创建组件分类...")
# 创建组件分类
categories = []
for category_enum in ComponentCategoryEnum:
logger.info(f"创建组件分类: {category_enum.value}")
category = ComponentCategory(
name=ComponentCategoryConfig.get_category_name(category_enum),
code=category_enum,
@ -101,27 +123,37 @@ def init_base_data(db_session):
categories.append(category)
db_session.add(category)
logger.info("提交组件分类到数据库...")
db_session.commit()
logger.info(f"创建了 {len(categories)} 个组件分类")
# 这里可以添加更多基础数据的初始化,如组件类型、系统组件等
except Exception as e:
db_session.rollback()
logger.error(f"基础数据初始化失败: {str(e)}")
logger.error(traceback.format_exc())
db_session.rollback()
raise
# 初始化数据库
init_database()
init_result = init_database()
if not init_result:
logger.warning("数据库初始化失败,但程序将继续执行。请检查日志获取详细错误信息。")
# 注册所有组件
register_all_components()
try:
register_all_components()
except Exception as e:
logger.error(f"组件注册失败: {str(e)}")
logger.error(traceback.format_exc())
# 注册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.include_router(common_params_router, prefix=ApiConfig.PREFIX)
app.include_router(task_instance_router, prefix=ApiConfig.PREFIX)
app.include_router(task_params_router, prefix=ApiConfig.PREFIX)
# 请求中间件
@app.middleware("http")

View File

@ -329,37 +329,36 @@ class ScriptComponentConfig:
"name": "function_name",
"label": "函数名",
"type": "string",
"required": False,
"required": True,
"description": "定义脚本中的主函数名称",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": True
"default": ""
},
{
"type": "expression",
"label": "表达式",
"default": False
"default": ""
}
]
},
{
"name": "params",
"label": "函数参数",
"type": "array",
"required": False,
"description": "传递给脚本的参数",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": True
"default": ""
},
{
"type": "expression",
"label": "表达式",
"default": False
"default": ""
}
]
}
@ -376,37 +375,37 @@ class ScriptComponentConfig:
"name": "function_name",
"label": "函数名",
"type": "string",
"required": False,
"required": True,
"description": "定义脚本中的主函数名称",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": True
"default": ""
},
{
"type": "expression",
"label": "表达式",
"default": False
"default": ""
}
]
},
{
"name": "params",
"label": "函数参数",
"type": "array",
"type": "string",
"required": False,
"description": "传递给脚本的参数",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": True
"default": ""
},
{
"type": "expression",
"label": "表达式",
"default": False
"default": ""
}
]
}
@ -418,7 +417,8 @@ class HttpComponentConfig:
"""HTTP请求组件配置"""
# HTTP请求组件类型
HTTP_REQUEST = "http_request" # HTTP请求
HTTP_GET = "http_get" # HTTP GET请求
HTTP_POST = "http_post" # HTTP POST请求
# HTTP请求组件详细配置
@classmethod
@ -427,46 +427,254 @@ class HttpComponentConfig:
return [
{
"type": "http",
"sub_type": cls.HTTP_REQUEST,
"name": "HTTP请求",
"description": "发送HTTP请求并处理响应",
"sub_type": cls.HTTP_GET,
"name": "GET请求",
"description": "发送HTTP GET请求并处理响应",
"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"
"description": "请求的目标URL",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": ""
},
{
"type": "expression",
"label": "表达式",
"default": ""
}
]
},
{
"name": "headers",
"name": "header",
"label": "请求头",
"type": "object",
"required": False,
"description": "HTTP请求头"
"description": "HTTP请求头",
"value_types": [
{
"type": "key_value_array",
"label": "键值对数组",
"default": [],
"key_types": [
{
"type": "string",
"label": "字符串"
},
{
"type": "expression",
"label": "表达式"
}
],
"value_types": [
{
"type": "string",
"label": "字符串"
},
{
"type": "number",
"label": "数字"
},
{
"type": "boolean",
"label": "布尔"
},
{
"type": "object",
"label": "对象"
},
{
"type": "expression",
"label": "表达式"
}
]
},
{
"type": "expression",
"label": "表达式",
"default": ""
}
]
},
{
"name": "body",
"label": "请求体",
"name": "retry",
"label": "是否重试",
"type": "boolean",
"required": False,
"description": "请求失败时是否重试",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": False
},
{
"type": "expression",
"label": "表达式",
"default": "false"
}
]
},
{
"name": "retry_interval",
"label": "重试时间间隔",
"type": "integer",
"required": False,
"description": "重试间隔时间(毫秒)",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": 0
},
{
"type": "expression",
"label": "表达式",
"default": ""
}
]
}
]
},
{
"type": "http",
"sub_type": cls.HTTP_POST,
"name": "POST请求",
"description": "发送HTTP POST请求并处理响应",
"icon": "http",
"params": [
{
"name": "url",
"label": "请求URL",
"type": "string",
"required": True,
"description": "请求的目标URL",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": ""
},
{
"type": "expression",
"label": "表达式",
"default": ""
}
]
},
{
"name": "header",
"label": "请求头",
"type": "object",
"required": False,
"description": "HTTP请求体"
"description": "HTTP请求头",
"value_types": [
{
"type": "key_value_array",
"label": "键值对数组",
"default": [],
"key_types": [
{
"type": "string",
"label": "字符串"
},
{
"type": "expression",
"label": "表达式"
}
],
"value_types": [
{
"type": "string",
"label": "字符串"
},
{
"type": "number",
"label": "数字"
},
{
"type": "boolean",
"label": "布尔"
},
{
"type": "object",
"label": "对象"
},
{
"type": "expression",
"label": "表达式"
}
]
},
{
"type": "expression",
"label": "表达式",
"default": ""
}
]
},
{
"name": "timeout",
"label": "超时时间",
"type": "number",
"name": "json",
"label": "请求参数",
"type": "json",
"required": False,
"description": "请求超时时间(毫秒)"
"description": "POST请求的JSON参数",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": {}
},
{
"type": "expression",
"label": "表达式",
"default": ""
}
]
},
{
"name": "retry",
"label": "是否重试",
"type": "boolean",
"required": False,
"description": "请求失败时是否重试",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": False
},
{
"type": "expression",
"label": "表达式",
"default": "false"
}
]
},
{
"name": "retry_interval",
"label": "重试时间间隔",
"type": "integer",
"required": False,
"description": "重试间隔时间(毫秒)",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": 0
},
{
"type": "expression",
"label": "表达式",
"default": ""
}
]
}
]
}
@ -568,7 +776,6 @@ class FlowComponentConfig:
class SubtaskComponentConfig:
"""子任务组件配置"""
# 子任务组件类型
SUBTASK = "subtask" # 子任务
@ -576,42 +783,130 @@ class SubtaskComponentConfig:
@classmethod
def get_components(cls) -> List[Dict[str, Any]]:
"""获取子任务组件列表"""
return [
{
# 直接返回基础模板
return [cls.get_subtask_component_template()]
@classmethod
def get_subtask_component_template(cls) -> Dict[str, Any]:
"""获取子任务组件模板"""
return {
"type": "subtask",
"sub_type": cls.SUBTASK,
"name": "子任务",
"description": "执行已定义的任务作为子任务",
"icon": "subtask",
"params": [
{
"name": "wait_complete",
"label": "是否异步执行任务",
"type": "boolean",
"required": False,
"default": False,
"description": "是否等待子任务完成后再继续执行"
},
{
"name": "instance_id",
"label": "任务实例ID",
"type": "string",
"required": False,
"description": "指定任务实例ID留空则自动创建新实例"
},
{
"name": "params",
"label": "任务参数",
"type": "subtask_params",
"required": False,
"description": "子任务输入参数配置",
"subtask_params": [
{
"key": "task_id",
"name": "任务ID",
"type": "string",
"is_system": True,
"is_readonly": True,
"description": "任务的唯一标识符"
},
{
"key": "instance_id",
"name": "任务实例ID",
"type": "string",
"is_system": True,
"is_readonly": True,
"description": "任务实例的唯一标识符"
},
{
"key": "task_name",
"name": "任务名称",
"type": "string",
"is_system": True,
"is_readonly": True,
"description": "任务的名称"
},
{
"key": "created_at",
"name": "创建时间",
"type": "datetime",
"is_system": True,
"is_readonly": True,
"description": "任务实例的创建时间"
},
{
"key": "variables",
"name": "任务变量",
"type": "json",
"is_system": True,
"is_readonly": False,
"description": "任务的变量集合"
},
{
"key": "priority",
"name": "任务优先级",
"type": "integer",
"is_system": True,
"is_readonly": False,
"description": "任务的优先级,数值越大优先级越高"
}
]
}
]
}
@classmethod
def generate_subtask_components(cls, available_subtasks: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
根据可用子任务列表生成子任务组件
Args:
available_subtasks: 可用子任务列表 [{"task_id": "xxx", "name": "xxx"}, ...]
Returns:
生成的子任务组件列表
"""
components = []
subtask_template = cls.get_subtask_component_template()
for subtask in available_subtasks:
# 创建新的子任务组件
new_subtask_component = {
"type": "subtask",
"sub_type": cls.SUBTASK,
"name": "子任务",
"description": "执行已定义的任务作为子任务",
"sub_type": "subtask",
"name": subtask["name"], # 使用子任务名称
"description": f"执行子任务: {subtask['name']}",
"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": "是否等待子任务完成后再继续执行"
}
]
"task_id": subtask["task_id"], # 添加任务ID
"params": subtask_template["params"].copy() # 复制参数定义
}
]
# 为params中的subtask_params字段添加task_id
for param in new_subtask_component["params"]:
if param["name"] == "params" and "subtask_params" in param:
for subtask_param in param["subtask_params"]:
if subtask_param["key"] == "task_id":
subtask_param["value"] = subtask["task_id"]
components.append(new_subtask_component)
return components
class ComponentDetailConfig:
"""组件详细配置管理"""
@ -759,6 +1054,9 @@ class ComponentDetailConfig:
# 添加HTTP请求组件
all_components.extend(HttpComponentConfig.get_components())
# 添加任务组件
all_components.extend(TaskComponentConfig.get_components())
# 添加流程控制组件
all_components.extend(FlowComponentConfig.get_components())
@ -903,4 +1201,271 @@ class CommonParamsConfig:
for pt in param_types:
if pt["type"] == param_type:
return pt
return None
return None
class TaskComponentConfig:
"""任务组件配置"""
# 任务组件类型
CACHE_DATA = "cache_data" # 缓存数据
CLEAR_CACHE_DATA = "clear_cache_data" # 清除缓存数据
GET_CACHE_DATA = "get_cache_data" # 获取缓存数据
GET_TASK_INPUT_PARAM = "get_task_input_param" # 获取任务的输入参数
SET_TASK_STATUS = "set_task_status" # 设置任务状态
JUMP_TO_BLOCK = "jump_to_block" # 跳到某个块
SET_TASK_VARIABLES = "set_task_variables" # 设置任务变量
# 任务组件详细配置
@classmethod
def get_components(cls) -> List[Dict[str, Any]]:
"""获取任务组件列表"""
return [
{
"type": "task",
"sub_type": cls.CACHE_DATA,
"name": "缓存数据",
"description": "将数据临时存储在任务上下文中",
"icon": "cache",
"params": [
{
"name": "cache_key",
"label": "缓存key",
"type": "string",
"required": True,
"description": "缓存数据的唯一标识符",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": ""
},
{
"type": "expression",
"label": "表达式",
"default": ""
}
]
},
{
"name": "cache_value",
"label": "缓存value",
"type": "string",
"required": True,
"description": "要缓存的数据值",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": ""
},
{
"type": "expression",
"label": "表达式",
"default": ""
}
]
}
]
},
{
"type": "task",
"sub_type": cls.CLEAR_CACHE_DATA,
"name": "清除缓存数据",
"description": "清除已缓存的临时数据",
"icon": "clear-cache",
"params": [
{
"name": "key",
"label": "key",
"type": "string",
"required": True,
"description": "要清除的缓存数据的标识符",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": ""
},
{
"type": "expression",
"label": "表达式",
"default": ""
}
]
}
]
},
{
"type": "task",
"sub_type": cls.GET_CACHE_DATA,
"name": "获取缓存数据",
"description": "读取已缓存的临时数据",
"icon": "get-cache",
"params": [
{
"name": "cache_key",
"label": "缓存key",
"type": "string",
"required": True,
"description": "要获取的缓存数据的标识符",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": ""
},
{
"type": "expression",
"label": "表达式",
"default": ""
}
]
}
]
},
{
"type": "task",
"sub_type": cls.GET_TASK_INPUT_PARAM,
"name": "获取任务的输入参数",
"description": "读取传递给任务的输入参数",
"icon": "input-param",
"params": [
{
"name": "task_instance_id",
"label": "任务实例 Id",
"type": "string",
"required": True,
"description": "要获取输入参数的任务实例ID",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": ""
},
{
"type": "expression",
"label": "表达式",
"default": ""
}
]
},
{
"name": "input_param_name",
"label": "输入参数名",
"type": "string",
"required": True,
"description": "要获取的输入参数的名称",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": ""
},
{
"type": "expression",
"label": "表达式",
"default": ""
}
]
}
]
},
{
"type": "task",
"sub_type": cls.SET_TASK_STATUS,
"name": "设置任务状态",
"description": "修改任务的当前状态",
"icon": "task-status",
"params": [
{
"name": "status_description",
"label": "状态描述",
"type": "string",
"required": True,
"description": "要设置的任务状态描述",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": ""
},
{
"type": "expression",
"label": "表达式",
"default": ""
}
]
}
]
},
{
"type": "task",
"sub_type": cls.JUMP_TO_BLOCK,
"name": "跳到某个块",
"description": "在任务流程中跳转到指定的节点",
"icon": "jump",
"params": [
{
"name": "block_id",
"label": "跳到块的标识",
"type": "string",
"required": True,
"description": "要跳转到的目标块的标识符",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": ""
},
{
"type": "expression",
"label": "表达式",
"default": ""
}
]
}
]
},
{
"type": "task",
"sub_type": cls.SET_TASK_VARIABLES,
"name": "设置任务变量",
"description": "定义或修改任务的变量",
"icon": "variable",
"params": [
{
"name": "variable_name",
"label": "变量名",
"type": "string",
"required": True,
"description": "要设置的变量名称",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": ""
}
]
},
{
"name": "variable_value",
"label": "变量值",
"type": "json",
"required": True,
"description": "要设置的变量值",
"value_types": [
{
"type": "simple",
"label": "简单值",
"default": {}
},
{
"type": "expression",
"label": "表达式",
"default": ""
}
]
}
]
}
]

View File

@ -11,6 +11,8 @@ import json
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session
import traceback
import sys
class ConfigDict:
"""配置字典类,支持通过点号访问配置项"""
@ -136,6 +138,18 @@ class DBConfig:
初始化数据库
创建所有表
"""
# 测试数据库连接
try:
print(f"尝试连接数据库: {cls.url}")
connection = cls.engine.connect()
print("数据库连接成功!")
connection.close()
except Exception as e:
print(f"数据库连接失败: {str(e)}")
print("详细错误信息:")
traceback.print_exc(file=sys.stdout)
raise
# 导入所有模型确保它们已注册到Base
import data.models
@ -144,20 +158,37 @@ class DBConfig:
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()
try:
print(f"尝试创建数据库 {db_conf.database} (如果不存在)")
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}"
)
print(f"临时连接URL: {temp_url}")
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()
print(f"数据库 {db_conf.database} 创建或已存在")
except Exception as e:
print(f"创建数据库失败: {str(e)}")
print("详细错误信息:")
traceback.print_exc(file=sys.stdout)
raise
# 创建所有表
cls.base.metadata.create_all(bind=cls.engine)
try:
print("开始创建所有表...")
cls.base.metadata.create_all(bind=cls.engine)
print("所有表创建成功")
except Exception as e:
print(f"创建表失败: {str(e)}")
print("详细错误信息:")
traceback.print_exc(file=sys.stdout)
raise
@classmethod
def shutdown_session(cls, exception=None):

View File

@ -192,6 +192,18 @@ class TaskInputParamType(str, Enum):
ARRAY = "array"
OBJECT = "object"
# 任务输入参数类型UI显示名称
PARAM_TYPE_DISPLAY_NAMES = {
TaskInputParamType.STRING.value: "字符串",
TaskInputParamType.INTEGER.value: "整数",
TaskInputParamType.FLOAT.value: "浮点数",
TaskInputParamType.BOOLEAN.value: "布尔",
TaskInputParamType.DATETIME.value: "日期时间",
TaskInputParamType.JSON.value: "JSON对象",
TaskInputParamType.ARRAY.value: "JSON数组",
TaskInputParamType.OBJECT.value: "对象"
}
class SystemParamKey(str, Enum):
"""系统参数键名枚举"""
TASK_ID = "task_id"
@ -269,6 +281,31 @@ class TaskInputParamConfig:
return param
return None
@classmethod
def build_system_params(cls, task_id: str) -> List[Dict[str, Any]]:
"""
构建系统参数列表不带值
Args:
task_id: 任务ID
Returns:
系统参数列表不带值
"""
params = []
# 复制系统参数配置但不添加具体值
for param_config in cls.SYSTEM_PARAMS:
param = param_config.copy()
# 只为任务ID设置值其他参数保持不变
if param["key"] == SystemParamKey.TASK_ID:
param["value"] = task_id
params.append(param)
return params
@classmethod
def build_system_params_with_values(cls, task_id: str, instance: Dict[str, Any]) -> List[Dict[str, Any]]:
"""
@ -304,6 +341,71 @@ class TaskInputParamConfig:
params.append(param)
return params
@classmethod
def get_input_params_form_fields(cls) -> List[Dict[str, Any]]:
"""
获取任务输入参数表单字段定义
Returns:
表单字段定义列表
"""
# 构建参数类型选项
param_type_options = []
for param_type in TaskInputParamType:
param_type_options.append({
"value": param_type.value,
"label": PARAM_TYPE_DISPLAY_NAMES.get(param_type.value, param_type.value)
})
# 返回表单字段定义
return [
{
"field_name": "param_name",
"label": "变量名",
"field_type": "input",
"required": True,
"description": "参数的唯一标识符,用于在任务执行过程中引用该参数"
},
{
"field_name": "label",
"label": "标签",
"field_type": "input",
"required": True,
"description": "参数的显示名称,用于在界面上展示"
},
{
"field_name": "param_type",
"label": "类型",
"field_type": "select",
"required": True,
"default": TaskInputParamType.STRING.value,
"options": param_type_options,
"description": "参数的数据类型"
},
{
"field_name": "required",
"label": "是否必填",
"field_type": "checkbox",
"required": False,
"default": False,
"description": "标记参数是否为必填项"
},
{
"field_name": "default_value",
"label": "默认值",
"field_type": "input",
"required": False,
"description": "参数的默认值,当用户未提供值时使用"
},
{
"field_name": "description",
"label": "说明",
"field_type": "textarea",
"required": False,
"description": "参数的详细说明,用于帮助用户理解参数的用途"
}
]
#################################################
# 默认值配置

View File

@ -22,6 +22,7 @@ from data.models.task_variable import TaskVariableDefinition
from data.models.task_backup import TaskBackup
from data.models.task_edit_history import TaskEditHistory
from data.models.task_instance import TaskInstance, TaskInstanceStatus
from data.models.task_input_param import TaskInputParam
__all__ = [
'BaseModel',
@ -41,5 +42,6 @@ __all__ = [
'TaskBackup',
'TaskEditHistory',
'TaskInstance',
'TaskInstanceStatus'
'TaskInstanceStatus',
'TaskInputParam'
]

View File

@ -1,84 +0,0 @@
#!/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 InputParamType(enum.Enum):
"""
输入参数类型枚举
"""
STRING = 'string' # 字符串
NUMBER = 'number' # 数字
BOOLEAN = 'boolean' # 布尔值
OBJECT = 'object' # 对象
ARRAY = 'array' # 数组
NULL = 'null' # 空值
class TaskInput(BaseModel):
"""
任务输入参数模型
表示任务执行所需的输入参数
"""
__tablename__ = 'task_inputs'
task_id = Column(Integer, ForeignKey('tasks.id'), nullable=False, comment='任务ID')
name = Column(String(100), nullable=False, comment='参数名称')
param_type = Column(Enum(InputParamType), nullable=False, default=InputParamType.STRING, comment='参数类型')
description = Column(String(500), nullable=True, comment='参数描述')
is_required = Column(Boolean, default=False, comment='是否必填')
default_value = Column(Text, nullable=True, comment='默认值')
validation_rules = Column(JSON, nullable=True, comment='验证规则JSON格式')
order = Column(Integer, default=0, comment='排序顺序')
def __repr__(self):
return f"<TaskInput(id={self.id}, task_id={self.task_id}, name='{self.name}', type='{self.param_type}')>"
@classmethod
def get_by_task(cls, task_id):
"""
获取任务的所有输入参数
"""
return cls.query.filter(cls.task_id == task_id, cls.is_deleted == False).order_by(cls.order).all()
@classmethod
def get_by_task_and_name(cls, task_id, name):
"""
根据任务ID和参数名称获取输入参数
"""
return cls.query.filter(cls.task_id == task_id, cls.name == name, cls.is_deleted == False).first()
class TaskInputValue(BaseModel):
"""
任务输入参数值模型
表示任务执行时的实际输入参数值
"""
__tablename__ = 'task_input_values'
task_record_id = Column(Integer, ForeignKey('task_records.id'), nullable=False, comment='任务记录ID')
input_id = Column(Integer, ForeignKey('task_inputs.id'), nullable=False, comment='输入参数ID')
value = Column(Text, nullable=True, comment='参数值')
def __repr__(self):
return f"<TaskInputValue(id={self.id}, task_record_id={self.task_record_id}, input_id={self.input_id})>"
@classmethod
def get_by_record(cls, task_record_id):
"""
获取任务记录的所有输入参数值
"""
return cls.query.filter(cls.task_record_id == task_record_id, cls.is_deleted == False).all()
@classmethod
def get_by_record_and_input(cls, task_record_id, input_id):
"""
根据任务记录ID和输入参数ID获取输入参数值
"""
return cls.query.filter(cls.task_record_id == task_record_id, cls.input_id == input_id, cls.is_deleted == False).first()

View File

@ -0,0 +1,112 @@
"""
任务输入参数模型
用于存储任务输入参数的数据
"""
from sqlalchemy import Column, Integer, String, Text, DateTime, Boolean, ForeignKey, JSON, Enum, Index
from sqlalchemy.orm import relationship, foreign
from sqlalchemy.sql.expression import and_
from datetime import datetime
import uuid
import json
from data.models.base import BaseModel
from config.task_config import TaskInputParamType
class TaskInputParam(BaseModel):
"""任务输入参数模型"""
__tablename__ = "task_input_params"
id = Column(Integer, primary_key=True, autoincrement=True, comment="主键ID")
param_id = Column(String(36), nullable=False, unique=True, index=True, comment="参数唯一ID用于外部引用")
instance_id = Column(String(36), index=True, nullable=False, comment="关联的任务实例ID")
task_id = Column(String(50), nullable=False, index=True, comment="任务ID冗余存储便于查询")
param_name = Column(String(50), nullable=True, comment="参数名称,变量名")
label = Column(String(100), nullable=True, comment="参数标签,显示名称")
param_type = Column(String(20), nullable=False, comment="参数类型")
required = Column(Boolean, default=False, comment="是否必填")
_default_value = Column('default_value', Text, nullable=True, comment="默认值")
description = Column(Text, nullable=True, comment="参数说明")
is_system = Column(Boolean, default=False, comment="是否系统参数")
is_readonly = Column(Boolean, default=False, comment="是否只读参数")
sort_order = Column(Integer, default=0, comment="排序顺序")
# 手动创建索引而不是使用外键约束
__table_args__ = (
Index('idx_task_input_params_instance_id', 'instance_id'),
{'mysql_engine': 'InnoDB'}
)
# 关联关系 - 使用foreign()显式标记外键
instance = relationship(
"TaskInstance",
primaryjoin="and_(TaskInputParam.instance_id==foreign(TaskInstance.instance_id), "
"TaskInstance.is_deleted==False)",
foreign_keys=[instance_id],
back_populates="input_params",
viewonly=True
)
def __init__(self, **kwargs):
"""初始化实例自动生成param_id"""
if 'param_id' not in kwargs:
kwargs['param_id'] = str(uuid.uuid4())
# 处理default_value参数
if 'default_value' in kwargs:
self.default_value = kwargs.pop('default_value')
super(TaskInputParam, self).__init__(**kwargs)
@property
def default_value(self):
"""获取默认值"""
if self._default_value is None:
return None
try:
return json.loads(self._default_value)
except (json.JSONDecodeError, TypeError):
return self._default_value
@default_value.setter
def default_value(self, value):
"""设置默认值"""
if value is None:
self._default_value = None
return
if isinstance(value, (str, int, float, bool)):
# 基本类型直接转为JSON字符串
self._default_value = json.dumps(value)
elif isinstance(value, (list, dict)):
# 复杂类型先验证是否可序列化然后转为JSON字符串
try:
self._default_value = json.dumps(value)
except:
self._default_value = None
else:
# 其他类型尝试序列化失败则设为None
try:
self._default_value = json.dumps(value)
except:
self._default_value = None
def to_dict(self):
"""转换为字典"""
return {
"id": self.param_id, # 使用param_id作为对外ID
"param_id": self.param_id,
"instance_id": self.instance_id,
"task_id": self.task_id,
"param_name": self.param_name,
"label": self.label,
"param_type": self.param_type,
"required": self.required,
"default_value": self.default_value,
"description": self.description,
"is_system": self.is_system,
"is_readonly": self.is_readonly,
"sort_order": self.sort_order,
"created_at": int(self.created_at.timestamp() * 1000) if self.created_at else None,
"updated_at": int(self.updated_at.timestamp() * 1000) if self.updated_at else None,
"is_deleted": self.is_deleted
}

View File

@ -2,8 +2,9 @@
任务实例模型
用于存储任务管理表的数据记录每次编辑任务的实例
"""
from sqlalchemy import Column, Integer, String, Text, DateTime, Boolean, ForeignKey, JSON, Enum
from sqlalchemy.orm import relationship
from sqlalchemy import Column, Integer, String, Text, DateTime, Boolean, ForeignKey, JSON, Enum, Index
from sqlalchemy.orm import relationship, foreign
from sqlalchemy.sql.expression import and_
from datetime import datetime
import enum
import uuid
@ -17,23 +18,31 @@ class TaskInstanceStatus(enum.Enum):
class TaskInstance(BaseModel):
"""任务实例模型"""
__tablename__ = "task_instances"
id = Column(Integer, primary_key=True, autoincrement=True, comment="主键ID")
instance_id = Column(String(36), nullable=False, unique=True, index=True, comment="实例唯一ID用于外部引用")
task_id = Column(String(50), ForeignKey("tasks.task_id"), nullable=False, comment="关联的任务ID")
name = Column(String(100), nullable=False, comment="任务名称(复制自任务表)")
variables = Column(JSON, nullable=True, comment="任务变量")
priority = Column(Integer, default=1, comment="任务优先级")
input_params = Column(JSON, nullable=True, comment="任务输入参数")
block_outputs = Column(JSON, nullable=True, comment="块输出参数")
context_params = Column(JSON, nullable=True, comment="上下文参数")
status = Column(Enum(TaskInstanceStatus), default=TaskInstanceStatus.EDITING, comment="任务实例状态")
created_at = Column(DateTime, default=datetime.now, comment="创建时间")
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment="更新时间")
is_deleted = Column(Boolean, default=False, comment="是否删除")
status = Column(Enum(TaskInstanceStatus), default=TaskInstanceStatus.EDITING, comment="任务实例状态")
# 关联关系
# 确保创建索引
__table_args__ = (
Index('idx_task_instances_instance_id', 'instance_id'),
{'mysql_engine': 'InnoDB'}
)
# 关联关系 - 使用foreign()显式标记外键
task = relationship("Task", back_populates="instances")
input_params = relationship(
"TaskInputParam",
back_populates="instance",
primaryjoin="and_(foreign(TaskInstance.instance_id)==TaskInputParam.instance_id, "
"TaskInputParam.is_deleted==False)",
viewonly=False,
cascade="all" # 移除delete-orphan级联选项
)
def __init__(self, **kwargs):
"""初始化实例自动生成instance_id"""
@ -50,11 +59,23 @@ class TaskInstance(BaseModel):
"name": self.name,
"variables": self.variables or {},
"priority": self.priority,
"input_params": self.input_params or {},
"block_outputs": self.block_outputs or {},
"context_params": self.context_params or {},
"status": self.status.value if self.status else None,
"created_at": int(self.created_at.timestamp() * 1000) if self.created_at else None,
"updated_at": int(self.updated_at.timestamp() * 1000) if self.updated_at else None,
"is_deleted": self.is_deleted
}
def to_api_dict(self):
"""转换为API响应字典只返回必要的字段"""
return {
"instance_id": self.instance_id,
"task_id": self.task_id,
"name": self.name,
"variables": self.variables or {},
"priority": self.priority,
"block_outputs": self.block_outputs or {},
"context_params": self.context_params or {},
"status": self.status.value if self.status else None
}

File diff suppressed because it is too large Load Diff

View File

@ -184,3 +184,16 @@ CREATE TABLE task_flow_connections (
2025-03-17 17:11:13,111 - utils.db_migration - INFO - 数据库迁移完成
2025-03-17 17:11:13,111 - utils.db_migration - INFO - 数据库迁移成功
2025-03-17 17:11:13,111 - migration - INFO - 数据库迁移成功完成!
2025-03-18 14:25:55,232 - migration - INFO - 开始执行数据库自动迁移...
2025-03-18 14:25:55,232 - utils.db_migration - INFO - 开始数据库迁移
2025-03-18 14:25:55,309 - utils.db_migration - INFO - 发现 22 个模型
2025-03-18 14:25:55,324 - utils.db_migration - INFO - 表 task_flow_connections 不存在,准备创建
2025-03-18 14:25:55,398 - utils.db_migration - INFO - 创建表 task_flow_connections 成功
2025-03-18 14:25:55,403 - utils.db_migration - INFO - 表 task_import_exports 不存在,准备创建
2025-03-18 14:25:55,500 - utils.db_migration - INFO - 创建表 task_import_exports 成功
2025-03-18 14:25:55,503 - utils.db_migration - INFO - 发现新增列 task_input_params.instance_id准备添加
2025-03-18 14:25:55,503 - utils.db_migration - INFO - 执行SQL: ALTER TABLE task_input_params ADD COLUMN instance_id VARCHAR(36) NOT NULL
2025-03-18 14:25:55,598 - utils.db_migration - INFO - 添加列 task_input_params.instance_id 成功
2025-03-18 14:25:55,611 - utils.db_migration - INFO - 数据库迁移完成
2025-03-18 14:25:55,611 - utils.db_migration - INFO - 数据库迁移成功
2025-03-18 14:25:55,611 - migration - INFO - 数据库迁移成功完成!

View File

@ -155,6 +155,18 @@ class TaskInstanceService:
return instance.to_dict()
def get_last_published_instance(self, task_id: str) -> Optional[Dict[str, Any]]:
"""
根据任务ID获取最新的已发布任务实例get_latest_published_instance_by_task_id的别名
Args:
task_id: 任务ID
Returns:
最新的已发布任务实例字典如果不存在则返回None
"""
return self.get_latest_published_instance_by_task_id(task_id)
def update_instance(self, instance_id: str, variables: Optional[Dict[str, Any]] = None,
priority: Optional[int] = None, input_params: Optional[Dict[str, Any]] = None,
block_outputs: Optional[Dict[str, Any]] = None, context_params: Optional[Dict[str, Any]] = None,

View File

@ -0,0 +1,304 @@
"""
任务参数服务模块
负责任务输入参数的管理
"""
from typing import Dict, Any, List, Optional, Tuple
import json
from config.task_config import TaskInputParamConfig, TaskInputParamType
from config.database import db_session
from sqlalchemy import and_, or_, desc
class TaskParamService:
"""任务参数服务类"""
def __init__(self):
# 导入数据模型
from data.models.task_input_param import TaskInputParam
self.TaskInputParam = TaskInputParam
def get_param_types(self) -> List[Dict[str, Any]]:
"""获取所有支持的参数类型"""
param_types = []
for param_type in TaskInputParamType:
param_types.append({
"key": param_type.value,
"name": param_type.value,
"description": f"{param_type.value}类型参数"
})
return param_types
def get_task_input_params(self, task_id: str, instance_id: str = None) -> Tuple[List[Dict[str, Any]], Optional[str]]:
"""
获取任务输入参数
Args:
task_id: 任务ID
instance_id: 任务实例ID如果为None则尝试获取最新的实例
Returns:
Tuple[List[Dict[str, Any]], Optional[str]]: (任务输入参数列表, 实际使用的实例ID)
"""
# 如果没有提供实例ID尝试查找最新的实例
found_instance_id = instance_id
if not found_instance_id:
from data.models.task_instance import TaskInstance
with db_session() as session:
instance = session.query(TaskInstance).filter(
TaskInstance.task_id == task_id,
TaskInstance.is_deleted == False
).order_by(TaskInstance.updated_at.desc()).first()
if instance:
found_instance_id = instance.instance_id
else:
# 如果没有找到实例返回空列表和None
return [], None
# 从数据库中查询并获取任务的输入参数
with db_session() as session:
# 查询任务输入参数,过滤掉已删除的
db_params = session.query(self.TaskInputParam).filter(
and_(
self.TaskInputParam.instance_id == found_instance_id,
self.TaskInputParam.is_deleted == False
)
).order_by(self.TaskInputParam.sort_order).all()
# 将数据库查询结果转换为字典
params = [param.to_dict() for param in db_params]
return params, found_instance_id
def update_task_input_params(self, task_id: str, params: List[Dict[str, Any]], instance_id: str = None) -> Tuple[int, str, bool]:
"""
更新任务输入参数
Args:
task_id: 任务ID
params: 任务输入参数列表
instance_id: 任务实例ID如果为None则尝试获取最新的实例
Returns:
Tuple[int, str, bool]: (更新的参数数量, 实际使用的实例ID, 是否有数据变动)
"""
# 如果没有提供实例ID尝试查找最新的实例
found_instance_id = instance_id
if not found_instance_id:
from data.models.task_instance import TaskInstance
with db_session() as session:
instance = session.query(TaskInstance).filter(
TaskInstance.task_id == task_id,
TaskInstance.is_deleted == False
).order_by(TaskInstance.updated_at.desc()).first()
if instance:
found_instance_id = instance.instance_id
else:
# 创建新的任务实例
from data.models.task import Task
task = session.query(Task).filter(Task.task_id == task_id).first()
if not task:
raise ValueError(f"任务不存在: {task_id}")
instance = TaskInstance(
task_id=task_id,
name=task.name,
variables={},
priority=1,
input_params={},
block_outputs={},
context_params={}
)
session.add(instance)
session.commit()
found_instance_id = instance.instance_id
# 新建实例,肯定有数据变动
return 0, found_instance_id, True
# 过滤出非系统参数
custom_params = []
system_param_keys = [param["key"] for param in TaskInputParamConfig.get_system_params()]
for param in params:
# 检查是否为系统参数
if param["param_name"] in system_param_keys:
continue
# 确保param存在default_value键
if "default_value" not in param:
param["default_value"] = None
custom_params.append(param)
# 检测是否有数据变动的标志
has_changes = False
# 更新数据库
with db_session() as session:
# 查询当前任务实例的所有自定义参数
existing_params = session.query(self.TaskInputParam).filter(
and_(
self.TaskInputParam.instance_id == found_instance_id,
self.TaskInputParam.is_system == False,
self.TaskInputParam.is_deleted == False
)
).all()
# 创建一个映射,用于快速找到现有参数
existing_param_map = {param.param_name: param for param in existing_params}
# 检查参数数量是否变化
if len(existing_param_map) != len(custom_params):
has_changes = True
# 处理每个自定义参数
for i, param_data in enumerate(custom_params):
param_name = param_data["param_name"]
# 检查是否存在现有参数
if param_name in existing_param_map:
# 获取现有参数
param = existing_param_map[param_name]
# 检查参数是否有变化
if (param.label != param_data.get("label", "") or
param.param_type != param_data.get("param_type", "") or
param.required != param_data.get("required", False) or
param.description != param_data.get("description", "") or
param.sort_order != i):
has_changes = True
# 检查default_value是否有变化需要特殊处理
old_value = param.default_value
new_value = param_data.get("default_value")
# 尝试比较值考虑到None, 空字符串, 空列表等特殊情况)
if ((old_value is None and new_value not in [None, "", [], {}]) or
(new_value is None and old_value not in [None, "", [], {}]) or
(str(old_value) != str(new_value))):
has_changes = True
# 更新现有参数
param.label = param_data.get("label", param_name)
param.param_type = param_data.get("param_type", param.param_type)
param.required = param_data.get("required", False)
param.default_value = new_value
param.description = param_data.get("description", "")
param.sort_order = i
# 从映射中移除,以便后面知道哪些需要删除
del existing_param_map[param_name]
else:
# 创建新参数 - 有新增肯定是有变化的
has_changes = True
new_param = self.TaskInputParam(
instance_id=found_instance_id,
task_id=task_id, # 冗余存储任务ID便于查询
param_name=param_name,
label=param_data.get("label", param_name),
param_type=param_data.get("param_type", TaskInputParamType.STRING.value),
required=param_data.get("required", False),
default_value=param_data.get("default_value"),
description=param_data.get("description", ""),
is_system=False,
is_readonly=False,
sort_order=i
)
session.add(new_param)
# 如果有需要删除的参数,标记变化
if existing_param_map:
has_changes = True
# 标记需要删除的参数
for param in existing_param_map.values():
param.is_deleted = True
# 只有在有变化时才提交事务
if has_changes:
session.commit()
return len(custom_params), found_instance_id, has_changes
def delete_task_input_param(self, task_id: str, param_id: str, instance_id: str = None) -> bool:
"""
删除任务输入参数
Args:
task_id: 任务ID
param_id: 参数ID
instance_id: 任务实例ID如果为None则尝试获取最新的实例
Returns:
bool: 是否成功删除
"""
# 如果没有提供实例ID尝试查找最新的实例
if not instance_id:
from data.models.task_instance import TaskInstance
with db_session() as session:
instance = session.query(TaskInstance).filter(
TaskInstance.task_id == task_id,
TaskInstance.is_deleted == False
).order_by(TaskInstance.updated_at.desc()).first()
if instance:
instance_id = instance.instance_id
else:
return False # 如果找不到实例,返回删除失败
with db_session() as session:
# 查询参数
param = session.query(self.TaskInputParam).filter(
and_(
self.TaskInputParam.instance_id == instance_id,
self.TaskInputParam.param_id == param_id,
self.TaskInputParam.is_deleted == False
)
).first()
if not param:
return False
# 系统参数不允许删除
if param.is_system:
return False
# 标记为已删除
param.is_deleted = True
session.commit()
return True
def get_default_input_params(self) -> List[Dict[str, Any]]:
"""
获取默认的任务输入参数
Returns:
List[Dict[str, Any]]: 默认的任务输入参数列表
"""
# 返回一些常用的默认参数作为示例
return [
{
"param_name": "robotId",
"label": "机器人ID",
"param_type": TaskInputParamType.STRING,
"required": True,
"default_value": "",
"description": "执行任务的机器人ID"
},
{
"param_name": "targetPosition",
"label": "目标位置",
"param_type": TaskInputParamType.STRING,
"required": False,
"default_value": "",
"description": "任务执行的目标位置"
},
{
"param_name": "timeout",
"label": "超时时间",
"param_type": TaskInputParamType.INTEGER,
"required": False,
"default_value": 3600,
"description": "任务执行的超时时间(秒)"
}
]

62
utils/api_response.py Normal file
View File

@ -0,0 +1,62 @@
"""
API响应工具模块
用于处理API响应数据的转换
"""
from typing import Dict, Any, List, Optional
from data.models.task_instance import TaskInstance
from data.models.task_input_param import TaskInputParam
class ApiResponseUtil:
"""API响应工具类"""
@staticmethod
def to_task_instance_response(instance: TaskInstance) -> Optional[Dict[str, Any]]:
"""
将任务实例转换为API响应数据
Args:
instance: 任务实例对象
Returns:
API响应数据字典
"""
if not instance:
return None
return {
"instance_id": instance.instance_id,
"task_id": instance.task_id,
"name": instance.name,
"variables": instance.variables or {},
"priority": instance.priority,
"block_outputs": instance.block_outputs or {},
"context_params": instance.context_params or {},
"status": instance.status.value if instance.status else None
}
@staticmethod
def to_task_input_param_response(param: TaskInputParam) -> Optional[Dict[str, Any]]:
"""
将任务输入参数转换为API响应数据
Args:
param: 任务输入参数对象
Returns:
API响应数据字典
"""
if not param:
return None
return {
"param_id": param.param_id,
"param_name": param.param_name,
"label": param.label,
"param_type": param.param_type,
"required": param.required,
"default_value": param.default_value,
"description": param.description,
"is_system": param.is_system,
"is_readonly": param.is_readonly,
"sort_order": param.sort_order
}