update config
This commit is contained in:
parent
a950b043c8
commit
6c70fe2109
52
.dockerignore
Normal file
52
.dockerignore
Normal file
@ -0,0 +1,52 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# 虚拟环境
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# 日志
|
||||
logs/*
|
||||
!logs/.gitkeep
|
||||
|
||||
# 测试
|
||||
tests/
|
||||
.pytest_cache/
|
||||
|
||||
# 其他
|
||||
.DS_Store
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
docker-compose.yml
|
||||
README.md
|
||||
*.md
|
24
.env.example
Normal file
24
.env.example
Normal file
@ -0,0 +1,24 @@
|
||||
# 应用配置
|
||||
DEBUG=false
|
||||
HOST=0.0.0.0
|
||||
PORT=8000
|
||||
|
||||
# 数据库配置
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_USER=root
|
||||
DB_PASSWORD=password
|
||||
DB_NAME=tianfeng_task
|
||||
|
||||
# Redis配置
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_DB=0
|
||||
REDIS_PASSWORD=
|
||||
|
||||
# 环境配置
|
||||
TIANFENG_ENV=default
|
||||
|
||||
# 日志配置
|
||||
LOG_LEVEL=INFO
|
||||
LOG_FILE=logs/tianfeng_task.log
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
43
Dockerfile
Normal file
43
Dockerfile
Normal file
@ -0,0 +1,43 @@
|
||||
# 使用Python 3.11作为基础镜像
|
||||
FROM python:3.11-slim
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 设置环境变量
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
TIANFENG_ENV=default \
|
||||
HOST=0.0.0.0 \
|
||||
PORT=8000 \
|
||||
DEBUG=false
|
||||
|
||||
# 安装系统依赖
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
default-libmysqlclient-dev \
|
||||
netcat-traditional \
|
||||
curl \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 复制项目文件
|
||||
COPY . /app/
|
||||
|
||||
# 安装Python依赖
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# 创建日志目录
|
||||
RUN mkdir -p /app/logs && chmod 777 /app/logs
|
||||
|
||||
# 设置启动脚本权限
|
||||
RUN chmod +x /app/scripts/docker-entrypoint.sh
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 8000
|
||||
|
||||
# 设置启动脚本为入口点
|
||||
ENTRYPOINT ["/app/scripts/docker-entrypoint.sh"]
|
||||
|
||||
# 启动命令
|
||||
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
|
98
README.md
98
README.md
@ -369,7 +369,7 @@ tianfeng_task/
|
||||
|
||||
### 环境要求
|
||||
|
||||
- Python 3.8+
|
||||
- Python 3.11+
|
||||
- 数据库(MySQL/PostgreSQL)
|
||||
- Node.js 14+(前端开发)
|
||||
|
||||
@ -428,4 +428,98 @@ tianfeng_task/
|
||||
|
||||
## 联系方式
|
||||
|
||||
如有问题或建议,请联系系统管理员或开发团队。
|
||||
如有问题或建议,请联系系统管理员或开发团队。
|
||||
|
||||
# Docker部署说明
|
||||
|
||||
## 使用Docker部署天风任务模块
|
||||
|
||||
本项目支持使用Docker进行部署,以下是部署步骤:
|
||||
|
||||
### 前提条件
|
||||
|
||||
- 安装Docker和Docker Compose
|
||||
- 确保端口8000、3306和6379未被占用
|
||||
|
||||
### 部署步骤
|
||||
|
||||
1. 克隆代码仓库
|
||||
|
||||
```bash
|
||||
git clone <仓库地址>
|
||||
cd tianfeng_task
|
||||
```
|
||||
|
||||
2. 使用Docker Compose构建并启动服务
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
这将启动三个容器:
|
||||
- tianfeng-task: 应用服务
|
||||
- tianfeng-mysql: MySQL数据库
|
||||
- tianfeng-redis: Redis缓存
|
||||
|
||||
3. 验证服务是否正常运行
|
||||
|
||||
```bash
|
||||
docker-compose ps
|
||||
```
|
||||
|
||||
4. 访问API文档
|
||||
|
||||
打开浏览器,访问 http://localhost:8000/docs 查看API文档。
|
||||
|
||||
### 环境变量配置
|
||||
|
||||
可以通过修改`docker-compose.yml`文件中的环境变量来配置应用:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- TIANFENG_ENV=default # 环境名称
|
||||
- DEBUG=false # 是否开启调试模式
|
||||
- DB_HOST=db # 数据库主机
|
||||
- DB_PORT=3306 # 数据库端口
|
||||
- DB_USER=root # 数据库用户名
|
||||
- DB_PASSWORD=password # 数据库密码
|
||||
- DB_NAME=tianfeng_task # 数据库名称
|
||||
- REDIS_HOST=redis # Redis主机
|
||||
- REDIS_PORT=6379 # Redis端口
|
||||
- REDIS_DB=0 # Redis数据库索引
|
||||
```
|
||||
|
||||
### 单独构建Docker镜像
|
||||
|
||||
如果需要单独构建Docker镜像,可以使用以下命令:
|
||||
|
||||
```bash
|
||||
docker build -t tianfeng-task:latest .
|
||||
```
|
||||
|
||||
### 停止服务
|
||||
|
||||
```bash
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
如果需要同时删除数据卷(会删除所有数据):
|
||||
|
||||
```bash
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
### 查看日志
|
||||
|
||||
```bash
|
||||
# 查看所有服务的日志
|
||||
docker-compose logs
|
||||
|
||||
# 查看特定服务的日志
|
||||
docker-compose logs app
|
||||
docker-compose logs db
|
||||
docker-compose logs redis
|
||||
|
||||
# 实时查看日志
|
||||
docker-compose logs -f app
|
||||
```
|
Binary file not shown.
BIN
api/__pycache__/common_params_api.cpython-312.pyc
Normal file
BIN
api/__pycache__/common_params_api.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
96
api/common_params_api.py
Normal file
96
api/common_params_api.py
Normal file
@ -0,0 +1,96 @@
|
||||
"""
|
||||
常用参数API模块
|
||||
提供获取各种常用参数数据的接口
|
||||
"""
|
||||
from typing import Dict, Any, List, Optional
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from pydantic import BaseModel, Field
|
||||
from services.common_params_service import CommonParamsService
|
||||
from config.component_config import CommonParamsConfig, CommonParamType
|
||||
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)}"
|
||||
)
|
@ -1,13 +1,9 @@
|
||||
# api/component_api.py
|
||||
from typing import Dict, Any, List, Optional
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from fastapi import APIRouter, HTTPException, Depends, Query
|
||||
from pydantic import BaseModel, Field
|
||||
from services.component_service import ComponentService
|
||||
from config.component_config import (
|
||||
ComponentDiscovery,
|
||||
ComponentCategory
|
||||
)
|
||||
from config.component_detail_config import ComponentDetailConfig
|
||||
from config.component_config import ComponentDetailConfig, ComponentCategoryConfig
|
||||
from config.api_config import (
|
||||
ApiResponseCode,
|
||||
ApiResponseMessage
|
||||
@ -44,10 +40,10 @@ async def get_components():
|
||||
component_categories = {}
|
||||
for component_type in component_types:
|
||||
# 根据组件类型推断类别
|
||||
category = ComponentCategory.BASE # 默认类别
|
||||
category = ComponentCategoryConfig.BASE # 默认类别
|
||||
|
||||
# 使用配置文件中的映射关系确定组件类别
|
||||
mapping = ComponentCategory.get_mapping()
|
||||
mapping = ComponentCategoryConfig.get_mapping()
|
||||
for prefix, cat in mapping.items():
|
||||
if component_type.startswith(prefix):
|
||||
category = cat
|
||||
|
@ -1,93 +0,0 @@
|
||||
#!/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]] = {}
|
22
api/models/__init__.py
Normal file
22
api/models/__init__.py
Normal file
@ -0,0 +1,22 @@
|
||||
"""
|
||||
API模型包
|
||||
包含API请求和响应的数据模型
|
||||
"""
|
||||
|
||||
# 导出基础模型
|
||||
from api.models.base import ApiResponse, SortField, SortOrder
|
||||
|
||||
# 导出任务相关模型
|
||||
from api.models.task import (
|
||||
TaskInput, TaskBatchInput, TaskIdList, TaskTypeInfo,
|
||||
TaskUpdateInput, TaskEditInput
|
||||
)
|
||||
|
||||
# 导出工作流相关模型
|
||||
from api.models.workflow import (
|
||||
WorkflowInput, WorkflowUpdateInput,
|
||||
WorkflowExecuteInput, WorkflowImportInput
|
||||
)
|
||||
|
||||
# 导出组件相关模型
|
||||
from api.models.component import ComponentDiscoverInput
|
BIN
api/models/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
api/models/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
api/models/__pycache__/base.cpython-312.pyc
Normal file
BIN
api/models/__pycache__/base.cpython-312.pyc
Normal file
Binary file not shown.
BIN
api/models/__pycache__/component.cpython-312.pyc
Normal file
BIN
api/models/__pycache__/component.cpython-312.pyc
Normal file
Binary file not shown.
BIN
api/models/__pycache__/task.cpython-312.pyc
Normal file
BIN
api/models/__pycache__/task.cpython-312.pyc
Normal file
Binary file not shown.
BIN
api/models/__pycache__/workflow.cpython-312.pyc
Normal file
BIN
api/models/__pycache__/workflow.cpython-312.pyc
Normal file
Binary file not shown.
25
api/models/base.py
Normal file
25
api/models/base.py
Normal file
@ -0,0 +1,25 @@
|
||||
"""
|
||||
基础API模型模块
|
||||
包含通用的API请求和响应的数据模型
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
from pydantic import BaseModel, Field
|
||||
from enum import Enum
|
||||
|
||||
# 排序字段枚举
|
||||
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="数据")
|
12
api/models/component.py
Normal file
12
api/models/component.py
Normal file
@ -0,0 +1,12 @@
|
||||
"""
|
||||
组件API模型模块
|
||||
包含组件相关的API请求和响应的数据模型
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
from pydantic import BaseModel, Field
|
||||
from config.component_config import ComponentDiscovery
|
||||
|
||||
# 组件相关模型
|
||||
class ComponentDiscoverInput(BaseModel):
|
||||
package_name: Optional[str] = ComponentDiscovery.DEFAULT_PACKAGE
|
46
api/models/task.py
Normal file
46
api/models/task.py
Normal file
@ -0,0 +1,46 @@
|
||||
"""
|
||||
任务API模型模块
|
||||
包含任务相关的API请求和响应的数据模型
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
from pydantic import BaseModel, Field
|
||||
from config.task_config import TaskType, TaskStatus
|
||||
|
||||
# 任务相关模型
|
||||
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 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]] = {}
|
||||
priority: Optional[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="上下文参数")
|
29
api/models/workflow.py
Normal file
29
api/models/workflow.py
Normal file
@ -0,0 +1,29 @@
|
||||
"""
|
||||
工作流API模型模块
|
||||
包含工作流相关的API请求和响应的数据模型
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
# 工作流相关模型
|
||||
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
|
144
api/task_api.py
144
api/task_api.py
@ -3,6 +3,7 @@ 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 services.task_instance_service import TaskInstanceService
|
||||
from core.exceptions import TianfengTaskError
|
||||
from config.task_config import (
|
||||
get_all_task_types,
|
||||
@ -17,15 +18,21 @@ from config.task_config import (
|
||||
DEFAULT_TASK_DESCRIPTION,
|
||||
DEFAULT_TEMPLATE_DESCRIPTION,
|
||||
TaskType,
|
||||
TaskStatus
|
||||
TaskStatus,
|
||||
TaskInputParamConfig,
|
||||
SystemParamKey
|
||||
)
|
||||
from api.models import (
|
||||
ApiResponse, TaskInput, TaskBatchInput, TaskIdList,
|
||||
TaskTypeInfo, SortField, SortOrder, TaskUpdateInput, TaskEditInput
|
||||
)
|
||||
from api.models import ApiResponse, TaskInput, TaskBatchInput, TaskIdList, TaskTypeInfo, SortField, SortOrder, TaskUpdateInput, TaskEditInput
|
||||
|
||||
# 创建路由器
|
||||
router = APIRouter(tags=["任务管理"])
|
||||
|
||||
# 创建服务实例
|
||||
task_service = TaskService()
|
||||
task_instance_service = TaskInstanceService()
|
||||
|
||||
@router.get("/tasks", response_model=ApiResponse)
|
||||
async def get_tasks(
|
||||
@ -329,10 +336,48 @@ async def edit_task(task_edit: TaskEditInput):
|
||||
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}
|
||||
"data": {
|
||||
"task": updated_task,
|
||||
"instance": instance
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"编辑任务失败: {str(e)}")
|
||||
@ -350,6 +395,9 @@ async def get_task_edit_info(task_id: str):
|
||||
"data": None
|
||||
}
|
||||
|
||||
# 获取或创建编辑中的任务实例
|
||||
instance = task_instance_service.get_or_create_editing_instance(task_id)
|
||||
|
||||
# 获取可用的子任务列表(排除当前任务自身)
|
||||
available_subtasks = []
|
||||
try:
|
||||
@ -363,9 +411,11 @@ async def get_task_edit_info(task_id: str):
|
||||
except Exception as e:
|
||||
# 如果获取任务列表失败,记录错误但继续执行
|
||||
print(f"获取可用子任务列表失败: {str(e)}")
|
||||
|
||||
# 获取组件详细信息
|
||||
from config.component_detail_config import ComponentDetailConfig
|
||||
from config.component_config import ComponentDetailConfig
|
||||
component_details = ComponentDetailConfig.get_all_components()
|
||||
|
||||
# 获取组件类型中文名称映射
|
||||
component_type_names = ComponentDetailConfig.get_component_type_names()
|
||||
|
||||
@ -387,17 +437,56 @@ async def get_task_edit_info(task_id: str):
|
||||
# 添加组件到对应类型下
|
||||
component_types[component_type]["components"].append(component)
|
||||
|
||||
# 不再将子任务列表嵌入到组件类型对象中
|
||||
if "subtask" in component_types:
|
||||
component_types["subtask"]["available_subtasks"] = available_subtasks
|
||||
# 获取常用参数数据
|
||||
from services.common_params_service import CommonParamsService
|
||||
from config.component_config import CommonParamsConfig
|
||||
|
||||
common_params_service = CommonParamsService()
|
||||
param_types = CommonParamsConfig.get_param_types()
|
||||
|
||||
# 获取所有常用参数数据
|
||||
common_params = {}
|
||||
for param_type_config in param_types:
|
||||
param_type = param_type_config["type"]
|
||||
param_data = common_params_service.get_param_data(param_type)
|
||||
common_params[param_type] = {
|
||||
"type_info": param_type_config,
|
||||
"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", {})
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "获取任务编辑信息成功",
|
||||
"data": {
|
||||
"task": task,
|
||||
"instance": instance,
|
||||
"component_types": component_types,
|
||||
"available_subtasks": available_subtasks # 作为单独的字段返回
|
||||
"available_subtasks": available_subtasks,
|
||||
"common_params": common_params,
|
||||
"task_input_params": task_input_params,
|
||||
"block_output_params": block_output_params,
|
||||
"context_params": context_params
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
@ -429,3 +518,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
|
||||
}
|
||||
|
||||
# 获取编辑中的任务实例
|
||||
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"])
|
||||
|
||||
# 构建响应数据
|
||||
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)}")
|
||||
|
||||
|
218
api/task_instance_api.py
Normal file
218
api/task_instance_api.py
Normal file
@ -0,0 +1,218 @@
|
||||
"""
|
||||
任务实例API模块
|
||||
提供任务实例的增删改查接口
|
||||
"""
|
||||
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
|
||||
|
||||
# 创建路由器
|
||||
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):
|
||||
"""创建任务实例"""
|
||||
try:
|
||||
# 创建任务实例
|
||||
instance = task_instance_service.create_instance(
|
||||
task_id=instance_input.task_id,
|
||||
name=instance_input.name,
|
||||
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
|
||||
)
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "创建任务实例成功",
|
||||
"data": instance
|
||||
}
|
||||
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.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)}")
|
@ -4,9 +4,12 @@ from fastapi import APIRouter, HTTPException, Depends, Query
|
||||
from pydantic import BaseModel, Field
|
||||
from services.workflow_service import WorkflowService
|
||||
from core.exceptions import TianfengTaskError
|
||||
from api.models import (
|
||||
ApiResponse, WorkflowInput, WorkflowUpdateInput,
|
||||
WorkflowExecuteInput, WorkflowImportInput
|
||||
)
|
||||
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=["工作流管理"])
|
||||
|
4
app.py
4
app.py
@ -5,13 +5,14 @@ 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.component_config 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 api.common_params_api import router as common_params_router
|
||||
from core.exceptions import TianfengTaskError
|
||||
from config.api_config import ApiResponseCode, ApiResponseMessage
|
||||
from config.component_config import ComponentCategoryConfig
|
||||
@ -120,6 +121,7 @@ register_all_components()
|
||||
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.middleware("http")
|
||||
|
BIN
config/__pycache__/common_params_config.cpython-312.pyc
Normal file
BIN
config/__pycache__/common_params_config.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
config/__pycache__/component_init_config.cpython-312.pyc
Normal file
BIN
config/__pycache__/component_init_config.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
@ -1,15 +1,185 @@
|
||||
"""
|
||||
组件配置文件
|
||||
包含组件发现、注册和分类的相关配置
|
||||
组件配置模块
|
||||
包含组件类型、详细配置、注册和常用参数等相关配置信息
|
||||
"""
|
||||
from typing import Dict, List, Any
|
||||
from data.models.component import ComponentCategoryEnum
|
||||
from typing import Dict, List, Any, Optional
|
||||
from enum import Enum
|
||||
from core.component import ComponentFactory
|
||||
import importlib
|
||||
from config.settings import COMPONENT_PACKAGES
|
||||
from utils.logger import get_logger
|
||||
|
||||
# 获取日志记录器
|
||||
logger = get_logger(__name__)
|
||||
|
||||
#################################################
|
||||
# 组件初始化配置
|
||||
#################################################
|
||||
|
||||
# 组件类别配置
|
||||
COMPONENT_CATEGORIES = [
|
||||
{"name": "基础组件", "code": "basic", "description": "基础功能组件", "sort_order": 1},
|
||||
{"name": "流程组件", "code": "flow", "description": "流程控制组件", "sort_order": 2},
|
||||
{"name": "机器人组件", "code": "robot", "description": "机器人控制组件", "sort_order": 3},
|
||||
{"name": "库位组件", "code": "storage", "description": "库位管理组件", "sort_order": 4},
|
||||
{"name": "设备组件", "code": "device", "description": "设备控制组件", "sort_order": 5},
|
||||
{"name": "HTTP组件", "code": "http", "description": "HTTP请求组件", "sort_order": 6},
|
||||
{"name": "脚本组件", "code": "script", "description": "脚本执行组件", "sort_order": 7},
|
||||
{"name": "子任务组件", "code": "subtask", "description": "子任务组件", "sort_order": 8},
|
||||
{"name": "任务组件", "code": "task", "description": "任务管理组件", "sort_order": 9}
|
||||
]
|
||||
|
||||
# 组件类型配置
|
||||
def get_component_types(categories):
|
||||
"""
|
||||
获取组件类型配置
|
||||
|
||||
Args:
|
||||
categories: 类别ID映射字典 {类别代码: 类别ID}
|
||||
|
||||
Returns:
|
||||
list: 组件类型配置列表
|
||||
"""
|
||||
return [
|
||||
# 基础组件
|
||||
{"name": "变量赋值", "code": "variable_assign", "category_id": categories["basic"], "description": "为变量赋值", "icon": "variable", "sort_order": 1},
|
||||
{"name": "条件判断", "code": "condition", "category_id": categories["basic"], "description": "条件判断", "icon": "condition", "sort_order": 2},
|
||||
{"name": "延时等待", "code": "delay", "category_id": categories["basic"], "description": "延时等待", "icon": "delay", "sort_order": 3},
|
||||
|
||||
# 流程组件
|
||||
{"name": "If条件", "code": "if", "category_id": categories["flow"], "description": "If条件判断", "icon": "if", "sort_order": 1},
|
||||
{"name": "If-Else条件", "code": "if_else", "category_id": categories["flow"], "description": "If-Else条件判断", "icon": "if_else", "sort_order": 2},
|
||||
{"name": "循环", "code": "loop", "category_id": categories["flow"], "description": "循环执行", "icon": "loop", "sort_order": 3},
|
||||
{"name": "并行执行", "code": "parallel", "category_id": categories["flow"], "description": "并行执行多个分支", "icon": "parallel", "sort_order": 4},
|
||||
|
||||
# 机器人组件
|
||||
{"name": "选择机器人", "code": "select_robot", "category_id": categories["robot"], "description": "选择执行机器人", "icon": "robot", "sort_order": 1},
|
||||
{"name": "机器人移动", "code": "robot_move", "category_id": categories["robot"], "description": "控制机器人移动", "icon": "move", "sort_order": 2},
|
||||
{"name": "获取机器人状态", "code": "robot_status", "category_id": categories["robot"], "description": "获取机器人状态", "icon": "status", "sort_order": 3},
|
||||
|
||||
# HTTP组件
|
||||
{"name": "HTTP请求", "code": "http_request", "category_id": categories["http"], "description": "发送HTTP请求", "icon": "http", "sort_order": 1},
|
||||
{"name": "API调用", "code": "api_call", "category_id": categories["http"], "description": "调用系统API", "icon": "api", "sort_order": 2},
|
||||
|
||||
# 脚本组件
|
||||
{"name": "JavaScript脚本", "code": "javascript", "category_id": categories["script"], "description": "执行JavaScript脚本", "icon": "script", "sort_order": 1},
|
||||
|
||||
# 子任务组件
|
||||
{"name": "执行子任务", "code": "execute_subtask", "category_id": categories["subtask"], "description": "执行子任务", "icon": "subtask", "sort_order": 1}
|
||||
]
|
||||
|
||||
# 系统组件配置
|
||||
def get_system_components(types):
|
||||
"""
|
||||
获取系统组件配置
|
||||
|
||||
Args:
|
||||
types: 组件类型ID映射字典 {类型代码: 类型ID}
|
||||
|
||||
Returns:
|
||||
list: 系统组件配置列表
|
||||
"""
|
||||
return [
|
||||
{
|
||||
"name": "变量赋值",
|
||||
"code": "variable_assign",
|
||||
"type_id": types["variable_assign"],
|
||||
"description": "为变量赋值",
|
||||
"is_system": True,
|
||||
"config_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"variable_name": {"type": "string", "title": "变量名"},
|
||||
"value_type": {"type": "string", "enum": ["string", "number", "boolean", "object"], "title": "值类型"},
|
||||
"value": {"type": "string", "title": "值"}
|
||||
},
|
||||
"required": ["variable_name", "value_type", "value"]
|
||||
},
|
||||
"input_schema": {},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {"type": "boolean", "title": "执行结果"}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "条件判断",
|
||||
"code": "condition",
|
||||
"type_id": types["condition"],
|
||||
"description": "条件判断",
|
||||
"is_system": True,
|
||||
"config_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"condition": {"type": "string", "title": "条件表达式"}
|
||||
},
|
||||
"required": ["condition"]
|
||||
},
|
||||
"input_schema": {},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {"type": "boolean", "title": "判断结果"}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "If条件",
|
||||
"code": "if",
|
||||
"type_id": types["if"],
|
||||
"description": "If条件判断",
|
||||
"is_system": True,
|
||||
"config_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"condition": {"type": "string", "title": "条件表达式"}
|
||||
},
|
||||
"required": ["condition"]
|
||||
},
|
||||
"input_schema": {},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {"type": "boolean", "title": "判断结果"}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
# 缓存配置
|
||||
CACHE_EXPIRE_TIME = 86400 # 缓存过期时间,单位:秒(24小时)
|
||||
CACHE_KEYS = {
|
||||
"COMPONENT_CATEGORIES": "component_categories",
|
||||
"COMPONENT_TYPES": "component_types",
|
||||
"SYSTEM_COMPONENTS": "system_components"
|
||||
}
|
||||
|
||||
#################################################
|
||||
# 组件自动发现与注册
|
||||
#################################################
|
||||
|
||||
class ComponentDiscovery:
|
||||
"""组件自动发现配置"""
|
||||
DEFAULT_PACKAGE = "components" # 默认组件包名
|
||||
AUTO_REGISTER = True # 是否在启动时自动注册所有组件
|
||||
|
||||
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)}")
|
||||
|
||||
#################################################
|
||||
# 组件分类配置
|
||||
#################################################
|
||||
|
||||
class ComponentCategory:
|
||||
"""组件类别配置"""
|
||||
# 组件类别定义
|
||||
@ -81,6 +251,8 @@ class ComponentCategoryConfig:
|
||||
@classmethod
|
||||
def get_category_name(cls, category_enum):
|
||||
"""获取分类名称"""
|
||||
from data.models.component import ComponentCategoryEnum
|
||||
|
||||
names = {
|
||||
ComponentCategoryEnum.SUBTASK: "子任务",
|
||||
ComponentCategoryEnum.SCRIPT: "脚本",
|
||||
@ -97,6 +269,8 @@ class ComponentCategoryConfig:
|
||||
@classmethod
|
||||
def get_category_description(cls, category_enum):
|
||||
"""获取分类描述"""
|
||||
from data.models.component import ComponentCategoryEnum
|
||||
|
||||
descriptions = {
|
||||
ComponentCategoryEnum.SUBTASK: "可重用的子任务组件",
|
||||
ComponentCategoryEnum.SCRIPT: "执行脚本代码的组件",
|
||||
@ -113,6 +287,8 @@ class ComponentCategoryConfig:
|
||||
@classmethod
|
||||
def get_category_order(cls, category_enum):
|
||||
"""获取分类排序"""
|
||||
from data.models.component import ComponentCategoryEnum
|
||||
|
||||
orders = {
|
||||
ComponentCategoryEnum.BASIC: 1,
|
||||
ComponentCategoryEnum.FLOW: 2,
|
||||
@ -124,4 +300,607 @@ class ComponentCategoryConfig:
|
||||
ComponentCategoryEnum.STORAGE: 8,
|
||||
ComponentCategoryEnum.DEVICE: 9
|
||||
}
|
||||
return orders.get(category_enum, 99)
|
||||
return orders.get(category_enum, 99)
|
||||
|
||||
#################################################
|
||||
# 组件详细配置
|
||||
#################################################
|
||||
|
||||
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]
|
||||
|
||||
#################################################
|
||||
# 常用参数配置
|
||||
#################################################
|
||||
|
||||
class CommonParamType(str, Enum):
|
||||
"""常用参数类型枚举"""
|
||||
ROBOT_ID = "robot_id" # 机器人ID
|
||||
ROBOT_GROUP = "robot_group" # 机器人组
|
||||
ROBOT_TAG = "robot_tag" # 机器人标签
|
||||
STORAGE_AREA_ID = "storage_area_id" # 库区ID
|
||||
STORAGE_AREA = "storage_area" # 库区
|
||||
SITE = "site" # 站点
|
||||
BIN_TASK = "bin_task" # binTask
|
||||
WORKSTATION = "workstation" # 工位
|
||||
POST = "post" # 岗位
|
||||
USER = "user" # 用户
|
||||
CACHE = "cache" # 缓存
|
||||
BUILT_IN_FUNCTION = "built_in_function" # 内置函数
|
||||
|
||||
class CommonParamsConfig:
|
||||
"""常用参数配置类"""
|
||||
|
||||
@classmethod
|
||||
def get_param_types(cls) -> List[Dict[str, Any]]:
|
||||
"""获取所有常用参数类型"""
|
||||
return [
|
||||
{
|
||||
"type": CommonParamType.ROBOT_ID,
|
||||
"name": "机器人ID",
|
||||
"description": "选择机器人ID",
|
||||
"api_path": "/api/robots/ids", # 获取数据的API路径
|
||||
"value_field": "id", # 值字段
|
||||
"display_field": "name" # 显示字段
|
||||
},
|
||||
{
|
||||
"type": CommonParamType.ROBOT_GROUP,
|
||||
"name": "机器人组",
|
||||
"description": "选择机器人组",
|
||||
"api_path": "/api/robots/groups",
|
||||
"value_field": "id",
|
||||
"display_field": "name"
|
||||
},
|
||||
{
|
||||
"type": CommonParamType.ROBOT_TAG,
|
||||
"name": "机器人标签",
|
||||
"description": "选择机器人标签",
|
||||
"api_path": "/api/robots/tags",
|
||||
"value_field": "id",
|
||||
"display_field": "name"
|
||||
},
|
||||
{
|
||||
"type": CommonParamType.STORAGE_AREA_ID,
|
||||
"name": "库区ID",
|
||||
"description": "选择库区ID",
|
||||
"api_path": "/api/storage/areas/ids",
|
||||
"value_field": "id",
|
||||
"display_field": "name"
|
||||
},
|
||||
{
|
||||
"type": CommonParamType.STORAGE_AREA,
|
||||
"name": "库区",
|
||||
"description": "选择库区",
|
||||
"api_path": "/api/storage/areas",
|
||||
"value_field": "id",
|
||||
"display_field": "name"
|
||||
},
|
||||
{
|
||||
"type": CommonParamType.SITE,
|
||||
"name": "站点",
|
||||
"description": "选择站点",
|
||||
"api_path": "/api/sites",
|
||||
"value_field": "id",
|
||||
"display_field": "name"
|
||||
},
|
||||
{
|
||||
"type": CommonParamType.BIN_TASK,
|
||||
"name": "binTask",
|
||||
"description": "选择binTask",
|
||||
"api_path": "/api/bin-tasks",
|
||||
"value_field": "id",
|
||||
"display_field": "name"
|
||||
},
|
||||
{
|
||||
"type": CommonParamType.WORKSTATION,
|
||||
"name": "工位",
|
||||
"description": "选择工位",
|
||||
"api_path": "/api/workstations",
|
||||
"value_field": "id",
|
||||
"display_field": "name"
|
||||
},
|
||||
{
|
||||
"type": CommonParamType.POST,
|
||||
"name": "岗位",
|
||||
"description": "选择岗位",
|
||||
"api_path": "/api/posts",
|
||||
"value_field": "id",
|
||||
"display_field": "name"
|
||||
},
|
||||
{
|
||||
"type": CommonParamType.USER,
|
||||
"name": "用户",
|
||||
"description": "选择用户",
|
||||
"api_path": "/api/users",
|
||||
"value_field": "id",
|
||||
"display_field": "name"
|
||||
},
|
||||
{
|
||||
"type": CommonParamType.CACHE,
|
||||
"name": "缓存",
|
||||
"description": "选择缓存",
|
||||
"api_path": "/api/caches",
|
||||
"value_field": "key",
|
||||
"display_field": "key"
|
||||
},
|
||||
{
|
||||
"type": CommonParamType.BUILT_IN_FUNCTION,
|
||||
"name": "内置函数",
|
||||
"description": "选择内置函数",
|
||||
"api_path": "/api/built-in-functions",
|
||||
"value_field": "name",
|
||||
"display_field": "name"
|
||||
}
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_param_type_by_type(cls, param_type: str) -> Optional[Dict[str, Any]]:
|
||||
"""根据类型获取参数类型配置"""
|
||||
param_types = cls.get_param_types()
|
||||
for pt in param_types:
|
||||
if pt["type"] == param_type:
|
||||
return pt
|
||||
return None
|
@ -1,472 +0,0 @@
|
||||
"""
|
||||
组件详细配置文件
|
||||
包含各种组件类型及其子组件的配置信息
|
||||
"""
|
||||
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]
|
@ -1,23 +0,0 @@
|
||||
"""
|
||||
组件注册表
|
||||
"""
|
||||
# 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)}")
|
@ -3,27 +3,21 @@
|
||||
|
||||
"""
|
||||
任务配置模块
|
||||
包含任务类型、状态等配置信息
|
||||
包含任务类型、状态、输入参数等配置信息
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
#################################################
|
||||
# 任务类型相关配置
|
||||
#################################################
|
||||
|
||||
# 任务类型枚举
|
||||
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
|
||||
@ -84,8 +78,20 @@ class TaskTypeConfig:
|
||||
})
|
||||
return task_types
|
||||
|
||||
#################################################
|
||||
# 任务状态相关配置
|
||||
#################################################
|
||||
|
||||
class TaskStatus(str, Enum):
|
||||
"""任务状态枚举"""
|
||||
PENDING = "PENDING"
|
||||
RUNNING = "RUNNING"
|
||||
COMPLETED = "COMPLETED"
|
||||
CANCELLED = "CANCELLED"
|
||||
FAILED = "FAILED"
|
||||
PAUSED = "PAUSED"
|
||||
WAITING = "WAITING"
|
||||
|
||||
# 任务状态配置
|
||||
class TaskStatusConfig:
|
||||
"""任务状态配置"""
|
||||
PENDING = TaskStatus.PENDING.value
|
||||
@ -171,15 +177,148 @@ class TaskStatusConfig:
|
||||
})
|
||||
return task_statuses
|
||||
|
||||
#################################################
|
||||
# 任务输入参数相关配置
|
||||
#################################################
|
||||
|
||||
class TaskInputParamType(str, Enum):
|
||||
"""任务输入参数类型枚举"""
|
||||
STRING = "string"
|
||||
INTEGER = "integer"
|
||||
FLOAT = "float"
|
||||
BOOLEAN = "boolean"
|
||||
DATETIME = "datetime"
|
||||
JSON = "json"
|
||||
ARRAY = "array"
|
||||
OBJECT = "object"
|
||||
|
||||
class SystemParamKey(str, Enum):
|
||||
"""系统参数键名枚举"""
|
||||
TASK_ID = "task_id"
|
||||
INSTANCE_ID = "instance_id"
|
||||
TASK_NAME = "task_name"
|
||||
CREATED_AT = "created_at"
|
||||
VARIABLES = "variables"
|
||||
PRIORITY = "priority"
|
||||
|
||||
class TaskInputParamConfig:
|
||||
"""任务输入参数配置类"""
|
||||
|
||||
# 系统默认参数配置
|
||||
SYSTEM_PARAMS = [
|
||||
{
|
||||
"key": SystemParamKey.TASK_ID,
|
||||
"name": "任务ID",
|
||||
"type": TaskInputParamType.STRING,
|
||||
"is_system": True,
|
||||
"is_readonly": True,
|
||||
"description": "任务的唯一标识符"
|
||||
},
|
||||
{
|
||||
"key": SystemParamKey.INSTANCE_ID,
|
||||
"name": "任务实例ID",
|
||||
"type": TaskInputParamType.STRING,
|
||||
"is_system": True,
|
||||
"is_readonly": True,
|
||||
"description": "任务实例的唯一标识符"
|
||||
},
|
||||
{
|
||||
"key": SystemParamKey.TASK_NAME,
|
||||
"name": "任务名称",
|
||||
"type": TaskInputParamType.STRING,
|
||||
"is_system": True,
|
||||
"is_readonly": True,
|
||||
"description": "任务的名称"
|
||||
},
|
||||
{
|
||||
"key": SystemParamKey.CREATED_AT,
|
||||
"name": "创建时间",
|
||||
"type": TaskInputParamType.DATETIME,
|
||||
"is_system": True,
|
||||
"is_readonly": True,
|
||||
"description": "任务实例的创建时间"
|
||||
},
|
||||
{
|
||||
"key": SystemParamKey.VARIABLES,
|
||||
"name": "任务变量",
|
||||
"type": TaskInputParamType.JSON,
|
||||
"is_system": True,
|
||||
"is_readonly": False,
|
||||
"description": "任务的变量集合"
|
||||
},
|
||||
{
|
||||
"key": SystemParamKey.PRIORITY,
|
||||
"name": "任务优先级",
|
||||
"type": TaskInputParamType.INTEGER,
|
||||
"is_system": True,
|
||||
"is_readonly": False,
|
||||
"description": "任务的优先级,数值越大优先级越高"
|
||||
}
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_system_params(cls) -> List[Dict[str, Any]]:
|
||||
"""获取系统默认参数配置"""
|
||||
return cls.SYSTEM_PARAMS
|
||||
|
||||
@classmethod
|
||||
def get_system_param_by_key(cls, key: str) -> Optional[Dict[str, Any]]:
|
||||
"""根据键名获取系统参数配置"""
|
||||
for param in cls.SYSTEM_PARAMS:
|
||||
if param["key"] == key:
|
||||
return param
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def build_system_params_with_values(cls, task_id: str, instance: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
构建带有值的系统参数列表
|
||||
|
||||
Args:
|
||||
task_id: 任务ID
|
||||
instance: 任务实例数据
|
||||
|
||||
Returns:
|
||||
带有值的系统参数列表
|
||||
"""
|
||||
params = []
|
||||
|
||||
# 复制系统参数配置并添加值
|
||||
for param_config in cls.SYSTEM_PARAMS:
|
||||
param = param_config.copy()
|
||||
|
||||
# 根据参数键名设置值
|
||||
if param["key"] == SystemParamKey.TASK_ID:
|
||||
param["value"] = task_id
|
||||
elif param["key"] == SystemParamKey.INSTANCE_ID:
|
||||
param["value"] = instance.get("instance_id")
|
||||
elif param["key"] == SystemParamKey.TASK_NAME:
|
||||
param["value"] = instance.get("name")
|
||||
elif param["key"] == SystemParamKey.CREATED_AT:
|
||||
param["value"] = instance.get("created_at")
|
||||
elif param["key"] == SystemParamKey.VARIABLES:
|
||||
param["value"] = instance.get("variables")
|
||||
elif param["key"] == SystemParamKey.PRIORITY:
|
||||
param["value"] = instance.get("priority", 1)
|
||||
|
||||
params.append(param)
|
||||
|
||||
return params
|
||||
|
||||
#################################################
|
||||
# 默认值配置
|
||||
#################################################
|
||||
|
||||
class DefaultConfig:
|
||||
"""默认值配置"""
|
||||
TASK_DESCRIPTION = "" # 默认任务描述(备注)
|
||||
TEMPLATE_DESCRIPTION = "用户自有模板" # 默认模板描述
|
||||
|
||||
#################################################
|
||||
# 向后兼容的常量和函数
|
||||
#################################################
|
||||
|
||||
# 为了向后兼容,保留原有的函数和变量
|
||||
# 任务类型和状态配置常量
|
||||
TASK_TYPE_CONFIG = TaskTypeConfig.DETAILS
|
||||
TASK_STATUS_CONFIG = TaskStatusConfig.DETAILS
|
||||
|
||||
|
@ -6,6 +6,20 @@
|
||||
包含数据库模型和数据访问功能
|
||||
"""
|
||||
|
||||
from data.session import initialize_database, get_session, session_scope
|
||||
# 避免循环导入
|
||||
# from data.session import initialize_database, get_session, session_scope
|
||||
|
||||
__all__ = ['initialize_database', 'get_session', 'session_scope']
|
||||
|
||||
# 延迟导入
|
||||
def initialize_database():
|
||||
from data.session import initialize_database as init_db
|
||||
return init_db()
|
||||
|
||||
def get_session():
|
||||
from data.session import get_session as get_db_session
|
||||
return get_db_session()
|
||||
|
||||
def session_scope():
|
||||
from data.session import session_scope as db_session_scope
|
||||
return db_session_scope()
|
||||
|
Binary file not shown.
Binary file not shown.
@ -21,6 +21,7 @@ from data.models.component_parameter import (
|
||||
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
|
||||
|
||||
__all__ = [
|
||||
'BaseModel',
|
||||
@ -38,5 +39,7 @@ __all__ = [
|
||||
'ParameterValueFormat',
|
||||
'TaskVariableDefinition',
|
||||
'TaskBackup',
|
||||
'TaskEditHistory'
|
||||
'TaskEditHistory',
|
||||
'TaskInstance',
|
||||
'TaskInstanceStatus'
|
||||
]
|
||||
|
Binary file not shown.
BIN
data/models/__pycache__/subtask.cpython-312.pyc
Normal file
BIN
data/models/__pycache__/subtask.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
data/models/__pycache__/task_flow.cpython-312.pyc
Normal file
BIN
data/models/__pycache__/task_flow.cpython-312.pyc
Normal file
Binary file not shown.
BIN
data/models/__pycache__/task_import_export.cpython-312.pyc
Normal file
BIN
data/models/__pycache__/task_import_export.cpython-312.pyc
Normal file
Binary file not shown.
BIN
data/models/__pycache__/task_input.cpython-312.pyc
Normal file
BIN
data/models/__pycache__/task_input.cpython-312.pyc
Normal file
Binary file not shown.
BIN
data/models/__pycache__/task_instance.cpython-312.pyc
Normal file
BIN
data/models/__pycache__/task_instance.cpython-312.pyc
Normal file
Binary file not shown.
@ -69,6 +69,7 @@ class Task(BaseModel):
|
||||
versions = relationship('TaskVersion', back_populates='task', cascade='all, delete-orphan')
|
||||
records = relationship('TaskRecord', back_populates='task')
|
||||
flow_blocks = relationship('TaskFlowBlock', back_populates='task')
|
||||
instances = relationship("TaskInstance", back_populates="task", cascade="all, delete-orphan")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Task(id={self.id}, uuid='{self.task_id}', name='{self.name}', type='{self.task_type}')>"
|
||||
|
60
data/models/task_instance.py
Normal file
60
data/models/task_instance.py
Normal file
@ -0,0 +1,60 @@
|
||||
"""
|
||||
任务实例模型
|
||||
用于存储任务管理表的数据,记录每次编辑任务的实例
|
||||
"""
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, Boolean, ForeignKey, JSON, Enum
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
import enum
|
||||
import uuid
|
||||
from data.models.base import BaseModel
|
||||
|
||||
class TaskInstanceStatus(enum.Enum):
|
||||
"""任务实例状态枚举"""
|
||||
EDITING = "editing" # 编辑中
|
||||
PUBLISHED = "published" # 已发布
|
||||
|
||||
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="是否删除")
|
||||
|
||||
# 关联关系
|
||||
task = relationship("Task", back_populates="instances")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""初始化实例,自动生成instance_id"""
|
||||
if 'instance_id' not in kwargs:
|
||||
kwargs['instance_id'] = str(uuid.uuid4())
|
||||
super(TaskInstance, self).__init__(**kwargs)
|
||||
|
||||
def to_dict(self):
|
||||
"""转换为字典"""
|
||||
return {
|
||||
"id": self.instance_id, # 使用instance_id作为对外ID
|
||||
"instance_id": self.instance_id,
|
||||
"task_id": self.task_id,
|
||||
"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
|
||||
}
|
140
data/session.py
140
data/session.py
@ -8,6 +8,13 @@
|
||||
|
||||
from contextlib import contextmanager
|
||||
from config.database import DBConfig, CacheConfig
|
||||
from config.component_config import (
|
||||
COMPONENT_CATEGORIES,
|
||||
get_component_types,
|
||||
get_system_components,
|
||||
CACHE_EXPIRE_TIME,
|
||||
CACHE_KEYS
|
||||
)
|
||||
|
||||
def get_session():
|
||||
"""
|
||||
@ -18,6 +25,19 @@ def get_session():
|
||||
"""
|
||||
return DBConfig.get_session()
|
||||
|
||||
def get_db():
|
||||
"""
|
||||
获取数据库会话生成器
|
||||
|
||||
Yields:
|
||||
Session: 数据库会话对象
|
||||
"""
|
||||
db = get_session()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@contextmanager
|
||||
def session_scope():
|
||||
"""
|
||||
@ -60,18 +80,8 @@ def _initialize_component_categories():
|
||||
if ComponentCategory.query.count() > 0:
|
||||
return
|
||||
|
||||
# 定义基础组件类别
|
||||
categories = [
|
||||
{"name": "基础组件", "code": "basic", "description": "基础功能组件", "sort_order": 1},
|
||||
{"name": "流程组件", "code": "flow", "description": "流程控制组件", "sort_order": 2},
|
||||
{"name": "机器人组件", "code": "robot", "description": "机器人控制组件", "sort_order": 3},
|
||||
{"name": "库位组件", "code": "storage", "description": "库位管理组件", "sort_order": 4},
|
||||
{"name": "设备组件", "code": "device", "description": "设备控制组件", "sort_order": 5},
|
||||
{"name": "HTTP组件", "code": "http", "description": "HTTP请求组件", "sort_order": 6},
|
||||
{"name": "脚本组件", "code": "script", "description": "脚本执行组件", "sort_order": 7},
|
||||
{"name": "子任务组件", "code": "subtask", "description": "子任务组件", "sort_order": 8},
|
||||
{"name": "任务组件", "code": "task", "description": "任务管理组件", "sort_order": 9}
|
||||
]
|
||||
# 从配置文件获取组件类别
|
||||
categories = COMPONENT_CATEGORIES
|
||||
|
||||
# 批量创建
|
||||
with session_scope() as session:
|
||||
@ -93,34 +103,8 @@ def _initialize_component_types():
|
||||
# 获取组件类别
|
||||
categories = {category.code: category.id for category in ComponentCategory.query.all()}
|
||||
|
||||
# 定义基础组件类型
|
||||
types = [
|
||||
# 基础组件
|
||||
{"name": "变量赋值", "code": "variable_assign", "category_id": categories["basic"], "description": "为变量赋值", "icon": "variable", "sort_order": 1},
|
||||
{"name": "条件判断", "code": "condition", "category_id": categories["basic"], "description": "条件判断", "icon": "condition", "sort_order": 2},
|
||||
{"name": "延时等待", "code": "delay", "category_id": categories["basic"], "description": "延时等待", "icon": "delay", "sort_order": 3},
|
||||
|
||||
# 流程组件
|
||||
{"name": "If条件", "code": "if", "category_id": categories["flow"], "description": "If条件判断", "icon": "if", "sort_order": 1},
|
||||
{"name": "If-Else条件", "code": "if_else", "category_id": categories["flow"], "description": "If-Else条件判断", "icon": "if_else", "sort_order": 2},
|
||||
{"name": "循环", "code": "loop", "category_id": categories["flow"], "description": "循环执行", "icon": "loop", "sort_order": 3},
|
||||
{"name": "并行执行", "code": "parallel", "category_id": categories["flow"], "description": "并行执行多个分支", "icon": "parallel", "sort_order": 4},
|
||||
|
||||
# 机器人组件
|
||||
{"name": "选择机器人", "code": "select_robot", "category_id": categories["robot"], "description": "选择执行机器人", "icon": "robot", "sort_order": 1},
|
||||
{"name": "机器人移动", "code": "robot_move", "category_id": categories["robot"], "description": "控制机器人移动", "icon": "move", "sort_order": 2},
|
||||
{"name": "获取机器人状态", "code": "robot_status", "category_id": categories["robot"], "description": "获取机器人状态", "icon": "status", "sort_order": 3},
|
||||
|
||||
# HTTP组件
|
||||
{"name": "HTTP请求", "code": "http_request", "category_id": categories["http"], "description": "发送HTTP请求", "icon": "http", "sort_order": 1},
|
||||
{"name": "API调用", "code": "api_call", "category_id": categories["http"], "description": "调用系统API", "icon": "api", "sort_order": 2},
|
||||
|
||||
# 脚本组件
|
||||
{"name": "JavaScript脚本", "code": "javascript", "category_id": categories["script"], "description": "执行JavaScript脚本", "icon": "script", "sort_order": 1},
|
||||
|
||||
# 子任务组件
|
||||
{"name": "执行子任务", "code": "execute_subtask", "category_id": categories["subtask"], "description": "执行子任务", "icon": "subtask", "sort_order": 1}
|
||||
]
|
||||
# 从配置文件获取组件类型
|
||||
types = get_component_types(categories)
|
||||
|
||||
# 批量创建
|
||||
with session_scope() as session:
|
||||
@ -142,74 +126,8 @@ def _initialize_system_components():
|
||||
# 获取组件类型
|
||||
types = {component_type.code: component_type.id for component_type in ComponentType.query.all()}
|
||||
|
||||
# 定义系统组件
|
||||
components = [
|
||||
{
|
||||
"name": "变量赋值",
|
||||
"code": "variable_assign",
|
||||
"type_id": types["variable_assign"],
|
||||
"description": "为变量赋值",
|
||||
"is_system": True,
|
||||
"config_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"variable_name": {"type": "string", "title": "变量名"},
|
||||
"value_type": {"type": "string", "enum": ["string", "number", "boolean", "object"], "title": "值类型"},
|
||||
"value": {"type": "string", "title": "值"}
|
||||
},
|
||||
"required": ["variable_name", "value_type", "value"]
|
||||
},
|
||||
"input_schema": {},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {"type": "boolean", "title": "执行结果"}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "条件判断",
|
||||
"code": "condition",
|
||||
"type_id": types["condition"],
|
||||
"description": "条件判断",
|
||||
"is_system": True,
|
||||
"config_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"condition": {"type": "string", "title": "条件表达式"}
|
||||
},
|
||||
"required": ["condition"]
|
||||
},
|
||||
"input_schema": {},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {"type": "boolean", "title": "判断结果"}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "If条件",
|
||||
"code": "if",
|
||||
"type_id": types["if"],
|
||||
"description": "If条件判断",
|
||||
"is_system": True,
|
||||
"config_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"condition": {"type": "string", "title": "条件表达式"}
|
||||
},
|
||||
"required": ["condition"]
|
||||
},
|
||||
"input_schema": {},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {"type": "boolean", "title": "判断结果"}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
# 从配置文件获取系统组件
|
||||
components = get_system_components(types)
|
||||
|
||||
# 批量创建
|
||||
with session_scope() as session:
|
||||
@ -234,7 +152,7 @@ def _cache_component_categories():
|
||||
} for category in categories}
|
||||
|
||||
# 使用Redis缓存
|
||||
CacheConfig.set("component_categories", categories_dict, expire=86400) # 缓存24小时
|
||||
CacheConfig.set(CACHE_KEYS["COMPONENT_CATEGORIES"], categories_dict, expire=CACHE_EXPIRE_TIME)
|
||||
|
||||
def _cache_component_types():
|
||||
"""缓存组件类型"""
|
||||
@ -252,7 +170,7 @@ def _cache_component_types():
|
||||
} for type in types}
|
||||
|
||||
# 使用Redis缓存
|
||||
CacheConfig.set("component_types", types_dict, expire=86400) # 缓存24小时
|
||||
CacheConfig.set(CACHE_KEYS["COMPONENT_TYPES"], types_dict, expire=CACHE_EXPIRE_TIME)
|
||||
|
||||
def _cache_system_components():
|
||||
"""缓存系统组件"""
|
||||
@ -271,4 +189,4 @@ def _cache_system_components():
|
||||
} for component in components}
|
||||
|
||||
# 使用Redis缓存
|
||||
CacheConfig.set("system_components", components_dict, expire=86400) # 缓存24小时
|
||||
CacheConfig.set(CACHE_KEYS["SYSTEM_COMPONENTS"], components_dict, expire=CACHE_EXPIRE_TIME)
|
||||
|
88
docker-compose.yml
Normal file
88
docker-compose.yml
Normal file
@ -0,0 +1,88 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# 应用服务
|
||||
app:
|
||||
build: .
|
||||
container_name: tianfeng-task
|
||||
restart: always
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- TIANFENG_ENV=default
|
||||
- DEBUG=false
|
||||
- DB_HOST=db
|
||||
- DB_PORT=3306
|
||||
- DB_USER=root
|
||||
- DB_PASSWORD=password
|
||||
- DB_NAME=tianfeng_task
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_DB=0
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./logs:/app/logs
|
||||
networks:
|
||||
- tianfeng-network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
# MySQL数据库服务
|
||||
db:
|
||||
image: mysql:8.0
|
||||
container_name: tianfeng-mysql
|
||||
restart: always
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=password
|
||||
- MYSQL_DATABASE=tianfeng_task
|
||||
- MYSQL_USER=tianfeng
|
||||
- MYSQL_PASSWORD=tianfeng
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- mysql-data:/var/lib/mysql
|
||||
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
networks:
|
||||
- tianfeng-network
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$$MYSQL_ROOT_PASSWORD"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
# Redis缓存服务
|
||||
redis:
|
||||
image: redis:6.2-alpine
|
||||
container_name: tianfeng-redis
|
||||
restart: always
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
command: redis-server --appendonly yes
|
||||
networks:
|
||||
- tianfeng-network
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
volumes:
|
||||
mysql-data:
|
||||
redis-data:
|
||||
|
||||
networks:
|
||||
tianfeng-network:
|
||||
driver: bridge
|
File diff suppressed because it is too large
Load Diff
26
scripts/docker-entrypoint.sh
Normal file
26
scripts/docker-entrypoint.sh
Normal file
@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# 等待数据库服务就绪
|
||||
echo "等待数据库服务就绪..."
|
||||
until nc -z ${DB_HOST} ${DB_PORT}; do
|
||||
echo "数据库服务未就绪 - 等待..."
|
||||
sleep 2
|
||||
done
|
||||
echo "数据库服务已就绪!"
|
||||
|
||||
# 等待Redis服务就绪
|
||||
echo "等待Redis服务就绪..."
|
||||
until nc -z ${REDIS_HOST} ${REDIS_PORT}; do
|
||||
echo "Redis服务未就绪 - 等待..."
|
||||
sleep 2
|
||||
done
|
||||
echo "Redis服务已就绪!"
|
||||
|
||||
# 初始化数据库(如果需要)
|
||||
echo "初始化数据库..."
|
||||
python -c "from app import init_database; init_database()"
|
||||
|
||||
# 启动应用
|
||||
echo "启动应用..."
|
||||
exec "$@"
|
83
scripts/fix_instance_ids.py
Normal file
83
scripts/fix_instance_ids.py
Normal file
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
任务实例ID修复脚本
|
||||
为所有现有的任务实例记录生成并设置instance_id
|
||||
|
||||
用法:
|
||||
python scripts/fix_instance_ids.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import uuid
|
||||
from sqlalchemy import text
|
||||
|
||||
# 添加项目根目录到系统路径
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
# 导入项目模块
|
||||
from data.session import get_db
|
||||
from data.models.task_instance import TaskInstance
|
||||
|
||||
# 设置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
handlers=[
|
||||
logging.StreamHandler(sys.stdout), # 输出到控制台
|
||||
logging.FileHandler("fix_instance_ids.log") # 输出到文件
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger("fix_instance_ids")
|
||||
|
||||
def fix_instance_ids():
|
||||
"""为所有现有的任务实例记录生成并设置instance_id"""
|
||||
try:
|
||||
# 获取数据库会话
|
||||
db = next(get_db())
|
||||
|
||||
# 查询所有没有instance_id的任务实例记录
|
||||
instances = db.query(TaskInstance).filter(
|
||||
(TaskInstance.instance_id == None) |
|
||||
(TaskInstance.instance_id == '')
|
||||
).all()
|
||||
|
||||
logger.info(f"发现 {len(instances)} 条没有instance_id的任务实例记录")
|
||||
|
||||
# 为每条记录生成并设置instance_id
|
||||
for instance in instances:
|
||||
instance_id = str(uuid.uuid4())
|
||||
instance.instance_id = instance_id
|
||||
logger.info(f"为任务实例 ID={instance.id} 设置instance_id={instance_id}")
|
||||
|
||||
# 提交更改
|
||||
db.commit()
|
||||
logger.info("所有任务实例记录的instance_id已更新")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"修复任务实例ID失败: {str(e)}")
|
||||
if 'db' in locals():
|
||||
db.rollback()
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
logger.info("开始修复任务实例ID...")
|
||||
|
||||
# 修复任务实例ID
|
||||
success = fix_instance_ids()
|
||||
|
||||
if success:
|
||||
logger.info("任务实例ID修复成功!")
|
||||
else:
|
||||
logger.error("任务实例ID修复失败!")
|
||||
|
||||
# 根据修复结果设置退出码
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
13
scripts/init-db.sql
Normal file
13
scripts/init-db.sql
Normal file
@ -0,0 +1,13 @@
|
||||
-- 创建数据库(如果不存在)
|
||||
CREATE DATABASE IF NOT EXISTS tianfeng_task CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- 使用数据库
|
||||
USE tianfeng_task;
|
||||
|
||||
-- 创建用户并授权(如果不存在)
|
||||
CREATE USER IF NOT EXISTS 'tianfeng'@'%' IDENTIFIED BY 'tianfeng';
|
||||
GRANT ALL PRIVILEGES ON tianfeng_task.* TO 'tianfeng'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
-- 初始化基础数据可以在这里添加
|
||||
-- 注意:大部分表结构会由SQLAlchemy自动创建,这里主要是为了初始化一些基础数据
|
36
scripts/logs/migration_upgrade_20250317.log
Normal file
36
scripts/logs/migration_upgrade_20250317.log
Normal file
@ -0,0 +1,36 @@
|
||||
INFO [alembic.runtime.migration] Context impl MySQLImpl.
|
||||
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
|
||||
|
||||
|
||||
================================================================================
|
||||
执行时间: 2025-03-17 16:48:29
|
||||
执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
================================================================================
|
||||
|
||||
INFO [alembic.runtime.migration] Context impl MySQLImpl.
|
||||
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
|
||||
|
||||
|
||||
================================================================================
|
||||
执行时间: 2025-03-17 16:50:12
|
||||
执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
================================================================================
|
||||
|
||||
INFO [alembic.runtime.migration] Context impl MySQLImpl.
|
||||
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
|
||||
|
||||
|
||||
================================================================================
|
||||
执行时间: 2025-03-17 16:51:14
|
||||
执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
================================================================================
|
||||
|
||||
INFO [alembic.runtime.migration] Context impl MySQLImpl.
|
||||
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
|
||||
|
||||
|
||||
================================================================================
|
||||
执行时间: 2025-03-17 16:51:54
|
||||
执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
================================================================================
|
||||
|
186
scripts/logs/tianfeng_task.log
Normal file
186
scripts/logs/tianfeng_task.log
Normal file
@ -0,0 +1,186 @@
|
||||
2025-03-17 16:48:29,929 - migration - INFO - 运行迁移,参数: {'revision': None, 'downgrade': False, 'verbose': False, 'message': None, 'branch': None, 'history': False, 'current': False, 'show': False, 'table': None, 'list_tables': False}
|
||||
2025-03-17 16:48:29,929 - migration - INFO - 使用已存在的临时配置文件: D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp
|
||||
2025-03-17 16:48:29,930 - migration - INFO - 命令参数: {'revision': None, 'downgrade': False, 'verbose': False, 'message': None, 'branch': None, 'history': False, 'current': False, 'show': False, 'table': None, 'list_tables': False}
|
||||
2025-03-17 16:48:29,930 - migration - INFO - 执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
2025-03-17 16:48:29,930 - migration - INFO - 工作目录: D:\jsw_code\project\tianfeng_task
|
||||
2025-03-17 16:48:29,930 - migration - INFO - Python 编码: utf-8
|
||||
2025-03-17 16:48:29,930 - migration - INFO - 文件系统编码: utf-8
|
||||
2025-03-17 16:48:29,930 - migration - INFO - 系统默认编码: cp936
|
||||
2025-03-17 16:48:29,930 - migration - INFO - 正在执行迁移,日志将写入 logs\migration_upgrade_20250317.log...
|
||||
2025-03-17 16:48:30,704 - migration - INFO - 命令执行状态: 成功
|
||||
2025-03-17 16:48:30,704 - migration - INFO - 输出(最后部分):
|
||||
INFO [alembic.runtime.migration] Context impl MySQLImpl.
|
||||
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
|
||||
|
||||
|
||||
================================================================================
|
||||
执行时间: 2025-03-17 16:48:29
|
||||
执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
================================================================================
|
||||
|
||||
|
||||
2025-03-17 16:50:12,449 - migration - INFO - 运行迁移,参数: {'revision': None, 'downgrade': False, 'verbose': False, 'message': None, 'branch': None, 'history': False, 'current': False, 'show': False, 'table': None, 'list_tables': False}
|
||||
2025-03-17 16:50:12,450 - migration - INFO - 使用已存在的临时配置文件: D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp
|
||||
2025-03-17 16:50:12,450 - migration - INFO - 命令参数: {'revision': None, 'downgrade': False, 'verbose': False, 'message': None, 'branch': None, 'history': False, 'current': False, 'show': False, 'table': None, 'list_tables': False}
|
||||
2025-03-17 16:50:12,450 - migration - INFO - 执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
2025-03-17 16:50:12,450 - migration - INFO - 工作目录: D:\jsw_code\project\tianfeng_task
|
||||
2025-03-17 16:50:12,450 - migration - INFO - Python 编码: utf-8
|
||||
2025-03-17 16:50:12,450 - migration - INFO - 文件系统编码: utf-8
|
||||
2025-03-17 16:50:12,450 - migration - INFO - 系统默认编码: cp936
|
||||
2025-03-17 16:50:12,450 - migration - INFO - 正在执行迁移,日志将写入 logs\migration_upgrade_20250317.log...
|
||||
2025-03-17 16:50:13,150 - migration - INFO - 命令执行状态: 成功
|
||||
2025-03-17 16:50:13,150 - migration - INFO - 输出(最后部分):
|
||||
INFO [alembic.runtime.migration] Context impl MySQLImpl.
|
||||
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
|
||||
|
||||
|
||||
================================================================================
|
||||
执行时间: 2025-03-17 16:48:29
|
||||
执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
================================================================================
|
||||
|
||||
INFO [alembic.runtime.migration] Context impl MySQLImpl.
|
||||
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
|
||||
|
||||
|
||||
================================================================================
|
||||
执行时间: 2025-03-17 16:50:12
|
||||
执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
================================================================================
|
||||
|
||||
|
||||
2025-03-17 16:51:14,892 - migration - INFO - 运行迁移,参数: {'revision': None, 'downgrade': False, 'verbose': False, 'message': None, 'branch': None, 'history': False, 'current': False, 'show': False, 'table': None, 'list_tables': False}
|
||||
2025-03-17 16:51:14,892 - migration - INFO - 使用已存在的临时配置文件: D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp
|
||||
2025-03-17 16:51:14,892 - migration - INFO - 命令参数: {'revision': None, 'downgrade': False, 'verbose': False, 'message': None, 'branch': None, 'history': False, 'current': False, 'show': False, 'table': None, 'list_tables': False}
|
||||
2025-03-17 16:51:14,892 - migration - INFO - 执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
2025-03-17 16:51:14,908 - migration - INFO - 工作目录: D:\jsw_code\project\tianfeng_task
|
||||
2025-03-17 16:51:14,908 - migration - INFO - Python 编码: utf-8
|
||||
2025-03-17 16:51:14,908 - migration - INFO - 文件系统编码: utf-8
|
||||
2025-03-17 16:51:14,908 - migration - INFO - 系统默认编码: cp936
|
||||
2025-03-17 16:51:14,908 - migration - INFO - 正在执行迁移,日志将写入 logs\migration_upgrade_20250317.log...
|
||||
2025-03-17 16:51:15,435 - migration - INFO - 命令执行状态: 成功
|
||||
2025-03-17 16:51:15,435 - migration - INFO - 输出(最后部分):
|
||||
INFO [alembic.runtime.migration] Context impl MySQLImpl.
|
||||
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
|
||||
|
||||
|
||||
================================================================================
|
||||
执行时间: 2025-03-17 16:48:29
|
||||
执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
================================================================================
|
||||
|
||||
INFO [alembic.runtime.migration] Context impl MySQLImpl.
|
||||
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
|
||||
|
||||
|
||||
================================================================================
|
||||
执行时间: 2025-03-17 16:50:12
|
||||
执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
================================================================================
|
||||
|
||||
INFO [alembic.runtime.migration] Context impl MySQLImpl.
|
||||
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
|
||||
|
||||
|
||||
================================================================================
|
||||
执行时间: 2025-03-17 16:51:14
|
||||
执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
================================================================================
|
||||
|
||||
|
||||
2025-03-17 16:51:54,260 - migration - INFO - 运行迁移,参数: {'revision': None, 'downgrade': False, 'verbose': False, 'message': None, 'branch': None, 'history': False, 'current': False, 'show': False, 'table': None, 'list_tables': False}
|
||||
2025-03-17 16:51:54,260 - migration - INFO - 使用已存在的临时配置文件: D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp
|
||||
2025-03-17 16:51:54,260 - migration - INFO - 命令参数: {'revision': None, 'downgrade': False, 'verbose': False, 'message': None, 'branch': None, 'history': False, 'current': False, 'show': False, 'table': None, 'list_tables': False}
|
||||
2025-03-17 16:51:54,260 - migration - INFO - 执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
2025-03-17 16:51:54,260 - migration - INFO - 工作目录: D:\jsw_code\project\tianfeng_task
|
||||
2025-03-17 16:51:54,260 - migration - INFO - Python 编码: utf-8
|
||||
2025-03-17 16:51:54,260 - migration - INFO - 文件系统编码: utf-8
|
||||
2025-03-17 16:51:54,260 - migration - INFO - 系统默认编码: cp936
|
||||
2025-03-17 16:51:54,260 - migration - INFO - 正在执行迁移,日志将写入 logs\migration_upgrade_20250317.log...
|
||||
2025-03-17 16:51:54,809 - migration - INFO - 命令执行状态: 成功
|
||||
2025-03-17 16:51:54,809 - migration - INFO - 输出(最后部分):
|
||||
INFO [alembic.runtime.migration] Context impl MySQLImpl.
|
||||
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
|
||||
|
||||
|
||||
================================================================================
|
||||
执行时间: 2025-03-17 16:48:29
|
||||
执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
================================================================================
|
||||
|
||||
INFO [alembic.runtime.migration] Context impl MySQLImpl.
|
||||
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
|
||||
|
||||
|
||||
================================================================================
|
||||
执行时间: 2025-03-17 16:50:12
|
||||
执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
================================================================================
|
||||
|
||||
INFO [alembic.runtime.migration] Context impl MySQLImpl.
|
||||
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
|
||||
|
||||
|
||||
================================================================================
|
||||
执行时间: 2025-03-17 16:51:14
|
||||
执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
================================================================================
|
||||
|
||||
INFO [alembic.runtime.migration] Context impl MySQLImpl.
|
||||
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
|
||||
|
||||
|
||||
================================================================================
|
||||
执行时间: 2025-03-17 16:51:54
|
||||
执行命令: alembic -c D:\jsw_code\project\tianfeng_task\migrations\alembic.ini.tmp upgrade head
|
||||
================================================================================
|
||||
|
||||
|
||||
2025-03-17 17:11:12,595 - migration - INFO - 开始执行数据库自动迁移...
|
||||
2025-03-17 17:11:12,595 - utils.db_migration - INFO - 开始数据库迁移
|
||||
2025-03-17 17:11:12,657 - utils.db_migration - INFO - 发现 23 个模型
|
||||
2025-03-17 17:11:12,669 - utils.db_migration - INFO - 表 subtasks 不存在,准备创建
|
||||
2025-03-17 17:11:12,733 - utils.db_migration - INFO - 创建表 subtasks 成功
|
||||
2025-03-17 17:11:12,734 - utils.db_migration - INFO - 表 subtask_versions 不存在,准备创建
|
||||
2025-03-17 17:11:12,785 - utils.db_migration - INFO - 创建表 subtask_versions 成功
|
||||
2025-03-17 17:11:12,792 - utils.db_migration - INFO - 表 task_flow_connections 不存在,准备创建
|
||||
2025-03-17 17:11:12,794 - utils.db_migration - ERROR - 创建表 task_flow_connections 失败: (pymysql.err.OperationalError) (1824, "Failed to open the referenced table 'task_flow_nodes'")
|
||||
[SQL:
|
||||
CREATE TABLE task_flow_connections (
|
||||
task_id INTEGER NOT NULL COMMENT '任务ID',
|
||||
version_id INTEGER NOT NULL COMMENT '任务版本ID',
|
||||
source_node_id INTEGER NOT NULL COMMENT '源节点ID',
|
||||
target_node_id INTEGER NOT NULL COMMENT '目标节点ID',
|
||||
label VARCHAR(100) COMMENT '连接标签',
|
||||
`condition` VARCHAR(500) COMMENT '连接条件',
|
||||
config JSON COMMENT '连接配置',
|
||||
is_default BOOL COMMENT '是否为默认连接',
|
||||
id INTEGER NOT NULL COMMENT '主键ID' AUTO_INCREMENT,
|
||||
created_at DATETIME COMMENT '创建时间',
|
||||
updated_at DATETIME COMMENT '更新时间',
|
||||
is_deleted BOOL COMMENT '是否删除(软删除标记)',
|
||||
PRIMARY KEY (id),
|
||||
FOREIGN KEY(task_id) REFERENCES tasks (id),
|
||||
FOREIGN KEY(version_id) REFERENCES task_versions (id),
|
||||
FOREIGN KEY(source_node_id) REFERENCES task_flow_nodes (id),
|
||||
FOREIGN KEY(target_node_id) REFERENCES task_flow_nodes (id)
|
||||
)
|
||||
|
||||
]
|
||||
(Background on this error at: https://sqlalche.me/e/20/e3q8)
|
||||
2025-03-17 17:11:12,794 - utils.db_migration - INFO - 表 task_flow_nodes 不存在,准备创建
|
||||
2025-03-17 17:11:12,864 - utils.db_migration - INFO - 创建表 task_flow_nodes 成功
|
||||
2025-03-17 17:11:12,867 - utils.db_migration - INFO - 表 task_import_exports 不存在,准备创建
|
||||
2025-03-17 17:11:12,915 - utils.db_migration - INFO - 创建表 task_import_exports 成功
|
||||
2025-03-17 17:11:12,916 - utils.db_migration - INFO - 表 task_inputs 不存在,准备创建
|
||||
2025-03-17 17:11:12,968 - utils.db_migration - INFO - 创建表 task_inputs 成功
|
||||
2025-03-17 17:11:12,969 - utils.db_migration - INFO - 表 task_input_values 不存在,准备创建
|
||||
2025-03-17 17:11:13,027 - utils.db_migration - INFO - 创建表 task_input_values 成功
|
||||
2025-03-17 17:11:13,028 - utils.db_migration - INFO - 发现新增列 task_instances.instance_id,准备添加
|
||||
2025-03-17 17:11:13,029 - utils.db_migration - INFO - 执行SQL: ALTER TABLE task_instances ADD COLUMN instance_id VARCHAR(36) NOT NULL
|
||||
2025-03-17 17:11:13,061 - utils.db_migration - INFO - 添加列 task_instances.instance_id 成功
|
||||
2025-03-17 17:11:13,074 - utils.db_migration - INFO - 表 user_operations 不存在,准备创建
|
||||
2025-03-17 17:11:13,111 - utils.db_migration - INFO - 创建表 user_operations 成功
|
||||
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 - 数据库迁移成功完成!
|
@ -2,340 +2,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
数据库迁移执行脚本
|
||||
数据库迁移脚本
|
||||
用于自动检测模型变更并应用到数据库
|
||||
|
||||
用法:
|
||||
python scripts/run_migration.py [--revision 版本号]
|
||||
python scripts/run_migration.py [--verbose]
|
||||
|
||||
示例:
|
||||
# 升级到最新版本
|
||||
# 执行自动迁移
|
||||
python scripts/run_migration.py
|
||||
|
||||
# 升级到指定版本
|
||||
python scripts/run_migration.py --revision 001
|
||||
|
||||
# 降级到指定版本
|
||||
python scripts/run_migration.py --revision 001 --downgrade
|
||||
|
||||
# 生成新的迁移脚本
|
||||
python scripts/run_migration.py --generate "添加新字段"
|
||||
|
||||
# 为特定表生成迁移脚本
|
||||
python scripts/run_migration.py --generate "为用户表添加邮箱字段" --table users
|
||||
|
||||
# 为多个表生成迁移脚本
|
||||
python scripts/run_migration.py --generate "添加审计字段" --table users,orders,products
|
||||
# 显示详细日志
|
||||
python scripts/run_migration.py --verbose
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import shutil
|
||||
import tempfile
|
||||
import locale
|
||||
import datetime
|
||||
|
||||
# 将项目根目录添加到 Python 路径
|
||||
root_dir = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(root_dir))
|
||||
# 添加项目根目录到系统路径
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
# 导入项目日志模块
|
||||
from utils.logger import get_logger, setup_logger
|
||||
from utils.logger import setup_logger
|
||||
from utils.db_migration import run_migration, DBMigration
|
||||
|
||||
# 设置日志
|
||||
setup_logger()
|
||||
logger = get_logger('migration')
|
||||
|
||||
def create_ascii_config(original_config_path):
|
||||
"""
|
||||
创建一个 ASCII 编码的配置文件副本
|
||||
|
||||
Args:
|
||||
original_config_path (Path): 原始配置文件路径
|
||||
|
||||
Returns:
|
||||
Path: 临时配置文件路径
|
||||
"""
|
||||
# 如果临时文件已存在,直接使用
|
||||
temp_config_path = original_config_path.with_suffix('.ini.tmp')
|
||||
if temp_config_path.exists():
|
||||
logger.info(f"使用已存在的临时配置文件: {temp_config_path}")
|
||||
return temp_config_path
|
||||
|
||||
# 创建临时文件
|
||||
logger.info(f"创建 ASCII 编码的临时配置文件: {temp_config_path}")
|
||||
|
||||
# 复制配置文件内容,去除中文注释
|
||||
with open(temp_config_path, 'w', encoding='ascii', errors='ignore') as temp_file:
|
||||
temp_file.write("""
|
||||
# Alembic Configuration File
|
||||
# This file contains basic configuration for Alembic
|
||||
|
||||
[alembic]
|
||||
# Path to migration scripts
|
||||
script_location = migrations
|
||||
|
||||
# Template uses jinja2 format
|
||||
output_encoding = utf-8
|
||||
|
||||
# Database connection configuration
|
||||
# In practice, this value will be overridden by the configuration in env.py
|
||||
sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
||||
""")
|
||||
|
||||
return temp_config_path
|
||||
|
||||
def run_migration_command(command, args):
|
||||
"""
|
||||
执行迁移命令
|
||||
|
||||
Args:
|
||||
command (str): 命令名称,如 'upgrade', 'downgrade', 'revision'
|
||||
args (argparse.Namespace): 命令行参数
|
||||
|
||||
Returns:
|
||||
bool: 是否成功执行命令
|
||||
"""
|
||||
# 获取项目根目录
|
||||
root_dir = Path(__file__).parent.parent
|
||||
|
||||
# 原始配置文件路径
|
||||
original_config_path = root_dir / "migrations" / "alembic.ini"
|
||||
|
||||
# 创建 ASCII 编码的临时配置文件
|
||||
temp_config_path = create_ascii_config(original_config_path)
|
||||
|
||||
# 构建命令
|
||||
cmd = ["alembic", "-c", str(temp_config_path)]
|
||||
|
||||
if args.verbose:
|
||||
cmd.append("--verbose")
|
||||
|
||||
# 打印所有参数,帮助调试
|
||||
logger.info(f"命令参数: {vars(args)}")
|
||||
|
||||
if command == 'upgrade':
|
||||
cmd.extend(["upgrade", args.revision or "head"])
|
||||
elif command == 'downgrade':
|
||||
cmd.extend(["downgrade", args.revision or "base"])
|
||||
elif command == 'revision':
|
||||
logger.info(f"生成迁移脚本,描述: {args.message}")
|
||||
cmd.extend(["revision", "--autogenerate", "-m", args.message])
|
||||
|
||||
# 如果指定了表名,创建一个临时的 env.py 文件,只包含指定的表
|
||||
if args.table:
|
||||
# 将表名列表转换为 Python 列表字符串
|
||||
tables = [f"'{table.strip()}'" for table in args.table.split(',')]
|
||||
tables_str = f"[{', '.join(tables)}]"
|
||||
|
||||
# 创建临时环境变量,传递表名列表
|
||||
os.environ["ALEMBIC_TABLES"] = args.table
|
||||
logger.info(f"指定迁移表: {args.table}")
|
||||
|
||||
if args.branch:
|
||||
cmd.extend(["--branch", args.branch])
|
||||
elif command == 'history':
|
||||
cmd.append("history")
|
||||
if args.verbose:
|
||||
cmd.append("-v")
|
||||
elif command == 'current':
|
||||
cmd.append("current")
|
||||
elif command == 'show':
|
||||
cmd.extend(["show", args.revision or "head"])
|
||||
elif command == 'list_tables':
|
||||
# 这不是 alembic 命令,而是我们自定义的命令
|
||||
return list_database_tables()
|
||||
else:
|
||||
logger.error(f"未知命令: {command}")
|
||||
return False
|
||||
|
||||
# 执行命令
|
||||
logger.info(f"执行命令: {' '.join(cmd)}")
|
||||
logger.info(f"工作目录: {root_dir}")
|
||||
logger.info(f"Python 编码: {sys.getdefaultencoding()}")
|
||||
logger.info(f"文件系统编码: {sys.getfilesystemencoding()}")
|
||||
logger.info(f"系统默认编码: {locale.getpreferredencoding()}")
|
||||
|
||||
# 设置环境变量,确保使用 UTF-8 编码
|
||||
env = os.environ.copy()
|
||||
env["PYTHONIOENCODING"] = "utf-8"
|
||||
|
||||
# 创建命令输出日志文件 - 使用项目日志目录
|
||||
from config.settings import LogConfig
|
||||
log_config = LogConfig.as_dict()
|
||||
log_dir = Path(os.path.dirname(log_config["file"]))
|
||||
log_dir.mkdir(exist_ok=True)
|
||||
|
||||
# 使用日期和命令类型命名日志文件
|
||||
today = datetime.datetime.now().strftime('%Y%m%d')
|
||||
log_file = log_dir / f"migration_{command}_{today}.log"
|
||||
|
||||
try:
|
||||
# 使用直接执行命令的方式,避免 subprocess 的编码问题
|
||||
logger.info(f"正在执行迁移,日志将写入 {log_file}...")
|
||||
|
||||
# 使用 subprocess.Popen 而不是 subprocess.run
|
||||
with open(log_file, "a", encoding="utf-8") as f_out:
|
||||
# 添加分隔线和时间戳
|
||||
f_out.write(f"\n\n{'='*80}\n")
|
||||
f_out.write(f"执行时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||||
f_out.write(f"执行命令: {' '.join(cmd)}\n")
|
||||
f_out.write(f"{'='*80}\n\n")
|
||||
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
cwd=str(root_dir),
|
||||
env=env,
|
||||
stdout=f_out,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace" # 使用 replace 策略处理无法解码的字符
|
||||
)
|
||||
process.wait()
|
||||
|
||||
# 读取日志文件
|
||||
with open(log_file, "r", encoding="utf-8") as f:
|
||||
# 只读取最后 50 行,避免日志过长
|
||||
lines = f.readlines()
|
||||
output = ''.join(lines[-50:]) if len(lines) > 50 else ''.join(lines)
|
||||
|
||||
logger.info(f"命令执行状态: {'成功' if process.returncode == 0 else '失败'}")
|
||||
logger.info(f"输出(最后部分):\n{output}")
|
||||
|
||||
# 清理环境变量
|
||||
if 'ALEMBIC_TABLES' in os.environ:
|
||||
del os.environ['ALEMBIC_TABLES']
|
||||
|
||||
return process.returncode == 0
|
||||
except Exception as e:
|
||||
logger.error(f"执行命令时发生错误: {e}", exc_info=True)
|
||||
|
||||
# 清理环境变量
|
||||
if 'ALEMBIC_TABLES' in os.environ:
|
||||
del os.environ['ALEMBIC_TABLES']
|
||||
|
||||
return False
|
||||
|
||||
def list_database_tables():
|
||||
"""
|
||||
列出数据库中的所有表
|
||||
|
||||
Returns:
|
||||
bool: 是否成功执行命令
|
||||
"""
|
||||
try:
|
||||
# 导入数据库配置
|
||||
from config.database import DBConfig
|
||||
|
||||
# 获取数据库连接
|
||||
engine = DBConfig.engine
|
||||
|
||||
# 获取所有表名
|
||||
from sqlalchemy import inspect
|
||||
inspector = inspect(engine)
|
||||
tables = inspector.get_table_names()
|
||||
|
||||
logger.info("数据库中的表:")
|
||||
for i, table in enumerate(tables, 1):
|
||||
logger.info(f"{i}. {table}")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"获取数据库表时发生错误: {e}", exc_info=True)
|
||||
return False
|
||||
|
||||
def run_migration(args):
|
||||
"""
|
||||
执行数据库迁移
|
||||
|
||||
Args:
|
||||
args (argparse.Namespace): 命令行参数
|
||||
|
||||
Returns:
|
||||
bool: 是否成功执行迁移
|
||||
"""
|
||||
# 打印所有参数,帮助调试
|
||||
logger.info(f"运行迁移,参数: {vars(args)}")
|
||||
|
||||
if args.list_tables:
|
||||
# 列出数据库中的所有表
|
||||
return run_migration_command('list_tables', args)
|
||||
elif args.message:
|
||||
# 生成新的迁移脚本
|
||||
logger.info(f"生成新的迁移脚本,描述: {args.message}")
|
||||
return run_migration_command('revision', args)
|
||||
elif args.history:
|
||||
# 显示迁移历史
|
||||
return run_migration_command('history', args)
|
||||
elif args.current:
|
||||
# 显示当前版本
|
||||
return run_migration_command('current', args)
|
||||
elif args.show:
|
||||
# 显示指定版本的详细信息
|
||||
return run_migration_command('show', args)
|
||||
elif args.downgrade:
|
||||
# 降级到指定版本
|
||||
return run_migration_command('downgrade', args)
|
||||
else:
|
||||
# 升级到指定版本
|
||||
return run_migration_command('upgrade', args)
|
||||
logger = logging.getLogger('migration')
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="执行数据库迁移")
|
||||
parser.add_argument("--revision", help="版本号,为空表示升级到最新版本")
|
||||
parser.add_argument("--downgrade", action="store_true", help="是否降级")
|
||||
"""主函数"""
|
||||
parser = argparse.ArgumentParser(description="执行数据库自动迁移")
|
||||
parser.add_argument("--verbose", "-v", action="store_true", help="显示详细日志")
|
||||
parser.add_argument("--generate", "--gen", "-m", dest="message", help="生成新的迁移脚本,需要提供描述信息")
|
||||
parser.add_argument("--branch", "-b", help="分支名称,用于生成迁移脚本")
|
||||
parser.add_argument("--history", action="store_true", help="显示迁移历史")
|
||||
parser.add_argument("--current", action="store_true", help="显示当前版本")
|
||||
parser.add_argument("--show", action="store_true", help="显示指定版本的详细信息")
|
||||
parser.add_argument("--table", "-t", help="指定要迁移的表名,多个表用逗号分隔")
|
||||
parser.add_argument("--list-tables", action="store_true", help="列出数据库中的所有表")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
success = run_migration(args)
|
||||
# 设置日志级别
|
||||
log_level = logging.DEBUG if args.verbose else logging.INFO
|
||||
logging.basicConfig(
|
||||
level=log_level,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
|
||||
logger.info("开始执行数据库自动迁移...")
|
||||
|
||||
# 运行迁移
|
||||
success = run_migration()
|
||||
|
||||
if success:
|
||||
logger.info("数据库迁移成功完成!")
|
||||
else:
|
||||
logger.error("数据库迁移失败!")
|
||||
|
||||
# 根据迁移结果设置退出码
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
BIN
services/__pycache__/common_params_service.cpython-312.pyc
Normal file
BIN
services/__pycache__/common_params_service.cpython-312.pyc
Normal file
Binary file not shown.
BIN
services/__pycache__/task_instance_service.cpython-312.pyc
Normal file
BIN
services/__pycache__/task_instance_service.cpython-312.pyc
Normal file
Binary file not shown.
92
services/common_params_service.py
Normal file
92
services/common_params_service.py
Normal file
@ -0,0 +1,92 @@
|
||||
"""
|
||||
常用参数服务模块
|
||||
提供获取各种常用参数数据的服务
|
||||
"""
|
||||
from typing import Dict, Any, List, Optional
|
||||
import json
|
||||
import random
|
||||
from datetime import datetime
|
||||
from config.component_config import CommonParamType
|
||||
|
||||
class CommonParamsService:
|
||||
"""常用参数服务类"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化服务"""
|
||||
# 模拟数据,实际项目中应该从数据库或外部API获取
|
||||
self.mock_data = {
|
||||
CommonParamType.ROBOT_ID: [
|
||||
{"id": "robot-001", "name": "机器人1号"},
|
||||
{"id": "robot-002", "name": "机器人2号"},
|
||||
{"id": "robot-003", "name": "机器人3号"}
|
||||
],
|
||||
CommonParamType.ROBOT_GROUP: [
|
||||
{"id": "group-001", "name": "机器人组A"},
|
||||
{"id": "group-002", "name": "机器人组B"},
|
||||
{"id": "group-003", "name": "机器人组C"}
|
||||
],
|
||||
CommonParamType.ROBOT_TAG: [
|
||||
{"id": "tag-001", "name": "标签1"},
|
||||
{"id": "tag-002", "name": "标签2"},
|
||||
{"id": "tag-003", "name": "标签3"}
|
||||
],
|
||||
CommonParamType.STORAGE_AREA_ID: [
|
||||
{"id": "area-001", "name": "库区1"},
|
||||
{"id": "area-002", "name": "库区2"},
|
||||
{"id": "area-003", "name": "库区3"}
|
||||
],
|
||||
CommonParamType.STORAGE_AREA: [
|
||||
{"id": "area-001", "name": "库区1", "code": "A001"},
|
||||
{"id": "area-002", "name": "库区2", "code": "A002"},
|
||||
{"id": "area-003", "name": "库区3", "code": "A003"}
|
||||
],
|
||||
CommonParamType.SITE: [
|
||||
{"id": "site-001", "name": "站点1", "code": "S001"},
|
||||
{"id": "site-002", "name": "站点2", "code": "S002"},
|
||||
{"id": "site-003", "name": "站点3", "code": "S003"}
|
||||
],
|
||||
CommonParamType.BIN_TASK: [
|
||||
{"id": "bin-001", "name": "Bin任务1"},
|
||||
{"id": "bin-002", "name": "Bin任务2"},
|
||||
{"id": "bin-003", "name": "Bin任务3"}
|
||||
],
|
||||
CommonParamType.WORKSTATION: [
|
||||
{"id": "ws-001", "name": "工位1"},
|
||||
{"id": "ws-002", "name": "工位2"},
|
||||
{"id": "ws-003", "name": "工位3"}
|
||||
],
|
||||
CommonParamType.POST: [
|
||||
{"id": "post-001", "name": "岗位1"},
|
||||
{"id": "post-002", "name": "岗位2"},
|
||||
{"id": "post-003", "name": "岗位3"}
|
||||
],
|
||||
CommonParamType.USER: [
|
||||
{"id": "user-001", "name": "用户1"},
|
||||
{"id": "user-002", "name": "用户2"},
|
||||
{"id": "user-003", "name": "用户3"}
|
||||
],
|
||||
CommonParamType.CACHE: [
|
||||
{"key": "cache-001", "value": "缓存值1"},
|
||||
{"key": "cache-002", "value": "缓存值2"},
|
||||
{"key": "cache-003", "value": "缓存值3"}
|
||||
],
|
||||
CommonParamType.BUILT_IN_FUNCTION: [
|
||||
{"name": "getCurrentTime", "description": "获取当前时间"},
|
||||
{"name": "generateUUID", "description": "生成UUID"},
|
||||
{"name": "formatDate", "description": "格式化日期"}
|
||||
]
|
||||
}
|
||||
|
||||
def get_param_data(self, param_type: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
获取指定类型的常用参数数据
|
||||
|
||||
Args:
|
||||
param_type: 参数类型
|
||||
|
||||
Returns:
|
||||
参数数据列表
|
||||
"""
|
||||
# 实际项目中,应该根据param_type调用相应的API或查询数据库
|
||||
# 这里使用模拟数据
|
||||
return self.mock_data.get(param_type, [])
|
281
services/task_instance_service.py
Normal file
281
services/task_instance_service.py
Normal file
@ -0,0 +1,281 @@
|
||||
"""
|
||||
任务实例服务模块
|
||||
提供任务实例的增删改查服务
|
||||
"""
|
||||
from typing import Dict, Any, List, Optional, Tuple
|
||||
from sqlalchemy.orm import Session
|
||||
from data.session import get_db
|
||||
from data.models.task_instance import TaskInstance, TaskInstanceStatus
|
||||
from data.models.task import Task
|
||||
from datetime import datetime
|
||||
|
||||
class TaskInstanceService:
|
||||
"""任务实例服务类"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化服务"""
|
||||
self.db = next(get_db())
|
||||
|
||||
def create_instance(self, task_id: str, name: Optional[str] = None, variables: Optional[Dict[str, Any]] = None,
|
||||
priority: int = 1, input_params: Optional[Dict[str, Any]] = None,
|
||||
block_outputs: Optional[Dict[str, Any]] = None, context_params: Optional[Dict[str, Any]] = None,
|
||||
status: TaskInstanceStatus = TaskInstanceStatus.EDITING) -> Dict[str, Any]:
|
||||
"""
|
||||
创建任务实例
|
||||
|
||||
Args:
|
||||
task_id: 任务ID
|
||||
name: 任务名称(如果为空,则从任务表中获取)
|
||||
variables: 任务变量
|
||||
priority: 任务优先级
|
||||
input_params: 任务输入参数
|
||||
block_outputs: 块输出参数
|
||||
context_params: 上下文参数
|
||||
status: 任务实例状态
|
||||
|
||||
Returns:
|
||||
任务实例字典
|
||||
"""
|
||||
# 检查任务是否存在
|
||||
task = self.db.query(Task).filter(Task.task_id == task_id, Task.is_deleted == False).first()
|
||||
if not task:
|
||||
raise ValueError(f"任务不存在: {task_id}")
|
||||
|
||||
# 如果名称为空,则使用任务表中的名称
|
||||
if not name:
|
||||
name = task.name
|
||||
|
||||
# 创建任务实例
|
||||
instance = TaskInstance(
|
||||
task_id=task_id,
|
||||
name=name,
|
||||
variables=variables or {},
|
||||
priority=priority,
|
||||
input_params=input_params or {},
|
||||
block_outputs=block_outputs or {},
|
||||
context_params=context_params or {},
|
||||
status=status
|
||||
)
|
||||
|
||||
# 保存到数据库
|
||||
self.db.add(instance)
|
||||
self.db.commit()
|
||||
self.db.refresh(instance)
|
||||
|
||||
return instance.to_dict()
|
||||
|
||||
def get_instance_by_id(self, instance_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
根据实例ID获取任务实例
|
||||
|
||||
Args:
|
||||
instance_id: 任务实例ID
|
||||
|
||||
Returns:
|
||||
任务实例字典,如果不存在则返回None
|
||||
"""
|
||||
instance = self.db.query(TaskInstance).filter(TaskInstance.instance_id == instance_id, TaskInstance.is_deleted == False).first()
|
||||
if not instance:
|
||||
return None
|
||||
|
||||
return instance.to_dict()
|
||||
|
||||
def get_instances_by_task_id(self, task_id: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
根据任务ID获取所有任务实例
|
||||
|
||||
Args:
|
||||
task_id: 任务ID
|
||||
|
||||
Returns:
|
||||
任务实例字典列表
|
||||
"""
|
||||
instances = self.db.query(TaskInstance).filter(TaskInstance.task_id == task_id, TaskInstance.is_deleted == False).all()
|
||||
return [instance.to_dict() for instance in instances]
|
||||
|
||||
def get_latest_instance_by_task_id(self, task_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
根据任务ID获取最新的任务实例
|
||||
|
||||
Args:
|
||||
task_id: 任务ID
|
||||
|
||||
Returns:
|
||||
最新的任务实例字典,如果不存在则返回None
|
||||
"""
|
||||
instance = self.db.query(TaskInstance).filter(
|
||||
TaskInstance.task_id == task_id,
|
||||
TaskInstance.is_deleted == False
|
||||
).order_by(TaskInstance.created_at.desc()).first()
|
||||
|
||||
if not instance:
|
||||
return None
|
||||
|
||||
return instance.to_dict()
|
||||
|
||||
def get_editing_instance_by_task_id(self, task_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
根据任务ID获取编辑中的任务实例
|
||||
|
||||
Args:
|
||||
task_id: 任务ID
|
||||
|
||||
Returns:
|
||||
编辑中的任务实例字典,如果不存在则返回None
|
||||
"""
|
||||
instance = self.db.query(TaskInstance).filter(
|
||||
TaskInstance.task_id == task_id,
|
||||
TaskInstance.status == TaskInstanceStatus.EDITING,
|
||||
TaskInstance.is_deleted == False
|
||||
).order_by(TaskInstance.created_at.desc()).first()
|
||||
|
||||
if not instance:
|
||||
return None
|
||||
|
||||
return instance.to_dict()
|
||||
|
||||
def get_latest_published_instance_by_task_id(self, task_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
根据任务ID获取最新的已发布任务实例
|
||||
|
||||
Args:
|
||||
task_id: 任务ID
|
||||
|
||||
Returns:
|
||||
最新的已发布任务实例字典,如果不存在则返回None
|
||||
"""
|
||||
instance = self.db.query(TaskInstance).filter(
|
||||
TaskInstance.task_id == task_id,
|
||||
TaskInstance.status == TaskInstanceStatus.PUBLISHED,
|
||||
TaskInstance.is_deleted == False
|
||||
).order_by(TaskInstance.created_at.desc()).first()
|
||||
|
||||
if not instance:
|
||||
return None
|
||||
|
||||
return instance.to_dict()
|
||||
|
||||
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,
|
||||
status: Optional[TaskInstanceStatus] = None) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
更新任务实例
|
||||
|
||||
Args:
|
||||
instance_id: 任务实例ID
|
||||
variables: 任务变量
|
||||
priority: 任务优先级
|
||||
input_params: 任务输入参数
|
||||
block_outputs: 块输出参数
|
||||
context_params: 上下文参数
|
||||
status: 任务实例状态
|
||||
|
||||
Returns:
|
||||
更新后的任务实例字典,如果不存在则返回None
|
||||
"""
|
||||
# 获取任务实例
|
||||
instance = self.db.query(TaskInstance).filter(TaskInstance.instance_id == instance_id, TaskInstance.is_deleted == False).first()
|
||||
if not instance:
|
||||
return None
|
||||
|
||||
# 更新字段
|
||||
if variables is not None:
|
||||
instance.variables = variables
|
||||
if priority is not None:
|
||||
instance.priority = priority
|
||||
if input_params is not None:
|
||||
instance.input_params = input_params
|
||||
if block_outputs is not None:
|
||||
instance.block_outputs = block_outputs
|
||||
if context_params is not None:
|
||||
instance.context_params = context_params
|
||||
if status is not None:
|
||||
instance.status = status
|
||||
|
||||
# 保存到数据库
|
||||
self.db.commit()
|
||||
self.db.refresh(instance)
|
||||
|
||||
return instance.to_dict()
|
||||
|
||||
def publish_instance(self, instance_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
发布任务实例
|
||||
|
||||
Args:
|
||||
instance_id: 任务实例ID
|
||||
|
||||
Returns:
|
||||
发布后的任务实例字典,如果不存在则返回None
|
||||
"""
|
||||
return self.update_instance(instance_id, status=TaskInstanceStatus.PUBLISHED)
|
||||
|
||||
def delete_instance(self, instance_id: str) -> bool:
|
||||
"""
|
||||
删除任务实例
|
||||
|
||||
Args:
|
||||
instance_id: 任务实例ID
|
||||
|
||||
Returns:
|
||||
是否删除成功
|
||||
"""
|
||||
# 获取任务实例
|
||||
instance = self.db.query(TaskInstance).filter(TaskInstance.instance_id == instance_id, TaskInstance.is_deleted == False).first()
|
||||
if not instance:
|
||||
return False
|
||||
|
||||
# 标记为已删除
|
||||
instance.is_deleted = True
|
||||
self.db.commit()
|
||||
|
||||
return True
|
||||
|
||||
def get_or_create_editing_instance(self, task_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
获取或创建编辑中的任务实例
|
||||
|
||||
如果任务已有编辑中的实例,则返回该实例
|
||||
如果任务没有编辑中的实例,但有已发布的实例,则基于最新的已发布实例创建新的编辑中实例
|
||||
如果任务没有任何实例,则创建新的编辑中实例
|
||||
|
||||
Args:
|
||||
task_id: 任务ID
|
||||
|
||||
Returns:
|
||||
编辑中的任务实例字典
|
||||
"""
|
||||
# 检查是否有编辑中的实例
|
||||
editing_instance = self.get_editing_instance_by_task_id(task_id)
|
||||
if editing_instance:
|
||||
return editing_instance
|
||||
|
||||
# 检查是否有已发布的实例
|
||||
published_instance = self.get_latest_published_instance_by_task_id(task_id)
|
||||
if published_instance:
|
||||
# 基于已发布实例创建新的编辑中实例
|
||||
return self.create_instance(
|
||||
task_id=task_id,
|
||||
name=published_instance.get("name"),
|
||||
variables=published_instance.get("variables"),
|
||||
priority=published_instance.get("priority", 1),
|
||||
input_params=published_instance.get("input_params"),
|
||||
block_outputs=published_instance.get("block_outputs"),
|
||||
context_params=published_instance.get("context_params"),
|
||||
status=TaskInstanceStatus.EDITING
|
||||
)
|
||||
|
||||
# 获取任务信息
|
||||
task = self.db.query(Task).filter(Task.task_id == task_id, Task.is_deleted == False).first()
|
||||
if not task:
|
||||
raise ValueError(f"任务不存在: {task_id}")
|
||||
|
||||
# 创建新的编辑中实例
|
||||
return self.create_instance(
|
||||
task_id=task_id,
|
||||
name=task.name,
|
||||
variables=task.variables if hasattr(task, "variables") else {},
|
||||
priority=1,
|
||||
status=TaskInstanceStatus.EDITING
|
||||
)
|
BIN
utils/__pycache__/db_migration.cpython-312.pyc
Normal file
BIN
utils/__pycache__/db_migration.cpython-312.pyc
Normal file
Binary file not shown.
218
utils/db_migration.py
Normal file
218
utils/db_migration.py
Normal file
@ -0,0 +1,218 @@
|
||||
"""
|
||||
数据库迁移工具
|
||||
用于自动检测模型变更并应用到数据库
|
||||
"""
|
||||
import logging
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import inspect, text, Enum
|
||||
from sqlalchemy.exc import OperationalError, ProgrammingError
|
||||
from data.session import get_db
|
||||
from config.database import DBConfig
|
||||
import importlib
|
||||
import pkgutil
|
||||
import data.models
|
||||
import enum
|
||||
import json
|
||||
|
||||
# 设置日志
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DBMigration:
|
||||
"""数据库迁移工具类"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化迁移工具"""
|
||||
self.db = next(get_db())
|
||||
self.engine = self.db.bind
|
||||
self.inspector = inspect(self.engine)
|
||||
self.metadata = sa.MetaData()
|
||||
self.metadata.reflect(bind=self.engine)
|
||||
|
||||
def get_all_models(self):
|
||||
"""获取所有模型类"""
|
||||
models = []
|
||||
# 导入所有模型模块
|
||||
for _, name, _ in pkgutil.iter_modules(data.models.__path__):
|
||||
try:
|
||||
module = importlib.import_module(f"data.models.{name}")
|
||||
# 获取模块中的所有类
|
||||
for attr_name in dir(module):
|
||||
attr = getattr(module, attr_name)
|
||||
# 检查是否为模型类
|
||||
if isinstance(attr, type) and hasattr(attr, "__tablename__") and attr.__name__ != "BaseModel":
|
||||
models.append(attr)
|
||||
except ImportError as e:
|
||||
logger.error(f"导入模块 data.models.{name} 失败: {str(e)}")
|
||||
|
||||
return models
|
||||
|
||||
def get_table_columns(self, table_name):
|
||||
"""获取表的所有列信息"""
|
||||
columns = {}
|
||||
try:
|
||||
for column in self.inspector.get_columns(table_name):
|
||||
columns[column["name"]] = column
|
||||
except Exception as e:
|
||||
logger.error(f"获取表 {table_name} 的列信息失败: {str(e)}")
|
||||
|
||||
return columns
|
||||
|
||||
def get_model_columns(self, model):
|
||||
"""获取模型的所有列信息"""
|
||||
columns = {}
|
||||
for name, column in model.__table__.columns.items():
|
||||
columns[name] = column
|
||||
|
||||
return columns
|
||||
|
||||
def check_table_exists(self, table_name):
|
||||
"""检查表是否存在"""
|
||||
return self.inspector.has_table(table_name)
|
||||
|
||||
def create_table(self, model):
|
||||
"""创建表"""
|
||||
try:
|
||||
model.__table__.create(self.engine)
|
||||
logger.info(f"创建表 {model.__tablename__} 成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"创建表 {model.__tablename__} 失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def get_column_type_sql(self, column):
|
||||
"""获取列类型的SQL表示"""
|
||||
try:
|
||||
# 处理枚举类型
|
||||
if isinstance(column.type, Enum):
|
||||
# MySQL中枚举类型的处理
|
||||
if self.engine.dialect.name == 'mysql':
|
||||
enum_values = [f"'{val}'" for val in column.type.enums]
|
||||
return f"ENUM({', '.join(enum_values)})"
|
||||
# PostgreSQL中枚举类型的处理
|
||||
elif self.engine.dialect.name == 'postgresql':
|
||||
# 获取枚举类型名称
|
||||
enum_name = column.type.name or f"{column.table.name}_{column.name}_enum"
|
||||
# 创建枚举类型
|
||||
enum_values = [f"'{val}'" for val in column.type.enums]
|
||||
self.db.execute(text(f"CREATE TYPE IF NOT EXISTS {enum_name} AS ENUM ({', '.join(enum_values)})"))
|
||||
self.db.commit()
|
||||
return enum_name
|
||||
|
||||
# 其他类型直接使用SQLAlchemy的编译功能
|
||||
return column.type.compile(self.engine.dialect)
|
||||
except Exception as e:
|
||||
logger.error(f"获取列类型失败: {str(e)}")
|
||||
# 默认返回VARCHAR类型
|
||||
return "VARCHAR(255)"
|
||||
|
||||
def get_column_default_sql(self, column):
|
||||
"""获取列默认值的SQL表示"""
|
||||
try:
|
||||
default = column.default
|
||||
if default is None:
|
||||
return ""
|
||||
|
||||
# 处理服务器端默认值
|
||||
if default.is_sequence or default.is_callable:
|
||||
return "" # 服务器端默认值不需要在ADD COLUMN中指定
|
||||
|
||||
# 处理标量默认值
|
||||
if default.is_scalar:
|
||||
if isinstance(default.arg, bool):
|
||||
return f" DEFAULT {1 if default.arg else 0}" if self.engine.dialect.name == 'mysql' else f" DEFAULT {str(default.arg).lower()}"
|
||||
elif isinstance(default.arg, (int, float)):
|
||||
return f" DEFAULT {default.arg}"
|
||||
elif isinstance(default.arg, str):
|
||||
return f" DEFAULT '{default.arg}'"
|
||||
elif isinstance(default.arg, enum.Enum):
|
||||
return f" DEFAULT '{default.arg.value}'"
|
||||
elif default.arg is None:
|
||||
return " DEFAULT NULL"
|
||||
|
||||
# 处理JSON默认值
|
||||
if hasattr(column.type, 'python_type') and column.type.python_type == dict:
|
||||
if default.arg is None:
|
||||
return " DEFAULT '{}'"
|
||||
return f" DEFAULT '{json.dumps(default.arg)}'"
|
||||
|
||||
return ""
|
||||
except Exception as e:
|
||||
logger.error(f"获取列默认值失败: {str(e)}")
|
||||
return ""
|
||||
|
||||
def add_column(self, table_name, column_name, column):
|
||||
"""添加列"""
|
||||
try:
|
||||
# 获取列类型
|
||||
column_type = self.get_column_type_sql(column)
|
||||
|
||||
# 获取列默认值
|
||||
default_value = self.get_column_default_sql(column)
|
||||
|
||||
# 获取列是否可为空
|
||||
nullable = "" if column.nullable else " NOT NULL"
|
||||
|
||||
# 构建SQL语句
|
||||
sql = f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type}{nullable}{default_value}"
|
||||
|
||||
# 执行SQL
|
||||
logger.info(f"执行SQL: {sql}")
|
||||
self.db.execute(text(sql))
|
||||
self.db.commit()
|
||||
|
||||
logger.info(f"添加列 {table_name}.{column_name} 成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"添加列 {table_name}.{column_name} 失败: {str(e)}")
|
||||
self.db.rollback()
|
||||
return False
|
||||
|
||||
def migrate(self):
|
||||
"""执行迁移"""
|
||||
models = self.get_all_models()
|
||||
logger.info(f"发现 {len(models)} 个模型")
|
||||
|
||||
for model in models:
|
||||
table_name = model.__tablename__
|
||||
|
||||
# 检查表是否存在
|
||||
if not self.check_table_exists(table_name):
|
||||
logger.info(f"表 {table_name} 不存在,准备创建")
|
||||
self.create_table(model)
|
||||
continue
|
||||
|
||||
# 获取表的列信息
|
||||
table_columns = self.get_table_columns(table_name)
|
||||
|
||||
# 获取模型的列信息
|
||||
model_columns = self.get_model_columns(model)
|
||||
|
||||
# 检查是否有新增的列
|
||||
for column_name, column in model_columns.items():
|
||||
if column_name not in table_columns:
|
||||
logger.info(f"发现新增列 {table_name}.{column_name},准备添加")
|
||||
self.add_column(table_name, column_name, column)
|
||||
|
||||
logger.info("数据库迁移完成")
|
||||
|
||||
def run_migration():
|
||||
"""运行数据库迁移"""
|
||||
try:
|
||||
logger.info("开始数据库迁移")
|
||||
migration = DBMigration()
|
||||
migration.migrate()
|
||||
logger.info("数据库迁移成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"数据库迁移失败: {str(e)}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 设置日志格式
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
|
||||
# 运行迁移
|
||||
run_migration()
|
@ -302,17 +302,25 @@
|
||||
2. **核心参数配置**:
|
||||
- **是否异步执行任务**(可选项):
|
||||
- 选择是否以异步方式执行子任务
|
||||
- 支持两种传参类型:
|
||||
- 简单值:直接手动输入
|
||||
- 表达式:通过系统默认参数拖拽生成,使用`${}`语法
|
||||
|
||||
- **任务变量ID**(必选项):
|
||||
- **任务实例ID**(可选项):
|
||||
- 指定子任务的唯一标识符
|
||||
|
||||
- **输入参数配置**:
|
||||
- 根据子任务定义的输入参数进行配置
|
||||
|
||||
- **输出参数映射**(可选项):
|
||||
- 定义子任务输出结果与上层任务变量的映射关系
|
||||
- 支持两种传参类型:
|
||||
- 简单值:直接手动输入
|
||||
- 表达式:通过系统默认参数拖拽生成,使用`${}`语法
|
||||
|
||||
3. **通用配置**:
|
||||
- 通过顶部的"任务输入参数"按钮进行配置,而非在核心参数配置中设置
|
||||
- 输入参数配置适用于所有组件块,是通用功能
|
||||
- 在此处可以定义和设置子任务所需的输入参数
|
||||
|
||||
**注意**:输入参数是所有块通用的配置项,不属于子任务特有的核心参数配置。每个参数都支持简单值和表达式两种传参类型。
|
||||
|
||||
详细说明参见:[天风任务-子任务功能说明.md](天风任务-子任务功能说明.md)
|
||||
|
||||
### 脚本组件功能详细说明
|
||||
|
||||
脚本组件是天风任务模块低代码配置工具中的核心功能之一,它允许用户通过编写和配置JavaScript脚本来实现复杂的业务逻辑和数据处理功能。脚本组件为任务流程提供了灵活的编程能力,使用户能够突破标准组件的限制,实现个性化的任务处理逻辑。
|
||||
|
Loading…
x
Reference in New Issue
Block a user